A lot of software makes use of menu's. Menu's are a means for the user to perform certain actions with the application. Often even the simplest applications have a menu built in, beit small, beit extensive and all variations in between.
Menu's are specified mostly by using struct NewMenu as found in libraries/gadtools.h and may look like this ('reverse engineered' from NotePad...:
So far nothing spectacular. That facet will come when we get a IDCMP message telling us a menu item has been choosen by the user and subsequently we want to decypher which one that was and act upon that. In the message loop code like this may be encountered, which is only partially provided here as all-in-all it is quite extensive! Even for a relatively small menu strip.
The more extended a menustrip is, the more tedious it becomes to decypher and properly handle the user's choice. The code provided does not even provide for SubItem handling, which would make it even more clunky as in every applicable case of MENUITEM(WMHI_Message & WMHI_MENUMASK), a subsequent switch (MENUSUB(WMHI_Message & WMHI_MENUMASK)), followed with all its relevant cases has to be written out. What's more; the moment you are going to add an item (or subitem), or remove it or put it in a different place, you have to reorganise the entire WMHI_MENUPICK-case to reflect those changes, every time you make any changes. A really cumbersome approach, as eachtime the structure of Menu, Item and SubItem has changed. Do you really want that?
In this snippet I've only elaborated to some extent on the 'Project'-menu. It is hardly imaginable that anybody really likes such a setup and wants to write the code for it. The C++-style comments at every relevant case are there to keep you as a maintainer sane and are a clear sign of the awkward nature of this method. That is exactly why I, for one, have a strong dislike for it and have come up with a solution, which makes use of a curiosity. That curiosity bears down to the fact that addresses of allocations and functions always start at a four-byte boundery and so leave the last 2 bits, the least significant ones, always 0. We can put that 'wasted' space to good use, as 2 bits give us hold of 4 different values. We actually only need 2 in this case, but the code provided here is set up for 4.
'struct NewMenu' has a member called 'nm_UserDate' and is of type 'APTR' so all values have to be cast to APTR. That very member we are going to use for our experiment.
We will use it for either an ordinal number, defined by an enumeration, or for an address of a function. And those 2 least significant bits will later on tell us what we have...
Allright lets redo our code.
First of all, we need that enumeration. Not for all entries, but only for those entries which won't make use of a function of their own. Like 'Project/Quit': the only thing it will do is setting the BOOLean 'Done' to TRUE.
I'll only set this up for menu 'Project' in order to keep things edible. Options 'Open...', 'Insert...', 'Close text' and 'Quit' will for no specific reason be handled in the traditional way, meaning use of a switch-and-case sequence, the others by calling their provided function. The switch-and-case sequence however is now no longer driven by Menu, Item and SubItem positions as in the original way, but by an ordinal number, enumerated value. You end up with only one switch-and-case sequence for the entire menusystem in stead of the nested current approach of a switch-and-case sequence for every Menu and within each menu for each Item and eventually each SubItem.
The enumeration:
The functions:
Now the menu is to be set up again, but with a little difference to the previous one, as the last entry, which reflects nm_UserData, on every applicable line is now receiving a value:
Clarification of this code with respect to nm_Userdata:
'(APTR)NewProject' is the address of function 'NewProject' cast to an APTR, while
'((MOID_OPEN << 2) | 1)' is the enumerated value of MOID_OPEN, which in turn is shifted to the left by 2 positions, after which it is 'or'-ed with '1' to make it known that this is an enumerated value and the whole value is then cast to APTR.
And our code for the WMHI_MENUPICK-event may, again partially, look like this:
When Menu's are added, removed or changed, little needs to be done: in case an enumerated option is added or removed then the switch-and-case statement needs to be adjusted and maybe the enumeration. Rearranging the entire menu, even on a grand scale, does not require any changes in the WMHI_MENUPICK-portion of the code.
However, if an option is added to (or removed from) the menu with a functionpointer in it nm_UserData-field, then only that function needs to be added (or removed). In that case the WMHI_MENUPICK-portion of the code requires no changes at all!
And finally the function that provides us with the ordinal number of the user's choice:
This may not neccessarily give you faster executable code, maybe a smaller executable, but that is not the primary aim. The profit must be sought in the size of the sourcecode for WMHI_MENUPICK-event, its readabillity and, hence, its maintainabillity.
Actually, come to think of it, the whole menusystem could use an overhaul and be 'Objectified' in a ReAction-befitting manner.
struct NewMenu NM[] = {{NM_TITLE, "Project", NULL, 0, 0, NULL} ,{ NM_ITEM, "New", "N", 0, 0, NULL} ,{ NM_ITEM, "Open...", "O", 0, 0, NULL} ,{ NM_ITEM, "Insert...", NULL, 0, 0, NULL} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "Save", "S", 0, 0, NULL} ,{ NM_ITEM, "Save as...", NULL, 0, 0, NULL} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "Print...", "P", 0, 0, NULL} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "Close text", NULL, 0, 0, NULL} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "About...", NULL, 0, 0, NULL} ,{ NM_ITEM, "Iconify", NULL, 0, 0, NULL} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "Quit", "Q", 0, 0, NULL} ,{NM_TITLE, "Update", NULL, 0, 0, NULL} ,{ NM_ITEM, "Cut", "X", 0, 0, NULL} ,{ NM_ITEM, "Copy", "C", 0, 0, NULL} ,{ NM_ITEM, "Paste", "V", 0, 0, NULL} ,{ NM_ITEM, "Mark All", "M", 0, 0, NULL} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "Clear", NULL, 0, 0, NULL} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "Undo", "U", 0, 0, NULL} ,{ NM_ITEM, "Redo", "Z", 0, 0, NULL} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "Clear All", NULL, 0, 0, NULL} ,{NM_TITLE, "Navigate", NULL, 0, 0, NULL} ,{ NM_ITEM, "Find...", "F", 0, 0, NULL} ,{ NM_ITEM, "Find next", ".", 0, 0, NULL} ,{ NM_ITEM, "Find & replace", "R", 0, 0, NULL} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "Jump to...", "J", 0, 0, NULL} ,{NM_TITLE, "Settings", NULL, 0, 0, NULL} ,{ NM_ITEM, "Make icons", NULL, 0, 0, NULL} ,{ NM_ITEM, "Auto indentation", NULL, 0, 0, NULL} ,{ NM_ITEM, "Select font...", NULL, 0, 0, NULL} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "Save settings", NULL, 0, 0, NULL} ,{ NM_END, NULL NULL, 0, 0, NULL} };
BOOL Done = FALSE; uint32 AnySig; while (!Done) { AnySig = IExec->Wait(AllSigs); if (AnySig & OurWindowSig) { while ((WMHI_Message = IIntuition->IDoMethod(WinObj, WM_HANDLEINPUT, 0)) != WMHI_LASTMSG) { switch (WMHI_Message & WMHI_CLASSMASK) { case WMHI_MENUPICK: { switch (MENUNUM(WMHI_Message & WMHI_MENUMASK)) { case 0: // Project { switch (MENUITEM(WMHI_Message & WMHI_MENUMASK)) { case 0: // Project --> New { break; } case 1: // Project --> Open { break; } case 2: // Project --> Insert { break; } // case 3: does not exist as that position is held by a nonselectable barlabel. case 4: // Project --> Save { break; } case 5: // Project --> SaveAs { break; } // case 6: does not exist as that position is held by a nonselectable barlabel. case 7: // Project --> Print { break; } // case 8: does not exist as that position is held by a nonselectable barlabel. case 9: // Project --> Close text { break; } // case 10: does not exist as that position is held by a nonselectable barlabel. case 11: // Project --> About { break; } case 12: // Project --> Iconify { break; } // case 13: does not exist as that position is held by a nonselectable barlabel. case 14: // Project --> Quit { break; } } break; } case 1: // Update { break; } case 2: // Navigate { break; } case 3: // Settings { break; } } break; } } } } }
enum {MOID_OPEN ,MOID_QUIT ,MOID_CLOSETEXT ,MOID_INSERT ,MOID_LAST };
BOOL NewProject(<A uniform set of parameters>) { BOOL Success = FALSE; /* ** Do here something, and when the results are OK ** set Success to TRUE. */ return Success; } BOOL SaveProject(<A uniform set of parameters>) { BOOL Success = FALSE; /* ** Do here something, and when the results are OK ** set Success to TRUE. */ return Success; } BOOL SaveProjectAs(<A uniform set of parameters>) { BOOL Success = FALSE; /* ** Do here something, and when the results are OK ** set Success to TRUE. */ return Success; } BOOL PrintProject(<A uniform set of parameters>) { BOOL Success = FALSE; /* ** Do here something, and when the results are OK ** set Success to TRUE. */ return Success; } BOOL About(<A uniform set of parameters>) { BOOL Success = FALSE; /* ** Do here something, and when the results are OK ** set Success to TRUE. */ return Success; } BOOL IconifyProject(<A uniform set of parameters>) { BOOL Success = FALSE; /* ** Do here something, and when the results are OK ** set Success to TRUE. */ return Success; }
struct NewMenu NM[] = {{NM_TITLE, "Project", NULL, 0, 0, NULL} ,{ NM_ITEM, "New", "N", 0, 0, (APTR)NewProject} ,{ NM_ITEM, "Open...", "O", 0, 0, (APTR)((MOID_OPEN << 2) | 1)} ,{ NM_ITEM, "Insert...", NULL, 0, 0, (APTR)((MOID_INSERT << 2) | 1)} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "Save", "S", 0, 0, (APTR)SaveProject} ,{ NM_ITEM, "Save as...", NULL, 0, 0, (APTR)SaveProjectAs} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "Print...", "P", 0, 0, (APTR)PrintProject} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "Close text", NULL, 0, 0, (APTR)((MOID_CLOSETEXT << 2) | 1)} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "About...", NULL, 0, 0, (APTR)About} ,{ NM_ITEM, "Iconify", NULL, 0, 0, (APTR)IconifyProject} ,{ NM_BARLABEL, NULL, NULL, 0, 0, NULL} ,{ NM_ITEM, "Quit", "Q", 0, 0, (APTR)((MOID_QUIT << 2) | 1)} ,{ NM_END, NULL NULL, 0, 0, NULL} };
BOOL Done = FALSE; BOOL Success; uint32 AnySig, WMHI_Message; uint16 MenuIndex; uint32 ActionType, ActionValue; while (!Done) { AnySig = IExec->Wait(AllSigs); if (AnySig & OurWindowSig) { while ((WMHI_Message = IIntuition->IDoMethod(WinObj, WM_HANDLEINPUT, 0)) != WMHI_LASTMSG) { switch (WMHI_Message & WMHI_CLASSMASK) { case WMHI_MENUPICK: { /* ** First thing we want to know which item is selected by the user. ** The function Retrieve_MenuIndex provides us with that: */ MenuIndex = Retrieve_MenuIndex((WMHI_Message & ~WMHI_CLASSMASK), NM); ActionType = ((uint32)NM[MenuIndex].nm_UserData) & 3); ActionValue = ((uint32)NM[MenuIndex].nm_UserData) & (uint32)~3); switch (ActionType) { case 0: // Will only be used for 'pure' addresses, so functions which we will call: { /* ** Be sure to call an address and not a NULL-value, ** as doing so will lead to a crash, something you ** are probably not after: ** ** N.b.: This is the ONLY point from which the functions are called, when asked ** for by the menu, saving you from lots and lots and lots of tedious code! */ if (ActionValue & (uint32)~3) { Success = ((BOOL (*)())ActionValue(<Here goes the uniform set of parameters!>); } break; } /* ** When ActionType is '1', We will use the ** enumeration values to determine what to do: */ case 1: { switch (ActionValue) { case MOID_QUIT: { Done = TRUE; break; } case MOID_OPEN: { /* ** Provide code here that Opens a file etc., etc. */ break; } case MOID_CLOSETEXT: { /* ** Provide here some code that will do ** what the user expects it to do: */ break; } case MOID_INSERT: { /* ** Perform the insertion here. ** You may even call a function here... */ break; } } break; } break; } } } } }
uint32 Retrieve_MenuChoice(uint32 MenuChoice, struct NewMenu *MenuList) { uint32 Index = ~0; // Set to an error value of 0xFFFFFFFF /* ** Has a choice been made? ** 31 if not. */ if (MENUNUM(MenuChoice) != 31) { Index = 0; while ((MenuList[notag][Index][/notag].nm_Type != NM_END) && (MENUNUM(MenuChoice) > 0) ) { if (MenuList[notag][Index][/notag].nm_Type == NM_TITLE) { /* ** Menu's index take up the 5 least significant bits: */ MenuChoice -= 1; Index++; while (MenuList[notag][Index][/notag].nm_Type != NM_TITLE) { Index++; } } } Index++; while ((MenuList[notag][Index][/notag].nm_Type != NM_END) && (ITEMNUM(MenuChoice) > 0) ) { if (MenuList[notag][Index][/notag].nm_Type == NM_ITEM) { /* ** MenuItem's index take up the middle 6 bits: */ MenuChoice -= (1 << 5); Index++; while (MenuList[notag][Index][/notag].nm_Type != NM_ITEM) { Index++; } } } if (SUBNUM(MenuChoice) != 31) { Index++; if (SUBNUM(MenuChoice) > 0) { while ((MenuList[notag][Index][/notag].nm_Type != NM_END) && (SUBNUM(MenuChoice) > 0) ) { if (MenuList[notag][Index][/notag].nm_Type == NM_SUB) { /* ** Menu's index take up the 5 most significant bits: */ MenuChoice -= (1 << 11); } Index++; } } } } return(Index); }
Comments
Submitted by whose on
Submitted by YesCop on
Submitted by OldFart on
Submitted by YesCop on