edu.wisc.ssec.mcidasv.chooser.TDSRadarChooser.java Source code

Java tutorial

Introduction

Here is the source code for edu.wisc.ssec.mcidasv.chooser.TDSRadarChooser.java

Source

/*
 * This file is part of McIDAS-V
 *
 * Copyright 2007-2019
 * Space Science and Engineering Center (SSEC)
 * University of Wisconsin - Madison
 * 1225 W. Dayton Street, Madison, WI 53706, USA
 * http://www.ssec.wisc.edu/mcidas
 * 
 * All Rights Reserved
 * 
 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
 * some McIDAS-V source code is based on IDV and VisAD source code.  
 * 
 * McIDAS-V is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * McIDAS-V is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser Public License
 * along with this program.  If not, see http://www.gnu.org/licenses.
 */

package edu.wisc.ssec.mcidasv.chooser;

import static javax.swing.GroupLayout.DEFAULT_SIZE;
import static javax.swing.GroupLayout.PREFERRED_SIZE;
import static javax.swing.GroupLayout.Alignment.BASELINE;
import static javax.swing.GroupLayout.Alignment.LEADING;
import static javax.swing.GroupLayout.Alignment.TRAILING;
import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import java.util.regex.Pattern;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;

import edu.wisc.ssec.mcidasv.chooser.adde.AddeChooser;
import edu.wisc.ssec.mcidasv.util.McVTextField;
import org.jdom2.Document;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;

import thredds.catalog.XMLEntityResolver;
import ucar.unidata.data.DataSelection;
import ucar.unidata.data.radar.TDSRadarDatasetCollection;
import ucar.nc2.units.DateUnit;
import ucar.unidata.data.radar.RadarQuery;
import ucar.unidata.geoloc.StationImpl;
import ucar.unidata.idv.chooser.IdvChooserManager;
import ucar.unidata.idv.chooser.TimesChooser;
import ucar.unidata.metdata.NamedStation;
import ucar.unidata.metdata.NamedStationImpl;
import ucar.unidata.util.DateSelection;
import ucar.unidata.util.DateUtil;
import ucar.unidata.util.DatedThing;
import ucar.unidata.util.GuiUtils;
import ucar.unidata.util.LogUtil;
import ucar.unidata.util.Misc;
import ucar.unidata.util.PreferenceList;
import ucar.unidata.util.Product;
import ucar.unidata.util.TwoFacedObject;

import visad.CommonUnit;
import visad.DateTime;

import edu.wisc.ssec.mcidasv.Constants;
import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Position;
import edu.wisc.ssec.mcidasv.util.McVGuiUtils.TextColor;
import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width;

/**
 * This class is responsible for the {@literal "Remote Level II Radar"} 
 * chooser in McIDAS-V.
 */
public class TDSRadarChooser extends TimesChooser implements Constants {

    /** Loggging object. */
    private static final Logger logger = LoggerFactory.getLogger(TDSRadarChooser.class);

    /** The collection */
    private TDSRadarDatasetCollection collection;

    /** The currently selected station */
    private NamedStation selectedStation;

    /** The currently selected level3 product */
    private String selectedProduct;

    /** Those urls we connect to */
    //"http://motherlode.ucar.edu:8080/thredds/radarServer/catalog.xml";
    private String serverUrl;

    ///** Each dataset collection URL */
    //"http://motherlode.ucar.edu:8080/thredds/radarServer/level2/idd/dataset.xml";
    //private String collectionUrl;

    /** Component to hold collections */
    private JComboBox collectionSelector;

    /** Component to hold product list */
    private JComboBox productComboBox;

    /** Level 3 panel that can be hidden */
    private JPanel productPanel;

    /** components that need a server for activation */
    private List compsThatNeedServer = new ArrayList();

    /** components that need a server for activation */
    private List level3CompsThatNeedServer = new ArrayList();

    /** persistent holder for catalog URLS */
    private PreferenceList urlListHandler;

    /** catalog URL holder */
    private JComboBox urlBox;

    /** ok flag */
    private boolean okToDoUrlListEvents = true;

    /** dataset list */
    private List datasetList;

    /** Command for connecting */
    protected static final String CMD_CONNECT = "cmd.connect";

