001package apps.gui3; 002 003import apps.*; 004import apps.gui3.tabbedpreferences.TabbedPreferencesAction; 005import apps.swing.AboutDialog; 006 007import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 008 009import java.awt.*; 010import java.awt.event.AWTEventListener; 011import java.awt.event.KeyEvent; 012import java.io.*; 013import java.util.EventObject; 014 015import javax.swing.*; 016 017import jmri.InstanceManager; 018import jmri.jmrit.logixng.LogixNG_Manager; 019import jmri.profile.*; 020import jmri.util.*; 021import jmri.util.swing.JmriJOptionPane; 022 023/** 024 * Base class for GUI3 JMRI applications. 025 * <p> 026 * This is a complete re-implementation of the apps.Apps support for JMRI 027 * applications. 028 * <p> 029 * Each using application provides its own main() method. 030 * <p> 031 * There are a large number of missing features marked with TODO in comments 032 * including code from the earlier implementation. 033 * 034 * @author Bob Jacobsen Copyright 2009, 2010 035 */ 036public abstract class Apps3 extends AppsBase { 037 038 /** 039 * Initial actions before frame is created, invoked in the applications 040 * main() routine. 041 * <ul> 042 * <li> Operations from {@link AppsBase#preInit(String)} 043 * <li> Initialize the console support 044 * </ul> 045 * 046 * @param applicationName application name 047 */ 048 static public void preInit(String applicationName) { 049 AppsBase.preInit(applicationName); 050 051 // Initialise system console 052 // Put this here rather than in apps.AppsBase as this is only relevant 053 // for GUI applications - non-gui apps will use STDOUT & STDERR 054 SystemConsole.getInstance(); 055 056 splash(true); 057 058 setButtonSpace(); 059 060 } 061 062 /** 063 * Create and initialize the application object. 064 * <p> 065 * Expects initialization from preInit() to already be done. 066 * 067 * @param applicationName application name 068 * @param configFileDef default configuration file name 069 * @param args command line arguments set at application launch 070 */ 071 public Apps3(String applicationName, String configFileDef, String[] args) { 072 // pre-GUI work 073 super(applicationName, configFileDef, args); 074 075 // create GUI 076 if (SystemType.isMacOSX()) { 077 initMacOSXMenus(); 078 } 079 if ( (((!configOK) || (!configDeferredLoadOK)) && (!preferenceFileExists)) || wizardLaunchCheck() ) { 080 launchFirstTimeStartupWizard(); 081 return; 082 } 083 createAndDisplayFrame(); 084 } 085 086 /** 087 * To be overridden by applications that need to make 088 * additional checks as to whether the first time wizard 089 * should be launched. 090 * @return true to force the wizard to be launched 091 */ 092 protected boolean wizardLaunchCheck() { 093 return false; 094 } 095 096 public void launchFirstTimeStartupWizard() { 097 FirstTimeStartUpWizardAction prefsAction = new FirstTimeStartUpWizardAction("Start Up Wizard"); 098 prefsAction.setApp(this); 099 prefsAction.actionPerformed(null); 100 } 101 102 /** 103 * For compatability with adding in buttons to the toolbar using the 104 * existing createbuttonmodel 105 */ 106 protected static void setButtonSpace() { 107 _buttonSpace = new JPanel(); 108 _buttonSpace.setLayout(new FlowLayout(FlowLayout.LEFT)); 109 } 110 111 /** 112 * Provide access to a place where applications can expect the configuration 113 * code to build run-time buttons. 114 * 115 * @see apps.startup.CreateButtonModelFactory 116 * @return null if no such space exists 117 */ 118 static public JComponent buttonSpace() { 119 return _buttonSpace; 120 } 121 static JComponent _buttonSpace = null; 122 123 protected JmriJFrame mainFrame; 124 125 abstract protected void createMainFrame(); 126 127 public void createAndDisplayFrame() { 128 createMainFrame(); 129 130 //A Shutdown manager handles the quiting of the application 131 mainFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 132 displayMainFrame(mainFrame.getMaximumSize()); 133 } 134 135 /** 136 * Set a toolbar to be initially floating. This doesn't quite work right. 137 * 138 * @param toolBar the toolbar to float 139 */ 140 protected void setFloating(JToolBar toolBar) { 141 //((javax.swing.plaf.basic.BasicToolBarUI) toolBar.getUI()).setFloatingLocation(100,100); 142 ((javax.swing.plaf.basic.BasicToolBarUI) toolBar.getUI()).setFloating(true, new Point(500, 500)); 143 } 144 145 protected void displayMainFrame(Dimension d) { 146 mainFrame.setSize(d); 147 mainFrame.setVisible(true); 148 } 149 150 /** 151 * Final actions before releasing control of app to user 152 */ 153 @Override 154 protected void start() { 155 // TODO: splash(false); 156 super.start(); 157 splash(false); 158 } 159 160 static protected void splash(boolean show) { 161 splash(show, false); 162 } 163 164 static SplashWindow sp = null; 165 static AWTEventListener debugListener = null; 166 static boolean debugFired = false; 167 static boolean debugmsg = false; 168 169 static protected void splash(boolean show, boolean debug) { 170 if (debugListener == null && debug) { 171 // set a global listener for debug options 172 debugFired = false; 173 debugListener = new AWTEventListener() { 174 175 @Override 176 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification = "debugmsg write is semi-global") 177 public void eventDispatched(AWTEvent e) { 178 if (!debugFired) { 179 /*We set the debugmsg flag on the first instance of the user pressing any button 180 and the if the debugFired hasn't been set, this allows us to ensure that we don't 181 miss the user pressing F8, while we are checking*/ 182 debugmsg = true; 183 if (e.getID() == KeyEvent.KEY_PRESSED && e instanceof KeyEvent && ((KeyEvent) e).getKeyCode() == 119) { // F8 184 startupDebug(); 185 } else if (e.getID() == KeyEvent.KEY_PRESSED && e instanceof KeyEvent && ((KeyEvent) e).getKeyCode() == 120) { // F9 186 InstanceManager.getDefault(LogixNG_Manager.class).startLogixNGsOnLoad(false); 187 } else { 188 debugmsg = false; 189 } 190 } 191 } 192 }; 193 Toolkit.getDefaultToolkit().addAWTEventListener(debugListener, 194 AWTEvent.KEY_EVENT_MASK); 195 } 196 197 // bring up splash window for startup 198 if (sp == null) { 199 sp = new SplashWindow((debug) ? splashDebugMsg() : null); 200 } 201 sp.setVisible(show); 202 if (!show) { 203 sp.dispose(); 204 Toolkit.getDefaultToolkit().removeAWTEventListener(debugListener); 205 debugListener = null; 206 sp = null; 207 } 208 } 209 210 static protected JPanel splashDebugMsg() { 211 JLabel panelLabelDisableLogix = new JLabel(Bundle.getMessage("PressF8ToDebug")); 212 panelLabelDisableLogix.setFont(panelLabelDisableLogix.getFont().deriveFont(9f)); 213 JLabel panelLabelDisableLogixNG = new JLabel(Bundle.getMessage("PressF9ToDisableLogixNG")); 214 panelLabelDisableLogixNG.setFont(panelLabelDisableLogix.getFont().deriveFont(9f)); 215 JPanel panel = new JPanel(); 216 panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); 217 panel.add(panelLabelDisableLogix); 218 panel.add(panelLabelDisableLogixNG); 219 return panel; 220 } 221 222 static protected void startupDebug() { 223 debugFired = true; 224 debugmsg = true; 225 226 debugmsg = false; 227 } 228 229 protected void initMacOSXMenus() { 230 apps.plaf.macosx.Application macApp = apps.plaf.macosx.Application.getApplication(); 231 macApp.setAboutHandler((EventObject eo) -> { 232 new AboutDialog(null, true).setVisible(true); 233 }); 234 macApp.setPreferencesHandler((EventObject eo) -> { 235 new TabbedPreferencesAction(Bundle.getMessage("MenuItemPreferences")).actionPerformed(); 236 }); 237 macApp.setQuitHandler((EventObject eo) -> handleQuit()); 238 } 239 240 /** 241 * Configure the {@link jmri.profile.Profile} to use for this application. 242 * <p> 243 * Overrides super() method so dialogs can be displayed. 244 */ 245 @Override 246 protected void configureProfile() { 247 String profileFilename; 248 FileUtil.createDirectory(FileUtil.getPreferencesPath()); 249 // Needs to be declared final as we might need to 250 // refer to this on the Swing thread 251 File profileFile; 252 profileFilename = getConfigFileName().replaceFirst(".xml", ".properties"); 253 // decide whether name is absolute or relative 254 if (!new File(profileFilename).isAbsolute()) { 255 // must be relative, but we want it to 256 // be relative to the preferences directory 257 profileFile = new File(FileUtil.getPreferencesPath() + profileFilename); 258 } else { 259 profileFile = new File(profileFilename); 260 } 261 262 ProfileManager.getDefault().setConfigFile(profileFile); 263 // See if the profile to use has been specified on the command line as 264 // a system property org.jmri.profile as a profile id. 265 if (System.getProperties().containsKey(ProfileManager.SYSTEM_PROPERTY)) { 266 ProfileManager.getDefault().setActiveProfile(System.getProperty(ProfileManager.SYSTEM_PROPERTY)); 267 } 268 // @see jmri.profile.ProfileManager#migrateToProfiles Javadoc for conditions handled here 269 if (!profileFile.exists()) { // no profile config for this app 270 log.trace("profileFile {} doesn't exist", profileFile); 271 try { 272 if (ProfileManager.getDefault().migrateToProfiles(getConfigFileName())) { // migration or first use 273 // notify user of change only if migration occurred 274 // TODO: a real migration message 275 JmriJOptionPane.showMessageDialog(sp, 276 Bundle.getMessage("ConfigMigratedToProfile"), 277 jmri.Application.getApplicationName(), 278 JmriJOptionPane.INFORMATION_MESSAGE); 279 } 280 } catch (IOException | IllegalArgumentException ex) { 281 JmriJOptionPane.showMessageDialog(sp, 282 ex.getLocalizedMessage(), 283 jmri.Application.getApplicationName(), 284 JmriJOptionPane.ERROR_MESSAGE); 285 log.error("Exception: ", ex); 286 } 287 } 288 try { 289 ProfileManagerDialog.getStartingProfile(sp); 290 // Manually setting the configFilename property since calling 291 // Apps.setConfigFilename() does not reset the system property 292 System.setProperty("org.jmri.Apps.configFilename", Profile.CONFIG_FILENAME); 293 Profile profile = ProfileManager.getDefault().getActiveProfile(); 294 if (profile != null) { 295 log.info("Starting with profile {}", profile.getId()); 296 } else { 297 log.info("Starting without a profile"); 298 } 299 300 // rapid language set; must follow up later with full setting as part of preferences 301 jmri.util.gui.GuiLafPreferencesManager.setLocaleMinimally(profile); 302 } catch (IOException ex) { 303 log.info("Profiles not configurable. Using fallback per-application configuration. Error: {}", ex.getMessage()); 304 } 305 } 306 307 @Override 308 protected void setAndLoadPreferenceFile() { 309 File sharedConfig = null; 310 try { 311 sharedConfig = FileUtil.getFile(FileUtil.PROFILE + Profile.SHARED_CONFIG); 312 if (!sharedConfig.canRead()) { 313 sharedConfig = null; 314 } 315 } catch (FileNotFoundException ex) { 316 // ignore - this only means that sharedConfig does not exist. 317 } 318 super.setAndLoadPreferenceFile(); 319 if (sharedConfig == null && configOK == true && configDeferredLoadOK == true) { 320 // this was logged in the super method 321 String name = ProfileManager.getDefault().getActiveProfileName(); 322 if (!GraphicsEnvironment.isHeadless()) { 323 JmriJOptionPane.showMessageDialog(sp, 324 Bundle.getMessage("SingleConfigMigratedToSharedConfig", name), 325 jmri.Application.getApplicationName(), 326 JmriJOptionPane.INFORMATION_MESSAGE); 327 } 328 } 329 } 330 331 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Apps3.class); 332 333}