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 }