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