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