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