001package apps; 002 003import apps.gui3.tabbedpreferences.TabbedPreferences; 004import apps.gui3.tabbedpreferences.TabbedPreferencesAction; 005import apps.plaf.macosx.Application; 006import apps.util.Log4JUtil; 007 008import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 009 010import java.awt.*; 011import java.awt.event.*; 012import java.beans.PropertyChangeEvent; 013import java.beans.PropertyChangeListener; 014import java.io.*; 015import java.lang.reflect.InvocationTargetException; 016import java.net.*; 017import java.util.*; 018 019import javax.swing.*; 020import javax.swing.text.DefaultEditorKit; 021import javax.swing.text.JTextComponent; 022 023import jmri.*; 024import jmri.jmrit.jython.*; 025import jmri.jmrit.logixng.LogixNG_Manager; 026import jmri.jmrit.logixng.LogixNGPreferences; 027import jmri.jmrit.revhistory.FileHistory; 028import jmri.jmrix.*; 029import jmri.profile.*; 030import jmri.script.JmriScriptEngineManager; 031import jmri.util.*; 032import jmri.util.iharder.dnd.URIDrop; 033import jmri.util.prefs.JmriPreferencesActionFactory; 034import jmri.util.swing.*; 035 036/** 037 * Base class for JMRI applications. 038 * 039 * @author Bob Jacobsen Copyright 2003, 2007, 2008, 2010 040 * @author Dennis Miller Copyright 2005 041 * @author Giorgio Terdina Copyright 2008 042 * @author Matthew Harris Copyright (C) 2011 043 */ 044public class Apps extends JPanel implements PropertyChangeListener, WindowListener { 045 046 static String profileFilename; 047 private Action prefsAction; // defer initialization until needed so that Bundle accesses translate 048 049 @SuppressFBWarnings(value = {"ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", "SC_START_IN_CTOR"}, 050 justification = "only one application at a time. The thread is only called to help improve user experiance when opening the preferences, it is not critical for it to be run at this stage") 051 public Apps() { 052 053 super(true); 054 long start = System.nanoTime(); 055 log.trace("starting ctor at {}", start); 056 057 splash(false); 058 splash(true, true); 059 log.trace("splash screens up, about to setButtonSpace"); 060 setButtonSpace(); 061 log.trace("about to setJynstrumentSpace"); 062 setJynstrumentSpace(); 063 064 log.trace("setLogo"); 065 jmri.Application.setLogo(logo()); 066 log.trace("setURL"); 067 jmri.Application.setURL(line2()); 068 069 // Get configuration profile 070 log.trace("start to get configuration profile - locate files"); 071 // Needs to be done before loading a ConfigManager or UserPreferencesManager 072 FileUtil.createDirectory(FileUtil.getPreferencesPath()); 073 // Load permission manager 074 InstanceManager.getDefault(PermissionManager.class); 075 // Needs to be declared final as we might need to 076 // refer to this on the Swing thread 077 final File profileFile; 078 profileFilename = configFilename.replaceFirst(".xml", ".properties"); 079 // decide whether name is absolute or relative 080 if (!new File(profileFilename).isAbsolute()) { 081 // must be relative, but we want it to 082 // be relative to the preferences directory 083 profileFile = new File(FileUtil.getPreferencesPath() + profileFilename); 084 } else { 085 profileFile = new File(profileFilename); 086 } 087 ProfileManager.getDefault().setConfigFile(profileFile); 088 // See if the profile to use has been specified on the command line as 089 // a system property org.jmri.profile as a profile id. 090 if (System.getProperties().containsKey(ProfileManager.SYSTEM_PROPERTY)) { 091 ProfileManager.getDefault().setActiveProfile(System.getProperty(ProfileManager.SYSTEM_PROPERTY)); 092 } 093 log.trace("check if profile exists"); 094 // @see jmri.profile.ProfileManager#migrateToProfiles Javadoc for conditions handled here 095 if (!profileFile.exists()) { // no profile config for this app 096 log.trace("profileFile {} doesn't exist", profileFile); 097 try { 098 if (ProfileManager.getDefault().migrateToProfiles(configFilename)) { // migration or first use 099 // notify user of change only if migration occurred 100 // TODO: a real migration message 101 JmriJOptionPane.showMessageDialog(sp, 102 Bundle.getMessage("ConfigMigratedToProfile"), 103 jmri.Application.getApplicationName(), 104 JmriJOptionPane.INFORMATION_MESSAGE); 105 } 106 } catch (IOException | IllegalArgumentException ex) { 107 JmriJOptionPane.showMessageDialog(sp, 108 ex.getLocalizedMessage(), 109 jmri.Application.getApplicationName(), 110 JmriJOptionPane.ERROR_MESSAGE); 111 log.error("Exception migrating configuration to profiles: {}",ex.getMessage()); 112 } 113 } 114 log.trace("about to try getStartingProfile"); 115 try { 116 ProfileManagerDialog.getStartingProfile(sp); 117 Profile profile = ProfileManager.getDefault().getActiveProfile(); 118 if (profile != null) { 119 log.info("Starting with profile {}", profile.getId()); 120 } else { 121 log.info("Starting without a profile"); 122 } 123 124 // rapid language set; must follow up later with full setting as part of preferences 125 jmri.util.gui.GuiLafPreferencesManager.setLocaleMinimally(profile); 126 } catch (IOException ex) { 127 log.info("Profiles not configurable. Using fallback per-application configuration. Error: {}", ex.getMessage()); 128 } 129 130 // install a Preferences Action Factory. 131 InstanceManager.store(new AppsPreferencesActionFactory(), JmriPreferencesActionFactory.class); 132 133 // Install configuration manager and Swing error handler 134 // Constructing the AppsConfigurationManager also loads various configuration services 135 ConfigureManager cm = InstanceManager.setDefault(ConfigureManager.class, new AppsConfigurationManager()); 136 137 // record startup 138 String appString = String.format("%s (v%s)", jmri.Application.getApplicationName(), Version.getCanonicalVersion()); 139 InstanceManager.getDefault(FileHistory.class).addOperation("app", appString, null); 140 141 // Install abstractActionModel 142 InstanceManager.store(new apps.CreateButtonModel(), apps.CreateButtonModel.class); 143 144 // find preference file and set location in configuration manager 145 // Needs to be declared final as we might need to 146 // refer to this on the Swing thread 147 final File file; 148 File singleConfig; 149 File sharedConfig = null; 150 // decide whether name is absolute or relative 151 if (!new File(configFilename).isAbsolute()) { 152 // must be relative, but we want it to 153 // be relative to the preferences directory 154 singleConfig = new File(FileUtil.getUserFilesPath() + configFilename); 155 } else { 156 singleConfig = new File(configFilename); 157 } 158 try { 159 // get preferences file 160 sharedConfig = FileUtil.getFile(FileUtil.PROFILE + Profile.SHARED_CONFIG); 161 if (!sharedConfig.canRead()) { 162 sharedConfig = null; 163 } 164 } catch (FileNotFoundException ex) { 165 // ignore - sharedConfig will remain null in this case 166 } 167 // load config file if it exists 168 if (sharedConfig != null) { 169 file = sharedConfig; 170 } else { 171 file = singleConfig; 172 } 173 174 // ensure the UserPreferencesManager has loaded. Done on GUI 175 // thread as it can modify GUI objects 176 log.debug("*** About to getDefault(jmri.UserPreferencesManager.class) with file {}", file); 177 ThreadingUtil.runOnGUI(() -> { 178 InstanceManager.getDefault(jmri.UserPreferencesManager.class); 179 }); 180 log.debug("*** Done"); 181 182 // now (attempt to) load the config file 183 log.debug("Using config file(s) {}", file.getPath()); 184 if (file.exists()) { 185 log.debug("start load config file {}", file.getPath()); 186 try { 187 configOK = cm.load(file, true); 188 } catch (JmriException e) { 189 log.error("Unhandled problem loading configuration", e); 190 configOK = false; 191 } 192 log.debug("end load config file, OK={}", configOK); 193 } else { 194 log.info("No saved preferences, will open preferences window. Searched for {}", file.getPath()); 195 configOK = false; 196 } 197 198 // populate GUI 199 log.debug("Start UI"); 200 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 201 202 // done 203 long end = System.nanoTime(); 204 205 long elapsedTime = (end - start) / 1000000; 206 /* 207 This ensures that the message is displayed on the screen for a minimum of 2.5seconds, if the time taken 208 to get to this point in the code is longer that 2.5seconds then the wait is not invoked. 209 */ 210 long sleep = 2500 - elapsedTime; 211 if (sleep > 0) { 212 log.debug("Debug message was displayed for less than 2500ms ({}ms). Sleeping for {}ms to allow user sufficient time to do something.", 213 elapsedTime, sleep); 214 try { 215 Thread.sleep(sleep); 216 } catch (InterruptedException e) { 217 log.error("uexpected ", e); 218 } 219 } 220 221 FileUtil.logFilePaths(); 222 223 splash(false); 224 splash(true, false); 225 Toolkit.getDefaultToolkit().removeAWTEventListener(debugListener); 226 while (debugmsg) { 227 /*The user has pressed the interupt key that allows them to disable logixs 228 at start up we do not want to process any more information until the user 229 has answered the question */ 230 try { 231 Thread.sleep(1000); 232 } catch (InterruptedException e) { 233 log.error("Unexpected:",e); 234 } 235 } 236 // Now load deferred config items 237 if (file.exists()) { 238 if (file.equals(singleConfig)) { 239 // To avoid possible locks, deferred load should be 240 // performed on the Swing thread 241 if (SwingUtilities.isEventDispatchThread()) { 242 configDeferredLoadOK = doDeferredLoad(file); 243 } else { 244 try { 245 // Use invokeAndWait method as we don't want to 246 // return until deferred load is completed 247 SwingUtilities.invokeAndWait(new Runnable() { 248 @Override 249 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification = "configDeferredLoadOK write is semi-global") 250 public void run() { 251 configDeferredLoadOK = doDeferredLoad(file); 252 } 253 }); 254 } catch (InterruptedException | InvocationTargetException ex) { 255 log.error("Exception creating system console frame", ex); 256 } 257 } 258 } else { 259 // deferred loading is not done in the new config 260 configDeferredLoadOK = true; 261 } 262 } else { 263 configDeferredLoadOK = false; 264 } 265 // If preferences need to be migrated, do it now 266 if (sharedConfig == null && configOK == true && configDeferredLoadOK == true) { 267 log.info("Migrating preferences to new format..."); 268 // migrate preferences 269 InstanceManager.getOptionalDefault(TabbedPreferences.class).ifPresent(tp -> { 270 // tp.init(); 271 tp.saveContents(); 272 cm.storePrefs(); 273 }); 274 // notify user of change 275 log.info("Preferences have been migrated to new format."); 276 log.info("New preferences format will be used after JMRI is restarted."); 277 if (!GraphicsEnvironment.isHeadless()) { 278 Profile profile = ProfileManager.getDefault().getActiveProfile(); 279 JmriJOptionPane.showMessageDialog(sp, 280 Bundle.getMessage("SingleConfigMigratedToSharedConfig", profile), 281 jmri.Application.getApplicationName(), 282 JmriJOptionPane.INFORMATION_MESSAGE); 283 } 284 } 285 286 // Before starting to load preferences, make sure some managers are created. 287 // This is needed because these aren't particularly well-behaved during 288 // creation. 289 InstanceManager.getDefault(jmri.LogixManager.class); 290 InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class); 291 292 // preload script engines if requested 293 if (Boolean.getBoolean("org.jmri.python.preload")) { 294 new Thread(() -> { 295 try { 296 JmriScriptEngineManager.getDefault().initializeAllEngines(); 297 } catch (RuntimeException ex) { 298 log.error("Error in trying to initialize script interpreters {}", ex.getMessage()); 299 } 300 }, "initialize python interpreter").start(); 301 } 302 303 // kick off update of decoder index if needed 304 jmri.util.ThreadingUtil.runOnGUI(() -> { 305 try { 306 jmri.jmrit.decoderdefn.DecoderIndexFile.updateIndexIfNeeded(); 307 } catch (org.jdom2.JDOMException| java.io.IOException e) { 308 log.error("Exception trying to pre-load decoderIndex", e); 309 } 310 }); 311 312 // if the configuration didn't complete OK, pop the prefs frame and help 313 log.debug("Config OK? {}, deferred config OK? {}", configOK, configDeferredLoadOK); 314 if (!configOK || !configDeferredLoadOK) { 315 HelpUtil.displayHelpRef("package.apps.AppConfigPanelErrorPage"); 316 doPreferences(); 317 } 318 log.debug("Done with doPreferences, start statusPanel"); 319 320 add(statusPanel()); 321 log.debug("Done with statusPanel, start buttonSpace"); 322 add(buttonSpace()); 323 add(_jynstrumentSpace); 324 325 // Add a copy-cut-paste menu to all text fields that don't have a popup menu 326 long eventMask = AWTEvent.MOUSE_EVENT_MASK; 327 Toolkit.getDefaultToolkit().addAWTEventListener((AWTEvent e) -> { 328 if (e instanceof MouseEvent) { 329 JmriMouseEvent me = new JmriMouseEvent((MouseEvent) e); 330 if (me.isPopupTrigger() && me.getComponent() instanceof JTextComponent) { 331 var tc = (JTextComponent)me.getComponent(); 332 // provide a pop up if one not already defined 333 if (tc.getComponentPopupMenu() == null) { 334 final JTextComponent component1 = (JTextComponent) me.getComponent(); 335 final JPopupMenu menu = new JPopupMenu(); 336 JMenuItem item; 337 item = new JMenuItem(new DefaultEditorKit.CopyAction()); 338 item.setText("Copy"); 339 item.setEnabled(component1.getSelectionStart() != component1.getSelectionEnd()); 340 menu.add(item); 341 item = new JMenuItem(new DefaultEditorKit.CutAction()); 342 item.setText("Cut"); 343 item.setEnabled(component1.isEditable() && component1.getSelectionStart() != component1.getSelectionEnd()); 344 menu.add(item); 345 item = new JMenuItem(new DefaultEditorKit.PasteAction()); 346 item.setText("Paste"); 347 item.setEnabled(component1.isEditable()); 348 menu.add(item); 349 menu.show(me.getComponent(), me.getX(), me.getY()); 350 } 351 } 352 } 353 }, eventMask); 354 355 // do final activation 356 InstanceManager.getDefault(jmri.LogixManager.class).activateAllLogixs(); 357 InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class).initializeLayoutBlockPaths(); 358 359 LogixNG_Manager logixNG_Manager = InstanceManager.getDefault(LogixNG_Manager.class); 360 logixNG_Manager.setupAllLogixNGs(); 361 if (InstanceManager.getDefault(LogixNGPreferences.class).getStartLogixNGOnStartup() 362 && InstanceManager.getDefault(jmri.jmrit.logixng.LogixNG_Manager.class).isStartLogixNGsOnLoad()) { 363 logixNG_Manager.activateAllLogixNGs(); 364 } 365 366 log.debug("End constructor"); 367 } 368 369 private boolean doDeferredLoad(File file) { 370 boolean result; 371 log.debug("start deferred load from config"); 372 try { 373 ConfigureManager cmOD = InstanceManager.getNullableDefault(jmri.ConfigureManager.class); 374 if (cmOD != null) { 375 result = cmOD.loadDeferred(file); 376 } else { 377 log.error("Failed to get default configure manager"); 378 result = false; 379 } 380 } catch (JmriException e) { 381 log.error("Unhandled problem loading deferred configuration", e); 382 result = false; 383 } 384 log.debug("end deferred load from config file, OK={}", result); 385 return result; 386 } 387 388 /** 389 * Prepare the JPanel to contain buttons in the startup GUI. Since it's 390 * possible to add buttons via the preferences, this space may have 391 * additional buttons appended to it later. The default implementation here 392 * just creates an empty space for these to be added to. 393 */ 394 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", 395 justification = "only one application at a time") 396 protected void setButtonSpace() { 397 _buttonSpace = new JPanel(); 398 _buttonSpace.setLayout(new FlowLayout()); 399 } 400 static JComponent _jynstrumentSpace = null; 401 402 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", 403 justification = "only one application at a time") 404 protected void setJynstrumentSpace() { 405 _jynstrumentSpace = new JPanel(); 406 _jynstrumentSpace.setLayout(new FlowLayout()); 407 new URIDrop(_jynstrumentSpace, (URI[] uris) -> { 408 for (URI uri : uris ) { 409 ynstrument(new File(uri).getPath()); 410 } 411 }); 412 } 413 414 public static void ynstrument(String path) { 415 Jynstrument it = JynstrumentFactory.createInstrument(path, _jynstrumentSpace); 416 if (it == null) { 417 log.error("Error while creating Jynstrument {}", path); 418 return; 419 } 420 TransparencyUtils.setOpacityRec(it); 421 it.setVisible(true); 422 _jynstrumentSpace.setVisible(true); 423 _jynstrumentSpace.add(it); 424 } 425 426 /** 427 * Create default menubar. 428 * <p> 429 * This does not include the development menu. 430 * 431 * @param menuBar Menu bar to be populated 432 * @param wi WindowInterface where this menu bar will appear 433 */ 434 protected void createMenus(JMenuBar menuBar, WindowInterface wi) { 435 if (SystemType.isMacOSX()) { 436 Application.getApplication().setQuitHandler((EventObject eo) -> handleQuit()); 437 } 438 439 AppsMainMenu.createMenus(menuBar, wi, this, mainWindowHelpID()); 440 } 441 442 /** 443 * Open Preferences action. Often done due to error 444 */ 445 public void doPreferences() { 446 if (prefsAction == null) { 447 prefsAction = new TabbedPreferencesAction(); 448 } 449 prefsAction.actionPerformed(null); 450 } 451 452 /** 453 * Set the location of the window-specific help for the preferences pane. 454 * Made a separate method so if can be overridden for application specific 455 * preferences help 456 * 457 * @param frame The frame being described in the help system 458 * @param location The location within the JavaHelp system 459 */ 460 protected void setPrefsFrameHelp(JmriJFrame frame, String location) { 461 frame.addHelpMenu(location, true); 462 } 463 464 /** 465 * Returns the ID for the main window's help, which is application specific 466 * 467 * @return help identifier for main window 468 */ 469 protected String mainWindowHelpID() { 470 return "package.apps.Apps"; 471 } 472 473 protected String line1() { 474 return Bundle.getMessage("DefaultVersionCredit", jmri.Version.name()); 475 } 476 477 protected String line2() { 478 return "https://jmri.org/"; 479 } 480 481 protected String line3() { 482 return " "; 483 } 484 // line 4 485 JLabel cs4 = new JLabel(); 486 487 protected void buildLine4(JPanel pane) { 488 if (connection[0] != null) { 489 buildLine(connection[0], cs4, pane); 490 } 491 } 492 // line 5 optional 493 JLabel cs5 = new JLabel(); 494 495 protected void buildLine5(JPanel pane) { 496 if (connection[1] != null) { 497 buildLine(connection[1], cs5, pane); 498 } 499 } 500 // line 6 optional 501 JLabel cs6 = new JLabel(); 502 503 protected void buildLine6(JPanel pane) { 504 if (connection[2] != null) { 505 buildLine(connection[2], cs6, pane); 506 } 507 } 508 // line 7 optional 509 JLabel cs7 = new JLabel(); 510 511 protected void buildLine7(JPanel pane) { 512 if (connection[3] != null) { 513 buildLine(connection[3], cs7, pane); 514 } 515 } 516 517 protected void buildLine(ConnectionConfig conn, JLabel cs, JPanel pane) { 518 if (conn.name().equals(JmrixConfigPane.NONE)) { 519 cs.setText(" "); 520 return; 521 } 522 523 log.debug("conn.name() is {} ", conn.name()); // eg CAN via MERG Network Interface 524 log.debug("conn.getConnectionName() is {} ", conn.getConnectionName()); // eg MERG2 525 log.debug("conn.getManufacturer() is {} ", conn.getManufacturer()); // eg MERG 526 527 ConnectionStatus.instance().addConnection(conn.getAdapter().getSystemConnectionMemo()); 528 cs.setFont(pane.getFont()); 529 updateLine(conn, cs); 530 pane.add(cs); 531 } 532 533 protected void updateLine(ConnectionConfig conn, JLabel cs) { 534 if (conn.getDisabled()) { 535 return; 536 } 537 String name = conn.getConnectionName(); 538 if (name == null) { 539 name = conn.getManufacturer(); 540 } 541 if (ConnectionStatus.instance().isConnectionOk(conn.getAdapter().getSystemConnectionMemo())) { 542 cs.setForeground(Color.black); 543 String cf = Bundle.getMessage("ConnectionSucceeded", name, conn.name(), conn.getInfo()); 544 cs.setText(cf); 545 } else { 546 cs.setForeground(Color.red); 547 String cf = Bundle.getMessage("ConnectionFailed", name, conn.name(), conn.getInfo()); 548 cs.setText(cf); 549 } 550 551 this.revalidate(); 552 } 553 554 protected String line8() { 555 return " "; 556 } 557 558 protected String line9() { 559 return Bundle.getMessage("JavaVersionCredit", 560 System.getProperty("java.version", "<unknown>"), 561 Locale.getDefault()); 562 } 563 564 protected String logo() { 565 return "resources/logo.gif"; 566 } 567 568 /** 569 * Fill in the logo and status panel 570 * 571 * @return Properly-filled out JPanel 572 */ 573 protected JPanel statusPanel() { 574 JPanel pane1 = new JPanel(); 575 pane1.setLayout(new BoxLayout(pane1, BoxLayout.X_AXIS)); 576 log.debug("Fetch main logo: {}", logo()); 577 pane1.add(new JLabel(new ImageIcon(getToolkit().getImage(FileUtil.findURL(logo(), FileUtil.Location.ALL)), "JMRI logo"), JLabel.LEFT)); 578 pane1.add(Box.createRigidArea(new Dimension(15, 0))); // Some spacing between logo and status panel 579 580 log.debug("start labels"); 581 JPanel pane2 = new JPanel(); 582 583 pane2.setLayout(new BoxLayout(pane2, BoxLayout.Y_AXIS)); 584 pane2.add(new JLabel(line1())); 585 pane2.add(new JLabel(line2())); 586 pane2.add(new JLabel(line3())); 587 588 String name = ProfileManager.getDefault().getActiveProfileName(); 589 pane2.add(new JLabel(Bundle.getMessage("ActiveProfile", name))); 590 591 // add listener for Com port updates 592 ConnectionStatus.instance().addPropertyChangeListener(this); 593 int i = 0; 594 for (ConnectionConfig conn : InstanceManager.getDefault(ConnectionConfigManager.class)) { 595 if (!conn.getDisabled()) { 596 connection[i] = conn; 597 i++; 598 } 599 if (i > 3) { 600 break; 601 } 602 } 603 buildLine4(pane2); 604 buildLine5(pane2); 605 buildLine6(pane2); 606 buildLine7(pane2); 607 608 pane2.add(new JLabel(line8())); 609 pane2.add(new JLabel(line9())); 610 pane1.add(pane2); 611 return pane1; 612 } 613 //int[] connection = {-1,-1,-1,-1}; 614 ConnectionConfig[] connection = {null, null, null, null}; 615 616 /** 617 * Closing the main window is a shutdown request. 618 * 619 * @param e the event triggering the close 620 */ 621 @Override 622 public void windowClosing(WindowEvent e) { 623 if (!InstanceManager.getDefault(ShutDownManager.class).isShuttingDown() 624 && JmriJOptionPane.YES_OPTION == JmriJOptionPane.showConfirmDialog( 625 null, 626 Bundle.getMessage("MessageLongCloseWarning"), 627 Bundle.getMessage("MessageShortCloseWarning"), 628 JmriJOptionPane.YES_NO_OPTION)) { 629 handleQuit(); 630 } 631 // if get here, didn't quit, so don't close window 632 } 633 634 @Override 635 public void windowActivated(WindowEvent e) { 636 } 637 638 @Override 639 public void windowClosed(WindowEvent e) { 640 } 641 642 @Override 643 public void windowDeactivated(WindowEvent e) { 644 } 645 646 @Override 647 public void windowDeiconified(WindowEvent e) { 648 } 649 650 @Override 651 public void windowIconified(WindowEvent e) { 652 } 653 654 @Override 655 public void windowOpened(WindowEvent e) { 656 } 657 658 protected static void setJmriSystemProperty(String key, String value) { 659 try { 660 String current = System.getProperty("org.jmri.Apps." + key); 661 if (current == null) { 662 System.setProperty("org.jmri.Apps." + key, value); 663 } else if (!current.equals(value)) { 664 log.warn("JMRI property {} already set to {}, skipping reset to {}", key, current, value); 665 } 666 } catch (RuntimeException e) { 667 log.error("Unable to set JMRI property {} to {} due to exception", key, value, e); 668 } 669 } 670 671 /** 672 * Provide access to a place where applications can expect the configuration 673 * code to build run-time buttons. 674 * 675 * @see apps.startup.CreateButtonModelFactory 676 * @return null if no such space exists 677 */ 678 public static JComponent buttonSpace() { 679 return _buttonSpace; 680 } 681 static JComponent _buttonSpace = null; 682 static SplashWindow sp = null; 683 static AWTEventListener debugListener = null; 684 685 // TODO: Remove the "static" nature of much of the initialization someday. 686 // It exits to allow splash() to be called first-thing in main(), see 687 // apps.DecoderPro.DecoderPro.main(...) 688 // Or maybe, just not worry about this here, in the older base class, 689 // and address it in the newer apps.gui3.Apps3 as that's the base class of the future. 690 static boolean debugFired = false; // true if we've seen F8 during startup 691 static boolean debugmsg = false; // true while we're handling the "No Logix?" prompt window on startup 692 693 protected static void splash(boolean show) { 694 splash(show, false); 695 } 696 697 protected static void splash(boolean show, boolean debug) { 698 Log4JUtil.initLogging(); 699 if (debugListener == null && debug) { 700 // set a global listener for debug options 701 debugFired = false; 702 Toolkit.getDefaultToolkit().addAWTEventListener( 703 debugListener = (AWTEvent e) -> { 704 if (!debugFired) { 705 /*We set the debugmsg flag on the first instance of the user pressing any button 706 and the if the debugFired hasn't been set, this allows us to ensure that we don't 707 miss the user pressing F8, while we are checking*/ 708 debugmsg = true; 709 if (e.getID() == KeyEvent.KEY_PRESSED && e instanceof KeyEvent && ((KeyEvent) e).getKeyCode() == 119) { // F8 710 startupDebug(); 711 } else if (e.getID() == KeyEvent.KEY_PRESSED && e instanceof KeyEvent && ((KeyEvent) e).getKeyCode() == 120) { // F9 712 InstanceManager.getDefault(LogixNG_Manager.class).startLogixNGsOnLoad(false); 713 } else { 714 debugmsg = false; 715 } 716 } 717 }, 718 AWTEvent.KEY_EVENT_MASK); 719 } 720 721 // bring up splash window for startup 722 if (sp == null) { 723 if (debug) { 724 sp = new SplashWindow(splashDebugMsg()); 725 } else { 726 sp = new SplashWindow(); 727 } 728 } 729 sp.setVisible(show); 730 if (!show) { 731 sp.dispose(); 732 Toolkit.getDefaultToolkit().removeAWTEventListener(debugListener); 733 debugListener = null; 734 sp = null; 735 } 736 } 737 738 protected static JPanel splashDebugMsg() { 739 JLabel panelLabelDisableLogix = new JLabel(Bundle.getMessage("PressF8ToDebug")); 740 panelLabelDisableLogix.setFont(panelLabelDisableLogix.getFont().deriveFont(9f)); 741 JLabel panelLabelDisableLogixNG = new JLabel(Bundle.getMessage("PressF9ToInactivateLogixNG")); 742 panelLabelDisableLogixNG.setFont(panelLabelDisableLogix.getFont().deriveFont(9f)); 743 JPanel panel = new JPanel(); 744 panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); 745 panel.add(panelLabelDisableLogix); 746 panel.add(panelLabelDisableLogixNG); 747 return panel; 748 } 749 750 protected static void startupDebug() { 751 debugFired = true; 752 debugmsg = true; 753 754 Object[] options = {"Disable", "Enable"}; 755 756 int retval = JmriJOptionPane.showOptionDialog(null, 757 Bundle.getMessage("StartJMRIwithLogixEnabledDisabled"), 758 Bundle.getMessage("StartJMRIwithLogixEnabledDisabledTitle"), 759 JmriJOptionPane.DEFAULT_OPTION, 760 JmriJOptionPane.QUESTION_MESSAGE, null, options, options[0]); 761 762 if (retval != 0) { 763 debugmsg = false; 764 return; 765 } 766 InstanceManager.getDefault(jmri.LogixManager.class).setLoadDisabled(true); 767 InstanceManager.getDefault(LogixNG_Manager.class).setLoadDisabled(true); 768 log.info("Requested loading with Logixs and LogixNGs disabled."); 769 debugmsg = false; 770 } 771 772 /** 773 * The application decided to quit, handle that. 774 * 775 * @return always returns false 776 */ 777 public static boolean handleQuit() { 778 AppsBase.handleQuit(); 779 return false; 780 } 781 782 /** 783 * The application decided to restart, handle that. 784 */ 785 public static void handleRestart() { 786 AppsBase.handleRestart(); 787 } 788 789 /** 790 * Set up the configuration file name at startup. 791 * <p> 792 * The Configuration File name variable holds the name used to load the 793 * configuration file during later startup processing. Applications invoke 794 * this method to handle the usual startup hierarchy: 795 * <ul> 796 * <li>If an absolute filename was provided on the command line, use it 797 * <li>If a filename was provided that's not absolute, consider it to be in 798 * the preferences directory 799 * <li>If no filename provided, use a default name (that's application 800 * specific) 801 * </ul> 802 * This name will be used for reading and writing the preferences. It need 803 * not exist when the program first starts up. This name may be proceeded 804 * with <em>config=</em> and may not contain the equals sign (=). 805 * 806 * @param def Default value if no other is provided 807 * @param args Argument array from the main routine 808 */ 809 protected static void setConfigFilename(String def, String[] args) { 810 // skip if org.jmri.Apps.configFilename is set 811 if (System.getProperty("org.jmri.Apps.configFilename") != null) { 812 return; 813 } 814 // save the configuration filename if present on the command line 815 if (args.length >= 1 && args[0] != null && !args[0].contains("=")) { 816 def = args[0]; 817 log.debug("Config file was specified as: {}", args[0]); 818 } 819 for (String arg : args) { 820 String[] split = arg.split("=", 2); 821 if (split[0].equalsIgnoreCase("config")) { 822 def = split[1]; 823 log.debug("Config file was specified as: {}", arg); 824 } 825 } 826 Apps.configFilename = def; 827 setJmriSystemProperty("configFilename", def); 828 } 829 830 public static String getConfigFileName() { 831 return configFilename; 832 } 833 834 protected static void createFrame(Apps containedPane, JmriJFrame frame) { 835 // create the main frame and menus 836 // Create a WindowInterface object based on the passed-in Frame 837 JFrameInterface wi = new JFrameInterface(frame); 838 // Create a menu bar 839 containedPane.menuBar = new JMenuBar(); 840 841 // Create menu categories and add to the menu bar, add actions to menus 842 containedPane.createMenus(containedPane.menuBar, wi); 843 // connect Help target now that globalHelpBroker has been instantiated 844 containedPane.attachHelp(); 845 846 frame.setJMenuBar(containedPane.menuBar); 847 frame.getContentPane().add(containedPane); 848 849 // handle window close 850 frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 851 frame.addWindowListener(containedPane); 852 853 // pack and center this frame 854 frame.pack(); 855 Dimension screen = frame.getToolkit().getScreenSize(); 856 Dimension size = frame.getSize(); 857 858 // first set a default position and size 859 frame.setLocation((screen.width - size.width) / 2, (screen.height - size.height) / 2); 860 861 // then attempt set from stored preference 862 frame.setFrameLocation(); 863 864 // and finally show 865 frame.setVisible(true); 866 } 867 868 protected static void loadFile(String name) { 869 ConfigureManager cmOD = InstanceManager.getNullableDefault(jmri.ConfigureManager.class); 870 if (cmOD != null) { 871 URL pFile = cmOD.find(name); 872 if (pFile != null) { 873 try { 874 boolean load = cmOD.load(pFile); 875 if (!load) { 876 log.error("Failed to load file:{}", pFile); 877 } 878 } catch (JmriException e) { 879 log.error("Unhandled problem in loadFile", e); 880 } 881 } else { 882 log.warn("Could not find {} config file", name); 883 } 884 } else { 885 log.error("Failed to get default configure manager"); 886 } 887 } 888 889 static String configFilename = System.getProperty("org.jmri.Apps.configFilename", "jmriconfig2.xml"); // usually overridden, this is default 890 // The following MUST be protected for 3rd party applications 891 // (such as CATS) which are derived from this class. 892 @SuppressFBWarnings(value = "MS_PKGPROTECT", 893 justification = "The following MUST be protected for 3rd party applications (such as CATS) which are derived from this class.") 894 protected static boolean configOK; 895 @SuppressFBWarnings(value = "MS_PKGPROTECT", 896 justification = "The following MUST be protected for 3rd party applications (such as CATS) which are derived from this class.") 897 protected static boolean configDeferredLoadOK; 898 // GUI members 899 private JMenuBar menuBar; 900 901 static String nameString = "JMRI program"; 902 903 protected static void setApplication(String name) { 904 try { 905 jmri.Application.setApplicationName(name); 906 } catch (IllegalArgumentException | IllegalAccessException ex) { 907 log.warn("Unable to set application name", ex); 908 } 909 } 910 911 /** 912 * Set and log some startup information. This is intended to be the central 913 * connection point for common startup and logging. 914 * 915 * @param name Program/application name as known by the user 916 */ 917 @SuppressFBWarnings(value = "SLF4J_SIGN_ONLY_FORMAT",justification = "info message contains context information") 918 protected static void setStartupInfo(String name) { 919 // Set the application name 920 try { 921 jmri.Application.setApplicationName(name); 922 } catch (IllegalArgumentException | IllegalAccessException ex) { 923 log.warn("Unable to set application name", ex); 924 } 925 926 // Log the startup information 927 log.info("{}",Log4JUtil.startupInfo(name)); 928 } 929 930 @Override 931 public void propertyChange(PropertyChangeEvent ev) { 932 log.debug("property change: comm port status update"); 933 if (connection[0] != null) { 934 updateLine(connection[0], cs4); 935 } 936 937 if (connection[1] != null) { 938 updateLine(connection[1], cs5); 939 } 940 941 if (connection[2] != null) { 942 updateLine(connection[2], cs6); 943 } 944 945 if (connection[3] != null) { 946 updateLine(connection[3], cs7); 947 } 948 949 } 950 951 /** 952 * Attach Help target to Help button on Main Screen. 953 */ 954 protected void attachHelp() { 955 } 956 957 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Apps.class); 958 959}