1 /*
   2  * Copyright (c) 2012, 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 com.sun.javafx.scene.control.skin;
  27 
  28 import com.sun.javafx.css.converters.BooleanConverter;
  29 import com.sun.javafx.css.converters.EnumConverter;
  30 import com.sun.javafx.css.converters.PaintConverter;
  31 import com.sun.javafx.css.converters.SizeConverter;
  32 
  33 import java.util.ArrayList;
  34 import java.util.Collections;
  35 import java.util.List;
  36 
  37 import javafx.beans.InvalidationListener;
  38 import javafx.beans.Observable;
  39 import javafx.beans.value.ObservableValue;
  40 import javafx.beans.value.WritableValue;
  41 import javafx.css.*;
  42 import javafx.scene.control.Labeled;
  43 import javafx.scene.paint.Color;
  44 import javafx.scene.paint.Paint;
  45 import javafx.scene.text.Font;
  46 import javafx.scene.text.Text;
  47 import javafx.scene.text.TextAlignment;
  48 
  49 /**
  50  * LabeledText allows the Text to be styled by the CSS properties of Labeled
  51  * that are meant to style the textual component of the Labeled.
  52  *
  53  * LabeledText has the style class "text"
  54  */
  55 public class LabeledText extends Text {
  56 
  57    private final Labeled labeled;
  58 
  59    public LabeledText(Labeled labeled) {
  60        super();
  61 
  62        if (labeled == null) {
  63            throw new IllegalArgumentException("labeled cannot be null");
  64        }
  65 
  66        this.labeled = labeled;
  67 
  68        //
  69        // init the state of this Text object to that of the Labeled
  70        //
  71        this.setFill(this.labeled.getTextFill());
  72        this.setFont(this.labeled.getFont());
  73        this.setTextAlignment(this.labeled.getTextAlignment());
  74        this.setUnderline(this.labeled.isUnderline());
  75        this.setLineSpacing(this.labeled.getLineSpacing());
  76 
  77        //
  78        // Bind the state of this Text object to that of the Labeled.
  79        // Binding these properties prevents CSS from setting them
  80        //
  81        this.fillProperty().bind(this.labeled.textFillProperty());
  82        this.fontProperty().bind(this.labeled.fontProperty());
  83        // do not bind text - Text doesn't have -fx-text
  84        this.textAlignmentProperty().bind(this.labeled.textAlignmentProperty());
  85        this.underlineProperty().bind(this.labeled.underlineProperty());
  86        this.lineSpacingProperty().bind(this.labeled.lineSpacingProperty());
  87 
  88        getStyleClass().addAll("text");
  89    }
  90 
  91     /**
  92      * @return The CssMetaData associated with this class, which may include the
  93      * CssMetaData of its super classes.
  94      */
  95     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
  96         return STYLEABLES;
  97     }
  98 
  99     /**
 100      * {@inheritDoc}
 101      */
 102     @Override
 103     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
 104         return getClassCssMetaData();
 105     }
 106 
 107    //
 108    // Replace all of Text's CssMetaData instances that overlap with Labeled
 109    // with instances of CssMetaData that redirect to Labeled. Thus, when
 110    // the Labeled is styled,
 111    //
 112 
 113     private StyleablePropertyMirror<Font> fontMirror = null;
 114     private StyleableProperty<Font> fontMirror() {
 115         if (fontMirror == null) {
 116             fontMirror = new StyleablePropertyMirror<Font>(FONT, "fontMirror", Font.getDefault(), (StyleableProperty<Font>)(WritableValue<Font>)labeled.fontProperty());
 117             fontProperty().addListener(fontMirror);
 118         }
 119         return fontMirror;
 120     }
 121 
 122     private static final CssMetaData<LabeledText,Font> FONT =
 123         new FontCssMetaData<LabeledText>("-fx-font", Font.getDefault()) {
 124 
 125         @Override
 126         public boolean isSettable(LabeledText node) {
 127             return node.labeled != null ? node.labeled.fontProperty().isBound() == false : true;
 128         }
 129 
 130         @Override
 131         public StyleableProperty<Font> getStyleableProperty(LabeledText node) {
 132             return node.fontMirror();
 133         }
 134     };
 135 
 136     private StyleablePropertyMirror<Paint> fillMirror;
 137     private StyleableProperty<Paint> fillMirror() {
 138         if (fillMirror == null) {
 139             fillMirror = new StyleablePropertyMirror<Paint>(FILL, "fillMirror", Color.BLACK, (StyleableProperty<Paint>)(WritableValue<Paint>)labeled.textFillProperty());
 140             fillProperty().addListener(fillMirror);
 141         }
 142         return fillMirror;        
 143     }
 144 
 145     private static final CssMetaData<LabeledText,Paint> FILL =
 146         new CssMetaData<LabeledText,Paint>("-fx-fill",
 147             PaintConverter.getInstance(), Color.BLACK) {
 148 
 149             @Override
 150             public boolean isSettable(LabeledText node) {
 151                 return node.labeled.textFillProperty().isBound() == false;
 152             }
 153 
 154             @Override
 155             public StyleableProperty<Paint> getStyleableProperty(LabeledText node) {
 156                 return node.fillMirror();
 157             }
 158         };
 159 
 160     private StyleablePropertyMirror<TextAlignment> textAlignmentMirror;
 161     private StyleableProperty<TextAlignment> textAlignmentMirror() {
 162         if (textAlignmentMirror == null) {
 163             textAlignmentMirror = new StyleablePropertyMirror<TextAlignment>(TEXT_ALIGNMENT, "textAlignmentMirror", TextAlignment.LEFT, (StyleableProperty<TextAlignment>)(WritableValue<TextAlignment>)labeled.textAlignmentProperty());
 164             textAlignmentProperty().addListener(textAlignmentMirror);
 165         }
 166         return textAlignmentMirror;        
 167     }
 168     
 169     private static final CssMetaData<LabeledText,TextAlignment> TEXT_ALIGNMENT =
 170         new CssMetaData<LabeledText,TextAlignment>("-fx-text-alignment",
 171         new EnumConverter<TextAlignment>(TextAlignment.class),
 172         TextAlignment.LEFT) {
 173 
 174             @Override
 175             public boolean isSettable(LabeledText node) {
 176                 return node.labeled.textAlignmentProperty().isBound() == false;
 177             }
 178 
 179             @Override
 180             public StyleableProperty<TextAlignment> getStyleableProperty(LabeledText node) {
 181                 return node.textAlignmentMirror();
 182             }
 183         };
 184 
 185     private StyleablePropertyMirror<Boolean> underlineMirror;
 186     private StyleableProperty<Boolean> underlineMirror() {
 187         if (underlineMirror == null) {
 188             underlineMirror = new StyleablePropertyMirror<Boolean>(UNDERLINE, "underLineMirror", Boolean.FALSE, (StyleableProperty<Boolean>)(WritableValue<Boolean>)labeled.underlineProperty());
 189             underlineProperty().addListener(underlineMirror);
 190         }
 191         return underlineMirror;        
 192     }
 193     
 194     private static final CssMetaData<LabeledText,Boolean> UNDERLINE =
 195             new CssMetaData<LabeledText,Boolean>("-fx-underline",
 196             BooleanConverter.getInstance(),
 197             Boolean.FALSE) {
 198  
 199             @Override
 200             public boolean isSettable(LabeledText node) {
 201                 return node.labeled.underlineProperty().isBound() == false;
 202             }
 203 
 204             @Override
 205             public StyleableProperty<Boolean> getStyleableProperty(LabeledText node) {
 206                 return node.underlineMirror();
 207             }
 208         };
 209 
 210     private StyleablePropertyMirror<Number> lineSpacingMirror;
 211     private StyleableProperty<Number> lineSpacingMirror() {
 212         if (lineSpacingMirror == null) {
 213             lineSpacingMirror = new StyleablePropertyMirror<Number>(LINE_SPACING, "lineSpacingMirror", 0d, (StyleableProperty<Number>)(WritableValue<Number>)labeled.lineSpacingProperty());
 214             lineSpacingProperty().addListener(lineSpacingMirror);
 215         }
 216         return lineSpacingMirror;        
 217     }
 218     
 219     private static final CssMetaData<LabeledText,Number> LINE_SPACING =
 220         new CssMetaData<LabeledText,Number>("-fx-line-spacing",
 221             SizeConverter.getInstance(),
 222                 0) {
 223 
 224             @Override
 225             public boolean isSettable(LabeledText node) {
 226                 return node.labeled.lineSpacingProperty().isBound() == false;
 227             }
 228 
 229             @Override
 230             public StyleableProperty<Number> getStyleableProperty(LabeledText node) {
 231                 return node.lineSpacingMirror();
 232             }
 233         };
 234 
 235     private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
 236     static {
 237 
 238        final List<CssMetaData<? extends Styleable, ?>> styleables =
 239            new ArrayList<CssMetaData<? extends Styleable, ?>>(Text.getClassCssMetaData());
 240 
 241        for (int n=0,nMax=styleables.size(); n<nMax; n++) {
 242            final String prop = styleables.get(n).getProperty();
 243 
 244            if ("-fx-fill".equals(prop)) {
 245                styleables.set(n, FILL);
 246            } else if ("-fx-font".equals(prop)) {
 247                styleables.set(n, FONT);
 248            } else if ("-fx-text-alignment".equals(prop)) {
 249                styleables.set(n, TEXT_ALIGNMENT);
 250            } else if ("-fx-underline".equals(prop)) {
 251                styleables.set(n, UNDERLINE);
 252            } else if ("-fx-line-spacing".equals(prop)) {
 253                styleables.set(n, LINE_SPACING);
 254            }
 255        }
 256 
 257        STYLEABLES = Collections.unmodifiableList(styleables);
 258     }       
 259 
 260     private class StyleablePropertyMirror<T> extends SimpleStyleableObjectProperty<T> implements InvalidationListener {
 261         
 262         private StyleablePropertyMirror(CssMetaData<LabeledText, T> cssMetaData, String name, T initialValue, StyleableProperty<T> property) {
 263             super(cssMetaData, LabeledText.this, name, initialValue);
 264             this.property = property;
 265             this.applying = false;
 266         }
 267 
 268         @Override
 269         public void invalidated(Observable observable) {
 270             // if Text's property is changing but not because a style is being applied,
 271             // then it's either because the set method was called on the Labeled's property or
 272             // because CSS is resetting Labeled's property to its initial value
 273             // (see CssStyleHelper#resetToInitialValues(Styleable))
 274             if (applying == false) {
 275                 super.applyStyle(null, ((ObservableValue<T>)observable).getValue());
 276             }
 277         }
 278 
 279         @Override
 280         public void applyStyle(StyleOrigin newOrigin, T value) {
 281 
 282             applying = true;
 283             //
 284             // In the case where the Labeled's property was set by an
 285             // inline style, this inline style should override values
 286             // from lesser origins.
 287             //
 288             StyleOrigin propOrigin = property.getStyleOrigin();
 289 
 290             //
 291             // if propOrigin is null, then the property is in init state
 292             // if newOrigin is null, then CSS is resetting this property -
 293             //    but don't let CSS overwrite a user set value
 294             // if propOrigin is greater than origin, then the style should
 295             //    not override
 296             //
 297             if (propOrigin == null ||
 298                     (newOrigin != null
 299                             ? propOrigin.compareTo(newOrigin) <= 0
 300                             : propOrigin != StyleOrigin.USER)) {
 301                 super.applyStyle(newOrigin, value);
 302                 property.applyStyle(newOrigin, value);
 303             }
 304             applying = false;
 305         }
 306 
 307         @Override public StyleOrigin getStyleOrigin() {
 308             return property.getStyleOrigin();
 309         }
 310 
 311         boolean applying;
 312         private final StyleableProperty<T> property;
 313     } 
 314        
 315 }