1 /* 2 * Copyright (c) 2010, 2017, 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.util.ArrayList; 29 import java.util.Collections; 30 import java.util.List; 31 32 import javafx.application.Application; 33 import javafx.beans.property.DoubleProperty; 34 import javafx.beans.property.DoublePropertyBase; 35 import javafx.beans.property.ObjectProperty; 36 import javafx.beans.property.ObjectPropertyBase; 37 import javafx.beans.property.StringProperty; 38 import javafx.beans.value.WritableValue; 39 import javafx.collections.FXCollections; 40 import javafx.collections.ObservableList; 41 import javafx.collections.ObservableSet; 42 import javafx.css.CssParser; 43 import javafx.scene.Node; 44 import javafx.scene.Parent; 45 import javafx.scene.Scene; 46 import javafx.scene.layout.Pane; 47 import javafx.stage.PopupWindow; 48 import com.sun.javafx.application.PlatformImpl; 49 import javafx.css.CssMetaData; 50 import javafx.css.PseudoClass; 51 import com.sun.javafx.css.StyleManager; 52 import com.sun.javafx.scene.NodeHelper; 53 import com.sun.javafx.scene.ParentHelper; 54 import javafx.css.Styleable; 55 import javafx.css.StyleableStringProperty; 56 import javafx.css.converter.StringConverter; 57 import com.sun.javafx.scene.control.Logging; 58 import com.sun.javafx.scene.layout.PaneHelper; 59 import com.sun.javafx.stage.PopupWindowHelper; 60 import javafx.css.StyleableProperty; 61 import javafx.stage.Window; 62 import sun.util.logging.PlatformLogger; 63 import sun.util.logging.PlatformLogger.Level; 64 65 /** 66 * An extension of PopupWindow that allows for CSS styling. 67 * @since JavaFX 2.0 68 */ 69 public class PopupControl extends PopupWindow implements Skinnable, Styleable { 70 71 /** 72 * Sentinel value which can be passed to a control's setMinWidth(), setMinHeight(), 73 * setMaxWidth() or setMaxHeight() methods to indicate that the preferred dimension 74 * should be used for that max and/or min constraint. 75 */ 76 public static final double USE_PREF_SIZE = Double.NEGATIVE_INFINITY; 77 78 /** 79 * Sentinel value which can be passed to a control's setMinWidth(), setMinHeight(), 80 * setPrefWidth(), setPrefHeight(), setMaxWidth(), setMaxHeight() methods 81 * to reset the control's size constraint back to it's intrinsic size returned 82 * by computeMinWidth(), computeMinHeight(), computePrefWidth(), computePrefHeight(), 83 * computeMaxWidth(), or computeMaxHeight(). 84 */ 85 public static final double USE_COMPUTED_SIZE = -1; 86 87 static { 88 // Ensures that the default application user agent stylesheet is loaded 89 if (Application.getUserAgentStylesheet() == null) { 90 PlatformImpl.setDefaultPlatformUserAgentStylesheet(); 91 } 92 } 93 94 /** 95 * We need a special root node, except we can't replace the special 96 * root node already in the PopupControl. So we'll set our own 97 * special almost-root node that is a child of the root. 98 * 99 * This special root node is responsible for mapping the id, styleClass, 100 * and style defined on the PopupControl such that CSS will read the 101 * values from the PopupControl, and then apply CSS state to that 102 * special node. The node will then be able to pass cssSet calls 103 * along, such that any subclass of PopupControl will be able to 104 * use the Styleable properties and we'll be able to style it from 105 * CSS, in such a way that it participates and applies to the skin, 106 * exactly the way that normal Skin's work for normal Controls. 107 * @since JavaFX 2.1 108 */ 109 protected CSSBridge bridge; 110 111 /** 112 * Create a new empty PopupControl. 113 */ 114 public PopupControl() { 115 super(); 116 this.bridge = new CSSBridge(); 117 setAnchorLocation(AnchorLocation.CONTENT_TOP_LEFT); 118 PopupWindowHelper.getContent(this).add(bridge); 119 } 120 121 // TODO the fact that PopupWindow uses a group for auto-moving things 122 // around means that the scene resize semantics don't work if the 123 // child is a resizable. I will need to replicate those semantics 124 // here sometime, such that if the Skin provides a resizable, it is 125 // given to match the popup window's width & height. 126 127 /** 128 * The id of this {@code PopupControl}. This simple string identifier is useful for 129 * finding a specific Node within the scene graph. While the id of a Node 130 * should be unique within the scene graph, this uniqueness is not enforced. 131 * This is analogous to the "id" attribute on an HTML element 132 * (<a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS ID Specification</a>). 133 * 134 * @return the id property 135 * @defaultValue null 136 */ 137 public final StringProperty idProperty() { return bridge.idProperty(); } 138 139 /** 140 * Sets the id of this {@code PopupControl}. This simple string identifier is useful for 141 * finding a specific Node within the scene graph. While the id of a Node 142 * should be unique within the scene graph, this uniqueness is not enforced. 143 * This is analogous to the "id" attribute on an HTML element 144 * (<a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS ID Specification</a>). 145 * 146 * @param value the id assigned to this {@code PopupControl} using the {@code setId} 147 * method or {@code null}, if no id has been assigned. 148 * @defaultValue null 149 */ 150 public final void setId(String value) { idProperty().set(value); } 151 152 /** 153 * The id of this {@code PopupControl}. This simple string identifier is useful for 154 * finding a specific Node within the scene graph. While the id of a Node 155 * should be unique within the scene graph, this uniqueness is not enforced. 156 * This is analogous to the "id" attribute on an HTML element 157 * (<a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS ID Specification</a>). 158 * 159 * @return the id assigned to this {@code PopupControl} using the {@code setId} 160 * method or {@code null}, if no id has been assigned. 161 * @defaultValue null 162 */ 163 @Override public final String getId() { return idProperty().get(); } 164 165 /** 166 * Returns the list of String identifiers that make up the styleClass 167 * for this PopupControl. 168 */ 169 @Override public final ObservableList<String> getStyleClass() { return bridge.getStyleClass(); } 170 171 /** 172 * A string representation of the CSS style associated with this 173 * specific {@code PopupControl}. This is analogous to the "style" attribute of an 174 * HTML element. Note that, like the HTML style attribute, this 175 * variable contains style properties and values and not the 176 * selector portion of a style rule. 177 * @param value The inline CSS style to use for this {@code PopupControl}. 178 * {@code null} is implicitly converted to an empty String. 179 * @defaultValue empty string 180 */ 181 public final void setStyle(String value) { styleProperty().set(value); } 182 183 // TODO: javadoc copied from property for the sole purpose of providing a return tag 184 /** 185 * A string representation of the CSS style associated with this 186 * specific {@code PopupControl}. This is analogous to the "style" attribute of an 187 * HTML element. Note that, like the HTML style attribute, this 188 * variable contains style properties and values and not the 189 * selector portion of a style rule. 190 * @defaultValue empty string 191 * @return The inline CSS style associated with this {@code PopupControl}. 192 * If this {@code PopupControl} does not have an inline style, 193 * an empty String is returned. 194 */ 195 @Override public final String getStyle() { return styleProperty().get(); } 196 public final StringProperty styleProperty() { return bridge.styleProperty(); } 197 198 /** 199 * Skin is responsible for rendering this {@code PopupControl}. From the 200 * perspective of the {@code PopupControl}, the {@code Skin} is a black box. 201 * It listens and responds to changes in state in a {@code PopupControl}. 202 * <p> 203 * There is a one-to-one relationship between a {@code PopupControl} and its 204 * {@code Skin}. Every {@code Skin} maintains a back reference to the 205 * {@code PopupControl}. 206 * <p> 207 * A skin may be null. 208 */ 209 @Override public final ObjectProperty<Skin<?>> skinProperty() { 210 return skin; 211 } 212 213 @Override public final void setSkin(Skin<?> value) { 214 skinProperty().setValue(value); 215 } 216 217 @Override public final Skin<?> getSkin() { 218 return skinProperty().getValue(); 219 } 220 221 private final ObjectProperty<Skin<?>> skin = new ObjectPropertyBase<Skin<?>>() { 222 // We store a reference to the oldValue so that we can handle 223 // changes in the skin properly in the case of binding. This is 224 // only needed because invalidated() does not currently take 225 // a reference to the old value. 226 private Skin<?> oldValue; 227 228 @Override 229 public void set(Skin<?> v) { 230 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 240 @Override protected void invalidated() { 241 Skin<?> skin = get(); 242 243 // Collect the name of the currently installed skin class. We do this 244 // so that subsequent updates from CSS to the same skin class will not 245 // result in reinstalling the skin 246 currentSkinClassName = skin == null ? null : skin.getClass().getName(); 247 248 // if someone calls setSkin, we need to make it look like they 249 // called set on skinClassName in order to keep CSS from overwriting 250 // the skin. 251 skinClassNameProperty().set(currentSkinClassName); 252 253 // Let CSS know that this property has been manually changed 254 // Dispose of the old skin 255 if (oldValue != null) { 256 oldValue.dispose(); 257 } 258 259 // Get the new value, and save it off as the new oldValue 260 oldValue = getValue(); 261 262 prefWidthCache = -1; 263 prefHeightCache = -1; 264 minWidthCache = -1; 265 minHeightCache = -1; 266 maxWidthCache = -1; 267 maxHeightCache = -1; 268 skinSizeComputed = false; 269 270 final Node n = getSkinNode(); 271 if (n != null) { 272 bridge.getChildren().setAll(n); 273 } else { 274 bridge.getChildren().clear(); 275 } 276 277 // calling NodeHelper.reapplyCSS() as the styleable properties may now 278 // be different, as we will now be able to return styleable properties 279 // belonging to the skin. If NodeHelper.reapplyCSS() is not called, the 280 // getCssMetaData() method is never called, so the 281 // skin properties are never exposed. 282 NodeHelper.reapplyCSS(bridge); 283 284 // DEBUG: Log that we've changed the skin 285 final PlatformLogger logger = Logging.getControlsLogger(); 286 if (logger.isLoggable(Level.FINEST)) { 287 logger.finest("Stored skin[" + getValue() + "] on " + this); 288 } 289 } 290 291 @Override 292 public Object getBean() { 293 return PopupControl.this; 294 } 295 296 @Override 297 public String getName() { 298 return "skin"; 299 } 300 }; 301 /** 302 * Keeps a reference to the name of the class currently acting as the skin. 303 */ 304 private String currentSkinClassName = null; 305 /** 306 * A property that acts as a proxy between the skin property and css. 307 */ 308 private StringProperty skinClassName = null; 309 private StringProperty skinClassNameProperty() { 310 if (skinClassName == null) { 311 skinClassName = new StyleableStringProperty() { 312 313 @Override 314 public void set(String v) { 315 // do not allow the skin to be set to null through CSS 316 if (v == null || v.isEmpty() || v.equals(get())) return; 317 super.set(v); 318 } 319 320 @Override 321 public void invalidated() { 322 323 // 324 // if the current skin is not null, then 325 // see if then check to see if the current skin's class name 326 // is the same as skinClassName. If it is, then there is 327 // no need to load the skin class. Note that the only time 328 // this would be the case is if someone called setSkin since 329 // the skin would be set ahead of the skinClassName 330 // (skinClassName is set from the skinProperty's invalidated 331 // method, so the skin would be set, then the skinClassName). 332 // If the skinClassName is set first (via CSS), then this 333 // invalidated method won't get called unless the value 334 // has changed (thus, we won't reload the same skin). 335 // 336 if (get() != null) { 337 if (!get().equals(currentSkinClassName)) { 338 Control.loadSkinClass(PopupControl.this, get()); 339 } 340 // CSS should not set skin to null 341 // } else { 342 // setSkin(null); 343 } 344 } 345 346 @Override 347 public Object getBean() { 348 return PopupControl.this; 349 } 350 351 @Override 352 public String getName() { 353 return "skinClassName"; 354 } 355 356 @Override 357 public CssMetaData<CSSBridge,String> getCssMetaData() { 358 return SKIN; 359 } 360 361 }; 362 } 363 return skinClassName; 364 } 365 366 /** 367 * Gets the Skin's node, or returns null if there is no Skin. 368 * Convenience method for getting the node of the skin. This is null-safe, 369 * meaning if skin is null then it will return null instead of throwing 370 * a NullPointerException. 371 * 372 * @return The Skin's node, or null. 373 */ 374 private Node getSkinNode() { 375 return getSkin() == null ? null : getSkin().getNode(); 376 } 377 378 /** 379 * Property for overriding the control's computed minimum width. 380 * This should only be set if the control's internally computed minimum width 381 * doesn't meet the application's layout needs. 382 * <p> 383 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 384 * <code>getMinWidth(forHeight)</code> will return the control's internally 385 * computed minimum width. 386 * <p> 387 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 388 * <code>getMinWidth(forHeight)</code> to return the control's preferred width, 389 * enabling applications to easily restrict the resizability of the control. 390 */ 391 private DoubleProperty minWidth; 392 393 /** 394 * Property for overriding the control's computed minimum width. 395 * This should only be set if the control's internally computed minimum width 396 * doesn't meet the application's layout needs. 397 * <p> 398 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 399 * <code>getMinWidth(forHeight)</code> will return the control's internally 400 * computed minimum width. 401 * <p> 402 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 403 * <code>getMinWidth(forHeight)</code> to return the control's preferred width, 404 * enabling applications to easily restrict the resizability of the control. 405 * @param value the minimum width 406 */ 407 public final void setMinWidth(double value) { minWidthProperty().set(value); } 408 409 /** 410 * Property for overriding the control's computed minimum width. 411 * This should only be set if the control's internally computed minimum width 412 * doesn't meet the application's layout needs. 413 * <p> 414 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 415 * <code>getMinWidth(forHeight)</code> will return the control's internally 416 * computed minimum width. 417 * <p> 418 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 419 * <code>getMinWidth(forHeight)</code> to return the control's preferred width, 420 * enabling applications to easily restrict the resizability of the control. 421 * @return the minimum width 422 */ 423 public final double getMinWidth() { return minWidth == null ? USE_COMPUTED_SIZE : minWidth.get(); } 424 public final DoubleProperty minWidthProperty() { 425 if (minWidth == null) { 426 minWidth = new DoublePropertyBase(USE_COMPUTED_SIZE) { 427 @Override public void invalidated() { 428 if (isShowing()) bridge.requestLayout(); 429 } 430 431 @Override 432 public Object getBean() { 433 return PopupControl.this; 434 } 435 436 @Override 437 public String getName() { 438 return "minWidth"; 439 } 440 }; 441 } 442 return minWidth; 443 } 444 445 446 /** 447 * Property for overriding the control's computed minimum height. 448 * This should only be set if the control's internally computed minimum height 449 * doesn't meet the application's layout needs. 450 * <p> 451 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 452 * <code>getMinHeight(forWidth)</code> will return the control's internally 453 * computed minimum height. 454 * <p> 455 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 456 * <code>getMinHeight(forWidth)</code> to return the control's preferred height, 457 * enabling applications to easily restrict the resizability of the control. 458 * 459 */ 460 private DoubleProperty minHeight; 461 462 /** 463 * Property for overriding the control's computed minimum height. 464 * This should only be set if the control's internally computed minimum height 465 * doesn't meet the application's layout needs. 466 * <p> 467 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 468 * <code>getMinHeight(forWidth)</code> will return the control's internally 469 * computed minimum height. 470 * <p> 471 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 472 * <code>getMinHeight(forWidth)</code> to return the control's preferred height, 473 * enabling applications to easily restrict the resizability of the control. 474 * 475 * @param value the minimum height 476 */ 477 public final void setMinHeight(double value) { minHeightProperty().set(value); } 478 479 /** 480 * Property for overriding the control's computed minimum height. 481 * This should only be set if the control's internally computed minimum height 482 * doesn't meet the application's layout needs. 483 * <p> 484 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 485 * <code>getMinHeight(forWidth)</code> will return the control's internally 486 * computed minimum height. 487 * <p> 488 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 489 * <code>getMinHeight(forWidth)</code> to return the control's preferred height, 490 * enabling applications to easily restrict the resizability of the control. 491 * 492 * @return the minimum height 493 */ 494 public final double getMinHeight() { return minHeight == null ? USE_COMPUTED_SIZE : minHeight.get(); } 495 public final DoubleProperty minHeightProperty() { 496 if (minHeight == null) { 497 minHeight = new DoublePropertyBase(USE_COMPUTED_SIZE) { 498 @Override public void invalidated() { 499 if (isShowing()) bridge.requestLayout(); 500 } 501 502 @Override 503 public Object getBean() { 504 return PopupControl.this; 505 } 506 507 @Override 508 public String getName() { 509 return "minHeight"; 510 } 511 }; 512 } 513 return minHeight; 514 } 515 516 /** 517 * Convenience method for overriding the control's computed minimum width and height. 518 * This should only be called if the control's internally computed minimum size 519 * doesn't meet the application's layout needs. 520 * 521 * @see #setMinWidth 522 * @see #setMinHeight 523 * @param minWidth the override value for minimum width 524 * @param minHeight the override value for minimum height 525 */ 526 public void setMinSize(double minWidth, double minHeight) { 527 setMinWidth(minWidth); 528 setMinHeight(minHeight); 529 } 530 531 /** 532 * Property for overriding the control's computed preferred width. 533 * This should only be set if the control's internally computed preferred width 534 * doesn't meet the application's layout needs. 535 * <p> 536 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 537 * <code>getPrefWidth(forHeight)</code> will return the control's internally 538 * computed preferred width. 539 */ 540 private DoubleProperty prefWidth; 541 542 /** 543 * Property for overriding the control's computed preferred width. 544 * This should only be set if the control's internally computed preferred width 545 * doesn't meet the application's layout needs. 546 * <p> 547 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 548 * <code>getPrefWidth(forHeight)</code> will return the control's internally 549 * computed preferred width. 550 * @param value the preferred width 551 */ 552 public final void setPrefWidth(double value) { prefWidthProperty().set(value); } 553 554 /** 555 * Property for overriding the control's computed preferred width. 556 * This should only be set if the control's internally computed preferred width 557 * doesn't meet the application's layout needs. 558 * <p> 559 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 560 * <code>getPrefWidth(forHeight)</code> will return the control's internally 561 * computed preferred width. 562 * @return the preferred width 563 */ 564 public final double getPrefWidth() { return prefWidth == null ? USE_COMPUTED_SIZE : prefWidth.get(); } 565 public final DoubleProperty prefWidthProperty() { 566 if (prefWidth == null) { 567 prefWidth = new DoublePropertyBase(USE_COMPUTED_SIZE) { 568 @Override public void invalidated() { 569 if (isShowing()) bridge.requestLayout(); 570 } 571 572 @Override 573 public Object getBean() { 574 return PopupControl.this; 575 } 576 577 @Override 578 public String getName() { 579 return "prefWidth"; 580 } 581 }; 582 } 583 return prefWidth; 584 } 585 586 /** 587 * Property for overriding the control's computed preferred height. 588 * This should only be set if the control's internally computed preferred height 589 * doesn't meet the application's layout needs. 590 * <p> 591 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 592 * <code>getPrefHeight(forWidth)</code> will return the control's internally 593 * computed preferred width. 594 * 595 */ 596 private DoubleProperty prefHeight; 597 598 /** 599 * Property for overriding the control's computed preferred height. 600 * This should only be set if the control's internally computed preferred height 601 * doesn't meet the application's layout needs. 602 * <p> 603 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 604 * <code>getPrefHeight(forWidth)</code> will return the control's internally 605 * computed preferred width. 606 * 607 * @param value the preferred height 608 */ 609 public final void setPrefHeight(double value) { prefHeightProperty().set(value); } 610 611 /** 612 * Property for overriding the control's computed preferred height. 613 * This should only be set if the control's internally computed preferred height 614 * doesn't meet the application's layout needs. 615 * <p> 616 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 617 * <code>getPrefHeight(forWidth)</code> will return the control's internally 618 * computed preferred width. 619 * 620 * @return the preferred height 621 */ 622 public final double getPrefHeight() { return prefHeight == null ? USE_COMPUTED_SIZE : prefHeight.get(); } 623 public final DoubleProperty prefHeightProperty() { 624 if (prefHeight == null) { 625 prefHeight = new DoublePropertyBase(USE_COMPUTED_SIZE) { 626 @Override public void invalidated() { 627 if (isShowing()) bridge.requestLayout(); 628 } 629 630 @Override 631 public Object getBean() { 632 return PopupControl.this; 633 } 634 635 @Override 636 public String getName() { 637 return "prefHeight"; 638 } 639 }; 640 } 641 return prefHeight; 642 } 643 644 /** 645 * Convenience method for overriding the control's computed preferred width and height. 646 * This should only be called if the control's internally computed preferred size 647 * doesn't meet the application's layout needs. 648 * 649 * @see #setPrefWidth 650 * @see #setPrefHeight 651 * @param prefWidth the override value for preferred width 652 * @param prefHeight the override value for preferred height 653 */ 654 public void setPrefSize(double prefWidth, double prefHeight) { 655 setPrefWidth(prefWidth); 656 setPrefHeight(prefHeight); 657 } 658 659 /** 660 * Property for overriding the control's computed maximum width. 661 * This should only be set if the control's internally computed maximum width 662 * doesn't meet the application's layout needs. 663 * <p> 664 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 665 * <code>getMaxWidth(forHeight)</code> will return the control's internally 666 * computed maximum width. 667 * <p> 668 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 669 * <code>getMaxWidth(forHeight)</code> to return the control's preferred width, 670 * enabling applications to easily restrict the resizability of the control. 671 */ 672 private DoubleProperty maxWidth; 673 674 /** 675 * Property for overriding the control's computed maximum width. 676 * This should only be set if the control's internally computed maximum width 677 * doesn't meet the application's layout needs. 678 * <p> 679 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 680 * <code>getMaxWidth(forHeight)</code> will return the control's internally 681 * computed maximum width. 682 * <p> 683 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 684 * <code>getMaxWidth(forHeight)</code> to return the control's preferred width, 685 * enabling applications to easily restrict the resizability of the control. 686 * @param value the maximum width 687 */ 688 public final void setMaxWidth(double value) { maxWidthProperty().set(value); } 689 690 /** 691 * Property for overriding the control's computed maximum width. 692 * This should only be set if the control's internally computed maximum width 693 * doesn't meet the application's layout needs. 694 * <p> 695 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 696 * <code>getMaxWidth(forHeight)</code> will return the control's internally 697 * computed maximum width. 698 * <p> 699 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 700 * <code>getMaxWidth(forHeight)</code> to return the control's preferred width, 701 * enabling applications to easily restrict the resizability of the control. 702 * @return the maximum width 703 */ 704 public final double getMaxWidth() { return maxWidth == null ? USE_COMPUTED_SIZE : maxWidth.get(); } 705 public final DoubleProperty maxWidthProperty() { 706 if (maxWidth == null) { 707 maxWidth = new DoublePropertyBase(USE_COMPUTED_SIZE) { 708 @Override public void invalidated() { 709 if (isShowing()) bridge.requestLayout(); 710 } 711 712 @Override 713 public Object getBean() { 714 return PopupControl.this; 715 } 716 717 @Override 718 public String getName() { 719 return "maxWidth"; 720 } 721 }; 722 } 723 return maxWidth; 724 } 725 726 /** 727 * Property for overriding the control's computed maximum height. 728 * This should only be set if the control's internally computed maximum height 729 * doesn't meet the application's layout needs. 730 * <p> 731 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 732 * <code>getMaxHeight(forWidth)</code> will return the control's internally 733 * computed maximum height. 734 * <p> 735 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 736 * <code>getMaxHeight(forWidth)</code> to return the control's preferred height, 737 * enabling applications to easily restrict the resizability of the control. 738 * 739 */ 740 private DoubleProperty maxHeight; 741 742 /** 743 * Property for overriding the control's computed maximum height. 744 * This should only be set if the control's internally computed maximum height 745 * doesn't meet the application's layout needs. 746 * <p> 747 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 748 * <code>getMaxHeight(forWidth)</code> will return the control's internally 749 * computed maximum height. 750 * <p> 751 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 752 * <code>getMaxHeight(forWidth)</code> to return the control's preferred height, 753 * enabling applications to easily restrict the resizability of the control. 754 * 755 * @param value the maximum height 756 */ 757 public final void setMaxHeight(double value) { maxHeightProperty().set(value); } 758 759 /** 760 * Property for overriding the control's computed maximum height. 761 * This should only be set if the control's internally computed maximum height 762 * doesn't meet the application's layout needs. 763 * <p> 764 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 765 * <code>getMaxHeight(forWidth)</code> will return the control's internally 766 * computed maximum height. 767 * <p> 768 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 769 * <code>getMaxHeight(forWidth)</code> to return the control's preferred height, 770 * enabling applications to easily restrict the resizability of the control. 771 * 772 * @return the maximum height 773 */ 774 public final double getMaxHeight() { return maxHeight == null ? USE_COMPUTED_SIZE : maxHeight.get(); } 775 public final DoubleProperty maxHeightProperty() { 776 if (maxHeight == null) { 777 maxHeight = new DoublePropertyBase(USE_COMPUTED_SIZE) { 778 @Override public void invalidated() { 779 if (isShowing()) bridge.requestLayout(); 780 } 781 782 @Override 783 public Object getBean() { 784 return PopupControl.this; 785 } 786 787 @Override 788 public String getName() { 789 return "maxHeight"; 790 } 791 }; 792 } 793 return maxHeight; 794 } 795 796 /** 797 * Convenience method for overriding the control's computed maximum width and height. 798 * This should only be called if the control's internally computed maximum size 799 * doesn't meet the application's layout needs. 800 * 801 * @see #setMaxWidth 802 * @see #setMaxHeight 803 * @param maxWidth the override value for maximum width 804 * @param maxHeight the override value for maximum height 805 */ 806 public void setMaxSize(double maxWidth, double maxHeight) { 807 setMaxWidth(maxWidth); 808 setMaxHeight(maxHeight); 809 } 810 811 /** 812 * Cached prefWidth, prefHeight, minWidth, minHeight. These 813 * results are repeatedly sought during the layout pass, 814 * and caching the results leads to a significant decrease 815 * in overhead. 816 */ 817 private double prefWidthCache = -1; 818 private double prefHeightCache = -1; 819 private double minWidthCache = -1; 820 private double minHeightCache = -1; 821 private double maxWidthCache = -1; 822 private double maxHeightCache = -1; 823 private boolean skinSizeComputed = false; 824 825 /** 826 * Called during layout to determine the minimum width for this node. 827 * Returns the value from <code>minWidth(forHeight)</code> unless 828 * the application overrode the minimum width by setting the minWidth property. 829 * 830 * @param height the height 831 * @see #setMinWidth 832 * @return the minimum width that this node should be resized to during layout 833 */ 834 public final double minWidth(double height) { 835 double override = getMinWidth(); 836 if (override == USE_COMPUTED_SIZE) { 837 if (minWidthCache == -1) minWidthCache = recalculateMinWidth(height); 838 return minWidthCache; 839 } else if (override == USE_PREF_SIZE) { 840 return prefWidth(height); 841 } 842 return override; 843 } 844 845 /** 846 * Called during layout to determine the minimum height for this node. 847 * Returns the value from <code>minHeight(forWidth)</code> unless 848 * the application overrode the minimum height by setting the minHeight property. 849 * 850 * @param width The width 851 * @see #setMinHeight 852 * @return the minimum height that this node should be resized to during layout 853 */ 854 public final double minHeight(double width) { 855 double override = getMinHeight(); 856 if (override == USE_COMPUTED_SIZE) { 857 if (minHeightCache == -1) minHeightCache = recalculateMinHeight(width); 858 return minHeightCache; 859 } else if (override == USE_PREF_SIZE) { 860 return prefHeight(width); 861 } 862 return override; 863 } 864 865 866 /** 867 * Called during layout to determine the preferred width for this node. 868 * Returns the value from <code>prefWidth(forHeight)</code> unless 869 * the application overrode the preferred width by setting the prefWidth property. 870 * 871 * @param height the height 872 * @see #setPrefWidth 873 * @return the preferred width that this node should be resized to during layout 874 */ 875 public final double prefWidth(double height) { 876 double override = getPrefWidth(); 877 if (override == USE_COMPUTED_SIZE) { 878 if (prefWidthCache == -1) prefWidthCache = recalculatePrefWidth(height); 879 return prefWidthCache; 880 } else if (override == USE_PREF_SIZE) { 881 return prefWidth(height); 882 } 883 return override; 884 } 885 886 /** 887 * Called during layout to determine the preferred height for this node. 888 * Returns the value from <code>prefHeight(forWidth)</code> unless 889 * the application overrode the preferred height by setting the prefHeight property. 890 * 891 * @param width the width 892 * @see #setPrefHeight 893 * @return the preferred height that this node should be resized to during layout 894 */ 895 public final double prefHeight(double width) { 896 double override = getPrefHeight(); 897 if (override == USE_COMPUTED_SIZE) { 898 if (prefHeightCache == -1) prefHeightCache = recalculatePrefHeight(width); 899 return prefHeightCache; 900 } else if (override == USE_PREF_SIZE) { 901 return prefHeight(width); 902 } 903 return override; 904 } 905 906 /** 907 * Called during layout to determine the maximum width for this node. 908 * Returns the value from <code>maxWidth(forHeight)</code> unless 909 * the application overrode the maximum width by setting the maxWidth property. 910 * 911 * @param height the height 912 * @see #setMaxWidth 913 * @return the maximum width that this node should be resized to during layout 914 */ 915 public final double maxWidth(double height) { 916 double override = getMaxWidth(); 917 if (override == USE_COMPUTED_SIZE) { 918 if (maxWidthCache == -1) maxWidthCache = recalculateMaxWidth(height); 919 return maxWidthCache; 920 } else if (override == USE_PREF_SIZE) { 921 return prefWidth(height); 922 } 923 return override; 924 } 925 926 /** 927 * Called during layout to determine the maximum height for this node. 928 * Returns the value from <code>maxHeight(forWidth)</code> unless 929 * the application overrode the maximum height by setting the maxHeight property. 930 * 931 * @param width the width 932 * @see #setMaxHeight 933 * @return the maximum height that this node should be resized to during layout 934 */ 935 public final double maxHeight(double width) { 936 double override = getMaxHeight(); 937 if (override == USE_COMPUTED_SIZE) { 938 if (maxHeightCache == -1) maxHeightCache = recalculateMaxHeight(width); 939 return maxHeightCache; 940 } else if (override == USE_PREF_SIZE) { 941 return prefHeight(width); 942 } 943 return override; 944 } 945 946 // Implementation of the Resizable interface. 947 // Because only the skin can know the min, pref, and max sizes, these 948 // functions are implemented to delegate to skin. If there is no skin then 949 // we simply return 0 for all the values since a Control without a Skin 950 // doesn't render 951 private double recalculateMinWidth(double height) { 952 recomputeSkinSize(); 953 return getSkinNode() == null ? 0 : getSkinNode().minWidth(height); 954 } 955 private double recalculateMinHeight(double width) { 956 recomputeSkinSize(); 957 return getSkinNode() == null ? 0 : getSkinNode().minHeight(width); 958 } 959 private double recalculateMaxWidth(double height) { 960 recomputeSkinSize(); 961 return getSkinNode() == null ? 0 : getSkinNode().maxWidth(height); 962 } 963 private double recalculateMaxHeight(double width) { 964 recomputeSkinSize(); 965 return getSkinNode() == null ? 0 : getSkinNode().maxHeight(width); 966 } 967 private double recalculatePrefWidth(double height) { 968 recomputeSkinSize(); 969 return getSkinNode() == null? 0 : getSkinNode().prefWidth(height); 970 } 971 private double recalculatePrefHeight(double width) { 972 recomputeSkinSize(); 973 return getSkinNode() == null? 0 : getSkinNode().prefHeight(width); 974 } 975 976 private void recomputeSkinSize() { 977 if (!skinSizeComputed) { 978 // RT-14094, RT-16754: We need the skins of the popup 979 // and it children before the stage is visible so we 980 // can calculate the popup position based on content 981 // size. 982 bridge.applyCss(); 983 skinSizeComputed = true; 984 } 985 } 986 // public double getBaselineOffset() { return getSkinNode() == null? 0 : getSkinNode().getBaselineOffset(); } 987 988 /** 989 * Create a new instance of the default skin for this control. This is called to create a skin for the control if 990 * no skin is provided via CSS {@code -fx-skin} or set explicitly in a sub-class with {@code setSkin(...)}. 991 * 992 * @return new instance of default skin for this control. If null then the control will have no skin unless one 993 * is provided by css. 994 * @since JavaFX 8.0 995 */ 996 protected Skin<?> createDefaultSkin() { 997 return null; 998 } 999 1000 /*************************************************************************** 1001 * * 1002 * StyleSheet Handling * 1003 * * 1004 **************************************************************************/ 1005 1006 private static final CssMetaData<CSSBridge,String> SKIN = 1007 new CssMetaData<CSSBridge,String>("-fx-skin", 1008 StringConverter.getInstance()) { 1009 1010 @Override 1011 public boolean isSettable(CSSBridge cssBridge) { 1012 return !cssBridge.popupControl.skinProperty().isBound(); 1013 } 1014 1015 @Override 1016 public StyleableProperty<String> getStyleableProperty(CSSBridge cssBridge) { 1017 return (StyleableProperty<String>)(WritableValue<String>)cssBridge.popupControl.skinClassNameProperty(); 1018 } 1019 }; 1020 1021 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 1022 static { 1023 final List<CssMetaData<? extends Styleable, ?>> styleables = 1024 new ArrayList<CssMetaData<? extends Styleable, ?>>(); 1025 Collections.addAll(styleables, 1026 SKIN 1027 ); 1028 STYLEABLES = Collections.unmodifiableList(styleables); 1029 } 1030 1031 /** 1032 * @return The CssMetaData associated with this class, which may include the 1033 * CssMetaData of its superclasses. 1034 * @since JavaFX 8.0 1035 */ 1036 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 1037 return STYLEABLES; 1038 } 1039 1040 /** 1041 * {@inheritDoc} 1042 * @since JavaFX 8.0 1043 */ 1044 @Override 1045 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 1046 return getClassCssMetaData(); 1047 } 1048 1049 /** 1050 * @param pseudoClass the pseudo class 1051 * @param active the active state 1052 * @see Node#pseudoClassStateChanged(javafx.css.PseudoClass, boolean) 1053 * @since JavaFX 8.0 1054 */ 1055 public final void pseudoClassStateChanged(PseudoClass pseudoClass, boolean active) { 1056 bridge.pseudoClassStateChanged(pseudoClass, active); 1057 } 1058 1059 /** 1060 * {@inheritDoc} 1061 * @return "PopupControl" 1062 * @since JavaFX 8.0 1063 */ 1064 @Override 1065 public String getTypeSelector() { 1066 return "PopupControl"; 1067 } 1068 1069 /** 1070 * {@inheritDoc} 1071 * 1072 * {@literal A PopupControl's styles are based on the popup "owner" which is the} 1073 * {@link javafx.stage.PopupWindow#getOwnerNode() ownerNode} or, 1074 * if the ownerNode is not set, the root of the 1075 * {@link javafx.stage.PopupWindow#getOwnerWindow() ownerWindow's} 1076 * scene. If the popup has not been shown, both ownerNode and ownerWindow will be null and {@code null} will be returned. 1077 * 1078 * {@literal Note that the PopupWindow's scene root is not returned because 1079 * there is no way to guarantee that the PopupWindow's scene root would 1080 * properly return the ownerNode or ownerWindow.} 1081 * 1082 * @return {@link javafx.stage.PopupWindow#getOwnerNode()}, {@link javafx.stage.PopupWindow#getOwnerWindow()}, 1083 * or null. 1084 * @since JavaFX 8.0 1085 */ 1086 @Override 1087 public Styleable getStyleableParent() { 1088 1089 final Node ownerNode = getOwnerNode(); 1090 if (ownerNode != null) { 1091 return ownerNode; 1092 1093 } else { 1094 1095 final Window ownerWindow = getOwnerWindow(); 1096 if (ownerWindow != null) { 1097 1098 final Scene ownerScene = ownerWindow.getScene(); 1099 if (ownerScene != null) { 1100 return ownerScene.getRoot(); 1101 } 1102 } 1103 } 1104 1105 return bridge.getParent(); 1106 1107 } 1108 1109 /** 1110 * {@inheritDoc} 1111 * @since JavaFX 8.0 1112 */ 1113 @Override 1114 public final ObservableSet<PseudoClass> getPseudoClassStates() { 1115 return FXCollections.emptyObservableSet(); 1116 } 1117 1118 /** {@inheritDoc} */ 1119 @Override public Node getStyleableNode() { 1120 return bridge; 1121 } 1122 1123 /** 1124 * The link between the popup window and the scenegraph. 1125 * 1126 * @since JavaFX 2.1 1127 */ 1128 protected class CSSBridge extends Pane { 1129 1130 private final PopupControl popupControl = PopupControl.this; 1131 1132 { 1133 // To initialize the class helper at the begining each constructor of this class 1134 CSSBridgeHelper.initHelper(this); 1135 } 1136 1137 /** 1138 * Requests a layout pass to be performed before the next scene is 1139 * rendered. This is batched up asynchronously to happen once per 1140 * "pulse", or frame of animation. 1141 * <p> 1142 * If this parent is either a layout root or unmanaged, then it will be 1143 * added directly to the scene's dirty layout list, otherwise requestLayout 1144 * will be invoked on its parent. 1145 */ 1146 @Override public void requestLayout() { 1147 prefWidthCache = -1; 1148 prefHeightCache = -1; 1149 minWidthCache = -1; 1150 minHeightCache = -1; 1151 maxWidthCache = -1; 1152 maxHeightCache = -1; 1153 //skinSizeComputed = false; -- RT-33073 disabled this 1154 super.requestLayout(); 1155 } 1156 1157 /** 1158 * This method should be treated as final and should not be overridden by any subclasses of CSSBridge. 1159 * @return the styleable parent 1160 */ 1161 @Override 1162 public Styleable getStyleableParent() { 1163 return PopupControl.this.getStyleableParent(); 1164 } 1165 1166 @Override 1167 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 1168 return PopupControl.this.getCssMetaData(); 1169 } 1170 1171 /* 1172 * Note: This method MUST only be called via its accessor method. 1173 */ 1174 private List<String> doGetAllParentStylesheets() { 1175 Styleable styleable = getStyleableParent(); 1176 if (styleable instanceof Parent) { 1177 return ParentHelper.getAllParentStylesheets((Parent)styleable); 1178 } 1179 return null; 1180 } 1181 1182 /* 1183 * Note: This method MUST only be called via its accessor method. 1184 */ 1185 private void doProcessCSS() { 1186 CSSBridgeHelper.superProcessCSS(this); 1187 1188 if (getSkin() == null) { 1189 // try to create default skin 1190 final Skin<?> defaultSkin = createDefaultSkin(); 1191 if (defaultSkin != null) { 1192 skinProperty().set(defaultSkin); 1193 CSSBridgeHelper.superProcessCSS(this); 1194 } else { 1195 final String msg = "The -fx-skin property has not been defined in CSS for " + this + 1196 " and createDefaultSkin() returned null."; 1197 final List<CssParser.ParseError> errors = StyleManager.getErrors(); 1198 if (errors != null) { 1199 CssParser.ParseError error = new CssParser.ParseError(msg); 1200 errors.add(error); // RT-19884 1201 } 1202 Logging.getControlsLogger().severe(msg); 1203 } 1204 } 1205 } 1206 1207 } 1208 1209 /* 1210 * Used to access internal methods of CSSBridge. 1211 */ 1212 static final class CSSBridgeHelper extends PaneHelper { 1213 private static final CSSBridgeHelper theInstance; 1214 1215 static { 1216 theInstance = new CSSBridgeHelper(); 1217 } 1218 1219 private static CSSBridgeHelper getInstance() { 1220 return theInstance; 1221 } 1222 1223 public static void initHelper(CSSBridge cssBridge) { 1224 setHelper(cssBridge, getInstance()); 1225 } 1226 1227 public static void superProcessCSS(Node node) { 1228 ((CSSBridgeHelper) getHelper(node)).superProcessCSSImpl(node); 1229 } 1230 1231 void superProcessCSSImpl(Node node) { 1232 super.processCSSImpl(node); 1233 } 1234 1235 @Override 1236 protected void processCSSImpl(Node node) { 1237 ((CSSBridge) node).doProcessCSS(); 1238 } 1239 1240 @Override 1241 protected List<String> getAllParentStylesheetsImpl(Parent parent) { 1242 return ((CSSBridge) parent).doGetAllParentStylesheets(); 1243 } 1244 } 1245 1246 }