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