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