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