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