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