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