Edit C:\Program Files\Java\jdk1.8.0_121\javafx\scene\CssStyleHelper.java
/* * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package javafx.scene; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; 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.beans.value.WritableValue; import javafx.css.CssMetaData; import javafx.css.FontCssMetaData; import javafx.css.ParsedValue; 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.util.Logging; import com.sun.javafx.util.Utils; import com.sun.javafx.css.CalculatedValue; import com.sun.javafx.css.CascadingStyle; import com.sun.javafx.css.CssError; 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 sun.util.logging.PlatformLogger; import sun.util.logging.PlatformLogger.Level; 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.util.Logging.getCSSLogger(); private CssStyleHelper() { this.triggerStates = new PseudoClassState(); } /** * Creates a new StyleHelper. */ static CssStyleHelper createStyleHelper(final Node node) { // 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<CacheEntry> 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, node.getSubScene(), triggerStates); // // reuse the existing styleHelper if possible. // if ( canReuseStyleHelper(node, styleMap) ) { // // RT-33080 // // If we're reusing a style helper, clear the fontSizeCache in case either this node or some parent // node has changed font from a user calling setFont. // // It may be the case that the node's font has changed from a call to setFont, which will // trigger a REAPPLY. If the REAPPLY comes because of a change in font, then the fontSizeCache // needs to be invalidated (cleared) so that new values will be looked up for all transition states. // if (node.styleHelper.cacheContainer != null && node.styleHelper.isUserSetFont(node)) { node.styleHelper.cacheContainer.fontSizeCache.clear(); } node.styleHelper.cacheContainer.forceSlowpath = true; node.styleHelper.triggerStates.addAll(triggerStates[0]); updateParentTriggerStates(node, depth, triggerStates); return node.styleHelper; } if (styleMap == null || styleMap.isEmpty()) { boolean mightInherit = false; final List<CssMetaData<? extends Styleable, ?>> props = node.getCssMetaData(); final int pMax = props != null ? props.size() : 0; for (int p=0; p<pMax; p++) { final CssMetaData<? extends Styleable, ?> prop = props.get(p); if (prop.isInherits()) { mightInherit = true; break; } } if (mightInherit == false) { // If this node had a style helper, then reset properties to their initial value // since the node won't have a style helper after this call if (node.styleHelper != null) { node.styleHelper.resetToInitialValues(node); } // // This node didn't have a StyleHelper before and it doesn't need one now since there are // no styles in the StyleMap and no inherited styles. return null; } } final CssStyleHelper helper = new CssStyleHelper(); helper.triggerStates.addAll(triggerStates[0]); updateParentTriggerStates(node, depth, triggerStates); helper.cacheContainer = new CacheContainer(node, styleMap, depth); // If this node had a style helper, then reset properties to their initial value // since the style map might now be different if (node.styleHelper != null) { node.styleHelper.resetToInitialValues(node); } return helper; } private static void updateParentTriggerStates(Styleable styleable, int depth, PseudoClassState[] triggerStates) { // make sure parent's transition states include the pseudo-classes // found when matching selectors Styleable parent = styleable.getStyleableParent(); for(int n=1; n<depth; n++) { // TODO: this means that a style like .menu-item:hover won't work. Need to separate CssStyleHelper tree from scene-graph tree if (parent instanceof Node == false) { parent=parent.getStyleableParent(); continue; } Node parentNode = (Node)parent; final PseudoClassState triggerState = triggerStates[n]; // if there is nothing in triggerState, then continue since there // isn't any pseudo-class state that might trigger a state change if (triggerState != null && triggerState.size() > 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(); } } // // return true if the fontStyleableProperty's origin is USER // private boolean isUserSetFont(Styleable node) { if (node == null) return false; // should never happen, but just to be safe... CssMetaData<Styleable, Font> fontCssMetaData = cacheContainer != null ? cacheContainer.fontProp : null; if (fontCssMetaData != null) { StyleableProperty<Font> fontStyleableProperty = fontCssMetaData != null ? fontCssMetaData.getStyleableProperty(node) : null; if (fontStyleableProperty != null && fontStyleableProperty.getStyleOrigin() == StyleOrigin.USER) return true; } CssStyleHelper parentStyleHelper = null; Styleable styleableParent = node; do { styleableParent = styleableParent.getStyleableParent(); if (styleableParent instanceof Node) { parentStyleHelper = ((Node)styleableParent).styleHelper; } } while (parentStyleHelper == null && styleableParent != null); if (parentStyleHelper != null) { return parentStyleHelper.isUserSetFont(styleableParent); } else { return false; } } // // return the value of the property // private static boolean isTrue(WritableValue<Boolean> booleanProperty) { return booleanProperty != null && booleanProperty.getValue(); } // // set the value of the property to true // private static void setTrue(WritableValue<Boolean> booleanProperty) { if (booleanProperty != null) booleanProperty.setValue(true); } // // return true if the Node's current styleHelper can be reused. // private static boolean canReuseStyleHelper(final Node node, final StyleMap styleMap) { // Obviously, we cannot reuse the node's style helper if it doesn't have one. if (node == null || node.styleHelper == null) { return false; } // If we have a styleHelper but the new styleMap is null, then we don't need a styleHelper at all if (styleMap == null) { return false; } StyleMap currentMap = node.styleHelper.getStyleMap(node); // We cannot reuse the style helper if the styleMap is not the same instance as the current one // Note: check instance equality! if (currentMap != styleMap) { return false; } // If the style maps are the same instance, we can re-use the current styleHelper if the cacheContainer is null. // Under this condition, there are no styles for this node _and_ no styles inherit. if (node.styleHelper.cacheContainer == null) { return true; } // // The current map might be the same, but one of the node's parent's maps might have changed which // might cause some calculated values to change. To see if we can re-use the style-helper, we need to // check if the StyleMap id's have changed, which we can do by inspecting the cacheContainer's styleCacheKey // since it is made up of the current set of StyleMap ids. // CssStyleHelper parentHelper = null; Styleable parent = node.getStyleableParent(); // if the node's parent is null and the style maps are the same, then we can certainly reuse the style-helper if (parent == null) { return true; } while (parent != null) { if (parent instanceof Node) { parentHelper = ((Node) parent).styleHelper; if (parentHelper != null) break; } parent = parent.getStyleableParent(); } if (parentHelper != null && parentHelper.cacheContainer != null) { int[] parentIds = parentHelper.cacheContainer.styleCacheKey.getStyleMapIds(); int[] nodeIds = node.styleHelper.cacheContainer.styleCacheKey.getStyleMapIds(); if (parentIds.length == nodeIds.length - 1) { boolean isSame = true; // check that all of the style map ids are the same. for (int i = 0; i < parentIds.length; i++) { if (nodeIds[i + 1] != parentIds[i]) { isSame = false; break; } } return isSame; } } return false; } private CacheContainer cacheContainer; private final static class CacheContainer { // Set internal internalState structures private CacheContainer( Node node, final 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<depth; d++) { // TODO: won't work for something like .menu-item:hover. Need to separate CssStyleHelper tree from scene-graph tree if ( parent instanceof Node) { Node parentNode = (Node)parent; final CssStyleHelper helper = parentNode.styleHelper; if (helper != null && helper.cacheContainer != null) { smapIds[ctr++] = helper.cacheContainer.smapId; } } parent = parent.getStyleableParent(); } this.styleCacheKey = new StyleCache.Key(smapIds, ctr); CssMetaData<Styleable,Font> styleableFontProperty = null; final List<CssMetaData<? extends Styleable, ?>> props = node.getCssMetaData(); final int pMax = props != null ? props.size() : 0; for (int p=0; p<pMax; p++) { final CssMetaData<? extends Styleable, ?> prop = props.get(p); if ("-fx-font".equals(prop.getProperty())) { // unchecked! styleableFontProperty = (CssMetaData<Styleable, Font>) prop; break; } } this.fontProp = styleableFontProperty; this.fontSizeCache = new HashMap<>(); this.cssSetProperties = new HashMap<>(); } private StyleMap getStyleMap(Styleable styleable) { if (styleable != null) { SubScene subScene = (styleable instanceof Node) ? ((Node) styleable).getSubScene() : null; return StyleManager.getInstance().getStyleMap(styleable, subScene, 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<Styleable,Font> 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<StyleCacheEntry.Key, CalculatedValue> 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 final Map<CssMetaData, CalculatedValue> cssSetProperties; private boolean forceSlowpath = false; } private void resetToInitialValues(final Styleable styleable) { if (cacheContainer == null || cacheContainer.cssSetProperties == null || cacheContainer.cssSetProperties.isEmpty()) return; // RT-31714 - make a copy of the entry set and clear the cssSetProperties immediately. Set<Entry<CssMetaData, CalculatedValue>> entrySet = new HashSet<>(cacheContainer.cssSetProperties.entrySet()); cacheContainer.cssSetProperties.clear(); for (Entry<CssMetaData, CalculatedValue> resetValues : 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); } /** * 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: * <pre><code> * .button { ... } * .button:hover { ... } * .button *.label { text-fill: black } * .button:hover *.label { text-fill: blue } * </code></pre> * 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<PseudoClass>[] getTransitionStates(final 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<PseudoClass>[] 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) ? parent.styleHelper : null; if (helper != null) { final Set<PseudoClass> pseudoClassState = 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<PseudoClass>[] transitionStates = new PseudoClassState[count]; System.arraycopy(retainedStates, 0, transitionStates, 0, count); return transitionStates; } /** * Called by the Node whenever it has transitioned from one set of * pseudo-class states to another. This function will then lookup the * new values for each of the styleable variables on the Node, and * then either set the value directly or start an animation based on * how things are specified in the CSS file. Currently animation support * is disabled until the new parser comes online with support for * animations and that support is detectable via the API. */ void transitionToState(final Node node) { if (cacheContainer == null) { return; } // // If styleMap is null, then StyleManager has blown it away and we need to reapply CSS. // final StyleMap styleMap = getStyleMap(node); if (styleMap == null) { cacheContainer = null; node.impl_reapplyCSS(); return; } // if the style-map is empty, then we are only looking for inherited styles. final boolean inheritOnly = styleMap.isEmpty(); // // Styles that need lookup can be cached provided none of the styles // are from Node.style. // final StyleCache sharedCache = StyleManager.getInstance().getSharedCache(node, node.getSubScene(), cacheContainer.styleCacheKey); if (sharedCache == null) { // Shared cache was blown away by StyleManager. // Therefore, this CssStyleHelper is no good. cacheContainer = null; node.impl_reapplyCSS(); return; } final Set<PseudoClass>[] transitionStates = getTransitionStates(node); final StyleCacheEntry.Key fontCacheKey = new StyleCacheEntry.Key(transitionStates, Font.getDefault()); CalculatedValue cachedFont = cacheContainer.fontSizeCache.get(fontCacheKey); if (cachedFont == null) { cachedFont = lookupFont(node, "-fx-font", styleMap, cachedFont); if (cachedFont == SKIP) cachedFont = getCachedFont(node.getStyleableParent()); if (cachedFont == null) cachedFont = new CalculatedValue(Font.getDefault(), null, false); cacheContainer.fontSizeCache.put(fontCacheKey,cachedFont); } final Font fontForRelativeSizes = (Font)cachedFont.getValue(); final StyleCacheEntry.Key cacheEntryKey = new StyleCacheEntry.Key(transitionStates, fontForRelativeSizes); StyleCacheEntry cacheEntry = sharedCache.getStyleCacheEntry(cacheEntryKey); // if the cacheEntry already exists, take the fastpath final boolean fastpath = cacheEntry != null; if (cacheEntry == null) { cacheEntry = new StyleCacheEntry(); sharedCache.addStyleCacheEntry(cacheEntryKey, cacheEntry); } final List<CssMetaData<? extends Styleable, ?>> styleables = node.getCssMetaData(); // Used in the for loop below, and a convenient place to stop when debugging. final int max = styleables.size(); final boolean isForceSlowpath = cacheContainer.forceSlowpath; cacheContainer.forceSlowpath = false; // RT-20643 CssError.setCurrentScene(node.getScene()); // For each property that is settable, we need to do a lookup and // transition to that value. for(int n=0; n<max; n++) { @SuppressWarnings("unchecked") // this is a widening conversion final CssMetaData<Styleable,Object> cssMetaData = (CssMetaData<Styleable,Object>)styleables.get(n); // Don't bother looking up styles that don't inherit. if (inheritOnly && cssMetaData.isInherits() == false) { continue; } // Skip the lookup if we know there isn't a chance for this property // to be set (usually due to a "bind"). if (!cssMetaData.isSettable(node)) continue; final String property = cssMetaData.getProperty(); CalculatedValue calculatedValue = cacheEntry.get(property); // If there is no calculatedValue and we're on the fast path, // take the slow path if cssFlags is REAPPLY (RT-31691) final boolean forceSlowpath = fastpath && calculatedValue == null && isForceSlowpath; final boolean addToCache = (!fastpath && calculatedValue == null) || forceSlowpath; if (fastpath && !forceSlowpath) { // If the cache contains SKIP, then there was an // exception thrown from applyStyle if (calculatedValue == SKIP) { continue; } } else if (calculatedValue == null) { // slowpath! calculatedValue = lookup(node, cssMetaData, styleMap, transitionStates[0], node, cachedFont); // lookup is not supposed to return null. if (calculatedValue == null) { assert false : "lookup returned null for " + property; continue; } } // StyleableProperty#applyStyle might throw an exception and it is called // from two places in this try block. try { // // RT-19089 // If the current value of the property was set by CSS // and there is no style for the property, then reset this // property to its initial value. If it was not set by CSS // then leave the property alone. // if (calculatedValue == null || calculatedValue == SKIP) { // cssSetProperties keeps track of the StyleableProperty's that were set by CSS in the previous state. // If this property is not in cssSetProperties map, then the property was not set in the previous state. // This accomplishes two things. First, it lets us know if the property was set in the previous state // so it can be reset in this state if there is no value for it. Second, it calling // CssMetaData#getStyleableProperty which is rather expensive as it may cause expansion of lazy // properties. CalculatedValue initialValue = cacheContainer.cssSetProperties.get(cssMetaData); // if the current value was set by CSS and there // is no calculated value for the property, then // there was no style for the property in the current // state, so reset the property to its initial value. if (initialValue != null) { StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(node); if (styleableProperty.getStyleOrigin() != StyleOrigin.USER) { styleableProperty.applyStyle(initialValue.getOrigin(), initialValue.getValue()); } } continue; } if (addToCache) { // If we're not on the fastpath, then add the calculated // value to cache. cacheEntry.put(property, calculatedValue); } StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(node); // need to know who set the current value - CSS, the user, or init final StyleOrigin originOfCurrentValue = styleableProperty.getStyleOrigin(); // RT-10522: // If the user set the property and there is a style and // the style came from the user agent stylesheet, then // skip the value. A style from a user agent stylesheet should // not override the user set style. // final StyleOrigin originOfCalculatedValue = calculatedValue.getOrigin(); // A calculated value should never have a null style origin since that would // imply the style didn't come from a stylesheet or in-line style. if (originOfCalculatedValue == null) { assert false : styleableProperty.toString(); continue; } if (originOfCurrentValue == StyleOrigin.USER) { if (originOfCalculatedValue == StyleOrigin.USER_AGENT) { continue; } } final Object value = calculatedValue.getValue(); final Object currentValue = styleableProperty.getValue(); // RT-21185: Only apply the style if something has changed. if ((originOfCurrentValue != originOfCalculatedValue) || (currentValue != null ? currentValue.equals(value) == false : value != null)) { if (LOGGER.isLoggable(Level.FINER)) { LOGGER.finer(property + ", call applyStyle: " + styleableProperty + ", value =" + String.valueOf(value) + ", originOfCalculatedValue=" + originOfCalculatedValue); } styleableProperty.applyStyle(originOfCalculatedValue, value); if (cacheContainer.cssSetProperties.containsKey(cssMetaData) == false) { // track this property CalculatedValue initialValue = new CalculatedValue(currentValue, originOfCurrentValue, false); cacheContainer.cssSetProperties.put(cssMetaData, initialValue); } } } catch (Exception e) { StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(node); final String msg = String.format("Failed to set css [%s] on [%s] due to '%s'\n", cssMetaData.getProperty(), styleableProperty, e.getMessage()); List<CssError> errors = null; if ((errors = StyleManager.getErrors()) != null) { final CssError error = new CssError.PropertySetError(cssMetaData, node, msg); errors.add(error); } PlatformLogger logger = Logging.getCSSLogger(); if (logger.isLoggable(Level.WARNING)) { logger.warning(msg); } // RT-27155: if setting value raises exception, reset value // the value to initial and thereafter skip setting the property cacheEntry.put(property, SKIP); CalculatedValue cachedValue = null; if (cacheContainer != null && cacheContainer.cssSetProperties != null) { cachedValue = cacheContainer.cssSetProperties.get(cssMetaData); } Object value = (cachedValue != null) ? cachedValue.getValue() : cssMetaData.getInitialValue(node); StyleOrigin origin = (cachedValue != null) ? cachedValue.getOrigin() : null; try { styleableProperty.applyStyle(origin, value); } catch (Exception ebad) { // This would be bad. if (logger.isLoggable(Level.SEVERE)) { logger.severe(String.format("Could not reset [%s] on [%s] due to %s\n" , cssMetaData.getProperty(), styleableProperty, e.getMessage())); } } } } // RT-20643 CssError.setCurrentScene(null); } /** * Gets the CSS CascadingStyle for the property of this node in these pseudo-class * states. A null style may be returned if there is no style information * for this combination of input parameters. * * * @param styleable * @param property * @param styleMap * @param states @return * */ private CascadingStyle getStyle(final Styleable styleable, final String property, final StyleMap styleMap, final Set<PseudoClass> states){ if (styleMap == null || styleMap.isEmpty()) return null; final Map<String, List<CascadingStyle>> cascadingStyleMap = styleMap.getCascadingStyles(); if (cascadingStyleMap == null || cascadingStyleMap.isEmpty()) return null; // Get all of the Styles which may apply to this particular property List<CascadingStyle> styles = cascadingStyleMap.get(property); // If there are no styles for this property then we can just bail if ((styles == null) || styles.isEmpty()) return null; // Go looking for the style. We do this by visiting each CascadingStyle in // order finding the first that matches the current node & set of // pseudo-class states. We use an iteration style that avoids creating // garbage iterators (and wish javac did it for us...) CascadingStyle style = null; final int max = (styles == null) ? 0 : styles.size(); for (int i=0; i<max; i++) { final CascadingStyle s = styles.get(i); final Selector sel = s == null ? null : s.getSelector(); if (sel == null) continue; // bail if the selector is null. //System.out.println(node.toString() + "\n\tstates=" + PseudoClassSet.getPseudoClasses(states) + "\n\tstateMatches? " + sel.stateMatches(node, states) + "\n\tsel=" + sel.toString()); if (sel.stateMatches(styleable, states)) { style = s; break; } } return style; } /** * The main workhorse of this class, the lookup method walks up the CSS * style tree looking for the style information for the Node, the * property associated with the given styleable, in these states for this font. * * * * * @param styleable * @param states * @param originatingStyleable * @return */ private CalculatedValue lookup(final Styleable styleable, final CssMetaData cssMetaData, final StyleMap styleMap, final Set<PseudoClass> states, final Styleable originatingStyleable, final CalculatedValue cachedFont) { if (cssMetaData.getConverter() == FontConverter.getInstance()) { return lookupFont(styleable, cssMetaData.getProperty(), styleMap, cachedFont); } final String property = cssMetaData.getProperty(); // Get the CascadingStyle which may apply to this particular property CascadingStyle style = getStyle(styleable, property, styleMap, states); // If no style was found and there are no sub styleables, then there // are no matching styles for this property. We will then either SKIP // or we will INHERIT. We will inspect the default value for the styleable, // and if it is INHERIT then we will inherit otherwise we just skip it. final List<CssMetaData<? extends Styleable, ?>> subProperties = cssMetaData.getSubProperties(); final int numSubProperties = (subProperties != null) ? subProperties.size() : 0; if (style == null) { if (numSubProperties == 0) { return handleNoStyleFound(styleable, cssMetaData, styleMap, states, originatingStyleable, cachedFont); } else { // If style is null then it means we didn't successfully find the // property we were looking for. However, there might be sub styleables, // in which case we should perform a lookup for them. For example, // there might not be a style for "font", but there might be one // for "font-size" or "font-weight". So if the style is null, then // we need to check with the sub-styleables. // Build up a list of all SubProperties which have a constituent part. // I default the array to be the size of the number of total // sub styleables to avoid having the array grow. Map<CssMetaData,Object> subs = null; StyleOrigin origin = null; boolean isRelative = false; for (int i=0; i<numSubProperties; i++) { CssMetaData subkey = subProperties.get(i); CalculatedValue constituent = lookup(styleable, subkey, styleMap, states, originatingStyleable, cachedFont); if (constituent != SKIP) { if (subs == null) { subs = new HashMap<>(); } subs.put(subkey, constituent.getValue()); // origin of this style is the most specific if ((origin != null && constituent.getOrigin() != null) ? origin.compareTo(constituent.getOrigin()) < 0 : constituent.getOrigin() != null) { origin = constituent.getOrigin(); } // if the constiuent uses relative sizes, then // isRelative is true; isRelative = isRelative || constituent.isRelative(); } } // If there are no subkeys which apply... if (subs == null || subs.isEmpty()) { return handleNoStyleFound(styleable, cssMetaData, styleMap, states, originatingStyleable, cachedFont); } try { final StyleConverter keyType = cssMetaData.getConverter(); if (keyType instanceof StyleConverterImpl) { Object ret = ((StyleConverterImpl)keyType).convert(subs); return new CalculatedValue(ret, origin, isRelative); } else { assert false; // TBD: should an explicit exception be thrown here? return SKIP; } } catch (ClassCastException cce) { final String msg = formatExceptionMessage(styleable, cssMetaData, null, cce); List<CssError> errors = null; if ((errors = StyleManager.getErrors()) != null) { final CssError error = new CssError.PropertySetError(cssMetaData, styleable, msg); errors.add(error); } if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.warning(msg); LOGGER.fine("caught: ", cce); LOGGER.fine("styleable = " + cssMetaData); LOGGER.fine("node = " + styleable.toString()); } return SKIP; } } } else { // style != null // RT-10522: // If the user set the property and there is a style and // the style came from the user agent stylesheet, then // skip the value. A style from a user agent stylesheet should // not override the user set style. if (style.getOrigin() == StyleOrigin.USER_AGENT) { StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(originatingStyleable); // if styleableProperty is null, then we're dealing with a sub-property. if (styleableProperty != null && styleableProperty.getStyleOrigin() == StyleOrigin.USER) { return SKIP; } } // If there was a style found, then we want to check whether the // value was "inherit". If so, then we will simply inherit. final ParsedValueImpl cssValue = style.getParsedValueImpl(); if (cssValue != null && "inherit".equals(cssValue.getValue())) { style = getInheritedStyle(styleable, property); if (style == null) return SKIP; } } // System.out.println("lookup " + property + // ", selector = \'" + style.selector.toString() + "\'" + // ", node = " + node.toString()); return calculateValue(style, styleable, cssMetaData, styleMap, states, originatingStyleable, cachedFont); } /** * Called when there is no style found. */ private CalculatedValue handleNoStyleFound(final Styleable styleable, final CssMetaData cssMetaData, final StyleMap styleMap, Set<PseudoClass> pseudoClassStates, Styleable originatingStyleable, final CalculatedValue cachedFont) { if (cssMetaData.isInherits()) { StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(styleable); StyleOrigin origin = styleableProperty != null ? styleableProperty.getStyleOrigin() : null; // RT-16308: if there is no matching style and the user set // the property, do not look for inherited styles. if (origin == StyleOrigin.USER) { return SKIP; } CascadingStyle style = getInheritedStyle(styleable, cssMetaData.getProperty()); if (style == null) return SKIP; CalculatedValue cv = calculateValue(style, styleable, cssMetaData, styleMap, pseudoClassStates, originatingStyleable, cachedFont); return cv; } else { // Not inherited. There is no style return SKIP; } } /** * Called when we must getInheritedStyle a value from a parent node in the scenegraph. */ private CascadingStyle getInheritedStyle( final Styleable styleable, final String property) { Styleable parent = styleable != null ? styleable.getStyleableParent() : null; while (parent != null) { CssStyleHelper parentStyleHelper = parent instanceof Node ? ((Node)parent).styleHelper : null; if (parentStyleHelper != null) { StyleMap parentStyleMap = parentStyleHelper.getStyleMap(parent); Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates; CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates); if (cascadingStyle != null) { final ParsedValueImpl cssValue = cascadingStyle.getParsedValueImpl(); if ("inherit".equals(cssValue.getValue())) { return getInheritedStyle(parent, property); } return cascadingStyle; } return null; } parent = parent.getStyleableParent(); } return null; } // helps with self-documenting the code private static final Set<PseudoClass> NULL_PSEUDO_CLASS_STATE = null; /** * Find the property among the styles that pertain to the Node */ private CascadingStyle resolveRef(final Styleable styleable, final String property, final StyleMap styleMap, final Set<PseudoClass> states) { final CascadingStyle style = getStyle(styleable, property, styleMap, states); if (style != null) { return style; } else { // if style is null, it may be because there isn't a style for this // node in this state, or we may need to look up the parent chain if (states != null && states.size() > 0) { // if states > 0, then we need to check this node again, // but without any states. return resolveRef(styleable,property, styleMap, NULL_PSEUDO_CLASS_STATE); } else { // TODO: This block was copied from inherit. Both should use same code somehow. Styleable styleableParent = styleable.getStyleableParent(); CssStyleHelper parentStyleHelper = null; if (styleableParent != null && styleableParent instanceof Node) { parentStyleHelper = ((Node)styleableParent).styleHelper; } while (styleableParent != null && parentStyleHelper == null) { styleableParent = styleableParent.getStyleableParent(); if (styleableParent != null && styleableParent instanceof Node) { parentStyleHelper = ((Node)styleableParent).styleHelper; } } if (styleableParent == null || parentStyleHelper == null) { return null; } StyleMap parentStyleMap = parentStyleHelper.getStyleMap(styleableParent); Set<PseudoClass> styleableParentPseudoClassStates = styleableParent instanceof Node ? ((Node)styleableParent).pseudoClassStates : styleable.getPseudoClassStates(); return parentStyleHelper.resolveRef(styleableParent, property, parentStyleMap, styleableParentPseudoClassStates); } } } // to resolve a lookup, we just need to find the parsed value. private ParsedValueImpl resolveLookups( final Styleable styleable, final ParsedValueImpl parsedValue, final StyleMap styleMap, Set<PseudoClass> states, final ObjectProperty<StyleOrigin> whence, Set<ParsedValue> resolves) { // // either the value itself is a lookup, or the value contain a lookup // if (parsedValue.isLookup()) { // The value we're looking for should be a Paint, one of the // containers for linear, radial or ladder, or a derived color. final Object val = parsedValue.getValue(); if (val instanceof String) { final String sval = ((String) val).toLowerCase(Locale.ROOT); CascadingStyle resolved = resolveRef(styleable, sval, styleMap, states); if (resolved != null) { if (resolves.contains(resolved.getParsedValueImpl())) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.warning("Loop detected in " + resolved.getRule().toString() + " while resolving '" + sval + "'"); } throw new IllegalArgumentException("Loop detected in " + resolved.getRule().toString() + " while resolving '" + sval + "'"); } else { resolves.add(parsedValue); } // The origin of this parsed value is the greatest of // any of the resolved reference. If a resolved reference // comes from an inline style, for example, then the value // calculated from the resolved lookup should have inline // as its origin. Otherwise, an inline style could be // stored in shared cache. final StyleOrigin wOrigin = whence.get(); final StyleOrigin rOrigin = resolved.getOrigin(); if (rOrigin != null && (wOrigin == null || wOrigin.compareTo(rOrigin) < 0)) { whence.set(rOrigin); } // the resolved value may itself need to be resolved. // For example, if the value "color" resolves to "base", // then "base" will need to be resolved as well. ParsedValueImpl pv = resolveLookups(styleable, resolved.getParsedValueImpl(), styleMap, states, whence, resolves); if (resolves != null) { resolves.remove(parsedValue); } return pv; } } } // If the value doesn't contain any values that need lookup, then bail if (!parsedValue.isContainsLookups()) { return parsedValue; } final Object val = parsedValue.getValue(); if (val instanceof ParsedValueImpl[][]) { // If ParsedValueImpl is a layered sequence of values, resolve the lookups for each. final ParsedValueImpl[][] layers = (ParsedValueImpl[][])val; ParsedValueImpl[][] resolved = new ParsedValueImpl[layers.length][0]; for (int l=0; l<layers.length; l++) { resolved[l] = new ParsedValueImpl[layers[l].length]; for (int ll=0; ll<layers[l].length; ll++) { if (layers[l][ll] == null) continue; resolved[l][ll] = resolveLookups(styleable, layers[l][ll], styleMap, states, whence, resolves); } } resolves.clear(); return new ParsedValueImpl(resolved, parsedValue.getConverter(), false); } else if (val instanceof ParsedValueImpl[]) { // If ParsedValueImpl is a sequence of values, resolve the lookups for each. final ParsedValueImpl[] layer = (ParsedValueImpl[])val; ParsedValueImpl[] resolved = new ParsedValueImpl[layer.length]; for (int l=0; l<layer.length; l++) { if (layer[l] == null) continue; resolved[l] = resolveLookups(styleable, layer[l], styleMap, states, whence, resolves); } resolves.clear(); return new ParsedValueImpl(resolved, parsedValue.getConverter(), false); } return parsedValue; } private String getUnresolvedLookup(final ParsedValueImpl resolved) { Object value = resolved.getValue(); if (resolved.isLookup() && value instanceof String) { return (String)value; } if (value instanceof ParsedValueImpl[][]) { final ParsedValueImpl[][] layers = (ParsedValueImpl[][])value; for (int l=0; l<layers.length; l++) { for (int ll=0; ll<layers[l].length; ll++) { if (layers[l][ll] == null) continue; String unresolvedLookup = getUnresolvedLookup(layers[l][ll]); if (unresolvedLookup != null) return unresolvedLookup; } } } else if (value instanceof ParsedValueImpl[]) { // If ParsedValueImpl is a sequence of values, resolve the lookups for each. final ParsedValueImpl[] layer = (ParsedValueImpl[])value; for (int l=0; l<layer.length; l++) { if (layer[l] == null) continue; String unresolvedLookup = getUnresolvedLookup(layer[l]); if (unresolvedLookup != null) return unresolvedLookup; } } return null; } private String formatUnresolvedLookupMessage(Styleable styleable, CssMetaData cssMetaData, Style style, ParsedValueImpl resolved, ClassCastException cce) { // Find value that could not be looked up. If the resolved value does not contain lookups, then the // ClassCastException is not because of trying to convert a String (which is the missing lookup) // to some value, but is because the convert method got some wrong value - like a paint when it should be a color. // See RT-33319 for an example of this. String missingLookup = resolved != null && resolved.isContainsLookups() ? getUnresolvedLookup(resolved) : null; StringBuilder sbuf = new StringBuilder(); if (missingLookup != null) { sbuf.append("Could not resolve '") .append(missingLookup) .append("'") .append(" while resolving lookups for '") .append(cssMetaData.getProperty()) .append("'"); } else { sbuf.append("Caught '") .append(cce) .append("'") .append(" while converting value for '") .append(cssMetaData.getProperty()) .append("'"); } final Rule rule = style != null ? style.getDeclaration().getRule(): null; final Stylesheet stylesheet = rule != null ? rule.getStylesheet() : null; final String url = stylesheet != null ? stylesheet.getUrl() : null; if (url != null) { sbuf.append(" from rule '") .append(style.getSelector()) .append("' in stylesheet ").append(url); } else if (stylesheet != null && StyleOrigin.INLINE == stylesheet.getOrigin()) { sbuf.append(" from inline style on " ) .append(styleable.toString()); } return sbuf.toString(); } private String formatExceptionMessage(Styleable styleable, CssMetaData cssMetaData, Style style, Exception e) { StringBuilder sbuf = new StringBuilder(); sbuf.append("Caught ") .append(String.valueOf(e)); if (cssMetaData != null) { sbuf.append("'") .append(" while calculating value for '") .append(cssMetaData.getProperty()) .append("'"); } if (style != null) { final Rule rule = style.getDeclaration().getRule(); final Stylesheet stylesheet = rule != null ? rule.getStylesheet() : null; final String url = stylesheet != null ? stylesheet.getUrl() : null; if (url != null) { sbuf.append(" from rule '") .append(style.getSelector()) .append("' in stylesheet ").append(url); } else if (styleable != null && stylesheet != null && StyleOrigin.INLINE == stylesheet.getOrigin()) { sbuf.append(" from inline style on " ) .append(styleable.toString()); } else { sbuf.append(" from style '") .append(String.valueOf(style)) .append("'"); } } return sbuf.toString(); } private CalculatedValue calculateValue( final CascadingStyle style, final Styleable styleable, final CssMetaData cssMetaData, final StyleMap styleMap, final Set<PseudoClass> states, final Styleable originatingStyleable, final CalculatedValue fontFromCacheEntry) { final ParsedValueImpl cssValue = style.getParsedValueImpl(); if (cssValue != null && !("null".equals(cssValue.getValue()) || "none".equals(cssValue.getValue()))) { ParsedValueImpl resolved = null; try { ObjectProperty<StyleOrigin> whence = new SimpleObjectProperty<>(style.getOrigin()); resolved = resolveLookups(styleable, cssValue, styleMap, states, whence, new HashSet<>()); final String property = cssMetaData.getProperty(); // The computed value Object val = null; boolean isFontProperty = "-fx-font".equals(property) || "-fx-font-size".equals(property); boolean isRelative = ParsedValueImpl.containsFontRelativeSize(resolved, isFontProperty); // // Avoid using a font calculated from a relative size // to calculate a font with a relative size. // For example: // Assume the default font size is 13 and we have a style with // -fx-font-size: 1.5em, then the cacheEntry font value will // have a size of 13*1.5=19.5. // Now, when converting that same font size again in response // to looking up a value for -fx-font, we do not want to use // 19.5 as the font for relative size conversion since this will // yield a font 19.5*1.5=29.25 when really what we want is // a font size of 19.5. // In this situation, then, we use the font from the parent's // cache entry. Font fontForFontRelativeSizes = null; if (isRelative && isFontProperty && (fontFromCacheEntry == null || fontFromCacheEntry.isRelative())) { Styleable parent = styleable; CalculatedValue childsCachedFont = fontFromCacheEntry; do { CalculatedValue parentsCachedFont = getCachedFont(parent.getStyleableParent()); if (parentsCachedFont != null) { if (parentsCachedFont.isRelative()) { // // If the cached fonts are the same, then the cached font came from the same // style and we need to keep looking. Otherwise, use the font we found. // if (childsCachedFont == null || parentsCachedFont.equals(childsCachedFont)) { childsCachedFont = parentsCachedFont; } else { fontForFontRelativeSizes = (Font)parentsCachedFont.getValue(); } } else { // fontValue.isRelative() == false! fontForFontRelativeSizes = (Font)parentsCachedFont.getValue(); } } } while(fontForFontRelativeSizes == null && (parent = parent.getStyleableParent()) != null); } // did we get a fontValue from the preceding block? // if not, get it from our cacheEntry or choose the default if (fontForFontRelativeSizes == null) { if (fontFromCacheEntry != null && fontFromCacheEntry.isRelative() == false) { fontForFontRelativeSizes = (Font)fontFromCacheEntry.getValue(); } else { fontForFontRelativeSizes = Font.getDefault(); } } final StyleConverter cssMetaDataConverter = cssMetaData.getConverter(); // RT-37727 - handling of properties that are insets is wonky. If the property is -fx-inset, then // there isn't an issue because the converter assigns the InsetsConverter to the ParsedValueImpl. // But -my-insets will parse as an array of numbers and the parser will assign the Size sequence // converter to it. So, if the CssMetaData says it uses InsetsConverter, use the InsetsConverter // and not the parser assigned converter. if (cssMetaDataConverter == StyleConverter.getInsetsConverter()) { if (resolved.getValue() instanceof ParsedValue) { // If you give the parser "-my-insets: 5;" you end up with a ParsedValueImpl<ParsedValue<?,Size>, Number> // and not a ParsedValueImpl<ParsedValue[], Number[]> so here we wrap the value into an array // to make the InsetsConverter happy. resolved = new ParsedValueImpl(new ParsedValue[] {(ParsedValue)resolved.getValue()}, null, false); } val = cssMetaDataConverter.convert(resolved, fontForFontRelativeSizes); } else if (resolved.getConverter() != null) val = resolved.convert(fontForFontRelativeSizes); else val = cssMetaData.getConverter().convert(resolved, fontForFontRelativeSizes); final StyleOrigin origin = whence.get(); return new CalculatedValue(val, origin, isRelative); } catch (ClassCastException cce) { final String msg = formatUnresolvedLookupMessage(styleable, cssMetaData, style.getStyle(),resolved, cce); List<CssError> errors = null; if ((errors = StyleManager.getErrors()) != null) { final CssError error = new CssError.PropertySetError(cssMetaData, styleable, msg); errors.add(error); } if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.warning(msg); LOGGER.fine("node = " + styleable.toString()); LOGGER.fine("cssMetaData = " + cssMetaData); LOGGER.fine("styles = " + getMatchingStyles(styleable, cssMetaData)); } return SKIP; } catch (IllegalArgumentException iae) { final String msg = formatExceptionMessage(styleable, cssMetaData, style.getStyle(), iae); List<CssError> errors = null; if ((errors = StyleManager.getErrors()) != null) { final CssError error = new CssError.PropertySetError(cssMetaData, styleable, msg); errors.add(error); } if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.warning(msg); LOGGER.fine("caught: ", iae); LOGGER.fine("styleable = " + cssMetaData); LOGGER.fine("node = " + styleable.toString()); } return SKIP; } catch (NullPointerException npe) { final String msg = formatExceptionMessage(styleable, cssMetaData, style.getStyle(), npe); List<CssError> errors = null; if ((errors = StyleManager.getErrors()) != null) { final CssError error = new CssError.PropertySetError(cssMetaData, styleable, msg); errors.add(error); } if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.warning(msg); LOGGER.fine("caught: ", npe); LOGGER.fine("styleable = " + cssMetaData); LOGGER.fine("node = " + styleable.toString()); } return SKIP; } } // either cssValue was null or cssValue's value was "null" or "none" return new CalculatedValue(null, style.getOrigin(), false); } private static final CssMetaData dummyFontProperty = new FontCssMetaData<Node>("-fx-font", Font.getDefault()) { @Override public boolean isSettable(Node node) { return true; } @Override public StyleableProperty<Font> getStyleableProperty(Node node) { return null; } }; private CalculatedValue getCachedFont(final Styleable styleable) { if (styleable instanceof Node == false) return null; CalculatedValue cachedFont = null; Node parent = (Node)styleable; final CssStyleHelper parentHelper = parent.styleHelper; // if there is no parentHelper, // or there is a parentHelper but no cacheContainer, // then look to the next parent if (parentHelper == null || parentHelper.cacheContainer == null) { cachedFont = getCachedFont(parent.getStyleableParent()); // there is a parent helper and a cacheContainer, } else { CacheContainer parentCacheContainer = parentHelper.cacheContainer; if ( parentCacheContainer != null && parentCacheContainer.fontSizeCache != null && parentCacheContainer.fontSizeCache.isEmpty() == false) { Set<PseudoClass>[] transitionStates = parentHelper.getTransitionStates(parent); StyleCacheEntry.Key parentCacheEntryKey = new StyleCacheEntry.Key(transitionStates, Font.getDefault()); cachedFont = parentCacheContainer.fontSizeCache.get(parentCacheEntryKey); } if (cachedFont == null) { StyleMap smap = parentHelper.getStyleMap(parent); cachedFont = parentHelper.lookupFont(parent, "-fx-font", smap, null); } } return cachedFont != SKIP ? cachedFont : null; } /*package access for testing*/ FontPosture getFontPosture(Font font) { if (font == null) return FontPosture.REGULAR; String fontName = font.getName().toLowerCase(Locale.ROOT); if (fontName.contains("italic")) { return FontPosture.ITALIC; } return FontPosture.REGULAR; } /*package access for testing*/ FontWeight getFontWeight(Font font) { if (font == null) return FontWeight.NORMAL; String fontName = font.getName().toLowerCase(Locale.ROOT); if (fontName.contains("bold")) { if (fontName.contains("extra")) return FontWeight.EXTRA_BOLD; if (fontName.contains("ultra")) return FontWeight.EXTRA_BOLD; else if (fontName.contains("semi")) return FontWeight.SEMI_BOLD; else if (fontName.contains("demi")) return FontWeight.SEMI_BOLD; else return FontWeight.BOLD; } else if (fontName.contains("light")) { if (fontName.contains("extra")) return FontWeight.EXTRA_LIGHT; if (fontName.contains("ultra")) return FontWeight.EXTRA_LIGHT; else return FontWeight.LIGHT; } else if (fontName.contains("black")) { return FontWeight.BLACK; } else if (fontName.contains("heavy")) { return FontWeight.BLACK; } else if (fontName.contains("medium")) { return FontWeight.MEDIUM; } return FontWeight.NORMAL; } /*package access for testing*/ String getFontFamily(Font font) { if (font == null) return Font.getDefault().getFamily(); return font.getFamily(); } /*package access for testing*/ Font deriveFont( Font font, String fontFamily, FontWeight fontWeight, FontPosture fontPosture, double fontSize) { if (font != null && fontFamily == null) fontFamily = getFontFamily(font); else if (fontFamily != null) fontFamily = Utils.stripQuotes(fontFamily); if (font != null && fontWeight == null) fontWeight = getFontWeight(font); if (font != null && fontPosture == null) fontPosture = getFontPosture(font); if (font != null && fontSize <= 0) fontSize = font.getSize(); return Font.font( fontFamily, fontWeight, fontPosture, fontSize); } /** * Look up a font property. This is handled separately from lookup since * font is inherited and has sub-properties. One should expect that the * text font for the following would be 16px Arial. The lookup method would * give 16px system since it would look <em>only</em> for font-size, * font-family, etc <em>only</em> if the lookup on font failed. * <pre> * Text text = new Text("Hello World"); * text.setStyle("-fx-font-size: 16px;"); * Group group = new Group(); * group.setStyle("-fx-font: 12px Arial;"); * group.getChildren().add(text); * </pre> */ /*package access for testing*/ CalculatedValue lookupFont( final Styleable styleable, final String property, final StyleMap styleMap, final CalculatedValue cachedFont) { StyleOrigin origin = null; // How far from this node did we travel to find a font shorthand? // Don't look past this distance for other font properties. int distance = 0; // Did we find a style? boolean foundStyle = false; String family = null; double size = -1; FontWeight weight = null; FontPosture posture = null; CalculatedValue cvFont = cachedFont; Set<PseudoClass> states = styleable instanceof Node ? ((Node)styleable).pseudoClassStates : styleable.getPseudoClassStates(); // RT-20145 - if looking for font size and the node has a font, // use the font property's value if it was set by the user and // there is not an inline or author style. if (cacheContainer.fontProp != null) { StyleableProperty<Font> styleableProp = cacheContainer.fontProp.getStyleableProperty(styleable); StyleOrigin fpOrigin = styleableProp.getStyleOrigin(); Font font = styleableProp.getValue(); if (font == null) font = Font.getDefault(); if (fpOrigin == StyleOrigin.USER) { origin = fpOrigin; family = getFontFamily(font); size = font.getSize(); weight = getFontWeight(font); posture = getFontPosture(font); cvFont = new CalculatedValue(font, fpOrigin, false); } } CalculatedValue parentCachedFont = getCachedFont(styleable.getStyleableParent()); if (parentCachedFont == null) parentCachedFont = new CalculatedValue(Font.getDefault(), null, false); // // Look up the font- properties // CascadingStyle fontShorthand = getStyle(styleable, property, styleMap, states); // don't look past current node for font shorthand if user set the font if (fontShorthand == null && origin != StyleOrigin.USER) { Styleable parent = styleable != null ? styleable.getStyleableParent() : null; while (parent != null) { CssStyleHelper parentStyleHelper = parent instanceof Node ? ((Node)parent).styleHelper : null; if (parentStyleHelper != null) { distance += 1; StyleMap parentStyleMap = parentStyleHelper.getStyleMap(parent); Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates; CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates); if (cascadingStyle != null) { final ParsedValueImpl cssValue = cascadingStyle.getParsedValueImpl(); if ("inherit".equals(cssValue.getValue()) == false) { fontShorthand = cascadingStyle; break; } } } parent = parent.getStyleableParent(); } } if (fontShorthand != null) { // // If we don't have an existing font, or if the origin of the // existing font is less than that of the shorthand, then // take the shorthand. If the origins compare equals, then take // the shorthand since the fontProp value will not have been // updated yet. // if (origin == null || origin.compareTo(fontShorthand.getOrigin()) <= 0) { final CalculatedValue cv = calculateValue(fontShorthand, styleable, dummyFontProperty, styleMap, states, styleable, parentCachedFont); // cv could be SKIP if (cv.getValue() instanceof Font) { origin = cv.getOrigin(); Font font = (Font)cv.getValue(); family = getFontFamily(font); size = font.getSize(); weight = getFontWeight(font); posture = getFontPosture(font); cvFont = cv; foundStyle = true; } } } CascadingStyle fontSize = getStyle(styleable, property.concat("-size"), styleMap, states); if (fontSize != null) { // if we have a font shorthand and it is more specific than font-size, then don't use the font-size style if (fontShorthand != null && fontShorthand.compareTo(fontSize) < 0) { fontSize = null; } else if (origin == StyleOrigin.USER) { // If fontSize is an inline or author-stylesheet style, use it. // Otherwise, fontSize is a user-agent stylesheet style and should not override the USER style. if (StyleOrigin.USER.compareTo(fontSize.getOrigin()) > 0) { fontSize = null; } } } else if (origin != StyleOrigin.USER) { // // If we don't have a font-size, see if there is an inherited font-size. // If lookupInheritedFontProperty returns other than null, then we know that font-size is closer (more specific) // than the font shorthand // fontSize = lookupInheritedFontProperty(styleable, property.concat("-size"), styleMap, distance, fontShorthand); } if (fontSize != null) { // The logic above ensures that, if fontSize is not null, then it is either // 1) a style matching this node and is more specific than the font shorthand or // 2) an inherited style that is more specific than the font shorthand // and, therefore, we can use the fontSize style final CalculatedValue cv = calculateValue(fontSize, styleable, dummyFontProperty, styleMap, states, styleable, parentCachedFont); if (cv.getValue() instanceof Double) { if (origin == null || origin.compareTo(fontSize.getOrigin()) <= 0) { origin = cv.getOrigin(); } size = (Double) cv.getValue(); if (cvFont != null) { boolean isRelative = cvFont.isRelative() || cv.isRelative(); Font font = deriveFont((Font) cvFont.getValue(), family, weight, posture, size); cvFont = new CalculatedValue(font, origin, isRelative); } else { boolean isRelative = cv.isRelative(); Font font = deriveFont(Font.getDefault(), family, weight, posture, size); cvFont = new CalculatedValue(font, origin, isRelative); } foundStyle = true; } } // if cachedFont is null, then we're in this method to look up a font for the CacheContainer's fontSizeCache // and we only care about font-size or the size from font shorthand. if (cachedFont == null) { return (cvFont != null) ? cvFont : SKIP; } CascadingStyle fontWeight = getStyle(styleable, property.concat("-weight"), styleMap, states); if (fontWeight != null) { // if we have a font shorthand and it is more specific than font-weight, then don't use the font-weight style if (fontShorthand != null && fontShorthand.compareTo(fontWeight) < 0) { fontWeight = null; } } else if (origin != StyleOrigin.USER) { // // If we don't have a font-weight, see if there is an inherited font-weight. // If lookupInheritedFontProperty returns other than null, then we know that font-weight is closer (more specific) // than the font shorthand // fontWeight = lookupInheritedFontProperty(styleable, property.concat("-weight"), styleMap, distance, fontShorthand); } if (fontWeight != null) { // The logic above ensures that, if fontWeight is not null, then it is either // 1) a style matching this node and is more specific than the font shorthand or // 2) an inherited style that is more specific than the font shorthand // and, therefore, we can use the fontWeight style final CalculatedValue cv = calculateValue(fontWeight, styleable, dummyFontProperty, styleMap, states, styleable, null); if (cv.getValue() instanceof FontWeight) { if (origin == null || origin.compareTo(fontWeight.getOrigin()) <= 0) { origin = cv.getOrigin(); } weight = (FontWeight)cv.getValue(); foundStyle = true; } } CascadingStyle fontStyle = getStyle(styleable, property.concat("-style"), styleMap, states); if (fontStyle != null) { // if we have a font shorthand and it is more specific than font-style, then don't use the font-style style if (fontShorthand != null && fontShorthand.compareTo(fontStyle) < 0) { fontStyle = null; } } else if (origin != StyleOrigin.USER) { // // If we don't have a font-style, see if there is an inherited font-style. // If lookupInheritedFontProperty returns other than null, then we know that font-style is closer (more specific) // than the font shorthand // fontStyle = lookupInheritedFontProperty(styleable, property.concat("-style"), styleMap, distance, fontShorthand); } if (fontStyle != null) { // The logic above ensures that, if fontStyle is not null, then it is either // 1) a style matching this node and is more specific than the font shorthand or // 2) an inherited style that is more specific than the font shorthand // and, therefore, we can use the fontStyle style final CalculatedValue cv = calculateValue(fontStyle, styleable, dummyFontProperty, styleMap, states, styleable, null); if (cv.getValue() instanceof FontPosture) { if (origin == null || origin.compareTo(fontStyle.getOrigin()) <= 0) { origin = cv.getOrigin(); } posture = (FontPosture)cv.getValue(); foundStyle = true; } } CascadingStyle fontFamily = getStyle(styleable, property.concat("-family"), styleMap, states); if (fontFamily != null) { // if we have a font shorthand and it is more specific than font-family, then don't use the font-family style if (fontShorthand != null && fontShorthand.compareTo(fontFamily) < 0) { fontFamily = null; } } else if (origin != StyleOrigin.USER) { // // If we don't have a font-family, see if there is an inherited font-family. // If lookupInheritedFontProperty returns other than null, then we know that font-family is closer (more specific) // than the font shorthand // fontFamily = lookupInheritedFontProperty(styleable, property.concat("-family"), styleMap, distance, fontShorthand); } if (fontFamily != null) { // The logic above ensures that, if fontFamily is not null, then it is either // 1) a style matching this node and is more specific than the font shorthand or // 2) an inherited style that is more specific than the font shorthand // and, therefore, we can use the fontFamily style final CalculatedValue cv = calculateValue(fontFamily, styleable, dummyFontProperty, styleMap, states, styleable, null); if (cv.getValue() instanceof String) { if (origin == null || origin.compareTo(fontFamily.getOrigin()) <= 0) { origin = cv.getOrigin(); } family = (String)cv.getValue(); foundStyle = true; } } if (foundStyle) { Font font = cvFont != null ? (Font)cvFont.getValue() : Font.getDefault(); Font derivedFont = deriveFont(font, family, weight, posture, size); return new CalculatedValue(derivedFont,origin,false); } return SKIP; } private CascadingStyle lookupInheritedFontProperty( final Styleable styleable, final String property, final StyleMap styleMap, final int distance, CascadingStyle fontShorthand) { Styleable parent = styleable != null ? styleable.getStyleableParent() : null; int nlooks = distance; while (parent != null && nlooks > 0) { CssStyleHelper parentStyleHelper = parent instanceof Node ? ((Node)parent).styleHelper : null; if (parentStyleHelper != null) { nlooks -= 1; StyleMap parentStyleMap = parentStyleHelper.getStyleMap((parent)); Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates; CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates); if (cascadingStyle != null) { // If we are closer to the node than the font shorthand, then font shorthand doesn't matter. // If the font shorthand and this style are the same distance, then we need to compare. if (fontShorthand != null && nlooks == 0) { if (fontShorthand.compareTo(cascadingStyle) < 0) { return null; } } final ParsedValueImpl cssValue = cascadingStyle.getParsedValueImpl(); if ("inherit".equals(cssValue.getValue()) == false) { return cascadingStyle; } } } parent = parent.getStyleableParent(); } return null; } /** * Called from Node impl_getMatchingStyles * @param styleable * @param styleableProperty * @return */ static List<Style> getMatchingStyles(final Styleable styleable, final CssMetaData styleableProperty) { if (!(styleable instanceof Node)) return Collections.<Style>emptyList(); Node node = (Node)styleable; final CssStyleHelper helper = (node.styleHelper != null) ? node.styleHelper : createStyleHelper(node); if (helper != null) { return helper.getMatchingStyles(node, styleableProperty, false); } else { return Collections.<Style>emptyList(); } } static Map<StyleableProperty<?>, List<Style>> getMatchingStyles(Map<StyleableProperty<?>, List<Style>> map, final Node node) { final CssStyleHelper helper = (node.styleHelper != null) ? node.styleHelper : createStyleHelper(node); if (helper != null) { if (map == null) map = new HashMap<>(); for (CssMetaData metaData : node.getCssMetaData()) { List<Style> styleList = helper.getMatchingStyles(node, metaData, true); if (styleList != null && !styleList.isEmpty()) { StyleableProperty prop = metaData.getStyleableProperty(node); map.put(prop, styleList); } } } if (node instanceof Parent) { for (Node child : ((Parent)node).getChildren()) { map = getMatchingStyles(map, child); } } return map; } private List<Style> getMatchingStyles(final Styleable node, final CssMetaData styleableProperty, boolean matchState) { final List<CascadingStyle> styleList = new ArrayList<>(); getMatchingStyles(node, styleableProperty, styleList, matchState); List<CssMetaData<? extends Styleable, ?>> subProperties = styleableProperty.getSubProperties(); if (subProperties != null) { for (int n=0,nMax=subProperties.size(); n<nMax; n++) { final CssMetaData subProperty = subProperties.get(n); getMatchingStyles(node, subProperty, styleList, matchState); } } Collections.sort(styleList); final List<Style> matchingStyles = new ArrayList<>(styleList.size()); for (int n=0,nMax=styleList.size(); n<nMax; n++) { final Style style = styleList.get(n).getStyle(); if (!matchingStyles.contains(style)) matchingStyles.add(style); } return matchingStyles; } private void getMatchingStyles(final Styleable node, final CssMetaData styleableProperty, final List<CascadingStyle> styleList, boolean matchState) { if (node != null) { String property = styleableProperty.getProperty(); Node _node = node instanceof Node ? (Node)node : null; final StyleMap smap = getStyleMap(_node); if (smap == null) return; if (matchState) { CascadingStyle cascadingStyle = getStyle(node, styleableProperty.getProperty(), smap, _node.pseudoClassStates); if (cascadingStyle != null) { styleList.add(cascadingStyle); final ParsedValueImpl parsedValue = cascadingStyle.getParsedValueImpl(); getMatchingLookupStyles(node, parsedValue, styleList, matchState); } } else { Map<String, List<CascadingStyle>> cascadingStyleMap = smap.getCascadingStyles(); // StyleMap.getCascadingStyles() does not return null List<CascadingStyle> styles = cascadingStyleMap.get(property); if (styles != null) { styleList.addAll(styles); for (int n=0, nMax=styles.size(); n<nMax; n++) { final CascadingStyle style = styles.get(n); final ParsedValueImpl parsedValue = style.getParsedValueImpl(); getMatchingLookupStyles(node, parsedValue, styleList, matchState); } } } if (styleableProperty.isInherits()) { Styleable parent = node.getStyleableParent(); while (parent != null) { CssStyleHelper parentHelper = parent instanceof Node ? ((Node)parent).styleHelper : null; if (parentHelper != null) { parentHelper.getMatchingStyles(parent, styleableProperty, styleList, matchState); } parent = parent.getStyleableParent(); } } } } // Pretty much a duplicate of resolveLookups, but without the state private void getMatchingLookupStyles(final Styleable node, final ParsedValueImpl parsedValue, final List<CascadingStyle> styleList, boolean matchState) { if (parsedValue.isLookup()) { Object value = parsedValue.getValue(); if (value instanceof String) { final String property = (String)value; // gather up any and all styles that contain this value as a property Styleable parent = node; do { final Node _parent = parent instanceof Node ? (Node)parent : null; final CssStyleHelper helper = _parent != null ? _parent.styleHelper : null; if (helper != null) { StyleMap styleMap = helper.getStyleMap(parent); if (styleMap == null || styleMap.isEmpty()) continue; final int start = styleList.size(); if (matchState) { CascadingStyle cascadingStyle = helper.resolveRef(_parent, property, styleMap, _parent.pseudoClassStates); if (cascadingStyle != null) { styleList.add(cascadingStyle); } } else { final Map<String, List<CascadingStyle>> smap = styleMap.getCascadingStyles(); // getCascadingStyles does not return null List<CascadingStyle> styles = smap.get(property); if (styles != null) { styleList.addAll(styles); } } final int end = styleList.size(); for (int index=start; index<end; index++) { final CascadingStyle style = styleList.get(index); getMatchingLookupStyles(parent, style.getParsedValueImpl(), styleList, matchState); } } } while ((parent = parent.getStyleableParent()) != null); } } // If the value doesn't contain any values that need lookup, then bail if (!parsedValue.isContainsLookups()) { return; } final Object val = parsedValue.getValue(); if (val instanceof ParsedValueImpl[][]) { // If ParsedValueImpl is a layered sequence of values, resolve the lookups for each. final ParsedValueImpl[][] layers = (ParsedValueImpl[][])val; for (int l=0; l<layers.length; l++) { for (int ll=0; ll<layers[l].length; ll++) { if (layers[l][ll] == null) continue; getMatchingLookupStyles(node, layers[l][ll], styleList, matchState); } } } else if (val instanceof ParsedValueImpl[]) { // If ParsedValueImpl is a sequence of values, resolve the lookups for each. final ParsedValueImpl[] layer = (ParsedValueImpl[])val; for (int l=0; l<layer.length; l++) { if (layer[l] == null) continue; getMatchingLookupStyles(node, layer[l], styleList, matchState); } } } }
Ms-Dos/Windows
Unix
Write backup
jsp File Browser version 1.2 by
www.vonloesch.de