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