JavaFXUtils.java
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 == nullcontinue;
120             JavaFXAction action = (JavaFXActione.getValue().getToolkitAction();
121 
122             if (control instanceof ButtonBase) {
123                 configure(((ButtonBasecontrol), action);
124             else if (control instanceof MenuItem) {
125                 JavaFXUtils.configure(((MenuItemcontrol), action);
126             else if (control instanceof Node) {
127                 ((Nodecontrol).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((ButtonBasecontrol, 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((ButtonBasecontrol, 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((ButtonBasecontrol, 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((MenuItemcontrol, 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((MenuItemcontrol, 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 (instanceof Node) {
357                     return (Nodeo;
358                 else if (instanceof Image) {
359                     return new ImageView((Imageo);
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 = (TabPaneroot;
380             for (Tab child : parent.getTabs()) {
381                 if (child.getContent() != null) {
382                     Node found = findNode(child.getContent(), id);
383                     if (found != nullreturn found;
384                 }
385             }
386         else if (root instanceof TitledPane) {
387             TitledPane parent = (TitledPaneroot;
388             if (parent.getContent() != null) {
389                 Node found = findNode(parent.getContent(), id);
390                 if (found != nullreturn found;
391             }
392         else if (root instanceof Accordion) {
393             Accordion parent = (Accordionroot;
394             for (TitledPane child : parent.getPanes()) {
395                 Node found = findNode(child, id);
396                 if (found != nullreturn found;
397             }
398         else if (root instanceof SplitPane) {
399             SplitPane parent = (SplitPaneroot;
400             for (Node child : parent.getItems()) {
401                 Node found = findNode(child, id);
402                 if (found != nullreturn found;
403             }
404         else if (root instanceof ScrollPane) {
405             ScrollPane scrollPane = (ScrollPaneroot;
406             if (scrollPane.getContent() != null) {
407                 Node found = findNode(scrollPane.getContent(), id);
408                 if (found != nullreturn found;
409             }
410         else if (root instanceof ToolBar) {
411             ToolBar toolBar = (ToolBarroot;
412             for (Node child : toolBar.getItems()) {
413                 Node found = findNode(child, id);
414                 if (found != nullreturn found;
415             }
416         else if (root instanceof Parent) {
417             Parent parent = (Parentroot;
418             for (Node child : parent.getChildrenUnmodifiable()) {
419                 Node found = findNode(child, id);
420                 if (found != nullreturn 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 = (MenuBarroot;
436             for (Menu child : menuBar.getMenus()) {
437                 Object found = findElement(child, id);
438                 if (found != nullreturn found;
439             }
440         if (root instanceof ContextMenu) {
441             ContextMenu contextMenu = (ContextMenuroot;
442             for (MenuItem child : contextMenu.getItems()) {
443                 Object found = findElement(child, id);
444                 if (found != nullreturn found;
445             }
446         else if (root instanceof Menu) {
447             Menu menu = (Menuroot;
448             for (MenuItem child : menu.getItems()) {
449                 Object found = findElement(child, id);
450                 if (found != nullreturn found;
451             }
452         else if (root instanceof TabPane) {
453             TabPane tabPane = (TabPaneroot;
454             for (Tab child : tabPane.getTabs()) {
455                 Object found = findElement(child, id);
456                 if (found != nullreturn found;
457             }
458         else if (root instanceof Tab) {
459             Tab tab = (Tabroot;
460             if (tab.getContent() != null) {
461                 Object found = findElement(tab.getContent(), id);
462                 if (found != nullreturn found;
463             }
464         else if (root instanceof TitledPane) {
465             TitledPane parent = (TitledPaneroot;
466             if (parent.getContent() != null) {
467                 Object found = findElement(parent.getContent(), id);
468                 if (found != nullreturn found;
469             }
470         else if (root instanceof Accordion) {
471             Accordion parent = (Accordionroot;
472             for (TitledPane child : parent.getPanes()) {
473                 Object found = findElement(child, id);
474                 if (found != nullreturn found;
475             }
476         else if (root instanceof SplitPane) {
477             SplitPane parent = (SplitPaneroot;
478             for (Node child : parent.getItems()) {
479                 Object found = findElement(child, id);
480                 if (found != nullreturn found;
481             }
482         else if (root instanceof ScrollPane) {
483             ScrollPane scrollPane = (ScrollPaneroot;
484             if (scrollPane.getContent() != null) {
485                 Object found = findElement(scrollPane.getContent(), id);
486                 if (found != nullreturn found;
487             }
488         else if (root instanceof ToolBar) {
489             ToolBar toolBar = (ToolBarroot;
490             for (Node child : toolBar.getItems()) {
491                 Node found = findNode(child, id);
492                 if (found != nullreturn found;
493             }
494         else if (root instanceof Parent) {
495             Parent parent = (Parentroot;
496             for (Node child : parent.getChildrenUnmodifiable()) {
497                 Object found = findElement(child, id);
498                 if (found != nullreturn 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 (Windownode;
511         else if (node instanceof Scene) {
512             return ((Scenenode).getWindow();
513         else if (node instanceof Node) {
514             Scene scene = ((Nodenode).getScene();
515             if (scene != null) {
516                 return scene.getWindow();
517             }
518         else if (node instanceof Tab) {
519             TabPane tabPane = ((Tabnode).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 }