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.create();
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}