Today we get into how Menus are refactored, and their new use of Actions.
Menu Grouping
Menus now no longer use numbers to do grouping of menu items. Instead, Menus are composed. A Menu may contain any number of Menus and MenuItems, and may be nested to any arbitrary depth.
Grouping is determined by a Menu being in a Menu. It's probably easier for me to give an example. Let's take the standard Popup menu that is in our existing Wrapper Workspace. It has 7 groups:
With the new Menu structure, each of these can be a Menu. The popup menu itself can thus be just 7 Menus. Or, it could be the first 6 as menus, and then a single "Cancel" menu item. Or, it could be an Accept menu item, a Format menu item, then 2 - 6 as Menus, then a single "Cancel" menu item.
Or (and this is where nesting can come in), we could do it like this (detailed):
The point is, a group is defined as being started as a Menu, and ends with items in that menu. More importantly, it means you can reuse Menus. For instance the "It" Menu can be reused in other panes, as well as the "Find/Replace" and "Edit" menus.
The API
Of course, composing menus means that we need some APIs to help out. First, adding and removing menu items:
Before we go on, you might catch that the "addMenuItem:..." API has as a parameter "aMenuOrMenuItem." That is because the "addMenu:..." API is sugar. Adding a single Menu or MenuItem (what I call a "Menu Part") is identical under the hood.
Interestingly the old #menuItems and #groupSizes are both still there, and do what they used to do. #groupSizes does an inject recursive iteration and gives you a collection of integers, each representing how many items in each group. #menuItems similarly recurses and gives you a flattened collection of Menu Items.
Under the hood, a Menu has in effect renamed the "menuItems" iVar to "menuParts", and indeed, there is a new #menuParts method that gives you the raw value it holds. The #menuItems method returns what you used to get in Wrapper: A collection of the items, all flattened out without regard to the grouping.
Hot Keys -> Keystrokes
Next, instead of the old "Wrapper" way of defining "hotkeys" for menu items, we use the new Keystroke as described previously. Thus, the old Wrapper like shortcutKey: and shortcutModifiers: API is gone, and replaced with:
And it will Display and Act appropriate to the platform!
Actions
To complete the move to Action/Keymap, instead of #action which were either symbols or blocks or message sends, the MenuItem has changed to "Actions" and instead uses #actionID(:). It will then use the action id to look in the pane, or user interface etc., to get the Action object, and execute it. All APIs have changed from action(:) to actionID(:).
Surprise
While putting this all together, I was surprised at something. Lets say we had a window with two InputFields. If we clicked on the top one, and went to a main menu's Paste menu item (for instance) that menu item had no idea where it was supposed to paste to!
Honest!
This is in Wrapper too!!!
So, I added a twist to Pollock's MenuItem's use of Actions. If you define an action like this:
(Note the explicit symbol for the final argument is #lastActivePane) Then, at execute time, the pane that had focus just before the menu was started will be dynamically replaced by the symbol #lastActivePane as the argument.
I created tests for this, which you can see in the "tests - Actions" of the MenuBarTest class in PollockTesting.
Under the hood, the Pollock EventDispatcher captures the initial and last non-menu focus pane, and there is an API on AbstractPane and Windows #lastActivePane, which allows you to access this if you want.
A Detail
While rooting around, I found a small bug in AbstractPane displayOn: where it wasn't obeying the rules of visibility or clipping. This is now fixed.
More To Come!
That concludes the description of the changes from the 7.3.1 release of Pollock to the public release of 6.13.0
But...
About a week or so ago, I was doing my regular updating of the internal work, but our internal repository was down. Being all ADD and all, I couldn't wait for the repository to come back up, so I instead published the newest version on the public repository!
Version 6.16.0
Since it's there, you can indeed use it. Therefore, next time I'll document the further changes between what was in 6.13.0 and what is now the newest publicly available version.