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;
030
031import static javax.swing.GroupLayout.DEFAULT_SIZE;
032import static javax.swing.GroupLayout.PREFERRED_SIZE;
033import static javax.swing.GroupLayout.Alignment.BASELINE;
034import static javax.swing.GroupLayout.Alignment.LEADING;
035import static javax.swing.GroupLayout.Alignment.TRAILING;
036import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
037import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED;
038
039import java.awt.Component;
040import java.awt.event.ActionEvent;
041import java.awt.event.ActionListener;
042import java.awt.event.ItemEvent;
043import java.awt.event.ItemListener;
044import java.io.IOException;
045import java.net.URI;
046import java.util.ArrayList;
047import java.util.Date;
048import java.util.Hashtable;
049import java.util.List;
050import java.util.Vector;
051
052import javax.swing.GroupLayout;
053import javax.swing.JButton;
054import javax.swing.JComboBox;
055import javax.swing.JComponent;
056import javax.swing.JLabel;
057import javax.swing.JPanel;
058import javax.swing.JTabbedPane;
059
060import org.jdom2.Document;
061import org.jdom2.JDOMException;
062import org.jdom2.Namespace;
063import org.jdom2.input.SAXBuilder;
064import org.w3c.dom.Element;
065
066import thredds.catalog.XMLEntityResolver;
067import ucar.unidata.data.DataSelection;
068import ucar.unidata.data.radar.TDSRadarDatasetCollection;
069import ucar.nc2.units.DateUnit;
070import ucar.unidata.data.radar.RadarQuery;
071import ucar.unidata.geoloc.StationImpl;
072import ucar.unidata.idv.chooser.IdvChooserManager;
073import ucar.unidata.idv.chooser.TimesChooser;
074import ucar.unidata.metdata.NamedStation;
075import ucar.unidata.metdata.NamedStationImpl;
076import ucar.unidata.util.DateSelection;
077import ucar.unidata.util.DateUtil;
078import ucar.unidata.util.DatedThing;
079import ucar.unidata.util.GuiUtils;
080import ucar.unidata.util.LogUtil;
081import ucar.unidata.util.Misc;
082import ucar.unidata.util.PreferenceList;
083import ucar.unidata.util.Product;
084import ucar.unidata.util.TwoFacedObject;
085
086import visad.CommonUnit;
087import visad.DateTime;
088
089import edu.wisc.ssec.mcidasv.Constants;
090import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
091import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Position;
092import edu.wisc.ssec.mcidasv.util.McVGuiUtils.TextColor;
093import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width;
094
095
096/**
097 * Created by IntelliJ IDEA.
098 * User: yuanho
099 * Date: Jan 16, 2008
100 * Time: 11:17:51 AM
101 * To change this template use File | Settings | File Templates.
102 */
103public class TDSRadarChooser extends TimesChooser implements Constants {
104
105    /** The collection */
106    private TDSRadarDatasetCollection collection;
107
108
109    /** The currently selected station */
110    private NamedStation selectedStation;
111
112    /** The currently selected level3 product */
113    private String selectedProduct;
114
115    /** Those urls we connect to */
116    //"http://motherlode.ucar.edu:8080/thredds/radarServer/catalog.xml";
117    private String serverUrl;
118
119    /** Each dataset collection URL */
120    //"http://motherlode.ucar.edu:8080/thredds/radarServer/level2/idd/dataset.xml";
121    //private String collectionUrl;
122
123    /** Component to hold collections */
124    private JComboBox collectionSelector;
125
126    /** Component to hold product list */
127    private JComboBox productComboBox;
128    
129    /** Level 3 panel that can be hidden */
130    private JPanel productPanel;
131    
132    /** components that need a server for activation */
133    private List compsThatNeedServer = new ArrayList();
134
135    /** components that need a server for activation */
136    private List level3CompsThatNeedServer = new ArrayList();
137
138    /** persistent holder for catalog URLS */
139    private PreferenceList urlListHandler;
140
141    /** catalog URL holder */
142    private JComboBox urlBox;
143
144    /** ok flag */
145    private boolean okToDoUrlListEvents = true;
146
147    /** dataset list */
148    private List datasetList;
149
150    /** Command for connecting */
151    protected static final String CMD_CONNECT = "cmd.connect";
152
153    /** _more_          */
154    private boolean isLevel3;
155
156    /** _more_          */
157    public static final String[] level3_ExName = { "NVW", "DPA" };
158
159
160    /**
161     * Create the RadarChooser.
162     *
163     * @param mgr {@code IdvChooserManager}
164     * @param root XML root that defines this chooser.
165     *
166     */
167    public TDSRadarChooser(IdvChooserManager mgr, Element root) {
168        super(mgr, root);
169        
170        loadButton = McVGuiUtils.makeImageTextButton(ICON_ACCEPT_SMALL, getLoadCommandName());
171        loadButton.setActionCommand(getLoadCommandName());
172        loadButton.addActionListener(this);
173        
174        cancelButton = McVGuiUtils.makeImageButton(ICON_CANCEL, "Cancel");
175        cancelButton.setActionCommand(GuiUtils.CMD_CANCEL);
176        cancelButton.addActionListener(this);
177        cancelButton.setEnabled(false);
178        
179    }
180
181
182
183    /**
184     * Handle the update event. Just pass it through to the imageChooser
185     */
186    public void doUpdate() {
187        if ((serverUrl == null) || (datasetList == null)
188                || (datasetList.size() == 0) || (selectedProduct == null)) {
189            if (urlBox != null) {
190                setServer((String) urlBox.getSelectedItem());
191            }
192            return;
193        }
194        Misc.run(this, "stationOrProductChanged");
195    }
196
197
198
199    /**
200     * Update the status of the gui
201     */
202    protected void updateStatus() {
203        super.updateStatus();
204        if (serverUrl == null) {
205            setHaveData(false);
206            setStatus("Please connect to the server");
207        }
208        else if (selectedStation == null) {
209            setHaveData(false);
210            setStatus("Please select a station", "stations");
211        }
212        else if (isLevel3 && (selectedProduct == null)) {
213            setHaveData(false);
214            setStatus("Please select a level 3 product", "products");
215        }
216        else {
217            boolean haveTimesSelected;
218            if (getDoAbsoluteTimes()) {
219                haveTimesSelected = getSelectedAbsoluteTimes().size() > 0;
220            } else {
221                haveTimesSelected = true;
222            }
223            setHaveData(haveTimesSelected);
224            if (haveTimesSelected) {
225                setStatus("Press \"" + CMD_LOAD + "\" to load the selected radar data", "buttons");
226            } else {
227                setStatus("Please select times", "timepanel");
228            }
229        }
230        GuiUtils.enableTree(loadButton, getHaveData());
231        if (drivercbx != null) {
232            drivercbx.setEnabled(anyTimeDrivers() && getHaveData());
233        }
234    }
235
236
237
238    /**
239     * Handle when there are newly selected stations
240     *
241     * @param stations list of newly selected stations
242     */
243    protected void newSelectedStations(List stations) {
244        super.newSelectedStations(stations);
245        if ((stations == null) || (stations.size() == 0)) {
246            selectedStation = null;
247        } else {
248            NamedStation newStation = (NamedStation) stations.get(0);
249            if (Misc.equals(newStation, selectedStation)) {
250                return;
251            }
252            selectedStation = newStation;
253        }
254        Misc.run(TDSRadarChooser.this, "stationOrProductChanged");
255    }
256
257
258    /** A widget for the list of dataset descriptors */
259
260
261    /** Flag to keep from infinite looping */
262    private boolean ignoreProductChange = false;
263
264    /** Selection label text */
265    protected static final String LABEL_SELECT = " -- Select -- ";
266
267    /**
268     * _more_
269     */
270    protected void productChanged() {
271        stationOrProductChanged();
272        // updateStatus();
273    }
274
275    /**
276     * Reset the descriptor stuff
277     */
278    private void resetProductBox() {
279        ignoreProductChange = true;
280        productComboBox.setSelectedItem(LABEL_SELECT);
281        ignoreProductChange = false;
282    }
283
284    /**
285     * Should we update on first display
286     *
287     * @return true
288     */
289    protected boolean shouldDoUpdateOnFirstDisplay() {
290        return false;
291    }
292
293    /**
294     * Set the server
295     *
296     * @param s the server URL
297     */
298    private void setServer(String s) {
299        datasetList = new ArrayList();
300        serverUrl   = s;
301        try {
302            List collections = getRadarCollections(serverUrl);
303            GuiUtils.setListData(collectionSelector, collections);
304        } catch (Exception e) {
305            GuiUtils.setListData(collectionSelector, new ArrayList());
306        }
307    }
308
309    /**
310     * Set the active collection
311     *
312     * @param s collection URL
313     */
314    private void setCollection(String s) {
315        isLevel3 = false;
316        GuiUtils.enableComponents(level3CompsThatNeedServer, false);
317        productPanel.setVisible(false);
318        GuiUtils.enableComponents(compsThatNeedServer, true);
319        if (drivercbx != null) {
320            drivercbx.setEnabled(anyTimeDrivers() && getHaveData());
321        }
322        setAbsoluteTimes(new ArrayList());
323        selectedProduct = null;
324        selectedStation = null;
325        Misc.run(this, "initializeCollection", s);
326    }
327
328    /**
329     * _more_
330     *
331     * @param s _more_
332     */
333    private void setLevel3Collection(String s) {
334        isLevel3 = true;
335        GuiUtils.enableComponents(level3CompsThatNeedServer, true);
336        productPanel.setVisible(true);
337        GuiUtils.enableComponents(compsThatNeedServer, true);
338        if (drivercbx != null) {
339            drivercbx.setEnabled(anyTimeDrivers() && getHaveData());
340        }
341        setAbsoluteTimes(new ArrayList());
342        selectedProduct = null;
343        selectedStation = null;
344        Misc.run(this, "initializeLevel3Collection", s);
345    }
346
347    /**
348     * Add a component that needs to have a valid server
349     *
350     * @param comp  the component
351     *
352     * @return  the component
353     */
354    protected JComponent addServerComp(JComponent comp) {
355        compsThatNeedServer.add(comp);
356        return comp;
357    }
358
359    /**
360     * Add a component that needs to have a valid server
361     *
362     * @param comp  the component
363     *
364     * @return  the component
365     */
366    protected JComponent addLevel3ServerComp(JComponent comp) {
367        level3CompsThatNeedServer.add(comp);
368        return comp;
369    }
370
371    /**
372     * Get  the radar collections for  the given server URL
373     *
374     * @param radarServerURL  server URL
375     *
376     * @return  a map of the collection names to URL
377     */
378    private List getRadarCollections(String radarServerURL) {
379        SAXBuilder        builder;
380        Document          doc  = null;
381        XMLEntityResolver jaxp = new XMLEntityResolver(true);
382        builder = jaxp.getSAXBuilder();
383        List collections = new ArrayList();
384
385        try {
386            doc = builder.build(radarServerURL);
387        } catch (JDOMException e) {
388            userMessage("Invalid catalog");
389            //e.printStackTrace();
390        } catch (IOException e) {
391            userMessage("Unable to open catalog");
392            //e.printStackTrace();
393        }
394
395        org.jdom2.Element rootElem    = doc.getRootElement();
396        org.jdom2.Element serviceElem = readElements(rootElem, "service");
397        String           uriBase     = serviceElem.getAttributeValue("base");
398        org.jdom2.Element dsElem      = readElements(rootElem, "dataset");
399        String           naming      = "catalogRef";
400        Namespace        nss         = rootElem.getNamespace("xlink");
401        List             children    = dsElem.getChildren();
402        for (int j = 0; j < children.size(); j++) {
403            org.jdom2.Element child     = (org.jdom2.Element) children.get(j);
404            String           childName = child.getName();
405            if (childName.equals(naming)) {
406                //String id   = child.getAttributeValue("ID");
407                String desc    = child.getAttributeValue("title", nss);
408                String urlpath = child.getAttributeValue("href", nss);
409                String[] c = radarServerURL.split(uriBase);  //.replaceFirst("catalog.xml", "");
410                String         ul     = c[0] + uriBase + urlpath;
411                TwoFacedObject twoObj = new TwoFacedObject(desc, ul);
412                collections.add(twoObj);
413                //collections.put(desc, ul);
414            }
415
416        }
417
418        return collections;
419    }
420
421    /**
422     * Read the elements
423     *
424     * @param elem  element
425     * @param eleName element name
426     *
427     * @return an element
428     */
429    public org.jdom2.Element readElements(org.jdom2.Element elem,
430                                         String eleName) {
431        List children = elem.getChildren();
432        for (int j = 0; j < children.size(); j++) {
433            org.jdom2.Element child     = (org.jdom2.Element) children.get(j);
434            String           childName = child.getName();
435            if (childName.equals(eleName)) {
436                return child;
437            }
438        }
439        return null;
440    }
441
442    /**
443     * Make the collection.  If there is an error, pop up a user message.
444     *
445     * @param url   URL for the collection
446     */
447    public void initializeCollection(String url) {
448
449        List<NamedStationImpl> stations = new ArrayList<NamedStationImpl>();
450        try {
451            StringBuffer errlog = new StringBuffer();
452            try {
453                collection = TDSRadarDatasetCollection.factory("test", url,
454                        errlog);
455            } catch (Exception exc) {
456                userMessage("Invalid catalog");
457
458                return;
459            }
460            List tdsStations = collection.getRadarStations();
461            for (int i = 0; i < tdsStations.size(); i++) {
462                StationImpl stn = (StationImpl) tdsStations.get(i);
463                // thredds.catalog.query.Location loc = stn.getLocation();
464                //TODO: need better station  need to switch lat lon
465                NamedStationImpl station =
466                    new NamedStationImpl(stn.getName(), stn.getName(),
467                                         stn.getLatitude(),
468                                         stn.getLongitude(),
469                                         stn.getAltitude(), CommonUnit.meter);
470                stations.add(station);
471
472            }
473
474            getStationMap().setStations(stations);
475        } catch (Exception exc) {
476            userMessage("Unable to load stations");
477            return;
478        }
479        urlListHandler.saveState(urlBox);
480    }
481
482    /**
483     * _more_
484     *
485     * @param url _more_
486     */
487    public void initializeLevel3Collection(String url) {
488
489        List<NamedStationImpl> stations = new ArrayList<NamedStationImpl>();
490        List<Product>          products;
491        List<String>           exProducts = new ArrayList<String>();
492
493        for(String ename: level3_ExName){
494            exProducts.add(ename);
495        }
496
497        try {
498            StringBuffer errlog = new StringBuffer();
499            try {
500                collection = TDSRadarDatasetCollection.factory("test", url,
501                        errlog);
502            } catch (Exception exc) {
503                userMessage("Invalid catalog");
504                return;
505            }
506            products = collection.getRadarProducts();
507            List tdsStations = collection.getRadarStations();
508            for (int i = 0; i < tdsStations.size(); i++) {
509                StationImpl stn = (StationImpl) tdsStations.get(i);
510                // thredds.catalog.query.Location loc = stn.getLocation();
511                //TODO: need better station  need to switch lat lon
512                NamedStationImpl station =
513                    new NamedStationImpl(stn.getName(), stn.getName(),
514                                         stn.getLatitude(),
515                                         stn.getLongitude(),
516                                         stn.getAltitude(), CommonUnit.meter);
517                stations.add(station);
518
519            }
520            List<TwoFacedObject> productNames = new ArrayList();
521            for (Product product : products) {
522               // if ( !product.getID().contains("DPA")
523                 //       && !product.getID().contains("NVW")) {
524                if ( !exProducts.contains(product.getID())) {
525                    String lable = product.getName() + " (" + product.getID()
526                                   + ")";
527                    TwoFacedObject twoObj = new TwoFacedObject(lable,
528                                                product.getID());
529                    productNames.add(twoObj);
530                }
531            }
532            GuiUtils.setListData(productComboBox, productNames);
533
534            // GuiUtils.setListData(dataTypeComboBox, dataTypes);
535            getStationMap().setStations(stations);
536        } catch (Exception exc) {
537            userMessage("Unable to load stations");
538            return;
539        }
540        urlListHandler.saveState(urlBox);
541    }
542
543
544    /**
545     * Handle when the user has selected a new station
546     */
547    public void stationOrProductChanged() {
548        Vector times = new Vector();
549        setHaveData(false);
550        if ((!isLevel3 && selectedStation != null) ||
551                (isLevel3 && selectedStation != null && selectedProduct != null)) {
552            List timeSpan = collection.getRadarTimeSpan();
553            Date fromDate =  DateUnit.getStandardOrISO((String) timeSpan.get(0));
554            //Date toDate = DateUnit.getStandardOrISO((String) timeSpan.get(1));
555            Date toDate = new Date(System.currentTimeMillis()
556                                   + DateUtil.daysToMillis(1));
557            //Go back 10 years (or so)
558            //Date fromDate = new Date(System.currentTimeMillis()
559            //                         - DateUtil.daysToMillis(365 * 10));
560            try {
561                showWaitCursor();
562                setAbsoluteTimes(new ArrayList());
563                setStatus("Reading times for station: " + selectedStation,
564                          "");
565                //                LogUtil.message("Reading times for station: "
566                //                                + selectedStation);
567                String pid = null;
568                if(isLevel3)
569                    pid = TwoFacedObject.getIdString(
570                                 productComboBox.getSelectedItem());
571                List allTimes =
572                    collection.getRadarStationTimes(selectedStation.getID(),
573                        pid, fromDate, toDate);
574
575             //   if(allTimes.size() == 0) {
576             //       toDate = new Date(System.currentTimeMillis()
577             //                + DateUtil.daysToMillis(1));
578             //       allTimes =
579             //       collection.getRadarStationTimes(selectedStation.getID(),
580             //           pid, fromDate, toDate);
581             //   }
582
583                for (int timeIdx = 0; timeIdx < allTimes.size(); timeIdx++) {
584                    Object timeObj = allTimes.get(timeIdx);
585                    Date   date;
586                    if (timeObj instanceof Date) {
587                        date = (Date) timeObj;
588                    } else {
589                        date = DateUnit.getStandardOrISO(timeObj.toString());
590                    }
591                    times.add(new DateTime(date));
592                }
593                //                LogUtil.message("");
594                showNormalCursor();
595            } catch (Exception exc) {
596                userMessage("Error reading times for station: "
597                            + selectedStation);
598                //logException("Getting times for station: " + selectedStation,
599                //             exc);
600                setStatus("Select a different collection", "collections");
601                showNormalCursor();
602                return;
603            }
604        }
605        setAbsoluteTimes(times);
606        updateStatus();
607    }
608
609
610
611
612
613    /**
614     * Load the data
615     */
616    public void doLoadInThread() {
617        // to the CDMRadarDataSource
618        Hashtable ht = new Hashtable();
619        if (selectedStation != null) {
620            ht.put(ucar.unidata.data.radar.RadarDataSource.STATION_LOCATION,
621                   selectedStation.getNamedLocation());
622            ht.put(DataSelection.PROP_CHOOSERTIMEMATCHING, getDoTimeDrivers());
623        } else {
624            LogUtil.userMessage("No Station selected");
625        }
626
627        if (isLevel3 && (selectedProduct == null)) {
628
629            LogUtil.userMessage("No Product selected");
630        }
631
632        try {
633            DateSelection dateSelection = new DateSelection();
634            String collectionUrl = TwoFacedObject.getIdString(
635                                       collectionSelector.getSelectedItem());
636            String     pid = null;
637            RadarQuery radarQuery;
638            if (isLevel3) {
639                pid = TwoFacedObject.getIdString(
640                    productComboBox.getSelectedItem());
641                radarQuery = new RadarQuery(collectionUrl,
642                                            selectedStation.getID(), pid,
643                                            dateSelection);
644            } else {
645                radarQuery = new RadarQuery(collectionUrl,
646                                            selectedStation.getID(),
647                                            dateSelection);
648            }
649
650            List urls = new ArrayList();
651
652            if (getDoAbsoluteTimes()) {
653                List times    = new ArrayList();
654                List selected = makeDatedObjects(getSelectedAbsoluteTimes());
655                for (int i = 0; i < selected.size(); i++) {
656                    DatedThing datedThing = (DatedThing) selected.get(i);
657                    Date       date       = datedThing.getDate();
658                    times.add(date);
659                    URI uri = null;
660                    try {
661                        uri = collection.getRadarDatasetURI(
662                            selectedStation.getID(), pid, date);
663                    } catch (Exception excp) {
664                        LogUtil.userMessage("incorrect times selected");
665                        return;
666                    }
667                    urls.add(uri.toString());
668                }
669                if (urls.size() == 0) {
670                    LogUtil.userMessage("No times selected");
671                    return;
672                }
673                dateSelection.setTimes(times);
674            } else {
675                int count = getRelativeTimesList().getSelectedIndex() + 1;
676                if (count == 0) {
677                    LogUtil.userMessage("No relative times selected");
678                    return;
679                }
680                Date toDate = new Date(System.currentTimeMillis()
681                                       + DateUtil.daysToMillis(365 * 100));
682                //Go back 10 years (or so)
683                Date fromDate = new Date(System.currentTimeMillis()
684                                         - DateUtil.daysToMillis(365 * 10));
685
686                dateSelection.setStartFixedTime(fromDate);
687                dateSelection.setEndFixedTime(toDate);
688                dateSelection.setCount(count);
689            }
690            makeDataSource(radarQuery, "FILE.RADAR", ht);
691        } catch (Exception exc) {
692            logException("Loading radar data", exc);
693        }
694        // uncheck the check box every time click the add source button
695        drivercbx.setSelected(false);
696        enableTimeWidgets();
697        setDoTimeDrivers(false);
698    }
699    
700    protected int getNumTimesToSelect() {
701        return 5;
702    }
703    
704    /**
705     * Get the default selected index for the relative times list.
706     *
707     * @return default index
708     */
709    protected int getDefaultRelativeTimeIndex() {
710        return 4;
711    }
712    
713    /**
714     * Check the times lists
715     */
716    protected void checkTimesLists() {
717        super.checkTimesLists();
718        if (timesCardPanelExtra == null) {
719            return;
720        }
721        if (getDoAbsoluteTimes()) {
722            timesCardPanelExtra.show("absolute");
723        } else {
724            timesCardPanelExtra.show("relative");
725        }
726    }
727        
728    /** Card panel to hold extra relative and absolute time components */
729    private GuiUtils.CardLayoutPanel timesCardPanelExtra;
730    
731    /**
732     * Add the interval selector to the component.
733     * @return superclass component with extra stuff
734     */
735    protected JPanel makeTimesPanel() {
736        JComponent extra = getExtraTimeComponent();
737        GuiUtils.enableTree(extra, false);
738        JPanel timesPanel = makeTimesPanel(extra, null);
739        return timesPanel;
740    }
741    
742    /**
743     * Set the relative and absolute extra components
744     */
745    protected JPanel makeTimesPanel(JComponent relativeCard, JComponent absoluteCard) {
746        JPanel timesPanel = super.makeTimesPanel(false, true, getIdv().getUseTimeDriver());
747
748        // Make a new timesPanel that has extra components tacked on the bottom, inside the tabs
749        Component[] comps = timesPanel.getComponents();
750
751        if ((comps.length == 2) && (comps[0] instanceof JTabbedPane) && (comps[1] instanceof JLabel)) {
752            timesCardPanelExtra = new GuiUtils.CardLayoutPanel();
753            if (relativeCard == null) {
754                relativeCard = new JPanel();
755            }
756            if (absoluteCard == null) {
757                absoluteCard = new JPanel();
758            }
759            absoluteCard = GuiUtils.hbox(comps[1], GuiUtils.right(absoluteCard));
760            timesCardPanelExtra.add(relativeCard, "relative");
761            timesCardPanelExtra.add(absoluteCard, "absolute");
762            timesPanel = GuiUtils.centerBottom(comps[0], timesCardPanelExtra);
763        }
764        return timesPanel;
765    }
766    
767    /**
768     * Get the time popup widget
769     *
770     * @return  a widget for selecting the day
771     */
772    protected JComponent getExtraTimeComponent() {
773        JPanel filler = new JPanel();
774        McVGuiUtils.setComponentHeight(filler, new JComboBox());
775        return filler;
776    }
777 
778    /**
779     * Make the contents
780     *
781     * @return  the contents
782     */
783    protected JPanel doMakeInnerPanel() {
784        JPanel myPanel = new JPanel();
785
786        JLabel collectionLabel = McVGuiUtils.makeLabelRight("Collection:");
787
788        collectionSelector = new JComboBox();
789        collectionSelector.addItemListener(new ItemListener() {
790            public void itemStateChanged(ItemEvent e) {
791                newSelectedStations(new ArrayList());
792                if (collectionSelector.getSelectedItem() == null) {
793                    return;
794                }
795                String collectionUrl =
796                    TwoFacedObject.getIdString(
797                        collectionSelector.getSelectedItem());
798
799                if (collectionUrl.contains("level3")) {
800                    setLevel3Collection(collectionUrl);
801                } else {
802                    setCollection(collectionUrl);
803                }
804            }
805
806        });
807        addServerComp(collectionLabel);
808        addServerComp(collectionSelector);
809                
810        productComboBox = new JComboBox();
811        productComboBox.addItemListener(new ItemListener() {
812            public void itemStateChanged(ItemEvent e) {
813                if (productComboBox.getSelectedItem() == null) {
814                    return;
815                }
816                selectedProduct =
817                    productComboBox.getSelectedItem().toString();
818                resetProductBox();
819                productChanged();
820            }
821
822        });
823        addLevel3ServerComp(productComboBox);
824                
825        productPanel = McVGuiUtils.makeLabeledComponent("Product:", productComboBox);
826        
827        JLabel stationLabel = McVGuiUtils.makeLabelRight("Station:");
828        addServerComp(stationLabel);
829
830        JComponent stationPanel = getStationMap();
831        registerStatusComp("stations", stationPanel);
832        addServerComp(stationPanel);
833        
834        JLabel timesLabel = McVGuiUtils.makeLabelRight("Times:");
835        addServerComp(timesLabel);
836        
837        JPanel timesPanel = makeTimesPanel();
838        timesPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
839        addServerComp(timesPanel);
840
841        GuiUtils.enableComponents(compsThatNeedServer, false);
842        GuiUtils.enableComponents(level3CompsThatNeedServer, false);
843        productPanel.setVisible(false);
844
845        GroupLayout layout = new GroupLayout(myPanel);
846        myPanel.setLayout(layout);
847        layout.setHorizontalGroup(
848            layout.createParallelGroup(LEADING)
849            .addGroup(layout.createSequentialGroup()
850                .addGroup(layout.createParallelGroup(LEADING)
851                    .addGroup(layout.createSequentialGroup()
852                        .addComponent(collectionLabel)
853                        .addGap(GAP_RELATED)
854                        .addComponent(collectionSelector, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
855                        .addComponent(productPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
856                    .addGroup(layout.createSequentialGroup()
857                        .addComponent(stationLabel)
858                        .addGap(GAP_RELATED)
859                        .addComponent(stationPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
860                    .addGroup(layout.createSequentialGroup()
861                        .addComponent(timesLabel)
862                        .addGap(GAP_RELATED)
863                        .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))))
864        );
865        layout.setVerticalGroup(
866            layout.createParallelGroup(LEADING)
867            .addGroup(layout.createSequentialGroup()
868                .addGroup(layout.createParallelGroup(BASELINE)
869                    .addComponent(collectionLabel)
870                    .addComponent(collectionSelector)
871                    .addComponent(productPanel))
872                .addPreferredGap(RELATED)
873                .addGroup(layout.createParallelGroup(LEADING)
874                    .addComponent(stationLabel)
875                    .addComponent(stationPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
876                .addPreferredGap(RELATED)
877                .addGroup(layout.createParallelGroup(LEADING)
878                    .addComponent(timesLabel)
879                    .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
880                .addPreferredGap(RELATED))
881        );
882        
883        return myPanel;
884    }
885
886    private JPanel innerPanel = doMakeInnerPanel();
887
888    private JLabel statusLabel = new JLabel("Status");
889
890    @Override
891    public void setStatus(String statusString, String foo) {
892        if (statusString == null)
893            statusString = "";
894        statusLabel.setText(statusString);
895    }
896        
897    protected void setInnerPanel(JPanel newInnerPanel) {
898        innerPanel = newInnerPanel;
899    }
900
901    /**
902     * Make the UI for this selector.
903     *
904     * Thank you NetBeans for helping with the layout!
905     *
906     * @return The GUI.
907     */
908    public JComponent doMakeContents() {
909        JPanel outerPanel = new JPanel();
910
911        JLabel serverLabel = McVGuiUtils.makeLabelRight("Catalog:");                
912
913        //Get the list of catalogs but remove the old catalog.xml entry
914        urlListHandler = getPreferenceList(PREF_TDSRADARSERVER);
915
916        ActionListener catListListener = new ActionListener() {
917            public void actionPerformed(ActionEvent ae) {
918                if ( !okToDoUrlListEvents) {
919                    return;
920                }
921                setServer((String) urlBox.getSelectedItem());
922            }
923        };
924        
925        urlBox = urlListHandler.createComboBox(GuiUtils.CMD_UPDATE, catListListener, true);
926        McVGuiUtils.setComponentWidth(urlBox, Width.DOUBLEDOUBLE);
927        
928        // productComboBox gets created a little too tall--set to same height as urlBox
929        if (productComboBox!=null)
930            McVGuiUtils.setComponentHeight(productComboBox, urlBox);
931                
932        JButton connectButton = McVGuiUtils.makeImageTextButton(ICON_CONNECT_SMALL, "Connect");
933        McVGuiUtils.setComponentWidth(connectButton, Width.DOUBLE);
934        connectButton.setActionCommand(GuiUtils.CMD_UPDATE);
935        connectButton.addActionListener(this);
936                
937        JLabel statusLabelLabel = McVGuiUtils.makeLabelRight("");
938        
939        statusLabel.setText("Status");
940        McVGuiUtils.setLabelPosition(statusLabel, Position.RIGHT);
941        McVGuiUtils.setComponentColor(statusLabel, TextColor.STATUS);
942        
943        JButton helpButton = McVGuiUtils.makeImageButton(ICON_HELP, "Show help");
944        helpButton.setActionCommand(GuiUtils.CMD_HELP);
945        helpButton.addActionListener(this);
946        
947        JButton refreshButton = McVGuiUtils.makeImageButton(ICON_REFRESH, "Refresh");
948        refreshButton.setActionCommand(GuiUtils.CMD_UPDATE);
949        refreshButton.addActionListener(this);
950        
951        McVGuiUtils.setComponentWidth(loadButton, Width.DOUBLE);
952
953        GroupLayout layout = new GroupLayout(outerPanel);
954        outerPanel.setLayout(layout);
955        layout.setHorizontalGroup(
956            layout.createParallelGroup(LEADING)
957            .addGroup(TRAILING, layout.createSequentialGroup()
958                .addGroup(layout.createParallelGroup(TRAILING)
959                    .addGroup(layout.createSequentialGroup()
960                        .addContainerGap()
961                        .addComponent(helpButton)
962                        .addGap(GAP_RELATED)
963                        .addComponent(refreshButton)
964                        .addGap(GAP_RELATED)
965                        .addComponent(cancelButton)
966                        .addPreferredGap(RELATED)
967                        .addComponent(loadButton))
968                        .addGroup(LEADING, layout.createSequentialGroup()
969                        .addContainerGap()
970                        .addGroup(layout.createParallelGroup(LEADING)
971                            .addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
972                            .addGroup(layout.createSequentialGroup()
973                                .addComponent(serverLabel)
974                                .addGap(GAP_RELATED)
975                                .addComponent(urlBox, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
976                                .addGap(GAP_UNRELATED)
977                                .addComponent(connectButton))
978                            .addGroup(layout.createSequentialGroup()
979                                .addComponent(statusLabelLabel)
980                                .addGap(GAP_RELATED)
981                                .addComponent(statusLabel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)))))
982                .addContainerGap())
983        );
984        layout.setVerticalGroup(
985            layout.createParallelGroup(LEADING)
986            .addGroup(layout.createSequentialGroup()
987                .addContainerGap()
988                .addGroup(layout.createParallelGroup(BASELINE)
989                    .addComponent(serverLabel)
990                    .addComponent(urlBox)
991                    .addComponent(connectButton))
992                .addPreferredGap(UNRELATED)
993                .addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
994                .addPreferredGap(UNRELATED)
995                .addGroup(layout.createParallelGroup(BASELINE)
996                    .addComponent(statusLabelLabel)
997                    .addComponent(statusLabel))
998                .addPreferredGap(UNRELATED)
999                .addGroup(layout.createParallelGroup(BASELINE)
1000                    .addComponent(loadButton)
1001                    .addComponent(cancelButton)
1002                    .addComponent(refreshButton)
1003                    .addComponent(helpButton))
1004                .addContainerGap())
1005        );
1006    
1007        return outerPanel;
1008
1009    }
1010
1011}
1012