A lot of tools, especially the Browser, rely on context menus (aka yellow button menus). Dynamically extending these is possible but requires a lot of care. The Pluggable Menus allow to add menu entries easily via extension methods. The Services package serves as example here.
-
As tool writer, you want a menu somewhere in your tool
-
Create an instance of
MenuMorph
or similar. -
Create a method that should build the menu entries:
someMenu: aMenu <myMenu> ^ aMenu addList: #( ('use' use) ('remove' remove) ('copy' copyName)); yourself
-
Send
self menu: aMorph for: #myMenu
-
-
As a tool extender, you want to extend an existing menu
-
Find the menu you want to extend, for example the Browser's message list menu. This is defined via
ToolBuilder
to be constructed inBrowser>>#messageListMenu:shifted:
asmessageListMenu: aMenu shifted: shifted "Answer the message-list menu" ^ self menu: aMenu for: #(messageListMenu messageListMenuShifted:) shifted: shifted
So the menu identifier is
#messageListMenu
-
Create an extension method in
Browser
and categorize it as, for example,*MyPackage-message list
.myMenu: aMenu <myMenu> ^ aMenu addList: #( ('use' use) ('remove' remove) ('copy' copyName)); yourself
-
The entries should show up now the next time you open the context menu on the message list in a Browser.
-
The Services-Base
package now uses this approach to add services to
several menus.
For example, to add the class service entries to the class list menu
in the browser, it adds this extension method in the category
*services-base
in the Browser
class:
classListMenuServices: aMenu
<classListMenu>
<menuPriority: 150>
ServiceGui browser: self classMenu: aMenu.
^ Preferences useOnlyServicesInMenu ifTrue: [nil] ifFalse: [aMenu]
This invokes the ServiceGui
functionality to create the menu and
- ensures the entries show up in the class list context menu (
<classListMenu>
), - make sure they appear near the top (
<menuPriority: 150>
), - (actually build the menu (
ServiceGui browser: self classMenu: aMenu.
)), - and indicate whether or not the system should continue with the menue building,
depending on a preference (
^ Preferences useOnlyServicesInMenu ifTrue: [nil] ifFalse: [aMenu]
).
-
Menu creation
-
StringHolder >> menu: aMenu for: aMenuSymbolOrCollection shifted: aBoolean
Collect all menu-building methods as defined by
aMenuSymbolOrCollection
, possibly reacting to the shiftedaBoolean
modifier.The
aMenuSymbolOrCollection
specification denotes the method annotation(s) (or Pragma(s)) to search the current class and all superclasses for.Then, all collected method selectors are performed on
self
. -
StringHolder >> menu: aMenu for: aMenuSymbolOrCollection
Delegates to
#menu:for:shifted:
with shifted beingfalse
.
-
-
Menu identifiers and annotations/pragmas
All senders of
#menu:for:shifted:
are free to choose what to use for the menu identifier(s) which can then be used as annotation/pragma in menu-building methods.The methods with these annotations/pragmas get called with the menu to build and (optionally) with the shift state boolean.
However, indentifiers that can take an argument in a method annotation/pragma are expected to have this argument being either
true
orfalse
. They are only included for menu building if the shifted state corresponds to the argument.Examples
The message list context menu for Browsers is build usingself menu: aMenu for: #(messageListMenu messageListMenuShifted:) shifted: shifted
with the two identifiers
#messageListMenu
and#messageListMenuShifted:
.-
The following method would be called when the shifted argument is
false
only:myMessageListMenuAdditiontTo: aMenu <messageListMenuShifted: false> "..."
-
The following method would be called when the shifted argument is
true
only:myShiftedMessageListMenuAdditiontTo: aMenu <messageListMenuShifted: true> "..."
-
The following method would be called regardless of the shift state:
myGeneralMessageListMenuAdditiontTo: aMenu <messageListMenu> "..."
-
The following method would be called regardless of the shift state, but with the shift state as additional argument:
myGeneralMessageListMenuAdditiontTo: aMenu shifted: aBoolean <messageListMenu> aBoolean ifTrue: ["..."]. "..."
-
-
Menu priorities (optional)
Menu-building methods are typically sorted by selector name first and inheritance order second (for example,
StringHolder
beforeBrowser
, butBrowser>>#aMenu:
beforeStringHolder>>#bMenu:
).It is optionally possible to influence the menu ordering by including an annotation/pragma
<menuPriority: ...>
defining a numeric priority that takes precedence over the default ordering.
The higher the number, the later it comes.The following menu-building method would hence be sorted rather late in the menu.
myMessageListMenuAdditiontTo: aMenu <messageListMenuShifted: false> <menuPriority: 700> "..."
If no priority is given, 500 is assumed.
Note: For evey menu identifier in this list, a second exists with 'Shifted:' appended to make it easy for tool exteders.
-
StringHolder
- code pane menu (codePaneMenu)
-
CodeHolder
-
Browser
- class list menu (classListMenu)
- message category menu (messageCategoryMenu)
- message list menu (messageListMenu)
- class/system category menu (systemCategoryMenu)
-
PackagePaneBrowser
- package list menu (packageListMenu)
-
FileContentsBrowser
- class list menu (fileClassListMenu)
- message category menu (fileMessageCategoryMenu)
- message list menu (fileMessageListMenu)
- package list menu (filePackageListMenu)
-
ChangeList
- change list menu (changeListMenu)
- VersionsBrowser
- versions menu (versionsMenu)
-
ChangeSorter
- change set menu (changeSetMenu)
- class list menu (classListMenu)
- message list menu (messageListMenu)
-
Debugger
- stack menu (contextStackMenu)
-
MessageSet
- message list menu (messageListMenu)
-
MessageNames
- selectors menu (selectorListMenu)
- Lexicon
- category menu (categoryListMenu)
-
-
FileList
- contents pane menu (fileContentsMenu)
- files menu (fileListMenu)
- directory menu (volumeMenu)
-
Inspector
- fields menu (fieldListMenu)
-
SelectorBrowser
- selector list menu (selectorListMenu)