1 /*
   2  * Copyright (c) 2012, 2014, 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.EditorController;
  35 import com.oracle.javafx.scenebuilder.kit.editor.EditorPlatform;
  36 import com.oracle.javafx.scenebuilder.kit.editor.EditorPlatform.Theme;
  37 import com.oracle.javafx.scenebuilder.kit.editor.drag.source.AbstractDragSource;
  38 import com.oracle.javafx.scenebuilder.kit.editor.i18n.I18N;
  39 import com.oracle.javafx.scenebuilder.kit.editor.panel.css.CssContentMaker.BeanPropertyState;
  40 import com.oracle.javafx.scenebuilder.kit.editor.panel.css.CssContentMaker.CssPropertyState;
  41 import com.oracle.javafx.scenebuilder.kit.editor.panel.css.CssContentMaker.CssPropertyState.CssStyle;
  42 import com.oracle.javafx.scenebuilder.kit.editor.panel.css.CssContentMaker.PropertyState;
  43 import com.oracle.javafx.scenebuilder.kit.editor.panel.css.CssValuePresenterFactory.CssValuePresenter;
  44 import com.oracle.javafx.scenebuilder.kit.editor.panel.css.NodeCssState.CssProperty;
  45 import com.oracle.javafx.scenebuilder.kit.editor.panel.css.SelectionPath.Item;
  46 import com.oracle.javafx.scenebuilder.kit.editor.panel.util.AbstractFxmlPanelController;
  47 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument;
  48 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject;
  49 import com.oracle.javafx.scenebuilder.kit.editor.panel.css.SelectionPath.Path;
  50 import com.oracle.javafx.scenebuilder.kit.editor.selection.ObjectSelectionGroup;
  51 import com.oracle.javafx.scenebuilder.kit.editor.selection.Selection;
  52 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance;
  53 import com.oracle.javafx.scenebuilder.kit.metadata.Metadata;
  54 import com.oracle.javafx.scenebuilder.kit.metadata.property.ValuePropertyMetadata;
  55 import com.oracle.javafx.scenebuilder.kit.metadata.util.PropertyName;
  56 import com.oracle.javafx.scenebuilder.kit.util.CssInternal;
  57 import com.oracle.javafx.scenebuilder.kit.util.Deprecation;
  58 import com.sun.javafx.css.ParsedValueImpl;
  59 import com.sun.javafx.css.Rule;
  60 
  61 import java.io.File;
  62 import java.io.IOException;
  63 import java.lang.reflect.Array;
  64 import java.net.MalformedURLException;
  65 import java.net.URISyntaxException;
  66 import java.net.URL;
  67 import java.text.MessageFormat;
  68 import java.util.*;
  69 
  70 import javafx.animation.FadeTransition;
  71 import javafx.beans.property.ObjectProperty;
  72 import javafx.beans.property.SimpleObjectProperty;
  73 import javafx.beans.value.ChangeListener;
  74 import javafx.beans.value.ObservableValue;
  75 import javafx.collections.FXCollections;
  76 import javafx.collections.ObservableList;
  77 import javafx.css.ParsedValue;
  78 import javafx.css.PseudoClass;
  79 import javafx.css.StyleOrigin;
  80 import javafx.event.ActionEvent;
  81 import javafx.event.EventHandler;
  82 import javafx.fxml.FXML;
  83 import javafx.geometry.Orientation;
  84 import javafx.geometry.Pos;
  85 import javafx.geometry.VPos;
  86 import javafx.scene.Node;
  87 import javafx.scene.Parent;
  88 import javafx.scene.control.*;
  89 import javafx.scene.image.Image;
  90 import javafx.scene.image.ImageView;
  91 import javafx.scene.input.Clipboard;
  92 import javafx.scene.input.ClipboardContent;
  93 import javafx.scene.layout.AnchorPane;
  94 import javafx.scene.layout.HBox;
  95 import javafx.scene.layout.Priority;
  96 import javafx.scene.layout.Region;
  97 import javafx.scene.layout.StackPane;
  98 import javafx.scene.layout.VBox;
  99 import javafx.scene.text.Text;
 100 import javafx.scene.web.WebView;
 101 import javafx.util.Callback;
 102 import javafx.util.Duration;
 103 
 104 /**
 105  * Controller for the CSS Panel.
 106  *
 107  */
 108 public class CssPanelController extends AbstractFxmlPanelController {
 109 
 110     @FXML
 111     private StackPane cssPanelHost;
 112 
 113     @FXML
 114     private StackPane cssSearchPanelHost;
 115 
 116     @FXML
 117     private TableColumn<CssProperty, CssProperty> beanApiColumn;
 118     @FXML
 119     private TableColumn<CssProperty, CssProperty> builtinColumn;
 120     @FXML
 121     private TableColumn<CssProperty, CssProperty> fxThemeColumn;
 122     @FXML
 123     private TableColumn<CssProperty, CssProperty> inlineColumn;
 124     @FXML
 125     private TableColumn<CssProperty, CssProperty> propertiesColumn;
 126     @FXML
 127     private TableColumn<CssProperty, CssProperty> defaultColumn;
 128     @FXML
 129     private ToggleButton pick;
 130     @FXML
 131     private ToggleButton edit;
 132     @FXML
 133     SelectionPath selectionPath;
 134     @FXML
 135     private VBox root;
 136     @FXML
 137     private HBox header;
 138     @FXML
 139     WebView textPane;
 140     @FXML
 141     private VBox rulesBox;
 142     @FXML
 143     private ScrollPane rulesPane;
 144     @FXML
 145     private TableColumn<CssProperty, CssProperty> stylesheetsColumn;
 146     @FXML
 147     private TableView<CssProperty> table;
 148     @FXML
 149     private StackPane messagePane;
 150     @FXML
 151     private Label messageLabel;
 152 
 153     private TreeView<Node> rulesTree;
 154 
 155     private boolean advanced = false;
 156     private boolean styledOnly = false;
 157     private boolean tableColumnsOrderingReversed = false;
 158     private boolean dragOnGoing = false;
 159 
 160     private ObservableList<CssProperty> model;
 161 
 162     private View currentView = View.TABLE;
 163 
 164     private String searchPattern;
 165     private static Image lookups = null;
 166 
 167     private static final String NO_MATCHING_RULES = I18N.getString("csspanel.no.matching.rule");
 168 
 169     public enum View {
 170 
 171         TABLE, RULES, TEXT;
 172     }
 173 
 174     private Object selectedObject; // Can be either an FXOMObject (selection mode), or a Node (pick mode)
 175     private Selection selection;
 176     private final EditorController editorController;
 177     private final Delegate applicationDelegate;
 178     private final ObjectProperty<NodeCssState> cssStateProperty = new SimpleObjectProperty<>();
 179 
 180     /**
 181      * Should be implemented by the application.
 182      *
 183      * @treatAsPrivate
 184      */
 185     public static abstract class Delegate {
 186 
 187         public abstract void revealInspectorEditor(ValuePropertyMetadata propMeta);
 188     }
 189 
 190     /*
 191      * AbstractPanelController
 192      */
 193     @Override
 194     protected void fxomDocumentDidChange(FXOMDocument oldDocument) {
 195         if (isCssPanelLoaded() && hasFxomDocument()) {
 196             updateSelectedObject();
 197             refresh();
 198         }
 199     }
 200 
 201     @Override
 202     protected void sceneGraphRevisionDidChange() {
 203         // System.out.println("CssPanelController.sceneGraphRevisionDidChange() called!");
 204         if (isCssPanelLoaded() && hasFxomDocument()) {
 205             refresh();
 206         }
 207     }
 208 
 209     @Override
 210     protected void cssRevisionDidChange() {
 211         // System.out.println("CssPanelController.cssRevisionDidChange() called!");
 212         if (isCssPanelLoaded() && hasFxomDocument()) {
 213             refresh();
 214         }
 215     }
 216 
 217     @Override
 218     protected void jobManagerRevisionDidChange() {
 219         // FXOMDocument has been modified by a job.
 220         // getEditorController().getJobManager().getLastJob()
 221         // is the job responsible of the change.
 222         // Since sceneGraphRevisionDidChange() will be called in this case, nothing to do here.
 223     }
 224 
 225     @Override
 226     protected void editorSelectionDidChange() {
 227         if (isCssPanelLoaded() && hasFxomDocument() && !dragOnGoing) {
 228             updateSelectedObject();
 229             refresh();
 230         }
 231     }
 232 
 233     /**
 234      * AbstractFxmlPanelController.
 235      *
 236      */
 237     @Override
 238     protected void controllerDidLoadFxml() {
 239         // Remove scrollPane for rules
 240         root.getChildren().remove(rulesPane);
 241         root.getChildren().remove(textPane);
 242         root.getChildren().remove(table);
 243 
 244         pick.setOnAction(t -> editorController.setPickModeEnabled(true));
 245         edit.setOnAction(t -> editorController.setPickModeEnabled(false));
 246         editorController.pickModeEnabledProperty().addListener((ChangeListener<Boolean>) (ov, oldVal, newVal) -> setPickMode(newVal));
 247         // Initialize the pick mode from the editorController value
 248         setPickMode(editorController.isPickModeEnabled());
 249 
 250         table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
 251 
 252         disableColumnReordering();
 253         final Callback<TableColumn.CellDataFeatures<CssProperty, CssProperty>, ObservableValue<CssProperty>> valueFactory
 254                 = new ValueFactory();
 255 
 256         propertiesColumn.setCellValueFactory(valueFactory);
 257         propertiesColumn.setCellFactory(new PropertiesCellFactory());
 258 
 259         builtinColumn.setCellValueFactory(valueFactory);
 260         builtinColumn.setCellFactory(new BuiltinCellFactory());
 261 
 262         fxThemeColumn.setCellValueFactory(valueFactory);
 263         fxThemeColumn.setCellFactory(new FxThemeCellFactory());
 264 
 265         beanApiColumn.setCellValueFactory(valueFactory);
 266         beanApiColumn.setCellFactory(new ModelCellFactory());
 267 
 268         stylesheetsColumn.setCellValueFactory(valueFactory);
 269         stylesheetsColumn.setCellFactory(new AuthorCellFactory());
 270 
 271         inlineColumn.setCellValueFactory(valueFactory);
 272         inlineColumn.setCellFactory(new InlineCellFactory());
 273 
 274         defaultColumn.setCellValueFactory(valueFactory);
 275         defaultColumn.setCellFactory(new DefaultCellFactory());
 276 
 277         editorController.themeProperty().addListener((ChangeListener<Theme>) (ov, t, t1) -> refresh());
 278 
 279         cssStateProperty.addListener((ChangeListener<NodeCssState>) (arg0, oldValue, newValue) -> fillPropertiesTable());
 280 
 281         ChangeListener<Item> selectionListener = (arg0, oldvalue, newValue) -> {
 282             if (newValue != null && newValue.getItem() != null) {
 283                 Node selectedSubNode = CssUtils.getNode(newValue.getItem());
 284                 selectedObject = selectedSubNode;
 285                 refresh();
 286                 // Switch to pick mode
 287                 editorController.setPickModeEnabled(true);
 288 //                    // Select the sub node
 289                 selection = editorController.getSelection();
 290                 selection.select(getFXOMInstance(selection), selectedSubNode);
 291             }
 292         };
 293         selectionPath.selected().addListener(selectionListener);
 294 
 295         // Listen the drag property changes
 296         getEditorController().getDragController().dragSourceProperty().addListener((ChangeListener<AbstractDragSource>) (ov, oldVal, newVal) -> {
 297             if (newVal != null) {
 298 //                    System.out.println("Drag started !");
 299                 dragOnGoing = true;
 300             } else {
 301 //                    System.out.println("Drag finished.");
 302                 dragOnGoing = false;
 303                 updateSelectedObject();
 304                 refresh();
 305             }
 306         });
 307 
 308         // View table by default
 309         changeView(CssPanelController.View.TABLE);
 310 
 311         editorSelectionDidChange();
 312     }
 313 
 314     private static class ValueFactory implements Callback<TableColumn.CellDataFeatures<CssProperty, CssProperty>, ObservableValue<CssProperty>> {
 315 
 316         @Override
 317         public ObservableValue<CssProperty> call(TableColumn.CellDataFeatures<CssProperty, CssProperty> param) {
 318             ObjectProperty<CssProperty> val = new SimpleObjectProperty<>();
 319             val.setValue(param.getValue());
 320             return val;
 321         }
 322     }
 323 
 324     private static class PropertiesCellFactory implements Callback<TableColumn<CssProperty, CssProperty>, TableCell<CssProperty, CssProperty>> {
 325 
 326         @Override
 327         public TableCell<CssProperty, CssProperty> call(TableColumn<CssProperty, CssProperty> param) {
 328             return new CssPropertyTableCell();
 329         }
 330     }
 331 
 332     private class BuiltinCellFactory implements Callback<TableColumn<CssProperty, CssProperty>, TableCell<CssProperty, CssProperty>> {
 333 
 334         @Override
 335         public TableCell<CssProperty, CssProperty> call(TableColumn<CssProperty, CssProperty> param) {
 336             return new BuiltinValueTableCell();
 337         }
 338     }
 339 
 340     private class FxThemeCellFactory implements Callback<TableColumn<CssProperty, CssProperty>, TableCell<CssProperty, CssProperty>> {
 341 
 342         @Override
 343         public TableCell<CssProperty, CssProperty> call(TableColumn<CssProperty, CssProperty> param) {
 344             return new FxThemeValueTableCell();
 345         }
 346     }
 347 
 348     private class ModelCellFactory implements Callback<TableColumn<CssProperty, CssProperty>, TableCell<CssProperty, CssProperty>> {
 349 
 350         @Override
 351         public TableCell<CssProperty, CssProperty> call(TableColumn<CssProperty, CssProperty> param) {
 352 //            System.out.println("Creating new ModelValueTableCell...");
 353             return new ModelValueTableCell();
 354         }
 355     }
 356 
 357     private class AuthorCellFactory implements Callback<TableColumn<CssProperty, CssProperty>, TableCell<CssProperty, CssProperty>> {
 358 
 359         @Override
 360         public TableCell<CssProperty, CssProperty> call(TableColumn<CssProperty, CssProperty> param) {
 361             return new AuthorValueTableCell();
 362         }
 363     }
 364 
 365     private class InlineCellFactory implements Callback<TableColumn<CssProperty, CssProperty>, TableCell<CssProperty, CssProperty>> {
 366 
 367         @Override
 368         public TableCell<CssProperty, CssProperty> call(TableColumn<CssProperty, CssProperty> param) {
 369             return new InlineValueTableCell();
 370         }
 371     }
 372 
 373     private class DefaultCellFactory implements Callback<TableColumn<CssProperty, CssProperty>, TableCell<CssProperty, CssProperty>> {
 374 
 375         @Override
 376         public TableCell<CssProperty, CssProperty> call(TableColumn<CssProperty, CssProperty> param) {
 377             return new DefaultValueTableCell();
 378         }
 379     }
 380 
 381     /*
 382      *
 383      * Public
 384      *
 385      */
 386     public CssPanelController(EditorController c, Delegate delegate) {
 387         super(CssPanelController.class.getResource("CssPanel.fxml"), I18N.getBundle(), c);
 388         this.editorController = c;
 389         this.applicationDelegate = delegate;
 390     }
 391 
 392     public String getSearchPattern() {
 393         return searchPattern;
 394     }
 395 
 396     public void setSearchPattern(String searchPattern) {
 397         this.searchPattern = searchPattern;
 398         searchPatternDidChange();
 399     }
 400 
 401     /**
 402      * 
 403      * @param selectionListener selection listener.
 404      * @treatAsPrivate
 405      */
 406     public void addSelectionListener(ChangeListener<Item> selectionListener) {
 407         selectionPath.selected().addListener(selectionListener);
 408     }
 409 
 410     /**
 411      * 
 412      * @param path path.
 413      * @treatAsPrivate
 414      */
 415     public void setSelectionPath(Path path) {
 416         selectionPath.setSelectionPath(path);
 417     }
 418 
 419     /**
 420      * @treatAsPrivate
 421      */
 422     public void resetSelectionPath() {
 423         selectionPath.setSelectionPath(new Path(new ArrayList<>()));
 424     }
 425 
 426     /**
 427      * 
 428      * @param mess message.
 429      * @treatAsPrivate
 430      */
 431     public void viewMessage(String mess) {
 432         root.getChildren().removeAll(messagePane, header, table, rulesPane, textPane);
 433 //        mainMenu.setDisable(true);
 434 //        searchBox.setDisable(true);
 435         messageLabel.setText(mess);
 436         root.getChildren().add(messagePane);
 437     }
 438 
 439     /**
 440      *
 441      * @return node.
 442      * @treatAsPrivate
 443      */
 444     public final Node getRulesPane() {
 445         return rulesPane;
 446     }
 447 
 448     /**
 449      *
 450      * @return node.
 451      * @treatAsPrivate
 452      */
 453     public final Node getTextPane() {
 454         return textPane;
 455     }
 456 
 457     /**
 458      *
 459      * @param model model.
 460      * @param state state.
 461      * @treatAsPrivate
 462      */
 463     public void setContent(ObservableList<CssProperty> model, NodeCssState state) {
 464         changeView(currentView);
 465         initializeRulesTextPanes(state);
 466         this.model = FXCollections.observableArrayList(model);
 467     }
 468 
 469     /**
 470      *
 471      * @treatAsPrivate
 472      */
 473     public void clearContent() {
 474         table.getItems().clear();
 475         resetSelectionPath();
 476     }
 477 
 478     public void filter(String pattern) {
 479         if (model == null) {
 480             return;
 481         }
 482         ObservableList<CssProperty> filtered;
 483         if (pattern == null || pattern.trim().length() == 0) {
 484             filtered = model;
 485         } else {
 486             filtered = FXCollections.observableArrayList();
 487             for (CssProperty p : model) {
 488                 if (p.propertyName().get().contains(pattern.trim())) {
 489                     filtered.add(p);
 490                 }
 491             }
 492         }
 493         updateTable(filtered);
 494     }
 495 
 496     /**
 497      *
 498      * @param parent parent.
 499      * @param cssProp css property.
 500      * @param style css style.
 501      * @param applied applied.
 502      * @param isLookup lookup.
 503      * @treatAsPrivate
 504      */
 505     public static void attachStyleProperty(TreeItem<Node> parent, CssPropertyState cssProp, CssStyle style,
 506             boolean applied, boolean isLookup) {
 507         if (isLookup) {
 508             String cssValue = CssValueConverter.toCssString(style.getCssProperty(), style.getCssRule(), style.getParsedValue());
 509             TreeItem<Node> item = new TreeItem<>(getContent(style.getCssProperty(), cssValue, style.getParsedValue(), applied));
 510             parent.getChildren().add(item);
 511         } else {
 512             attachStylePropertyNoLookup(parent, cssProp, style, applied);
 513         }
 514     }
 515 
 516     public void changeView(View view) {
 517         switch (view) {
 518             case TABLE: {
 519                 root.getChildren().removeAll(messagePane, header, table, rulesPane, textPane);
 520                 root.getChildren().addAll(header, table);
 521                 break;
 522             }
 523             case RULES: {
 524                 root.getChildren().removeAll(messagePane, header, rulesPane, table, textPane);
 525                 root.getChildren().addAll(header, rulesPane);
 526                 break;
 527             }
 528             case TEXT: {
 529                 root.getChildren().removeAll(messagePane, header, textPane, table, rulesPane);
 530                 root.getChildren().addAll(header, textPane);
 531                 break;
 532             }
 533         }
 534         currentView = view;
 535     }
 536 
 537     /**
 538      *
 539      * @treatAsPrivate
 540      */
 541     public void copyStyleablePath() {
 542         final ClipboardContent content = new ClipboardContent();
 543         content.putString(selectionPath.toString());
 544         Clipboard.getSystemClipboard().setContent(content);
 545     }
 546 
 547     public void splitDefaultsAction() {
 548         advanced = !advanced;
 549         unmerge();
 550     }
 551 
 552     public void showStyledOnly() {
 553         styledOnly = !styledOnly;
 554         if (model == null) {
 555             return;
 556         }
 557         showStyled(model);
 558     }
 559 
 560     public void toggleTableColumnsOrdering() {
 561         // switch the table columns:
 562         // Default to Inline ==> Inline to Defaults
 563         // (and vice-versa)
 564         ObservableList<TableColumn<CssProperty, ?>> columns = table.getColumns();
 565         FXCollections.reverse(columns);
 566         // Property column is always first
 567         TableColumn<CssProperty, ?> propertyColumn = columns.get(columns.size() - 1);
 568         columns.remove(propertyColumn);
 569         columns.add(0, propertyColumn);
 570         tableColumnsOrderingReversed = !tableColumnsOrderingReversed;
 571     }
 572 
 573     public boolean isTableColumnsOrderingReversed() {
 574         return tableColumnsOrderingReversed;
 575     }
 576 
 577     public void setTableColumnsOrderingReversed(boolean value) {
 578         if (table != null && tableColumnsOrderingReversed != value) {
 579             toggleTableColumnsOrdering();
 580         }
 581     }
 582 
 583     /*
 584      *
 585      * FXML methods.
 586      *
 587      * @treatAsPrivate
 588      */
 589     public void initialize() {
 590 
 591     }
 592 
 593     /*
 594      *
 595      * Private
 596      *
 597      */
 598     private void refresh() {
 599         setCSSContent();
 600     }
 601 
 602     private void updateSelectedObject() {
 603         selection = editorController.getSelection();
 604         if (!isMultipleSelection()) {
 605             if (isPickMode()) {
 606                 // In pick mode:
 607                 // If the selected node is the "root" node ==> we get its FXOMInstance
 608                 // Else, we don't have an FXOMInstance
 609                 Object pickObject = selection.getCheckedHitNode();
 610                 FXOMInstance fxomInstance = getFXOMInstance(selection);
 611                 if (fxomInstance != null && fxomInstance.getSceneGraphObject() == pickObject) {
 612                     selectedObject = fxomInstance;
 613                 } else {
 614                     selectedObject = pickObject;
 615                 }
 616 //                System.out.println("(pick mode) selectedObject = " + selectedObject);
 617             } else {
 618                 selectedObject = getFXOMInstance(selection);
 619 //                System.out.println("selectedObject = " + selectedObject);
 620             }
 621         }
 622     }
 623 
 624     private static String getFirstStandardClassName(final Class<?> type) {
 625         Class<?> clazz = type;
 626         while (clazz != null) {
 627             if (clazz.getName().startsWith("javafx")) {//NOI18N
 628                 return clazz.getSimpleName();
 629             }
 630             clazz = clazz.getSuperclass();
 631         }
 632         return type.getName();
 633     }
 634 
 635     private void collectCss() {
 636         if (selectedObject != null) {
 637             NodeCssState state = CssContentMaker.getCssState(selectedObject);
 638             if (state == null) {
 639                 return;
 640             }
 641             cssStateProperty.setValue(state);
 642         }
 643     }
 644 
 645     private void setCSSContent() {
 646         if (selectedObject instanceof Skinnable) {
 647             if (((Skinnable) selectedObject).getSkin() == null) {
 648                 return;
 649             }
 650         }
 651 
 652         clearContent();
 653         if (isMultipleSelection()) {
 654             viewMessage(I18N.getString("csspanel.multiselection"));
 655             return;
 656         }
 657         
 658         if (selectedObject != null) { // Update content.
 659             fillSelectionContent();
 660             collectCss();
 661         }
 662     }
 663 
 664     private boolean isMultipleSelection() {
 665         if (selection.getGroup() instanceof ObjectSelectionGroup) {
 666             return ((ObjectSelectionGroup) selection.getGroup()).getItems().size() > 1;
 667         } else {
 668             // GridSelectionGroup: consider the GridPane only
 669             return false;
 670         }
 671     }
 672 
 673     private boolean isCssPanelLoaded() {
 674         return root != null;
 675     }
 676 
 677     private boolean hasFxomDocument() {
 678         return getEditorController().getFxomDocument() != null;
 679     }
 680 
 681     private boolean isPickMode() {
 682         return editorController.isPickModeEnabled();
 683     }
 684 
 685     private void setPickMode(boolean pickMode) {
 686         pick.setSelected(pickMode);
 687         edit.setSelected(!pickMode);
 688     }
 689 
 690     private void fillSelectionContent() {
 691         assert selectedObject != null;
 692         // Start from the Component;
 693         Node selectedRootNode = CssUtils.getSelectedNode(getFXOMInstance(selection));
 694         if (selectedRootNode == null) {
 695             return;
 696         }
 697         Item rootItem = new Item(selectedRootNode, createItemName(selectedRootNode), createOptional(selectedRootNode));//NOI18N
 698 
 699         // Seems we can skip the skin now, which is not in the scene graph anymore.
 700 //        if (componentRootNode instanceof Skinnable) {
 701 //            assert ((Skinnable) componentRootNode).getSkin() != null;
 702 //            Node skinNode = ((Skinnable) componentRootNode).getSkin().getNode();
 703 //            if (skinNode instanceof Parent) {
 704 //                addSubStructure(componentRootNode, rootItem, (Parent) skinNode);
 705 //            }
 706 //        } else {
 707         if (selectedRootNode instanceof Parent) {
 708             addSubStructure(selectedRootNode, rootItem, (Parent) selectedRootNode);
 709         }
 710 //        }
 711         Object selectedNode = CssUtils.getSelectedNode(selectedObject);
 712         List<Item> items = SelectionPath.lookupPath(rootItem, selectedNode);
 713         setSelectionPath(new Path(items));
 714     }
 715 
 716     private void addSubStructure(Node componentRootNode, Item parentItem, Node node) {
 717         FXOMDocument fxomDoc = getEditorController().getFxomDocument();
 718         assert fxomDoc != null;
 719         Node enclosingNode = getEnclosingNode(fxomDoc, node);
 720         // The componentRootNode can be a skin structure (Tab, Column), in this case the enclosingNode 
 721         // is not == to the componentRootNode. That is why we need to compare the enclosingNode of both
 722         // n and componentRootNode nodes.
 723         Node componentRootNodeEnclosingNode = getEnclosingNode(fxomDoc, componentRootNode);
 724         // this is a skin's node and not a node from a component located inside 
 725         // the skin (eg: SplitPane content being a Button is not part of the SplitPane Skin.
 726         boolean isOtherComponentNode = enclosingNode != componentRootNodeEnclosingNode;
 727         if (componentRootNode != node && !node.getStyleClass().isEmpty() && !isOtherComponentNode && !(node instanceof Skin)) {
 728             Item ni = new Item(node, createItemName(node), createOptional(node));//NOI18N
 729             parentItem.getChildren().add(ni);
 730             parentItem = ni;
 731         }
 732         if (node instanceof Parent && !isOtherComponentNode) {
 733             Parent parentNode = (Parent) node;
 734             for (Node child : parentNode.getChildrenUnmodifiable()) {
 735                 addSubStructure(componentRootNode, parentItem, child);
 736             }
 737         }
 738     }
 739 
 740     private Node getEnclosingNode(FXOMDocument fxomDoc, Node n) {
 741         Node node = n;
 742         FXOMObject enclosingFXOMObj = fxomDoc.searchWithSceneGraphObject(node);
 743         while (enclosingFXOMObj == null) {
 744             node = node.getParent();
 745             if (node == null) {
 746                 return null;
 747             }
 748             enclosingFXOMObj = fxomDoc.searchWithSceneGraphObject(node);
 749         }
 750         Object enclosingObj = enclosingFXOMObj.getSceneGraphObject();
 751         assert enclosingObj instanceof Node;
 752         return (Node) enclosingObj;
 753     }
 754 
 755     private void fillPropertiesTable() {
 756         NodeCssState state = cssStateProperty.getValue();
 757         if (state == null) {
 758             return;
 759         }
 760         fillContent(state);
 761         filter(searchPattern);
 762     }
 763 
 764     private void fillContent(NodeCssState state) {
 765         ObservableList<CssProperty> cssModel = FXCollections.observableArrayList();
 766         Collection<CssProperty> styleables = state.getAllStyleables();
 767         for (CssProperty sp : styleables) {
 768             cssModel.add(sp);
 769             for (CssProperty sub : sp.getSubProperties()) {
 770                 cssModel.add(sub);
 771             }
 772         }
 773         setContent(cssModel, state);
 774     }
 775 
 776     private void attachNotAppliedStyles(TreeItem<Node> ti, PropertyState css) {
 777         for (CssStyle style : css.getNotAppliedStyles()) {
 778             attachStyle(css, style, ti, false);
 779         }
 780     }
 781 
 782     private void attachSubProperties(TreeItem<Node> parent, PropertyState ss) {
 783         for (PropertyState sub : ss.getSubProperties()) {
 784             attachProperty(parent, sub);
 785         }
 786         attachNotAppliedStyles(parent, ss);
 787     }
 788 
 789     private void attachProperty(TreeItem<Node> parent, PropertyState ss) {
 790         boolean hasSubs = !ss.getSubProperties().isEmpty();
 791         if (hasSubs) {
 792             if (ss instanceof CssPropertyState) {
 793                 CssPropertyState cssProp = (CssPropertyState) ss;
 794                 if (cssProp.getStyle() != null) {
 795                     // Need to add the container, not the sub properties
 796                     Node content = getContent(ss.getCssProperty(), ss.getCssValue(), ss.getFxValue(), true);
 797                     TreeItem<Node> ti = newTreeItem(content, ss);
 798                     parent.getChildren().add(ti);
 799                     attachStyles(cssProp, ti);
 800                     attachNotAppliedStyles(parent, ss);
 801                 } else {
 802                     attachSubProperties(parent, ss);
 803                 }
 804             } else {
 805                 attachSubProperties(parent, ss);
 806             }
 807         } else {
 808             Node content = getContent(ss.getCssProperty(), ss.getCssValue(), ss.getFxValue(), true);
 809             TreeItem<Node> ti = newTreeItem(content, ss);
 810             parent.getChildren().add(ti);
 811             if (ss instanceof CssPropertyState) {
 812                 CssPropertyState css = (CssPropertyState) ss;
 813                 attachStyles(css, ti);
 814             } else {
 815                 if (ss instanceof BeanPropertyState) {
 816                     BeanPropertyState beanProp = (BeanPropertyState) ss;
 817                     String source = beanProp.getPropertyMeta().getName().toString();
 818                     StringBuilder contentBuilder = new StringBuilder();
 819                     contentBuilder.append(source);
 820                     ti.getChildren().add(newTreeItem(new Label(contentBuilder.toString()), ss));
 821                 }
 822             }
 823             attachNotAppliedStyles(ti, ss);
 824         }
 825     }
 826 
 827     private void searchPatternDidChange() {
 828         filter(searchPattern);
 829     }
 830 
 831     private void updateTable(ObservableList<CssProperty> currentModel) {
 832         showStyled(currentModel);
 833         unmerge();
 834     }
 835 
 836     private void disableColumnReordering() {
 837         for (TableColumn<CssProperty, ?> column : table.getColumns()) {
 838             Deprecation.setTableColumnReordable(column, false);
 839         }
 840     }
 841 
 842     private void initializeRulesTextPanes(NodeCssState state) {
 843         rulesBox.getChildren().clear();
 844         List<NodeCssState.MatchingRule> rulesList = state.getMatchingRules();
 845         HtmlStyler htmlStyler = new HtmlStyler();
 846         rulesTree = new TreeView<>();
 847         CopyHandler.attachContextMenu(rulesTree);
 848         rulesTree.setShowRoot(false);
 849         TreeItem<Node> ruleRoot = new TreeItem<>(new Text(""));//NOI18N
 850         rulesTree.setRoot(ruleRoot);
 851         for (NodeCssState.MatchingRule rule : rulesList) {
 852             List<NodeCssState.MatchingDeclaration> lst = rule.getDeclarations();
 853 
 854             String selector = rule.getSelector();
 855 
 856             String txt = selector + " { ";//NOI18N
 857             String source = nonNull(getSource(rule.getRule()));
 858             Text text = CopyHandler.makeCopyableNode(new Text(txt + source), txt);
 859             TreeItem<Node> start = new TreeItem<>(text);
 860             ruleRoot.getChildren().add(start);
 861             htmlStyler.cssRuleStart(selector, source);
 862 
 863             for (NodeCssState.MatchingDeclaration p : lst) {
 864                 CssPropertyState prop = p.getProp();
 865                 attachStyleProperty(ruleRoot, prop, p.getStyle(), p.isApplied(), p.isLookup());
 866                 CssStyle style = p.getStyle();
 867                 String cssValue = CssValueConverter.toCssString(style.getCssProperty(), style.getCssRule(), style.getParsedValue());
 868                 htmlStyler.addProperty(p.getStyle().getCssProperty(), cssValue, p.isApplied());
 869             }
 870             TreeItem<Node> end = new TreeItem<>(CopyHandler.createCopyableText("}"));//NOI18N
 871             ruleRoot.getChildren().add(end);
 872             setTreeHeight(rulesTree);
 873             htmlStyler.cssRuleEnd();
 874         }
 875         if (ruleRoot.getChildren().isEmpty()) {
 876             rulesBox.getChildren().add(new Label(NO_MATCHING_RULES));
 877         } else {
 878             rulesBox.getChildren().add(rulesTree);
 879         }
 880         if (htmlStyler.isEmpty()) {
 881             htmlStyler.addMessage(NO_MATCHING_RULES);
 882         }
 883         textPane.getEngine().loadContent(htmlStyler.getHtmlString());
 884     }
 885 
 886     void close(ChangeListener<Item> selectionListener) {
 887         selectionPath.selected().removeListener(selectionListener);
 888     }
 889 
 890     private void unmerge() {
 891         defaultColumn.setVisible(!advanced);
 892         fxThemeColumn.setVisible(advanced);
 893         builtinColumn.setVisible(advanced);
 894     }
 895 
 896     private void showStyled(ObservableList<CssProperty> currentModel) {
 897         if (styledOnly) {
 898             currentModel = extractStyled(currentModel);
 899         }
 900         table.getItems().setAll(currentModel);
 901     }
 902 
 903     private static ObservableList<CssProperty> extractStyled(ObservableList<CssProperty> currentModel) {
 904         ObservableList<CssProperty> ret = FXCollections.observableArrayList();
 905         for (CssProperty prop : currentModel) {
 906             if (prop.isAuthorSource() || prop.isInlineSource() || prop.isModelSource()) {
 907                 ret.add(prop);
 908             }
 909         }
 910         return ret;
 911     }
 912 
 913     private static String nodeIdentifier(Node n) {
 914         if (n.getId() != null && !n.getId().equals("")) {//NOI18N
 915             return n.getId();
 916         } else {
 917             return n.getClass().getSimpleName();
 918         }
 919     }
 920 
 921     /*
 922      *
 923      * TABLE CELLS CONTENT
 924      *
 925      */
 926     // "Properties" column
 927     private static class CssPropertyTableCell extends TableCell<CssProperty, CssProperty> {
 928 
 929         CssPropertyTableCell() {
 930             getStyleClass().add("property-background");//NOI18N
 931         }
 932 
 933         @Override
 934         public void updateItem(final CssProperty item, boolean empty) {
 935             super.updateItem(item, empty);
 936             if (empty) {
 937                 setGraphic(null);
 938             } else {
 939                 Hyperlink hl = new Hyperlink();
 940                 hl.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
 941                 hl.setOnAction(new LinkActionListener(item));
 942                 hl.setAlignment(Pos.CENTER_LEFT);
 943                 if (item.getMainProperty() != null) {
 944                     hl.setText("     " + item.propertyName().get());//NOI18N
 945                 } else {
 946                     hl.setText(item.propertyName().get());
 947                 }
 948                 setGraphic(hl);
 949                 setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
 950             }
 951         }
 952     }
 953 
 954     private static class LinkActionListener implements EventHandler<ActionEvent> {
 955 
 956         final CssProperty item;
 957 
 958         public LinkActionListener(CssProperty item) {
 959             this.item = item;
 960         }
 961 
 962         @Override
 963         public void handle(ActionEvent event) {
 964             try {
 965                 // XXX jfdenise, for now can't do better than opening the file, no Anchor per property...
 966                 // Retrieve defining class
 967                 EditorPlatform.open(EditorPlatform.JAVADOC_HOME
 968                         + "javafx/scene/doc-files/cssref.html#" + //NOI18N
 969                         item.getTarget().getClass().getSimpleName().toLowerCase(Locale.ROOT));
 970             } catch (IOException ex) {
 971                 System.out.println(ex.getMessage());
 972             }
 973         }
 974     }
 975 
 976     // Css value cell content
 977     private abstract class CssValueTableCell extends TableCell<CssProperty, CssProperty> {
 978 
 979         private final List<Value> values = new ArrayList<>();
 980         private VBox valueBox;
 981         private MenuButton navigationMenuButton;
 982         private FadeTransition fadeTransition;
 983         private final MenuItem revealInInspectorMenuItem = new MenuItem(I18N.getString("csspanel.reveal.inspector"));
 984         private final MenuItem revealInFileBrowserMenuItem = new MenuItem();
 985         private MenuItem openStylesheetMenuItem = new MenuItem();
 986 
 987         // TODO : UI should be in an fxml file
 988         private class Value extends AnchorPane {
 989 
 990             private final VBox vbox;
 991             private Label sourceLabel;
 992             private MenuButton navigationMenuButton;
 993 
 994             private Value(Node value) {
 995                 vbox = new VBox(2);
 996                 setAlignment(Pos.CENTER_LEFT);
 997                 vbox.getChildren().add(value);
 998                 getChildren().add(vbox);
 999                 AnchorPane.setTopAnchor(vbox, 4.0);
1000                 AnchorPane.setLeftAnchor(vbox, 4.0);
1001                 AnchorPane.setRightAnchor(vbox, 4.0);
1002                 AnchorPane.setBottomAnchor(vbox, 4.0);
1003                 setOnMouseEntered(arg0 -> {
1004                     if ((navigationMenuButton != null) && !navigationMenuButton.isShowing()) {
1005                         fadeMenuButtonTo(1);
1006                     }
1007                 });
1008                 setOnMouseExited(arg0 -> {
1009                     if ((navigationMenuButton != null) && !navigationMenuButton.isShowing()) {
1010                         fadeMenuButtonTo(0);
1011                     }
1012                 });
1013             }
1014 
1015             private void setSource(Label sourceLabel) {
1016                 this.sourceLabel = sourceLabel;
1017                 vbox.getChildren().add(sourceLabel);
1018             }
1019 
1020             private void setNavigation(Label navigationLabel, MenuButton navigationMenuButton) {
1021                 this.navigationMenuButton = navigationMenuButton;
1022                 vbox.getChildren().add(navigationLabel);
1023                 if (navigationMenuButton != null) {
1024                     getChildren().add(navigationMenuButton);
1025                     AnchorPane.setTopAnchor(navigationMenuButton, 4.0);
1026                     AnchorPane.setRightAnchor(navigationMenuButton, 4.0);
1027                 }
1028             }
1029 
1030             private void showSource(boolean show) {
1031                 if (sourceLabel != null) {
1032                     sourceLabel.setVisible(show);
1033                 }
1034                 if (navigationMenuButton != null) {
1035                     navigationMenuButton.setVisible(show);
1036                 }
1037             }
1038 
1039             private void fadeMenuButtonTo(double toValue) {
1040                 fadeTransition.stop();
1041                 fadeTransition.setFromValue(navigationMenuButton.getOpacity());
1042                 fadeTransition.setToValue(toValue);
1043                 fadeTransition.play();
1044             }
1045         }
1046 
1047         private CssValueTableCell() {
1048             //  showSources.selectedProperty().addListener(new WeakChangeListener<>(sourceListener));
1049         }
1050 
1051         /**
1052          * WARNING: TableCell instances are reused by TableView. It must be
1053          * stateless. In our case, value, sourceLabel and navigationLabel MUST
1054          * be cleared each time updateItem is called.
1055          *
1056          * @param item
1057          * @param empty
1058          */
1059         @Override
1060         public void updateItem(CssProperty item, boolean empty) {
1061             super.updateItem(item, empty);
1062 //            System.out.println("CssValueTableCell.updateItem() called for property: " + item.getStyleable().getProperty() );
1063             values.clear();
1064             setGraphic(null);
1065             if (!empty) {
1066                 if (getStyle(item) != null && !getStyle(item).isUsed()) {//eg: -fx-backgroundfills
1067                     return;
1068                 }
1069                 // A new node MUST be constructed on each call, otherwise TableView looses the UI<->model relationship
1070                 valueBox = new VBox(2);
1071                 Value currentValue;
1072                 valueBox.setAlignment(Pos.CENTER);
1073                 PropertyState cssState = getPropertyState(item);
1074                 if (cssState != null) {
1075                     Node n;
1076                     n = createValueUI(item, cssState, cssState.getFxValue(), getStyle(item));
1077                     if (n == null) {
1078                         Label label = new Label(cssState.getCssValue());
1079                         n = label;
1080                     }
1081                     currentValue = new Value(n);
1082                     if (isWinner(item)) {
1083                         currentValue.getStyleClass().add("winner-background");//NOI18N
1084                     }
1085                     values.add(currentValue);
1086                     handleSource(currentValue, item, getStyle(item));
1087                 }
1088 
1089                 handleNotApplied(item);
1090 
1091                 displayValues();
1092             }
1093         }
1094 
1095         private void displayValues() {
1096             for (Value v : values) {
1097                 VBox.setVgrow(v, Priority.ALWAYS);
1098                 valueBox.getChildren().add(v);
1099             }
1100             setGraphic(valueBox);
1101             setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
1102         }
1103 
1104         protected abstract PropertyState getPropertyState(CssProperty item);
1105 
1106         protected abstract CssStyle getStyle(CssProperty item);
1107 
1108         protected abstract boolean isWinner(CssProperty item);
1109 
1110         protected abstract StyleOrigin getOrigin(CssProperty item);
1111 
1112         protected String getNavigation(CssProperty item, CssStyle style) {
1113             return getNavigationInfo(item, style, getOrigin(item));
1114         }
1115 
1116         private void handleNotApplied(CssProperty item) {
1117             CssPropertyState ps = item.getWinner();
1118             if (ps != null) {
1119                 for (CssStyle style : ps.getNotAppliedStyles()) {
1120                     if (style.getOrigin() == getOrigin(item) && !CssContentMaker.containsPseudoState(style.getSelector())) {
1121                         Node n = createValueUI(item, style);
1122                         if (n == null) {
1123                             n = getLabel(style);
1124                         }
1125                         Value currentValue = new Value(n);
1126                         values.add(currentValue);
1127                         handleSource(currentValue, item, style);
1128                     }
1129                 }
1130             }
1131             // Case where an API call has been made, not applied fxTheme (if any) are hidden
1132             if (getOrigin(item) == StyleOrigin.USER_AGENT) {
1133                 List<CssPropertyState.CssStyle> styles = item.getFxThemeHiddenByModel();
1134                 for (CssPropertyState.CssStyle style : styles) {
1135                     Node n = createValueUI(item, style);
1136                     if (n == null) {
1137                         String l = CssValueConverter.toCssString(style.getCssProperty(), style.getCssRule(), style.getParsedValue());
1138                         Label label = new Label(l);
1139                         n = label;
1140                     }
1141                     Value currentValue = new Value(n);
1142                     values.add(currentValue);
1143                     handleSource(currentValue, item, style);
1144                 }
1145             }
1146         }
1147 
1148         private void handleSource(Value currentValue, final CssProperty item, final CssStyle style) {
1149             if (style != null && !style.isUsed()) {//eg: -fx-background-fills;
1150                 return;
1151             }
1152             final StyleOrigin origin = getOrigin(item);
1153             String source = getSourceInfo(item, style, origin);
1154             if (source != null) {
1155                 Label sourceLabel = new Label(source);
1156                 sourceLabel.getStyleClass().add("note-label");//NOI18N
1157                 currentValue.setSource(sourceLabel);
1158             }
1159             String nav = getNavigation(item, style);
1160             if (nav != null) {
1161                 Label navigationLabel = new Label(nav);
1162                 navigationLabel.getStyleClass().add("note-label");//NOI18N
1163                 if (origin != null && origin != StyleOrigin.USER_AGENT) {// No arrow for builtin and fxTheme
1164                     createNavigationMenuButton();
1165                     openStylesheetMenuItem
1166                             = new MenuItem(MessageFormat.format(I18N.getString("csspanel.open.stylesheet"), nav));
1167                     if ((origin == StyleOrigin.USER) || (origin == StyleOrigin.INLINE)) {
1168                         // Inspector or Inline columns
1169                         navigationMenuButton.getItems().add(revealInInspectorMenuItem);
1170                         revealInInspectorMenuItem.setOnAction(event -> navigate(item, getPropertyState(item), style, origin));
1171                         if (CssPanelController.this.applicationDelegate == null) {
1172                             // disable the menu item in this case
1173                             revealInInspectorMenuItem.setDisable(true);
1174                         }
1175                     } else if (origin == StyleOrigin.AUTHOR) {
1176                         // Stylesheets column
1177                         navigationMenuButton.getItems().add(openStylesheetMenuItem);
1178                         navigationMenuButton.getItems().add(revealInFileBrowserMenuItem);
1179                         revealInFileBrowserMenuItem.setText(EditorPlatform.IS_MAC
1180                                 ? MessageFormat.format(I18N.getString("csspanel.reveal.finder"), nav)
1181                                 : MessageFormat.format(I18N.getString("csspanel.reveal.explorer"), nav));
1182                         revealInFileBrowserMenuItem.setOnAction(event -> navigate(item, getPropertyState(item), style, origin));
1183                         openStylesheetMenuItem.setOnAction(event -> open(item, getPropertyState(item), style, origin));
1184                     }
1185                 }
1186                 currentValue.setNavigation(navigationLabel, navigationMenuButton);
1187             }
1188             currentValue.showSource(true);//showSources.isSelected()
1189         }
1190 
1191         private void createNavigationMenuButton() {
1192             navigationMenuButton = new MenuButton();
1193 
1194             Region region = new Region();
1195             navigationMenuButton.setGraphic(region);
1196             region.getStyleClass().add("cog-shape"); //NOI18N
1197 
1198             navigationMenuButton.setOpacity(0);
1199             navigationMenuButton.getStyleClass().addAll("css-panel-cog-menubutton"); //NOI18N
1200             fadeTransition = new FadeTransition(Duration.millis(500), navigationMenuButton);
1201         }
1202     }
1203 
1204     // "API defaults" column
1205     private class BuiltinValueTableCell extends CssValueTableCell {
1206 
1207         @Override
1208         protected PropertyState getPropertyState(CssProperty item) {
1209             return item.builtinState().get();
1210         }
1211 
1212         @Override
1213         protected boolean isWinner(CssProperty item) {
1214             return item.isBuiltinSource();
1215         }
1216 
1217         @Override
1218         protected StyleOrigin getOrigin(CssProperty item) {
1219             return null;
1220         }
1221 
1222         @Override
1223         protected CssStyle getStyle(CssProperty item) {
1224             return null;
1225         }
1226     }
1227 
1228     // "FX Theme defaults" column
1229     private class FxThemeValueTableCell extends CssValueTableCell {
1230 
1231         @Override
1232         protected PropertyState getPropertyState(CssProperty item) {
1233             return item.fxThemeState().get();
1234         }
1235 
1236         @Override
1237         protected boolean isWinner(CssProperty item) {
1238             return item.isFxThemeSource();
1239         }
1240 
1241         @Override
1242         protected StyleOrigin getOrigin(CssProperty item) {
1243             return StyleOrigin.USER_AGENT;
1244         }
1245 
1246         @Override
1247         protected CssStyle getStyle(CssProperty item) {
1248             CssPropertyState ps = item.fxThemeState().get();
1249             return ps == null ? null : ps.getStyle();
1250         }
1251     }
1252 
1253     // "Inspector" column
1254     private class ModelValueTableCell extends CssValueTableCell {
1255 
1256         @Override
1257         protected PropertyState getPropertyState(CssProperty item) {
1258             return item.modelState().get();
1259         }
1260 
1261         @Override
1262         protected boolean isWinner(CssProperty item) {
1263             return item.isModelSource();
1264         }
1265 
1266         @Override
1267         protected StyleOrigin getOrigin(CssProperty item) {
1268             return StyleOrigin.USER;
1269         }
1270 
1271         @Override
1272         protected CssStyle getStyle(CssProperty item) {
1273             return null;
1274         }
1275     }
1276 
1277     // "Stylesheets" column
1278     private class AuthorValueTableCell extends CssValueTableCell {
1279 
1280         @Override
1281         protected PropertyState getPropertyState(CssProperty item) {
1282             return item.authorState().get();
1283         }
1284 
1285         @Override
1286         protected boolean isWinner(CssProperty item) {
1287             return item.isAuthorSource();
1288         }
1289 
1290         @Override
1291         protected StyleOrigin getOrigin(CssProperty item) {
1292             return StyleOrigin.AUTHOR;
1293         }
1294 
1295         @Override
1296         protected CssStyle getStyle(CssProperty item) {
1297             CssPropertyState ps = item.authorState().get();
1298             return ps == null ? null : ps.getStyle();
1299         }
1300     }
1301 
1302     // "Inline Styles" column
1303     private class InlineValueTableCell extends CssValueTableCell {
1304 
1305         @Override
1306         protected PropertyState getPropertyState(CssProperty item) {
1307             return item.inlineState().get();
1308         }
1309 
1310         @Override
1311         protected boolean isWinner(CssProperty item) {
1312             return item.isInlineSource();
1313         }
1314 
1315         @Override
1316         protected StyleOrigin getOrigin(CssProperty item) {
1317             return StyleOrigin.INLINE;
1318         }
1319 
1320         @Override
1321         protected CssStyle getStyle(CssProperty item) {
1322             CssPropertyState ps = item.inlineState().get();
1323             return ps == null ? null : ps.getStyle();
1324         }
1325     }
1326 
1327     /**
1328      * XXX jfdenise, handle case where the Fx Theme is not the winning style.
1329      * The complex case is that we need to return a propertyState BUT we don't
1330      * know if Fx Theme has been overriden, then we do return null.
1331      */
1332     // "Defaults" column
1333     private class DefaultValueTableCell extends CssValueTableCell {
1334 
1335         @Override
1336         protected PropertyState getPropertyState(CssProperty item) {
1337             PropertyState ret = item.fxThemeState().get();
1338             if (ret == null) {
1339                 // Do we have an override
1340                 boolean foundNotApplied = false;
1341                 PropertyState winner = item.getWinner();
1342                 if (winner != null) {
1343                     for (CssStyle np : winner.getNotAppliedStyles()) {
1344                         // Not applied handling will had the value.
1345                         if (np.getOrigin() == StyleOrigin.USER_AGENT) {
1346                             foundNotApplied = true;
1347                             break;
1348                         }
1349                     }
1350                 }
1351                 if (!foundNotApplied) {
1352                     ret = item.builtinState().get();
1353                 }
1354             }
1355             return ret;
1356         }
1357 
1358         @Override
1359         protected CssStyle getStyle(CssProperty item) {
1360             CssStyle style = null;
1361             CssPropertyState fxTheme = item.fxThemeState().get();
1362             if (fxTheme == null) {
1363                 // Do we have an override
1364                 PropertyState winner = item.getWinner();
1365                 if (winner != null) {
1366                     for (CssStyle np : winner.getNotAppliedStyles()) {
1367                         // Not applied handling will had the value.
1368                         if (np.getOrigin() == StyleOrigin.USER_AGENT) {
1369                             style = np;
1370                             break;
1371                         }
1372                     }
1373                 }
1374 
1375             } else {
1376                 style = fxTheme.getStyle();
1377             }
1378             return style;
1379         }
1380 
1381         @Override
1382         protected boolean isWinner(CssProperty item) {
1383             return item.isFxThemeSource() || item.isBuiltinSource();
1384         }
1385 
1386         @Override
1387         protected StyleOrigin getOrigin(CssProperty item) {
1388             PropertyState state = getPropertyState(item);
1389             // If null is returned, means that there is a not applied for fxTheme
1390             if (state instanceof CssPropertyState || state == null) {
1391                 return StyleOrigin.USER_AGENT;
1392             } else {
1393                 return null;
1394             }
1395         }
1396 
1397         // Special case, display some info when merged.
1398         @Override
1399         protected String getNavigation(CssProperty item, CssStyle style) {
1400             PropertyState ps = getPropertyState(item);
1401             if (ps == null || ps instanceof CssPropertyState) {
1402                 return I18N.getString("csspanel.fxtheme.defaults.navigation")
1403                         + " (" + CssInternal.getThemeDisplayName(style.getStyle()) + ")";//NOI18N
1404             } else {
1405                 return I18N.getString("csspanel.api.defaults.navigation");
1406             }
1407         }
1408     }
1409 
1410     /*
1411      *
1412      *
1413      * NAVIGATION (to inspector, file explorer, css editor, ...
1414      *
1415      *
1416      */
1417     private void open(CssProperty item, PropertyState state, CssStyle style, StyleOrigin origin) {
1418         navigate(item, state, style, origin, true);
1419     }
1420 
1421     private void navigate(CssProperty item, PropertyState state, CssStyle style, StyleOrigin origin) {
1422         navigate(item, state, style, origin, false);
1423     }
1424 
1425     private void navigate(CssProperty item, PropertyState state, CssStyle style, StyleOrigin origin, boolean open) {
1426 
1427         if (origin == StyleOrigin.USER) {// Navigate to property
1428             PropertyName propName = ((BeanPropertyState) state).getPropertyMeta().getName();
1429             // Navigate to inspector property
1430             if (applicationDelegate != null) {
1431                 applicationDelegate.revealInspectorEditor(getValuePropertyMeta(propName));
1432             }
1433         }
1434         if (style != null) {
1435             if (style.getOrigin() == StyleOrigin.AUTHOR) {// Navigate to file
1436                 URL url = style.getUrl();
1437                 String path = url.toExternalForm();
1438                 if (path.toLowerCase(Locale.ROOT).startsWith("file:/")) { //NOI18N
1439                     try {
1440                         if (open) {
1441                             EditorPlatform.open(path);
1442                         } else {
1443                             File f = new File(url.toURI());
1444                             EditorPlatform.revealInFileBrowser(f);
1445                         }
1446                     } catch (URISyntaxException | IOException ex) {
1447                         System.out.println(ex.getMessage() + ": " + ex);
1448                     }
1449                 }
1450             } else {
1451                 if (style.getOrigin() == StyleOrigin.INLINE) {
1452                     // Navigate to inspector style property
1453                     if (applicationDelegate != null) {
1454                         applicationDelegate.revealInspectorEditor(
1455                                 getValuePropertyMeta(new PropertyName("style"))); //NOI18N
1456                     }
1457                 }
1458             }
1459         }
1460     }
1461 
1462     private static String getNavigationInfo(
1463             CssProperty item, CssStyle cssStyle, StyleOrigin origin) {
1464         if (origin == StyleOrigin.USER_AGENT) {
1465             return CssInternal.getThemeDisplayName(cssStyle.getStyle());
1466         }
1467         if (origin == StyleOrigin.USER) {
1468             BeanPropertyState state = (BeanPropertyState) item.modelState().get();
1469             return item.getTarget().getClass().getSimpleName() + "."
1470                     + state.getPropertyMeta().getName().getName();//NOI18N
1471         }
1472         if (origin == StyleOrigin.AUTHOR) {
1473             if (cssStyle != null) {
1474                 URL url = cssStyle.getUrl();
1475                 String name = null;
1476                 if (url != null) {
1477                     name = url.toExternalForm();
1478                     if (name.toLowerCase(Locale.ROOT).startsWith("file:/")) { //NOI18N
1479                         try {
1480                             File f = new File(url.toURI());
1481                             name = f.getName();
1482                         } catch (URISyntaxException ex) {
1483                             System.out.println(ex.getMessage() + ": " + ex);
1484                         }
1485                     }
1486                 }
1487                 return name;
1488             }
1489         }
1490         if (origin == StyleOrigin.INLINE) {
1491             Node n = item.getSourceNodeForInline();
1492             if (n != null) {
1493                 return nodeIdentifier(n);
1494             }
1495             return null;
1496         }
1497 
1498         return null;
1499     }
1500 
1501     /*
1502      *
1503      * Private static
1504      *
1505      */
1506     private static FXOMInstance getFXOMInstance(Selection selection) {
1507         FXOMInstance fxomInstance = null;
1508         if (selection == null) {
1509             return null;
1510         }
1511         if (selection.getGroup() instanceof ObjectSelectionGroup) {
1512             final ObjectSelectionGroup osg = (ObjectSelectionGroup) selection.getGroup();
1513             for (FXOMObject item : osg.getItems()) {
1514                 if (item instanceof FXOMInstance) {
1515                     fxomInstance = (FXOMInstance) item;
1516                 }
1517             }
1518         }
1519 
1520         // In case of GridSelectionGroup, nothing to show (?)
1521         return fxomInstance;
1522     }
1523 
1524     private ValuePropertyMetadata getValuePropertyMeta(PropertyName propName) {
1525         ValuePropertyMetadata valuePropMeta = null;
1526         if (selectedObject instanceof FXOMInstance) {
1527             valuePropMeta = Metadata.getMetadata().queryValueProperty(
1528                     (FXOMInstance) selectedObject, propName);
1529         }
1530         return valuePropMeta;
1531     }
1532 
1533     private static String getPseudoStates(Node node) {
1534         StringBuilder pseudoClasses = new StringBuilder();
1535         Set<PseudoClass> pseudoClassSet = node.getPseudoClassStates();
1536         for (PseudoClass pc : pseudoClassSet) {
1537             pseudoClasses.append(":").append(pc.getPseudoClassName()); //NOI18N
1538         }
1539         return pseudoClasses.toString();
1540     }
1541 
1542     // Best effort to express a potential selector. There is more than one...
1543     private static String localSelector(Node node) {
1544         String ret = "";//NOI18N
1545         String pseudoClasses = getPseudoStates(node);
1546         if (!node.getStyleClass().isEmpty()) {
1547             ret = "." + node.getStyleClass().get(node.getStyleClass().size() - 1) + pseudoClasses;//NOI18N
1548         } else if (node.getId() != null && !node.getId().equals("")) {//NOI18N
1549             ret = "#" + node.getId() + pseudoClasses;//NOI18N
1550         }
1551         return ret;
1552     }
1553 
1554     private static String createItemName(Node n) {
1555         return localSelector(n);
1556     }
1557 
1558     private static String createOptional(Node n) {
1559         return "(" + getFirstStandardClassName(n.getClass()) + ")";//NOI18N
1560     }
1561 
1562     private static Node getContent(String property, String cssValue, Object value, boolean applied) {
1563         HBox hbox = new HBox();
1564         Node l = createPropertyLabel(property + ": ", applied);//NOI18N
1565         hbox.getChildren().add(l);
1566         // Custom content. Mainly for paints and images
1567         Node n = getCustomContent(value);
1568         if (n != null) {
1569             hbox.getChildren().add(n);
1570         } else {
1571             Node cssValueNode = createLabel(cssValue + ";", applied);//NOI18N
1572             hbox.getChildren().add(cssValueNode);
1573         }
1574         return CopyHandler.makeCopyableNode(hbox, property + ": " + cssValue + ";");//NOI18N
1575     }
1576 
1577     private static Node getCustomContent(Object value) {
1578         Node ret = null;
1579         if (value instanceof ParsedValue) {
1580             ParsedValue<?, ?> pv = (ParsedValue<?, ?>) value;
1581             value = CssValueConverter.convert(pv);
1582         }
1583         if (value != null) {
1584             if (value.getClass().isArray()) {
1585                 HBox hbox = new HBox(5);
1586                 int size = Array.getLength(value);
1587                 for (int i = 0; i < size; i++) {
1588                     Node n = getCustomContent(Array.get(value, i));
1589                     if (n != null) {
1590                         hbox.getChildren().add(n);
1591                         if (i < size - 1) {
1592                             hbox.getChildren().add(new Label(", "));//NOI18N
1593                         }
1594                     }
1595                 }
1596                 if (!hbox.getChildren().isEmpty()) {
1597                     ret = hbox;
1598                 }
1599             } else {
1600                 if (value instanceof Collection) {
1601                     HBox hbox = new HBox(5);
1602                     Collection<?> collection = (Collection<?>) value;
1603                     Iterator<?> it = collection.iterator();
1604                     while (it.hasNext()) {
1605                         Object obj = it.next();
1606                         Node n = getCustomContent(obj);
1607                         if (n != null) {
1608                             hbox.getChildren().add(n);
1609                             if (it.hasNext()) {
1610                                 hbox.getChildren().add(new Label(", "));//NOI18N
1611                             }
1612                         }
1613                     }
1614                     if (!hbox.getChildren().isEmpty()) {
1615                         ret = hbox;
1616                     }
1617                 } else {// Leaf value
1618                     CssValuePresenter<?> presenter = CssValuePresenterFactory.getInstance().newValuePresenter(value);
1619                     Node customPresenter = presenter.getCustomPresenter();
1620                     ret = customPresenter;
1621                 }
1622             }
1623         }
1624         return ret;
1625     }
1626 
1627     private static Node createLabel(String text, String styleclass, boolean isApplied) {
1628         Node node = new Label(text);
1629         if (styleclass != null) {
1630             node.getStyleClass().add(styleclass);
1631         }
1632         if (!isApplied) {
1633             node = createLine((Label) node);
1634         }
1635         return node;
1636     }
1637 
1638     private static Node createLabel(String text, boolean isApplied) {
1639         return createLabel(text, null, isApplied);
1640     }
1641 
1642     private static Node createPropertyLabel(String text, boolean isApplied) {
1643         return createLabel(text, "css-panel-property", isApplied);//NOI18N
1644     }
1645 
1646     private static Node createLine(Node node) {
1647         StackPane sp = new StackPane();
1648         sp.getChildren().add(node);
1649         Separator s = new Separator(Orientation.HORIZONTAL);
1650         s.setValignment(VPos.CENTER);
1651         s.getStyleClass().add("notAppliedStyleLine");//NOI18N
1652         sp.getChildren().add(s);
1653         return sp;
1654     }
1655 
1656     private static TreeItem<Node> attachSource(PropertyState css, CssStyle cssStyle, TreeItem<Node> parent, boolean applied) {
1657         String source = getSource(cssStyle);
1658         TreeItem<Node> srcItem = null;
1659         if (source != null) {
1660             HBox hbox = new HBox(5);
1661             if (cssStyle.getOrigin() != StyleOrigin.INLINE) {
1662                 Label selector = new Label(cssStyle.getSelector());
1663                 // Workaround RT layout bug
1664                 selector.setMinWidth(30);
1665                 hbox.getChildren().add(selector);
1666                 hbox.getChildren().add(new Label("{"));//NOI18N
1667             }
1668             hbox.getChildren().add(createLabel(cssStyle.getCssProperty() + ": ", applied));//NOI18N
1669             Node n = getCustomContent(cssStyle.getParsedValue());
1670             if (n != null) {
1671                 hbox.getChildren().add(n);
1672             }
1673             Node label2 = createLabel(CssValueConverter.toCssString(cssStyle.getCssProperty(), cssStyle.getCssRule(), cssStyle.getParsedValue())
1674                     + ";", applied);//NOI18N
1675             hbox.getChildren().add(label2);
1676             if (cssStyle.getOrigin() != StyleOrigin.INLINE) {
1677                 hbox.getChildren().add(new Label("}"));//NOI18N
1678             }
1679             Label label = new Label(source);
1680             hbox.getChildren().add(label);
1681             srcItem = newTreeItem(hbox, css);
1682             parent.getChildren().add(srcItem);
1683         }
1684         return srcItem;
1685     }
1686 
1687     private static void attachStyle(PropertyState css, CssStyle style, TreeItem<Node> parent, boolean applied) {
1688         attachStyle(css, style, parent, applied, null);
1689     }
1690 
1691     private static void attachStyle(PropertyState css, CssStyle style, TreeItem<Node> parent, boolean applied, ArrayList<String> cssPropertyList) {
1692         TreeItem<Node> sourceItem = attachSource(css, style, parent, applied);
1693         if (cssPropertyList != null) {
1694             cssPropertyList.add(style.getCssProperty());
1695         }
1696         if (sourceItem != null) {
1697             // Do we have a chain of lookups?
1698             for (CssStyle lookup : style.getLookupChain()) {
1699 
1700                 if ((cssPropertyList != null) && cssPropertyList.contains(lookup.getCssProperty())) {
1701                     // This css property has already been attached
1702                     continue;
1703                 }
1704                 attachStyle(css, lookup, sourceItem, applied, cssPropertyList);
1705             }
1706         }
1707     }
1708 
1709     /**
1710      *
1711      * @param component component.
1712      * @param css css property state.
1713      * @param lookupRoot root css style.
1714      * @param parent parent.
1715      * @treatAsPrivate
1716      */
1717     public static void attachLookupStyles(Object component, CssPropertyState css, CssStyle lookupRoot, TreeItem<Node> parent) {
1718         // Some lookup that comes from the SB itself, skip them.
1719         // This is expected, these lookups are superceeded by the 
1720         // CssUtils.createCSSFrontier
1721         ArrayList<String> cssPropertyList = new ArrayList<>();
1722         // cssPropertyList will allow to check that the same css property is not added multiple times
1723         attachStyle(css, lookupRoot, parent, true, cssPropertyList);
1724     }
1725 
1726     private static void attachStyles(CssPropertyState css, TreeItem<Node> parent) {
1727         if (css.getStyle() != null) {
1728             attachStyle(css, css.getStyle(), parent, true);
1729         }
1730     }
1731 
1732     private static class HtmlStyler {
1733 
1734         private final static String INIT_STRING = "<html><body>"; //NOI18N
1735         private final static String END_STRING = "</body></html>"; //NOI18N
1736         private final StringBuilder builder = new StringBuilder();
1737         private String html;
1738 
1739         HtmlStyler() {
1740             builder.append(INIT_STRING);//NOI18N
1741         }
1742 
1743         public void check() {
1744             if (html != null) {
1745                 throw new IllegalArgumentException("Locked, html already generated");//NOI18N
1746             }
1747         }
1748 
1749         public void cssRuleStart(String selector, String source) {
1750             check();
1751             builder.append("<p>");//NOI18N
1752             builder.append("<b>").append(selector).append("</b>");//NOI18N
1753             builder.append("&nbsp;<b>{</b>&nbsp;").append("/*&nbsp;").append(source).append("&nbsp;*/");//NOI18N
1754         }
1755 
1756         public void cssRuleEnd() {
1757             check();
1758             builder.append("<br>");//NOI18N
1759             builder.append("<b>}</b>");//NOI18N
1760             builder.append("</p>");//NOI18N
1761         }
1762 
1763         public void addProperty(String name, String content, boolean applied) {
1764             check();
1765             String sepName = name + ":&nbsp;";//NOI18N
1766             String propName = "<b>" + (applied ? sepName : "<strike>" + sepName + "</strike>") + "</b>";//NOI18N
1767             builder.append("<br>");//NOI18N
1768             content = applied ? content : "<strike>" + content + "</strike>";//NOI18N
1769             builder.append("<span style=\"margin-left:10px;\">").append(propName).append(content).append(";").append("</span>");//NOI18N
1770         }
1771 
1772         public void addMessage(String mess) {
1773             check();
1774             builder.append(mess);
1775         }
1776 
1777         public String getHtmlString() {
1778             if (html == null) {
1779                 builder.append(END_STRING);//NOI18N
1780                 html = builder.toString();
1781             }
1782             return html;
1783         }
1784 
1785         public boolean isEmpty() {
1786             return builder.toString().equals(INIT_STRING);
1787         }
1788     }
1789 
1790     /**
1791      *
1792      * @treatAsPrivate
1793      */
1794     public void copyRules() {
1795         CopyHandler.copy(rulesTree);
1796     }
1797 
1798     private static class CopyHandler {
1799 
1800         private static String getContent(TreeView<Node> tv) {
1801             StringBuilder builder = new StringBuilder();
1802             for (TreeItem<Node> item : tv.getSelectionModel().getSelectedItems()) {
1803                 Node n = item.getValue();
1804                 String str = (String) n.getProperties().get(CSS_TEXT);
1805                 if (str != null) {
1806                     builder.append(str);
1807                 }
1808             }
1809             return builder.toString();
1810         }
1811 
1812         private static void copy(final TreeView<Node> tv) {
1813             final String cssContent = getContent(tv);
1814             final ClipboardContent content = new ClipboardContent();
1815             content.putString(cssContent);
1816             Clipboard.getSystemClipboard().setContent(content);
1817         }
1818 
1819         private static void attachContextMenu(final TreeView<Node> tv) {
1820             ContextMenu ctxMenu = new ContextMenu();
1821             final MenuItem cssContentAction = new MenuItem(I18N.getString("csspanel.copy"));
1822             ctxMenu.setOnShowing(arg0 -> {
1823             });
1824             tv.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
1825 
1826             cssContentAction.setOnAction(arg0 -> copy(tv));
1827 
1828             ctxMenu.getItems().add(cssContentAction);
1829             tv.setContextMenu(ctxMenu);
1830         }
1831         private static final String CSS_TEXT = "CSS_TEXT";//NOI18N
1832 
1833         private static <T extends Node> T makeCopyableNode(T node, String str) {
1834             node.getProperties().put(CSS_TEXT, str + "\n");//NOI18N
1835             return node;
1836         }
1837 
1838         private static Text createCopyableText(String str) {
1839             Text text = new Text(str);
1840             text.getProperties().put(CSS_TEXT, str + "\n");//NOI18N
1841             return text;
1842         }
1843     }
1844 
1845     private static void setTreeHeight(TreeView<?> tv) {
1846         // XXX jfdenise how to properly compute the height of the tree
1847         int minHeight = 70;
1848         int topLevelItems = tv.getRoot().getChildren().size();
1849         int computed = topLevelItems == 0 ? 15 : (25 * topLevelItems);
1850         tv.setPrefHeight(Math.max(minHeight, computed));
1851     }
1852 
1853     private static String nonNull(String string) {
1854         if (string == null) {
1855             return ""; //NOI18N
1856         } else {
1857             return string;
1858         }
1859     }
1860 
1861     private static TreeItem<Node> newTreeItem(Node content, PropertyState ss) {
1862         TreeItem<Node> ti = new TreeItem<>(content);
1863         return ti;
1864     }
1865 
1866     private static String getSource(CssStyle style) {
1867         URL url = style.getUrl();
1868         String source = null;
1869         if (url != null) {
1870             source = getSource(url, style.getOrigin());
1871         }
1872         return source;
1873     }
1874 
1875     private static String getSource(Rule rule) {
1876         URL url = null;
1877         StyleOrigin origin = null;
1878         // Workaround!
1879         if (rule != null) {
1880             try {
1881                 url = new URL(rule.getStylesheet().getUrl());
1882             } catch (MalformedURLException ex) {
1883                 System.out.println("Invalid URL: " + ex);
1884             }
1885             origin = rule.getOrigin();
1886         }
1887         return getSource(url, origin);
1888     }
1889 
1890     private static String getSource(URL url, StyleOrigin origin) {
1891         String source = null;
1892         if (url != null) {
1893             if (origin == StyleOrigin.USER_AGENT) {
1894                 source = I18N.getString("csspanel.fxtheme.origin");
1895             } else {
1896                 if (origin == StyleOrigin.USER) {
1897                     source = I18N.getString("csspanel.api.origin")
1898                             + " " //NOI18N
1899                             + I18N.getString("csspanel.node.property");
1900                 } else {
1901                     if (origin == StyleOrigin.AUTHOR) {
1902                         source = url.toExternalForm();
1903                     }
1904                 }
1905             }
1906         }
1907         return source;
1908     }
1909 
1910     private static String getSourceInfo(CssProperty item, CssStyle style, StyleOrigin origin) {
1911         if (origin == StyleOrigin.USER_AGENT) {
1912             if (style != null) {
1913                 return style.getSelector();
1914             }
1915             return null;
1916         }
1917         if (origin == StyleOrigin.USER) {
1918             BeanPropertyState state = (BeanPropertyState) item.modelState().get();
1919             return state.getPropertyMeta().getName().getName();
1920         }
1921         if (origin == StyleOrigin.AUTHOR) {
1922             if (style != null) {
1923                 return style.getSelector();
1924             }
1925             return null;
1926         }
1927         if (origin == StyleOrigin.INLINE) {
1928             boolean inherited = item.isInlineInherited();
1929             return "style" + (inherited ? " (" + I18N.getString("csspanel.inherited") + ")" : "");//NOI18N
1930         }
1931 
1932         return null;
1933     }
1934 
1935     private static Node createValueUI(CssProperty item, CssStyle style) {
1936         ParsedValue<?, ?> value = null;
1937         if (style != null && !style.getLookupChain().isEmpty()) {
1938             if (style.getLookupChain().size() == 1) {
1939                 value = style.getLookupChain().get(0).getParsedValue();
1940             } else {
1941                 value = style.getParsedValue();
1942             }
1943         } else {
1944             if (style != null) {
1945                 value = style.getParsedValue();
1946             }
1947         }
1948 
1949         return createValueUI(item, null, value, style);
1950     }
1951 
1952     private static Node createValueUI(CssProperty item, PropertyState ps, Object value, CssStyle style) {
1953         ParsedValue<?, ?>[] parsedValues = null;
1954         if (style != null) {
1955             ParsedValue<?, ?> pv = style.getParsedValue();
1956             Object v = pv.getValue();
1957             if (v instanceof ParsedValue<?, ?>[]) {//Means lookups
1958                 parsedValues = (ParsedValue<?, ?>[]) v;
1959             }
1960         }
1961         return createValueUI(item, ps, value, style, parsedValues);
1962     }
1963 
1964     private static Node createValueUI(CssProperty item, PropertyState ps, Object value, CssStyle style, ParsedValue<?, ?>[] parsedValues) {
1965         Node ret = null;
1966         if (value instanceof ParsedValue) {
1967             ParsedValue<?, ?> pv = (ParsedValue<?, ?>) value;
1968             value = CssValueConverter.convert(pv);
1969         }
1970         if (value != null) {
1971             if (value.getClass().isArray()) {
1972                 HBox hbox = new HBox(5);
1973                 int size = Array.getLength(value);
1974                 int lookupIndex = 0;
1975                 for (int i = 0; i < size; i++) {
1976                     Object v = Array.get(value, i);
1977                     Node n = getLeaf(v);
1978                     if (n == null) {
1979                         break;
1980                     }
1981                     boolean lookup = false;
1982                     if (parsedValues != null) {
1983                         ParsedValue<?, ?> pv = parsedValues[i];
1984                         lookup = ((ParsedValueImpl<?, ?>) pv).isContainsLookups();
1985                     }
1986                     if (lookup) {
1987                         assert style != null;
1988                         CssStyle lookupRoot = null;
1989                         if (style.getLookupChain().size() - 1 < lookupIndex) {
1990                             // We are in NOT APPLIED case, no lookup in matching Styles.
1991                             // This is an RT bug logged.
1992                             // XXX jfdenise, we can reconstruct the lookup chain based on the ParsedValue
1993                             // We need to access private field ParsedValue.resolved
1994                             // That is a null if this is the leaf of the Lookup
1995                             // That is a ParsedValue with a getvalue that is a ParsedValue
1996                             // to introspect.
1997 //                            ParsedValue<?, ?> pv = parsedValues[i];
1998 //                            if(pv.getValue() instanceof String){
1999 //                                // OK, this is a lookup name.
2000 //                                Object obj = pv.convert(null);
2001 //                            } else {
2002 //                                
2003 //                            }
2004                         } else {
2005                             lookupRoot = style.getLookupChain().get(lookupIndex);
2006                         }
2007 
2008                         lookupIndex += 1;
2009                         Node lookupUI = createLookupUI(item, ps, style, lookupRoot, n);
2010                         hbox.getChildren().add(lookupUI);
2011                     } else {
2012                         hbox.getChildren().add(n);
2013                     }
2014                     if (i < size - 1) {
2015                         hbox.getChildren().add(new Label(","));//NOI18N
2016                     }
2017                 }
2018                 if (!hbox.getChildren().isEmpty()) {
2019                     ret = hbox;
2020                 }
2021             } else {
2022                 if (value instanceof Collection) {
2023                     HBox hbox = new HBox(5);
2024                     int lookupIndex = 0;
2025                     Collection<?> collection = (Collection<?>) value;
2026                     Iterator<?> it = collection.iterator();
2027                     int index = 0;
2028                     while (it.hasNext()) {
2029                         Object v = it.next();
2030                         Node n = getLeaf(v);
2031                         if (n == null) {
2032                             break;
2033                         }
2034                         boolean lookup = false;
2035                         if (parsedValues != null) {
2036                             ParsedValue<?, ?> pv = parsedValues[index];
2037                             lookup = ((ParsedValueImpl<?, ?>) pv).isContainsLookups();
2038                         }
2039                         if (lookup) {
2040                             CssStyle lookupRoot = null;
2041                             assert style != null;
2042                             if (style.getLookupChain().size() - 1 < lookupIndex) {
2043                                 // We are in NOT APPLIED case, no lookup in matching Styles.
2044                                 // This is an RT bug logged.
2045                                 // XXX jfdenise, we can reconstruct the lookup chain based on the ParsedValue
2046                                 // We need to access private field ParsedValue.resolved
2047                                 // That is a null if this is the leaf of the Lookup
2048                                 // That is a ParsedValue with a getvalue that is a ParsedValue
2049                                 // to introspect.
2050 //                            ParsedValue<?, ?> pv = parsedValues[i];
2051 //                            if(pv.getValue() instanceof String){
2052 //                                // OK, this is a lookup name.
2053 //                                Object obj = pv.convert(null);
2054 //                            } else {
2055 //                                
2056 //                            }
2057                             } else {
2058                                 lookupRoot = style.getLookupChain().get(lookupIndex);
2059                             }
2060                             Node lookupUI = createLookupUI(item, ps, style, lookupRoot, n);
2061                             hbox.getChildren().add(lookupUI);
2062                             lookupIndex += 1;
2063                         } else {
2064                             hbox.getChildren().add(n);
2065                         }
2066                         if (it.hasNext()) {
2067                             hbox.getChildren().add(new Label(","));//NOI18N
2068                         }
2069                         index++;
2070                     }
2071                     if (!hbox.getChildren().isEmpty()) {
2072                         ret = hbox;
2073                     }
2074                 } else {// Leaf value
2075                     Node n = getLeaf(value);
2076                     if (n == null && style != null) {
2077                         n = getLabel(style);
2078                     }
2079                     if (style != null && !style.getLookupChain().isEmpty()) {
2080                         ret = createLookupUI(item, ps, style, style, n);
2081                     } else {
2082                         ret = n;
2083                     }
2084                 }
2085             }
2086         }
2087         return ret;
2088     }
2089 
2090     private static Label getLabel(CssStyle style) {
2091         String l = CssValueConverter.toCssString(style.getCssProperty(), style.getCssRule(), style.getParsedValue());
2092         return new Label(l);
2093     }
2094     
2095     private static synchronized Image getLookupImage() {
2096         if (lookups == null) {
2097             lookups = new Image(
2098                 CssPanelController.class.getResource("images/css-lookup-icon.png").toExternalForm()); //NOI18N
2099         }
2100         
2101         return lookups;
2102     }
2103 
2104     private static Node createLookupUI(
2105             final CssProperty item, final PropertyState ps, final CssStyle style,
2106             final CssStyle lookupRoot, Node n) {
2107 
2108         // TODO: make an fxml file for this
2109         // MenuButton
2110         final HBox hbox = new HBox();
2111         hbox.setMaxWidth(Region.USE_PREF_SIZE);
2112         ImageView imgView = new ImageView();
2113         imgView.setImage(getLookupImage());
2114         hbox.getChildren().addAll(n, imgView);
2115         MenuButton lookupMb = new MenuButton();
2116         lookupMb.setGraphic(hbox);
2117         lookupMb.getStyleClass().add("lookup-button");
2118         CustomMenuItem popupContentMi = new CustomMenuItem();
2119         popupContentMi.setHideOnClick(false);
2120         lookupMb.getItems().add(popupContentMi);
2121 
2122         // Popup content
2123         StackPane popupContent = new StackPane();
2124         popupContentMi.setContent(popupContent);
2125         TreeView<Node> lookupTv = new TreeView<>();
2126         lookupTv.setPrefSize(400, 100);
2127         Object val = null;
2128         if (ps instanceof CssPropertyState) {
2129             val = ((CssPropertyState) ps).getFxValue();
2130         } else {
2131             if (style != null) {
2132                 val = style.getParsedValue();
2133             }
2134         }
2135         assert val != null;
2136         TreeItem<Node> root = new TreeItem<>();
2137         lookupTv.setRoot(root);
2138         lookupTv.setShowRoot(false);
2139         if (ps != null) {
2140             assert ps instanceof CssPropertyState;
2141             attachLookupStyles(item.getTarget(), ((CssPropertyState) ps), lookupRoot, root);
2142         } else {
2143             attachLookupStyles(item.getTarget(), null, lookupRoot, root);
2144         }
2145 
2146         popupContent.getChildren().add(lookupTv);
2147         return lookupMb;
2148     }
2149 
2150     private static Node getLeaf(Object value) {
2151         CssValuePresenterFactory.CssValuePresenter<?> presenter = CssValuePresenterFactory.getInstance().newValuePresenter(value);
2152         Node customPresenter = presenter.getCustomPresenter();
2153         return customPresenter;
2154     }
2155 
2156     private static void attachStylePropertyNoLookup(TreeItem<Node> parent,
2157             CssPropertyState ps, CssStyle style, boolean applied) {
2158         CssStyle cssStyle = applied ? ps.getStyle() : style;
2159         Object value = applied ? ps.getFxValue() : style.getParsedValue();
2160         String cssValue = CssValueConverter.toCssString(cssStyle.getCssProperty(), cssStyle.getCssRule(), cssStyle.getParsedValue());
2161         TreeItem<Node> item = new TreeItem<>(getContent(ps.getCssProperty(), cssValue, value, applied));
2162         parent.getChildren().add(item);
2163     }
2164 
2165 }