This post originated from an RSS feed registered with Agile Buzz
by James Robertson.
Original Post: How To Build A GUI With Pollock - Popup Menus
Feed Title: Pollock
Feed URL: http://www.cincomsmalltalk.com/rssBlog/pollock-rss.xml
Feed Description: Pollock - the next VW GUI
Pollock.AbstractPane>>parentPane
^self topPane ifNotNil: [:value | value parentPane]
-+-+-+-
Pollock.AbstractPane>>userInterface
^self parentPane ifNotNil: [:value | value userInterface]
-+-+-+-
Pollock.ScheduledWindow>>parentPane
^self
Now, back to today's task. We want to do two tasks today. First, add to the Class Tree's popup menu, adding our "Open On Class" menu option, and then add a simple menu to the Method Lists.
In our hookupInterface method, we'll add a bit of code at the end for the Tree's menu
hookupInterface
...
when: #pageChangedTo: send: #displayMethodSource to: self.
self classTree menu addMenuItemGroup: (Array with:
((MenuItem label: '&Open On Class')
nameKey: #openOnClass;
image: (ResourceRetriever new className: #{ToolbarIconLibrary}; selector: #fileOpen);
shortcutKey: #F5;
shortcutModifiers: InputState ctrlMask + InputState altMask;
action: #openOnClass)).
If the menu item itself seems familiar, well, it is... We're copying the code and just changing the label. Other ways to do this, of course, with refactoring and so on, but, I'm not here to give you a course on that. The TreeView pane already has an existing menu for expanding and collapsing items, so here we use the #addMenuItemGroup: method. This not only adds our menu items(s) to the bottom of the existing item, it also puts a separator between the existing menu items and our new group of items.
Now, if you go ahead and open our ClassHierarchyBrowser, the menu item will work... IF you added the fixes at the top of this posting. Otherwise, nothing happens, which is why... you need... to add... the three changes! Other than the bug fixes, you don't have to add any other code... it just works.
The bug that the code fixes is related to this "Just Make It Work" feature. As we saw last time, adding code to the UserInterface for the action selector was all we needed to get the MenuBar menus to work. But there are a whole lot more options than that. When a menu item is chosen, here are the rules it goes through to find and/or execute the behavior associated with it:
If my action is a symbol and the actionPerformer is not nil, use the action as the argument of a perform: with the actionPerformer as the receiver.
If my action is a symbol and the actionPerformer IS nil, AND the object is a menuBar, then attempt to send it to the UserInterface of the main window, if there is one.
If my action is a symbol and the actionPerformer IS nil, AND the object is a menuPane, then attempt to send it to the launching pane's agent, and if it does not do that, try the top pane's user interface.
If the action is a BlockClosure send value: and pass self (the MenuItem) and etc depending on how many arguments it has
If the action responds otherwise to value: (probably an ObservedValue of some kind) then just send it value: with myself (the MenuItem)
Here, the "launching pane" is the pane which launched the popup menu. Many panes have callable actions. For instance, the InputField and TextEdit panes have agents that perform #cut, #copy, #paste and #pasteFromList... and such things as #selectAll. This makes it possible for you to just write code that calls behavior that already exists on the agents.
The "action performer" is an object you can give your menu item so that it targets some other object than the default performers: UserInterface and/or a pane's agent. In rules 4 & 5 we see that you can set the action to be a Block or anything that responds to #value:, and away you go.
Next we'll move on to our Method List menu. Here, we'll "do the right thing" and write a method that answers a Menu, and then call that method in our #protocolAndMethodListForm method, since we want the same menu on both the class and instance version of the method lists. We'll start with our menu:
Let's discuss the #methodListMenu method. Here, for the sake of showing more ways of creating menus, I'm using one of Menu's special creation methods... This one is #labels:lines:actions:. As you see, each parameter is just an array of things. The first for #labels: is an array of strings that will be used for the menu item labels itself. The second, for #lines: is another array that says "Which Items Should Have A Separator After It". In this case, we're saying we want to have one after the second and third items. The last, for #actions: is an array of symbols. We could have built up blocks or other things, but this is the E-Z way.
If we add this and open our ClassHierarchyBrowser, we'll see that the menu shows just fine. If we select a class and a protocol, we'll see that the Select all menu item works. This is because all List, Grid and TreeView agents respond to the #selectAllKeyboardEvent: message. Currently they don't do #selectAll, but in the next published version of Pollock, they will.
What is not said in the rules above, if the action is a keyword selector, such as this one is, the MenuItem that is selected will be sent as the parameter. In this case, when we look at #selectAllKeyboardEvent:, it expects an Event. But since that method doesn't use that parameter, we can safely call it!
Also what is not said explicitly in the rules above, if the action is a symbol, the symbol is performed "safely." That is, the target objects are first asked if they can execute the requested method, and if not, the action is not asked to perform. This is why the #doNothing and #inspectMethod actions don't blow up.
Since we haven't got a Pollock "Methods Browser" (and aren't doing one at this time) we'll leave those #doNothings alone. But, before we go, we'll create an inspectMethod method: