001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2016
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028package edu.wisc.ssec.mcidasv.chooser.adde;
029
030import static javax.swing.GroupLayout.DEFAULT_SIZE;
031import static javax.swing.GroupLayout.PREFERRED_SIZE;
032import static javax.swing.GroupLayout.Alignment.LEADING;
033import static javax.swing.GroupLayout.Alignment.TRAILING;
034import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
035
036import java.awt.Dimension;
037import java.awt.Point;
038import java.awt.event.ActionEvent;
039import java.awt.event.ActionListener;
040import java.awt.event.ItemEvent;
041import java.awt.event.ItemListener;
042import java.awt.event.MouseAdapter;
043import java.awt.event.MouseEvent;
044import java.beans.PropertyChangeEvent;
045import java.beans.PropertyChangeListener;
046import java.util.ArrayList;
047import java.util.Arrays;
048import java.util.Collections;
049import java.util.Enumeration;
050import java.util.Hashtable;
051import java.util.List;
052import java.util.Map;
053import java.util.Vector;
054
055import javax.swing.GroupLayout;
056import javax.swing.JButton;
057import javax.swing.JCheckBox;
058import javax.swing.JComboBox;
059import javax.swing.JComponent;
060import javax.swing.JLabel;
061import javax.swing.JList;
062import javax.swing.JMenuItem;
063import javax.swing.JPanel;
064import javax.swing.JPopupMenu;
065import javax.swing.JScrollPane;
066import javax.swing.JTextField;
067import javax.swing.ListModel;
068import javax.swing.ListSelectionModel;
069import javax.swing.SwingUtilities;
070import javax.swing.event.ListSelectionEvent;
071import javax.swing.event.ListSelectionListener;
072
073import org.w3c.dom.Element;
074
075import edu.wisc.ssec.mcidas.McIDASUtil;
076import edu.wisc.ssec.mcidas.adde.AddePointDataReader;
077import edu.wisc.ssec.mcidas.adde.DataSetInfo;
078
079import visad.DateTime;
080
081import ucar.unidata.data.sounding.RaobDataSet;
082import ucar.unidata.data.sounding.SoundingOb;
083import ucar.unidata.data.sounding.SoundingStation;
084import ucar.unidata.gis.mcidasmap.McidasMap;
085import ucar.unidata.idv.chooser.IdvChooserManager;
086import ucar.unidata.metdata.Station;
087import ucar.unidata.util.GuiUtils;
088import ucar.unidata.util.LogUtil;
089import ucar.unidata.util.Misc;
090import ucar.unidata.view.CompositeRenderer;
091import ucar.unidata.view.station.StationLocationMap;
092
093import edu.wisc.ssec.mcidasv.data.adde.AddeSoundingAdapter;
094import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
095import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width;
096
097/**
098 * A chooser class for selecting Raob data.
099 * Mostly just a wrapper around a
100 *  {@link ucar.unidata.view.sounding.SoundingSelector}
101 * that does most of the work
102 *
103 * @author IDV development team
104 * @version $Revision$Date: 2011/03/24 16:06:32 $
105 */
106public class AddeRaobChooser extends AddePointDataChooser {
107    
108    /** Property for the data type. */
109    public static String DATA_TYPE = "RAOB";
110    
111    /** Significant level objects corresponding to mandatory level objects */
112    private Hashtable descriptorTable2 = new Hashtable();
113    private JComboBox descriptorComboBox2 = new JComboBox();
114    protected String[] descriptorNames2;
115    private String LABEL_SELECT2 = " -- Optional Significant Levels -- ";
116    private JCheckBox showAll = new JCheckBox("Show all sources");
117    private Object readSatelliteTask;
118
119    /** This flag keeps track of observed/satellite soundings */
120    private boolean satelliteSounding = false;
121    
122    /** Selector for times when pointing to satellite data (required field) */
123    private JLabel satelliteTimeLabel = McVGuiUtils.makeLabelRight("");
124    private JPanel satelliteTimePanel;
125    private JButton satelliteTimeButton;
126    private JComboBox satelliteTimeComboBox;
127    private JTextField satellitePixelTextField;
128    private String satelliteTime = "";
129    private String satellitePixel = "1";
130    private List satelliteTimes = new ArrayList();
131    
132    /** We need to be able to enable/disable this based on sounding type */
133    private JCheckBox mainHoursCbx;
134    
135    /** This is a virtual timestamp that tracks if the threaded adde connection should be aborted or not */
136    private int connectionStep = 0;
137
138    /** handle on the station update task */
139    private Object readStationTask;
140    
141    /** list of times */
142    private JList timesList;
143
144    /** list of observations */
145    private JList obsList;
146
147    /** selected observations */
148    private Vector selectedObs = new Vector();
149    
150    /** sounding adapter used by this selector */
151    AddeSoundingAdapter soundingAdapter;
152    
153    /** flag for 0 and 12z only */
154    private boolean showMainHoursOnly = true;
155
156    /**
157     * Construct a {@code RaobChooser} using the manager
158     * and the root XML that defines this object.
159     *
160     * @param mgr {@code IdvChooserManager} that controls this chooser.
161     * @param root Root element of the XML that defines this object.
162     */
163    public AddeRaobChooser(IdvChooserManager mgr, Element root) {
164        super(mgr, root);
165        
166        setSelectString(" -- Select Mandatory Levels -- ");
167        
168        descriptorComboBox2.addItemListener(new ItemListener() {
169            public void itemStateChanged(ItemEvent e) {
170                if ( !ignoreDescriptorChange
171                        && (e.getStateChange() == e.SELECTED)) {
172                    descriptorChanged(false);
173                }
174            }
175        });
176        descriptorComboBox2.setEnabled(false);
177        
178        showAll.addItemListener(new ItemListener() {
179            public void itemStateChanged(ItemEvent e) {
180                if (getState() == STATE_CONNECTED) {
181                    doConnect();
182                }
183            }
184        });
185        
186        satelliteTimeComboBox = new JComboBox();
187        satelliteTimeComboBox.setEditable(true);
188        satelliteTimeComboBox.addItemListener(new ItemListener() {
189            public void itemStateChanged(ItemEvent e) {
190                if (e.getStateChange()==e.DESELECTED) return;
191                satelliteTime = satelliteTimeComboBox.getSelectedItem().toString();
192                Misc.run(new Runnable() {
193                    public void run() {
194                        setAvailableStations(true);
195                    }
196                });
197            }
198        });
199
200        satelliteTimeButton = McVGuiUtils.makeImageButton(ICON_UPDATE, "Request list of available times from server");
201        satelliteTimeButton.addActionListener(new ActionListener() {
202            public void actionPerformed(ActionEvent e) {
203                sampleTimes();
204            }
205        });
206
207        satellitePixelTextField = new JTextField(satellitePixel);
208        satellitePixelTextField.addActionListener(new ActionListener() {
209            public void actionPerformed(ActionEvent e) {
210                satellitePixel = satellitePixelTextField.getText().replace('-', ' ');
211                Misc.run(new Runnable() {
212                    public void run() {
213                        setAvailableStations(true);
214                    }
215                });
216            }
217        });
218    }
219    
220    /**
221     * Tell the AddeChooser our name
222     *
223     * @return  The name
224     */
225    public String getDataName() {
226        return "Sounding Data";
227    }
228
229    /**
230     * Get the descriptor widget label.
231     *
232     * @return  label for the descriptor  widget
233     */
234    public String getDescriptorLabel() { 
235        return "Soundings"; 
236    }
237    
238    /**
239     * get default display to create
240     *
241     * @return default display
242     */
243    protected String getDefaultDisplayType() {
244        return "raob_skewt";
245    }
246    
247    /**
248     * Get the mandatory dataset name.
249     *
250     * @return mandatory dataset name
251     */
252    private String getMandatoryDataset() {
253        if (getDescriptor() == null) return null;
254        return getGroup() + "/" + getDescriptor();
255    }
256
257    /**
258     * Get the sig level dataset name.
259     *
260     * @return sig level dataset name
261     */
262    private String getSigLevelDataset() {
263        if (getDescriptor2() == null) return getMandatoryDataset();
264        return getGroup() + "/" + getDescriptor2();
265    }
266    
267    /**
268     * Add a listener to the given combobox that will set the
269     * state to unconnected.
270     *
271     * @param box The box to listen to.
272     */
273    protected void clearOnChange(final JComboBox box) {
274        box.addItemListener(new ItemListener() {
275            public void itemStateChanged(ItemEvent e) {
276                if ( !ignoreStateChangedEvents) {
277                    setState(STATE_UNCONNECTED);
278                    GuiUtils.setListData(descriptorComboBox, new Vector());
279                    GuiUtils.setListData(descriptorComboBox2, new Vector());
280                }
281            }
282        });
283    }
284    
285    /**
286     * Reset the descriptor stuff.
287     */
288    protected void resetDescriptorBox() {
289        ignoreDescriptorChange = true;
290        descriptorComboBox.setSelectedItem(LABEL_SELECT);
291        if (descriptorComboBox2 != null) {
292            descriptorComboBox2.setSelectedItem(LABEL_SELECT2);
293            descriptorComboBox2.setEnabled(false);
294        }
295        ignoreDescriptorChange = false;
296    }
297    
298    /**
299     * Initialize the descriptor list from a list of names.
300     *
301     * @param names2 List of names.
302     */
303    protected void setDescriptors2(String[] names2) {
304        synchronized (WIDGET_MUTEX) {
305            ignoreDescriptorChange = true;
306            descriptorComboBox2.removeAllItems();
307            descriptorComboBox2.addItem(LABEL_SELECT2);
308            descriptorNames2 = names2;
309            if ((names2 == null) || (names2.length == 0)) {
310                ignoreDescriptorChange = false;
311                return;
312            }
313            for (int j = 0; j < names2.length; j++) {
314                descriptorComboBox2.addItem(names2[j]);
315            }
316            ignoreDescriptorChange = false;
317        }
318    }
319    
320    /**
321     * Get the selected descriptor.
322     *
323     * @return  the currently selected descriptor.
324     */
325    protected String getDescriptor2() {
326        if (descriptorTable2 == null) {
327            return null;
328        }
329        String selection = (String) descriptorComboBox2.getSelectedItem();
330        if (selection == null) {
331            return null;
332        }
333        if (selection.equals(LABEL_SELECT2)) {
334            return null;
335        }
336        if (!selection.contains(nameSeparator)) {
337            return (String)descriptorTable2.get(selection);
338        }
339        else {
340            String[] toks = selection.split(nameSeparator);
341            String key = toks[1].trim();
342            return (String)descriptorTable2.get(key);
343        }
344    }
345
346    /**
347     * Method to call if the server changed.
348     */
349    protected void connectToServer() {
350        clearStations();
351        setDescriptors2(null);
352        super.connectToServer();
353        setAvailableStations(true);
354    }
355    
356    /**
357     * Do we have times selected.
358     * @return Do we have times
359     */
360    public boolean timesOk() {
361        return haveTimeSelected();
362    }
363
364    /**
365     * Are there any times selected.
366     *
367     * @return Any times selected.
368     */
369    protected boolean haveTimeSelected() {
370        if (selectedObs!=null) {
371            if (selectedObs.size() > 0) return true;
372        }
373        return false;
374    }
375    
376    /**
377     * Do nothing for read times...
378     * doUpdateInner handles all of this with an AddeSoundingAdapter
379     */
380    public void readTimes() { }
381    
382    /**
383     * Wrapper for sampleTimesInner
384     * Starts in a new thread and handles UI updating
385     */
386    private void sampleTimes() {
387        readSatelliteTask = startTask();
388        enableWidgets();
389        Misc.run(new Runnable() {
390            public void run() {
391                sampleTimesInner();
392                if(stopTaskAndIsOk(readSatelliteTask)) {
393                    readSatelliteTask = null;
394                    GuiUtils.setListData(satelliteTimeComboBox, satelliteTimes);
395                    revalidate();
396                } else {
397                    //User pressed cancel
398                    setState(STATE_UNCONNECTED);
399                }
400            }
401        });
402        updateStatus();
403    }
404    
405    /**
406     * Different way of reading times... for satellite soundings, do the following:
407     * PTLIST GROUP/DESCRIPTOR.Z SEL='ROW X; COL Y' PAR=TIME
408     *   where Z starts at 0 (expect an error), then goes to 1 and increases monotonically in outer loop until error
409     *     and X starts at 1 and increases monotonically in middle loop until error
410     *       and Y starts at 1 and increases by 25000 or so in inner loop until error
411     * This samples times across the dataset
412     */
413    private void sampleTimesInner() {
414        if (getDescriptor()==null) return;
415        showWaitCursor();
416        int posMax = 9999;
417        int rowMax = 9999;
418        int colMax = 999999;
419        int colSkip = 24000;
420        int consecutiveFailures = 0;
421        Map<String, String> acctInfo = getAccountingInfo();
422        String user = acctInfo.get("user");
423        String proj = acctInfo.get("proj");
424        String appendUserProj = "";
425        if (!(user.equals("") || proj.equals("")))
426            appendUserProj += "&user=" + user + "&proj=" + proj;
427        satelliteTimes = new ArrayList();
428        for (int pos = 0; pos < posMax; pos++) {
429            for (int row=1; row<rowMax; row++) {
430                for (int col=1; col<colMax; col+=colSkip) {
431                    
432                    String[] paramString = new String[] {
433                            "group", getGroup(), "descr", getDescriptor(), "param", "DAY TIME", "num", "1",
434                            "pos", Integer.toString(pos),
435                            "select", "'ROW " + row + "; COL " + col + "'"
436                        };
437                        String request = Misc.makeUrl("adde", getServer(), "/point", paramString);
438                        request += appendUserProj;
439                        try {
440                            AddePointDataReader dataReader = new AddePointDataReader(request);
441                            int[][] data = dataReader.getData();
442                            if (data[0].length == 0) throw new Exception();
443                            for (int i = 0; i < data[0].length; i++) {
444                                int day = data[0][i];
445                                int time = data[1][i];
446                                DateTime dt = new DateTime(McIDASUtil.mcDayTimeToSecs(day, time));
447                                String timeString = dt.timeString().substring(0,5);
448                                if (satelliteTimes.indexOf(timeString) < 0) {
449                                    satelliteTimes.add(timeString);
450                                }                               
451                            }
452                            // Reset consecutive failure count when you get good data
453                            consecutiveFailures=0;
454                        }
455                        catch (Exception e) {
456                                                                    
457                            // We are at the beginning of a position
458                            // Log a failure and increment the position
459                            if (col==1 && row==1) {
460                                row=rowMax;
461                                consecutiveFailures++;
462                                // If we have failed a few times in a row, bail completely
463                                if (consecutiveFailures > 2) {
464                                    pos=posMax;
465                                }
466                            }
467                            
468                            // If we failed at the first column, increment the position
469                            if (col==1) row=rowMax;
470
471                            // We have an exception, increment the row
472                            col = colMax;
473                            
474                        }
475                }
476            }
477        }
478        
479        Collections.sort(satelliteTimes);
480        showNormalCursor();
481    }
482        
483    /**
484     *  Generate a list of image descriptors for the descriptor list.
485     */
486    protected void readDescriptors() {
487        try {
488            StringBuffer buff   = getGroupUrl(REQ_DATASETINFO, getGroup());
489            buff.append("&type=" + getDataType());
490            DataSetInfo  dsinfo = new DataSetInfo(buff.toString());
491            descriptorTable = dsinfo.getDescriptionTable();
492            descriptorTable2 = new Hashtable();
493            
494            if (!showAll.isSelected()) {
495                // Filter out anything not Upper Air Mandatory or Significant
496                for (Enumeration enumeration = descriptorTable.keys(); enumeration.hasMoreElements();) {
497                    Object key = enumeration.nextElement();
498                    String keyString = key.toString();
499                    String descriptorString = descriptorTable.get(key).toString();
500                    if (keyString.toUpperCase().indexOf("MAND") >= 0 || descriptorString.indexOf("MAND") >= 0) {
501                        continue;
502                    }
503                    if (keyString.toUpperCase().indexOf("SIG") >= 0 || descriptorString.indexOf("SIG") >= 0) {
504                        descriptorTable2.put(key, descriptorTable.get(key));
505                        descriptorTable.remove(key);
506                        continue;
507                    }
508                    if (keyString.toUpperCase().indexOf("UPPER AIR") >= 0 ||
509                            descriptorString.indexOf("UPPER") >= 0 ||
510                            descriptorString.indexOf("UPPR") >= 0) {
511                        descriptorTable2.put(key, descriptorTable.get(key));
512                        continue;
513                    }
514                    if (keyString.toUpperCase().indexOf("SOUNDER") >= 0 ||
515                            descriptorString.indexOf("SND") >= 0 ||
516                            descriptorString.indexOf("SNDR") >= 0) {
517                        descriptorTable2.put(key, descriptorTable.get(key));
518                        continue;
519                    }
520                    if (keyString.toUpperCase().indexOf("GRET") >= 0 || descriptorString.indexOf("GRET") >= 0) {
521                        descriptorTable2.put(key, descriptorTable.get(key));
522                        continue;
523                    }
524                    if (keyString.toUpperCase().indexOf("SRET") >= 0 || descriptorString.indexOf("SRET") >= 0) {
525                        descriptorTable2.put(key, descriptorTable.get(key));
526                        continue;
527                    }
528                    descriptorTable.remove(key);
529                }
530            }
531            else {
532                // We have been told to Show All... put all descriptors into both categories
533                for (Enumeration enumeration = descriptorTable.keys(); enumeration.hasMoreElements();) {
534                    Object key = enumeration.nextElement();
535                    descriptorTable2.put(key, descriptorTable.get(key));
536                }
537            }
538            
539            String[]    names       = new String[descriptorTable.size()];            
540            Enumeration enumeration = descriptorTable.keys();
541            for (int i = 0; enumeration.hasMoreElements(); i++) {
542                Object thisElement = enumeration.nextElement();
543                if (!isLocalServer())
544                    names[i] = descriptorTable.get(thisElement).toString() + nameSeparator + thisElement.toString();
545                else
546                    names[i] = thisElement.toString();
547            }
548            Arrays.sort(names);
549            setDescriptors(names);
550            
551            String[]    names2       = new String[descriptorTable2.size()];
552            Enumeration enumeration2 = descriptorTable2.keys();
553            for (int i = 0; enumeration2.hasMoreElements(); i++) {
554                Object thisElement2 = enumeration2.nextElement();
555                if (!isLocalServer())
556                    names2[i] = descriptorTable2.get(thisElement2).toString() + nameSeparator + thisElement2.toString();
557                else
558                    names2[i] = nameSeparator + thisElement2.toString();
559            }
560            Arrays.sort(names2);
561            setDescriptors2(names2);
562            
563            setState(STATE_CONNECTED);
564        } catch (Exception e) {
565            handleConnectionError(e);
566        }
567    }
568    
569    /**
570     * See if we are pointing to observed or satellite soundings
571     */
572    private void checkSetObsSat() {
573        System.out.println("checkSetObsSat: init");
574        if (getServer() == null || getGroup() == null || getDescriptor() == null) return;
575        System.out.println("checkSetObsSat: start");
576        satelliteSounding = false;
577        showWaitCursor();
578        Map<String, String> acctInfo = getAccountingInfo();
579        System.out.println("got acct info");
580        String user = acctInfo.get("user");
581        String proj = acctInfo.get("proj");
582        String[] paramString = new String[] {
583            "group", getGroup(), "descr", getDescriptor(), "param", "ZS", "num", "1", "pos", "all"
584        };
585        String request = Misc.makeUrl("adde", getServer(), "/point", paramString);
586        if (!(user.equals("") || proj.equals("")))
587            request += "&user=" + user + "&proj=" + proj;
588        System.out.println("Making request: " + request);
589        try {
590            AddePointDataReader dataReader = new AddePointDataReader(request);
591        }
592        catch (Exception e) {
593            if (e.getMessage().indexOf("Accounting data") >= 0) handleConnectionError(e);
594            else satelliteSounding = true;
595        }
596        
597        showNormalCursor();
598        System.out.println("checkSetObsSat: done: " + satelliteSounding);
599    }
600    
601    /**
602     * Override clearStations to clear times as well
603     */
604    protected void clearStations() {
605        super.clearStations();
606        clearTimes();
607    }
608    
609    /**
610     * Remove all times from the user lists
611     */
612    protected void clearTimes() {
613        if (obsList!=null) obsList.setListData(new Vector());
614        if (timesList!=null) timesList.setListData(new Vector());
615    }
616    
617    /**
618     * Update labels, etc.
619     */
620    protected void updateStatus() {
621        super.updateStatus();
622        if (getState() != STATE_CONNECTED) {
623            resetDescriptorBox();
624            clearStations();
625        }
626        else {
627            if (getDescriptor() == null) {
628                if (descriptorComboBox2 != null) {
629                    descriptorComboBox2.setSelectedItem(LABEL_SELECT2);
630                }
631                clearStations();
632                setStatus("Select mandatory levels dataset");
633                return;
634            }
635        }
636        if (readSatelliteTask!=null) {
637            if(taskOk(readSatelliteTask)) {
638                setStatus("Reading sounding info from server");
639            } else {
640                readSatelliteTask  = null;
641                setState(STATE_UNCONNECTED);
642            }
643        }
644        if (readStationTask!=null) {
645            if(taskOk(readStationTask)) {
646                setStatus("Reading available stations from server");
647            } else {
648                readStationTask  = null;
649                setState(STATE_UNCONNECTED);
650            }
651        }
652        enableWidgets();
653    }
654    
655    /**
656     * Overwrite base class method to create the station map
657     * with the appropriate properties.
658     *
659     * @return The new station map
660     */
661    protected StationLocationMap createStationMap() {
662        return new StationLocationMap(true) {
663            public void setDeclutter(boolean declutter) {
664                super.setDeclutter(declutter);
665                updateStatus();
666            }
667        };
668    }
669    
670    /**
671     * Initialize the stations
672     *
673     * @param stationMap The station map
674     */
675    protected void initStationMap(StationLocationMap stationMap) {
676        CompositeRenderer renderer = new CompositeRenderer();
677        renderer.addRenderer(new McidasMap("/auxdata/maps/OUTLSUPW"));
678        renderer.addRenderer(new McidasMap("/auxdata/maps/OUTLSUPU"));
679        renderer.setColor(MAP_COLOR);
680        stationMap.setMapRenderer(renderer);
681
682        stationMap.addPropertyChangeListener(new PropertyChangeListener() {
683            public void propertyChange(PropertyChangeEvent pe) {
684                if (pe.getPropertyName().equals(
685                        StationLocationMap.SELECTED_PROPERTY)) {
686                    stationSelected((Station) pe.getNewValue());
687                } else if (pe.getPropertyName().equals(
688                        StationLocationMap.UNSELECTED_PROPERTY)) {
689                    stationUnselected((Station) pe.getNewValue());
690                } else if (pe.getPropertyName().equals(
691                    StationLocationMap.ALL_UNSELECTED_PROPERTY)) {
692                    unselectAll();
693                }
694            }
695        });
696
697    }
698
699    /**
700     * Handle a station selection
701     *
702     * @param station  selected station
703     */
704    private void stationSelected(Station station) {
705        List selectedTimes = getSelectedTimes();
706        if ((selectedTimes == null) || (selectedTimes.size() < 1)) {
707            return;
708        }
709        for (int i = 0; i < selectedTimes.size(); i++) {
710            DateTime dt = (DateTime) selectedTimes.get(i);
711            List times =
712                soundingAdapter.getSoundingTimes((SoundingStation) station);
713            if ((times != null) && (times.size() > 0)) {
714                if (times.contains(dt)) {
715                    SoundingOb newObs = new SoundingOb((SoundingStation)station, dt);
716                    if ( !selectedObs.contains(newObs)) {
717                        selectedObs.add(newObs);
718                    }
719                }
720            }
721        }
722        obsList.setListData(selectedObs);
723        updateStatus();
724    }
725
726    /**
727     * Unselect a station
728     *
729     * @param station  station to unselect
730     */
731    private void stationUnselected(Station station) {
732        List selectedTimes = getSelectedTimes();
733        if ((selectedTimes == null) || (selectedTimes.size() < 1)) {
734            return;
735        }
736        for (int i = 0; i < selectedTimes.size(); i++) {
737            SoundingOb newObs = new SoundingOb((SoundingStation)station,
738                                    (DateTime) selectedTimes.get(i));
739            if (selectedObs.contains(newObs)) {
740                selectedObs.remove(newObs);
741            }
742        }
743        obsList.setListData(selectedObs);
744        updateStatus();
745    }
746    
747    /**
748     * Unselect all station
749     */
750    private void unselectAll() {
751        List selectedTimes = getSelectedTimes();
752        if ((selectedTimes == null) || (selectedTimes.size() < 1)) {
753            return;
754        }
755        selectedObs.removeAllElements();
756        obsList.setListData(selectedObs);
757        updateStatus();
758    }
759
760    /**
761     *  This looks in the selectedList of SoundingOb-s for all stations
762     *  that are selected for the current time. It creates and returns
763     *  a list of the Station-s held by these current SoundingOb-s
764     *
765     * @return list of currently selected stations
766     */
767    // Question: why does this care about current time?
768    //           more than one time can be selected...
769    private List getCurrentSelectedStations() {
770        List     current     = new ArrayList();
771//        DateTime currentTime = getSelectedTime();
772        for (int i = 0; i < selectedObs.size(); i++) {
773            SoundingOb ob = (SoundingOb) selectedObs.get(i);
774//            if (ob.getTimestamp().equals(currentTime)) {
775                current.add(ob.getStation());
776//            }
777        }
778        return current;
779    }
780    
781    /**
782     * Get the current list of stations that are selected
783     */
784    private void setStations() {
785        stationMap.setStations(soundingAdapter.getStations(),
786                getCurrentSelectedStations(), stationMap.getDeclutter());
787        stationMap.redraw();
788    }
789
790    /**
791     * Set the SoundingAdapter used by this selector
792     *
793     * @param newAdapter   new adapter
794     */
795    protected void setSoundingAdapter(AddeSoundingAdapter newAdapter) {
796        soundingAdapter = newAdapter;
797        selectedObs.removeAllElements();
798        obsList.setListData(selectedObs);
799        setStations();
800        setTimesListData(null);
801        updateStatus();
802    }
803
804    /**
805     * Set the data in the times list
806     *
807     * @param selected  a list of times that should be selected
808     */
809    private void setTimesListData(List selected) {
810        if (soundingAdapter==null) return;
811        DateTime[] times = soundingAdapter.getSoundingTimes();
812        if (times != null) {
813            timesList.setListData(times);
814            if ((selected != null) && (selected.size() > 0)) {
815                ListModel lm      = timesList.getModel();
816                int[]     indices = new int[times.length];
817                int       l       = 0;
818                for (int i = 0; i < lm.getSize(); i++) {
819                    if (selected.contains(lm.getElementAt(i))) {
820                        indices[l++] = i;
821                    }
822                }
823                if (l > 0) {
824                    int[] selectedIndices = new int[l];
825                    System.arraycopy(indices, 0, selectedIndices, 0, l);
826                    timesList.setSelectedIndices(selectedIndices);
827                    timesList.ensureIndexIsVisible(selectedIndices[l - 1]);
828                } else {
829                    timesList.setSelectedValue(times[times.length - 1], true);
830                }
831            } else if (times.length > 0) {
832                timesList.setSelectedValue(times[times.length - 1], true);
833            }
834        } else {
835            LogUtil.userMessage("No data available");
836        }
837    }
838
839    /**
840     * Get the selected time.
841     *
842     * @return the time selected in the list
843     */
844    public DateTime getSelectedTime() {
845        return (DateTime)timesList.getSelectedValue();
846    }
847
848    /**
849     * Get the selected time.
850     *
851     * @return the time selected in the list
852     */
853    public List getSelectedTimes() {
854        return timesList.getSelectedValuesList();
855    }
856    
857    /**
858     * Create the list of times.
859     *
860     * @return List of times
861     */
862    private JList createTimesList() {
863        timesList = new JList();
864        timesList.setSelectionMode(
865            ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
866        timesList.addListSelectionListener(new ListSelectionListener() {
867            public void valueChanged(ListSelectionEvent e) {
868                if ( !timesList.isSelectionEmpty()
869                        && !e.getValueIsAdjusting()) {
870                    newTimes(timesList.getSelectedValuesList());
871                }
872            }
873        });
874        return timesList;
875    }
876    
877    /**
878     * Set the new times
879     *
880     * @param times new times to use
881     */
882    private void newTimes(List times) {
883        if (stationMap == null) return;
884        List current = stationMap.getSelectedStations();
885        if ((current == null) || (current.size() < 1)) {
886            return;
887        }
888        selectedObs.removeAllElements();
889        for (int i = 0; i < times.size(); i++) {
890            DateTime dt = (DateTime) times.get(i);
891            for (int j = 0; j < current.size(); j++) {
892                SoundingStation ss      = (SoundingStation) current.get(j);
893                List            ssTimes =
894                    soundingAdapter.getSoundingTimes(ss);
895                if ((ssTimes != null) && (times.size() > 0)) {
896                    if (ssTimes.contains(dt)) {
897                        SoundingOb newObs = new SoundingOb(ss, dt);
898                        if ( !selectedObs.contains(newObs)) {
899                            selectedObs.add(newObs);
900                        }
901                    }
902                }
903            }
904        }
905        obsList.setListData(selectedObs);
906        updateStatus();
907    }
908
909    /**
910     * Get the selected soundings
911     *
912     * @return List of selected soundings
913     */
914    public List getSelectedSoundings() {
915        return selectedObs;
916    }
917    
918    /**
919     * Handle the selection of an ob
920     *
921     * @param event  MouseEvent for selection
922     */
923    private void obsListClicked(MouseEvent event) {
924        if ( !SwingUtilities.isRightMouseButton(event)) {
925            return;
926        }
927        int index = obsList.locationToIndex(new Point(event.getX(),
928                        event.getY()));
929        if ((index < 0) || (index >= selectedObs.size())) {
930            return;
931        }
932
933        final SoundingOb obs   = (SoundingOb) selectedObs.get(index);
934
935        JPopupMenu       popup = new JPopupMenu();
936        JMenuItem        mi;
937
938        mi = new JMenuItem("Remove " + obs);
939        mi.addActionListener(new ActionListener() {
940            public void actionPerformed(ActionEvent e) {
941                selectedObs.remove(obs);
942                obsList.setListData(selectedObs);
943                updateStatus();
944                stationMap.setSelectedStations(getCurrentSelectedStations());
945            }
946        });
947
948        popup.add(mi);
949
950        mi = new JMenuItem("Remove all");
951        mi.addActionListener(new ActionListener() {
952            public void actionPerformed(ActionEvent e) {
953                selectedObs.removeAllElements();
954                obsList.setListData(selectedObs);
955                updateStatus();
956                stationMap.setSelectedStations(getCurrentSelectedStations());
957            }
958        });
959
960        popup.add(mi);
961
962        popup.show(obsList, event.getX(), event.getY());
963    }
964    
965    /**
966     * Update the widget with the latest data.
967     *
968     * @throws Exception On badness
969     */
970    public void handleUpdate() throws Exception {
971        if (getState() != STATE_CONNECTED) {
972            //If not connected then update the server list
973            updateServerList();
974        } else {
975            //If we are already connected then update the rest of the chooser
976            descriptorChanged();
977        }
978        updateStatus();
979    }
980    
981    /**
982     * Enable or disable the GUI widgets based on what has been
983     * selected.
984     */
985    protected void enableWidgets() {
986        super.enableWidgets();
987        boolean readingTask = (readSatelliteTask!=null || readStationTask!=null);
988        if (mainHoursCbx != null) mainHoursCbx.setVisible(!satelliteSounding);
989        if (descriptorComboBox2 != null) {
990            if (satelliteSounding) setDescriptors2(null);
991            descriptorComboBox2.setVisible(!satelliteSounding);
992            descriptorComboBox2.setEnabled(!readingTask &&
993                    descriptorComboBox.getSelectedIndex() > 0);
994        }
995        if (satelliteTimePanel!=null) {
996            satelliteTimePanel.setVisible(satelliteSounding);
997            GuiUtils.enableTree(satelliteTimePanel, !readingTask);
998            if (satelliteSounding)
999                satelliteTimeLabel.setText("Time:");
1000            else
1001                satelliteTimeLabel.setText("");
1002        }
1003        if (showAll!=null) showAll.setEnabled(!readingTask);
1004    }
1005    
1006    /**
1007     * Respond to a change in the descriptor list.
1008     */
1009    protected void descriptorChanged() {
1010        descriptorChanged(true);
1011    }
1012    
1013    /**
1014     * Respond to a change in the descriptor list.
1015     */
1016    protected void descriptorChanged(final boolean checkObsSat) {
1017        satelliteSounding = false;
1018        readSatelliteTask = startTask();
1019        enableWidgets();
1020        Misc.run(new Runnable() {
1021            public void run() {
1022                if (checkObsSat) checkSetObsSat();
1023                setAvailableStations(true);
1024                updateStatus();
1025                if(stopTaskAndIsOk(readSatelliteTask)) {
1026                    readSatelliteTask = null;
1027                    updateStatus();
1028                    revalidate();
1029                } else {
1030                    //User pressed cancel
1031                    setState(STATE_UNCONNECTED);
1032                }
1033            }
1034        });
1035        updateStatus();
1036    }
1037
1038    /**
1039     *  Update the station map with available stations.
1040     */
1041    private void setAvailableStations(final boolean forceNewAdapter) {
1042        if (getMandatoryDataset() == null) {
1043            updateStatus();
1044            return;
1045        }
1046        showWaitCursor();
1047        readStationTask = startTask();
1048        clearSelectedStations();
1049        updateStatus();
1050        doUpdateInner(forceNewAdapter);
1051        if(stopTaskAndIsOk(readStationTask)) {
1052            readStationTask = null;
1053            updateStatus();
1054            revalidate();
1055        } else {
1056            //User pressed cancel
1057            setState(STATE_UNCONNECTED);
1058        }
1059        showNormalCursor();
1060    }
1061
1062    
1063    /**
1064     * Really update station map.
1065     *
1066     * @param forceNewAdapter If true then create a new adapter.
1067     *                        Else, tell the existing one to update.
1068     */
1069    private void doUpdateInner(final boolean forceNewAdapter) {
1070        try {
1071            if (forceNewAdapter || soundingAdapter == null) {
1072                AddeSoundingAdapter newAdapter;
1073                if (!satelliteSounding) {
1074                    newAdapter = new AddeSoundingAdapter(getServer(),
1075                        getMandatoryDataset(),
1076                        getSigLevelDataset(),
1077                        showMainHoursOnly,
1078                        this);
1079                }
1080                else {
1081                    newAdapter = new AddeSoundingAdapter(getServer(),
1082                        getMandatoryDataset(),
1083                        getSigLevelDataset(),
1084                        satelliteTime,
1085                        satellitePixel,
1086                        this);
1087                }
1088                soundingAdapter = null;
1089                setSoundingAdapter(newAdapter);
1090            } else {
1091                List times = getSelectedTimes();
1092                soundingAdapter.update();
1093                setStations();
1094                setTimesListData(times);
1095            }
1096        } catch (Exception exc) {
1097            LogUtil.logException("Updating sounding data", exc);
1098        }
1099    }
1100    
1101    /**
1102     * Load the data source in a thread
1103     */
1104    public void doLoadInThread() {
1105        List soundings = getSelectedSoundings();
1106        if (soundings.size() == 0) {
1107            userMessage("Please select one or more soundings.");
1108            return;
1109        }
1110        Hashtable ht = new Hashtable();
1111        getDataSourceProperties(ht);
1112
1113        makeDataSource(new RaobDataSet(soundingAdapter, soundings), DATA_TYPE, ht);
1114        saveServerState();
1115    }
1116    
1117    /**
1118     * Add the times selector to the component.
1119     * @return superclass component with extra stuff
1120     */
1121    protected JPanel makeTimesPanel() {
1122        
1123        // Make the 0 & 12 checkbox
1124        mainHoursCbx = new JCheckBox("00 & 12Z only", showMainHoursOnly);
1125        mainHoursCbx.addActionListener(new ActionListener() {
1126            public void actionPerformed(ActionEvent ev) {
1127                showMainHoursOnly = ((JCheckBox) ev.getSource()).isSelected();
1128                Misc.run(new Runnable() {
1129                    public void run() {
1130                        setAvailableStations(true);
1131                    }
1132                });
1133            }
1134        });
1135
1136        // Make the select panel
1137        JScrollPane availablePanel = new JScrollPane(createTimesList());
1138        availablePanel.setPreferredSize(new Dimension(175, 50));
1139        JPanel selectPanel = GuiUtils.centerBottom(availablePanel, mainHoursCbx);
1140        selectPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Available"));
1141        
1142        // Make the selected panel
1143        obsList = new JList();
1144        obsList.addMouseListener(new MouseAdapter() {
1145            public void mouseClicked(MouseEvent e) {
1146                obsListClicked(e);
1147            }
1148        });
1149        JScrollPane selectedPanel = new JScrollPane(obsList);
1150        selectedPanel.setPreferredSize(new Dimension(175, 50));
1151        selectedPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Selected"));
1152        
1153        // Make the container panel
1154        JPanel timesPanel = new JPanel();
1155        selectPanel.setBackground(timesPanel.getBackground());
1156        selectedPanel.setBackground(timesPanel.getBackground());
1157        
1158        GroupLayout layout = new GroupLayout(timesPanel);
1159        timesPanel.setLayout(layout);
1160        layout.setHorizontalGroup(
1161            layout.createParallelGroup(LEADING)
1162            .addGroup(layout.createSequentialGroup()
1163//                .addContainerGap()
1164                .addComponent(selectPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1165                .addGap(GAP_RELATED)
1166                .addComponent(selectedPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1167                )
1168//                .addContainerGap())
1169        );
1170        layout.setVerticalGroup(
1171            layout.createParallelGroup(LEADING)
1172            .addGroup(layout.createSequentialGroup()
1173//                .addContainerGap()
1174                .addGroup(layout.createParallelGroup(TRAILING)
1175                    .addComponent(selectedPanel, LEADING, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
1176                    .addComponent(selectPanel, LEADING, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
1177                    )
1178//                .addContainerGap())
1179        );
1180        
1181        JComponent temp = super.makeTimesPanel();
1182        temp.setBorder(javax.swing.BorderFactory.createEtchedBorder());
1183        McVGuiUtils.setComponentHeight(timesPanel, temp);
1184
1185        return timesPanel;
1186    }
1187    
1188    /**
1189     * Make the UI for this selector.
1190     * 
1191     * @return The gui
1192     */   
1193    public JComponent doMakeContents() {
1194        JPanel myPanel = new JPanel();
1195        
1196        McVGuiUtils.setComponentWidth(descriptorComboBox, Width.DOUBLEDOUBLE);
1197        McVGuiUtils.setComponentWidth(descriptorComboBox2, descriptorComboBox);
1198        McVGuiUtils.setComponentWidth(satelliteTimeComboBox, Width.DOUBLE);
1199        McVGuiUtils.setComponentWidth(satellitePixelTextField, Width.DOUBLE);
1200        
1201        satelliteTimePanel = McVGuiUtils.sideBySide(
1202                McVGuiUtils.sideBySide(satelliteTimeComboBox, satelliteTimeButton),
1203                McVGuiUtils.makeLabeledComponent("IDN:", satellitePixelTextField)
1204                );
1205        satelliteTimePanel.setVisible(false);
1206
1207        JPanel extraPanel = McVGuiUtils.sideBySide(
1208                GuiUtils.left(McVGuiUtils.sideBySide(descriptorComboBox2, satelliteTimePanel, 0)),
1209                GuiUtils.right(showAll));
1210        
1211//      McVGuiUtils.setComponentWidth(extraPanel, descriptorComboBox);
1212        
1213        JLabel stationLabel = McVGuiUtils.makeLabelRight("Stations:");
1214        addServerComp(stationLabel);
1215
1216        JComponent stationPanel = getStationMap();
1217        registerStatusComp("stations", stationPanel);
1218//        addServerComp(stationPanel);
1219        addDescComp(stationPanel);
1220        
1221        JLabel timesLabel = McVGuiUtils.makeLabelRight("");
1222//        addServerComp(timesLabel);
1223        addDescComp(timesLabel);
1224        
1225        JPanel timesPanel = makeTimesPanel();
1226//        timesPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
1227//        addServerComp(timesPanel);
1228        addDescComp(timesPanel);
1229        
1230        enableWidgets();
1231        updateStatus();
1232
1233        GroupLayout layout = new GroupLayout(myPanel);
1234        myPanel.setLayout(layout);
1235        layout.setHorizontalGroup(
1236            layout.createParallelGroup(LEADING)
1237            .addGroup(layout.createSequentialGroup()
1238                .addGroup(layout.createParallelGroup(LEADING)
1239                    .addGroup(layout.createSequentialGroup()
1240                        .addComponent(descriptorLabel)
1241                        .addGap(GAP_RELATED)
1242                        .addComponent(descriptorComboBox))
1243                    .addGroup(layout.createSequentialGroup()
1244                        .addComponent(satelliteTimeLabel)
1245                        .addGap(GAP_RELATED)
1246                        .addComponent(extraPanel))
1247                    .addGroup(layout.createSequentialGroup()
1248                        .addComponent(stationLabel)
1249                        .addGap(GAP_RELATED)
1250                        .addComponent(stationPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
1251                    .addGroup(layout.createSequentialGroup()
1252                        .addComponent(timesLabel)
1253                        .addGap(GAP_RELATED)
1254                        .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))))
1255        );
1256        layout.setVerticalGroup(
1257            layout.createParallelGroup(LEADING)
1258            .addGroup(layout.createSequentialGroup()
1259                .addGroup(layout.createParallelGroup(LEADING)
1260                    .addComponent(descriptorLabel)
1261                    .addComponent(descriptorComboBox))
1262                .addPreferredGap(RELATED)
1263                .addGroup(layout.createParallelGroup(LEADING)
1264                    .addComponent(satelliteTimeLabel)
1265                    .addComponent(extraPanel))
1266                .addPreferredGap(RELATED)
1267                .addGroup(layout.createParallelGroup(LEADING)
1268                    .addComponent(stationLabel)
1269                    .addComponent(stationPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
1270                .addPreferredGap(RELATED)
1271                .addGroup(layout.createParallelGroup(LEADING)
1272                    .addComponent(timesLabel)
1273                    .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
1274                .addPreferredGap(RELATED))
1275        );
1276
1277        
1278        
1279        
1280        
1281        setInnerPanel(myPanel);
1282        return super.doMakeContents(true);
1283    }
1284    
1285}