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