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