Copyright (c) Hyperion Entertainment and contributors.

Difference between revisions of "Intuition Context Menus"

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
(Created page with "INTUITION CONTEXT MENUS = An overview = Starting with version 54.6, Intuition makes it possible to add context menus to windows and gadgets. A context menu is a menu offerin...")
 
Line 1: Line 1:
INTUITION CONTEXT MENUS
 
 
 
= An overview =
 
= An overview =
   
  +
Starting with version 54.6, Intuition makes it possible to add context menus to windows and gadgets. A context menu is a menu offering a limited set of choices that apply specifically to a particular element of the application, rather than to the application as a whole. Furthermore, a context menu can be brought up by the user only when the element it applies to lies under the mouse pointer, and it always pops up near, if not over, the element itself.
Starting with version 54.6, Intuition makes it possible to add context menus to
 
windows and gadgets. A context menu is a menu offering a limited set of choices
 
that apply specifically to a particular element of the application, rather than
 
to the application as a whole. Furthermore, a context menu can be brought up by
 
the user only when the element it applies to lies under the mouse pointer, and
 
it always pops up near, if not over, the element itself.
 
   
From an application's viewpoint, a context menu is a menu strip which contains
+
From an application's viewpoint, a context menu is a menu strip which contains just one menu, having as many items and sub-items as desired. It can be either of the traditional type, or of the BOOPSI type (i.e. built with menuclass).
just one menu, having as many items and sub-items as desired. It can be either
 
of the traditional type, or of the BOOPSI type (i.e. built with menuclass).
 
   
An Intuition context menu can be tied either to some zone of the window or to a
+
An Intuition context menu can be tied either to some zone of the window or to a gadget. When the user presses the right mouse button while the mouse pointer is over that zone or gadget, the context menu appears under the pointer and allows the user to select some action or option which is meaningful for the object the menu is tied to.
gadget. When the user presses the right mouse button while the mouse pointer is
 
over that zone or gadget, the context menu appears under the pointer and allows
 
the user to select some action or option which is meaningful for the object the
 
menu is tied to.
 
   
If the RMB is pressed at coordinates that don't correspond to any context menu,
+
If the RMB is pressed at coordinates that don't correspond to any context menu, the normal window menus (if any exist) will come up as usual.
the normal window menus (if any exist) will come up as usual.
 
   
 
== Context menus tied to a window zone ==
 
== Context menus tied to a window zone ==
   
You can enable context menus for a window by way of a context menu hook. That's
+
You can enable context menus for a window by way of a context menu hook. That's passed to the window with the WA_ContextMenuHook tag, and is invoked each time Intuition is about to bring up the menus of a window in response to a RMB click by the user.
passed to the window with the WA_ContextMenuHook tag, and is invoked each time
 
Intuition is about to bring up the menus of a window in response to a RMB click
 
by the user.
 
   
By examining the current mouse position, the hook can decide whether to return
+
By examining the current mouse position, the hook can decide whether to return a specific context menu which will pop up under the mouse pointer, or NULL to get the window's normal menu strip.
a specific context menu which will pop up under the mouse pointer, or NULL to
 
get the window's normal menu strip.
 
   
  +
