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