    /** _more_          */
    private boolean isLevel3;

    /** _more_          */
    public static final String[] level3_ExName = { "NVW", "DPA" };

    /** Number of relative time steps to load */
    private int relativeTimes = 5;

    /**
     * Create the RadarChooser.
     *
     * @param mgr {@code IdvChooserManager}
     * @param root XML root that defines this chooser.
     *
     */
    public TDSRadarChooser(IdvChooserManager mgr, Element root) {
        super(mgr, root);

        loadButton = McVGuiUtils.makeImageTextButton(ICON_ACCEPT_SMALL, getLoadCommandName());
        loadButton.setActionCommand(getLoadCommandName());
        loadButton.addActionListener(this);

        cancelButton = McVGuiUtils.makeImageButton(ICON_CANCEL, "Cancel");
        cancelButton.setActionCommand(GuiUtils.CMD_CANCEL);
        cancelButton.addActionListener(this);
        cancelButton.setEnabled(false);

    }

    /**
     * Handle the update event. Just pass it through to the imageChooser
     */
    public void doUpdate() {
        if ((serverUrl == null) || (datasetList == null) || (datasetList.size() == 0)
                || (selectedProduct == null)) {
            if (urlBox != null) {
                setServer((String) urlBox.getSelectedItem());
            }
            return;
        }
        Misc.run(this, "stationOrProductChanged");
    }

    /**
     * Update the status of the gui
     */
    protected void updateStatus() {
        super.updateStatus();
        if (serverUrl == null) {
            setHaveData(false);
            setStatus("Please connect to the server");
        } else if (selectedStation == null) {
            setHaveData(false);
            setStatus("Please select a station", "stations");
        } else if (isLevel3 && (selectedProduct == null)) {
            setHaveData(false);
            setStatus("Please select a level 3 product", "products");
        } else {
            boolean haveTimesSelected;
            if (getDoAbsoluteTimes()) {
                haveTimesSelected = getSelectedAbsoluteTimes().size() > 0;
            } else {
                haveTimesSelected = true;
            }
            setHaveData(haveTimesSelected);
            if (haveTimesSelected) {
                setStatus("Press \"" + CMD_LOAD + "\" to load the selected radar data", "buttons");
            } else {
                setStatus("Please select times", "timepanel");
            }
        }
        GuiUtils.enableTree(loadButton, getHaveData());
        if (drivercbx != null) {
            drivercbx.setEnabled(anyTimeDrivers() && getHaveData());
        }
    }

    /**
     * Handle when there are newly selected stations
     *
     * @param stations list of newly selected stations
     */
    protected void newSelectedStations(List stations) {
        super.newSelectedStations(stations);
        if ((stations == null) || (stations.size() == 0)) {
            selectedStation = null;
        } else {
            NamedStation newStation = (NamedStation) stations.get(0);
            if (Misc.equals(newStation, selectedStation)) {
                return;
            }
            selectedStation = newStation;
        }
        Misc.run(TDSRadarChooser.this, "stationOrProductChanged");
    }

    /** A widget for the list of dataset descriptors */

    /** Flag to keep from infinite looping */
    private boolean ignoreProductChange = false;

    /** Selection label text */
    protected static final String LABEL_SELECT = " -- Select -- ";

    /**
     * _more_
     */
    protected void productChanged() {
        stationOrProductChanged();
        // updateStatus();
    }

    /**
     * Reset the descriptor stuff
     */
    private void resetProductBox() {
        ignoreProductChange = true;
        productComboBox.setSelectedItem(LABEL_SELECT);
        ignoreProductChange = false;
    }

    /**
     * Should we update on first display
     *
     * @return true
     */
    protected boolean shouldDoUpdateOnFirstDisplay() {
        return false;
    }

    /**
     * Set the server
     *
     * @param s the server URL
     */
    private void setServer(String s) {
        datasetList = new ArrayList();
        serverUrl = s;
        try {
            datasetList = getRadarCollections(serverUrl);
            GuiUtils.setListData(collectionSelector, datasetList);
            for (int i = 0; i < datasetList.size(); i++) {
                TwoFacedObject obj = (TwoFacedObject) datasetList.get(i);
                String tmpStr = (String) (obj.getLabel());
                if ((tmpStr).contains("NEXRAD Level II Radar from IDD")) {
                    collectionSelector.setSelectedIndex(i);
                }
            }
        } catch (Exception e) {
            GuiUtils.setListData(collectionSelector, new ArrayList());
        }
    }

