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