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