Skip to content

Package: DefaultJavaFXBinder$Exclusions

DefaultJavaFXBinder$Exclusions

Coverage

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.javafx.impl;
27:
28: import javax.annotation.Nonnull;
29: import java.util.Collection;
30: import java.util.List;
31: import java.util.concurrent.Executor;
32: import java.util.concurrent.atomic.AtomicInteger;
33: import javafx.beans.binding.BooleanExpression;
34: import javafx.beans.property.Property;
35: import javafx.geometry.HPos;
36: import javafx.geometry.VPos;
37: import javafx.scene.control.Button;
38: import javafx.scene.control.ButtonBase;
39: import javafx.scene.control.MenuItem;
40: import javafx.scene.control.TextField;
41: import javafx.scene.control.ToggleButton;
42: import javafx.scene.control.ToggleGroup;
43: import javafx.scene.layout.ColumnConstraints;
44: import javafx.scene.layout.GridPane;
45: import javafx.scene.layout.Pane;
46: import javafx.scene.layout.Priority;
47: import javafx.stage.Window;
48: import javafx.application.Platform;
49: import it.tidalwave.role.SimpleComposite;
50: import it.tidalwave.role.ui.BoundProperty;
51: import it.tidalwave.role.ui.Displayable;
52: import it.tidalwave.role.ui.PresentationModel;
53: import it.tidalwave.role.ui.Styleable;
54: import it.tidalwave.role.ui.UserAction;
55: import it.tidalwave.role.ui.UserActionProvider;
56: import it.tidalwave.role.ui.javafx.JavaFXBinder;
57: import it.tidalwave.role.ui.javafx.impl.combobox.ComboBoxBindings;
58: import it.tidalwave.role.ui.javafx.impl.common.CellBinder;
59: import it.tidalwave.role.ui.javafx.impl.common.ChangeListenerSelectableAdapter;
60: import it.tidalwave.role.ui.javafx.impl.common.DefaultCellBinder;
61: import it.tidalwave.role.ui.javafx.impl.common.PropertyAdapter;
62: import it.tidalwave.role.ui.javafx.impl.dialog.DialogBindings;
63: import it.tidalwave.role.ui.javafx.impl.filechooser.FileChooserBindings;
64: import it.tidalwave.role.ui.javafx.impl.list.ListViewBindings;
65: import it.tidalwave.role.ui.javafx.impl.tableview.TableViewBindings;
66: import it.tidalwave.role.ui.javafx.impl.tree.TreeViewBindings;
67: import it.tidalwave.role.ui.javafx.impl.treetable.TreeTableViewBindings;
68: import lombok.experimental.Delegate;
69: import lombok.extern.slf4j.Slf4j;
70: import static java.util.Collections.emptyList;
71: import static java.util.Objects.requireNonNull;
72: import static java.util.stream.Collectors.*;
73: import static it.tidalwave.role.SimpleComposite._SimpleComposite_;
74: import static it.tidalwave.role.ui.Displayable._Displayable_;
75: import static it.tidalwave.role.ui.Styleable._Styleable_;
76: import static it.tidalwave.role.ui.UserActionProvider._UserActionProvider_;
77:
78: /***************************************************************************************************************************************************************
79: *
80: * @author Fabrizio Giudici
81: *
82: **************************************************************************************************************************************************************/
83: @Slf4j
84: public class DefaultJavaFXBinder implements JavaFXBinder
85: {
86: private final Executor executor;
87:
88: private final String invalidTextFieldStyle = "-fx-background-color: pink";
89:
90: interface Exclusions
91: {
92: public void setMainWindow (Window window);
93: // duplicated in TableViewBindings and TreeTableViewBindings due to common super class
94: public ChangeListenerSelectableAdapter getSelectionListener();
95: }
96:
97: @Delegate(excludes = Exclusions.class)
98: private final TreeViewBindings treeItemBindings;
99:
100: @Delegate(excludes = Exclusions.class)
101: private final TableViewBindings tableViewBindings;
102:
103: @Delegate(excludes = Exclusions.class)
104: private final TreeTableViewBindings treeTableViewBindings;
105:
106: @Delegate(excludes = Exclusions.class)
107: private final ListViewBindings listViewBindings;
108:
109: @Delegate(excludes = Exclusions.class)
110: private final ComboBoxBindings comboBoxBindings;
111:
112: @Delegate(excludes = Exclusions.class)
113: private final DialogBindings dialogBindings;
114:
115: @Delegate(excludes = Exclusions.class)
116: private final FileChooserBindings fileChooserBindings;
117:
118: private final CellBinder cellBinder;
119:
120: /***********************************************************************************************************************************************************
121: *
122: **********************************************************************************************************************************************************/
123: public DefaultJavaFXBinder (@Nonnull final Executor executor)
124: {
125: this.executor = executor;
126: cellBinder = new DefaultCellBinder(executor);
127: comboBoxBindings = new ComboBoxBindings(executor, cellBinder);
128: treeItemBindings = new TreeViewBindings(executor, cellBinder);
129: tableViewBindings = new TableViewBindings(executor, cellBinder);
130: treeTableViewBindings = new TreeTableViewBindings(executor, cellBinder);
131: listViewBindings = new ListViewBindings(executor, cellBinder);
132: dialogBindings = new DialogBindings(executor);
133: fileChooserBindings = new FileChooserBindings(executor);
134: }
135:
136: /***********************************************************************************************************************************************************
137: * {@inheritDoc}
138: **********************************************************************************************************************************************************/
139: @Override
140: public void setMainWindow (@Nonnull final Window mainWindow)
141: {
142: treeItemBindings.setMainWindow(mainWindow);
143: tableViewBindings.setMainWindow(mainWindow);
144: dialogBindings.setMainWindow(mainWindow);
145: fileChooserBindings.setMainWindow(mainWindow);
146: }
147:
148: /***********************************************************************************************************************************************************
149: * {@inheritDoc}
150: **********************************************************************************************************************************************************/
151: @Override
152: public void bind (@Nonnull final ButtonBase button, @Nonnull final UserAction action)
153: {
154: assertIsFxApplicationThread();
155: button.setText(action.maybeAs(_Displayable_).map(Displayable::getDisplayName).orElse(""));
156: button.disableProperty().bind(adaptBoolean(action.enabled()).not());
157: button.setOnAction(__ -> executor.execute(action::actionPerformed));
158: }
159:
160: /***********************************************************************************************************************************************************
161: * {@inheritDoc}
162: **********************************************************************************************************************************************************/
163: @Override
164: public void bind (@Nonnull final MenuItem menuItem, @Nonnull final UserAction action)
165: {
166: assertIsFxApplicationThread();
167: menuItem.setText(action.maybeAs(_Displayable_).map(Displayable::getDisplayName).orElse(""));
168: menuItem.disableProperty().bind(adaptBoolean(action.enabled()).not());
169: menuItem.setOnAction(__ -> executor.execute(action::actionPerformed));
170: }
171:
172: /***********************************************************************************************************************************************************
173: * {@inheritDoc}
174: **********************************************************************************************************************************************************/
175: @Override
176: public <T> void bindBidirectionally (@Nonnull final Property<T> property1, @Nonnull final BoundProperty<T> property2)
177: {
178: assertIsFxApplicationThread();
179: property1.bindBidirectional(new PropertyAdapter<>(executor, property2));
180: }
181:
182: /***********************************************************************************************************************************************************
183: * {@inheritDoc}
184: **********************************************************************************************************************************************************/
185: @Override
186: public <T> void bindBidirectionally (@Nonnull final TextField textField,
187: @Nonnull final BoundProperty<String> textProperty,
188: @Nonnull final BoundProperty<Boolean> validProperty)
189: {
190: assertIsFxApplicationThread();
191: requireNonNull(textField, "textField");
192: requireNonNull(textProperty, "textProperty");
193: requireNonNull(validProperty, "validProperty");
194:
195: textField.textProperty().bindBidirectional(new PropertyAdapter<>(executor, textProperty));
196:
197: // FIXME: weak listener
198: validProperty.addPropertyChangeListener(__ -> textField.setStyle(validProperty.get() ? "" : invalidTextFieldStyle));
199: }
200:
201: /***********************************************************************************************************************************************************
202: * {@inheritDoc}
203: **********************************************************************************************************************************************************/
204: @Override
205: public void bindToggleButtons (@Nonnull final Pane pane, @Nonnull final PresentationModel pm)
206: {
207: assertIsFxApplicationThread();
208: final var group = new ToggleGroup();
209: final var children = pane.getChildren();
210: final var prototypeStyleClass = children.get(0).getStyleClass();
211: final SimpleComposite<PresentationModel> pmc = pm.as(_SimpleComposite_);
212: children.setAll(pmc.findChildren().stream().map(cpm -> createToggleButton(cpm, prototypeStyleClass, group)).collect(toList()));
213: }
214:
215: /***********************************************************************************************************************************************************
216: *
217: **********************************************************************************************************************************************************/
218: @Override
219: public void bindButtonsInPane (@Nonnull final GridPane gridPane, @Nonnull final Collection<UserAction> actions)
220: {
221: assertIsFxApplicationThread();
222: final var columnConstraints = gridPane.getColumnConstraints();
223: final var children = gridPane.getChildren();
224:
225: columnConstraints.clear();
226: children.clear();
227: final var columnIndex = new AtomicInteger(0);
228:
229: actions.forEach(menuAction ->
230: {
231: final var column = new ColumnConstraints();
232: column.setPercentWidth(100.0 / actions.size());
233: columnConstraints.add(column);
234: final var button = createButton();
235: GridPane.setConstraints(button, columnIndex.getAndIncrement(), 0);
236: bind(button, menuAction);
237: children.add(button);
238: });
239: }
240:
241: /***********************************************************************************************************************************************************
242: * {@return a new {@code Button}} for the menu bar.
243: **********************************************************************************************************************************************************/
244: @Nonnull
245: private Button createButton()
246: {
247: final var button = new Button();
248: GridPane.setHgrow(button, Priority.ALWAYS);
249: GridPane.setVgrow(button, Priority.ALWAYS);
250: GridPane.setHalignment(button, HPos.CENTER);
251: GridPane.setValignment(button, VPos.CENTER);
252: button.setPrefSize(999, 999); // fill
253: button.getStyleClass().add("mainMenuButton");
254:
255: return button;
256: }
257:
258: /***********************************************************************************************************************************************************
259: *
260: **********************************************************************************************************************************************************/
261: @Nonnull
262: private ToggleButton createToggleButton (@Nonnull final PresentationModel pm, @Nonnull final List<String> baseStyleClass, @Nonnull final ToggleGroup group)
263: {
264: final var button = new ToggleButton();
265: button.setToggleGroup(group);
266: button.setText(pm.maybeAs(_Displayable_).map(Displayable::getDisplayName).orElse(""));
267: button.getStyleClass().addAll(baseStyleClass);
268: button.getStyleClass().addAll(pm.maybeAs(_Styleable_).map(Styleable::getStyles).orElse(emptyList()));
269: pm.maybeAs(_UserActionProvider_).flatMap(UserActionProvider::getOptionalDefaultAction)
270: .ifPresent(action -> bind(button, action));
271:
272: if (group.getSelectedToggle() == null)
273: {
274: group.selectToggle(button);
275: }
276:
277: return button;
278: }
279:
280: /***********************************************************************************************************************************************************
281: *
282: **********************************************************************************************************************************************************/
283: private void assertIsFxApplicationThread()
284: {
285: if (!Platform.isFxApplicationThread())
286: {
287: throw new AssertionError("Must run in the JavaFX Application Thread");
288: }
289: }
290:
291: /***********************************************************************************************************************************************************
292: *
293: **********************************************************************************************************************************************************/
294: @Nonnull
295: private BooleanExpression adaptBoolean (@Nonnull final BoundProperty<Boolean> property)
296: {
297: return BooleanExpression.booleanExpression(new PropertyAdapter<>(executor, property));
298: }
299: }