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