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.app; 33 34 import com.oracle.javafx.scenebuilder.app.i18n.I18N; 35 import com.oracle.javafx.scenebuilder.app.info.InfoPanelController; 36 import com.oracle.javafx.scenebuilder.app.menubar.MenuBarController; 37 import com.oracle.javafx.scenebuilder.app.message.MessageBarController; 38 import com.oracle.javafx.scenebuilder.app.preferences.PreferencesController; 39 import com.oracle.javafx.scenebuilder.app.preferences.PreferencesRecordDocument; 40 import com.oracle.javafx.scenebuilder.app.preferences.PreferencesRecordGlobal; 41 import com.oracle.javafx.scenebuilder.app.preview.PreviewWindowController; 42 import com.oracle.javafx.scenebuilder.app.report.JarAnalysisReportController; 43 import com.oracle.javafx.scenebuilder.app.selectionbar.SelectionBarController; 44 import com.oracle.javafx.scenebuilder.app.skeleton.SkeletonWindowController; 45 import com.oracle.javafx.scenebuilder.kit.editor.EditorController; 46 import com.oracle.javafx.scenebuilder.kit.editor.EditorController.ControlAction; 47 import com.oracle.javafx.scenebuilder.kit.editor.EditorController.EditAction; 48 import com.oracle.javafx.scenebuilder.kit.editor.EditorPlatform; 49 import com.oracle.javafx.scenebuilder.kit.editor.job.Job; 50 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.ContentPanelController; 51 import com.oracle.javafx.scenebuilder.kit.editor.panel.css.CssPanelController; 52 import com.oracle.javafx.scenebuilder.kit.editor.panel.hierarchy.AbstractHierarchyPanelController; 53 import com.oracle.javafx.scenebuilder.kit.editor.panel.hierarchy.AbstractHierarchyPanelController.DisplayOption; 54 import com.oracle.javafx.scenebuilder.kit.editor.panel.hierarchy.HierarchyPanelController; 55 import com.oracle.javafx.scenebuilder.kit.editor.panel.inspector.InspectorPanelController; 56 import com.oracle.javafx.scenebuilder.kit.editor.panel.inspector.InspectorPanelController.SectionId; 57 import com.oracle.javafx.scenebuilder.kit.editor.panel.library.LibraryPanelController; 58 import com.oracle.javafx.scenebuilder.kit.editor.panel.util.AbstractFxmlWindowController; 59 import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.AbstractModalDialog; 60 import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.AbstractModalDialog.ButtonID; 61 import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.AlertDialog; 62 import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.ErrorDialog; 63 import com.oracle.javafx.scenebuilder.kit.editor.search.SearchController; 64 import com.oracle.javafx.scenebuilder.kit.editor.selection.AbstractSelectionGroup; 65 import com.oracle.javafx.scenebuilder.kit.editor.selection.GridSelectionGroup; 66 import com.oracle.javafx.scenebuilder.kit.editor.selection.ObjectSelectionGroup; 67 import com.oracle.javafx.scenebuilder.kit.editor.selection.Selection; 68 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument; 69 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMNodes; 70 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject; 71 import com.oracle.javafx.scenebuilder.kit.library.Library; 72 import com.oracle.javafx.scenebuilder.kit.library.user.UserLibrary; 73 import com.sun.javafx.scene.control.behavior.KeyBinding; 74 75 import java.io.File; 76 import java.io.IOException; 77 import java.io.UnsupportedEncodingException; 78 import java.net.MalformedURLException; 79 import java.net.URISyntaxException; 80 import java.net.URL; 81 import java.nio.file.Files; 82 import java.nio.file.NoSuchFileException; 83 import java.nio.file.Path; 84 import java.nio.file.Paths; 85 import java.nio.file.attribute.FileTime; 86 import java.util.ArrayList; 87 import java.util.Collections; 88 import java.util.Comparator; 89 import java.util.HashMap; 90 import java.util.List; 91 import java.util.Map; 92 93 import javafx.beans.InvalidationListener; 94 import javafx.beans.value.ChangeListener; 95 import javafx.event.ActionEvent; 96 import javafx.event.EventHandler; 97 import javafx.fxml.FXML; 98 import javafx.geometry.Insets; 99 import javafx.scene.Node; 100 import javafx.scene.control.Accordion; 101 import javafx.scene.control.ComboBox; 102 import javafx.scene.control.DialogPane; 103 import javafx.scene.control.Menu; 104 import javafx.scene.control.MenuButton; 105 import javafx.scene.control.MenuItem; 106 import javafx.scene.control.RadioMenuItem; 107 import javafx.scene.control.SplitPane; 108 import javafx.scene.control.TextInputControl; 109 import javafx.scene.input.Clipboard; 110 import javafx.scene.input.KeyCode; 111 import javafx.scene.input.KeyCombination; 112 import javafx.scene.input.KeyEvent; 113 import javafx.scene.layout.StackPane; 114 import javafx.scene.layout.VBox; 115 import javafx.stage.FileChooser; 116 import javafx.stage.FileChooser.ExtensionFilter; 117 import javafx.stage.WindowEvent; 118 119 /** 120 * 121 */ 122 public class DocumentWindowController extends AbstractFxmlWindowController { 123 124 125 public enum DocumentControlAction { 126 COPY, 127 SELECT_ALL, 128 SELECT_NONE, 129 SAVE_FILE, 130 SAVE_AS_FILE, 131 REVERT_FILE, 132 CLOSE_FILE, 133 REVEAL_FILE, 134 GOTO_CONTENT, 135 GOTO_PROPERTIES, 136 GOTO_LAYOUT, 137 GOTO_CODE, 138 TOGGLE_LIBRARY_PANEL, 139 TOGGLE_DOCUMENT_PANEL, 140 TOGGLE_CSS_PANEL, 141 TOGGLE_LEFT_PANEL, 142 TOGGLE_RIGHT_PANEL, 143 TOGGLE_OUTLINES_VISIBILITY, 144 TOGGLE_GUIDES_VISIBILITY, 145 SHOW_PREVIEW_WINDOW, 146 SHOW_PREVIEW_DIALOG, 147 ADD_SCENE_STYLE_SHEET, 148 SET_RESOURCE, 149 REMOVE_RESOURCE, 150 REVEAL_RESOURCE, 151 HELP, 152 SHOW_SAMPLE_CONTROLLER 153 } 154 155 public enum DocumentEditAction { 156 DELETE, 157 CUT, 158 PASTE, 159 IMPORT_FXML, 160 IMPORT_MEDIA, 161 INCLUDE_FXML 162 } 163 164 public enum ActionStatus { 165 CANCELLED, 166 DONE 167 } 168 169 private final EditorController editorController = new EditorController(); 170 private final MenuBarController menuBarController = new MenuBarController(this); 171 private final ContentPanelController contentPanelController = new ContentPanelController(editorController); 172 private final AbstractHierarchyPanelController hierarchyPanelController = new HierarchyPanelController(editorController); 173 private final InfoPanelController infoPanelController = new InfoPanelController(editorController); 174 private final InspectorPanelController inspectorPanelController = new InspectorPanelController(editorController); 175 private final CssPanelDelegate cssPanelDelegate = new CssPanelDelegate(inspectorPanelController, this); 176 private final CssPanelController cssPanelController = new CssPanelController(editorController, cssPanelDelegate); 177 private final LibraryPanelController libraryPanelController = new LibraryPanelController(editorController); 178 private final SelectionBarController selectionBarController = new SelectionBarController(editorController); 179 private final MessageBarController messageBarController = new MessageBarController(editorController); 180 private final SearchController librarySearchController = new SearchController(editorController); 181 private final SearchController inspectorSearchController = new SearchController(editorController); 182 private final SearchController cssPanelSearchController = new SearchController(editorController);; 183 private final SceneStyleSheetMenuController sceneStyleSheetMenuController = new SceneStyleSheetMenuController(this); 184 private final CssPanelMenuController cssPanelMenuController = new CssPanelMenuController(cssPanelController); 185 private final ResourceController resourceController = new ResourceController((this)); 186 private final DocumentWatchingController watchingController = new DocumentWatchingController(this); 187 188 // The controller below are created lazily because they need an owner 189 // and computing them here would be too costly (impact on start-up time): 190 // - PreviewWindowController 191 // - SkeletonWindowController 192 // - JarAnalysisReportController 193 private PreviewWindowController previewWindowController = null; 194 private SkeletonWindowController skeletonWindowController = null; 195 private JarAnalysisReportController jarAnalysisReportController = null; 196 197 @FXML private StackPane libraryPanelHost; 198 @FXML private StackPane librarySearchPanelHost; 199 @FXML private StackPane hierarchyPanelHost; 200 @FXML private StackPane infoPanelHost; 201 @FXML private StackPane contentPanelHost; 202 @FXML private StackPane inspectorPanelHost; 203 @FXML private StackPane inspectorSearchPanelHost; 204 @FXML private StackPane cssPanelHost; 205 @FXML private StackPane cssPanelSearchPanelHost; 206 @FXML private StackPane messageBarHost; 207 @FXML private Accordion documentAccordion; 208 @FXML private SplitPane mainSplitPane; 209 @FXML private SplitPane leftRightSplitPane; 210 @FXML private SplitPane libraryDocumentSplitPane; 211 212 @FXML private MenuButton libraryMenuButton; 213 @FXML private MenuItem libraryImportSelection; 214 @FXML private RadioMenuItem libraryViewAsList; 215 @FXML private RadioMenuItem libraryViewAsSections; 216 @FXML private MenuItem libraryReveal; 217 @FXML private Menu customLibraryMenu; 218 219 @FXML private MenuItem cssPanelShowStyledOnlyMi; 220 @FXML private MenuItem cssPanelSplitDefaultsMi; 221 222 @FXML private RadioMenuItem showInfoMenuItem; 223 @FXML private RadioMenuItem showFxIdMenuItem; 224 @FXML private RadioMenuItem showNodeIdMenuItem; 225 226 private SplitController bottomSplitController; 227 private SplitController leftSplitController; 228 private SplitController rightSplitController; 229 private SplitController librarySplitController; 230 private SplitController documentSplitController; 231 232 private FileTime loadFileTime; 233 private Job saveJob; 234 235 private static List<String> imageExtensions; 236 private static List<String> audioExtensions; 237 private static List<String> videoExtensions; 238 private static List<String> mediaExtensions; 239 240 private final EventHandler<KeyEvent> mainKeyEventFilter = event -> { 241 //------------------------------------------------------------------ 242 // TEXT INPUT CONTROL 243 //------------------------------------------------------------------ 244 // Common editing actions handled natively and defined as application accelerators 245 // 246 // The platform support is not mature/stable enough to rely on. 247 // Indeed, the behavior may differ : 248 // - when using system menu bar vs not using it 249 // - when using accelerators vs using menu items 250 // - depending on the focused control (TextField vs ComboBox) 251 // 252 // On SB side, we decide for now to consume events that may be handled natively 253 // so ALL actions are defined in our ApplicationMenu class. 254 // 255 // This may be revisit when platform implementation will be more reliable. 256 // 257 final Node focusOwner = getScene().getFocusOwner(); 258 final KeyCombination accelerator = getAccelerator(event); 259 if (isTextInputControlEditing(focusOwner) == true 260 && accelerator != null) { 261 for (KeyBinding binding : SBTextInputControlBindings.getBindings()) { 262 // The event is handled natively 263 if (binding.getSpecificity(null, event) > 0) { 264 // 265 // When using system menu bar, the event is handled natively 266 // before the application receives it : we just consume the event 267 // so the editing action is not performed a second time by the app. 268 if (menuBarController.getMenuBar().isUseSystemMenuBar()) { 269 event.consume(); 270 } 271 break; 272 } 273 } 274 } 275 276 //------------------------------------------------------------------ 277 // Hierarchy TreeView + select all 278 //------------------------------------------------------------------ 279 // Select all is handled natively by TreeView (= hierarchy panel control). 280 boolean modifierDown = (EditorPlatform.IS_MAC ? event.isMetaDown() : event.isControlDown()); 281 boolean isSelectAll = KeyCode.A.equals(event.getCode()) && modifierDown; 282 if (getHierarchyPanelController().getPanelControl().isFocused() && isSelectAll) { 283 // Consume the event so the control action is not performed natively. 284 event.consume(); 285 // When using system menu bar, the control action is performed by the app. 286 if (menuBarController.getMenuBar().isUseSystemMenuBar() == false) { 287 if (canPerformControlAction(DocumentControlAction.SELECT_ALL)) { 288 performControlAction(DocumentControlAction.SELECT_ALL); 289 } 290 } 291 } 292 293 // MenuItems define a single accelerator. 294 // BACK_SPACE key must be handled same way as DELETE key. 295 boolean isBackspace = KeyCode.BACK_SPACE.equals(event.getCode()); 296 if (isTextInputControlEditing(focusOwner) == false && isBackspace) { 297 if (canPerformEditAction(DocumentEditAction.DELETE)) { 298 performEditAction(DocumentEditAction.DELETE); 299 } 300 event.consume(); 301 } 302 }; 303 304 /* 305 * DocumentWindowController 306 */ 307 308 public DocumentWindowController() { 309 super(DocumentWindowController.class.getResource("DocumentWindow.fxml"), //NOI18N 310 I18N.getBundle(), false); // sizeToScene = false because sizing is defined in preferences 311 editorController.setLibrary(SceneBuilderApp.getSingleton().getUserLibrary()); 312 } 313 314 public EditorController getEditorController() { 315 return editorController; 316 } 317 318 public MenuBarController getMenuBarController() { 319 return menuBarController; 320 } 321 322 public ContentPanelController getContentPanelController() { 323 return contentPanelController; 324 } 325 326 public InspectorPanelController getInspectorPanelController() { 327 return inspectorPanelController; 328 } 329 330 public CssPanelController getCssPanelController() { 331 return cssPanelController; 332 } 333 334 public AbstractHierarchyPanelController getHierarchyPanelController() { 335 return hierarchyPanelController; 336 } 337 338 public InfoPanelController getInfoPanelController() { 339 return infoPanelController; 340 } 341 342 public PreviewWindowController getPreviewWindowController() { 343 return previewWindowController; 344 } 345 346 public SceneStyleSheetMenuController getSceneStyleSheetMenuController() { 347 return sceneStyleSheetMenuController; 348 } 349 350 public ResourceController getResourceController() { 351 return resourceController; 352 } 353 354 public DocumentWatchingController getWatchingController() { 355 return watchingController; 356 } 357 358 public SplitController getBottomSplitController() { 359 return bottomSplitController; 360 } 361 362 public SplitController getLeftSplitController() { 363 return leftSplitController; 364 } 365 366 public SplitController getRightSplitController() { 367 return rightSplitController; 368 } 369 370 public SplitController getLibrarySplitController() { 371 return librarySplitController; 372 } 373 374 public SplitController getDocumentSplitController() { 375 return documentSplitController; 376 } 377 378 public void loadFromFile(File fxmlFile) throws IOException { 379 final URL fxmlURL = fxmlFile.toURI().toURL(); 380 final String fxmlText = FXOMDocument.readContentFromURL(fxmlURL); 381 editorController.setFxmlTextAndLocation(fxmlText, fxmlURL); 382 updateLoadFileTime(); 383 updateStageTitle(); // No-op if fxml has not been loaded yet 384 updateFromDocumentPreferences(); 385 watchingController.update(); 386 } 387 388 public void loadFromURL(URL fxmlURL) { 389 assert fxmlURL != null; 390 try { 391 final String fxmlText = FXOMDocument.readContentFromURL(fxmlURL); 392 editorController.setFxmlTextAndLocation(fxmlText, null); 393 updateLoadFileTime(); 394 updateStageTitle(); // No-op if fxml has not been loaded yet 395 updateFromDocumentPreferences(); 396 watchingController.update(); 397 } catch(IOException x) { 398 throw new IllegalStateException(x); 399 } 400 } 401 402 public void loadWithDefaultContent() { 403 try { 404 editorController.setFxmlTextAndLocation("", null); //NOI18N 405 updateLoadFileTime(); 406 updateStageTitle(); // No-op if fxml has not been loaded yet 407 watchingController.update(); 408 } catch (IOException x) { 409 throw new IllegalStateException(x); 410 } 411 } 412 413 public void reload() throws IOException { 414 final FXOMDocument fxomDocument = editorController.getFxomDocument(); 415 assert (fxomDocument != null) && (fxomDocument.getLocation() != null); 416 final URL fxmlURL = fxomDocument.getLocation(); 417 final String fxmlText = FXOMDocument.readContentFromURL(fxmlURL); 418 editorController.setFxmlTextAndLocation(fxmlText, fxmlURL); 419 updateLoadFileTime(); 420 // Here we do not invoke updateStageTitleAndPreferences() neither watchingController.update() 421 } 422 423 public String getFxmlText() { 424 return editorController.getFxmlText(); 425 } 426 427 public void refreshLibraryDisplayOption(LibraryPanelController.DISPLAY_MODE option) { 428 switch (option) { 429 case LIST: 430 libraryViewAsList.setSelected(true); 431 break; 432 case SECTIONS: 433 libraryViewAsSections.setSelected(true); 434 break; 435 default: 436 assert false; 437 break; 438 } 439 libraryPanelController.setDisplayMode(option); 440 } 441 442 public void refreshHierarchyDisplayOption(DisplayOption option) { 443 switch(option) { 444 case INFO: 445 showInfoMenuItem.setSelected(true); 446 break; 447 case FXID: 448 showFxIdMenuItem.setSelected(true); 449 break; 450 case NODEID: 451 showNodeIdMenuItem.setSelected(true); 452 break; 453 default: 454 assert false; 455 break; 456 } 457 hierarchyPanelController.setDisplayOption(option); 458 } 459 460 public void refreshCssTableColumnsOrderingReversed(boolean cssTableColumnsOrderingReversed) { 461 cssPanelController.setTableColumnsOrderingReversed(cssTableColumnsOrderingReversed); 462 } 463 464 public static final String makeTitle(FXOMDocument fxomDocument) { 465 final String title; 466 467 if (fxomDocument == null) { 468 title = I18N.getString("label.no.document"); 469 } else if (fxomDocument.getLocation() == null) { 470 title = I18N.getString("label.untitled"); 471 } else { 472 String name = ""; //NOI18N 473 try { 474 final File toto = new File(fxomDocument.getLocation().toURI()); 475 name = toto.getName(); 476 } catch (URISyntaxException ex) { 477 throw new RuntimeException("Bug", ex); //NOI18N 478 } 479 title = name; 480 } 481 482 return title; 483 } 484 485 public boolean canPerformControlAction(DocumentControlAction controlAction) { 486 final boolean result; 487 488 switch(controlAction) { 489 case COPY: 490 result = canPerformCopy(); 491 break; 492 493 case SELECT_ALL: 494 result = canPerformSelectAll(); 495 break; 496 497 case SELECT_NONE: 498 result = canPerformSelectNone(); 499 break; 500 501 case SHOW_SAMPLE_CONTROLLER: 502 result = editorController.getFxomDocument() != null; 503 break; 504 505 case TOGGLE_LIBRARY_PANEL: 506 case TOGGLE_DOCUMENT_PANEL: 507 case TOGGLE_CSS_PANEL: 508 case TOGGLE_LEFT_PANEL: 509 case TOGGLE_RIGHT_PANEL: 510 case TOGGLE_OUTLINES_VISIBILITY: 511 case TOGGLE_GUIDES_VISIBILITY: 512 case SHOW_PREVIEW_WINDOW: 513 result = true; 514 break; 515 516 case SHOW_PREVIEW_DIALOG: 517 final FXOMDocument fxomDocument = editorController.getFxomDocument(); 518 if (fxomDocument != null) { 519 Object sceneGraphRoot = fxomDocument.getSceneGraphRoot(); 520 return sceneGraphRoot instanceof DialogPane; 521 } 522 result = false; 523 break; 524 525 case SAVE_FILE: 526 result = isDocumentDirty() 527 || editorController.getFxomDocument().getLocation() == null; // Save new empty document 528 break; 529 530 case SAVE_AS_FILE: 531 case CLOSE_FILE: 532 result = true; 533 break; 534 535 case REVERT_FILE: 536 result = isDocumentDirty() 537 && editorController.getFxomDocument().getLocation() != null; 538 break; 539 540 case REVEAL_FILE: 541 result = (editorController.getFxomDocument() != null) 542 && (editorController.getFxomDocument().getLocation() != null); 543 break; 544 545 case GOTO_CONTENT: 546 case GOTO_PROPERTIES: 547 case GOTO_LAYOUT: 548 case GOTO_CODE: 549 result = true; 550 break; 551 552 case ADD_SCENE_STYLE_SHEET: 553 result = true; 554 break; 555 556 case SET_RESOURCE: 557 result = true; 558 break; 559 560 case REMOVE_RESOURCE: 561 case REVEAL_RESOURCE: 562 result = resourceController.getResourceFile() != null; 563 break; 564 565 case HELP: 566 result = true; 567 break; 568 569 default: 570 result = false; 571 assert false; 572 break; 573 } 574 575 return result; 576 } 577 578 public void performControlAction(DocumentControlAction controlAction) { 579 assert canPerformControlAction(controlAction); 580 581 final PreferencesController pc = PreferencesController.getSingleton(); 582 final PreferencesRecordDocument recordDocument = pc.getRecordDocument(this); 583 584 switch(controlAction) { 585 case COPY: 586 performCopy(); 587 break; 588 589 case SELECT_ALL: 590 performSelectAll(); 591 break; 592 593 case SELECT_NONE: 594 performSelectNone(); 595 break; 596 597 case SHOW_PREVIEW_WINDOW: 598 if (previewWindowController == null) { 599 previewWindowController = new PreviewWindowController(editorController, getStage()); 600 previewWindowController.setToolStylesheet(getToolStylesheet()); 601 } 602 previewWindowController.openWindow(); 603 break; 604 605 case SHOW_PREVIEW_DIALOG: 606 if (previewWindowController == null) { 607 previewWindowController = new PreviewWindowController(editorController, getStage()); 608 previewWindowController.setToolStylesheet(getToolStylesheet()); 609 } 610 previewWindowController.openDialog(); 611 break; 612 613 case SAVE_FILE: 614 performSaveOrSaveAsAction(); 615 break; 616 617 case SAVE_AS_FILE: 618 performSaveAsAction(); 619 break; 620 621 case REVERT_FILE: 622 performRevertAction(); 623 break; 624 625 case CLOSE_FILE: 626 performCloseAction(); 627 break; 628 629 case REVEAL_FILE: 630 performRevealAction(); 631 break; 632 633 case GOTO_CONTENT: 634 contentPanelController.getGlassLayer().requestFocus(); 635 break; 636 637 case GOTO_PROPERTIES: 638 performGoToSection(SectionId.PROPERTIES); 639 break; 640 641 case GOTO_LAYOUT: 642 performGoToSection(SectionId.LAYOUT); 643 break; 644 645 case GOTO_CODE: 646 performGoToSection(SectionId.CODE); 647 break; 648 649 case TOGGLE_LEFT_PANEL: 650 if (leftSplitController.isTargetVisible()) { 651 assert librarySplitController.isTargetVisible() 652 || documentSplitController.isTargetVisible(); 653 // Hide Left => hide both Library + Document 654 librarySplitController.hideTarget(); 655 documentSplitController.hideTarget(); 656 leftSplitController.hideTarget(); 657 } else { 658 assert librarySplitController.isTargetVisible() == false 659 && documentSplitController.isTargetVisible() == false; 660 // Show Left => show both Library + Document 661 librarySplitController.showTarget(); 662 documentSplitController.showTarget(); 663 leftSplitController.showTarget(); 664 665 // This workarounds layout issues when showing Left 666 libraryDocumentSplitPane.layout(); 667 libraryDocumentSplitPane.setDividerPositions(0.5); 668 } 669 // Update preferences 670 recordDocument.setLibraryVisible(librarySplitController.isTargetVisible()); 671 recordDocument.setDocumentVisible(documentSplitController.isTargetVisible()); 672 recordDocument.setLeftVisible(leftSplitController.isTargetVisible()); 673 break; 674 675 case TOGGLE_RIGHT_PANEL: 676 rightSplitController.toggleTarget(); 677 // Update preferences 678 recordDocument.setRightVisible(rightSplitController.isTargetVisible()); 679 break; 680 681 case TOGGLE_CSS_PANEL: 682 // CSS panel is built lazely : initialize the CSS panel first 683 initializeCssPanel(); 684 bottomSplitController.toggleTarget(); 685 if (bottomSplitController.isTargetVisible()) { 686 // CSS panel is built lazely 687 // Need to update its table column ordering with preference value 688 final PreferencesRecordGlobal recordGlobal = pc.getRecordGlobal(); 689 refreshCssTableColumnsOrderingReversed(recordGlobal.isCssTableColumnsOrderingReversed()); 690 // Enable pick mode 691 editorController.setPickModeEnabled(true); 692 } else { 693 // Disable pick mode 694 editorController.setPickModeEnabled(false); 695 } 696 // Update preferences 697 recordDocument.setBottomVisible(bottomSplitController.isTargetVisible()); 698 break; 699 700 case TOGGLE_LIBRARY_PANEL: 701 if (librarySplitController.isTargetVisible()) { 702 assert leftSplitController.isTargetVisible(); 703 librarySplitController.hideTarget(); 704 if (documentSplitController.isTargetVisible() == false) { 705 leftSplitController.hideTarget(); 706 } 707 } else { 708 if (leftSplitController.isTargetVisible() == false) { 709 leftSplitController.showTarget(); 710 } 711 librarySplitController.showTarget(); 712 } 713 // Update preferences 714 recordDocument.setLibraryVisible(librarySplitController.isTargetVisible()); 715 recordDocument.setLeftVisible(leftSplitController.isTargetVisible()); 716 break; 717 718 case TOGGLE_DOCUMENT_PANEL: 719 if (documentSplitController.isTargetVisible()) { 720 assert leftSplitController.isTargetVisible(); 721 documentSplitController.hideTarget(); 722 if (librarySplitController.isTargetVisible() == false) { 723 leftSplitController.hideTarget(); 724 } 725 } else { 726 if (leftSplitController.isTargetVisible() == false) { 727 leftSplitController.showTarget(); 728 } 729 documentSplitController.showTarget(); 730 } 731 // Update preferences 732 recordDocument.setDocumentVisible(documentSplitController.isTargetVisible()); 733 recordDocument.setLeftVisible(leftSplitController.isTargetVisible()); 734 break; 735 736 case TOGGLE_OUTLINES_VISIBILITY: 737 contentPanelController.setOutlinesVisible( 738 ! contentPanelController.isOutlinesVisible()); 739 break; 740 741 case TOGGLE_GUIDES_VISIBILITY: 742 contentPanelController.setGuidesVisible( 743 ! contentPanelController.isGuidesVisible()); 744 break; 745 746 case ADD_SCENE_STYLE_SHEET: 747 sceneStyleSheetMenuController.performAddSceneStyleSheet(); 748 break; 749 750 case SET_RESOURCE: 751 resourceController.performSetResource(); 752 // Update preferences 753 recordDocument.setI18NResourceFile(getResourceFile()); 754 break; 755 756 case REMOVE_RESOURCE: 757 resourceController.performRemoveResource(); 758 // Update preferences 759 recordDocument.setI18NResourceFile(getResourceFile()); 760 break; 761 762 case REVEAL_RESOURCE: 763 resourceController.performRevealResource(); 764 break; 765 766 case HELP: 767 performHelp(); 768 break; 769 770 case SHOW_SAMPLE_CONTROLLER: 771 if (skeletonWindowController == null) { 772 skeletonWindowController = new SkeletonWindowController(editorController, getStage()); 773 skeletonWindowController.setToolStylesheet(getToolStylesheet()); 774 } 775 skeletonWindowController.openWindow(); 776 break; 777 778 default: 779 assert false; 780 break; 781 } 782 } 783 784 public boolean canPerformEditAction(DocumentEditAction editAction) { 785 final boolean result; 786 787 switch(editAction) { 788 case DELETE: 789 result = canPerformDelete(); 790 break; 791 792 case CUT: 793 result = canPerformCut(); 794 break; 795 796 case IMPORT_FXML: 797 case IMPORT_MEDIA: 798 result = true; 799 break; 800 801 case INCLUDE_FXML: 802 // Cannot include as root or if the document is not saved yet 803 final FXOMDocument fxomDocument = editorController.getFxomDocument(); 804 result = (fxomDocument != null) 805 && (fxomDocument.getFxomRoot() != null) 806 && (fxomDocument.getLocation() != null); 807 break; 808 809 case PASTE: 810 result = canPerformPaste(); 811 break; 812 813 default: 814 result = false; 815 assert false; 816 break; 817 } 818 819 return result; 820 } 821 822 public void performEditAction(DocumentEditAction editAction) { 823 assert canPerformEditAction(editAction); 824 825 switch(editAction) { 826 case DELETE: 827 performDelete(); 828 break; 829 830 case CUT: 831 performCut(); 832 break; 833 834 case IMPORT_FXML: 835 performImportFxml(); 836 break; 837 838 case IMPORT_MEDIA: 839 performImportMedia(); 840 break; 841 842 case INCLUDE_FXML: 843 performIncludeFxml(); 844 break; 845 846 case PASTE: 847 performPaste(); 848 break; 849 850 default: 851 assert false; 852 break; 853 } 854 } 855 856 public boolean isLeftPanelVisible() { 857 return leftSplitController.isTargetVisible(); 858 } 859 860 861 public boolean isRightPanelVisible() { 862 return rightSplitController.isTargetVisible(); 863 } 864 865 866 public boolean isBottomPanelVisible() { 867 return bottomSplitController.isTargetVisible(); 868 } 869 870 871 public boolean isHierarchyPanelVisible() { 872 return documentSplitController.isTargetVisible(); 873 } 874 875 876 public boolean isLibraryPanelVisible() { 877 return librarySplitController.isTargetVisible(); 878 } 879 880 public File getResourceFile() { 881 return resourceController.getResourceFile(); 882 } 883 884 public void setResourceFile(File file) { 885 resourceController.setResourceFile(file); 886 } 887 888 public boolean isDocumentDirty() { 889 return getEditorController().getJobManager().getCurrentJob() != saveJob; 890 } 891 892 public boolean isUnused() { 893 /* 894 * A document window controller is considered as "unused" if: //NOI18N 895 * 1) it has not fxml text 896 * 2) it is not dirty 897 * 3) it is unamed 898 */ 899 900 final FXOMDocument fxomDocument = editorController.getFxomDocument(); 901 final boolean noFxmlText = (fxomDocument == null) || (fxomDocument.getFxomRoot() == null); 902 final boolean clean = isDocumentDirty() == false; 903 final boolean noName = (fxomDocument != null) && (fxomDocument.getLocation() == null); 904 905 return noFxmlText && clean && noName; 906 } 907 908 public static class TitleComparator implements Comparator<DocumentWindowController> { 909 910 @Override 911 public int compare(DocumentWindowController d1, DocumentWindowController d2) { 912 final int result; 913 914 assert d1 != null; 915 assert d2 != null; 916 917 if (d1 == d2) { 918 result = 0; 919 } else { 920 final String t1 = d1.getStage().getTitle(); 921 final String t2 = d2.getStage().getTitle(); 922 assert t1 != null; 923 assert t2 != null; 924 result = t1.compareTo(t2); 925 } 926 927 return result; 928 } 929 930 } 931 932 public void initializeCssPanel() { 933 assert cssPanelHost != null; 934 assert cssPanelSearchPanelHost != null; 935 if (cssPanelHost.getChildren().isEmpty()) { 936 cssPanelHost.getChildren().add(cssPanelController.getPanelRoot()); 937 } 938 if (cssPanelSearchPanelHost.getChildren().isEmpty()) { 939 cssPanelSearchPanelHost.getChildren().add(cssPanelSearchController.getPanelRoot()); 940 addCssPanelSearchListener(); 941 } 942 } 943 944 public void updatePreferences() { 945 final PreferencesController pc = PreferencesController.getSingleton(); 946 final URL fxmlLocation = getEditorController().getFxmlLocation(); 947 if (fxmlLocation == null) { 948 // Document has not been saved => nothing to write 949 // This is the case with initial empty document 950 return; 951 } 952 // Update record document 953 final PreferencesRecordDocument recordDocument = pc.getRecordDocument(this); 954 recordDocument.writeToJavaPreferences(); 955 // Update record global 956 final PreferencesRecordGlobal recordGlobal = pc.getRecordGlobal(); 957 // recentItems may not contain the current document 958 // if the Open Recent -> Clear menu has been invoked 959 if (recordGlobal.containsRecentItem(fxmlLocation) == false) { 960 recordGlobal.addRecentItem(fxmlLocation); 961 } 962 } 963 964 /* 965 * AbstractFxmlWindowController 966 */ 967 968 @Override 969 protected void controllerDidLoadFxml() { 970 971 assert libraryPanelHost != null; 972 assert librarySearchPanelHost != null; 973 assert hierarchyPanelHost != null; 974 assert infoPanelHost != null; 975 assert contentPanelHost != null; 976 assert inspectorPanelHost != null; 977 assert inspectorSearchPanelHost != null; 978 assert messageBarHost != null; 979 assert mainSplitPane != null; 980 assert mainSplitPane.getItems().size() == 2; 981 assert leftRightSplitPane != null; 982 assert leftRightSplitPane.getItems().size() == 3; 983 assert libraryDocumentSplitPane != null; 984 assert libraryDocumentSplitPane.getItems().size() == 2; 985 assert documentAccordion != null; 986 assert documentAccordion.getPanes().isEmpty() == false; 987 assert libraryViewAsList != null; 988 assert libraryViewAsSections != null; 989 assert libraryReveal != null; 990 assert libraryMenuButton != null; 991 assert libraryImportSelection != null; 992 assert customLibraryMenu != null; 993 994 // Add a border to the Windows app, because of the specific window decoration on Windows. 995 if (EditorPlatform.IS_WINDOWS) { 996 getRoot().getStyleClass().add("windows-document-decoration");//NOI18N 997 } 998 999 mainSplitPane.addEventFilter(KeyEvent.KEY_PRESSED, mainKeyEventFilter); 1000 1001 // Insert the menu bar 1002 assert getRoot() instanceof VBox; 1003 final VBox rootVBox = (VBox) getRoot(); 1004 rootVBox.getChildren().add(0, menuBarController.getMenuBar()); 1005 1006 libraryPanelHost.getChildren().add(libraryPanelController.getPanelRoot()); 1007 librarySearchPanelHost.getChildren().add(librarySearchController.getPanelRoot()); 1008 hierarchyPanelHost.getChildren().add(hierarchyPanelController.getPanelRoot()); 1009 infoPanelHost.getChildren().add(infoPanelController.getPanelRoot()); 1010 contentPanelHost.getChildren().add(contentPanelController.getPanelRoot()); 1011 inspectorPanelHost.getChildren().add(inspectorPanelController.getPanelRoot()); 1012 inspectorSearchPanelHost.getChildren().add(inspectorSearchController.getPanelRoot()); 1013 messageBarHost.getChildren().add(messageBarController.getPanelRoot()); 1014 1015 messageBarController.getSelectionBarHost().getChildren().add( 1016 selectionBarController.getPanelRoot()); 1017 1018 inspectorSearchController.textProperty().addListener((ChangeListener<String>) (ov, oldStr, newStr) -> inspectorPanelController.setSearchPattern(newStr)); 1019 1020 librarySearchController.textProperty().addListener((ChangeListener<String>) (ov, oldStr, newStr) -> libraryPanelController.setSearchPattern(newStr)); 1021 1022 bottomSplitController = new SplitController(mainSplitPane, SplitController.Target.LAST); 1023 leftSplitController = new SplitController(leftRightSplitPane, SplitController.Target.FIRST); 1024 rightSplitController = new SplitController(leftRightSplitPane, SplitController.Target.LAST); 1025 librarySplitController = new SplitController(libraryDocumentSplitPane, SplitController.Target.FIRST); 1026 documentSplitController = new SplitController(libraryDocumentSplitPane, SplitController.Target.LAST); 1027 1028 messageBarHost.heightProperty().addListener((InvalidationListener) o -> { 1029 final double h = messageBarHost.getHeight(); 1030 contentPanelHost.setPadding(new Insets(h, 0.0, 0.0, 0.0)); 1031 }); 1032 1033 documentAccordion.setExpandedPane(documentAccordion.getPanes().get(0)); 1034 1035 // Monitor the status of the document to set status icon accordingly in message bar 1036 getEditorController().getJobManager().revisionProperty().addListener((ChangeListener<Number>) (ov, t, t1) -> messageBarController.setDocumentDirty(isDocumentDirty())); 1037 1038 // Setup title of the Library Reveal menu item according the underlying o/s. 1039 final String revealMenuKey; 1040 if (EditorPlatform.IS_MAC) { 1041 revealMenuKey = "menu.title.reveal.mac"; 1042 } else if (EditorPlatform.IS_WINDOWS) { 1043 revealMenuKey = "menu.title.reveal.win"; 1044 } else { 1045 assert EditorPlatform.IS_LINUX; 1046 revealMenuKey = "menu.title.reveal.linux"; 1047 } 1048 libraryReveal.setText(I18N.getString(revealMenuKey)); 1049 1050 // We need to tune the content of the library menu according if there's 1051 // or not a selection likely to be dropped onto Library panel. 1052 libraryMenuButton.showingProperty().addListener((ChangeListener<Boolean>) (ov, t, t1) -> { 1053 if (t1) { 1054 AbstractSelectionGroup asg = getEditorController().getSelection().getGroup(); 1055 libraryImportSelection.setDisable(true); 1056 1057 if (asg instanceof ObjectSelectionGroup) { 1058 if (((ObjectSelectionGroup)asg).getItems().size() >= 1) { 1059 libraryImportSelection.setDisable(false); 1060 } 1061 } 1062 1063 // DTL-6439. The custom library menu shall be enabled only 1064 // in the case there is a user library directory on disk. 1065 Library lib = getEditorController().getLibrary(); 1066 if (lib instanceof UserLibrary) { 1067 File userLibDir = new File(((UserLibrary)lib).getPath()); 1068 if (userLibDir.canRead()) { 1069 customLibraryMenu.setDisable(false); 1070 } else { 1071 customLibraryMenu.setDisable(true); 1072 } 1073 } 1074 } 1075 }); 1076 } 1077 1078 @Override 1079 protected void controllerDidCreateStage() { 1080 updateStageTitle(); 1081 updateFromDocumentPreferences(); 1082 } 1083 1084 @Override 1085 public void openWindow() { 1086 1087 if (getStage().isShowing() == false) { 1088 // Starts watching document: 1089 // - editorController watches files referenced from the FXML text 1090 // - watchingController watches the document file, i18n resources, 1091 // preview stylesheets... 1092 assert editorController.isFileWatchingStarted() == false; 1093 editorController.startFileWatching(); 1094 watchingController.start(); 1095 } 1096 1097 super.openWindow(); 1098 1099 // Give focus to the library search TextField 1100 assert librarySearchController != null; 1101 librarySearchController.requestFocus(); 1102 } 1103 1104 @Override 1105 public void closeWindow() { 1106 1107 super.closeWindow(); 1108 1109 // Stops watching 1110 editorController.stopFileWatching(); 1111 watchingController.stop(); 1112 } 1113 1114 @Override 1115 public void onCloseRequest(WindowEvent event) { 1116 performCloseAction(); 1117 } 1118 1119 public boolean isFrontDocumentWindow() { 1120 return getStage().isFocused() 1121 || (previewWindowController != null && previewWindowController.getStage().isFocused()) 1122 || (skeletonWindowController != null && skeletonWindowController.getStage().isFocused()) 1123 || (jarAnalysisReportController != null && jarAnalysisReportController.getStage().isFocused()); 1124 } 1125 1126 public void performCloseFrontDocumentWindow() { 1127 if (getStage().isFocused()) { 1128 performCloseAction(); 1129 } else if (previewWindowController != null 1130 && previewWindowController.getStage().isFocused()) { 1131 previewWindowController.closeWindow(); 1132 } else if (skeletonWindowController != null 1133 && skeletonWindowController.getStage().isFocused()) { 1134 skeletonWindowController.closeWindow(); 1135 } else if (jarAnalysisReportController != null 1136 && jarAnalysisReportController.getStage().isFocused()) { 1137 jarAnalysisReportController.closeWindow(); 1138 } 1139 } 1140 1141 1142 @Override 1143 protected void toolStylesheetDidChange(String oldStylesheet) { 1144 super.toolStylesheetDidChange(oldStylesheet); 1145 editorController.setToolStylesheet(getToolStylesheet()); 1146 // previewWindowController should not be affected by tool style sheet 1147 if (skeletonWindowController != null) { 1148 skeletonWindowController.setToolStylesheet(getToolStylesheet()); 1149 } 1150 if (jarAnalysisReportController != null) { 1151 jarAnalysisReportController.setToolStylesheet(getToolStylesheet()); 1152 } 1153 } 1154 1155 1156 // 1157 // Inspector menu 1158 // 1159 @FXML 1160 void onInspectorShowAllAction(ActionEvent event) { 1161 inspectorPanelController.setShowMode(InspectorPanelController.ShowMode.ALL); 1162 1163 } 1164 1165 @FXML 1166 void onInspectorShowEditedAction(ActionEvent event) { 1167 inspectorPanelController.setShowMode(InspectorPanelController.ShowMode.EDITED); 1168 } 1169 1170 @FXML 1171 void onInspectorViewSectionsAction(ActionEvent event) { 1172 inspectorPanelController.setViewMode(InspectorPanelController.ViewMode.SECTION); 1173 } 1174 1175 @FXML 1176 void onInspectorViewByPropertyNameAction(ActionEvent event) { 1177 inspectorPanelController.setViewMode(InspectorPanelController.ViewMode.PROPERTY_NAME); 1178 } 1179 1180 @FXML 1181 void onInspectorViewByPropertyTypeAction(ActionEvent event) { 1182 inspectorPanelController.setViewMode(InspectorPanelController.ViewMode.PROPERTY_TYPE); 1183 } 1184 1185 // 1186 // CSS menu 1187 // 1188 1189 @FXML 1190 void onCssPanelViewRulesAction(ActionEvent event) { 1191 cssPanelMenuController.viewRules(); 1192 cssPanelSplitDefaultsMi.setDisable(true); 1193 cssPanelShowStyledOnlyMi.setDisable(true); 1194 } 1195 1196 @FXML 1197 void onCssPanelViewTableAction(ActionEvent event) { 1198 cssPanelMenuController.viewTable(); 1199 cssPanelSplitDefaultsMi.setDisable(false); 1200 cssPanelShowStyledOnlyMi.setDisable(false); 1201 } 1202 1203 @FXML 1204 void onCssPanelViewTextAction(ActionEvent event) { 1205 cssPanelMenuController.viewText(); 1206 cssPanelSplitDefaultsMi.setDisable(true); 1207 cssPanelShowStyledOnlyMi.setDisable(true); 1208 } 1209 1210 @FXML 1211 void onCssPanelCopyStyleablePathAction(ActionEvent event) { 1212 cssPanelMenuController.copyStyleablePath(); 1213 } 1214 1215 @FXML 1216 void onCssPanelSplitDefaultsAction(ActionEvent event) { 1217 cssPanelMenuController.splitDefaultsAction(cssPanelSplitDefaultsMi); 1218 } 1219 1220 @FXML 1221 void onCssPanelShowStyledOnlyAction(ActionEvent event) { 1222 cssPanelMenuController.showStyledOnly(cssPanelShowStyledOnlyMi); 1223 } 1224 1225 // 1226 // Hierarchy menu 1227 // 1228 @FXML 1229 void onHierarchyShowInfo(ActionEvent event) { 1230 hierarchyPanelController.setDisplayOption(AbstractHierarchyPanelController.DisplayOption.INFO); 1231 documentAccordion.setExpandedPane(documentAccordion.getPanes().get(0)); 1232 } 1233 1234 @FXML 1235 void onHierarchyShowFxId(ActionEvent event) { 1236 hierarchyPanelController.setDisplayOption(AbstractHierarchyPanelController.DisplayOption.FXID); 1237 documentAccordion.setExpandedPane(documentAccordion.getPanes().get(0)); 1238 } 1239 1240 @FXML 1241 void onHierarchyShowNodeId(ActionEvent event) { 1242 hierarchyPanelController.setDisplayOption(AbstractHierarchyPanelController.DisplayOption.NODEID); 1243 documentAccordion.setExpandedPane(documentAccordion.getPanes().get(0)); 1244 } 1245 1246 // 1247 // Library menu 1248 // 1249 @FXML 1250 void onLibraryImportJarFxml(ActionEvent event) { 1251 libraryPanelController.performImportJarFxml(); 1252 } 1253 1254 @FXML 1255 void onLibraryViewAsList(ActionEvent event) { 1256 if (libraryPanelController.getDisplayMode() != LibraryPanelController.DISPLAY_MODE.SEARCH) { 1257 libraryPanelController.setDisplayMode(LibraryPanelController.DISPLAY_MODE.LIST); 1258 } else { 1259 libraryPanelController.setPreviousDisplayMode(LibraryPanelController.DISPLAY_MODE.LIST); 1260 } 1261 } 1262 1263 @FXML 1264 void onLibraryViewAsSections(ActionEvent event) { 1265 if (libraryPanelController.getDisplayMode() != LibraryPanelController.DISPLAY_MODE.SEARCH) { 1266 libraryPanelController.setDisplayMode(LibraryPanelController.DISPLAY_MODE.SECTIONS); 1267 } else { 1268 libraryPanelController.setPreviousDisplayMode(LibraryPanelController.DISPLAY_MODE.SECTIONS); 1269 } 1270 } 1271 1272 // This method cannot be called if there is not a valid selection, a selection 1273 // eligible for being dropped onto Library panel. 1274 @FXML 1275 void onLibraryImportSelection(ActionEvent event) { 1276 AbstractSelectionGroup asg = getEditorController().getSelection().getGroup(); 1277 1278 if (asg instanceof ObjectSelectionGroup) { 1279 ObjectSelectionGroup osg = (ObjectSelectionGroup)asg; 1280 assert osg.getItems().isEmpty() == false; 1281 List<FXOMObject> selection = new ArrayList<>(osg.getItems()); 1282 libraryPanelController.performImportSelection(selection); 1283 } 1284 } 1285 1286 @FXML 1287 void onLibraryRevealCustomFolder(ActionEvent event) { 1288 String userLibraryPath = ((UserLibrary) getEditorController().getLibrary()).getPath(); 1289 try { 1290 EditorPlatform.revealInFileBrowser(new File(userLibraryPath)); 1291 } catch(IOException x) { 1292 final ErrorDialog errorDialog = new ErrorDialog(null); 1293 errorDialog.setMessage(I18N.getString("alert.reveal.failure.message", getStage().getTitle())); 1294 errorDialog.setDetails(I18N.getString("alert.reveal.failure.details")); 1295 errorDialog.setDebugInfoWithThrowable(x); 1296 errorDialog.showAndWait(); 1297 } 1298 } 1299 1300 @FXML 1301 void onLibraryShowJarAnalysisReport(ActionEvent event) { 1302 if (jarAnalysisReportController == null) { 1303 jarAnalysisReportController = new JarAnalysisReportController(getEditorController(), getStage()); 1304 jarAnalysisReportController.setToolStylesheet(getToolStylesheet()); 1305 } 1306 1307 jarAnalysisReportController.openWindow(); 1308 } 1309 1310 /* 1311 * Private 1312 */ 1313 1314 private boolean canPerformSelectAll() { 1315 final boolean result; 1316 final Node focusOwner = this.getScene().getFocusOwner(); 1317 if (isPopupEditing(focusOwner)) { 1318 return false; 1319 } else if (isTextInputControlEditing(focusOwner)) { 1320 final TextInputControl tic = getTextInputControl(focusOwner); 1321 final String text = tic.getText(); 1322 final String selectedText = tic.getSelectedText(); 1323 if (text == null || text.isEmpty()) { 1324 result = false; 1325 } else { 1326 // Check if the TextInputControl is not already ALL selected 1327 result = selectedText == null 1328 || selectedText.length() < tic.getText().length(); 1329 } 1330 } else { 1331 result = getEditorController().canPerformControlAction(ControlAction.SELECT_ALL); 1332 } 1333 return result; 1334 } 1335 1336 private void performSelectAll() { 1337 final Node focusOwner = this.getScene().getFocusOwner(); 1338 if (isTextInputControlEditing(focusOwner)) { 1339 final TextInputControl tic = getTextInputControl(focusOwner); 1340 tic.selectAll(); 1341 } else { 1342 this.getEditorController().performControlAction(ControlAction.SELECT_ALL); 1343 } 1344 } 1345 1346 private boolean canPerformSelectNone() { 1347 boolean result; 1348 final Node focusOwner = this.getScene().getFocusOwner(); 1349 if (isPopupEditing(focusOwner)) { 1350 return false; 1351 } else if (isTextInputControlEditing(focusOwner)) { 1352 final TextInputControl tic = getTextInputControl(focusOwner); 1353 result = tic.getSelectedText() != null && tic.getSelectedText().isEmpty() == false; 1354 } else { 1355 result = getEditorController().canPerformControlAction(ControlAction.SELECT_NONE); 1356 } 1357 return result; 1358 } 1359 1360 private void performSelectNone() { 1361 final Node focusOwner = this.getScene().getFocusOwner(); 1362 if (isTextInputControlEditing(focusOwner)) { 1363 final TextInputControl tic = getTextInputControl(focusOwner); 1364 tic.deselect(); 1365 } else { 1366 this.getEditorController().performControlAction(ControlAction.SELECT_NONE); 1367 } 1368 } 1369 1370 private boolean canPerformCopy() { 1371 boolean result; 1372 final Node focusOwner = this.getScene().getFocusOwner(); 1373 if (isPopupEditing(focusOwner)) { 1374 return false; 1375 } else if (isTextInputControlEditing(focusOwner)) { 1376 final TextInputControl tic = getTextInputControl(focusOwner); 1377 result = tic.getSelectedText() != null && tic.getSelectedText().isEmpty() == false; 1378 } else if (isCssRulesEditing(focusOwner) || isCssTextEditing(focusOwner)) { 1379 result = true; 1380 } else { 1381 result = getEditorController().canPerformControlAction(ControlAction.COPY); 1382 } 1383 return result; 1384 } 1385 1386 private void performCopy() { 1387 final Node focusOwner = this.getScene().getFocusOwner(); 1388 if (isTextInputControlEditing(focusOwner)) { 1389 final TextInputControl tic = getTextInputControl(focusOwner); 1390 tic.copy(); 1391 } else if (isCssRulesEditing(focusOwner)) { 1392 cssPanelController.copyRules(); 1393 } else if (isCssTextEditing(focusOwner)) { 1394 // CSS text pane is a WebView 1395 // Let the WebView handle the copy action natively 1396 } else { 1397 this.getEditorController().performControlAction(ControlAction.COPY); 1398 } 1399 } 1400 1401 private boolean canPerformCut() { 1402 boolean result; 1403 final Node focusOwner = this.getScene().getFocusOwner(); 1404 if (isPopupEditing(focusOwner)) { 1405 return false; 1406 } else if (isTextInputControlEditing(focusOwner)) { 1407 final TextInputControl tic = getTextInputControl(focusOwner); 1408 result = tic.getSelectedText() != null && tic.getSelectedText().isEmpty() == false; 1409 } else { 1410 result = getEditorController().canPerformEditAction(EditAction.CUT); 1411 } 1412 return result; 1413 } 1414 1415 private void performCut() { 1416 final Node focusOwner = this.getScene().getFocusOwner(); 1417 if (isTextInputControlEditing(focusOwner)) { 1418 final TextInputControl tic = getTextInputControl(focusOwner); 1419 tic.cut(); 1420 } else { 1421 this.getEditorController().performEditAction(EditAction.CUT); 1422 } 1423 } 1424 1425 private boolean canPerformPaste() { 1426 boolean result; 1427 final Node focusOwner = this.getScene().getFocusOwner(); 1428 // If there is FXML in the clipboard, we paste the FXML whatever the focus owner is 1429 if (getEditorController().canPerformEditAction(EditAction.PASTE)) { 1430 result = true; 1431 } else if (isTextInputControlEditing(focusOwner)) { 1432 result = Clipboard.getSystemClipboard().hasString(); 1433 } else { 1434 result = false; 1435 } 1436 return result; 1437 } 1438 1439 private void performPaste() { 1440 final Node focusOwner = this.getScene().getFocusOwner(); 1441 // If there is FXML in the clipboard, we paste the FXML whatever the focus owner is 1442 if (getEditorController().canPerformEditAction(EditAction.PASTE)) { 1443 this.getEditorController().performEditAction(EditAction.PASTE); 1444 // Give focus to content panel 1445 contentPanelController.getGlassLayer().requestFocus(); 1446 } else { 1447 assert isTextInputControlEditing(focusOwner); 1448 final TextInputControl tic = getTextInputControl(focusOwner); 1449 tic.paste(); 1450 } 1451 } 1452 1453 private boolean canPerformDelete() { 1454 boolean result; 1455 final Node focusOwner = this.getScene().getFocusOwner(); 1456 if (isTextInputControlEditing(focusOwner)) { 1457 final TextInputControl tic = getTextInputControl(focusOwner); 1458 result = tic.getCaretPosition() < tic.getLength(); 1459 } else { 1460 result = getEditorController().canPerformEditAction(EditAction.DELETE); 1461 } 1462 return result; 1463 } 1464 1465 private void performDelete() { 1466 1467 final Node focusOwner = this.getScene().getFocusOwner(); 1468 if (isTextInputControlEditing(focusOwner)) { 1469 final TextInputControl tic = getTextInputControl(focusOwner); 1470 tic.deleteNextChar(); 1471 } else { 1472 1473 // Collects all the selected objects 1474 final List<FXOMObject> selectedObjects = new ArrayList<>(); 1475 final Selection selection = editorController.getSelection(); 1476 if (selection.getGroup() instanceof ObjectSelectionGroup) { 1477 final ObjectSelectionGroup osg = (ObjectSelectionGroup) selection.getGroup(); 1478 selectedObjects.addAll(osg.getItems()); 1479 } else if (selection.getGroup() instanceof GridSelectionGroup) { 1480 final GridSelectionGroup gsg = (GridSelectionGroup) selection.getGroup(); 1481 selectedObjects.addAll(gsg.collectSelectedObjects()); 1482 } else { 1483 assert false; 1484 } 1485 1486 // Collects fx:ids in selected objects and their descendants. 1487 // We filter out toggle groups because their fx:ids are managed automatically. 1488 final Map<String, FXOMObject> fxIdMap = new HashMap<>(); 1489 for (FXOMObject selectedObject : selectedObjects) { 1490 fxIdMap.putAll(selectedObject.collectFxIds()); 1491 } 1492 FXOMNodes.removeToggleGroups(fxIdMap); 1493 1494 // Checks if deleted objects have some fx:ids and ask for confirmation. 1495 final boolean deleteConfirmed; 1496 if (fxIdMap.isEmpty()) { 1497 deleteConfirmed = true; 1498 } else { 1499 final String message; 1500 1501 if (fxIdMap.size() == 1) { 1502 if (selectedObjects.size() == 1) { 1503 message = I18N.getString("alert.delete.fxid1of1.message"); 1504 } else { 1505 message = I18N.getString("alert.delete.fxid1ofN.message"); 1506 } 1507 } else { 1508 if (selectedObjects.size() == fxIdMap.size()) { 1509 message = I18N.getString("alert.delete.fxidNofN.message"); 1510 } else { 1511 message = I18N.getString("alert.delete.fxidKofN.message"); 1512 } 1513 } 1514 1515 final AlertDialog d = new AlertDialog(getStage()); 1516 d.setMessage(message); 1517 d.setDetails(I18N.getString("alert.delete.fxid.details")); 1518 d.setOKButtonTitle(I18N.getString("label.delete")); 1519 1520 deleteConfirmed = (d.showAndWait() == AbstractModalDialog.ButtonID.OK); 1521 } 1522 1523 if (deleteConfirmed) { 1524 editorController.performEditAction(EditAction.DELETE); 1525 } 1526 } 1527 } 1528 1529 private void performImportFxml() { 1530 1531 final FileChooser fileChooser = new FileChooser(); 1532 final ExtensionFilter f 1533 = new ExtensionFilter(I18N.getString("file.filter.label.fxml"), 1534 "*.fxml"); //NOI18N 1535 fileChooser.getExtensionFilters().add(f); 1536 fileChooser.setInitialDirectory(EditorController.getNextInitialDirectory()); 1537 1538 File fxmlFile = fileChooser.showOpenDialog(getStage()); 1539 if (fxmlFile != null) { 1540 // See DTL-5948: on Linux we anticipate an extension less path. 1541 final String path = fxmlFile.getPath(); 1542 if (!path.endsWith(".fxml")) { //NOI18N 1543 fxmlFile = new File(path + ".fxml"); //NOI18N 1544 } 1545 1546 // Keep track of the user choice for next time 1547 EditorController.updateNextInitialDirectory(fxmlFile); 1548 1549 this.getEditorController().performImportFxml(fxmlFile); 1550 } 1551 } 1552 1553 private void performImportMedia() { 1554 1555 final FileChooser fileChooser = new FileChooser(); 1556 final ExtensionFilter imageFilter 1557 = new ExtensionFilter(I18N.getString("file.filter.label.image"), 1558 getImageExtensions()); 1559 final ExtensionFilter audioFilter 1560 = new ExtensionFilter(I18N.getString("file.filter.label.audio"), 1561 getAudioExtensions()); 1562 final ExtensionFilter videoFilter 1563 = new ExtensionFilter(I18N.getString("file.filter.label.video"), 1564 getVideoExtensions()); 1565 final ExtensionFilter mediaFilter 1566 = new ExtensionFilter(I18N.getString("file.filter.label.media"), 1567 getMediaExtensions()); 1568 1569 fileChooser.getExtensionFilters().add(mediaFilter); 1570 fileChooser.getExtensionFilters().add(imageFilter); 1571 fileChooser.getExtensionFilters().add(audioFilter); 1572 fileChooser.getExtensionFilters().add(videoFilter); 1573 1574 fileChooser.setInitialDirectory(EditorController.getNextInitialDirectory()); 1575 1576 File mediaFile = fileChooser.showOpenDialog(getStage()); 1577 if (mediaFile != null) { 1578 1579 // Keep track of the user choice for next time 1580 EditorController.updateNextInitialDirectory(mediaFile); 1581 1582 this.getEditorController().performImportMedia(mediaFile); 1583 } 1584 } 1585 1586 private static synchronized List<String> getImageExtensions() { 1587 if (imageExtensions == null) { 1588 imageExtensions = new ArrayList<>(); 1589 imageExtensions.add("*.jpg"); //NOI18N 1590 imageExtensions.add("*.jpeg"); //NOI18N 1591 imageExtensions.add("*.png"); //NOI18N 1592 imageExtensions.add("*.gif"); //NOI18N 1593 imageExtensions = Collections.unmodifiableList(imageExtensions); 1594 } 1595 return imageExtensions; 1596 } 1597 1598 private static synchronized List<String> getAudioExtensions() { 1599 if (audioExtensions == null) { 1600 audioExtensions = new ArrayList<>(); 1601 audioExtensions.add("*.aif"); //NOI18N 1602 audioExtensions.add("*.aiff"); //NOI18N 1603 audioExtensions.add("*.mp3"); //NOI18N 1604 audioExtensions.add("*.m4a"); //NOI18N 1605 audioExtensions.add("*.wav"); //NOI18N 1606 audioExtensions.add("*.m3u"); //NOI18N 1607 audioExtensions.add("*.m3u8"); //NOI18N 1608 audioExtensions = Collections.unmodifiableList(audioExtensions); 1609 } 1610 return audioExtensions; 1611 } 1612 1613 private static synchronized List<String> getVideoExtensions() { 1614 if (videoExtensions == null) { 1615 videoExtensions = new ArrayList<>(); 1616 videoExtensions.add("*.flv"); //NOI18N 1617 videoExtensions.add("*.fxm"); //NOI18N 1618 videoExtensions.add("*.mp4"); //NOI18N 1619 videoExtensions.add("*.m4v"); //NOI18N 1620 videoExtensions = Collections.unmodifiableList(videoExtensions); 1621 } 1622 return videoExtensions; 1623 } 1624 1625 private static synchronized List<String> getMediaExtensions() { 1626 if (mediaExtensions == null) { 1627 mediaExtensions = new ArrayList<>(); 1628 mediaExtensions.addAll(getImageExtensions()); 1629 mediaExtensions.addAll(getAudioExtensions()); 1630 mediaExtensions.addAll(getVideoExtensions()); 1631 mediaExtensions = Collections.unmodifiableList(mediaExtensions); 1632 } 1633 return mediaExtensions; 1634 } 1635 1636 private void performIncludeFxml() { 1637 1638 final FileChooser fileChooser = new FileChooser(); 1639 final ExtensionFilter f 1640 = new ExtensionFilter(I18N.getString("file.filter.label.fxml"), 1641 "*.fxml"); //NOI18N 1642 fileChooser.getExtensionFilters().add(f); 1643 fileChooser.setInitialDirectory(EditorController.getNextInitialDirectory()); 1644 1645 File fxmlFile = fileChooser.showOpenDialog(getStage()); 1646 if (fxmlFile != null) { 1647 // See DTL-5948: on Linux we anticipate an extension less path. 1648 final String path = fxmlFile.getPath(); 1649 if (!path.endsWith(".fxml")) { //NOI18N 1650 fxmlFile = new File(path + ".fxml"); //NOI18N 1651 } 1652 1653 // Keep track of the user choice for next time 1654 EditorController.updateNextInitialDirectory(fxmlFile); 1655 1656 this.getEditorController().performIncludeFxml(fxmlFile); 1657 } 1658 } 1659 1660 /** 1661 * Returns true if the specified node is part of the main scene and is 1662 * either a TextInputControl or a ComboBox. 1663 * 1664 * @param node the focused node of the main scene 1665 * @return 1666 */ 1667 private boolean isTextInputControlEditing(Node node) { 1668 return (node instanceof TextInputControl 1669 || node instanceof ComboBox); 1670 } 1671 1672 private TextInputControl getTextInputControl(Node node) { 1673 assert isTextInputControlEditing(node); 1674 final TextInputControl tic; 1675 if (node instanceof TextInputControl) { 1676 tic = (TextInputControl) node; 1677 } else { 1678 assert node instanceof ComboBox; 1679 final ComboBox<?> cb = (ComboBox<?>) node; 1680 tic = cb.getEditor(); 1681 } 1682 return tic; 1683 } 1684 1685 /** 1686 * Returns true if we are editing within a popup window : 1687 * either the specified node is showing a popup window 1688 * or the inline editing popup is showing. 1689 * 1690 * @param node the focused node of the main scene 1691 * @return 1692 */ 1693 private boolean isPopupEditing(Node node) { 1694 return (node instanceof MenuButton && ((MenuButton) node).isShowing()) 1695 || editorController.getInlineEditController().isWindowOpened(); 1696 } 1697 1698 private boolean isCssRulesEditing(Node node) { 1699 final Node cssRules = cssPanelController.getRulesPane(); 1700 if (cssRules != null) { 1701 return isDescendantOf(cssRules, node); 1702 } 1703 return false; 1704 } 1705 1706 private boolean isCssTextEditing(Node node) { 1707 final Node cssText = cssPanelController.getTextPane(); 1708 if (cssText != null) { 1709 return isDescendantOf(cssText, node); 1710 } 1711 return false; 1712 } 1713 1714 private boolean isDescendantOf(Node container, Node node) { 1715 Node child = node; 1716 while (child != null) { 1717 if (child == container) { 1718 return true; 1719 } 1720 child = child.getParent(); 1721 } 1722 return false; 1723 } 1724 1725 private KeyCombination getAccelerator(final KeyEvent event) { 1726 KeyCombination result = null; 1727 for (KeyCombination kc : menuBarController.getAccelerators()) { 1728 if (kc.match(event)) { 1729 result = kc; 1730 break; 1731 } 1732 } 1733 return result; 1734 } 1735 1736 private void updateStageTitle() { 1737 if (libraryPanelHost != null) { 1738 getStage().setTitle(makeTitle(editorController.getFxomDocument())); 1739 } // else controllerDidLoadFxml() will invoke me again 1740 } 1741 1742 private void updateFromDocumentPreferences() { 1743 if (libraryPanelHost != null) { // Layout is over 1744 // Refresh UI with preferences 1745 final PreferencesController pc = PreferencesController.getSingleton(); 1746 // Preferences global to the application 1747 final PreferencesRecordGlobal recordGlobal = pc.getRecordGlobal(); 1748 recordGlobal.refresh(this); 1749 // Preferences specific to the document 1750 final PreferencesRecordDocument recordDocument = pc.getRecordDocument(this); 1751 recordDocument.readFromJavaPreferences(); 1752 // Update UI accordingly 1753 recordDocument.refresh(); 1754 } 1755 } 1756 1757 private void resetDocumentPreferences() { 1758 final PreferencesController pc = PreferencesController.getSingleton(); 1759 final PreferencesRecordDocument recordDocument = pc.getRecordDocument(this); 1760 recordDocument.resetDocumentPreferences(); 1761 } 1762 1763 ActionStatus performSaveOrSaveAsAction() { 1764 final ActionStatus result; 1765 1766 if (editorController.getFxomDocument().getLocation() == null) { 1767 result = performSaveAsAction(); 1768 } else { 1769 result = performSaveAction(); 1770 } 1771 1772 if (result.equals(ActionStatus.DONE)) { 1773 messageBarController.setDocumentDirty(false); 1774 saveJob = getEditorController().getJobManager().getCurrentJob(); 1775 } 1776 1777 return result; 1778 } 1779 1780 private void addCssPanelSearchListener() { 1781 cssPanelSearchController.textProperty().addListener((ChangeListener<String>) (ov, oldStr, newStr) -> cssPanelController.setSearchPattern(newStr)); 1782 } 1783 1784 private void performGoToSection(SectionId sectionId) { 1785 // First make the right panel visible if not already the case 1786 if (isRightPanelVisible() == false) { 1787 performControlAction(DocumentControlAction.TOGGLE_RIGHT_PANEL); 1788 } 1789 inspectorPanelController.setExpandedSection(sectionId); 1790 } 1791 1792 private ActionStatus performSaveAction() { 1793 final FXOMDocument fxomDocument = editorController.getFxomDocument(); 1794 assert fxomDocument != null; 1795 assert fxomDocument.getLocation() != null; 1796 1797 ActionStatus result; 1798 if (editorController.canGetFxmlText()) { 1799 final Path fxmlPath; 1800 try { 1801 fxmlPath = Paths.get(fxomDocument.getLocation().toURI()); 1802 } catch(URISyntaxException x) { 1803 // Should not happen 1804 throw new RuntimeException("Bug in " + getClass().getSimpleName(), x); //NOI18N 1805 } 1806 final String fileName = fxmlPath.getFileName().toString(); 1807 1808 try { 1809 final boolean saveConfirmed; 1810 if (checkLoadFileTime()) { 1811 saveConfirmed = true; 1812 } else { 1813 final AlertDialog d = new AlertDialog(getStage()); 1814 d.setMessage(I18N.getString("alert.overwrite.message", fileName)); 1815 d.setDetails(I18N.getString("alert.overwrite.details")); 1816 d.setOKButtonVisible(true); 1817 d.setOKButtonTitle(I18N.getString("label.overwrite")); 1818 d.setDefaultButtonID(ButtonID.CANCEL); 1819 d.setShowDefaultButton(true); 1820 saveConfirmed = (d.showAndWait() == ButtonID.OK); 1821 } 1822 1823 if (saveConfirmed) { 1824 try { 1825 watchingController.removeDocumentTarget(); 1826 final byte[] fxmlBytes = editorController.getFxmlText().getBytes("UTF-8"); //NOI18N 1827 Files.write(fxmlPath, fxmlBytes); 1828 updateLoadFileTime(); 1829 watchingController.update(); 1830 1831 editorController.getMessageLog().logInfoMessage( 1832 "log.info.save.confirmation", I18N.getBundle(), fileName); 1833 result = ActionStatus.DONE; 1834 } catch(UnsupportedEncodingException x) { 1835 // Should not happen 1836 throw new RuntimeException("Bug", x); //NOI18N 1837 } 1838 } else { 1839 result = ActionStatus.CANCELLED; 1840 } 1841 } catch(IOException x) { 1842 final ErrorDialog d = new ErrorDialog(getStage()); 1843 d.setMessage(I18N.getString("alert.save.failure.message", fileName)); 1844 d.setDetails(I18N.getString("alert.save.failure.details")); 1845 d.setDebugInfoWithThrowable(x); 1846 d.showAndWait(); 1847 result = ActionStatus.CANCELLED; 1848 } 1849 } else { 1850 result = ActionStatus.CANCELLED; 1851 } 1852 1853 return result; 1854 } 1855 1856 1857 private ActionStatus performSaveAsAction() { 1858 1859 final ActionStatus result; 1860 if (editorController.canGetFxmlText()) { 1861 final FileChooser fileChooser = new FileChooser(); 1862 final FileChooser.ExtensionFilter f 1863 = new FileChooser.ExtensionFilter(I18N.getString("file.filter.label.fxml"), 1864 "*.fxml"); //NOI18N 1865 fileChooser.getExtensionFilters().add(f); 1866 fileChooser.setInitialDirectory(EditorController.getNextInitialDirectory()); 1867 1868 File fxmlFile = fileChooser.showSaveDialog(getStage()); 1869 if (fxmlFile == null) { 1870 result = ActionStatus.CANCELLED; 1871 } else { 1872 boolean forgetSave = false; 1873 // It is only on Linux where you can get the case the path doesn't 1874 // end with the extension, thanks the behavior of the FX 8 FileChooser 1875 // on this specific OS (see RT-31956). 1876 // Below we ask the user if the extension shall be added or not. 1877 // See DTL-5948. 1878 final String path = fxmlFile.getPath(); 1879 if (! path.endsWith(".fxml")) { //NOI18N 1880 try { 1881 URL alternateURL = new URL(fxmlFile.toURI().toURL().toExternalForm() + ".fxml"); //NOI18N 1882 File alternateFxmlFile = new File(alternateURL.toURI()); 1883 final AlertDialog d = new AlertDialog(getStage()); 1884 d.setMessage(I18N.getString("alert.save.noextension.message", fxmlFile.getName())); 1885 String details = I18N.getString("alert.save.noextension.details"); 1886 1887 if (alternateFxmlFile.exists()) { 1888 details += "\n" //NOI18N 1889 + I18N.getString("alert.save.noextension.details.overwrite", alternateFxmlFile.getName()); 1890 } 1891 1892 d.setDetails(details); 1893 d.setOKButtonVisible(true); 1894 d.setOKButtonTitle(I18N.getString("alert.save.noextension.savewith")); 1895 d.setDefaultButtonID(ButtonID.OK); 1896 d.setShowDefaultButton(true); 1897 d.setActionButtonDisable(false); 1898 d.setActionButtonVisible(true); 1899 d.setActionButtonTitle(I18N.getString("alert.save.noextension.savewithout")); 1900 1901 switch (d.showAndWait()) { 1902 case ACTION: 1903 // Nothing to do, we save with the no extension name 1904 break; 1905 case CANCEL: 1906 forgetSave = true; 1907 break; 1908 case OK: 1909 fxmlFile = alternateFxmlFile; 1910 break; 1911 } 1912 } catch (MalformedURLException | URISyntaxException ex) { 1913 forgetSave = true; 1914 } 1915 } 1916 1917 // Transform File into URL 1918 final URL newLocation; 1919 try { 1920 newLocation = fxmlFile.toURI().toURL(); 1921 } catch(MalformedURLException x) { 1922 // Should not happen 1923 throw new RuntimeException("Bug in " + getClass().getSimpleName(), x); //NOI18N 1924 } 1925 1926 // Checks if fxmlFile is the name of an already opened document 1927 final DocumentWindowController dwc 1928 = SceneBuilderApp.getSingleton().lookupDocumentWindowControllers(newLocation); 1929 if (dwc != null && dwc != this) { 1930 final Path fxmlPath = Paths.get(fxmlFile.toString()); 1931 final String fileName = fxmlPath.getFileName().toString(); 1932 final ErrorDialog d = new ErrorDialog(getStage()); 1933 d.setMessage(I18N.getString("alert.save.conflict.message", fileName)); 1934 d.setDetails(I18N.getString("alert.save.conflict.details")); 1935 d.showAndWait(); 1936 result = ActionStatus.CANCELLED; 1937 } else if (forgetSave) { 1938 result = ActionStatus.CANCELLED; 1939 } else { 1940 // Recalculates references if needed 1941 // TODO(elp) 1942 1943 // First change the location of the fxom document 1944 editorController.setFxmlLocation(newLocation); 1945 updateLoadFileTime(); 1946 updateStageTitle(); 1947 // We use same DocumentWindowController BUT we change its fxml : 1948 // => reset document preferences 1949 resetDocumentPreferences(); 1950 1951 watchingController.update(); 1952 1953 // Now performs a regular save action 1954 result = performSaveAction(); 1955 if (result.equals(ActionStatus.DONE)) { 1956 messageBarController.setDocumentDirty(false); 1957 saveJob = getEditorController().getJobManager().getCurrentJob(); 1958 } 1959 1960 // Keep track of the user choice for next time 1961 EditorController.updateNextInitialDirectory(fxmlFile); 1962 1963 // Update recent items with just saved file 1964 final PreferencesController pc = PreferencesController.getSingleton(); 1965 final PreferencesRecordGlobal recordGlobal = pc.getRecordGlobal(); 1966 recordGlobal.addRecentItem(fxmlFile); 1967 } 1968 } 1969 } else { 1970 result = ActionStatus.CANCELLED; 1971 } 1972 1973 return result; 1974 } 1975 1976 1977 private void performRevertAction() { 1978 assert editorController.getFxomDocument() != null; 1979 assert editorController.getFxomDocument().getLocation() != null; 1980 1981 final AlertDialog d = new AlertDialog(getStage()); 1982 d.setMessage(I18N.getString("alert.revert.question.message", getStage().getTitle())); 1983 d.setDetails(I18N.getString("alert.revert.question.details")); 1984 d.setOKButtonTitle(I18N.getString("label.revert")); 1985 1986 if (d.showAndWait() == AlertDialog.ButtonID.OK) { 1987 try { 1988 reload(); 1989 } catch(IOException x) { 1990 final ErrorDialog errorDialog = new ErrorDialog(null); 1991 errorDialog.setMessage(I18N.getString("alert.open.failure1.message", getStage().getTitle())); 1992 errorDialog.setDetails(I18N.getString("alert.open.failure1.details")); 1993 errorDialog.setDebugInfoWithThrowable(x); 1994 errorDialog.setTitle(I18N.getString("alert.title.open")); 1995 errorDialog.showAndWait(); 1996 SceneBuilderApp.getSingleton().documentWindowRequestClose(this); 1997 } 1998 } 1999 } 2000 2001 2002 ActionStatus performCloseAction() { 2003 2004 // Makes sure that our window is front 2005 getStage().toFront(); 2006 2007 // Check if an editing session is on going 2008 if (getEditorController().isTextEditingSessionOnGoing()) { 2009 // Check if we can commit the editing session 2010 if (getEditorController().canGetFxmlText() == false) { 2011 // Commit failed 2012 return ActionStatus.CANCELLED; 2013 } 2014 } 2015 2016 // Checks if there are some pending changes 2017 final boolean closeConfirmed; 2018 if (isDocumentDirty()) { 2019 2020 final AlertDialog d = new AlertDialog(getStage()); 2021 d.setMessage(I18N.getString("alert.save.question.message", getStage().getTitle())); 2022 d.setDetails(I18N.getString("alert.save.question.details")); 2023 d.setOKButtonTitle(I18N.getString("label.save")); 2024 d.setActionButtonTitle(I18N.getString("label.do.not.save")); 2025 d.setActionButtonVisible(true); 2026 2027 switch(d.showAndWait()) { 2028 default: 2029 case OK: 2030 if (editorController.getFxomDocument().getLocation() == null) { 2031 closeConfirmed = (performSaveAsAction() == ActionStatus.DONE); 2032 } else { 2033 closeConfirmed = (performSaveAction() == ActionStatus.DONE); 2034 } 2035 break; 2036 case CANCEL: 2037 closeConfirmed = false; 2038 break; 2039 case ACTION: // Do not save 2040 closeConfirmed = true; 2041 break; 2042 } 2043 2044 } else { 2045 // No pending changes 2046 closeConfirmed = true; 2047 } 2048 2049 // Closes if confirmed 2050 if (closeConfirmed) { 2051 SceneBuilderApp.getSingleton().documentWindowRequestClose(this); 2052 2053 // Write java preferences at close time 2054 updatePreferences(); 2055 } 2056 2057 return closeConfirmed ? ActionStatus.DONE : ActionStatus.CANCELLED; 2058 } 2059 2060 2061 private void performRevealAction() { 2062 assert editorController.getFxomDocument() != null; 2063 assert editorController.getFxomDocument().getLocation() != null; 2064 2065 final URL location = editorController.getFxomDocument().getLocation(); 2066 2067 try { 2068 final File fxmlFile = new File(location.toURI()); 2069 EditorPlatform.revealInFileBrowser(fxmlFile); 2070 } catch(IOException | URISyntaxException x) { 2071 final ErrorDialog errorDialog = new ErrorDialog(null); 2072 errorDialog.setMessage(I18N.getString("alert.reveal.failure.message", getStage().getTitle())); 2073 errorDialog.setDetails(I18N.getString("alert.reveal.failure.details")); 2074 errorDialog.setDebugInfoWithThrowable(x); 2075 errorDialog.showAndWait(); 2076 } 2077 } 2078 2079 2080 private void updateLoadFileTime() { 2081 2082 final URL fxmlURL = editorController.getFxmlLocation(); 2083 if (fxmlURL == null) { 2084 loadFileTime = null; 2085 } else { 2086 try { 2087 final Path fxmlPath = Paths.get(fxmlURL.toURI()); 2088 if (Files.exists(fxmlPath)) { 2089 loadFileTime = Files.getLastModifiedTime(fxmlPath); 2090 } else { 2091 loadFileTime = null; 2092 } 2093 } catch(URISyntaxException x) { 2094 throw new RuntimeException("Bug", x); //NOI18N 2095 } catch(IOException x) { 2096 loadFileTime = null; 2097 } 2098 } 2099 } 2100 2101 2102 private boolean checkLoadFileTime() throws IOException { 2103 assert editorController.getFxmlLocation() != null; 2104 2105 /* 2106 * loadFileTime == null 2107 * => fxml file does not exist 2108 * => TRUE 2109 * 2110 * loadFileTime != null 2111 * => fxml file does/did exist 2112 * 2113 * currentFileTime == null 2114 * => fxml file no longer exists 2115 * => TRUE 2116 * 2117 * currentFileTime != null 2118 * => fxml file still exists 2119 * => loadFileTime.compare(currentFileTime) == 0 2120 */ 2121 2122 boolean result; 2123 if (loadFileTime == null) { 2124 // editorController.getFxmlLocation() does not exist yet 2125 result = true; 2126 } else { 2127 try { 2128 // editorController.getFxmlLocation() still exists 2129 // Check if its file time matches loadFileTime 2130 Path fxmlPath = Paths.get(editorController.getFxmlLocation().toURI()); 2131 FileTime currentFileTime = Files.getLastModifiedTime(fxmlPath); 2132 result = loadFileTime.compareTo(currentFileTime) == 0; 2133 } catch(NoSuchFileException x) { 2134 // editorController.getFxmlLocation() no longer exists 2135 result = true; 2136 } catch(URISyntaxException x) { 2137 throw new RuntimeException("Bug", x); //NOI18N 2138 } 2139 } 2140 2141 return result; 2142 } 2143 2144 2145 private void performHelp() { 2146 try { 2147 EditorPlatform.open(EditorPlatform.DOCUMENTATION_URL); 2148 } catch (IOException ioe) { 2149 final ErrorDialog errorDialog = new ErrorDialog(null); 2150 errorDialog.setMessage(I18N.getString("alert.help.failure.message", EditorPlatform.DOCUMENTATION_URL)); 2151 errorDialog.setDetails(I18N.getString("alert.messagebox.failure.details")); 2152 errorDialog.setDebugInfoWithThrowable(ioe); 2153 errorDialog.showAndWait(); 2154 } 2155 } 2156 } 2157 2158 /** 2159 * This class setup key bindings for the TextInputControl type classes and 2160 * provide a way to access the key binding list. 2161 */ 2162 class SBTextInputControlBindings extends com.sun.javafx.scene.control.behavior.TextInputControlBindings { 2163 2164 private SBTextInputControlBindings() { 2165 assert false; 2166 } 2167 2168 public static List<KeyBinding> getBindings() { 2169 return BINDINGS; 2170 } 2171 }