Inconsistent behaviour of popup menus with submenus attached

I’ve been experimenting with menu structures (i.e. a tree of menus and submenus) installed either on the menu bar or as context menus, but I’ve been finding strange behaviour using the same structures as popup menus attached to form buttons.

popup, popupclick, popupbutton, etc., all seem to do much the same thing, just placing the menu in a different position. The first parameter is the menu definition. The help for popup states, ‘If you want to create a more complex menu (colors, styles, fonts, submenus, etc.) you can create the menu with the menu( and menuitem( functions (see Custom Menus).’ A post by Jim 3½ years ago states that ‘Using submenus with popupclick is not documented to work’ but the text above would seem to supersede that.

The example given in the help text for popupclick:

local colorChoice
popupclick "Red"+cr()+"Green"+cr()+"Blue","Red",colorChoice
// colorChoice now contains the color name

may be extended to use LMSL with menu identifiers in the suffix of each item, e.g.

local colorChoice,MenuText
MenuText="(Colours)"+cr()+
    tab()+"Red"+tab()+"$1ɜ"+cr()+
    tab()+"Green"+tab()+"$2ɜ"+cr()+
    tab()+"Blue"+tab()+"$3ɜ"
popupbutton MenuText,"",colorChoice
message colorChoice+cr()+info("menuidentifier")

By selecting ‘Green’, this displays the expected menu text (‘Green’) and identifier (‘2’).

However, if I extend that structure to include two levels of submenus . . .

local colorChoice,MenuText
MenuText="(YellowSubmenu)"+tab()+"*S"+cr()+
        tab()+"White"+tab()+"$1.1.1ɜ"+cr()+
        tab()+"Magenta"+tab()+"$1.1.2ɜ"+cr()+
    "(RedSubmenu)"+tab()+"*S"+cr()+
        tab()+"Yellow"+tab()+"(YellowSubmenu)$1.1ɜ"+cr()+
        tab()+"Cyan"+tab()+"$1.2ɜ"+cr()+
    "(Colours)"+cr()+
        tab()+"Red"+tab()+"(RedSubmenu)$1ɜ"+cr()+
        tab()+"Green"+tab()+"$2ɜ"+cr()+
        tab()+"Blue"+tab()+"$3ɜ"
popupbutton MenuText,"",colorChoice
message colorChoice+cr()+info("menuidentifier")

. . . the leaves of the first menu level (Green and Blue), and the first item in the next level (Yellow, which itself has a submenu) display correctly and report the appropriate menu identifier. Those in the first level (Red, Green and Blue) also report the menu text. The remaining leaves (Cyan in the second level, and White and Magenta in the third) are greyed out, therefore can’t be selected and return neither menu text nor identifier.

(I only realised that they were greyed-out at a late stage of investigating this, because my original menus all contained @#RichText; in the header to enable RTML, which — as previously discussed — has the side-effect of making the foreground colour black unless explcitly changed using the <color> tag.)

So I wondered what might force them to be considered valid menu items, and tried adding a procedure name, {Test}, to those three leaf items beyond the first level:

local colorChoice,MenuText
MenuText="(YellowSubmenu)"+tab()+"*S"+cr()+
        tab()+"White"+tab()+"$1.1.1ɜ{Test}"+cr()+
        tab()+"Magenta"+tab()+"$1.1.2ɜ{Test}"+cr()+
    "(RedSubmenu)"+tab()+"*S"+cr()+
        tab()+"Yellow"+tab()+"(YellowSubmenu)$1.1ɜ"+cr()+
        tab()+"Cyan"+tab()+"$1.2ɜ{Test}"+cr()+
    "(Colours)"+cr()+
        tab()+"Red"+tab()+"(RedSubmenu)$1ɜ"+cr()+
        tab()+"Green"+tab()+"$2ɜ"+cr()+
        tab()+"Blue"+tab()+"$3ɜ"
popupbutton MenuText,"",colorChoice
message colorChoice+cr()+info("menuidentifier")

That did the trick. The procedure may be empty or even non-existent: it doesn’t need to do anything and, as far as I can tell, is never executed. There is no need to add the procedure name to items in the first level nor to any item with a submenu. By this means a click on any item in the menu tree will report its correct identifier to the procedure associated with the button, which is what I needed it to do. Note that it still doesn’t report the menu text for items beyond the first level, but that’s not a problem for me.

It seems odd, however, that items in the first level are treated differently from those further up the tree, and that the kludge of using a dummy procedure name seems to be the solution to my problem. Is this a feature or a bug?

I think that post 3½ years ago must have been connected with this bug report:

This bug report also indicates that adding code to a menuitem( will make it enable (no reason to use a dummy procedure name, just empty text will do). However, I just tried this again, and though adding code enables menu item, it still doesn’t work. When I select one of these items, the menu pops up again over and over. I can never get it to report that one of these submenu items is selected.

Ok, experimenting with the code you submitted gave me a clue. When a submenu item is selected, it attempts to run the code embedded in the menu item. After it does that, it pops up the menu again, the problem I saw. But if the embedded code contains an error, it falls through and doesn’t pop up the menu again. That’s how you got it to work, your dummy procedure name is not valid code. Any invalid code would work, just the letter “a” is enough.

So, this is still a bug. For now, the documentation is wrong, submenus cannot be used with a popup menu. Well, sort of, with the hack you’ve discovered, but that is not by design and not supported. It’s possible (perhaps probable) that if/when this bug is fixed, your hack may stop working. Sorry.

I’ve updated the bug report with this new information, which will probably be helpful in properly diagnosing this.

Sorry, I had found that post but somehow overlooked the subsequent bug report.

Thank you. Nevertheless, I’ll continue using it for the moment because it’s extremely useful. If it should stop working I’ll have to think again — perhaps use the routine that currently constructs the menus to make a file structure instead, for instance.

Menu bar and context menus have to be defined (I think) by a single function which recalculates all menus from scratch every time Panorama becomes the active application, (almost) every time one of them is clicked on then again (I think) after code triggered by a menu selection has been run. With multiple large menus that’s a big time overhead.A popup menu, it turns out, has the considerable advantage that it seems to be calculated only when clicked on, and each popup uses a separate function which is potentially much quicker to execute than one encompassing multiple root menus. In that respect they are more suitable for launching complex menu structures.

A popup menu is specified as an LMSL structure just like a menu-bar menu or a context menu, whether in the form of a simple list, or one explicitly marked-up in LMSL format, or LMSL constructed secretly using menu(, menuitem(, etc. For the user, the only difference is that a popup menu has a single root-level menu whereas a menu-bar function can include multiple root menus to appear in a row along the menu bar. In that case, it seems a pity deliberately to restrict popup menus to a subset of the functionality available to other menus.

There was nothing deliberate about this. Ultimately, all this relies on how Apple handles NSMenu and NSMenuItem classes, Panorama has to set it up all “just so” so that Apple’s code knows what to do, and Apple’s code handles the popup situation slightly differently than the main menu bar. Unfortunately Apple’s code is all very opaque and poorly documented, so often requires extensive reverse engineering that is very time consuming. There are many time consuming projects in the queue, so each has to wait its turn.

I’m not discouraging that, just wanted to avoid a possible unpleasant surprise later. It would still be unpleasant, just not a surprise :grinning:

The formula is not recalculated after code triggered by a menu selection has been run. Other than that, you are correct. In my opinion, this is a feature, it allows fully dynamic menus that respond to varying conditions in your application.

If performance is an issue, there are often possibilities to speed things up. If you find yourself needing to use the same time consuming expression in multiple locations in your formula, you can use the cache( function to calculate the value once and then use it in multiple spots.

Another option is to precreate portions of the menus in advance and store them in variables, so that they don’t have to be recalculated every time the menu is clicked. This can be more work because you have to make sure the variable value is always available and up-to-date, but performance enhancements usually have a cost and in this case the cost is additional programming work.

I agree that from the user’s point of view there should be no difference, that’s why this is a bug. Unfortunately not a simple bug to fix.

Internally Panorama converts LMSL into a structure of NSMenu and NSMenuItem objects. Unfortunately these objects apparently have to be generated differently to make a popup menu with submenus work correctly. Reverse engineering of Apple’s code is going to be required to figure out exactly what “differently” means.