001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2017
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028
029package edu.wisc.ssec.mcidasv.chooser.adde;
030
031import static edu.wisc.ssec.mcidasv.servermanager.EntryTransforms.strToEntryType;
032import static edu.wisc.ssec.mcidasv.servermanager.AddeEntry.DEFAULT_ACCOUNT;
033import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList;
034import static edu.wisc.ssec.mcidasv.McIDASV.isLoopback;
035
036import static javax.swing.GroupLayout.DEFAULT_SIZE;
037import static javax.swing.GroupLayout.Alignment.BASELINE;
038import static javax.swing.GroupLayout.Alignment.LEADING;
039import static javax.swing.GroupLayout.Alignment.TRAILING;
040import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
041import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED;
042
043import java.awt.Component;
044import java.awt.Dimension;
045import java.awt.event.ActionEvent;
046import java.awt.event.ActionListener;
047import java.awt.event.ItemEvent;
048import java.awt.event.ItemListener;
049import java.awt.event.KeyEvent;
050import java.awt.event.KeyListener;
051import java.awt.event.MouseAdapter;
052import java.awt.event.MouseEvent;
053import java.io.EOFException;
054import java.io.InputStream;
055import java.net.ConnectException;
056import java.net.URL;
057import java.net.URLConnection;
058import java.util.ArrayList;
059import java.util.Arrays;
060import java.util.Collections;
061import java.util.Comparator;
062import java.util.Enumeration;
063import java.util.HashMap;
064import java.util.Hashtable;
065import java.util.LinkedHashMap;
066import java.util.List;
067import java.util.Map;
068import java.util.Objects;
069import java.util.Vector;
070import java.util.regex.Pattern;
071
072import javax.swing.GroupLayout;
073import javax.swing.JButton;
074import javax.swing.JCheckBox;
075import javax.swing.JComboBox;
076import javax.swing.JComponent;
077import javax.swing.JLabel;
078import javax.swing.JMenu;
079import javax.swing.JMenuItem;
080import javax.swing.JPanel;
081import javax.swing.JPopupMenu;
082import javax.swing.JScrollPane;
083import javax.swing.JTabbedPane;
084import javax.swing.JTextField;
085import javax.swing.SwingUtilities;
086import javax.swing.event.DocumentEvent;
087import javax.swing.event.DocumentListener;
088import javax.swing.text.BadLocationException;
089
090import edu.wisc.ssec.mcidasv.ui.MenuScroller;
091import edu.wisc.ssec.mcidasv.util.McVTextField;
092import org.bushe.swing.event.annotation.AnnotationProcessor;
093import org.bushe.swing.event.annotation.EventSubscriber;
094import org.slf4j.Logger;
095import org.slf4j.LoggerFactory;
096import org.w3c.dom.Element;
097
098import edu.wisc.ssec.mcidas.adde.AddeURLException;
099import edu.wisc.ssec.mcidas.adde.DataSetInfo;
100
101import ucar.unidata.util.IOUtil;
102import visad.DateTime;
103
104import ucar.unidata.idv.chooser.IdvChooser;
105import ucar.unidata.idv.chooser.IdvChooserManager;
106import ucar.unidata.idv.chooser.adde.AddeServer;
107import ucar.unidata.idv.chooser.adde.AddeServer.Group;
108import ucar.unidata.util.DatedThing;
109import ucar.unidata.util.GuiUtils;
110import ucar.unidata.util.LogUtil;
111import ucar.unidata.util.Misc;
112import ucar.unidata.util.PreferenceList;
113import ucar.unidata.util.StringUtil;
114import ucar.unidata.xml.XmlObjectStore;
115
116import edu.wisc.ssec.mcidasv.Constants;
117import edu.wisc.ssec.mcidasv.McIDASV;
118import edu.wisc.ssec.mcidasv.ParameterSet;
119import edu.wisc.ssec.mcidasv.PersistenceManager;
120import edu.wisc.ssec.mcidasv.servermanager.AddeAccount;
121import edu.wisc.ssec.mcidasv.servermanager.AddeEntry;
122import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EditorAction;
123import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntrySource;
124import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryType;
125import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryValidity;
126import edu.wisc.ssec.mcidasv.servermanager.EntryStore;
127import edu.wisc.ssec.mcidasv.servermanager.EntryTransforms;
128import edu.wisc.ssec.mcidasv.servermanager.LocalEntryEditor;
129import edu.wisc.ssec.mcidasv.servermanager.RemoteAddeEntry;
130import edu.wisc.ssec.mcidasv.servermanager.RemoteEntryEditor;
131import edu.wisc.ssec.mcidasv.servermanager.TabbedAddeManager;
132import edu.wisc.ssec.mcidasv.ui.ParameterTree;
133import edu.wisc.ssec.mcidasv.ui.UIManager;
134import edu.wisc.ssec.mcidasv.util.CollectionHelpers;
135import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
136import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Position;
137import edu.wisc.ssec.mcidasv.util.McVGuiUtils.TextColor;
138import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width;
139
140/**
141 *
142 * @version $Revision$
143 */
144public class AddeChooser extends ucar.unidata.idv.chooser.adde.AddeChooser implements Constants {
145    
146    private static final Logger logger = LoggerFactory.getLogger(AddeChooser.class);
147    
148    /** Label to use with the relative times {@link JTextField}. */
149    public static final String RELATIVE_TIMES_LABEL = "Number of times: ";
150    
151    /** Tooltip for the relative times {@link JTextField}. */
152    public static final String RELATIVE_TIMES_TOOLTIP =
153        "<html>Load the N most recent images.<br/><br/>" +
154        "Values must be integers greater than zero.</html>";
155        
156    private JComboBox serverSelector;
157    
158    /** List of descriptors */
159    private PreferenceList descList;
160    
161    /** Descriptor/name hashtable */
162    protected Hashtable descriptorTable;
163    
164    /** List of available descriptors. */
165    protected List<String> descriptorList;
166    
167    /** List of comments associated with list of descriptors. */
168    protected List<String> commentList;
169    
170    /** Property for the descriptor table */
171    public static final String DESCRIPTOR_TABLE = "DESCRIPTOR_TABLE";
172
173    /** Connect button--we need to be able to disable this */
174    JButton connectButton = McVGuiUtils.makeImageTextButton(ICON_CONNECT_SMALL, "Connect");
175
176    /** Parameter button--we need to be able to disable this */
177    JButton parameterButton =
178        McVGuiUtils.makeImageButton("/edu/wisc/ssec/mcidasv/resources/icons/toolbar/document-open22.png",
179            this, "doParameters", null, "Load parameter set");
180
181    /** Manage button */
182    JButton manageButton =
183        McVGuiUtils.makeImageButton("/edu/wisc/ssec/mcidasv/resources/icons/toolbar/preferences-system22.png",
184            this, "doManager", null, "Manage servers");
185
186    /** Public button--we need to draw a menu from this */
187    JButton publicButton =
188        McVGuiUtils.makeImageButton("/edu/wisc/ssec/mcidasv/resources/icons/toolbar/show-layer-controls22.png",
189            this, "showGroups", null, "List public datasets");
190
191    /** descriptor label */
192    protected JLabel descriptorLabel = new JLabel(getDescriptorLabel()+":");
193
194    /** A widget for the list of dataset descriptors */
195    protected JComboBox descriptorComboBox = new JComboBox();
196
197    /** The descriptor names */
198    protected String[] descriptorNames;
199
200    /** Flag to keep from infinite looping */
201    protected boolean ignoreDescriptorChange = false;
202
203    /**
204     * List of JComponent-s that depend on a descriptor being selected
205     * to be enabled
206     */
207    protected ArrayList compsThatNeedDescriptor = new ArrayList();
208
209    /** Selection label text */
210    protected String LABEL_SELECT = " -- Select -- ";
211
212    /** Separator string */
213    protected static String separator = "----------------";
214
215    /** Name separator string */
216    protected static String nameSeparator = " - ";
217
218    /** Reference back to the server manager */
219    protected EntryStore serverManager;
220
221    public boolean allServersFlag;
222
223    /** Command for opening up the server manager */
224    protected static final String CMD_MANAGER = "cmd.manager";
225
226    private String lastBadServer = "";
227    private String lastBadGroup = "";
228
229    private String lastServerName = "";
230    private String lastServerGroup = "";
231    private String lastServerUser = "";
232    private String lastServerProj = "";
233    private AddeServer lastServer = new AddeServer("");
234
235    private List<AddeServer> addeServers;
236
237    /** Used for parameter set restore */
238    private static final String TAG_FOLDER = "folder";
239    private static final String TAG_DEFAULT = "default";
240    private static final String ATTR_NAME = "name";
241    private static final String ATTR_SERVER = "server";
242    private static final String ATTR_GROUP = "GROUP";
243    private static final String ATTR_DESCRIPTOR = "DESCRIPTOR";
244    private static final String ATTR_POS = "POS";
245    private static final String ATTR_DAY = "DAY";
246    private static final String ATTR_TIME = "TIME";
247    private List restoreTimes = new ArrayList();
248    public Element restoreElement;
249    private boolean shouldAddSource = false;
250    final JCheckBox cb = new JCheckBox("Add source",shouldAddSource);
251
252    /** Maps favorite type to the BundleTree that shows the Manage window for the type */
253    private Hashtable parameterTrees = new Hashtable();
254    
255    /** Number of relative time steps to load */
256    private int relativeTimes = 5;
257    
258    /**
259     * Create an AddeChooser associated with an IdvChooser
260     *
261     * @param mgr The chooser manager
262     * @param root The chooser.xml node
263     */
264    public AddeChooser(IdvChooserManager mgr, Element root) {
265        super(mgr, root);
266        AnnotationProcessor.process(this);
267        descriptorList = new ArrayList<String>();
268        commentList = new ArrayList<String>();
269        
270        simpleMode = !getProperty(IdvChooser.ATTR_SHOWDETAILS, true);
271
272        loadButton = McVGuiUtils.makeImageTextButton(ICON_ACCEPT_SMALL, getLoadCommandName());
273        loadButton.setActionCommand(getLoadCommandName());
274        loadButton.addActionListener(this);
275
276        cancelButton = McVGuiUtils.makeImageButton(ICON_CANCEL, "Cancel");
277        cancelButton.setActionCommand(GuiUtils.CMD_CANCEL);
278        cancelButton.addActionListener(this);
279        cancelButton.setEnabled(false);
280
281        serverSelector = getServerSelector();
282
283        serverSelector.setToolTipText("Right click to manage servers");
284        serverSelector.getEditor().getEditorComponent().addMouseListener(
285            new MouseAdapter() {
286                public void mouseReleased(MouseEvent e) {
287                    if (!SwingUtilities.isRightMouseButton(e)) {
288                        return;
289                    }
290
291                    AddeServer server = getAddeServer();
292                    if (server == null) {
293                        return;
294                    }
295                    List<JMenuItem> items = new ArrayList<JMenuItem>();
296
297                    // Set the right-click behavior
298                    if (isLocalServer()) {
299                        items.add(GuiUtils.makeMenuItem("Manage local ADDE data",
300                            AddeChooser.this,
301                            "doManager", null));
302                    }
303                    else {
304                        items.add(GuiUtils.makeMenuItem("Manage ADDE servers",
305                            AddeChooser.this,
306                            "doManager", null));
307                    }
308                    JPopupMenu popup = GuiUtils.makePopupMenu(items);
309                    popup.show(serverSelector, e.getX(), e.getY());
310                }
311            });
312        serverSelector.setMaximumRowCount(16);
313
314        groupSelector.setToolTipText("Right click to manage servers");
315        groupSelector.getEditor().getEditorComponent().addMouseListener(
316            new MouseAdapter() {
317                public void mouseReleased(MouseEvent e) {
318                    if (!SwingUtilities.isRightMouseButton(e)) {
319                        return;
320                    }
321
322                    AddeServer server = getAddeServer();
323                    if (server == null) {
324                        return;
325                    }
326                    List<JMenuItem> items = new ArrayList<JMenuItem>();
327
328                    // Set the right-click behavior
329                    if (isLocalServer()) {
330                        items.add(GuiUtils.makeMenuItem("Manage local ADDE data",
331                            AddeChooser.this, "doManager", null));
332                    }
333                    else {
334                        items.add(GuiUtils.makeMenuItem("Manage ADDE servers",
335                            AddeChooser.this, "doManager", null));
336                    }
337                    JPopupMenu popup = GuiUtils.makePopupMenu(items);
338                    popup.show(groupSelector, e.getX(), e.getY());
339                }
340            });
341        groupSelector.setMaximumRowCount(16);
342
343        //        serverManager = ((McIDASV)getIdv()).getServerManager();
344        //        serverManager.addManagedChooser(this);
345        addServerComp(descriptorLabel);
346        //        addServerComp(descriptorComboBox);
347
348        descriptorComboBox.addItemListener(new ItemListener() {
349            public void itemStateChanged(ItemEvent e) {
350                if ( !ignoreDescriptorChange
351                    && (e.getStateChange() == e.SELECTED)) {
352                    descriptorChanged();
353                }
354            }
355        });
356
357        // Update the server list and load the saved state
358        updateServerList();
359        loadServerState();
360
361        // Default to no parameter button unless the overriding class wants one
362        hideParameterButton();
363    }
364
365    /**
366     * Force a reload of the available servers and groups.
367     */
368    public void updateServerList() {
369        updateServers();
370        updateGroups();
371    }
372
373    /**
374     * Returns a {@link java.util.Map Map} containing {@code user} and {@code proj}
375     * keys for the given {@code server/group} combination.
376     * 
377     * <p>The values are either the specific ADDE account details for 
378     * {@code server/group} or {@link edu.wisc.ssec.mcidasv.servermanager.AddeEntry#DEFAULT_ACCOUNT DEFAULT_ACCOUNT}
379     * values.
380     * 
381     * @param server Server name. Should not be {@code null}.
382     * @param group Group name on {@code name}. Should not be {@code null}.
383     * 
384     * @return {@code Map} containing the accounting details for {@code server/group}.
385     */
386    protected Map<String, String> getAccounting(final String server, final String group) {
387        Map<String, String> acctInfo = new HashMap<String, String>();
388        EntryStore entryStore = ((McIDASV)getIdv()).getServerManager();
389        String strType = this.getDataType();
390        EntryType type = strToEntryType(strType);
391        AddeAccount acct = entryStore.getAccountingFor(server, group, type);
392        acctInfo.put("user", acct.getUsername());
393        acctInfo.put("proj", acct.getProject());
394        return acctInfo;
395    }
396
397    /**
398     * Returns a {@link java.util.Map Map} containing {@code user} and {@code proj}
399     * keys for the given {@code server/group} combination.
400     * 
401     * <p>The values are either the specific ADDE account details for 
402     * {@code server/group} or {@link edu.wisc.ssec.mcidasv.servermanager.AddeEntry#DEFAULT_ACCOUNT DEFAULT_ACCOUNT}
403     * values.
404     * 
405     * @param server Server name. Should not be {@code null}.
406     * @param group Group name on {@code name}. Should not be {@code null}.
407     * 
408     * @return {@code Map} containing the accounting details for {@code server/group}.
409     */
410    protected Map<String, String> getAccounting(final AddeServer server, final String group) {
411        return getAccounting(server.getName(), group);
412    }
413
414    private List<AddeServer> getManagedServers(final String type) {
415        EntryStore entryStore = ((McIDASV)getIdv()).getServerManager();
416        return arrList(entryStore.getIdvStyleEntries(type));
417    }
418
419    public void updateServers() {
420        Object selected = serverSelector.getSelectedItem();
421
422        String type = getGroupType();
423        List<AddeServer> managedServers = getManagedServers(type);
424        List<AddeServer> localList = arrList();
425        List<AddeServer> remoteList = arrList();
426        addeServers = CollectionHelpers.arrList();
427        for (AddeServer server : managedServers) {
428            if (server.getIsLocal())
429                localList.add(server);
430            else
431                remoteList.add(server);
432        }
433
434//        logger.debug("{}: updateServers: local size={} contents={}", new Object[] { getDataType(), localList.size(), localList });
435//        logger.debug("{}: updateServers: remote size={} contents={}", new Object[] { getDataType(), remoteList.size(), remoteList });
436
437        // server list doesn't need a separator if there's only remote servers
438        if (!localList.isEmpty()) {
439            addeServers.addAll(localList);
440            addeServers.add(new AddeServer(separator));
441        }
442        Comparator<AddeServer> byServer = new ServerComparator();
443        Collections.sort(remoteList, byServer);
444        addeServers.addAll(remoteList);
445
446        // always making this call helps to ensure the chooser stays up to date
447        // with the server manager.
448        GuiUtils.setListData(serverSelector, addeServers);
449        if (!addeServers.isEmpty()) {
450            if (selected == null || !containsServerName(addeServers, selected)) {
451                selected = serverSelector.getItemAt(0);
452//                logger.debug("updateServers: selecting item at idx=0, item={} chooser={}", selected, this.getDataType());
453            }
454            
455            int index = getSelectorIndex(selected, serverSelector);
456            serverSelector.setSelectedIndex(index);
457        }
458    }
459
460    /**
461     * Searches the given {@link java.util.List List} of {@link ucar.unidata.idv.chooser.adde.AddeServer AddeServers}
462     * for {@code server}.
463     * 
464     * @param servers Servers to search. {@code null} is permitted.
465     * @param server Server to search for within {@code servers}. {@code null} is permitted.
466     * 
467     * @return {@code true} if {@code servers} contains {@code server} or {@code false} otherwise.
468     */
469    protected static boolean containsServerName(final List<AddeServer> servers, final Object server) {
470        if (servers == null || server == null) {
471            return false;
472        }
473        String serverName = (server instanceof AddeServer) ? ((AddeServer)server).getName() : server.toString();
474        for (AddeServer tmp : servers) {
475            if (tmp.getName().equals(serverName)) {
476                return true;
477            }
478        }
479        return false;
480    }
481
482    /**
483     * Searches the given {@link java.util.List List} of {@link ucar.unidata.idv.chooser.adde.AddeServer.Group Groups}
484     * for {@code group}.
485     * 
486     * @param groups Groups to search. {@code null} is permitted.
487     * @param group Group to search for within {@code group}. {@code null} is permitted.
488     * 
489     * @return {@code true} if {@code groups} contains {@code group} or {@code false} otherwise.
490     */
491    protected static boolean containsGroupName(final List<Group> groups, final Object group) {
492        if (groups == null || group == null) {
493            return false;
494        }
495        String groupName = (group instanceof Group) ? ((Group)group).getName() : group.toString();
496        for (Group tmp : groups) {
497            if (tmp.getName().equals(groupName)) {
498                return true;
499            }
500        }
501        return false;
502    }
503
504    /**
505     * Sort the groups alphabetically
506     */
507    public void updateGroups() {
508        if (addingServer || groupSelector == null || getAddeServer() == null)
509            return;
510
511        Object selected = groupSelector.getSelectedItem();
512
513        EntryStore servManager = ((McIDASV)getIdv()).getServerManager();
514
515        List<Group> groups = CollectionHelpers.arrList();
516        if (isLocalServer()) {
517            groups.addAll(servManager.getIdvStyleLocalGroups());
518        } else {
519            String sel = null;
520            Object obj = serverSelector.getSelectedItem();
521            if (obj instanceof String) {
522                sel = (String)obj;
523//                logger.debug("updateGroups: string={} chooser={}", sel, this.getDataType());
524            } else if (obj instanceof AddeServer) {
525                sel = ((AddeServer)obj).getName();
526//                logger.debug("updateGroups: server selection={} chooser={}", sel, this.getDataType());
527            } else {
528                sel = obj.toString();
529//                logger.debug("updateGroups: unknown type={}; toString={}", sel.getClass().getName(), sel);
530            }
531
532            EntryType selType = strToEntryType(getGroupType());
533            groups.addAll(servManager.getIdvStyleRemoteGroups(sel, selType));
534        }
535//        logger.trace("updateGroups: selected={} (type={}) chooser={} contents={}", new Object[] { serverSelector.getSelectedItem(), serverSelector.getSelectedItem().getClass().getName(), this.getDataType(), groups});
536        Comparator<Group> byGroup = new GroupComparator();
537        Collections.sort(groups, byGroup);
538        GuiUtils.setListData(groupSelector, groups);
539        if (!groups.isEmpty()) {
540            if (selected == null || !containsGroupName(groups, selected)) {
541                selected = groupSelector.getItemAt(0);
542            }
543            groupSelector.setSelectedItem(selected);
544        }
545    }
546
547    /**
548     * Load any saved server state
549     */
550    //TODO: Make loadServerState protected in IDV, remove from here
551    private void loadServerState() {
552        if (addeServers == null) {
553//            logger.debug("loadServerState: addeServers == null chooser={}", this.getDataType());
554            return;
555        }
556        String id = getId();
557        String[] serverState =
558            (String[]) getIdv().getStore().get(Constants.PREF_SERVERSTATE + '.' + id);
559        if (serverState == null) {
560//            serverState = Constants.DEFAULT_SERVERSTATE;
561//            logger.debug("loadServerState: serverState == null chooser={}",this.getDataType());
562            return;
563        }
564        AddeServer server = AddeServer.findServer(addeServers, serverState[0]);
565        if (server == null) {
566//            logger.debug("loadServerState: server == null chooser={}",this.getDataType());
567            return;
568        }
569//        logger.debug("loadServerState: selecting server={} chooser={}", server, this.getDataType());
570        serverSelector.setSelectedItem(server);
571        setGroups();
572        updateGroups();
573        if (serverState[1] != null) {
574            Group group = new Group(getDataType(), serverState[1], serverState[1]);
575            int index = getSelectorIndex(group, groupSelector);
576            if (index >= 0) {
577//                logger.debug("loadServerState: selecting index={} group={} chooser={}", new Object[] { index, group, this.getDataType() });
578                groupSelector.setSelectedIndex(index);
579            } else {
580//                logger.debug("loadServerState: group == null chooser={}", this.getDataType());
581            }
582        } else {
583//            logger.debug("loadServerState: serverState[1] == null chooser={}", this.getDataType());
584        }
585    }
586
587    /**
588     * Decide if the server you're asking about is actually a separator
589     */
590    protected static boolean isSeparator(AddeServer checkServer) {
591        if (checkServer != null) {
592            if (checkServer.getName().equals(separator)) {
593                return true;
594            }
595        }
596        return false;
597    }
598
599    /**
600     * Decide if the server you're asking about is local
601     */
602    protected boolean isLocalServer() {
603        return isLocalServer(getAddeServer());
604    }
605
606    protected static boolean isLocalServer(AddeServer checkServer) {
607        if (checkServer != null) {
608            return checkServer.getIsLocal();
609        }
610        return false;
611    }
612
613    private void setBadServer(String name, String group) {
614        if (name == null) {
615            name = "";
616        }
617        if (group == null) {
618            group = "";
619        }
620
621        lastBadServer = name;
622        lastBadGroup = group;
623    }
624
625    private boolean isBadServer(String name, String group) {
626        assert lastBadServer != null;
627        assert lastBadGroup != null;
628        return lastBadServer.equals(name) && lastBadGroup.equals(group);
629    }
630
631    private void setLastServer(String name, String group, AddeServer server) {
632//        logger.trace("name='{}' group='{}' server='{}' old: name='{}' group='{}' server='{}'", new Object[] { name, group, server, lastServerName, lastServerGroup, lastServer });
633        if (name == null) {
634            name = "";
635        }
636        if (group == null) {
637            group = "";
638        }
639        if (server == null) {
640            server = new AddeServer(name);
641            Group addeGroup = new Group(getDataType(), group, group);
642            server.addGroup(addeGroup);
643        }
644        lastServerName = name;
645        lastServerGroup = group;
646        lastServer = server;
647    }
648
649    private boolean isLastServer(String name, String group) {
650        assert lastServer != null;
651        assert lastServerName != null;
652        assert lastServerGroup != null;
653        return lastServerName.equals(name) && lastServerGroup.equals(group);
654    }
655
656    @EventSubscriber(eventClass=EntryStore.Event.class)
657    public void onServerManagerDataEvent(EntryStore.Event evt) {
658        EntryStore servManager = ((McIDASV)getIdv()).getServerManager();
659//        logger.debug("onServerManagerDataEvent: evt={} server={}", evt, servManager.getLastAdded());
660        this.updateServerList();
661    }
662
663    @EventSubscriber(eventClass=TabbedAddeManager.Event.class)
664    public void onServerManagerWindowEvent(TabbedAddeManager.Event evt) {
665//        logger.debug("onServerManagerWindowEvent: caught event bus obj");
666    }
667
668    private boolean addingServer = false;
669
670    /**
671     * Search a given {@link JComboBox} for the index of a given object. Mostly
672     * useful for searching {@link #serverSelector} or {@link #groupSelector}.
673     * 
674     * @param needle An object. {@code null} values are permitted.
675     * @param haystack {@code JComboBox} to search. {@code null} values are 
676     * permitted, but return {@code -1}.
677     * 
678     * @return Either the index of {@code needle} within {@code haystack}, or
679     * {@code -1} if {@code needle} could not be found (or {@code haystack} is 
680     * {@code null}).
681     */
682    protected static int getSelectorIndex(final Object needle, 
683        final JComboBox haystack) 
684    {
685        if (haystack == null) {
686            return -1;
687        }
688
689        String name = null;
690        if (needle instanceof AddeServer) {
691            name = ((AddeServer)needle).getName();
692        } else if (needle instanceof Group) {
693            name = ((Group)needle).getName();
694        } else if (needle instanceof AddeEntry) {
695            name = ((AddeEntry)needle).getAddress();
696        } else {
697            name = needle.toString();
698        }
699
700        if (isLoopback(name)) {
701            return 0;
702        }
703
704        for (int i = 0; i < haystack.getItemCount(); i++) {
705            Object item = haystack.getItemAt(i);
706            String tmpName;
707            if (item instanceof AddeServer) {
708                tmpName = ((AddeServer)item).getName();
709            } else {
710                tmpName = item.toString();
711            }
712
713            if (name.equals(tmpName)) {
714                return i;
715            }
716        }
717        return -1;
718    }
719
720    /**
721     * Get the selected AddeServer
722     *
723     * @return the server or null
724     */
725    protected AddeServer getAddeServer() {
726        if (lastServerName != null && lastServerName.equals("unset")) {
727            return null;
728        }
729
730        Object selected = serverSelector.getSelectedItem();
731        if ((selected != null) && (selected instanceof AddeServer)) {
732            AddeServer server = (AddeServer)selected;
733            String group = getGroup(true);
734            Map<String, String> accounting = getAccounting(server, group);
735//            logger.trace("accounting: new: u='{}' p='{}' old: u='{}' p='{}'", new Object[] { accounting.get("user"), accounting.get("proj"), lastServerUser, lastServerProj });
736            lastServerUser = accounting.get("user");
737            lastServerProj = accounting.get("proj");
738            setLastServer(server.getName(), group, server);
739            return (AddeServer)selected;
740        } else if ((selected != null) && (selected instanceof String)) {
741
742            EntryStore servManager = ((McIDASV)getIdv()).getServerManager();
743            String server = (String)selected;
744            String group = getGroup(true);
745
746            if (isBadServer(server, group)) {
747//                logger.trace("getAddeServer: returning null; known bad server; server={} group={}", server, group);
748                return null;
749            }
750
751            if (isLastServer(server, group)) {
752//                logger.trace("getAddeServer: returning last server name; server={} group={}", server, group);
753                return lastServer;
754            }
755
756            EditorAction editorAction = EditorAction.INVALID;
757            if (!isLoopback(server)) {
758                RemoteEntryEditor editor = new RemoteEntryEditor(servManager, server, "");
759                editor.setVisible(true);
760                editorAction = editor.getEditorAction();
761            } else {
762                LocalEntryEditor editor = new LocalEntryEditor(servManager, group);
763                editor.setVisible(true);
764                editorAction = editor.getEditorAction();
765            }
766
767            int servIndex = 0;
768            int groupIndex = 0;
769
770            if (editorAction != EditorAction.CANCELLED && editorAction != EditorAction.INVALID) {
771
772                List<AddeServer> added = arrList(EntryTransforms.convertMcvServers(servManager.getLastAddedByType(strToEntryType(getDataType()))));
773                AddeServer first = null;
774                if (!added.isEmpty()) {
775                    first = added.get(0);
776                    servIndex = getSelectorIndex(first, serverSelector);
777                    setLastServer(server, group, first);
778                } 
779
780                serverSelector.setSelectedIndex(servIndex);
781                groupSelector.setSelectedIndex(groupIndex);
782//                logger.trace("getAddeServer: serverIdx={} groupIdx={}", servIndex, groupIndex);
783
784                return first;
785            } else {
786//                logger.trace("getAddeServer: returning null due to cancel request");
787                setBadServer(server, group);
788                return null;
789            }
790
791            
792            
793        } else if (selected == null) {
794//            logger.trace("getAddeServer: null object in selector; returning null");
795        } else {
796//            logger.debug("getAddeServer: unknown obj type={}; toString={}", selected.getClass().getName(), selected.toString());
797        }
798        return null;
799    }
800
801    /**
802     * A utility to add a component to the list of components that
803     * need the descriptor
804     *
805     * @param comp The component
806     * @return The component
807     */
808    protected JComponent addDescComp(JComponent comp) {
809        compsThatNeedDescriptor.add(comp);
810        return comp;
811    }
812    
813    /**
814     * Set LABEL_SELECT from elsewhere
815     */
816    protected void setSelectString(String string) {
817        LABEL_SELECT = string;
818    }
819    
820    /**
821     * Reset the descriptor stuff
822     */
823    protected void resetDescriptorBox() {
824        ignoreDescriptorChange = true;
825        descriptorComboBox.setSelectedItem(LABEL_SELECT);
826        ignoreDescriptorChange = false;
827    }
828    
829    /**
830     * Handle when the user presses the connect button
831     *
832     * @throws Exception On badness
833     */
834    public void handleConnect() throws Exception {
835        AddeServer server = getAddeServer();
836        if (server == null) {
837            return;
838        }
839        setState(STATE_CONNECTING);
840        connectToServer();
841        handleUpdate();
842    }
843
844    /**
845     * Show the user a descriptive error message in a dialog (if in foreground
846     * mode) depending on the state of {@code e}.
847     *
848     * @param e Exception to handle. Cannot be {@code null}.
849     *
850     * @throws NullPointerException if {@code e} is {@code null}.
851     *
852     * @see #handleConnectionError(String, Exception)
853     */
854    @Override protected void handleConnectionError(Exception e) {
855        handleConnectionError("", e);
856    }
857
858    /**
859     * Show the user a descriptive error message (with optional details) in a
860     * dialog.
861     *
862     * @param details Details about the context of {@code e}. {@code null} will
863     * be treated as an empty {@code String}.
864     * @param e Exception to handle. Cannot be {@code null}.
865     *
866     * @throws NullPointerException if {@code e} is {@code null}.
867     */
868    protected void handleConnectionError(String details, Exception e) {
869        Objects.requireNonNull(e, "Cannot handle null exception");
870        logger.error("attempting to handle connection error", e);
871
872        if ((details != null) && !details.isEmpty()) {
873            details = details+":\n";
874        } else {
875            details = "";
876        }
877
878        boolean isError = true;
879        if (e.getMessage() != null) {
880            String msg = e.getMessage();
881            int msgPos = msg.indexOf("AddeURLException:");
882            if ((msgPos >= 0) && (msg.length() > 18)) {
883                msg = msg.substring(msgPos + 18);
884                GuiUtils.showDialog("ADDE Error", new JLabel(details+msg));
885            } else if (msg.indexOf("Connecting to server:localhost:") >= 0) {
886                GuiUtils.showDialog("ADDE Error", new JLabel("Local server is not responding."));
887            } else if (msg.toLowerCase().contains("unknownhostexception")) {
888                LogUtil.userErrorMessage("Could not access server: " + getServer());
889            } else if ((e instanceof AddeURLException) || msg.toLowerCase().contains("server unable to resolve this dataset")) {
890                handleUnknownDataSetError();
891            } else if ((msg.toLowerCase().contains("no images satisfy"))
892                || (msg.toLowerCase().contains("error generating list of files")))
893            {
894                LogUtil.userErrorMessage("No data available for the selection");
895                isError = false;
896            } else {
897                LogUtil.logException("Encountered a problem (server: '" + getServer()+"'):\n"+details, e);
898            }
899        } else {
900            LogUtil.userErrorMessage("Encountered a problem (server: '" + getServer() + "'):\n" + details +e);
901        }
902
903        if (isError && (getState() == STATE_CONNECTED)) {
904            setHaveData(false);
905            resetDescriptorBox();
906            updateStatus();
907            setState(STATE_UNCONNECTED);
908        }
909    }
910
911    /**
912     * Handle unknown data set error
913     */
914    @Override protected void handleUnknownDataSetError() {
915        String server = getServer();
916        String group = getGroup();
917        Map<String, String> acct = getAccounting(server, group);
918        String user = acct.get("user");
919        String proj = acct.get("proj");
920
921        StringBuilder msg = new StringBuilder("Could not connect to dataset \"");
922        msg.append(getGroup()).append("\" on server \"").append(getServer()).append("\".");
923        if (DEFAULT_ACCOUNT.getUsername().equals(user) && DEFAULT_ACCOUNT.getProject().equals(proj)) {
924            msg.append("\n\nDataset may require ADDE accounting information.");
925        } else {
926            msg.append("\n\nAccounting information:\nusername: \"")
927            .append(user).append("\"\nproject: \"").append(proj).append('"');
928        }
929        LogUtil.userErrorMessage(msg.toString());
930        setState(STATE_UNCONNECTED);
931    }
932
933    /**
934     * Handle the event
935     *
936     * @param ae The event
937     */
938    public void actionPerformed(ActionEvent ae) {
939        String cmd = ae.getActionCommand();
940        if (cmd.equals(CMD_MANAGER)) {
941            doManager();
942        }
943        else {
944            super.actionPerformed(ae);
945        }
946    }
947
948    /**
949     * Go directly to the Server Manager
950     */
951    public void doManager() {
952//      if (isLocalServer()) {
953//          ((McIDASV)getIdv()).showAddeManager();
954//          return;
955//      }
956        getIdv().getPreferenceManager().showTab(Constants.PREF_LIST_ADDE_SERVERS);
957    }
958    
959    /**
960     * Show the parameter restore tree
961     */
962    public void doParameters() {
963        JPopupMenu popup = new JPopupMenu();
964        JMenuItem mi = new JMenuItem("Manage...");
965        mi.addActionListener(new ActionListener() {
966            public void actionPerformed(ActionEvent ae) {
967                System.out.println(ae);
968                showParameterSetDialog(getParameterSetType());
969            }
970        });
971        popup.add(mi);
972        
973        // Add the checkbox to automatically create a data source
974        cb.addActionListener(new ActionListener() {
975            public void actionPerformed(ActionEvent ae) {
976                shouldAddSource = cb.isSelected();
977            }
978        });
979        popup.addSeparator();
980        popup.add(cb);
981
982        final PersistenceManager pm = (PersistenceManager)getIdv().getPersistenceManager();
983        List<ParameterSet> parameterSets = pm.getAllParameterSets(getParameterSetType());
984
985        for (int i=0; i<parameterSets.size(); i++) {
986            if (i==0) popup.addSeparator();
987            final ParameterSet ps = parameterSets.get(i);
988            
989            // Parameter set at root
990            if (ps.getCategories().size() == 0) {
991                mi = new JMenuItem(ps.getName());
992                mi.addActionListener(new ActionListener() {
993                    public void actionPerformed(ActionEvent ae) {
994                        restoreParameterSet(ps.getElement());
995                    }
996                });
997                popup.add(mi);
998            }
999            
1000            // Recurse into folders
1001            else {
1002                // Find or make the menu for the given parameter set
1003                JMenu m = getPopupSubMenuForParameterSet(popup, ps);
1004                // Create parameter set entry
1005                mi = new JMenuItem(ps.getName());
1006                mi.addActionListener(new ActionListener() {
1007                    public void actionPerformed(ActionEvent ae) {
1008                        restoreParameterSet(ps.getElement());
1009                    }
1010                });
1011                m.add(mi);
1012            }
1013            
1014        }
1015
1016        popup.show(parameterButton, 0, (int) parameterButton.getBounds().getHeight());
1017    }
1018    
1019    private JMenu getPopupSubMenuForParameterSet(JPopupMenu popup, final ParameterSet ps) {
1020        List<String> menuNames = ps.getCategories();
1021        if (menuNames.size() < 1) return null;
1022
1023        // Build the complete menu
1024        String menuName = menuNames.get(0);
1025        menuNames.remove(0);
1026        JMenu theMenu = new JMenu();
1027        
1028        // Look for the menu in popup
1029        boolean found = false;
1030        for (int i=0; i<popup.getComponentCount(); i++) {
1031            Component thisComponent = popup.getComponent(i);
1032            if (thisComponent instanceof JMenu && ((JMenu)thisComponent).getText().equals(menuName)) {
1033                theMenu = mergeMenuNames((JMenu)thisComponent, menuNames);
1034                found = true;
1035            }
1036        }
1037        
1038        // Make a new menu, add the root, return the leaf
1039        if (!found) {
1040            JMenu theRoot = new JMenu(menuName);
1041            theMenu = makeMenuRecursive(theRoot, menuNames);
1042            popup.add(theRoot);
1043        }
1044        
1045        return theMenu;
1046    }
1047    
1048    /**
1049     * Make a new recursive menu
1050     * 
1051     * @param rootMenu The root menu to add items to
1052     * @param menuNames List of string names for submenus
1053     * @return A new JMenu representing the leaf
1054     */
1055    private JMenu makeMenuRecursive(JMenu rootMenu, List<String> menuNames) {
1056        if (menuNames.size() < 1) return rootMenu;
1057        JMenu newMenu = new JMenu(menuNames.get(0));
1058        rootMenu.add(newMenu);
1059        menuNames.remove(0);
1060        return makeMenuRecursive(newMenu, menuNames);
1061    }
1062    
1063    /**
1064     * Recurse into a menu, returning either a pointer to the designated names path
1065     *  or a pointer to the leaf menu added by merging new names
1066     * 
1067     * @param thisMenu The root menu to merge
1068     * @param menuNames List of string names to look for
1069     * @return A new JMenu representing the leaf matched by menuNames
1070     */
1071    private JMenu mergeMenuNames(JMenu thisMenu, List<String> menuNames) {
1072        if (menuNames.size() < 1) return thisMenu;
1073        boolean found = false;
1074        String menuName = menuNames.get(0);
1075        for (int i=0; i<thisMenu.getItemCount(); i++) {
1076            JMenuItem mi = thisMenu.getItem(i);
1077            if (!(mi instanceof JMenu)) continue;
1078            if (mi.getText().equals(menuName)) {
1079                menuNames.remove(0);
1080                thisMenu = mergeMenuNames((JMenu)mi, menuNames);
1081                found = true;
1082            }
1083        }
1084        if (!found) {
1085            thisMenu = makeMenuRecursive(thisMenu, menuNames);
1086        }
1087        return thisMenu;
1088    }
1089    
1090    /**
1091     * Return the parameter type associated with this chooser.  Override!
1092     */
1093    protected String getParameterSetType() {
1094        return "adde";
1095    }
1096    
1097    /**
1098     * Show the parameter set manager.
1099     */
1100    private void showParameterSetDialog(final String parameterSetType) {
1101        ParameterTree tree = (ParameterTree) parameterTrees.get(parameterSetType);
1102        if (tree == null) {
1103            tree = new ParameterTree((UIManager)getIdv().getIdvUIManager() , parameterSetType);
1104            parameterTrees.put(parameterSetType, tree);
1105        }
1106        else {
1107            //DAVEP
1108            System.out.println("Should refresh the parameter tree here");
1109        }
1110        tree.setVisible(true);
1111    }
1112
1113    /**
1114     * Clear the selected parameter set.
1115     */
1116    protected void clearParameterSet() {
1117        restoreElement = null;
1118        restoreTimes = new ArrayList(); 
1119        shouldAddSource = false;
1120    }
1121
1122    /**
1123     * Restore the selected parameter set using element attributes.
1124     * 
1125     * @param restoreElement {@code Element} with the desired attributes.
1126     * {@code null} values are permitted.
1127     *
1128     * @return {@code true} if the parameter set was restored, {@code false}
1129     * otherwise.
1130     */
1131    protected boolean restoreParameterSet(Element restoreElement) {
1132        if (restoreElement == null) return false;
1133        if (!restoreElement.getTagName().equals("default")) return false;
1134
1135        this.restoreElement = restoreElement;
1136
1137        boolean oldISCE = ignoreStateChangedEvents;
1138        ignoreStateChangedEvents = true;
1139
1140        // Restore server
1141        String server = restoreElement.getAttribute(ATTR_SERVER);
1142        if (server != null) serverSelector.setSelectedItem(new AddeServer(server));
1143
1144        // Restore group
1145        String group = restoreElement.getAttribute(ATTR_GROUP);
1146        if (group != null) groupSelector.setSelectedItem(group);
1147
1148        // Act as though the user hit "connect"
1149        readFromServer();
1150
1151        // Restore descriptor
1152        String descriptor = restoreElement.getAttribute(ATTR_DESCRIPTOR);
1153        if (descriptor != null) {
1154            Enumeration enumeration = descriptorTable.keys();
1155            for (int i = 0; enumeration.hasMoreElements(); i++) {
1156                String key = enumeration.nextElement().toString();
1157                Object val = descriptorTable.get(key);
1158                if (descriptor.equals(val)) {
1159                    descriptorComboBox.setSelectedItem(val + nameSeparator + key);
1160                    descriptorChanged();
1161                    break;
1162                }
1163            } 
1164        }
1165
1166        // Restore date/time
1167        if (restoreElement.hasAttribute(ATTR_POS)) {
1168            setDoAbsoluteTimes(false);
1169            Integer pos = new Integer(restoreElement.getAttribute(ATTR_POS));
1170            if (pos.intValue() >= 0) {
1171                getRelativeTimesList().setSelectedIndex(pos);
1172            }
1173            restoreTimes = new ArrayList(); 
1174        }
1175        else if ((restoreElement.hasAttribute(ATTR_DAY)) && (restoreElement.hasAttribute(ATTR_TIME))) {
1176            setDoAbsoluteTimes(true);
1177            String dateStr = restoreElement.getAttribute(ATTR_DAY);
1178            String timeStr = restoreElement.getAttribute(ATTR_TIME);
1179            List dateS = StringUtil.split(dateStr, ",");
1180            List timeS = StringUtil.split(timeStr, ",");
1181            int numImages = timeS.size();
1182            restoreTimes = new ArrayList(); 
1183            try {
1184                DateTime dt = new DateTime();
1185                dt.resetFormat();
1186                String dtformat = dt.getFormatPattern();
1187                for (int ix=0; ix<numImages; ix++) {
1188                    DateTime restoreTime = dt.createDateTime((String)dateS.get(ix) + " " + (String)timeS.get(ix));
1189                    restoreTimes.add(restoreTime);
1190                }
1191            } catch (Exception e) {
1192                System.out.println("Exception e=" + e);
1193                return false;
1194            }
1195        }
1196
1197        System.out.println("Returning from AddeChooser.restoreParameterSet()");
1198
1199        ignoreStateChangedEvents = oldISCE;
1200        return true;
1201    }
1202
1203    /**
1204     * Set the absolute times list. The times list can contain any of the object types
1205     * that makeDatedObjects knows how to handle, i.e., Date, visad.DateTime, DatedThing, AddeImageDescriptor, etc.
1206     *
1207     * @param times List of thinggs to put into absolute times list
1208     */
1209    protected void setAbsoluteTimes(List times) {
1210        super.setAbsoluteTimes(times);
1211        restoreAbsoluteTimes();
1212    }
1213
1214    protected void restoreAbsoluteTimes() {
1215        List allTimes = makeDatedObjects(super.getAbsoluteTimes());
1216        if (restoreTimes.size() > 0 && allTimes.size() > 0) {
1217            int[] indices  = new int[restoreTimes.size()];
1218            try {
1219                DateTime rtdt;
1220                DateTime atdt;
1221                DatedThing at;
1222                for (int i = 0; i < restoreTimes.size(); i++) {
1223                    rtdt = (DateTime)restoreTimes.get(i);
1224                    for (int j = 0; j < allTimes.size(); j++) {
1225                        at = (DatedThing)allTimes.get(j);
1226                        atdt = new DateTime(at.getDate());
1227                        if (atdt.equals(rtdt)) {
1228                            indices[i] = j;
1229                        }
1230                    }
1231                }
1232            } catch (Exception e) {
1233                System.out.println("Exception e=" + e);
1234            }
1235            setSelectedAbsoluteTimes(indices);
1236        }
1237    }
1238
1239    /**
1240     * show/hide the parameter restore button
1241     */
1242    public void showParameterButton() {
1243        parameterButton.setVisible(true);
1244    }
1245
1246    public void hideParameterButton() {
1247        parameterButton.setVisible(false);
1248    }
1249
1250    /**
1251     * Override and simulate clicking Add Source if requested
1252     */
1253    public void setHaveData(boolean have) {
1254        super.setHaveData(have);
1255        if (have && shouldAddSource) {
1256            // Even though setHaveData should mean we can go, we can't... wait a few jiffies
1257            Misc.runInABit(100, AddeChooser.this, "doClickLoad", null);
1258        }
1259    }
1260
1261    public void doClickLoad() {
1262        loadButton.doClick();
1263    }
1264
1265    public void showServers() {
1266        allServersFlag = !allServersFlag;
1267        XmlObjectStore store = getIdv().getStore();
1268        store.put(Constants.PREF_SYSTEMSERVERSIMG, allServersFlag);
1269        store.save();
1270        updateServers();
1271        updateGroups();
1272    }
1273
1274    protected String getStateString() {
1275        int state = getState();
1276        switch (state) {
1277            case STATE_CONNECTED: return "Connected to server";
1278            case STATE_UNCONNECTED: return "Not connected to server";
1279            case STATE_CONNECTING: return "Connecting to server";
1280            default: return "Unknown state: " + state;
1281        }
1282    }
1283
1284    /**
1285     * Disable/enable any components that depend on the server.
1286     * Try to update the status label with what we know here.
1287     */
1288    protected void updateStatus() {
1289        super.updateStatus();
1290        if (getState() == STATE_CONNECTED) {
1291            lastServer = new AddeServer("");
1292            lastServerGroup = "";
1293            lastServerName = "";
1294            lastServerProj = "";
1295            lastServerUser = "";
1296
1297            if (!haveDescriptorSelected()) {
1298                if (!usingStations() || haveStationSelected()) {
1299                    //                String name = getDataName().toLowerCase();
1300                    String name = getDescriptorLabel().toLowerCase();
1301                    if (StringUtil.startsWithVowel(name)) {
1302                        setStatus("Please select an " + name);
1303                    } else {
1304                        setStatus("Please select a " + name);
1305                    }
1306                }
1307            }
1308        }
1309
1310        GuiUtils.enableTree(connectButton, getState() != STATE_CONNECTING);
1311    }
1312    
1313    /**
1314     * Get the data type ID
1315     *
1316     * @return  the data type
1317     */
1318    public String getDataType() {
1319        return "ANY";
1320    }
1321
1322    /**
1323     * Check if the server is ok
1324     *
1325     * @return status code
1326     */
1327    protected int checkIfServerIsOk() {
1328        EntryStore servManager = ((McIDASV)getIdv()).getServerManager();
1329        if (isLocalServer() && !servManager.checkLocalServer()) {
1330                LogUtil.userErrorMessage("Local servers are stopped.\n\nLocal servers can be restarted from the 'Tools' menu:\n  Tools > Manage ADDE Datasets >\nLocal Servers > Start Local Servers");
1331                logger.info("Local servers are stopped");
1332            return STATUS_ERROR;
1333        }
1334        try {
1335            StringBuffer buff = getUrl(REQ_TEXT);
1336            appendKeyValue(buff, PROP_FILE, FILE_PUBLICSRV);
1337//            URL           url  = new URL(buff.toString());
1338            URLConnection urlc = IOUtil.getUrlConnection(buff.toString());
1339            InputStream   is   = urlc.getInputStream();
1340            is.close();
1341            return STATUS_OK;
1342        } catch (AddeURLException ae) {
1343            String aes = ae.toString();
1344            if (aes.indexOf("Invalid project number") >= 0 ||
1345                aes.indexOf("Invalid user id") >= 0 ||
1346                aes.indexOf("Accounting data") >= 0) {
1347                LogUtil.userErrorMessage("Invalid login.\n\nPlease verify your username and password.");
1348                logger.info("Invalid login");
1349                setState(STATE_UNCONNECTED);
1350                setHaveData(false);
1351                resetDescriptorBox();
1352                return STATUS_NEEDSLOGIN;
1353            }
1354            if (aes.indexOf("cannot run server 'txtgserv'") >= 0) {
1355                return STATUS_OK;
1356            }
1357            LogUtil.userErrorMessage("Error connecting to server " + getServer() + ":\n" + ae.getMessage());
1358            logger.info("Error connecting to server");
1359            setState(STATE_UNCONNECTED);
1360            setHaveData(false);
1361            resetDescriptorBox();
1362            return STATUS_ERROR;
1363        } catch (ConnectException exc) {
1364            setState(STATE_UNCONNECTED);
1365            setHaveData(false);
1366            resetDescriptorBox();
1367            String message = "Error connecting to server " + getServer();
1368            String info = "Error connecting to server";
1369            if (isLocalServer()) {
1370                if (!servManager.checkLocalServer()) {
1371                    message += "\n\nLocal servers can be restarted from the 'Tools' menu:\n  Tools > Manage ADDE Datasets >\n Local Servers > Start Local Servers";                     
1372                    info += " (Local servers are stopped)";
1373                }
1374                else {
1375                    message += "\n\nLocal servers appear to be running.\nYour firewall may be preventing access.";
1376                    info += " (Local servers are running)";
1377                }
1378            }
1379            LogUtil.userErrorMessage(message);
1380            logger.info(info);
1381            return STATUS_ERROR;
1382        } catch (EOFException exc) {
1383            setState(STATE_UNCONNECTED);
1384            setHaveData(false);
1385            resetDescriptorBox();
1386            LogUtil.userErrorMessage("Server " + getServer() + " is not responding");
1387            logger.info("Server is not responding");
1388            return STATUS_ERROR;
1389        } catch (Exception exc) {
1390            setState(STATE_UNCONNECTED);
1391            setHaveData(false);
1392            resetDescriptorBox();
1393            logException("Connecting to server: " + getServer(), exc);
1394            logger.info("Error connecting to server");
1395            return STATUS_ERROR;
1396        }
1397    }
1398
1399    public boolean canAccessServer() {
1400        return (checkIfServerIsOk() == STATUS_OK);
1401    }
1402
1403    public Map<String, String> getAccountingInfo() {
1404        AddeServer server = getAddeServer();
1405        Map<String, String> map = new LinkedHashMap<String, String>();
1406        if (server != null) {
1407            List<AddeServer.Group> groups = server.getGroups();
1408            Map<String, String>acctInfo = getAccounting(server, groups.get(0).toString());
1409            map.put("user", acctInfo.get("user"));
1410            map.put("proj", acctInfo.get("proj"));
1411            map.put("server", server.getName());
1412            map.put("group", getGroup());
1413        } else {
1414            map.put("user", RemoteAddeEntry.DEFAULT_ACCOUNT.getUsername());
1415            map.put("proj", RemoteAddeEntry.DEFAULT_ACCOUNT.getUsername());
1416            map.put("server", "");
1417            map.put("group", "");
1418        }
1419        return map;
1420    }
1421
1422    /**
1423     * Saves the currently selected server and group to a chooser-specific 
1424     * preference. Preference ID is {@code PREF_SERVERSTATE+'.'+getId()}.
1425     */
1426    @Override public void saveServerState() {
1427        String[] serverState = { getServer(), getGroup() };
1428        getIdv().getStore().put(PREF_SERVERSTATE+'.'+getId(), serverState);
1429        getIdv().getStore().save();
1430    }
1431
1432    /**
1433     * Connect to the server.
1434     */
1435    protected void connectToServer() {
1436        clearParameterSet();
1437        setDescriptors(null);
1438        setDoAbsoluteTimes(false);
1439        if (!canAccessServer()) {
1440            return;
1441        }
1442        readFromServer();
1443        saveServerState();
1444        ignoreStateChangedEvents = true;
1445        if (descList != null) {
1446            descList.saveState(groupSelector);
1447        }
1448        ignoreStateChangedEvents = false;
1449    }
1450
1451    /**
1452     * Do server connection stuff... override this with type-specific methods
1453     */
1454    protected void readFromServer() {
1455        readDescriptors();
1456        readTimes();
1457    }
1458
1459//    what the request needs to look like:
1460//    adde://localhost:8112/imagedata?&PORT=112&COMPRES S=gzip&USER=idv&PROJ=0
1461//        &VERSION=1&DEBUG=false&TRAC E=0&GROUP=MYDATA&DESCRIPTOR=ENTRY4&BAND=1
1462//        &LATLON= 30.37139 71.74912&PLACE=CENTER&SIZE=1000 1000&UNI T=BRIT
1463//        &MAG=1 1&SPAC=1&NAV=X&AUX=YES&DOC=X&POS=0
1464
1465    /**
1466     *  Generate a list of image descriptors for the descriptor list.
1467     */
1468    protected void readDescriptors() {
1469        try {
1470            StringBuffer buff = getGroupUrl(REQ_DATASETINFO, getGroup());
1471            buff.append("&type=").append(getDataType());
1472            logger.debug("readDesc: buff={}", buff.toString());
1473            DataSetInfo  dsinfo = new DataSetInfo(buff.toString());
1474            
1475            descriptorTable = dsinfo.getDescriptionTable();
1476            descriptorList.clear();
1477            commentList.clear();
1478            descriptorList.addAll(dsinfo.getDescriptorList());
1479            commentList.addAll(dsinfo.getCommentList());
1480            int count = commentList.size();
1481            String[] names = new String[count];
1482            for (int i = 0; i < count; i++) {
1483                if (!isLocalServer()) {
1484                    names[i] = descriptorList.get(i) + nameSeparator + commentList.get(i);
1485                } else {
1486                    names[i] = commentList.get(i);
1487                }
1488            }
1489            logger.debug("readDesc: names={}", names);
1490            Arrays.sort(names);
1491            setDescriptors(names);
1492            setState(STATE_CONNECTED);
1493        } catch (Exception e) {
1494            handleConnectionError(e);
1495        }
1496    }
1497
1498    /**
1499     * Initialize the descriptor list from a list of names
1500     *
1501     * @param names  list of names
1502     */
1503    protected void setDescriptors(String[] names) {
1504        synchronized (WIDGET_MUTEX) {
1505            ignoreDescriptorChange = true;
1506            descriptorComboBox.removeAllItems();
1507            descriptorNames = names;
1508            if ((names == null) || (names.length == 0)) {
1509                return;
1510            }
1511            descriptorComboBox.addItem(LABEL_SELECT);
1512            for (int j = 0; j < names.length; j++) {
1513                logger.trace("adding names[{}]='{}' to combo box", j, names[j]);
1514                descriptorComboBox.addItem(names[j]);
1515            }
1516            ignoreDescriptorChange = false;
1517        }
1518    }
1519
1520    /**
1521     * Respond to a change in the descriptor list.
1522     */
1523    protected void descriptorChanged() {
1524        readTimes();
1525        updateStatus();
1526    }
1527
1528    /**
1529     * Check if a descriptor (image type) has been chosen
1530     *
1531     * @return  true if an image type has been chosen
1532     */
1533    protected boolean haveDescriptorSelected() {
1534        if (!GuiUtils.anySelected(descriptorComboBox)) {
1535            return false;
1536        }
1537        return getDescriptor() != null;
1538    }
1539
1540    /**
1541     * Get the selected descriptor.
1542     *
1543     * @return  the currently selected descriptor.
1544     */
1545    protected String getDescriptor() {
1546        return getDescriptorFromSelection(getSelectedDescriptor());
1547    }
1548    
1549    /**
1550     * Get the descriptor relating to the selection.
1551     *
1552     * @param selection String name from the widget. Can be {@code null}.
1553     *
1554     * @return Either the descriptor associated with {@code selection} or {@code null} if {@link #descriptorTable} or 
1555     * {@code selection} is {@code null}.
1556     */
1557    protected String getDescriptorFromSelection(String selection) {
1558        if (descriptorTable == null) {
1559            return null;
1560        }
1561        if (selection == null) {
1562            return null;
1563        }
1564        
1565        String descriptor = null;
1566        if (!selection.contains(nameSeparator)) {
1567            descriptor = (String)descriptorTable.get(selection);
1568        } else {
1569            String[] toks = selection.split(nameSeparator, 2);
1570            String firstToken = toks[0].trim();
1571            if (descriptorList.contains(firstToken)) {
1572                descriptor = firstToken;
1573            } else {
1574                String key = toks[1].trim();
1575                descriptor = (String)descriptorTable.get(key);
1576            }
1577        }
1578        return descriptor;
1579    }
1580    
1581    /**
1582     * Get the selected descriptor.
1583     *
1584     * @return the selected descriptor
1585     */
1586    public String getSelectedDescriptor() {
1587        String selection = (String)descriptorComboBox.getSelectedItem();
1588        if (selection == null) {
1589            return null;
1590        }
1591        if (selection.equals(LABEL_SELECT)) {
1592            return null;
1593        }
1594        return selection;
1595    }
1596
1597    /**
1598     * Get the descriptor table for this chooser
1599     *
1600     * @return a Hashtable of descriptors and names
1601     */
1602    public Hashtable getDescriptorTable() {
1603        return descriptorTable;
1604    }
1605
1606    /**
1607     * Get any extra key=value pairs that are appended to all requests.
1608     *
1609     * @param buff The buffer to append onto
1610     */
1611    protected void appendMiscKeyValues(StringBuffer buff) {
1612        appendKeyValue(buff, PROP_COMPRESS, DEFAULT_COMPRESS);
1613        appendKeyValue(buff, PROP_PORT, DEFAULT_PORT);
1614        // appendKeyValue(buff, PROP_DEBUG, DEFAULT_DEBUG);
1615        appendKeyValue(buff, PROP_DEBUG, Boolean.toString(EntryStore.isAddeDebugEnabled(false)));
1616        appendKeyValue(buff, PROP_VERSION, DEFAULT_VERSION);
1617        appendKeyValue(buff, PROP_USER, getLastAddedUser());
1618        appendKeyValue(buff, PROP_PROJ, getLastAddedProj());
1619    }
1620
1621    public String getLastAddedUser() {
1622        if ((lastServerUser != null) && !lastServerUser.isEmpty()) {
1623            logger.debug("getLastAddedUser: using non-default {}", lastServerUser);
1624            return lastServerUser;
1625        } else {
1626            logger.debug("getLastAddedUser: using default {}", DEFAULT_USER);
1627            return DEFAULT_USER;
1628        }
1629    }
1630
1631    public String getLastAddedProj() {
1632       if ((lastServerProj != null) && !lastServerProj.isEmpty()) {
1633            logger.debug("getLastAddedProj: using non-default {}", lastServerProj);
1634            return lastServerProj;
1635        } else {
1636            logger.debug("getLastAddedProj: using default {}", DEFAULT_PROJ);
1637            return DEFAULT_PROJ;
1638        }
1639    }
1640
1641    /**
1642     * Show the groups dialog.  This method is not meant to be called
1643     * but is public by reason of implementation (or insanity).
1644     */
1645    public void showGroups() {
1646        JPopupMenu popup = new JPopupMenu();
1647        popup.add(new JMenuItem("Reading public datasets..."));
1648
1649
1650        List groups = readGroups();
1651        popup.removeAll();
1652        if ((groups == null) || (groups.isEmpty())) {
1653            popup.add(new JMenuItem("The list of public datasets is not available"));
1654            popup.show(publicButton, 0, (int) publicButton.getBounds().getHeight());
1655            return;
1656        }
1657
1658        JMenuItem mi;
1659        for (int i = 0; i < groups.size(); i++) {
1660            final String group = groups.get(i).toString();
1661            mi = new JMenuItem(group);
1662            mi.addActionListener(ae -> {
1663                EntryStore servManager = ((McIDASV)getIdv()).getServerManager();
1664
1665                servManager.addEntry(
1666                    new RemoteAddeEntry.Builder(lastServerName, group)
1667                                       .account(lastServerUser, lastServerProj)
1668                                       .type(strToEntryType(getDataType()))
1669                                       .source(EntrySource.USER)
1670                                       .validity(EntryValidity.VERIFIED)
1671                                       .build());
1672
1673                groupSelector.setSelectedItem(group);
1674                doConnect();
1675            });
1676            popup.add(mi);
1677        }
1678        MenuScroller foo = new MenuScroller(popup, 125);
1679        foo.setParent(publicButton);
1680        popup.show(publicButton, 0, (int) publicButton.getBounds().getHeight());
1681    }
1682
1683    /**
1684     * return the String id of the chosen server name
1685     *
1686     * @return  the server name
1687     */
1688    public String getServer() {
1689        AddeServer server = getAddeServer();
1690        if (server != null) {
1691            return server.getName();
1692        } else {
1693            return "";
1694        }
1695    }
1696
1697    protected String getGroup() {
1698        return getGroup(false);
1699    }
1700
1701    /**
1702     * Is the group selector editable?
1703     *
1704     * @return Always returns {@code true}.
1705     */
1706    protected boolean isGroupEditable() {
1707        return true;
1708    }
1709
1710    /**
1711     * Get the image group from the GUI.
1712     *
1713     * @return The image group.
1714     */
1715    protected String getGroup(final boolean fromGetServer) {
1716        Object selected = groupSelector.getSelectedItem();
1717        if (selected == null) {
1718            return null;
1719        }
1720
1721        if (selected instanceof AddeServer.Group) {
1722            AddeServer.Group group = (AddeServer.Group) selected;
1723            return group.getName();
1724        }
1725
1726        if (selected instanceof String) {
1727            return (String)selected;
1728        }
1729
1730        String groupName = selected.toString().trim();
1731        if (!fromGetServer && (!groupName.isEmpty())) {
1732            //Force the get in case they typed a server name
1733            getServer();
1734
1735            AddeServer server = getAddeServer();
1736            if (server != null) {
1737                AddeServer.Group group =
1738                    getIdv().getIdvChooserManager().addAddeServerGroup(
1739                        server, groupName, getGroupType());
1740                if (!group.getActive()) {
1741                    getIdv().getIdvChooserManager().activateAddeServerGroup(
1742                        server, group);
1743                }
1744                //Now put the list of groups back in to the selector
1745                setGroups();
1746                groupSelector.setSelectedItem(group);
1747            }
1748        }
1749        return groupName;
1750    }
1751
1752    /**
1753     * Get the server selector
1754     * @return The server selector
1755     */
1756    public JComboBox getServerSelector() {
1757        if (serverSelector == null)
1758            serverSelector = super.getServerSelector();
1759
1760        ItemListener[] ell = serverSelector.getItemListeners();
1761        for (int i=0; i < ell.length; i++) {
1762            serverSelector.removeItemListener((ItemListener)ell[i]);
1763        }
1764        updateServers();
1765        updateGroups();
1766        serverSelector.addItemListener(new ItemListener() {
1767            public void itemStateChanged(ItemEvent e) {
1768                if (!ignoreStateChangedEvents) {
1769                    Object selected = serverSelector.getSelectedItem();
1770                    if (selected instanceof AddeServer) {
1771                        AddeServer selectedServer = (AddeServer)selected;
1772                        if (selectedServer != null) {
1773                            if (isSeparator(selectedServer)) {
1774                                connectButton.setEnabled(false);
1775                                return;
1776                            }
1777                        }
1778                    }
1779                    setState(STATE_UNCONNECTED);
1780                    connectButton.setEnabled(true);
1781//                    setGroups();
1782                    resetDescriptorBox();
1783                    updateGroups();
1784//                    System.err.println("itemStateChanged");
1785                }
1786//                else {
1787//                  System.out.println("Ignoring state change here...");
1788//                }
1789            }
1790        });
1791
1792        serverSelector.getEditor().getEditorComponent().addKeyListener(new KeyListener() {
1793            public void keyTyped(final KeyEvent e) {}
1794            public void keyPressed(final KeyEvent e) {}
1795            public void keyReleased(final KeyEvent e) {
1796                JTextField field = (JTextField)serverSelector.getEditor().getEditorComponent();
1797                boolean partialMatch = false;
1798                for (int i = 0; i < serverSelector.getItemCount(); i++) {
1799                    String entry = serverSelector.getItemAt(i).toString();
1800                    if (entry.toLowerCase().startsWith(field.getText().toLowerCase()))
1801                        partialMatch = true;
1802                }
1803
1804                if (!partialMatch && groupSelector != null) {
1805                    logger.debug("aha! chooser=", getDataType());
1806                    ((JTextField)groupSelector.getEditor().getEditorComponent()).setText("");
1807                }
1808            }
1809        });
1810
1811        return serverSelector;
1812    }
1813
1814    /**
1815     * Enable or disable the GUI widgets based on what has been
1816     * selected.
1817     */
1818    protected void enableWidgets() {
1819        synchronized (WIDGET_MUTEX) {
1820            boolean newEnabledState = (getState() == STATE_CONNECTED);
1821            for (int i = 0; i < compsThatNeedDescriptor.size(); i++) {
1822                JComponent comp = (JComponent) compsThatNeedDescriptor.get(i);
1823                if (comp.isEnabled() != newEnabledState) {
1824                    GuiUtils.enableTree(comp, newEnabledState);
1825                }
1826            }
1827            if (drivercbx != null) {
1828                boolean descriptorState = newEnabledState && haveDescriptorSelected();
1829//                logger.trace("hrm set drivercbx={}", anyTimeDrivers() && descriptorState);
1830                drivercbx.setEnabled(anyTimeDrivers() && descriptorState);
1831            }
1832        }
1833    }
1834    
1835    /**
1836     * Add a listener to the given combobox that will set the
1837     * state to unconnected
1838     *
1839     * @param box The box to listen to.
1840     */
1841    protected void clearOnChange(final JComboBox box) {
1842        box.addItemListener(new ItemListener() {
1843            public void itemStateChanged(ItemEvent e) {
1844                if ( !ignoreStateChangedEvents) {
1845                    setState(STATE_UNCONNECTED);
1846                    GuiUtils.setListData(descriptorComboBox, new Vector());
1847//                    System.err.println("clearOnChange");
1848                }
1849//                else {
1850//                  System.out.println("Ignoring state change in clearOnChange for: " + box.toString());
1851//                }
1852            }
1853        });
1854    }
1855
1856    /**
1857     * Get the descriptor widget label
1858     *
1859     * @return  label for the descriptor  widget
1860     */
1861    public String getDescriptorLabel() {
1862        return "Descriptor";
1863    }
1864
1865    protected int getNumTimesToSelect() {
1866        return 5;
1867    }
1868
1869    /**
1870     * Get the default selected index for the relative times list.
1871     *
1872     * @return default index
1873     */
1874    protected int getDefaultRelativeTimeIndex() {
1875        return 4;
1876    }
1877
1878    /**
1879     * Check the times lists
1880     */
1881    protected void checkTimesLists() {
1882        super.checkTimesLists();
1883        if (timesCardPanelExtra == null) {
1884            return;
1885        }
1886        if (getDoAbsoluteTimes()) {
1887            timesCardPanelExtra.show("absolute");
1888        } else {
1889            timesCardPanelExtra.show("relative");
1890        }
1891    }
1892
1893    /** Card panel to hold extra relative and absolute time components */
1894    protected GuiUtils.CardLayoutPanel timesCardPanelExtra;
1895
1896    /**
1897     * Set the relative and absolute extra components.
1898     */
1899    protected JPanel makeTimesPanel(JComponent relativeCard, JComponent absoluteCard) {
1900//        JPanel timesPanel = super.makeTimesPanel(false, true);
1901        JPanel timesPanel = super.makeTimesPanel(false, true, getIdv().getUseTimeDriver());
1902
1903        // Make a new timesPanel that has extra components tacked on the bottom, inside the tabs
1904        Component[] comps = timesPanel.getComponents();
1905
1906//        if (drivercbx != null) {
1907//            drivercbx.setEnabled(anyTimeDrivers());
1908//        }
1909
1910        if ((comps.length == 1) && (comps[0] instanceof JTabbedPane)) {
1911            timesCardPanelExtra = new GuiUtils.CardLayoutPanel();
1912            if (relativeCard == null) {
1913                relativeCard = new JPanel();
1914            }
1915            if (absoluteCard == null) {
1916                absoluteCard = new JPanel();
1917            }
1918            timesCardPanelExtra.add(relativeCard, "relative");
1919            timesCardPanelExtra.add(absoluteCard, "absolute");
1920            timesPanel = GuiUtils.centerBottom(comps[0], timesCardPanelExtra);
1921        }
1922
1923        return timesPanel;
1924    }
1925
1926    private JPanel innerPanel = new JPanel();
1927
1928    private JLabel statusLabel = new JLabel("Status");
1929
1930    /**
1931     * Super setStatus() takes a second string to enable "simple" mode
1932     * which highlights the required component.  We don't really care
1933     * about that feature, and we don't want getStatusLabel() to
1934     * change the label background color.
1935     */
1936    @Override
1937    public void setStatus(String statusString, String foo) {
1938        if (statusString == null) {
1939            statusString = "";
1940        }
1941        statusLabel.setText(statusString);
1942    }
1943
1944    protected void setInnerPanel(JPanel newInnerPanel) {
1945        innerPanel = newInnerPanel;
1946    }
1947    
1948    /**
1949     * Create the widget responsible for handling relative time selection.
1950     *
1951     * @return GUI widget.
1952     */
1953    @Override public JComponent getRelativeTimesChooser() {
1954        McVTextField relativeTimesField =
1955            McVGuiUtils.makeTextFieldAllow(String.valueOf(relativeTimes),
1956                                           4,
1957                                           false,
1958                                           '0', '1', '2', '3', '4', '5', 
1959                                           '6', '7','8','9');
1960                                           
1961        // need to keep *both* the ActionListener and DocumentListener around.
1962        // removing the ActionListener results in strange behavior when you've
1963        // accidentally cleared out the text field.
1964        relativeTimesField.setAllow(Pattern.compile("^[1-9][0-9]*$"), true);
1965//        relativeTimesField.setDeny(Pattern.compile("^0$"), true);
1966        relativeTimesField.setColumns(4);
1967        relativeTimesField.addActionListener(e -> {
1968            String text = ((JTextField)e.getSource()).getText();
1969            validateRelativeTimeInput(text);
1970        });
1971        relativeTimesField.getDocument().addDocumentListener(new DocumentListener() {
1972            @Override public void insertUpdate(DocumentEvent e) {
1973                handleRelativeTimeChange(e);
1974            }
1975            
1976            @Override public void removeUpdate(DocumentEvent e) {
1977                handleRelativeTimeChange(e);
1978            }
1979            
1980            @Override public void changedUpdate(DocumentEvent e) {
1981                handleRelativeTimeChange(e);
1982            }
1983        });
1984        relativeTimesField.setToolTipText(RELATIVE_TIMES_TOOLTIP);
1985        
1986        JPanel panel =
1987            GuiUtils.topLeft(GuiUtils.label(RELATIVE_TIMES_LABEL,
1988                relativeTimesField));
1989        JScrollPane scrollPane = new JScrollPane(panel);
1990        scrollPane.setPreferredSize(new Dimension(150, 100));
1991        return scrollPane;
1992    }
1993    
1994    /**
1995     * Validate the contents of the relative times text field.
1996     * 
1997     * <p>This method overwrites {@link #relativeTimes} if {@code text} is an 
1998     * integer greater than zero.</p>
1999     * 
2000     * @param text Contents of the text field.
2001     */
2002    private void validateRelativeTimeInput(String text) {
2003        try {
2004            int value = Integer.valueOf(text);
2005            if (value > 0) {
2006                relativeTimes = value;
2007                setHaveData(true);
2008                setState(STATE_CONNECTED);
2009                updateStatus();
2010            }
2011        } catch (NumberFormatException e) {
2012            setState(STATUS_ERROR);
2013            setHaveData(false);
2014            setStatus("Please provide an integer value greater than zero.");
2015        }
2016    }
2017    
2018    /**
2019     * Handle {@link DocumentListener} events for the {@link JTextField}
2020     * created by {@link #getRelativeTimesChooser()}.
2021     *
2022     * @param event Event to handle. Cannot be {@code null}.
2023     */
2024    private void handleRelativeTimeChange(DocumentEvent event) {
2025        int len = event.getDocument().getLength();
2026        try {
2027            String text = event.getDocument().getText(0, len);
2028            validateRelativeTimeInput(text);
2029        } catch (BadLocationException ex) {
2030            logger.warn("Could not get contents of text field!", ex);
2031        }
2032    }
2033    
2034    /**
2035     * Get the relative time indices
2036     *
2037     * @return an array of indices
2038     */
2039    @Override public int[] getRelativeTimeIndices() {
2040        int[] indices = new int[relativeTimes];
2041        for (int i = 0; i < indices.length; i++) {
2042            indices[i] = i;
2043        }
2044        return indices;
2045    }
2046    
2047    /**
2048     * Make the UI for this selector.
2049     *
2050     * Thank you NetBeans for helping with the layout!
2051     *
2052     * @return The GUI.
2053     */
2054    protected JComponent doMakeContents() {
2055        JPanel outerPanel = new JPanel();
2056
2057        JLabel serverLabelInner = new JLabel("Server:");    
2058        McVGuiUtils.setLabelPosition(serverLabelInner, Position.RIGHT);
2059        JPanel serverLabel = GuiUtils.leftRight(parameterButton, serverLabelInner);
2060        McVGuiUtils.setComponentWidth(serverLabel);
2061
2062        clearOnChange(serverSelector);
2063        McVGuiUtils.setComponentWidth(serverSelector, Width.DOUBLE);
2064
2065        JLabel groupLabel = McVGuiUtils.makeLabelRight("Dataset:");
2066
2067        groupSelector.setEditable(isGroupEditable());
2068        clearOnChange(groupSelector);
2069        McVGuiUtils.setComponentWidth(groupSelector, Width.DOUBLE);
2070
2071        McVGuiUtils.setComponentWidth(connectButton, Width.DOUBLE);
2072        connectButton.setActionCommand(CMD_CONNECT);
2073        connectButton.addActionListener(this);
2074
2075        /* Set the attributes for the descriptor label and combo box, even though
2076         * they are not used here.  Extending classes can add them to the panel if
2077         * necessary.
2078         */
2079        McVGuiUtils.setComponentWidth(descriptorLabel);
2080        McVGuiUtils.setLabelPosition(descriptorLabel, Position.RIGHT);
2081
2082        McVGuiUtils.setComponentWidth(descriptorComboBox, Width.DOUBLEDOUBLE);
2083
2084        if (descriptorComboBox.getMinimumSize().getWidth() < ELEMENT_DOUBLE_WIDTH) {
2085            McVGuiUtils.setComponentWidth(descriptorComboBox, Width.DOUBLE);
2086        }
2087
2088        JLabel statusLabelLabel = McVGuiUtils.makeLabelRight("");
2089
2090        statusLabel.setText("Status");
2091        McVGuiUtils.setLabelPosition(statusLabel, Position.RIGHT);
2092        McVGuiUtils.setComponentColor(statusLabel, TextColor.STATUS);
2093
2094        JButton helpButton = McVGuiUtils.makeImageButton(ICON_HELP, "Show help");
2095        helpButton.setActionCommand(GuiUtils.CMD_HELP);
2096        helpButton.addActionListener(this);
2097
2098        JButton refreshButton = McVGuiUtils.makeImageButton(ICON_REFRESH, "Refresh");
2099        refreshButton.setActionCommand(GuiUtils.CMD_UPDATE);
2100        refreshButton.addActionListener(this);
2101
2102        McVGuiUtils.setComponentWidth(loadButton, Width.DOUBLE);
2103
2104        GroupLayout layout = new GroupLayout(outerPanel);
2105        outerPanel.setLayout(layout);
2106        layout.setHorizontalGroup(
2107            layout.createParallelGroup(LEADING)
2108            .addGroup(TRAILING, layout.createSequentialGroup()
2109                .addGroup(layout.createParallelGroup(TRAILING)
2110                    .addGroup(layout.createSequentialGroup()
2111                        .addContainerGap()
2112                        .addComponent(helpButton)
2113                        .addGap(GAP_RELATED)
2114                        .addComponent(refreshButton)
2115                        .addGap(GAP_RELATED)
2116                        .addComponent(cancelButton)
2117                        .addPreferredGap(RELATED)
2118                        .addComponent(loadButton))
2119                        .addGroup(LEADING, layout.createSequentialGroup()
2120                        .addContainerGap()
2121                        .addGroup(layout.createParallelGroup(LEADING)
2122                            .addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
2123                            .addGroup(layout.createSequentialGroup()
2124                                .addComponent(serverLabel)
2125                                .addGap(GAP_RELATED)
2126                                .addComponent(serverSelector)
2127                                .addGap(GAP_RELATED)
2128                                .addComponent(manageButton)
2129                                .addGap(GAP_RELATED)
2130                                .addComponent(groupLabel)
2131                                .addGap(GAP_RELATED)
2132                                .addComponent(groupSelector)
2133                                .addGap(GAP_RELATED)
2134                                .addComponent(publicButton)
2135                                .addPreferredGap(RELATED, DEFAULT_SIZE, Short.MAX_VALUE)
2136                                .addComponent(connectButton))
2137                            .addGroup(layout.createSequentialGroup()
2138                                .addComponent(statusLabelLabel)
2139                                .addGap(GAP_RELATED)
2140                                .addComponent(statusLabel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)))))
2141                .addContainerGap())
2142        );
2143        layout.setVerticalGroup(
2144            layout.createParallelGroup(LEADING)
2145            .addGroup(layout.createSequentialGroup()
2146                .addContainerGap()
2147                .addGroup(layout.createParallelGroup(BASELINE)
2148                    .addComponent(serverLabel)
2149                    .addComponent(serverSelector)
2150                    .addComponent(manageButton)
2151                    .addComponent(groupLabel)
2152                    .addComponent(groupSelector)
2153                    .addComponent(publicButton)
2154                    .addComponent(connectButton))
2155                .addPreferredGap(UNRELATED)
2156                .addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
2157                .addPreferredGap(UNRELATED)
2158                .addGroup(layout.createParallelGroup(BASELINE)
2159                    .addComponent(statusLabelLabel)
2160                    .addComponent(statusLabel))
2161                .addPreferredGap(UNRELATED)
2162                .addGroup(layout.createParallelGroup(BASELINE)
2163                    .addComponent(loadButton)
2164                    .addComponent(cancelButton)
2165                    .addComponent(refreshButton)
2166                    .addComponent(helpButton))
2167                .addContainerGap())
2168        );
2169    
2170        return outerPanel;
2171
2172    }
2173
2174    public class ServerComparator implements Comparator<AddeServer> {
2175        public int compare(AddeServer server1, AddeServer server2) {
2176            return server1.getName().compareTo(server2.getName());
2177        }
2178    }
2179
2180    public class GroupComparator implements Comparator<Group> {
2181        public int compare(Group group1, Group group2) {
2182            return group1.getName().compareTo(group2.getName());
2183        }
2184    }
2185}
2186