Skip to content

Instantly share code, notes, and snippets.

@krono
Last active August 29, 2015 14:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krono/5f7f5197df5f1d1da052 to your computer and use it in GitHub Desktop.
Save krono/5f7f5197df5f1d1da052 to your computer and use it in GitHub Desktop.
Pluggable Menus for many Squeak tools

Pluggable Menus for many tools

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.

Simple Usage

  • As tool writer, you want a menu somewhere in your tool

    1. Create an instance of MenuMorph or similar.

    2. Create a method that should build the menu entries:

        someMenu: aMenu
            <myMenu>
            ^ aMenu addList: #(
                    ('use'      use)
                    ('remove'   remove)
                    ('copy'     copyName));
                yourself
      
    3. Send self menu: aMorph for: #myMenu

  • As a tool extender, you want to extend an existing menu

    1. Find the menu you want to extend, for example the Browser's message list menu. This is defined via ToolBuilder to be constructed in Browser>>#messageListMenu:shifted: as

        messageListMenu: aMenu shifted: shifted
            "Answer the message-list menu"
            ^ self menu: aMenu for: #(messageListMenu messageListMenuShifted:) shifted: shifted
      

    So the menu identifier is #messageListMenu

    1. 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
      
    2. The entries should show up now the next time you open the context menu on the message list in a Browser.

Examples

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]).

Reference

  • Menu creation

    • StringHolder >> menu: aMenu for: aMenuSymbolOrCollection shifted: aBoolean

      Collect all menu-building methods as defined by aMenuSymbolOrCollection, possibly reacting to the shifted aBoolean 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 being false.

  • 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 or false. 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 using

    self 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 before Browser, but Browser>>#aMenu: before StringHolder>>#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.

Appendix: Affected Menus

Note: For evey menu identifier in this list, a second exists with 'Shifted:' appended to make it easy for tool exteders.

  • StringHolder

    1. code pane menu (codePaneMenu)
    • CodeHolder

      • Browser

        1. class list menu (classListMenu)
        2. message category menu (messageCategoryMenu)
        3. message list menu (messageListMenu)
        4. class/system category menu (systemCategoryMenu)
        • PackagePaneBrowser

          1. package list menu (packageListMenu)
        • FileContentsBrowser

          1. class list menu (fileClassListMenu)
          2. message category menu (fileMessageCategoryMenu)
          3. message list menu (fileMessageListMenu)
          4. package list menu (filePackageListMenu)
      • ChangeList

        1. change list menu (changeListMenu)
        • VersionsBrowser
          1. versions menu (versionsMenu)
      • ChangeSorter

        1. change set menu (changeSetMenu)
        2. class list menu (classListMenu)
        3. message list menu (messageListMenu)
      • Debugger

        1. stack menu (contextStackMenu)
      • MessageSet

        1. message list menu (messageListMenu)
        • MessageNames

          1. selectors menu (selectorListMenu)
          • Lexicon
            1. category menu (categoryListMenu)
    • FileList

      1. contents pane menu (fileContentsMenu)
      2. files menu (fileListMenu)
      3. directory menu (volumeMenu)
    • Inspector

      1. fields menu (fieldListMenu)
    • SelectorBrowser

      1. selector list menu (selectorListMenu)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment