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