    /**
     * Set the active collection
     *
     * @param s collection URL
     */
    private void setCollection(String s) {
        isLevel3 = false;
        GuiUtils.enableComponents(level3CompsThatNeedServer, false);
        productPanel.setVisible(false);
        GuiUtils.enableComponents(compsThatNeedServer, true);
        if (drivercbx != null) {
            drivercbx.setEnabled(anyTimeDrivers() && getHaveData());
        }
        setAbsoluteTimes(new ArrayList());
        selectedProduct = null;
        selectedStation = null;
        Misc.run(this, "initializeCollection", s);
    }

    /**
     * _more_
     *
     * @param s _more_
     */
    private void setLevel3Collection(String s) {
        isLevel3 = true;
        GuiUtils.enableComponents(level3CompsThatNeedServer, true);
        productPanel.setVisible(true);
        GuiUtils.enableComponents(compsThatNeedServer, true);
        if (drivercbx != null) {
            drivercbx.setEnabled(anyTimeDrivers() && getHaveData());
        }
        setAbsoluteTimes(new ArrayList());
        selectedProduct = null;
        selectedStation = null;
        Misc.run(this, "initializeLevel3Collection", s);
    }

    /**
     * Add a component that needs to have a valid server
     *
     * @param comp  the component
     *
     * @return  the component
     */
    protected JComponent addServerComp(JComponent comp) {
        compsThatNeedServer.add(comp);
        return comp;
    }

    /**
     * Add a component that needs to have a valid server
     *
     * @param comp  the component
     *
     * @return  the component
     */
    protected JComponent addLevel3ServerComp(JComponent comp) {
        level3CompsThatNeedServer.add(comp);
        return comp;
    }

    /**
     * Get  the radar collections for  the given server URL
     *
     * @param radarServerURL  server URL
     *
     * @return  a map of the collection names to URL
     */
    private List getRadarCollections(String radarServerURL) {
        SAXBuilder builder;
        Document doc = null;
        XMLEntityResolver jaxp = new XMLEntityResolver(true);
        builder = jaxp.getSAXBuilder();
        List collections = new ArrayList();

        try {
            doc = builder.build(radarServerURL);
        } catch (JDOMException e) {
            userMessage("Invalid catalog");
        } catch (IOException e) {
            userMessage("Unable to open catalog");
        }

        org.jdom2.Element rootElem = doc.getRootElement();
        org.jdom2.Element serviceElem = readElements(rootElem, "service");
        String uriBase = serviceElem.getAttributeValue("base");
        org.jdom2.Element dsElem = readElements(rootElem, "dataset");
        String naming = "catalogRef";
        Namespace nss = rootElem.getNamespace("xlink");
        List children = dsElem.getChildren();
        for (int j = 0; j < children.size(); j++) {
            org.jdom2.Element child = (org.jdom2.Element) children.get(j);
            String childName = child.getName();
            if (childName.equals(naming)) {
                //String id   = child.getAttributeValue("ID");
                String desc = child.getAttributeValue("title", nss);
                String urlpath = child.getAttributeValue("href", nss);
                String[] c = radarServerURL.split(uriBase); //.replaceFirst("catalog.xml", "");
                String ul = c[0] + uriBase + urlpath;
                TwoFacedObject twoObj = new TwoFacedObject(desc, ul);
                collections.add(twoObj);
                //collections.put(desc, ul);
            }

        }

        return collections;
    }

    /**
     * Read the elements
     *
     * @param elem  element
     * @param eleName element name
     *
     * @return an element
     */
    public org.jdom2.Element readElements(org.jdom2.Element elem, String eleName) {
        List children = elem.getChildren();
        for (int j = 0; j < children.size(); j++) {
            org.jdom2.Element child = (org.jdom2.Element) children.get(j);
            String childName = child.getName();
            if (childName.equals(eleName)) {
                return child;
            }
        }
        return null;
    }