If a context menu is actually passed back to Intuition, any event from it will be sent to the application as an IDCMP_MENUPICK or IDCMP_MENUHELP IntuiMessage, as usual. The application is able to check whether a menu event comes from a context menu, and also obtain all the needed information to process the event correctly, by reading the additional tag list attached to the IntuiMessage. (Later on in this document you'll find a list of menu event tags along with an explanation of their usage.)
If a context menu is actually passed back to Intuition, any event from it will
 
be sent to the application as an IDCMP_MENUPICK or IDCMP_MENUHELP IntuiMessage,
 
as usual. The application is able to check whether a menu event comes from a
 
context menu, and also obtain all the needed information to process the event
 
correctly, by reading the additional tag list attached to the IntuiMessage.
 
(Later on in this document you'll find a list of menu event tags along with an
 
explanation of their usage.)
 
   
The hook's function gets called with the window as object, and a ContextMenuMsg
+
The hook's function gets called with the window as object, and a ContextMenuMsg structure as message. This structure is defined as follows:
structure as message. This structure is defined as follows:
 
   
  +
<syntaxhighlight>
 
struct ContextMenuMsg
 
struct ContextMenuMsg
 
{
 
{
Line 54: Line 29:
 
APTR Context; /* The application-specific element the menu is tied to */
 
APTR Context; /* The application-specific element the menu is tied to */
 
};
 
};
  +
</syntaxhighlight>
   
The State field of the message tells the hook what action it is being invoked
+
The State field of the message tells the hook what action it is being invoked for; currently this can only be CM_QUERY, which means "supply a context menu pointer to Intuition".
for; currently this can only be CM_QUERY, which means "supply a context menu
 
pointer to Intuition".
 
   
When the state is CM_QUERY, the hook function must fill in the Menu field with
+
When the state is CM_QUERY, the hook function must fill in the Menu field with a menu pointer (either a BOOPSI "menuclass" object tree or a traditional Menu structure with its chain of items and sub-items).
a menu pointer (either a BOOPSI "menuclass" object tree or a traditional Menu
 
structure with its chain of items and sub-items).
 
   
  +
It can also fill in the Context field with some unique value (e.g. an address) that identifies the application-specific element the returned context menu is being brought up for; the application can read this value back when receiving an IDCMP_MENUPICK or IDCMP_MENUHELP message from the context menu, in order to know exactly what element the context menu selection applies to. That's useful since the context menu hook may return the same context menu for more than one element, i.e. its choice of context menu may depend exclusively on the TYPE of the element lying under the mouse pointer at the time the RMB is pressed.
It can also fill in the Context field with some unique value (e.g. an address)
 
that identifies the application-specific element the returned context menu is
 
being brought up for; the application can read this value back when receiving
 
an IDCMP_MENUPICK or IDCMP_MENUHELP message from the context menu, in order to
 
know exactly what element the context menu selection applies to. That's useful
 
since the context menu hook may return the same context menu for more than one
 
element, i.e. its choice of context menu may depend exclusively on the TYPE of
 
the element lying under the mouse pointer at the time the RMB is pressed.
 
   
The function can read the current mouse position from the MouseX/MouseY fields
+
The function can read the current mouse position from the MouseX/MouseY fields of the passed window structure to decide what context menu to pass back in the Menu field. Returning NULL as menu is valid, and just makes Intuition bring up the window's normal menu strip.
of the passed window structure to decide what context menu to pass back in the
 
Menu field. Returning NULL as menu is valid, and just makes Intuition bring up
 
the window's normal menu strip.
 
   
The context menu returned by the hook may have been set up by the application
+
The context menu returned by the hook may have been set up by the application at start-up time and stored somewhere for the hook to use, or it may even get built on-the-fly by the hook itself, in case a dynamic context menu is needed. Any context menus built by the hook should be cached, and freed later by the application (typically on exit).
at start-up time and stored somewhere for the hook to use, or it may even get
 
built on-the-fly by the hook itself, in case a dynamic context menu is needed.
 
Any context menus built by the hook should be cached, and freed later by the
 
application (typically on exit).
 
   
The hook's return value is currently ignored, but it is recommended to always
+
The hook's return value is currently ignored, but it is recommended to always set it to zero for future compatibility.
set it to zero for future compatibility.
 
   
 
== Context menus tied to a gadget ==
 
== Context menus tied to a gadget ==
Line 90: Line 47:
 
Gadgets can have a context menu attached to them in one of two possible ways.
 
Gadgets can have a context menu attached to them in one of two possible ways.
   
The simplest one is to set a gadget's GA_ContextMenu attribute to the address
+
The simplest one is to set a gadget's GA_ContextMenu attribute to the address of a context menu set up by the application. In this case the application is fully responsible for handling any event coming from the context menu; in most cases, the gadget is not even aware of its context menu's existence.
of a context menu set up by the application. In this case the application is
 
fully responsible for handling any event coming from the context menu; in most
 
cases, the gadget is not even aware of its context menu's existence.
 
   
  +
The other way is to have the gadget itself set up and return a context menu on demand. This lets the gadget have full control on the menu's contents and even be able to return different menus depending on what part of the gadget is under the mouse pointer when the RMB is clicked. When employing this technique, menu events are handled by the gadget in a dedicated method, although they are first sent to the application as it has the responsibility of invoking said method on the gadget (this is covered in the next subsection).
The other way is to have the gadget itself set up and return a context menu on
 
demand. This lets the gadget have full control on the menu's contents and even
 
be able to return different menus depending on what part of the gadget is under
 
the mouse pointer when the RMB is clicked. When employing this technique, menu
 
events are handled by the gadget in a dedicated method, although they are first
 
sent to the application as it has the responsibility of invoking said method on
 
the gadget (this is covered in the next subsection).
 
   
Whenever the RMB is pressed while the mouse pointer is over a gadget, Intuition
+
Whenever the RMB is pressed while the mouse pointer is over a gadget, Intuition asks the gadget to provide, if available, a context menu to be opened in place of the normal menus. It does so by invoking GM_QUERY on the gadget, with query type GMQ_CONTEXTMENU.
asks the gadget to provide, if available, a context menu to be opened in place
 
of the normal menus. It does so by invoking GM_QUERY on the gadget, with query
 
type GMQ_CONTEXTMENU.
 
   
  +
When a gadget receives a GM_QUERY/GMQ_CONTEXTMENU message, it is given a chance of returning a context menu pointer in the gpq_Data field of the passed gpQuery structure. The gadget can read the mouse pointer position from gpq_IEvent and use this information to decide whether or not a context menu should be opened, and what its contents should be (each different part of the gadget may offer an individual context menu, and some parts might even have none).
When a gadget receives a GM_QUERY/GMQ_CONTEXTMENU message, it is given a chance
 
of returning a context menu pointer in the gpq_Data field of the passed gpQuery
 
structure. The gadget can read the mouse pointer position from gpq_IEvent and
 
use this information to decide whether or not a context menu should be opened,
 
and what its contents should be (each different part of the gadget may offer an
 
individual context menu, and some parts might even have none).
 
   
Context menus returned by a gadget upon GM_QUERY may have been set up at OM_NEW
+
Context menus returned by a gadget upon GM_QUERY may have been set up at OM_NEW time and kept in memory for the entire life of the gadget, or they may be built on-the-fly only when needed, which also allows for dynamic context menus.
time and kept in memory for the entire life of the gadget, or they may be built
 
on-the-fly only when needed, which also allows for dynamic context menus.
 
   
If no menu is returned by this method, Intuition will check if a context menu
+
If no menu is returned by this method, Intuition will check if a context menu was attached to the gadget by the application via the GA_ContextMenu attribute, and use that one if this is the case. If that check fails as well, the normal window menu strip will be displayed as usual.
was attached to the gadget by the application via the GA_ContextMenu attribute,
 
and use that one if this is the case. If that check fails as well, the normal
 
window menu strip will be displayed as usual.
 
   
If a gadget class returns a valid context menu through GM_QUERY, it should then
+
If a gadget class returns a valid context menu through GM_QUERY, it should then be prepared to handle user input from the menu being fed back to it in the form of GM_MENUPICK/GM_MENUHELP messages.
be prepared to handle user input from the menu being fed back to it in the form
 
of GM_MENUPICK/GM_MENUHELP messages.
 
   
The GM_MENUPICK and GM_MENUHELP methods (new for V54) are used to let a custom
+
The GM_MENUPICK and GM_MENUHELP methods (new for V54) are used to let a custom gadget hear context menu selections or help requests by the user, and have the chance of handling them.
gadget hear context menu selections or help requests by the user, and have the
 
chance of handling them.
 
   
 
These two methods use the following message structure:
 
These two methods use the following message structure:
   
  +
<syntaxhighlight>
 
struct gpMenuEvent
 
struct gpMenuEvent
 
{
 
{
Line 142: Line 74:
 
struct Window *gpme_Window; /* The gadget's window */
 
struct Window *gpme_Window; /* The gadget's window */
 
};
 
};
  +
</syntaxhighlight>
   
Note that unlike most input methods, GM_MENUPICK and GM_MENUHELP are normally
+
Note that unlike most input methods, GM_MENUPICK and GM_MENUHELP are normally invoked on the context of the application rather than that of input.device. This is because the gadget could need to perform any sort of complex actions in response to a context menu selection, even involving disk I/O or high-level system calls that would be unfeasible on the input.device task.
invoked on the context of the application rather than that of input.device.
 
This is because the gadget could need to perform any sort of complex actions
 
in response to a context menu selection, even involving disk I/O or high-level
 
system calls that would be unfeasible on the input.device task.
 
   
For the same reason, these methods are never invoked under Intuition's state
+
For the same reason, these methods are never invoked under Intuition's state machine (e.g. via DoGadgetMethod()), since that would make them unable to call a number of Intuition functions without deadlocking. A side effect of this is that GM_MENUPICK/GM_MENUHELP are always passed a NULL
  +
GadgetInfo pointer, with the needed context information provided instead by a pointer to the gadget's window (gpme_Window). Implementors of these two input methods should therefore use DoGadgetMethod() themselves to invoke other gadget methods requiring a GadgetInfo.
machine (e.g. via DoGadgetMethod()), since that would make them unable to call
 
a number of Intuition functions without deadlocking.
 
A side effect of this is that GM_MENUPICK/GM_MENUHELP are always passed a NULL
 
GadgetInfo pointer, with the needed context information provided instead by a
 
pointer to the gadget's window (gpme_Window).
 
Implementors of these two input methods should therefore use DoGadgetMethod()
 
themselves to invoke other gadget methods requiring a GadgetInfo.
 
   
  +
The address of the context menu that originated the event can be found in the gpme_MenuAddress field of the message structure. The menu number of the first item or sub-item that was picked by the user is stored in the gpme_MenuNumber field; this number is the menu item's ID for BOOPSI (menuclass) menus, and a traditional menu code for old-style menus. If the method is GM_MENUPICK, there can be other items in the selection chain
The address of the context menu that originated the event can be found in the
 
gpme_MenuAddress field of the message structure. The menu number of the first
 
item or sub-item that was picked by the user is stored in the gpme_MenuNumber
 
field; this number is the menu item's ID for BOOPSI (menuclass) menus, and a
 
traditional menu code for old-style menus.
 
If the method is GM_MENUPICK, there can be other items in the selection chain
 
 
in case the user performed multiple selection.
 
in case the user performed multiple selection.
   
For GM_MENUPICK, if the context menu is BOOPSI, you can ignore gpme_MenuNumber
+
For GM_MENUPICK, if the context menu is BOOPSI, you can ignore gpme_MenuNumber and simply read all item selections in sequence with MM_NEXTSELECT.
and simply read all item selections in sequence with MM_NEXTSELECT.
 
   
GM_MENUHELP, on the other hand, is always triggered for a single menu element,
+
GM_MENUHELP, on the other hand, is always triggered for a single menu element, and there's no selection chain to follow. So, even for BOOPSI menus, reading gpme_MenuNumber is the quickest way to find out which item the user asked for help about. Alternatively, you can always query the MA_MenuHelpID attribute of the context menu's root object (pointed to by gpme_MenuAddress).
and there's no selection chain to follow. So, even for BOOPSI menus, reading
 
gpme_MenuNumber is the quickest way to find out which item the user asked for
 
help about. Alternatively, you can always query the MA_MenuHelpID attribute of
 
the context menu's root object (pointed to by gpme_MenuAddress).
 
   
After processing the event, the gadget may free the menu, if it was generated
+
After processing the event, the gadget may free the menu, if it was generated by the gadget itself upon GM_QUERY. If this is not done, the menu will have to be cached in the gadget's instance data and freed at OM_DISPOSE time.
by the gadget itself upon GM_QUERY. If this is not done, the menu will have to
 
be cached in the gadget's instance data and freed at OM_DISPOSE time.
 
   
The return value of these two methods is currently ignored; it should always
+
The return value of these two methods is currently ignored; it should always be set to zero for future compatibility.
be set to zero for future compatibility.
 
   
 
== Handling IDCMP events from context menus ==
 
== Handling IDCMP events from context menus ==
   
Context menu events will be sent to your window's UserPort as an IDCMP_MENUPICK
+
Context menu events will be sent to your window's UserPort as an IDCMP_MENUPICK or IDCMP_MENUHELP IntuiMessage, just like it happens with normal menu events.
or IDCMP_MENUHELP IntuiMessage, just like it happens with normal menu events.
 
   
  +
Such messages are handled mostly the same way as those that come from a window menu strip. In order to interpret them correctly, however, the application will first need to determine whether an IDCMP message is in fact caused by a context menu selection; if so, it also has to find out which context menu originated it (unless the application's GUI has just one context menu) and what element (that is, what "context") the received message actually applies to.
Such messages are handled mostly the same way as those that come from a window
 
menu strip. In order to interpret them correctly, however, the application will
 
first need to determine whether an IDCMP message is in fact caused by a context
 
menu selection; if so, it also has to find out which context menu originated it
 
(unless the application's GUI has just one context menu) and what element (that
 
is, what "context") the received message actually applies to.
 
   
In order to help determine all this, IntuiMessages associated to menu events do
+
In order to help determine all this, IntuiMessages associated to menu events do now carry some additional information.
now carry some additional information.
 
   
The IAddress field of IDCMP_MENUPICK and IDCMP_MENUHELP messages points to the
+
The IAddress field of IDCMP_MENUPICK and IDCMP_MENUHELP messages points to the specific menu strip, or context menu, the menu event is coming from. This is to allow applications to quickly get the address of the menu strip that triggered a context menu event. (In the case of an event from the window's normal menu strip, IAddress will be the same as IDCMPWindow->MenuStrip.)
specific menu strip, or context menu, the menu event is coming from. This is to
 
allow applications to quickly get the address of the menu strip that triggered
 
a context menu event.
 
(In the case of an event from the window's normal menu strip, IAddress will be
 
the same as IDCMPWindow->MenuStrip.)
 
   
Also, IDCMP_MENUPICK and IDCMP_MENUHELP IntuiMessages have a tag list attached
+
Also, IDCMP_MENUPICK and IDCMP_MENUHELP IntuiMessages have a tag list attached which can be accessed by casting the message to an ExtIntuiMessage and reading its eim_TagList field. (This can always be done, since all IntuiMessages coming from Intuition are actually ExtIntuiMessages.)
which can be accessed by casting the message to an ExtIntuiMessage and reading
 
its eim_TagList field. (This can always be done, since all IntuiMessages coming
 
from Intuition are actually ExtIntuiMessages.)
 
   
 
The following tags are currently defined for menu-related IntuiMessages:
 
The following tags are currently defined for menu-related IntuiMessages:
Line 234: Line 129:
 
This tag is not present for a normal window menu strip.
 
This tag is not present for a normal window menu strip.
   
  +
Note well: whenever the originating menu is of type IMT_CONTEXT_GADGET_OBJ, the application needs to react to the event by invoking GM_MENUPICK or GM_MENUHELP (according to the IDCMP class of the IntuiMessage) on the context menu's gadget via IDoMethod(). The three arguments passed to these methods (gpme_MenuAddress, gpme_MenuNumber and gpme_Window) are the values of the IAddress, eim_LongCode and IDCMPWindow fields of the received IntuiMessage (cast it to ExtIntuiMessage to access eim_LongCode).
Note well: whenever the originating menu is of type IMT_CONTEXT_GADGET_OBJ, the
 
application needs to react to the event by invoking GM_MENUPICK or GM_MENUHELP
 
(according to the IDCMP class of the IntuiMessage) on the context menu's gadget
 
via IDoMethod(). The three arguments passed to these methods (gpme_MenuAddress,
 
gpme_MenuNumber and gpme_Window) are the values of the IAddress, eim_LongCode
 
and IDCMPWindow fields of the received IntuiMessage (cast it to ExtIntuiMessage
 
to access eim_LongCode).
 
   
  +
The reason for this special handling is to allow the gadget to process its menu selections completely on the application's context, since the processing could well involve operations which cannot be carried out on the input.device task's context (like performing disk I/O) or Intuition function calls that would cause a deadlock if executed under Intuition's state machine (for instance, a gadget might have a context menu featuring an option that brings up a file requester).
The reason for this special handling is to allow the gadget to process its menu
 
selections completely on the application's context, since the processing could
 
well involve operations which cannot be carried out on the input.device task's
 
context (like performing disk I/O) or Intuition function calls that would cause
 
a deadlock if executed under Intuition's state machine (for instance, a gadget
 
might have a context menu featuring an option that brings up a file requester).
 
   
When using window.class, all of this is handled automatically so that you don't
+
When using window.class, all of this is handled automatically so that you don't have to care about the above in your code.
have to care about the above in your code.
 
   
 
Here's a simple example of how all this could be put to use:
 
Here's a simple example of how all this could be put to use:
   
  +
<syntaxhighlight>
 
/* This could be a part of the event loop of an AmiDock-like program */
 
/* This could be a part of the event loop of an AmiDock-like program */
 
/* In this example we're using BOOPSI menus to keep the code simple */
 
/* In this example we're using BOOPSI menus to keep the code simple */
Line 385: Line 269:
 
...
 
...
 
}
 
}
  +
