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