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