Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions Chapters/CaseStudyOne/HowTo.pillar
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,7 @@ initializePresenters
!! Menu


contextMenu: [ ]
si dynamic


self newMenu
addItem
on group per default
aPresenter actions: anActionGroup


!! Style
Expand Down
29 changes: 15 additions & 14 deletions Chapters/CaseStudyTwo/CaseStudyTwo.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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

Expand Down
6 changes: 3 additions & 3 deletions Chapters/Commander2/Commander.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
```

Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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.
```
Expand Down
11 changes: 4 additions & 7 deletions Chapters/ListTreeTable/ListTreeTable.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `dynamicName:`, `actionEnabled:` and `actionVisible:`.

```
| tree |
Expand All @@ -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: [ :group | group
addActionWith: [ :action | action
dynamicName: [ tree selectedItem asString ] ] ];
open
```

Expand Down
68 changes: 39 additions & 29 deletions Chapters/Menus/Menus.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,19 @@ 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggestion is not correct. SpMenuPresenter>>#addKeybindingsTo: is written with a lowercase b, contrary to other method selectors with "KeyBinding" in them, like SpAbstractMorphicAdapter>>#addKeyBindingsTo:.


```
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
menuBar addKeyBindingsTo: aWindowPresenter
menuBar addKeybindingsTo: aWindowPresenter

```


Expand Down Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
menuBar addKeyBindingsTo: aWindowPresenter
menuBar addKeybindingsTo: aWindowPresenter

```

`toolbar` is an instance variable, so we have to elaborate the class definition:
Expand Down Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
menuBar addKeyBindingsTo: aWindowPresenter
menuBar addKeybindingsTo: aWindowPresenter

```

`statusBar` is a new instance variable, which we add to the class definition of the presenter.
Expand Down Expand Up @@ -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*.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Therefore `MailClientPresenter` defines the context menu for the subpresenter, creating an *action grouop*.
Therefore `MailClientPresenter` defines the context menu for the subpresenter, creating an *action group*.


```
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.
Expand Down
6 changes: 3 additions & 3 deletions Chapters/ToPlaceSomewhere.pillar
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down