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