/* * Copyright (c) 2012, 2014, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * - Neither the name of Oracle Corporation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.oracle.javafx.scenebuilder.kit.util; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import javafx.beans.property.ReadOnlyProperty; import javafx.collections.FXCollections; import javafx.css.CssMetaData; import javafx.css.StyleOrigin; import javafx.css.Styleable; import javafx.css.StyleableProperty; import javafx.scene.Node; import javafx.scene.Parent; import com.oracle.javafx.scenebuilder.kit.editor.EditorController; import com.oracle.javafx.scenebuilder.kit.editor.EditorPlatform; import com.oracle.javafx.scenebuilder.kit.editor.EditorPlatform.Theme; import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance; import com.oracle.javafx.scenebuilder.kit.metadata.property.ValuePropertyMetadata; import javafx.css.CompoundSelector; import javafx.css.Rule; import javafx.css.Selector; import javafx.css.SimpleSelector; import javafx.css.Style; import javafx.css.Stylesheet; import javafx.css.CssParser; /** * * Utility classes using css internal classes (from com.sun package). Note that * the CSS Analyzer is also using extensively com.sun classes. * */ public class CssInternal { private final static String[] themeUrls = { Deprecation.CASPIAN_EMBEDDED_HIGHCONTRAST_STYLESHEET, Deprecation.CASPIAN_EMBEDDED_QVGA_HIGHCONTRAST_STYLESHEET, Deprecation.CASPIAN_EMBEDDED_QVGA_STYLESHEET, Deprecation.CASPIAN_EMBEDDED_STYLESHEET, Deprecation.CASPIAN_HIGHCONTRAST_STYLESHEET, Deprecation.CASPIAN_STYLESHEET, Deprecation.MODENA_HIGHCONTRAST_BLACKONWHITE_STYLESHEET, Deprecation.MODENA_HIGHCONTRAST_WHITEONBLACK_STYLESHEET, Deprecation.MODENA_HIGHCONTRAST_YELLOWONBLACK_STYLESHEET, Deprecation.MODENA_STYLESHEET, Deprecation.MODENA_TOUCH_HIGHCONTRAST_BLACKONWHITE_STYLESHEET, Deprecation.MODENA_TOUCH_HIGHCONTRAST_WHITEONBLACK_STYLESHEET, Deprecation.MODENA_TOUCH_HIGHCONTRAST_YELLOWONBLACK_STYLESHEET, Deprecation.MODENA_TOUCH_STYLESHEET }; /** * Check if the input style is from a theme stylesheet (caspian or modena). * * @param style style to be checked * @return true if the style is from a theme css. */ public static boolean isThemeStyle(Style style) { return isThemeRule(style.getDeclaration().getRule()); } public static boolean isCaspianTheme(Style style) { return style.getDeclaration().getRule().getStylesheet().getUrl() .endsWith(Deprecation.CASPIAN_STYLESHEET); } public static boolean isModenaTheme(Style style) { return style.getDeclaration().getRule().getStylesheet().getUrl() .endsWith(Deprecation.MODENA_TOUCH_STYLESHEET); } public static String getThemeDisplayName(Style style) { String themeName = ""; //NOI18N String url = style.getDeclaration().getRule().getStylesheet().getUrl(); if (url.contains("modena")) {//NOI18N themeName += "modena/"; //NOI18N } else if (url.contains("caspian")) {//NOI18N themeName += "caspian/"; //NOI18N } File file = new File(url); themeName += file.getName().replace(".bss", ".css");//NOI18N if (themeName.endsWith("modena.css")) {//NOI18N themeName = "modena.css";//NOI18N } else if (themeName.endsWith("caspian.css")) {//NOI18N themeName = "caspian.css";//NOI18N } return themeName; } public static boolean isThemeRule(Rule rule) { String stylePath = rule.getStylesheet().getUrl(); assert stylePath != null; for (String themeUrl : themeUrls) { if (stylePath.endsWith(themeUrl)) { return true; } } return false; } public static boolean isThemeClass(Theme theme, String styleClass) { return getThemeStyleClasses(theme).contains(styleClass); } public static List getThemeStyleClasses(Theme theme) { String themeStyleSheet = EditorPlatform.getThemeStylesheetURL(theme); Set themeClasses = new HashSet<>(); // For Theme css, we need to get the text css (.css) to be able to parse it. // (instead of the default binary format .bss) themeClasses.addAll(getStyleClasses(Deprecation.getThemeTextStylesheet(themeStyleSheet))); return new ArrayList<>(themeClasses); } // Return the stylesheet corresponding to a style class. // (input parameter: a map returned by getStyleClassesMap(), styleClass) public static String getStyleSheet(Map styleClassMap, String styleClass) { return styleClassMap.get(styleClass); } public static List getStyleClasses(EditorController editorController, Set instances) { return new ArrayList<>(getStyleClassesMap(editorController, instances).keySet()); } public static Map getStyleClassesMap(EditorController editorController, Set instances) { Map classesMap = new TreeMap<>(); Object fxRoot = null; for (FXOMInstance instance : instances) { if (fxRoot == null) { fxRoot = instance.getFxomDocument().getSceneGraphRoot(); } Object fxObject = instance.getSceneGraphObject(); classesMap.putAll(getFxObjectClassesMap(fxObject, fxRoot)); } // Handle the Scene stylesheets (if any) List sceneStyleSheets = editorController.getSceneStyleSheets(); if (sceneStyleSheets != null) { for (File stylesheet : sceneStyleSheets) { try { URL stylesheetUrl = stylesheet.toURI().toURL(); for (String styleClass : getStyleClasses(stylesheetUrl)) { classesMap.put(styleClass, stylesheetUrl.toExternalForm()); } } catch (MalformedURLException ex) { return classesMap; } } } return classesMap; } // Retrieve the styClasses in the fx object scene graph private static Map getFxObjectClassesMap(Object fxObject, Object fxRoot) { Map classesMap = new HashMap<>(); classesMap.putAll(getSingleFxObjectClassesMap(fxObject)); if (!(fxObject instanceof Node)) { return classesMap; } Node node = (Node) fxObject; if (node == fxRoot) { return classesMap; } // Loop on scene graph tree, and stop at root node (to avoid to handle SB nodes) while (node.getParent() != null) { node = node.getParent(); classesMap.putAll(getSingleFxObjectClassesMap(node)); if (node == fxRoot) { break; } } return classesMap; } // Retrieve the styleClasses in the fx object only (not inherited ones) private static Map getSingleFxObjectClassesMap(Object fxObject) { Map classesMap = new HashMap<>(); if (fxObject instanceof Parent) { List stylesheets = ((Parent) fxObject).getStylesheets(); for (String stylesheet : stylesheets) { try { for (String styleClass : getStyleClasses(new URL(stylesheet))) { classesMap.put(styleClass, stylesheet); } } catch (MalformedURLException ex) { return classesMap; } } } return classesMap; } private static Set getStyleClasses(final URL url) { Set styleClasses = new HashSet<>(); Stylesheet s; try { s = new CssParser().parse(url); } catch (IOException ex) { System.out.println("Warning: Invalid Stylesheet " + url); //NOI18N return styleClasses; } if (s == null) { // The parsed CSS file was empty. No parsing occured. return styleClasses; } for (Rule r : s.getRules()) { for (Selector ss : r.getSelectors()) { if (ss instanceof SimpleSelector) { SimpleSelector simple = (SimpleSelector) ss; styleClasses.addAll(simple.getStyleClasses()); } else { if (ss instanceof CompoundSelector) { CompoundSelector cs = (CompoundSelector) ss; for (Selector selector : cs.getSelectors()) { if (selector instanceof SimpleSelector) { SimpleSelector simple = (SimpleSelector) selector; styleClasses.addAll(simple.getStyleClasses()); } } } } } } return styleClasses; } @SuppressWarnings("unchecked") public static List getCssProperties(Set> classes) { TreeSet cssProperties = new TreeSet<>(); for (Class clazz : classes) { if (Node.class.isAssignableFrom(clazz)) { Object metadatas = null; try { metadatas = clazz.getMethod("getClassCssMetaData").invoke(null, (Object[]) null); //NOI18N } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { assert false; } for (CssMetaData metadata : ((List>) metadatas)) { cssProperties.add(metadata.getProperty()); if (metadata.getSubProperties() != null) { for (CssMetaData subMetadata : metadata.getSubProperties()) { cssProperties.add(subMetadata.getProperty()); } } } } } return new ArrayList<>(cssProperties); } // If this property is ruled by CSS, return a CssPropAuthorInfo. Otherwise returns null. public static CssPropAuthorInfo getCssInfo(Object fxObject, ValuePropertyMetadata prop) { CssPropAuthorInfo info = null; Node node = null; if (fxObject instanceof Node) { node = (Node) fxObject; } else { Styleable styleable = fxObject instanceof Styleable ? (Styleable) fxObject : null; if (styleable != null) { node = Deprecation.getNode(styleable); } } if (node != null) { info = getCssInfoForNode(node, prop); } return info; } private static CssPropAuthorInfo getCssInfoForNode(Node node, ValuePropertyMetadata prop) { @SuppressWarnings("rawtypes") Map> map = collectCssState(node); for (@SuppressWarnings("rawtypes") Map.Entry> entry : map.entrySet()) {//NOI18N StyleableProperty beanProp = entry.getKey(); List