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