Let's take another look at the example from the command documentation
For the paste buttons (first column), the command that was used to project all four buttons looks like this:
this.pasteActionCommand = Command.builder()
.setText(resourceBundle.getString("Paste.text"))
.setExtraText(resourceBundle.getString("Paste.textExtra"))
.setIconFactory(Edit_paste.factory())
.build();
The only difference is the presentation model associated with each one of the projection:
- In the first row (big state), the button is showing the text (that might go to two lines) and a big icon, stacked vertically.
- In the second row (tile state), the big icon is on the left, and the vertical stack on the right displays the text and the extra text.
- In the third row (medium state), the icon is smaller, and only text is showing.
- In the fourth row (small state), only the small icon is showing.
Here is how the first (big) button is created:
AbstractCommandButton bigButton =
this.pasteActionCommand.project(
CommandButtonPresentationModel.builder()
.setPresentationState(CommandButtonPresentationState.BIG)
.setFlat(false)
.build())
.buildComponent();
There are two important parts here - the presentation model and projecting the command onto the screen. Let's talk about these two parts.
In Flamingo's terminology, a command (represented by the Command
class and its builder pattern) is a content model. It describes the basic elements of a command (such as text and icon), how the user interacts with it, and what happens when that interaction happens.
The presentation model describes how to "convert" (or project) a content model into a Swing component that can be added to the application UI hierarchy to present the data backed by that content model and react to the user interaction.
In this particular case, we are projecting our command as a button - hence the CommandButtonPresentationModel
class name. With the same builder pattern, .builder()
creates a new builder instance, and then .build()
creates an immutable presentation model instance:
CommandButtonPresentationModel.builder()
.setPresentationState(CommandButtonPresentationState.BIG)
.setFlat(false)
.build())
Going back to our screenshot:
What is different between the four "Paste" buttons in the first column? The only thing is the presentation state passed to the .setPresentationState()
call on the presentation model builder. The rest is identical - the command itself, as well as the .setFlat(false)
to mark the projected button to always display background.
Now let's talk about the projection is.
Projection is the act of "combining" a content model and a presentation model and creating a Swing component. In our case, Command
is our content model and CommandButtonPresentationModel
is our presentation model.
The same command object can be projected multiple times on the screen - four in the case of our demo app. And the same presentation model object can be used to project multiple commands in case all of them use the same presentation "instructions".
Taking another look at the combined code:
this.pasteActionCommand = Command.builder()
.setText(resourceBundle.getString("Paste.text"))
.setExtraText(resourceBundle.getString("Paste.textExtra"))
.setIconFactory(Edit_paste.factory())
.build();
AbstractCommandButton bigButton = this.pasteActionCommand.project(
CommandButtonPresentationModel.builder()
.setPresentationState(CommandButtonPresentationState.BIG)
.setFlat(false)
.build())
.buildComponent();
In an earlier example we have four buttons to change content styling (bold, italic, underline and strikethrough) of JTextPane
content:
Let's take a look at how the "bold" styling is done. First, we create the command (which is the content model):
// Bold style command
Command commandBold = Command.builder()
.setIconFactory(Format_text_bold.factory())
.setAction((CommandActionEvent event) -> {
// toggle bold in current selection
toggleStyleInSelection(textPane, StyleConstants.CharacterConstants.Bold);
// and update command selection state based on the presence of bold
event.getCommand().setToggleSelected(
hasStyleInSelection(textPane,
StyleConstants.CharacterConstants.Bold));
})
.setToggle(true)
.build();
The important part here is what happens in the setAction
block. The first thing the command action does is to update the styling of the currently selected content in our text pane. And then, we update the toggleSelected
attribute of the command itself - not the Swing component that represents that command on the screen.
And then, we have the caret listener registered on the text pane to update the enabled state of our command:
textPane.addCaretListener((CaretEvent e) -> {
// Compute selection presence
boolean hasSelection =
(textPane.getSelectionEnd() - textPane.getSelectionStart()) > 0;
// Enable or disable the command based on that
commandBold.setActionEnabled(hasSelection);
// Determine whether the bold toggle selection is "on" based on
// the presence of the matching style in the text pane selection
commandBold.setToggleSelected(hasStyleInSelection(textPane,
StyleConstants.CharacterConstants.Bold));
});
Here, again, we update the actionEnabled
and toggleSelected
attributes of the command itself.
This is the whole purpose of existence for content model (command), presentation model (command button presentation model) and projection (command button projection).
Content model encapsulates the "business logic", if you will, of one piece of the application model realm. In our case, it is a piece of model realm that deals with applying bold styling on a selected text somewhere in the application UI. That piece of model realm is the one that should be tracking whether that bold styling is on or off (the toggleSelected
attribute). It is the one that also should be tracking whether it is active or not (the enabled
attribute).
Flamingo then takes care of updating all the projections based on the changes in the content model - be it a single projection of each styling command in our last example, or more than one projection of the same content model as can be seen in the ribbon.
It's worth noting that a particular command may not be projected in the current screen at all. In this case you would still want to continue updating the content model (which is that command) based on the specific application logic - as you would do with any other piece of your model realm that you keep in sync with the latest local or remote data changes.
Continue to the command button presentation models.