001 /*
002 * Copyright 2008-2015 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package griffon.javafx.support;
017
018 import griffon.core.artifact.GriffonController;
019 import griffon.core.controller.Action;
020 import griffon.core.controller.ActionManager;
021 import griffon.core.editors.ValueConversionException;
022 import griffon.exceptions.InstanceMethodInvocationException;
023 import javafx.beans.property.Property;
024 import javafx.event.ActionEvent;
025 import javafx.scene.Node;
026 import javafx.scene.Parent;
027 import javafx.scene.Scene;
028 import javafx.scene.control.Accordion;
029 import javafx.scene.control.ButtonBase;
030 import javafx.scene.control.CheckBox;
031 import javafx.scene.control.CheckMenuItem;
032 import javafx.scene.control.ContextMenu;
033 import javafx.scene.control.Control;
034 import javafx.scene.control.Labeled;
035 import javafx.scene.control.Menu;
036 import javafx.scene.control.MenuBar;
037 import javafx.scene.control.MenuItem;
038 import javafx.scene.control.RadioButton;
039 import javafx.scene.control.RadioMenuItem;
040 import javafx.scene.control.ScrollPane;
041 import javafx.scene.control.SplitPane;
042 import javafx.scene.control.Tab;
043 import javafx.scene.control.TabPane;
044 import javafx.scene.control.TitledPane;
045 import javafx.scene.control.ToggleButton;
046 import javafx.scene.control.ToolBar;
047 import javafx.scene.control.Tooltip;
048 import javafx.scene.image.Image;
049 import javafx.scene.image.ImageView;
050 import javafx.stage.Window;
051
052 import javax.annotation.Nonnull;
053 import javax.annotation.Nullable;
054 import java.lang.reflect.Constructor;
055 import java.lang.reflect.InvocationTargetException;
056 import java.net.URL;
057 import java.util.Map;
058
059 import static griffon.util.GriffonClassUtils.getGetterName;
060 import static griffon.util.GriffonClassUtils.getPropertyValue;
061 import static griffon.util.GriffonClassUtils.invokeExactInstanceMethod;
062 import static griffon.util.GriffonClassUtils.invokeInstanceMethod;
063 import static griffon.util.GriffonNameUtils.isBlank;
064 import static griffon.util.GriffonNameUtils.requireNonBlank;
065 import static java.util.Objects.requireNonNull;
066
067 /**
068 * @author Andres Almiray
069 */
070 public final class JavaFXUtils {
071 private static final String ERROR_NODE_NULL = "Argument 'node' must not be null";
072 private static final String ERROR_CONTROL_NULL = "Argument 'control' must not be null";
073 private static final String ERROR_ACTION_NULL = "Argument 'action' must not be null";
074 private static final String ERROR_ICON_BLANK = "Argument 'iconUrl' must not be blank";
075 private static final String ERROR_ID_BLANK = "Argument 'id' must not be blank";
076 private static final String ERROR_URL_BLANK = "Argument 'url' must not be blank";
077 private static final String ERROR_ROOT_NULL = "Argument 'root' must not be null";
078 private static final String ERROR_CONTROLLER_NULL = "Argument 'controller' must not be null";
079 private static final String ACTION_TARGET_SUFFIX = "ActionTarget";
080 private static final String PROPERTY_SUFFIX = "Property";
081
082 private JavaFXUtils() {
083
084 }
085
086 @Nonnull
087 @SuppressWarnings("ConstantConditions")
088 public static <B> Property<?> extractProperty(@Nonnull B bean, @Nonnull String propertyName) {
089 requireNonNull(bean, "Argument 'bean' must not be null");
090 requireNonBlank(propertyName, "Argument 'propertyName' must not be null");
091
092 if (!propertyName.endsWith(PROPERTY_SUFFIX)) {
093 propertyName += PROPERTY_SUFFIX;
094 }
095
096 InstanceMethodInvocationException imie;
097 try {
098 // 1. try <columnName>Property() first
099 return (Property<?>) invokeExactInstanceMethod(bean, propertyName);
100 } catch (InstanceMethodInvocationException e) {
101 imie = e;
102 }
103
104 // 2. fallback to get<columnName>Property()
105 try {
106 return (Property<?>) invokeExactInstanceMethod(bean, getGetterName(propertyName));
107 } catch (InstanceMethodInvocationException e) {
108 throw imie;
109 }
110 }
111
112 public static void connectActions(@Nonnull Object node, @Nonnull GriffonController controller) {
113 requireNonNull(node, ERROR_NODE_NULL);
114 requireNonNull(controller, ERROR_CONTROLLER_NULL);
115 ActionManager actionManager = controller.getApplication().getActionManager();
116 for (Map.Entry<String, Action> e : actionManager.actionsFor(controller).entrySet()) {
117 String actionTargetName = actionManager.normalizeName(e.getKey()) + ACTION_TARGET_SUFFIX;
118 Object control = findElement(node, actionTargetName);
119 if (control == null) continue;
120 JavaFXAction action = (JavaFXAction) e.getValue().getToolkitAction();
121
122 if (control instanceof ButtonBase) {
123 configure(((ButtonBase) control), action);
124 } else if (control instanceof MenuItem) {
125 JavaFXUtils.configure(((MenuItem) control), action);
126 } else if (control instanceof Node) {
127 ((Node) control).addEventHandler(ActionEvent.ACTION, action.getOnAction());
128 } else {
129 // does it support the onAction property?
130 try {
131 invokeInstanceMethod(control, "setOnAction", action.getOnAction());
132 } catch (InstanceMethodInvocationException imie) {
133 // ignore
134 }
135 }
136 }
137 }
138
139 public static void configure(final @Nonnull ToggleButton control, final @Nonnull JavaFXAction action) {
140 configure((ButtonBase) control, action);
141
142 action.selectedProperty().addListener((observableValue, oldValue, newValue) -> control.setSelected(newValue));
143 control.setSelected(action.isSelected());
144 }
145
146 public static void configure(final @Nonnull CheckBox control, final @Nonnull JavaFXAction action) {
147 configure((ButtonBase) control, action);
148
149 action.selectedProperty().addListener((observableValue, oldValue, newValue) -> control.setSelected(newValue));
150 control.setSelected(action.isSelected());
151 }
152
153 public static void configure(final @Nonnull RadioButton control, final @Nonnull JavaFXAction action) {
154 configure((ButtonBase) control, action);
155
156 action.selectedProperty().addListener((observableValue, oldValue, newValue) -> control.setSelected(newValue));
157 control.setSelected(action.isSelected());
158 }
159
160 public static void configure(final @Nonnull ButtonBase control, final @Nonnull JavaFXAction action) {
161 requireNonNull(control, ERROR_CONTROL_NULL);
162 requireNonNull(action, ERROR_ACTION_NULL);
163
164 action.onActionProperty().addListener((observableValue, oldValue, newValue) -> control.setOnAction(newValue));
165 control.setOnAction(action.getOnAction());
166
167 action.nameProperty().addListener((observableValue, oldValue, newValue) -> control.setText(newValue));
168 control.setText(action.getName());
169
170 action.descriptionProperty().addListener((observableValue, oldValue, newValue) -> setTooltip(control, newValue));
171 setTooltip(control, action.getDescription());
172
173 action.iconProperty().addListener((observableValue, oldValue, newValue) -> setIcon(control, newValue));
174 if (!isBlank(action.getIcon())) {
175 setIcon(control, action.getIcon());
176 }
177
178 action.imageProperty().addListener((observableValue, oldValue, newValue) -> setGraphic(control, newValue));
179 if (null != action.getImage()) {
180 setGraphic(control, action.getImage());
181 }
182
183 action.graphicProperty().addListener((observableValue, oldValue, newValue) -> setGraphic(control, newValue));
184 if (null != action.getGraphic()) {
185 setGraphic(control, action.getGraphic());
186 }
187
188 action.enabledProperty().addListener((observableValue, oldValue, newValue) -> control.setDisable(!newValue));
189 control.setDisable(!action.isEnabled());
190
191 action.visibleProperty().addListener((observableValue, oldValue, newValue) -> control.setVisible(newValue));
192 control.setVisible(action.isVisible());
193 }
194
195 public static void configure(final @Nonnull CheckMenuItem control, final @Nonnull JavaFXAction action) {
196 configure((MenuItem) control, action);
197
198 action.selectedProperty().addListener((observableValue, oldValue, newValue) -> control.setSelected(newValue));
199 control.setSelected(action.isSelected());
200 }
201
202 public static void configure(final @Nonnull RadioMenuItem control, final @Nonnull JavaFXAction action) {
203 configure((MenuItem) control, action);
204
205 action.selectedProperty().addListener((observableValue, oldValue, newValue) -> control.setSelected(newValue));
206 control.setSelected(action.isSelected());
207 }
208
209 public static void configure(final @Nonnull MenuItem control, final @Nonnull JavaFXAction action) {
210 requireNonNull(control, ERROR_CONTROL_NULL);
211 requireNonNull(action, ERROR_ACTION_NULL);
212
213 action.onActionProperty().addListener((observableValue, oldValue, newValue) -> control.setOnAction(newValue));
214 control.setOnAction(action.getOnAction());
215
216 action.nameProperty().addListener((observableValue, oldValue, newValue) -> control.setText(newValue));
217 control.setText(action.getName());
218
219 action.iconProperty().addListener((observableValue, oldValue, newValue) -> setIcon(control, newValue));
220 if (!isBlank(action.getIcon())) {
221 setIcon(control, action.getIcon());
222 }
223
224 action.imageProperty().addListener((observableValue, oldValue, newValue) -> setGraphic(control, newValue));
225 if (null != action.getImage()) {
226 setGraphic(control, action.getImage());
227 }
228
229 action.graphicProperty().addListener((observableValue, oldValue, newValue) -> setGraphic(control, newValue));
230 if (null != action.getGraphic()) {
231 setGraphic(control, action.getGraphic());
232 }
233
234 action.enabledProperty().addListener((observableValue, oldValue, newValue) -> control.setDisable(!newValue));
235 control.setDisable(!action.getEnabled());
236
237 action.acceleratorProperty().addListener((observableValue, oldValue, newValue) -> control.setAccelerator(newValue));
238 control.setAccelerator(action.getAccelerator());
239
240 action.visibleProperty().addListener((observableValue, oldValue, newValue) -> control.setVisible(newValue));
241 control.setVisible(action.isVisible());
242 }
243
244 public static void setTooltip(@Nonnull Control control, @Nullable String text) {
245 if (isBlank(text)) {
246 return;
247 }
248 requireNonNull(control, ERROR_CONTROL_NULL);
249
250 Tooltip tooltip = control.tooltipProperty().get();
251 if (tooltip == null) {
252 tooltip = new Tooltip();
253 control.tooltipProperty().set(tooltip);
254 }
255 tooltip.setText(text);
256 }
257
258 public static void setIcon(@Nonnull Labeled control, @Nonnull String iconUrl) {
259 requireNonNull(control, ERROR_CONTROL_NULL);
260 requireNonBlank(iconUrl, ERROR_ICON_BLANK);
261
262 Node graphicNode = resolveIcon(iconUrl);
263 if (graphicNode != null) {
264 control.graphicProperty().set(graphicNode);
265 }
266 }
267
268 public static void setIcon(@Nonnull MenuItem control, @Nonnull String iconUrl) {
269 requireNonNull(control, ERROR_CONTROL_NULL);
270 requireNonBlank(iconUrl, ERROR_ICON_BLANK);
271
272 Node graphicNode = resolveIcon(iconUrl);
273 if (graphicNode != null) {
274 control.graphicProperty().set(graphicNode);
275 }
276 }
277
278 public static void setGraphic(@Nonnull Labeled control, @Nullable Image graphic) {
279 requireNonNull(control, ERROR_CONTROL_NULL);
280
281 if (graphic != null) {
282 Node graphicNode = new ImageView(graphic);
283 control.graphicProperty().set(graphicNode);
284 } else {
285 control.graphicProperty().set(null);
286 }
287 }
288
289 public static void setGraphic(@Nonnull MenuItem control, @Nullable Image graphic) {
290 requireNonNull(control, ERROR_CONTROL_NULL);
291
292 if (graphic != null) {
293 Node graphicNode = new ImageView(graphic);
294 control.graphicProperty().set(graphicNode);
295 } else {
296 control.graphicProperty().set(null);
297 }
298 }
299
300 public static void setGraphic(@Nonnull Labeled control, @Nullable Node graphic) {
301 requireNonNull(control, ERROR_CONTROL_NULL);
302
303 if (graphic != null) {
304 control.graphicProperty().set(graphic);
305 } else {
306 control.graphicProperty().set(null);
307 }
308 }
309
310 public static void setGraphic(@Nonnull MenuItem control, @Nullable Node graphic) {
311 requireNonNull(control, ERROR_CONTROL_NULL);
312
313 if (graphic != null) {
314 control.graphicProperty().set(graphic);
315 } else {
316 control.graphicProperty().set(null);
317 }
318 }
319
320 @Nullable
321 public static Node resolveIcon(@Nonnull String iconUrl) {
322 requireNonBlank(iconUrl, ERROR_URL_BLANK);
323
324 if (iconUrl.contains("|")) {
325 // assume classname|arg format
326 return handleAsClassWithArg(iconUrl);
327 } else {
328 URL resource = Thread.currentThread().getContextClassLoader().getResource(iconUrl);
329 if (resource != null) {
330 return new ImageView(new Image(resource.toString()));
331 }
332 }
333 return null;
334 }
335
336 @SuppressWarnings("unchecked")
337 private static Node handleAsClassWithArg(String str) {
338 String[] args = str.split("\\|");
339 if (args.length == 2) {
340 Class<?> iconClass = null;
341 try {
342 iconClass = (Class<?>) JavaFXUtils.class.getClassLoader().loadClass(args[0]);
343 } catch (ClassNotFoundException e) {
344 throw illegalValue(str, Node.class, e);
345 }
346
347 Constructor<?> constructor = null;
348 try {
349 constructor = iconClass.getConstructor(String.class);
350 } catch (NoSuchMethodException e) {
351 throw illegalValue(str, Node.class, e);
352 }
353
354 try {
355 Object o = constructor.newInstance(args[1]);
356 if (o instanceof Node) {
357 return (Node) o;
358 } else if (o instanceof Image) {
359 return new ImageView((Image) o);
360 } else {
361 throw illegalValue(str, Node.class);
362 }
363 } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
364 throw illegalValue(str, Node.class, e);
365 }
366 } else {
367 throw illegalValue(str, Node.class);
368 }
369 }
370
371 @Nullable
372 public static Node findNode(@Nonnull Node root, @Nonnull String id) {
373 requireNonNull(root, ERROR_ROOT_NULL);
374 requireNonBlank(id, ERROR_ID_BLANK);
375
376 if (id.equals(root.getId())) return root;
377
378 if (root instanceof TabPane) {
379 TabPane parent = (TabPane) root;
380 for (Tab child : parent.getTabs()) {
381 if (child.getContent() != null) {
382 Node found = findNode(child.getContent(), id);
383 if (found != null) return found;
384 }
385 }
386 } else if (root instanceof TitledPane) {
387 TitledPane parent = (TitledPane) root;
388 if (parent.getContent() != null) {
389 Node found = findNode(parent.getContent(), id);
390 if (found != null) return found;
391 }
392 } else if (root instanceof Accordion) {
393 Accordion parent = (Accordion) root;
394 for (TitledPane child : parent.getPanes()) {
395 Node found = findNode(child, id);
396 if (found != null) return found;
397 }
398 } else if (root instanceof SplitPane) {
399 SplitPane parent = (SplitPane) root;
400 for (Node child : parent.getItems()) {
401 Node found = findNode(child, id);
402 if (found != null) return found;
403 }
404 } else if (root instanceof ScrollPane) {
405 ScrollPane scrollPane = (ScrollPane) root;
406 if (scrollPane.getContent() != null) {
407 Node found = findNode(scrollPane.getContent(), id);
408 if (found != null) return found;
409 }
410 } else if (root instanceof ToolBar) {
411 ToolBar toolBar = (ToolBar) root;
412 for (Node child : toolBar.getItems()) {
413 Node found = findNode(child, id);
414 if (found != null) return found;
415 }
416 } else if (root instanceof Parent) {
417 Parent parent = (Parent) root;
418 for (Node child : parent.getChildrenUnmodifiable()) {
419 Node found = findNode(child, id);
420 if (found != null) return found;
421 }
422 }
423
424 return null;
425 }
426
427 @Nullable
428 public static Object findElement(@Nonnull Object root, @Nonnull String id) {
429 requireNonNull(root, ERROR_ROOT_NULL);
430 requireNonBlank(id, ERROR_ID_BLANK);
431
432 if (id.equals(getPropertyValue(root, "id"))) return root;
433
434 if (root instanceof MenuBar) {
435 MenuBar menuBar = (MenuBar) root;
436 for (Menu child : menuBar.getMenus()) {
437 Object found = findElement(child, id);
438 if (found != null) return found;
439 }
440 } if (root instanceof ContextMenu) {
441 ContextMenu contextMenu = (ContextMenu) root;
442 for (MenuItem child : contextMenu.getItems()) {
443 Object found = findElement(child, id);
444 if (found != null) return found;
445 }
446 } else if (root instanceof Menu) {
447 Menu menu = (Menu) root;
448 for (MenuItem child : menu.getItems()) {
449 Object found = findElement(child, id);
450 if (found != null) return found;
451 }
452 } else if (root instanceof TabPane) {
453 TabPane tabPane = (TabPane) root;
454 for (Tab child : tabPane.getTabs()) {
455 Object found = findElement(child, id);
456 if (found != null) return found;
457 }
458 } else if (root instanceof Tab) {
459 Tab tab = (Tab) root;
460 if (tab.getContent() != null) {
461 Object found = findElement(tab.getContent(), id);
462 if (found != null) return found;
463 }
464 } else if (root instanceof TitledPane) {
465 TitledPane parent = (TitledPane) root;
466 if (parent.getContent() != null) {
467 Object found = findElement(parent.getContent(), id);
468 if (found != null) return found;
469 }
470 } else if (root instanceof Accordion) {
471 Accordion parent = (Accordion) root;
472 for (TitledPane child : parent.getPanes()) {
473 Object found = findElement(child, id);
474 if (found != null) return found;
475 }
476 } else if (root instanceof SplitPane) {
477 SplitPane parent = (SplitPane) root;
478 for (Node child : parent.getItems()) {
479 Object found = findElement(child, id);
480 if (found != null) return found;
481 }
482 } else if (root instanceof ScrollPane) {
483 ScrollPane scrollPane = (ScrollPane) root;
484 if (scrollPane.getContent() != null) {
485 Object found = findElement(scrollPane.getContent(), id);
486 if (found != null) return found;
487 }
488 } else if (root instanceof ToolBar) {
489 ToolBar toolBar = (ToolBar) root;
490 for (Node child : toolBar.getItems()) {
491 Node found = findNode(child, id);
492 if (found != null) return found;
493 }
494 } else if (root instanceof Parent) {
495 Parent parent = (Parent) root;
496 for (Node child : parent.getChildrenUnmodifiable()) {
497 Object found = findElement(child, id);
498 if (found != null) return found;
499 }
500 }
501
502 return null;
503 }
504
505 @Nullable
506 public static Window getWindowAncestor(@Nonnull Object node) {
507 requireNonNull(node, ERROR_NODE_NULL);
508
509 if (node instanceof Window) {
510 return (Window) node;
511 } else if (node instanceof Scene) {
512 return ((Scene) node).getWindow();
513 } else if (node instanceof Node) {
514 Scene scene = ((Node) node).getScene();
515 if (scene != null) {
516 return scene.getWindow();
517 }
518 } else if (node instanceof Tab) {
519 TabPane tabPane = ((Tab) node).getTabPane();
520 if (tabPane != null) {
521 return getWindowAncestor(tabPane);
522 }
523 }
524
525 return null;
526 }
527
528 private static ValueConversionException illegalValue(Object value, Class<?> klass) {
529 throw new ValueConversionException(value, klass);
530 }
531
532 private static ValueConversionException illegalValue(Object value, Class<?> klass, Exception e) {
533 throw new ValueConversionException(value, klass, e);
534 }
535 }
|