    /**
     * Make the collection.  If there is an error, pop up a user message.
     *
     * @param url   URL for the collection
     */
    public void initializeCollection(String url) {

        List<NamedStationImpl> stations = new ArrayList<NamedStationImpl>();
        try {
            StringBuffer errlog = new StringBuffer();
            try {
                collection = TDSRadarDatasetCollection.factory("test", url, errlog);
            } catch (Exception exc) {
                userMessage("Invalid catalog");

                return;
            }
            List tdsStations = collection.getRadarStations();
            for (int i = 0; i < tdsStations.size(); i++) {
                StationImpl stn = (StationImpl) tdsStations.get(i);
                // thredds.catalog.query.Location loc = stn.getLocation();
                //TODO: need better station  need to switch lat lon
                NamedStationImpl station = new NamedStationImpl(stn.getName(), stn.getName(), stn.getLatitude(),
                        stn.getLongitude(), stn.getAltitude(), CommonUnit.meter);
                stations.add(station);

            }

            getStationMap().setStations(stations);
        } catch (Exception exc) {
            userMessage("Unable to load stations");
            return;
        }
        urlListHandler.saveState(urlBox);
    }

    /**
     * _more_
     *
     * @param url _more_
     */
    public void initializeLevel3Collection(String url) {

        List<NamedStationImpl> stations = new ArrayList<NamedStationImpl>();
        List<Product> products;
        List<String> exProducts = new ArrayList<String>();

        for (String ename : level3_ExName) {
            exProducts.add(ename);
        }

        try {
            StringBuffer errlog = new StringBuffer();
            try {
                collection = TDSRadarDatasetCollection.factory("test", url, errlog);
            } catch (Exception exc) {
                userMessage("Invalid catalog");
                return;
            }
            products = collection.getRadarProducts();
            List tdsStations = collection.getRadarStations();
            for (int i = 0; i < tdsStations.size(); i++) {
                StationImpl stn = (StationImpl) tdsStations.get(i);
                // thredds.catalog.query.Location loc = stn.getLocation();
                //TODO: need better station  need to switch lat lon
                NamedStationImpl station = new NamedStationImpl(stn.getName(), stn.getName(), stn.getLatitude(),
                        stn.getLongitude(), stn.getAltitude(), CommonUnit.meter);
                stations.add(station);

            }
            List<TwoFacedObject> productNames = new ArrayList();
            for (Product product : products) {
                // if ( !product.getID().contains("DPA")
                //       && !product.getID().contains("NVW")) {
                if (!exProducts.contains(product.getID())) {
                    String lable = product.getName() + " (" + product.getID() + ")";
                    TwoFacedObject twoObj = new TwoFacedObject(lable, product.getID());
                    productNames.add(twoObj);
                }
            }
            GuiUtils.setListData(productComboBox, productNames);

            // GuiUtils.setListData(dataTypeComboBox, dataTypes);
            getStationMap().setStations(stations);
        } catch (Exception exc) {
            userMessage("Unable to load stations");
            return;
        }
        urlListHandler.saveState(urlBox);
    }

    /**
     * Handle when the user has selected a new station
     */
    public void stationOrProductChanged() {
        Vector times = new Vector();
        setHaveData(false);
        if ((!isLevel3 && selectedStation != null)
                || (isLevel3 && selectedStation != null && selectedProduct != null)) {
            List timeSpan = collection.getRadarTimeSpan();
            Date fromDate = DateUnit.getStandardOrISO((String) timeSpan.get(0));
            //Date toDate = DateUnit.getStandardOrISO((String) timeSpan.get(1));
            Date toDate = new Date(System.currentTimeMillis() + DateUtil.daysToMillis(1));
            //Go back 10 years (or so)
            //Date fromDate = new Date(System.currentTimeMillis()
            //                         - DateUtil.daysToMillis(365 * 10));
            try {
                showWaitCursor();
                setAbsoluteTimes(new ArrayList());
                setStatus("Reading times for station: " + selectedStation, "");
                //                LogUtil.message("Reading times for station: "
                //                                + selectedStation);
                String pid = null;
                if (isLevel3)
                    pid = TwoFacedObject.getIdString(productComboBox.getSelectedItem());
                List allTimes = collection.getRadarStationTimes(selectedStation.getID(), pid, fromDate, toDate);

                //   if(allTimes.size() == 0) {
                //       toDate = new Date(System.currentTimeMillis()
                //                + DateUtil.daysToMillis(1));
                //       allTimes =
                //       collection.getRadarStationTimes(selectedStation.getID(),
                //           pid, fromDate, toDate);
                //   }

                for (int timeIdx = 0; timeIdx < allTimes.size(); timeIdx++) {
                    Object timeObj = allTimes.get(timeIdx);
                    Date date;
                    if (timeObj instanceof Date) {
                        date = (Date) timeObj;
                    } else {
                        date = DateUnit.getStandardOrISO(timeObj.toString());
                    }
                    times.add(new DateTime(date));
                }
                //                LogUtil.message("");
                showNormalCursor();
            } catch (Exception exc) {
                userMessage("Error reading times for station: " + selectedStation);
                //logException("Getting times for station: " + selectedStation,
                //             exc);
                setStatus("Select a different collection", "collections");
                showNormalCursor();
                return;
            }
        }
        setAbsoluteTimes(times);
        updateStatus();
    }

