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; 33 34 import com.oracle.javafx.scenebuilder.kit.editor.EditorPlatform.Theme; 35 import com.oracle.javafx.scenebuilder.kit.editor.drag.DragController; 36 import com.oracle.javafx.scenebuilder.kit.editor.i18n.I18N; 37 import com.oracle.javafx.scenebuilder.kit.editor.job.AddContextMenuToSelectionJob; 38 import com.oracle.javafx.scenebuilder.kit.editor.job.AddTooltipToSelectionJob; 39 import com.oracle.javafx.scenebuilder.kit.editor.job.ModifySelectionJob; 40 import com.oracle.javafx.scenebuilder.kit.editor.job.BringForwardJob; 41 import com.oracle.javafx.scenebuilder.kit.editor.job.BringToFrontJob; 42 import com.oracle.javafx.scenebuilder.kit.editor.job.CutSelectionJob; 43 import com.oracle.javafx.scenebuilder.kit.editor.job.DeleteSelectionJob; 44 import com.oracle.javafx.scenebuilder.kit.editor.job.DuplicateSelectionJob; 45 import com.oracle.javafx.scenebuilder.kit.editor.job.FitToParentSelectionJob; 46 import com.oracle.javafx.scenebuilder.kit.editor.job.ImportFileJob; 47 import com.oracle.javafx.scenebuilder.kit.editor.job.IncludeFileJob; 48 import com.oracle.javafx.scenebuilder.kit.editor.job.InsertAsSubComponentJob; 49 import com.oracle.javafx.scenebuilder.kit.editor.job.Job; 50 import com.oracle.javafx.scenebuilder.kit.editor.job.PasteIntoJob; 51 import com.oracle.javafx.scenebuilder.kit.editor.job.PasteJob; 52 import com.oracle.javafx.scenebuilder.kit.editor.job.SendBackwardJob; 53 import com.oracle.javafx.scenebuilder.kit.editor.job.SendToBackJob; 54 import com.oracle.javafx.scenebuilder.kit.editor.job.SetDocumentRootJob; 55 import com.oracle.javafx.scenebuilder.kit.editor.job.TrimSelectionJob; 56 import com.oracle.javafx.scenebuilder.kit.editor.job.UseComputedSizesSelectionJob; 57 import com.oracle.javafx.scenebuilder.kit.editor.job.UsePredefinedSizeJob; 58 import com.oracle.javafx.scenebuilder.kit.editor.job.gridpane.AddColumnJob; 59 import com.oracle.javafx.scenebuilder.kit.editor.job.gridpane.AddRowJob; 60 import com.oracle.javafx.scenebuilder.kit.editor.job.gridpane.GridPaneJobUtils.Position; 61 import com.oracle.javafx.scenebuilder.kit.editor.job.gridpane.MoveColumnJob; 62 import com.oracle.javafx.scenebuilder.kit.editor.job.gridpane.MoveRowJob; 63 import com.oracle.javafx.scenebuilder.kit.editor.job.gridpane.v2.SpanJob; 64 import com.oracle.javafx.scenebuilder.kit.editor.job.wrap.AbstractWrapInJob; 65 import com.oracle.javafx.scenebuilder.kit.editor.job.wrap.UnwrapJob; 66 import com.oracle.javafx.scenebuilder.kit.editor.messagelog.MessageLog; 67 import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.ErrorDialog; 68 import com.oracle.javafx.scenebuilder.kit.editor.util.InlineEditController; 69 import com.oracle.javafx.scenebuilder.kit.editor.report.ErrorReport; 70 import com.oracle.javafx.scenebuilder.kit.editor.selection.AbstractSelectionGroup; 71 import com.oracle.javafx.scenebuilder.kit.editor.selection.GridSelectionGroup; 72 import com.oracle.javafx.scenebuilder.kit.editor.selection.ObjectSelectionGroup; 73 import com.oracle.javafx.scenebuilder.kit.editor.selection.Selection; 74 import com.oracle.javafx.scenebuilder.kit.editor.util.ContextMenuController; 75 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument; 76 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance; 77 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMIntrinsic; 78 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject; 79 import com.oracle.javafx.scenebuilder.kit.glossary.Glossary; 80 import com.oracle.javafx.scenebuilder.kit.glossary.BuiltinGlossary; 81 import com.oracle.javafx.scenebuilder.kit.library.BuiltinLibrary; 82 import com.oracle.javafx.scenebuilder.kit.library.Library; 83 import com.oracle.javafx.scenebuilder.kit.library.LibraryItem; 84 import com.oracle.javafx.scenebuilder.kit.metadata.Metadata; 85 import com.oracle.javafx.scenebuilder.kit.metadata.property.PropertyMetadata; 86 import com.oracle.javafx.scenebuilder.kit.metadata.property.ValuePropertyMetadata; 87 import com.oracle.javafx.scenebuilder.kit.metadata.util.ClipboardEncoder; 88 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask; 89 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask.Accessory; 90 import com.oracle.javafx.scenebuilder.kit.metadata.util.PrefixedValue; 91 import com.oracle.javafx.scenebuilder.kit.metadata.util.PropertyName; 92 import com.oracle.javafx.scenebuilder.kit.util.control.effectpicker.Utils; 93 94 import java.io.File; 95 import java.io.IOException; 96 import java.net.URISyntaxException; 97 import java.net.URL; 98 import java.nio.file.Path; 99 import java.util.ArrayList; 100 import java.util.Collection; 101 import java.util.Collections; 102 import java.util.HashSet; 103 import java.util.List; 104 import java.util.ResourceBundle; 105 import java.util.Set; 106 107 import javafx.beans.property.BooleanProperty; 108 import javafx.beans.property.ListProperty; 109 import javafx.beans.property.ObjectProperty; 110 import javafx.beans.property.SimpleBooleanProperty; 111 import javafx.beans.property.SimpleListProperty; 112 import javafx.beans.property.SimpleObjectProperty; 113 import javafx.beans.property.SimpleStringProperty; 114 import javafx.beans.value.ChangeListener; 115 import javafx.beans.value.ObservableListValue; 116 import javafx.beans.value.ObservableValue; 117 import javafx.collections.ObservableList; 118 import javafx.geometry.Bounds; 119 import javafx.scene.Node; 120 import javafx.scene.Parent; 121 import javafx.scene.control.Control; 122 import javafx.scene.effect.Effect; 123 import javafx.scene.input.Clipboard; 124 import javafx.scene.layout.BorderPane; 125 import javafx.util.Callback; 126 127 /** 128 * An editor controller is the central object which coordinates the editing 129 * of an FXML document across the different panels (hierarchy, content, 130 * inspector...). 131 * <p> 132 * An editor controller is associated to an FXML document. It can perform 133 * editing and control actions on this document. It also maintains the list of 134 * objects selected by the user. 135 * <p> 136 * Some panel controllers can be attached to an editor controller. They listen 137 * to the editor and update their content accordingly. 138 */ 139 public class EditorController { 140 141 /** 142 * An 'edit' action is an action which modifies the document associated 143 * to this editor. It makes the document dirty and pushes a 144 * new item on the undo/redo stack. 145 */ 146 public enum EditAction { 147 // Candidates for Edit menu 148 CUT, 149 PASTE, 150 PASTE_INTO, 151 DUPLICATE, 152 DELETE, 153 TRIM, 154 TOGGLE_FX_ROOT, 155 // Candidates for Modify menu 156 FIT_TO_PARENT, 157 USE_COMPUTED_SIZES, 158 ADD_CONTEXT_MENU, 159 ADD_TOOLTIP, 160 SET_SIZE_320x240, 161 SET_SIZE_640x480, 162 SET_SIZE_1280x800, 163 SET_SIZE_1920x1080, 164 // Candidates for Modify/GridPane menu 165 MOVE_ROW_ABOVE, 166 MOVE_ROW_BELOW, 167 MOVE_COLUMN_BEFORE, 168 MOVE_COLUMN_AFTER, 169 ADD_ROW_ABOVE, 170 ADD_ROW_BELOW, 171 ADD_COLUMN_BEFORE, 172 ADD_COLUMN_AFTER, 173 INCREASE_ROW_SPAN, 174 DECREASE_ROW_SPAN, 175 INCREASE_COLUMN_SPAN, 176 DECREASE_COLUMN_SPAN, 177 // Candidates for Arrange menu 178 BRING_TO_FRONT, 179 SEND_TO_BACK, 180 BRING_FORWARD, 181 SEND_BACKWARD, 182 UNWRAP, 183 WRAP_IN_ANCHOR_PANE, 184 WRAP_IN_BORDER_PANE, 185 WRAP_IN_BUTTON_BAR, 186 WRAP_IN_DIALOG_PANE, 187 WRAP_IN_FLOW_PANE, 188 WRAP_IN_GRID_PANE, 189 WRAP_IN_GROUP, 190 WRAP_IN_HBOX, 191 WRAP_IN_PANE, 192 WRAP_IN_SCROLL_PANE, 193 WRAP_IN_SPLIT_PANE, 194 WRAP_IN_STACK_PANE, 195 WRAP_IN_TAB_PANE, 196 WRAP_IN_TEXT_FLOW, 197 WRAP_IN_TILE_PANE, 198 WRAP_IN_TITLED_PANE, 199 WRAP_IN_TOOL_BAR, 200 WRAP_IN_VBOX 201 } 202 203 /** 204 * A 'control' action does not modify the document. It only changes a 205 * state or a mode in this editor. 206 */ 207 public enum ControlAction { 208 // Candidates for Edit menu 209 COPY, 210 SELECT_ALL, 211 SELECT_NONE, 212 SELECT_PARENT, 213 SELECT_NEXT, 214 SELECT_PREVIOUS, 215 EDIT_INCLUDED_FILE, 216 REVEAL_INCLUDED_FILE, 217 TOGGLE_CSS_SELECTION, 218 TOGGLE_SAMPLE_DATA 219 } 220 221 /** 222 * Predefined sizes (width x height). 223 * Preferred one refers to the one explicitly set by the user: it is for 224 * use for previewing only. 225 * Default one is the one stored as a global preference: its value is 226 * under user control. 227 */ 228 public enum Size { 229 SIZE_320x240, 230 SIZE_640x480, 231 SIZE_1280x800, 232 SIZE_1920x1080, 233 SIZE_PREFERRED, 234 SIZE_DEFAULT 235 } 236 237 private final Selection selection = new Selection(); 238 private final JobManager jobManager = new JobManager(this, 50); 239 private final MessageLog messageLog = new MessageLog(); 240 private final ErrorReport errorReport = new ErrorReport(); 241 private final DragController dragController = new DragController(this); 242 private final InlineEditController inlineEditController = new InlineEditController(this); 243 private final ContextMenuController contextMenuController = new ContextMenuController(this); 244 private final WatchingController watchingController = new WatchingController(this); 245 246 // At start-up the setter for the two variables below might be called by the 247 // Preferences controller. 248 private double defaultRootContainerWidth = 600; 249 private double defaultRootContainerHeight = 400; 250 251 private final ObjectProperty<FXOMDocument> fxomDocumentProperty 252 = new SimpleObjectProperty<>(); 253 private final ObjectProperty<URL> fxmlLocationProperty 254 = new SimpleObjectProperty<>(); 255 private final ObjectProperty<Library> libraryProperty 256 = new SimpleObjectProperty<>(BuiltinLibrary.getLibrary()); 257 private final ObjectProperty<Glossary> glossaryProperty 258 = new SimpleObjectProperty<>(new BuiltinGlossary()); 259 private final ObjectProperty<ResourceBundle> resourcesProperty 260 = new SimpleObjectProperty<>(null); 261 private final ObjectProperty<Theme> themeProperty 262 = new SimpleObjectProperty<>(Theme.MODENA); 263 private final ListProperty<File> sceneStyleSheetProperty 264 = new SimpleListProperty<>(); 265 private final BooleanProperty pickModeEnabledProperty 266 = new SimpleBooleanProperty(false); 267 private final BooleanProperty sampleDataEnabledProperty 268 = new SimpleBooleanProperty(false); 269 private final SimpleStringProperty toolStylesheetProperty 270 = new SimpleStringProperty(getBuiltinToolStylesheet()); 271 272 private Callback<Void, Boolean> requestTextEditingSessionEnd; 273 274 private static String builtinToolStylesheet; 275 private static File nextInitialDirectory = new File(System.getProperty("user.home")); //NOI18N 276 277 278 /** 279 * Creates an empty editor controller (ie it has no associated fxom document). 280 */ 281 public EditorController() { 282 jobManager.revisionProperty().addListener((ChangeListener<Number>) (ov, t, t1) -> jobManagerRevisionDidChange()); 283 } 284 285 /** 286 * Get the width to use by default for the root container. 287 * 288 * @return default width for the root container. 289 */ 290 public double getDefaultRootContainerWidth() { 291 return defaultRootContainerWidth; 292 } 293 294 /** 295 * Set the width to use by default for the root container. 296 * 297 * @param defaultRootContainerWidth the new root container's default width. 298 */ 299 public void setDefaultRootContainerWidth(double defaultRootContainerWidth) { 300 this.defaultRootContainerWidth = defaultRootContainerWidth; 301 } 302 303 /** 304 * Get the height to use by default for the root container. 305 * 306 * @return default height for the root container. 307 */ 308 public double getDefaultRootContainerHeight() { 309 return defaultRootContainerHeight; 310 } 311 312 /** 313 * Set the height to use by default for the root container. 314 * 315 * @param defaultRootContainerHeight the new root container's default height. 316 */ 317 public void setDefaultRootContainerHeight(double defaultRootContainerHeight) { 318 this.defaultRootContainerHeight = defaultRootContainerHeight; 319 } 320 321 /** 322 * Sets the fxml content to be edited by this editor. 323 * A null value makes this editor empty. 324 * 325 * @param fxmlText null or the fxml text to be edited 326 * @throws IOException if fxml text cannot be parsed and loaded correctly. 327 */ 328 public void setFxmlText(String fxmlText) throws IOException { 329 setFxmlTextAndLocation(fxmlText, getFxmlLocation()); 330 } 331 332 /** 333 * Returns null or the fxml content being edited by this editor. 334 * 335 * @return null or the fxml content being edited by this editor. 336 */ 337 public String getFxmlText() { 338 final String result; 339 340 final FXOMDocument fxomDocument = getFxomDocument(); 341 if (fxomDocument == null) { 342 result = null; 343 } else { 344 final boolean sampleDataEnabled = fxomDocument.isSampleDataEnabled(); 345 if (sampleDataEnabled) { 346 fxomDocument.setSampleDataEnabled(false); 347 } 348 result = fxomDocument.getFxmlText(); 349 if (sampleDataEnabled) { 350 fxomDocument.setSampleDataEnabled(true); 351 } 352 } 353 354 return result; 355 } 356 357 /** 358 * Returns true if fxml content being edited can be returned safely. 359 * This method will return false if there is a text editing session on-going. 360 * 361 * @return true if fxml content being edited can be returned safely. 362 */ 363 public boolean canGetFxmlText() { 364 final boolean result; 365 366 if (requestTextEditingSessionEnd == null) { 367 result = true; 368 } else { 369 result = requestTextEditingSessionEnd.call(null); 370 // If the callback returns true, then it should have call 371 // textEditingSessionDidEnd() 372 // => requestTextEditingSessionEnd should be null 373 assert (requestTextEditingSessionEnd == null) || (result == false); 374 } 375 376 return result; 377 } 378 379 /** 380 * Tells this editor that a text editing session has started. 381 * The editor controller may invoke the requestSessionEnd() callback 382 * if it needs the text editing session to stop. The callback should; 383 * - either stop the text editing session, invoke textEditingSessionDidEnd() 384 * and return true 385 * - either keep the text editing session on-going and return false 386 * 387 * @param requestSessionEnd Callback that should end the text editing session or return false 388 */ 389 public void textEditingSessionDidBegin(Callback<Void, Boolean> requestSessionEnd) { 390 assert requestTextEditingSessionEnd == null; 391 requestTextEditingSessionEnd = requestSessionEnd; 392 } 393 394 395 /** 396 * Tells this editor that the text editing session has ended. 397 */ 398 public void textEditingSessionDidEnd() { 399 assert requestTextEditingSessionEnd != null; 400 requestTextEditingSessionEnd = null; 401 } 402 403 /* 404 * Returns true if a text editing session is currently on going. 405 */ 406 public boolean isTextEditingSessionOnGoing() { 407 return requestTextEditingSessionEnd != null; 408 } 409 410 /** 411 * The property holding the fxml location associated to this editor. 412 * @return the property holding the fxml location associated to this editor. 413 */ 414 public ObservableValue<URL> fxmlLocationProperty() { 415 return fxmlLocationProperty; 416 } 417 418 /** 419 * Sets the location of the fxml being edited. 420 * If null value is passed, fxml text is being interpreted with any location 421 * (ie some references may be broken). 422 * 423 * @param fxmlLocation null or the location of the fxml being edited. 424 */ 425 public void setFxmlLocation(URL fxmlLocation) { 426 fxmlLocationProperty.setValue(fxmlLocation); 427 if (getFxomDocument() != null) { 428 getFxomDocument().setLocation(fxmlLocation); 429 clearUndoRedo(); // Because FXOMDocument.setLocation() mutates the document 430 } 431 if (fxmlLocation != null) { 432 final File newInitialDirectory = new File(fxmlLocation.getPath()); 433 EditorController.updateNextInitialDirectory(newInitialDirectory); 434 } 435 } 436 437 /** 438 * Returns the library used by this editor. 439 * 440 * @return the library used by this editor (never null). 441 */ 442 public Library getLibrary() { 443 return libraryProperty.getValue(); 444 } 445 446 /** 447 * Sets the library used by this editor. 448 * When this method is called, user scene graph is fully rebuilt using 449 * the new library and all panel refresh their contents. 450 * 451 * @param library the library to be used by this editor (never null). 452 */ 453 public void setLibrary(Library library) { 454 assert library != null; 455 libraryProperty.getValue().classLoaderProperty().removeListener(libraryClassLoaderListener); 456 libraryProperty.setValue(library); 457 libraryProperty.getValue().classLoaderProperty().addListener(libraryClassLoaderListener); 458 libraryClassLoaderDidChange(); 459 } 460 461 /** 462 * The property holding the library used by this editor. 463 * 464 * @return the property holding the library used by this editor (never null). 465 */ 466 public ObservableValue<Library> libraryProperty() { 467 return libraryProperty; 468 } 469 470 /** 471 * Returns the glossary used by this editor. 472 * 473 * @return the glossary used by this editor (never null). 474 */ 475 public Glossary getGlossary() { 476 return glossaryProperty.getValue(); 477 } 478 479 /** 480 * Sets the glossary used by this editor. 481 * The Inspector panel(s) connected to this editor will update 482 * their suggested lists in Code section. 483 * 484 * @param glossary the glossary to be used by this editor (never null). 485 */ 486 public void setLibrary(Glossary glossary) { 487 assert glossary != null; 488 glossaryProperty.setValue(glossary); 489 } 490 491 /** 492 * The property holding the glossary used by this editor. 493 * 494 * @return the property holding the glossary used by this editor (never null). 495 */ 496 public ObservableValue<Glossary> glossaryProperty() { 497 return glossaryProperty; 498 } 499 500 /** 501 * Returns the resource bundle used by this editor. 502 * 503 * @return the resource bundle used by this editor. 504 */ 505 public ResourceBundle getResources() { 506 return resourcesProperty.getValue(); 507 } 508 509 /** 510 * Sets the resource bundle used by this editor. 511 * Content and Preview panels sharing this editor will update 512 * their content to use this new theme. 513 * 514 * @param resources null of the resource bundle to be used by this editor. 515 */ 516 public void setResources(ResourceBundle resources) { 517 resourcesProperty.setValue(resources); 518 resourcesDidChange(); 519 } 520 521 /** 522 * The property holding the resource bundle used by this editor. 523 * 524 * @return the property holding the resource bundle used by this editor (never null). 525 */ 526 public ObservableValue<ResourceBundle> resourcesProperty() { 527 return resourcesProperty; 528 } 529 530 /** 531 * Returns the theme used by this editor. 532 * 533 * @return the theme used by this editor. 534 */ 535 public Theme getTheme() { 536 return themeProperty.getValue(); 537 } 538 539 /** 540 * Sets the theme used by this editor. 541 * Content and Preview panels sharing this editor will update 542 * their content to use this new theme. 543 * 544 * @param theme the theme to be used by this editor 545 */ 546 public void setTheme(Theme theme) { 547 themeProperty.setValue(theme); 548 } 549 550 /** 551 * The property holding the theme used by this editor. 552 * 553 * @return the property holding the theme associated to the editor (never null). 554 */ 555 public ObservableValue<Theme> themeProperty() { 556 return themeProperty; 557 } 558 559 /** 560 * 561 * @return the list of scene style sheet used by this editor 562 */ 563 public ObservableList<File> getSceneStyleSheets() { 564 return sceneStyleSheetProperty.getValue(); 565 } 566 567 /** 568 * 569 * @param styleSheets the list of scene style sheet to be used by this editor 570 */ 571 public void setSceneStyleSheets(ObservableList<File> styleSheets) { 572 sceneStyleSheetProperty.setValue(styleSheets); 573 } 574 575 /** 576 * The property holding the list of scene style sheet used by this editor. 577 * 578 * @return the property holding the set of scene style sheet used by the editor, 579 * or null if has not been set. 580 */ 581 public ObservableListValue<File> sceneStyleSheetProperty() { 582 return sceneStyleSheetProperty; 583 } 584 585 /** 586 * Returns true if 'pick mode' is enabled for this editor. 587 * @return true if 'pick mode' is enabled for this editor. 588 */ 589 public boolean isPickModeEnabled() { 590 return pickModeEnabledProperty.getValue(); 591 } 592 593 /** 594 * Enables or disables 'pick mode' on this editor. 595 * 596 * @param pickModeEnabled true if 'pick mode' should be enabled. 597 */ 598 public void setPickModeEnabled(boolean pickModeEnabled) { 599 pickModeEnabledProperty.setValue(pickModeEnabled); 600 } 601 602 /** 603 * The property indicating if 'pick mode' is enabled or not. 604 * 605 * @return the property indicating if 'pick mode' is enabled or not. 606 */ 607 public ObservableValue<Boolean> pickModeEnabledProperty() { 608 return pickModeEnabledProperty; 609 } 610 611 /** 612 * Returns true if content and preview panels attached to this editor 613 * should display sample data. 614 * 615 * @return true if content and preview panels should display sample data. 616 */ 617 public boolean isSampleDataEnabled() { 618 return sampleDataEnabledProperty.getValue(); 619 } 620 621 /** 622 * Enables or disables display of sample data in content and preview panels 623 * attached to this editor. 624 * 625 * @param sampleDataEnabled true if sample data should be displayed 626 */ 627 public void setSampleDataEnabled(boolean sampleDataEnabled) { 628 setPickModeEnabled(false); 629 sampleDataEnabledProperty.setValue(sampleDataEnabled); 630 if (getFxomDocument() != null) { 631 getFxomDocument().setSampleDataEnabled(isSampleDataEnabled()); 632 } 633 } 634 635 /** 636 * The property indicating if sample data should be displayed or not. 637 * 638 * @return the property indicating if sample data should be displayed or not. 639 */ 640 public ObservableValue<Boolean> sampleDataEnabledProperty() { 641 return sampleDataEnabledProperty; 642 } 643 644 645 /** 646 * Returns null or the location of the fxml being edited. 647 * 648 * @return null or the location of the fxml being edited. 649 */ 650 public URL getFxmlLocation() { 651 return fxmlLocationProperty.getValue(); 652 } 653 654 /** 655 * Sets both fxml text and location to be edited by this editor. 656 * Performs setFxmlText() and setFxmlLocation() but in a optimized manner 657 * (it avoids an extra scene graph refresh). 658 * 659 * @param fxmlText null or the fxml text to be edited 660 * @param fxmlLocation null or the location of the fxml text being edited 661 * @throws IOException if fxml text cannot be parsed and loaded correctly. 662 */ 663 public void setFxmlTextAndLocation(String fxmlText, URL fxmlLocation) throws IOException { 664 updateFxomDocument(fxmlText, fxmlLocation, getResources()); 665 this.fxmlLocationProperty.setValue(fxmlLocation); 666 } 667 668 /** 669 * Sets fxml text, location and resources to be edited by this editor. 670 * Performs setFxmlText(), setFxmlLocation() and setResources() but in an 671 * optimized manner (it avoids extra scene graph refresh). 672 * 673 * @param fxmlText null or the fxml text to be edited 674 * @param fxmlLocation null or the location of the fxml text being edited 675 * @param resources null or the resource bundle used to load the fxml text 676 * @throws IOException if fxml text cannot be parsed and loaded correctly. 677 */ 678 public void setFxmlTextLocationAndResources(String fxmlText, URL fxmlLocation, 679 ResourceBundle resources) throws IOException { 680 updateFxomDocument(fxmlText, fxmlLocation, resources); 681 this.fxmlLocationProperty.setValue(fxmlLocation); 682 } 683 684 /** 685 * The property holding the document associated to this editor. 686 * @return the property holding the document associated to this editor. 687 */ 688 public ObservableValue<FXOMDocument> fxomDocumentProperty() { 689 return fxomDocumentProperty; 690 } 691 692 /** 693 * Returns the document associated to this editor. 694 * 695 * @return the document associated to this editor. 696 */ 697 public FXOMDocument getFxomDocument() { 698 return fxomDocumentProperty.getValue(); 699 } 700 701 /** 702 * Returns the tool stylesheet associated to this editor controller. 703 * Its default value equals to getBuiltinToolStylesheet(). 704 * 705 * @return the tool stylesheet associated to this editor controller (never null) 706 */ 707 public String getToolStylesheet() { 708 return toolStylesheetProperty.getValue(); 709 } 710 711 /** 712 * Sets the tool stylesheet associated to this editor controller. 713 * Each panel connected to this editor controller will install this style 714 * sheet in its root object. 715 * 716 * @param stylesheet the tool stylesheet associated to this editor controller (never null) 717 */ 718 public void setToolStylesheet(String stylesheet) { 719 assert stylesheet != null; 720 toolStylesheetProperty.setValue(stylesheet); 721 } 722 723 /** 724 * The property holding tool stylesheet associated to this editor controller. 725 * @return the property holding tool stylesheet associated to this editor controller. 726 */ 727 public ObservableValue<String> toolStylesheetProperty() { 728 return toolStylesheetProperty; 729 } 730 731 /** 732 * Returns the builtin tool stylesheet. 733 * This is the default value for EditorController#toolStylesheet property. 734 * 735 * @return the builtin tool stylesheet. 736 */ 737 public static synchronized String getBuiltinToolStylesheet() { 738 if (builtinToolStylesheet == null) { 739 final URL url = EditorController.class.getResource("css/Theme.css"); //NOI18N 740 assert url != null; 741 builtinToolStylesheet = url.toExternalForm(); 742 } 743 return builtinToolStylesheet; 744 } 745 746 /** 747 * Starts file watching on this editor. 748 * This editor will now monitor the files referenced by the FXML text 749 * (like images, medias, stylesheets, included fxmls...) and automatically 750 * request attached panels to update themselves. 751 */ 752 public void startFileWatching() { 753 watchingController.start(); 754 } 755 756 /** 757 * Stops file watching on this editor. 758 */ 759 public void stopFileWatching() { 760 watchingController.stop(); 761 } 762 763 /** 764 * Returns true if file watching is started on this editor. 765 * 766 * @return true if file watching is started on this editor. 767 */ 768 public boolean isFileWatchingStarted() { 769 return watchingController.isStarted(); 770 } 771 772 /** 773 * @treatAsPrivate Returns the selection associated to this editor. 774 * 775 * @return the selection associated to this editor. 776 */ 777 public Selection getSelection() { 778 return selection; 779 } 780 781 782 /** 783 * @treatAsPrivate Returns the job manager associated to this editor. 784 * 785 * @return the job manager associated to this editor. 786 */ 787 public JobManager getJobManager() { 788 return jobManager; 789 } 790 791 /** 792 * @treatAsPrivate Returns the message log associated to this editor. 793 * 794 * @return the message log associated to this editor. 795 */ 796 public MessageLog getMessageLog() { 797 return messageLog; 798 } 799 800 /** 801 * @treatAsPrivate Returns the error report associated to this editor. 802 * 803 * @return the error report associated to this editor. 804 */ 805 public ErrorReport getErrorReport() { 806 return errorReport; 807 } 808 809 /** 810 * @treatAsPrivate Returns the drag controller associated to this editor. 811 * 812 * @return the drag controller associated to this editor. 813 */ 814 public DragController getDragController() { 815 return dragController; 816 } 817 818 /** 819 * @treatAsPrivate Returns the inline edit controller associated to this editor. 820 * 821 * @return the inline edit controller associated to this editor. 822 */ 823 public InlineEditController getInlineEditController() { 824 return inlineEditController; 825 } 826 827 /** 828 * @treatAsPrivate Returns the context menu controller associated to this editor. 829 * 830 * @return the context menu controller associated to this editor. 831 */ 832 public ContextMenuController getContextMenuController() { 833 return contextMenuController; 834 } 835 836 /** 837 * Returns true if the undo action is permitted (ie there is something 838 * to be undone). 839 * 840 * @return true if the undo action is permitted. 841 */ 842 public boolean canUndo() { 843 return jobManager.canUndo(); 844 } 845 846 /** 847 * Returns null or the description of the action to be undone. 848 * 849 * @return null or the description of the action to be undone. 850 */ 851 public String getUndoDescription() { 852 return jobManager.getUndoDescription(); 853 } 854 855 /** 856 * Performs the undo action. 857 */ 858 public void undo() { 859 jobManager.undo(); 860 assert getFxomDocument().isUpdateOnGoing() == false; 861 } 862 863 /** 864 * Returns true if the redo action is permitted (ie there is something 865 * to be redone). 866 * 867 * @return true if the redo action is permitted. 868 */ 869 public boolean canRedo() { 870 return jobManager.canRedo(); 871 } 872 873 /** 874 * Returns null or the description of the action to be redone. 875 * 876 * @return null or the description of the action to be redone. 877 */ 878 public String getRedoDescription() { 879 return jobManager.getRedoDescription(); 880 } 881 882 /** 883 * Performs the redo action. 884 */ 885 public void redo() { 886 jobManager.redo(); 887 assert getFxomDocument().isUpdateOnGoing() == false; 888 } 889 890 /** 891 * Clears the undo/redo stack of this editor controller. 892 */ 893 public void clearUndoRedo() { 894 jobManager.clear(); 895 } 896 897 /** 898 * Performs an edit action. 899 * 900 * @param editAction the edit action to be performed. 901 */ 902 public void performEditAction(EditAction editAction) { 903 switch(editAction) { 904 case ADD_CONTEXT_MENU: { 905 performAddContextMenu(); 906 break; 907 } 908 case ADD_TOOLTIP: { 909 performAddTooltip(); 910 break; 911 } 912 case ADD_COLUMN_BEFORE: { 913 final AddColumnJob job = new AddColumnJob(this, Position.BEFORE); 914 jobManager.push(job); 915 break; 916 } 917 case ADD_COLUMN_AFTER: { 918 final AddColumnJob job = new AddColumnJob(this, Position.AFTER); 919 jobManager.push(job); 920 break; 921 } 922 case ADD_ROW_ABOVE: { 923 final AddRowJob job = new AddRowJob(this, Position.ABOVE); 924 jobManager.push(job); 925 break; 926 } 927 case ADD_ROW_BELOW: { 928 final AddRowJob job = new AddRowJob(this, Position.BELOW); 929 jobManager.push(job); 930 break; 931 } 932 case BRING_FORWARD: { 933 final BringForwardJob job = new BringForwardJob(this); 934 jobManager.push(job); 935 break; 936 } 937 case BRING_TO_FRONT: { 938 final BringToFrontJob job = new BringToFrontJob(this); 939 jobManager.push(job); 940 break; 941 } 942 case CUT: { 943 final CutSelectionJob job = new CutSelectionJob(this); 944 jobManager.push(job); 945 break; 946 } 947 case DECREASE_COLUMN_SPAN: { 948 final SpanJob job = new SpanJob(this, EditAction.DECREASE_COLUMN_SPAN); 949 jobManager.push(job); 950 break; 951 } 952 case DECREASE_ROW_SPAN: { 953 final SpanJob job = new SpanJob(this, EditAction.DECREASE_ROW_SPAN); 954 jobManager.push(job); 955 break; 956 } 957 case DELETE: { 958 final DeleteSelectionJob job = new DeleteSelectionJob(this); 959 jobManager.push(job); 960 break; 961 } 962 case DUPLICATE: { 963 final DuplicateSelectionJob job = new DuplicateSelectionJob(this); 964 jobManager.push(job); 965 break; 966 } 967 case FIT_TO_PARENT: { 968 final FitToParentSelectionJob job 969 = new FitToParentSelectionJob(this); 970 jobManager.push(job); 971 break; 972 } 973 case INCREASE_COLUMN_SPAN: { 974 final SpanJob job = new SpanJob(this, EditAction.INCREASE_COLUMN_SPAN); 975 jobManager.push(job); 976 break; 977 } 978 case INCREASE_ROW_SPAN: { 979 final SpanJob job = new SpanJob(this, EditAction.INCREASE_ROW_SPAN); 980 jobManager.push(job); 981 break; 982 } 983 case MOVE_COLUMN_BEFORE: { 984 final MoveColumnJob job = new MoveColumnJob(this, Position.BEFORE); 985 jobManager.push(job); 986 break; 987 } 988 case MOVE_COLUMN_AFTER: { 989 final MoveColumnJob job = new MoveColumnJob(this, Position.AFTER); 990 jobManager.push(job); 991 break; 992 } 993 case MOVE_ROW_ABOVE: { 994 final MoveRowJob job = new MoveRowJob(this, Position.ABOVE); 995 jobManager.push(job); 996 break; 997 } 998 case MOVE_ROW_BELOW: { 999 final MoveRowJob job = new MoveRowJob(this, Position.BELOW); 1000 jobManager.push(job); 1001 break; 1002 } 1003 case PASTE: { 1004 final PasteJob job = new PasteJob(this); 1005 jobManager.push(job); 1006 break; 1007 } 1008 case PASTE_INTO: { 1009 final PasteIntoJob job = new PasteIntoJob(this); 1010 jobManager.push(job); 1011 break; 1012 } 1013 case SEND_BACKWARD: { 1014 final SendBackwardJob job = new SendBackwardJob(this); 1015 jobManager.push(job); 1016 break; 1017 } 1018 case SEND_TO_BACK: { 1019 final SendToBackJob job = new SendToBackJob(this); 1020 jobManager.push(job); 1021 break; 1022 } 1023 case SET_SIZE_320x240: { 1024 final UsePredefinedSizeJob job = new UsePredefinedSizeJob(this, Size.SIZE_320x240); 1025 jobManager.push(job); 1026 break; 1027 } 1028 case SET_SIZE_640x480: { 1029 final UsePredefinedSizeJob job = new UsePredefinedSizeJob(this, Size.SIZE_640x480); 1030 jobManager.push(job); 1031 break; 1032 } 1033 case SET_SIZE_1280x800: { 1034 final UsePredefinedSizeJob job = new UsePredefinedSizeJob(this, Size.SIZE_1280x800); 1035 jobManager.push(job); 1036 break; 1037 } 1038 case SET_SIZE_1920x1080: { 1039 final UsePredefinedSizeJob job = new UsePredefinedSizeJob(this, Size.SIZE_1920x1080); 1040 jobManager.push(job); 1041 break; 1042 } 1043 case TRIM: { 1044 final TrimSelectionJob job = new TrimSelectionJob(this); 1045 jobManager.push(job); 1046 break; 1047 } 1048 case UNWRAP: { 1049 final UnwrapJob job = new UnwrapJob(this); 1050 jobManager.push(job); 1051 break; 1052 } 1053 case USE_COMPUTED_SIZES: { 1054 final UseComputedSizesSelectionJob job 1055 = new UseComputedSizesSelectionJob(this); 1056 jobManager.push(job); 1057 break; 1058 } 1059 case WRAP_IN_ANCHOR_PANE: { 1060 performWrap(javafx.scene.layout.AnchorPane.class); 1061 break; 1062 } 1063 case WRAP_IN_BORDER_PANE: { 1064 performWrap(javafx.scene.layout.BorderPane.class); 1065 break; 1066 } 1067 case WRAP_IN_BUTTON_BAR: { 1068 performWrap(javafx.scene.control.ButtonBar.class); 1069 break; 1070 } 1071 case WRAP_IN_DIALOG_PANE: { 1072 performWrap(javafx.scene.control.DialogPane.class); 1073 break; 1074 } 1075 case WRAP_IN_FLOW_PANE: { 1076 performWrap(javafx.scene.layout.FlowPane.class); 1077 break; 1078 } 1079 case WRAP_IN_GRID_PANE: { 1080 performWrap(javafx.scene.layout.GridPane.class); 1081 break; 1082 } 1083 case WRAP_IN_GROUP: { 1084 performWrap(javafx.scene.Group.class); 1085 break; 1086 } 1087 case WRAP_IN_HBOX: { 1088 performWrap(javafx.scene.layout.HBox.class); 1089 break; 1090 } 1091 case WRAP_IN_PANE: { 1092 performWrap(javafx.scene.layout.Pane.class); 1093 break; 1094 } 1095 case WRAP_IN_SCROLL_PANE: { 1096 performWrap(javafx.scene.control.ScrollPane.class); 1097 break; 1098 } 1099 case WRAP_IN_SPLIT_PANE: { 1100 performWrap(javafx.scene.control.SplitPane.class); 1101 break; 1102 } 1103 case WRAP_IN_STACK_PANE: { 1104 performWrap(javafx.scene.layout.StackPane.class); 1105 break; 1106 } 1107 case WRAP_IN_TAB_PANE: { 1108 performWrap(javafx.scene.control.TabPane.class); 1109 break; 1110 } 1111 case WRAP_IN_TEXT_FLOW: { 1112 performWrap(javafx.scene.text.TextFlow.class); 1113 break; 1114 } 1115 case WRAP_IN_TILE_PANE: { 1116 performWrap(javafx.scene.layout.TilePane.class); 1117 break; 1118 } 1119 case WRAP_IN_TITLED_PANE: { 1120 performWrap(javafx.scene.control.TitledPane.class); 1121 break; 1122 } 1123 case WRAP_IN_TOOL_BAR: { 1124 performWrap(javafx.scene.control.ToolBar.class); 1125 break; 1126 } 1127 case WRAP_IN_VBOX: { 1128 performWrap(javafx.scene.layout.VBox.class); 1129 break; 1130 } 1131 default: 1132 throw new UnsupportedOperationException("Not yet implemented"); //NOI18N 1133 } 1134 assert getFxomDocument().isUpdateOnGoing() == false; 1135 } 1136 1137 /** 1138 * Returns true if the specified edit action is permitted. 1139 * 1140 * @param editAction the edit action to be tested. 1141 * @return true if the specified edit action is permitted. 1142 */ 1143 public boolean canPerformEditAction(EditAction editAction) { 1144 final boolean result; 1145 switch(editAction) { 1146 case ADD_CONTEXT_MENU: { 1147 result = canPerformAddContextMenu(); 1148 break; 1149 } 1150 case ADD_TOOLTIP: { 1151 result = canPerformAddTooltip(); 1152 break; 1153 } 1154 case ADD_COLUMN_BEFORE: { 1155 final AddColumnJob job = new AddColumnJob(this, Position.BEFORE); 1156 result = job.isExecutable(); 1157 break; 1158 } 1159 case ADD_COLUMN_AFTER: { 1160 final AddColumnJob job = new AddColumnJob(this, Position.AFTER); 1161 result = job.isExecutable(); 1162 break; 1163 } 1164 case ADD_ROW_ABOVE: { 1165 final AddRowJob job = new AddRowJob(this, Position.ABOVE); 1166 result = job.isExecutable(); 1167 break; 1168 } 1169 case ADD_ROW_BELOW: { 1170 final AddRowJob job = new AddRowJob(this, Position.BELOW); 1171 result = job.isExecutable(); 1172 break; 1173 } 1174 case BRING_FORWARD: { 1175 final BringForwardJob job = new BringForwardJob(this); 1176 result = job.isExecutable(); 1177 break; 1178 } 1179 case BRING_TO_FRONT: { 1180 final BringToFrontJob job = new BringToFrontJob(this); 1181 result = job.isExecutable(); 1182 break; 1183 } 1184 case CUT: { 1185 final CutSelectionJob job = new CutSelectionJob(this); 1186 result = job.isExecutable(); 1187 break; 1188 } 1189 case DECREASE_COLUMN_SPAN: { 1190 final SpanJob job = new SpanJob(this, EditAction.DECREASE_COLUMN_SPAN); 1191 result = job.isExecutable(); 1192 break; 1193 } 1194 case DECREASE_ROW_SPAN: { 1195 final SpanJob job = new SpanJob(this, EditAction.DECREASE_ROW_SPAN); 1196 result = job.isExecutable(); 1197 break; 1198 } 1199 case DELETE: { 1200 final DeleteSelectionJob job = new DeleteSelectionJob(this); 1201 result = job.isExecutable(); 1202 break; 1203 } 1204 case DUPLICATE: { 1205 final DuplicateSelectionJob job = new DuplicateSelectionJob(this); 1206 result = job.isExecutable(); 1207 break; 1208 } 1209 case FIT_TO_PARENT: { 1210 final FitToParentSelectionJob job 1211 = new FitToParentSelectionJob(this); 1212 result = job.isExecutable(); 1213 break; 1214 } 1215 case INCREASE_COLUMN_SPAN: { 1216 final SpanJob job = new SpanJob(this, EditAction.INCREASE_COLUMN_SPAN); 1217 result = job.isExecutable(); 1218 break; 1219 } 1220 case INCREASE_ROW_SPAN: { 1221 final SpanJob job = new SpanJob(this, EditAction.INCREASE_ROW_SPAN); 1222 result = job.isExecutable(); 1223 break; 1224 } 1225 case MOVE_COLUMN_BEFORE: { 1226 final MoveColumnJob job = new MoveColumnJob(this, Position.BEFORE); 1227 result = job.isExecutable(); 1228 break; 1229 } 1230 case MOVE_COLUMN_AFTER: { 1231 final MoveColumnJob job = new MoveColumnJob(this, Position.AFTER); 1232 result = job.isExecutable(); 1233 break; 1234 } 1235 case MOVE_ROW_ABOVE: { 1236 final MoveRowJob job = new MoveRowJob(this, Position.ABOVE); 1237 result = job.isExecutable(); 1238 break; 1239 } 1240 case MOVE_ROW_BELOW: { 1241 final MoveRowJob job = new MoveRowJob(this, Position.BELOW); 1242 result = job.isExecutable(); 1243 break; 1244 } 1245 case PASTE: { 1246 final PasteJob job = new PasteJob(this); 1247 result = job.isExecutable(); 1248 break; 1249 } 1250 case PASTE_INTO: { 1251 final PasteIntoJob job = new PasteIntoJob(this); 1252 result = job.isExecutable(); 1253 break; 1254 } 1255 case SEND_BACKWARD: { 1256 final SendBackwardJob job = new SendBackwardJob(this); 1257 result = job.isExecutable(); 1258 break; 1259 } 1260 case SEND_TO_BACK: { 1261 final SendToBackJob job = new SendToBackJob(this); 1262 result = job.isExecutable(); 1263 break; 1264 } 1265 case SET_SIZE_320x240: { 1266 final UsePredefinedSizeJob job = new UsePredefinedSizeJob(this, Size.SIZE_320x240); 1267 result = job.isExecutable(); 1268 break; 1269 } 1270 case SET_SIZE_640x480: { 1271 final UsePredefinedSizeJob job = new UsePredefinedSizeJob(this, Size.SIZE_640x480); 1272 result = job.isExecutable(); 1273 break; 1274 } 1275 case SET_SIZE_1280x800: { 1276 final UsePredefinedSizeJob job = new UsePredefinedSizeJob(this, Size.SIZE_1280x800); 1277 result = job.isExecutable(); 1278 break; 1279 } 1280 case SET_SIZE_1920x1080: { 1281 final UsePredefinedSizeJob job = new UsePredefinedSizeJob(this, Size.SIZE_1920x1080); 1282 result = job.isExecutable(); 1283 break; 1284 } 1285 case TRIM: { 1286 final TrimSelectionJob job = new TrimSelectionJob(this); 1287 result = job.isExecutable(); 1288 break; 1289 } 1290 case UNWRAP: { 1291 final UnwrapJob job = new UnwrapJob(this); 1292 result = job.isExecutable(); 1293 break; 1294 } 1295 case USE_COMPUTED_SIZES: { 1296 final UseComputedSizesSelectionJob job 1297 = new UseComputedSizesSelectionJob(this); 1298 result = job.isExecutable(); 1299 break; 1300 } 1301 case WRAP_IN_ANCHOR_PANE: { 1302 result = canPerformWrap(javafx.scene.layout.AnchorPane.class); 1303 break; 1304 } 1305 case WRAP_IN_BORDER_PANE: { 1306 result = canPerformWrap(javafx.scene.layout.BorderPane.class); 1307 break; 1308 } 1309 case WRAP_IN_BUTTON_BAR: { 1310 result = canPerformWrap(javafx.scene.control.ButtonBar.class); 1311 break; 1312 } 1313 case WRAP_IN_DIALOG_PANE: { 1314 result = canPerformWrap(javafx.scene.control.DialogPane.class); 1315 break; 1316 } 1317 case WRAP_IN_FLOW_PANE: { 1318 result = canPerformWrap(javafx.scene.layout.FlowPane.class); 1319 break; 1320 } 1321 case WRAP_IN_GRID_PANE: { 1322 result = canPerformWrap(javafx.scene.layout.GridPane.class); 1323 break; 1324 } 1325 case WRAP_IN_GROUP: { 1326 result = canPerformWrap(javafx.scene.Group.class); 1327 break; 1328 } 1329 case WRAP_IN_HBOX: { 1330 result = canPerformWrap(javafx.scene.layout.HBox.class); 1331 break; 1332 } 1333 case WRAP_IN_PANE: { 1334 result = canPerformWrap(javafx.scene.layout.Pane.class); 1335 break; 1336 } 1337 case WRAP_IN_SCROLL_PANE: { 1338 result = canPerformWrap(javafx.scene.control.ScrollPane.class); 1339 break; 1340 } 1341 case WRAP_IN_SPLIT_PANE: { 1342 result = canPerformWrap(javafx.scene.control.SplitPane.class); 1343 break; 1344 } 1345 case WRAP_IN_STACK_PANE: { 1346 result = canPerformWrap(javafx.scene.layout.StackPane.class); 1347 break; 1348 } 1349 case WRAP_IN_TAB_PANE: { 1350 result = canPerformWrap(javafx.scene.control.TabPane.class); 1351 break; 1352 } 1353 case WRAP_IN_TEXT_FLOW: { 1354 result = canPerformWrap(javafx.scene.text.TextFlow.class); 1355 break; 1356 } 1357 case WRAP_IN_TILE_PANE: { 1358 result = canPerformWrap(javafx.scene.layout.TilePane.class); 1359 break; 1360 } 1361 case WRAP_IN_TITLED_PANE: { 1362 result = canPerformWrap(javafx.scene.control.TitledPane.class); 1363 break; 1364 } 1365 case WRAP_IN_TOOL_BAR: { 1366 result = canPerformWrap(javafx.scene.control.ToolBar.class); 1367 break; 1368 } 1369 case WRAP_IN_VBOX: { 1370 result = canPerformWrap(javafx.scene.layout.VBox.class); 1371 break; 1372 } 1373 default: 1374 result = false; 1375 break; 1376 } 1377 1378 return result; 1379 } 1380 1381 /** 1382 * Performs the specified control action. 1383 * 1384 * @param controlAction the control action to be performed. 1385 */ 1386 public void performControlAction(ControlAction controlAction) { 1387 switch(controlAction) { 1388 case COPY: { 1389 performCopy(); 1390 break; 1391 } 1392 case EDIT_INCLUDED_FILE: { 1393 performEditIncludedFile(); 1394 break; 1395 } 1396 case REVEAL_INCLUDED_FILE: { 1397 performRevealIncludedFile(); 1398 break; 1399 } 1400 case SELECT_ALL: { 1401 performSelectAll(); 1402 break; 1403 } 1404 case SELECT_NONE: { 1405 performSelectNone(); 1406 break; 1407 } 1408 case SELECT_PARENT: { 1409 performSelectParent(); 1410 break; 1411 } 1412 case SELECT_NEXT: { 1413 performSelectNext(); 1414 break; 1415 } 1416 case SELECT_PREVIOUS: { 1417 performSelectPrevious(); 1418 break; 1419 } 1420 case TOGGLE_SAMPLE_DATA: { 1421 setSampleDataEnabled( ! isSampleDataEnabled()); 1422 break; 1423 } 1424 default: 1425 throw new UnsupportedOperationException("Not yet implemented"); //NOI18N 1426 } 1427 } 1428 1429 /** 1430 * Returns true if the specified control action is permitted. 1431 * 1432 * @param controlAction the control action to be tested. 1433 * @return true if the specified control action is permitted. 1434 */ 1435 public boolean canPerformControlAction(ControlAction controlAction) { 1436 final boolean result; 1437 1438 // If there is no document loaded, we cannot perform control actions 1439 if (getFxomDocument() == null || getFxomDocument().getFxomRoot() == null) { 1440 return false; 1441 } 1442 switch(controlAction) { 1443 case COPY: { 1444 result = canPerformCopy(); 1445 break; 1446 } 1447 case EDIT_INCLUDED_FILE: 1448 case REVEAL_INCLUDED_FILE: { 1449 result = canPerformIncludedFileAction(); 1450 break; 1451 } 1452 case SELECT_ALL: { 1453 result = canPerformSelectAll(); 1454 break; 1455 } 1456 case SELECT_NONE: { 1457 result = canPerformSelectNone(); 1458 break; 1459 } 1460 case SELECT_PARENT: { 1461 result = canPerformSelectParent(); 1462 break; 1463 } 1464 case SELECT_NEXT: { 1465 result = canPerformSelectNext(); 1466 break; 1467 } 1468 case SELECT_PREVIOUS: { 1469 result = canPerformSelectPrevious(); 1470 break; 1471 } 1472 case TOGGLE_SAMPLE_DATA: { 1473 result = true; 1474 break; 1475 } 1476 default: 1477 result = false; 1478 break; 1479 } 1480 1481 return result; 1482 } 1483 1484 /** 1485 * Performs the 'import' FXML edit action. 1486 * This action creates an object matching the root node of the selected 1487 * FXML file and insert it in the document (either as root if the document 1488 * is empty or under the selection common ancestor node otherwise). 1489 * 1490 * @param fxmlFile the FXML file to be imported 1491 */ 1492 public void performImportFxml(File fxmlFile) { 1493 performImport(fxmlFile); 1494 } 1495 1496 /** 1497 * Performs the 'import' media edit action. 1498 * This action creates an object matching the type of the selected 1499 * media file (either ImageView or MediaView) and insert it in the document 1500 * (either as root if the document is empty or under the selection common 1501 * ancestor node otherwise). 1502 * 1503 * @param mediaFile the media file to be imported 1504 */ 1505 public void performImportMedia(File mediaFile) { 1506 performImport(mediaFile); 1507 } 1508 1509 private void performImport(File file) { 1510 final ImportFileJob job = new ImportFileJob(file, this); 1511 if (job.isExecutable()) { 1512 jobManager.push(job); 1513 } else { 1514 final String target; 1515 if (job.getTargetObject() == null) { 1516 target = null; 1517 } else { 1518 final Object sceneGraphTarget 1519 = job.getTargetObject().getSceneGraphObject(); 1520 if (sceneGraphTarget == null) { 1521 target = null; 1522 } else { 1523 target = sceneGraphTarget.getClass().getSimpleName(); 1524 } 1525 } 1526 if (target != null) { 1527 getMessageLog().logWarningMessage( 1528 "import.from.file.failed.target", 1529 file.getName(), target); 1530 } else { 1531 getMessageLog().logWarningMessage( 1532 "import.from.file.failed", 1533 file.getName()); 1534 } 1535 } 1536 } 1537 1538 /** 1539 * Performs the 'include' FXML edit action. 1540 * As opposed to the 'import' edit action, the 'include' action does not 1541 * copy the FXML content but adds an fx:include element to the FXML document. 1542 * 1543 * @param fxmlFile the FXML file to be included 1544 */ 1545 public void performIncludeFxml(File fxmlFile) { 1546 final IncludeFileJob job = new IncludeFileJob(fxmlFile, this); 1547 if (job.isExecutable()) { 1548 jobManager.push(job); 1549 } else { 1550 final String target; 1551 if (job.getTargetObject() == null) { 1552 target = null; 1553 } else { 1554 final Object sceneGraphTarget 1555 = job.getTargetObject().getSceneGraphObject(); 1556 if (sceneGraphTarget == null) { 1557 target = null; 1558 } else { 1559 target = sceneGraphTarget.getClass().getSimpleName(); 1560 } 1561 } 1562 if (target != null) { 1563 getMessageLog().logWarningMessage( 1564 "include.file.failed.target", 1565 fxmlFile.getName(), target); 1566 } else { 1567 getMessageLog().logWarningMessage( 1568 "include.file.failed", 1569 fxmlFile.getName()); 1570 } 1571 } 1572 } 1573 1574 /** 1575 * Performs the 'insert' edit action. This action creates an object 1576 * matching the specified library item and insert it in the document 1577 * (according the selection state). 1578 * 1579 * @param libraryItem the library item describing the object to be inserted. 1580 */ 1581 public void performInsert(LibraryItem libraryItem) { 1582 final Job job; 1583 final FXOMObject target; 1584 1585 assert canPerformInsert(libraryItem); // (1) 1586 1587 final FXOMDocument newItemDocument = libraryItem.instantiate(); 1588 assert newItemDocument != null; // Because (1) 1589 final FXOMObject newObject = newItemDocument.getFxomRoot(); 1590 assert newObject != null; 1591 newObject.moveToFxomDocument(getFxomDocument()); 1592 final FXOMObject rootObject = getFxomDocument().getFxomRoot(); 1593 if (rootObject == null) { // Empty document 1594 final String description 1595 = I18N.getString("drop.job.insert.library.item", libraryItem.getName()); 1596 job = new SetDocumentRootJob(newObject, true /* usePredefinedSize */, description, this); 1597 1598 } else { 1599 if (selection.isEmpty() || selection.isSelected(rootObject)) { 1600 // No selection or root is selected -> we insert below root 1601 target = rootObject; 1602 } else { 1603 // Let's use the common parent of the selected objects. 1604 // It might be null if selection holds some non FXOMObject entries 1605 target = selection.getAncestor(); 1606 } 1607 job = new InsertAsSubComponentJob(newObject, target, -1, this); 1608 } 1609 1610 jobManager.push(job); 1611 } 1612 1613 /** 1614 * Returns true if the 'insert' action is permitted with the specified 1615 * library item. 1616 * 1617 * @param libraryItem the library item describing the object to be inserted. 1618 * @return true if the 'insert' action is permitted. 1619 */ 1620 public boolean canPerformInsert(LibraryItem libraryItem) { 1621 final FXOMObject targetCandidate; 1622 final boolean result; 1623 1624 if (getFxomDocument() == null) { 1625 result = false; 1626 } else { 1627 assert (libraryItem.getLibrary().getClassLoader() == null) 1628 || (libraryItem.getLibrary().getClassLoader() == getFxomDocument().getClassLoader()); 1629 final FXOMDocument newItemDocument = libraryItem.instantiate(); 1630 if (newItemDocument == null) { 1631 // For some reason, library is unable to instantiate this item 1632 result = false; 1633 } else { 1634 final FXOMObject newItemRoot = newItemDocument.getFxomRoot(); 1635 newItemRoot.moveToFxomDocument(getFxomDocument()); 1636 assert newItemDocument.getFxomRoot() == null; 1637 final FXOMObject rootObject = getFxomDocument().getFxomRoot(); 1638 if (rootObject == null) { // Empty document 1639 final SetDocumentRootJob job = new SetDocumentRootJob( 1640 newItemRoot, true /* usePredefinedSize */, "unused", this); //NOI18N 1641 result = job.isExecutable(); 1642 } else { 1643 if (selection.isEmpty() || selection.isSelected(rootObject)) { 1644 // No selection or root is selected -> we insert below root 1645 targetCandidate = rootObject; 1646 } else { 1647 // Let's use the common parent of the selected objects. 1648 // It might be null if selection holds some non FXOMObject entries 1649 targetCandidate = selection.getAncestor(); 1650 } 1651 final InsertAsSubComponentJob job = new InsertAsSubComponentJob( 1652 newItemRoot, targetCandidate, -1, this); 1653 result = job.isExecutable(); 1654 } 1655 } 1656 } 1657 1658 return result; 1659 } 1660 1661 /** 1662 * Performs the 'wrap' edit action. This action creates an object 1663 * matching the specified class and reparent all the selected objects 1664 * below this new object. 1665 * 1666 * @param wrappingClass the wrapping class 1667 */ 1668 public void performWrap(Class<? extends Parent> wrappingClass) { 1669 assert canPerformWrap(wrappingClass); 1670 final AbstractWrapInJob job = AbstractWrapInJob.getWrapInJob(this, wrappingClass); 1671 jobManager.push(job); 1672 } 1673 1674 /** 1675 * Returns true if the 'wrap' action is permitted with the specified class. 1676 * 1677 * @param wrappingClass the wrapping class. 1678 * @return true if the 'wrap' action is permitted. 1679 */ 1680 public boolean canPerformWrap(Class<? extends Parent> wrappingClass) { 1681 if (getClassesSupportingWrapping().contains(wrappingClass) == false) { 1682 return false; 1683 } 1684 final AbstractWrapInJob job = AbstractWrapInJob.getWrapInJob(this, wrappingClass); 1685 return job.isExecutable(); 1686 } 1687 1688 private static List<Class<? extends Parent>> classesSupportingWrapping; 1689 1690 /** 1691 * Return the list of classes that can be passed to 1692 * {@link EditorController#performWrap(java.lang.Class)}. 1693 * 1694 * @return the list of classes. 1695 */ 1696 public synchronized static Collection<Class<? extends Parent>> getClassesSupportingWrapping() { 1697 if (classesSupportingWrapping == null) { 1698 classesSupportingWrapping = new ArrayList<>(); 1699 classesSupportingWrapping.add(javafx.scene.layout.AnchorPane.class); 1700 classesSupportingWrapping.add(javafx.scene.layout.BorderPane.class); 1701 classesSupportingWrapping.add(javafx.scene.control.ButtonBar.class); 1702 classesSupportingWrapping.add(javafx.scene.control.DialogPane.class); 1703 classesSupportingWrapping.add(javafx.scene.layout.FlowPane.class); 1704 classesSupportingWrapping.add(javafx.scene.layout.GridPane.class); 1705 classesSupportingWrapping.add(javafx.scene.Group.class); 1706 classesSupportingWrapping.add(javafx.scene.layout.HBox.class); 1707 classesSupportingWrapping.add(javafx.scene.layout.Pane.class); 1708 classesSupportingWrapping.add(javafx.scene.control.ScrollPane.class); 1709 classesSupportingWrapping.add(javafx.scene.control.SplitPane.class); 1710 classesSupportingWrapping.add(javafx.scene.layout.StackPane.class); 1711 classesSupportingWrapping.add(javafx.scene.control.TabPane.class); 1712 classesSupportingWrapping.add(javafx.scene.text.TextFlow.class); 1713 classesSupportingWrapping.add(javafx.scene.layout.TilePane.class); 1714 classesSupportingWrapping.add(javafx.scene.control.TitledPane.class); 1715 classesSupportingWrapping.add(javafx.scene.control.ToolBar.class); 1716 classesSupportingWrapping.add(javafx.scene.layout.VBox.class); 1717 classesSupportingWrapping = Collections.unmodifiableList(classesSupportingWrapping); 1718 } 1719 1720 return classesSupportingWrapping; 1721 } 1722 1723 /** 1724 * Performs the copy control action. 1725 */ 1726 private void performCopy() { 1727 assert canPerformCopy(); // (1) 1728 assert selection.getGroup() instanceof ObjectSelectionGroup; // Because of (1) 1729 final ObjectSelectionGroup osg = (ObjectSelectionGroup) selection.getGroup(); 1730 1731 final ClipboardEncoder encoder = new ClipboardEncoder(osg.getSortedItems()); 1732 assert encoder.isEncodable(); 1733 Clipboard.getSystemClipboard().setContent(encoder.makeEncoding()); 1734 } 1735 1736 /** 1737 * Returns true if the selection is not empty. 1738 * 1739 * @return if the selection is not empty. 1740 */ 1741 private boolean canPerformCopy() { 1742 return selection.getGroup() instanceof ObjectSelectionGroup; 1743 } 1744 1745 /** 1746 * Performs the select all control action. 1747 * Select all sub components of the selection common ancestor. 1748 */ 1749 private void performSelectAll() { 1750 assert canPerformSelectAll(); // (1) 1751 final FXOMObject rootObject = getFxomDocument().getFxomRoot(); 1752 if (selection.isEmpty()) { // (1) 1753 // If the current selection is empty, we select the root object 1754 selection.select(rootObject); 1755 } else if (selection.getGroup() instanceof ObjectSelectionGroup) { 1756 // Otherwise, select all sub components of the common ancestor ?? 1757 final FXOMObject ancestor = selection.getAncestor(); 1758 assert ancestor != null; // Because of (1) 1759 final DesignHierarchyMask mask = new DesignHierarchyMask(ancestor); 1760 final Set<FXOMObject> selectableObjects = new HashSet<>(); 1761 // BorderPane special case : use accessories 1762 if (mask.getFxomObject().getSceneGraphObject() instanceof BorderPane) { 1763 final FXOMObject top = mask.getAccessory(Accessory.TOP); 1764 final FXOMObject left = mask.getAccessory(Accessory.LEFT); 1765 final FXOMObject center = mask.getAccessory(Accessory.CENTER); 1766 final FXOMObject right = mask.getAccessory(Accessory.RIGHT); 1767 final FXOMObject bottom = mask.getAccessory(Accessory.BOTTOM); 1768 for (FXOMObject accessoryObject : new FXOMObject[]{ 1769 top, left, center, right, bottom}) { 1770 if (accessoryObject != null) { 1771 selectableObjects.add(accessoryObject); 1772 } 1773 } 1774 } else { 1775 assert mask.isAcceptingSubComponent(); // Because of (1) 1776 selectableObjects.addAll(mask.getSubComponents()); 1777 } 1778 selection.select(selectableObjects); 1779 } else if (selection.getGroup() instanceof GridSelectionGroup) { 1780 // Select ALL rows / columns 1781 final GridSelectionGroup gsg = (GridSelectionGroup) selection.getGroup(); 1782 final FXOMObject gridPane = gsg.getParentObject(); 1783 assert gridPane instanceof FXOMInstance; 1784 final DesignHierarchyMask gridPaneMask = new DesignHierarchyMask(gridPane); 1785 int size = 0; 1786 switch (gsg.getType()) { 1787 case ROW: 1788 size = gridPaneMask.getRowsSize(); 1789 break; 1790 case COLUMN: 1791 size = gridPaneMask.getColumnsSize(); 1792 break; 1793 default: 1794 assert false; 1795 break; 1796 } 1797 // Select first index 1798 selection.select((FXOMInstance) gridPane, gsg.getType(), 0); 1799 for (int index = 1; index < size; index++) { 1800 selection.toggleSelection((FXOMInstance) gridPane, gsg.getType(), index); 1801 } 1802 } else { 1803 assert selection.getGroup() == null : 1804 "Add implementation for " + selection.getGroup(); //NOI18N 1805 1806 } 1807 } 1808 1809 /** 1810 * Returns true if the root object is not selected and if the sub components 1811 * of the selection common ancestor are not all already selected. 1812 * 1813 * @return if the root object is not selected and if the sub components of 1814 * the selection common ancestor are not all already selected. 1815 */ 1816 private boolean canPerformSelectAll() { 1817 assert getFxomDocument() != null && getFxomDocument().getFxomRoot() != null; 1818 if (selection.isEmpty()) { // (1) 1819 return true; 1820 } else if (selection.getGroup() instanceof ObjectSelectionGroup) { 1821 final FXOMObject rootObject = getFxomDocument().getFxomRoot(); 1822 // Cannot select all if root is selected 1823 if (selection.isSelected(rootObject)) { // (1) 1824 return false; 1825 } else { 1826 // Cannot select all if all sub components are already selected 1827 final FXOMObject ancestor = selection.getAncestor(); 1828 assert ancestor != null; // Because of (1) 1829 final DesignHierarchyMask mask = new DesignHierarchyMask(ancestor); 1830 // BorderPane special case : use accessories 1831 if (mask.getFxomObject().getSceneGraphObject() instanceof BorderPane) { 1832 final FXOMObject top = mask.getAccessory(Accessory.TOP); 1833 final FXOMObject left = mask.getAccessory(Accessory.LEFT); 1834 final FXOMObject center = mask.getAccessory(Accessory.CENTER); 1835 final FXOMObject right = mask.getAccessory(Accessory.RIGHT); 1836 final FXOMObject bottom = mask.getAccessory(Accessory.BOTTOM); 1837 for (FXOMObject bpAccessoryObject : new FXOMObject[] { 1838 top, left, center, right, bottom}) { 1839 if (bpAccessoryObject != null 1840 && selection.isSelected(bpAccessoryObject) == false) { 1841 return true; 1842 } 1843 } 1844 } else if (mask.isAcceptingSubComponent()) { 1845 for (FXOMObject subComponentObject : mask.getSubComponents()) { 1846 if (selection.isSelected(subComponentObject) == false) { 1847 return true; 1848 } 1849 } 1850 } 1851 } 1852 } else if (selection.getGroup() instanceof GridSelectionGroup) { 1853 final GridSelectionGroup gsg = (GridSelectionGroup) selection.getGroup(); 1854 // GridSelectionGroup => at least 1 row/column is selected 1855 assert gsg.getIndexes().isEmpty() == false; 1856 return true; 1857 } else { 1858 assert selection.getGroup() == null : 1859 "Add implementation for " + selection.getGroup(); //NOI18N 1860 } 1861 return false; 1862 } 1863 1864 /** 1865 * Performs the select parent control action. 1866 * If the selection is multiple, we select the common ancestor. 1867 */ 1868 private void performSelectParent() { 1869 assert canPerformSelectParent(); // (1) 1870 final FXOMObject ancestor = selection.getAncestor(); 1871 assert ancestor != null; // Because of (1) 1872 selection.select(ancestor); 1873 } 1874 1875 /** 1876 * Returns true if the selection is not empty and the root object is not 1877 * selected. 1878 * 1879 * @return if the selection is not empty and the root object is not 1880 * selected. 1881 */ 1882 private boolean canPerformSelectParent() { 1883 assert getFxomDocument() != null && getFxomDocument().getFxomRoot() != null; 1884 final FXOMObject rootObject = getFxomDocument().getFxomRoot(); 1885 return !selection.isEmpty() && !selection.isSelected(rootObject); 1886 } 1887 1888 /** 1889 * Performs the select next control action. 1890 */ 1891 private void performSelectNext() { 1892 assert canPerformSelectNext(); // (1) 1893 1894 final AbstractSelectionGroup asg = selection.getGroup(); 1895 if (asg instanceof ObjectSelectionGroup) { 1896 final ObjectSelectionGroup osg = (ObjectSelectionGroup) asg; 1897 final Set<FXOMObject> items = osg.getItems(); 1898 assert items.size() == 1; // Because of (1) 1899 final FXOMObject selectedObject = items.iterator().next(); 1900 final FXOMObject nextSibling = selectedObject.getNextSlibing(); 1901 assert nextSibling != null; // Because of (1) 1902 selection.select(nextSibling); 1903 } else { 1904 assert asg instanceof GridSelectionGroup; // Because of (1) 1905 final GridSelectionGroup gsg = (GridSelectionGroup) asg; 1906 final FXOMObject gridPane = gsg.getParentObject(); 1907 final DesignHierarchyMask mask = new DesignHierarchyMask(gridPane); 1908 assert gridPane instanceof FXOMInstance; 1909 final Set<Integer> indexes = gsg.getIndexes(); 1910 assert indexes.size() == 1; // Because of (1) 1911 int selectedIndex = indexes.iterator().next(); 1912 int nextIndex = selectedIndex + 1; 1913 int size = 0; 1914 switch (gsg.getType()) { 1915 case ROW: 1916 size = mask.getRowsSize(); 1917 break; 1918 case COLUMN: 1919 size = mask.getColumnsSize(); 1920 break; 1921 default: 1922 assert false; 1923 break; 1924 } 1925 assert nextIndex < size; // Because of (1) 1926 selection.select((FXOMInstance) gridPane, gsg.getType(), nextIndex); 1927 } 1928 } 1929 1930 /** 1931 * Returns true if the selection is single and the container of the selected 1932 * object container contains a child next to the selected one. 1933 * 1934 * @return if the selection is single and the container of the selected 1935 * object container contains a child next to the selected one. 1936 */ 1937 private boolean canPerformSelectNext() { 1938 assert getFxomDocument() != null && getFxomDocument().getFxomRoot() != null; 1939 if (selection.isEmpty()) { 1940 return false; 1941 } 1942 final AbstractSelectionGroup asg = selection.getGroup(); 1943 if (asg instanceof ObjectSelectionGroup) { 1944 final ObjectSelectionGroup osg = (ObjectSelectionGroup) asg; 1945 final Set<FXOMObject> items = osg.getItems(); 1946 if (items.size() != 1) { 1947 return false; 1948 } 1949 final FXOMObject selectedObject = items.iterator().next(); 1950 return selectedObject.getNextSlibing() != null; 1951 } else if (asg instanceof GridSelectionGroup) { 1952 final GridSelectionGroup gsg = (GridSelectionGroup) asg; 1953 final Set<Integer> indexes = gsg.getIndexes(); 1954 if (indexes.size() != 1) { 1955 return false; 1956 } 1957 final FXOMObject gridPane = gsg.getParentObject(); 1958 final DesignHierarchyMask mask = new DesignHierarchyMask(gridPane); 1959 int size = 0; 1960 switch (gsg.getType()) { 1961 case ROW: 1962 size = mask.getRowsSize(); 1963 break; 1964 case COLUMN: 1965 size = mask.getColumnsSize(); 1966 break; 1967 default: 1968 assert false; 1969 break; 1970 } 1971 final int index = indexes.iterator().next(); 1972 return index < size - 1; 1973 } else { 1974 assert selection.getGroup() == null : 1975 "Add implementation for " + selection.getGroup(); //NOI18N 1976 } 1977 return false; 1978 } 1979 1980 /** 1981 * Performs the select previous control action. 1982 */ 1983 private void performSelectPrevious() { 1984 assert canPerformSelectPrevious(); // (1) 1985 1986 final AbstractSelectionGroup asg = selection.getGroup(); 1987 if (asg instanceof ObjectSelectionGroup) { 1988 final ObjectSelectionGroup osg = (ObjectSelectionGroup) asg; 1989 final Set<FXOMObject> items = osg.getItems(); 1990 assert items.size() == 1; // Because of (1) 1991 final FXOMObject selectedObject = items.iterator().next(); 1992 final FXOMObject previousSibling = selectedObject.getPreviousSlibing(); 1993 assert previousSibling != null; // Because of (1) 1994 selection.select(previousSibling); 1995 } else { 1996 assert asg instanceof GridSelectionGroup; // Because of (1) 1997 final GridSelectionGroup gsg = (GridSelectionGroup) asg; 1998 final FXOMObject gridPane = gsg.getParentObject(); 1999 assert gridPane instanceof FXOMInstance; 2000 final Set<Integer> indexes = gsg.getIndexes(); 2001 assert indexes.size() == 1; // Because of (1) 2002 int selectedIndex = indexes.iterator().next(); 2003 int previousIndex = selectedIndex - 1; 2004 assert previousIndex >= 0; // Because of (1) 2005 selection.select((FXOMInstance) gridPane, gsg.getType(), previousIndex); 2006 } 2007 } 2008 2009 /** 2010 * Returns true if the selection is single and the container of the selected 2011 * object container contains a child previous to the selected one. 2012 * 2013 * @return if the selection is single and the container of the selected 2014 * object container contains a child previous to the selected one. 2015 */ 2016 private boolean canPerformSelectPrevious() { 2017 assert getFxomDocument() != null && getFxomDocument().getFxomRoot() != null; 2018 if (selection.isEmpty()) { 2019 return false; 2020 } 2021 final AbstractSelectionGroup asg = selection.getGroup(); 2022 if (asg instanceof ObjectSelectionGroup) { 2023 final ObjectSelectionGroup osg = (ObjectSelectionGroup) asg; 2024 final Set<FXOMObject> items = osg.getItems(); 2025 if (items.size() != 1) { 2026 return false; 2027 } 2028 final FXOMObject selectedObject = items.iterator().next(); 2029 return selectedObject.getPreviousSlibing() != null; 2030 } else if (asg instanceof GridSelectionGroup) { 2031 final GridSelectionGroup gsg = (GridSelectionGroup) asg; 2032 final Set<Integer> indexes = gsg.getIndexes(); 2033 if (indexes.size() != 1) { 2034 return false; 2035 } 2036 final int index = indexes.iterator().next(); 2037 return index > 0; 2038 } else { 2039 assert selection.getGroup() == null : 2040 "Add implementation for " + selection.getGroup(); //NOI18N 2041 } 2042 return false; 2043 } 2044 2045 /** 2046 * Performs the select none control action. 2047 */ 2048 private void performSelectNone() { 2049 assert canPerformSelectNone(); 2050 selection.clear(); 2051 } 2052 2053 /** 2054 * Returns true if the selection is not empty. 2055 * 2056 * @return if the selection is not empty. 2057 */ 2058 private boolean canPerformSelectNone() { 2059 return getSelection().isEmpty() == false; 2060 } 2061 2062 /** 2063 * If selection contains single FXOM object and this an fx:include instance, then 2064 * returns the included file. Else returns null. 2065 * 2066 * If the selection is single and is an included FXOM object : 2067 * 1) if included file source does not start with /, 2068 * it's a path relative to the document location. 2069 * - if FXOM document location is null (document not saved yet), return null 2070 * - else return selection included file 2071 * 2072 * 2) if included file source starts with /, 2073 * it's a path relative to the document class loader. 2074 * - if FXOM document class loader is null, return null 2075 * - else return selection included file 2076 * 2077 * @return the included file associated to the selected object or null. 2078 */ 2079 public File getIncludedFile() { 2080 final AbstractSelectionGroup asg = getSelection().getGroup(); 2081 if (asg instanceof ObjectSelectionGroup == false) { 2082 return null; 2083 } 2084 final ObjectSelectionGroup osg = (ObjectSelectionGroup) asg; 2085 if (osg.getItems().size() != 1) { 2086 return null; 2087 } 2088 final FXOMObject fxomObject = osg.getItems().iterator().next(); 2089 if (fxomObject instanceof FXOMIntrinsic == false) { 2090 return null; 2091 } 2092 final FXOMIntrinsic fxomIntrinsic = (FXOMIntrinsic) fxomObject; 2093 if (fxomIntrinsic.getType() != FXOMIntrinsic.Type.FX_INCLUDE) { 2094 return null; 2095 } 2096 final String source = fxomIntrinsic.getSource(); 2097 if (source == null) { 2098 return null; // Can this happen ? 2099 } 2100 if (source.startsWith("/")) { //NOI18N 2101 // Source relative to FXOM document class loader 2102 final ClassLoader classLoader = getFxomDocument().getClassLoader(); 2103 if (classLoader != null) { 2104 final PrefixedValue pv = new PrefixedValue( 2105 PrefixedValue.Type.CLASSLOADER_RELATIVE_PATH, source); 2106 final URL url = pv.resolveClassLoaderRelativePath(classLoader); 2107 final File file; 2108 try { 2109 file = new File(url.toURI()); 2110 } catch (URISyntaxException ex) { 2111 throw new IllegalArgumentException(ex); 2112 } 2113 return file; 2114 } 2115 } else { 2116 // Source relative to FXOM document location 2117 final URL location = getFxmlLocation(); 2118 if (location != null) { 2119 final PrefixedValue pv = new PrefixedValue( 2120 PrefixedValue.Type.DOCUMENT_RELATIVE_PATH, source); 2121 final URL url = pv.resolveDocumentRelativePath(location); 2122 final File file; 2123 try { 2124 file = new File(url.toURI()); 2125 } catch (URISyntaxException ex) { 2126 throw new IllegalArgumentException(ex); 2127 } 2128 return file; 2129 } 2130 } 2131 return null; 2132 } 2133 2134 /** 2135 * Returns true if the selection is an included file that can be edited/revealed. 2136 * 2137 * @return true if the selection is an included file that can be edited/revealed. 2138 */ 2139 private boolean canPerformIncludedFileAction() { 2140 return getIncludedFile() != null; 2141 } 2142 2143 private void performEditIncludedFile() { 2144 assert canPerformIncludedFileAction(); // (1) 2145 final File includedFile = getIncludedFile(); 2146 assert includedFile != null; // Because of (1) 2147 try { 2148 EditorPlatform.open(includedFile.getAbsolutePath()); 2149 } catch (IOException ioe) { 2150 final ErrorDialog errorDialog = new ErrorDialog(null); 2151 errorDialog.setTitle(I18N.getString("error.file.open.title")); 2152 errorDialog.setMessage(I18N.getString("error.file.open.message", 2153 includedFile.getAbsolutePath())); 2154 errorDialog.setDebugInfoWithThrowable(ioe); 2155 errorDialog.showAndWait(); 2156 } 2157 } 2158 2159 private void performRevealIncludedFile() { 2160 assert canPerformIncludedFileAction(); // (1) 2161 final File includedFile = getIncludedFile(); 2162 assert includedFile != null; // Because of (1) 2163 try { 2164 EditorPlatform.revealInFileBrowser(includedFile); 2165 } catch (IOException ioe) { 2166 final ErrorDialog errorDialog = new ErrorDialog(null); 2167 errorDialog.setTitle(I18N.getString("error.file.reveal.title")); 2168 errorDialog.setMessage(I18N.getString("error.file.reveal.message", 2169 includedFile.getAbsolutePath())); 2170 errorDialog.setDetails(I18N.getString("error.write.details")); 2171 errorDialog.setDebugInfoWithThrowable(ioe); 2172 errorDialog.showAndWait(); 2173 } 2174 } 2175 2176 /** 2177 * Returns true if the 'set effect' action is permitted with the current 2178 * selection. 2179 * In other words, returns true if the selection contains only Node objects. 2180 * 2181 * @return true if the 'set effect' action is permitted. 2182 */ 2183 public boolean canPerformSetEffect() { 2184 return isSelectionNode(); 2185 } 2186 2187 /** 2188 * Performs the 'set effect' edit action. This method creates an instance of 2189 * the specified effect class and sets it in the effect property of the 2190 * selected objects. 2191 * 2192 * @param effectClass class of the effect to be added (never null) 2193 */ 2194 public void performSetEffect(Class<? extends Effect> effectClass) { 2195 assert canPerformSetEffect(); // (1) 2196 2197 final Effect effect = Utils.newInstance(effectClass); 2198 final PropertyName pn = new PropertyName("effect"); //NOI18N 2199 2200 final PropertyMetadata pm 2201 = Metadata.getMetadata().queryProperty(Node.class, pn); 2202 assert pm instanceof ValuePropertyMetadata; 2203 final ValuePropertyMetadata vpm = (ValuePropertyMetadata) pm; 2204 final ModifySelectionJob job = new ModifySelectionJob(vpm, effect, this); 2205 getJobManager().push(job); 2206 } 2207 2208 /** 2209 * Returns true if the 'add context menu' action is permitted with the current 2210 * selection. 2211 * In other words, returns true if the selection contains only Control objects. 2212 * 2213 * @return true if the 'add context menu' action is permitted. 2214 */ 2215 public boolean canPerformAddContextMenu() { 2216 return isSelectionControl(); 2217 } 2218 2219 /** 2220 * Performs the 'add context menu' edit action. This method creates an instance of 2221 * ContextMenu and sets it in the contextMenu property of the 2222 * selected objects. 2223 */ 2224 public void performAddContextMenu() { 2225 assert canPerformAddContextMenu(); 2226 final Job addContextMenuJob = new AddContextMenuToSelectionJob(this); 2227 getJobManager().push(addContextMenuJob); 2228 } 2229 2230 /** 2231 * Returns true if the 'add tooltip' action is permitted with the current 2232 * selection. 2233 * In other words, returns true if the selection contains only Control objects. 2234 * 2235 * @return true if the 'add tooltip' action is permitted. 2236 */ 2237 public boolean canPerformAddTooltip() { 2238 return isSelectionControl(); 2239 } 2240 2241 /** 2242 * Performs the 'add tooltip' edit action. This method creates an instance of 2243 * Tooltip and sets it in the tooltip property of the 2244 * selected objects. 2245 */ 2246 public void performAddTooltip() { 2247 assert canPerformAddTooltip(); // (1) 2248 final Job addTooltipJob = new AddTooltipToSelectionJob(this); 2249 getJobManager().push(addTooltipJob); 2250 } 2251 2252 /** 2253 * Returns the URL of the CSS style associated to EditorController class. 2254 * This stylesheet contains rules shareable by all other components of 2255 * SB kit. 2256 * 2257 * @return URL of EditorController class style sheet (never null). 2258 */ 2259 private static URL stylesheet = null; 2260 public synchronized static URL getStylesheet() { 2261 if (stylesheet == null) { 2262 stylesheet = EditorController.class.getResource("EditorController.css"); //NOI18N 2263 assert stylesheet != null; 2264 } 2265 return stylesheet; 2266 } 2267 2268 2269 /** 2270 * Returns the last directory selected from the file chooser. 2271 * 2272 * @return the last selected directory (never null). 2273 */ 2274 public static File getNextInitialDirectory() { 2275 return nextInitialDirectory; 2276 } 2277 2278 /** 2279 * @treatAsPrivate 2280 * 2281 * Updates the initial directory used by the file chooser. 2282 * 2283 * @param chosenFile the selected file from which the initial directory is set. 2284 */ 2285 public static void updateNextInitialDirectory(File chosenFile) { 2286 assert chosenFile != null; 2287 2288 final Path chosenFolder = chosenFile.toPath().getParent(); 2289 if (chosenFolder != null) { 2290 nextInitialDirectory = chosenFolder.toFile(); 2291 } 2292 } 2293 2294 /** 2295 * @treatAsPrivate 2296 * 2297 * @return true if the current FXOM document represents a 3D layout, false 2298 * otherwise. 2299 */ 2300 public boolean is3D() { 2301 boolean res = false; 2302 FXOMDocument doc = getFxomDocument(); 2303 2304 if (doc != null) { 2305 Object sgroot = doc.getSceneGraphRoot(); 2306 2307 if (sgroot instanceof Node) { 2308 final Bounds rootBounds = ((Node)sgroot).getLayoutBounds(); 2309 res = (rootBounds.getDepth() > 0); 2310 } 2311 } 2312 2313 return res; 2314 } 2315 2316 2317 /** 2318 * @treatAsPrivate 2319 * 2320 * @return true if the current FXOM document is an instance of a Node, false 2321 * otherwise. 2322 */ 2323 public boolean isNode() { 2324 boolean res = false; 2325 FXOMDocument doc = getFxomDocument(); 2326 2327 if (doc != null) { 2328 Object sgroot = doc.getSceneGraphRoot(); 2329 2330 if (sgroot instanceof Node) { 2331 res = true; 2332 } 2333 } 2334 2335 return res; 2336 } 2337 2338 /** 2339 * @treatAsPrivate 2340 * 2341 * @return true if the current selection objects are all instances of a Node, 2342 * false otherwise. 2343 */ 2344 public boolean isSelectionNode() { 2345 final AbstractSelectionGroup asg = selection.getGroup(); 2346 if (asg instanceof ObjectSelectionGroup) { 2347 final ObjectSelectionGroup osg = (ObjectSelectionGroup) asg; 2348 for (FXOMObject fxomObject : osg.getItems()) { 2349 final boolean isNode = fxomObject.getSceneGraphObject() instanceof Node; 2350 if (isNode == false) { 2351 return false; 2352 } 2353 } 2354 } else { 2355 return false; 2356 } 2357 return true; 2358 } 2359 2360 /* 2361 * Private 2362 */ 2363 2364 private boolean isSelectionControl() { 2365 final AbstractSelectionGroup asg = selection.getGroup(); 2366 if (asg instanceof ObjectSelectionGroup) { 2367 final ObjectSelectionGroup osg = (ObjectSelectionGroup) asg; 2368 for (FXOMObject fxomObject : osg.getItems()) { 2369 final boolean isControl = fxomObject.getSceneGraphObject() instanceof Control; 2370 if (isControl == false) { 2371 return false; 2372 } 2373 } 2374 } else { 2375 return false; 2376 } 2377 return true; 2378 } 2379 2380 private void updateFxomDocument(String fxmlText, URL fxmlLocation, ResourceBundle resources) throws IOException { 2381 final FXOMDocument newFxomDocument; 2382 2383 if (fxmlText != null) { 2384 newFxomDocument = new FXOMDocument(fxmlText, fxmlLocation, getLibrary().getClassLoader(), resources); 2385 } else { 2386 newFxomDocument = null; 2387 } 2388 jobManager.clear(); 2389 selection.clear(); 2390 messageLog.clear(); 2391 errorReport.setFxomDocument(newFxomDocument); 2392 fxomDocumentProperty.setValue(newFxomDocument); 2393 2394 watchingController.fxomDocumentDidChange(); 2395 2396 } 2397 2398 private final ChangeListener<ClassLoader> libraryClassLoaderListener 2399 = (ov, t, t1) -> libraryClassLoaderDidChange(); 2400 2401 private void libraryClassLoaderDidChange() { 2402 if (getFxomDocument() != null) { 2403 errorReport.forget(); 2404 getFxomDocument().setClassLoader(libraryProperty.get().getClassLoader()); 2405 } 2406 } 2407 2408 private void resourcesDidChange() { 2409 if (getFxomDocument() != null) { 2410 errorReport.forget(); 2411 getFxomDocument().setResources(getResources()); 2412 } 2413 } 2414 2415 private void jobManagerRevisionDidChange() { 2416 errorReport.forget(); 2417 watchingController.jobManagerRevisionDidChange(); 2418 // setPickModeEnabled(false); 2419 } 2420 }