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