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