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 com.sun.javafx.scene.control.ModuleHelper; 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.setShowRelativeToWindow(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 // TODO re-enable when InputMap moves back to Node / Control 425 // // Most controls need an input map, so we set this to be non-null in 426 // // Control to save people from running into NPEs. 427 // setInputMap(new InputMap(this)); 428 } 429 430 431 432 /*************************************************************************** 433 * * 434 * Public API * 435 * * 436 **************************************************************************/ 437 438 // Proposed dispose() API. 439 // Note that there is impl code for a dispose method in TableRowSkinBase 440 // and TableCell (just search for dispose()) 441 // public void dispose() { 442 // Skin skin = getSkin(); 443 // if (skin != null) { 444 // skin.dispose(); 445 // } 446 // } 447 448 /** 449 * Returns <code>true</code> since all Controls are resizable. 450 * @return whether this node can be resized by its parent during layout 451 */ 452 @Override public boolean isResizable() { 453 return true; 454 } 455 456 // Implementation of the Resizable interface. 457 // Because only the skin can know the min, pref, and max sizes, these 458 // functions are implemented to delegate to skin. If there is no skin then 459 // we simply return 0 for all the values since a Control without a Skin 460 // doesn't render 461 /** 462 * Computes the minimum allowable width of the Control, based on the provided 463 * height. The minimum width is not calculated within the Control, instead 464 * the calculation is delegated to the {@link Node#minWidth(double)} method 465 * of the {@link Skin}. If the Skin is null, the returned value is 0. 466 * 467 * @param height The height of the Control, in case this value might dictate 468 * the minimum width. 469 * @return A double representing the minimum width of this control. 470 */ 471 @Override protected double computeMinWidth(final double height) { 472 if (skinBase != null) { 473 return skinBase.computeMinWidth(height, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 474 } else { 475 final Node skinNode = getSkinNode(); 476 return skinNode == null ? 0 : skinNode.minWidth(height); 477 } 478 } 479 480 /** 481 * Computes the minimum allowable height of the Control, based on the provided 482 * width. The minimum height is not calculated within the Control, instead 483 * the calculation is delegated to the {@link Node#minHeight(double)} method 484 * of the {@link Skin}. If the Skin is null, the returned value is 0. 485 * 486 * @param width The width of the Control, in case this value might dictate 487 * the minimum height. 488 * @return A double representing the minimum height of this control. 489 */ 490 @Override protected double computeMinHeight(final double width) { 491 if (skinBase != null) { 492 return skinBase.computeMinHeight(width, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 493 } else { 494 final Node skinNode = getSkinNode(); 495 return skinNode == null ? 0 : skinNode.minHeight(width); 496 } 497 } 498 499 /** 500 * Computes the maximum allowable width of the Control, based on the provided 501 * height. The maximum width is not calculated within the Control, instead 502 * the calculation is delegated to the {@link Node#maxWidth(double)} method 503 * of the {@link Skin}. If the Skin is null, the returned value is 0. 504 * 505 * @param height The height of the Control, in case this value might dictate 506 * the maximum width. 507 * @return A double representing the maximum width of this control. 508 */ 509 @Override protected double computeMaxWidth(double height) { 510 if (skinBase != null) { 511 return skinBase.computeMaxWidth(height, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 512 } else { 513 final Node skinNode = getSkinNode(); 514 return skinNode == null ? 0 : skinNode.maxWidth(height); 515 } 516 } 517 518 /** 519 * Computes the maximum allowable height of the Control, based on the provided 520 * width. The maximum height is not calculated within the Control, instead 521 * the calculation is delegated to the {@link Node#maxHeight(double)} method 522 * of the {@link Skin}. If the Skin is null, the returned value is 0. 523 * 524 * @param width The width of the Control, in case this value might dictate 525 * the maximum height. 526 * @return A double representing the maximum height of this control. 527 */ 528 @Override protected double computeMaxHeight(double width) { 529 if (skinBase != null) { 530 return skinBase.computeMaxHeight(width, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 531 } else { 532 final Node skinNode = getSkinNode(); 533 return skinNode == null ? 0 : skinNode.maxHeight(width); 534 } 535 } 536 537 /** {@inheritDoc} */ 538 @Override protected double computePrefWidth(double height) { 539 if (skinBase != null) { 540 return skinBase.computePrefWidth(height, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 541 } else { 542 final Node skinNode = getSkinNode(); 543 return skinNode == null ? 0 : skinNode.prefWidth(height); 544 } 545 } 546 547 /** {@inheritDoc} */ 548 @Override protected double computePrefHeight(double width) { 549 if (skinBase != null) { 550 return skinBase.computePrefHeight(width, snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 551 } else { 552 final Node skinNode = getSkinNode(); 553 return skinNode == null ? 0 : skinNode.prefHeight(width); 554 } 555 } 556 557 /** {@inheritDoc} */ 558 @Override public double getBaselineOffset() { 559 if (skinBase != null) { 560 return skinBase.computeBaselineOffset(snappedTopInset(), snappedRightInset(), snappedBottomInset(), snappedLeftInset()); 561 } else { 562 final Node skinNode = getSkinNode(); 563 return skinNode == null ? 0 : skinNode.getBaselineOffset(); 564 } 565 } 566 567 /*************************************************************************** 568 * Implementation of layout bounds for the Control. We want to preserve * 569 * the lazy semantics of layout bounds. So whenever the width/height * 570 * changes on the node, we end up invalidating layout bounds. We then * 571 * recompute it on demand. * 572 **************************************************************************/ 573 574 /** {@inheritDoc} */ 575 @Override protected void layoutChildren() { 576 if (skinBase != null) { 577 final double x = snappedLeftInset(); 578 final double y = snappedTopInset(); 579 final double w = snapSize(getWidth()) - x - snappedRightInset(); 580 final double h = snapSize(getHeight()) - y - snappedBottomInset(); 581 skinBase.layoutChildren(x, y, w, h); 582 } else { 583 Node n = getSkinNode(); 584 if (n != null) { 585 n.resizeRelocate(0, 0, getWidth(), getHeight()); 586 } 587 } 588 } 589 590 /*************************************************************************** 591 * Forward the following to the skin * 592 **************************************************************************/ 593 594 /** 595 * Create a new instance of the default skin for this control. This is called to create a skin for the control if 596 * no skin is provided via CSS {@code -fx-skin} or set explicitly in a sub-class with {@code setSkin(...)}. 597 * 598 * @return new instance of default skin for this control. If null then the control will have no skin unless one 599 * is provided by css. 600 * @since JavaFX 8.0 601 */ 602 protected Skin<?> createDefaultSkin() { 603 return null; 604 } 605 606 /*************************************************************************** 607 * * 608 * Package API for SkinBase * 609 * * 610 **************************************************************************/ 611 612 // package private for SkinBase 613 ObservableList<Node> getControlChildren() { 614 return getChildren(); 615 } 616 617 618 /*************************************************************************** 619 * * 620 * Private implementation * 621 * * 622 **************************************************************************/ 623 624 /** 625 * Gets the Skin's node, or returns null if there is no Skin. 626 * Convenience method for getting the node of the skin. This is null-safe, 627 * meaning if skin is null then it will return null instead of throwing 628 * a NullPointerException. 629 * 630 * @return The Skin's node, or null. 631 */ 632 private Node getSkinNode() { 633 assert skinBase == null; 634 Skin<?> skin = getSkin(); 635 return skin == null ? null : skin.getNode(); 636 } 637 638 /** 639 * Keeps a reference to the name of the class currently acting as the skin. 640 */ 641 private String currentSkinClassName = null; 642 private StringProperty skinClassName; 643 644 /** 645 * @treatAsPrivate 646 * @since JavaFX 2.1 647 */ 648 @Deprecated protected StringProperty skinClassNameProperty() { 649 if (skinClassName == null) { 650 skinClassName = new StyleableStringProperty() { 651 652 @Override 653 public void set(String v) { 654 // do not allow the skin to be set to null through CSS 655 if (v == null || v.isEmpty() || v.equals(get())) return; 656 super.set(v); 657 } 658 659 @Override 660 public void invalidated() { 661 662 if (get() != null) { 663 if (!get().equals(currentSkinClassName)) { 664 loadSkinClass(Control.this, skinClassName.get()); 665 } 666 // Note: CSS should not set skin to null 667 } 668 } 669 670 @Override 671 public Object getBean() { 672 return Control.this; 673 } 674 675 @Override 676 public String getName() { 677 return "skinClassName"; 678 } 679 680 @Override 681 public CssMetaData<Control,String> getCssMetaData() { 682 return StyleableProperties.SKIN; 683 } 684 685 }; 686 } 687 return skinClassName; 688 } 689 690 static void loadSkinClass(final Skinnable control, final String skinClassName) { 691 if (skinClassName == null || skinClassName.isEmpty()) { 692 final String msg = 693 "Empty -fx-skin property specified for control " + control; 694 final List<CssParser.ParseError> errors = StyleManager.getErrors(); 695 if (errors != null) { 696 CssParser.ParseError error = new CssParser.ParseError(msg); 697 errors.add(error); // RT-19884 698 } 699 Logging.getControlsLogger().severe(msg); 700 return; 701 } 702 703 try { 704 final Class<?> skinClass = Control.loadClass(skinClassName, control); 705 Object skinModule = ModuleHelper.getModule(skinClass); 706 Object thisModule = ModuleHelper.getModule(Control.class); 707 if (skinModule != thisModule) { 708 ModuleHelper.addReads(thisModule, skinModule); 709 } 710 Constructor<?>[] constructors = skinClass.getConstructors(); 711 Constructor<?> skinConstructor = null; 712 for (Constructor<?> c : constructors) { 713 Class<?>[] parameterTypes = c.getParameterTypes(); 714 if (parameterTypes.length == 1 && Skinnable.class.isAssignableFrom(parameterTypes[0])) { 715 skinConstructor = c; 716 break; 717 } 718 } 719 720 if (skinConstructor == null) { 721 final String msg = 722 "No valid constructor defined in '" + skinClassName + "' for control " + control + 723 ".\r\nYou must provide a constructor that accepts a single " 724 + "Skinnable (e.g. Control or PopupControl) parameter in " + skinClassName + "."; 725 final List<CssParser.ParseError> errors = StyleManager.getErrors(); 726 if (errors != null) { 727 CssParser.ParseError error = new CssParser.ParseError(msg); 728 errors.add(error); // RT-19884 729 } 730 Logging.getControlsLogger().severe(msg); 731 } else { 732 Skin<?> skinInstance = (Skin<?>) skinConstructor.newInstance(control); 733 // Do not call setSkin here since it has the side effect of 734 // also setting the skinClassName! 735 control.skinProperty().set(skinInstance); 736 } 737 } catch (InvocationTargetException e) { 738 final String msg = 739 "Failed to load skin '" + skinClassName + "' for control " + control; 740 final List<CssParser.ParseError> errors = StyleManager.getErrors(); 741 if (errors != null) { 742 CssParser.ParseError error = new CssParser.ParseError(msg + " :" + e.getLocalizedMessage()); 743 errors.add(error); // RT-19884 744 } 745 Logging.getControlsLogger().severe(msg, e.getCause()); 746 } catch (Exception e) { 747 final String msg = 748 "Failed to load skin '" + skinClassName + "' for control " + control; 749 final List<CssParser.ParseError> errors = StyleManager.getErrors(); 750 if (errors != null) { 751 CssParser.ParseError error = new CssParser.ParseError(msg + " :" + e.getLocalizedMessage()); 752 errors.add(error); // RT-19884 753 } 754 Logging.getControlsLogger().severe(msg, e); 755 } 756 } 757 758 /*************************************************************************** 759 * * 760 * StyleSheet Handling * 761 * * 762 **************************************************************************/ 763 764 private static class StyleableProperties { 765 private static final CssMetaData<Control,String> SKIN = 766 new CssMetaData<Control,String>("-fx-skin", 767 StringConverter.getInstance()) { 768 769 @Override 770 public boolean isSettable(Control n) { 771 return (n.skin == null || !n.skin.isBound()); 772 } 773 774 @Override 775 public StyleableProperty<String> getStyleableProperty(Control n) { 776 return (StyleableProperty<String>)(WritableValue<String>)n.skinClassNameProperty(); 777 } 778 }; 779 780 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 781 static { 782 final List<CssMetaData<? extends Styleable, ?>> styleables = 783 new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData()); 784 styleables.add(SKIN); 785 STYLEABLES = Collections.unmodifiableList(styleables); 786 } 787 } 788 789 /** 790 * @return The CssMetaData associated with this class, which may include the 791 * CssMetaData of its super classes. 792 * @since JavaFX 8.0 793 */ 794 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 795 return StyleableProperties.STYLEABLES; 796 } 797 798 /** 799 * This method returns a {@link List} containing all {@link CssMetaData} for 800 * both this Control (returned from {@link #getControlCssMetaData()} and its 801 * {@link Skin}, assuming the {@link #skinProperty() skin property} is a 802 * {@link SkinBase}. 803 * 804 * <p>Developers who wish to provide custom CssMetaData are therefore 805 * encouraged to override {@link Control#getControlCssMetaData()} or 806 * {@link SkinBase#getCssMetaData()}, depending on where the CssMetaData 807 * resides. 808 * @since JavaFX 8.0 809 */ 810 @Override 811 public final List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 812 if (styleableProperties == null) { 813 814 // RT-29162: make sure properties only show up once in the list 815 java.util.Map<String, CssMetaData<? extends Styleable, ?>> map = 816 new java.util.HashMap<String, CssMetaData<? extends Styleable, ?>>(); 817 818 List<CssMetaData<? extends Styleable, ?>> list = getControlCssMetaData(); 819 820 for (int n=0, nMax = list != null ? list.size() : 0; n<nMax; n++) { 821 822 CssMetaData<? extends Styleable, ?> metaData = list.get(n); 823 if (metaData == null) continue; 824 825 map.put(metaData.getProperty(), metaData); 826 } 827 828 // 829 // if both control and skin base have the same property, use the 830 // one from skin base since it may be a specialization of the 831 // property in the control. For instance, Label has -fx-font and 832 // so does LabeledText which is Label's skin. 833 // 834 list = skinBase != null ? skinBase.getCssMetaData() : null; 835 836 for (int n=0, nMax = list != null ? list.size() : 0; n<nMax; n++) { 837 838 CssMetaData<? extends Styleable, ?> metaData = list.get(n); 839 if (metaData == null) continue; 840 841 map.put(metaData.getProperty(), metaData); 842 } 843 844 styleableProperties = new ArrayList<CssMetaData<? extends Styleable, ?>>(); 845 styleableProperties.addAll(map.values()); 846 } 847 return styleableProperties; 848 } 849 850 /** 851 * @return unmodifiable list of the controls css styleable properties 852 * @since JavaFX 8.0 853 */ 854 protected List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() { 855 return getClassCssMetaData(); 856 } 857 858 /** 859 * @treatAsPrivate implementation detail 860 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 861 */ 862 @Deprecated 863 @Override protected void impl_processCSS(WritableValue<Boolean> unused) { 864 865 super.impl_processCSS(unused); 866 867 if (getSkin() == null) { 868 // try to create default skin 869 final Skin<?> defaultSkin = createDefaultSkin(); 870 if (defaultSkin != null) { 871 skinProperty().set(defaultSkin); 872 super.impl_processCSS(unused); 873 } else { 874 final String msg = "The -fx-skin property has not been defined in CSS for " + this + 875 " and createDefaultSkin() returned null."; 876 final List<CssParser.ParseError> errors = StyleManager.getErrors(); 877 if (errors != null) { 878 CssParser.ParseError error = new CssParser.ParseError(msg); 879 errors.add(error); // RT-19884 880 } 881 Logging.getControlsLogger().severe(msg); 882 } 883 } 884 } 885 886 /** 887 * Returns the initial focus traversable state of this control, for use 888 * by the JavaFX CSS engine to correctly set its initial value. By default all 889 * UI controls are focus traversable, so this method is overridden in Control 890 * to set the initial traversable state to true. 891 * 892 * @since 9 893 */ 894 @Override protected Boolean getInitialFocusTraversable() { 895 return Boolean.TRUE; 896 } 897 898 899 /*************************************************************************** 900 * * 901 * Accessibility handling * 902 * * 903 **************************************************************************/ 904 905 @Override 906 public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { 907 switch (attribute) { 908 case HELP: 909 String help = getAccessibleHelp(); 910 if (help != null && !help.isEmpty()) return help; 911 Tooltip tooltip = getTooltip(); 912 return tooltip == null ? "" : tooltip.getText(); 913 default: 914 } 915 if (skinBase != null) { 916 Object result = skinBase.queryAccessibleAttribute(attribute, parameters); 917 if (result != null) return result; 918 } 919 return super.queryAccessibleAttribute(attribute, parameters); 920 } 921 922 @Override 923 public void executeAccessibleAction(AccessibleAction action, Object... parameters) { 924 if (skinBase != null) { 925 skinBase.executeAccessibleAction(action, parameters); 926 } 927 super.executeAccessibleAction(action, parameters); 928 } 929 }