# gMenu

Definition: | include xpGUI.e gdx menu = gMenu(sequence children, rtn handler, bool bRadio=false)
Description: | Creates a menu element, with items, submenus and separators, which can be attached to a gDialog() or shown as an standalone popup menu. children: simple nested structure detailing the menu contents, see example below. handler: function for processing menu interactions, see HANDLER below. bRadio: for popup menus only, instead of '?' in the (non-existent) submenu title, see below. Returns: the identifier of the created element.
pwa/p2js: | Supported. (However checkbox/radio group handling is incomplete, actually all handling is incomplete, and gPopupMenu not even started.)
See Also: | gDialog
Example: |
    -- demo\xpGUI\gMenu.exw, somewhat simplified
    include xpGUI.e
    constant MAIN_MENU = {{"&File",{{"&New",NEW:=$},
                                    {"&Open",OPEN:=$},
                                    {"&Close",CLOSE:=$},
                                    "|",
                                    {"E&xit",EXIT:=$}}},
                          {"&Edit",EDIT_MENU:=$,
                                   {{"Copy",COPY:=$}}},
                          {"&Help",HELP:=$}}

    function menu_handler(gdx menu, integer id, bool bHighlight)
        string s = gMenuGetAttribute(menu,id,"TITLE")
        printf(1,"%s%s\n",{s,iff(bHighlight?" (highlight)","")})
        return iff(id=EXIT and not bHighlight?XPG_CLOSE:XPG_DEFAULT)
    end function

    gdx menu = gMenu(MAIN_MENU,menu_handler),
        vbox = gVbox({menu/*,...*/}),
         dlg = gDialog(vbox,`gMenu`,`SIZE = 240x80`)
    gShow(dlg)
    gMainLoop()
