All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javafx.scene.CssStyleHelper Maven / Gradle / Ivy

/*
 * 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