1 /* 2 * Copyright (c) 2012, 2015, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.oracle.javafx.scenebuilder.kit.editor.panel.css; 33 34 import com.oracle.javafx.scenebuilder.kit.editor.panel.css.CssContentMaker.CssPropertyState.CssStyle; 35 import com.oracle.javafx.scenebuilder.kit.editor.panel.css.NodeCssState.CssProperty; 36 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance; 37 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject; 38 import com.oracle.javafx.scenebuilder.kit.metadata.Metadata; 39 import com.oracle.javafx.scenebuilder.kit.metadata.property.PropertyMetadata; 40 import com.oracle.javafx.scenebuilder.kit.metadata.property.ValuePropertyMetadata; 41 import com.oracle.javafx.scenebuilder.kit.metadata.util.PropertyName; 42 import com.oracle.javafx.scenebuilder.kit.util.CssInternal; 43 import com.oracle.javafx.scenebuilder.kit.util.Deprecation; 44 import javafx.css.Rule; 45 import javafx.css.Style; 46 import javafx.css.StyleOrigin; 47 import java.net.MalformedURLException; 48 import java.net.URL; 49 import java.util.*; 50 import javafx.css.CssMetaData; 51 import javafx.css.ParsedValue; 52 import javafx.css.StyleableProperty; 53 import javafx.scene.Node; 54 import javafx.scene.Parent; 55 56 /** 57 * This class construct the model exposed by the CSS Panel. 58 * 59 * @treatAsPrivate 60 */ 61 public class CssContentMaker { 62 63 private CssContentMaker() { 64 assert false; 65 } 66 67 /* 68 * 69 * Public methods 70 * 71 */ 72 public static <N extends Node> PropertyState initialValue(N n, CssMetaData<N, ?> sub) { 73 PropertyState val = null; 74 75 try { 76 Object fxValue; 77 String cssValue; 78 Object value = sub.getInitialValue(n); 79 if (value == null) { 80 cssValue = "none";//NOI18N 81 fxValue = cssValue; 82 } else { 83 fxValue = sub.getInitialValue(n); 84 cssValue = CssValueConverter.toCssString(sub.getProperty(), n); 85 } 86 val = newInitialPropertyState(fxValue, cssValue, n, sub); 87 } catch (RuntimeException ex) { 88 System.out.println(ex.getMessage() + " " + ex); 89 // Ok no initial value or InitialValue bug. 90 } 91 return val; 92 } 93 94 @SuppressWarnings("unchecked") 95 public static <N extends Node> PropertyState initialValue(N n, CssProperty complex, 96 CssMetaData<N, ?> sub) { 97 PropertyState val = null; 98 99 try { 100 Object fxValue; 101 String cssValue; 102 Object complexInitial = complex.getStyleable().getInitialValue(complex.getTarget()); 103 if (complexInitial == null) { 104 cssValue = "none";//NOI18N 105 fxValue = cssValue; 106 } else { 107 fxValue = sub.getInitialValue(n); 108 cssValue = CssValueConverter.toCssString(sub.getProperty(), complexInitial); 109 } 110 val = newInitialPropertyState(fxValue, cssValue, n, sub); 111 } catch (RuntimeException ex) { 112 System.out.println(ex.getMessage() + " " + ex); 113 // Ok no initial value or InitialValue bug. 114 } 115 return val; 116 } 117 118 @SuppressWarnings("rawtypes") 119 public static <N extends Node> PropertyState modelValue(N node, CssMetaData<?, ?> cssMeta, FXOMObject fxomObject) { 120 PropertyState val = null; 121 122 if (fxomObject == null) { 123 // In this case, we are handling a sub-component, no model value then. 124 return null; 125 } 126 // First retrieve the java bean property and check if it is overriden by the inspector. 127 String beanPropName = CssUtils.getBeanPropertyName(node, cssMeta); 128 if (beanPropName == null) { 129 // No corresponding java bean property 130 return null; 131 } 132 PropertyName beanPropertyName = new PropertyName(beanPropName); 133 assert fxomObject instanceof FXOMInstance; 134 FXOMInstance fxomInstance = (FXOMInstance) fxomObject; 135 ValuePropertyMetadata propMeta 136 = Metadata.getMetadata().queryValueProperty(fxomInstance, beanPropertyName); 137 if (propMeta == null) { 138 // No corresponding metadata 139 return null; 140 } 141 if (!propMeta.isReadWrite()) { 142 // R/O : no overridden 143 return null; 144 } 145 boolean overriden = false; 146 Object defaultValue = propMeta.getDefaultValueObject(); 147 Object propertyValue = propMeta.getValueObject(fxomInstance); 148 if ((propertyValue == null) || (defaultValue == null)) { 149 if (propertyValue != defaultValue) { 150 overriden = true; 151 } 152 } else if (!propertyValue.equals(defaultValue)) { 153 overriden = true; 154 } 155 156 if (overriden) { 157 // We have an override. 158 val = new BeanPropertyState(propMeta, cssMeta.getProperty(), propertyValue, 159 CssValueConverter.toCssString(cssMeta.getProperty(), propertyValue)); 160 // An overriden can have sub properties 161 if (cssMeta.getSubProperties() != null && !cssMeta.getSubProperties().isEmpty()) { 162 for (CssMetaData sub : cssMeta.getSubProperties()) { 163 // Create a virtual sub property 164 PropertyState subProp = new BeanPropertyState(propMeta, sub.getProperty(), 165 propertyValue, CssValueConverter.toCssString(sub.getProperty(), propertyValue)); 166 val.getSubProperties().add(subProp); 167 } 168 } 169 } 170 return val; 171 } 172 173 public static Node getSourceNodeForStyle(Object component, String property) { 174 Node ret = null; 175 Node n = CssUtils.getNode(component); 176 if (n != null) { 177 if (n.getStyle() != null && n.getStyle().contains(property)) { 178 ret = n; 179 } else { 180 Parent p = n.getParent(); 181 while (p != null) { 182 String s = p.getStyle(); 183 if (s != null && s.contains(property)) { 184 ret = p; 185 break; 186 } 187 p = p.getParent(); 188 } 189 } 190 } 191 return ret; 192 } 193 194 public static boolean isInlineInherited(Object component, CssPropertyState cssProperty) { 195 boolean isInherited = false; 196 Node node = CssUtils.getNode(component); 197 198 if (node == null) { 199 return false; 200 } 201 202 // Not located on this node, must be inherited then 203 if (node.getStyle() == null) { 204 return true; 205 } 206 207 if (!containsInStyle(cssProperty, node.getStyle())) { 208 isInherited = true; 209 } 210 211 return isInherited; 212 } 213 214 public static boolean containsPseudoState(String selector) { 215 return selector.contains(":");//NOI18N 216 } 217 218 219 /* 220 * 221 * Private methods 222 * 223 */ 224 private static boolean containsInStyle(CssPropertyState prop, String style) { 225 return style.contains(prop.getCssProperty()); 226 } 227 228 public static NodeCssState getCssState(Object selectedObject) { 229 Node node = CssUtils.getSelectedNode(selectedObject); 230 if (node == null) { 231 return null; 232 } 233 Parent p = null; 234 double current = 1; 235 try { 236 if (node.getScene() == null) { 237 // The node is not visible (ContextMenu, Tooltip, ...) 238 // A node MUST be in the scene to allow for CSS content collect, 239 // so we add it (temporarily) to the scene. 240 Node inScene = CssUtils.getFirstAncestorWithNonNullScene(node); 241 if (inScene == null) { 242 // May happen if the Content Panel is not present 243 return null; 244 } 245 p = inScene.getParent(); 246 current = node.getOpacity(); 247 node.setOpacity(0); 248 CssUtils.addToParent(p, node); 249 } 250 NodeCssState state = new NodeCssState(CssInternal.collectCssState(node), node, getFXOMObject(selectedObject)); 251 return state; 252 } finally { 253 if (p != null) { 254 CssUtils.removeFromParent(p, node); 255 node.setOpacity(current); 256 } 257 } 258 } 259 260 private static FXOMObject getFXOMObject(Object selectedObject) { 261 if (selectedObject instanceof FXOMObject) { 262 return (FXOMObject) selectedObject; 263 } else { 264 return null; 265 } 266 } 267 268 @SuppressWarnings("rawtypes") 269 private static <N extends Node> InitialPropertyState newInitialPropertyState( 270 Object fxValue, String cssValue, N n, CssMetaData<?, ?> cssMeta) { 271 InitialPropertyState val 272 = new InitialPropertyState(cssMeta.getProperty(), fxValue, cssValue); 273 if (cssMeta.getSubProperties() != null && !cssMeta.getSubProperties().isEmpty()) { 274 for (CssMetaData sub : cssMeta.getSubProperties()) { 275 Object subValue 276 = CssValueConverter.getSubPropertyValue(sub.getProperty(), fxValue); 277 String subCssValue = CssValueConverter.toCssString(subValue); 278 PropertyState subProp 279 = new InitialPropertyState(sub.getProperty(), subValue, subCssValue); 280 val.getSubProperties().add(subProp); 281 } 282 } 283 return val; 284 } 285 286 // Retrieve the styles associated to the value. This is the case of lookup (or variable) 287 @SuppressWarnings("rawtypes") 288 protected static CssStyle retrieveStyle(List<Style> styles, Style style) { 289 CssStyle st = new CssStyle(style); 290 ParsedValue parsedValue = style.getDeclaration().getParsedValue(); 291 if (parsedValue.isContainsLookups() || parsedValue.isLookup()) { 292 retrieveStylesFromParsedValue(styles, st, style.getDeclaration().getParsedValue()); 293 } 294 return st; 295 } 296 297 @SuppressWarnings("rawtypes") 298 private static void retrieveStylesFromParsedValue( 299 List<Style> lst, CssStyle current, ParsedValue<?, ?> parsedValue) { 300 final Object val = parsedValue.getValue(); 301 if (val instanceof ParsedValue[][]) { 302 // If ParsedValue is a layered sequence of values, resolve the lookups for each. 303 304 final ParsedValue[][] layers2 = (ParsedValue[][]) val; 305 for (ParsedValue[] layers : layers2) { 306 for (ParsedValue layer : layers) { 307 if (layer == null) { 308 continue; 309 } 310 retrieveStylesFromParsedValue(lst, current, layer); 311 } 312 } 313 } else if (val instanceof ParsedValue[]) { 314 // If ParsedValue is a sequence of values, resolve the lookups for each. 315 final ParsedValue[] layers = (ParsedValue[]) val; 316 for (ParsedValue layer : layers) { 317 if (layer == null) { 318 continue; 319 } 320 retrieveStylesFromParsedValue(lst, current, layer); 321 } 322 } else { 323 if (val instanceof String) { 324 String value = (String) val; 325 for (Style info : lst) { 326 if (value.equals(info.getDeclaration().getProperty())) { 327 // Ok matching Style 328 CssStyle cssStyle = retrieveStyle(lst, info); 329 current.getLookupChain().add(cssStyle); 330 } 331 } 332 } 333 } 334 } 335 336 protected static List<CssStyle> getNotAppliedStyles( 337 List<Style> appliedStyles, Node node, CssMetaData<?, ?> cssMeta) { 338 List<CssStyle> ret = new ArrayList<>(); 339 340 List<Style> allStyles = Deprecation.getMatchingStyles(cssMeta, node); 341 // System.out.println("==========================="); 342 // System.out.println("getNotAppliedStyles() called!"); 343 // System.out.println("==========================="); 344 // System.out.println("\n\n"); 345 // printStyles(allStyles); 346 List<Style> matchingStyles = removeUserAgentStyles(allStyles); 347 List<Style> notApplied = new ArrayList<>(); 348 for (Style style : matchingStyles) { 349 if (!appliedStyles.contains(style)) { 350 notApplied.add(style); 351 } 352 } 353 for (Style style : notApplied) { 354 if (style.getDeclaration().getProperty().equals(cssMeta.getProperty())) { 355 // We need to retrieve from allStyles, in case a lookup is shared by appliedStyles and notApplied 356 CssStyle cssStyle = retrieveStyle(matchingStyles, style); 357 ret.add(cssStyle); 358 } 359 } 360 return ret; 361 } 362 363 protected static List<Style> removeUserAgentStyles(List<Style> allStyles) { 364 // With SB 2, we apply explicitly Modena/Caspian theme css on user scene graph. 365 // The rules that appear with an AUTHOR origin has already been considered as USER_AGENT. 366 // So when an internal css method (such as impl_getMatchingStyles()) is called, 367 // we need here to remove all USER_AGENT styles, to avoid doublons. 368 List<Style> matchingStyles = new ArrayList<>(); 369 for (Style style : allStyles) { 370 if (!(style.getDeclaration().getRule().getOrigin() == StyleOrigin.USER_AGENT)) { 371 matchingStyles.add(style); 372 } 373 } 374 return matchingStyles; 375 } 376 377 // protected static void printStyles(List<Style> styles) { 378 // for (Style style : styles) { 379 // printStyle(style); 380 // } 381 // 382 // } 383 // private static void printStyle(Style style) { 384 // System.out.println(style.getDeclaration().getRule().getOrigin() + " ==> STYLE " + style.getDeclaration()); 385 // System.out.println("--> css url = " + style.getDeclaration().getRule().getStylesheet().getUrl()); 386 // } 387 388 /** 389 * 390 * Public classes. 391 * 392 * @treatAsPrivate 393 */ 394 public static abstract class PropertyState implements Comparable<PropertyState> { 395 396 protected PropertyState(String cssValue) { 397 this.cssValue = cssValue; 398 } 399 private final List<CssStyle> notAppliedStyles = new ArrayList<>(); 400 private final List<PropertyState> lst = new ArrayList<>(); 401 private final String cssValue; 402 403 public abstract String getCssProperty(); 404 405 public abstract Object getFxValue(); 406 407 public String getCssValue() { 408 return cssValue; 409 } 410 411 public List<PropertyState> getSubProperties() { 412 return lst; 413 } 414 415 public List<CssStyle> getNotAppliedStyles() { 416 return notAppliedStyles; 417 } 418 419 @Override 420 public int compareTo(PropertyState t) { 421 PropertyState ps = t; 422 return getCssProperty().compareTo(ps.getCssProperty()); 423 } 424 425 @Override 426 public boolean equals(Object obj) { 427 if (obj == null) { 428 return false; 429 } 430 if (getClass() != obj.getClass()) { 431 return false; 432 } 433 PropertyState ps = (PropertyState) obj; 434 return getCssProperty().compareTo(ps.getCssProperty()) == 0; 435 } 436 437 @Override 438 public int hashCode() { 439 int hash = 7; 440 hash = 53 * hash + Objects.hashCode(this.notAppliedStyles); 441 hash = 53 * hash + Objects.hashCode(this.lst); 442 hash = 53 * hash + Objects.hashCode(this.cssValue); 443 return hash; 444 } 445 } 446 447 /** 448 * 449 * @treatAsPrivate 450 */ 451 public static class BeanPropertyState extends PropertyState { 452 453 PropertyMetadata propMeta; 454 private final String cssPropName; 455 private final Object fxValue; 456 457 BeanPropertyState(PropertyMetadata propMeta, String cssPropName, Object fxValue, String cssValue) { 458 super(cssValue); 459 this.propMeta = propMeta; 460 this.cssPropName = cssPropName; 461 this.fxValue = fxValue; 462 } 463 464 @Override 465 public String getCssProperty() { 466 return cssPropName; 467 } 468 469 public PropertyMetadata getPropertyMeta() { 470 return propMeta; 471 } 472 473 @Override 474 public Object getFxValue() { 475 return fxValue; 476 } 477 } 478 479 /** 480 * 481 * @treatAsPrivate 482 */ 483 public static class CssPropertyState extends PropertyState { 484 485 protected final StyleableProperty<?> value; 486 protected final CssMetaData<?, ?> cssMeta; 487 private CssStyle style; 488 489 CssPropertyState(StyleableProperty<?> value, CssMetaData<?, ?> cssMeta, String cssValue) { 490 super(cssValue); 491 this.value = value; 492 this.cssMeta = cssMeta; 493 } 494 495 @Override 496 public String getCssProperty() { 497 return cssMeta.getProperty(); 498 } 499 500 public CssStyle getStyle() { 501 return style; 502 } 503 504 void setStyle(CssStyle style) { 505 this.style = style; 506 } 507 508 @Override 509 public Object getFxValue() { 510 return value.getValue(); 511 } 512 513 /** 514 * 515 * @treatAsPrivate 516 */ 517 public static class CssStyle { 518 519 private final Style style; 520 private boolean used = true; 521 private final List<CssStyle> lookupSet = new ArrayList<>(); 522 523 public CssStyle(Style style) { 524 this.style = style; 525 } 526 527 protected void setUnused() { 528 used = false; 529 } 530 531 public boolean isUsed() { 532 return used; 533 } 534 535 public Style getStyle() { 536 return style; 537 } 538 539 public String getCssProperty() { 540 return style.getDeclaration().getProperty(); 541 } 542 543 @SuppressWarnings("rawtypes") 544 public ParsedValue getParsedValue() { 545 return style.getDeclaration().getParsedValue(); 546 } 547 548 public StyleOrigin getOrigin() { 549 return CssInternal.getOrigin(style); 550 } 551 552 public String getSelector() { 553 String sel = style.getSelector().toString(); 554 if (sel.startsWith("*")) {//NOI18N 555 sel = sel.substring(1); 556 } 557 return sel; 558 } 559 560 public Rule getCssRule() { 561 return style.getDeclaration().getRule(); 562 } 563 564 public URL getUrl() { 565 // Workaround! 566 Rule rule = getCssRule(); 567 if (rule == null) { 568 return null; 569 } else { 570 try { 571 return new URL(rule.getStylesheet().getUrl()); 572 } catch (MalformedURLException ex) { 573 System.out.println(ex.getMessage() + " " + ex); 574 return null; 575 } 576 } 577 } 578 579 @Override 580 public String toString() { 581 return style.toString(); 582 } 583 584 public List<CssStyle> getLookupChain() { 585 return lookupSet; 586 } 587 588 @Override 589 public int hashCode() { 590 int hash = 7; 591 hash = 47 * hash + (this.style != null ? this.style.hashCode() : 0); 592 return hash; 593 } 594 595 @Override 596 public boolean equals(Object obj) { 597 if (!(obj instanceof CssStyle)) { 598 return false; 599 } 600 CssStyle cssStyle = (CssStyle) obj; 601 return style.equals(cssStyle.style); 602 } 603 } 604 } 605 606 /** 607 * 608 * Private classes. 609 * 610 * @treatAsPrivate 611 */ 612 protected static class CssSubPropertyState extends CssPropertyState { 613 614 CssSubPropertyState(StyleableProperty<?> value, CssMetaData<?, ?> cssMeta, String cssValue) { 615 super(value, cssMeta, cssValue); 616 } 617 618 @Override 619 public Object getFxValue() { 620 return CssValueConverter.getSubPropertyValue(cssMeta.getProperty(), value.getValue()); 621 } 622 } 623 624 private static class InitialPropertyState extends PropertyState { 625 626 private final String name; 627 private final Object fxValue; 628 629 InitialPropertyState(String name, Object fxValue, String cssValue) { 630 super(cssValue); 631 this.name = name; 632 this.fxValue = fxValue; 633 } 634 635 @Override 636 public String getCssProperty() { 637 return name; 638 } 639 640 @Override 641 public Object getFxValue() { 642 return fxValue; 643 } 644 } 645 646 }