From 68874c7c70811981e8d5dc894eb437347365f597 Mon Sep 17 00:00:00 2001 From: Esteban Lorenzano Date: Thu, 19 Sep 2024 11:27:07 +0200 Subject: [PATCH 1/2] adapted to new action system for context menus --- Chapters/CaseStudyOne/HowTo.pillar | 8 +-- Chapters/CaseStudyTwo/CaseStudyTwo.md | 29 ++++++----- Chapters/Commander2/Commander.md | 6 +-- Chapters/ListTreeTable/ListTreeTable.md | 11 ++-- Chapters/Menus/Menus.md | 68 ++++++++++++++----------- Chapters/ToPlaceSomewhere.pillar | 6 +-- 6 files changed, 65 insertions(+), 63 deletions(-) diff --git a/Chapters/CaseStudyOne/HowTo.pillar b/Chapters/CaseStudyOne/HowTo.pillar index f2c3b13..96b191e 100644 --- a/Chapters/CaseStudyOne/HowTo.pillar +++ b/Chapters/CaseStudyOne/HowTo.pillar @@ -67,13 +67,7 @@ initializePresenters !! Menu -contextMenu: [ ] - si dynamic - - -self newMenu - addItem -on group per default +aPresenter actions: anActionGroup !! Style diff --git a/Chapters/CaseStudyTwo/CaseStudyTwo.md b/Chapters/CaseStudyTwo/CaseStudyTwo.md index 8e3882b..a674463 100644 --- a/Chapters/CaseStudyTwo/CaseStudyTwo.md +++ b/Chapters/CaseStudyTwo/CaseStudyTwo.md @@ -439,7 +439,7 @@ TodoListPresenter >> initializePresenters title: 'Title' evaluated: [ :task | task title ]); yourself. - todoListPresenter contextMenu: self todoListContextMenu. + todoListPresenter actions: self todoListActions. addButton := self newButton label: 'Add task'; @@ -449,25 +449,26 @@ TodoListPresenter >> initializePresenters What is added now? -- `contextMenu: self todoListContextMenu` sets the context menu to what is defined in the method `todoListContextMenu`. Let us study right now. +- `actions: self todoListActions` sets the context menu to what is defined in the method `todoListActions`. Let us study right now. ```Smalltalk -TodoListPresenter >> todoListContextMenu - - ^ self newMenu - addItem: [ :item | item - name: 'Edit...'; - action: [ self editSelectedTask ] ]; - addItem: [ :item | item - name: 'delete'; - action: [ self deleteSelectedTask ] ] +TodoListPresenter >> todoListActions + + ^ SpGroupAction new + addItemWith: [ :item | item + name: 'Edit...'; + action: [ self editSelectedTask ] ]; + addItemWith: [ :item | item + name: 'delete'; + action: [ self deleteSelectedTask ] ]; + yourself ``` -This method creates a menu to be displayed when pressing right-click on the table. Let's see what it contains: +This method creates aa action group that will be used to the presenter as a context menu to be displayed when pressing right-click on the table. Let's see what it contains: -- `self newMenu` as all other _factory methods_, this creates a menu presenter to be attached to another presenter. -- `addItem: [ :item | ... ]` add an item, with a `name:` and an associated `action:` +- `SpGroupAction new` this creates an action group to contain the actions that will be attached to another presenter. +- `addItemWith: [ :item | ... ]` add an item, with a `name:` and an associated `action:` And now let's define the actions diff --git a/Chapters/Commander2/Commander.md b/Chapters/Commander2/Commander.md index 3e29a3d..aa89820 100644 --- a/Chapters/Commander2/Commander.md +++ b/Chapters/Commander2/Commander.md @@ -205,7 +205,7 @@ ContactBookPresenter >> initializePresenters table addColumn: (SpStringTableColumn title: 'Name' evaluated: #name); addColumn: (SpStringTableColumn title: 'Phone' evaluated: #phone). - table contextMenu: [ self rootCommandsGroup beRoot asMenuPresenter ]. + table actions: self rootCommandsGroup. table items: contactBook contacts. ``` @@ -281,7 +281,7 @@ ContactBookPresenter >> initializePresenters table addColumn: (SpStringTableColumn title: 'Name' evaluated: #name); addColumn: (SpStringTableColumn title: 'Phone' evaluated: #phone). - table contextMenu: [ (self rootCommandsGroup / 'Context Menu') beRoot asMenuPresenter ]. + table actions: self rootCommandsGroup / 'Context Menu'. table items: contactBook contacts ``` @@ -527,7 +527,7 @@ ContactBookPresenter >> initializePresenters table addColumn: (SpStringTableColumn title: 'Name' evaluated: #name); addColumn: (SpStringTableColumn title: 'Phone' evaluated: #phone). - table contextMenu: [ (self rootCommandsGroup / 'Context Menu') beRoot asMenuPresenter ]. + table actions: self rootCommandsGroup / 'Context Menu'. table items: contactBook contents. menuBar := (self rootCommandsGroup / 'MenuBar') asMenuBarPresenter. ``` diff --git a/Chapters/ListTreeTable/ListTreeTable.md b/Chapters/ListTreeTable/ListTreeTable.md index 250a1b5..001a292 100644 --- a/Chapters/ListTreeTable/ListTreeTable.md +++ b/Chapters/ListTreeTable/ListTreeTable.md @@ -239,9 +239,7 @@ The message `expandPath:` shows that we can expand a specific item by a path. ![A tree with a menu. %width=45&anchor=figTreemenu](figures/TreeWithMenu.png) -The following script shows how to use a dynamic context menu. This is a dynamic menu because its content is recalculated. -The dynamic aspect is expressed by a block. Figure *@figTreemenu@* shows the result. - +The following script shows how to use a context menu, you can add, remove and replace menus, but it's dynamic nature is also present in the api, by seinding blocks to messages like `name:`, `actionEnabled:` and `actionVisible:`. ``` | tree | @@ -250,10 +248,9 @@ tree roots: { Object }; children: [ :aClass | aClass subclasses ]; displayIcon: [ :aClass | self iconNamed: aClass systemIconName ]; display: [ :aClass | aClass name ]; - contextMenu: [ - SpMenuPresenter new - addGroup: [ :group | - group addItem: [ :item | item name: tree selectedItem asString ] ] ]; + actionsWith: [ :aGroup | + aGroup addActionWith: [ :action | + action name: [ tree selectedItem asString ] ] ]; open ``` diff --git a/Chapters/Menus/Menus.md b/Chapters/Menus/Menus.md index cc6f489..1619712 100644 --- a/Chapters/Menus/Menus.md +++ b/Chapters/Menus/Menus.md @@ -147,7 +147,7 @@ Look at the shortcuts in the `messageMenu` method. `$n meta` means that the char ### Installing shortcuts -Adding shortcuts to menu items does not automatically install them. Keyboard shortcuts have to be installed after the window has been opened. Therefore we have to adapt the `initializeWindow:` method with the `whenOpenedDo:` message, so that the keyboard shortcuts can be installed after opening the window. `SpMenuPresenter`, which is the superclass of `SpMenuBarPresenter`, implements the method `addKeybindingsTo:`, which comes in handy here. +Adding shortcuts to menu items does not automatically install them. Keyboard shortcuts have to be installed after the window has been opened. Therefore we have to adapt the `initializeWindow:` method with the `whenOpenedDo:` message, so that the keyboard shortcuts can be installed after opening the window. `SpMenuPresenter`, which is the superclass of `SpMenuBarPresenter`, implements the method `addKeyBindingsTo:`, which comes in handy here. ``` MailClientPresenter >> initializeWindow: aWindowPresenter @@ -155,8 +155,11 @@ MailClientPresenter >> initializeWindow: aWindowPresenter aWindowPresenter title: 'Mail'; initialExtent: 650@500; - menu: menuBar; - whenOpenedDo: [ menuBar addKeybindingsTo: aWindowPresenter ] + menu: menuBar. + + "this will copy the menubar shortcuts from menuBar to the window presenter, + to allow the window to answer to them" + menuBar addKeyBindingsTo: aWindowPresenter ``` @@ -227,8 +230,9 @@ MailClientPresenter >> initializeWindow: aWindowPresenter title: 'Mail'; initialExtent: 650@500; menu: menuBar; - whenOpenedDo: [ menuBar addKeybindingsTo: aWindowPresenter ]; - toolbar: toolBar + toolbar: toolBar. + + menuBar addKeyBindingsTo: aWindowPresenter ``` `toolbar` is an instance variable, so we have to elaborate the class definition: @@ -357,9 +361,10 @@ MailClientPresenter >> initializeWindow: aWindowPresenter title: 'Mail'; initialExtent: 650@500; menu: menuBar; - whenOpenedDo: [ menuBar addKeybindingsTo: aWindowPresenter ]; toolbar: toolBar; - statusBar: statusBar + statusBar: statusBar. + + menuBar addKeyBindingsTo: aWindowPresenter ``` `statusBar` is a new instance variable, which we add to the class definition of the presenter. @@ -458,55 +463,60 @@ All actions that change the status bar have been tested. ### Adding a context menu to a presenter -The final step to complete the mail client presenter is the addition of a context menu. We will add a context menu to the tree with the folders and emails. We will not add a big context menu. For demonstration purposes, we will restrict the menu to two menu items, one to delete an email and one to send an email. +The final step to complete the mail client presenter is the addition of a context menu. We will add a context menu to the tree with the folders and emails. We will not add a big context menu. For demonstration purposes, we will restrict the menu to two items, one to delete an email and one to send an email. The tree includes folders and emails, so the desired menu items should be disabled when a folder is selected. They should also be disabled when no selection has been made. On top of that condition, the send command can only be applied to emails that are in the "Draft" folder because received and sent mails cannot be sent. Typically, a presenter adds a context menu to a subpresenter. Given that the tree of folders and emails is a subpresenter of the `MailAccountPresenter`, we would expect the `MailAccountPresenter` to install a context menu on the tree presenter. However, the `MailAccountPresenter` cannot decide what needs to be done for deleting or sending an email. What needs to be done is the responsibility of the `MailClientPresenter`, which defines the methods `deleteMail` and `sendMail`. Both methods do what they have to do to perform the action, and then send the `modelChanged` message and update the status bar. -Therefore `MailClientPresenter` defines the menu. +Therefore `MailClientPresenter` defines the context menu for the subpresenter, creating an *action grouop*. ``` -MailClientPresenter >> accountMenu +MailClientPresenter >> mailAccountActions - ^ self newMenu - addItem: [ :item | - item - name: 'Delete'; - enabled: [ account hasSelectedEmail ]; - action: [ self deleteMail ] ]; - addItem: [ :item | - item - name: 'Send'; - enabled: [ account hasSelectedEmail - and: [ account selectedItem isDraft] ]; - action: [ self sendMail ] ]; - yourself + ^ SpActionGroup new + addActionWith: [ :item | + item + name: 'Delete'; + actionEnabled: [ account hasSelectedEmail ]; + action: [ self deleteMail ] ]; + addActionWith: [ :item | + item + name: 'Send'; + actionEnabled: [ + account hasSelectedEmail + and: [ account selectedItem isDraft] ]; + action: [ self sendMail ] ]; + yourself ``` +Action groups and Actions are used to add behavior to a subpresenter, behavior that will act as a context menu or a key binding, dependending on the definition. Here we defined two actions that will be exposed as a context menu. + +For each added action, you can see some messages sent to configure it. + The action blocks are simple, like the action blocks of the menu items in the menubar and the buttons in the toolbar. They send the action messages `deleteMail` and `sendMail` we have defined before. -More interestingly are the `enabled:` blocks, which define the enablement of the menu items. Deleting an email is possible only when an email is selected. That is expressed by the `enabled:` block of the "Delete" menu item. As described in the introduction of this section, sending an email is possible only if the selected email is a draft email. That is exactly what the `enabled:` block for the "Send" menu item expresses. +More interestingly are the `actionEnabled:` blocks, which define the enablement of the menu items. Deleting an email is possible only when an email is selected. That is expressed by the `actionEnabled:` block of the "Delete" menu item. As described in the introduction of this section, sending an email is possible only if the selected email is a draft email. That is exactly what the `actionEnabled:` block for the "Send" menu item expresses. -Note the name of the method. We use the name `accountMennu` because the context menu will be installed on the `MailAccountPresenter`. However, the context menu has to be installed on the tree presenter with the folders and the emails. Therefore `MailAccountPresenter` delegates to the tree presenter. Let's realise that in code. First, from within `initializePresenters` of `MailClientPresenter`, we send the `contextMenu:` message to install the context menu on the `MailAccountPresenter`. +Note the name of the method. We use the name `mailAccountActions` because the context menu will be installed on the `MailAccountPresenter`. However, the context menu has to be installed on the tree presenter with the folders and the emails. Therefore `MailAccountPresenter` delegates to the tree presenter. Let's realise that in code. First, from within `initializePresenters` of `MailClientPresenter`, we send the `actions:` message to install the context menu on the `MailAccountPresenter`. ``` MailClientPresenter >> initializePresenters account := MailAccountPresenter on: self model. - account contextMenu: [ self accountMenu ]. + account actions: self mailAccountActions. reader := MailReaderPresenter new. self initializeMenuBar. self initializeToolBar. statusBar := self newStatusBar ``` -Then we implement the `contextMenu:` on `MailAccountPresenter`. It delegates to the tree presenter, +Then we implement the `actions:` on `MailAccountPresenter`. It delegates to the tree presenter, ``` -MailAccountPresenter >> contextMenu: aBlock +MailAccountPresenter >> actions: aBlock - foldersAndEmails contextMenu: aBlock + foldersAndEmails actions: aBlock ``` That concludes the implementation. It is time to open the window again and try the new context menu. diff --git a/Chapters/ToPlaceSomewhere.pillar b/Chapters/ToPlaceSomewhere.pillar index ecf5d0d..1fc1b76 100644 --- a/Chapters/ToPlaceSomewhere.pillar +++ b/Chapters/ToPlaceSomewhere.pillar @@ -64,19 +64,19 @@ how do you have a CmUICommandGroup ? That's abstract... you should have a CmComm in anycase, if this is the first case, then you do: -myGroup asSpecGroup asMenuPresenter +myGroup asSpecGroup and if is the second (most likely) you just do -myGroup asMenuPresenter +myGroup Esteban Lorenzano — 12/06/2022 9:36 PM also, status of presenters are not updated all the time to get that, you need to pass the context menu as a block, not as a direct instance, e.g. : myPresenter - contextMenu; [ myGroup asMenuPresenter ]; + actions: myGroup; yourself Yes, I have a SpecCommandGroup but I just got lost in this class hierarchy From f74596316a3f08e9559f7b789cb578a6ef02e9ac Mon Sep 17 00:00:00 2001 From: Esteban Lorenzano Date: Thu, 19 Sep 2024 11:56:42 +0200 Subject: [PATCH 2/2] using right message for dynamic names --- Chapters/ListTreeTable/ListTreeTable.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Chapters/ListTreeTable/ListTreeTable.md b/Chapters/ListTreeTable/ListTreeTable.md index 001a292..4bf1b14 100644 --- a/Chapters/ListTreeTable/ListTreeTable.md +++ b/Chapters/ListTreeTable/ListTreeTable.md @@ -239,7 +239,7 @@ The message `expandPath:` shows that we can expand a specific item by a path. ![A tree with a menu. %width=45&anchor=figTreemenu](figures/TreeWithMenu.png) -The following script shows how to use a context menu, you can add, remove and replace menus, but it's dynamic nature is also present in the api, by seinding blocks to messages like `name:`, `actionEnabled:` and `actionVisible:`. +The following script shows how to use a context menu, you can add, remove and replace menus, but it's dynamic nature is also present in the api, by seinding blocks to messages like `dynamicName:`, `actionEnabled:` and `actionVisible:`. ``` | tree | @@ -248,9 +248,9 @@ tree roots: { Object }; children: [ :aClass | aClass subclasses ]; displayIcon: [ :aClass | self iconNamed: aClass systemIconName ]; display: [ :aClass | aClass name ]; - actionsWith: [ :aGroup | - aGroup addActionWith: [ :action | - action name: [ tree selectedItem asString ] ] ]; + actionsWith: [ :group | group + addActionWith: [ :action | action + dynamicName: [ tree selectedItem asString ] ] ]; open ```