Skip to content

Method: populate()

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