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 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", 107 justification = "only one application at a time") 108 protected static void setButtonSpace() { 109 _buttonSpace = new JPanel(); 110 _buttonSpace.setLayout(new FlowLayout(FlowLayout.LEFT)); 111 } 112 113 /** 114 * Provide access to a place where applications can expect the configuration 115 * code to build run-time buttons. 116 * 117 * @see apps.startup.CreateButtonModelFactory 118 * @return null if no such space exists 119 */ 120 static public JComponent buttonSpace() { 121 return _buttonSpace; 122 } 123 static JComponent _buttonSpace = null; 124 125 protected JmriJFrame mainFrame; 126 127 abstract protected void createMainFrame(); 128 129 public void createAndDisplayFrame() { 130 createMainFrame(); 131 132 //A Shutdown manager handles the quiting of the application 133 mainFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 134 displayMainFrame(mainFrame.getMaximumSize()); 135 } 136 137 /** 138 * Set a toolbar to be initially floating. This doesn't quite work right. 139 * 140 * @param toolBar the toolbar to float 141 */ 142 protected void setFloating(JToolBar toolBar) { 143 //((javax.swing.plaf.basic.BasicToolBarUI) toolBar.getUI()).setFloatingLocation(100,100); 144 ((javax.swing.plaf.basic.BasicToolBarUI) toolBar.getUI()).setFloating(true, new Point(500, 500)); 145 } 146 147 protected void displayMainFrame(Dimension d) { 148 mainFrame.setSize(d); 149 mainFrame.setVisible(true); 150 } 151 152 /** 153 * Final actions before releasing control of app to user 154 */ 155 @Override 156 protected void start() { 157 // TODO: splash(false); 158 super.start(); 159 splash(false); 160 } 161 162 static protected void splash(boolean show) { 163 splash(show, false); 164 } 165 166 static SplashWindow sp = null; 167 static AWTEventListener debugListener = null; 168 static boolean debugFired = false; 169 static boolean debugmsg = false; 170 171 static protected void splash(boolean show, boolean debug) { 172 if (debugListener == null && debug) { 173 // set a global listener for debug options 174 debugFired = false; 175 debugListener = new AWTEventListener() { 176 177 @Override 178 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification = "debugmsg write is semi-global") 179 public void eventDispatched(AWTEvent e) { 180 if (!debugFired) { 181 /*We set the debugmsg flag on the first instance of the user pressing any button 182 and the if the debugFired hasn't been set, this allows us to ensure that we don't 183 miss the user pressing F8, while we are checking*/ 184 debugmsg = true; 185 if (e.getID() == KeyEvent.KEY_PRESSED && e instanceof KeyEvent && ((KeyEvent) e).getKeyCode() == 119) { // F8 186 startupDebug(); 187 } else if (e.getID() == KeyEvent.KEY_PRESSED && e instanceof KeyEvent && ((KeyEvent) e).getKeyCode() == 120) { // F9 188 InstanceManager.getDefault(LogixNG_Manager.class).startLogixNGsOnLoad(false); 189 } else { 190 debugmsg = false; 191 } 192 } 193 } 194 }; 195 Toolkit.getDefaultToolkit().addAWTEventListener(debugListener, 196 AWTEvent.KEY_EVENT_MASK); 197 } 198 199 // bring up splash window for startup 200 if (sp == null) { 201 sp = new SplashWindow((debug) ? splashDebugMsg() : null); 202 } 203 sp.setVisible(show); 204 if (!show) { 205 sp.dispose(); 206 Toolkit.getDefaultToolkit().removeAWTEventListener(debugListener); 207 debugListener = null; 208 sp = null; 209 } 210 } 211 212 static protected JPanel splashDebugMsg() { 213 JLabel panelLabelDisableLogix = new JLabel(Bundle.getMessage("PressF8ToDebug")); 214 panelLabelDisableLogix.setFont(panelLabelDisableLogix.getFont().deriveFont(9f)); 215 JLabel panelLabelDisableLogixNG = new JLabel(Bundle.getMessage("PressF9ToDisableLogixNG")); 216 panelLabelDisableLogixNG.setFont(panelLabelDisableLogix.getFont().deriveFont(9f)); 217 JPanel panel = new JPanel(); 218 panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); 219 panel.add(panelLabelDisableLogix); 220 panel.add(panelLabelDisableLogixNG); 221 return panel; 222 } 223 224 static protected void startupDebug() { 225 debugFired = true; 226 debugmsg = true; 227 228 debugmsg = false; 229 } 230 231 protected void initMacOSXMenus() { 232 apps.plaf.macosx.Application macApp = apps.plaf.macosx.Application.getApplication(); 233 macApp.setAboutHandler((EventObject eo) -> { 234 new AboutDialog(null, true).setVisible(true); 235 }); 236 macApp.setPreferencesHandler((EventObject eo) -> { 237 new TabbedPreferencesAction(Bundle.getMessage("MenuItemPreferences")).actionPerformed(); 238 }); 239 macApp.setQuitHandler((EventObject eo) -> handleQuit()); 240 } 241 242 /** 243 * Configure the {@link jmri.profile.Profile} to use for this application. 244 * <p> 245 * Overrides super() method so dialogs can be displayed. 246 */ 247 @Override 248 protected void configureProfile() { 249 String profileFilename; 250 FileUtil.createDirectory(FileUtil.getPreferencesPath()); 251 // Needs to be declared final as we might need to 252 // refer to this on the Swing thread 253 File profileFile; 254 profileFilename = getConfigFileName().replaceFirst(".xml", ".properties"); 255 // decide whether name is absolute or relative 256 if (!new File(profileFilename).isAbsolute()) { 257 // must be relative, but we want it to 258 // be relative to the preferences directory 259 profileFile = new File(FileUtil.getPreferencesPath() + profileFilename); 260 } else { 261 profileFile = new File(profileFilename); 262 } 263 264 ProfileManager.getDefault().setConfigFile(profileFile); 265 // See if the profile to use has been specified on the command line as 266 // a system property org.jmri.profile as a profile id. 267 if (System.getProperties().containsKey(ProfileManager.SYSTEM_PROPERTY)) { 268 ProfileManager.getDefault().setActiveProfile(System.getProperty(ProfileManager.SYSTEM_PROPERTY)); 269 } 270 // @see jmri.profile.ProfileManager#migrateToProfiles Javadoc for conditions handled here 271 if (!profileFile.exists()) { // no profile config for this app 272 log.trace("profileFile {} doesn't exist", profileFile); 273 try { 274 if (ProfileManager.getDefault().migrateToProfiles(getConfigFileName())) { // migration or first use 275 // notify user of change only if migration occurred 276 // TODO: a real migration message 277 JmriJOptionPane.showMessageDialog(sp, 278 Bundle.getMessage("ConfigMigratedToProfile"), 279 jmri.Application.getApplicationName(), 280 JmriJOptionPane.INFORMATION_MESSAGE); 281 } 282 } catch (IOException | IllegalArgumentException ex) { 283 JmriJOptionPane.showMessageDialog(sp, 284 ex.getLocalizedMessage(), 285 jmri.Application.getApplicationName(), 286 JmriJOptionPane.ERROR_MESSAGE); 287 log.error("Exception: ", ex); 288 } 289 } 290 try { 291 ProfileManagerDialog.getStartingProfile(sp); 292 // Manually setting the configFilename property since calling 293 // Apps.setConfigFilename() does not reset the system property 294 System.setProperty("org.jmri.Apps.configFilename", Profile.CONFIG_FILENAME); 295 Profile profile = ProfileManager.getDefault().getActiveProfile(); 296 if (profile != null) { 297 log.info("Starting with profile {}", profile.getId()); 298 } else { 299 log.info("Starting without a profile"); 300 } 301 302 // rapid language set; must follow up later with full setting as part of preferences 303 jmri.util.gui.GuiLafPreferencesManager.setLocaleMinimally(profile); 304 } catch (IOException ex) { 305 log.info("Profiles not configurable. Using fallback per-application configuration. Error: {}", ex.getMessage()); 306 } 307 } 308 309 @Override 310 protected void setAndLoadPreferenceFile() { 311 File sharedConfig = null; 312 try { 313 sharedConfig = FileUtil.getFile(FileUtil.PROFILE + Profile.SHARED_CONFIG); 314 if (!sharedConfig.canRead()) { 315 sharedConfig = null; 316 } 317 } catch (FileNotFoundException ex) { 318 // ignore - this only means that sharedConfig does not exist. 319 } 320 super.setAndLoadPreferenceFile(); 321 if (sharedConfig == null && configOK == true && configDeferredLoadOK == true) { 322 // this was logged in the super method 323 String name = ProfileManager.getDefault().getActiveProfileName(); 324 if (!GraphicsEnvironment.isHeadless()) { 325 JmriJOptionPane.showMessageDialog(sp, 326 Bundle.getMessage("SingleConfigMigratedToSharedConfig", name), 327 jmri.Application.getApplicationName(), 328 JmriJOptionPane.INFORMATION_MESSAGE); 329 } 330 } 331 } 332 333 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Apps3.class); 334 335}