</syntaxhighlight>
   
 
== Context menus and window.class ==
 
== Context menus and window.class ==
   
When using window.class there is no direct access to the IntuiMessage unless an
+
When using window.class there is no direct access to the IntuiMessage unless an IDCMP hook is used. To keep menu handling a little simpler, window.class (V54+) provides a few attributes that can be read upon WMHI_MENUPICK/WMHI_MENUHELP and carry the same extra information found in menu-related (Ext)IntuiMessages.
IDCMP hook is used. To keep menu handling a little simpler, window.class (V54+)
 
provides a few attributes that can be read upon WMHI_MENUPICK/WMHI_MENUHELP and
 
carry the same extra information found in menu-related (Ext)IntuiMessages.
 
   
 
These attributes are:
 
These attributes are:
   
WINDOW_MenuType -- the data from IMTAG_MenuType
+
; WINDOW_MenuType
WINDOW_MenuContext -- the data from IMTAG_MenuContext
+
: the data from IMTAG_MenuType
  +
WINDOW_MenuAddress -- the value of the IntuiMessage's IAddress field
 
  +
; WINDOW_MenuContext
  +
: the data from IMTAG_MenuContext
  +
  +
; WINDOW_MenuAddress
  +
: the value of the IntuiMessage's IAddress field
   
