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 }