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