You can query these attributes of your window object when you need to handle a
+
You can query these attributes of your window object when you need to handle a menu event that might come from a context menu. Note that if your application doesn't use context menus, you never need to care about these attributes.
menu event that might come from a context menu. Note that if your application
 
doesn't use context menus, you never need to care about these attributes.
 
   
Also note that when using window.class you'll never see IMT_CONTEXT_GADGET_OBJ
+
Also note that when using window.class you'll never see IMT_CONTEXT_GADGET_OBJ events, as these are handled transparently by the class.
events, as these are handled transparently by the class.
 
   
 
== Peculiarities of context menus compared to normal menus ==
 
== Peculiarities of context menus compared to normal menus ==
   
  +
It is important to point out that, contrary to normal menus, a context menu can be brought up by a RMB click even when its window is inactive, as long as the mouse pointer is located over the GUI element it is tied to. This allows to add context menus to icons of docks, or gadgets of floating toolbars, and have them always open instantly as the user would expect, without any need for him/her to manually activate the respective window first.
It is important to point out that, contrary to normal menus, a context menu can
 
be brought up by a RMB click even when its window is inactive, as long as the
 
mouse pointer is located over the GUI element it is tied to. This allows to add
 
context menus to icons of docks, or gadgets of floating toolbars, and have them
 
