1 /* 2 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javafx.scene; 26 27 import java.util.ArrayList; 28 import java.util.Collections; 29 import java.util.HashMap; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Locale; 33 import java.util.Map; 34 import java.util.Map.Entry; 35 import java.util.Set; 36 37 import javafx.beans.property.ObjectProperty; 38 import javafx.beans.property.SimpleObjectProperty; 39 import javafx.beans.value.WritableValue; 40 import com.sun.javafx.css.CascadingStyle; 41 import javafx.css.CssMetaData; 42 import javafx.css.CssParser; 43 import javafx.css.FontCssMetaData; 44 import javafx.css.ParsedValue; 45 import javafx.css.PseudoClass; 46 import javafx.css.Rule; 47 import javafx.css.Selector; 48 import javafx.css.Style; 49 import javafx.css.StyleConverter; 50 import javafx.css.StyleOrigin; 51 import javafx.css.Styleable; 52 import javafx.css.StyleableProperty; 53 import javafx.css.Stylesheet; 54 import javafx.scene.text.Font; 55 import javafx.scene.text.FontPosture; 56 import javafx.scene.text.FontWeight; 57 58 import com.sun.javafx.css.CalculatedValue; 59 import com.sun.javafx.css.ParsedValueImpl; 60 import com.sun.javafx.css.PseudoClassState; 61 import com.sun.javafx.css.StyleCache; 62 import com.sun.javafx.css.StyleCacheEntry; 63 import com.sun.javafx.css.StyleManager; 64 import com.sun.javafx.css.StyleMap; 65 import javafx.css.converter.FontConverter; 66 import com.sun.javafx.util.Logging; 67 import com.sun.javafx.util.Utils; 68 69 import com.sun.javafx.logging.PlatformLogger; 70 import com.sun.javafx.logging.PlatformLogger.Level; 71 72 import static com.sun.javafx.css.CalculatedValue.*; 73 74 /** 75 * The StyleHelper is a helper class used for applying CSS information to Nodes. 76 */ 77 final class CssStyleHelper { 78 79 private static final PlatformLogger LOGGER = com.sun.javafx.util.Logging.getCSSLogger(); 80 81 private CssStyleHelper() { 82 this.triggerStates = new PseudoClassState(); 83 } 84 85 /** 86 * Creates a new StyleHelper. 87 */ 88 static CssStyleHelper createStyleHelper(final Node node) { 89 90 // need to know how far we are to root in order to init arrays. 91 // TODO: should we hang onto depth to avoid this nonsense later? 92 // TODO: is there some other way of knowing how far from the root a node is? 93 Styleable parent = node; 94 int depth = 0; 95 while(parent != null) { 96 depth++; 97 parent = parent.getStyleableParent(); 98 } 99 100 // The List<CacheEntry> should only contain entries for those 101 // pseudo-class states that have styles. The StyleHelper's 102 // pseudoclassStateMask is a bitmask of those pseudoclasses that 103 // appear in the node's StyleHelper's smap. This list of 104 // pseudo-class masks is held by the StyleCacheKey. When a node is 105 // styled, its pseudoclasses and the pseudoclasses of its parents 106 // are gotten. By comparing the actual pseudo-class state to the 107 // pseudo-class states that apply, a CacheEntry can be created or 108 // fetched using only those pseudoclasses that matter. 109 final PseudoClassState[] triggerStates = new PseudoClassState[depth]; 110 111 final StyleMap styleMap = 112 StyleManager.getInstance().findMatchingStyles(node, node.getSubScene(), triggerStates); 113 114 // 115 // reuse the existing styleHelper if possible. 116 // 117 if ( canReuseStyleHelper(node, styleMap) ) { 118 119 // 120 // RT-33080 121 // 122 // If we're reusing a style helper, clear the fontSizeCache in case either this node or some parent 123 // node has changed font from a user calling setFont. 124 // 125 // It may be the case that the node's font has changed from a call to setFont, which will 126 // trigger a REAPPLY. If the REAPPLY comes because of a change in font, then the fontSizeCache 127 // needs to be invalidated (cleared) so that new values will be looked up for all transition states. 128 // 129 if (node.styleHelper.cacheContainer != null && node.styleHelper.isUserSetFont(node)) { 130 node.styleHelper.cacheContainer.fontSizeCache.clear(); 131 } 132 node.styleHelper.cacheContainer.forceSlowpath = true; 133 node.styleHelper.triggerStates.addAll(triggerStates[0]); 134 node.styleHelper.firstStyleableAncestor = findFirstStyleableAncestor(node); 135 updateParentTriggerStates(node, depth, triggerStates); 136 return node.styleHelper; 137 138 } 139 140 if (styleMap == null || styleMap.isEmpty()) { 141 142 boolean mightInherit = false; 143 144 final List<CssMetaData<? extends Styleable, ?>> props = node.getCssMetaData(); 145 146 final int pMax = props != null ? props.size() : 0; 147 for (int p=0; p<pMax; p++) { 148 149 final CssMetaData<? extends Styleable, ?> prop = props.get(p); 150 if (prop.isInherits()) { 151 mightInherit = true; 152 break; 153 } 154 } 155 156 if (mightInherit == false) { 157 158 // If this node had a style helper, then reset properties to their initial value 159 // since the node won't have a style helper after this call 160 if (node.styleHelper != null) { 161 node.styleHelper.resetToInitialValues(node, null); 162 } 163 164 // 165 // This node didn't have a StyleHelper before and it doesn't need one now since there are 166 // no styles in the StyleMap and no inherited styles. 167 return null; 168 } 169 170 } 171 172 final CssStyleHelper helper = new CssStyleHelper(); 173 helper.triggerStates.addAll(triggerStates[0]); 174 175 updateParentTriggerStates(node, depth, triggerStates); 176 177 helper.cacheContainer = new CacheContainer(node, styleMap, depth); 178 179 helper.firstStyleableAncestor = findFirstStyleableAncestor(node); 180 181 // If this node had a style helper, then reset properties to their initial value 182 // since the style map might now be different 183 if (node.styleHelper != null) { 184 StyleMap oldMap = node.styleHelper.getStyleMap(node); 185 StyleMap newMap = helper.getStyleMap(node); 186 187 if (oldMap != newMap) { 188 node.styleHelper.resetToInitialValues(node, null); 189 } else { 190 node.styleHelper.resetToInitialValues(node, newMap); 191 } 192 } 193 return helper; 194 } 195 196 private static void updateParentTriggerStates(Styleable styleable, int depth, PseudoClassState[] triggerStates) { 197 // make sure parent's transition states include the pseudo-classes 198 // found when matching selectors 199 Styleable parent = styleable.getStyleableParent(); 200 for(int n=1; n<depth; n++) { 201 202 // TODO: this means that a style like .menu-item:hover won't work. Need to separate CssStyleHelper tree from scene-graph tree 203 if (parent instanceof Node == false) { 204 parent=parent.getStyleableParent(); 205 continue; 206 } 207 Node parentNode = (Node)parent; 208 209 final PseudoClassState triggerState = triggerStates[n]; 210 211 // if there is nothing in triggerState, then continue since there 212 // isn't any pseudo-class state that might trigger a state change 213 if (triggerState != null && triggerState.size() > 0) { 214 215 // Create a StyleHelper for the parent, if necessary. 216 // TODO : check why calling createStyleHelper(parentNode) does not work here? 217 if (parentNode.styleHelper == null) { 218 parentNode.styleHelper = new CssStyleHelper(); 219 parentNode.styleHelper.firstStyleableAncestor = findFirstStyleableAncestor(parentNode) ; 220 } 221 parentNode.styleHelper.triggerStates.addAll(triggerState); 222 223 } 224 225 parent=parent.getStyleableParent(); 226 } 227 228 } 229 // 230 // return true if the fontStyleableProperty's origin is USER 231 // 232 private boolean isUserSetFont(Styleable node) { 233 234 if (node == null) return false; // should never happen, but just to be safe... 235 236 CssMetaData<Styleable, Font> fontCssMetaData = cacheContainer != null ? cacheContainer.fontProp : null; 237 if (fontCssMetaData != null) { 238 StyleableProperty<Font> fontStyleableProperty = fontCssMetaData != null ? fontCssMetaData.getStyleableProperty(node) : null; 239 if (fontStyleableProperty != null && fontStyleableProperty.getStyleOrigin() == StyleOrigin.USER) return true; 240 } 241 242 Styleable styleableParent = firstStyleableAncestor; 243 CssStyleHelper parentStyleHelper = getStyleHelper(firstStyleableAncestor); 244 245 if (parentStyleHelper != null) { 246 return parentStyleHelper.isUserSetFont(styleableParent); 247 } else { 248 return false; 249 } 250 } 251 252 private static CssStyleHelper getStyleHelper(Node n) { 253 return (n != null)? n.styleHelper : null; 254 } 255 256 private static Node findFirstStyleableAncestor(Styleable st) { 257 Node ancestor = null; 258 Styleable parent = st.getStyleableParent(); 259 while (parent != null) { 260 if (parent instanceof Node) { 261 if (((Node) parent).styleHelper != null) { 262 ancestor = (Node) parent; 263 break; 264 } 265 } 266 parent = parent.getStyleableParent(); 267 } 268 269 return ancestor; 270 } 271 272 // 273 // return the value of the property 274 // 275 private static boolean isTrue(WritableValue<Boolean> booleanProperty) { 276 return booleanProperty != null && booleanProperty.getValue(); 277 } 278 279 // 280 // set the value of the property to true 281 // 282 private static void setTrue(WritableValue<Boolean> booleanProperty) { 283 if (booleanProperty != null) booleanProperty.setValue(true); 284 } 285 286 // 287 // return true if the Node's current styleHelper can be reused. 288 // 289 private static boolean canReuseStyleHelper(final Node node, final StyleMap styleMap) { 290 291 // Obviously, we cannot reuse the node's style helper if it doesn't have one. 292 if (node == null || node.styleHelper == null) { 293 return false; 294 } 295 296 // If we have a styleHelper but the new styleMap is null, then we don't need a styleHelper at all 297 if (styleMap == null) { 298 return false; 299 } 300 301 StyleMap currentMap = node.styleHelper.getStyleMap(node); 302 303 // We cannot reuse the style helper if the styleMap is not the same instance as the current one 304 // Note: check instance equality! 305 if (currentMap != styleMap) { 306 return false; 307 } 308 309 // If the style maps are the same instance, we can re-use the current styleHelper if the cacheContainer is null. 310 // Under this condition, there are no styles for this node _and_ no styles inherit. 311 if (node.styleHelper.cacheContainer == null) { 312 return true; 313 } 314 315 // 316 // The current map might be the same, but one of the node's parent's maps might have changed which 317 // might cause some calculated values to change. To see if we can re-use the style-helper, we need to 318 // check if the StyleMap id's have changed, which we can do by inspecting the cacheContainer's styleCacheKey 319 // since it is made up of the current set of StyleMap ids. 320 // 321 322 Styleable parent = node.getStyleableParent(); 323 324 // if the node's parent is null and the style maps are the same, then we can certainly reuse the style-helper 325 if (parent == null) { 326 return true; 327 } 328 329 CssStyleHelper parentHelper = getStyleHelper(node.styleHelper.firstStyleableAncestor); 330 331 if (parentHelper != null && parentHelper.cacheContainer != null) { 332 333 int[] parentIds = parentHelper.cacheContainer.styleCacheKey.getStyleMapIds(); 334 int[] nodeIds = node.styleHelper.cacheContainer.styleCacheKey.getStyleMapIds(); 335 336 if (parentIds.length == nodeIds.length - 1) { 337 338 boolean isSame = true; 339 340 // check that all of the style map ids are the same. 341 for (int i = 0; i < parentIds.length; i++) { 342 if (nodeIds[i + 1] != parentIds[i]) { 343 isSame = false; 344 break; 345 } 346 } 347 348 return isSame; 349 350 } 351 } 352 353 return false; 354 } 355 356 /* This is the first Styleable parent (of Node this StyleHelper belongs to) 357 * having a valid StyleHelper */ 358 private Node firstStyleableAncestor; 359 360 private CacheContainer cacheContainer; 361 362 private final static class CacheContainer { 363 364 // Set internal internalState structures 365 private CacheContainer( 366 Node node, 367 final StyleMap styleMap, 368 int depth) { 369 370 int ctr = 0; 371 int[] smapIds = new int[depth]; 372 smapIds[ctr++] = this.smapId = styleMap.getId(); 373 374 // 375 // Create a set of StyleMap id's from the parent's smapIds. 376 // The resulting smapIds array may have less than depth elements. 377 // If a parent doesn't have a styleHelper or the styleHelper's 378 // internal state is null, then that parent doesn't contribute 379 // to the selection of a style. Any Node that has the same 380 // set of smapId's can potentially share previously calculated 381 // values. 382 // 383 Styleable parent = node.getStyleableParent(); 384 for(int d=1; d<depth; d++) { 385 386 // TODO: won't work for something like .menu-item:hover. Need to separate CssStyleHelper tree from scene-graph tree 387 if ( parent instanceof Node) { 388 Node parentNode = (Node)parent; 389 final CssStyleHelper helper = parentNode.styleHelper; 390 if (helper != null && helper.cacheContainer != null) { 391 smapIds[ctr++] = helper.cacheContainer.smapId; 392 } 393 } 394 parent = parent.getStyleableParent(); 395 396 } 397 398 this.styleCacheKey = new StyleCache.Key(smapIds, ctr); 399 400 CssMetaData<Styleable,Font> styleableFontProperty = null; 401 402 final List<CssMetaData<? extends Styleable, ?>> props = node.getCssMetaData(); 403 final int pMax = props != null ? props.size() : 0; 404 for (int p=0; p<pMax; p++) { 405 final CssMetaData<? extends Styleable, ?> prop = props.get(p); 406 407 if ("-fx-font".equals(prop.getProperty())) { 408 // unchecked! 409 styleableFontProperty = (CssMetaData<Styleable, Font>) prop; 410 break; 411 } 412 } 413 414 this.fontProp = styleableFontProperty; 415 this.fontSizeCache = new HashMap<>(); 416 417 this.cssSetProperties = new HashMap<>(); 418 419 } 420 421 private StyleMap getStyleMap(Styleable styleable) { 422 if (styleable != null) { 423 SubScene subScene = (styleable instanceof Node) ? ((Node) styleable).getSubScene() : null; 424 return StyleManager.getInstance().getStyleMap(styleable, subScene, smapId); 425 } else { 426 return StyleMap.EMPTY_MAP; 427 } 428 429 } 430 431 // This is the key we use to find the shared cache 432 private final StyleCache.Key styleCacheKey; 433 434 // If the node has a fontProperty, we hang onto the CssMetaData for it 435 // so we can get at it later. 436 // TBD - why not the fontProperty itself? 437 private final CssMetaData<Styleable,Font> fontProp; 438 439 // The id of StyleMap that contains the styles that apply to this node 440 private final int smapId; 441 442 // All nodes with the same set of styles share the same cache of 443 // calculated values. But one node might have a different font-size 444 // than another so the values are stored in cache by font-size. 445 // This map associates a style cache entry with the font to use when 446 // getting a value from or putting a value into cache. 447 private final Map<StyleCacheEntry.Key, CalculatedValue> fontSizeCache; 448 449 // Any properties that have been set by this style helper are tracked 450 // here so the property can be reset without expanding properties that 451 // were not set by css. 452 private final Map<CssMetaData, CalculatedValue> cssSetProperties; 453 454 private boolean forceSlowpath = false; 455 } 456 457 private void resetToInitialValues(final Styleable styleable, StyleMap smap) { 458 459 // This method is invoked on a styleHelper before it is being reset 460 // to null or a new StyleHelper. 461 // 462 // if smap is null/empty -- it means node will not have any new styles 463 // in immediate future. 464 // 465 // Action taken in this method : 466 // reset all styleableProperties in cssSetProperties to initial values 467 468 // if smap is a valid StyleMap -- it means node will have styles from 469 // new stylemap in immediate future 470 // 471 // Action taken in this method : 472 // reset styleableProperties in cssSetProperties in case they are not 473 // present in smap 474 475 if (cacheContainer == null || 476 cacheContainer.cssSetProperties == null || 477 cacheContainer.cssSetProperties.isEmpty()) return; 478 479 // RT-31714 - make a copy of the entry set and clear the cssSetProperties immediately. 480 Set<Entry<CssMetaData, CalculatedValue>> entrySet = 481 new HashSet<>(cacheContainer.cssSetProperties.entrySet()); 482 cacheContainer.cssSetProperties.clear(); 483 484 if (smap == null || smap.isEmpty() ) { 485 for (Entry<CssMetaData, CalculatedValue> resetValues : entrySet) { 486 final CssMetaData metaData = resetValues.getKey(); 487 setStyleableProperty(metaData.getStyleableProperty(styleable), resetValues.getValue()); 488 } 489 } else { 490 Map<String, List<CascadingStyle>> newStyles = smap.getCascadingStyles(); 491 492 for (Entry<CssMetaData, CalculatedValue> resetValues : entrySet) { 493 final CssMetaData metaData = resetValues.getKey(); 494 495 if (newStyles.containsKey(metaData.getProperty()) == false) { 496 // earlier set property is not available in new Stylemap - reset it to initial value 497 setStyleableProperty(metaData.getStyleableProperty(styleable), resetValues.getValue()); 498 } 499 } 500 } 501 } 502 503 private void setStyleableProperty(StyleableProperty property, CalculatedValue value) { 504 final StyleOrigin styleOrigin = property.getStyleOrigin(); 505 if (styleOrigin != null && styleOrigin != StyleOrigin.USER) { 506 property.applyStyle(value.getOrigin(), value.getValue()); 507 } 508 } 509 510 private StyleMap getStyleMap(Styleable styleable) { 511 if (cacheContainer == null || styleable == null) return null; 512 return cacheContainer.getStyleMap(styleable); 513 } 514 515 /** 516 * A Set of all the pseudo-class states which, if they change, need to 517 * cause the Node to be set to UPDATE its CSS styles on the next pulse. 518 * For example, your stylesheet might have: 519 * <pre><code> 520 * .button { ... } 521 * .button:hover { ... } 522 * .button *.label { text-fill: black } 523 * .button:hover *.label { text-fill: blue } 524 * </code></pre> 525 * In this case, the first 2 rules apply to the Button itself, but the 526 * second two rules apply to the label within a Button. When the hover 527 * changes on the Button, however, we must mark the Button as needing 528 * an UPDATE. StyleHelper though only contains styles for the first two 529 * rules for Button. The pseudoclassStateMask would in this case have 530 * only a single bit set for "hover". In this way the StyleHelper associated 531 * with the Button would know whether a change to "hover" requires the 532 * button and all children to be update. Other pseudo-class state changes 533 * that are not in this hash set are ignored. 534 * * 535 * Called "triggerStates" since they would trigger a CSS update. 536 */ 537 private PseudoClassState triggerStates = new PseudoClassState(); 538 539 boolean pseudoClassStateChanged(PseudoClass pseudoClass) { 540 return triggerStates.contains(pseudoClass); 541 } 542 543 /** 544 * Dynamic pseudo-class state of the node and its parents. 545 * Only valid during a pulse. 546 * 547 * The StyleCacheEntry to choose depends on the Node's pseudo-class state 548 * and the pseudo-class state of its parents. Without the parent 549 * pseudo-class state, the fact that the the node in this pseudo-class state 550 * matched foo:blah bar { } is lost. 551 */ 552 // TODO: this should work on Styleable, not Node 553 private Set<PseudoClass>[] getTransitionStates(final Node node) { 554 555 // if cacheContainer is null, then CSS just doesn't apply to this node 556 if (cacheContainer == null) return null; 557 558 int depth = 0; 559 Node parent = node; 560 while (parent != null) { 561 depth += 1; 562 parent = parent.getParent(); 563 } 564 565 // 566 // StyleHelper#triggerStates is the set of pseudo-classes that appear 567 // in the style maps of this StyleHelper. Calculated values are 568 // cached by pseudo-class state, but only the pseudo-class states 569 // that mater are used in the search. So we take the transition states 570 // and intersect them with triggerStates to remove the 571 // transition states that don't matter when it comes to matching states 572 // on a selector. For example if the style map contains only 573 // .foo:hover { -fx-fill: red; } then only the hover state matters 574 // but the transtion state could be [hover, focused] 575 // 576 final Set<PseudoClass>[] retainedStates = new PseudoClassState[depth]; 577 578 // 579 // Note Well: The array runs from leaf to root. That is, 580 // retainedStates[0] is the pseudo-class state for node and 581 // retainedStates[1..(states.length-1)] are the retainedStates for the 582 // node's parents. 583 // 584 585 int count = 0; 586 parent = node; 587 while (parent != null) { // This loop traverses through all ancestors till root 588 final CssStyleHelper helper = (parent instanceof Node) ? parent.styleHelper : null; 589 if (helper != null) { 590 final Set<PseudoClass> pseudoClassState = parent.pseudoClassStates; 591 retainedStates[count] = new PseudoClassState(); 592 retainedStates[count].addAll(pseudoClassState); 593 // retainAll method takes the intersection of pseudoClassState and helper.triggerStates 594 retainedStates[count].retainAll(helper.triggerStates); 595 count += 1; 596 } 597 parent = parent.getParent(); 598 } 599 600 final Set<PseudoClass>[] transitionStates = new PseudoClassState[count]; 601 System.arraycopy(retainedStates, 0, transitionStates, 0, count); 602 603 return transitionStates; 604 605 } 606 607 /** 608 * Called by the Node whenever it has transitioned from one set of 609 * pseudo-class states to another. This function will then lookup the 610 * new values for each of the styleable variables on the Node, and 611 * then either set the value directly or start an animation based on 612 * how things are specified in the CSS file. Currently animation support 613 * is disabled until the new parser comes online with support for 614 * animations and that support is detectable via the API. 615 */ 616 void transitionToState(final Node node) { 617 618 if (cacheContainer == null) { 619 return; 620 } 621 622 // 623 // If styleMap is null, then StyleManager has blown it away and we need to reapply CSS. 624 // 625 final StyleMap styleMap = getStyleMap(node); 626 if (styleMap == null) { 627 cacheContainer = null; 628 node.reapplyCSS(); 629 return; 630 } 631 632 // if the style-map is empty, then we are only looking for inherited styles. 633 final boolean inheritOnly = styleMap.isEmpty(); 634 635 // 636 // Styles that need lookup can be cached provided none of the styles 637 // are from Node.style. 638 // 639 final StyleCache sharedCache = StyleManager.getInstance().getSharedCache(node, node.getSubScene(), cacheContainer.styleCacheKey); 640 641 if (sharedCache == null) { 642 // Shared cache was blown away by StyleManager. 643 // Therefore, this CssStyleHelper is no good. 644 cacheContainer = null; 645 node.reapplyCSS(); 646 return; 647 648 } 649 650 final Set<PseudoClass>[] transitionStates = getTransitionStates(node); 651 652 final StyleCacheEntry.Key fontCacheKey = new StyleCacheEntry.Key(transitionStates, Font.getDefault()); 653 CalculatedValue cachedFont = cacheContainer.fontSizeCache.get(fontCacheKey); 654 655 if (cachedFont == null) { 656 657 cachedFont = lookupFont(node, "-fx-font", styleMap, cachedFont); 658 659 if (cachedFont == SKIP) cachedFont = getCachedFont(node.getStyleableParent()); 660 if (cachedFont == null) cachedFont = new CalculatedValue(Font.getDefault(), null, false); 661 662 cacheContainer.fontSizeCache.put(fontCacheKey,cachedFont); 663 664 } 665 666 final Font fontForRelativeSizes = (Font)cachedFont.getValue(); 667 668 final StyleCacheEntry.Key cacheEntryKey = new StyleCacheEntry.Key(transitionStates, fontForRelativeSizes); 669 StyleCacheEntry cacheEntry = sharedCache.getStyleCacheEntry(cacheEntryKey); 670 671 // if the cacheEntry already exists, take the fastpath 672 final boolean fastpath = cacheEntry != null; 673 674 if (cacheEntry == null) { 675 cacheEntry = new StyleCacheEntry(); 676 sharedCache.addStyleCacheEntry(cacheEntryKey, cacheEntry); 677 } 678 679 final List<CssMetaData<? extends Styleable, ?>> styleables = node.getCssMetaData(); 680 681 // Used in the for loop below, and a convenient place to stop when debugging. 682 final int max = styleables.size(); 683 684 final boolean isForceSlowpath = cacheContainer.forceSlowpath; 685 cacheContainer.forceSlowpath = false; 686 687 // For each property that is settable, we need to do a lookup and 688 // transition to that value. 689 for(int n=0; n<max; n++) { 690 691 @SuppressWarnings("unchecked") // this is a widening conversion 692 final CssMetaData<Styleable,Object> cssMetaData = 693 (CssMetaData<Styleable,Object>)styleables.get(n); 694 695 // Don't bother looking up styles that don't inherit. 696 if (inheritOnly && cssMetaData.isInherits() == false) { 697 continue; 698 } 699 700 // Skip the lookup if we know there isn't a chance for this property 701 // to be set (usually due to a "bind"). 702 if (!cssMetaData.isSettable(node)) continue; 703 704 final String property = cssMetaData.getProperty(); 705 706 CalculatedValue calculatedValue = cacheEntry.get(property); 707 708 // If there is no calculatedValue and we're on the fast path, 709 // take the slow path if cssFlags is REAPPLY (RT-31691) 710 final boolean forceSlowpath = 711 fastpath && calculatedValue == null && isForceSlowpath; 712 713 final boolean addToCache = 714 (!fastpath && calculatedValue == null) || forceSlowpath; 715 716 if (fastpath && !forceSlowpath) { 717 718 // If the cache contains SKIP, then there was an 719 // exception thrown from applyStyle 720 if (calculatedValue == SKIP) { 721 continue; 722 } 723 724 } else if (calculatedValue == null) { 725 726 // slowpath! 727 calculatedValue = lookup(node, cssMetaData, styleMap, transitionStates[0], 728 node, cachedFont); 729 730 // lookup is not supposed to return null. 731 if (calculatedValue == null) { 732 assert false : "lookup returned null for " + property; 733 continue; 734 } 735 736 } 737 738 // StyleableProperty#applyStyle might throw an exception and it is called 739 // from two places in this try block. 740 try { 741 742 // 743 // RT-19089 744 // If the current value of the property was set by CSS 745 // and there is no style for the property, then reset this 746 // property to its initial value. If it was not set by CSS 747 // then leave the property alone. 748 // 749 if (calculatedValue == null || calculatedValue == SKIP) { 750 751 // cssSetProperties keeps track of the StyleableProperty's that were set by CSS in the previous state. 752 // If this property is not in cssSetProperties map, then the property was not set in the previous state. 753 // This accomplishes two things. First, it lets us know if the property was set in the previous state 754 // so it can be reset in this state if there is no value for it. Second, it calling 755 // CssMetaData#getStyleableProperty which is rather expensive as it may cause expansion of lazy 756 // properties. 757 CalculatedValue initialValue = cacheContainer.cssSetProperties.get(cssMetaData); 758 759 // if the current value was set by CSS and there 760 // is no calculated value for the property, then 761 // there was no style for the property in the current 762 // state, so reset the property to its initial value. 763 if (initialValue != null) { 764 765 StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(node); 766 if (styleableProperty.getStyleOrigin() != StyleOrigin.USER) { 767 styleableProperty.applyStyle(initialValue.getOrigin(), initialValue.getValue()); 768 } 769 } 770 771 continue; 772 773 } 774 775 if (addToCache) { 776 777 // If we're not on the fastpath, then add the calculated 778 // value to cache. 779 cacheEntry.put(property, calculatedValue); 780 } 781 782 StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(node); 783 784 // need to know who set the current value - CSS, the user, or init 785 final StyleOrigin originOfCurrentValue = styleableProperty.getStyleOrigin(); 786 787 788 // RT-10522: 789 // If the user set the property and there is a style and 790 // the style came from the user agent stylesheet, then 791 // skip the value. A style from a user agent stylesheet should 792 // not override the user set style. 793 // 794 final StyleOrigin originOfCalculatedValue = calculatedValue.getOrigin(); 795 796 // A calculated value should never have a null style origin since that would 797 // imply the style didn't come from a stylesheet or in-line style. 798 if (originOfCalculatedValue == null) { 799 assert false : styleableProperty.toString(); 800 continue; 801 } 802 803 if (originOfCurrentValue == StyleOrigin.USER) { 804 if (originOfCalculatedValue == StyleOrigin.USER_AGENT) { 805 continue; 806 } 807 } 808 809 final Object value = calculatedValue.getValue(); 810 final Object currentValue = styleableProperty.getValue(); 811 812 // RT-21185: Only apply the style if something has changed. 813 if ((originOfCurrentValue != originOfCalculatedValue) 814 || (currentValue != null 815 ? currentValue.equals(value) == false 816 : value != null)) { 817 818 if (LOGGER.isLoggable(Level.FINER)) { 819 LOGGER.finer(property + ", call applyStyle: " + styleableProperty + ", value =" + 820 String.valueOf(value) + ", originOfCalculatedValue=" + originOfCalculatedValue); 821 } 822 823 styleableProperty.applyStyle(originOfCalculatedValue, value); 824 825 if (cacheContainer.cssSetProperties.containsKey(cssMetaData) == false) { 826 // track this property 827 CalculatedValue initialValue = new CalculatedValue(currentValue, originOfCurrentValue, false); 828 cacheContainer.cssSetProperties.put(cssMetaData, initialValue); 829 } 830 831 } 832 833 } catch (Exception e) { 834 835 StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(node); 836 837 final String msg = String.format("Failed to set css [%s] on [%s] due to '%s'\n", 838 cssMetaData.getProperty(), styleableProperty, e.getMessage()); 839 840 List<CssParser.ParseError> errors = null; 841 if ((errors = StyleManager.getErrors()) != null) { 842 final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, node, msg); 843 errors.add(error); 844 } 845 846 PlatformLogger logger = Logging.getCSSLogger(); 847 if (logger.isLoggable(Level.WARNING)) { 848 logger.warning(msg); 849 } 850 851 // RT-27155: if setting value raises exception, reset value 852 // the value to initial and thereafter skip setting the property 853 cacheEntry.put(property, SKIP); 854 855 CalculatedValue cachedValue = null; 856 if (cacheContainer != null && cacheContainer.cssSetProperties != null) { 857 cachedValue = cacheContainer.cssSetProperties.get(cssMetaData); 858 } 859 Object value = (cachedValue != null) ? cachedValue.getValue() : cssMetaData.getInitialValue(node); 860 StyleOrigin origin = (cachedValue != null) ? cachedValue.getOrigin() : null; 861 try { 862 styleableProperty.applyStyle(origin, value); 863 } catch (Exception ebad) { 864 // This would be bad. 865 if (logger.isLoggable(Level.SEVERE)) { 866 logger.severe(String.format("Could not reset [%s] on [%s] due to %s\n" , 867 cssMetaData.getProperty(), styleableProperty, e.getMessage())); 868 } 869 } 870 871 } 872 873 } 874 } 875 876 /** 877 * Gets the CSS CascadingStyle for the property of this node in these pseudo-class 878 * states. A null style may be returned if there is no style information 879 * for this combination of input parameters. 880 * 881 * 882 * @param styleable 883 * @param property 884 * @param styleMap 885 * @param states @return 886 * */ 887 private CascadingStyle getStyle(final Styleable styleable, final String property, final StyleMap styleMap, final Set<PseudoClass> states){ 888 889 if (styleMap == null || styleMap.isEmpty()) return null; 890 891 final Map<String, List<CascadingStyle>> cascadingStyleMap = styleMap.getCascadingStyles(); 892 if (cascadingStyleMap == null || cascadingStyleMap.isEmpty()) return null; 893 894 // Get all of the Styles which may apply to this particular property 895 List<CascadingStyle> styles = cascadingStyleMap.get(property); 896 897 // If there are no styles for this property then we can just bail 898 if ((styles == null) || styles.isEmpty()) return null; 899 900 // Go looking for the style. We do this by visiting each CascadingStyle in 901 // order finding the first that matches the current node & set of 902 // pseudo-class states. We use an iteration style that avoids creating 903 // garbage iterators (and wish javac did it for us...) 904 CascadingStyle style = null; 905 final int max = (styles == null) ? 0 : styles.size(); 906 for (int i=0; i<max; i++) { 907 final CascadingStyle s = styles.get(i); 908 final Selector sel = s == null ? null : s.getSelector(); 909 if (sel == null) continue; // bail if the selector is null. 910 //System.out.println(node.toString() + "\n\tstates=" + PseudoClassSet.getPseudoClasses(states) + "\n\tstateMatches? " + sel.stateMatches(node, states) + "\n\tsel=" + sel.toString()); 911 if (sel.stateMatches(styleable, states)) { 912 style = s; 913 break; 914 } 915 } 916 917 return style; 918 } 919 920 /** 921 * The main workhorse of this class, the lookup method walks up the CSS 922 * style tree looking for the style information for the Node, the 923 * property associated with the given styleable, in these states for this font. 924 * 925 * 926 * 927 * 928 * @param styleable 929 * @param states 930 * @param originatingStyleable 931 * @return 932 */ 933 private CalculatedValue lookup(final Styleable styleable, 934 final CssMetaData cssMetaData, 935 final StyleMap styleMap, 936 final Set<PseudoClass> states, 937 final Styleable originatingStyleable, 938 final CalculatedValue cachedFont) { 939 940 if (cssMetaData.getConverter() == FontConverter.getInstance()) { 941 return lookupFont(styleable, cssMetaData.getProperty(), styleMap, cachedFont); 942 } 943 944 final String property = cssMetaData.getProperty(); 945 946 // Get the CascadingStyle which may apply to this particular property 947 CascadingStyle style = getStyle(styleable, property, styleMap, states); 948 949 // If no style was found and there are no sub styleables, then there 950 // are no matching styles for this property. We will then either SKIP 951 // or we will INHERIT. We will inspect the default value for the styleable, 952 // and if it is INHERIT then we will inherit otherwise we just skip it. 953 final List<CssMetaData<? extends Styleable, ?>> subProperties = cssMetaData.getSubProperties(); 954 final int numSubProperties = (subProperties != null) ? subProperties.size() : 0; 955 if (style == null) { 956 957 if (numSubProperties == 0) { 958 959 return handleNoStyleFound(styleable, cssMetaData, 960 styleMap, states, originatingStyleable, cachedFont); 961 962 } else { 963 964 // If style is null then it means we didn't successfully find the 965 // property we were looking for. However, there might be sub styleables, 966 // in which case we should perform a lookup for them. For example, 967 // there might not be a style for "font", but there might be one 968 // for "font-size" or "font-weight". So if the style is null, then 969 // we need to check with the sub-styleables. 970 971 // Build up a list of all SubProperties which have a constituent part. 972 // I default the array to be the size of the number of total 973 // sub styleables to avoid having the array grow. 974 Map<CssMetaData,Object> subs = null; 975 StyleOrigin origin = null; 976 977 boolean isRelative = false; 978 979 for (int i=0; i<numSubProperties; i++) { 980 CssMetaData subkey = subProperties.get(i); 981 CalculatedValue constituent = 982 lookup(styleable, subkey, styleMap, states, 983 originatingStyleable, cachedFont); 984 if (constituent != SKIP) { 985 if (subs == null) { 986 subs = new HashMap<>(); 987 } 988 subs.put(subkey, constituent.getValue()); 989 990 // origin of this style is the most specific 991 if ((origin != null && constituent.getOrigin() != null) 992 ? origin.compareTo(constituent.getOrigin()) < 0 993 : constituent.getOrigin() != null) { 994 origin = constituent.getOrigin(); 995 } 996 997 // if the constiuent uses relative sizes, then 998 // isRelative is true; 999 isRelative = isRelative || constituent.isRelative(); 1000 1001 } 1002 } 1003 1004 // If there are no subkeys which apply... 1005 if (subs == null || subs.isEmpty()) { 1006 return handleNoStyleFound(styleable, cssMetaData, 1007 styleMap, states, originatingStyleable, cachedFont); 1008 } 1009 1010 try { 1011 final StyleConverter keyType = cssMetaData.getConverter(); 1012 Object ret = keyType.convert(subs); 1013 return new CalculatedValue(ret, origin, isRelative); 1014 } catch (ClassCastException cce) { 1015 final String msg = formatExceptionMessage(styleable, cssMetaData, null, cce); 1016 List<CssParser.ParseError> errors = null; 1017 if ((errors = StyleManager.getErrors()) != null) { 1018 final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, styleable, msg); 1019 errors.add(error); 1020 } 1021 if (LOGGER.isLoggable(Level.WARNING)) { 1022 LOGGER.warning(msg); 1023 LOGGER.fine("caught: ", cce); 1024 LOGGER.fine("styleable = " + cssMetaData); 1025 LOGGER.fine("node = " + styleable.toString()); 1026 } 1027 return SKIP; 1028 } 1029 } 1030 1031 } else { // style != null 1032 1033 // RT-10522: 1034 // If the user set the property and there is a style and 1035 // the style came from the user agent stylesheet, then 1036 // skip the value. A style from a user agent stylesheet should 1037 // not override the user set style. 1038 if (style.getOrigin() == StyleOrigin.USER_AGENT) { 1039 1040 StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(originatingStyleable); 1041 // if styleableProperty is null, then we're dealing with a sub-property. 1042 if (styleableProperty != null && styleableProperty.getStyleOrigin() == StyleOrigin.USER) { 1043 return SKIP; 1044 } 1045 } 1046 1047 // If there was a style found, then we want to check whether the 1048 // value was "inherit". If so, then we will simply inherit. 1049 final ParsedValue cssValue = style.getParsedValue(); 1050 if (cssValue != null && "inherit".equals(cssValue.getValue())) { 1051 style = getInheritedStyle(styleable, property); 1052 if (style == null) return SKIP; 1053 } 1054 } 1055 1056 // System.out.println("lookup " + property + 1057 // ", selector = \'" + style.selector.toString() + "\'" + 1058 // ", node = " + node.toString()); 1059 1060 return calculateValue(style, styleable, cssMetaData, styleMap, states, 1061 originatingStyleable, cachedFont); 1062 } 1063 1064 /** 1065 * Called when there is no style found. 1066 */ 1067 private CalculatedValue handleNoStyleFound(final Styleable styleable, 1068 final CssMetaData cssMetaData, 1069 final StyleMap styleMap, Set<PseudoClass> pseudoClassStates, Styleable originatingStyleable, 1070 final CalculatedValue cachedFont) { 1071 1072 if (cssMetaData.isInherits()) { 1073 1074 1075 StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(styleable); 1076 StyleOrigin origin = styleableProperty != null ? styleableProperty.getStyleOrigin() : null; 1077 1078 // RT-16308: if there is no matching style and the user set 1079 // the property, do not look for inherited styles. 1080 if (origin == StyleOrigin.USER) { 1081 1082 return SKIP; 1083 1084 } 1085 1086 CascadingStyle style = getInheritedStyle(styleable, cssMetaData.getProperty()); 1087 if (style == null) return SKIP; 1088 1089 CalculatedValue cv = 1090 calculateValue(style, styleable, cssMetaData, 1091 styleMap, pseudoClassStates, originatingStyleable, 1092 cachedFont); 1093 1094 return cv; 1095 1096 } else { 1097 1098 // Not inherited. There is no style 1099 return SKIP; 1100 1101 } 1102 } 1103 /** 1104 * Called when we must getInheritedStyle a value from a parent node in the scenegraph. 1105 */ 1106 private CascadingStyle getInheritedStyle( 1107 final Styleable styleable, 1108 final String property) { 1109 1110 Styleable parent = ((Node)styleable).styleHelper.firstStyleableAncestor; 1111 CssStyleHelper parentStyleHelper = getStyleHelper((Node) parent); 1112 1113 if (parent != null && parentStyleHelper != null) { 1114 1115 StyleMap parentStyleMap = parentStyleHelper.getStyleMap(parent); 1116 Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates; 1117 CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates); 1118 1119 if (cascadingStyle != null) { 1120 1121 final ParsedValue cssValue = cascadingStyle.getParsedValue(); 1122 1123 if ("inherit".equals(cssValue.getValue())) { 1124 return getInheritedStyle(parent, property); 1125 } 1126 return cascadingStyle; 1127 } 1128 } 1129 1130 return null; 1131 } 1132 1133 1134 // helps with self-documenting the code 1135 private static final Set<PseudoClass> NULL_PSEUDO_CLASS_STATE = null; 1136 1137 /** 1138 * Find the property among the styles that pertain to the Node 1139 */ 1140 private CascadingStyle resolveRef(final Styleable styleable, final String property, final StyleMap styleMap, final Set<PseudoClass> states) { 1141 1142 final CascadingStyle style = getStyle(styleable, property, styleMap, states); 1143 if (style != null) { 1144 return style; 1145 } else { 1146 // if style is null, it may be because there isn't a style for this 1147 // node in this state, or we may need to look up the parent chain 1148 if (states != null && states.size() > 0) { 1149 // if states > 0, then we need to check this node again, 1150 // but without any states. 1151 return resolveRef(styleable,property, styleMap, NULL_PSEUDO_CLASS_STATE); 1152 } else { 1153 // TODO: This block was copied from inherit. Both should use same code somehow. 1154 1155 Styleable styleableParent = ((Node)styleable).styleHelper.firstStyleableAncestor; 1156 CssStyleHelper parentStyleHelper = getStyleHelper((Node) styleableParent); 1157 1158 if (styleableParent == null || parentStyleHelper == null) { 1159 return null; 1160 } 1161 1162 StyleMap parentStyleMap = parentStyleHelper.getStyleMap(styleableParent); 1163 Set<PseudoClass> styleableParentPseudoClassStates = 1164 styleableParent instanceof Node 1165 ? ((Node)styleableParent).pseudoClassStates 1166 : styleable.getPseudoClassStates(); 1167 1168 return parentStyleHelper.resolveRef(styleableParent, property, 1169 parentStyleMap, styleableParentPseudoClassStates); 1170 } 1171 } 1172 } 1173 1174 // to resolve a lookup, we just need to find the parsed value. 1175 private ParsedValue resolveLookups( 1176 final Styleable styleable, 1177 final ParsedValue parsedValue, 1178 final StyleMap styleMap, Set<PseudoClass> states, 1179 final ObjectProperty<StyleOrigin> whence, 1180 Set<ParsedValue> resolves) { 1181 1182 // 1183 // either the value itself is a lookup, or the value contain a lookup 1184 // 1185 if (parsedValue.isLookup()) { 1186 1187 // The value we're looking for should be a Paint, one of the 1188 // containers for linear, radial or ladder, or a derived color. 1189 final Object val = parsedValue.getValue(); 1190 if (val instanceof String) { 1191 1192 final String sval = ((String) val).toLowerCase(Locale.ROOT); 1193 1194 CascadingStyle resolved = 1195 resolveRef(styleable, sval, styleMap, states); 1196 1197 if (resolved != null) { 1198 1199 if (resolves.contains(resolved.getParsedValue())) { 1200 1201 if (LOGGER.isLoggable(Level.WARNING)) { 1202 LOGGER.warning("Loop detected in " + resolved.getRule().toString() + " while resolving '" + sval + "'"); 1203 } 1204 throw new IllegalArgumentException("Loop detected in " + resolved.getRule().toString() + " while resolving '" + sval + "'"); 1205 1206 } else { 1207 resolves.add(parsedValue); 1208 } 1209 1210 // The origin of this parsed value is the greatest of 1211 // any of the resolved reference. If a resolved reference 1212 // comes from an inline style, for example, then the value 1213 // calculated from the resolved lookup should have inline 1214 // as its origin. Otherwise, an inline style could be 1215 // stored in shared cache. 1216 final StyleOrigin wOrigin = whence.get(); 1217 final StyleOrigin rOrigin = resolved.getOrigin(); 1218 if (rOrigin != null && (wOrigin == null || wOrigin.compareTo(rOrigin) < 0)) { 1219 whence.set(rOrigin); 1220 } 1221 1222 // the resolved value may itself need to be resolved. 1223 // For example, if the value "color" resolves to "base", 1224 // then "base" will need to be resolved as well. 1225 ParsedValue pv = resolveLookups(styleable, resolved.getParsedValue(), styleMap, states, whence, resolves); 1226 1227 if (resolves != null) { 1228 resolves.remove(parsedValue); 1229 } 1230 1231 return pv; 1232 1233 } 1234 } 1235 } 1236 1237 // If the value doesn't contain any values that need lookup, then bail 1238 if (!parsedValue.isContainsLookups()) { 1239 return parsedValue; 1240 } 1241 1242 final Object val = parsedValue.getValue(); 1243 1244 if (val instanceof ParsedValue[][]) { 1245 1246 // If ParsedValue is a layered sequence of values, resolve the lookups for each. 1247 final ParsedValue[][] layers = (ParsedValue[][])val; 1248 ParsedValue[][] resolved = new ParsedValue[layers.length][0]; 1249 for (int l=0; l<layers.length; l++) { 1250 resolved[l] = new ParsedValue[layers[l].length]; 1251 for (int ll=0; ll<layers[l].length; ll++) { 1252 if (layers[l][ll] == null) continue; 1253 resolved[l][ll] = 1254 resolveLookups(styleable, layers[l][ll], styleMap, states, whence, resolves); 1255 } 1256 } 1257 1258 resolves.clear(); 1259 1260 return new ParsedValueImpl(resolved, parsedValue.getConverter(), false); 1261 1262 } else if (val instanceof ParsedValueImpl[]) { 1263 1264 // If ParsedValue is a sequence of values, resolve the lookups for each. 1265 final ParsedValue[] layer = (ParsedValue[])val; 1266 ParsedValue[] resolved = new ParsedValue[layer.length]; 1267 for (int l=0; l<layer.length; l++) { 1268 if (layer[l] == null) continue; 1269 resolved[l] = 1270 resolveLookups(styleable, layer[l], styleMap, states, whence, resolves); 1271 } 1272 1273 resolves.clear(); 1274 1275 return new ParsedValueImpl(resolved, parsedValue.getConverter(), false); 1276 1277 } 1278 1279 return parsedValue; 1280 1281 } 1282 1283 private String getUnresolvedLookup(final ParsedValue resolved) { 1284 1285 Object value = resolved.getValue(); 1286 1287 if (resolved.isLookup() && value instanceof String) { 1288 return (String)value; 1289 } 1290 1291 if (value instanceof ParsedValue[][]) { 1292 final ParsedValue[][] layers = (ParsedValue[][])value; 1293 for (int l=0; l<layers.length; l++) { 1294 for (int ll=0; ll<layers[l].length; ll++) { 1295 if (layers[l][ll] == null) continue; 1296 String unresolvedLookup = getUnresolvedLookup(layers[l][ll]); 1297 if (unresolvedLookup != null) return unresolvedLookup; 1298 } 1299 } 1300 1301 } else if (value instanceof ParsedValue[]) { 1302 // If ParsedValue is a sequence of values, resolve the lookups for each. 1303 final ParsedValue[] layer = (ParsedValue[])value; 1304 for (int l=0; l<layer.length; l++) { 1305 if (layer[l] == null) continue; 1306 String unresolvedLookup = getUnresolvedLookup(layer[l]); 1307 if (unresolvedLookup != null) return unresolvedLookup; 1308 } 1309 } 1310 1311 return null; 1312 } 1313 1314 private String formatUnresolvedLookupMessage(Styleable styleable, CssMetaData cssMetaData, Style style, ParsedValue resolved, ClassCastException cce) { 1315 1316 // Find value that could not be looked up. If the resolved value does not contain lookups, then the 1317 // ClassCastException is not because of trying to convert a String (which is the missing lookup) 1318 // to some value, but is because the convert method got some wrong value - like a paint when it should be a color. 1319 // See RT-33319 for an example of this. 1320 String missingLookup = resolved != null && resolved.isContainsLookups() ? getUnresolvedLookup(resolved) : null; 1321 1322 StringBuilder sbuf = new StringBuilder(); 1323 if (missingLookup != null) { 1324 sbuf.append("Could not resolve '") 1325 .append(missingLookup) 1326 .append("'") 1327 .append(" while resolving lookups for '") 1328 .append(cssMetaData.getProperty()) 1329 .append("'"); 1330 } else { 1331 sbuf.append("Caught '") 1332 .append(cce) 1333 .append("'") 1334 .append(" while converting value for '") 1335 .append(cssMetaData.getProperty()) 1336 .append("'"); 1337 } 1338 1339 final Rule rule = style != null ? style.getDeclaration().getRule(): null; 1340 final Stylesheet stylesheet = rule != null ? rule.getStylesheet() : null; 1341 final String url = stylesheet != null ? stylesheet.getUrl() : null; 1342 if (url != null) { 1343 sbuf.append(" from rule '") 1344 .append(style.getSelector()) 1345 .append("' in stylesheet ").append(url); 1346 } else if (stylesheet != null && StyleOrigin.INLINE == stylesheet.getOrigin()) { 1347 sbuf.append(" from inline style on " ) 1348 .append(styleable.toString()); 1349 } 1350 1351 return sbuf.toString(); 1352 } 1353 1354 private String formatExceptionMessage(Styleable styleable, CssMetaData cssMetaData, Style style, Exception e) { 1355 1356 StringBuilder sbuf = new StringBuilder(); 1357 sbuf.append("Caught ") 1358 .append(String.valueOf(e)); 1359 1360 if (cssMetaData != null) { 1361 sbuf.append("'") 1362 .append(" while calculating value for '") 1363 .append(cssMetaData.getProperty()) 1364 .append("'"); 1365 } 1366 1367 if (style != null) { 1368 1369 final Rule rule = style.getDeclaration().getRule(); 1370 final Stylesheet stylesheet = rule != null ? rule.getStylesheet() : null; 1371 final String url = stylesheet != null ? stylesheet.getUrl() : null; 1372 1373 if (url != null) { 1374 sbuf.append(" from rule '") 1375 .append(style.getSelector()) 1376 .append("' in stylesheet ").append(url); 1377 } else if (styleable != null && stylesheet != null && StyleOrigin.INLINE == stylesheet.getOrigin()) { 1378 sbuf.append(" from inline style on " ) 1379 .append(styleable.toString()); 1380 } else { 1381 sbuf.append(" from style '") 1382 .append(String.valueOf(style)) 1383 .append("'"); 1384 } 1385 } 1386 1387 return sbuf.toString(); 1388 } 1389 1390 1391 private CalculatedValue calculateValue( 1392 final CascadingStyle style, 1393 final Styleable styleable, 1394 final CssMetaData cssMetaData, 1395 final StyleMap styleMap, final Set<PseudoClass> states, 1396 final Styleable originatingStyleable, 1397 final CalculatedValue fontFromCacheEntry) { 1398 1399 final ParsedValue cssValue = style.getParsedValue(); 1400 if (cssValue != null && !("null".equals(cssValue.getValue()) || "none".equals(cssValue.getValue()))) { 1401 1402 ParsedValue resolved = null; 1403 try { 1404 1405 ObjectProperty<StyleOrigin> whence = new SimpleObjectProperty<>(style.getOrigin()); 1406 resolved = resolveLookups(styleable, cssValue, styleMap, states, whence, new HashSet<>()); 1407 1408 final String property = cssMetaData.getProperty(); 1409 1410 // The computed value 1411 Object val = null; 1412 boolean isFontProperty = 1413 "-fx-font".equals(property) || 1414 "-fx-font-size".equals(property); 1415 1416 boolean isRelative = ParsedValueImpl.containsFontRelativeSize(resolved, isFontProperty); 1417 1418 // 1419 // Avoid using a font calculated from a relative size 1420 // to calculate a font with a relative size. 1421 // For example: 1422 // Assume the default font size is 13 and we have a style with 1423 // -fx-font-size: 1.5em, then the cacheEntry font value will 1424 // have a size of 13*1.5=19.5. 1425 // Now, when converting that same font size again in response 1426 // to looking up a value for -fx-font, we do not want to use 1427 // 19.5 as the font for relative size conversion since this will 1428 // yield a font 19.5*1.5=29.25 when really what we want is 1429 // a font size of 19.5. 1430 // In this situation, then, we use the font from the parent's 1431 // cache entry. 1432 Font fontForFontRelativeSizes = null; 1433 1434 if (isRelative && isFontProperty && 1435 (fontFromCacheEntry == null || fontFromCacheEntry.isRelative())) { 1436 1437 Styleable parent = styleable; 1438 CalculatedValue childsCachedFont = fontFromCacheEntry; 1439 do { 1440 1441 CalculatedValue parentsCachedFont = getCachedFont(parent.getStyleableParent()); 1442 1443 if (parentsCachedFont != null) { 1444 1445 if (parentsCachedFont.isRelative()) { 1446 1447 // 1448 // If the cached fonts are the same, then the cached font came from the same 1449 // style and we need to keep looking. Otherwise, use the font we found. 1450 // 1451 if (childsCachedFont == null || parentsCachedFont.equals(childsCachedFont)) { 1452 childsCachedFont = parentsCachedFont; 1453 } else { 1454 fontForFontRelativeSizes = (Font)parentsCachedFont.getValue(); 1455 } 1456 1457 } else { 1458 // fontValue.isRelative() == false! 1459 fontForFontRelativeSizes = (Font)parentsCachedFont.getValue(); 1460 } 1461 1462 } 1463 1464 } while(fontForFontRelativeSizes == null && 1465 (parent = parent.getStyleableParent()) != null); 1466 } 1467 1468 // did we get a fontValue from the preceding block? 1469 // if not, get it from our cacheEntry or choose the default 1470 if (fontForFontRelativeSizes == null) { 1471 if (fontFromCacheEntry != null && fontFromCacheEntry.isRelative() == false) { 1472 fontForFontRelativeSizes = (Font)fontFromCacheEntry.getValue(); 1473 } else { 1474 fontForFontRelativeSizes = Font.getDefault(); 1475 } 1476 } 1477 1478 final StyleConverter cssMetaDataConverter = cssMetaData.getConverter(); 1479 // RT-37727 - handling of properties that are insets is wonky. If the property is -fx-inset, then 1480 // there isn't an issue because the converter assigns the InsetsConverter to the ParsedValue. 1481 // But -my-insets will parse as an array of numbers and the parser will assign the Size sequence 1482 // converter to it. So, if the CssMetaData says it uses InsetsConverter, use the InsetsConverter 1483 // and not the parser assigned converter. 1484 if (cssMetaDataConverter == StyleConverter.getInsetsConverter()) { 1485 if (resolved.getValue() instanceof ParsedValue) { 1486 // If you give the parser "-my-insets: 5;" you end up with a ParsedValue<ParsedValue<?,Size>, Number> 1487 // and not a ParsedValue<ParsedValue[], Number[]> so here we wrap the value into an array 1488 // to make the InsetsConverter happy. 1489 resolved = new ParsedValueImpl(new ParsedValue[] {(ParsedValue)resolved.getValue()}, null, false); 1490 } 1491 val = cssMetaDataConverter.convert(resolved, fontForFontRelativeSizes); 1492 } 1493 else if (resolved.getConverter() != null) 1494 val = resolved.convert(fontForFontRelativeSizes); 1495 else 1496 val = cssMetaData.getConverter().convert(resolved, fontForFontRelativeSizes); 1497 1498 final StyleOrigin origin = whence.get(); 1499 return new CalculatedValue(val, origin, isRelative); 1500 1501 } catch (ClassCastException cce) { 1502 final String msg = formatUnresolvedLookupMessage(styleable, cssMetaData, style.getStyle(),resolved, cce); 1503 List<CssParser.ParseError> errors = null; 1504 if ((errors = StyleManager.getErrors()) != null) { 1505 final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, styleable, msg); 1506 errors.add(error); 1507 } 1508 if (LOGGER.isLoggable(Level.WARNING)) { 1509 LOGGER.warning(msg); 1510 LOGGER.fine("node = " + styleable.toString()); 1511 LOGGER.fine("cssMetaData = " + cssMetaData); 1512 LOGGER.fine("styles = " + getMatchingStyles(styleable, cssMetaData)); 1513 } 1514 return SKIP; 1515 } catch (IllegalArgumentException iae) { 1516 final String msg = formatExceptionMessage(styleable, cssMetaData, style.getStyle(), iae); 1517 List<CssParser.ParseError> errors = null; 1518 if ((errors = StyleManager.getErrors()) != null) { 1519 final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, styleable, msg); 1520 errors.add(error); 1521 } 1522 if (LOGGER.isLoggable(Level.WARNING)) { 1523 LOGGER.warning(msg); 1524 LOGGER.fine("caught: ", iae); 1525 LOGGER.fine("styleable = " + cssMetaData); 1526 LOGGER.fine("node = " + styleable.toString()); 1527 } 1528 return SKIP; 1529 } catch (NullPointerException npe) { 1530 final String msg = formatExceptionMessage(styleable, cssMetaData, style.getStyle(), npe); 1531 List<CssParser.ParseError> errors = null; 1532 if ((errors = StyleManager.getErrors()) != null) { 1533 final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, styleable, msg); 1534 errors.add(error); 1535 } 1536 if (LOGGER.isLoggable(Level.WARNING)) { 1537 LOGGER.warning(msg); 1538 LOGGER.fine("caught: ", npe); 1539 LOGGER.fine("styleable = " + cssMetaData); 1540 LOGGER.fine("node = " + styleable.toString()); 1541 } 1542 return SKIP; 1543 } 1544 1545 } 1546 // either cssValue was null or cssValue's value was "null" or "none" 1547 return new CalculatedValue(null, style.getOrigin(), false); 1548 1549 } 1550 1551 private static final CssMetaData dummyFontProperty = 1552 new FontCssMetaData<Node>("-fx-font", Font.getDefault()) { 1553 1554 @Override 1555 public boolean isSettable(Node node) { 1556 return true; 1557 } 1558 1559 @Override 1560 public StyleableProperty<Font> getStyleableProperty(Node node) { 1561 return null; 1562 } 1563 }; 1564 1565 private CalculatedValue getCachedFont(final Styleable styleable) { 1566 1567 if (styleable instanceof Node == false) return null; 1568 1569 CalculatedValue cachedFont = null; 1570 1571 Node parent = (Node)styleable; 1572 1573 final CssStyleHelper parentHelper = parent.styleHelper; 1574 1575 // if there is no parentHelper, 1576 // or there is a parentHelper but no cacheContainer, 1577 // then look to the next parent 1578 if (parentHelper == null || parentHelper.cacheContainer == null) { 1579 1580 cachedFont = getCachedFont(parent.getStyleableParent()); 1581 1582 // there is a parent helper and a cacheContainer, 1583 } else { 1584 1585 CacheContainer parentCacheContainer = parentHelper.cacheContainer; 1586 if ( parentCacheContainer != null 1587 && parentCacheContainer.fontSizeCache != null 1588 && parentCacheContainer.fontSizeCache.isEmpty() == false) { 1589 1590 Set<PseudoClass>[] transitionStates = parentHelper.getTransitionStates(parent); 1591 StyleCacheEntry.Key parentCacheEntryKey = new StyleCacheEntry.Key(transitionStates, Font.getDefault()); 1592 cachedFont = parentCacheContainer.fontSizeCache.get(parentCacheEntryKey); 1593 } 1594 1595 if (cachedFont == null) { 1596 StyleMap smap = parentHelper.getStyleMap(parent); 1597 cachedFont = parentHelper.lookupFont(parent, "-fx-font", smap, null); 1598 } 1599 } 1600 1601 return cachedFont != SKIP ? cachedFont : null; 1602 } 1603 1604 /*package access for testing*/ FontPosture getFontPosture(Font font) { 1605 if (font == null) return FontPosture.REGULAR; 1606 1607 String fontName = font.getName().toLowerCase(Locale.ROOT); 1608 1609 if (fontName.contains("italic")) { 1610 return FontPosture.ITALIC; 1611 } 1612 1613 return FontPosture.REGULAR; 1614 } 1615 1616 /*package access for testing*/ FontWeight getFontWeight(Font font) { 1617 if (font == null) return FontWeight.NORMAL; 1618 1619 String fontName = font.getName().toLowerCase(Locale.ROOT); 1620 1621 if (fontName.contains("bold")) { 1622 if (fontName.contains("extra")) return FontWeight.EXTRA_BOLD; 1623 if (fontName.contains("ultra")) return FontWeight.EXTRA_BOLD; 1624 else if (fontName.contains("semi")) return FontWeight.SEMI_BOLD; 1625 else if (fontName.contains("demi")) return FontWeight.SEMI_BOLD; 1626 else return FontWeight.BOLD; 1627 1628 } else if (fontName.contains("light")) { 1629 if (fontName.contains("extra")) return FontWeight.EXTRA_LIGHT; 1630 if (fontName.contains("ultra")) return FontWeight.EXTRA_LIGHT; 1631 else return FontWeight.LIGHT; 1632 1633 } else if (fontName.contains("black")) { 1634 return FontWeight.BLACK; 1635 1636 } else if (fontName.contains("heavy")) { 1637 return FontWeight.BLACK; 1638 1639 } else if (fontName.contains("medium")) { 1640 return FontWeight.MEDIUM; 1641 } 1642 1643 return FontWeight.NORMAL; 1644 1645 } 1646 1647 /*package access for testing*/ String getFontFamily(Font font) { 1648 if (font == null) return Font.getDefault().getFamily(); 1649 return font.getFamily(); 1650 } 1651 1652 1653 /*package access for testing*/ Font deriveFont( 1654 Font font, 1655 String fontFamily, 1656 FontWeight fontWeight, 1657 FontPosture fontPosture, 1658 double fontSize) { 1659 1660 if (font != null && fontFamily == null) fontFamily = getFontFamily(font); 1661 else if (fontFamily != null) fontFamily = Utils.stripQuotes(fontFamily); 1662 1663 if (font != null && fontWeight == null) fontWeight = getFontWeight(font); 1664 if (font != null && fontPosture == null) fontPosture = getFontPosture(font); 1665 if (font != null && fontSize <= 0) fontSize = font.getSize(); 1666 1667 return Font.font( 1668 fontFamily, 1669 fontWeight, 1670 fontPosture, 1671 fontSize); 1672 } 1673 1674 /** 1675 * Look up a font property. This is handled separately from lookup since 1676 * font is inherited and has sub-properties. One should expect that the 1677 * text font for the following would be 16px Arial. The lookup method would 1678 * give 16px system since it would look <em>only</em> for font-size, 1679 * font-family, etc <em>only</em> if the lookup on font failed. 1680 * <pre> 1681 * Text text = new Text("Hello World"); 1682 * text.setStyle("-fx-font-size: 16px;"); 1683 * Group group = new Group(); 1684 * group.setStyle("-fx-font: 12px Arial;"); 1685 * group.getChildren().add(text); 1686 * </pre> 1687 */ 1688 /*package access for testing*/ CalculatedValue lookupFont( 1689 final Styleable styleable, 1690 final String property, 1691 final StyleMap styleMap, 1692 final CalculatedValue cachedFont) 1693 { 1694 1695 StyleOrigin origin = null; 1696 1697 // How far from this node did we travel to find a font shorthand? 1698 // Don't look past this distance for other font properties. 1699 int distance = 0; 1700 1701 // Did we find a style? 1702 boolean foundStyle = false; 1703 1704 String family = null; 1705 double size = -1; 1706 FontWeight weight = null; 1707 FontPosture posture = null; 1708 1709 CalculatedValue cvFont = cachedFont; 1710 1711 1712 Set<PseudoClass> states = styleable instanceof Node ? ((Node)styleable).pseudoClassStates : styleable.getPseudoClassStates(); 1713 1714 // RT-20145 - if looking for font size and the node has a font, 1715 // use the font property's value if it was set by the user and 1716 // there is not an inline or author style. 1717 1718 if (cacheContainer.fontProp != null) { 1719 StyleableProperty<Font> styleableProp = cacheContainer.fontProp.getStyleableProperty(styleable); 1720 StyleOrigin fpOrigin = styleableProp.getStyleOrigin(); 1721 Font font = styleableProp.getValue(); 1722 if (font == null) font = Font.getDefault(); 1723 if (fpOrigin == StyleOrigin.USER) { 1724 origin = fpOrigin; 1725 family = getFontFamily(font); 1726 size = font.getSize(); 1727 weight = getFontWeight(font); 1728 posture = getFontPosture(font); 1729 cvFont = new CalculatedValue(font, fpOrigin, false); 1730 } 1731 } 1732 1733 CalculatedValue parentCachedFont = getCachedFont(styleable.getStyleableParent()); 1734 if (parentCachedFont == null) parentCachedFont = new CalculatedValue(Font.getDefault(), null, false); 1735 1736 // 1737 // Look up the font- properties 1738 // 1739 CascadingStyle fontShorthand = getStyle(styleable, property, styleMap, states); 1740 1741 // don't look past current node for font shorthand if user set the font 1742 if (fontShorthand == null && origin != StyleOrigin.USER) { 1743 1744 Styleable parent = styleable != null ? styleable.getStyleableParent() : null; 1745 1746 while (parent != null) { // This loop traverses through all ancestors till root 1747 1748 CssStyleHelper parentStyleHelper = parent instanceof Node ? ((Node)parent).styleHelper : null; 1749 if (parentStyleHelper != null) { 1750 1751 distance += 1; 1752 1753 StyleMap parentStyleMap = parentStyleHelper.getStyleMap(parent); 1754 Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates; 1755 CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates); 1756 1757 if (cascadingStyle != null) { 1758 1759 final ParsedValue cssValue = cascadingStyle.getParsedValue(); 1760 1761 if ("inherit".equals(cssValue.getValue()) == false) { 1762 fontShorthand = cascadingStyle; 1763 break; 1764 } 1765 } 1766 1767 } 1768 1769 parent = parent.getStyleableParent(); 1770 1771 } 1772 1773 } 1774 1775 if (fontShorthand != null) { 1776 1777 // 1778 // If we don't have an existing font, or if the origin of the 1779 // existing font is less than that of the shorthand, then 1780 // take the shorthand. If the origins compare equals, then take 1781 // the shorthand since the fontProp value will not have been 1782 // updated yet. 1783 // 1784 if (origin == null || origin.compareTo(fontShorthand.getOrigin()) <= 0) { 1785 1786 final CalculatedValue cv = 1787 calculateValue(fontShorthand, styleable, dummyFontProperty, 1788 styleMap, states, styleable, parentCachedFont); 1789 1790 // cv could be SKIP 1791 if (cv.getValue() instanceof Font) { 1792 origin = cv.getOrigin(); 1793 Font font = (Font)cv.getValue(); 1794 family = getFontFamily(font); 1795 size = font.getSize(); 1796 weight = getFontWeight(font); 1797 posture = getFontPosture(font); 1798 cvFont = cv; 1799 foundStyle = true; 1800 } 1801 1802 } 1803 } 1804 1805 CascadingStyle fontSize = getStyle(styleable, property.concat("-size"), styleMap, states); 1806 if (fontSize != null) { 1807 // if we have a font shorthand and it is more specific than font-size, then don't use the font-size style 1808 if (fontShorthand != null && fontShorthand.compareTo(fontSize) < 0) { 1809 fontSize = null; 1810 } else if (origin == StyleOrigin.USER) { 1811 // If fontSize is an inline or author-stylesheet style, use it. 1812 // Otherwise, fontSize is a user-agent stylesheet style and should not override the USER style. 1813 if (StyleOrigin.USER.compareTo(fontSize.getOrigin()) > 0) { 1814 fontSize = null; 1815 } 1816 } 1817 } else if (origin != StyleOrigin.USER) { 1818 // 1819 // If we don't have a font-size, see if there is an inherited font-size. 1820 // If lookupInheritedFontProperty returns other than null, then we know that font-size is closer (more specific) 1821 // than the font shorthand 1822 // 1823 fontSize = lookupInheritedFontProperty(styleable, property.concat("-size"), styleMap, distance, fontShorthand); 1824 } 1825 1826 if (fontSize != null) { 1827 1828 // The logic above ensures that, if fontSize is not null, then it is either 1829 // 1) a style matching this node and is more specific than the font shorthand or 1830 // 2) an inherited style that is more specific than the font shorthand 1831 // and, therefore, we can use the fontSize style 1832 1833 final CalculatedValue cv = 1834 calculateValue(fontSize, styleable, dummyFontProperty, 1835 styleMap, states, styleable, parentCachedFont); 1836 1837 if (cv.getValue() instanceof Double) { 1838 if (origin == null || origin.compareTo(fontSize.getOrigin()) <= 0) { 1839 1840 origin = cv.getOrigin(); 1841 } 1842 size = (Double) cv.getValue(); 1843 1844 if (cvFont != null) { 1845 boolean isRelative = cvFont.isRelative() || cv.isRelative(); 1846 Font font = deriveFont((Font) cvFont.getValue(), family, weight, posture, size); 1847 cvFont = new CalculatedValue(font, origin, isRelative); 1848 } else { 1849 boolean isRelative = cv.isRelative(); 1850 Font font = deriveFont(Font.getDefault(), family, weight, posture, size); 1851 cvFont = new CalculatedValue(font, origin, isRelative); 1852 } 1853 foundStyle = true; 1854 } 1855 1856 } 1857 1858 // if cachedFont is null, then we're in this method to look up a font for the CacheContainer's fontSizeCache 1859 // and we only care about font-size or the size from font shorthand. 1860 if (cachedFont == null) { 1861 return (cvFont != null) ? cvFont : SKIP; 1862 } 1863 1864 CascadingStyle fontWeight = getStyle(styleable, property.concat("-weight"), styleMap, states); 1865 if (fontWeight != null) { 1866 // if we have a font shorthand and it is more specific than font-weight, then don't use the font-weight style 1867 if (fontShorthand != null && fontShorthand.compareTo(fontWeight) < 0) { 1868 fontWeight = null; 1869 } 1870 1871 } else if (origin != StyleOrigin.USER) { 1872 // 1873 // If we don't have a font-weight, see if there is an inherited font-weight. 1874 // If lookupInheritedFontProperty returns other than null, then we know that font-weight is closer (more specific) 1875 // than the font shorthand 1876 // 1877 fontWeight = lookupInheritedFontProperty(styleable, property.concat("-weight"), styleMap, distance, fontShorthand); 1878 } 1879 1880 if (fontWeight != null) { 1881 1882 // The logic above ensures that, if fontWeight is not null, then it is either 1883 // 1) a style matching this node and is more specific than the font shorthand or 1884 // 2) an inherited style that is more specific than the font shorthand 1885 // and, therefore, we can use the fontWeight style 1886 1887 final CalculatedValue cv = 1888 calculateValue(fontWeight, styleable, dummyFontProperty, 1889 styleMap, states, styleable, null); 1890 1891 if (cv.getValue() instanceof FontWeight) { 1892 if (origin == null || origin.compareTo(fontWeight.getOrigin()) <= 0) { 1893 origin = cv.getOrigin(); 1894 } 1895 weight = (FontWeight)cv.getValue(); 1896 foundStyle = true; 1897 } 1898 } 1899 1900 1901 CascadingStyle fontStyle = getStyle(styleable, property.concat("-style"), styleMap, states); 1902 if (fontStyle != null) { 1903 // if we have a font shorthand and it is more specific than font-style, then don't use the font-style style 1904 if (fontShorthand != null && fontShorthand.compareTo(fontStyle) < 0) { 1905 fontStyle = null; 1906 } 1907 1908 } else if (origin != StyleOrigin.USER) { 1909 // 1910 // If we don't have a font-style, see if there is an inherited font-style. 1911 // If lookupInheritedFontProperty returns other than null, then we know that font-style is closer (more specific) 1912 // than the font shorthand 1913 // 1914 fontStyle = lookupInheritedFontProperty(styleable, property.concat("-style"), styleMap, distance, fontShorthand); 1915 } 1916 1917 if (fontStyle != null) { 1918 1919 // The logic above ensures that, if fontStyle is not null, then it is either 1920 // 1) a style matching this node and is more specific than the font shorthand or 1921 // 2) an inherited style that is more specific than the font shorthand 1922 // and, therefore, we can use the fontStyle style 1923 1924 final CalculatedValue cv = 1925 calculateValue(fontStyle, styleable, dummyFontProperty, 1926 styleMap, states, styleable, null); 1927 1928 if (cv.getValue() instanceof FontPosture) { 1929 if (origin == null || origin.compareTo(fontStyle.getOrigin()) <= 0) { 1930 origin = cv.getOrigin(); 1931 } 1932 posture = (FontPosture)cv.getValue(); 1933 foundStyle = true; 1934 } 1935 1936 } 1937 1938 CascadingStyle fontFamily = getStyle(styleable, property.concat("-family"), styleMap, states); 1939 if (fontFamily != null) { 1940 // if we have a font shorthand and it is more specific than font-family, then don't use the font-family style 1941 if (fontShorthand != null && fontShorthand.compareTo(fontFamily) < 0) { 1942 fontFamily = null; 1943 } 1944 1945 } else if (origin != StyleOrigin.USER) { 1946 // 1947 // If we don't have a font-family, see if there is an inherited font-family. 1948 // If lookupInheritedFontProperty returns other than null, then we know that font-family is closer (more specific) 1949 // than the font shorthand 1950 // 1951 fontFamily = lookupInheritedFontProperty(styleable, property.concat("-family"), styleMap, distance, fontShorthand); 1952 } 1953 1954 if (fontFamily != null) { 1955 1956 // The logic above ensures that, if fontFamily is not null, then it is either 1957 // 1) a style matching this node and is more specific than the font shorthand or 1958 // 2) an inherited style that is more specific than the font shorthand 1959 // and, therefore, we can use the fontFamily style 1960 1961 final CalculatedValue cv = 1962 calculateValue(fontFamily, styleable, dummyFontProperty, 1963 styleMap, states, styleable, null); 1964 1965 if (cv.getValue() instanceof String) { 1966 if (origin == null || origin.compareTo(fontFamily.getOrigin()) <= 0) { 1967 origin = cv.getOrigin(); 1968 } 1969 family = (String)cv.getValue(); 1970 foundStyle = true; 1971 } 1972 1973 } 1974 1975 if (foundStyle) { 1976 1977 Font font = cvFont != null ? (Font)cvFont.getValue() : Font.getDefault(); 1978 Font derivedFont = deriveFont(font, family, weight, posture, size); 1979 return new CalculatedValue(derivedFont,origin,false); 1980 1981 } 1982 1983 return SKIP; 1984 } 1985 1986 private CascadingStyle lookupInheritedFontProperty( 1987 final Styleable styleable, 1988 final String property, 1989 final StyleMap styleMap, 1990 final int distance, 1991 CascadingStyle fontShorthand) { 1992 1993 Styleable parent = styleable != null ? styleable.getStyleableParent() : null; 1994 1995 int nlooks = distance; 1996 while (parent != null && nlooks > 0) { // This loop traverses through all ancestors till root 1997 1998 CssStyleHelper parentStyleHelper = parent instanceof Node ? ((Node)parent).styleHelper : null; 1999 if (parentStyleHelper != null) { 2000 2001 nlooks -= 1; 2002 2003 StyleMap parentStyleMap = parentStyleHelper.getStyleMap((parent)); 2004 Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates; 2005 CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates); 2006 2007 if (cascadingStyle != null) { 2008 2009 // If we are closer to the node than the font shorthand, then font shorthand doesn't matter. 2010 // If the font shorthand and this style are the same distance, then we need to compare. 2011 if (fontShorthand != null && nlooks == 0) { 2012 if (fontShorthand.compareTo(cascadingStyle) < 0) { 2013 return null; 2014 } 2015 } 2016 2017 final ParsedValue cssValue = cascadingStyle.getParsedValue(); 2018 2019 if ("inherit".equals(cssValue.getValue()) == false) { 2020 return cascadingStyle; 2021 } 2022 } 2023 2024 } 2025 2026 parent = parent.getStyleableParent(); 2027 2028 } 2029 2030 return null; 2031 } 2032 2033 2034 /** 2035 * Called from Node NodeHelper.getMatchingStyles 2036 * @param styleable 2037 * @param styleableProperty 2038 * @return 2039 */ 2040 static List<Style> getMatchingStyles(final Styleable styleable, final CssMetaData styleableProperty) { 2041 2042 if (!(styleable instanceof Node)) return Collections.<Style>emptyList(); 2043 2044 Node node = (Node)styleable; 2045 final CssStyleHelper helper = (node.styleHelper != null) ? node.styleHelper : createStyleHelper(node); 2046 2047 if (helper != null) { 2048 return helper.getMatchingStyles(node, styleableProperty, false); 2049 } 2050 else { 2051 return Collections.<Style>emptyList(); 2052 } 2053 } 2054 2055 static Map<StyleableProperty<?>, List<Style>> getMatchingStyles(Map<StyleableProperty<?>, List<Style>> map, final Node node) { 2056 2057 final CssStyleHelper helper = (node.styleHelper != null) ? node.styleHelper : createStyleHelper(node); 2058 if (helper != null) { 2059 if (map == null) map = new HashMap<>(); 2060 for (CssMetaData metaData : node.getCssMetaData()) { 2061 List<Style> styleList = helper.getMatchingStyles(node, metaData, true); 2062 if (styleList != null && !styleList.isEmpty()) { 2063 StyleableProperty prop = metaData.getStyleableProperty(node); 2064 map.put(prop, styleList); 2065 } 2066 } 2067 } 2068 2069 if (node instanceof Parent) { 2070 for (Node child : ((Parent)node).getChildren()) { 2071 map = getMatchingStyles(map, child); 2072 } 2073 } 2074 2075 return map; 2076 } 2077 2078 private List<Style> getMatchingStyles(final Styleable node, final CssMetaData styleableProperty, boolean matchState) { 2079 2080 final List<CascadingStyle> styleList = new ArrayList<>(); 2081 2082 getMatchingStyles(node, styleableProperty, styleList, matchState); 2083 2084 List<CssMetaData<? extends Styleable, ?>> subProperties = styleableProperty.getSubProperties(); 2085 if (subProperties != null) { 2086 for (int n=0,nMax=subProperties.size(); n<nMax; n++) { 2087 final CssMetaData subProperty = subProperties.get(n); 2088 getMatchingStyles(node, subProperty, styleList, matchState); 2089 } 2090 } 2091 2092 Collections.sort(styleList); 2093 2094 final List<Style> matchingStyles = new ArrayList<>(styleList.size()); 2095 for (int n=0,nMax=styleList.size(); n<nMax; n++) { 2096 final Style style = styleList.get(n).getStyle(); 2097 if (!matchingStyles.contains(style)) matchingStyles.add(style); 2098 } 2099 2100 return matchingStyles; 2101 } 2102 2103 private void getMatchingStyles(final Styleable node, final CssMetaData styleableProperty, final List<CascadingStyle> styleList, boolean matchState) { 2104 2105 if (node != null) { 2106 2107 String property = styleableProperty.getProperty(); 2108 Node _node = node instanceof Node ? (Node)node : null; 2109 final StyleMap smap = getStyleMap(_node); 2110 if (smap == null) return; 2111 2112 if (matchState) { 2113 CascadingStyle cascadingStyle = getStyle(node, styleableProperty.getProperty(), smap, _node.pseudoClassStates); 2114 if (cascadingStyle != null) { 2115 styleList.add(cascadingStyle); 2116 final ParsedValue parsedValue = cascadingStyle.getParsedValue(); 2117 getMatchingLookupStyles(node, parsedValue, styleList, matchState); 2118 } 2119 } else { 2120 2121 Map<String, List<CascadingStyle>> cascadingStyleMap = smap.getCascadingStyles(); 2122 // StyleMap.getCascadingStyles() does not return null 2123 List<CascadingStyle> styles = cascadingStyleMap.get(property); 2124 2125 if (styles != null) { 2126 styleList.addAll(styles); 2127 for (int n=0, nMax=styles.size(); n<nMax; n++) { 2128 final CascadingStyle style = styles.get(n); 2129 final ParsedValue parsedValue = style.getParsedValue(); 2130 getMatchingLookupStyles(node, parsedValue, styleList, matchState); 2131 } 2132 } 2133 } 2134 2135 if (styleableProperty.isInherits()) { 2136 Styleable parent = node.getStyleableParent(); 2137 while (parent != null) { // This loop traverses through all ancestors till root 2138 CssStyleHelper parentHelper = parent instanceof Node 2139 ? ((Node)parent).styleHelper 2140 : null; 2141 if (parentHelper != null) { 2142 parentHelper.getMatchingStyles(parent, styleableProperty, styleList, matchState); 2143 } 2144 parent = parent.getStyleableParent(); 2145 } 2146 } 2147 2148 } 2149 2150 } 2151 2152 // Pretty much a duplicate of resolveLookups, but without the state 2153 private void getMatchingLookupStyles(final Styleable node, final ParsedValue parsedValue, final List<CascadingStyle> styleList, boolean matchState) { 2154 2155 if (parsedValue.isLookup()) { 2156 2157 Object value = parsedValue.getValue(); 2158 2159 if (value instanceof String) { 2160 2161 final String property = (String)value; 2162 // gather up any and all styles that contain this value as a property 2163 Styleable parent = node; 2164 do { 2165 2166 final Node _parent = parent instanceof Node ? (Node)parent : null; 2167 final CssStyleHelper helper = _parent != null 2168 ? _parent.styleHelper 2169 : null; 2170 if (helper != null) { 2171 2172 StyleMap styleMap = helper.getStyleMap(parent); 2173 if (styleMap == null || styleMap.isEmpty()) continue; 2174 2175 final int start = styleList.size(); 2176 2177 if (matchState) { 2178 CascadingStyle cascadingStyle = helper.resolveRef(_parent, property, styleMap, _parent.pseudoClassStates); 2179 if (cascadingStyle != null) { 2180 styleList.add(cascadingStyle); 2181 } 2182 } else { 2183 final Map<String, List<CascadingStyle>> smap = styleMap.getCascadingStyles(); 2184 // getCascadingStyles does not return null 2185 List<CascadingStyle> styles = smap.get(property); 2186 2187 if (styles != null) { 2188 styleList.addAll(styles); 2189 } 2190 2191 } 2192 2193 final int end = styleList.size(); 2194 2195 for (int index=start; index<end; index++) { 2196 final CascadingStyle style = styleList.get(index); 2197 getMatchingLookupStyles(parent, style.getParsedValue(), styleList, matchState); 2198 } 2199 } 2200 2201 } while ((parent = parent.getStyleableParent()) != null); // This loop traverses through all ancestors till root 2202 2203 } 2204 } 2205 2206 // If the value doesn't contain any values that need lookup, then bail 2207 if (!parsedValue.isContainsLookups()) { 2208 return; 2209 } 2210 2211 final Object val = parsedValue.getValue(); 2212 if (val instanceof ParsedValue[][]) { 2213 // If ParsedValue is a layered sequence of values, resolve the lookups for each. 2214 final ParsedValue[][] layers = (ParsedValue[][])val; 2215 for (int l=0; l<layers.length; l++) { 2216 for (int ll=0; ll<layers[l].length; ll++) { 2217 if (layers[l][ll] == null) continue; 2218 getMatchingLookupStyles(node, layers[l][ll], styleList, matchState); 2219 } 2220 } 2221 2222 } else if (val instanceof ParsedValue[]) { 2223 // If ParsedValue is a sequence of values, resolve the lookups for each. 2224 final ParsedValue[] layer = (ParsedValue[])val; 2225 for (int l=0; l<layer.length; l++) { 2226 if (layer[l] == null) continue; 2227 getMatchingLookupStyles(node, layer[l], styleList, matchState); 2228 } 2229 } 2230 2231 } 2232 2233 }