001    /*
002     * $Id: AddeRaobChooser.java,v 1.33 2012/02/19 17:35:35 davep Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
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     */
030    package edu.wisc.ssec.mcidasv.chooser.adde;
031    
032    import static javax.swing.GroupLayout.DEFAULT_SIZE;
033    import static javax.swing.GroupLayout.PREFERRED_SIZE;
034    import static javax.swing.GroupLayout.Alignment.LEADING;
035    import static javax.swing.GroupLayout.Alignment.TRAILING;
036    import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
037    
038    import java.awt.Dimension;
039    import java.awt.Point;
040    import java.awt.event.ActionEvent;
041    import java.awt.event.ActionListener;
042    import java.awt.event.ItemEvent;
043    import java.awt.event.ItemListener;
044    import java.awt.event.MouseAdapter;
045    import java.awt.event.MouseEvent;
046    import java.beans.PropertyChangeEvent;
047    import java.beans.PropertyChangeListener;
048    import java.util.ArrayList;
049    import java.util.Arrays;
050    import java.util.Collections;
051    import java.util.Enumeration;
052    import java.util.Hashtable;
053    import java.util.List;
054    import java.util.Map;
055    import java.util.Vector;
056    
057    import javax.swing.GroupLayout;
058    import javax.swing.JButton;
059    import javax.swing.JCheckBox;
060    import javax.swing.JComboBox;
061    import javax.swing.JComponent;
062    import javax.swing.JLabel;
063    import javax.swing.JList;
064    import javax.swing.JMenuItem;
065    import javax.swing.JPanel;
066    import javax.swing.JPopupMenu;
067    import javax.swing.JScrollPane;
068    import javax.swing.JTextField;
069    import javax.swing.ListModel;
070    import javax.swing.ListSelectionModel;
071    import javax.swing.SwingUtilities;
072    import javax.swing.event.ListSelectionEvent;
073    import javax.swing.event.ListSelectionListener;
074    
075    import org.w3c.dom.Element;
076    
077    import edu.wisc.ssec.mcidas.McIDASUtil;
078    import edu.wisc.ssec.mcidas.adde.AddePointDataReader;
079    import edu.wisc.ssec.mcidas.adde.DataSetInfo;
080    
081    import visad.DateTime;
082    
083    import ucar.unidata.data.sounding.RaobDataSet;
084    import ucar.unidata.data.sounding.SoundingOb;
085    import ucar.unidata.data.sounding.SoundingStation;
086    import ucar.unidata.gis.mcidasmap.McidasMap;
087    import ucar.unidata.idv.chooser.IdvChooserManager;
088    import ucar.unidata.metdata.Station;
089    import ucar.unidata.util.GuiUtils;
090    import ucar.unidata.util.LogUtil;
091    import ucar.unidata.util.Misc;
092    import ucar.unidata.view.CompositeRenderer;
093    import ucar.unidata.view.station.StationLocationMap;
094    
095    import edu.wisc.ssec.mcidasv.data.adde.AddeSoundingAdapter;
096    import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
097    import 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.33 $Date: 2012/02/19 17:35:35 $
107     */
108    
109    
110    public 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    }