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