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