001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2016
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028
029package edu.wisc.ssec.mcidasv.startupmanager;
030
031import java.awt.BorderLayout;
032import java.awt.Component;
033import java.awt.Container;
034import java.awt.Dimension;
035import java.awt.FlowLayout;
036import java.awt.Graphics;
037import java.awt.Graphics2D;
038import java.awt.RenderingHints;
039import java.awt.event.ActionEvent;
040import java.awt.event.ActionListener;
041
042import java.io.File;
043import java.io.FileInputStream;
044import java.io.FileOutputStream;
045import java.io.IOException;
046import java.io.InputStream;
047import java.io.OutputStream;
048
049import java.util.List;
050import java.util.Objects;
051import java.util.Properties;
052
053import javax.swing.BorderFactory;
054import javax.swing.DefaultListCellRenderer;
055import javax.swing.DefaultListModel;
056import javax.swing.GroupLayout;
057import javax.swing.ImageIcon;
058import javax.swing.JButton;
059import javax.swing.JCheckBox;
060import javax.swing.JComboBox;
061import javax.swing.JComponent;
062import javax.swing.JFrame;
063import javax.swing.JLabel;
064import javax.swing.JList;
065import javax.swing.JPanel;
066import javax.swing.JScrollPane;
067import javax.swing.JSplitPane;
068import javax.swing.JTextField;
069import javax.swing.JTree;
070import javax.swing.LayoutStyle;
071import javax.swing.ListModel;
072import javax.swing.ListSelectionModel;
073import javax.swing.SwingConstants;
074import javax.swing.WindowConstants;
075import javax.swing.tree.DefaultMutableTreeNode;
076import javax.swing.tree.DefaultTreeCellRenderer;
077
078import edu.wisc.ssec.mcidasv.startupmanager.options.FileOption;
079import edu.wisc.ssec.mcidasv.util.GetMem;
080import ucar.unidata.ui.Help;
081import ucar.unidata.util.GuiUtils;
082import ucar.unidata.util.LogUtil;
083import ucar.unidata.util.StringUtil;
084
085import edu.wisc.ssec.mcidasv.ArgumentManager;
086import edu.wisc.ssec.mcidasv.Constants;
087import edu.wisc.ssec.mcidasv.startupmanager.options.BooleanOption;
088import edu.wisc.ssec.mcidasv.startupmanager.options.DirectoryOption;
089import edu.wisc.ssec.mcidasv.startupmanager.options.LoggerLevelOption;
090import edu.wisc.ssec.mcidasv.startupmanager.options.MemoryOption;
091import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster;
092import edu.wisc.ssec.mcidasv.startupmanager.options.TextOption;
093import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
094
095/**
096 * Manages the McIDAS-V startup options in a context that is completely free
097 * from the traditional IDV/McIDAS-V overhead.
098 */
099public class StartupManager implements edu.wisc.ssec.mcidasv.Constants {
100    
101    // TODO(jon): replace
102    public static final String[][] PREF_PANELS = {
103        { Constants.PREF_LIST_GENERAL, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/mcidasv-round32.png" },
104        { Constants.PREF_LIST_VIEW, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/tab-new32.png" },
105        { Constants.PREF_LIST_TOOLBAR, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/application-x-executable32.png" },
106        { Constants.PREF_LIST_DATA_CHOOSERS, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/preferences-desktop-remote-desktop32.png" },
107        { Constants.PREF_LIST_ADDE_SERVERS, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/applications-internet32.png" },
108        { Constants.PREF_LIST_AVAILABLE_DISPLAYS, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/video-display32.png" },
109        { Constants.PREF_LIST_NAV_CONTROLS, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/input-mouse32.png" },
110        { Constants.PREF_LIST_FORMATS_DATA,"/edu/wisc/ssec/mcidasv/resources/icons/prefs/preferences-desktop-theme32.png" },
111        { Constants.PREF_LIST_ADVANCED, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/applications-internet32.png" },
112    };
113    
114    // TODO(jon): replace
115    public static final Object[][] RENDER_HINTS = {
116        { RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON },
117        { RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY },
118        { RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON },
119    };
120    
121    /** usage message */
122    public static final String USAGE_MESSAGE =
123        "Usage: runMcV-Prefs <args>";
124    
125    /** Path to the McIDAS-V help set within {@literal mcv_userguide.jar}. */
126    private static final String HELP_PATH = "/docs/userguide";
127    
128    /** ID of the startup prefs help page. */
129    private static final String HELP_TARGET = "idv.tools.preferences.advancedpreferences";
130    
131    /** The type of platform as reported by {@link #determinePlatform()}. */
132    private final Platform platform = determinePlatform();
133    
134    /** Cached copy of the application rendering hints. */
135    public static final RenderingHints HINTS = getRenderingHints();
136    
137    /** Contains the list of the different preference panels. */
138    private final JList panelList = new JList(new DefaultListModel());
139    
140    /** Panel containing the startup options. */
141    private JPanel ADVANCED_PANEL;
142    
143    /**
144     * Panel to use for all other preference panels while running startup 
145     * manager.
146     */
147    private JPanel BAD_CHOICE_PANEL;
148    
149    /** Contains the various buttons (Apply, Ok, Help, Cancel). */
150    private JPanel COMMAND_ROW_PANEL;
151    
152    private static StartupManager instance;
153    
154    private StartupManager() {
155        
156    }
157    
158    public static StartupManager getInstance() {
159        if (instance == null) {
160            instance = new StartupManager();
161        }
162        return instance;
163    }
164    
165    /**
166     * Creates and returns the rendering hints for the GUI. 
167     * Built from {@link #RENDER_HINTS}
168     * 
169     * @return Hints to use when displaying the GUI.
170     */
171    public static RenderingHints getRenderingHints() {
172        RenderingHints hints = new RenderingHints(null);
173        for (int i = 0; i < RENDER_HINTS.length; i++)
174            hints.put(RENDER_HINTS[i][0], RENDER_HINTS[i][1]);
175        return hints;
176    }
177    
178    /**
179     * Figures out the type of platform. Queries the {@literal "os.name"}
180     * system property to determine the platform type.
181     * 
182     * @return {@link Platform#UNIXLIKE} or {@link Platform#WINDOWS}.
183     */
184    private Platform determinePlatform() {
185        String os = System.getProperty("os.name");
186        if (os == null) {
187            throw new RuntimeException();
188        }
189        return os.startsWith("Windows") ? Platform.WINDOWS : Platform.UNIXLIKE;
190    }
191    
192    /** 
193     * Returns either {@link Platform#UNIXLIKE} or 
194     * {@link Platform#WINDOWS}.
195     * 
196     * @return The platform as determined by {@link #determinePlatform()}.
197     */
198    public Platform getPlatform() {
199        return platform;
200    }
201    
202    /**
203     * Saves the changes to the preferences and quits. Unlike the other button
204     * handling methods, this one is public. This was done so that the advanced
205     * preferences (within McIDAS-V) can force an update to the startup prefs.
206     */
207    public void handleApply() {
208        OptionMaster.getInstance().writeStartup();
209    }
210    
211    /**
212     * Saves the preference changes.
213     */
214    protected void handleOk() {
215        OptionMaster.getInstance().writeStartup();
216        System.exit(0);
217    }
218    
219    /** 
220     * Shows the startup preferences help page.
221     */
222    protected void handleHelp() {
223        Help.setTopDir(HELP_PATH);
224        Help.getDefaultHelp().gotoTarget(HELP_TARGET);
225    }
226    
227    /**
228     * Simply quits the program.
229     */
230    protected void handleCancel() {
231        System.exit(0);
232    }
233    
234    /**
235     * Returns the preferences panel that corresponds with the user's 
236     * {@code JList} selection.
237     * 
238     * <p>In the context of the startup manager, this means that any 
239     * {@code JList} selection <i>other than</i> {@literal "Advanced"} will
240     * return the results of {@link #getUnavailablePanel()}. Otherwise the
241     * results of {@link #getAdvancedPanel(boolean)} will be returned.
242     * 
243     * @return Either the advanced preferences panel or an 
244     * {@literal "unavailable"}, depending upon the user's selection.
245     */
246    private Container getSelectedPanel() {
247        ListModel listModel = panelList.getModel();
248        int index = panelList.getSelectedIndex();
249        if (index == -1) {
250            return getAdvancedPanel(true);
251        }
252        String key = ((JLabel)listModel.getElementAt(index)).getText();
253        if (!Constants.PREF_LIST_ADVANCED.equals(key)) {
254            return getUnavailablePanel();
255        }
256        return getAdvancedPanel(true);
257    }
258    
259    /**
260     * Creates and returns a dummy panel.
261     * 
262     * @return Panel containing only a note about 
263     * &quot;options unavailable.&quot;
264     */
265    private JPanel buildUnavailablePanel() {
266        JPanel panel = new JPanel();
267        panel.add(new JLabel("These options are unavailable in this context"));
268        return panel;
269    }
270    
271    /**
272     * Creates and returns the advanced preferences panel.
273     * 
274     * @return Panel with all of the various startup options.
275     */
276    private JPanel buildAdvancedPanel() {
277        OptionMaster optMaster = OptionMaster.getInstance();
278        MemoryOption heapSize = optMaster.getMemoryOption("HEAP_SIZE");
279        BooleanOption jogl = optMaster.getBooleanOption("JOGL_TOGL");
280        BooleanOption use3d = optMaster.getBooleanOption("USE_3DSTUFF");
281        BooleanOption defaultBundle = optMaster.getBooleanOption("DEFAULT_LAYOUT");
282        BooleanOption useCmsCollector = optMaster.getBooleanOption("USE_CMSGC");
283        BooleanOption useNpot = optMaster.getBooleanOption("USE_NPOT");
284        BooleanOption useGeometryByRef = optMaster.getBooleanOption("USE_GEOBYREF");
285        BooleanOption useImageByRef = optMaster.getBooleanOption("USE_IMAGEBYREF");
286        FileOption startupBundle = optMaster.getFileOption("STARTUP_BUNDLE");
287        TextOption jvmArgs = optMaster.getTextOption("JVM_OPTIONS");
288        LoggerLevelOption logLevel = optMaster.getLoggerLevelOption("LOG_LEVEL");
289        TextOption textureWidth = optMaster.getTextOption("TEXTURE_WIDTH");
290        
291        JPanel startupPanel = new JPanel();
292        startupPanel.setBorder(BorderFactory.createTitledBorder("Startup Options"));
293        
294        // Build the memory panel
295        JPanel heapPanel = McVGuiUtils.makeLabeledComponent(heapSize.getLabel()+':', heapSize.getComponent());
296        
297        // Build the 3D panel
298        JCheckBox use3dCheckBox = use3d.getComponent();
299        use3dCheckBox.setText(use3d.getLabel());
300        final JCheckBox joglCheckBox = jogl.getComponent();
301        joglCheckBox.setText(jogl.getLabel());
302        JPanel texturePanel = McVGuiUtils.makeLabeledComponent(textureWidth.getLabel()+':', textureWidth.getComponent());
303//        JTextField textureField = textureWidth.getComponent();
304
305        JPanel internalPanel = McVGuiUtils.topBottom(use3dCheckBox, joglCheckBox, McVGuiUtils.Prefer.TOP);
306        JPanel j3dPanel = McVGuiUtils.makeLabeledComponent("3D:", internalPanel);
307        
308        // Build the bundle panel
309        JComponent startupBundlePanel = startupBundle.getComponent();
310        JCheckBox defaultBundleCheckBox = defaultBundle.getComponent();
311        defaultBundleCheckBox.setText(defaultBundle.getLabel());
312        JPanel bundlePanel = McVGuiUtils.makeLabeledComponent(startupBundle.getLabel()+ ':',
313            McVGuiUtils.topBottom(startupBundlePanel, defaultBundleCheckBox, McVGuiUtils.Prefer.TOP));
314            
315        JCheckBox useCmsCollectorCheckBox = useCmsCollector.getComponent();
316        useCmsCollectorCheckBox.setText(useCmsCollector.getLabel());
317        
318        JCheckBox useGeometryByRefCheckBox = useGeometryByRef.getComponent();
319        useGeometryByRefCheckBox.setText(useGeometryByRef.getLabel());
320        
321        JCheckBox useImageByRefCheckBox = useImageByRef.getComponent();
322        useImageByRefCheckBox.setText(useImageByRef.getLabel());
323        
324        JCheckBox useNpotCheckBox = useNpot.getComponent();
325        useNpotCheckBox.setText(useNpot.getLabel());
326
327        // this is a JComboBox<String>; kinda struggling to represent this
328        // in java's type system.
329        JComboBox logLevelComboBox = logLevel.getComponent();
330
331        JPanel logLevelPanel = McVGuiUtils.makeLabeledComponent(logLevel.getLabel()+':', logLevelComboBox);
332        
333        JPanel miscPanel = McVGuiUtils.makeLabeledComponent("Misc:", useCmsCollectorCheckBox);
334
335        JTextField jvmArgsField = jvmArgs.getComponent();
336        JPanel jvmPanel = McVGuiUtils.makeLabeledComponent("Java Flags:", jvmArgsField);
337
338        Component[] visadComponents = {
339            useGeometryByRefCheckBox,
340            useImageByRefCheckBox,
341            useNpotCheckBox,
342            texturePanel,
343        };
344        
345        JPanel visadPanel = McVGuiUtils.makeLabeledComponent("VisAD:", McVGuiUtils.vertical(visadComponents));
346        
347        GroupLayout panelLayout = new GroupLayout(startupPanel);
348        startupPanel.setLayout(panelLayout);
349        panelLayout.setHorizontalGroup(
350            panelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
351                .addComponent(heapPanel)
352                .addComponent(j3dPanel)
353                .addComponent(bundlePanel)
354                .addComponent(visadPanel)
355                .addComponent(logLevelPanel)
356                .addComponent(miscPanel)
357                .addComponent(jvmPanel)
358        );
359        panelLayout.setVerticalGroup(
360            panelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
361            .addGroup(panelLayout.createSequentialGroup()
362                .addComponent(heapPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
363                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
364                .addComponent(bundlePanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
365                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
366                .addComponent(j3dPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
367                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
368                .addComponent(visadPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
369                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
370                .addComponent(logLevelPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
371                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
372                .addComponent(miscPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
373                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
374                .addComponent(jvmPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
375            )
376        );
377        return startupPanel;
378    }
379    
380    /**
381     * Builds and returns a {@link JPanel} containing the various buttons that
382     * control the startup manager. These buttons offer identical 
383     * functionality to those built by the IDV's preference manager code.
384     * 
385     * @return A {@code JPanel} containing the following types of buttons:
386     * {@link ApplyButton}, {@link OkButton}, {@link HelpButton}, 
387     * and {@link CancelButton}.
388     * 
389     * @see GuiUtils#makeApplyOkHelpCancelButtons(ActionListener)
390     */
391    private JPanel buildCommandRow() {
392        JPanel panel = new JPanel(new FlowLayout());
393        // Apply doesn't really mean anything in standalone mode...
394//        panel.add(new ApplyButton());
395        panel.add(new OkButton());
396        panel.add(new HelpButton());
397        panel.add(new CancelButton());
398        panel = McVGuiUtils.makePrettyButtons(panel);
399        return panel;
400    }
401    
402    /**
403     * Returns the advanced preferences panel. Differs from the 
404     * {@link #buildAdvancedPanel()} in that a panel isn't created, unless
405     * {@code forceBuild} is {@code true}.
406     * 
407     * @param forceBuild Always rebuilds the advanced panel if {@code true}.
408     * 
409     * @return Panel containing the startup options.
410     */
411    public JPanel getAdvancedPanel(final boolean forceBuild) {
412        if (forceBuild || (ADVANCED_PANEL == null)) {
413            OptionMaster.getInstance().readStartup();
414            ADVANCED_PANEL = buildAdvancedPanel();
415        }
416        return ADVANCED_PANEL;
417    }
418    
419    public JPanel getUnavailablePanel() {
420        if (BAD_CHOICE_PANEL == null) {
421            BAD_CHOICE_PANEL = buildUnavailablePanel();
422        }
423        return BAD_CHOICE_PANEL;
424    }
425    
426    /**
427     * Returns a panel containing the Apply/Ok/Help/Cancel buttons.
428     * 
429     * @return Panel containing the the command row.
430     */
431    public JPanel getCommandRow() {
432        if (COMMAND_ROW_PANEL == null) {
433            COMMAND_ROW_PANEL = buildCommandRow();
434        }
435        return COMMAND_ROW_PANEL;
436    }
437    
438    /**
439     * Build and display the startup manager window.
440     */
441    protected void createDisplay() {
442        DefaultListModel listModel = (DefaultListModel)panelList.getModel();
443
444        for (String[] PREF_PANEL : PREF_PANELS) {
445            ImageIcon icon = new ImageIcon(getClass().getResource(PREF_PANEL[1]));
446            JLabel label = new JLabel(PREF_PANEL[0], icon, SwingConstants.LEADING);
447            listModel.addElement(label);
448        }
449        
450        JScrollPane scroller = new JScrollPane(panelList);
451        final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
452        splitPane.setResizeWeight(0.0);
453        splitPane.setLeftComponent(scroller);
454        scroller.setMinimumSize(new Dimension(166, 319));
455        
456        panelList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
457        panelList.setSelectedIndex(PREF_PANELS.length - 1);
458        panelList.setVisibleRowCount(PREF_PANELS.length);
459        panelList.setCellRenderer(new IconCellRenderer());
460        
461        panelList.addListSelectionListener(e -> {
462            if (!e.getValueIsAdjusting()) {
463                splitPane.setRightComponent(getSelectedPanel());
464            }
465        });
466        
467        splitPane.setRightComponent(getSelectedPanel());
468        
469        JFrame frame = new JFrame("User Preferences");
470        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
471        frame.getContentPane().add(splitPane);
472        frame.getContentPane().add(getCommandRow(), BorderLayout.PAGE_END);
473        
474        frame.pack();
475        frame.setVisible(true);
476    }
477    
478    /**
479     * Copies a file.
480     * 
481     * @param src The file to copy.
482     * @param dst The path to the copy of {@code src}.
483     * 
484     * @throws IOException If there was a problem while attempting to copy.
485     */
486    public void copy(final File src, final File dst) throws IOException {
487        InputStream in = new FileInputStream(src);
488        OutputStream out = new FileOutputStream(dst);
489        
490        byte[] buf = new byte[1024];
491        int length;
492        
493        while ((length = in.read(buf)) > 0) {
494            out.write(buf, 0, length);
495        }
496        in.close();
497        out.close();
498    }
499    
500    public static class TreeCellRenderer extends DefaultTreeCellRenderer {
501        @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
502            super.getTreeCellRendererComponent(tree, value, sel, expanded,
503                leaf, row, hasFocus);
504                
505            DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
506            
507            File f = (File)node.getUserObject();
508            String path = f.getPath();
509            
510            if (f.isDirectory()) {
511                setToolTipText("Bundle Directory: " + path);
512            } else if (ArgumentManager.isZippedBundle(path)) {
513                setToolTipText("Zipped Bundle: " + path);
514            } else if (ArgumentManager.isXmlBundle(path)) {
515                setToolTipText("XML Bundle: " + path);
516            } else {
517                setToolTipText("Unknown file type: " + path);
518            }
519            setText(f.getName().replace(f.getParent(), ""));
520            return this;
521        }
522    }
523    
524    public static class IconCellRenderer extends DefaultListCellRenderer {
525        @Override public Component getListCellRendererComponent(JList list, 
526            Object value, int index, boolean isSelected, boolean cellHasFocus) 
527        {
528            super.getListCellRendererComponent(list, value, index, isSelected, 
529                cellHasFocus);
530                
531            if (value instanceof JLabel) {
532                setText(((JLabel)value).getText());
533                setIcon(((JLabel)value).getIcon());
534            }
535            
536            return this;
537        }
538        
539        @Override protected void paintComponent(Graphics g) {
540            Graphics2D g2d = (Graphics2D)g;
541            g2d.setRenderingHints(StartupManager.HINTS);
542            super.paintComponent(g2d);
543        }
544    }
545    
546    private static abstract class CommandButton extends JButton 
547        implements ActionListener 
548    {
549        public CommandButton(final String label) {
550            super(label);
551            McVGuiUtils.setComponentWidth(this);
552            addActionListener(this);
553        }
554        
555        @Override public void paintComponent(Graphics g) {
556            Graphics2D g2d = (Graphics2D)g;
557            g2d.setRenderingHints(StartupManager.HINTS);
558            super.paintComponent(g2d);
559        }
560        
561        abstract public void actionPerformed(final ActionEvent e);
562    }
563    
564    private static class ApplyButton extends CommandButton {
565        public ApplyButton() {
566            super("Apply");
567        }
568        public void actionPerformed(final ActionEvent e) {
569            StartupManager.getInstance().handleApply();
570        }
571    }
572    
573    private static class OkButton extends CommandButton {
574        public OkButton() {
575            super("OK");
576        }
577        public void actionPerformed(final ActionEvent e) {
578            StartupManager.getInstance().handleOk();
579        }
580    }
581    
582    private static class HelpButton extends CommandButton {
583        public HelpButton() {
584            super("Help");
585        }
586        public void actionPerformed(final ActionEvent e) {
587            StartupManager.getInstance().handleHelp();
588        }
589    }
590    
591    private static class CancelButton extends CommandButton {
592        public CancelButton() {
593            super("Cancel");
594        }
595        public void actionPerformed(final ActionEvent e) {
596            StartupManager.getInstance().handleCancel();
597        }
598    }
599    
600    public static Properties getDefaultProperties() {
601        Properties props = new Properties();
602        String osName = System.getProperty("os.name");
603        if (osName.startsWith("Mac OS X")) {
604            props.setProperty("userpath", String.format("%s%s%s%s%s", System.getProperty("user.home"), File.separator, "Documents", File.separator, Constants.USER_DIRECTORY_NAME));
605        } else {
606            props.setProperty("userpath", String.format("%s%s%s", System.getProperty("user.home"), File.separator, Constants.USER_DIRECTORY_NAME));
607        }
608        props.setProperty(Constants.PROP_SYSMEM, "0");
609        return props;
610    }
611    
612    /**
613     * Extract any command-line properties and their corresponding values.
614     * 
615     * <p>May print out usage information if a badly formatted 
616     * {@literal "property=value"} pair is encountered, or when an unknown 
617     * argument is found (depending on value of the {@code ignoreUnknown} 
618     * parameter). 
619     * 
620     * <p><b>NOTE:</b> {@code null} is not a permitted value for any parameter.
621     * 
622     * @param ignoreUnknown Whether or not to handle unknown arguments.
623     * @param fromStartupManager Whether or not this call originated from 
624     * {@code startupmanager.jar}.
625     * @param args Array containing command-line arguments.
626     * @param defaults Default parameter values.
627     * 
628     * @return Command-line arguments as a collection of property identifiers
629     * and values.
630     */
631    public static Properties getArgs(final boolean ignoreUnknown, 
632        final boolean fromStartupManager, final String[] args, 
633        final Properties defaults) 
634    {
635        Properties props = new Properties(defaults);
636        for (int i = 0; i < args.length; i++) {
637            
638            // handle property definitions
639            if (args[i].startsWith("-D")) {
640                List<String> l = StringUtil.split(args[i].substring(2), "=");
641                if (l.size() == 2) {
642                    props.setProperty(l.get(0), l.get(1));
643                } else {
644                    usage("Invalid property:" + args[i]);
645                }
646            }
647            
648            // handle userpath changes
649            else if (ARG_USERPATH.equals(args[i]) && ((i + 1) < args.length)) {
650                props.setProperty("userpath", args[++i]);
651            }
652            
653            // handle help requests
654            else if (ARG_HELP.equals(args[i]) && (fromStartupManager)) {
655                System.err.println(USAGE_MESSAGE);
656                System.err.println(getUsageMessage());
657                System.exit(1);
658            }
659            
660            // bail out for unknown args, unless we don't care!
661            else if (!ignoreUnknown){
662                usage("Unknown argument: " + args[i]);
663            }
664        }
665        return props;
666    }
667    
668    public static int getMaximumHeapSize() {
669        int sysmem =
670            StartupManager.getInstance().getPlatform().getAvailableMemory();
671        if ((sysmem > Constants.MAX_MEMORY_32BIT) &&
672            (!System.getProperty("os.arch").contains("64")))
673        {
674            return Constants.MAX_MEMORY_32BIT;
675        }
676        return sysmem;
677    }
678    
679    /**
680     * Print out the command line usage message and exit. Taken entirely from
681     * {@link ucar.unidata.idv.ArgsManager}.
682     * 
683     * @param err The usage message
684     */
685    private static void usage(final String err) {
686        String msg = USAGE_MESSAGE;
687        msg = msg + '\n' + getUsageMessage();
688        LogUtil.userErrorMessage(err + '\n' + msg);
689        System.exit(1);
690    }
691    
692    /**
693     * Return the command line usage message.
694     * 
695     * @return The usage message
696     */
697    protected static String getUsageMessage() {
698        return '\t'+ARG_HELP+"  (this message)\n"+
699               '\t'+ARG_USERPATH+"  <user directory to use>\n"+
700               "\t-Dpropertyname=value  (Define the property value)\n";
701    }
702    
703    /**
704     * Applies the command line arguments to the startup preferences.
705     *
706     * This method is mostly useful because it allows us to supply an
707     * arbitrary {@code args} array, link in
708     * {@link edu.wisc.ssec.mcidasv.McIDASV#main(String[])}.
709     * 
710     * @param ignoreUnknown If {@code true} ignore any parameters that do not 
711     *                      apply to the startup manager. If {@code false},
712     *                      the non-applicable parameters should signify an
713     *                      error.
714     * @param fromStartupManager Whether or not this call originated from the 
715     *                           startup manager (rather than preferences).
716     * @param args Incoming command line arguments. Cannot be {@code null}.
717     * 
718     * @throws NullPointerException if {@code args} is null.
719     * 
720     * @see #getArgs(boolean, boolean, String[], Properties)
721     */
722    public static void applyArgs(final boolean ignoreUnknown,
723                                 final boolean fromStartupManager,
724                                 final String[] args)
725        throws IllegalArgumentException
726    {
727        Objects.requireNonNull(args, "Argument list cannot be null");
728
729        StartupManager sm = StartupManager.getInstance();
730        Platform platform = sm.getPlatform();
731        
732        Properties props = getArgs(ignoreUnknown,
733                                   fromStartupManager,
734                                   args,
735                                   getDefaultProperties());
736        platform.setUserDirectory(props.getProperty("userpath"));
737        platform.setAvailableMemory(GetMem.getMemory());
738    }
739    
740    public static void main(String[] args) {
741        applyArgs(false, true, args);
742        StartupManager.getInstance().createDisplay();
743    }
744}