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 
  29 import com.sun.javafx.css.StyleManager;
  30 import com.sun.javafx.scene.NodeHelper;
  31 import javafx.css.converter.BooleanConverter;
  32 import javafx.css.converter.EnumConverter;
  33 import javafx.css.converter.InsetsConverter;
  34 import javafx.css.converter.PaintConverter;
  35 import javafx.css.converter.SizeConverter;
  36 import javafx.css.converter.StringConverter;
  37 
  38 import java.util.ArrayList;
  39 import java.util.Collections;
  40 import java.util.List;
  41 
  42 import javafx.beans.property.BooleanProperty;
  43 import javafx.beans.property.DoubleProperty;
  44 import javafx.beans.property.ObjectProperty;
  45 import javafx.beans.property.ReadOnlyObjectProperty;
  46 import javafx.beans.property.SimpleBooleanProperty;
  47 import javafx.beans.property.SimpleStringProperty;
  48 import javafx.beans.property.StringProperty;
  49 import javafx.beans.value.WritableValue;
  50 import javafx.geometry.Insets;
  51 import javafx.geometry.Orientation;
  52 import javafx.geometry.Pos;
  53 import javafx.scene.Node;
  54 import javafx.scene.image.Image;
  55 import javafx.scene.image.ImageView;
  56 import javafx.scene.paint.Color;
  57 import javafx.scene.paint.Paint;
  58 import javafx.scene.text.Font;
  59 import javafx.scene.text.TextAlignment;
  60 import javafx.beans.DefaultProperty;
  61 import javafx.css.CssMetaData;
  62 import javafx.css.FontCssMetaData;
  63 import javafx.css.StyleOrigin;
  64 import javafx.css.Styleable;
  65 import javafx.css.StyleableBooleanProperty;
  66 import javafx.css.StyleableDoubleProperty;
  67 import javafx.css.StyleableObjectProperty;
  68 import javafx.css.StyleableProperty;
  69 import javafx.css.StyleableStringProperty;
  70 
  71 
  72 /**
  73  * A Labeled {@link Control} is one which has as part of its user interface
  74  * a textual content associated with it. For example, a {@link Button} displays
  75  * {@code text}, as does a {@link Label}, a {@link Tooltip}, and many
  76  * other controls.
  77  * <p>
  78  * Labeled is also a convenient base class from which to extend when building
  79  * new Controls which, as part of their UI, display read-only textual content.
  80  * </p>
  81  *
  82  * <p>Example of how to place a graphic above the text:
  83  * <pre><code>
  84  *  Image image = new Image(getClass().getResourceAsStream("image.png"));
  85  *  ImageView imageView = new ImageView();
  86  *  imageView.setImage(image);
  87  *  Label label = new Label("text", imageView);
  88  *  label.setContentDisplay(ContentDisplay.TOP);
  89  * </code></pre>
  90  *
  91  * @see Button
  92  * @see Label
  93  * @see ToggleButton
  94  * @since JavaFX 2.0
  95  */
  96 @DefaultProperty("text")
  97 public abstract class Labeled extends Control {
  98 
  99     private final static String DEFAULT_ELLIPSIS_STRING = "...";
 100 
 101 
 102     /***************************************************************************
 103      *                                                                         *
 104      * Constructors                                                            *
 105      *                                                                         *
 106      **************************************************************************/
 107 
 108     /**
 109      * Creates a Label with no text and graphic
 110      */
 111     public Labeled() { }
 112 
 113     /**
 114      * Creates a Label with text
 115      * @param text The text for the label.
 116      */
 117     public Labeled(String text) {
 118         setText(text);
 119     }
 120 
 121     /**
 122      * Creates a Label with text and a graphic
 123      * @param text The text for the label.
 124      * @param graphic The graphic for the label.
 125      */
 126     public Labeled(String text, Node graphic) {
 127         setText(text);
 128         ((StyleableProperty<Node>)(WritableValue<Node>)graphicProperty()).applyStyle(null, graphic);
 129     }
 130 
 131     /***************************************************************************
 132      *                                                                         *
 133      * Properties                                                              *
 134      *                                                                         *
 135      **************************************************************************/
 136     /**
 137      * The text to display in the label. The text may be null.
 138      * @return the text to display in the label
 139      */
 140     public final StringProperty textProperty() {
 141         if (text == null) {
 142             text = new SimpleStringProperty(this, "text", "");
 143         }
 144         return text;
 145     }
 146     private StringProperty text;
 147     public final void setText(String value) { textProperty().setValue(value); }
 148     public final String getText() { return text == null ? "" : text.getValue(); }
 149 
 150     /**
 151      * Specifies how the text and graphic within the Labeled should be
 152      * aligned when there is empty space within the Labeled.
 153      * @return the alignment within this labeled
 154      */
 155     public final ObjectProperty<Pos> alignmentProperty() {
 156         if (alignment == null) {
 157             alignment = new StyleableObjectProperty<Pos>(Pos.CENTER_LEFT) {
 158 
 159                 @Override public CssMetaData<Labeled,Pos> getCssMetaData() {
 160                     return StyleableProperties.ALIGNMENT;
 161                 }
 162 
 163                 @Override
 164                 public Object getBean() {
 165                     return Labeled.this;
 166                 }
 167 
 168                 @Override
 169                 public String getName() {
 170                     return "alignment";
 171                 }
 172             };
 173         }
 174         return alignment;
 175     }
 176     private ObjectProperty<Pos> alignment;
 177     public final void setAlignment(Pos value) { alignmentProperty().set(value); }
 178     public final Pos getAlignment() { return alignment == null ? Pos.CENTER_LEFT : alignment.get(); }
 179 
 180 
 181     /**
 182      * Specifies the behavior for lines of text <em>when text is multiline</em>
 183      * Unlike {@link #contentDisplayProperty} which affects the graphic and text, this setting
 184      * only affects multiple lines of text relative to the text bounds.
 185      * @return the alignment of lines of text within this labeled
 186      */
 187     public final ObjectProperty<TextAlignment> textAlignmentProperty() {
 188         if (textAlignment == null) {
 189             textAlignment = new StyleableObjectProperty<TextAlignment>(TextAlignment.LEFT) {
 190 
 191                 @Override
 192                 public CssMetaData<Labeled,TextAlignment> getCssMetaData() {
 193                     return StyleableProperties.TEXT_ALIGNMENT;
 194                 }
 195 
 196                 @Override
 197                 public Object getBean() {
 198                     return Labeled.this;
 199                 }
 200 
 201                 @Override
 202                 public String getName() {
 203                     return "textAlignment";
 204                 }
 205             };
 206         }
 207         return textAlignment;
 208     }
 209     private ObjectProperty<TextAlignment> textAlignment;
 210     public final void setTextAlignment(TextAlignment value) { textAlignmentProperty().setValue(value); }
 211     public final TextAlignment getTextAlignment() { return textAlignment == null ? TextAlignment.LEFT : textAlignment.getValue(); }
 212 
 213     /**
 214      * Specifies the behavior to use if the text of the {@code Labeled}
 215      * exceeds the available space for rendering the text.
 216      * @return the overrun behavior if the text exceeds the available space
 217      */
 218     public final ObjectProperty<OverrunStyle> textOverrunProperty() {
 219         if (textOverrun == null) {
 220             textOverrun = new StyleableObjectProperty<OverrunStyle>(OverrunStyle.ELLIPSIS) {
 221 
 222                 @Override
 223                 public CssMetaData<Labeled,OverrunStyle> getCssMetaData() {
 224                     return StyleableProperties.TEXT_OVERRUN;
 225                 }
 226 
 227                 @Override
 228                 public Object getBean() {
 229                     return Labeled.this;
 230                 }
 231 
 232                 @Override
 233                 public String getName() {
 234                     return "textOverrun";
 235                 }
 236             };
 237         }
 238         return textOverrun;
 239     }
 240     private ObjectProperty<OverrunStyle> textOverrun;
 241     public final void setTextOverrun(OverrunStyle value) { textOverrunProperty().setValue(value); }
 242     public final OverrunStyle getTextOverrun() { return textOverrun == null ? OverrunStyle.ELLIPSIS : textOverrun.getValue(); }
 243 
 244     /**
 245      * Specifies the string to display for the ellipsis when text is truncated.
 246      *
 247      * <table summary="" border="0" cellpadding="0" cellspacing="0"><tr><th>Examples</th></tr>
 248      *   <tr class="altColor"><td align="right">"..."</td>        <td>- Default value for most locales</td>
 249      *   <tr class="rowColor"><td align="right">" . . . "</td>    <td></td>
 250      *   <tr class="altColor"><td align="right">" [...] "</td>    <td></td>
 251      *   <tr class="rowColor"><td align="right">"\u2026"</td> <td>- The Unicode ellipsis character '&hellip;'</td>
 252      *   <tr class="altColor"><td align="right">""</td>           <td>- No ellipsis, just display the truncated string</td>
 253      * </table>
 254      *
 255      * <p>Note that not all fonts support all Unicode characters.
 256      *
 257      * @return the ellipsis property on the string to display for the ellipsis
 258      * when text is truncated
 259      * @see <a href="http://en.wikipedia.org/wiki/Ellipsis#Computer_representations">Wikipedia:ellipsis</a>
 260      * @since JavaFX 2.2
 261      */
 262     public final StringProperty ellipsisStringProperty() {
 263         if (ellipsisString == null) {
 264             ellipsisString = new StyleableStringProperty(DEFAULT_ELLIPSIS_STRING) {
 265                 @Override public Object getBean() {
 266                     return Labeled.this;
 267                 }
 268 
 269                 @Override public String getName() {
 270                     return "ellipsisString";
 271                 }
 272 
 273                 @Override public CssMetaData<Labeled,String> getCssMetaData() {
 274                     return StyleableProperties.ELLIPSIS_STRING;
 275                 }
 276             };
 277         }
 278         return ellipsisString;
 279     }
 280     private StringProperty ellipsisString;
 281     public final void setEllipsisString(String value) { ellipsisStringProperty().set((value == null) ? "" : value); }
 282     public final String getEllipsisString() { return ellipsisString == null ? DEFAULT_ELLIPSIS_STRING : ellipsisString.get(); }
 283 
 284 
 285     /**
 286      * If a run of text exceeds the width of the Labeled, then this variable
 287      * indicates whether the text should wrap onto another line.
 288      * @return the wrap property if a run of text exceeds the width of the Labeled
 289      */
 290     public final BooleanProperty wrapTextProperty() {
 291         if (wrapText == null) {
 292             wrapText = new StyleableBooleanProperty() {
 293 
 294                 @Override
 295                 public CssMetaData<Labeled,Boolean> getCssMetaData() {
 296                     return StyleableProperties.WRAP_TEXT;
 297                 }
 298 
 299                 @Override
 300                 public Object getBean() {
 301                     return Labeled.this;
 302                 }
 303 
 304                 @Override
 305                 public String getName() {
 306                     return "wrapText";
 307                 }
 308             };
 309         }
 310         return wrapText;
 311     }
 312     private BooleanProperty wrapText;
 313     public final void setWrapText(boolean value) { wrapTextProperty().setValue(value); }
 314     public final boolean isWrapText() { return wrapText == null ? false : wrapText.getValue(); }
 315 
 316     /**
 317      * If wrapText is true, then contentBias will be HORIZONTAL, otherwise it is null.
 318      * @return orientation of width/height dependency or null if there is none
 319      */
 320     @Override public Orientation getContentBias() {
 321         return isWrapText()? Orientation.HORIZONTAL : null;
 322     }
 323 
 324     /**
 325      * The default font to use for text in the Labeled. If the Label's text is
 326      * rich text then this font may or may not be used depending on the font
 327      * information embedded in the rich text, but in any case where a default
 328      * font is required, this font will be used.
 329      * @return the default font to use for text in this labeled
 330      */
 331     public final ObjectProperty<Font> fontProperty() {
 332 
 333         if (font == null) {
 334             font = new StyleableObjectProperty<Font>(Font.getDefault()) {
 335 
 336                 private boolean fontSetByCss = false;
 337 
 338                 @Override
 339                 public void applyStyle(StyleOrigin newOrigin, Font value) {
 340 
 341                     //
 342                     // RT-20727 - if CSS is setting the font, then make sure invalidate doesn't call NodeHelper.reapplyCSS
 343                     //
 344                     try {
 345                         // super.applyStyle calls set which might throw if value is bound.
 346                         // Have to make sure fontSetByCss is reset.
 347                         fontSetByCss = true;
 348                         super.applyStyle(newOrigin, value);
 349                     } catch(Exception e) {
 350                         throw e;
 351                     } finally {
 352                         fontSetByCss = false;
 353                     }
 354                 }
 355 
 356                 @Override
 357                 public void set(Font value) {
 358 
 359                     final Font oldValue = get();
 360                     if (value != null ? !value.equals(oldValue) : oldValue != null) {
 361                         super.set(value);
 362                     }
 363 
 364                 }
 365 
 366                 @Override
 367                 protected void invalidated() {
 368                     // RT-20727 - if font is changed by calling setFont, then
 369                     // css might need to be reapplied since font size affects
 370                     // calculated values for styles with relative values
 371                     if(fontSetByCss == false) {
 372                         NodeHelper.reapplyCSS(Labeled.this);
 373                     }
 374                 }
 375 
 376                 @Override
 377                 public CssMetaData<Labeled,Font> getCssMetaData() {
 378                     return StyleableProperties.FONT;
 379                 }
 380 
 381                 @Override
 382                 public Object getBean() {
 383                     return Labeled.this;
 384                 }
 385 
 386                 @Override
 387                 public String getName() {
 388                     return "font";
 389                 }
 390             };
 391         }
 392         return font;
 393     }
 394     private ObjectProperty<Font> font;
 395     public final void setFont(Font value) { fontProperty().setValue(value); }
 396     public final Font getFont() { return font == null ? Font.getDefault() : font.getValue(); }
 397 
 398 
 399     /**
 400      * An optional icon for the Labeled. This can be positioned relative to the
 401      * text by using {@link #setContentDisplay}.  The node specified for this
 402      * variable cannot appear elsewhere in the scene graph, otherwise
 403      * the {@code IllegalArgumentException} is thrown.  See the class
 404      * description of {@link javafx.scene.Node Node} for more detail.
 405      * @return the optional icon for this labeled
 406      */
 407     public final ObjectProperty<Node> graphicProperty() {
 408         if (graphic == null) {
 409             graphic = new StyleableObjectProperty<Node>() {
 410 
 411                 // The graphic is styleable by css, but it is the
 412                 // imageUrlProperty that handles the style value.
 413                 @Override
 414                 public CssMetaData getCssMetaData() {
 415                     return StyleableProperties.GRAPHIC;
 416                 }
 417 
 418                 @Override
 419                 public Object getBean() {
 420                     return Labeled.this;
 421                 }
 422 
 423                 @Override
 424                 public String getName() {
 425                     return "graphic";
 426                 }
 427             };
 428         }
 429         return graphic;
 430     }
 431     private ObjectProperty<Node> graphic;
 432     public final void setGraphic(Node value) {
 433         graphicProperty().setValue(value);
 434     }
 435     public final Node getGraphic() { return graphic == null ? null : graphic.getValue(); }
 436 
 437     private StyleableStringProperty imageUrl = null;
 438     /**
 439      * The imageUrl property is set from CSS and then the graphic property is
 440      * set from the invalidated method. This ensures that the same image isn't
 441      * reloaded.
 442      */
 443     private StyleableStringProperty imageUrlProperty() {
 444         if (imageUrl == null) {
 445             imageUrl = new StyleableStringProperty() {
 446 
 447                 //
 448                 // If imageUrlProperty is invalidated, this is the origin of the style that
 449                 // triggered the invalidation. This is used in the invaildated() method where the
 450                 // value of super.getStyleOrigin() is not valid until after the call to set(v) returns,
 451                 // by which time invalidated will have been called.
 452                 // This value is initialized to USER in case someone calls set on the imageUrlProperty, which
 453                 // is possible:
 454                 //     CssMetaData metaData = ((StyleableProperty)labeled.graphicProperty()).getCssMetaData();
 455                 //     StyleableProperty prop = metaData.getStyleableProperty(labeled);
 456                 //     prop.set(someUrl);
 457                 //
 458                 // TODO: Note that prop != labeled, which violates the contract between StyleableProperty and CssMetaData.
 459                 //
 460                 StyleOrigin origin = StyleOrigin.USER;
 461 
 462                 @Override
 463                 public void applyStyle(StyleOrigin origin, String v) {
 464 
 465                     this.origin = origin;
 466 
 467                     // Don't want applyStyle to throw an exception which would leave this.origin set to the wrong value
 468                     if (graphic == null || graphic.isBound() == false) super.applyStyle(origin, v);
 469 
 470                     // Origin is only valid for this invocation of applyStyle, so reset it to USER in case someone calls set.
 471                     this.origin = StyleOrigin.USER;
 472                 }
 473 
 474                 @Override
 475                 protected void invalidated() {
 476 
 477                     // need to call super.get() here since get() is overridden to return the graphicProperty's value
 478                     final String url = super.get();
 479 
 480                     if (url == null) {
 481                         ((StyleableProperty<Node>)(WritableValue<Node>)graphicProperty()).applyStyle(origin, null);
 482                     } else {
 483                         // RT-34466 - if graphic's url is the same as this property's value, then don't overwrite.
 484                         final Node graphicNode = Labeled.this.getGraphic();
 485                         if (graphicNode instanceof ImageView) {
 486                             final ImageView imageView = (ImageView)graphicNode;
 487                             final Image image = imageView.getImage();
 488                             if (image != null) {
 489                                 final String imageViewUrl = image.getUrl();
 490                                 if (url.equals(imageViewUrl)) return;
 491                             }
 492 
 493                         }
 494 
 495                         final Image img = StyleManager.getInstance().getCachedImage(url);
 496 
 497                         if (img != null) {
 498                             //
 499                             // Note that it is tempting to try to re-use existing ImageView simply by setting
 500                             // the image on the current ImageView, if there is one. This would effectively change
 501                             // the image, but not the ImageView which means that no graphicProperty listeners would
 502                             // be notified. This is probably not what we want.
 503                             //
 504 
 505                             //
 506                             // Have to call applyStyle on graphicProperty so that the graphicProperty's
 507                             // origin matches the imageUrlProperty's origin.
 508                             //
 509                             ((StyleableProperty<Node>)(WritableValue<Node>)graphicProperty()).applyStyle(origin, new ImageView(img));
 510                         }
 511                     }
 512                 }
 513 
 514                 @Override
 515                 public String get() {
 516 
 517                     //
 518                     // The value of the imageUrlProperty is that of the graphicProperty.
 519                     // Return the value in a way that doesn't expand the graphicProperty.
 520                     //
 521                     final Node graphic = getGraphic();
 522                     if (graphic instanceof ImageView) {
 523                         final Image image = ((ImageView)graphic).getImage();
 524                         if (image != null) {
 525                             return image.getUrl();
 526                         }
 527                     }
 528                     return null;
 529                 }
 530 
 531                 @Override
 532                 public StyleOrigin getStyleOrigin() {
 533 
 534                     //
 535                     // The origin of the imageUrlProperty is that of the graphicProperty.
 536                     // Return the origin in a way that doesn't expand the graphicProperty.
 537                     //
 538                     return graphic != null ? ((StyleableProperty<Node>)(WritableValue<Node>)graphic).getStyleOrigin() : null;
 539                 }
 540 
 541                 @Override
 542                 public Object getBean() {
 543                     return Labeled.this;
 544                 }
 545 
 546                 @Override
 547                 public String getName() {
 548                     return "imageUrl";
 549                 }
 550 
 551                 @Override
 552                 public CssMetaData<Labeled,String> getCssMetaData() {
 553                     return StyleableProperties.GRAPHIC;
 554                 }
 555 
 556             };
 557         }
 558         return imageUrl;
 559     }
 560 
 561     /**
 562      * Whether all text should be underlined.
 563      * @return the underline property of all text in this labeled
 564      */
 565     public final BooleanProperty underlineProperty() {
 566         if (underline == null) {
 567             underline = new StyleableBooleanProperty(false) {
 568 
 569                 @Override
 570                 public CssMetaData<Labeled, Boolean> getCssMetaData() {
 571                     return StyleableProperties.UNDERLINE;
 572                 }
 573 
 574                 @Override
 575                 public Object getBean() {
 576                     return Labeled.this;
 577                 }
 578 
 579                 @Override
 580                 public String getName() {
 581                     return "underline";
 582                 }
 583             };
 584         }
 585         return underline;
 586     }
 587     private BooleanProperty underline;
 588     public final void setUnderline(boolean value) { underlineProperty().setValue(value); }
 589     public final boolean isUnderline() { return underline == null ? false : underline.getValue(); }
 590 
 591     /**
 592      * Specifies the space in pixel between lines.
 593      * @return the line spacing property between lines in this labeled
 594      * @since JavaFX 8.0
 595      */
 596     public final DoubleProperty lineSpacingProperty() {
 597         if (lineSpacing == null) {
 598             lineSpacing = new StyleableDoubleProperty(0) {
 599 
 600                 @Override
 601                 public CssMetaData<Labeled,Number> getCssMetaData() {
 602                     return StyleableProperties.LINE_SPACING;
 603                 }
 604 
 605                 @Override
 606                 public Object getBean() {
 607                     return Labeled.this;
 608                 }
 609 
 610                 @Override
 611                 public String getName() {
 612                     return "lineSpacing";
 613                 }
 614             };
 615         }
 616         return lineSpacing;
 617     }
 618     private DoubleProperty lineSpacing;
 619     public final void setLineSpacing(double value) { lineSpacingProperty().setValue(value); }
 620     public final double getLineSpacing() { return lineSpacing == null ? 0 : lineSpacing.getValue(); }
 621 
 622     /**
 623      * Specifies the positioning of the graphic relative to the text.
 624      * @return content display property of this labeled
 625      */
 626     public final ObjectProperty<ContentDisplay> contentDisplayProperty() {
 627         if (contentDisplay == null) {
 628             contentDisplay = new StyleableObjectProperty<ContentDisplay>(ContentDisplay.LEFT) {
 629 
 630                 @Override
 631                 public CssMetaData<Labeled,ContentDisplay> getCssMetaData() {
 632                     return StyleableProperties.CONTENT_DISPLAY;
 633                 }
 634 
 635                 @Override
 636                 public Object getBean() {
 637                     return Labeled.this;
 638                 }
 639 
 640                 @Override
 641                 public String getName() {
 642                     return "contentDisplay";
 643                 }
 644             };
 645         }
 646         return contentDisplay;
 647     }
 648     private ObjectProperty<ContentDisplay> contentDisplay;
 649     public final void setContentDisplay(ContentDisplay value) { contentDisplayProperty().setValue(value); }
 650     public final ContentDisplay getContentDisplay() { return contentDisplay == null ? ContentDisplay.LEFT : contentDisplay.getValue(); }
 651 
 652     /**
 653      * The padding around the Labeled's text and graphic content.
 654      * By default labelPadding is Insets.EMPTY and cannot be set to null.
 655      * Subclasses may add nodes outside this padding and inside the Labeled's padding.
 656      *
 657      * This property can only be set from CSS.
 658      * @return  the label padding property of this labeled
 659      */
 660     public final ReadOnlyObjectProperty<Insets> labelPaddingProperty() {
 661         return labelPaddingPropertyImpl();
 662     }
 663     private ObjectProperty<Insets> labelPaddingPropertyImpl() {
 664         if (labelPadding == null) {
 665             labelPadding = new StyleableObjectProperty<Insets>(Insets.EMPTY) {
 666                 private Insets lastValidValue = Insets.EMPTY;
 667 
 668                 @Override
 669                 public void invalidated() {
 670                     final Insets newValue = get();
 671                     if (newValue == null) {
 672                         set(lastValidValue);
 673                         throw new NullPointerException("cannot set labelPadding to null");
 674                     }
 675                     lastValidValue = newValue;
 676                     requestLayout();
 677                 }
 678 
 679                 @Override
 680                 public CssMetaData<Labeled,Insets> getCssMetaData() {
 681                     return StyleableProperties.LABEL_PADDING;
 682                 }
 683 
 684                 @Override
 685                 public Object getBean() {
 686                     return Labeled.this;
 687                 }
 688 
 689                 @Override
 690                 public String getName() {
 691                     return "labelPadding";
 692                 }
 693             };
 694         }
 695         return labelPadding;
 696     }
 697     private ObjectProperty<Insets> labelPadding;
 698     private void setLabelPadding(Insets value) { labelPaddingPropertyImpl().set(value); }
 699     public final Insets getLabelPadding() { return labelPadding == null ? Insets.EMPTY : labelPadding.get(); }
 700 
 701     /**
 702      * The amount of space between the graphic and text
 703      * @return the graphics text gap property of this labeled
 704      */
 705     public final DoubleProperty graphicTextGapProperty() {
 706         if (graphicTextGap == null) {
 707             graphicTextGap = new StyleableDoubleProperty(4) {
 708 
 709                 @Override
 710                 public CssMetaData<Labeled,Number> getCssMetaData() {
 711                     return StyleableProperties.GRAPHIC_TEXT_GAP;
 712                 }
 713 
 714                 @Override
 715                 public Object getBean() {
 716                     return Labeled.this;
 717                 }
 718 
 719                 @Override
 720                 public String getName() {
 721                     return "graphicTextGap";
 722                 }
 723             };
 724         }
 725         return graphicTextGap;
 726     }
 727     private DoubleProperty graphicTextGap;
 728     public final void setGraphicTextGap(double value) { graphicTextGapProperty().setValue(value); }
 729     public final double getGraphicTextGap() { return graphicTextGap == null ? 4 : graphicTextGap.getValue(); }
 730 
 731 
 732     /**
 733      * The {@link Paint} used to fill the text.
 734      */
 735     private ObjectProperty<Paint> textFill; // TODO for now change this
 736 
 737     public final void setTextFill(Paint value) {
 738         textFillProperty().set(value);
 739     }
 740 
 741     public final Paint getTextFill() {
 742         return textFill == null ? Color.BLACK : textFill.get();
 743     }
 744 
 745     public final ObjectProperty<Paint> textFillProperty() {
 746         if (textFill == null) {
 747             textFill = new StyleableObjectProperty<Paint>(Color.BLACK) {
 748 
 749                 @Override
 750                 public CssMetaData<Labeled,Paint> getCssMetaData() {
 751                     return StyleableProperties.TEXT_FILL;
 752                 }
 753 
 754                 @Override
 755                 public Object getBean() {
 756                     return Labeled.this;
 757                 }
 758 
 759                 @Override
 760                 public String getName() {
 761                     return "textFill";
 762                 }
 763             };
 764         }
 765         return textFill;
 766     }
 767 
 768 
 769     /**
 770      * MnemonicParsing property to enable/disable text parsing.
 771      * If this is set to true, then the Label text will be
 772      * parsed to see if it contains the mnemonic parsing character '_'.
 773      * When a mnemonic is detected the key combination will
 774      * be determined based on the succeeding character, and the mnemonic
 775      * added.
 776      *
 777      * <p>
 778      * The default value for Labeled is false, but it
 779      * is enabled by default on some Controls.
 780      * </p>
 781      */
 782     private BooleanProperty mnemonicParsing;
 783     public final void setMnemonicParsing(boolean value) {
 784         mnemonicParsingProperty().set(value);
 785     }
 786     public final boolean isMnemonicParsing() {
 787         return mnemonicParsing == null ? false : mnemonicParsing.get();
 788     }
 789     public final BooleanProperty mnemonicParsingProperty() {
 790         if (mnemonicParsing == null) {
 791             mnemonicParsing = new SimpleBooleanProperty(this, "mnemonicParsing");
 792         }
 793         return mnemonicParsing;
 794     }
 795 
 796     //    /**
 797     //     * This is the symbol that is searched for in the text and used as
 798     //     * a mnemonic. You can change what symbol is used. Using the symbol
 799     //     * more than once will cause the symbol to be escaped. Thus, if "_"
 800     //     * (the default) is used, then the string "H_ello World" will use
 801     //     * "e" as the mnemonic. If "H__ello World" is used, then no mnemonic
 802     //     * will be used and the text will be rendered as "H_ello World".
 803     //     * TODO: Have i18n review this part of the API to confirm proper
 804     //     * externalization will work as expected
 805     //     */
 806 
 807     @Override public String toString() {
 808         StringBuilder builder =
 809             new StringBuilder(super.toString())
 810                 .append("'").append(getText()).append("'");
 811         return builder.toString();
 812     }
 813 
 814     /***************************************************************************
 815      *                                                                         *
 816      * Stylesheet Handling                                                     *
 817      *                                                                         *
 818      **************************************************************************/
 819 
 820     /**
 821      * Returns the initial alignment state of this control, for use
 822      * by the JavaFX CSS engine to correctly set its initial value. This method
 823      * is overridden to use Pos.CENTER_LEFT initially.
 824      *
 825      * @return the initial alignment state of this control
 826      * @since 9
 827      */
 828     protected Pos getInitialAlignment() {
 829         return Pos.CENTER_LEFT;
 830     }
 831 
 832     private static class StyleableProperties {
 833         private static final FontCssMetaData<Labeled> FONT =
 834             new FontCssMetaData<Labeled>("-fx-font", Font.getDefault()) {
 835 
 836             @Override
 837             public boolean isSettable(Labeled n) {
 838                 return n.font == null || !n.font.isBound();
 839             }
 840 
 841             @Override
 842             public StyleableProperty<Font> getStyleableProperty(Labeled n) {
 843                 return (StyleableProperty<Font>)(WritableValue<Font>)n.fontProperty();
 844             }
 845         };
 846 
 847         private static final CssMetaData<Labeled,Pos> ALIGNMENT =
 848                 new CssMetaData<Labeled,Pos>("-fx-alignment",
 849                 new EnumConverter<Pos>(Pos.class), Pos.CENTER_LEFT ) {
 850 
 851             @Override
 852             public boolean isSettable(Labeled n) {
 853                 return n.alignment == null || !n.alignment.isBound();
 854             }
 855 
 856             @Override
 857             public StyleableProperty<Pos> getStyleableProperty(Labeled n) {
 858                 return (StyleableProperty<Pos>)(WritableValue<Pos>)n.alignmentProperty();
 859             }
 860 
 861             @Override
 862             public Pos getInitialValue(Labeled n) {
 863                 return n.getInitialAlignment();
 864             }
 865         };
 866 
 867         private static final CssMetaData<Labeled,TextAlignment> TEXT_ALIGNMENT =
 868                 new CssMetaData<Labeled,TextAlignment>("-fx-text-alignment",
 869                 new EnumConverter<TextAlignment>(TextAlignment.class),
 870                 TextAlignment.LEFT) {
 871 
 872             @Override
 873             public boolean isSettable(Labeled n) {
 874                 return n.textAlignment == null || !n.textAlignment.isBound();
 875             }
 876 
 877             @Override
 878             public StyleableProperty<TextAlignment> getStyleableProperty(Labeled n) {
 879                 return (StyleableProperty<TextAlignment>)(WritableValue<TextAlignment>)n.textAlignmentProperty();
 880             }
 881         };
 882 
 883         private static final CssMetaData<Labeled,Paint> TEXT_FILL =
 884                 new CssMetaData<Labeled,Paint>("-fx-text-fill",
 885                 PaintConverter.getInstance(), Color.BLACK) {
 886 
 887             @Override
 888             public boolean isSettable(Labeled n) {
 889                 return n.textFill == null || !n.textFill.isBound();
 890             }
 891 
 892             @Override
 893             public StyleableProperty<Paint> getStyleableProperty(Labeled n) {
 894                 return (StyleableProperty<Paint>)(WritableValue<Paint>)n.textFillProperty();
 895             }
 896         };
 897 
 898         private static final CssMetaData<Labeled,OverrunStyle> TEXT_OVERRUN =
 899                 new CssMetaData<Labeled,OverrunStyle>("-fx-text-overrun",
 900                 new EnumConverter<OverrunStyle>(OverrunStyle.class),
 901                 OverrunStyle.ELLIPSIS) {
 902 
 903             @Override
 904             public boolean isSettable(Labeled n) {
 905                 return n.textOverrun == null || !n.textOverrun.isBound();
 906             }
 907 
 908             @Override
 909             public StyleableProperty<OverrunStyle> getStyleableProperty(Labeled n) {
 910                 return (StyleableProperty<OverrunStyle>)(WritableValue<OverrunStyle>)n.textOverrunProperty();
 911             }
 912         };
 913 
 914         private static final CssMetaData<Labeled,String> ELLIPSIS_STRING =
 915                 new CssMetaData<Labeled,String>("-fx-ellipsis-string",
 916                 StringConverter.getInstance(), DEFAULT_ELLIPSIS_STRING) {
 917 
 918             @Override public boolean isSettable(Labeled n) {
 919                 return n.ellipsisString == null || !n.ellipsisString.isBound();
 920             }
 921 
 922             @Override public StyleableProperty<String> getStyleableProperty(Labeled n) {
 923                 return (StyleableProperty<String>)(WritableValue<String>)n.ellipsisStringProperty();
 924             }
 925         };
 926 
 927         private static final CssMetaData<Labeled,Boolean> WRAP_TEXT =
 928                 new CssMetaData<Labeled,Boolean>("-fx-wrap-text",
 929                 BooleanConverter.getInstance(), false) {
 930 
 931             @Override
 932             public boolean isSettable(Labeled n) {
 933                 return n.wrapText == null || !n.wrapText.isBound();
 934             }
 935 
 936             @Override
 937             public StyleableProperty<Boolean> getStyleableProperty(Labeled n) {
 938                 return (StyleableProperty<Boolean>)(WritableValue<Boolean>)n.wrapTextProperty();
 939             }
 940         };
 941 
 942         private static final CssMetaData<Labeled,String> GRAPHIC =
 943             new CssMetaData<Labeled,String>("-fx-graphic",
 944                 StringConverter.getInstance()) {
 945 
 946             @Override
 947             public boolean isSettable(Labeled n) {
 948                 // Note that we care about the graphic, not imageUrl
 949                 return n.graphic == null || !n.graphic.isBound();
 950             }
 951 
 952             @Override
 953             public StyleableProperty<String> getStyleableProperty(Labeled n) {
 954                 return n.imageUrlProperty();
 955             }
 956         };
 957 
 958         private static final CssMetaData<Labeled,Boolean> UNDERLINE =
 959             new CssMetaData<Labeled,Boolean>("-fx-underline",
 960                 BooleanConverter.getInstance(), Boolean.FALSE) {
 961 
 962             @Override
 963             public boolean isSettable(Labeled n) {
 964                 return n.underline == null || !n.underline.isBound();
 965             }
 966 
 967             @Override
 968             public StyleableProperty<Boolean> getStyleableProperty(Labeled n) {
 969                 return (StyleableProperty<Boolean>)(WritableValue<Boolean>)n.underlineProperty();
 970             }
 971         };
 972 
 973         private static final CssMetaData<Labeled,Number> LINE_SPACING =
 974             new CssMetaData<Labeled,Number>("-fx-line-spacing",
 975                 SizeConverter.getInstance(), 0) {
 976 
 977             @Override
 978             public boolean isSettable(Labeled n) {
 979                 return n.lineSpacing == null || !n.lineSpacing.isBound();
 980             }
 981 
 982             @Override
 983             public StyleableProperty<Number> getStyleableProperty(Labeled n) {
 984                 return (StyleableProperty<Number>)(WritableValue<Number>)n.lineSpacingProperty();
 985             }
 986         };
 987 
 988         private static final CssMetaData<Labeled,ContentDisplay> CONTENT_DISPLAY =
 989             new CssMetaData<Labeled,ContentDisplay>("-fx-content-display",
 990                 new EnumConverter<ContentDisplay>(ContentDisplay.class),
 991                 ContentDisplay.LEFT) {
 992 
 993             @Override
 994             public boolean isSettable(Labeled n) {
 995                 return n.contentDisplay == null || !n.contentDisplay.isBound();
 996             }
 997 
 998             @Override
 999             public StyleableProperty<ContentDisplay> getStyleableProperty(Labeled n) {
1000                 return (StyleableProperty<ContentDisplay>)(WritableValue<ContentDisplay>)n.contentDisplayProperty();
1001             }
1002         };
1003 
1004         private static final CssMetaData<Labeled,Insets> LABEL_PADDING =
1005             new CssMetaData<Labeled,Insets>("-fx-label-padding",
1006                 InsetsConverter.getInstance(), Insets.EMPTY) {
1007 
1008             @Override
1009             public boolean isSettable(Labeled n) {
1010                 return n.labelPadding == null || !n.labelPadding.isBound();
1011             }
1012 
1013             @Override
1014             public StyleableProperty<Insets> getStyleableProperty(Labeled n) {
1015                 return (StyleableProperty<Insets>)(WritableValue<Insets>)n.labelPaddingPropertyImpl();
1016             }
1017         };
1018 
1019         private static final CssMetaData<Labeled,Number> GRAPHIC_TEXT_GAP =
1020             new CssMetaData<Labeled,Number>("-fx-graphic-text-gap",
1021                 SizeConverter.getInstance(), 4.0) {
1022 
1023             @Override
1024             public boolean isSettable(Labeled n) {
1025                 return n.graphicTextGap == null || !n.graphicTextGap.isBound();
1026             }
1027 
1028             @Override
1029             public StyleableProperty<Number> getStyleableProperty(Labeled n) {
1030                 return (StyleableProperty<Number>)(WritableValue<Number>)n.graphicTextGapProperty();
1031             }
1032         };
1033 
1034         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
1035         static {
1036             final List<CssMetaData<? extends Styleable, ?>> styleables =
1037                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
1038             Collections.addAll(styleables,
1039                 FONT,
1040                 ALIGNMENT,
1041                 TEXT_ALIGNMENT,
1042                 TEXT_FILL,
1043                 TEXT_OVERRUN,
1044                 ELLIPSIS_STRING,
1045                 WRAP_TEXT,
1046                 GRAPHIC,
1047                 UNDERLINE,
1048                 LINE_SPACING,
1049                 CONTENT_DISPLAY,
1050                 LABEL_PADDING,
1051                 GRAPHIC_TEXT_GAP
1052             );
1053             STYLEABLES = Collections.unmodifiableList(styleables);
1054         }
1055     }
1056 
1057     /**
1058      * @return The CssMetaData associated with this class, which may include the
1059      * CssMetaData of its superclasses.
1060      * @since JavaFX 8.0
1061      */
1062     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1063         return StyleableProperties.STYLEABLES;
1064     }
1065 
1066     /**
1067      * {@inheritDoc}
1068      * @since JavaFX 8.0
1069      */
1070     @Override
1071     public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
1072         return getClassCssMetaData();
1073     }
1074  }