1 /* 2 * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.control; 27 28 import java.lang.ref.WeakReference; 29 import java.lang.reflect.Constructor; 30 import java.lang.reflect.InvocationTargetException; 31 import java.util.ArrayList; 32 import java.util.Collections; 33 import java.util.List; 34 35 import com.sun.javafx.scene.control.ControlAcceleratorSupport; 36 import javafx.application.Application; 37 import javafx.beans.property.ObjectProperty; 38 import javafx.beans.property.ObjectPropertyBase; 39 import javafx.beans.property.SimpleObjectProperty; 40 import javafx.beans.property.StringProperty; 41 import javafx.beans.value.WritableValue; 42 import javafx.collections.ObservableList; 43 import javafx.css.CssParser; 44 import javafx.event.EventHandler; 45 import javafx.scene.AccessibleAction; 46 import javafx.scene.AccessibleAttribute; 47 import javafx.scene.Node; 48 import javafx.scene.input.ContextMenuEvent; 49 import javafx.scene.layout.Region; 50 import com.sun.javafx.application.PlatformImpl; 51 import javafx.css.CssMetaData; 52 import com.sun.javafx.css.StyleManager; 53 import javafx.css.StyleableObjectProperty; 54 import javafx.css.StyleableStringProperty; 55 import javafx.css.converter.StringConverter; 56 import com.sun.javafx.scene.control.Logging; 57 import javafx.css.Styleable; 58 import javafx.css.StyleableProperty; 59 import sun.util.logging.PlatformLogger; 60 import sun.util.logging.PlatformLogger.Level; 61 62 63 /** 64 * Base class for all user interface controls. A "Control" is a node in the 65 * scene graph which can be manipulated by the user. Controls provide 66 * additional variables and behaviors beyond those of Node to support 67 * common user interactions in a manner which is consistent and predictable 68 * for the user. 69 * <p> 70 * Additionally, controls support explicit skinning to make it easy to 71 * leverage the functionality of a control while customizing its appearance. 72 * <p> 73 * See specific Control subclasses for information on how to use individual 74 * types of controls. 75 * <p> Most controls have their focusTraversable property set to true by default, however 76 * read-only controls such as {@link Label} and {@link ProgressIndicator}, and some 77 * controls that are containers {@link ScrollPane} and {@link ToolBar} do not. 78 * Consult individual control documentation for details. 79 * @since JavaFX 2.0 80 */ 81 public abstract class Control extends Region implements Skinnable { 82 83 static { 84 // Ensures that the default application user agent stylesheet is loaded 85 if (Application.getUserAgentStylesheet() == null) { 86 PlatformImpl.setDefaultPlatformUserAgentStylesheet(); 87 } 88 } 89 90 /** 91 * Utility for loading a class in a manner that will work with multiple 92 * class loaders, as is typically found in OSGI modular applications. 93 * In particular, this method will attempt to just load the class 94 * identified by className. If that fails, it attempts to load the 95 * class using the current thread's context class loader. If that fails, 96 * it attempts to use the class loader of the supplied "instance", and 97 * if it still fails it walks up the class hierarchy of the instance 98 * and attempts to use the class loader of each class in the super-type 99 * hierarchy. 100 * 101 * @param className The name of the class we want to load 102 * @param instance An optional instance used to help find the class to load 103 * @return The class. Cannot return null 104 * @throws ClassNotFoundException If the class cannot be found using any technique. 105 */ 106 private static Class<?> loadClass(final String className, final Object instance) 107 throws ClassNotFoundException 108 { 109 try { 110 // Try just loading the class 111 return Class.forName(className, false, Control.class.getClassLoader()); 112 } catch (ClassNotFoundException ex) { 113 // RT-17525 : Use context class loader only if Class.forName fails. 114 if (Thread.currentThread().getContextClassLoader() != null) { 115 try { 116 final ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 117 return Class.forName(className, false, ccl); 118 } catch (ClassNotFoundException ex2) { 119 // Do nothing, just fall through 120 } 121 } 122 123 // RT-14177: Try looking up the class using the class loader of the 124 // current class, walking up the list of superclasses 125 // and checking each of them, before bailing and using 126 // the context class loader. 127 if (instance != null) { 128 Class<?> currentType = instance.getClass(); 129 while (currentType != null) { 130 try { 131 final ClassLoader loader = currentType.getClassLoader(); 132 return Class.forName(className, false, loader); 133 } catch (ClassNotFoundException ex2) { 134 currentType = currentType.getSuperclass(); 135 } 136 } 137 } 138 139 // We failed to find the class using any of the above means, so we're going 140 // to just throw the ClassNotFoundException that we caught earlier 141 throw ex; 142 } 143 } 144 145 /*************************************************************************** 146 * * 147 * Private fields * 148 * * 149 **************************************************************************/ 150 151 private List<CssMetaData<? extends Styleable, ?>> styleableProperties; 152 153 /** 154 * A private reference directly to the SkinBase instance that is used as the 155 * Skin for this Control. A Control's Skin doesn't have to be of type 156 * SkinBase, although 98% of the time or greater it probably will be. 157 * Because instanceof checks and reading a value from a property are 158 * not cheap (on interpreters on slower hardware or mobile devices) 159 * it pays to have a direct reference here to the skinBase. We simply 160 * need to check this variable -- if it is not null then we know the 161 * Skin is a SkinBase and this is a direct reference to it. If it is null 162 * then we know the skin is not a SkinBase and we need to call getSkin(). 163 */ 164 private SkinBase<?> skinBase; 165 166 /*************************************************************************** 167 * * 168 * Event Handlers / Listeners * 169 * * 170 **************************************************************************/ 171 172 /** 173 * Handles context menu requests by popping up the menu. 174 * Note that we use this pattern to remove some of the anonymous inner 175 * classes which we'd otherwise have to create. When lambda expressions 176 * are supported, we could do it that way instead (or use MethodHandles). 177 */ 178 private final static EventHandler<ContextMenuEvent> contextMenuHandler = event -> { 179 if (event.isConsumed()) return; 180 181 // If a context menu was shown, consume the event to prevent multiple context menus 182 Object source = event.getSource(); 183 if (source instanceof Control) { 184 Control c = (Control) source; 185 if (c.getContextMenu() != null) { 186 c.getContextMenu().show(c, event.getScreenX(), event.getScreenY()); 187 event.consume(); 188 } 189 } 190 }; 191 192 193 194 /*************************************************************************** 195 * * 196 * Properties * 197 * * 198 **************************************************************************/ 199 200 201 202 // --- skin 203 /** 204 * Skin is responsible for rendering this {@code Control}. From the 205 * perspective of the {@code Control}, the {@code Skin} is a black box. 206 * It listens and responds to changes in state in a {@code Control}. 207 * <p> 208 * There is a one-to-one relationship between a {@code Control} and its 209 * {@code Skin}. Every {@code Skin} maintains a back reference to the 210 * {@code Control} via the {@link Skin#getSkinnable()} method. 211 * <p> 212 * A skin may be null. 213 */ 214 @Override public final ObjectProperty<Skin<?>> skinProperty() { return skin; } 215 @Override public final void setSkin(Skin<?> value) { 216 skinProperty().set(value); 217 } 218 @Override public final Skin<?> getSkin() { return skinProperty().getValue(); } 219 private ObjectProperty<Skin<?>> skin = new StyleableObjectProperty<Skin<?>>() { 220 // We store a reference to the oldValue so that we can handle 221 // changes in the skin properly in the case of binding. This is 222 // only needed because invalidated() does not currently take 223 // a reference to the old value. 224 private Skin<?> oldValue; 225 226 @Override 227 //This code is basically a kind of optimization that prevents a Skin that is equal but not instance equal. 228 //Although it's not kosher from the property perspective (bindings won't pass through set), it should not do any harm. 229 //But it should be evaluated in the future. 230 public void set(Skin<?> v) { 231 if (v == null 232 ? oldValue == null 233 : oldValue != null && v.getClass().equals(oldValue.getClass())) 234 return; 235 236 super.set(v); 237 } 238 239 @Override protected void invalidated() { 240 Skin<?> skin = get(); 241 // Collect the name of the currently installed skin class. We do this 242 // so that subsequent updates from CSS to the same skin class will not 243 // result in reinstalling the skin 244 currentSkinClassName = skin == null ? null : skin.getClass().getName(); 245 246 // if someone calls setSkin, we need to make it look like they 247 // called set on skinClassName in order to keep CSS from overwriting 248 // the skin. 249 skinClassNameProperty().set(currentSkinClassName); 250 251 252 // Dispose of the old skin 253 if (oldValue != null) oldValue.dispose(); 254 255 // Get the new value, and save it off as the new oldValue 256 oldValue = skin; 257 258 // Reset skinBase to null - it will be set to the new Skin if it 259 // is a SkinBase, otherwise it will remain null, as expected 260 skinBase = null; 261 262 // We have two paths, one for "legacy" Skins, and one for 263 // any Skin which extends from SkinBase. Legacy Skins will 264 // produce a single node which will be the only child of 265 // the Control via the getNode() method on the Skin. A 266 // SkinBase will manipulate the children of the Control 267 // directly. Further, we maintain a direct reference to 268 // the skinBase for more optimal updates later. 269 if (skin instanceof SkinBase) { 270 // record a reference of the skin, if it is a SkinBase, for 271 // performance reasons 272 skinBase = (SkinBase<?>) skin; 273 // Note I do not remove any children here, because the 274 // skin will have already configured all the children 275 // by the time setSkin has been called. This is because 276 // our Skin interface was lacking an initialize method (doh!) 277 // and so the Skin constructor is where it adds listeners 278 // and so forth. For SkinBase implementations, the 279 // constructor is also where it will take ownership of 280 // the children. 281 } else { 282 final Node n = getSkinNode(); 283 if (n != null) { 284 getChildren().setAll(n); 285 } else { 286 getChildren().clear(); 287 } 288 } 289 290 // clear out the styleable properties so that the list is rebuilt 291 // next time they are requested. 292 styleableProperties = null; 293 294 // calling impl_reapplyCSS() as the styleable properties may now 295 // be different, as we will now be able to return styleable properties 296 // belonging to the skin. If impl_reapplyCSS() is not called, the 297 // getCssMetaData() method is never called, so the 298 // skin properties are never exposed. 299 impl_reapplyCSS(); 300 301 // DEBUG: Log that we've changed the skin 302 final PlatformLogger logger = Logging.getControlsLogger(); 303 if (logger.isLoggable(Level.FINEST)) { 304 logger.finest("Stored skin[" + getValue() + "] on " + this); 305 } 306 } 307 308 // This method should be CssMetaData<Control,Skin> getCssMetaData(), 309 // but SKIN is CssMetaData<Control,String>. This does not matter to 310 // the CSS code which doesn't care about the actual type. Hence, 311 // we'll suppress the warnings 312 @Override @SuppressWarnings({"unchecked", "rawtype"}) 313 public CssMetaData getCssMetaData() { 314 return StyleableProperties.SKIN; 315 } 316 317 @Override 318 public Object getBean() { 319 return Control.this; 320 } 321 322 @Override 323 public String getName() { 324 return "skin"; 325 } 326 }; 327 328 329 // --- tooltip 330 /** 331 * The ToolTip for this control. 332 */ 333 public final ObjectProperty<Tooltip> tooltipProperty() { 334 if (tooltip == null) { 335 tooltip = new ObjectPropertyBase<Tooltip>() { 336 private Tooltip old = null; 337 @Override protected void invalidated() { 338 Tooltip t = get(); 339 // install / uninstall 340 if (t != old) { 341 if (old != null) { 342 Tooltip.uninstall(Control.this, old); 343 } 344 if (t != null) { 345 Tooltip.install(Control.this, t); 346 } 347 old = t; 348 } 349 } 350 351 @Override 352 public Object getBean() { 353 return Control.this; 354 } 355 356 @Override 357 public String getName() { 358 return "tooltip"; 359 } 360 }; 361 } 362 return tooltip; 363 } 364 private ObjectProperty<Tooltip> tooltip; 365 public final void setTooltip(Tooltip value) { tooltipProperty().setValue(value); } 366 public final Tooltip getTooltip() { return tooltip == null ? null : tooltip.getValue(); } 367 368 369 // --- context menu 370 /** 371 * The ContextMenu to show for this control. 372 */ 373 private ObjectProperty<ContextMenu> contextMenu = new SimpleObjectProperty<ContextMenu>(this, "contextMenu") { 374 private WeakReference<ContextMenu> contextMenuRef; 375 376 @Override protected void invalidated() { 377 ContextMenu oldMenu = contextMenuRef == null ? null : contextMenuRef.get(); 378 if (oldMenu != null) { 379 ControlAcceleratorSupport.removeAcceleratorsFromScene(oldMenu.getItems(), Control.this); 380 } 381 382 ContextMenu ctx = get(); 383 contextMenuRef = new WeakReference<>(ctx); 384 385 if (ctx != null) { 386 // set this flag so contextmenu show will be relative to parent window not anchor 387 ctx.setShowRelativeToWindow(true); //RT-15160 388 389 // if a context menu is set, we need to install any accelerators 390 // belonging to its menu items ASAP into the scene that this 391 // Control is in (if the control is not in a Scene, we will need 392 // to wait until it is and then do it). 393 ControlAcceleratorSupport.addAcceleratorsIntoScene(ctx.getItems(), Control.this); 394 } 395 } 396 }; 397 public final ObjectProperty<ContextMenu> contextMenuProperty() { return contextMenu; } 398 public final void setContextMenu(ContextMenu value) { contextMenu.setValue(value); } 399 public final ContextMenu getContextMenu() { return contextMenu == null ? null : contextMenu.getValue(); } 400 401 402 403 /*************************************************************************** 404 * * 405 * Constructors * 406 * * 407 **************************************************************************/ 408 409 /** 410 * Create a new Control. 411 */ 412 protected Control() { 413 // focusTraversable is styleable through css. Calling setFocusTraversable 414 // makes it look to css like the user set the value and css will not 415 // override. Initializing focusTraversable by calling applyStyle 416 // with null for StyleOrigin ensures that css will be able to override 417 // the value. 418 final StyleableProperty<Boolean> prop = (StyleableProperty<Boolean>)(WritableValue<Boolean>)focusTraversableProperty(); 419 prop.applyStyle(null, Boolean.TRUE); 420 421 // we add a listener for menu request events to show the context menu 422 // that may be set on the Control 423 this.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, contextMenuHandler); 424 425 // TODO re-enable when InputMap moves back to Node / Control 426 // // Most controls need an input map, so we set this to be non-null in 427 // // Control to save people from running into NPEs. 428 // setInputMap(new InputMap(this)); 429 } 430 431 432 433 /*************************************************************************** 434 * * 435 * Public API * 436 * * 437 **************************************************************************/ 438 439 // Proposed dispose() API. 440 // Note that there is impl code for a dispose method in TableRowSkinBase 441 // and TableCell (just search for dispose()) 442 // public void dispose() { 443 // Skin skin = getSkin(); 444 // if (skin != null) { 445 // skin.dispose(); 446 // } 447 // } 448 449 /** 450 * Returns <code>true</code> since all Controls are resizable. 451 * @return whether this node can be resized by its parent during layout 452 */ 453 @Override public boolean isResizable() { 454 return true; 455 } 456 457 // Implementation of the Resizable interface. 458 // Because only the skin can know the min, pref, and max sizes, these 459 // functions are implemented to delegate to skin. If there is no skin then 460 // we simply return 0 for all the values since a Control without a Skin 461 // doesn't render 462 /** 463 * Computes the minimum allowable width of the Control, based on the provided 464 * height. The minimum width is not calculated within the Control, instead 465 * the calculation is delegated to the {@link Node#minWidth(double)} method 466 * of the {@link Skin}. If the Skin is null, the returned value is 0. 467 * 468 * @param height The height of the Control, in case this value might dictate 469 * the minimum width. 470 * @return A double representing the minimum width of this control. 471 */ 472 @Override protected double computeMinWidth(final double height) { 473 if (skinBase != null) { 474 return skinBase.computeMinWidth(height, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 475 } else { 476 final Node skinNode = getSkinNode(); 477 return skinNode == null ? 0 : skinNode.minWidth(height); 478 } 479 } 480 481 /** 482 * Computes the minimum allowable height of the Control, based on the provided 483 * width. The minimum height is not calculated within the Control, instead 484 * the calculation is delegated to the {@link Node#minHeight(double)} method 485 * of the {@link Skin}. If the Skin is null, the returned value is 0. 486 * 487 * @param width The width of the Control, in case this value might dictate 488 * the minimum height. 489 * @return A double representing the minimum height of this control. 490 */ 491 @Override protected double computeMinHeight(final double width) { 492 if (skinBase != null) { 493 return skinBase.computeMinHeight(width, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 494 } else { 495 final Node skinNode = getSkinNode(); 496 return skinNode == null ? 0 : skinNode.minHeight(width); 497 } 498 } 499 500 /** 501 * Computes the maximum allowable width of the Control, based on the provided 502 * height. The maximum width is not calculated within the Control, instead 503 * the calculation is delegated to the {@link Node#maxWidth(double)} method 504 * of the {@link Skin}. If the Skin is null, the returned value is 0. 505 * 506 * @param height The height of the Control, in case this value might dictate 507 * the maximum width. 508 * @return A double representing the maximum width of this control. 509 */ 510 @Override protected double computeMaxWidth(double height) { 511 if (skinBase != null) { 512 return skinBase.computeMaxWidth(height, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 513 } else { 514 final Node skinNode = getSkinNode(); 515 return skinNode == null ? 0 : skinNode.maxWidth(height); 516 } 517 } 518 519 /** 520 * Computes the maximum allowable height of the Control, based on the provided 521 * width. The maximum height is not calculated within the Control, instead 522 * the calculation is delegated to the {@link Node#maxHeight(double)} method 523 * of the {@link Skin}. If the Skin is null, the returned value is 0. 524 * 525 * @param width The width of the Control, in case this value might dictate 526 * the maximum height. 527 * @return A double representing the maximum height of this control. 528 */ 529 @Override protected double computeMaxHeight(double width) { 530 if (skinBase != null) { 531 return skinBase.computeMaxHeight(width, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 532 } else { 533 final Node skinNode = getSkinNode(); 534 return skinNode == null ? 0 : skinNode.maxHeight(width); 535 } 536 } 537 538 /** {@inheritDoc} */ 539 @Override protected double computePrefWidth(double height) { 540 if (skinBase != null) { 541 return skinBase.computePrefWidth(height, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 542 } else { 543 final Node skinNode = getSkinNode(); 544 return skinNode == null ? 0 : skinNode.prefWidth(height); 545 } 546 } 547 548 /** {@inheritDoc} */ 549 @Override protected double computePrefHeight(double width) { 550 if (skinBase != null) { 551 return skinBase.computePrefHeight(width, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 552 } else { 553 final Node skinNode = getSkinNode(); 554 return skinNode == null ? 0 : skinNode.prefHeight(width); 555 } 556 } 557 558 /** {@inheritDoc} */ 559 @Override public double getBaselineOffset() { 560 if (skinBase != null) { 561 return skinBase.computeBaselineOffset(snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 562 } else { 563 final Node skinNode = getSkinNode(); 564 return skinNode == null ? 0 : skinNode.getBaselineOffset(); 565 } 566 } 567 568 /*************************************************************************** 569 * Implementation of layout bounds for the Control. We want to preserve * 570 * the lazy semantics of layout bounds. So whenever the width/height * 571 * changes on the node, we end up invalidating layout bounds. We then * 572 * recompute it on demand. * 573 **************************************************************************/ 574 575 /** {@inheritDoc} */ 576 @Override protected void layoutChildren() { 577 if (skinBase != null) { 578 final double x = snappedLeftInset(); 579 final double y = snappedTopInset(); 580 final double w = snapSizeX(getWidth()) - x - snappedRightInset(); 581 final double h = snapSizeY(getHeight()) - y - snappedBottomInset(); 582 skinBase.layoutChildren(x, y, w, h); 583 } else { 584 Node n = getSkinNode(); 585 if (n != null) { 586 n.resizeRelocate(0, 0, getWidth(), getHeight()); 587 } 588 } 589 } 590 591 /*************************************************************************** 592 * Forward the following to the skin * 593 **************************************************************************/ 594 595 /** 596 * Create a new instance of the default skin for this control. This is called to create a skin for the control if 597 * no skin is provided via CSS {@code -fx-skin} or set explicitly in a sub-class with {@code setSkin(...)}. 598 * 599 * @return new instance of default skin for this control. If null then the control will have no skin unless one 600 * is provided by css. 601 * @since JavaFX 8.0 602 */ 603 protected Skin<?> createDefaultSkin() { 604 return null; 605 } 606 607 /*************************************************************************** 608 * * 609 * Package API for SkinBase * 610 * * 611 **************************************************************************/ 612 613 // package private for SkinBase 614 ObservableList<Node> getControlChildren() { 615 return getChildren(); 616 } 617 618 619 /*************************************************************************** 620 * * 621 * Private implementation * 622 * * 623 **************************************************************************/ 624 625 /** 626 * Gets the Skin's node, or returns null if there is no Skin. 627 * Convenience method for getting the node of the skin. This is null-safe, 628 * meaning if skin is null then it will return null instead of throwing 629 * a NullPointerException. 630 * 631 * @return The Skin's node, or null. 632 */ 633 private Node getSkinNode() { 634 assert skinBase == null; 635 Skin<?> skin = getSkin(); 636 return skin == null ? null : skin.getNode(); 637 } 638 639 /** 640 * Keeps a reference to the name of the class currently acting as the skin. 641 */ 642 private String currentSkinClassName = null; 643 private StringProperty skinClassName; 644 645 /** 646 * @treatAsPrivate 647 * @since JavaFX 2.1 648 */ 649 @Deprecated protected StringProperty skinClassNameProperty() { 650 if (skinClassName == null) { 651 skinClassName = new StyleableStringProperty() { 652 653 @Override 654 public void set(String v) { 655 // do not allow the skin to be set to null through CSS 656 if (v == null || v.isEmpty() || v.equals(get())) return; 657 super.set(v); 658 } 659 660 @Override 661 public void invalidated() { 662 663 if (get() != null) { 664 if (!get().equals(currentSkinClassName)) { 665 loadSkinClass(Control.this, skinClassName.get()); 666 } 667 // Note: CSS should not set skin to null 668 } 669 } 670 671 @Override 672 public Object getBean() { 673 return Control.this; 674 } 675 676 @Override 677 public String getName() { 678 return "skinClassName"; 679 } 680 681 @Override 682 public CssMetaData<Control,String> getCssMetaData() { 683 return StyleableProperties.SKIN; 684 } 685 686 }; 687 } 688 return skinClassName; 689 } 690 691 static void loadSkinClass(final Skinnable control, final String skinClassName) { 692 if (skinClassName == null || skinClassName.isEmpty()) { 693 final String msg = 694 "Empty -fx-skin property specified for control " + control; 695 final List<CssParser.ParseError> errors = StyleManager.getErrors(); 696 if (errors != null) { 697 CssParser.ParseError error = new CssParser.ParseError(msg); 698 errors.add(error); // RT-19884 699 } 700 Logging.getControlsLogger().severe(msg); 701 return; 702 } 703 704 try { 705 final Class<?> skinClass = Control.loadClass(skinClassName, control); 706 if (!Skin.class.isAssignableFrom(skinClass)) { 707 final String msg = 708 "'" + skinClassName + "' is not a valid Skin class for control " + control; 709 final List<CssParser.ParseError> errors = StyleManager.getErrors(); 710 if (errors != null) { 711 CssParser.ParseError error = new CssParser.ParseError(msg); 712 errors.add(error); // RT-19884 713 } 714 Logging.getControlsLogger().severe(msg); 715 return; 716 } 717 Constructor<?>[] constructors = skinClass.getConstructors(); 718 Constructor<?> skinConstructor = null; 719 for (Constructor<?> c : constructors) { 720 Class<?>[] parameterTypes = c.getParameterTypes(); 721 if (parameterTypes.length == 1 && Skinnable.class.isAssignableFrom(parameterTypes[0])) { 722 skinConstructor = c; 723 break; 724 } 725 } 726 727 if (skinConstructor == null) { 728 final String msg = 729 "No valid constructor defined in '" + skinClassName + "' for control " + control + 730 ".\r\nYou must provide a constructor that accepts a single " 731 + "Skinnable (e.g. Control or PopupControl) parameter in " + skinClassName + "."; 732 final List<CssParser.ParseError> errors = StyleManager.getErrors(); 733 if (errors != null) { 734 CssParser.ParseError error = new CssParser.ParseError(msg); 735 errors.add(error); // RT-19884 736 } 737 Logging.getControlsLogger().severe(msg); 738 } else { 739 Skin<?> skinInstance = (Skin<?>) skinConstructor.newInstance(control); 740 // Do not call setSkin here since it has the side effect of 741 // also setting the skinClassName! 742 control.skinProperty().set(skinInstance); 743 } 744 } catch (InvocationTargetException e) { 745 final String msg = 746 "Failed to load skin '" + skinClassName + "' for control " + control; 747 final List<CssParser.ParseError> errors = StyleManager.getErrors(); 748 if (errors != null) { 749 CssParser.ParseError error = new CssParser.ParseError(msg + " :" + e.getLocalizedMessage()); 750 errors.add(error); // RT-19884 751 } 752 Logging.getControlsLogger().severe(msg, e.getCause()); 753 } catch (Exception e) { 754 final String msg = 755 "Failed to load skin '" + skinClassName + "' for control " + control; 756 final List<CssParser.ParseError> errors = StyleManager.getErrors(); 757 if (errors != null) { 758 CssParser.ParseError error = new CssParser.ParseError(msg + " :" + e.getLocalizedMessage()); 759 errors.add(error); // RT-19884 760 } 761 Logging.getControlsLogger().severe(msg, e); 762 } 763 } 764 765 /*************************************************************************** 766 * * 767 * StyleSheet Handling * 768 * * 769 **************************************************************************/ 770 771 private static class StyleableProperties { 772 private static final CssMetaData<Control,String> SKIN = 773 new CssMetaData<Control,String>("-fx-skin", 774 StringConverter.getInstance()) { 775 776 @Override 777 public boolean isSettable(Control n) { 778 return (n.skin == null || !n.skin.isBound()); 779 } 780 781 @Override 782 public StyleableProperty<String> getStyleableProperty(Control n) { 783 return (StyleableProperty<String>)(WritableValue<String>)n.skinClassNameProperty(); 784 } 785 }; 786 787 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 788 static { 789 final List<CssMetaData<? extends Styleable, ?>> styleables = 790 new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData()); 791 styleables.add(SKIN); 792 STYLEABLES = Collections.unmodifiableList(styleables); 793 } 794 } 795 796 /** 797 * @return The CssMetaData associated with this class, which may include the 798 * CssMetaData of its super classes. 799 * @since JavaFX 8.0 800 */ 801 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 802 return StyleableProperties.STYLEABLES; 803 } 804 805 /** 806 * This method returns a {@link List} containing all {@link CssMetaData} for 807 * both this Control (returned from {@link #getControlCssMetaData()} and its 808 * {@link Skin}, assuming the {@link #skinProperty() skin property} is a 809 * {@link SkinBase}. 810 * 811 * <p>Developers who wish to provide custom CssMetaData are therefore 812 * encouraged to override {@link Control#getControlCssMetaData()} or 813 * {@link SkinBase#getCssMetaData()}, depending on where the CssMetaData 814 * resides. 815 * @since JavaFX 8.0 816 */ 817 @Override 818 public final List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 819 if (styleableProperties == null) { 820 821 // RT-29162: make sure properties only show up once in the list 822 java.util.Map<String, CssMetaData<? extends Styleable, ?>> map = 823 new java.util.HashMap<String, CssMetaData<? extends Styleable, ?>>(); 824 825 List<CssMetaData<? extends Styleable, ?>> list = getControlCssMetaData(); 826 827 for (int n=0, nMax = list != null ? list.size() : 0; n<nMax; n++) { 828 829 CssMetaData<? extends Styleable, ?> metaData = list.get(n); 830 if (metaData == null) continue; 831 832 map.put(metaData.getProperty(), metaData); 833 } 834 835 // 836 // if both control and skin base have the same property, use the 837 // one from skin base since it may be a specialization of the 838 // property in the control. For instance, Label has -fx-font and 839 // so does LabeledText which is Label's skin. 840 // 841 list = skinBase != null ? skinBase.getCssMetaData() : null; 842 843 for (int n=0, nMax = list != null ? list.size() : 0; n<nMax; n++) { 844 845 CssMetaData<? extends Styleable, ?> metaData = list.get(n); 846 if (metaData == null) continue; 847 848 map.put(metaData.getProperty(), metaData); 849 } 850 851 styleableProperties = new ArrayList<CssMetaData<? extends Styleable, ?>>(); 852 styleableProperties.addAll(map.values()); 853 } 854 return styleableProperties; 855 } 856 857 /** 858 * @return unmodifiable list of the controls css styleable properties 859 * @since JavaFX 8.0 860 */ 861 protected List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() { 862 return getClassCssMetaData(); 863 } 864 865 /** 866 * @treatAsPrivate implementation detail 867 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 868 */ 869 @Deprecated 870 @Override protected void impl_processCSS() { 871 872 super.impl_processCSS(); 873 874 if (getSkin() == null) { 875 // try to create default skin 876 final Skin<?> defaultSkin = createDefaultSkin(); 877 if (defaultSkin != null) { 878 skinProperty().set(defaultSkin); 879 super.impl_processCSS(); 880 } else { 881 final String msg = "The -fx-skin property has not been defined in CSS for " + this + 882 " and createDefaultSkin() returned null."; 883 final List<CssParser.ParseError> errors = StyleManager.getErrors(); 884 if (errors != null) { 885 CssParser.ParseError error = new CssParser.ParseError(msg); 886 errors.add(error); // RT-19884 887 } 888 Logging.getControlsLogger().severe(msg); 889 } 890 } 891 } 892 893 /** 894 * Returns the initial focus traversable state of this control, for use 895 * by the JavaFX CSS engine to correctly set its initial value. By default all 896 * UI controls are focus traversable, so this method is overridden in Control 897 * to set the initial traversable state to true. 898 * 899 * @since 9 900 */ 901 @Override protected Boolean getInitialFocusTraversable() { 902 return Boolean.TRUE; 903 } 904 905 906 /*************************************************************************** 907 * * 908 * Accessibility handling * 909 * * 910 **************************************************************************/ 911 912 @Override 913 public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { 914 switch (attribute) { 915 case HELP: 916 String help = getAccessibleHelp(); 917 if (help != null && !help.isEmpty()) return help; 918 Tooltip tooltip = getTooltip(); 919 return tooltip == null ? "" : tooltip.getText(); 920 default: 921 } 922 if (skinBase != null) { 923 Object result = skinBase.queryAccessibleAttribute(attribute, parameters); 924 if (result != null) return result; 925 } 926 return super.queryAccessibleAttribute(attribute, parameters); 927 } 928 929 @Override 930 public void executeAccessibleAction(AccessibleAction action, Object... parameters) { 931 if (skinBase != null) { 932 skinBase.executeAccessibleAction(action, parameters); 933 } 934 super.executeAccessibleAction(action, parameters); 935 } 936 }