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