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