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 }