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(" <b>{</b> ").append("/* ").append(source).append(" */");//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 + ": ";//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 }