View Javadoc
1   /*
2    * *************************************************************************************************************************************************************
3    *
4    * SteelBlue: DCI User Interfaces
5    * http://tidalwave.it/projects/steelblue
6    *
7    * Copyright (C) 2015 - 2024 by Tidalwave s.a.s. (http://tidalwave.it)
8    *
9    * *************************************************************************************************************************************************************
10   *
11   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12   * You may obtain a copy of the License at
13   *
14   *     http://www.apache.org/licenses/LICENSE-2.0
15   *
16   * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
17   * CONDITIONS OF ANY KIND, either express or implied.  See the License for the specific language governing permissions and limitations under the License.
18   *
19   * *************************************************************************************************************************************************************
20   *
21   * git clone https://bitbucket.org/tidalwave/steelblue-src
22   * git clone https://github.com/tidalwave-it/steelblue-src
23   *
24   * *************************************************************************************************************************************************************
25   */
26  package it.tidalwave.role.ui.example.presentation.impl;
27  
28  import javax.annotation.Nonnull;
29  import javax.annotation.PostConstruct;
30  import java.nio.file.Path;
31  import java.nio.file.Paths;
32  import org.springframework.stereotype.Component;
33  import it.tidalwave.util.annotation.VisibleForTesting;
34  import it.tidalwave.role.Aggregate;
35  import it.tidalwave.role.ui.BoundProperty;
36  import it.tidalwave.role.ui.Displayable;
37  import it.tidalwave.role.ui.PresentationModel;
38  import it.tidalwave.role.ui.PresentationModelAggregate;
39  import it.tidalwave.role.ui.Selectable;
40  import it.tidalwave.role.ui.UserAction;
41  import it.tidalwave.role.ui.UserActionProvider;
42  import it.tidalwave.role.ui.Visible;
43  import it.tidalwave.role.ui.example.model.Dao;
44  import it.tidalwave.role.ui.example.model.SimpleDciEntity;
45  import it.tidalwave.role.ui.example.model.SimpleEntity;
46  import it.tidalwave.role.ui.example.presentation.MainPanelPresentation;
47  import it.tidalwave.role.ui.example.presentation.MainPanelPresentation.Bindings;
48  import it.tidalwave.role.ui.example.presentation.MainPanelPresentationControl;
49  import lombok.Getter;
50  import lombok.RequiredArgsConstructor;
51  import static it.tidalwave.util.Parameters.r;
52  import static it.tidalwave.util.ui.UserNotificationWithFeedback.*;
53  import static it.tidalwave.role.ui.Presentable._Presentable_;
54  import static it.tidalwave.role.ui.spi.PresentationModelCollectors.toCompositePresentationModel;
55  
56  /***************************************************************************************************************************************************************
57   *
58   * @stereotype  Control
59   *
60   * @author  Fabrizio Giudici
61   *
62   **************************************************************************************************************************************************************/
63  @Component @RequiredArgsConstructor
64  public class DefaultMainPanelPresentationControl implements MainPanelPresentationControl
65    {
66      private static final Path USER_HOME = Paths.get(System.getProperty("user.home"));
67  
68      // START SNIPPET: injections
69      @Nonnull
70      private final Dao dao;
71  
72      @Nonnull
73      private final MainPanelPresentation presentation;
74      // END SNIPPET: injections
75  
76      // For each button on the presentation that can do something, a UserAction is provided.
77      // START SNIPPET: userActions
78      @Getter
79      private final UserAction actionButton = UserAction.of(this::onButtonPressed,
80                                                            Displayable.of("Press me"));
81  
82      @Getter
83      private final UserAction actionDialogOk = UserAction.of(this::onButtonDialogOkPressed,
84                                                              Displayable.of("Dialog with ok"));
85  
86      @Getter
87      private final UserAction actionDialogCancelOk = UserAction.of(this::onButtonDialogOkCancelPressed,
88                                                                    Displayable.of("Dialog with ok/cancel"));
89  
90      @Getter
91      private final UserAction actionPickFile = UserAction.of(this::onButtonPickFilePressed,
92                                                              Displayable.of("Pick file"));
93  
94      @Getter
95      private final UserAction actionPickDirectory = UserAction.of(this::onButtonPickDirectoryPressed,
96                                                                   Displayable.of("Pick directory"));
97      // END SNIPPET: userActions
98      // START SNIPPET: bindings
99      private final Bindings bindings = Bindings.builder()
100                                               .actionButton(actionButton)
101                                               .actionDialogOk(actionDialogOk)
102                                               .actionDialogCancelOk(actionDialogCancelOk)
103                                               .actionPickFile(actionPickFile)
104                                               .actionPickDirectory(actionPickDirectory)
105                                               .build();
106     // END SNIPPET: bindings
107 
108     // Then there can be a set of variables that represent the internal state of the control.
109     @VisibleForTesting int status = 1;
110 
111     /***********************************************************************************************************************************************************
112      * At {@link PostConstruct} time the control just performs the binding to the presentation.
113      **********************************************************************************************************************************************************/
114     // START SNIPPET: initialization
115     @PostConstruct
116     @VisibleForTesting void initialize()
117       {
118         presentation.bind(bindings);
119       }
120     // END SNIPPET: initialization
121 
122     /***********************************************************************************************************************************************************
123      * {@inheritDoc}
124      *
125      * This method demonstrates the typical idiom for populating model:
126      *
127      * 1. A dao is called to provide raw model - let's say in form of collections;
128      * 2. Objects in the collection are transformed into PresentationModels.
129      * 3. The PresentationModels are then passed to the presentation.
130      **********************************************************************************************************************************************************/
131     // START SNIPPET: populate
132     @Override
133     public void populate ()
134       {
135         final var entities1 = dao.getSimpleEntities();
136         final var entities2 = dao.getDciEntities();
137         final var files = dao.getFiles();
138         final var pm1 = entities1.stream().map(this::pmFor).collect(toCompositePresentationModel());
139         final var pm2 = entities2.stream().map(this::pmFor).collect(toCompositePresentationModel());
140         final var pm3 = files.stream()
141                              .map(item -> item.as(_Presentable_).createPresentationModel())
142                              .collect(toCompositePresentationModel(r(Visible.INVISIBLE)));
143         presentation.populate(pm1, pm2, pm3);
144       }
145     // END SNIPPET: populate
146 
147     /***********************************************************************************************************************************************************
148      * Factory method for the PresentationModel of SimpleEntity instances.
149      *
150      * It aggregates a few extra roles into the PresentationModel that are used by the control, such as callbacks
151      * for action associated to the context menu. Also a Displayable role is usually injected to control the rendering
152      * of entities.
153      **********************************************************************************************************************************************************/
154     // START SNIPPET: pmSimpleEntity
155     @Nonnull
156     private PresentationModel pmFor (@Nonnull final SimpleEntity entity)
157       {
158         final Selectable selectable = () -> onSelected(entity);
159         final var action1 = UserAction.of(() -> action1(entity), Displayable.of("Action 1"));
160         final var action2 = UserAction.of(() -> action2(entity), Displayable.of("Action 2"));
161         final var action3 = UserAction.of(() -> action3(entity), Displayable.of("Action 3"));
162         return PresentationModel.of(entity, r(Displayable.of("Item #" + entity.getName()),
163                                               selectable,
164                                               UserActionProvider.of(action1, action2, action3)));
165       }
166     // END SNIPPET: pmSimpleEntity
167 
168     /***********************************************************************************************************************************************************
169      * Factory method for the PresentationModel of SimpleDciEntity instances.
170      **********************************************************************************************************************************************************/
171     // START SNIPPET: pmSimpleDciEntity
172     @Nonnull
173     private PresentationModel pmFor (@Nonnull final SimpleDciEntity entity)
174       {
175         // FIXME: column names
176         final Aggregate<PresentationModel> aggregate = PresentationModelAggregate.newInstance()
177              .withPmOf("C1", r(Displayable.of(entity.getName())))
178              .withPmOf("C2", r(Displayable.of("" + entity.getAttribute1())))
179              .withPmOf("C3", r(Displayable.of("" + entity.getAttribute2())));
180         final Selectable selectable = () -> onSelected(entity);
181         final var action1 = UserAction.of(() -> action1(entity), Displayable.of("Action 1"));
182         final var action2 = UserAction.of(() -> action2(entity), Displayable.of("Action 2"));
183         final var action3 = UserAction.of(() -> action3(entity), Displayable.of("Action 3"));
184         // No explicit Displayable here, as the one inside SimpleDciEntity is used.
185         return PresentationModel.of(entity, r(aggregate, selectable, UserActionProvider.of(action1, action2, action3)));
186       }
187     // END SNIPPET: pmSimpleDciEntity
188 
189     // Below simple business methods, as per usual business.
190 
191     /***********************************************************************************************************************************************************
192      *
193      **********************************************************************************************************************************************************/
194     // START SNIPPET: onButtonPressed
195     private void onButtonPressed()
196       {
197         presentation.notify("Button pressed");
198         status++;
199         bindings.textProperty.set(Integer.toString(status));
200       }
201     // END SNIPPET: onButtonPressed
202 
203     /***********************************************************************************************************************************************************
204      *
205      **********************************************************************************************************************************************************/
206     // START SNIPPET: onButtonDialogOkPressed
207     private void onButtonDialogOkPressed()
208       {
209         presentation.notify(notificationWithFeedback()
210                 .withCaption("Notification")
211                 .withText("Now press the button")
212                 .withFeedback(feedback().withOnConfirm(() -> presentation.notify("Pressed ok"))));
213       }
214     // END SNIPPET: onButtonDialogOkPressed
215 
216     /***********************************************************************************************************************************************************
217      *
218      **********************************************************************************************************************************************************/
219     // START SNIPPET: onButtonDialogOkCancelPressed
220     private void onButtonDialogOkCancelPressed()
221       {
222         presentation.notify(notificationWithFeedback()
223                 .withCaption("Notification")
224                 .withText("Now press the button")
225                 .withFeedback(feedback().withOnConfirm(() -> presentation.notify("Pressed ok"))
226                                         .withOnCancel(() -> presentation.notify("Pressed cancel"))));
227       }
228     // END SNIPPET: onButtonDialogOkCancelPressed
229 
230     /***********************************************************************************************************************************************************
231      * This method demonstrates how to pick a file name by using the proper UI dialog.
232      **********************************************************************************************************************************************************/
233     // START SNIPPET: onButtonPickFilePressed
234     private void onButtonPickFilePressed()
235       {
236         final var selectedFile = new BoundProperty<>(USER_HOME);
237         presentation.pickFile(selectedFile,
238             notificationWithFeedback()
239                 .withCaption("Pick a file")
240                 .withFeedback(feedback().withOnConfirm(() -> presentation.notify("Selected file: " + selectedFile.get()))
241                                         .withOnCancel(() -> presentation.notify("Selection cancelled"))));
242       }
243     // END SNIPPET: onButtonPickFilePressed
244 
245     /***********************************************************************************************************************************************************
246      * This method demonstrates how to pick a directory name by using the proper UI dialog.
247      **********************************************************************************************************************************************************/
248     // START SNIPPET: onButtonPickDirectoryPressed
249     private void onButtonPickDirectoryPressed()
250       {
251         final var selectedFolder = new BoundProperty<>(USER_HOME);
252         presentation.pickDirectory(selectedFolder,
253             notificationWithFeedback()
254                 .withCaption("Pick a directory")
255                 .withFeedback(feedback().withOnConfirm(() -> presentation.notify("Selected directory: " + selectedFolder.get()))
256                                         .withOnCancel(() -> presentation.notify("Selection cancelled"))));
257       }
258     // END SNIPPET: onButtonPickDirectoryPressed
259 
260     /***********************************************************************************************************************************************************
261      *
262      **********************************************************************************************************************************************************/
263     // START SNIPPET: onSelected
264     private void onSelected (@Nonnull final Object object)
265       {
266         presentation.notify("Selected " + object);
267       }
268     // END SNIPPET: onSelected
269 
270     /***********************************************************************************************************************************************************
271      *
272      **********************************************************************************************************************************************************/
273     // START SNIPPET: action1
274     private void action1 (@Nonnull final Object object)
275       {
276         presentation.notify("Action 1 on " + object);
277       }
278     // END SNIPPET: action1
279 
280     /***********************************************************************************************************************************************************
281      *
282      **********************************************************************************************************************************************************/
283     private void action2 (@Nonnull final Object object)
284       {
285         presentation.notify("Action 2 on " + object);
286       }
287 
288     /***********************************************************************************************************************************************************
289      *
290      **********************************************************************************************************************************************************/
291     private void action3 (@Nonnull final Object object)
292       {
293         presentation.notify("Action 3 on " + object);
294       }
295   }