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