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