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