0001 /*
0002 * Copyright 2008-2017 the original author or authors.
0003 *
0004 * Licensed under the Apache License, Version 2.0 (the "License");
0005 * you may not use this file except in compliance with the License.
0006 * You may obtain a copy of the License at
0007 *
0008 * http://www.apache.org/licenses/LICENSE-2.0
0009 *
0010 * Unless required by applicable law or agreed to in writing, software
0011 * distributed under the License is distributed on an "AS IS" BASIS,
0012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013 * See the License for the specific language governing permissions and
0014 * limitations under the License.
0015 */
0016 package griffon.javafx.support;
0017
0018 import griffon.core.GriffonApplication;
0019 import griffon.core.artifact.GriffonController;
0020 import griffon.core.controller.Action;
0021 import griffon.core.controller.ActionManager;
0022 import griffon.core.editors.ValueConversionException;
0023 import griffon.core.i18n.MessageSource;
0024 import griffon.exceptions.InstanceMethodInvocationException;
0025 import griffon.javafx.collections.GriffonFXCollections;
0026 import javafx.application.Platform;
0027 import javafx.beans.property.Property;
0028 import javafx.collections.ObservableList;
0029 import javafx.collections.ObservableMap;
0030 import javafx.collections.ObservableSet;
0031 import javafx.event.ActionEvent;
0032 import javafx.event.EventHandler;
0033 import javafx.scene.Node;
0034 import javafx.scene.Parent;
0035 import javafx.scene.Scene;
0036 import javafx.scene.control.Accordion;
0037 import javafx.scene.control.ButtonBar;
0038 import javafx.scene.control.ButtonBase;
0039 import javafx.scene.control.CheckBox;
0040 import javafx.scene.control.CheckMenuItem;
0041 import javafx.scene.control.ContextMenu;
0042 import javafx.scene.control.Control;
0043 import javafx.scene.control.Labeled;
0044 import javafx.scene.control.Menu;
0045 import javafx.scene.control.MenuBar;
0046 import javafx.scene.control.MenuItem;
0047 import javafx.scene.control.RadioButton;
0048 import javafx.scene.control.RadioMenuItem;
0049 import javafx.scene.control.ScrollPane;
0050 import javafx.scene.control.SplitPane;
0051 import javafx.scene.control.Tab;
0052 import javafx.scene.control.TabPane;
0053 import javafx.scene.control.TitledPane;
0054 import javafx.scene.control.ToggleButton;
0055 import javafx.scene.control.ToolBar;
0056 import javafx.scene.control.Tooltip;
0057 import javafx.scene.image.Image;
0058 import javafx.scene.image.ImageView;
0059 import javafx.stage.Window;
0060
0061 import javax.annotation.Nonnull;
0062 import javax.annotation.Nullable;
0063 import java.lang.reflect.Constructor;
0064 import java.lang.reflect.InvocationTargetException;
0065 import java.net.URL;
0066 import java.util.Collection;
0067 import java.util.LinkedHashSet;
0068 import java.util.Map;
0069 import java.util.Set;
0070 import java.util.function.Predicate;
0071
0072 import static griffon.core.GriffonApplication.PROPERTY_LOCALE;
0073 import static griffon.util.GriffonClassUtils.EMPTY_OBJECT_ARRAY;
0074 import static griffon.util.GriffonClassUtils.getGetterName;
0075 import static griffon.util.GriffonClassUtils.getPropertyValue;
0076 import static griffon.util.GriffonClassUtils.invokeExactInstanceMethod;
0077 import static griffon.util.GriffonClassUtils.invokeInstanceMethod;
0078 import static griffon.util.GriffonNameUtils.isBlank;
0079 import static griffon.util.GriffonNameUtils.requireNonBlank;
0080 import static java.util.Objects.isNull;
0081 import static java.util.Objects.requireNonNull;
0082
0083 /**
0084 * @author Andres Almiray
0085 */
0086 public final class JavaFXUtils {
0087 private static final String ERROR_NODE_NULL = "Argument 'node' must not be null";
0088 private static final String ERROR_CONTROL_NULL = "Argument 'control' must not be null";
0089 private static final String ERROR_ACTION_NULL = "Argument 'action' must not be null";
0090 private static final String ERROR_ICON_BLANK = "Argument 'iconUrl' must not be blank";
0091 private static final String ERROR_ID_BLANK = "Argument 'id' must not be blank";
0092 private static final String ERROR_URL_BLANK = "Argument 'url' must not be blank";
0093 private static final String ERROR_KEY_BLANK = "Argument 'key' must not be blank";
0094 private static final String ERROR_ARGS_BLANK = "Argument 'args' must not be blank";
0095 private static final String ERROR_ROOT_NULL = "Argument 'root' must not be null";
0096 private static final String ERROR_PREDICATE_NULL = "Argument 'predicate' must not be null";
0097 private static final String ERROR_CONTROLLER_NULL = "Argument 'controller' must not be null";
0098 private static final String ERROR_APPLICATION_NULL = "Argument 'application' must not be null";
0099 private static final String PROPERTY_SUFFIX = "Property";
0100 private static final String SUFFIX_KEY = "-KEY";
0101 private static final String SUFFIX_ARGS = "-ARGS";
0102 private static final String SUFFIX_DEFAULT_VALUE = "-DEFAULT_VALUE";
0103 private static final ActionMatcher DEFAULT_ACTION_MATCHER = ActionMatcher.DEFAULT;
0104
0105 private JavaFXUtils() {
0106
0107 }
0108
0109 /**
0110 * Finds out if an i18n {@code key} has been registered with the target {@code Node}, returning the key if found.
0111 *
0112 * @param node the target node on which the key may have been registered.
0113 *
0114 * @return the key registered with the target {@code Node} or {@code null} if not found.
0115 *
0116 * @since 2.9.0
0117 */
0118 @Nullable
0119 public static String getI18nKey(@Nonnull Labeled node) {
0120 requireNonNull(node, ERROR_NODE_NULL);
0121 return (String) node.getProperties().get(MessageSource.class.getName() + SUFFIX_KEY);
0122 }
0123
0124 /**
0125 * Associates an i18n arrays of arguments to a {@code node}.
0126 * These arguments will be used alongside a key to resolve a message via the application's {@code MessageSource}.
0127 *
0128 * @param node the target node on which the key will be registered.
0129 * @param args the array of arguments to be registered.
0130 *
0131 * @since 2.9.0
0132 */
0133 public static void setI18nArgs(@Nonnull Labeled node, @Nullable String args) {
0134 requireNonNull(node, ERROR_NODE_NULL);
0135 requireNonBlank(args, ERROR_ARGS_BLANK);
0136 node.getProperties().put(MessageSource.class.getName() + SUFFIX_ARGS, args);
0137 }
0138
0139 /**
0140 * Finds out if an {@code arguments array} has been registered with the target {@code Node}, returning the array if found.
0141 *
0142 * @param node the target node on which the arguments may have been registered.
0143 *
0144 * @return the arguments registered with the target {@code Node} or {@code null} if not found.
0145 *
0146 * @since 2.9.0
0147 */
0148 @Nullable
0149 public static String getI18nArgs(@Nonnull Labeled node) {
0150 requireNonNull(node, ERROR_NODE_NULL);
0151 return (String) node.getProperties().get(MessageSource.class.getName() + SUFFIX_ARGS);
0152 }
0153
0154 /**
0155 * Associates an default value {@code node}.
0156 * The value will be used alongside a key to resolve a message via the application's {@code MessageSource}.
0157 *
0158 * @param node the target node on which the key will be registered.
0159 * @param defaultValue the value to be registered.
0160 *
0161 * @since 2.9.0
0162 */
0163 public static void setI18nDefaultValue(@Nonnull Labeled node, @Nullable String defaultValue) {
0164 requireNonNull(node, ERROR_NODE_NULL);
0165 node.getProperties().put(MessageSource.class.getName() + SUFFIX_DEFAULT_VALUE, defaultValue);
0166 }
0167
0168 /**
0169 * Finds out if a {@code default value} has been registered with the target {@code Node}, returning the value if found.
0170 *
0171 * @param node the target node on which the value may have been registered.
0172 *
0173 * @return the value registered with the target {@code Node} or {@code null} if not found.
0174 *
0175 * @since 2.9.0
0176 */
0177 @Nullable
0178 public static String getI18nDefaultValue(@Nonnull Labeled node) {
0179 requireNonNull(node, ERROR_NODE_NULL);
0180 return (String) node.getProperties().get(MessageSource.class.getName() + SUFFIX_DEFAULT_VALUE);
0181 }
0182
0183 /**
0184 * Associates an i18n key to a {@code node}. The key is used to resolve a message via the application's {@code MessageSource}.
0185 *
0186 * @param node the target node on which the key will be registered.
0187 * @param key the message key to be registered.
0188 *
0189 * @since 2.9.0
0190 */
0191 public static void setI18nKey(@Nonnull Tab node, @Nonnull String key) {
0192 requireNonNull(node, ERROR_NODE_NULL);
0193 requireNonBlank(key, ERROR_KEY_BLANK);
0194 node.getProperties().put(MessageSource.class.getName() + SUFFIX_KEY, key);
0195 }
0196
0197 /**
0198 * Finds out if an i18n {@code key} has been registered with the target {@code Node}, returning the key if found.
0199 *
0200 * @param node the target node on which the key may have been registered.
0201 *
0202 * @return the key registered with the target {@code Node} or {@code null} if not found.
0203 *
0204 * @since 2.9.0
0205 */
0206 @Nullable
0207 public static String getI18nKey(@Nonnull Tab node) {
0208 requireNonNull(node, ERROR_NODE_NULL);
0209 return (String) node.getProperties().get(MessageSource.class.getName() + SUFFIX_KEY);
0210 }
0211
0212 /**
0213 * Associates an i18n arrays of arguments to a {@code node}.
0214 * These arguments will be used alongside a key to resolve a message via the application's {@code MessageSource}.
0215 *
0216 * @param node the target node on which the key will be registered.
0217 * @param args the array of arguments to be registered.
0218 *
0219 * @since 2.9.0
0220 */
0221 public static void setI18nArgs(@Nonnull Tab node, @Nullable String args) {
0222 requireNonNull(node, ERROR_NODE_NULL);
0223 requireNonBlank(args, ERROR_ARGS_BLANK);
0224 node.getProperties().put(MessageSource.class.getName() + SUFFIX_ARGS, args);
0225 }
0226
0227 /**
0228 * Finds out if an {@code arguments array} has been registered with the target {@code Node}, returning the array if found.
0229 *
0230 * @param node the target node on which the arguments may have been registered.
0231 *
0232 * @return the arguments registered with the target {@code Node} or {@code null} if not found.
0233 *
0234 * @since 2.9.0
0235 */
0236 @Nullable
0237 public static String getI18nArgs(@Nonnull Tab node) {
0238 requireNonNull(node, ERROR_NODE_NULL);
0239 return (String) node.getProperties().get(MessageSource.class.getName() + SUFFIX_ARGS);
0240 }
0241
0242 /**
0243 * Associates an default value {@code node}.
0244 * The value will be used alongside a key to resolve a message via the application's {@code MessageSource}.
0245 *
0246 * @param node the target node on which the key will be registered.
0247 * @param defaultValue the value to be registered.
0248 *
0249 * @since 2.9.0
0250 */
0251 public static void setI18nDefaultValue(@Nonnull Tab node, @Nullable String defaultValue) {
0252 requireNonNull(node, ERROR_NODE_NULL);
0253 node.getProperties().put(MessageSource.class.getName() + SUFFIX_DEFAULT_VALUE, defaultValue);
0254 }
0255
0256 /**
0257 * Finds out if a {@code default value} has been registered with the target {@code Node}, returning the value if found.
0258 *
0259 * @param node the target node on which the value may have been registered.
0260 *
0261 * @return the value registered with the target {@code Node} or {@code null} if not found.
0262 *
0263 * @since 2.9.0
0264 */
0265 @Nullable
0266 public static String getI18nDefaultValue(@Nonnull Tab node) {
0267 requireNonNull(node, ERROR_NODE_NULL);
0268 return (String) node.getProperties().get(MessageSource.class.getName() + SUFFIX_DEFAULT_VALUE);
0269 }
0270
0271 /**
0272 * Associates an i18n key to a {@code node}. The key is used to resolve a message via the application's {@code MessageSource}.
0273 *
0274 * @param node the target node on which the key will be registered.
0275 * @param key the message key to be registered.
0276 *
0277 * @since 2.9.0
0278 */
0279 public static void setI18nKey(@Nonnull MenuItem node, @Nonnull String key) {
0280 requireNonNull(node, ERROR_NODE_NULL);
0281 requireNonBlank(key, ERROR_KEY_BLANK);
0282 node.getProperties().put(MessageSource.class.getName() + SUFFIX_KEY, key);
0283 }
0284
0285 /**
0286 * Finds out if an i18n {@code key} has been registered with the target {@code Node}, returning the key if found.
0287 *
0288 * @param node the target node on which the key may have been registered.
0289 *
0290 * @return the key registered with the target {@code Node} or {@code null} if not found.
0291 *
0292 * @since 2.9.0
0293 */
0294 @Nullable
0295 public static String getI18nKey(@Nonnull MenuItem node) {
0296 requireNonNull(node, ERROR_NODE_NULL);
0297 return (String) node.getProperties().get(MessageSource.class.getName() + SUFFIX_KEY);
0298 }
0299
0300 /**
0301 * Associates an i18n arrays of arguments to a {@code node}.
0302 * These arguments will be used alongside a key to resolve a message via the application's {@code MessageSource}.
0303 *
0304 * @param node the target node on which the key will be registered.
0305 * @param args the array of arguments to be registered.
0306 *
0307 * @since 2.9.0
0308 */
0309 public static void setI18nArgs(@Nonnull MenuItem node, @Nullable String args) {
0310 requireNonNull(node, ERROR_NODE_NULL);
0311 requireNonBlank(args, ERROR_ARGS_BLANK);
0312 node.getProperties().put(MessageSource.class.getName() + SUFFIX_ARGS, args);
0313 }
0314
0315 /**
0316 * Finds out if an {@code arguments array} has been registered with the target {@code Node}, returning the array if found.
0317 *
0318 * @param node the target node on which the arguments may have been registered.
0319 *
0320 * @return the arguments registered with the target {@code Node} or {@code null} if not found.
0321 *
0322 * @since 2.9.0
0323 */
0324 @Nullable
0325 public static String getI18nArgs(@Nonnull MenuItem node) {
0326 requireNonNull(node, ERROR_NODE_NULL);
0327 return (String) node.getProperties().get(MessageSource.class.getName() + SUFFIX_ARGS);
0328 }
0329
0330 /**
0331 * Associates an default value {@code node}.
0332 * The value will be used alongside a key to resolve a message via the application's {@code MessageSource}.
0333 *
0334 * @param node the target node on which the key will be registered.
0335 * @param defaultValue the value to be registered.
0336 *
0337 * @since 2.9.0
0338 */
0339 public static void setI18nDefaultValue(@Nonnull MenuItem node, @Nullable String defaultValue) {
0340 requireNonNull(node, ERROR_NODE_NULL);
0341 node.getProperties().put(MessageSource.class.getName() + SUFFIX_DEFAULT_VALUE, defaultValue);
0342 }
0343
0344 /**
0345 * Finds out if a {@code default value} has been registered with the target {@code Node}, returning the value if found.
0346 *
0347 * @param node the target node on which the value may have been registered.
0348 *
0349 * @return the value registered with the target {@code Node} or {@code null} if not found.
0350 *
0351 * @since 2.9.0
0352 */
0353 @Nullable
0354 public static String getI18nDefaultValue(@Nonnull MenuItem node) {
0355 requireNonNull(node, ERROR_NODE_NULL);
0356 return (String) node.getProperties().get(MessageSource.class.getName() + SUFFIX_DEFAULT_VALUE);
0357 }
0358
0359 public static void connectMessageSource(@Nonnull Object node, @Nonnull GriffonApplication application) {
0360 requireNonNull(node, ERROR_NODE_NULL);
0361 requireNonNull(application, ERROR_APPLICATION_NULL);
0362
0363 findElements(node, arg -> (arg instanceof Labeled && !isBlank(getI18nKey((Labeled) arg))) ||
0364 (arg instanceof Tab && !isBlank(getI18nKey((Tab) arg))) ||
0365 (arg instanceof MenuItem && !isBlank(getI18nKey((MenuItem) arg))))
0366 .forEach(element -> {
0367 if (element instanceof Labeled) {
0368 doConnectMessageSource((Labeled) element, application);
0369 } else if (element instanceof Tab) {
0370 doConnectMessageSource((Tab) element, application);
0371 } else if (element instanceof MenuItem) {
0372 doConnectMessageSource((MenuItem) element, application);
0373 }
0374 });
0375 }
0376
0377 private static void doConnectMessageSource(@Nonnull final Labeled node, @Nonnull final GriffonApplication application) {
0378 application.addPropertyChangeListener(PROPERTY_LOCALE, evt -> updateLabeled(node, application));
0379 updateLabeled(node, application);
0380 }
0381
0382 private static void doConnectMessageSource(@Nonnull final Tab node, @Nonnull final GriffonApplication application) {
0383 application.addPropertyChangeListener(PROPERTY_LOCALE, evt -> updateLabeled(node, application));
0384 updateLabeled(node, application);
0385 }
0386
0387 private static void doConnectMessageSource(@Nonnull final MenuItem node, @Nonnull final GriffonApplication application) {
0388 application.addPropertyChangeListener(PROPERTY_LOCALE, evt -> updateLabeled(node, application));
0389 updateLabeled(node, application);
0390 }
0391
0392 private static void updateLabeled(@Nonnull final Labeled node, @Nonnull final GriffonApplication application) {
0393 runInsideUIThread(() -> {
0394 String key = getI18nKey(node);
0395 String args = getI18nArgs(node);
0396 String defaultValue = getI18nDefaultValue(node);
0397
0398 Object[] argArray = isBlank(args) ? EMPTY_OBJECT_ARRAY : args.split(",");
0399
0400 if (isBlank(defaultValue)) {
0401 node.setText(application.getMessageSource().getMessage(key, argArray, application.getLocale()));
0402 } else {
0403 node.setText(application.getMessageSource().getMessage(key, argArray, application.getLocale(), defaultValue));
0404 }
0405 });
0406 }
0407
0408 private static void updateLabeled(@Nonnull final Tab node, @Nonnull final GriffonApplication application) {
0409 runInsideUIThread(() -> {
0410 String key = getI18nKey(node);
0411 String args = getI18nArgs(node);
0412 String defaultValue = getI18nDefaultValue(node);
0413
0414 Object[] argArray = isBlank(args) ? EMPTY_OBJECT_ARRAY : args.split(",");
0415
0416 if (isBlank(defaultValue)) {
0417 node.setText(application.getMessageSource().getMessage(key, argArray, application.getLocale()));
0418 } else {
0419 node.setText(application.getMessageSource().getMessage(key, argArray, application.getLocale(), defaultValue));
0420 }
0421 });
0422 }
0423
0424 private static void updateLabeled(@Nonnull final MenuItem node, @Nonnull final GriffonApplication application) {
0425 runInsideUIThread(() -> {
0426 String key = getI18nKey(node);
0427 String args = getI18nArgs(node);
0428 String defaultValue = getI18nDefaultValue(node);
0429
0430 Object[] argArray = isBlank(args) ? EMPTY_OBJECT_ARRAY : args.split(",");
0431
0432 if (isBlank(defaultValue)) {
0433 node.setText(application.getMessageSource().getMessage(key, argArray, application.getLocale()));
0434 } else {
0435 node.setText(application.getMessageSource().getMessage(key, argArray, application.getLocale(), defaultValue));
0436 }
0437 });
0438 }
0439
0440 /**
0441 * Associates a {@code Action} with a target {@code Node}.
0442 *
0443 * @param node the target node on which the action will be registered.
0444 * @param actionId the id of the action to be registered.
0445 *
0446 * @since 2.8.0
0447 */
0448 public static void setGriffonActionId(@Nonnull Node node, @Nonnull String actionId) {
0449 requireNonNull(node, ERROR_NODE_NULL);
0450 requireNonBlank(actionId, ERROR_ID_BLANK);
0451 node.getProperties().put(Action.class.getName(), actionId);
0452 }
0453
0454 /**
0455 * Finds out if an {@code Action} has been registered with the target {@code Node}, returning the action id if found.
0456 *
0457 * @param node the target node on which the action may have been registered.
0458 *
0459 * @return the name of the action registered with the target {@code Node} or {@code null} if not found.
0460 *
0461 * @since 2.8.0
0462 */
0463 @Nullable
0464 public static String getGriffonActionId(@Nonnull Node node) {
0465 requireNonNull(node, ERROR_NODE_NULL);
0466 return (String) node.getProperties().get(Action.class.getName());
0467 }
0468
0469 /**
0470 * Associates a {@code Action} with a target {@code MenuItem}.
0471 *
0472 * @param menuItem the target menuItem on which the action will be registered.
0473 * @param actionId the id of the action to be registered.
0474 *
0475 * @since 2.8.0
0476 */
0477 public static void setGriffonActionId(@Nonnull MenuItem menuItem, @Nonnull String actionId) {
0478 requireNonNull(menuItem, ERROR_NODE_NULL);
0479 requireNonBlank(actionId, ERROR_ID_BLANK);
0480 menuItem.getProperties().put(Action.class.getName(), actionId);
0481 }
0482
0483 /**
0484 * Finds out if an {@code Action} has been registered with the target {@code MenuItem}, returning the action id if found.
0485 *
0486 * @param menuItem the target menuItem on which the action may have been registered.
0487 *
0488 * @return the name of the action registered with the target {@code MenuItem} or {@code null} if not found.
0489 *
0490 * @since 2.8.0
0491 */
0492 @Nullable
0493 public static String getGriffonActionId(@Nonnull MenuItem menuItem) {
0494 requireNonNull(menuItem, ERROR_NODE_NULL);
0495 return (String) menuItem.getProperties().get(Action.class.getName());
0496 }
0497
0498 /**
0499 * Wraps an <tt>ObservableList</tt>, publishing updates inside the UI thread.
0500 *
0501 * @param source the <tt>ObservableList</tt> to be wrapped
0502 * @param <E> the list's parameter type.
0503 *
0504 * @return a new <tt>ObservableList</tt>
0505 *
0506 * @see GriffonFXCollections#uiThreadAwareObservableList
0507 * @since 2.6.0
0508 * @deprecated Use {@code GriffonFXCollections.uiThreadAwareObservableList} instead.
0509 */
0510 @Deprecated
0511 @Nonnull
0512 public static <E> ObservableList<E> createJavaFXThreadProxyList(@Nonnull ObservableList<E> source) {
0513 return GriffonFXCollections.uiThreadAwareObservableList(source);
0514 }
0515
0516 /**
0517 * Wraps an <tt>ObservableSet</tt>, publishing updates inside the UI thread.
0518 *
0519 * @param source the <tt>ObservableSet</tt> to be wrapped
0520 * @param <E> the set's parameter type.
0521 *
0522 * @return a new <tt>ObservableSet</tt>
0523 *
0524 * @see GriffonFXCollections#uiThreadAwareObservableSet
0525 * @since 2.9.0
0526 * @deprecated Use {@code GriffonFXCollections.uiThreadAwareObservableSet} instead.
0527 */
0528 @Deprecated
0529 @Nonnull
0530 public static <E> ObservableSet<E> createJavaFXThreadProxySet(@Nonnull ObservableSet<E> source) {
0531 return GriffonFXCollections.uiThreadAwareObservableSet(source);
0532 }
0533
0534 /**
0535 * Wraps an <tt>ObservableMap</tt>, publishing updates inside the UI thread.
0536 *
0537 * @param source the <tt>ObservableMap</tt> to be wrapped
0538 * @param <K> the type of keys maintained by the map
0539 * @param <V> the type of mapped values
0540 *
0541 * @return a new <tt>ObservableMap</tt>
0542 *
0543 * @see GriffonFXCollections#uiThreadAwareObservableMap
0544 * @since 2.9.0
0545 * @deprecated Use {@code GriffonFXCollections.uiThreadAwareObservableMap} instead.
0546 */
0547 @Deprecated
0548 @Nonnull
0549 public static <K, V> ObservableMap<K, V> createJavaFXThreadProxyMap(@Nonnull ObservableMap<K, V> source) {
0550 return GriffonFXCollections.uiThreadAwareObservableMap(source);
0551 }
0552
0553 @Nonnull
0554 @SuppressWarnings("ConstantConditions")
0555 public static <B> Property<?> extractProperty(@Nonnull B bean, @Nonnull String propertyName) {
0556 requireNonNull(bean, "Argument 'bean' must not be null");
0557 requireNonBlank(propertyName, "Argument 'propertyName' must not be null");
0558
0559 if (!propertyName.endsWith(PROPERTY_SUFFIX)) {
0560 propertyName += PROPERTY_SUFFIX;
0561 }
0562
0563 InstanceMethodInvocationException imie;
0564 try {
0565 // 1. try <columnName>Property() first
0566 return (Property<?>) invokeExactInstanceMethod(bean, propertyName);
0567 } catch (InstanceMethodInvocationException e) {
0568 imie = e;
0569 }
0570
0571 // 2. fallback to get<columnName>Property()
0572 try {
0573 return (Property<?>) invokeExactInstanceMethod(bean, getGetterName(propertyName));
0574 } catch (InstanceMethodInvocationException e) {
0575 throw imie;
0576 }
0577 }
0578
0579 public static void connectActions(@Nonnull Object node, @Nonnull GriffonController controller, @Nonnull ActionMatcher actionMatcher) {
0580 requireNonNull(node, ERROR_NODE_NULL);
0581 requireNonNull(controller, ERROR_CONTROLLER_NULL);
0582 actionMatcher = actionMatcher != null ? actionMatcher : DEFAULT_ACTION_MATCHER;
0583 ActionManager actionManager = controller.getApplication().getActionManager();
0584 for (Map.Entry<String, Action> e : actionManager.actionsFor(controller).entrySet()) {
0585 String actionName = actionManager.normalizeName(e.getKey());
0586 JavaFXAction action = (JavaFXAction) e.getValue().getToolkitAction();
0587 actionMatcher.match(node, actionName, action);
0588 }
0589 }
0590
0591 public static void connectActions(@Nonnull Object node, @Nonnull GriffonController controller) {
0592 connectActions(node, controller, DEFAULT_ACTION_MATCHER);
0593 }
0594
0595 public static void configureControl(@Nonnull Object control, @Nonnull JavaFXAction action) {
0596 if (control instanceof ButtonBase) {
0597 configure(((ButtonBase) control), action);
0598 } else if (control instanceof MenuItem) {
0599 JavaFXUtils.configure(((MenuItem) control), action);
0600 } else if (control instanceof Node) {
0601 ((Node) control).addEventHandler(ActionEvent.ACTION, wrapAction(action));
0602 } else {
0603 // does it support the onAction property?
0604 try {
0605 invokeInstanceMethod(control, "setOnAction", wrapAction(action));
0606 } catch (InstanceMethodInvocationException imie) {
0607 // ignore
0608 }
0609 }
0610 }
0611
0612 private static EventHandler<ActionEvent> wrapAction(@Nonnull final JavaFXAction action) {
0613 return event -> {
0614 if (action.isEnabled()) {
0615 action.getOnAction().handle(event);
0616 }
0617 };
0618 }
0619
0620 private static void runInsideUIThread(@Nonnull Runnable runnable) {
0621 if (Platform.isFxApplicationThread()) {
0622 runnable.run();
0623 } else {
0624 Platform.runLater(runnable);
0625 }
0626 }
0627
0628 public static String normalizeStyle(@Nonnull String style, @Nonnull String key, @Nonnull String value) {
0629 requireNonBlank(style, "Argument 'style' must not be blank");
0630 requireNonBlank(key, "Argument 'key' must not be blank");
0631 requireNonBlank(value, "Argument 'value' must not be blank");
0632
0633 int start = style.indexOf(key);
0634 if (start != -1) {
0635 int end = style.indexOf(";", start);
0636 end = end >= start ? end : style.length() - 1;
0637 style = style.substring(0, start) + style.substring(end + 1);
0638 }
0639 return style + key + ": " + value + ";";
0640 }
0641
0642 public static void configure(@Nonnull final ToggleButton control, @Nonnull final JavaFXAction action) {
0643 configure((ButtonBase) control, action);
0644
0645 action.selectedProperty().addListener((v, o, n) -> runInsideUIThread(() -> control.setSelected(n)));
0646 runInsideUIThread(() -> control.setSelected(action.isSelected()));
0647 }
0648
0649 public static void configure(@Nonnull final CheckBox control, @Nonnull final JavaFXAction action) {
0650 configure((ButtonBase) control, action);
0651
0652 action.selectedProperty().addListener((v, o, n) -> runInsideUIThread(() -> control.setSelected(n)));
0653 runInsideUIThread(() -> control.setSelected(action.isSelected()));
0654 }
0655
0656 public static void configure(@Nonnull final RadioButton control, @Nonnull final JavaFXAction action) {
0657 configure((ButtonBase) control, action);
0658
0659 action.selectedProperty().addListener((v, o, n) -> runInsideUIThread(() -> control.setSelected(n)));
0660 runInsideUIThread(() -> control.setSelected(action.isSelected()));
0661 }
0662
0663 public static void configure(@Nonnull final ButtonBase control, @Nonnull final JavaFXAction action) {
0664 requireNonNull(control, ERROR_CONTROL_NULL);
0665 requireNonNull(action, ERROR_ACTION_NULL);
0666
0667 action.onActionProperty().addListener((v, o, n) -> control.setOnAction(n));
0668 control.setOnAction(action.getOnAction());
0669
0670 action.nameProperty().addListener((v, o, n) -> runInsideUIThread(() -> control.setText(n)));
0671 runInsideUIThread(() -> control.setText(action.getName()));
0672
0673 action.descriptionProperty().addListener((v, o, n) -> setTooltip(control, n));
0674 setTooltip(control, action.getDescription());
0675
0676 action.iconProperty().addListener((v, o, n) -> setIcon(control, n));
0677 if (!isBlank(action.getIcon())) {
0678 setIcon(control, action.getIcon());
0679 }
0680
0681 action.imageProperty().addListener((v, o, n) -> setGraphic(control, n));
0682 if (null != action.getImage()) {
0683 setGraphic(control, action.getImage());
0684 }
0685
0686 action.graphicProperty().addListener((v, o, n) -> setGraphic(control, n));
0687 if (null != action.getGraphic()) {
0688 setGraphic(control, action.getGraphic());
0689 }
0690
0691 action.enabledProperty().addListener((v, o, n) -> runInsideUIThread(() -> control.setDisable(!n)));
0692 runInsideUIThread(() -> control.setDisable(!action.isEnabled()));
0693
0694 action.visibleProperty().addListener((v, o, n) -> runInsideUIThread(() -> control.setVisible(n)));
0695 runInsideUIThread(() -> control.setVisible(action.isVisible()));
0696
0697 action.styleClassProperty().addListener((v, o, n) -> {
0698 setStyleClass(control, o, true);
0699 setStyleClass(control, n);
0700 });
0701 setStyleClass(control, action.getStyleClass());
0702
0703 action.styleProperty().addListener((v, o, n) -> setStyle(control, n));
0704 setStyle(control, action.getStyle());
0705
0706 action.graphicStyleClassProperty().addListener((v, o, n) -> {
0707 setGraphicStyleClass(control, o, true);
0708 setGraphicStyleClass(control, n);
0709 });
0710 setGraphicStyleClass(control, action.getGraphicStyleClass());
0711
0712 action.graphicStyleProperty().addListener((v, o, n) -> setGraphicStyle(control, n));
0713 setGraphicStyle(control, action.getGraphicStyle());
0714 }
0715
0716 public static void configure(@Nonnull final CheckMenuItem control, @Nonnull final JavaFXAction action) {
0717 configure((MenuItem) control, action);
0718
0719 action.selectedProperty().addListener((v, o, n) -> runInsideUIThread(() -> control.setSelected(n)));
0720 runInsideUIThread(() -> control.setSelected(action.isSelected()));
0721 }
0722
0723 public static void configure(@Nonnull final RadioMenuItem control, @Nonnull final JavaFXAction action) {
0724 configure((MenuItem) control, action);
0725
0726 action.selectedProperty().addListener((v, o, n) -> runInsideUIThread(() -> control.setSelected(n)));
0727 runInsideUIThread(() -> control.setSelected(action.isSelected()));
0728 }
0729
0730 public static void configure(@Nonnull final MenuItem control, @Nonnull final JavaFXAction action) {
0731 requireNonNull(control, ERROR_CONTROL_NULL);
0732 requireNonNull(action, ERROR_ACTION_NULL);
0733
0734 action.onActionProperty().addListener((v, o, n) -> control.setOnAction(n));
0735 control.setOnAction(action.getOnAction());
0736
0737 action.nameProperty().addListener((v, o, n) -> runInsideUIThread(() -> control.setText(n)));
0738 runInsideUIThread(() -> control.setText(action.getName()));
0739
0740 action.iconProperty().addListener((v, o, n) -> setIcon(control, n));
0741 if (!isBlank(action.getIcon())) {
0742 setIcon(control, action.getIcon());
0743 }
0744
0745 action.imageProperty().addListener((v, o, n) -> setGraphic(control, n));
0746 if (null != action.getImage()) {
0747 setGraphic(control, action.getImage());
0748 }
0749
0750 action.graphicProperty().addListener((v, o, n) -> setGraphic(control, n));
0751 if (null != action.getGraphic()) {
0752 setGraphic(control, action.getGraphic());
0753 }
0754
0755 action.enabledProperty().addListener((v, o, n) -> runInsideUIThread(() -> control.setDisable(!n)));
0756 runInsideUIThread(() -> control.setDisable(!action.getEnabled()));
0757
0758 action.acceleratorProperty().addListener((v, o, n) -> runInsideUIThread(() -> control.setAccelerator(n)));
0759 runInsideUIThread(() -> control.setAccelerator(action.getAccelerator()));
0760
0761 action.visibleProperty().addListener((v, o, n) -> runInsideUIThread(() -> control.setVisible(n)));
0762 runInsideUIThread(() -> control.setVisible(action.isVisible()));
0763
0764 action.styleClassProperty().addListener((v, o, n) -> {
0765 setStyleClass(control, o, true);
0766 setStyleClass(control, n);
0767 });
0768 setStyleClass(control, action.getStyleClass());
0769
0770 action.styleProperty().addListener((v, o, n) -> setStyle(control, n));
0771 setStyle(control, action.getStyle());
0772
0773 action.graphicStyleClassProperty().addListener((v, o, n) -> {
0774 setGraphicStyleClass(control, o, true);
0775 setGraphicStyleClass(control, n);
0776 });
0777 setGraphicStyleClass(control, action.getGraphicStyleClass());
0778
0779 action.graphicStyleProperty().addListener((v, o, n) -> setGraphicStyle(control, n));
0780 setGraphicStyle(control, action.getGraphicStyle());
0781 }
0782
0783 public static void setStyle(@Nonnull Node node, @Nonnull String style) {
0784 requireNonNull(node, ERROR_CONTROL_NULL);
0785 if (isNull(style)) { return; }
0786 if (style.startsWith("&")) {
0787 // append style
0788 String nodeStyle = node.getStyle();
0789 node.setStyle(nodeStyle + (nodeStyle.endsWith(";") ? "" : ";") + style.substring(1));
0790 } else {
0791 node.setStyle(style);
0792 }
0793 }
0794
0795 public static void setStyle(@Nonnull MenuItem node, @Nonnull String style) {
0796 requireNonNull(node, ERROR_CONTROL_NULL);
0797 if (isNull(style)) { return; }
0798 if (style.startsWith("&")) {
0799 // append style
0800 String nodeStyle = node.getStyle();
0801 node.setStyle(nodeStyle + (nodeStyle.endsWith(";") ? "" : ";") + style.substring(1));
0802 } else {
0803 node.setStyle(style);
0804 }
0805 }
0806
0807 public static void setGraphicStyle(@Nonnull ButtonBase node, @Nonnull String graphicStyle) {
0808 requireNonNull(node, ERROR_CONTROL_NULL);
0809 if (isNull(graphicStyle)) { return; }
0810 if (node.getGraphic() != null) {
0811 setStyle(node.getGraphic(), graphicStyle);
0812 }
0813 }
0814
0815 public static void setGraphicStyle(@Nonnull MenuItem node, @Nonnull String graphicStyle) {
0816 requireNonNull(node, ERROR_CONTROL_NULL);
0817 if (isNull(graphicStyle)) { return; }
0818 if (node.getGraphic() != null) {
0819 setStyle(node.getGraphic(), graphicStyle);
0820 }
0821 }
0822
0823 public static void setStyleClass(@Nonnull Node node, @Nonnull String styleClass) {
0824 setStyleClass(node, styleClass, false);
0825 }
0826
0827 public static void setStyleClass(@Nonnull Node node, @Nonnull String styleClass, boolean remove) {
0828 requireNonNull(node, ERROR_CONTROL_NULL);
0829 if (isBlank(styleClass)) { return; }
0830
0831 ObservableList<String> styleClasses = node.getStyleClass();
0832 applyStyleClass(styleClass, styleClasses, remove);
0833 }
0834
0835 public static void setStyleClass(@Nonnull MenuItem node, @Nonnull String styleClass) {
0836 setStyleClass(node, styleClass, false);
0837 }
0838
0839 public static void setStyleClass(@Nonnull MenuItem node, @Nonnull String styleClass, boolean remove) {
0840 requireNonNull(node, ERROR_CONTROL_NULL);
0841 if (isBlank(styleClass)) { return; }
0842 ObservableList<String> styleClasses = node.getStyleClass();
0843 applyStyleClass(styleClass, styleClasses, remove);
0844 }
0845
0846 public static void setGraphicStyleClass(@Nonnull ButtonBase node, @Nonnull String graphicStyleClass) {
0847 setGraphicStyleClass(node, graphicStyleClass, false);
0848 }
0849
0850 public static void setGraphicStyleClass(@Nonnull ButtonBase node, @Nonnull String graphicStyleClass, boolean remove) {
0851 requireNonNull(node, ERROR_CONTROL_NULL);
0852 if (isBlank(graphicStyleClass) || node.getGraphic() == null) { return; }
0853
0854 ObservableList<String> graphicStyleClasses = node.getGraphic().getStyleClass();
0855 applyStyleClass(graphicStyleClass, graphicStyleClasses, remove);
0856 }
0857
0858 public static void setGraphicStyleClass(@Nonnull MenuItem node, @Nonnull String graphicStyleClass) {
0859 setGraphicStyleClass(node, graphicStyleClass, false);
0860 }
0861
0862 public static void setGraphicStyleClass(@Nonnull MenuItem node, @Nonnull String graphicStyleClass, boolean remove) {
0863 requireNonNull(node, ERROR_CONTROL_NULL);
0864 if (isBlank(graphicStyleClass) || node.getGraphic() == null) { return; }
0865
0866 ObservableList<String> graphicStyleClasses = node.getGraphic().getStyleClass();
0867 applyStyleClass(graphicStyleClass, graphicStyleClasses, remove);
0868 }
0869
0870 private static void applyStyleClass(String styleClass, ObservableList<String> styleClasses, boolean remove) {
0871 runInsideUIThread(() -> {
0872 String[] strings = styleClass.split("[,\\ ]");
0873 if (remove) {
0874 styleClasses.removeAll(strings);
0875 } else {
0876 Set<String> classes = new LinkedHashSet<>(styleClasses);
0877 for (String s : strings) {
0878 if (isBlank(s)) { continue; }
0879 classes.add(s.trim());
0880 }
0881 styleClasses.setAll(classes);
0882 }
0883 });
0884 }
0885
0886 public static void setTooltip(@Nonnull Control control, @Nullable String text) {
0887 runInsideUIThread(() -> {
0888 if (isBlank(text)) {
0889 return;
0890 }
0891 requireNonNull(control, ERROR_CONTROL_NULL);
0892
0893 Tooltip tooltip = control.tooltipProperty().get();
0894 if (tooltip == null) {
0895 tooltip = new Tooltip();
0896 control.tooltipProperty().set(tooltip);
0897 }
0898 tooltip.setText(text);
0899 });
0900 }
0901
0902 public static void setIcon(@Nonnull Labeled control, @Nonnull String iconUrl) {
0903 requireNonNull(control, ERROR_CONTROL_NULL);
0904 requireNonBlank(iconUrl, ERROR_ICON_BLANK);
0905
0906 Node graphicNode = resolveIcon(iconUrl);
0907 if (graphicNode != null) {
0908 runInsideUIThread(() -> control.graphicProperty().set(graphicNode));
0909 }
0910 }
0911
0912 public static void setIcon(@Nonnull MenuItem control, @Nonnull String iconUrl) {
0913 requireNonNull(control, ERROR_CONTROL_NULL);
0914 requireNonBlank(iconUrl, ERROR_ICON_BLANK);
0915
0916 Node graphicNode = resolveIcon(iconUrl);
0917 if (graphicNode != null) {
0918 runInsideUIThread(() -> control.graphicProperty().set(graphicNode));
0919 }
0920 }
0921
0922 public static void setGraphic(@Nonnull Labeled control, @Nullable Image graphic) {
0923 requireNonNull(control, ERROR_CONTROL_NULL);
0924
0925 runInsideUIThread(() -> {
0926 if (graphic != null) {
0927 Node graphicNode = new ImageView(graphic);
0928 control.graphicProperty().set(graphicNode);
0929 } else {
0930 control.graphicProperty().set(null);
0931 }
0932 });
0933 }
0934
0935 public static void setGraphic(@Nonnull MenuItem control, @Nullable Image graphic) {
0936 requireNonNull(control, ERROR_CONTROL_NULL);
0937
0938 runInsideUIThread(() -> {
0939 if (graphic != null) {
0940 Node graphicNode = new ImageView(graphic);
0941 control.graphicProperty().set(graphicNode);
0942 } else {
0943 control.graphicProperty().set(null);
0944 }
0945 });
0946 }
0947
0948 public static void setGraphic(@Nonnull Labeled control, @Nullable Node graphic) {
0949 requireNonNull(control, ERROR_CONTROL_NULL);
0950
0951 runInsideUIThread(() -> {
0952 if (graphic != null) {
0953 control.graphicProperty().set(graphic);
0954 } else {
0955 control.graphicProperty().set(null);
0956 }
0957 });
0958 }
0959
0960 public static void setGraphic(@Nonnull MenuItem control, @Nullable Node graphic) {
0961 requireNonNull(control, ERROR_CONTROL_NULL);
0962
0963 runInsideUIThread(() -> {
0964 if (graphic != null) {
0965 control.graphicProperty().set(graphic);
0966 } else {
0967 control.graphicProperty().set(null);
0968 }
0969 });
0970 }
0971
0972 @Nullable
0973 public static Node resolveIcon(@Nonnull String iconUrl) {
0974 requireNonBlank(iconUrl, ERROR_URL_BLANK);
0975
0976 if (iconUrl.contains("|")) {
0977 // assume classname|arg format
0978 return handleAsClassWithArg(iconUrl);
0979 } else {
0980 URL resource = Thread.currentThread().getContextClassLoader().getResource(iconUrl);
0981 if (resource != null) {
0982 return new ImageView(new Image(resource.toString()));
0983 }
0984 }
0985 return null;
0986 }
0987
0988 @SuppressWarnings("unchecked")
0989 private static Node handleAsClassWithArg(String str) {
0990 String[] args = str.split("\\|");
0991 if (args.length == 2) {
0992 Class<?> iconClass = null;
0993 try {
0994 iconClass = (Class<?>) JavaFXUtils.class.getClassLoader().loadClass(args[0]);
0995 } catch (ClassNotFoundException e) {
0996 throw illegalValue(str, Node.class, e);
0997 }
0998
0999 Constructor<?> constructor = null;
1000 try {
1001 constructor = iconClass.getConstructor(String.class);
1002 } catch (NoSuchMethodException e) {
1003 throw illegalValue(str, Node.class, e);
1004 }
1005
1006 try {
1007 Object o = constructor.newInstance(args[1]);
1008 if (o instanceof Node) {
1009 return (Node) o;
1010 } else if (o instanceof Image) {
1011 return new ImageView((Image) o);
1012 } else {
1013 throw illegalValue(str, Node.class);
1014 }
1015 } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
1016 throw illegalValue(str, Node.class, e);
1017 }
1018 } else {
1019 throw illegalValue(str, Node.class);
1020 }
1021 }
1022
1023 @Nullable
1024 public static Node findNode(@Nonnull Node root, @Nonnull String id) {
1025 requireNonNull(root, ERROR_ROOT_NULL);
1026 requireNonBlank(id, ERROR_ID_BLANK);
1027
1028 if (id.equals(root.getId())) { return root; }
1029
1030
1031 if (root instanceof TabPane) {
1032 TabPane parent = (TabPane) root;
1033 for (Tab child : parent.getTabs()) {
1034 if (child.getContent() != null) {
1035 Node found = findNode(child.getContent(), id);
1036 if (found != null) { return found; }
1037 }
1038 }
1039 } else if (root instanceof TitledPane) {
1040 TitledPane parent = (TitledPane) root;
1041 if (parent.getContent() != null) {
1042 Node found = findNode(parent.getContent(), id);
1043 if (found != null) { return found; }
1044 }
1045 } else if (root instanceof Accordion) {
1046 Accordion parent = (Accordion) root;
1047 for (TitledPane child : parent.getPanes()) {
1048 Node found = findNode(child, id);
1049 if (found != null) { return found; }
1050 }
1051 } else if (root instanceof SplitPane) {
1052 SplitPane parent = (SplitPane) root;
1053 for (Node child : parent.getItems()) {
1054 Node found = findNode(child, id);
1055 if (found != null) { return found; }
1056 }
1057 } else if (root instanceof ScrollPane) {
1058 ScrollPane scrollPane = (ScrollPane) root;
1059 if (scrollPane.getContent() != null) {
1060 Node found = findNode(scrollPane.getContent(), id);
1061 if (found != null) { return found; }
1062 }
1063 } else if (root instanceof ToolBar) {
1064 ToolBar toolBar = (ToolBar) root;
1065 for (Node child : toolBar.getItems()) {
1066 Node found = findNode(child, id);
1067 if (found != null) { return found; }
1068 }
1069 } else if (root instanceof ButtonBar) {
1070 ButtonBar buttonBar = (ButtonBar) root;
1071 for (Node child : buttonBar.getButtons()) {
1072 Node found = findNode(child, id);
1073 if (found != null) { return found; }
1074 }
1075 } else if (root instanceof Parent) {
1076 Parent parent = (Parent) root;
1077 for (Node child : parent.getChildrenUnmodifiable()) {
1078 Node found = findNode(child, id);
1079 if (found != null) { return found; }
1080 }
1081 }
1082
1083 return null;
1084 }
1085
1086 @Nullable
1087 public static Object findElement(@Nonnull Object root, @Nonnull String id) {
1088 requireNonNull(root, ERROR_ROOT_NULL);
1089 requireNonBlank(id, ERROR_ID_BLANK);
1090
1091 if (id.equals(getPropertyValue(root, "id"))) { return root; }
1092
1093 if (root instanceof Control) {
1094 Control control = (Control) root;
1095 ContextMenu contextMenu = control.getContextMenu();
1096 if (contextMenu != null) {
1097 Object found = findElement(contextMenu, id);
1098 if (found != null) {return found;}
1099 }
1100 Tooltip tooltip = control.getTooltip();
1101 if (tooltip != null) {
1102 Object found = findElement(tooltip, id);
1103 if (found != null) {return found;}
1104 }
1105 }
1106
1107 if (root instanceof ButtonBar) {
1108 ButtonBar buttonBar = (ButtonBar) root;
1109 for (Node child : buttonBar.getButtons()) {
1110 Object found = findElement(child, id);
1111 if (found != null) { return found; }
1112 }
1113 } else if (root instanceof MenuBar) {
1114 MenuBar menuBar = (MenuBar) root;
1115 for (Menu child : menuBar.getMenus()) {
1116 Object found = findElement(child, id);
1117 if (found != null) { return found; }
1118 }
1119 } else if (root instanceof ContextMenu) {
1120 ContextMenu contextMenu = (ContextMenu) root;
1121 for (MenuItem child : contextMenu.getItems()) {
1122 Object found = findElement(child, id);
1123 if (found != null) { return found; }
1124 }
1125 } else if (root instanceof Menu) {
1126 Menu menu = (Menu) root;
1127 for (MenuItem child : menu.getItems()) {
1128 Object found = findElement(child, id);
1129 if (found != null) { return found; }
1130 }
1131 } else if (root instanceof TabPane) {
1132 TabPane tabPane = (TabPane) root;
1133 for (Tab child : tabPane.getTabs()) {
1134 Object found = findElement(child, id);
1135 if (found != null) { return found; }
1136 }
1137 } else if (root instanceof Tab) {
1138 Tab tab = (Tab) root;
1139 if (tab.getContent() != null) {
1140 Object found = findElement(tab.getContent(), id);
1141 if (found != null) { return found; }
1142 }
1143 } else if (root instanceof TitledPane) {
1144 TitledPane parent = (TitledPane) root;
1145 if (parent.getContent() != null) {
1146 Object found = findElement(parent.getContent(), id);
1147 if (found != null) { return found; }
1148 }
1149 } else if (root instanceof Accordion) {
1150 Accordion parent = (Accordion) root;
1151 for (TitledPane child : parent.getPanes()) {
1152 Object found = findElement(child, id);
1153 if (found != null) { return found; }
1154 }
1155 } else if (root instanceof SplitPane) {
1156 SplitPane parent = (SplitPane) root;
1157 for (Node child : parent.getItems()) {
1158 Object found = findElement(child, id);
1159 if (found != null) { return found; }
1160 }
1161 } else if (root instanceof ScrollPane) {
1162 ScrollPane scrollPane = (ScrollPane) root;
1163 if (scrollPane.getContent() != null) {
1164 Object found = findElement(scrollPane.getContent(), id);
1165 if (found != null) { return found; }
1166 }
1167 } else if (root instanceof ToolBar) {
1168 ToolBar toolBar = (ToolBar) root;
1169 for (Node child : toolBar.getItems()) {
1170 Object found = findElement(child, id);
1171 if (found != null) { return found; }
1172 }
1173 } else if (root instanceof Parent) {
1174 Parent parent = (Parent) root;
1175 for (Node child : parent.getChildrenUnmodifiable()) {
1176 Object found = findElement(child, id);
1177 if (found != null) { return found; }
1178 }
1179 }
1180
1181 return null;
1182 }
1183
1184 @Nullable
1185 public static Object findElement(@Nonnull Object root, @Nonnull Predicate<Object> predicate) {
1186 requireNonNull(root, ERROR_ROOT_NULL);
1187 requireNonNull(predicate, ERROR_PREDICATE_NULL);
1188
1189 if (predicate.test(root)) {
1190 return root;
1191 }
1192
1193 if (root instanceof Control) {
1194 Control control = (Control) root;
1195 ContextMenu contextMenu = control.getContextMenu();
1196 if (contextMenu != null) {
1197 Object found = findElement(contextMenu, predicate);
1198 if (found != null) {return found;}
1199 }
1200 Tooltip tooltip = control.getTooltip();
1201 if (tooltip != null) {
1202 Object found = findElement(tooltip, predicate);
1203 if (found != null) {return found;}
1204 }
1205 }
1206
1207 if (root instanceof ButtonBar) {
1208 ButtonBar buttonBar = (ButtonBar) root;
1209 for (Node child : buttonBar.getButtons()) {
1210 Object found = findElement(child, predicate);
1211 if (found != null) { return found; }
1212 }
1213 } else if (root instanceof MenuBar) {
1214 MenuBar menuBar = (MenuBar) root;
1215 for (Menu child : menuBar.getMenus()) {
1216 Object found = findElement(child, predicate);
1217 if (found != null) { return found; }
1218 }
1219 } else if (root instanceof ContextMenu) {
1220 ContextMenu contextMenu = (ContextMenu) root;
1221 for (MenuItem child : contextMenu.getItems()) {
1222 Object found = findElement(child, predicate);
1223 if (found != null) { return found; }
1224 }
1225 } else if (root instanceof Menu) {
1226 Menu menu = (Menu) root;
1227 for (MenuItem child : menu.getItems()) {
1228 Object found = findElement(child, predicate);
1229 if (found != null) { return found; }
1230 }
1231 } else if (root instanceof TabPane) {
1232 TabPane tabPane = (TabPane) root;
1233 for (Tab child : tabPane.getTabs()) {
1234 Object found = findElement(child, predicate);
1235 if (found != null) { return found; }
1236 }
1237 } else if (root instanceof Tab) {
1238 Tab tab = (Tab) root;
1239 if (tab.getContent() != null) {
1240 Object found = findElement(tab.getContent(), predicate);
1241 if (found != null) { return found; }
1242 }
1243 } else if (root instanceof TitledPane) {
1244 TitledPane parent = (TitledPane) root;
1245 if (parent.getContent() != null) {
1246 Object found = findElement(parent.getContent(), predicate);
1247 if (found != null) { return found; }
1248 }
1249 } else if (root instanceof Accordion) {
1250 Accordion parent = (Accordion) root;
1251 for (TitledPane child : parent.getPanes()) {
1252 Object found = findElement(child, predicate);
1253 if (found != null) { return found; }
1254 }
1255 } else if (root instanceof SplitPane) {
1256 SplitPane parent = (SplitPane) root;
1257 for (Node child : parent.getItems()) {
1258 Object found = findElement(child, predicate);
1259 if (found != null) { return found; }
1260 }
1261 } else if (root instanceof ScrollPane) {
1262 ScrollPane scrollPane = (ScrollPane) root;
1263 if (scrollPane.getContent() != null) {
1264 Object found = findElement(scrollPane.getContent(), predicate);
1265 if (found != null) { return found; }
1266 }
1267 } else if (root instanceof ToolBar) {
1268 ToolBar toolBar = (ToolBar) root;
1269 for (Node child : toolBar.getItems()) {
1270 Object found = findElement(child, predicate);
1271 if (found != null) { return found; }
1272 }
1273 } else if (root instanceof Parent) {
1274 Parent parent = (Parent) root;
1275 for (Node child : parent.getChildrenUnmodifiable()) {
1276 Object found = findElement(child, predicate);
1277 if (found != null) { return found; }
1278 }
1279 }
1280
1281 return null;
1282 }
1283
1284 @Nonnull
1285 public static Collection<Object> findElements(@Nonnull Object root, @Nonnull Predicate<Object> predicate) {
1286 Set<Object> accumulator = new LinkedHashSet<>();
1287 findElements(root, predicate, accumulator);
1288 return accumulator;
1289 }
1290
1291 private static void findElements(@Nonnull Object root, @Nonnull Predicate<Object> predicate, @Nonnull Collection<Object> accumulator) {
1292 requireNonNull(root, ERROR_ROOT_NULL);
1293 requireNonNull(predicate, ERROR_PREDICATE_NULL);
1294
1295 if (predicate.test(root)) {
1296 accumulator.add(root);
1297 }
1298
1299 if (root instanceof Control) {
1300 Control control = (Control) root;
1301 ContextMenu contextMenu = control.getContextMenu();
1302 if (contextMenu != null) {
1303 findElements(contextMenu, predicate, accumulator);
1304 }
1305 Tooltip tooltip = control.getTooltip();
1306 if (tooltip != null) {
1307 findElements(tooltip, predicate, accumulator);
1308 }
1309 }
1310
1311 if (root instanceof ButtonBar) {
1312 ButtonBar buttonBar = (ButtonBar) root;
1313 for (Node child : buttonBar.getButtons()) {
1314 findElements(child, predicate, accumulator);
1315 }
1316 } else if (root instanceof MenuBar) {
1317 MenuBar menuBar = (MenuBar) root;
1318 for (Menu child : menuBar.getMenus()) {
1319 findElements(child, predicate, accumulator);
1320 }
1321 } else if (root instanceof ContextMenu) {
1322 ContextMenu contextMenu = (ContextMenu) root;
1323 for (MenuItem child : contextMenu.getItems()) {
1324 findElements(child, predicate, accumulator);
1325 }
1326 } else if (root instanceof Menu) {
1327 Menu menu = (Menu) root;
1328 for (MenuItem child : menu.getItems()) {
1329 findElements(child, predicate, accumulator);
1330 }
1331 } else if (root instanceof TabPane) {
1332 TabPane tabPane = (TabPane) root;
1333 for (Tab child : tabPane.getTabs()) {
1334 findElements(child, predicate, accumulator);
1335 }
1336 } else if (root instanceof Tab) {
1337 Tab tab = (Tab) root;
1338 if (tab.getContent() != null) {
1339 findElements(tab.getContent(), predicate, accumulator);
1340 }
1341 } else if (root instanceof TitledPane) {
1342 TitledPane parent = (TitledPane) root;
1343 if (parent.getContent() != null) {
1344 findElements(parent.getContent(), predicate, accumulator);
1345 }
1346 } else if (root instanceof Accordion) {
1347 Accordion parent = (Accordion) root;
1348 for (TitledPane child : parent.getPanes()) {
1349 findElements(child, predicate, accumulator);
1350 }
1351 } else if (root instanceof SplitPane) {
1352 SplitPane parent = (SplitPane) root;
1353 for (Node child : parent.getItems()) {
1354 findElements(child, predicate, accumulator);
1355 }
1356 } else if (root instanceof ScrollPane) {
1357 ScrollPane scrollPane = (ScrollPane) root;
1358 if (scrollPane.getContent() != null) {
1359 findElements(scrollPane.getContent(), predicate, accumulator);
1360 }
1361 } else if (root instanceof ToolBar) {
1362 ToolBar toolBar = (ToolBar) root;
1363 for (Node child : toolBar.getItems()) {
1364 findElements(child, predicate, accumulator);
1365 }
1366 } else if (root instanceof Parent) {
1367 Parent parent = (Parent) root;
1368 for (Node child : parent.getChildrenUnmodifiable()) {
1369 findElements(child, predicate, accumulator);
1370 }
1371 }
1372 }
1373
1374 @Nullable
1375 public static Window getWindowAncestor(@Nonnull Object node) {
1376 requireNonNull(node, ERROR_NODE_NULL);
1377
1378 if (node instanceof Window) {
1379 return (Window) node;
1380 } else if (node instanceof Scene) {
1381 return ((Scene) node).getWindow();
1382 } else if (node instanceof Node) {
1383 Scene scene = ((Node) node).getScene();
1384 if (scene != null) {
1385 return scene.getWindow();
1386 }
1387 } else if (node instanceof Tab) {
1388 TabPane tabPane = ((Tab) node).getTabPane();
1389 if (tabPane != null) {
1390 return getWindowAncestor(tabPane);
1391 }
1392 }
1393
1394 return null;
1395 }
1396
1397 private static ValueConversionException illegalValue(Object value, Class<?> klass) {
1398 throw new ValueConversionException(value, klass);
1399 }
1400
1401 private static ValueConversionException illegalValue(Object value, Class<?> klass, Exception e) {
1402 throw new ValueConversionException(value, klass, e);
1403 }
1404 }
|