always open instantly as the user would expect, without any need for him/her to
 
manually activate the respective window first.
 
   
If this behavior is not needed or desired, just check if the relevant window is
+
If this behavior is not needed or desired, just check if the relevant window is active in your window's context menu hook or in your gadget's GM_QUERY method dispatcher, and return a NULL context menu pointer if that is not the case.
active in your window's context menu hook or in your gadget's GM_QUERY method
 
dispatcher, and return a NULL context menu pointer if that is not the case.
 
   
Another significant difference between context menus and normal ones is that a
+
Another significant difference between context menus and normal ones is that a context menu doesn't currently support keyboard shortcuts, as the same context menu can be tied to more than one GUI element, and no way is defined to decide which one of those element should hear the key press. (Intuition still displays the shortcut if you add it to an item, but it will be non-functional.)
context menu doesn't currently support keyboard shortcuts, as the same context
 
menu can be tied to more than one GUI element, and no way is defined to decide
 
which one of those element should hear the key press. (Intuition still displays
 
the shortcut if you add it to an item, but it will be non-functional.)
 
   
As of V54, context menus do not support the "menu verify" mechanism and always
+
As of V54, context menus do not support the "menu verify" mechanism and always open immediately. This may or may not change in future Intuition versions.
open immediately. This may or may not change in future Intuition versions.
 
   
 
Lastly, please note that context menus are always non-blocking.
 
Lastly, please note that context menus are always non-blocking.

Revision as of 05:44, 9 July 2015

An overview

Starting with version 54.6, Intuition makes it possible to add context menus to windows and gadgets. A context menu is a menu offering a limited set of choices that apply specifically to a particular element of the application, rather than to the application as a whole. Furthermore, a context menu can be brought up by the user only when the element it applies to lies under the mouse pointer, and it always pops up near, if not over, the element itself.

From an application's viewpoint, a context menu is a menu strip which contains just one menu, having as many items and sub-items as desired. It can be either of the traditional type, or of the BOOPSI type (i.e. built with menuclass).

