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