    /**
     * Load the data
     */
    public void doLoadInThread() {
        // to the CDMRadarDataSource
        Hashtable ht = new Hashtable();
        if (selectedStation != null) {
            ht.put(ucar.unidata.data.radar.RadarDataSource.STATION_LOCATION, selectedStation.getNamedLocation());
            ht.put(DataSelection.PROP_CHOOSERTIMEMATCHING, getDoTimeDrivers());
        } else {
            LogUtil.userMessage("No Station selected");
        }

        if (isLevel3 && (selectedProduct == null)) {

            LogUtil.userMessage("No Product selected");
        }

        try {
            DateSelection dateSelection = new DateSelection();
            String collectionUrl = TwoFacedObject.getIdString(collectionSelector.getSelectedItem());
            String pid = null;
            RadarQuery radarQuery;
            if (isLevel3) {
                pid = TwoFacedObject.getIdString(productComboBox.getSelectedItem());
                radarQuery = new RadarQuery(collectionUrl, selectedStation.getID(), pid, dateSelection);
            } else {
                radarQuery = new RadarQuery(collectionUrl, selectedStation.getID(), dateSelection);
            }

            List urls = new ArrayList();

            if (getDoAbsoluteTimes()) {
                List times = new ArrayList();
                List selected = makeDatedObjects(getSelectedAbsoluteTimes());
                for (int i = 0; i < selected.size(); i++) {
                    DatedThing datedThing = (DatedThing) selected.get(i);
                    Date date = datedThing.getDate();
                    times.add(date);
                    URI uri = null;
                    try {
                        uri = collection.getRadarDatasetURI(selectedStation.getID(), pid, date);
                    } catch (Exception excp) {
                        LogUtil.userMessage("incorrect times selected");
                        return;
                    }
                    urls.add(uri.toString());
                }
                if (urls.size() == 0) {
                    LogUtil.userMessage("No times selected");
                    return;
                }
                dateSelection.setTimes(times);
            } else {
                //                int count = getRelativeTimesList().getSelectedIndex() + 1;
                if (relativeTimes == 0) {
                    LogUtil.userMessage("No relative times selected");
                    return;
                }
                Date toDate = new Date(System.currentTimeMillis() + DateUtil.daysToMillis(365 * 100));
                //Go back 10 years (or so)
                Date fromDate = new Date(System.currentTimeMillis() - DateUtil.daysToMillis(365 * 10));

                dateSelection.setStartFixedTime(fromDate);
                dateSelection.setEndFixedTime(toDate);
                dateSelection.setCount(relativeTimes);
            }
            makeDataSource(radarQuery, "FILE.RADAR", ht);
        } catch (Exception exc) {
            logException("Loading radar data", exc);
        }
        // uncheck the check box every time click the add source button
        drivercbx.setSelected(false);
        enableTimeWidgets();
        setDoTimeDrivers(false);
    }

    protected int getNumTimesToSelect() {
        return 5;
    }

    /**
     * Get the default selected index for the relative times list.
     *
     * @return default index
     */
    protected int getDefaultRelativeTimeIndex() {
        return 4;
    }

