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