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