1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package it.tidalwave.ui.javafx.impl;
27
28 import jakarta.annotation.Nonnull;
29 import java.util.concurrent.Executor;
30 import java.util.function.Function;
31 import javafx.beans.property.Property;
32 import javafx.scene.control.TextField;
33 import javafx.stage.Window;
34 import javafx.application.Platform;
35 import it.tidalwave.ui.core.BoundProperty;
36 import it.tidalwave.ui.javafx.JavaFXBinder;
37 import it.tidalwave.ui.javafx.impl.button.ButtonBindings;
38 import it.tidalwave.ui.javafx.impl.combobox.ComboBoxBindings;
39 import it.tidalwave.ui.javafx.impl.common.DefaultCellBinder;
40 import it.tidalwave.ui.javafx.impl.common.PropertyAdapter;
41 import it.tidalwave.ui.javafx.impl.dialog.DialogBindings;
42 import it.tidalwave.ui.javafx.impl.filechooser.FileChooserBindings;
43 import it.tidalwave.ui.javafx.impl.list.ListViewBindings;
44 import it.tidalwave.ui.javafx.impl.tableview.TableViewBindings;
45 import it.tidalwave.ui.javafx.impl.tree.TreeViewBindings;
46 import it.tidalwave.ui.javafx.impl.treetable.TreeTableViewBindings;
47 import lombok.experimental.Delegate;
48 import lombok.extern.slf4j.Slf4j;
49 import static java.util.Objects.requireNonNull;
50
51
52
53
54
55
56 @Slf4j
57 public class DefaultJavaFXBinder implements JavaFXBinder
58 {
59 private final Executor executor;
60
61 private final String invalidTextFieldStyle = "-fx-background-color: pink";
62
63 @Delegate
64 private final ButtonBindings buttonBindings;
65
66 @Delegate
67 private final TreeViewBindings treeItemBindings;
68
69 @Delegate
70 private final TableViewBindings tableViewBindings;
71
72 @Delegate
73 private final TreeTableViewBindings treeTableViewBindings;
74
75 @Delegate
76 private final ListViewBindings listViewBindings;
77
78 @Delegate
79 private final ComboBoxBindings comboBoxBindings;
80
81 @Delegate
82 private final DialogBindings dialogBindings;
83
84 @Delegate
85 private final FileChooserBindings fileChooserBindings;
86
87
88
89
90 public DefaultJavaFXBinder (@Nonnull final Executor executor)
91 {
92 this.executor = executor;
93 final var cellBinder = new DefaultCellBinder(executor);
94 buttonBindings = new ButtonBindings(executor);
95 comboBoxBindings = new ComboBoxBindings(executor, cellBinder);
96 treeItemBindings = new TreeViewBindings(executor, cellBinder);
97 tableViewBindings = new TableViewBindings(executor, cellBinder);
98 treeTableViewBindings = new TreeTableViewBindings(executor, cellBinder);
99 listViewBindings = new ListViewBindings(executor, cellBinder);
100 dialogBindings = new DialogBindings(executor);
101 fileChooserBindings = new FileChooserBindings(executor);
102 }
103
104
105
106
107 @Override
108 public void setMainWindow (@Nonnull final Window mainWindow)
109 {
110 treeItemBindings.setMainWindow(mainWindow);
111 tableViewBindings.setMainWindow(mainWindow);
112 dialogBindings.setMainWindow(mainWindow);
113 fileChooserBindings.setMainWindow(mainWindow);
114 }
115
116
117
118
119 @Override
120 public <T, S> void bind (@Nonnull final BoundProperty<? super T> target, @Nonnull final Property<? extends S> source, @Nonnull final Function<S, T> adapter)
121 {
122 enforceFxApplicationThread();
123 source.addListener((_1, _2, newValue) -> executor.execute(() -> target.set(adapter.apply(newValue))));
124 }
125
126
127
128
129 @Override @SuppressWarnings("unchecked")
130 public <T, S> void bindBidirectionally (@Nonnull final BoundProperty<? super T> property1,
131 @Nonnull final Property<S> property2,
132 @Nonnull final Function<? super S, T> adapter,
133 @Nonnull final Function<? super T, ? extends S> reverseAdapter)
134 {
135 enforceFxApplicationThread();
136 property2.addListener((_1, _2, newValue) -> executor.execute(() -> property1.set(adapter.apply(newValue))));
137 property1.addPropertyChangeListener(evt -> Platform.runLater(() -> property2.setValue(reverseAdapter.apply((T)evt.getNewValue()))));
138 }
139
140
141
142
143 @Override
144 public void bindBidirectionally (@Nonnull final TextField textField,
145 @Nonnull final BoundProperty<String> textProperty,
146 @Nonnull final BoundProperty<Boolean> validProperty)
147 {
148 enforceFxApplicationThread();
149 requireNonNull(textField, "textField");
150 requireNonNull(textProperty, "textProperty");
151 requireNonNull(validProperty, "validProperty");
152
153 textField.textProperty().bindBidirectional(new PropertyAdapter<>(executor, textProperty));
154
155
156 validProperty.addPropertyChangeListener(__ -> textField.setStyle(validProperty.get() ? "" : invalidTextFieldStyle));
157 }
158
159
160
161
162 public static void enforceFxApplicationThread()
163 {
164 if (!Platform.isFxApplicationThread())
165 {
166 throw new IllegalStateException("Must run in the JavaFX Application Thread");
167 }
168 }
169 }