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