and when opened, showing an icon on a menu entry: The "children" structure (MAIN_MENU above) should be fairly self-explanatory. Each entry is: {string menu_item, [gImage,] integer id} for a "leaf" menu entry, or any of {"|","-",'|','-'} for a separator (which are all treated identically), or {string submenu, [integer id,] sequence children} That final [nested sub-]children obviously follows the exact same rules, recursively, as deep as you like. There is of course nothing to prevent such a menu from being built dynamically, rather than it being a constant. There is no id on the &File menu, hence you cannot enable/disable it, and won’t be notified on open or mouseover. It would also not be possible to set/get the checked status, except for the fact that you cannot have checkboxes on [sub]menu[header]s, only menu items, and also it is an outright error not to have an id on "leaf" items anyway. In contrast, the presence of EDIT_MENU means you get events, can change the text, etc. Aside: Should you not have met them before, "NEW:=$" is just a nested declaration, and no different to using a preceding "enum NEW,...", less the tedious repetition and first opportunity for silly typos. You could of course also use [nested] "NEW:=17" style declarations instead if preferred, perhaps more effort to maintain but possibly sometimes a little easier to debug, since you can see straightaway what a 17 might refer to, without any counting. On that note, pGUI.e contains several uses of vslice() to help map between meaningless numbers and human readable strings. There is no need to keep ids in ascending order, nor any prohibition on using say [#]0301 as some app-specific meaning, though certainly under WinAPI you are limited to 16 bits, and it is probably wise to limit yourself to 1..#7FFF (=32767), and of course separate menus can use the same ids for completely different purposes, as long as they either have different HANDLER or it copes/uses the menu gdx parameter to differentiate any clashes. See below or the unabridged gMenu.exw for further details of popups, radios, checkboxes, and images [the cut and paste entries of the second image have been removed from the source code here for clarity, see that file].
Notes: | A menu can be the menubar of a dialog, as shown above, or a popup menu. For the former it must be the first element of a gDialog(gVbox({})) construct. For the latter it must not have a parent, ie be no part of any dialog hierarchy. gPopupMenu(gdx menu, integer x=XPG_MOUSEPOS, y=XPG_MOUSEPOS) menu: a result from gMenu() which has not already been used as a child of any other element. x,y: screen position, literal values or XPG_MOUSEPOS as per gShow, which is the only such flag supported here, and implicitly uses gGetGlobalIntInt("MOUSEPOS"). A popup menu is displayed for the user and disappears when an item is selected. Under GTK and JavaScript control returns immediately, however under WinAPI that does not happen until the menu has gone, though the main message loop remains active. Obviously, therefore, you need to deal with the menu selection immediately in the HANDLER routine; were you instead to save that and deal with it when gPopupMenu returns, that would only work properly under WinAPI. A menu item with a leading '?' generates a checked menu item (with the '?' removed). A submenu item with a leading '?' makes the first set of checked menu items on the submenu ("") into a radio set. In the case of a popup menu there is no (top-level) submenu item title for that, so the bRadio flag is used instead. That set is terminated by the next separator or submenu after one or more checked items have been seen. The command ids on those items (such as NEW/OPEN/CLOSE above) must form a contiguous integer range, that is for a radio group, though they need not be in order, with the lowest numerically being initially selected. Menu items can also show an icon before the text, how nicely that plays with checkboxes and radio buttons has not yet been tested. Under WinAPI such icons are not naturally transparent, so gImage_from_XPM lets you specify "MENU" as the usage, to replace any transparent pixels with the menu background colour.
Attributes: | All menu attributes are set via gMenuSetAttribute() rather than the usual gSetAttribute(), likewise Get. gMenuSetAttribute(gdx menu, integer id, string name, object v) object v = gMenuGetAttribute(gdx menu, integer id, string name) menu,id: both must always be provided, and id be non-zero. name,v: as detailed below
ACTIVE | Same as the standard attribute, except for needing both a gdx and (non-zero) command-id. The above example could perhaps contain gMenuSetAttribute(menu,CLOSE,"ACTIVE",bThereIsAnOpenFile)
CHECKED | Set or retrieve the state of a specified checked menu item. Unlike gRadioItem(), there is no "which one is selected?" query for a radio group on a menu, you’ll either have to remember what events your handler was sent, or iterate through all of the menu items until you find it. Also, attempting to unset any member of a radio group quietly does nothing, since the internal handling of clicking on an already set item is significantly simpler that way, plus you don’t want to risk it getting left in a nothing selected/invalid state anyway.
IMAGE | (replace only) Under GTK xpGUI initially creates a GtkImageMenuItem rather than GtkMenuItem when the menu is first created, hence you cannot set an image on a menu item which has not had one from the get-go, you can however replace it. (Rather than making it destroy/re-create controls, the programmer is expected to use/replace a blank/transparent image.) Under WinAPI xpGUI replaces [highlit] menu item icons with the appropriate background colour when needed, to mimic transparency. [In other words, all platforms honour (say) the transparent_colour specified on a gImage_from_XPM(), albeit with WinAPI faking that a bit. Most non-square icons deserve some transparent bits.] Note that having an image on a check/radio button w/should be considered poor design and rather mangles the menu layout (on WinAPI anyway).
TITLE | Use a '&' to indicate a mnemonic, and "&&" for a literal ampersand. UTF-8 should be fully supported. The separator characters '|', '-' and initial '?' for checkboxes and radio groups do not apply here, only as initially embedded inside the children structure, in other words they are strictly creation-only. Note that retrieved titles may have been preprocessed to GTK or other standards, ie/eg replacing '&' with '_' (etc). A fatal error occurs if an attempt is made to retrieve a menu title before the menu has been mapped - strings within the original nested children structure parameter of gMenu() would not have been processed before that point, and it is a fairly strong indicator of some sort of logic error to attempt such before the window/popup menu has even been displayed, anyway. At a push you could perhaps invoke gMap explicitly yourself, assuming you were really sure that’s alright.
Handlers: |
HANDLER | A function that handles all menu interactions. function handler(gdx menu, integer id [,bool bHighlight]) menu: identifier of the (toplevel) menu, as originally returned from gMenu(). id: identifier of the menu entry (must be unique within each menu). bHighlight: (optional) when true the event is just select/mouseover, false for an actual click or keyboard activation. If there is no bHighlight parameter, only click events are forwarded. Some applications may benefit from displaying a longer description of what a menu entry does in a status bar, say, as mouseover and similar events occur. As noted above, signals from sub-menus opening (there are no close signals) and highlighting events are only sent when an id is actually specified on the individual menu[item]s. There is no guarantee of cross-platform/backend consistency of event signals, for instance changing a radio menu entry may signal {old,new} or {new,old}, heck some backends might only send new, or even send them in command-id or menu order. Under WinAPI, you may also get mouseover events from disabled menu items and sub-menus, but not disabled MenuBar items, in contrast with none such at all for any and all disabled items under GTK. Returns of XPG_CLOSE are honoured for a menu attached to a dialog (closing it), but simply ignored from a popup menu handler.
Technicalia | On the desktop, a plain Alt opens the app’s menu, and causes any accelerator keys to be underlined, but in a browser that opens the browser’s menu, not the app-within-the-browser’s menu. In the browser, you can use Alt Gr or Ctrl+Alt, but they don’t work on the desktop. However, Shift+Alt works consistently in the browser and on the desktop, at least in my tests, so it might be sensible to get into the habit of using that. The "look and feel" under pwa/p2js is currently 100% windows-based. Should you want something more consistent with linux applications, try modifying pwa/js/poc/menus.css (and maybe .html/js): hopefully I might then be able to use that, along with (say) gSetGlobal("MENU_OS","LINUX") [which could be quietly ignored by desktop/Phix], and change all "menuitem" to say "wmenumiten" and "lmenuitem", etc. The standard practice of popping up left of the cursor occurs when using XPG_MOUSPOS and clicking too near the right hand edge of the screen (on all, GTK2 too), and similarly above/too near the bottom, except for GTK2 (which is a law unto itself) whereby menus first get scrollbars then become too small to be usable, sadly not much I can realistically do about that, even if I used a GtkMenuPositionFunc, there is a gboolean *push_in but not a gboolean *push_up, though at least it works fine on GTK3+, when they added gtk_menu_popup_at_pointer(), and other than for testing no-one should still be using GTK2 anyway.