Copyright (c) Hyperion Entertainment and contributors.
Difference between revisions of "Intuition Menu Class"
Steven Solie (talk | contribs) |
Steven Solie (talk | contribs) |
||
(14 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | THE NEW BOOPSI MENU CLASS |
||
− | |||
= An overview = |
= An overview = |
||
Line 49: | Line 47: | ||
</syntaxhighlight> |
</syntaxhighlight> |
||
− | If the flag BOOPSIMENU is set, the object is a menuclass instance, otherwise it is a traditional Menu or MenuItem structure |
+ | If the flag BOOPSIMENU is set, the object is a menuclass instance, otherwise it is a traditional Menu or MenuItem structure. |
+ | |||
+ | {{Note|text=In the above test it would have been exactly the same if we had chosen to cast the object to a struct MenuItem * rather than to a struct Menu * because the Flags field is located at the same offset (and has the same size) in both structures.}} |
||
= Adding BOOPSI menus to an application = |
= Adding BOOPSI menus to an application = |
||
Line 88: | Line 88: | ||
Object *menustripobj; |
Object *menustripobj; |
||
− | menustripobj = NewObject(NULL,"menuclass",MA_Type,T_ROOT, |
+ | menustripobj = IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ROOT, |
− | MA_AddChild, NewObject(NULL,"menuclass",MA_Type,T_MENU, |
+ | MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_MENU, |
MA_Label, "Project", |
MA_Label, "Project", |
||
− | MA_AddChild, NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
+ | MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
MA_Label, "Open...", |
MA_Label, "Open...", |
||
MA_ID, MID_PROJECT_OPEN, |
MA_ID, MID_PROJECT_OPEN, |
||
TAG_END), |
TAG_END), |
||
− | MA_AddChild, NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
+ | MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
MA_Label, "Save", |
MA_Label, "Save", |
||
MA_ID, MID_PROJECT_SAVE, |
MA_ID, MID_PROJECT_SAVE, |
||
TAG_END), |
TAG_END), |
||
− | MA_AddChild, NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
+ | MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
MA_Label, "Save as...", |
MA_Label, "Save as...", |
||
MA_ID, MID_PROJECT_SAVEAS, |
MA_ID, MID_PROJECT_SAVEAS, |
||
TAG_END), |
TAG_END), |
||
− | MA_AddChild, NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
+ | MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
MA_Label, ML_SEPARATOR, |
MA_Label, ML_SEPARATOR, |
||
TAG_END), |
TAG_END), |
||
− | MA_AddChild, NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
+ | MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
MA_Label, "About...", |
MA_Label, "About...", |
||
MA_ID, MID_PROJECT_ABOUT, |
MA_ID, MID_PROJECT_ABOUT, |
||
TAG_END), |
TAG_END), |
||
− | MA_AddChild, NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
+ | MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
MA_Label, ML_SEPARATOR, |
MA_Label, ML_SEPARATOR, |
||
TAG_END), |
TAG_END), |
||
− | MA_AddChild, NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
+ | MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, |
MA_Label, "Quit", |
MA_Label, "Quit", |
||
MA_ID, MID_PROJECT_QUIT, |
MA_ID, MID_PROJECT_QUIT, |
||
Line 151: | Line 151: | ||
</syntaxhighlight> |
</syntaxhighlight> |
||
− | Note |
+ | {{Note|text=When using the MTitle() and MItem() macros, the label is passed as an argument to the macro itself rather than via an explicit MA_Label tag.}} |
+ | |||
+ | We'll use the compact style in the rest of this document, except in cases where the more verbose one provides greater clarity. |
||
== Menu item keyboard shortcuts == |
== Menu item keyboard shortcuts == |
||
Line 180: | Line 182: | ||
If you opt for the latter approach, you may also use a NUL ('\0') character in place of the '|'. This can prove useful when converting to BOOPSI menu usage an existing application employing such a method to embed shortcuts in its catalog strings. |
If you opt for the latter approach, you may also use a NUL ('\0') character in place of the '|'. This can prove useful when converting to BOOPSI menu usage an existing application employing such a method to embed shortcuts in its catalog strings. |
||
− | + | {{Note|text=The NUL byte solution only works for single-letter shortcuts and requires that no item in the menu tree has a label of just one letter, as any such label would be mistaken for a shortcut followed by random bytes. When you have single-letter labels, therefore, you cannot use this feature and must disable it for the whole tree. That's done by passing { MA_EmbeddedKey, FALSE } to the menu root object; this attribute will be inherited by all objects.}} |
|
== Inheritance of menu attributes == |
== Inheritance of menu attributes == |
||
Line 194: | Line 196: | ||
<syntaxhighlight> |
<syntaxhighlight> |
||
− | SetMenuStrip(window,menustripobj); |
+ | IIntuition->SetMenuStrip(window,menustripobj); |
</syntaxhighlight> |
</syntaxhighlight> |
||
Line 215: | Line 217: | ||
Additionally, as of Intuition V54 it is no longer mandatory to remove the menus with ClearMenuStrip() before closing the window. CloseWindow() itself will take care of clearing the menus if needed (no matter if BOOPSI or old-style). |
Additionally, as of Intuition V54 it is no longer mandatory to remove the menus with ClearMenuStrip() before closing the window. CloseWindow() itself will take care of clearing the menus if needed (no matter if BOOPSI or old-style). |
||
− | Note |
+ | {{Note|text=It is still required, however, to use ClearMenuStrip() to remove the menu strip of a window before setting a different one with SetMenuStrip().}} |
== Handling menu item selections by the user == |
== Handling menu item selections by the user == |
||
Line 223: | Line 225: | ||
Once all IDs have been retrieved, MM_NEXTSELECT will return NO_MENU_ID which means there are no more selections in the list. |
Once all IDs have been retrieved, MM_NEXTSELECT will return NO_MENU_ID which means there are no more selections in the list. |
||
− | Note |
+ | {{Note|text=MM_NEXTSELECT might even return NO_MENU_ID immediately on the first invocation; this would mean the user initiated and terminated a menu session without picking any item. Be prepared to handle this case.}} |
The MM_NEXTSELECT method uses the following mpNextSelect message structure: |
The MM_NEXTSELECT method uses the following mpNextSelect message structure: |
||
Line 243: | Line 245: | ||
uint32 id = NO_MENU_ID; |
uint32 id = NO_MENU_ID; |
||
− | while ((id = IDoMethod(menustripobj,MM_NEXTSELECT,0,id)) != NO_MENU_ID) |
+ | while ((id = IIntuition->IDoMethod(menustripobj,MM_NEXTSELECT,0,id)) != NO_MENU_ID) |
{ |
{ |
||
switch (id) |
switch (id) |
||
Line 272: | Line 274: | ||
} |
} |
||
− | id = IDoMethod(menustripobj,MM_NEXTSELECT,0,id); |
+ | id = IIntuition->IDoMethod(menustripobj,MM_NEXTSELECT,0,id); |
} |
} |
||
</syntaxhighlight> |
</syntaxhighlight> |
||
Line 287: | Line 289: | ||
uint32 help_id; |
uint32 help_id; |
||
− | GetAttr(MA_MenuHelpID,menustripobj,&help_id); |
+ | IIntuition->GetAttr(MA_MenuHelpID,menustripobj,&help_id); |
</syntaxhighlight> |
</syntaxhighlight> |
||
− | Note |
+ | {{Note|text=The menu help ID value may be NO_MENU_ID, which means the [Help] key was pressed by the user while no menu item or title was highlighted.}} |
Menu help doesn't support multiple selection, so you have to deal with just one ID per event. As with menu pick, you can also retrieve the ID value by reading the ExtIntuiMessage.eim_LongCode field. |
Menu help doesn't support multiple selection, so you have to deal with just one ID per event. As with menu pick, you can also retrieve the ID value by reading the ExtIntuiMessage.eim_LongCode field. |
||
Line 352: | Line 354: | ||
uint32 id = NO_MENU_ID; |
uint32 id = NO_MENU_ID; |
||
− | while ((id = IDoMethod(menustripobj,MM_HANDLEPICK, |
+ | while ((id = IIntuition->IDoMethod(menustripobj,MM_HANDLEPICK, |
− | 0, |
+ | 0, |
− | id, |
+ | id, |
− | imsg->IDCMPWindow, |
+ | imsg->IDCMPWindow, |
− | mycustomdata)) != NO_MENU_ID) |
+ | mycustomdata)) != NO_MENU_ID) |
{ |
{ |
||
/* This selected menu object doesn't have |
/* This selected menu object doesn't have |
||
Line 402: | Line 404: | ||
<syntaxhighlight> |
<syntaxhighlight> |
||
− | m_obj = (Object *)IDoMethod(menustripobj,MM_FINDID,0,id); |
+ | m_obj = (Object *)IIntuition->IDoMethod(menustripobj,MM_FINDID,0,id); |
</syntaxhighlight> |
</syntaxhighlight> |
||
Line 423: | Line 425: | ||
== Selection state of toggle select and mutual exclude items == |
== Selection state of toggle select and mutual exclude items == |
||
− | Whenever a toggle select or mutual exclude menu item is picked by the user, you |
+ | Whenever a toggle select or mutual exclude menu item is picked by the user, you can examine its selection state by reading the item's MA_Selected attribute. If its value is TRUE, the item is currently selected (checked) and is displayed with some small checkmark or radio button symbol drawn in front of it. |
− | can examine its selection state by reading the item's MA_Selected attribute. |
||
− | If its value is TRUE, the item is currently selected (checked) and is displayed |
||
− | with some small checkmark or radio button symbol drawn in front of it. |
||
− | You can also programmatically change the selection state of this kind of items |
+ | You can also programmatically change the selection state of this kind of items at any time by setting the value of MA_Selected with SetAttrs(), without having to clear and then reset the menu strip as you would with non-BOOPSI menus. |
− | at any time by setting the value of MA_Selected with SetAttrs(), without having |
||
− | to clear and then reset the menu strip as you would with non-BOOPSI menus. |
||
− | This attribute has no meaning for action items, i.e. items that don't represent |
+ | This attribute has no meaning for action items, i.e. items that don't represent the "on" or "off" state of some option. |
− | the "on" or "off" state of some option. |
||
− | Another way to examine and modify the selection state of a menu or menu item is |
+ | Another way to examine and modify the selection state of a menu or menu item is to invoke the MM_GETSTATE and MM_SETSTATE methods (see below for more on this). |
− | to invoke the MM_GETSTATE and MM_SETSTATE methods (see below for more on this). |
||
== Methods for controlling the enable and selection state of menu items == |
== Methods for controlling the enable and selection state of menu items == |
||
− | It is possible to examine and modify the enable state of a menu or menu item by |
+ | It is possible to examine and modify the enable state of a menu or menu item by invoking the MM_GETSTATE and MM_SETSTATE methods. These may prove more handy in many cases since they take an ID number to specify the target object instead of the object's actual address. This means they can be directly fed the result of MM_NEXTSELECT without having to go through MM_FINDID first. |
− | invoking the MM_GETSTATE and MM_SETSTATE methods. These may prove more handy in |
||
− | many cases since they take an ID number to specify the target object instead of |
||
− | the object's actual address. This means they can be directly fed the result of |
||
− | MM_NEXTSELECT without having to go through MM_FINDID first. |
||
− | Both these methods use the bitmask constants MS_CHECKED and MS_DISABLED to read |
+ | Both these methods use the bitmask constants MS_CHECKED and MS_DISABLED to read or modify the corresponding MA_Selected and MA_Disabled attributes. |
− | or modify the corresponding MA_Selected and MA_Disabled attributes. |
||
The MM_GETSTATE method uses the following message structure: |
The MM_GETSTATE method uses the following message structure: |
||
+ | <syntaxhighlight> |
||
struct mpGetState |
struct mpGetState |
||
{ |
{ |
||
Line 457: | Line 448: | ||
uint32 mpgs_ID; |
uint32 mpgs_ID; |
||
}; |
}; |
||
+ | </syntaxhighlight> |
||
+ | where MethodID is MM_GETSTATE, mpgs_Reserved should always be set to zero, and mpgs_ID is the ID number of the menuclass object whose state you want to read. The method should be invoked on the menu root object (or on the parent of the target object, if you have its address) and will return a bit mask whose value can be MS_CHECKED, MS_DISABLED, both constants ORed together, or zero. For instance, if MS_CHECKED is set in the method's return value it means that the object's MA_Selected attribute is currently TRUE. |
||
− | where MethodID is MM_GETSTATE, mpgs_Reserved should always be set to zero, and |
||
− | mpgs_ID is the ID number of the menuclass object whose state you want to read. |
||
− | The method should be invoked on the menu root object (or on the parent of the |
||
− | target object, if you have its address) and will return a bit mask whose value |
||
− | can be MS_CHECKED, MS_DISABLED, both constants ORed together, or zero. |
||
− | For instance, if MS_CHECKED is set in the method's return value it means that |
||
− | the object's MA_Selected attribute is currently TRUE. |
||
The MM_SETSTATE method uses the following message structure: |
The MM_SETSTATE method uses the following message structure: |
||
+ | <syntaxhighlight> |
||
struct mpSetState |
struct mpSetState |
||
{ |
{ |
||
Line 476: | Line 463: | ||
uint32 mpss_StateMask; |
uint32 mpss_StateMask; |
||
}; |
}; |
||
+ | </syntaxhighlight> |
||
+ | where the first three fields are analogous to those of the mpGetState structure while mpss_ApplyMask is used to specify which attribute(s) should be modified, and mpss_StateMask contains the new values. For instance, if MS_DISABLED is set in mpss_ApplyMask, but not in mpss_StateMask, the object will become enabled as its MA_Disabled attribute will be set to FALSE. If MS_CHECKED is cleared in the mpss_ApplyMask field, on the other hand, the object's selection state will stay unchanged no matter what the value of mpss_StateMask is. |
||
− | where the first three fields are analogous to those of the mpGetState structure |
||
− | while mpss_ApplyMask is used to specify which attribute(s) should be modified, |
||
− | and mpss_StateMask contains the new values. For instance, if MS_DISABLED is set |
||
− | in mpss_ApplyMask, but not in mpss_StateMask, the object will become enabled as |
||
− | its MA_Disabled attribute will be set to FALSE. If MS_CHECKED is cleared in the |
||
− | mpss_ApplyMask field, on the other hand, the object's selection state will stay |
||
− | unchanged no matter what the value of mpss_StateMask is. |
||
== Disposing of the menu tree == |
== Disposing of the menu tree == |
||
− | Due to its dynamic nature, usually a BOOPSI menu tree doesn't need to be freed |
+ | Due to its dynamic nature, usually a BOOPSI menu tree doesn't need to be freed and rebuilt by your application on environment changes or each time it has to undergo some significant modification. You may for instance reuse the same menu tree on different screens (as long as it's used on one screen at a time). |
− | and rebuilt by your application on environment changes or each time it has to |
||
− | undergo some significant modification. You may for instance reuse the same menu |
||
− | tree on different screens (as long as it's used on one screen at a time). |
||
− | This means that all important state information stored in the menu tree such as |
+ | This means that all important state information stored in the menu tree such as which items are checked, disabled, etc. is never lost while the application is running, and that the menu tree can be disposed of just once on program's exit. |
− | which items are checked, disabled, etc. is never lost while the application is |
||
− | running, and that the menu tree can be disposed of just once on program's exit. |
||
− | To free an entire menu tree, you just have to call DisposeObject() on its root |
+ | To free an entire menu tree, you just have to call DisposeObject() on its root object. Whatever disposable object you attached to some menu item, such as e.g. a BOOPSI image, will be freed as well unless you asked not to do so. |
− | object. Whatever disposable object you attached to some menu item, such as e.g. |
||
− | a BOOPSI image, will be freed as well unless you asked not to do so. |
||
= Further menuclass features = |
= Further menuclass features = |
||
− | This section will outline a number of attributes and methods giving access to |
+ | This section will outline a number of attributes and methods giving access to some more specialized functionality of menuclass. |
− | some more specialized functionality of menuclass. |
||
== Specifying the menu font == |
== Specifying the menu font == |
||
− | Each menu item object can be displayed in its own font and size, although this |
+ | Each menu item object can be displayed in its own font and size, although this is strongly discouraged as such an arrangement would look quite unprofessional. Still, if you for any reason need to set the font used by a menu item, you can do so with the MA_TextAttr attribute. |
− | is strongly discouraged as such an arrangement would look quite unprofessional. |
||
− | Still, if you for any reason need to set the font used by a menu item, you can |
||
− | do so with the MA_TextAttr attribute. |
||
The font specified this way will be opened and closed by menuclass as needed. |
The font specified this way will be opened and closed by menuclass as needed. |
||
− | If you set MA_TextAttr for a menuclass object, it will be inherited by all of |
+ | If you set MA_TextAttr for a menuclass object, it will be inherited by all of its children. To specify a font for the entire menu tree, therefore, just set MA_TextAttr for the menu root object. |
− | its children. To specify a font for the entire menu tree, therefore, just set |
||
− | MA_TextAttr for the menu root object. |
||
− | Whenever there isn't any specific reason to do otherwise, it's recommended not |
+ | Whenever there isn't any specific reason to do otherwise, it's recommended not to use MA_TextAttr at all and just let the menus be displayed in the font that was chosen in the user's preferences for the screen they appear on. |
− | to use MA_TextAttr at all and just let the menus be displayed in the font that |
||
− | was chosen in the user's preferences for the screen they appear on. |
||
== Adding images to menu items == |
== Adding images to menu items == |
||
− | A BOOPSI menu item can have an image displayed at the left side of (or in place |
+ | A BOOPSI menu item can have an image displayed at the left side of (or in place of) the item's label. The image is specified through the MA_Image attribute; it may be either traditional or BOOPSI. |
− | of) the item's label. The image is specified through the MA_Image attribute; it |
||
− | may be either traditional or BOOPSI. |
||
The item's height is adjusted if needed to make enough room for the image. |
The item's height is adjusted if needed to make enough room for the image. |
||
− | If the image is BOOPSI, it will be automatically disposed of when the menu item |
+ | If the image is BOOPSI, it will be automatically disposed of when the menu item object is; to avoid that, you should set the item's MA_FreeImage attribute to FALSE. However, if you want to prevent automatic disposal for ALL images in the menu tree, it is more practical to set MA_FreeImage to FALSE for the menu root object since this way it will be inherited by all objects. |
− | object is; to avoid that, you should set the item's MA_FreeImage attribute to |
||
− | FALSE. However, if you want to prevent automatic disposal for ALL images in the |
||
− | menu tree, it is more practical to set MA_FreeImage to FALSE for the menu root |
||
− | object since this way it will be inherited by all objects. |
||
− | If the menu item has no label (i.e. it's an image-only item), you can still use |
+ | If the menu item has no label (i.e. it's an image-only item), you can still use MA_TextAttr to specify the font used for its keyboard shortcut character. |
− | MA_TextAttr to specify the font used for its keyboard shortcut character. |
||
== Hiding menus and menu items == |
== Hiding menus and menu items == |
||
− | Any member of a BOOPSI menu tree can be hidden or revealed again at any time by |
+ | Any member of a BOOPSI menu tree can be hidden or revealed again at any time by your application. A hidden menuclass object is still physically present in the menu tree, but does not appear on the screen when menus are displayed, as if it had been actually removed. |
− | your application. A hidden menuclass object is still physically present in the |
||
− | menu tree, but does not appear on the screen when menus are displayed, as if it |
||
− | had been actually removed. |
||
− | The MA_Hidden attribute is used to control the hiding of a BOOPSI menu or item |
+ | The MA_Hidden attribute is used to control the hiding of a BOOPSI menu or item objects. It is possible to make a menu or item hidden right from the start by passing { MA_Hidden, TRUE } to NewObject(), or change the object's hiding state later via SetAttrs(). |
− | objects. It is possible to make a menu or item hidden right from the start by |
||
− | passing { MA_Hidden, TRUE } to NewObject(), or change the object's hiding state |
||
− | later via SetAttrs(). |
||
− | Hiding a menu or a menu item will (obviously) hide all of its children as well. |
+ | Hiding a menu or a menu item will (obviously) hide all of its children as well. Unlike disabling, however, you cannot hide the menu strip object -- if you want to make the entire menu tree disappear, just call ClearMenuStrip(). |
− | Unlike disabling, however, you cannot hide the menu strip object -- if you want |
||
− | to make the entire menu tree disappear, just call ClearMenuStrip(). |
||
== Dynamic menu localization == |
== Dynamic menu localization == |
||
− | So far, we've only examined the simple case where you pass an actual string as |
+ | So far, we've only examined the simple case where you pass an actual string as the value of a menuclass object's MA_Label attribute. There is however another possibility, and that is to specify a numeric index as MA_Label's value, which is later used to look up the appropriate string in the currently open catalog. |
− | the value of a menuclass object's MA_Label attribute. There is however another |
||
− | possibility, and that is to specify a numeric index as MA_Label's value, which |
||
− | is later used to look up the appropriate string in the currently open catalog. |
||
+ | To be able to pass an integer value for MA_Label instead of a string, you need to tell menuclass what is the numeric range of string IDs. Any value outside of that range will be considered a string, and used directly as such. Values lying in the range, on the other hand, will be treated as catalog indices. The string ID range is specified with the MA_MinStringID and MA_MaxStringID attributes. (By default, the string ID range is empty and MA_Label is always interpreted as a string.) |
||
− | To be able to pass an integer value for MA_Label instead of a string, you need |
||
− | to tell menuclass what is the numeric range of string IDs. Any value outside of |
||
− | that range will be considered a string, and used directly as such. Values lying |
||
− | in the range, on the other hand, will be treated as catalog indices. The string |
||
− | ID range is specified with the MA_MinStringID and MA_MaxStringID attributes. |
||
− | (By default, the string ID range is empty and MA_Label is always interpreted as |
||
− | a string.) |
||
− | Whenever an object's MA_Label attribute is actually a numeric index, its value |
+ | Whenever an object's MA_Label attribute is actually a numeric index, its value is passed on each menu layout to a "localization hook" which will convert it to the correct string for the current language and charset. |
− | is passed on each menu layout to a "localization hook" which will convert it to |
||
− | the correct string for the current language and charset. |
||
− | The localization hook (aka string hook) is supplied by your application via the |
+ | The localization hook (aka string hook) is supplied by your application via the MA_StringHook attribute. You can simply set it for the root object, and it will be inherited by all objects in the menu tree. |
− | MA_StringHook attribute. You can simply set it for the root object, and it will |
||
− | be inherited by all objects in the menu tree. |
||
Your hook will be invoked as follows: |
Your hook will be invoked as follows: |
||
+ | <syntaxhighlight> |
||
HookFunction(struct Hook *hook, Object *obj, struct MenuStringMessage *msg) |
HookFunction(struct Hook *hook, Object *obj, struct MenuStringMessage *msg) |
||
+ | </syntaxhighlight> |
||
− | where 'obj' is the menu or item the string ID to be converted belongs to, and |
+ | where 'obj' is the menu or item the string ID to be converted belongs to, and 'msg' is a pointer to a MenuStringMessage structure (see below for definition) which carries the string ID. |
− | 'msg' is a pointer to a MenuStringMessage structure (see below for definition) |
||
− | which carries the string ID. |
||
− | You can also specify the address of an open catalog and/or a charset by setting |
+ | You can also specify the address of an open catalog and/or a charset by setting the MA_Catalog and MA_CharSet attributes for the menu root object; if supplied, this information is also passed back to the hook in the MenuStringMessage. |
− | the MA_Catalog and MA_CharSet attributes for the menu root object; if supplied, |
||
− | this information is also passed back to the hook in the MenuStringMessage. |
||
The MenuStringMessage structure is defined as follows: |
The MenuStringMessage structure is defined as follows: |
||
+ | <syntaxhighlight> |
||
struct MenuStringMessage |
struct MenuStringMessage |
||
{ |
{ |
||
Line 599: | Line 537: | ||
uint32 CharSet; /* Charset number, may be zero */ |
uint32 CharSet; /* Charset number, may be zero */ |
||
}; |
}; |
||
+ | </syntaxhighlight> |
||
− | Your string hook should return the correct catalog string for the passed string |
+ | Your string hook should return the correct catalog string for the passed string ID, or a default string if no catalog is available. |
− | ID, or a default string if no catalog is available. |
||
− | Note |
+ | {{Note|text=All of the above is equally valid for the MA_Key attribute, which can be a string ID instead of an actual string just like MA_Label. Its string ID range is the same one of MA_Label (as defined by MA_MinStringID and MA_MaxStringID).}} |
− | a string ID instead of an actual string just like MA_Label. Its string ID range |
||
− | is the same one of MA_Label (as defined by MA_MinStringID and MA_MaxStringID). |
||
− | Also |
+ | {{Note|title=Also Note|text=If you don't use a string hook, MA_Label and MA_Key should always be real strings and not numeric IDs, otherwise they'll get converted by menuclass to "????", which isn't very meaningful. The same will happen if the string hook returns a NULL string pointer.}} |
− | real strings and not numeric IDs, otherwise they'll get converted by menuclass |
||
− | to "????", which isn't very meaningful. The same will happen if the string hook |
||
− | returns a NULL string pointer. |
||
== Adding and removing menus and menu items == |
== Adding and removing menus and menu items == |
||
− | A BOOPSI menu tree built with menuclass allows for dynamic addition and removal |
+ | A BOOPSI menu tree built with menuclass allows for dynamic addition and removal of menus and items at any time. As with other modifications, there's no need to detach the menu tree from the window in order to perform these operations; it's menuclass that takes care of synchronizing each change with Intuition and doing any necessary relayout afterwards. |
− | of menus and items at any time. As with other modifications, there's no need to |
||
− | detach the menu tree from the window in order to perform these operations; it's |
||
− | menuclass that takes care of synchronizing each change with Intuition and doing |
||
− | any necessary relayout afterwards. |
||
− | To add a child to a menuclass object, you can simply call SetAttrs() to pass it |
+ | To add a child to a menuclass object, you can simply call SetAttrs() to pass it the MA_AddChild tag with the address of the new child as data, the same way you would at OM_NEW time. |
− | the MA_AddChild tag with the address of the new child as data, the same way you |
||
− | would at OM_NEW time. |
||
Here's an example of adding a sub-item to an item: |
Here's an example of adding a sub-item to an item: |
||
+ | <syntaxhighlight> |
||
/* Append a new entry to our "recent files" sub-menu */ |
/* Append a new entry to our "recent files" sub-menu */ |
||
new_item = MItem(filename), MA_ID, new_id++, MEnd; |
new_item = MItem(filename), MA_ID, new_id++, MEnd; |
||
− | SetAttrs(recentfiles_itemobj,MA_AddChild,new_item,TAG_END); |
+ | IIntuition->SetAttrs(recentfiles_itemobj,MA_AddChild,new_item,TAG_END); |
+ | </syntaxhighlight> |
||
A NULL pointer can safely be passed with MA_AddChild, and is simply ignored. |
A NULL pointer can safely be passed with MA_AddChild, and is simply ignored. |
||
− | Note |
+ | {{Note|text=In this example we're using a simple counter variable to generate an unique ID for each new sub-item -- we don't care about the ID value itself, we simply want to take notice if the item gets selected, and be able to retrieve its address with MM_FINDID whenever needed. This requires the new item to have a valid and unique ID number.}} |
− | unique ID for each new sub-item -- we don't care about the ID value itself, we |
||
− | simply want to take notice if the item gets selected, and be able to retrieve |
||
− | its address with MM_FINDID whenever needed. This requires the new item to have |
||
− | a valid and unique ID number. |
||
− | Just like they can be added, menus and menu items can also be removed from the |
+ | Just like they can be added, menus and menu items can also be removed from the menu tree as needed. This is achieved by passing the MA_RemoveChild tag to the parent menu object via SetAttrs(), with the address of the child as data. |
− | menu tree as needed. This is achieved by passing the MA_RemoveChild tag to the |
||
− | parent menu object via SetAttrs(), with the address of the child as data. |
||
For instance, we could remove an item from a menu this way: |
For instance, we could remove an item from a menu this way: |
||
+ | <syntaxhighlight> |
||
/* Close a window and remove its entry from our "open windows" menu */ |
/* Close a window and remove its entry from our "open windows" menu */ |
||
id = (uint32)window->UserData; /* We stored the menu item's ID there */ |
id = (uint32)window->UserData; /* We stored the menu item's ID there */ |
||
− | CloseWindow(window); |
+ | IIntuition->CloseWindow(window); |
− | item = (Object *)IDoMethod(openwindows_menuobj,MM_FINDID,0,id); |
+ | item = (Object *)IIntuition->IDoMethod(openwindows_menuobj,MM_FINDID,0,id); |
− | SetAttrs(openwindows_menuobj,MA_RemoveChild,item,TAG_END); |
+ | IIntuition->SetAttrs(openwindows_menuobj,MA_RemoveChild,item,TAG_END); |
− | DisposeObject(item); /* Accepts a NULL argument */ |
+ | IIntuition->DisposeObject(item); /* Accepts a NULL argument */ |
+ | </syntaxhighlight> |
||
As with MA_AddChild, you can safely pass a NULL pointer with MA_RemoveChild. |
As with MA_AddChild, you can safely pass a NULL pointer with MA_RemoveChild. |
||
− | Another way to perform addition and removal of children with menuclass objects |
+ | Another way to perform addition and removal of children with menuclass objects is to use rootclass' OM_ADDMEMBER and OM_REMMEMBER methods. The above examples could thus be rewritten by replacing the SetAttrs() calls with: |
− | is to use rootclass' OM_ADDMEMBER and OM_REMMEMBER methods. The above examples |
||
− | could thus be rewritten by replacing the SetAttrs() calls with: |
||
+ | <syntaxhighlight> |
||
− | IDoMethod(recentfiles_itemobj,OM_ADDMEMBER,new_item); |
||
+ | IIntuition->IDoMethod(recentfiles_itemobj,OM_ADDMEMBER,new_item); |
||
− | IDoMethod(openwindows_menuobj,OM_REMMEMBER,item); |
+ | IIntuition->IDoMethod(openwindows_menuobj,OM_REMMEMBER,item); |
+ | </syntaxhighlight> |
||
− | As you would when dealing with Exec lists, you must be careful to avoid adding |
+ | As you would when dealing with Exec lists, you must be careful to avoid adding or removing the same child twice, or removing a child which was never added in the first place, since doing so will cause memory corruption. |
− | or removing the same child twice, or removing a child which was never added in |
||
− | the first place, since doing so will cause memory corruption. |
||
== Browsing through all children of a menu object == |
== Browsing through all children of a menu object == |
||
− | You can obtain the addresses of all children of a menuclass object in sequence |
+ | You can obtain the addresses of all children of a menuclass object in sequence by invoking the MM_NEXTCHILD method in a loop. Once all the object's children have been been retrieved, MM_NEXTCHILD will return NULL to signal there are no more children. |
− | by invoking the MM_NEXTCHILD method in a loop. Once all the object's children |
||
− | have been been retrieved, MM_NEXTCHILD will return NULL to signal there are no |
||
− | more children. |
||
The MM_NEXTCHILD method uses the following message structure: |
The MM_NEXTCHILD method uses the following message structure: |
||
+ | <syntaxhighlight> |
||
struct mpNextChild |
struct mpNextChild |
||
{ |
{ |
||
Line 687: | Line 608: | ||
Object *mpnc_Current; |
Object *mpnc_Current; |
||
}; |
}; |
||
+ | </syntaxhighlight> |
||
− | where MethodID is MM_NEXTCHILD, mpnc_Reserved should always be set to zero, and |
+ | where MethodID is MM_NEXTCHILD, mpnc_Reserved should always be set to zero, and mpnc_Current is the child object address the method did return on the previous invocation. Pass NULL in mpnc_Current to get the address of the first child. |
− | mpnc_Current is the child object address the method did return on the previous |
||
− | invocation. Pass NULL in mpnc_Current to get the address of the first child. |
||
An example of a loop retrieving all items of a menu: |
An example of a loop retrieving all items of a menu: |
||
+ | <syntaxhighlight> |
||
Object *obj = NULL; |
Object *obj = NULL; |
||
− | while ((obj = (Object *)IDoMethod(menuobj,MM_NEXTCHILD,0,obj)) != NULL) |
+ | while ((obj = (Object *)IIntuition->IDoMethod(menuobj,MM_NEXTCHILD,0,obj)) != NULL) |
{ |
{ |
||
/* Do something with the item object */ |
/* Do something with the item object */ |
||
} |
} |
||
+ | </syntaxhighlight> |
||
− | If you use MM_NEXTCHILD in a loop to remove an object's children one by one, be |
+ | If you use MM_NEXTCHILD in a loop to remove an object's children one by one, be careful not to pass the method the address of an already removed object. |
− | careful not to pass the method the address of an already removed object. |
||
The correct way to do a sequential child removal is the following: |
The correct way to do a sequential child removal is the following: |
||
+ | <syntaxhighlight> |
||
− | while ((obj = (Object *)IDoMethod(menuobj,MM_NEXTCHILD,0,NULL)) != NULL) |
||
+ | while ((obj = (Object *)IIntuition->IDoMethod(menuobj,MM_NEXTCHILD,0,NULL)) != NULL) |
||
{ |
{ |
||
− | IDoMethod(menuobj,OM_REMMEMBER,obj); |
+ | IIntuition->IDoMethod(menuobj,OM_REMMEMBER,obj); |
} |
} |
||
+ | </syntaxhighlight> |
||
This is analogous to performing a RemHead() loop on an Exec list. |
This is analogous to performing a RemHead() loop on an Exec list. |
||
Line 715: | Line 638: | ||
== Scanning the menu tree == |
== Scanning the menu tree == |
||
− | You can perform a scan of the whole menu tree, or even just a subsection of it, |
+ | You can perform a scan of the whole menu tree, or even just a subsection of it, and have a custom function called on each object that is part of it. This makes it possible to implement any kind of operation that is not already provided by the existing menuclass methods. |
− | and have a custom function called on each object that is part of it. This makes |
||
− | it possible to implement any kind of operation that is not already provided by |
||
− | the existing menuclass methods. |
||
− | To scan a menu (sub-)tree, you invoke the MM_SCAN method on its root object and |
+ | To scan a menu (sub-)tree, you invoke the MM_SCAN method on its root object and specify a callback function and some parameters that will be passed back to it on each call. The MM_SCAN method uses the following message structure: |
− | specify a callback function and some parameters that will be passed back to it |
||
− | on each call. The MM_SCAN method uses the following message structure: |
||
+ | <syntaxhighlight> |
||
struct mpScan |
struct mpScan |
||
{ |
{ |
||
Line 731: | Line 650: | ||
uint32 mps_Args[4]; |
uint32 mps_Args[4]; |
||
}; |
}; |
||
+ | </syntaxhighlight> |
||
− | where MethodID is MM_SCAN, mps_Reserved should always be set to zero, mps_Hook |
+ | where MethodID is MM_SCAN, mps_Reserved should always be set to zero, mps_Hook is a pointer to your custom hook, and mps_Args[0] to mps_Args[3] are (optional) arguments for the hook's function. Your hook will be invoked as follows: |
− | is a pointer to your custom hook, and mps_Args[0] to mps_Args[3] are (optional) |
||
− | arguments for the hook's function. Your hook will be invoked as follows: |
||
+ | <syntaxhighlight> |
||
HookFunction(struct Hook *hook, Object *obj, struct MenuScanMessage *msg) |
HookFunction(struct Hook *hook, Object *obj, struct MenuScanMessage *msg) |
||
+ | </syntaxhighlight> |
||
− | where 'obj' is a menuclass object (whose exact type you can inspect by reading |
+ | where 'obj' is a menuclass object (whose exact type you can inspect by reading its MA_Type attribute) and 'msg' is a pointer to a MenuScanMessage structure. |
− | its MA_Type attribute) and 'msg' is a pointer to a MenuScanMessage structure. |
||
The MenuScanMessage structure is defined as follows: |
The MenuScanMessage structure is defined as follows: |
||
+ | <syntaxhighlight> |
||
struct MenuScanMessage |
struct MenuScanMessage |
||
{ |
{ |
||
Line 749: | Line 669: | ||
uint32 Args[4]; /* Custom arguments */ |
uint32 Args[4]; /* Custom arguments */ |
||
}; |
}; |
||
+ | </syntaxhighlight> |
||
− | The Level field indicates the depth in the tree of the current object, starting |
+ | The Level field indicates the depth in the tree of the current object, starting from -1 for the root object. The four Args variables hold the same values which were passed upon invocation of the MM_SCAN method and can be used to feed your custom arguments back to the hook. |
− | from -1 for the root object. The four Args variables hold the same values which |
||
− | were passed upon invocation of the MM_SCAN method and can be used to feed your |
||
− | custom arguments back to the hook. |
||
− | If your hook returns a non-zero value, the scan stops with the method returning |
+ | If your hook returns a non-zero value, the scan stops with the method returning the address of the object on which it stopped. If the hook always returns zero, all objects are scanned and the MM_SCAN method returns NULL. |
− | the address of the object on which it stopped. If the hook always returns zero, |
||
− | all objects are scanned and the MM_SCAN method returns NULL. |
||
An example of this method's usage could be the following: |
An example of this method's usage could be the following: |
||
+ | <syntaxhighlight> |
||
/* A simple function to count how many items a menu has, using MM_SCAN */ |
/* A simple function to count how many items a menu has, using MM_SCAN */ |
||
Line 779: | Line 696: | ||
hook.h_Entry = (HOOKFUNC)HookFunction; |
hook.h_Entry = (HOOKFUNC)HookFunction; |
||
− | IDoMethod(menuobj,MM_SCAN,0,&hook,&count,0,0,0); |
+ | IIntuition->IDoMethod(menuobj,MM_SCAN,0,&hook,&count,0,0,0); |
return (count); |
return (count); |
||
} |
} |
||
+ | </syntaxhighlight> |
||
+ | In this example we pass as the only custom argument the address of an variable which, once the MM_SCAN method returns, will hold the amount of children of the specified menu object. In the hook function we check that the object's level is 1, to make sure we only take it into account if it is a direct child of a menu (menus have a level of zero), rather than being a sub-item of an item, or even the menu object itself on which the method is invoked. |
||
− | In this example we pass as the only custom argument the address of an variable |
||
− | which, once the MM_SCAN method returns, will hold the amount of children of the |
||
− | specified menu object. In the hook function we check that the object's level is |
||
− | 1, to make sure we only take it into account if it is a direct child of a menu |
||
− | (menus have a level of zero), rather than being a sub-item of an item, or even |
||
− | the menu object itself on which the method is invoked. |
||
== Generating a whole menu (sub-)tree from a single tag list == |
== Generating a whole menu (sub-)tree from a single tag list == |
||
− | So far we have only seen menu trees built by way of a series of nested calls to |
+ | So far we have only seen menu trees built by way of a series of nested calls to NewObject(). This is certainly a working approach to putting together a tree of BOOPSI objects organized in a hierarchy. Still, it presents a few drawbacks: |
− | NewObject(). This is certainly a working approach to putting together a tree of |
||
− | BOOPSI objects organized in a hierarchy. Still, it presents a few drawbacks: |
||
− | + | * It can consume a significant amount of stack with very deep trees; |
|
− | + | * It may produce code which is hard to read unless special macros are used; |
|
− | + | * It makes error checking not very easy. |
|
− | All in all, nesting NewObject() calls is quite acceptable with relatively small |
+ | All in all, nesting NewObject() calls is quite acceptable with relatively small menu trees, while it can become less manageable as the tree's size grows. |
− | menu trees, while it can become less manageable as the tree's size grows. |
||
− | A different technique for building menu trees, which attempts to overcome these |
+ | A different technique for building menu trees, which attempts to overcome these issues, is provided by menuclass' MM_NEWMENU method. This allows to generate an entire tree, or a sub-tree, out of a flat description of its structure given in the form of a single tag list. |
− | issues, is provided by menuclass' MM_NEWMENU method. This allows to generate an |
||
− | entire tree, or a sub-tree, out of a flat description of its structure given in |
||
− | the form of a single tag list. |
||
The MM_NEWMENU method uses the following message structure: |
The MM_NEWMENU method uses the following message structure: |
||
+ | <syntaxhighlight> |
||
struct mpNewMenu |
struct mpNewMenu |
||
{ |
{ |
||
Line 817: | Line 725: | ||
struct TagItem *mpnm_AttrList; |
struct TagItem *mpnm_AttrList; |
||
}; |
}; |
||
+ | </syntaxhighlight> |
||
− | where MethodID is MM_NEWMENU, mpnm_Reserved should always be set to zero, and |
+ | where MethodID is MM_NEWMENU, mpnm_Reserved should always be set to zero, and mpnm_AttrList is the tag list describing the menu tree to be generated. |
− | mpnm_AttrList is the tag list describing the menu tree to be generated. |
||
− | Said tag list is made up for the most part of the usual menuclass tags that are |
+ | Said tag list is made up for the most part of the usual menuclass tags that are used to specify attributes of the individual objects; however, the menu objects themselves are introduced by special tags prepended with NM_ which are actually directives for the method. |
− | used to specify attributes of the individual objects; however, the menu objects |
||
− | themselves are introduced by special tags prepended with NM_ which are actually |
||
− | directives for the method. |
||
+ | MM_NEWMENU understands three directives: NM_Menu / NM_Item, indicating to add a new menu or item object at the current level (the tag data is the object's text label), and NM_SubItems, indicating to move down or up by one level before the next item object is added. If the tag data of NM_SubItems is SI_BEGIN, the next item will become a child of the most recent one added (i.e. we move down by one level); if it is SI_END, the next item will become a sibling of the most recent one's parent (i.e. we move up by one level). |
||
− | MM_NEWMENU understands three directives: NM_Menu / NM_Item, indicating to add a |
||
− | new menu or item object at the current level (the tag data is the object's text |
||
− | label), and NM_SubItems, indicating to move down or up by one level before the |
||
− | next item object is added. If the tag data of NM_SubItems is SI_BEGIN, the next |
||
− | item will become a child of the most recent one added (i.e. we move down by one |
||
− | level); if it is SI_END, the next item will become a sibling of the most recent |
||
− | one's parent (i.e. we move up by one level). |
||
− | Note |
+ | {{Note|text=NM_SubItems is not needed to add menus to a menu strip object, nor to add items to a menu object -- its only purpose is to act as a "delimiter" for a list of sub-items of an item object.}} |
− | add items to a menu object -- its only purpose is to act as a "delimiter" for a |
||
− | list of sub-items of an item object. |
||
− | Any menuclass tag that is encountered in the tag list is passed as an attribute |
+ | Any menuclass tag that is encountered in the tag list is passed as an attribute to the most recently added object. |
− | to the most recently added object. |
||
− | The method should be invoked on an existing menuclass object, which in case of |
+ | The method should be invoked on an existing menuclass object, which in case of success will become the new tree's root. MM_NEWMENU returns FALSE to indicate failure, the exact reasons for which can be inspected through the MA_ErrorCode and MA_ErrorTagItem tags. See <intuition/menuclass.h> for a list of menuclass error codes and their explanation. |
− | success will become the new tree's root. MM_NEWMENU returns FALSE to indicate |
||
− | failure, the exact reasons for which can be inspected through the MA_ErrorCode |
||
− | and MA_ErrorTagItem tags. See <intuition/menuclass.h> for a list of menuclass |
||
− | error codes and their explanation. |
||
Here's a simple example to illustrate the method's usage: |
Here's a simple example to illustrate the method's usage: |
||
+ | <syntaxhighlight> |
||
/* Service function allowing to pass varargs tag list to MM_NEWMENU |
/* Service function allowing to pass varargs tag list to MM_NEWMENU |
||
*/ |
*/ |
||
Line 859: | Line 752: | ||
va_startlinear(ap,root); |
va_startlinear(ap,root); |
||
tags = (struct TagItem *)va_getlinearva(ap,struct TagItem *); |
tags = (struct TagItem *)va_getlinearva(ap,struct TagItem *); |
||
− | result = IDoMethod(root,MM_NEWMENU,0,tags); |
+ | result = IIntuition->IDoMethod(root,MM_NEWMENU,0,tags); |
va_end(ap); |
va_end(ap); |
||
Line 873: | Line 766: | ||
struct TagItem *error_ti; |
struct TagItem *error_ti; |
||
− | menustripobj = NewObject(NULL,"menuclass",MA_Type,T_ROOT,TAG_END); |
+ | menustripobj = IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ROOT,TAG_END); |
if (menustripobj) |
if (menustripobj) |
||
Line 899: | Line 792: | ||
if (!success) |
if (!success) |
||
{ |
{ |
||
− | Printf("Failed with error %ld, at tag %08lX\n",error,error_ti->ti_Tag); |
+ | IDOS->Printf("Failed with error %ld, at tag %08lX\n",error,error_ti->ti_Tag); |
− | IDoMethod(menustripobj,MM_DELETEMENU,0); |
+ | IIntuition->IDoMethod(menustripobj,MM_DELETEMENU,0); |
− | DisposeObject(menustripobj); |
+ | IIntuition->DisposeObject(menustripobj); |
menustripobj = NULL; |
menustripobj = NULL; |
||
} |
} |
||
Line 908: | Line 801: | ||
return (menustripobj); |
return (menustripobj); |
||
} |
} |
||
+ | </syntaxhighlight> |
||
+ | In this example we make use of a small service function to pass the tag list on the stack as varargs; of course the tag list can also be allocated statically. Also, the above tree is rather small for the sake of keeping the example short, so it does not benefit much from using MM_NEWMENU instead of nested NewObject() calls, but obviously the same technique can be used to build trees of any size. |
||
− | In this example we make use of a small service function to pass the tag list on |
||
− | the stack as varargs; of course the tag list can also be allocated statically. |
||
− | Also, the above tree is rather small for the sake of keeping the example short, |
||
− | so it does not benefit much from using MM_NEWMENU instead of nested NewObject() |
||
− | calls, but obviously the same technique can be used to build trees of any size. |
||
− | As you can see, with MM_NEWMENU you can write compact and quite readable code |
+ | As you can see, with MM_NEWMENU you can write compact and quite readable code to describe your menu trees, without having to resort to using macros on top of nested calls. The tree's description can even be made somewhat reminiscent of traditional NewMenu arrays as used with GadTools, which may look more familiar to long-time developers. |
− | to describe your menu trees, without having to resort to using macros on top of |
||
− | nested calls. The tree's description can even be made somewhat reminiscent of |
||
− | traditional NewMenu arrays as used with GadTools, which may look more familiar |
||
− | to long-time developers. |
||
− | To free a menu (sub-)tree generated with MM_NEWMENU, you invoke MM_DELETEMENU |
+ | To free a menu (sub-)tree generated with MM_NEWMENU, you invoke MM_DELETEMENU on the root object. This method uses the following message structure: |
− | on the root object. This method uses the following message structure: |
||
+ | <syntaxhighlight> |
||
struct mpDeleteMenu |
struct mpDeleteMenu |
||
{ |
{ |
||
Line 929: | Line 815: | ||
uint32 mpdm_Reserved; |
uint32 mpdm_Reserved; |
||
}; |
}; |
||
+ | </syntaxhighlight> |
||
− | where MethodID is MM_DELETEMENU, and mpdm_Reserved should (as usual) be set to |
+ | where MethodID is MM_DELETEMENU, and mpdm_Reserved should (as usual) be set to zero. Therefore, the correct way to invoke the method is the following: |
− | zero. Therefore, the correct way to invoke the method is the following: |
||
+ | <syntaxhighlight> |
||
− | IDoMethod(menustripobj,MM_DELETEMENU,0); |
||
+ | IIntuition->IDoMethod(menustripobj,MM_DELETEMENU,0); |
||
+ | </syntaxhighlight> |
||
− | Note |
+ | {{Note|text=This call only disposes of all of the object's children, not of the root object itself. This way you can reuse the object in case you need to build another menu tree with MM_NEWMENU. To free the root object (and its children if you didn't already do that), simply use DisposeObject().}} |
− | root object itself. This way you can reuse the object in case you need to build |
||
− | another menu tree with MM_NEWMENU. To free the root object (and its children if |
||
− | you didn't already do that), simply use DisposeObject(). |
Latest revision as of 19:26, 9 July 2015
Contents
- 1 An overview
- 2 Adding BOOPSI menus to an application
- 2.1 Label and ID of menus and menu items
- 2.2 Building a menu tree with menuclass
- 2.3 Menu item keyboard shortcuts
- 2.4 Inheritance of menu attributes
- 2.5 Attaching the menu tree to a window
- 2.6 Detaching the menu tree from a window
- 2.7 Handling menu item selections by the user
- 2.8 Handling menu help requests by the user
- 2.9 Menu pick hooks and menu help hooks
- 2.10 Retrieving a menuclass object's address from its ID number
- 2.11 Enabling and disabling menus and menu items
- 2.12 Selection state of toggle select and mutual exclude items
- 2.13 Methods for controlling the enable and selection state of menu items
- 2.14 Disposing of the menu tree
- 3 Further menuclass features
- 3.1 Specifying the menu font
- 3.2 Adding images to menu items
- 3.3 Hiding menus and menu items
- 3.4 Dynamic menu localization
- 3.5 Adding and removing menus and menu items
- 3.6 Browsing through all children of a menu object
- 3.7 Scanning the menu tree
- 3.8 Generating a whole menu (sub-)tree from a single tag list
An overview
Starting with version 54.6, Intuition features a built-in BOOPSI menu class ("menuclass") which aims to fully replace the traditional way of setting up and handling menus in applications.
Its object-oriented API allows to easily build complex menu trees, freeing a program from the need to use gadtools.library for that job. It also provides extensive control over menus, making it possible to define, read and change their properties thanks to simple yet flexible methods and attributes.
Moreover, menuclass extends the capabilities of the original Intuition menu system. For instance, it can handle any number of sub-menu levels, as well as any amount of items in menus and sub-menus. For nearly all operations, it transparently synchronizes itself with Intuition and performs menu relayout if needed. Automatic placement of custom images in front of menu item labels is also supported.
BOOPSI menus may look and feel identical to traditional ones from the point of view of the user, but are handled quite differently in application code.
The old-style, white-box Menu and MenuItem structures are replaced by abstract objects (instances of menuclass) accessed through an object-oriented interface, removing the need for manual poking or dedicated menu functions.
Such BOOPSI menu objects are able to perform automatically most operations for which their traditional counterparts would require special assistance from the application.
There exist three distinct types of menuclass objects -- menu root, menu and menu item. Any instance of the class belongs to one of these types, specified at OM_NEW time through the MA_Type attribute.
The possible values for MA_Type are:
- T_ROOT
- A menu root object, also known as a menu strip as it is the parent of all menu objects. This differs from the old-style menu system, where a "menu strip" was just a linked list of Menu structures. Besides being the actual root of a menu tree, a menu root object also holds essential state information on the tree as a whole. It may be used in any place an old-style menu strip can, except for the ItemAddress() function (replaced by the MM_FINDID method). A menu root object can have any number of menu objects as children.
- T_MENU
- A menu object, equivalent to the old-style Menu structure. A menu object can have any number of menu item objects as children (and should always have at least one).
- T_ITEM
- A menu item object, equivalent to the old-style MenuItem structure. A menu item object can have any number of menu item objects as children (sub-items), even in the case it is itself a sub-item of some other menu item. Only a menu (sub-)item having no children (i.e. a leaf item) can be actually selected by the user.
Compatibility
Save where explicitly allowed, menu and menu item objects instantiated from menuclass cannot be transparently used in place of the equivalent old-style Intuition elements, as not all of the traditional menu-related functions and rules apply to them.
A limited form of compatibility exists due to menuclass objects having Menu or MenuItem structures embedded in them, just like gadgetclass ones do with the (Ext)Gadget structure. However, such structures should never be directly accessed by user code unless otherwise documented.
Under most circumstances, applications should treat menuclass instances as "black boxes", the only exception to this rule currently being that it is permitted to read certain bits from the Flags field of their embedded legacy structure (see also next subsection).
Although an application normally should know if it is using BOOPSI menus, it may happen that a library or a class needs to check the type of menus passed to it by a client. This is easily done with the following test:
if ((((struct Menu *)menu_object_ptr)->Flags & BOOPSIMENU) != 0) ...
If the flag BOOPSIMENU is set, the object is a menuclass instance, otherwise it is a traditional Menu or MenuItem structure.
Note |
---|
In the above test it would have been exactly the same if we had chosen to cast the object to a struct MenuItem * rather than to a struct Menu * because the Flags field is located at the same offset (and has the same size) in both structures. |
In this section we'll cover the basics of using the new menuclass in a typical application, and the main differences between BOOPSI and traditional menus.
The first step in adding BOOPSI menus to your application is to build a tree of menuclass objects, each with its appearance and behavior defined by attributes that can be set at NewObject() time.
For menu and menu item objects, the application should specify at least a label string and an ID number as a minimal set of attributes.
The label is assigned via MA_Label and is the text Intuition will display to represent a menu or menu item on the screen. A special value for this attribute is the constant ML_SEPARATOR, which turns a menu item into a separator line.
The ID, passed with the MA_ID tag, is a 32-bit unsigned integer that uniquely identifies a particular menu or menu item object. This number is passed back to the application through the IDCMP mechanism whenever the user picks a menu item or asks for help on a menu or menu item.
Any number may be chosen as an ID for a menuclass object, except for 0 (zero): this constant has the alias NO_MENU_ID in <intuition/menuclass.h> and is always an invalid value for an object's MA_ID attribute. Also, when an IDCMP_MENUPICK or IDCMP_MENUHELP ExtIntuiMessage reports NO_MENU_ID in its eim_LongCode field, this is to be interpreted as "no menu selection" (i.e. it has the same meaning MENUNULL has for old-style menus).
Menu root objects, as well as menu items acting as separator lines, don't need to have an ID number. Menu root objects don't need a label string either.
A menu tree is organized as a hierarchy of nested menuclass objects.
At the very top of the tree there is the menu root, having one or more menus as children. These make up the menu strip that appears on the screen when the user presses the right mouse button.
Each menu is the parent of one or more menu items. A menu item can have other menu items as children as well, thus forming a sub-menu. A menu item which is part of a sub-menu (also called a sub-item, i.e. the child of another menu item) can in turn have children (sub-items) of its own.
The leaf nodes of the tree are menu items or sub-items with no children. These are the actually selectable menu options.
There is no limit to the depth of the menu tree other than the available system memory.
You can append children to a menuclass object at OM_NEW or OM_SET time with the MA_AddChild tag, or by using the OM_ADDMEMBER method (more on this later).
An example of a very simple menu tree generation could be:
Object *menustripobj; menustripobj = IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ROOT, MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_MENU, MA_Label, "Project", MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, MA_Label, "Open...", MA_ID, MID_PROJECT_OPEN, TAG_END), MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, MA_Label, "Save", MA_ID, MID_PROJECT_SAVE, TAG_END), MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, MA_Label, "Save as...", MA_ID, MID_PROJECT_SAVEAS, TAG_END), MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, MA_Label, ML_SEPARATOR, TAG_END), MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, MA_Label, "About...", MA_ID, MID_PROJECT_ABOUT, TAG_END), MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, MA_Label, ML_SEPARATOR, TAG_END), MA_AddChild, IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ITEM, MA_Label, "Quit", MA_ID, MID_PROJECT_QUIT, TAG_END), TAG_END), TAG_END);
If you prefer a more compact style of coding, <intuition/menuclass.h> offers a number of macros allowing to simplify the tree description a little. Using said macros, the above code could be rewritten as follows:
Object *menustripobj; menustripobj = MStrip, MA_AddChild, MTitle("Project"), MA_AddChild, MItem("Open..."), MA_ID, MID_PROJECT_OPEN, MEnd, MA_AddChild, MItem("Save"), MA_ID, MID_PROJECT_SAVE, MEnd, MA_AddChild, MItem("Save as..."), MA_ID, MID_PROJECT_SAVEAS, MEnd, MA_AddChild, MSeparator, MEnd, MA_AddChild, MItem("About..."), MA_ID, MID_PROJECT_ABOUT, MEnd, MA_AddChild, MSeparator, MEnd, MA_AddChild, MItem("Quit"), MA_ID, MID_PROJECT_QUIT, MEnd, MEnd, MEnd;
Note |
---|
When using the MTitle() and MItem() macros, the label is passed as an argument to the macro itself rather than via an explicit MA_Label tag. |
We'll use the compact style in the rest of this document, except in cases where the more verbose one provides greater clarity.
Menu item keyboard shortcuts
As with traditional Intuition menus, menuclass item objects can have a keyboard shortcut which is displayed next to the item's label. Shortcuts can have one or more characters; a shortcut of more than one character is also called "command string" (for instance, "ctrl z").
A single-character shortcut is processed directly by Intuition, and appears at the right side of the menu item with an "Amiga key" symbol prepended to it. When using a command string of more than one character, however, Intuition will display it at the right side of the item (without the "Amiga key" symbol) but won't process the corresponding key combination; the application has to handle that on its own.
There are two ways to specify a keyboard shortcut for a menuclass item object.
You can pass it with the MA_Key attribute, as in the following example:
item = MItem("Save"), MA_ID, MID_SAVE, MA_Key, "S", MEnd;
Alternatively, you can prepend it to the item's label string, separed by a '|' character, this way:
item = MItem("S|Save"), MA_ID, MID_SAVE, MEnd;
If you opt for the latter approach, you may also use a NUL ('\0') character in place of the '|'. This can prove useful when converting to BOOPSI menu usage an existing application employing such a method to embed shortcuts in its catalog strings.
Note |
---|
The NUL byte solution only works for single-letter shortcuts and requires that no item in the menu tree has a label of just one letter, as any such label would be mistaken for a shortcut followed by random bytes. When you have single-letter labels, therefore, you cannot use this feature and must disable it for the whole tree. That's done by passing { MA_EmbeddedKey, FALSE } to the menu root object; this attribute will be inherited by all objects. |
There are some attributes of menuclass objects that are automatically inherited by all their descendants, except those for which they're explicitly set to some different value. For instance, if you want your menus to use a particular font, you only need to set the MA_TextAttr attribute for the menu root, and all items (and sub-items) of all menus will be displayed with that font.
Any menuclass attribute which is automatically inherited from the parent object when not specified explicitly is clearly described as such, both in the autodoc and in the <intuition/menuclass.h> header file.
You can attach a menu tree built with menuclass to a window just like you would do with traditional non-BOOPSI menus, i.e. by calling SetMenuStrip(). Starting with Intuition V54, this function accepts a BOOPSI menu root object as the menu strip pointer. Therefore, once you've opened your window, you can simply do the following:
IIntuition->SetMenuStrip(window,menustripobj);
and the menu tree will be ready to use, after undergoing an automatic layout to adapt itself to the window's screen.
An alternative to the above is passing the menus to the window at opening time, via the new V54 tag WA_MenuStrip. This way your window will open with the menus already attached and laid out.
When using window.class, you can achieve the same by passing the class-specific tag WINDOW_MenuStrip. (Starting with V54, window.class supports WA_MenuStrip as well, as an alias for WINDOW_MenuStrip.)
BOOPSI menus, like traditional ones, can be detached from a window at any time with ClearMenuStrip(). After having been detached, they can also be reattached by way of ResetMenuStrip(), as usual.
In many cases, however, you don't need to remove BOOPSI menus as you would with old-style menus. Changing the selection state of "checkmarkable" items, as well as disabling or enabling single items or whole menus, may be performed by just setting the appropriate attribute of the menu object in question, regardless of whether or not the menus are in use. Any needed synchronization with Intuition is handled internally by menuclass.
The above is generally true for all operations you can do on menuclass objects, such as changing their label, image, font or charset, or even their position in the menu tree. In fact, a BOOPSI menu tree even supports addition or removal of menus and items on-the-fly without any clearing and resetting of the menus.
Whenever a change requires a relayout of some part of the menu tree, menuclass will automatically take care of that.
Additionally, as of Intuition V54 it is no longer mandatory to remove the menus with ClearMenuStrip() before closing the window. CloseWindow() itself will take care of clearing the menus if needed (no matter if BOOPSI or old-style).
Note |
---|
It is still required, however, to use ClearMenuStrip() to remove the menu strip of a window before setting a different one with SetMenuStrip(). |
Upon receiving a menu pick event such as IDCMP_MENUPICK (or WMHI_MENUPICK if using window.class), your application can find out which menu items the user selected by invoking repeatedly the MM_NEXTSELECT method on the root object of the menu tree (i.e. the menu strip object). This will return all selected item IDs in sequence; in many cases there will be only one, but there may be more if the user performed multiple selection.
Once all IDs have been retrieved, MM_NEXTSELECT will return NO_MENU_ID which means there are no more selections in the list.
Note |
---|
MM_NEXTSELECT might even return NO_MENU_ID immediately on the first invocation; this would mean the user initiated and terminated a menu session without picking any item. Be prepared to handle this case. |
The MM_NEXTSELECT method uses the following mpNextSelect message structure:
struct mpNextSelect { uint32 MethodID; uint32 mpns_Reserved; uint32 mpns_CurrentID; };
where MethodID is MM_NEXTSELECT, mpns_Reserved should be always set to zero, and mpns_CurrentID is the menu item ID the method returned on the previous invocation. To obtain the first ID in the selection list, pass NO_MENU_ID in the mpns_CurrentID field.
An example of this method's usage would be:
uint32 id = NO_MENU_ID; while ((id = IIntuition->IDoMethod(menustripobj,MM_NEXTSELECT,0,id)) != NO_MENU_ID) { switch (id) { /* Process menu selections */ case MID_PROJECT_OPEN: ... } }
An alternative way to get the ID of the first (or only) selected menu item is to read it from the eim_LongCode field of the ExtIntuiMessage reporting the IDCMP_MENUPICK event (IntuiMessages generated by Intuition are always really ExtIntuiMessages, and can be cast as such). Since a menuclass object's ID is a 32-bit value, it can't fit in the 16-bit IntuiMessage.Code field, the value of which is currently undefined for IDCMP events coming from BOOPSI menus.
The above example could thus be modified as follows:
uint32 id = ((struct ExtIntuiMessage *)intuimsg)->eim_LongCode; while (id != NO_MENU_ID) { switch (id) { /* Process menu selections */ case MID_PROJECT_OPEN: ... } id = IIntuition->IDoMethod(menustripobj,MM_NEXTSELECT,0,id); }
The first approach is probably more convenient when using window.class since in that case you would need an additional IDCMP hook to access the ExtIntuiMessage in order to read its eim_LongCode field.
If your window has the WA_MenuHelp attribute set to TRUE, and it's listening to IDCMP_MENUHELP events, the user can request help on a menu item or a menu title by pressing the [Help] key when that item or title is being highlighted by the mouse pointer. Upon reception of a menu help event, your application can easily find out the ID of the item or title the help request was about by querying the MA_MenuHelpID attribute of the menu root object.
An example of this might be:
uint32 help_id; IIntuition->GetAttr(MA_MenuHelpID,menustripobj,&help_id);
Note |
---|
The menu help ID value may be NO_MENU_ID, which means the [Help] key was pressed by the user while no menu item or title was highlighted. |
Menu help doesn't support multiple selection, so you have to deal with just one ID per event. As with menu pick, you can also retrieve the ID value by reading the ExtIntuiMessage.eim_LongCode field.
Any menuclass object can have custom hooks associated to it with dedicated code to handle menu selections or help requests for that specific object. Such hooks can be invoked automatically by the main event handling loop every time a menu pick or menu help event occurs.
The hook that handles menu item selections is specified through the MA_PickHook attribute; similarly, you can use the MA_HelpHook attribute to specify the hook handling menu help requests.
These two attributes are inherited by all children of a menuclass object. This allows, if desired, to reuse the same hook for all the items of a menu (or all the sub-items of an item) simply by setting it for that menu (or item), rather than having to pass it to each child explicitly. Such a "common" hook could act as a dispatcher, calling the appropriate individual handling function according to the ID of the object it is invoked on.
A menuclass object's pick or help hook will be invoked as follows:
HookFunction(struct Hook *hook, Object *obj, struct MenuEventMessage *msg)
where 'obj' is the object itself and 'msg' is a pointer to a MenuEventMessage structure.
The MenuEventMessage structure is defined as follows:
struct MenuEventMessage { uint32 StructSize; /* For future expansion */ uint32 EventType; /* ET_MENUPICK or ET_MENUHELP */ struct Window *Window; /* Event window pointer */ APTR UserData; /* Custom data pointer */ };
The EventType field is useful to distinguish between the two types of event the hook could be invoked for, in case the same hook function is used for both. The Window field holds the address of the event's window (i.e. the window your menu tree is associated to) and the UserData field is used to pass to the hook any kind of context information it might require, as explained a little below.
The pick or help hook can use the supplied information (menu object, window and custom data pointer) to process the event. The return value of the hook is not currently defined; it's recommended to set it to zero for future compatibility.
To have menuclass invoke the pick hook of a menuclass object, your application needs to use the MM_HANDLEPICK method in its event loop.
Like MM_NEXTSELECT, MM_HANDLEPICK can be invoked repeatedly to browse through all items in the menu selection chain. The difference is that this method only returns the ID of selected items that don't have a pick hook; whenever it finds a selected item whose pick hook is non-NULL, it invokes that hook and proceeds to the next item without returning anything. When there are no more items left in the menu selection chain, MM_HANDLEPICK returns NO_MENU_ID.
The mechanism is exactly the same for help hooks, except in that case you have to use the MM_HANDLEHELP method, which only needs to invoked once as menu help events don't support multiple selection: if the menu object the user asked help about doesn't have a help hook, MM_HANDLEHELP will return its ID, otherwise it will invoke the hook and return NO_MENU_ID.
Both the MM_HANDLEPICK and MM_HANDLEHELP methods should be invoked on the menu root object, and use the same message structure:
struct mpHandleEvent { uint32 MethodID; uint32 mphe_Reserved; uint32 mphe_CurrentID; struct Window *mphe_Window; APTR mphe_UserData; };
where MethodID is MM_HANDLEPICK or MM_HANDLEHELP, mphe_Reserved should always be set to zero, and mphe_CurrentID is the ID returned by the method's previous invocation (ignored by MM_HANDLEHELP; pass NO_MENU_ID on the first invocation of MM_HANDLEPICK). Additionally, you should store the event's window address in mphe_Window (e.g. IntuiMessage->IDCMPWindow) and any custom data to be passed to the pick or help hook in mphe_UserData; your hook will be able to read back this information from the MenuEventMessage.
A simple example of how to take advantage of the custom pick hook feature might be the following:
uint32 id = NO_MENU_ID; while ((id = IIntuition->IDoMethod(menustripobj,MM_HANDLEPICK, 0, id, imsg->IDCMPWindow, mycustomdata)) != NO_MENU_ID) { /* This selected menu object doesn't have * a pick hook, so let's handle it here. */ switch (id) { /* Process menu selections */ case MID_PROJECT_OPEN: ... } }
It goes without saying that if none of your application's menuclass object has a pick hook or help hook, you never need to invoke MM_HANDLEPICK/MM_HANDLEHELP in your event handling code; you can just use the simpler MM_NEXTSELECT method and MA_MenuHelpID attribute instead, as covered in previous subsections.
Once you have found out which menu item was picked by the user by examining its ID, you may want to do some operations on the item object itself, and thus need its actual address. One way to achieve this could be to keep a look-up table in your application, initialized as the menu tree gets built, and using the IDs as indices. For instance, something like the following:
... MA_AddChild, mtable[MID_PROJECT_OPEN] = MItem("Open..."), MA_ID, MID_PROJECT_OPEN, MEnd, MA_AddChild, mtable[MID_PROJECT_SAVE] = MItem("Save"), MA_ID, MID_PROJECT_SAVE, MEnd, ...
The drawbacks of such an approach are increased memory consumption and reduced code readability. A simpler solution is offered by menuclass in the form of the MM_FINDID method. This uses the mpFindID structure, defined as:
struct mpFindID { uint32 MethodID; uint32 mpfi_Reserved; uint32 mpfi_ID; };
where MethodID is MM_FINDID, mpfi_Reserved should always be set to zero, while mpfi_ID is the ID of the menu object whose address you're looking for. Here's a simple example of this method's invocation on the menu root object:
m_obj = (Object *)IIntuition->IDoMethod(menustripobj,MM_FINDID,0,id);
If a menu object with the specified ID is found within the menu tree, MM_FINDID will return its address, otherwise it will return NULL.
The MM_FINDID method doesn't actually need to be invoked on the menu root as in the above example. It just searches for an object having a given ID within the menu (sub-)tree hanging from the object it's invoked on. So if you already know the address of an object whose local sub-tree contains the one you want, it is possible to invoke MM_FINDID directly on that object and restrict the search to only a part of the menu tree.
Invoking the MM_FINDID method is the BOOPSI equivalent of calling ItemAddress() with traditional menus.
By default, a BOOPSI menu or item is enabled, meaning it looks clearly readable and can be selected by the user. When disabled, it is instead rendered with low contrast or a recessed appearance and is not selectable.
The MA_Disabled attribute is used to control the enable state of BOOPSI menu or item objects. It is possible to disable a menuclass object right from the start by passing { MA_Disabled, TRUE } to NewObject(), or change the object's enable state later at any time via SetAttrs(). Setting MA_Disabled to TRUE or FALSE is the BOOPSI equivalent of calling OnMenu() or OffMenu() on old-style menus.
Disabling a menu or a menu item will make all of its children show up disabled as well, down to the deepest sub-menu levels. Additionally, you can disable all your menus at once (i.e. make the entire menu tree disabled and not selectable) simply by disabling the menu strip object.
Another way to examine and modify the enable state of a menu or menu item is to invoke the MM_GETSTATE and MM_SETSTATE methods (see below for more on this).
Selection state of toggle select and mutual exclude items
Whenever a toggle select or mutual exclude menu item is picked by the user, you can examine its selection state by reading the item's MA_Selected attribute. If its value is TRUE, the item is currently selected (checked) and is displayed with some small checkmark or radio button symbol drawn in front of it.
You can also programmatically change the selection state of this kind of items at any time by setting the value of MA_Selected with SetAttrs(), without having to clear and then reset the menu strip as you would with non-BOOPSI menus.
This attribute has no meaning for action items, i.e. items that don't represent the "on" or "off" state of some option.
Another way to examine and modify the selection state of a menu or menu item is to invoke the MM_GETSTATE and MM_SETSTATE methods (see below for more on this).
It is possible to examine and modify the enable state of a menu or menu item by invoking the MM_GETSTATE and MM_SETSTATE methods. These may prove more handy in many cases since they take an ID number to specify the target object instead of the object's actual address. This means they can be directly fed the result of MM_NEXTSELECT without having to go through MM_FINDID first.
Both these methods use the bitmask constants MS_CHECKED and MS_DISABLED to read or modify the corresponding MA_Selected and MA_Disabled attributes.
The MM_GETSTATE method uses the following message structure:
struct mpGetState { uint32 MethodID; uint32 mpgs_Reserved; uint32 mpgs_ID; };
where MethodID is MM_GETSTATE, mpgs_Reserved should always be set to zero, and mpgs_ID is the ID number of the menuclass object whose state you want to read. The method should be invoked on the menu root object (or on the parent of the target object, if you have its address) and will return a bit mask whose value can be MS_CHECKED, MS_DISABLED, both constants ORed together, or zero. For instance, if MS_CHECKED is set in the method's return value it means that the object's MA_Selected attribute is currently TRUE.
The MM_SETSTATE method uses the following message structure:
struct mpSetState { uint32 MethodID; uint32 mpss_Reserved; uint32 mpss_ID; uint32 mpss_ApplyMask; uint32 mpss_StateMask; };
where the first three fields are analogous to those of the mpGetState structure while mpss_ApplyMask is used to specify which attribute(s) should be modified, and mpss_StateMask contains the new values. For instance, if MS_DISABLED is set in mpss_ApplyMask, but not in mpss_StateMask, the object will become enabled as its MA_Disabled attribute will be set to FALSE. If MS_CHECKED is cleared in the mpss_ApplyMask field, on the other hand, the object's selection state will stay unchanged no matter what the value of mpss_StateMask is.
Due to its dynamic nature, usually a BOOPSI menu tree doesn't need to be freed and rebuilt by your application on environment changes or each time it has to undergo some significant modification. You may for instance reuse the same menu tree on different screens (as long as it's used on one screen at a time).
This means that all important state information stored in the menu tree such as which items are checked, disabled, etc. is never lost while the application is running, and that the menu tree can be disposed of just once on program's exit.
To free an entire menu tree, you just have to call DisposeObject() on its root object. Whatever disposable object you attached to some menu item, such as e.g. a BOOPSI image, will be freed as well unless you asked not to do so.
This section will outline a number of attributes and methods giving access to some more specialized functionality of menuclass.
Each menu item object can be displayed in its own font and size, although this is strongly discouraged as such an arrangement would look quite unprofessional. Still, if you for any reason need to set the font used by a menu item, you can do so with the MA_TextAttr attribute.
The font specified this way will be opened and closed by menuclass as needed.
If you set MA_TextAttr for a menuclass object, it will be inherited by all of its children. To specify a font for the entire menu tree, therefore, just set MA_TextAttr for the menu root object.
Whenever there isn't any specific reason to do otherwise, it's recommended not to use MA_TextAttr at all and just let the menus be displayed in the font that was chosen in the user's preferences for the screen they appear on.
A BOOPSI menu item can have an image displayed at the left side of (or in place of) the item's label. The image is specified through the MA_Image attribute; it may be either traditional or BOOPSI.
The item's height is adjusted if needed to make enough room for the image.
If the image is BOOPSI, it will be automatically disposed of when the menu item object is; to avoid that, you should set the item's MA_FreeImage attribute to FALSE. However, if you want to prevent automatic disposal for ALL images in the menu tree, it is more practical to set MA_FreeImage to FALSE for the menu root object since this way it will be inherited by all objects.
If the menu item has no label (i.e. it's an image-only item), you can still use MA_TextAttr to specify the font used for its keyboard shortcut character.
Any member of a BOOPSI menu tree can be hidden or revealed again at any time by your application. A hidden menuclass object is still physically present in the menu tree, but does not appear on the screen when menus are displayed, as if it had been actually removed.
The MA_Hidden attribute is used to control the hiding of a BOOPSI menu or item objects. It is possible to make a menu or item hidden right from the start by passing { MA_Hidden, TRUE } to NewObject(), or change the object's hiding state later via SetAttrs().
Hiding a menu or a menu item will (obviously) hide all of its children as well. Unlike disabling, however, you cannot hide the menu strip object -- if you want to make the entire menu tree disappear, just call ClearMenuStrip().
So far, we've only examined the simple case where you pass an actual string as the value of a menuclass object's MA_Label attribute. There is however another possibility, and that is to specify a numeric index as MA_Label's value, which is later used to look up the appropriate string in the currently open catalog.
To be able to pass an integer value for MA_Label instead of a string, you need to tell menuclass what is the numeric range of string IDs. Any value outside of that range will be considered a string, and used directly as such. Values lying in the range, on the other hand, will be treated as catalog indices. The string ID range is specified with the MA_MinStringID and MA_MaxStringID attributes. (By default, the string ID range is empty and MA_Label is always interpreted as a string.)
Whenever an object's MA_Label attribute is actually a numeric index, its value is passed on each menu layout to a "localization hook" which will convert it to the correct string for the current language and charset.
The localization hook (aka string hook) is supplied by your application via the MA_StringHook attribute. You can simply set it for the root object, and it will be inherited by all objects in the menu tree.
Your hook will be invoked as follows:
HookFunction(struct Hook *hook, Object *obj, struct MenuStringMessage *msg)
where 'obj' is the menu or item the string ID to be converted belongs to, and 'msg' is a pointer to a MenuStringMessage structure (see below for definition) which carries the string ID.
You can also specify the address of an open catalog and/or a charset by setting the MA_Catalog and MA_CharSet attributes for the menu root object; if supplied, this information is also passed back to the hook in the MenuStringMessage.
The MenuStringMessage structure is defined as follows:
struct MenuStringMessage { uint32 StructSize; /* For future expansion */ uint32 StringID; /* The string ID number */ struct Catalog *Catalog; /* Catalog pointer, may be NULL */ uint32 CharSet; /* Charset number, may be zero */ };
Your string hook should return the correct catalog string for the passed string ID, or a default string if no catalog is available.
Note |
---|
All of the above is equally valid for the MA_Key attribute, which can be a string ID instead of an actual string just like MA_Label. Its string ID range is the same one of MA_Label (as defined by MA_MinStringID and MA_MaxStringID). |
Also Note |
---|
If you don't use a string hook, MA_Label and MA_Key should always be real strings and not numeric IDs, otherwise they'll get converted by menuclass to "????", which isn't very meaningful. The same will happen if the string hook returns a NULL string pointer. |
A BOOPSI menu tree built with menuclass allows for dynamic addition and removal of menus and items at any time. As with other modifications, there's no need to detach the menu tree from the window in order to perform these operations; it's menuclass that takes care of synchronizing each change with Intuition and doing any necessary relayout afterwards.
To add a child to a menuclass object, you can simply call SetAttrs() to pass it the MA_AddChild tag with the address of the new child as data, the same way you would at OM_NEW time.
Here's an example of adding a sub-item to an item:
/* Append a new entry to our "recent files" sub-menu */ new_item = MItem(filename), MA_ID, new_id++, MEnd; IIntuition->SetAttrs(recentfiles_itemobj,MA_AddChild,new_item,TAG_END);
A NULL pointer can safely be passed with MA_AddChild, and is simply ignored.
Note |
---|
In this example we're using a simple counter variable to generate an unique ID for each new sub-item -- we don't care about the ID value itself, we simply want to take notice if the item gets selected, and be able to retrieve its address with MM_FINDID whenever needed. This requires the new item to have a valid and unique ID number. |
Just like they can be added, menus and menu items can also be removed from the menu tree as needed. This is achieved by passing the MA_RemoveChild tag to the parent menu object via SetAttrs(), with the address of the child as data.
For instance, we could remove an item from a menu this way:
/* Close a window and remove its entry from our "open windows" menu */ id = (uint32)window->UserData; /* We stored the menu item's ID there */ IIntuition->CloseWindow(window); item = (Object *)IIntuition->IDoMethod(openwindows_menuobj,MM_FINDID,0,id); IIntuition->SetAttrs(openwindows_menuobj,MA_RemoveChild,item,TAG_END); IIntuition->DisposeObject(item); /* Accepts a NULL argument */
As with MA_AddChild, you can safely pass a NULL pointer with MA_RemoveChild.
Another way to perform addition and removal of children with menuclass objects is to use rootclass' OM_ADDMEMBER and OM_REMMEMBER methods. The above examples could thus be rewritten by replacing the SetAttrs() calls with:
IIntuition->IDoMethod(recentfiles_itemobj,OM_ADDMEMBER,new_item); IIntuition->IDoMethod(openwindows_menuobj,OM_REMMEMBER,item);
As you would when dealing with Exec lists, you must be careful to avoid adding or removing the same child twice, or removing a child which was never added in the first place, since doing so will cause memory corruption.
You can obtain the addresses of all children of a menuclass object in sequence by invoking the MM_NEXTCHILD method in a loop. Once all the object's children have been been retrieved, MM_NEXTCHILD will return NULL to signal there are no more children.
The MM_NEXTCHILD method uses the following message structure:
struct mpNextChild { uint32 MethodID; uint32 mpnc_Reserved; Object *mpnc_Current; };
where MethodID is MM_NEXTCHILD, mpnc_Reserved should always be set to zero, and mpnc_Current is the child object address the method did return on the previous invocation. Pass NULL in mpnc_Current to get the address of the first child.
An example of a loop retrieving all items of a menu:
Object *obj = NULL; while ((obj = (Object *)IIntuition->IDoMethod(menuobj,MM_NEXTCHILD,0,obj)) != NULL) { /* Do something with the item object */ }
If you use MM_NEXTCHILD in a loop to remove an object's children one by one, be careful not to pass the method the address of an already removed object.
The correct way to do a sequential child removal is the following:
while ((obj = (Object *)IIntuition->IDoMethod(menuobj,MM_NEXTCHILD,0,NULL)) != NULL) { IIntuition->IDoMethod(menuobj,OM_REMMEMBER,obj); }
This is analogous to performing a RemHead() loop on an Exec list.
You can perform a scan of the whole menu tree, or even just a subsection of it, and have a custom function called on each object that is part of it. This makes it possible to implement any kind of operation that is not already provided by the existing menuclass methods.
To scan a menu (sub-)tree, you invoke the MM_SCAN method on its root object and specify a callback function and some parameters that will be passed back to it on each call. The MM_SCAN method uses the following message structure:
struct mpScan { uint32 MethodID; uint32 mps_Reserved; struct Hook *mps_Hook; uint32 mps_Args[4]; };
where MethodID is MM_SCAN, mps_Reserved should always be set to zero, mps_Hook is a pointer to your custom hook, and mps_Args[0] to mps_Args[3] are (optional) arguments for the hook's function. Your hook will be invoked as follows:
HookFunction(struct Hook *hook, Object *obj, struct MenuScanMessage *msg)
where 'obj' is a menuclass object (whose exact type you can inspect by reading its MA_Type attribute) and 'msg' is a pointer to a MenuScanMessage structure.
The MenuScanMessage structure is defined as follows:
struct MenuScanMessage { uint32 StructSize; /* For future expansion */ int32 Level; /* How deep we are in the menu tree */ uint32 Args[4]; /* Custom arguments */ };
The Level field indicates the depth in the tree of the current object, starting from -1 for the root object. The four Args variables hold the same values which were passed upon invocation of the MM_SCAN method and can be used to feed your custom arguments back to the hook.
If your hook returns a non-zero value, the scan stops with the method returning the address of the object on which it stopped. If the hook always returns zero, all objects are scanned and the MM_SCAN method returns NULL.
An example of this method's usage could be the following:
/* A simple function to count how many items a menu has, using MM_SCAN */ uint32 HookFunction(struct Hook *h, Object *o, struct MenuScanMessage *msg) { if (msg->Level == 1) *((uint32 *)msg->Args[0]) += 1; return (0); } uint32 CountMenuItems(Object *menuobj) { struct Hook hook; uint32 count = 0; memset(&hook,0,sizeof(struct Hook)); hook.h_Entry = (HOOKFUNC)HookFunction; IIntuition->IDoMethod(menuobj,MM_SCAN,0,&hook,&count,0,0,0); return (count); }
In this example we pass as the only custom argument the address of an variable which, once the MM_SCAN method returns, will hold the amount of children of the specified menu object. In the hook function we check that the object's level is 1, to make sure we only take it into account if it is a direct child of a menu (menus have a level of zero), rather than being a sub-item of an item, or even the menu object itself on which the method is invoked.
So far we have only seen menu trees built by way of a series of nested calls to NewObject(). This is certainly a working approach to putting together a tree of BOOPSI objects organized in a hierarchy. Still, it presents a few drawbacks:
- It can consume a significant amount of stack with very deep trees;
- It may produce code which is hard to read unless special macros are used;
- It makes error checking not very easy.
All in all, nesting NewObject() calls is quite acceptable with relatively small menu trees, while it can become less manageable as the tree's size grows.
A different technique for building menu trees, which attempts to overcome these issues, is provided by menuclass' MM_NEWMENU method. This allows to generate an entire tree, or a sub-tree, out of a flat description of its structure given in the form of a single tag list.
The MM_NEWMENU method uses the following message structure:
struct mpNewMenu { uint32 MethodID; uint32 mpnm_Reserved; struct TagItem *mpnm_AttrList; };
where MethodID is MM_NEWMENU, mpnm_Reserved should always be set to zero, and mpnm_AttrList is the tag list describing the menu tree to be generated.
Said tag list is made up for the most part of the usual menuclass tags that are used to specify attributes of the individual objects; however, the menu objects themselves are introduced by special tags prepended with NM_ which are actually directives for the method.
MM_NEWMENU understands three directives: NM_Menu / NM_Item, indicating to add a new menu or item object at the current level (the tag data is the object's text label), and NM_SubItems, indicating to move down or up by one level before the next item object is added. If the tag data of NM_SubItems is SI_BEGIN, the next item will become a child of the most recent one added (i.e. we move down by one level); if it is SI_END, the next item will become a sibling of the most recent one's parent (i.e. we move up by one level).
Note |
---|
NM_SubItems is not needed to add menus to a menu strip object, nor to add items to a menu object -- its only purpose is to act as a "delimiter" for a list of sub-items of an item object. |
Any menuclass tag that is encountered in the tag list is passed as an attribute to the most recently added object.
The method should be invoked on an existing menuclass object, which in case of success will become the new tree's root. MM_NEWMENU returns FALSE to indicate failure, the exact reasons for which can be inspected through the MA_ErrorCode and MA_ErrorTagItem tags. See <intuition/menuclass.h> for a list of menuclass error codes and their explanation.
Here's a simple example to illustrate the method's usage:
/* Service function allowing to pass varargs tag list to MM_NEWMENU */ static BOOL VARARGS68K DoNewMenu(Object *root, ...) { va_list ap; struct TagItem *tags; BOOL result; va_startlinear(ap,root); tags = (struct TagItem *)va_getlinearva(ap,struct TagItem *); result = IIntuition->IDoMethod(root,MM_NEWMENU,0,tags); va_end(ap); return (result); } /* Build a menu tree with the MM_NEWMENU method */ Object *BuildMenuTree(void) { Object *menustripobj; uint32 success, error; struct TagItem *error_ti; menustripobj = IIntuition->NewObject(NULL,"menuclass",MA_Type,T_ROOT,TAG_END); if (menustripobj) { success = DoNewMenu(menustripobj, MA_ErrorCode, &error, MA_ErrorTagItem, &error_ti, NM_Menu, "Project", MA_ID, MID_PROJECT, NM_Item, "O|Open", MA_ID, MID_OPEN, NM_Item, "S|Save", MA_ID, MID_SAVE, NM_Item, "A|Save as...", MA_ID, MID_SAVEAS, NM_Item, ML_SEPARATOR, NM_Item, "Q|Quit", MA_ID, MID_QUIT, NM_Menu, "Options", MA_ID, MID_OPTIONS, NM_Item, "F|Font...", MA_ID, MID_FONT, NM_Item, "Style", MA_ID, MID_STYLE, NM_SubItems, SI_BEGIN, NM_Item, "B|Bold", MA_ID, MID_BOLD, MA_Toggle, TRUE, NM_Item, "I|Italic", MA_ID, MID_ITALIC, MA_Toggle, TRUE, NM_Item, "U|Underline", MA_ID, MID_UNDERLINE, MA_Toggle, TRUE, NM_SubItems, SI_END, NM_Item, "W|Word wrap", MA_ID, MID_WORDWRAP, MA_Toggle, TRUE, TAG_END); if (!success) { IDOS->Printf("Failed with error %ld, at tag %08lX\n",error,error_ti->ti_Tag); IIntuition->IDoMethod(menustripobj,MM_DELETEMENU,0); IIntuition->DisposeObject(menustripobj); menustripobj = NULL; } } return (menustripobj); }
In this example we make use of a small service function to pass the tag list on the stack as varargs; of course the tag list can also be allocated statically. Also, the above tree is rather small for the sake of keeping the example short, so it does not benefit much from using MM_NEWMENU instead of nested NewObject() calls, but obviously the same technique can be used to build trees of any size.
As you can see, with MM_NEWMENU you can write compact and quite readable code to describe your menu trees, without having to resort to using macros on top of nested calls. The tree's description can even be made somewhat reminiscent of traditional NewMenu arrays as used with GadTools, which may look more familiar to long-time developers.
To free a menu (sub-)tree generated with MM_NEWMENU, you invoke MM_DELETEMENU on the root object. This method uses the following message structure:
struct mpDeleteMenu { uint32 MethodID; uint32 mpdm_Reserved; };
where MethodID is MM_DELETEMENU, and mpdm_Reserved should (as usual) be set to zero. Therefore, the correct way to invoke the method is the following:
IIntuition->IDoMethod(menustripobj,MM_DELETEMENU,0);
Note |
---|
This call only disposes of all of the object's children, not of the root object itself. This way you can reuse the object in case you need to build another menu tree with MM_NEWMENU. To free the root object (and its children if you didn't already do that), simply use DisposeObject(). |