001    /*
002     * $Id: McIdasPreferenceManager.java,v 1.100 2012/02/19 17:35:53 davep Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
007     * Space Science and Engineering Center (SSEC)
008     * University of Wisconsin - Madison
009     * 1225 W. Dayton Street, Madison, WI 53706, USA
010     * https://www.ssec.wisc.edu/mcidas
011     * 
012     * All Rights Reserved
013     * 
014     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015     * some McIDAS-V source code is based on IDV and VisAD source code.  
016     * 
017     * McIDAS-V is free software; you can redistribute it and/or modify
018     * it under the terms of the GNU Lesser Public License as published by
019     * the Free Software Foundation; either version 3 of the License, or
020     * (at your option) any later version.
021     * 
022     * McIDAS-V is distributed in the hope that it will be useful,
023     * but WITHOUT ANY WARRANTY; without even the implied warranty of
024     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025     * GNU Lesser Public License for more details.
026     * 
027     * You should have received a copy of the GNU Lesser Public License
028     * along with this program.  If not, see http://www.gnu.org/licenses.
029     */
030    
031    package edu.wisc.ssec.mcidasv;
032    
033    import static javax.swing.GroupLayout.Alignment.BASELINE;
034    import static javax.swing.GroupLayout.Alignment.LEADING;
035    import static javax.swing.GroupLayout.Alignment.TRAILING;
036    import static javax.swing.GroupLayout.DEFAULT_SIZE;
037    import static javax.swing.GroupLayout.PREFERRED_SIZE;
038    import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
039    
040    
041    import java.awt.CardLayout;
042    import java.awt.Color;
043    import java.awt.Component;
044    import java.awt.Container;
045    import java.awt.Dimension;
046    import java.awt.Font;
047    import java.awt.Graphics;
048    import java.awt.Graphics2D;
049    import java.awt.Insets;
050    import java.awt.RenderingHints;
051    import java.awt.event.ActionEvent;
052    import java.awt.event.ActionListener;
053    import java.beans.PropertyChangeEvent;
054    import java.beans.PropertyChangeListener;
055    import java.net.URL;
056    import java.text.DecimalFormat;
057    import java.util.ArrayList;
058    import java.util.Arrays;
059    import java.util.Collections;
060    import java.util.Enumeration;
061    import java.util.Hashtable;
062    import java.util.LinkedHashSet;
063    import java.util.List;
064    import java.util.Map;
065    import java.util.Set;
066    import java.util.TimeZone;
067    import java.util.Vector;
068    
069    import javax.swing.BorderFactory;
070    import javax.swing.DefaultListCellRenderer;
071    import javax.swing.DefaultListModel;
072    import javax.swing.ImageIcon;
073    import javax.swing.JButton;
074    import javax.swing.JCheckBox;
075    import javax.swing.JComboBox;
076    import javax.swing.JComponent;
077    import javax.swing.JLabel;
078    import javax.swing.JList;
079    import javax.swing.JPanel;
080    import javax.swing.JRadioButton;
081    import javax.swing.JScrollPane;
082    import javax.swing.JTextField;
083    import javax.swing.ListSelectionModel;
084    import javax.swing.SwingUtilities;
085    import javax.swing.border.BevelBorder;
086    import javax.swing.event.ListSelectionEvent;
087    import javax.swing.event.ListSelectionListener;
088    
089    import org.bushe.swing.event.annotation.AnnotationProcessor;
090    import org.bushe.swing.event.annotation.EventSubscriber;
091    import org.slf4j.Logger;
092    import org.slf4j.LoggerFactory;
093    import org.w3c.dom.Element;
094    import org.w3c.dom.NodeList;
095    
096    import ucar.unidata.data.DataUtil;
097    import ucar.unidata.idv.ControlDescriptor;
098    import ucar.unidata.idv.DisplayControl;
099    import ucar.unidata.idv.IdvConstants;
100    import ucar.unidata.idv.IdvObjectStore;
101    import ucar.unidata.idv.IdvPreferenceManager;
102    import ucar.unidata.idv.IntegratedDataViewer;
103    import ucar.unidata.idv.MapViewManager;
104    import ucar.unidata.idv.ViewManager;
105    import ucar.unidata.idv.control.DisplayControlImpl;
106    import ucar.unidata.ui.CheckboxCategoryPanel;
107    import ucar.unidata.ui.FontSelector;
108    import ucar.unidata.ui.HelpTipDialog;
109    import ucar.unidata.ui.XmlUi;
110    import ucar.unidata.util.GuiUtils;
111    import ucar.unidata.util.IOUtil;
112    import ucar.unidata.util.LogUtil;
113    import ucar.unidata.util.Misc;
114    import ucar.unidata.util.Msg;
115    import ucar.unidata.util.ObjectListener;
116    import ucar.unidata.util.StringUtil;
117    import ucar.unidata.xml.PreferenceManager;
118    import ucar.unidata.xml.XmlObjectStore;
119    import ucar.unidata.xml.XmlUtil;
120    import ucar.visad.UtcDate;
121    import visad.DateTime;
122    import visad.Unit;
123    import edu.wisc.ssec.mcidasv.servermanager.EntryStore;
124    import edu.wisc.ssec.mcidasv.servermanager.AddePreferences;
125    import edu.wisc.ssec.mcidasv.servermanager.AddePreferences.AddePrefConglomeration;
126    import edu.wisc.ssec.mcidasv.startupmanager.StartupManager;
127    import edu.wisc.ssec.mcidasv.ui.McvToolbarEditor;
128    import edu.wisc.ssec.mcidasv.ui.UIManager;
129    import edu.wisc.ssec.mcidasv.util.CollectionHelpers;
130    import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
131    import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width;
132    import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Prefer;
133    
134    /**
135     * <p>An extension of {@link ucar.unidata.idv.IdvPreferenceManager} that uses
136     * a JList instead of tabs to lay out the various PreferenceManagers.</p>
137     *
138     * @author McIDAS-V Dev Team
139     */
140    public class McIdasPreferenceManager extends IdvPreferenceManager implements ListSelectionListener, Constants {
141    
142        /** Logger object. */
143        private static final Logger logger = LoggerFactory.getLogger(McIdasPreferenceManager.class);
144    
145        /** 
146         * <p>Controls how the preference panel list is displayed. Want to modify 
147         * the preferences UI in some way? PREF_PANELS is your friend. Think of 
148         * it like a really brain-dead SQLite.</p>
149         * 
150         * <p>Each row is a panel, and <b>must</b> consist of three columns:
151         * <ol start="0">
152         * <li>Name of the panel.</li>
153         * <li>Path to the icon associated with the panel.</li>
154         * <li>The panel's {@literal "help ID."}</li>
155         * </ol>
156         * The {@link JList} in the preferences window will order the panels based
157         * upon {@code PREF_PANELS}.
158         * </p>
159         */
160        public static final String[][] PREF_PANELS = {
161            { Constants.PREF_LIST_GENERAL, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/mcidasv-round32.png", "idv.tools.preferences.generalpreferences" },
162            { Constants.PREF_LIST_VIEW, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/tab-new32.png", "idv.tools.preferences.displaywindowpreferences" },
163            { Constants.PREF_LIST_TOOLBAR, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/application-x-executable32.png", "idv.tools.preferences.toolbarpreferences" },
164            { Constants.PREF_LIST_DATA_CHOOSERS, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/preferences-desktop-remote-desktop32.png", "idv.tools.preferences.datapreferences" },
165            { Constants.PREF_LIST_ADDE_SERVERS, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/applications-internet32.png", "idv.tools.preferences.serverpreferences" },
166            { Constants.PREF_LIST_AVAILABLE_DISPLAYS, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/video-display32.png", "idv.tools.preferences.availabledisplayspreferences" },
167            { Constants.PREF_LIST_NAV_CONTROLS, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/input-mouse32.png", "idv.tools.preferences.navigationpreferences" },
168            { Constants.PREF_LIST_FORMATS_DATA,"/edu/wisc/ssec/mcidasv/resources/icons/prefs/preferences-desktop-theme32.png", "idv.tools.preferences.formatpreferences" },
169            { Constants.PREF_LIST_ADVANCED, "/edu/wisc/ssec/mcidasv/resources/icons/prefs/applications-internet32.png", "idv.tools.preferences.advancedpreferences" }
170        };
171    
172        /** Desired rendering hints with their desired values. */
173        public static final Object[][] RENDER_HINTS = {
174            { RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON },
175            { RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY },
176            { RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON }
177        };
178    
179        /** Options for bundle loading */
180        public static final String[] loadComboOptions = {
181            "Create new window(s)",
182            "Merge with active tab(s)",
183            "Add new tab(s) to current window",
184            "Replace session"
185        };
186    
187        /**
188         * @return The rendering hints to use, as determined by RENDER_HINTS.
189         */
190        public static RenderingHints getRenderingHints() {
191            RenderingHints hints = new RenderingHints(null);
192            for (int i = 0; i < RENDER_HINTS.length; i++) {
193                hints.put(RENDER_HINTS[i][0], RENDER_HINTS[i][1]);
194            }
195            return hints;
196        }
197    
198        /** Help McV remember the last preference panel the user selected. */
199        private static final String LAST_PREF_PANEL = "mcv.prefs.lastpanel";
200    
201        private static final String LEGEND_TEMPLATE_DATA = "%datasourcename% - %displayname%";
202        private static final String DISPLAY_LIST_TEMPLATE_DATA = "%datasourcename% - %displayname% " + UtcDate.MACRO_TIMESTAMP;
203    
204        private static final String TEMPLATE_NO_DATA = "%displayname%";
205    
206        /** test value for formatting */
207        private static double latlonValue = -104.56284;
208    
209        /** Decimal format */
210        private static DecimalFormat latlonFormat = new DecimalFormat();
211    
212        /** Provide some default values for the lat-lon preference drop down. */
213        private static final Set<String> defaultLatLonFormats = CollectionHelpers.set("##0","##0.0","##0.0#","##0.0##","0.0","0.00","0.000");
214    
215        private static final Set<String> probeFormatsList = CollectionHelpers.set(DisplayControl.DEFAULT_PROBEFORMAT, "%rawvalue% [%rawunit%]", "%value%", "%rawvalue%", "%value% <i>%unit%</i>");
216    
217        /** 
218         * Replacing the "incoming" IDV preference tab names with whatever's in
219         * this map.
220         */
221        private static final Map<String, String> replaceMap = 
222            CollectionHelpers.zipMap(
223                CollectionHelpers.arr("Toolbar", "View"), 
224                CollectionHelpers.arr(Constants.PREF_LIST_TOOLBAR, Constants.PREF_LIST_VIEW));
225    
226        /** Path to the McV choosers.xml */
227        private static final String MCV_CHOOSERS = "/edu/wisc/ssec/mcidasv/resources/choosers.xml";
228    
229        /** 
230         * Maps the {@literal "name"} of a panel to the actual thing holding the 
231         * PreferenceManager. 
232         */
233        private final Map<String, Container> prefMap = CollectionHelpers.concurrentMap();
234    
235        /** Maps the name of a panel to an icon. */
236        private final Map<String, ImageIcon> iconCache = CollectionHelpers.concurrentMap();
237    
238        /** 
239         * A table of the different preference managers that'll wind up in the
240         * list.
241         */
242        private final Map<String, PreferenceManager> managerMap = CollectionHelpers.concurrentMap();
243    
244        /**
245         * Each PreferenceManager has associated data contained in this table.
246         * TODO: bug Unidata about getting IdvPreferenceManager's dataList protected
247         */
248        private final Map<String, Object> dataMap = CollectionHelpers.concurrentMap();
249    
250        private final Set<String> labelSet = new LinkedHashSet<String>();
251    
252        /** 
253         * The list that'll contain all the names of the different 
254         * PreferenceManagers 
255         */
256        private JList labelList;
257    
258        /** The "M" in the MVC for JLists. Contains all the list data. */
259        private DefaultListModel listModel;
260    
261        /** Handle scrolling like a pro. */
262        private JScrollPane listScrollPane;
263    
264        /** Holds the main preference pane */
265        private JPanel mainPane;
266    
267        /** Holds the buttons at the bottom */
268        private JPanel buttonPane;
269    
270        /** Date formats */
271        private final Set<String> dateFormats = CollectionHelpers.set(
272            DEFAULT_DATE_FORMAT, "MM/dd/yy HH:mm z", "dd.MM.yy HH:mm z", 
273            "yyyy-MM-dd", "EEE, MMM dd yyyy HH:mm z", "HH:mm:ss", "HH:mm", 
274            "yyyy-MM-dd'T'HH:mm:ss'Z'", "yyyy-MM-dd'T'HH:mm:ssZ");
275    
276        /** Is this a Unix-style platform? */
277        private boolean isUnixLike = false;
278    
279        /** Is this a Windows platform? */
280        private boolean isWindows = false;
281    
282        /** The toolbar editor */
283        private McvToolbarEditor toolbarEditor;
284    
285        /** */
286        private String userDirectory;
287    
288        /** */
289        private String userPrefs;
290    
291        /** */
292        private String defaultPrefs;
293    
294        /**
295         * Prep as much as possible for displaying the preference window: load up
296         * icons and create some of the window features.
297         * 
298         * @param idv Reference to the supreme IDV object.
299         */
300        public McIdasPreferenceManager(IntegratedDataViewer idv) {
301            super(idv);
302            AnnotationProcessor.process(this);
303            init();
304    
305            for (int i = 0; i < PREF_PANELS.length; i++) {
306                URL url = getClass().getResource(PREF_PANELS[i][1]);
307                iconCache.put(PREF_PANELS[i][0], new ImageIcon(url));
308            }
309    
310            setEmptyPref("idv.displaylist.template.data", DISPLAY_LIST_TEMPLATE_DATA);
311            setEmptyPref("idv.displaylist.template.nodata", TEMPLATE_NO_DATA);
312            setEmptyPref("idv.legendlabel.template.data", LEGEND_TEMPLATE_DATA);
313            setEmptyPref("idv.legendlabel.template.nodata", TEMPLATE_NO_DATA);
314        }
315    
316        private boolean setEmptyPref(final String id, final String val) {
317            IdvObjectStore store = getIdv().getStore();
318            if (store.get(id, (String)null) == null) {
319                store.put(id, val);
320                return true;
321            }
322            return false;
323        }
324    
325        /**
326         * Overridden so McIDAS-V can direct users to specific help sections for
327         * each preference panel.
328         */
329        @Override public void actionPerformed(ActionEvent event) {
330            String cmd = event.getActionCommand();
331            if (!GuiUtils.CMD_HELP.equals(cmd) || labelList == null) {
332                super.actionPerformed(event);
333                return;
334            }
335    
336            int selectedIndex = labelList.getSelectedIndex();
337            getIdvUIManager().showHelp(PREF_PANELS[selectedIndex][2]);
338        }
339    
340        public void replaceServerPrefPanel(final JPanel panel) {
341            String name = "SERVER MANAGER";
342            mainPane.add(name, panel);
343            ((CardLayout)mainPane.getLayout()).show(mainPane, name);
344        }
345    
346        @EventSubscriber(eventClass=EntryStore.Event.class)
347        public void replaceServerPreferences(EntryStore.Event evt) {
348            EntryStore remoteAddeStore = ((McIDASV)getIdv()).getServerManager();
349            AddePreferences prefs = new AddePreferences(remoteAddeStore);
350            AddePrefConglomeration eww = prefs.buildPanel((McIDASV)getIdv());
351            String name = "SERVER MANAGER";
352    //        mainPane.add(name, eww.getEntryPanel());
353    //        ((CardLayout)mainPane.getLayout()).show(mainPane, name);
354        }
355    
356        /**
357         * Add a PreferenceManager to the list of things that should be shown in
358         * the preference dialog.
359         * 
360         * @param tabLabel The label (or name) of the PreferenceManager.
361         * @param description Not used.
362         * @param listener The actual PreferenceManager.
363         * @param panel The container holding all of the PreferenceManager stuff.
364         * @param data Data passed to the preference manager.
365         */
366        @Override public void add(String tabLabel, String description, 
367            PreferenceManager listener, Container panel, Object data) {
368    
369            // if there is an alternate name for tabLabel, find and use it.
370            if (replaceMap.containsKey(tabLabel) == true) {
371                tabLabel = replaceMap.get(tabLabel);
372            }
373    
374            if (prefMap.containsKey(tabLabel) == true) {
375                return;
376            }
377    
378            // figure out the last panel that was selected.
379            int selected = getIdv().getObjectStore().get(LAST_PREF_PANEL, 0);
380            if (selected < 0 || selected >= PREF_PANELS.length) {
381                logger.warn("attempted to select an invalid preference panel: {}", selected);
382                selected = 0;
383            }
384            String selectedPanel = PREF_PANELS[selected][0];
385    
386            panel.setPreferredSize(null);
387    
388            Msg.translateTree(panel);
389    
390            managerMap.put(tabLabel, listener);
391            if (data == null) {
392                dataMap.put(tabLabel, new Hashtable());
393            } else {
394                dataMap.put(tabLabel, data);
395            }
396            prefMap.put(tabLabel, panel);
397    
398            if (labelSet.add(tabLabel)) {
399                JLabel label = new JLabel();
400                label.setText(tabLabel);
401                label.setIcon(iconCache.get(tabLabel));
402                listModel.addElement(label);
403    
404                labelList.setSelectedIndex(selected);
405                mainPane.add(tabLabel, panel);
406                if (selectedPanel.equals(tabLabel)) {
407                    ((CardLayout)mainPane.getLayout()).show(mainPane, tabLabel);
408                }
409            }
410    
411            mainPane.repaint();
412        }
413    
414        /**
415         * Apply the preferences (taken straight from IDV). 
416         * TODO: bug Unidata about making managers and dataList protected instead of private
417         * 
418         * @return Whether or not each of the preference managers applied properly.
419         */
420        @Override public boolean apply() {
421            try {
422                for (String id : labelSet) {
423                    PreferenceManager manager = managerMap.get(id);
424                    manager.applyPreference(getStore(), dataMap.get(id));
425                }
426                fixDisplayListFont();
427                getStore().save();
428                return true;
429            } catch (Exception exc) {
430                LogUtil.logException("Error applying preferences", exc);
431                return false;
432            }
433        }
434    
435        // For some reason the display list font can have a size of zero if your
436        // new font size didn't change after starting the prefs panel. 
437        private void fixDisplayListFont() {
438            IdvObjectStore s = getStore();
439            Font f = s.get(ViewManager.PREF_DISPLAYLISTFONT, FontSelector.DEFAULT_FONT);
440            if (f.getSize() == 0) {
441                f = f.deriveFont(8f);
442                s.put(ViewManager.PREF_DISPLAYLISTFONT, f);
443            }
444        }
445    
446        /**
447         * Select a list item and its corresponding panel that both live within the 
448         * preference window JList.
449         * 
450         * @param labelName The "name" of the JLabel within the JList.
451         */
452        public void selectListItem(String labelName) {
453            show();
454            toFront();
455    
456            for (int i = 0; i < listModel.getSize(); i++) {
457                String labelText = ((JLabel)listModel.get(i)).getText();
458                if (StringUtil.stringMatch(labelText, labelName)) {
459                    // persist across restarts
460                    getIdv().getObjectStore().put(LAST_PREF_PANEL, i);
461                    labelList.setSelectedIndex(i);
462                    return;
463                }
464            }
465        }
466    
467        /**
468         * Wrapper so that IDV code can still select which preference pane to show.
469         * 
470         * @param tabNameToShow The name of the pane to be shown. Regular
471         * expressions are supported.
472         */
473        public void showTab(String tabNameToShow) {
474            selectListItem(tabNameToShow);
475        }
476    
477        /**
478         * Handle the user clicking around.
479         * 
480         * @param e The event to be handled! Use your imagination!
481         */
482        public void valueChanged(ListSelectionEvent e) {
483            if (e.getValueIsAdjusting() == false) {
484                String name = getSelectedName();
485                ((CardLayout)mainPane.getLayout()).show(mainPane, name);
486            }
487        }
488    
489        /**
490         * Returns the container the corresponds to the currently selected label in
491         * the JList. Also stores the selected panel so that the next time a user
492         * tries to open the preferences they will start off in the panel they last
493         * selected.
494         * 
495         * @return The current container.
496         */
497        private Container getSelectedPanel() {
498            // make sure the selected panel persists across restarts
499            getIdv().getObjectStore().put(LAST_PREF_PANEL, labelList.getSelectedIndex());
500            String key = ((JLabel)listModel.getElementAt(labelList.getSelectedIndex())).getText();
501            if (key.equals(Constants.PREF_LIST_NAV_CONTROLS)) {
502                return makeEventPanel();
503            }
504            return prefMap.get(key);
505        }
506    
507        private Container getSelectedPanel(final String name) {
508            if (Constants.PREF_LIST_NAV_CONTROLS.equals(name)) {
509                return makeEventPanel();
510            } else if (prefMap.containsKey(name)) {
511                return prefMap.get(name);
512            } else {
513                return null;
514            }
515        }
516    
517        /**
518         * Returns the container the corresponds to the currently selected label in
519         * the JList. Also stores the selected panel so that the next time a user
520         * tries to open the preferences they will start off in the panel they last
521         * selected.
522         * 
523         * @return The current container.
524         */
525        private String getSelectedName() {
526            // make sure the selected panel persists across restarts
527            getIdv().getObjectStore().put(LAST_PREF_PANEL, labelList.getSelectedIndex());
528            String key = ((JLabel)listModel.getElementAt(labelList.getSelectedIndex())).getText();
529            return key;
530        }
531    
532        /**
533         * Perform the GUI initialization for the preference dialog.
534         */
535        public void init() {
536            listModel = new DefaultListModel();
537            labelList = new JList(listModel);
538    
539            labelList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
540            labelList.setCellRenderer(new IconCellRenderer());
541            labelList.addListSelectionListener(new ListSelectionListener() {
542                public void valueChanged(ListSelectionEvent e) {
543                    if (e.getValueIsAdjusting() == false) {
544                        String name = getSelectedName();
545                        if (Constants.PREF_LIST_NAV_CONTROLS.equals(name)) {
546                            mainPane.add(name, makeEventPanel());
547                            mainPane.validate();
548                        }
549                        ((CardLayout)mainPane.getLayout()).show(mainPane, name);
550                    }
551                }
552            });
553    
554            listScrollPane = new JScrollPane(labelList);
555            listScrollPane.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
556    
557            mainPane = new JPanel(new CardLayout());
558            mainPane.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
559            mainPane.addPropertyChangeListener(new PropertyChangeListener() {
560                public void propertyChange(PropertyChangeEvent e) {
561    //                System.err.println("prop change: prop="+e.getPropertyName()+" old="+e.getOldValue()+" new="+e.getNewValue());
562                    String p = e.getPropertyName();
563                    if (!"Frame.active".equals(p) && !"ancestor".equals(p)) {
564                        return;
565                    }
566    
567                    Object v = e.getNewValue();
568                    boolean okay = false;
569                    if (v instanceof Boolean) {
570                        okay = ((Boolean)v).booleanValue();
571                    } else if (v instanceof JPanel) {
572                        okay = true;
573                    } else {
574                        okay = false;
575                    }
576    
577                    if (okay) {
578                        if (getSelectedName().equals(Constants.PREF_LIST_NAV_CONTROLS)) {
579                            mainPane.add(Constants.PREF_LIST_NAV_CONTROLS, makeEventPanel());
580                            mainPane.validate();
581                            ((CardLayout)mainPane.getLayout()).show(mainPane, Constants.PREF_LIST_NAV_CONTROLS);
582                        }
583                    }
584                }
585            });
586    
587            JPanel buttons = GuiUtils.makeApplyOkHelpCancelButtons(this);
588            buttonPane = McVGuiUtils.makePrettyButtons(buttons);
589    
590            contents = new JPanel();
591            javax.swing.GroupLayout layout = new javax.swing.GroupLayout(contents);
592            contents.setLayout(layout);
593            layout.setHorizontalGroup(
594                layout.createParallelGroup(LEADING)
595                .addGroup(layout.createSequentialGroup()
596                    .addComponent(listScrollPane, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)
597                    .addPreferredGap(RELATED)
598                    .addComponent(mainPane, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
599                    .addComponent(buttonPane, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
600            );
601            layout.setVerticalGroup(
602                layout.createParallelGroup(LEADING)
603                .addGroup(layout.createSequentialGroup()
604                    .addGroup(layout.createParallelGroup(TRAILING)
605                        .addComponent(mainPane, LEADING, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
606                        .addComponent(listScrollPane, LEADING, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
607                        .addPreferredGap(RELATED)
608                        .addComponent(buttonPane, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE))
609            );
610        }
611    
612        /**
613         * Initialize the preference dialog. Leave most of the heavy lifting to
614         * the IDV, except for creating the server manager.
615         */
616        protected void initPreferences() {
617            // General/McIDAS-V
618            addMcVPreferences();
619    
620            // View/Display Window
621            addDisplayWindowPreferences();
622    
623            // Toolbar/Toolbar Options
624            addToolbarPreferences();
625    
626            // Available Choosers/Data Sources
627            addChooserPreferences();
628    
629            // ADDE Servers
630            addServerPreferences();
631    
632            // Available Displays/Display Types
633            addDisplayPreferences();
634    
635            // Navigation/Navigation Controls
636            addNavigationPreferences();
637    
638            // Formats & Data
639            addFormatDataPreferences();
640    
641            // Advanced
642            addAdvancedPreferences();
643        }
644    
645        /**
646         * Build a {@link AddePreferences} panel {@literal "around"} the
647         * server manager {@link EntryStore}.
648         * 
649         * @see McIDASV#getServerManager()
650         */
651        public void addServerPreferences() {
652            EntryStore remoteAddeStore = ((McIDASV)getIdv()).getServerManager();
653            AddePreferences prefs = new AddePreferences(remoteAddeStore);
654            prefs.addPanel(this);
655        }
656    
657        /**
658         * Create the navigation preference panel
659         */
660        public void addNavigationPreferences() {
661            PreferenceManager navigationManager = new PreferenceManager() {
662                public void applyPreference(XmlObjectStore theStore, Object data) {
663    //                System.err.println("applying nav prefs");
664                }
665            };
666            this.add(Constants.PREF_LIST_NAV_CONTROLS, "", navigationManager, makeEventPanel(), new Hashtable());
667        }
668    
669        /**
670         * Create the toolbar preference panel
671         *
672         * @param preferenceManager The preference manager
673         */
674        public void addToolbarPreferences() {
675            if (toolbarEditor == null) {
676                toolbarEditor = 
677                    new McvToolbarEditor((UIManager)getIdv().getIdvUIManager());
678            }
679    
680            PreferenceManager toolbarManager = new PreferenceManager() {
681                public void applyPreference(XmlObjectStore s, Object d) {
682                    if (toolbarEditor.anyChanges() == true) {
683                        toolbarEditor.doApply();
684                        UIManager mngr = (UIManager)getIdv().getIdvUIManager();
685                        mngr.setCurrentToolbars(toolbarEditor);
686                    }
687                }
688            };
689            this.add("Toolbar", "Toolbar icons", toolbarManager,
690                                  toolbarEditor.getContents(), toolbarEditor);
691        }
692    
693        /**
694         * Make a checkbox preference panel
695         *
696         * @param objects Holds (Label, preference id, Boolean default value).
697         * If preference id is null then just show the label. If the entry is only length
698         * 2 (i.e., no value) then default to true.
699         * @param widgets The map to store the id to widget
700         * @param store  Where to look up the preference value
701         *
702         * @return The created panel
703         */
704        @SuppressWarnings("unchecked") // idv-style.
705        public static JPanel makePrefPanel(final Object[][] objects, final Hashtable widgets, final XmlObjectStore store) {
706            List<JComponent> comps = CollectionHelpers.arrList();
707            for (int i = 0; i < objects.length; i++) {
708                final String name = (String)objects[i][0];
709                final String id = (String)objects[i][1];
710                final boolean value = ((objects[i].length > 2) ? ((Boolean) objects[i][2]).booleanValue() : true);
711    
712                if (id == null) {
713                    if (i > 0) {
714                        comps.add(new JLabel(" "));
715                    }
716                    comps.add(new JLabel(name));
717                    continue;
718                }
719    
720                final JCheckBox cb = new JCheckBox(name, store.get(id, value));
721                cb.addPropertyChangeListener(new PropertyChangeListener() {
722                    public void propertyChange(final PropertyChangeEvent e) {
723                        SwingUtilities.invokeLater(new Runnable() {
724                            public void run() {
725                                boolean internalSel = store.get(id, value);
726    
727                                cb.setSelected(store.get(id, value));
728                            }
729                        });
730                    }
731                });
732                if (objects[i].length > 3) {
733                    cb.setToolTipText(objects[i][3].toString());
734                }
735                widgets.put(id, cb);
736                comps.add(cb);
737            }
738            return GuiUtils.top(GuiUtils.vbox(comps));
739        }
740    
741        public void addAdvancedPreferences() {
742            Hashtable<String, Component> widgets = new Hashtable<String, Component>();
743    
744            McIDASV mcv = (McIDASV)getIdv();
745            
746            // Build the threads panel
747            Vector threadRenderList = new Vector();
748            for(int i = 1;i <= Runtime.getRuntime().availableProcessors();i++) {
749                threadRenderList.add(new Integer(i));
750            }
751            Integer threadRenderMax = new Integer(mcv.getMaxRenderThreadCount());
752            final JComboBox threadRenderComboBox = McVGuiUtils.makeComboBox(threadRenderList, threadRenderMax);        
753            widgets.put(PREF_THREADS_RENDER, threadRenderComboBox);
754    
755            Vector threadReadList = new Vector();
756            for(int i = 1; i <= 12; i++) {
757                threadReadList.add(new Integer(i));
758            }
759            Integer threadReadMax = new Integer(mcv.getMaxDataThreadCount());
760            final JComboBox threadReadComboBox = McVGuiUtils.makeComboBox(threadReadList, threadReadMax);        
761            widgets.put(PREF_THREADS_DATA, threadReadComboBox);
762    
763            JPanel threadsPanel = McVGuiUtils.topBottom(
764                McVGuiUtils.makeLabeledComponent("Rendering:", threadRenderComboBox),
765                McVGuiUtils.makeLabeledComponent("Reading:", threadReadComboBox),
766                Prefer.NEITHER);
767            threadsPanel.setBorder(BorderFactory.createTitledBorder("Java Threads"));
768    
769            // Build the startup options panel
770            StartupManager.INSTANCE.getPlatform().setUserDirectory(
771                    mcv.getObjectStore().getUserDirectory().toString());
772            StartupManager.INSTANCE.getPlatform().setAvailableMemory(
773                   mcv.getStateManager().getProperty(Constants.PROP_SYSMEM, "0"));
774            JPanel smPanel = StartupManager.INSTANCE.getAdvancedPanel(true);
775            List<JPanel> stuff = Collections.singletonList(smPanel);
776    
777            PreferenceManager advancedManager = new PreferenceManager() {
778                public void applyPreference(XmlObjectStore theStore, Object data) {
779                    IdvPreferenceManager.applyWidgets((Hashtable)data, theStore);
780                    StartupManager.INSTANCE.handleApply();
781                }
782            };
783    
784            JPanel outerPanel = new JPanel();
785    
786            // Outer panel layout
787            javax.swing.GroupLayout layout = new javax.swing.GroupLayout(outerPanel);
788            outerPanel.setLayout(layout);
789            layout.setHorizontalGroup(
790                layout.createParallelGroup(LEADING)
791                .addGroup(layout.createSequentialGroup()
792                    .addContainerGap()
793                    .addGroup(layout.createParallelGroup(LEADING)
794                        .addComponent(smPanel, TRAILING, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
795                        .addComponent(threadsPanel, TRAILING, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
796                    .addContainerGap())
797            );
798            layout.setVerticalGroup(
799                layout.createParallelGroup(LEADING)
800                .addGroup(layout.createSequentialGroup()
801                    .addContainerGap()
802                    .addComponent(smPanel, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)
803                    .addGap(GAP_UNRELATED)
804                    .addComponent(threadsPanel, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)
805                    .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE))
806            );
807    
808            this.add(Constants.PREF_LIST_ADVANCED, "complicated stuff dude", 
809                advancedManager, outerPanel, widgets);
810        }
811    
812        /**
813         * Add in the user preference tab for the controls to show
814         */
815        protected void addDisplayPreferences() {
816            McIDASV mcv = (McIDASV)getIdv();
817            cbxToCdMap = new Hashtable<JCheckBox, ControlDescriptor>();
818            List<JPanel> compList = new ArrayList<JPanel>();
819            List<ControlDescriptor> controlDescriptors = 
820                getIdv().getAllControlDescriptors();
821    
822            final List<CheckboxCategoryPanel> catPanels = 
823                new ArrayList<CheckboxCategoryPanel>();
824    
825            final Hashtable<String, CheckboxCategoryPanel> catMap = 
826                new Hashtable<String, CheckboxCategoryPanel>();
827    
828            for (ControlDescriptor cd : controlDescriptors) {
829    
830                final String displayCategory = cd.getDisplayCategory();
831    
832                CheckboxCategoryPanel catPanel =
833                    (CheckboxCategoryPanel) catMap.get(displayCategory);
834    
835                if (catPanel == null) {
836                    catPanel = new CheckboxCategoryPanel(displayCategory, false);
837                    catPanels.add(catPanel);
838                    catMap.put(displayCategory, catPanel);
839                    compList.add(catPanel.getTopPanel());
840                    compList.add(catPanel);
841                }
842    
843                JCheckBox cbx = 
844                    new JCheckBox(cd.getLabel(), shouldShowControl(cd, true));
845                cbx.setToolTipText(cd.getDescription());
846                cbxToCdMap.put(cbx, cd);
847                catPanel.addItem(cbx);
848                catPanel.add(GuiUtils.inset(cbx, new Insets(0, 20, 0, 0)));
849            }
850    
851            for (CheckboxCategoryPanel cbcp : catPanels) {
852                cbcp.checkVisCbx();
853            }
854    
855            final JButton allOn = new JButton("All on");
856            allOn.addActionListener(new ActionListener() {
857                public void actionPerformed(ActionEvent ae) {
858                    for (CheckboxCategoryPanel cbcp : catPanels) {
859                        cbcp.toggleAll(true);
860                    }
861                }
862            });
863            final JButton allOff = new JButton("All off");
864            allOff.addActionListener(new ActionListener() {
865                public void actionPerformed(ActionEvent ae) {
866                    for (CheckboxCategoryPanel cbcp : catPanels) {
867                        cbcp.toggleAll(false);
868                    }
869                }
870            });
871    
872            Boolean controlsAll =
873                (Boolean)mcv.getPreference(PROP_CONTROLDESCRIPTORS_ALL, Boolean.TRUE);
874            final JRadioButton useAllBtn = new JRadioButton("Use all displays",
875                                               controlsAll.booleanValue());
876            final JRadioButton useTheseBtn =
877                new JRadioButton("Use selected displays:",
878                                 !controlsAll.booleanValue());
879            GuiUtils.buttonGroup(useAllBtn, useTheseBtn);
880    
881            final JPanel cbPanel = GuiUtils.top(GuiUtils.vbox(compList));
882    
883            JScrollPane cbScroller = new JScrollPane(cbPanel);
884            cbScroller.getVerticalScrollBar().setUnitIncrement(10);
885            cbScroller.setPreferredSize(new Dimension(300, 300));
886    
887            JComponent exportComp =
888                GuiUtils.right(GuiUtils.makeButton("Export to Plugin", this,
889                    "exportControlsToPlugin"));
890    
891            JComponent cbComp = GuiUtils.centerBottom(cbScroller, exportComp);
892    
893            JPanel bottomPanel =
894                GuiUtils.leftCenter(
895                    GuiUtils.inset(
896                        GuiUtils.top(GuiUtils.vbox(allOn, allOff)),
897                        4), new Msg.SkipPanel(
898                            GuiUtils.hgrid(
899                                Misc.newList(cbComp, GuiUtils.filler()), 0)));
900    
901            JPanel controlsPanel =
902                GuiUtils.inset(GuiUtils.topCenter(GuiUtils.hbox(useAllBtn,
903                    useTheseBtn), bottomPanel), 6);
904    
905            GuiUtils.enableTree(cbPanel, !useAllBtn.isSelected());
906            useAllBtn.addActionListener(new ActionListener() {
907                public void actionPerformed(ActionEvent ae) {
908                    GuiUtils.enableTree(cbPanel, !useAllBtn.isSelected());
909                    allOn.setEnabled(!useAllBtn.isSelected());
910                    allOff.setEnabled(!useAllBtn.isSelected());
911                }
912            });
913    
914            useTheseBtn.addActionListener(new ActionListener() {
915                public void actionPerformed(ActionEvent ae) {
916                    GuiUtils.enableTree(cbPanel, !useAllBtn.isSelected());
917                    allOn.setEnabled(!useAllBtn.isSelected());
918                    allOff.setEnabled(!useAllBtn.isSelected());
919                }
920            });
921    
922            GuiUtils.enableTree(cbPanel, !useAllBtn.isSelected());
923    
924            allOn.setEnabled(!useAllBtn.isSelected());
925    
926            allOff.setEnabled(!useAllBtn.isSelected());
927    
928            PreferenceManager controlsManager = new PreferenceManager() {
929                public void applyPreference(XmlObjectStore theStore, Object data) {
930                    controlDescriptorsToShow = new Hashtable();
931    
932                    Hashtable<JCheckBox, ControlDescriptor> table = (Hashtable)data;
933    
934                    List<ControlDescriptor> controlDescriptors = getIdv().getAllControlDescriptors();
935    
936                    for (Enumeration keys = table.keys(); keys.hasMoreElements(); ) {
937                        JCheckBox cbx = (JCheckBox) keys.nextElement();
938                        ControlDescriptor cd = (ControlDescriptor)table.get(cbx);
939                        controlDescriptorsToShow.put(cd.getControlId(), Boolean.valueOf(cbx.isSelected()));
940                    }
941    
942                    showAllControls = useAllBtn.isSelected();
943    
944                    theStore.put(PROP_CONTROLDESCRIPTORS, controlDescriptorsToShow);
945                    theStore.put(PROP_CONTROLDESCRIPTORS_ALL, Boolean.valueOf(showAllControls));
946                }
947            };
948    
949            this.add(Constants.PREF_LIST_AVAILABLE_DISPLAYS,
950                     "What displays should be available in the user interface?",
951                     controlsManager, controlsPanel, cbxToCdMap);
952        }
953    
954        protected void addDisplayWindowPreferences() {
955    
956            Hashtable<String, JCheckBox> widgets = new Hashtable<String, JCheckBox>();
957            MapViewManager mappy = new MapViewManager(getIdv());
958    
959            Object[][] legendObjects = {
960                { "Show Side Legend", MapViewManager.PREF_SHOWSIDELEGEND, Boolean.valueOf(mappy.getShowSideLegend()) },
961                { "Show Bottom Legend", MapViewManager.PREF_SHOWBOTTOMLEGEND, Boolean.valueOf(mappy.getShowBottomLegend()) }
962            };
963            JPanel legendPanel = makePrefPanel(legendObjects, widgets, getStore());
964            legendPanel.setBorder(BorderFactory.createTitledBorder("Legends"));
965    
966            Object[][] navigationObjects = {
967                { "Show Earth Navigation Panel", MapViewManager.PREF_SHOWEARTHNAVPANEL, Boolean.valueOf(mappy.getShowEarthNavPanel()) },
968                { "Show Viewpoint Toolbar", MapViewManager.PREF_SHOWTOOLBAR + "perspective" },
969                { "Show Zoom/Pan Toolbar", MapViewManager.PREF_SHOWTOOLBAR + "zoompan" },
970                { "Show Undo/Redo Toolbar", MapViewManager.PREF_SHOWTOOLBAR + "undoredo" }
971            };
972            JPanel navigationPanel = makePrefPanel(navigationObjects, widgets, getStore());
973            navigationPanel.setBorder(BorderFactory.createTitledBorder("Navigation Toolbars"));
974    
975            Object[][] panelObjects = {
976                { "Show Globe Background", MapViewManager.PREF_SHOWGLOBEBACKGROUND, Boolean.valueOf(getStore().get(MapViewManager.PREF_SHOWGLOBEBACKGROUND, false)) },
977                { "Show Wireframe Box", MapViewManager.PREF_WIREFRAME, Boolean.valueOf(mappy.getWireframe()) },
978                { "Show Cursor Readout", MapViewManager.PREF_SHOWCURSOR, Boolean.valueOf(mappy.getShowCursor()) },
979                { "Clip View At Box", MapViewManager.PREF_3DCLIP, Boolean.valueOf(mappy.getClipping()) },
980                { "Show Layer List in Panel", MapViewManager.PREF_SHOWDISPLAYLIST, Boolean.valueOf(mappy.getShowDisplayList()) },
981                { "Show Times In Panel", MapViewManager.PREF_ANIREADOUT, Boolean.valueOf(mappy.getAniReadout()) },
982                { "Show Map Display Scales", MapViewManager.PREF_SHOWSCALES, Boolean.valueOf(mappy.getLabelsVisible()) },
983                { "Show Transect Display Scales", MapViewManager.PREF_SHOWTRANSECTSCALES, Boolean.valueOf(mappy.getTransectLabelsVisible()) },
984                { "Show \"Please Wait\" Message", MapViewManager.PREF_WAITMSG, Boolean.valueOf(mappy.getWaitMessageVisible()) },
985                { "Reset Projection With New Data", MapViewManager.PREF_PROJ_USEFROMDATA }
986            };
987            JPanel panelPanel = makePrefPanel(panelObjects, widgets, getStore());
988            panelPanel.setBorder(BorderFactory.createTitledBorder("Panel Configuration"));
989    
990            final JComponent[] globeBg = 
991              GuiUtils.makeColorSwatchWidget(mappy.getGlobeBackgroundColorToUse(), 
992                  "Set Globe Background Color");
993            final JComponent[] bgComps =
994                GuiUtils.makeColorSwatchWidget(getStore().get(MapViewManager.PREF_BGCOLOR,
995                    mappy.getBackground()), "Set Background Color");
996            final JComponent[] fgComps =
997                GuiUtils.makeColorSwatchWidget(getStore().get(MapViewManager.PREF_FGCOLOR,
998                    mappy.getForeground()), "Set Foreground Color"); 
999            final JComponent[] border = 
1000                GuiUtils.makeColorSwatchWidget(getStore().get(MapViewManager.PREF_BORDERCOLOR, 
1001                    Constants.MCV_BLUE_DARK), "Set Selected Panel Border Color");
1002    
1003            JPanel colorPanel = GuiUtils.vbox(
1004                    GuiUtils.hbox(
1005                            McVGuiUtils.makeLabelRight("Globe Background:", Width.ONEHALF),
1006                            GuiUtils.left(globeBg[0]),
1007                            GAP_RELATED
1008                    ),
1009                    GuiUtils.hbox(
1010                            McVGuiUtils.makeLabelRight("Background:", Width.ONEHALF),
1011                            GuiUtils.left(bgComps[0]),
1012                            GAP_RELATED
1013                    ),
1014                    GuiUtils.hbox(
1015                            McVGuiUtils.makeLabelRight("Foreground:", Width.ONEHALF),
1016                            GuiUtils.left(fgComps[0]),
1017                            GAP_RELATED
1018                    ),
1019                    GuiUtils.hbox(
1020                            McVGuiUtils.makeLabelRight("Selected Panel:", Width.ONEHALF),
1021                            GuiUtils.left(border[0]),
1022                            GAP_RELATED
1023                    )
1024            );
1025    
1026            colorPanel.setBorder(BorderFactory.createTitledBorder("Color Scheme"));
1027    
1028            final FontSelector fontSelector = new FontSelector(FontSelector.COMBOBOX_UI, false, false);
1029            Font f = getStore().get(MapViewManager.PREF_DISPLAYLISTFONT, mappy.getDisplayListFont());
1030            fontSelector.setFont(f);
1031            final GuiUtils.ColorSwatch dlColorWidget =
1032                new GuiUtils.ColorSwatch(getStore().get(MapViewManager.PREF_DISPLAYLISTCOLOR,
1033                    mappy.getDisplayListColor()), "Set Display List Color");
1034    
1035            JPanel fontPanel = GuiUtils.vbox(
1036                GuiUtils.hbox(
1037                    McVGuiUtils.makeLabelRight("Font:", Width.ONEHALF),
1038                    GuiUtils.left(fontSelector.getComponent()),
1039                    GAP_RELATED
1040                ),
1041                GuiUtils.hbox(
1042                    McVGuiUtils.makeLabelRight("Color:", Width.ONEHALF),
1043                    GuiUtils.left(GuiUtils.hbox(dlColorWidget, dlColorWidget.getClearButton(), GAP_RELATED)),
1044                    GAP_RELATED
1045                )
1046            );
1047            fontPanel.setBorder(BorderFactory.createTitledBorder("Layer List Properties"));
1048    
1049            final JComboBox projBox = new JComboBox();
1050            GuiUtils.setListData(projBox, mappy.getProjectionList().toArray());
1051            Object defaultProj = mappy.getDefaultProjection();
1052            if (defaultProj != null) projBox.setSelectedItem(defaultProj);
1053            JPanel projPanel = GuiUtils.left(projBox);
1054            projPanel.setBorder(BorderFactory.createTitledBorder("Default Projection"));
1055    
1056            JPanel outerPanel = new JPanel();
1057    
1058            // Outer panel layout
1059            javax.swing.GroupLayout layout = new javax.swing.GroupLayout(outerPanel);
1060            outerPanel.setLayout(layout);
1061            layout.setHorizontalGroup(
1062                layout.createParallelGroup(LEADING)
1063                .addGroup(layout.createSequentialGroup()
1064                    .addContainerGap()
1065                    .addGroup(layout.createParallelGroup(LEADING)
1066                        .addComponent(navigationPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1067                        .addComponent(panelPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
1068                    .addGap(GAP_RELATED)
1069                    .addGroup(layout.createParallelGroup(LEADING)
1070                        .addComponent(colorPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1071                        .addComponent(legendPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1072                        .addComponent(fontPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1073                        .addComponent(projPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
1074                    .addContainerGap())
1075            );
1076            layout.setVerticalGroup(
1077                layout.createParallelGroup(LEADING)
1078                .addGroup(layout.createSequentialGroup()
1079                    .addContainerGap()
1080                    .addGroup(layout.createParallelGroup(LEADING, false)
1081                        .addComponent(navigationPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1082                        .addComponent(legendPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
1083                    .addPreferredGap(RELATED)
1084                    .addGroup(layout.createParallelGroup(LEADING, false)
1085                        .addGroup(layout.createSequentialGroup()
1086                            .addComponent(colorPanel, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)
1087                            .addPreferredGap(RELATED)
1088                            .addComponent(fontPanel, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)
1089                            .addPreferredGap(RELATED)
1090                            .addComponent(projPanel, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE))
1091                        .addComponent(panelPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
1092                    .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE))
1093            );
1094    
1095            PreferenceManager miscManager = new PreferenceManager() {
1096                @SuppressWarnings("unchecked") // applyWidgets called the same way the IDV does it.
1097                public void applyPreference(XmlObjectStore theStore, Object data) {
1098                    IdvPreferenceManager.applyWidgets((Hashtable)data, theStore);
1099                    theStore.put(MapViewManager.PREF_PROJ_DFLT, projBox.getSelectedItem());
1100                    theStore.put(MapViewManager.PREF_BGCOLOR, bgComps[0].getBackground());
1101                    theStore.put(MapViewManager.PREF_FGCOLOR, fgComps[0].getBackground());
1102                    theStore.put(MapViewManager.PREF_BORDERCOLOR, border[0].getBackground());
1103                    theStore.put(MapViewManager.PREF_DISPLAYLISTFONT, fontSelector.getFont());
1104                    theStore.put(MapViewManager.PREF_DISPLAYLISTCOLOR, dlColorWidget.getSwatchColor());
1105                    theStore.put(MapViewManager.PREF_GLOBEBACKGROUND, globeBg[0].getBackground());
1106                    ViewManager.setHighlightBorder(border[0].getBackground());
1107                }
1108            };
1109    
1110            this.add(Constants.PREF_LIST_VIEW, "Display Window Preferences", miscManager, outerPanel, widgets);
1111        }
1112    
1113        /**
1114         * Creates and adds the basic preference panel.
1115         */
1116        protected void addMcVPreferences() {
1117    
1118            Hashtable<String, Component> widgets = new Hashtable<String, Component>();
1119            McIDASV mcv = (McIDASV)getIdv();
1120            StateManager sm = (edu.wisc.ssec.mcidasv.StateManager)mcv.getStateManager();
1121    
1122            PreferenceManager basicManager = new PreferenceManager() {
1123                @SuppressWarnings("unchecked") // IDV-style call to applyWidgets.
1124                public void applyPreference(XmlObjectStore theStore, Object data) {
1125                    applyWidgets((Hashtable)data, theStore);
1126                    getIdv().getIdvUIManager().setDateFormat();
1127                    getIdv().initCacheManager();
1128                    applyEventPreferences(theStore);
1129                }
1130            };
1131    
1132            boolean isPrerelease = sm.getIsPrerelease();
1133            Object[][] generalObjects = {
1134                { "Show Help Tips on start", HelpTipDialog.PREF_HELPTIPSHOW },
1135                { "Show Data Explorer on start", PREF_SHOWDASHBOARD, Boolean.TRUE },
1136                { "Check for new version and notice on start", Constants.PREF_VERSION_CHECK, Boolean.TRUE },
1137                { "Include prereleases in version check", Constants.PREF_PRERELEASE_CHECK, isPrerelease },
1138                { "Confirm before exiting", PREF_SHOWQUITCONFIRM },
1139                { "Automatically save default layout at exit", Constants.PREF_AUTO_SAVE_DEFAULT_LAYOUT, Boolean.FALSE },
1140                { "Save visibility of Data Explorer", Constants.PREF_SAVE_DASHBOARD_VIZ, Boolean.FALSE },
1141                { "Confirm removal of all data sources", PREF_CONFIRM_REMOVE_DATA, Boolean.TRUE },
1142                { "Confirm removal of all layers", PREF_CONFIRM_REMOVE_LAYERS, Boolean.TRUE },
1143                { "Confirm removal of all layers and data sources", PREF_CONFIRM_REMOVE_BOTH, Boolean.TRUE },
1144            };
1145            final IdvObjectStore store = getStore();
1146            JPanel generalPanel = makePrefPanel(generalObjects, widgets, store);
1147            generalPanel.setBorder(BorderFactory.createTitledBorder("General"));
1148    
1149            // Turn what used to be a set of checkboxes into a corresponding menu selection
1150            // The options have to be checkboxes in the widget collection
1151            // That way "applyWidgets" will work as expected
1152            boolean shouldRemove = store.get(PREF_OPEN_REMOVE, false);
1153            boolean shouldMerge  = store.get(PREF_OPEN_MERGE, false);
1154            final JCheckBox shouldRemoveCbx = new JCheckBox("You shouldn't see this", shouldRemove);
1155            final JCheckBox shouldMergeCbx  = new JCheckBox("You shouldn't see this", shouldMerge);
1156            widgets.put(PREF_OPEN_REMOVE, shouldRemoveCbx);
1157            widgets.put(PREF_OPEN_MERGE, shouldMergeCbx);
1158    
1159            final JComboBox loadComboBox = new JComboBox(loadComboOptions);
1160            loadComboBox.addActionListener(new ActionListener() {
1161                public void actionPerformed(ActionEvent e) {
1162                    switch (((JComboBox)e.getSource()).getSelectedIndex()) {
1163                    case 0:
1164                        shouldRemoveCbx.setSelected(false);
1165                        shouldMergeCbx.setSelected(false);
1166                        break;
1167                    case 1:
1168                        shouldRemoveCbx.setSelected(true);
1169                        shouldMergeCbx.setSelected(false);
1170                        break;
1171                    case 2:
1172                        shouldRemoveCbx.setSelected(false);
1173                        shouldMergeCbx.setSelected(true);
1174                        break;
1175                    case 3:
1176                        shouldRemoveCbx.setSelected(true);
1177                        shouldMergeCbx.setSelected(true);
1178                        break;
1179                    }
1180                }
1181            });
1182    
1183            // update the bundle loading options upon visibility changes.
1184            loadComboBox.addPropertyChangeListener(new PropertyChangeListener() {
1185                public void propertyChange(final PropertyChangeEvent e) {
1186                    String prop = e.getPropertyName();
1187                    if (!"ancestor".equals(prop) && !"Frame.active".equals(prop)) {
1188                        return;
1189                    }
1190    
1191                    boolean remove = store.get(PREF_OPEN_REMOVE, false);
1192                    boolean merge  = store.get(PREF_OPEN_MERGE, false);
1193    
1194                    if (!remove) {
1195                        if (!merge) { 
1196                            loadComboBox.setSelectedIndex(0);
1197                        } else {
1198                            loadComboBox.setSelectedIndex(2);
1199                        }
1200                    }
1201                    else {
1202                        if (!merge) {
1203                            loadComboBox.setSelectedIndex(1);
1204                        } else {
1205                            loadComboBox.setSelectedIndex(3);
1206                        }
1207                    }
1208                }
1209            });
1210    
1211            if (!shouldRemove) {
1212                if (!shouldMerge) {
1213                    loadComboBox.setSelectedIndex(0);
1214                } else {
1215                    loadComboBox.setSelectedIndex(2);
1216                }
1217            }
1218            else {
1219                if (!shouldMerge) {
1220                    loadComboBox.setSelectedIndex(1);
1221                } else { 
1222                    loadComboBox.setSelectedIndex(3);
1223                }
1224            }
1225    
1226            Object[][] bundleObjects = {
1227                { "Prompt when opening bundles", PREF_OPEN_ASK },
1228                { "Prompt for location for zipped data", PREF_ZIDV_ASK }
1229            };
1230            JPanel bundlePanelInner = makePrefPanel(bundleObjects, widgets, getStore());
1231            JPanel bundlePanel = GuiUtils.topCenter(loadComboBox, bundlePanelInner);
1232            bundlePanel.setBorder(BorderFactory.createTitledBorder("When Opening a Bundle"));
1233    
1234            Object[][] layerObjects = {
1235                { "Show windows when they are created", PREF_SHOWCONTROLWINDOW },
1236                { "Use fast rendering", PREF_FAST_RENDER, Boolean.FALSE, "<html>Turn this on for better performance at the risk of having funky displays</html>" },
1237                { "Auto-select data when loading a template", IdvConstants.PREF_AUTOSELECTDATA, Boolean.FALSE, "<html>When loading a display template should the data be automatically selected</html>" },
1238            };
1239            JPanel layerPanel = makePrefPanel(layerObjects, widgets, getStore());
1240            layerPanel.setBorder(BorderFactory.createTitledBorder("Layer Controls"));
1241    
1242            Object[][] layerclosedObjects = {
1243                { "Remove the display", DisplayControl.PREF_REMOVEONWINDOWCLOSE, Boolean.FALSE },
1244                { "Remove standalone displays", DisplayControl.PREF_STANDALONE_REMOVEONCLOSE, Boolean.FALSE }
1245            };
1246            JPanel layerclosedPanel = makePrefPanel(layerclosedObjects, widgets, getStore());
1247            layerclosedPanel.setBorder(BorderFactory.createTitledBorder("When Layer Control Window is Closed"));
1248    
1249            JPanel outerPanel = new JPanel();
1250    
1251            // Outer panel layout
1252            javax.swing.GroupLayout layout = new javax.swing.GroupLayout(outerPanel);
1253            outerPanel.setLayout(layout);
1254            layout.setHorizontalGroup(
1255                layout.createParallelGroup(LEADING)
1256                .addGroup(layout.createSequentialGroup()
1257                    .addContainerGap()
1258                    .addGroup(layout.createParallelGroup(TRAILING)
1259                        .addComponent(generalPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1260                        .addComponent(layerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
1261                    .addGap(GAP_RELATED)
1262                    .addGroup(layout.createParallelGroup(LEADING)
1263                        .addComponent(bundlePanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1264                        .addComponent(layerclosedPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
1265                    .addContainerGap())
1266            );
1267            layout.setVerticalGroup(
1268                layout.createParallelGroup(LEADING)
1269                .addGroup(layout.createSequentialGroup()
1270                    .addContainerGap()
1271                    .addGroup(layout.createParallelGroup(LEADING, false)
1272                        .addComponent(bundlePanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1273                        .addComponent(generalPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
1274                    .addPreferredGap(RELATED)
1275                    .addGroup(layout.createParallelGroup(LEADING, false)
1276                        .addComponent(layerclosedPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1277                        .addComponent(layerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
1278                    .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE))
1279            );
1280    
1281            this.add(Constants.PREF_LIST_GENERAL, "General Preferences", basicManager, outerPanel, widgets);
1282        }
1283    
1284        /**
1285         * <p>This determines whether the IDV should do a remove display and data 
1286         * before a bundle is loaded. It returns a 2 element boolean array. The 
1287         * first element is whether the open should take place at all. The second 
1288         * element determines whether displays and data should be removed before 
1289         * the load.</p>
1290         *
1291         * <p>Overridden by McIDAS-V so that we can ask the user whether or not we
1292         * should limit the number of new windows a bundle can create.</p>
1293         *
1294         * @param name Bundle name - may be null.
1295         *
1296         * @return Element 0: did user hit cancel; Element 1: Should remove data 
1297         *         and displays; Element 2: limit new windows.
1298         * 
1299         * @see IdvPreferenceManager#getDoRemoveBeforeOpening(String)
1300         */
1301        @Override public boolean[] getDoRemoveBeforeOpening(String name) {
1302            IdvObjectStore store = getStore();
1303            boolean shouldAsk    = store.get(PREF_OPEN_ASK, true);
1304            boolean shouldRemove = store.get(PREF_OPEN_REMOVE, false);
1305            boolean shouldMerge  = store.get(PREF_OPEN_MERGE, false);
1306    
1307            if (shouldAsk) {
1308                JComboBox loadComboBox = new JComboBox(loadComboOptions);
1309                JCheckBox preferenceCbx = new JCheckBox("Save as default preference", true);
1310                JCheckBox askCbx = new JCheckBox("Don't show this window again", false);
1311    
1312                if (!shouldRemove) {
1313                    if (!shouldMerge) {
1314                        loadComboBox.setSelectedIndex(0);
1315                    } else { 
1316                        loadComboBox.setSelectedIndex(2);
1317                    }
1318                }
1319                else {
1320                    if (!shouldMerge) {
1321                        loadComboBox.setSelectedIndex(1);
1322                    } else {
1323                        loadComboBox.setSelectedIndex(3);
1324                    }
1325                }
1326    
1327                JPanel inner = new JPanel();
1328                javax.swing.GroupLayout layout = new javax.swing.GroupLayout(inner);
1329                inner.setLayout(layout);
1330                layout.setHorizontalGroup(
1331                    layout.createParallelGroup(LEADING)
1332                    .addGroup(layout.createSequentialGroup()
1333                        .addContainerGap()
1334                        .addGroup(layout.createParallelGroup(LEADING)
1335                            .addComponent(loadComboBox, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1336                            .addComponent(preferenceCbx)
1337                            .addComponent(askCbx))
1338                        .addContainerGap())
1339                );
1340                layout.setVerticalGroup(
1341                    layout.createParallelGroup(LEADING)
1342                    .addGroup(layout.createSequentialGroup()
1343                        .addContainerGap()
1344                        .addComponent(loadComboBox, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)
1345                        .addPreferredGap(RELATED)
1346                        .addComponent(preferenceCbx)
1347                        .addPreferredGap(RELATED)
1348                        .addComponent(askCbx)
1349                        .addContainerGap())
1350                );
1351    
1352                if (!GuiUtils.showOkCancelDialog(null, "Open bundle", inner, null)) {
1353                    return new boolean[] { false, false, false };
1354                }
1355    
1356                switch (loadComboBox.getSelectedIndex()) {
1357                    case 0: // new windows
1358                        shouldRemove = false;
1359                        shouldMerge = false;
1360                        break;
1361                    case 1: // merge with existing tabs
1362                        shouldRemove = true;
1363                        shouldMerge = false;
1364                        break;
1365                    case 2: // add new tab(s) to current
1366                        shouldRemove = false;
1367                        shouldMerge = true;
1368                        break;
1369                    case 3: // replace session
1370                        shouldRemove = true;
1371                        shouldMerge = true;
1372                        break;
1373                }
1374    
1375                // Save these as default preference if the user wants to
1376                if (preferenceCbx.isSelected()) {
1377                    store.put(PREF_OPEN_REMOVE, shouldRemove);
1378                    store.put(PREF_OPEN_MERGE, shouldMerge);
1379                }
1380                store.put(PREF_OPEN_ASK, !askCbx.isSelected());
1381            }
1382            return new boolean[] { true, shouldRemove, shouldMerge };
1383        }
1384    
1385        /**
1386         * Creates and adds the formats and data preference panel.
1387         */
1388        protected void addFormatDataPreferences() {
1389            Hashtable<String, Component> widgets = new Hashtable<String, Component>();
1390    
1391            JPanel formatPanel = new JPanel();
1392            formatPanel.setBorder(BorderFactory.createTitledBorder("Formats"));
1393    
1394            // Date stuff
1395            JLabel dateLabel = McVGuiUtils.makeLabelRight("Date Format:", Width.ONEHALF);
1396    
1397            String dateFormat = getStore().get(PREF_DATE_FORMAT, DEFAULT_DATE_FORMAT);
1398    
1399            if (!dateFormats.contains(dateFormat)) {
1400                dateFormats.add(dateFormat);
1401            }
1402    
1403            final JComboBox dateComboBox = McVGuiUtils.makeComboBox(dateFormats, dateFormat, Width.DOUBLE);
1404            widgets.put(PREF_DATE_FORMAT, dateComboBox);
1405    
1406            JComponent dateHelpButton = getIdv().makeHelpButton("idv.tools.preferences.dateformat");
1407    
1408            JLabel dateExLabel = new JLabel("");
1409    
1410            // Time stuff
1411            JLabel timeLabel = McVGuiUtils.makeLabelRight("Time Zone:", Width.ONEHALF);
1412    
1413            String timeString = getStore().get(PREF_TIMEZONE, DEFAULT_TIMEZONE);
1414            String[] zoneStrings = TimeZone.getAvailableIDs();
1415            Arrays.sort(zoneStrings);
1416    
1417            final JComboBox timeComboBox = McVGuiUtils.makeComboBox(zoneStrings, timeString, Width.DOUBLE);
1418            widgets.put(PREF_TIMEZONE, timeComboBox);
1419    
1420            JComponent timeHelpButton = getIdv().makeHelpButton("idv.tools.preferences.dateformat");
1421    
1422            JLabel timeExLabel = new JLabel("");
1423    
1424            try {
1425                dateExLabel.setText("ex:  " + new DateTime().toString());
1426            } catch (Exception ve) {
1427                dateExLabel.setText("Can't format date: " + ve);
1428            }
1429    
1430            ObjectListener timeLabelListener = new ObjectListener(dateExLabel) {
1431                public void actionPerformed(ActionEvent ae) {
1432                    JLabel label  = (JLabel) theObject;
1433                    String format = dateComboBox.getSelectedItem().toString();
1434                    String zone = timeComboBox.getSelectedItem().toString();
1435                    try {
1436                        TimeZone tz = TimeZone.getTimeZone(zone);
1437                        // hack to make it the DateTime default
1438                        if (format.equals(DEFAULT_DATE_FORMAT)) {
1439                            if (zone.equals(DEFAULT_TIMEZONE)) {
1440                                format = DateTime.DEFAULT_TIME_FORMAT + "'Z'";
1441                            }
1442                        }
1443                        label.setText("ex:  " + new DateTime().formattedString(format, tz));
1444                    } catch (Exception ve) {
1445                        label.setText("Invalid format or time zone");
1446                        LogUtil.userMessage("Invalid format or time zone");
1447                    }
1448                }
1449            };
1450            dateComboBox.addActionListener(timeLabelListener);
1451            timeComboBox.addActionListener(timeLabelListener);
1452    
1453            // Lat/Lon stuff
1454            JLabel latlonLabel = McVGuiUtils.makeLabelRight("Lat/Lon Format:", Width.ONEHALF);
1455    
1456            String latlonFormatString = getStore().get(PREF_LATLON_FORMAT, "##0.0");
1457            JComboBox latlonComboBox = McVGuiUtils.makeComboBox(defaultLatLonFormats, latlonFormatString, Width.DOUBLE);
1458            widgets.put(PREF_LATLON_FORMAT, latlonComboBox);
1459    
1460            JComponent latlonHelpButton = getIdv().makeHelpButton("idv.tools.preferences.latlonformat");
1461    
1462            JLabel latlonExLabel = new JLabel("");
1463    
1464            try {
1465                latlonFormat.applyPattern(latlonFormatString);
1466                latlonExLabel.setText("ex: " + latlonFormat.format(latlonValue));
1467            } catch (IllegalArgumentException iae) {
1468                latlonExLabel.setText("Bad format: " + latlonFormatString);
1469            }
1470            latlonComboBox.addActionListener(new ObjectListener(latlonExLabel) {
1471                public void actionPerformed(final ActionEvent ae) {
1472                    JLabel label = (JLabel)theObject;
1473                    JComboBox box = (JComboBox)ae.getSource();
1474                    String pattern = box.getSelectedItem().toString();
1475                    try {
1476                        latlonFormat.applyPattern(pattern);
1477                        label.setText("ex: " + latlonFormat.format(latlonValue));
1478                    } catch (IllegalArgumentException iae) {
1479                        label.setText("bad pattern: " + pattern);
1480                        LogUtil.userMessage("Bad format:" + pattern);
1481                    }
1482                }
1483            });
1484    
1485            // Probe stuff
1486            JLabel probeLabel = McVGuiUtils.makeLabelRight("Probe Format:", Width.ONEHALF);
1487    
1488            String probeFormat = getStore().get(DisplayControl.PREF_PROBEFORMAT, DisplayControl.DEFAULT_PROBEFORMAT);
1489    //        List probeFormatsList = Misc.newList(DisplayControl.DEFAULT_PROBEFORMAT,
1490    //              "%rawvalue% [%rawunit%]", "%value%", "%rawvalue%", "%value% <i>%unit%</i>");
1491            JComboBox probeComboBox = McVGuiUtils.makeComboBox(probeFormatsList, probeFormat, Width.DOUBLE);
1492            widgets.put(DisplayControl.PREF_PROBEFORMAT, probeComboBox);
1493    
1494            JComponent probeHelpButton = getIdv().makeHelpButton("idv.tools.preferences.probeformat");
1495    
1496            // Distance stuff
1497            JLabel distanceLabel = McVGuiUtils.makeLabelRight("Distance Unit:", Width.ONEHALF);
1498    
1499            Unit distanceUnit = null;
1500            try {
1501                distanceUnit = ucar.visad.Util.parseUnit(getStore().get(PREF_DISTANCEUNIT, "km"));
1502            } catch (Exception exc) {}
1503            JComboBox distanceComboBox = getIdv().getDisplayConventions().makeUnitBox(distanceUnit, null);
1504            McVGuiUtils.setComponentWidth(distanceComboBox, Width.DOUBLE);
1505            widgets.put(PREF_DISTANCEUNIT, distanceComboBox);
1506    
1507            // Format panel layout
1508            javax.swing.GroupLayout formatLayout = new javax.swing.GroupLayout(formatPanel);
1509            formatPanel.setLayout(formatLayout);
1510            formatLayout.setHorizontalGroup(
1511                formatLayout.createParallelGroup(LEADING)
1512                .addGroup(formatLayout.createSequentialGroup()
1513                    .addContainerGap()
1514                    .addGroup(formatLayout.createParallelGroup(LEADING)
1515                        .addGroup(formatLayout.createSequentialGroup()
1516                            .addComponent(dateLabel)
1517                            .addGap(GAP_RELATED)
1518                            .addComponent(dateComboBox)
1519                            .addGap(GAP_RELATED)
1520                            .addComponent(dateHelpButton)
1521                            .addGap(GAP_RELATED)
1522                            .addComponent(dateExLabel))
1523                        .addGroup(formatLayout.createSequentialGroup()
1524                            .addComponent(timeLabel)
1525                            .addGap(GAP_RELATED)
1526                            .addComponent(timeComboBox))
1527                        .addGroup(formatLayout.createSequentialGroup()
1528                            .addComponent(latlonLabel)
1529                            .addGap(GAP_RELATED)
1530                            .addComponent(latlonComboBox)
1531                            .addGap(GAP_RELATED)
1532                            .addComponent(latlonHelpButton)
1533                            .addGap(GAP_RELATED)
1534                            .addComponent(latlonExLabel))
1535                        .addGroup(formatLayout.createSequentialGroup()
1536                            .addComponent(probeLabel)
1537                            .addGap(GAP_RELATED)
1538                            .addComponent(probeComboBox)
1539                            .addGap(GAP_RELATED)
1540                            .addComponent(probeHelpButton))
1541                        .addGroup(formatLayout.createSequentialGroup()
1542                            .addComponent(distanceLabel)
1543                            .addGap(GAP_RELATED)
1544                            .addComponent(distanceComboBox)))
1545                    .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE))
1546            );
1547            formatLayout.setVerticalGroup(
1548                formatLayout.createParallelGroup(LEADING)
1549                .addGroup(formatLayout.createSequentialGroup()
1550                    .addGroup(formatLayout.createParallelGroup(BASELINE)
1551                        .addComponent(dateComboBox)
1552                        .addComponent(dateLabel)
1553                        .addComponent(dateHelpButton)
1554                        .addComponent(dateExLabel))
1555                    .addPreferredGap(RELATED)
1556                    .addGroup(formatLayout.createParallelGroup(BASELINE)
1557                        .addComponent(timeComboBox)
1558                        .addComponent(timeLabel))
1559                    .addPreferredGap(RELATED)
1560                    .addGroup(formatLayout.createParallelGroup(BASELINE)
1561                        .addComponent(latlonComboBox)
1562                        .addComponent(latlonLabel)
1563                        .addComponent(latlonHelpButton)
1564                        .addComponent(latlonExLabel))
1565                    .addPreferredGap(RELATED)
1566                    .addGroup(formatLayout.createParallelGroup(BASELINE)
1567                        .addComponent(probeComboBox)
1568                        .addComponent(probeLabel)
1569                        .addComponent(probeHelpButton))
1570                    .addPreferredGap(RELATED)
1571                    .addGroup(formatLayout.createParallelGroup(BASELINE)
1572                        .addComponent(distanceComboBox)
1573                        .addComponent(distanceLabel))
1574                    .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE))
1575            );
1576    
1577            JPanel dataPanel = new JPanel();
1578            dataPanel.setBorder(BorderFactory.createTitledBorder("Data"));
1579    
1580            // Sampling stuff
1581            JLabel sampleLabel = McVGuiUtils.makeLabelRight("Sampling Mode:", Width.ONEHALF);
1582    
1583            String sampleValue = getStore().get(PREF_SAMPLINGMODE, DisplayControlImpl.WEIGHTED_AVERAGE);
1584            JRadioButton sampleWA = new JRadioButton(DisplayControlImpl.WEIGHTED_AVERAGE,
1585                sampleValue.equals(DisplayControlImpl.WEIGHTED_AVERAGE));
1586    
1587            sampleWA.setToolTipText("Use a weighted average sampling");
1588            JRadioButton sampleNN = new JRadioButton(DisplayControlImpl.NEAREST_NEIGHBOR,
1589                sampleValue.equals(DisplayControlImpl.NEAREST_NEIGHBOR));
1590    
1591            sampleNN.setToolTipText("Use a nearest neighbor sampling");
1592            GuiUtils.buttonGroup(sampleWA, sampleNN);
1593            widgets.put("WEIGHTED_AVERAGE", sampleWA);
1594            widgets.put("NEAREST_NEIGHBOR", sampleNN);
1595    
1596            // Pressure stuff
1597            JLabel verticalLabel = McVGuiUtils.makeLabelRight("Pressure to Height:", Width.ONEHALF);
1598    
1599            String verticalValue = getStore().get(PREF_VERTICALCS, DataUtil.STD_ATMOSPHERE);
1600            JRadioButton verticalSA = new JRadioButton("Standard Atmosphere", verticalValue.equals(DataUtil.STD_ATMOSPHERE));
1601            verticalSA.setToolTipText("Use a standard atmosphere height approximation");
1602            JRadioButton verticalV5D = new JRadioButton("Vis5D", verticalValue.equals(DataUtil.VIS5D_VERTICALCS));
1603            verticalV5D.setToolTipText("Use the Vis5D vertical transformation");
1604            GuiUtils.buttonGroup(verticalSA, verticalV5D);
1605            widgets.put(DataUtil.STD_ATMOSPHERE, verticalSA);
1606            widgets.put(DataUtil.VIS5D_VERTICALCS, verticalV5D);
1607    
1608            // Caching stuff
1609            JLabel cacheLabel = McVGuiUtils.makeLabelRight("Caching:", Width.ONEHALF);
1610    
1611            JCheckBox cacheCheckBox = new JCheckBox("Cache Data in Memory", getStore().get(PREF_DOCACHE, true));
1612            widgets.put(PREF_DOCACHE, cacheCheckBox);
1613    
1614            JLabel cacheEmptyLabel = McVGuiUtils.makeLabelRight("", Width.ONEHALF);
1615    
1616            JTextField cacheTextField = McVGuiUtils.makeTextField(Misc.format(getStore().get(PREF_CACHESIZE, 20.0)));
1617            JComponent cacheTextFieldComponent = GuiUtils.hbox(new JLabel("Disk Cache Size: "), cacheTextField, new JLabel(" megabytes"));
1618            widgets.put(PREF_CACHESIZE, cacheTextField);
1619    
1620            // Image stuff
1621            JLabel imageLabel = McVGuiUtils.makeLabelRight("Max Image Size:", Width.ONEHALF);
1622    
1623            JTextField imageField = McVGuiUtils.makeTextField(Misc.format(getStore().get(PREF_MAXIMAGESIZE, -1)));
1624            JComponent imageFieldComponent = GuiUtils.hbox(imageField, new JLabel(" pixels (-1 = no limit)"));
1625            widgets.put(PREF_MAXIMAGESIZE, imageField);
1626    
1627            // Grid stuff
1628            JLabel gridLabel = McVGuiUtils.makeLabelRight("Grid Threshold:", Width.ONEHALF);
1629    
1630            JTextField gridField = McVGuiUtils.makeTextField(Misc.format(getStore().get(PREF_FIELD_CACHETHRESHOLD, 1000000.)));
1631            JComponent gridFieldComponent = GuiUtils.hbox(gridField, new JLabel(" bytes (Cache grids larger than this to disk)"));
1632            widgets.put(PREF_FIELD_CACHETHRESHOLD, gridField);
1633    
1634            // Data panel layout
1635            javax.swing.GroupLayout dataLayout = new javax.swing.GroupLayout(dataPanel);
1636            dataPanel.setLayout(dataLayout);
1637            dataLayout.setHorizontalGroup(
1638                dataLayout.createParallelGroup(LEADING)
1639                .addGroup(dataLayout.createSequentialGroup()
1640                    .addContainerGap()
1641                    .addGroup(dataLayout.createParallelGroup(LEADING)
1642                        .addGroup(dataLayout.createSequentialGroup()
1643                            .addComponent(sampleLabel)
1644                            .addGap(GAP_RELATED)
1645                            .addComponent(sampleWA)
1646                            .addGap(GAP_RELATED)
1647                            .addComponent(sampleNN))
1648                        .addGroup(dataLayout.createSequentialGroup()
1649                            .addComponent(verticalLabel)
1650                            .addGap(GAP_RELATED)
1651                            .addComponent(verticalSA)
1652                            .addGap(GAP_RELATED)
1653                            .addComponent(verticalV5D))
1654                        .addGroup(dataLayout.createSequentialGroup()
1655                            .addComponent(cacheLabel)
1656                            .addGap(GAP_RELATED)
1657                            .addComponent(cacheCheckBox))
1658                        .addGroup(dataLayout.createSequentialGroup()
1659                            .addComponent(cacheEmptyLabel)
1660                            .addGap(GAP_RELATED)
1661                            .addComponent(cacheTextFieldComponent))
1662                        .addGroup(dataLayout.createSequentialGroup()
1663                            .addComponent(imageLabel)
1664                            .addGap(GAP_RELATED)
1665                            .addComponent(imageFieldComponent))
1666                        .addGroup(dataLayout.createSequentialGroup()
1667                            .addComponent(gridLabel)
1668                            .addGap(GAP_RELATED)
1669                            .addComponent(gridFieldComponent)))
1670                    .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE))
1671            );
1672            dataLayout.setVerticalGroup(
1673                dataLayout.createParallelGroup(LEADING)
1674                .addGroup(dataLayout.createSequentialGroup()
1675                    .addGroup(dataLayout.createParallelGroup(BASELINE)
1676                        .addComponent(sampleLabel)
1677                        .addComponent(sampleWA)
1678                        .addComponent(sampleNN))
1679                    .addPreferredGap(RELATED)
1680                    .addGroup(dataLayout.createParallelGroup(BASELINE)
1681                        .addComponent(verticalLabel)
1682                        .addComponent(verticalSA)
1683                        .addComponent(verticalV5D))
1684                    .addPreferredGap(RELATED)
1685                    .addGroup(dataLayout.createParallelGroup(BASELINE)
1686                        .addComponent(cacheLabel)
1687                        .addComponent(cacheCheckBox))
1688                    .addPreferredGap(RELATED)
1689                    .addGroup(dataLayout.createParallelGroup(BASELINE)
1690                        .addComponent(cacheEmptyLabel)
1691                        .addComponent(cacheTextFieldComponent))
1692                    .addPreferredGap(RELATED)
1693                    .addGroup(dataLayout.createParallelGroup(BASELINE)
1694                        .addComponent(imageLabel)
1695                        .addComponent(imageFieldComponent))
1696                    .addPreferredGap(RELATED)
1697                    .addGroup(dataLayout.createParallelGroup(BASELINE)
1698                        .addComponent(gridLabel)
1699                        .addComponent(gridFieldComponent))
1700                    .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE))
1701            ); 
1702    
1703            JPanel outerPanel = new JPanel();
1704    
1705            // Outer panel layout
1706            javax.swing.GroupLayout layout = new javax.swing.GroupLayout(outerPanel);
1707            outerPanel.setLayout(layout);
1708            layout.setHorizontalGroup(
1709                layout.createParallelGroup(LEADING)
1710                .addGroup(layout.createSequentialGroup()
1711                    .addContainerGap()
1712                    .addGroup(layout.createParallelGroup(LEADING)
1713                        .addComponent(formatPanel, TRAILING, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1714                        .addComponent(dataPanel, TRAILING, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
1715                    .addContainerGap())
1716            );
1717            layout.setVerticalGroup(
1718                layout.createParallelGroup(LEADING)
1719                .addGroup(layout.createSequentialGroup()
1720                    .addContainerGap()
1721                    .addComponent(formatPanel, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)
1722                    .addGap(GAP_UNRELATED)
1723                    .addComponent(dataPanel, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)
1724                    .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE))
1725            );
1726    
1727            PreferenceManager formatsManager = new PreferenceManager() {
1728                public void applyPreference(XmlObjectStore theStore, Object data) {
1729                    IdvPreferenceManager.applyWidgets((Hashtable)data, theStore);
1730    
1731                    // if we ever need to add formats and data prefs, here's where
1732                    // they get saved off (unless we override applyWidgets).
1733                }
1734            };
1735    
1736            this.add(Constants.PREF_LIST_FORMATS_DATA, "", formatsManager, outerPanel, widgets);
1737        }
1738    
1739        /**
1740         * Add in the user preference tab for the choosers to show.
1741         */
1742        protected void addChooserPreferences() {
1743            Hashtable<String, JCheckBox> choosersData = new Hashtable<String, JCheckBox>();
1744            List<JPanel> compList = new ArrayList<JPanel>();
1745    
1746            Boolean choosersAll =
1747                (Boolean) getIdv().getPreference(PROP_CHOOSERS_ALL, Boolean.TRUE);
1748    
1749            final List<String[]> choosers = getChooserData();
1750    
1751            final List<JCheckBox> choosersList = new ArrayList<JCheckBox>();
1752    
1753            final JRadioButton useAllBtn = new JRadioButton("Use all data sources",
1754                                               choosersAll.booleanValue());
1755            final JRadioButton useTheseBtn =
1756                new JRadioButton("Use selected data sources:",
1757                                 !choosersAll.booleanValue());
1758    
1759            GuiUtils.buttonGroup(useAllBtn, useTheseBtn);
1760    
1761            final List<CheckboxCategoryPanel> chooserPanels = 
1762                new ArrayList<CheckboxCategoryPanel>();
1763    
1764            final Hashtable<String, CheckboxCategoryPanel> chooserMap = 
1765                new Hashtable<String, CheckboxCategoryPanel>();
1766    
1767            // create the checkbox + chooser name that'll show up in the preference
1768            // panel.
1769            for (String[] cs : choosers) {
1770                final String chooserCategory = getChooserCategory(cs[1]);
1771                String chooserShortName = getChooserShortName(cs[1]);
1772    
1773                CheckboxCategoryPanel chooserPanel =
1774                    (CheckboxCategoryPanel) chooserMap.get(chooserCategory);
1775    
1776                if (chooserPanel == null) {
1777                    chooserPanel = new CheckboxCategoryPanel(chooserCategory, false);
1778                    chooserPanels.add(chooserPanel);
1779                    chooserMap.put(chooserCategory, chooserPanel);
1780                    compList.add(chooserPanel.getTopPanel());
1781                    compList.add(chooserPanel);
1782                }
1783    
1784                JCheckBox cbx = new JCheckBox(chooserShortName, shouldShowChooser(cs[0], true));
1785                choosersData.put(cs[0], cbx);
1786                chooserPanel.addItem(cbx);
1787                chooserPanel.add(GuiUtils.inset(cbx, new Insets(0, 20, 0, 0)));
1788            }
1789    
1790            for (CheckboxCategoryPanel cbcp : chooserPanels)
1791                cbcp.checkVisCbx();
1792    
1793            // handle the user opting to enable all choosers.
1794            final JButton allOn = new JButton("All on");
1795            allOn.addActionListener(new ActionListener() {
1796                public void actionPerformed(ActionEvent ae) {
1797                    for (CheckboxCategoryPanel cbcp : chooserPanels)
1798                        cbcp.toggleAll(true);
1799                }
1800            });
1801    
1802            // handle the user opting to disable all choosers.
1803            final JButton allOff = new JButton("All off");
1804            allOff.addActionListener(new ActionListener() {
1805                public void actionPerformed(ActionEvent ae) {
1806                    for (CheckboxCategoryPanel cbcp : chooserPanels)
1807                        cbcp.toggleAll(false);
1808                }
1809            });
1810    
1811            final JPanel cbPanel = GuiUtils.top(GuiUtils.vbox(compList));
1812    
1813            JScrollPane cbScroller = new JScrollPane(cbPanel);
1814            cbScroller.getVerticalScrollBar().setUnitIncrement(10);
1815            cbScroller.setPreferredSize(new Dimension(300, 300));
1816            
1817            GuiUtils.enableTree(cbPanel, !useAllBtn.isSelected());
1818            GuiUtils.enableTree(allOn, !useAllBtn.isSelected());
1819            GuiUtils.enableTree(allOff, !useAllBtn.isSelected());
1820    
1821            JPanel widgetPanel =
1822                GuiUtils.topCenter(
1823                    GuiUtils.hbox(useAllBtn, useTheseBtn),
1824                    GuiUtils.leftCenter(
1825                        GuiUtils.inset(
1826                            GuiUtils.top(GuiUtils.vbox(allOn, allOff)),
1827                            4), cbScroller));
1828            JPanel choosersPanel =
1829                GuiUtils.topCenter(
1830                    GuiUtils.inset(
1831                        new JLabel("Note: This will take effect the next run"),
1832                        4), widgetPanel);
1833            choosersPanel = GuiUtils.inset(GuiUtils.left(choosersPanel), 6);
1834            useAllBtn.addActionListener(new ActionListener() {
1835                public void actionPerformed(ActionEvent ae) {
1836                    GuiUtils.enableTree(cbPanel, !useAllBtn.isSelected());
1837                    GuiUtils.enableTree(allOn, !useAllBtn.isSelected());
1838                    GuiUtils.enableTree(allOff, !useAllBtn.isSelected());
1839    
1840                }
1841            });
1842            useTheseBtn.addActionListener(new ActionListener() {
1843                public void actionPerformed(ActionEvent ae) {
1844                    GuiUtils.enableTree(cbPanel, !useAllBtn.isSelected());
1845                    GuiUtils.enableTree(allOn, !useAllBtn.isSelected());
1846                    GuiUtils.enableTree(allOff, !useAllBtn.isSelected());
1847                }
1848            });
1849    
1850            PreferenceManager choosersManager = new PreferenceManager() {
1851                public void applyPreference(XmlObjectStore theStore, Object data) {
1852    
1853                    Hashtable<String, Boolean> newToShow = new Hashtable<String, Boolean>();
1854    
1855                    Hashtable table = (Hashtable)data;
1856                    for (Enumeration keys = table.keys(); keys.hasMoreElements(); ) {
1857                        String chooserId = (String) keys.nextElement();
1858                        JCheckBox chooserCB = (JCheckBox) table.get(chooserId);
1859                        newToShow.put(chooserId, Boolean.valueOf(chooserCB.isSelected()));
1860                    }
1861    
1862                    choosersToShow = newToShow;
1863                    theStore.put(PROP_CHOOSERS_ALL, Boolean.valueOf(useAllBtn.isSelected()));
1864                    theStore.put(PROP_CHOOSERS, choosersToShow);
1865                }
1866            };
1867            this.add(Constants.PREF_LIST_DATA_CHOOSERS,
1868                     "What data sources should be shown in the user interface?",
1869                     choosersManager, choosersPanel, choosersData);
1870        }
1871    
1872        /**
1873         * <p>Return a list that contains a bunch of arrays of two strings.</p>
1874         * 
1875         * <p>The first item in one of the arrays is the chooser id, and the second
1876         * item is the "name" of the chooser. The name is formed by working through
1877         * choosers.xml and concatenating each panel's category and title.</p>
1878         * 
1879         * @return A list of chooser ids and names.
1880         */
1881        private final List<String[]> getChooserData() {
1882            List<String[]> choosers = new ArrayList<String[]>();
1883            String tempString;
1884            
1885            try {
1886                // get the root element so we can iterate through
1887                final String xml = 
1888                    IOUtil.readContents(MCV_CHOOSERS, McIdasPreferenceManager.class);
1889    
1890                final Element root = XmlUtil.getRoot(xml);
1891                if (root == null)
1892                    return null;
1893                
1894                // grab all the children, which should be panels.
1895                final NodeList nodeList = XmlUtil.getElements(root);
1896                for (int i = 0; i < nodeList.getLength(); i++) {
1897                    
1898                    final Element item = (Element)nodeList.item(i);
1899                                    
1900                    if (item.getTagName().equals(XmlUi.TAG_PANEL) || item.getTagName().equals("chooser")) {
1901    
1902                        // form the name of the chooser.
1903                        final String title = 
1904                            XmlUtil.getAttribute(item, XmlUi.ATTR_TITLE, "");
1905                        
1906                        final String cat = 
1907                            XmlUtil.getAttribute(item, XmlUi.ATTR_CATEGORY, "");
1908    
1909                        if (cat.equals(""))
1910                            tempString = title;
1911                        else
1912                            tempString = cat + ">" + title;
1913                        
1914                        final NodeList children = XmlUtil.getElements(item);
1915                        
1916                        if (item.getTagName().equals("chooser")) {
1917                            final String id = 
1918                                XmlUtil.getAttribute(item, XmlUi.ATTR_ID, "");
1919                            String[] tmp = {id, tempString};
1920                            choosers.add(tmp);
1921                        }
1922                        else {
1923                            for (int j = 0; j < children.getLength(); j++) {
1924                                final Element child = (Element)children.item(j);
1925    
1926                                // form the id of the chooser and add it to the list.
1927                                if (child.getTagName().equals("chooser")) {
1928                                    final String id = 
1929                                        XmlUtil.getAttribute(child, XmlUi.ATTR_ID, "");
1930                                    String[] tmp = {id, tempString};
1931                                    choosers.add(tmp);
1932                                }
1933                            }
1934                        }
1935                    }
1936                }
1937            } catch (Exception e) {
1938                e.printStackTrace();
1939            }
1940            return choosers;
1941        }
1942    
1943        /**
1944         * Parse the full chooser name for a category
1945         * @param chooserName
1946         * @return
1947         */
1948        private String getChooserCategory(String chooserName) {
1949            String chooserCategory = "Other";
1950            int indexSep = chooserName.indexOf(">");
1951            if (indexSep >= 0) {
1952                chooserCategory = chooserName.substring(0, indexSep);
1953            }
1954            return chooserCategory;
1955        }
1956    
1957        /**
1958         * Parse the full chooser name for a short name
1959         * @param chooserName
1960         * @return
1961         */
1962        private String getChooserShortName(String chooserName) {
1963            String chooserShortName = chooserName;
1964            int indexSep = chooserName.indexOf(">");
1965            if (indexSep >= 0 && chooserName.length() > indexSep + 1) {
1966                chooserShortName = chooserName.substring(indexSep + 1, chooserName.length());
1967            }
1968            return chooserShortName;
1969        }
1970    
1971        public class IconCellRenderer extends DefaultListCellRenderer {
1972    
1973            /**
1974             * Extends the default list cell renderer to use icons in addition to
1975             * the typical text.
1976             */
1977            public Component getListCellRendererComponent(JList list, Object value, 
1978                    int index, boolean isSelected, boolean cellHasFocus) {
1979    
1980                super.getListCellRendererComponent(list, value, index, isSelected, 
1981                        cellHasFocus);
1982    
1983                if (value instanceof JLabel) {
1984                    setText(((JLabel)value).getText());
1985                    setIcon(((JLabel)value).getIcon());
1986                }
1987    
1988                return this;
1989            }
1990    
1991            /** 
1992             * I wear some pretty fancy pants, so you'd better believe that I'm
1993             * going to enable fancy-pants text antialiasing.
1994             * 
1995             * @param g The graphics object that we'll use as a base.
1996             */
1997            protected void paintComponent(Graphics g) {
1998                Graphics2D g2d = (Graphics2D)g;
1999                g2d.setRenderingHints(getRenderingHints());
2000                super.paintComponent(g2d);
2001            }
2002        }
2003    }
2004