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