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