javafx.scene.CssStyleHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openjfx-78-backport Show documentation
Show all versions of openjfx-78-backport Show documentation
This is a backport of OpenJFX 8 to run on Java 7.
The newest version!
/*
* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import javafx.css.CssMetaData;
import javafx.css.FontCssMetaData;
import javafx.css.PseudoClass;
import javafx.css.StyleConverter;
import javafx.css.StyleOrigin;
import javafx.css.Styleable;
import javafx.css.StyleableProperty;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import com.sun.javafx.Logging;
import com.sun.javafx.Utils;
import com.sun.javafx.css.CalculatedValue;
import com.sun.javafx.css.CascadingStyle;
import com.sun.javafx.css.CssError;
import com.sun.javafx.css.Declaration;
import com.sun.javafx.css.ParsedValueImpl;
import com.sun.javafx.css.PseudoClassState;
import com.sun.javafx.css.Rule;
import com.sun.javafx.css.Selector;
import com.sun.javafx.css.Style;
import com.sun.javafx.css.StyleCache;
import com.sun.javafx.css.StyleCacheEntry;
import com.sun.javafx.css.StyleConverterImpl;
import com.sun.javafx.css.StyleManager;
import com.sun.javafx.css.StyleMap;
import com.sun.javafx.css.Stylesheet;
import com.sun.javafx.css.converters.FontConverter;
import com.sun.javafx.css.parser.CSSParser;
import sun.util.logging.PlatformLogger;
import static com.sun.javafx.css.CalculatedValue.*;
/**
* The StyleHelper is a helper class used for applying CSS information to Nodes.
*/
final class CssStyleHelper {
private static final PlatformLogger LOGGER = com.sun.javafx.Logging.getCSSLogger();
private CssStyleHelper() {
this.triggerStates = new PseudoClassState();
}
/**
* Creates a new StyleHelper.
*/
static CssStyleHelper createStyleHelper(Node node) {
// If this node had a style helper, then reset properties to their initial value
// since the node might not have a style helper after this call
if (node.styleHelper != null && node.styleHelper.cacheContainer != null) {
node.styleHelper.resetToInitialValues(node);
node.styleHelper.cacheContainer.cssSetProperties.clear();
}
// need to know how far we are to root in order to init arrays.
// TODO: should we hang onto depth to avoid this nonsense later?
// TODO: is there some other way of knowing how far from the root a node is?
Styleable parent = node;
int depth = 0;
while(parent != null) {
depth++;
parent = parent.getStyleableParent();
}
// The List should only contain entries for those
// pseudo-class states that have styles. The StyleHelper's
// pseudoclassStateMask is a bitmask of those pseudoclasses that
// appear in the node's StyleHelper's smap. This list of
// pseudo-class masks is held by the StyleCacheKey. When a node is
// styled, its pseudoclasses and the pseudoclasses of its parents
// are gotten. By comparing the actual pseudo-class state to the
// pseudo-class states that apply, a CacheEntry can be created or
// fetched using only those pseudoclasses that matter.
final PseudoClassState[] triggerStates = new PseudoClassState[depth];
final StyleMap styleMap =
StyleManager.getInstance().findMatchingStyles(node, triggerStates);
if (styleMap == null || styleMap.isEmpty()) {
boolean mightInherit = false;
final List> props = node.getCssMetaData();
final int pMax = props != null ? props.size() : 0;
for (int p=0; p prop = props.get(p);
if (prop.isInherits()) {
mightInherit = true;
break;
}
}
if (mightInherit == false) {
return null;
}
}
final CssStyleHelper helper = new CssStyleHelper();
helper.triggerStates.addAll(triggerStates[0]);
// make sure parent's transition states include the pseudo-classes
// found when matching selectors
parent = node.getStyleableParent();
for(int n=1; n 0) {
// Create a StyleHelper for the parent, if necessary.
if (parentNode.styleHelper == null) {
parentNode.styleHelper = new CssStyleHelper();
}
parentNode.styleHelper.triggerStates.addAll(triggerState);
}
parent=parent.getStyleableParent();
}
helper.cacheContainer = new CacheContainer(node, styleMap, depth);
return helper;
}
private CacheContainer cacheContainer;
private final static class CacheContainer {
// Set internal internalState structures
private CacheContainer(
Node node,
StyleMap styleMap,
int depth) {
int ctr = 0;
int[] smapIds = new int[depth];
smapIds[ctr++] = this.smapId = styleMap.getId();
//
// Create a set of StyleMap id's from the parent's smapIds.
// The resulting smapIds array may have less than depth elements.
// If a parent doesn't have a styleHelper or the styleHelper's
// internal state is null, then that parent doesn't contribute
// to the selection of a style. Any Node that has the same
// set of smapId's can potentially share previously calculated
// values.
//
Styleable parent = node.getStyleableParent();
for(int d=1; d styleableFontProperty = null;
final List> props = node.getCssMetaData();
final int pMax = props != null ? props.size() : 0;
for (int p=0; p prop = props.get(p);
if ("-fx-font".equals(prop.getProperty())) {
// unchecked!
styleableFontProperty = (CssMetaData) prop;
break;
}
}
this.fontProp = styleableFontProperty;
this.fontSizeCache = new HashMap<>();
this.cssSetProperties = new HashMap<>();
}
private StyleMap getStyleMap(Styleable styleable) {
if (styleable != null) {
return StyleManager.getInstance().getStyleMap(styleable, smapId);
} else {
return StyleMap.EMPTY_MAP;
}
}
// This is the key we use to find the shared cache
private final StyleCache.Key styleCacheKey;
// If the node has a fontProperty, we hang onto the CssMetaData for it
// so we can get at it later.
// TBD - why not the fontProperty itself?
private final CssMetaData fontProp;
// The id of StyleMap that contains the styles that apply to this node
private final int smapId;
// All nodes with the same set of styles share the same cache of
// calculated values. But one node might have a different font-size
// than another so the values are stored in cache by font-size.
// This map associates a style cache entry with the font to use when
// getting a value from or putting a value into cache.
private final Map fontSizeCache;
// Any properties that have been set by this style helper are tracked
// here so the property can be reset without expanding properties that
// were not set by css.
private Map cssSetProperties;
}
private void resetToInitialValues(Styleable styleable) {
if (cacheContainer == null ||
cacheContainer.cssSetProperties == null ||
cacheContainer.cssSetProperties.isEmpty()) return;
for (Entry resetValues : cacheContainer.cssSetProperties.entrySet()) {
final CssMetaData metaData = resetValues.getKey();
final StyleableProperty styleableProperty = metaData.getStyleableProperty(styleable);
final StyleOrigin styleOrigin = styleableProperty.getStyleOrigin();
if (styleOrigin != null && styleOrigin != StyleOrigin.USER) {
final CalculatedValue calculatedValue = resetValues.getValue();
styleableProperty.applyStyle(calculatedValue.getOrigin(), calculatedValue.getValue());
}
}
}
private StyleMap getStyleMap(Styleable styleable) {
if (cacheContainer == null || styleable == null) return null;
return cacheContainer.getStyleMap(styleable);
}
private Map> getCascadingStyles(Styleable styleable) {
StyleMap styleMap = getStyleMap(styleable);
// code looks for null return to indicate that the cache was blown away
return (styleMap != null) ? styleMap.getCascadingStyles() : null;
}
/**
* A Set of all the pseudo-class states which, if they change, need to
* cause the Node to be set to UPDATE its CSS styles on the next pulse.
* For example, your stylesheet might have:
*
* .button { ... }
* .button:hover { ... }
* .button *.label { text-fill: black }
* .button:hover *.label { text-fill: blue }
*
* In this case, the first 2 rules apply to the Button itself, but the
* second two rules apply to the label within a Button. When the hover
* changes on the Button, however, we must mark the Button as needing
* an UPDATE. StyleHelper though only contains styles for the first two
* rules for Button. The pseudoclassStateMask would in this case have
* only a single bit set for "hover". In this way the StyleHelper associated
* with the Button would know whether a change to "hover" requires the
* button and all children to be update. Other pseudo-class state changes
* that are not in this hash set are ignored.
* *
* Called "triggerStates" since they would trigger a CSS update.
*/
private PseudoClassState triggerStates = new PseudoClassState();
boolean pseudoClassStateChanged(PseudoClass pseudoClass) {
return triggerStates.contains(pseudoClass);
}
/**
* Dynamic pseudo-class state of the node and its parents.
* Only valid during a pulse.
*
* The StyleCacheEntry to choose depends on the Node's pseudo-class state
* and the pseudo-class state of its parents. Without the parent
* pseudo-class state, the fact that the the node in this pseudo-class state
* matched foo:blah bar { } is lost.
*/
// TODO: this should work on Styleable, not Node
private Set[] getTransitionStates(Node node) {
// if cacheContainer is null, then CSS just doesn't apply to this node
if (cacheContainer == null) return null;
int depth = 0;
Node parent = node;
while (parent != null) {
depth += 1;
parent = parent.getParent();
}
//
// StyleHelper#triggerStates is the set of pseudo-classes that appear
// in the style maps of this StyleHelper. Calculated values are
// cached by pseudo-class state, but only the pseudo-class states
// that mater are used in the search. So we take the transition states
// and intersect them with triggerStates to remove the
// transition states that don't matter when it comes to matching states
// on a selector. For example if the style map contains only
// .foo:hover { -fx-fill: red; } then only the hover state matters
// but the transtion state could be [hover, focused]
//
final Set[] retainedStates = new PseudoClassState[depth];
//
// Note Well: The array runs from leaf to root. That is,
// retainedStates[0] is the pseudo-class state for node and
// retainedStates[1..(states.length-1)] are the retainedStates for the
// node's parents.
//
int count = 0;
parent = node;
while (parent != null) {
final CssStyleHelper helper = (parent instanceof Node) ? ((Node)parent).styleHelper : null;
if (helper != null) {
final Set pseudoClassState = ((Node)parent).pseudoClassStates;
retainedStates[count] = new PseudoClassState();
retainedStates[count].addAll(pseudoClassState);
// retainAll method takes the intersection of pseudoClassState and helper.triggerStates
retainedStates[count].retainAll(helper.triggerStates);
count += 1;
}
parent = parent.getParent();
}
final Set[] transitionStates = new PseudoClassState[count];
System.arraycopy(retainedStates, 0, transitionStates, 0, count);
return transitionStates;
}
ObservableMap, List