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.DocumentWindowController.ActionStatus; 35 import com.oracle.javafx.scenebuilder.app.about.AboutWindowController; 36 import com.oracle.javafx.scenebuilder.app.i18n.I18N; 37 import com.oracle.javafx.scenebuilder.app.menubar.MenuBarController; 38 import com.oracle.javafx.scenebuilder.app.preferences.PreferencesController; 39 import com.oracle.javafx.scenebuilder.app.preferences.PreferencesRecordGlobal; 40 import com.oracle.javafx.scenebuilder.app.preferences.PreferencesWindowController; 41 import com.oracle.javafx.scenebuilder.app.template.FxmlTemplates; 42 import com.oracle.javafx.scenebuilder.app.template.TemplateDialogController; 43 import com.oracle.javafx.scenebuilder.kit.editor.EditorController; 44 import com.oracle.javafx.scenebuilder.kit.editor.EditorPlatform; 45 import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.AlertDialog; 46 import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.ErrorDialog; 47 import com.oracle.javafx.scenebuilder.kit.library.BuiltinLibrary; 48 import com.oracle.javafx.scenebuilder.kit.library.user.UserLibrary; 49 import com.oracle.javafx.scenebuilder.kit.metadata.Metadata; 50 import com.oracle.javafx.scenebuilder.kit.util.Deprecation; 51 import com.oracle.javafx.scenebuilder.kit.util.control.effectpicker.EffectPicker; 52 53 import java.io.File; 54 import java.io.IOException; 55 import java.net.URI; 56 import java.net.URISyntaxException; 57 import java.net.URL; 58 import java.nio.file.Path; 59 import java.nio.file.Paths; 60 import java.util.ArrayList; 61 import java.util.Collections; 62 import java.util.HashMap; 63 import java.util.List; 64 import java.util.Map; 65 import java.util.concurrent.CountDownLatch; 66 import java.util.logging.Level; 67 import java.util.logging.Logger; 68 69 import javafx.application.Application; 70 import javafx.application.Platform; 71 import javafx.beans.value.ChangeListener; 72 import javafx.stage.FileChooser; 73 import javafx.stage.Stage; 74 75 /** 76 * 77 */ 78 public class SceneBuilderApp extends Application implements AppPlatform.AppNotificationHandler { 79 80 public enum ApplicationControlAction { 81 82 ABOUT, 83 NEW_FILE, 84 NEW_ALERT_DIALOG, 85 NEW_ALERT_DIALOG_CSS, 86 NEW_ALERT_DIALOG_I18N, 87 NEW_BASIC_APPLICATION, 88 NEW_BASIC_APPLICATION_CSS, 89 NEW_BASIC_APPLICATION_I18N, 90 NEW_COMPLEX_APPLICATION, 91 NEW_COMPLEX_APPLICATION_CSS, 92 NEW_COMPLEX_APPLICATION_I18N, 93 OPEN_FILE, 94 CLOSE_FRONT_WINDOW, 95 USE_DEFAULT_THEME, 96 USE_DARK_THEME, 97 SHOW_PREFERENCES, 98 EXIT 99 } 100 101 public enum ToolTheme { 102 103 DEFAULT { 104 @Override 105 public String toString() { 106 return I18N.getString("prefs.tool.theme.default"); 107 } 108 }, 109 DARK { 110 @Override 111 public String toString() { 112 return I18N.getString("prefs.tool.theme.dark"); 113 } 114 } 115 } 116 117 private static SceneBuilderApp singleton; 118 private static String darkToolStylesheet; 119 private static final CountDownLatch launchLatch = new CountDownLatch(1); 120 121 private final List<DocumentWindowController> windowList = new ArrayList<>(); 122 private final PreferencesWindowController preferencesWindowController 123 = new PreferencesWindowController(); 124 private final AboutWindowController aboutWindowController 125 = new AboutWindowController(); 126 private UserLibrary userLibrary; 127 private ToolTheme toolTheme = ToolTheme.DEFAULT; 128 129 130 /* 131 * Public 132 */ 133 public static SceneBuilderApp getSingleton() { 134 return singleton; 135 } 136 137 public SceneBuilderApp() { 138 assert singleton == null; 139 singleton = this; 140 141 /* 142 * We spawn our two threads for handling background startup. 143 */ 144 final Runnable p0 = () -> backgroundStartPhase0(); 145 final Runnable p1 = () -> { 146 try { 147 launchLatch.await(); 148 backgroundStartPhase2(); 149 } catch(InterruptedException x) { 150 // JavaFX thread has been interrupted. Simply exits. 151 } 152 }; 153 final Thread phase0 = new Thread(p0, "Phase 0"); //NOI18N 154 final Thread phase1 = new Thread(p1, "Phase 1"); //NOI18N 155 phase0.setDaemon(true); 156 phase1.setDaemon(true); 157 158 // Note : if you suspect a race condition bug, comment the two next 159 // lines to make startup fully sequential. 160 phase0.start(); 161 phase1.start(); 162 } 163 164 public void performControlAction(ApplicationControlAction a, DocumentWindowController source) { 165 switch (a) { 166 case ABOUT: 167 aboutWindowController.openWindow(); 168 break; 169 170 case NEW_FILE: 171 final DocumentWindowController newWindow = makeNewWindow(); 172 newWindow.loadWithDefaultContent(); 173 newWindow.openWindow(); 174 break; 175 176 case NEW_ALERT_DIALOG: 177 case NEW_BASIC_APPLICATION: 178 case NEW_COMPLEX_APPLICATION: 179 performNewTemplate(a); 180 break; 181 182 case NEW_ALERT_DIALOG_CSS: 183 case NEW_ALERT_DIALOG_I18N: 184 case NEW_BASIC_APPLICATION_CSS: 185 case NEW_BASIC_APPLICATION_I18N: 186 case NEW_COMPLEX_APPLICATION_CSS: 187 case NEW_COMPLEX_APPLICATION_I18N: 188 performNewTemplateWithResources(a); 189 break; 190 191 case OPEN_FILE: 192 performOpenFile(source); 193 break; 194 195 case CLOSE_FRONT_WINDOW: 196 performCloseFrontWindow(); 197 break; 198 199 case USE_DEFAULT_THEME: 200 performUseToolTheme(ToolTheme.DEFAULT); 201 break; 202 203 case USE_DARK_THEME: 204 performUseToolTheme(ToolTheme.DARK); 205 break; 206 207 case SHOW_PREFERENCES: 208 preferencesWindowController.openWindow(); 209 break; 210 211 case EXIT: 212 performExit(); 213 break; 214 } 215 } 216 217 218 public boolean canPerformControlAction(ApplicationControlAction a, DocumentWindowController source) { 219 final boolean result; 220 switch (a) { 221 case ABOUT: 222 case NEW_FILE: 223 case NEW_ALERT_DIALOG: 224 case NEW_BASIC_APPLICATION: 225 case NEW_COMPLEX_APPLICATION: 226 case NEW_ALERT_DIALOG_CSS: 227 case NEW_ALERT_DIALOG_I18N: 228 case NEW_BASIC_APPLICATION_CSS: 229 case NEW_BASIC_APPLICATION_I18N: 230 case NEW_COMPLEX_APPLICATION_CSS: 231 case NEW_COMPLEX_APPLICATION_I18N: 232 case OPEN_FILE: 233 case SHOW_PREFERENCES: 234 case EXIT: 235 result = true; 236 break; 237 238 case CLOSE_FRONT_WINDOW: 239 result = windowList.isEmpty() == false; 240 break; 241 242 case USE_DEFAULT_THEME: 243 result = toolTheme != ToolTheme.DEFAULT; 244 break; 245 246 case USE_DARK_THEME: 247 result = toolTheme != ToolTheme.DARK; 248 break; 249 250 default: 251 result = false; 252 assert false; 253 break; 254 } 255 return result; 256 } 257 258 public void performOpenRecent(DocumentWindowController source, final File fxmlFile) { 259 assert fxmlFile != null && fxmlFile.exists(); 260 261 final List<File> fxmlFiles = new ArrayList<>(); 262 fxmlFiles.add(fxmlFile); 263 performOpenFiles(fxmlFiles, source); 264 } 265 266 public void documentWindowRequestClose(DocumentWindowController fromWindow) { 267 closeWindow(fromWindow); 268 } 269 270 public UserLibrary getUserLibrary() { 271 return userLibrary; 272 } 273 274 public List<DocumentWindowController> getDocumentWindowControllers() { 275 return Collections.unmodifiableList(windowList); 276 } 277 278 public DocumentWindowController lookupDocumentWindowControllers(URL fxmlLocation) { 279 assert fxmlLocation != null; 280 281 DocumentWindowController result = null; 282 try { 283 final URI fxmlURI = fxmlLocation.toURI(); 284 for (DocumentWindowController dwc : windowList) { 285 final URL docLocation = dwc.getEditorController().getFxmlLocation(); 286 if ((docLocation != null) && fxmlURI.equals(docLocation.toURI())) { 287 result = dwc; 288 break; 289 } 290 } 291 } catch (URISyntaxException x) { 292 // Should not happen 293 throw new RuntimeException("Bug in " + getClass().getSimpleName(), x); //NOI18N 294 } 295 296 return result; 297 } 298 299 public DocumentWindowController lookupUnusedDocumentWindowController() { 300 DocumentWindowController result = null; 301 302 for (DocumentWindowController dwc : windowList) { 303 if (dwc.isUnused()) { 304 result = dwc; 305 break; 306 } 307 } 308 309 return result; 310 } 311 312 public void toggleDebugMenu() { 313 final boolean visible; 314 315 if (windowList.isEmpty()) { 316 visible = false; 317 } else { 318 final DocumentWindowController dwc = windowList.get(0); 319 visible = dwc.getMenuBarController().isDebugMenuVisible(); 320 } 321 322 for (DocumentWindowController dwc : windowList) { 323 dwc.getMenuBarController().setDebugMenuVisible(!visible); 324 } 325 326 if (EditorPlatform.IS_MAC) { 327 MenuBarController.getSystemMenuBarController().setDebugMenuVisible(!visible); 328 } 329 } 330 331 public static synchronized String getDarkToolStylesheet() { 332 if (darkToolStylesheet == null) { 333 final URL url = SceneBuilderApp.class.getResource("css/ThemeDark.css"); //NOI18N 334 assert url != null; 335 darkToolStylesheet = url.toExternalForm(); 336 } 337 return darkToolStylesheet; 338 } 339 340 /* 341 * Application 342 */ 343 @Override 344 public void start(Stage stage) throws Exception { 345 launchLatch.countDown(); 346 setApplicationUncaughtExceptionHandler(); 347 348 try { 349 if (AppPlatform.requestStart(this, getParameters()) == false) { 350 // Start has been denied because another instance is running. 351 Platform.exit(); 352 } 353 // else { 354 // No other Scene Builder instance is already running. 355 // AppPlatform.requestStart() has/will invoke(d) handleLaunch(). 356 // start() has now finished its job and should imply return. 357 // } 358 359 } catch (IOException x) { 360 final ErrorDialog errorDialog = new ErrorDialog(null); 361 errorDialog.setTitle(I18N.getString("alert.title.start")); 362 errorDialog.setMessage(I18N.getString("alert.start.failure.message")); 363 errorDialog.setDetails(I18N.getString("alert.start.failure.details")); 364 errorDialog.setDebugInfoWithThrowable(x); 365 errorDialog.showAndWait(); 366 Platform.exit(); 367 } 368 369 logTimestamp(ACTION.START); 370 } 371 372 /* 373 * AppPlatform.AppNotificationHandler 374 */ 375 @Override 376 public void handleLaunch(List<String> files) { 377 setApplicationUncaughtExceptionHandler(); 378 379 // Creates the user library 380 userLibrary = new UserLibrary(AppPlatform.getUserLibraryFolder()); 381 382 userLibrary.explorationCountProperty().addListener((ChangeListener<Number>) (ov, t, t1) -> userLibraryExplorationCountDidChange()); 383 384 userLibrary.startWatching(); 385 386 if (files.isEmpty()) { 387 // Creates an empty document 388 final DocumentWindowController newWindow = makeNewWindow(); 389 newWindow.loadWithDefaultContent(); 390 newWindow.openWindow(); 391 392 // Show ScenicView Tool when the JVM is started with option -Dscenic. 393 // NetBeans: set it on [VM Options] line in [Run] category of project's Properties. 394 if (System.getProperty("scenic") != null) { //NOI18N 395 Platform.runLater(new ScenicViewStarter(newWindow.getScene())); 396 } 397 } else { 398 // Open files passed as arguments by the platform 399 handleOpenFilesAction(files); 400 } 401 402 // On Mac, AppPlatform disables implicit exit. 403 // So we need to set a default system menu bar. 404 if (Platform.isImplicitExit() == false) { 405 Deprecation.setDefaultSystemMenuBar(MenuBarController.getSystemMenuBarController().getMenuBar()); 406 } 407 } 408 409 @Override 410 public void handleOpenFilesAction(List<String> files) { 411 assert files != null; 412 assert files.isEmpty() == false; 413 414 final List<File> fileObjs = new ArrayList<>(); 415 for (String file : files) { 416 fileObjs.add(new File(file)); 417 } 418 419 performOpenFiles(fileObjs, null); 420 } 421 422 @Override 423 public void handleMessageBoxFailure(Exception x) { 424 final ErrorDialog errorDialog = new ErrorDialog(null); 425 errorDialog.setTitle(I18N.getString("alert.title.messagebox")); 426 errorDialog.setMessage(I18N.getString("alert.messagebox.failure.message")); 427 errorDialog.setDetails(I18N.getString("alert.messagebox.failure.details")); 428 errorDialog.setDebugInfoWithThrowable(x); 429 errorDialog.showAndWait(); 430 } 431 432 @Override 433 public void handleQuitAction() { 434 435 /* 436 * Note : this callback is called on Mac OS X only when the user 437 * selects the 'Quit App' command in the Application menu. 438 * 439 * Before calling this callback, FX automatically sends a close event 440 * to each open window ie DocumentWindowController.performCloseAction() 441 * is invoked for each open window. 442 * 443 * When we arrive here, windowList is empty if the user has confirmed 444 * the close operation for each window : thus exit operation can 445 * be performed. If windowList is not empty, this means the user has 446 * cancelled at least one close operation : in that case, exit operation 447 * should be not be executed. 448 */ 449 if (windowList.isEmpty()) { 450 logTimestamp(ACTION.STOP); 451 Platform.exit(); 452 } 453 } 454 455 /** 456 * Normally ignored in correctly deployed JavaFX application. 457 * But on Mac OS, this method seems to be called by the javafx launcher. 458 */ 459 public static void main(String[] args) { 460 launch(args); 461 } 462 463 /* 464 * Private 465 */ 466 public DocumentWindowController makeNewWindow() { 467 final DocumentWindowController result = new DocumentWindowController(); 468 windowList.add(result); 469 return result; 470 } 471 472 private void closeWindow(DocumentWindowController w) { 473 assert windowList.contains(w); 474 windowList.remove(w); 475 w.closeWindow(); 476 } 477 478 private static String displayName(String pathString) { 479 return Paths.get(pathString).getFileName().toString(); 480 } 481 482 /* 483 * Private (control actions) 484 */ 485 private void performOpenFile(DocumentWindowController fromWindow) { 486 final FileChooser fileChooser = new FileChooser(); 487 488 fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(I18N.getString("file.filter.label.fxml"), 489 "*.fxml")); //NOI18N 490 fileChooser.setInitialDirectory(EditorController.getNextInitialDirectory()); 491 final List<File> fxmlFiles = fileChooser.showOpenMultipleDialog(null); 492 if (fxmlFiles != null) { 493 assert fxmlFiles.isEmpty() == false; 494 EditorController.updateNextInitialDirectory(fxmlFiles.get(0)); 495 performOpenFiles(fxmlFiles, fromWindow); 496 } 497 } 498 499 private void performNewTemplate(ApplicationControlAction action) { 500 final DocumentWindowController newTemplateWindow = makeNewWindow(); 501 final URL url = FxmlTemplates.getContentURL(action); 502 newTemplateWindow.loadFromURL(url); 503 newTemplateWindow.openWindow(); 504 } 505 506 private void performNewTemplateWithResources(ApplicationControlAction action) { 507 final TemplateDialogController tdc = new TemplateDialogController(action); 508 tdc.setToolStylesheet(getToolStylesheet()); 509 tdc.openWindow(); 510 } 511 512 private void performCloseFrontWindow() { 513 if (preferencesWindowController != null 514 && preferencesWindowController.getStage().isFocused()) { 515 preferencesWindowController.closeWindow(); 516 } else { 517 for (DocumentWindowController dwc : windowList) { 518 if (dwc.isFrontDocumentWindow()) { 519 dwc.performCloseFrontDocumentWindow(); 520 break; 521 } 522 } 523 } 524 } 525 526 private void performOpenFiles(List<File> fxmlFiles, 527 DocumentWindowController fromWindow) { 528 assert fxmlFiles != null; 529 assert fxmlFiles.isEmpty() == false; 530 531 final Map<File, IOException> exceptions = new HashMap<>(); 532 for (File fxmlFile : fxmlFiles) { 533 try { 534 final DocumentWindowController dwc 535 = lookupDocumentWindowControllers(fxmlFile.toURI().toURL()); 536 if (dwc != null) { 537 // fxmlFile is already opened 538 dwc.getStage().toFront(); 539 } else { 540 // Open fxmlFile 541 final DocumentWindowController hostWindow; 542 final DocumentWindowController unusedWindow 543 = lookupUnusedDocumentWindowController(); 544 if (unusedWindow != null) { 545 hostWindow = unusedWindow; 546 } else { 547 hostWindow = makeNewWindow(); 548 } 549 hostWindow.loadFromFile(fxmlFile); 550 hostWindow.openWindow(); 551 } 552 } catch (IOException xx) { 553 exceptions.put(fxmlFile, xx); 554 } 555 } 556 557 switch (exceptions.size()) { 558 case 0: { // Good 559 // Update recent items with opened files 560 final PreferencesController pc = PreferencesController.getSingleton(); 561 final PreferencesRecordGlobal recordGlobal = pc.getRecordGlobal(); 562 recordGlobal.addRecentItems(fxmlFiles); 563 break; 564 } 565 case 1: { 566 final File fxmlFile = exceptions.keySet().iterator().next(); 567 final Exception x = exceptions.get(fxmlFile); 568 final ErrorDialog errorDialog = new ErrorDialog(null); 569 errorDialog.setMessage(I18N.getString("alert.open.failure1.message", displayName(fxmlFile.getPath()))); 570 errorDialog.setDetails(I18N.getString("alert.open.failure1.details")); 571 errorDialog.setDebugInfoWithThrowable(x); 572 errorDialog.setTitle(I18N.getString("alert.title.open")); 573 errorDialog.showAndWait(); 574 break; 575 } 576 default: { 577 final ErrorDialog errorDialog = new ErrorDialog(null); 578 if (exceptions.size() == fxmlFiles.size()) { 579 // Open operation has failed for all the files 580 errorDialog.setMessage(I18N.getString("alert.open.failureN.message")); 581 errorDialog.setDetails(I18N.getString("alert.open.failureN.details")); 582 } else { 583 // Open operation has failed for some files 584 errorDialog.setMessage(I18N.getString("alert.open.failureMofN.message", 585 exceptions.size(), fxmlFiles.size())); 586 errorDialog.setDetails(I18N.getString("alert.open.failureMofN.details")); 587 } 588 errorDialog.setTitle(I18N.getString("alert.title.open")); 589 errorDialog.showAndWait(); 590 break; 591 } 592 } 593 } 594 595 private void performExit() { 596 597 // Check if an editing session is on going 598 for (DocumentWindowController dwc : windowList) { 599 if (dwc.getEditorController().isTextEditingSessionOnGoing()) { 600 // Check if we can commit the editing session 601 if (dwc.getEditorController().canGetFxmlText() == false) { 602 // Commit failed 603 return; 604 } 605 } 606 } 607 608 // Collects the documents with pending changes 609 final List<DocumentWindowController> pendingDocs = new ArrayList<>(); 610 for (DocumentWindowController dwc : windowList) { 611 if (dwc.isDocumentDirty()) { 612 pendingDocs.add(dwc); 613 } 614 } 615 616 // Notifies the user if some documents are dirty 617 final boolean exitConfirmed; 618 switch (pendingDocs.size()) { 619 case 0: { 620 exitConfirmed = true; 621 break; 622 } 623 624 case 1: { 625 final DocumentWindowController dwc0 = pendingDocs.get(0); 626 exitConfirmed = dwc0.performCloseAction() == ActionStatus.DONE; 627 break; 628 } 629 630 default: { 631 assert pendingDocs.size() >= 2; 632 633 final AlertDialog d = new AlertDialog(null); 634 d.setMessage(I18N.getString("alert.review.question.message", pendingDocs.size())); 635 d.setDetails(I18N.getString("alert.review.question.details")); 636 d.setOKButtonTitle(I18N.getString("label.review.changes")); 637 d.setActionButtonTitle(I18N.getString("label.discard.changes")); 638 d.setActionButtonVisible(true); 639 640 switch (d.showAndWait()) { 641 default: 642 case OK: { // Review 643 int i = 0; 644 ActionStatus status; 645 do { 646 status = pendingDocs.get(i++).performCloseAction(); 647 } while ((status == ActionStatus.DONE) && (i < pendingDocs.size())); 648 exitConfirmed = (status == ActionStatus.DONE); 649 break; 650 } 651 case CANCEL: { 652 exitConfirmed = false; 653 break; 654 } 655 case ACTION: { // Do not review 656 exitConfirmed = true; 657 break; 658 } 659 } 660 break; 661 } 662 } 663 664 // Exit if confirmed 665 if (exitConfirmed) { 666 for (DocumentWindowController dwc : new ArrayList<>(windowList)) { 667 // Write to java preferences before closing 668 dwc.updatePreferences(); 669 documentWindowRequestClose(dwc); 670 } 671 logTimestamp(ACTION.STOP); 672 // TODO (elp): something else here ? 673 Platform.exit(); 674 } 675 } 676 677 private enum ACTION {START, STOP}; 678 679 private void logTimestamp(ACTION type) { 680 switch (type) { 681 case START: 682 Logger.getLogger(this.getClass().getName()).info(I18N.getString("log.start")); 683 break; 684 case STOP: 685 Logger.getLogger(this.getClass().getName()).info(I18N.getString("log.stop")); 686 break; 687 default: 688 assert false; 689 } 690 } 691 692 private void setApplicationUncaughtExceptionHandler() { 693 if (Thread.getDefaultUncaughtExceptionHandler() == null) { 694 // Register a Default Uncaught Exception Handler for the application 695 Thread.setDefaultUncaughtExceptionHandler(new SceneBuilderUncaughtExceptionHandler()); 696 } 697 } 698 699 private static class SceneBuilderUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{ 700 701 @Override 702 public void uncaughtException(Thread t, Throwable e) { 703 // Print the details of the exception in SceneBuilder log file 704 Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "An exception was thrown:", e); //NOI18N 705 } 706 } 707 708 709 private void performUseToolTheme(ToolTheme toolTheme) { 710 this.toolTheme = toolTheme; 711 712 final String toolStylesheet = getToolStylesheet(); 713 714 for (DocumentWindowController dwc : windowList) { 715 dwc.setToolStylesheet(toolStylesheet); 716 } 717 preferencesWindowController.setToolStylesheet(toolStylesheet); 718 aboutWindowController.setToolStylesheet(toolStylesheet); 719 } 720 721 722 private String getToolStylesheet() { 723 final String result; 724 725 switch(this.toolTheme) { 726 727 default: 728 case DEFAULT: 729 result = EditorController.getBuiltinToolStylesheet(); 730 break; 731 732 case DARK: 733 result = getDarkToolStylesheet(); 734 break; 735 } 736 737 return result; 738 } 739 740 741 /* 742 * Background startup 743 * 744 * To speed SB startup, we create two threads which anticipate some 745 * initialization tasks and offload the JFX thread: 746 * - 'Phase 0' thread executes tasks that do not require JFX initialization 747 * - 'Phase 1' thread executes tasks that requires JFX initialization 748 * 749 * Tasks executed here must be carefully chosen: 750 * 1) they must be thread-safe 751 * 2) they should be order-safe : whether they are executed in background 752 * or by the JFX thread should make no difference. 753 * 754 * Currently we simply anticipate creation of big singleton instances 755 * (like Metadata, Preferences...) 756 */ 757 758 private void backgroundStartPhase0() { 759 assert Platform.isFxApplicationThread() == false; // Warning 760 761 PreferencesController.getSingleton(); 762 Metadata.getMetadata(); 763 } 764 765 private void backgroundStartPhase2() { 766 assert Platform.isFxApplicationThread() == false; // Warning 767 assert launchLatch.getCount() == 0; // i.e JavaFX is initialized 768 769 BuiltinLibrary.getLibrary(); 770 if (EditorPlatform.IS_MAC) { 771 MenuBarController.getSystemMenuBarController(); 772 } 773 EffectPicker.getEffectClasses(); 774 } 775 776 private void userLibraryExplorationCountDidChange() { 777 // We can have 0, 1 or N FXML file, same for JAR one. 778 final int numOfFxmlFiles = userLibrary.getFxmlFileReports().size(); 779 final int numOfJarFiles = userLibrary.getJarReports().size(); 780 final int jarCount = userLibrary.getJarReports().size(); 781 final int fxmlCount = userLibrary.getFxmlFileReports().size(); 782 783 switch (numOfFxmlFiles + numOfJarFiles) { 784 case 0: // Case 0-0 785 final int previousNumOfJarFiles = userLibrary.getPreviousJarReports().size(); 786 final int previousNumOfFxmlFiles = userLibrary.getPreviousFxmlFileReports().size(); 787 if (previousNumOfFxmlFiles > 0 || previousNumOfJarFiles > 0) { 788 logInfoMessage("log.user.exploration.0"); 789 } 790 break; 791 case 1: 792 Path path; 793 if (numOfFxmlFiles == 1) { // Case 1-0 794 path = userLibrary.getFxmlFileReports().get(0); 795 } else { // Case 0-1 796 path = userLibrary.getJarReports().get(0).getJar(); 797 } 798 logInfoMessage("log.user.exploration.1", path.getFileName()); 799 break; 800 default: 801 switch (numOfFxmlFiles) { 802 case 0: // Case 0-N 803 logInfoMessage("log.user.jar.exploration.n", jarCount); 804 break; 805 case 1: 806 final Path fxmlName = userLibrary.getFxmlFileReports().get(0).getFileName(); 807 if (numOfFxmlFiles == numOfJarFiles) { // Case 1-1 808 final Path jarName = userLibrary.getJarReports().get(0).getJar().getFileName(); 809 logInfoMessage("log.user.fxml.jar.exploration.1.1", fxmlName, jarName); 810 } else { // Case 1-N 811 logInfoMessage("log.user.fxml.jar.exploration.1.n", fxmlName, jarCount); 812 } 813 break; 814 default: 815 switch (numOfJarFiles) { 816 case 0: // Case N-0 817 logInfoMessage("log.user.fxml.exploration.n", fxmlCount); 818 break; 819 case 1: // Case N-1 820 final Path jarName = userLibrary.getJarReports().get(0).getJar().getFileName(); 821 logInfoMessage("log.user.fxml.jar.exploration.n.1", fxmlCount, jarName); 822 break; 823 default: // Case N-N 824 logInfoMessage("log.user.fxml.jar.exploration.n.n", fxmlCount, jarCount); 825 break; 826 } 827 break; 828 } 829 break; 830 } 831 } 832 833 private void logInfoMessage(String key) { 834 for (DocumentWindowController dwc : windowList) { 835 dwc.getEditorController().getMessageLog().logInfoMessage(key, I18N.getBundle()); 836 } 837 } 838 839 private void logInfoMessage(String key, Object... args) { 840 for (DocumentWindowController dwc : windowList) { 841 dwc.getEditorController().getMessageLog().logInfoMessage(key, I18N.getBundle(), args); 842 } 843 } 844 }