    /**
     * Check the times lists
     */
    protected void checkTimesLists() {
        super.checkTimesLists();
        if (timesCardPanelExtra == null) {
            return;
        }
        if (getDoAbsoluteTimes()) {
            timesCardPanelExtra.show("absolute");
        } else {
            timesCardPanelExtra.show("relative");
        }
    }

    /** Card panel to hold extra relative and absolute time components */
    private GuiUtils.CardLayoutPanel timesCardPanelExtra;

    /**
     * Add the interval selector to the component.
     * @return superclass component with extra stuff
     */
    protected JPanel makeTimesPanel() {
        JComponent extra = getExtraTimeComponent();
        GuiUtils.enableTree(extra, false);
        JPanel timesPanel = makeTimesPanel(extra, null);
        return timesPanel;
    }

    /**
     * Set the relative and absolute extra components
     */
    protected JPanel makeTimesPanel(JComponent relativeCard, JComponent absoluteCard) {
        JPanel timesPanel = super.makeTimesPanel(false, true, getIdv().getUseTimeDriver());

        // Make a new timesPanel that has extra components tacked on the bottom, inside the tabs
        Component[] comps = timesPanel.getComponents();

        if ((comps.length == 2) && (comps[0] instanceof JTabbedPane) && (comps[1] instanceof JLabel)) {
            timesCardPanelExtra = new GuiUtils.CardLayoutPanel();
            if (relativeCard == null) {
                relativeCard = new JPanel();
            }
            if (absoluteCard == null) {
                absoluteCard = new JPanel();
            }
            absoluteCard = GuiUtils.hbox(comps[1], GuiUtils.right(absoluteCard));
            timesCardPanelExtra.add(relativeCard, "relative");
            timesCardPanelExtra.add(absoluteCard, "absolute");
            timesPanel = GuiUtils.centerBottom(comps[0], timesCardPanelExtra);
        }
        return timesPanel;
    }

    /**
     * Get the time popup widget
     *
     * @return  a widget for selecting the day
     */
    protected JComponent getExtraTimeComponent() {
        JPanel filler = new JPanel();
        McVGuiUtils.setComponentHeight(filler, new JComboBox());
        return filler;
    }