An Intuition context menu can be tied either to some zone of the window or to a gadget. When the user presses the right mouse button while the mouse pointer is over that zone or gadget, the context menu appears under the pointer and allows the user to select some action or option which is meaningful for the object the menu is tied to.

If the RMB is pressed at coordinates that don't correspond to any context menu, the normal window menus (if any exist) will come up as usual.

Context menus tied to a window zone

You can enable context menus for a window by way of a context menu hook. That's passed to the window with the WA_ContextMenuHook tag, and is invoked each time Intuition is about to bring up the menus of a window in response to a RMB click by the user.

By examining the current mouse position, the hook can decide whether to return a specific context menu which will pop up under the mouse pointer, or NULL to get the window's normal menu strip.

If a context menu is actually passed back to Intuition, any event from it will be sent to the application as an IDCMP_MENUPICK or IDCMP_MENUHELP IntuiMessage, as usual. The application is able to check whether a menu event comes from a context menu, and also obtain all the needed information to process the event correctly, by reading the additional tag list attached to the IntuiMessage. (Later on in this document you'll find a list of menu event tags along with an explanation of their usage.)

The hook's function gets called with the window as object, and a ContextMenuMsg structure as message. This structure is defined as follows:

  struct ContextMenuMsg
  {
    uint32 State;  /* CM_QUERY */
 
    /* Set the following fields on CM_QUERY */
 
    APTR Menu;     /* A context menu, or NULL for the window menu */
    APTR Context;  /* The application-specific element the menu is tied to */
  };

The State field of the message tells the hook what action it is being invoked for; currently this can only be CM_QUERY, which means "supply a context menu pointer to Intuition".

When the state is CM_QUERY, the hook function must fill in the Menu field with a menu pointer (either a BOOPSI "menuclass" object tree or a traditional Menu structure with its chain of items and sub-items).

It can also fill in the Context field with some unique value (e.g. an address) that identifies the application-specific element the returned context menu is being brought up for; the application can read this value back when receiving an IDCMP_MENUPICK or IDCMP_MENUHELP message from the context menu, in order to know exactly what element the context menu selection applies to. That's useful since the context menu hook may return the same context menu for more than one element, i.e. its choice of context menu may depend exclusively on the TYPE of the element lying under the mouse pointer at the time the RMB is pressed.

The function can read the current mouse position from the MouseX/MouseY fields of the passed window structure to decide what context menu to pass back in the Menu field. Returning NULL as menu is valid, and just makes Intuition bring up the window's normal menu strip.

The context menu returned by the hook may have been set up by the application at start-up time and stored somewhere for the hook to use, or it may even get built on-the-fly by the hook itself, in case a dynamic context menu is needed. Any context menus built by the hook should be cached, and freed later by the application (typically on exit).

The hook's return value is currently ignored, but it is recommended to always set it to zero for future compatibility.

Context menus tied to a gadget

Gadgets can have a context menu attached to them in one of two possible ways.

The simplest one is to set a gadget's GA_ContextMenu attribute to the address of a context menu set up by the application. In this case the application is fully responsible for handling any event coming from the context menu; in most cases, the gadget is not even aware of its context menu's existence.

The other way is to have the gadget itself set up and return a context menu on demand. This lets the gadget have full control on the menu's contents and even be able to return different menus depending on what part of the gadget is under the mouse pointer when the RMB is clicked. When employing this technique, menu events are handled by the gadget in a dedicated method, although they are first sent to the application as it has the responsibility of invoking said method on the gadget (this is covered in the next subsection).

Whenever the RMB is pressed while the mouse pointer is over a gadget, Intuition asks the gadget to provide, if available, a context menu to be opened in place of the normal menus. It does so by invoking GM_QUERY on the gadget, with query type GMQ_CONTEXTMENU.

When a gadget receives a GM_QUERY/GMQ_CONTEXTMENU message, it is given a chance of returning a context menu pointer in the gpq_Data field of the passed gpQuery structure. The gadget can read the mouse pointer position from gpq_IEvent and use this information to decide whether or not a context menu should be opened, and what its contents should be (each different part of the gadget may offer an individual context menu, and some parts might even have none).

Context menus returned by a gadget upon GM_QUERY may have been set up at OM_NEW time and kept in memory for the entire life of the gadget, or they may be built on-the-fly only when needed, which also allows for dynamic context menus.

If no menu is returned by this method, Intuition will check if a context menu was attached to the gadget by the application via the GA_ContextMenu attribute, and use that one if this is the case. If that check fails as well, the normal window menu strip will be displayed as usual.

If a gadget class returns a valid context menu through GM_QUERY, it should then be prepared to handle user input from the menu being fed back to it in the form of GM_MENUPICK/GM_MENUHELP messages.

The GM_MENUPICK and GM_MENUHELP methods (new for V54) are used to let a custom gadget hear context menu selections or help requests by the user, and have the chance of handling them.

These two methods use the following message structure:

  struct gpMenuEvent
  {
     uint32             MethodID;
     struct GadgetInfo *gpme_GInfo;       /* Always NULL */
     APTR               gpme_MenuAddress; /* Originating context menu */
     uint32             gpme_MenuNumber;  /* First item selected */
     struct Window     *gpme_Window;      /* The gadget's window */
  };

Note that unlike most input methods, GM_MENUPICK and GM_MENUHELP are normally invoked on the context of the application rather than that of input.device. This is because the gadget could need to perform any sort of complex actions in response to a context menu selection, even involving disk I/O or high-level system calls that would be unfeasible on the input.device task.

For the same reason, these methods are never invoked under Intuition's state machine (e.g. via DoGadgetMethod()), since that would make them unable to call a number of Intuition functions without deadlocking. A side effect of this is that GM_MENUPICK/GM_MENUHELP are always passed a NULL GadgetInfo pointer, with the needed context information provided instead by a pointer to the gadget's window (gpme_Window). Implementors of these two input methods should therefore use DoGadgetMethod() themselves to invoke other gadget methods requiring a GadgetInfo.

The address of the context menu that originated the event can be found in the gpme_MenuAddress field of the message structure. The menu number of the first item or sub-item that was picked by the user is stored in the gpme_MenuNumber field; this number is the menu item's ID for BOOPSI (menuclass) menus, and a traditional menu code for old-style menus. If the method is GM_MENUPICK, there can be other items in the selection chain in case the user performed multiple selection.

For GM_MENUPICK, if the context menu is BOOPSI, you can ignore gpme_MenuNumber and simply read all item selections in sequence with MM_NEXTSELECT.

GM_MENUHELP, on the other hand, is always triggered for a single menu element, and there's no selection chain to follow. So, even for BOOPSI menus, reading gpme_MenuNumber is the quickest way to find out which item the user asked for help about. Alternatively, you can always query the MA_MenuHelpID attribute of the context menu's root object (pointed to by gpme_MenuAddress).

After processing the event, the gadget may free the menu, if it was generated by the gadget itself upon GM_QUERY. If this is not done, the menu will have to be cached in the gadget's instance data and freed at OM_DISPOSE time.

The return value of these two methods is currently ignored; it should always be set to zero for future compatibility.

Handling IDCMP events from context menus

Context menu events will be sent to your window's UserPort as an IDCMP_MENUPICK or IDCMP_MENUHELP IntuiMessage, just like it happens with normal menu events.

Such messages are handled mostly the same way as those that come from a window menu strip. In order to interpret them correctly, however, the application will first need to determine whether an IDCMP message is in fact caused by a context menu selection; if so, it also has to find out which context menu originated it (unless the application's GUI has just one context menu) and what element (that is, what "context") the received message actually applies to.

In order to help determine all this, IntuiMessages associated to menu events do now carry some additional information.

The IAddress field of IDCMP_MENUPICK and IDCMP_MENUHELP messages points to the specific menu strip, or context menu, the menu event is coming from. This is to allow applications to quickly get the address of the menu strip that triggered a context menu event. (In the case of an event from the window's normal menu strip, IAddress will be the same as IDCMPWindow->MenuStrip.)

Also, IDCMP_MENUPICK and IDCMP_MENUHELP IntuiMessages have a tag list attached which can be accessed by casting the message to an ExtIntuiMessage and reading its eim_TagList field. (This can always be done, since all IntuiMessages coming from Intuition are actually ExtIntuiMessages.)

The following tags are currently defined for menu-related IntuiMessages:

 IMTAG_MenuType    - indicates the type of the menu that triggered the event:
                     IMT_DEFAULT means the window's normal menu strip.
                     IMT_CONTEXT_WINDOW means a context menu originated from
                     the window's context menu hook.
                     IMT_CONTEXT_GADGET_APP means a context menu belonging
                     to a gadget, attached to it by the application with the
                     GA_ContextMenu attribute.
                     IMT_CONTEXT_GADGET_OBJ means a context menu belonging
                     to a gadget, generated by the gadget itself through the
                     GM_QUERY method (see also note below).
 IMTAG_MenuContext - for a context menu event, returns the actual "context"
                     the event applies to. In case of an IMT_CONTEXT_WINDOW
                     event, this is whatever value your window context menu
                     hook passed back in the ContextMenuMsg.Context field.
                     For IMT_CONTEXT_GADGET_APP and IMT_CONTEXT_GADGET_OBJ,
                     this is the address of the gadget the menu belongs to.
                     This tag is not present for a normal window menu strip.

Note well: whenever the originating menu is of type IMT_CONTEXT_GADGET_OBJ, the application needs to react to the event by invoking GM_MENUPICK or GM_MENUHELP (according to the IDCMP class of the IntuiMessage) on the context menu's gadget via IDoMethod(). The three arguments passed to these methods (gpme_MenuAddress, gpme_MenuNumber and gpme_Window) are the values of the IAddress, eim_LongCode and IDCMPWindow fields of the received IntuiMessage (cast it to ExtIntuiMessage to access eim_LongCode).

The reason for this special handling is to allow the gadget to process its menu selections completely on the application's context, since the processing could well involve operations which cannot be carried out on the input.device task's context (like performing disk I/O) or Intuition function calls that would cause a deadlock if executed under Intuition's state machine (for instance, a gadget might have a context menu featuring an option that brings up a file requester).

When using window.class, all of this is handled automatically so that you don't have to care about the above in your code.

Here's a simple example of how all this could be put to use:

  /* This could be a part of the event loop of an AmiDock-like program */
  /* In this example we're using BOOPSI menus to keep the code simple */
 
  switch (imsg->Class)
  {
    case IDCMP_MENUPICK:
 
      /* The ID of the first menu item in the selection chain
       */
      longcode = ((struct ExtIntuiMessage *)imsg)->eim_LongCode;
 
      /* The tag list of this IntuiMessage
       */
      taglist = ((struct ExtIntuiMessage *)imsg)->eim_TagList;
 
      /* Retrieve some useful information from the tag list
       */
      menu_type = GetTagData(IMTAG_MenuType,IMT_DEFAULT,taglist);
      menu_ctx  = GetTagData(IMTAG_MenuContext,(uint32)NULL,taglist);
 
      /* Finally, get the address of the menu which originated the event and
       * the ID number that uniquely identifies it (if it's a context menu).
       */
      menustrip = imsg->IAddress;
      GetAttr(MA_ID,menustrip,&menu_id);
 
      if (menu_type == IMT_CONTEXT_WINDOW)
      {
        /* Handle window context menus here */
 
        if (menu_id == CMENU_DOCKICON)
        {
          /* Our window context menu hook uses the Context
           * field to store the address of the object the
           * context menu is brought up for. This allows to
           * reuse the same context menu for all objects of
           * the same type.
           */
          dockicon = (struct DockIcon *)menu_ctx;
 
          while (longcode != NO_MENU_ID)
          {
            switch (longcode)
            {
              case MID_ICON_EDIT:
                EditDockIcon(dockicon);
                break;
 
              case MID_ICON_REVEAL:
                RevealDockIcon(dockicon);
                break;
 
              ...
            }
 
            longcode = IDoMethod(menustrip,MM_NEXTSELECT,0,longcode);
          }
        }
        else if (menu_id == CMENU_DOCK)
        {
          dock = (struct Dock *)menu_ctx;
 
          while (longcode != NO_MENU_ID)
          {
            ...
          }
        }
        else if (menu_id == CMENU_DOCKY)
        {
          docky = (struct Docky *)menu_ctx;
          ...
        }
      }
      else if (menu_type == IMT_CONTEXT_GADGET_APP)
      {
        /* Handle gadget context menus owned by the application here */
 
        if (menu_id == CMENU_GETCOLORGADGET)
        {
          while (longcode != NO_MENU_ID)
          {
            switch (longcode)
            {
              case MID_GETCOLOR_RESET_TO_DEFAULTS:
                ResetGetColorGadget((struct Gadget *)menu_ctx);
                break;
 
              ...
            }
 
            longcode = IDoMethod(menustrip,MM_NEXTSELECT,0,longcode);
          }
        }
        else if (menu_id == ...)
        {
          ...
        }
      }
      else if (menu_type == IMT_CONTEXT_GADGET_OBJ)
      {
        /* Handle gadget context menus spawned by gadgets here */
 
        IDoMethod((Object *)menu_ctx,GM_MENUPICK,NULL,menustrip,longcode,win);
      }
      else  /* menu_type == IMT_DEFAULT */
      {
        /* Handle normal window menus here */
 
        while (longcode != NO_MENU_ID)
        {
          switch (longcode)
          {
            case MID_EDITPREFS:
              EditPreferences();
              break;
 
            case MID_OPENPREFS:
              OpenPreferences();
              break;
 
            ...
          }
 
          longcode = IDoMethod(menustrip,MM_NEXTSELECT,0,longcode);
        }
      }
 
      break;
 
      ...
  }

Context menus and window.class

When using window.class there is no direct access to the IntuiMessage unless an IDCMP hook is used. To keep menu handling a little simpler, window.class (V54+) provides a few attributes that can be read upon WMHI_MENUPICK/WMHI_MENUHELP and carry the same extra information found in menu-related (Ext)IntuiMessages.

These attributes are:

WINDOW_MenuType
the data from IMTAG_MenuType
WINDOW_MenuContext
the data from IMTAG_MenuContext
WINDOW_MenuAddress
the value of the IntuiMessage's IAddress field

You can query these attributes of your window object when you need to handle a menu event that might come from a context menu. Note that if your application doesn't use context menus, you never need to care about these attributes.

Also note that when using window.class you'll never see IMT_CONTEXT_GADGET_OBJ events, as these are handled transparently by the class.

Peculiarities of context menus compared to normal menus

It is important to point out that, contrary to normal menus, a context menu can be brought up by a RMB click even when its window is inactive, as long as the mouse pointer is located over the GUI element it is tied to. This allows to add context menus to icons of docks, or gadgets of floating toolbars, and have them always open instantly as the user would expect, without any need for him/her to manually activate the respective window first.

If this behavior is not needed or desired, just check if the relevant window is active in your window's context menu hook or in your gadget's GM_QUERY method dispatcher, and return a NULL context menu pointer if that is not the case.

Another significant difference between context menus and normal ones is that a context menu doesn't currently support keyboard shortcuts, as the same context menu can be tied to more than one GUI element, and no way is defined to decide which one of those element should hear the key press. (Intuition still displays the shortcut if you add it to an item, but it will be non-functional.)

As of V54, context menus do not support the "menu verify" mechanism and always open immediately. This may or may not change in future Intuition versions.

Lastly, please note that context menus are always non-blocking.