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