    /**
     * Make the contents
     *
     * @return  the contents
     */
    protected JPanel doMakeInnerPanel() {
        JPanel myPanel = new JPanel();

        JLabel collectionLabel = McVGuiUtils.makeLabelRight("Collection:");

        collectionSelector = new JComboBox();
        collectionSelector.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                newSelectedStations(new ArrayList());
                if (collectionSelector.getSelectedItem() == null) {
                    return;
                }
                String collectionUrl = TwoFacedObject.getIdString(collectionSelector.getSelectedItem());

                if (collectionUrl.contains("level3")) {
                    setLevel3Collection(collectionUrl);
                } else {
                    setCollection(collectionUrl);
                }
            }

        });
        addServerComp(collectionLabel);
        addServerComp(collectionSelector);

        productComboBox = new JComboBox();
        productComboBox.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                if (productComboBox.getSelectedItem() == null) {
                    return;
                }
                selectedProduct = productComboBox.getSelectedItem().toString();
                resetProductBox();
                productChanged();
            }

        });
        addLevel3ServerComp(productComboBox);

        productPanel = McVGuiUtils.makeLabeledComponent("Product:", productComboBox);

        JLabel stationLabel = McVGuiUtils.makeLabelRight("Station:");
        addServerComp(stationLabel);

        JComponent stationPanel = getStationMap();
        registerStatusComp("stations", stationPanel);
        addServerComp(stationPanel);

        JLabel timesLabel = McVGuiUtils.makeLabelRight("Times:");
        addServerComp(timesLabel);

        JPanel timesPanel = makeTimesPanel();
        timesPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        addServerComp(timesPanel);

        GuiUtils.enableComponents(compsThatNeedServer, false);
        GuiUtils.enableComponents(level3CompsThatNeedServer, false);
        productPanel.setVisible(false);

        GroupLayout layout = new GroupLayout(myPanel);
        myPanel.setLayout(layout);
        layout.setHorizontalGroup(layout.createParallelGroup(LEADING)
                .addGroup(layout.createSequentialGroup().addGroup(layout.createParallelGroup(LEADING)
                        .addGroup(layout.createSequentialGroup().addComponent(collectionLabel).addGap(GAP_RELATED)
                                .addComponent(collectionSelector, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(productPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
                        .addGroup(layout.createSequentialGroup().addComponent(stationLabel).addGap(GAP_RELATED)
                                .addComponent(stationPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
                        .addGroup(layout.createSequentialGroup().addComponent(timesLabel).addGap(GAP_RELATED)
                                .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)))));
        layout.setVerticalGroup(layout.createParallelGroup(LEADING)
                .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(BASELINE).addComponent(collectionLabel)
                                .addComponent(collectionSelector).addComponent(productPanel))
                        .addPreferredGap(RELATED)
                        .addGroup(layout.createParallelGroup(LEADING).addComponent(stationLabel)
                                .addComponent(stationPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
                        .addPreferredGap(RELATED)
                        .addGroup(layout.createParallelGroup(LEADING).addComponent(timesLabel)
                                .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
                        .addPreferredGap(RELATED)));

        return myPanel;
    }

    private JPanel innerPanel = doMakeInnerPanel();

    private JLabel statusLabel = new JLabel("Status");

    @Override
    public void setStatus(String statusString, String foo) {
        if (statusString == null) {
            statusString = "";
        }
        statusLabel.setText(statusString);
    }

    protected void setInnerPanel(JPanel newInnerPanel) {
        innerPanel = newInnerPanel;
    }

    /**
     * Create the widget responsible for handling relative time selection.
     *
     * @return GUI widget.
     */
    @Override
    public JComponent getRelativeTimesChooser() {
        McVTextField relativeTimesField = McVGuiUtils.makeTextFieldAllow(String.valueOf(relativeTimes), 4, false,
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9');

        // need to keep *both* the ActionListener and DocumentListener around.
        // removing the ActionListener results in strange behavior when you've
        // accidentally cleared out the text field.
        relativeTimesField.setAllow(Pattern.compile("^[1-9][0-9]*$"), true);
        //        relativeTimesField.setDeny(Pattern.compile("^0$"), true);
        relativeTimesField.setColumns(4);
        relativeTimesField.addActionListener(e -> {
            String text = ((JTextField) e.getSource()).getText();
            validateRelativeTimeInput(text);
        });
        relativeTimesField.getDocument().addDocumentListener(new DocumentListener() {
            @Override
            public void insertUpdate(DocumentEvent e) {
                handleRelativeTimeChange(e);
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                handleRelativeTimeChange(e);
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                handleRelativeTimeChange(e);
            }
        });
        relativeTimesField.setToolTipText(AddeChooser.RELATIVE_TIMES_TOOLTIP);

        JPanel panel = GuiUtils.topLeft(GuiUtils.label(AddeChooser.RELATIVE_TIMES_LABEL, relativeTimesField));
        JScrollPane scrollPane = new JScrollPane(panel);
        scrollPane.setPreferredSize(new Dimension(150, 100));
        return scrollPane;
    }

    /**
     * Validate the contents of the relative times text field.
     *
     * <p>This method overwrites {@link #relativeTimes} if {@code text} is an 
     * integer greater than zero.</p>
     *
     * @param text Contents of the text field.
     */
    private void validateRelativeTimeInput(String text) {
        try {
            int value = Integer.valueOf(text);
            if (value > 0) {
                relativeTimes = value;
                setHaveData(true);
                updateStatus();
            }
        } catch (NumberFormatException e) {
            setHaveData(false);
            setStatus("Please provide an integer value greater than zero.");
        }
    }

    /**
     * Handle {@link DocumentListener} events for the {@link JTextField}
     * created by {@link #getRelativeTimesChooser()}.
     *
     * @param event Event to handle. Cannot be {@code null}.
     */
    private void handleRelativeTimeChange(DocumentEvent event) {
        int len = event.getDocument().getLength();
        try {
            String text = event.getDocument().getText(0, len);
            validateRelativeTimeInput(text);
        } catch (BadLocationException ex) {
            logger.warn("Could not get contents of text field!", ex);
        }
    }

    /**
     * Get the relative time indices
     *
     * @return an array of indices
     */
    @Override
    public int[] getRelativeTimeIndices() {
        int[] indices = new int[relativeTimes];
        for (int i = 0; i < indices.length; i++) {
            indices[i] = i;
        }
        return indices;
    }

    /**
     * Make the UI for this selector.
     *
     * Thank you NetBeans for helping with the layout!
     *
     * @return The GUI.
     */
    public JComponent doMakeContents() {
        JPanel outerPanel = new JPanel();

        JLabel serverLabel = McVGuiUtils.makeLabelRight("Catalog:");

        //Get the list of catalogs but remove the old catalog.xml entry
        urlListHandler = getPreferenceList(PREF_TDSRADARSERVER);

        ActionListener catListListener = ae -> {
            if (okToDoUrlListEvents) {
                setServer((String) urlBox.getSelectedItem());
            }
        };

        urlBox = urlListHandler.createComboBox(GuiUtils.CMD_UPDATE, catListListener, true);
        McVGuiUtils.setComponentWidth(urlBox, Width.DOUBLEDOUBLE);

        // productComboBox gets created a little too tall--set to same height as urlBox
        if (productComboBox != null) {
            McVGuiUtils.setComponentHeight(productComboBox, urlBox);
        }
        JButton connectButton = McVGuiUtils.makeImageTextButton(ICON_CONNECT_SMALL, "Connect");
        McVGuiUtils.setComponentWidth(connectButton, Width.DOUBLE);
        connectButton.setActionCommand(GuiUtils.CMD_UPDATE);
        connectButton.addActionListener(this);

        JLabel statusLabelLabel = McVGuiUtils.makeLabelRight("");

        statusLabel.setText("Status");
        McVGuiUtils.setLabelPosition(statusLabel, Position.RIGHT);
        McVGuiUtils.setComponentColor(statusLabel, TextColor.STATUS);

        JButton helpButton = McVGuiUtils.makeImageButton(ICON_HELP, "Show help");
        helpButton.setActionCommand(GuiUtils.CMD_HELP);
        helpButton.addActionListener(this);

        JButton refreshButton = McVGuiUtils.makeImageButton(ICON_REFRESH, "Refresh");
        refreshButton.setActionCommand(GuiUtils.CMD_UPDATE);
        refreshButton.addActionListener(this);

        McVGuiUtils.setComponentWidth(loadButton, Width.DOUBLE);

        GroupLayout layout = new GroupLayout(outerPanel);
        outerPanel.setLayout(layout);
        layout.setHorizontalGroup(layout.createParallelGroup(LEADING).addGroup(TRAILING,
                layout.createSequentialGroup().addGroup(layout.createParallelGroup(TRAILING)
                        .addGroup(layout.createSequentialGroup().addContainerGap().addComponent(helpButton)
                                .addGap(GAP_RELATED).addComponent(refreshButton).addGap(GAP_RELATED)
                                .addComponent(cancelButton).addPreferredGap(RELATED).addComponent(loadButton))
                        .addGroup(LEADING, layout.createSequentialGroup().addContainerGap()
                                .addGroup(layout.createParallelGroup(LEADING)
                                        .addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
                                        .addGroup(layout.createSequentialGroup().addComponent(serverLabel)
                                                .addGap(GAP_RELATED)
                                                .addComponent(urlBox, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
                                                .addGap(GAP_UNRELATED).addComponent(connectButton))
                                        .addGroup(layout.createSequentialGroup().addComponent(statusLabelLabel)
                                                .addGap(GAP_RELATED).addComponent(statusLabel, DEFAULT_SIZE,
                                                        DEFAULT_SIZE, Short.MAX_VALUE)))))
                        .addContainerGap()));
        layout.setVerticalGroup(layout.createParallelGroup(LEADING).addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(BASELINE).addComponent(serverLabel).addComponent(urlBox)
                        .addComponent(connectButton))
                .addPreferredGap(UNRELATED).addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
                .addPreferredGap(UNRELATED)
                .addGroup(layout.createParallelGroup(BASELINE).addComponent(statusLabelLabel)
                        .addComponent(statusLabel))
                .addPreferredGap(UNRELATED).addGroup(layout.createParallelGroup(BASELINE).addComponent(loadButton)
                        .addComponent(cancelButton).addComponent(refreshButton).addComponent(helpButton))
                .addContainerGap()));
        return outerPanel;
    }
}