1 /* 2 * Copyright (c) 2010, 2016, 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 */ 227 @Override public final ObjectProperty<Skin<?>> skinProperty() { return skin; } 228 @Override public final void setSkin(Skin<?> value) { 229 skinProperty().set(value); 230 } 231 @Override public final Skin<?> getSkin() { return skinProperty().getValue(); } 232 private ObjectProperty<Skin<?>> skin = new StyleableObjectProperty<Skin<?>>() { 233 // We store a reference to the oldValue so that we can handle 234 // changes in the skin properly in the case of binding. This is 235 // only needed because invalidated() does not currently take 236 // a reference to the old value. 237 private Skin<?> oldValue; 238 239 @Override 240 //This code is basically a kind of optimization that prevents a Skin that is equal but not instance equal. 241 //Although it's not kosher from the property perspective (bindings won't pass through set), it should not do any harm. 242 //But it should be evaluated in the future. 243 public void set(Skin<?> v) { 244 if (v == null 245 ? oldValue == null 246 : oldValue != null && v.getClass().equals(oldValue.getClass())) 247 return; 248 249 super.set(v); 250 } 251 252 @Override protected void invalidated() { 253 Skin<?> skin = get(); 254 // Collect the name of the currently installed skin class. We do this 255 // so that subsequent updates from CSS to the same skin class will not 256 // result in reinstalling the skin 257 currentSkinClassName = skin == null ? null : skin.getClass().getName(); 258 259 // if someone calls setSkin, we need to make it look like they 260 // called set on skinClassName in order to keep CSS from overwriting 261 // the skin. 262 skinClassNameProperty().set(currentSkinClassName); 263 264 265 // Dispose of the old skin 266 if (oldValue != null) oldValue.dispose(); 267 268 // Get the new value, and save it off as the new oldValue 269 oldValue = skin; 270 271 // Reset skinBase to null - it will be set to the new Skin if it 272 // is a SkinBase, otherwise it will remain null, as expected 273 skinBase = null; 274 275 // We have two paths, one for "legacy" Skins, and one for 276 // any Skin which extends from SkinBase. Legacy Skins will 277 // produce a single node which will be the only child of 278 // the Control via the getNode() method on the Skin. A 279 // SkinBase will manipulate the children of the Control 280 // directly. Further, we maintain a direct reference to 281 // the skinBase for more optimal updates later. 282 if (skin instanceof SkinBase) { 283 // record a reference of the skin, if it is a SkinBase, for 284 // performance reasons 285 skinBase = (SkinBase<?>) skin; 286 // Note I do not remove any children here, because the 287 // skin will have already configured all the children 288 // by the time setSkin has been called. This is because 289 // our Skin interface was lacking an initialize method (doh!) 290 // and so the Skin constructor is where it adds listeners 291 // and so forth. For SkinBase implementations, the 292 // constructor is also where it will take ownership of 293 // the children. 294 } else { 295 final Node n = getSkinNode(); 296 if (n != null) { 297 getChildren().setAll(n); 298 } else { 299 getChildren().clear(); 300 } 301 } 302 303 // clear out the styleable properties so that the list is rebuilt 304 // next time they are requested. 305 styleableProperties = null; 306 307 // calling NodeHelper.reapplyCSS() as the styleable properties may now 308 // be different, as we will now be able to return styleable properties 309 // belonging to the skin. If NodeHelper.reapplyCSS() is not called, the 310 // getCssMetaData() method is never called, so the 311 // skin properties are never exposed. 312 NodeHelper.reapplyCSS(Control.this); 313 314 // DEBUG: Log that we've changed the skin 315 final PlatformLogger logger = Logging.getControlsLogger(); 316 if (logger.isLoggable(Level.FINEST)) { 317 logger.finest("Stored skin[" + getValue() + "] on " + this); 318 } 319 } 320 321 // This method should be CssMetaData<Control,Skin> getCssMetaData(), 322 // but SKIN is CssMetaData<Control,String>. This does not matter to 323 // the CSS code which doesn't care about the actual type. Hence, 324 // we'll suppress the warnings 325 @Override @SuppressWarnings({"unchecked", "rawtype"}) 326 public CssMetaData getCssMetaData() { 327 return StyleableProperties.SKIN; 328 } 329 330 @Override 331 public Object getBean() { 332 return Control.this; 333 } 334 335 @Override 336 public String getName() { 337 return "skin"; 338 } 339 }; 340 341 342 // --- tooltip 343 /** 344 * The ToolTip for this control. 345 */ 346 public final ObjectProperty<Tooltip> tooltipProperty() { 347 if (tooltip == null) { 348 tooltip = new ObjectPropertyBase<Tooltip>() { 349 private Tooltip old = null; 350 @Override protected void invalidated() { 351 Tooltip t = get(); 352 // install / uninstall 353 if (t != old) { 354 if (old != null) { 355 Tooltip.uninstall(Control.this, old); 356 } 357 if (t != null) { 358 Tooltip.install(Control.this, t); 359 } 360 old = t; 361 } 362 } 363 364 @Override 365 public Object getBean() { 366 return Control.this; 367 } 368 369 @Override 370 public String getName() { 371 return "tooltip"; 372 } 373 }; 374 } 375 return tooltip; 376 } 377 private ObjectProperty<Tooltip> tooltip; 378 public final void setTooltip(Tooltip value) { tooltipProperty().setValue(value); } 379 public final Tooltip getTooltip() { return tooltip == null ? null : tooltip.getValue(); } 380 381 382 // --- context menu 383 /** 384 * The ContextMenu to show for this control. 385 */ 386 private ObjectProperty<ContextMenu> contextMenu = new SimpleObjectProperty<ContextMenu>(this, "contextMenu") { 387 private WeakReference<ContextMenu> contextMenuRef; 388 389 @Override protected void invalidated() { 390 ContextMenu oldMenu = contextMenuRef == null ? null : contextMenuRef.get(); 391 if (oldMenu != null) { 392 ControlAcceleratorSupport.removeAcceleratorsFromScene(oldMenu.getItems(), Control.this); 393 } 394 395 ContextMenu ctx = get(); 396 contextMenuRef = new WeakReference<>(ctx); 397 398 if (ctx != null) { 399 // set this flag so contextmenu show will be relative to parent window not anchor 400 ctx.setShowRelativeToWindow(true); //RT-15160 401 402 // if a context menu is set, we need to install any accelerators 403 // belonging to its menu items ASAP into the scene that this 404 // Control is in (if the control is not in a Scene, we will need 405 // to wait until it is and then do it). 406 ControlAcceleratorSupport.addAcceleratorsIntoScene(ctx.getItems(), Control.this); 407 } 408 } 409 }; 410 public final ObjectProperty<ContextMenu> contextMenuProperty() { return contextMenu; } 411 public final void setContextMenu(ContextMenu value) { contextMenu.setValue(value); } 412 public final ContextMenu getContextMenu() { return contextMenu == null ? null : contextMenu.getValue(); } 413 414 415 416 /*************************************************************************** 417 * * 418 * Constructors * 419 * * 420 **************************************************************************/ 421 422 { 423 // To initialize the class helper at the begining each constructor of this class 424 ControlHelper.initHelper(this); 425 } 426 /** 427 * Create a new Control. 428 */ 429 protected Control() { 430 // focusTraversable is styleable through css. Calling setFocusTraversable 431 // makes it look to css like the user set the value and css will not 432 // override. Initializing focusTraversable by calling applyStyle 433 // with null for StyleOrigin ensures that css will be able to override 434 // the value. 435 final StyleableProperty<Boolean> prop = (StyleableProperty<Boolean>)(WritableValue<Boolean>)focusTraversableProperty(); 436 prop.applyStyle(null, Boolean.TRUE); 437 438 // we add a listener for menu request events to show the context menu 439 // that may be set on the Control 440 this.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, contextMenuHandler); 441 442 // TODO re-enable when InputMap moves back to Node / Control 443 // // Most controls need an input map, so we set this to be non-null in 444 // // Control to save people from running into NPEs. 445 // setInputMap(new InputMap(this)); 446 } 447 448 449 450 /*************************************************************************** 451 * * 452 * Public API * 453 * * 454 **************************************************************************/ 455 456 // Proposed dispose() API. 457 // Note that there is impl code for a dispose method in TableRowSkinBase 458 // and TableCell (just search for dispose()) 459 // public void dispose() { 460 // Skin skin = getSkin(); 461 // if (skin != null) { 462 // skin.dispose(); 463 // } 464 // } 465 466 /** 467 * Returns <code>true</code> since all Controls are resizable. 468 * @return whether this node can be resized by its parent during layout 469 */ 470 @Override public boolean isResizable() { 471 return true; 472 } 473 474 // Implementation of the Resizable interface. 475 // Because only the skin can know the min, pref, and max sizes, these 476 // functions are implemented to delegate to skin. If there is no skin then 477 // we simply return 0 for all the values since a Control without a Skin 478 // doesn't render 479 /** 480 * Computes the minimum allowable width of the Control, based on the provided 481 * height. The minimum width is not calculated within the Control, instead 482 * the calculation is delegated to the {@link Node#minWidth(double)} method 483 * of the {@link Skin}. If the Skin is null, the returned value is 0. 484 * 485 * @param height The height of the Control, in case this value might dictate 486 * the minimum width. 487 * @return A double representing the minimum width of this control. 488 */ 489 @Override protected double computeMinWidth(final double height) { 490 if (skinBase != null) { 491 return skinBase.computeMinWidth(height, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 492 } else { 493 final Node skinNode = getSkinNode(); 494 return skinNode == null ? 0 : skinNode.minWidth(height); 495 } 496 } 497 498 /** 499 * Computes the minimum allowable height of the Control, based on the provided 500 * width. The minimum height is not calculated within the Control, instead 501 * the calculation is delegated to the {@link Node#minHeight(double)} method 502 * of the {@link Skin}. If the Skin is null, the returned value is 0. 503 * 504 * @param width The width of the Control, in case this value might dictate 505 * the minimum height. 506 * @return A double representing the minimum height of this control. 507 */ 508 @Override protected double computeMinHeight(final double width) { 509 if (skinBase != null) { 510 return skinBase.computeMinHeight(width, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 511 } else { 512 final Node skinNode = getSkinNode(); 513 return skinNode == null ? 0 : skinNode.minHeight(width); 514 } 515 } 516 517 /** 518 * Computes the maximum allowable width of the Control, based on the provided 519 * height. The maximum width is not calculated within the Control, instead 520 * the calculation is delegated to the {@link Node#maxWidth(double)} method 521 * of the {@link Skin}. If the Skin is null, the returned value is 0. 522 * 523 * @param height The height of the Control, in case this value might dictate 524 * the maximum width. 525 * @return A double representing the maximum width of this control. 526 */ 527 @Override protected double computeMaxWidth(double height) { 528 if (skinBase != null) { 529 return skinBase.computeMaxWidth(height, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 530 } else { 531 final Node skinNode = getSkinNode(); 532 return skinNode == null ? 0 : skinNode.maxWidth(height); 533 } 534 } 535 536 /** 537 * Computes the maximum allowable height of the Control, based on the provided 538 * width. The maximum height is not calculated within the Control, instead 539 * the calculation is delegated to the {@link Node#maxHeight(double)} method 540 * of the {@link Skin}. If the Skin is null, the returned value is 0. 541 * 542 * @param width The width of the Control, in case this value might dictate 543 * the maximum height. 544 * @return A double representing the maximum height of this control. 545 */ 546 @Override protected double computeMaxHeight(double width) { 547 if (skinBase != null) { 548 return skinBase.computeMaxHeight(width, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 549 } else { 550 final Node skinNode = getSkinNode(); 551 return skinNode == null ? 0 : skinNode.maxHeight(width); 552 } 553 } 554 555 /** {@inheritDoc} */ 556 @Override protected double computePrefWidth(double height) { 557 if (skinBase != null) { 558 return skinBase.computePrefWidth(height, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 559 } else { 560 final Node skinNode = getSkinNode(); 561 return skinNode == null ? 0 : skinNode.prefWidth(height); 562 } 563 } 564 565 /** {@inheritDoc} */ 566 @Override protected double computePrefHeight(double width) { 567 if (skinBase != null) { 568 return skinBase.computePrefHeight(width, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 569 } else { 570 final Node skinNode = getSkinNode(); 571 return skinNode == null ? 0 : skinNode.prefHeight(width); 572 } 573 } 574 575 /** {@inheritDoc} */ 576 @Override public double getBaselineOffset() { 577 if (skinBase != null) { 578 return skinBase.computeBaselineOffset(snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 579 } else { 580 final Node skinNode = getSkinNode(); 581 return skinNode == null ? 0 : skinNode.getBaselineOffset(); 582 } 583 } 584 585 /*************************************************************************** 586 * Implementation of layout bounds for the Control. We want to preserve * 587 * the lazy semantics of layout bounds. So whenever the width/height * 588 * changes on the node, we end up invalidating layout bounds. We then * 589 * recompute it on demand. * 590 **************************************************************************/ 591 592 /** {@inheritDoc} */ 593 @Override protected void layoutChildren() { 594 if (skinBase != null) { 595 final double x = snappedLeftInset(); 596 final double y = snappedTopInset(); 597 final double w = snapSizeX(getWidth()) - x - snappedRightInset(); 598 final double h = snapSizeY(getHeight()) - y - snappedBottomInset(); 599 skinBase.layoutChildren(x, y, w, h); 600 } else { 601 Node n = getSkinNode(); 602 if (n != null) { 603 n.resizeRelocate(0, 0, getWidth(), getHeight()); 604 } 605 } 606 } 607 608 /*************************************************************************** 609 * Forward the following to the skin * 610 **************************************************************************/ 611 612 /** 613 * Create a new instance of the default skin for this control. This is called to create a skin for the control if 614 * no skin is provided via CSS {@code -fx-skin} or set explicitly in a sub-class with {@code setSkin(...)}. 615 * 616 * @return new instance of default skin for this control. If null then the control will have no skin unless one 617 * is provided by css. 618 * @since JavaFX 8.0 619 */ 620 protected Skin<?> createDefaultSkin() { 621 return null; 622 } 623 624 /*************************************************************************** 625 * * 626 * Package API for SkinBase * 627 * * 628 **************************************************************************/ 629 630 // package private for SkinBase 631 ObservableList<Node> getControlChildren() { 632 return getChildren(); 633 } 634 635 636 /*************************************************************************** 637 * * 638 * Private implementation * 639 * * 640 **************************************************************************/ 641 642 /** 643 * Gets the Skin's node, or returns null if there is no Skin. 644 * Convenience method for getting the node of the skin. This is null-safe, 645 * meaning if skin is null then it will return null instead of throwing 646 * a NullPointerException. 647 * 648 * @return The Skin's node, or null. 649 */ 650 private Node getSkinNode() { 651 assert skinBase == null; 652 Skin<?> skin = getSkin(); 653 return skin == null ? null : skin.getNode(); 654 } 655 656 /** 657 * Keeps a reference to the name of the class currently acting as the skin. 658 */ 659 private String currentSkinClassName = null; 660 private StringProperty skinClassName; 661 662 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 }