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 }