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    
029    package edu.wisc.ssec.mcidasv.chooser.adde;
030    
031    import static javax.swing.GroupLayout.DEFAULT_SIZE;
032    import static javax.swing.GroupLayout.PREFERRED_SIZE;
033    import static javax.swing.GroupLayout.Alignment.BASELINE;
034    import static javax.swing.GroupLayout.Alignment.LEADING;
035    import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
036    
037    import java.awt.IllegalComponentStateException;
038    import java.awt.Point;
039    import java.awt.event.ActionEvent;
040    import java.awt.event.ActionListener;
041    import java.text.SimpleDateFormat;
042    import java.util.ArrayList;
043    import java.util.Arrays;
044    import java.util.Collections;
045    import java.util.Date;
046    import java.util.Enumeration;
047    import java.util.Hashtable;
048    import java.util.List;
049    import java.util.SortedSet;
050    import java.util.TreeSet;
051    
052    import javax.swing.GroupLayout;
053    import javax.swing.JButton;
054    import javax.swing.JComboBox;
055    import javax.swing.JComponent;
056    import javax.swing.JDialog;
057    import javax.swing.JLabel;
058    import javax.swing.JPanel;
059    import javax.swing.ListSelectionModel;
060    
061    import org.slf4j.Logger;
062    import org.slf4j.LoggerFactory;
063    import org.w3c.dom.Element;
064    
065    import edu.wisc.ssec.mcidas.McIDASUtil;
066    import edu.wisc.ssec.mcidas.adde.AddePointDataReader;
067    import edu.wisc.ssec.mcidas.adde.DataSetInfo;
068    
069    import visad.DateTime;
070    import visad.VisADException;
071    
072    import ucar.unidata.data.AddeUtil;
073    import ucar.unidata.data.point.AddePointDataSource;
074    import ucar.unidata.idv.chooser.IdvChooserManager;
075    import ucar.unidata.idv.chooser.adde.AddeServer;
076    import ucar.unidata.ui.DateTimePicker;
077    import ucar.unidata.ui.symbol.StationModel;
078    import ucar.unidata.ui.symbol.StationModelManager;
079    import ucar.unidata.util.GuiUtils;
080    import ucar.unidata.util.Misc;
081    import ucar.unidata.util.TwoFacedObject;
082    import ucar.visad.UtcDate;
083    
084    import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
085    import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width;
086    
087    /**
088     * Selection widget for ADDE point data
089     * 
090     * @version $Revision$ $Date$
091     */
092    public class AddePointDataChooser extends AddeChooser {
093    
094        /** Logging object. Use it! */
095        private static final Logger logger = LoggerFactory.getLogger(AddePointDataChooser.class);
096    
097        /**
098         * Property for the dataset name key.
099         * @see edu.wisc.ssec.mcidasv.chooser.adde.AddeChooser#getDataSetName()
100         */
101        public static String DATASET_NAME_KEY = "name";
102    
103        /** Property for the data type. */
104        public static String DATA_TYPE = "ADDE.POINT.V";
105    
106        /** Are we currently reading times */
107        private Object readTimesTask;
108        
109        /** box and label for the relative time */
110        protected JLabel relTimeIncLabel;
111        protected JComboBox relTimeIncBox;
112    
113        /** the relative time increment */
114        private float relativeTimeIncrement = 1.0f;
115        
116        /** archive date */
117        protected String archiveDay = null;
118    
119        /** archive day button and label */
120        protected JLabel archiveDayLabel;
121        protected JButton archiveDayBtn;
122    
123        /** archive date formatter */
124        private SimpleDateFormat archiveDayFormatter;
125    
126        /** station model manager */
127        private StationModelManager stationModelManager;
128        
129        /** allowed descriptor prefix */
130        protected String descriptorsAllowPrefix = "";
131            
132        protected boolean firstTime = true;
133        protected boolean retry = true;
134        
135        /** Possibly ask for times a second time if the first sampling doesn't get any */
136        private boolean gotObs = false;
137        protected boolean tryWithoutSampling = false;
138            
139        /**
140         * Create a chooser for Adde POINT data
141         *
142         * @param mgr The chooser manager
143         * @param root The chooser.xml node
144         */
145        public AddePointDataChooser(IdvChooserManager mgr, Element root) {
146            super(mgr, root);
147                                    
148            this.stationModelManager = getIdv().getStationModelManager();
149    
150            relTimeIncLabel = new JLabel(" Interval:");
151            relTimeIncBox = new JComboBox();
152            relTimeIncBox.setToolTipText("Set the increment between relative times");
153            relTimeIncBox.addActionListener(new ActionListener() {
154                @Override public void actionPerformed(ActionEvent ae) {
155                    JComboBox box = (JComboBox) ae.getSource();
156                    if (GuiUtils.anySelected(box)) {
157                        setRelativeTimeIncrement(getRelBoxValue());
158                    }
159                }
160            });
161            McVGuiUtils.setComponentWidth(relTimeIncBox, Width.ONEHALF);
162            
163            descriptorsAllowPrefix = "";
164            
165            archiveDayBtn = GuiUtils.makeImageButton(
166                "/auxdata/ui/icons/calendar_edit.png", this, "getArchiveDay", null,
167                true);
168            archiveDayBtn.setToolTipText("Select a day for archive datasets");
169            archiveDayLabel = new JLabel("Select day:");
170            archiveDayFormatter = new SimpleDateFormat(UtcDate.YMD_FORMAT);
171        }
172        
173        /**
174         * Do server connection stuff... override this with type-specific methods
175         */
176        @Override protected void readFromServer() {
177            archiveDay = null;
178            if (archiveDayLabel != null) {
179                archiveDayLabel.setText("Select day:");
180            }
181            super.readFromServer();
182        }
183    
184        /**
185         *  Generate a list of image descriptors for the descriptor list.
186         */
187        @Override protected void readDescriptors() {
188            try {
189                StringBuffer buff = getGroupUrl(REQ_DATASETINFO, getGroup());
190                buff.append("&type=").append(getDataType());
191                DataSetInfo  dsinfo = new DataSetInfo(buff.toString());
192                descriptorTable = dsinfo.getDescriptionTable();
193                
194                // Only show descriptorsAllowPrefix if set
195                for (Enumeration e = descriptorTable.keys(); e.hasMoreElements();) {
196                    Object key = e.nextElement();
197                    String str = (String) descriptorTable.get(key);
198                    if (!descriptorsAllowPrefix.isEmpty() && str.indexOf(descriptorsAllowPrefix) != 0)
199                        descriptorTable.remove(key);
200                }
201                
202                String[] names = new String[descriptorTable.size()];
203                Enumeration enumeration = descriptorTable.keys();
204                for (int i = 0; enumeration.hasMoreElements(); i++) {
205                    Object thisElement = enumeration.nextElement();
206                    names[i] = descriptorTable.get(thisElement).toString() + " - " + thisElement.toString();
207                }
208                Arrays.sort(names);
209                setDescriptors(names);
210                setState(STATE_CONNECTED);
211            } catch (Exception e) {
212                handleConnectionError(e);
213            }
214        }
215    
216        /**
217         * Load in an ADDE point data set based on the
218         * <code>PropertyChangeEvent<code>.
219         *
220         */
221        @Override public void doLoadInThread() {
222            showWaitCursor();
223            try {
224                StationModel selectedStationModel = getSelectedStationModel();
225                String source = getRequestUrl();
226    
227                // make properties Hashtable to hand the station name
228                // to the AddeProfilerDataSource
229                Hashtable ht = new Hashtable();
230                getDataSourceProperties(ht);
231                ht.put(AddePointDataSource.PROP_STATIONMODELNAME,
232                       selectedStationModel.getName());
233                ht.put(DATASET_NAME_KEY, getDescriptor());
234                ht.put(DATA_NAME_KEY, getDataName());
235                if (source.contains(AddeUtil.RELATIVE_TIME)) {
236                    ht.put(AddeUtil.NUM_RELATIVE_TIMES, getRelativeTimeIndices());
237                    ht.put(AddeUtil.RELATIVE_TIME_INCREMENT, getRelativeTimeIncrement());
238                }
239    
240                if (getDoAbsoluteTimes()) {
241                  ht.put(AddeUtil.ABSOLUTE_TIMES, getSelectedAbsoluteTimes());
242                }
243    
244                makeDataSource(source, DATA_TYPE, ht);
245                saveServerState();
246            } catch (Exception excp) {
247                logException("Unable to open ADDE point dataset", excp);
248            }
249            showNormalCursor();
250        }
251            
252        /**
253         * Show the archive dialog. This method is not meant to be called but is
254         * public by reason of implementation (or insanity).
255         */
256        public void getArchiveDay() {
257            final JDialog dialog = GuiUtils.createDialog("Set Archive Day", true);
258            final DateTimePicker dtp = new DateTimePicker((Date) null, false);
259            if (archiveDay != null) {
260                if (archiveDayFormatter == null) {
261                    archiveDayFormatter = new SimpleDateFormat(UtcDate.YMD_FORMAT);
262                }
263                Date d = null;
264                try {
265                    d = archiveDayFormatter.parse(archiveDay);
266                    dtp.setDate(d);
267                } catch (Exception e) {
268                    logException("parsing archive day " + archiveDay, e);
269                }
270            }
271    
272            ActionListener listener = new ActionListener() {
273                public void actionPerformed(ActionEvent ae) {
274                    String cmd = ae.getActionCommand();
275                    if (cmd.equals(GuiUtils.CMD_REMOVE)) {
276                        archiveDay = null;
277                        archiveDayLabel.setText("Select day:");
278                        setDoAbsoluteTimes(true);
279                        descriptorChanged();
280                    } else if (cmd.equals(GuiUtils.CMD_OK)) {
281                        try {
282                            DateTime dt = new DateTime(dtp.getDate());
283                            String myDay = UtcDate.getYMD(dt);
284                            // archiveDayLabel.setText(UtcDate.formatUtcDate(dt,
285                            // "MMM dd, yyyy"));
286                            archiveDayLabel.setText(myDay);
287                        } catch (Exception e) {
288                            logger.error("problem while setting archive day label", e);
289                        }
290                        // System.out.println("archiveDay = " + archiveDay);
291                        setDoAbsoluteTimes(true);
292                        descriptorChanged();
293                    }
294                    dialog.dispose();
295                }
296            };
297    
298            JPanel buttons = GuiUtils.makeButtons(listener, new String[] {
299                    GuiUtils.CMD_OK, GuiUtils.CMD_REMOVE, GuiUtils.CMD_CANCEL });
300    
301            JComponent contents = GuiUtils.topCenterBottom(GuiUtils.inset(GuiUtils
302                    .lLabel("Please select a day for this dataset:"), 10), GuiUtils
303                    .inset(dtp, 10), buttons);
304            Point p = new Point(200, 200);
305            if (archiveDayBtn != null) {
306                try {
307                    p = archiveDayBtn.getLocationOnScreen();
308                } catch (IllegalComponentStateException ice) {
309                    logger.error("archive day button in illegal state", ice);
310                }
311            }
312            dialog.setLocation(p);
313            dialog.getContentPane().add(contents);
314            dialog.pack();
315            dialog.setVisible(true);
316        }
317    
318        /**
319         * Get the selected station model.
320         *
321         * @return StationModel to use: defined by defaultModels list in ctor
322         */
323        public StationModel getSelectedStationModel() {
324            StationModel returnModel = null;
325            if (isUpperAir()) {
326                returnModel = this.stationModelManager.getStationModel("Observations>Upper Air");
327            } else if (isSynoptic()) {
328                returnModel = this.stationModelManager.getStationModel("Observations>SYNOP");
329            } else {
330                returnModel = this.stationModelManager.getStationModel("Observations>METAR");
331            }
332            return returnModel;
333        }
334        
335        /**
336         * Add the interval selector to the component.
337         * @return superclass component with extra stuff
338         */
339        @Override protected JPanel makeTimesPanel() {
340            JComponent extra1 = getExtraTimeComponentRelative();
341            GuiUtils.enableTree(extra1, false);
342            JComponent extra2 = getExtraTimeComponentAbsolute();
343            JPanel timesPanel = super.makeTimesPanel(extra1, extra2);
344            return timesPanel;
345        }
346        
347        /**
348         * Get the extra time widget, but built in a different way.
349         * Designed to be put into a GroupLayout
350         *
351         * @return Extra time widget
352         */
353        protected JComponent getExtraTimeComponentRelative() {
354            TwoFacedObject[] intervals = { 
355                new TwoFacedObject("30 minute", .5f),
356                new TwoFacedObject("Hourly", 1f),
357                new TwoFacedObject("Three hourly", 3f),
358                new TwoFacedObject("Six hourly", 6f),
359                new TwoFacedObject("12 hourly", 12f),
360                new TwoFacedObject("24 hourly", 24f)
361            };
362    
363            GuiUtils.setListData(relTimeIncBox, intervals);
364            if (relTimeIncBox.getItemCount()>=2) relTimeIncBox.setSelectedIndex(1);
365            
366            return McVGuiUtils.makeLabeledComponent(relTimeIncLabel, relTimeIncBox, McVGuiUtils.Position.LEFT);
367        }
368        
369        /**
370         * Get the time popup widget
371         * 
372         * @return a widget for selecing the day
373         */
374        protected JComponent getExtraTimeComponentAbsolute() {
375            return null;
376    //      return McVGuiUtils.makeLabeledComponent(archiveDayLabel, archiveDayBtn);
377        }
378            
379        /**
380         * Get the value from the relative increment box
381         *
382         * @return the selected value or a default
383         */
384        protected float getRelBoxValue() {
385            float value = relativeTimeIncrement;
386            if (relTimeIncBox != null) {
387                Object o = relTimeIncBox.getSelectedItem();
388                if (o != null) {
389                    String val = TwoFacedObject.getIdString(o);
390                    value = (float) Misc.parseNumber(val);
391                }
392            }
393            return value;
394        }
395        
396        /**
397         * Get the string from the relative increment box
398         *
399         * @return the selected string or a default
400         */
401        public String getRelBoxString() {
402            String value = "";
403            if (relTimeIncBox != null) {
404                Object o = relTimeIncBox.getSelectedItem();
405                if (o != null) {
406                    value = TwoFacedObject.getIdString(o);
407                }
408            }
409            return value;
410        }
411    
412        /**
413         * Get the request URL
414         *
415         * @return the request URL
416         */
417        public String getRequestUrl() {
418            StringBuffer request = getGroupUrl(REQ_POINTDATA, getGroup());
419            appendKeyValue(request, PROP_USER, getLastAddedUser());
420            appendKeyValue(request, PROP_PROJ, getLastAddedProj());
421            appendKeyValue(request, PROP_DESCR, getDescriptor());
422            appendRequestSelectClause(request);
423            appendKeyValue(request, PROP_NUM, "ALL");
424            appendKeyValue(request, PROP_POS, getDoRelativeTimes() ? "ALL" : "0");
425            return request.toString();
426        }
427            
428        /**
429         * Get the select clause for the adde request specific to this
430         * type of data.
431         *
432         * @param buf The buffer to append to
433         */
434        protected void appendRequestSelectClause(StringBuffer buf) {
435            StringBuilder selectValue = new StringBuilder(1024);
436            selectValue.append('\'');
437            selectValue.append(getDayTimeSelectString());
438            //TODO: why is SFCHOURLY explicit here?  better way to do it?
439            if ("SFCHOURLY".equalsIgnoreCase(getDescriptor())) {
440                selectValue.append(";type 0");
441            }
442            selectValue.append(';');
443    
444            if (isUpperAir()){
445                selectValue.append(AddeUtil.LEVEL);
446                selectValue.append(';');
447            }
448            selectValue.append(AddeUtil.LATLON_BOX);
449            selectValue.append('\'');
450            appendKeyValue(buf, PROP_SELECT, selectValue.toString());
451        }
452        
453        /**
454         * Check if we are ready to read times
455         *
456         * @return  true if times can be read
457         */
458        protected boolean canReadTimes() {
459            return haveDescriptorSelected();
460        }
461            
462        /**
463         * Enable or disable the GUI widgets based on what has been
464         * selected.
465         */
466        @Override protected void enableWidgets() {
467            boolean descriptorState = ((getState() == STATE_CONNECTED)
468                                       && canReadTimes());
469    
470            for (int i = 0; i < compsThatNeedDescriptor.size(); i++) {
471                JComponent comp = (JComponent) compsThatNeedDescriptor.get(i);
472                GuiUtils.enableTree(comp, descriptorState);
473            }
474    
475            boolean timesOk = timesOk();
476            
477            // Require times to be selected
478            GuiUtils.enableTree(loadButton, descriptorState && timesOk);
479    
480            checkTimesLists();
481            
482            enableAbsoluteTimesList(getDoAbsoluteTimes() && descriptorState);
483    
484            getRelativeTimesChooser().setEnabled( !getDoAbsoluteTimes()
485                    && descriptorState);
486    
487            revalidate();
488        }
489        
490        /**
491         * Do we have times selected. Either we are doing absolute
492         * times and there are some selected in the list. Or we
493         * are doing relative times and we have done a connect to the
494         * server
495         *
496         * @return Do we have times
497         */
498        public boolean timesOk() {
499            if (getDoAbsoluteTimes() && !haveTimeSelected()) {
500                return false;
501            }
502            return true;
503        }
504        
505        /**
506         * Return {@code true} if selected descriptor is for SYNOPTIC data.
507         *
508         * @return {@code true} iff {@link edu.wisc.ssec.mcidasv.chooser.adde.AddePointDataChooser#getDescriptor()}
509         * is {@literal "SYNOP"}.
510         */
511        protected boolean isSynoptic() {
512            return "SYNOP".equals(getDescriptor());
513        }
514        
515        /**
516         * Return {@code true} if selected descriptor is for upper air.
517         *
518         * @return {@code true} iff {@link edu.wisc.ssec.mcidasv.chooser.adde.AddePointDataChooser#getDescriptor()}
519         * is {@literal "UPPERMAND"}.
520         */
521        protected boolean isUpperAir() {
522            return "UPPERMAND".equals(getDescriptor());
523    
524        }
525        
526        /**
527         * Return {@code true} if selected descriptor is for profiler.
528         *
529         * @return {@code true} iff {@link edu.wisc.ssec.mcidasv.chooser.adde.AddePointDataChooser#getDescriptor()}
530         * is {@literal "PROF"}.
531         */
532        protected boolean isProfiler() {
533            return "PROF".equals(getDescriptor());
534        }
535            
536        /**
537         * Update the widget with the latest data.
538         *
539         * @throws Exception On badness
540         */
541        @Override public void handleUpdate() throws Exception {
542            if (getState() != STATE_CONNECTED) {
543                //If not connected then update the server list
544                updateServerList();
545            } else {
546                //If we are already connected then update the rest of the chooser
547                descriptorChanged();
548            }
549            updateStatus();
550        }
551            
552        /**
553         * Get the request string for times particular to this chooser
554         *
555         * @return request string
556         */
557        protected String getTimesRequest() {
558            StringBuffer buf = getGroupUrl(REQ_POINTDATA, getGroup());
559            appendKeyValue(buf, PROP_USER, getLastAddedUser());
560            appendKeyValue(buf, PROP_PROJ, getLastAddedProj());
561            appendKeyValue(buf, PROP_DESCR, getDescriptor());
562            if (!isUpperAir() && !tryWithoutSampling) {
563    //            appendKeyValue(buf, PROP_POS, "0");
564                appendKeyValue(buf, PROP_POS, "ALL");
565                appendKeyValue(buf, PROP_SELECT, "'DAY "+getJulianDay()+";LAT 38 42;LON 70 75'");
566            }
567            else {
568                appendKeyValue(buf, PROP_SELECT, "'DAY "+getJulianDay()+"'");
569                appendKeyValue(buf, PROP_POS, "ALL");
570            }
571            if (getDoAbsoluteTimes()) {
572                appendKeyValue(buf, PROP_NUM, "ALL");
573            }
574            appendKeyValue(buf, PROP_PARAM, "DAY TIME");
575            return buf.toString();
576        }
577        
578        /**
579         * Get the current  Julian day as a String
580         *
581         * @return the current day as a string (yyyyDDD)
582         */
583        // TODO: This should really be more accessible
584        private String getJulianDay() {
585            String retString = "";
586            try {
587                DateTime dt = new DateTime();
588                retString = UtcDate.formatUtcDate(dt, "yyyyDDD");
589            } catch (VisADException ve) {
590                logger.error("error building julian date", ve);
591            }
592            return retString;
593        }
594    
595        /**
596         * This allows derived classes to provide their own name for labeling, etc.
597         *
598         * @return  the dataset name
599         */
600        @Override public String getDataName() {
601            return "Point Data";
602        }
603        
604        /**
605         * _more_
606         */
607        @Override public void doCancel() {
608            readTimesTask = null;
609            setState(STATE_UNCONNECTED);
610            super.doCancel();
611        }
612    
613        /** locking mutex */
614        private final Object MUTEX = new Object();
615        
616        /**
617         *  Read the set of image times available for the current server/group/type
618         *  This method is a wrapper, setting the wait cursor and wrapping the
619         *  call to {@link #readTimesInner()}; in a try/catch block
620         */
621        @Override public void readTimes() {
622            clearTimesList();
623            if (!canReadTimes()) {
624                return;
625            }
626            Misc.run(new Runnable() {
627                @Override public void run() {
628                    updateStatus();
629                    showWaitCursor();
630                    try {
631                        gotObs = false;
632                        tryWithoutSampling = false;
633                        readTimesInner();
634                        // Try again, this time not sampling by LAT/LON
635                        if (!gotObs) {
636                            tryWithoutSampling = true;
637                            readTimesInner();
638                        }
639                    } catch (Exception e) {
640                        handleConnectionError(e);
641                    }
642                    showNormalCursor();
643                    updateStatus();
644                }
645            });
646        }
647    
648        /**
649         * Set the list of dates/times based on the image selection
650         */
651        protected void readTimesInner() {
652            SortedSet<DateTime> uniqueTimes = Collections.synchronizedSortedSet(new TreeSet<DateTime>());
653    
654            readTimesTask = startTask();
655            updateStatus();
656            Object task = readTimesTask;
657            try {
658                AddePointDataReader apr = new AddePointDataReader(getTimesRequest());
659                //Make sure no other loads are  occurred
660                boolean ok = stopTaskAndIsOk(task);
661                if (!Misc.equals(readTimesTask, task) || !ok) {
662                    return;
663                }
664                readTimesTask = null;
665    
666                synchronized (MUTEX) {
667                    int[][] data = apr.getData();
668                    String[] units = apr.getUnits();
669                    if ( !"CYD".equals(units[0]) || !"HMS".equals(units[1])) {
670                        throw new Exception("can't handle date/time units");
671                    }
672                    int numObs = data[0].length;
673                    //System.out.println("found " + numObs + " obs");
674                    // loop through and find all the unique times
675                    try {
676                        for (int i = 0; i < numObs; i++) {
677                            DateTime dt =
678                                new DateTime(McIDASUtil.mcDayTimeToSecs(data[0][i],
679                                    data[1][i]));
680                            uniqueTimes.add(dt);
681                        }
682                    } catch (Exception e) {
683                        logger.error("problem building list of unique times", e);
684                    }
685                    //System.out.println(
686                    //      "found " + uniqueTimes.size() + " unique times");
687                    if (getDoAbsoluteTimes()) {
688                        if (!uniqueTimes.isEmpty()) {
689                            setAbsoluteTimes(new ArrayList<DateTime>(uniqueTimes));
690                            getTimesList().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
691                        }
692    
693                        //   Select the last n hours 
694                        int selectedIndex = getAbsoluteTimes().size() - 1;
695                        int firstIndex = Math.max(0, selectedIndex
696                                - getDefaultRelativeTimeIndex());
697                        if (selectedIndex >= 0)
698                            setSelectedAbsoluteTime(selectedIndex, firstIndex);
699                    }
700                    if (numObs>0) {
701                        gotObs = true;
702                    }
703                }
704                setState(STATE_CONNECTED);
705            } catch (Exception excp) {
706                stopTask(task);
707                readTimesTask = null;
708                handleConnectionError(excp);
709                if (!retry) {
710                    return;
711                }
712                try {
713                    handleUpdate();
714                } catch (Exception e) {
715                    logger.error("problem handling update", e);
716                }
717            }
718        }
719    
720        /**
721         * Show the given error to the user. If it was an Adde exception
722         * that was a bad server error then print out a nice message.
723         *
724         * @param e The exception
725         */
726        @Override protected void handleConnectionError(Exception e) {
727            retry = false;
728            super.handleConnectionError(e);
729        }
730    
731        /**
732         * Are there any times selected.
733         *
734         * @return Any times selected.
735         */
736        @Override protected boolean haveTimeSelected() {
737            return !getDoAbsoluteTimes() || getHaveAbsoluteTimesSelected();
738        }
739    
740        /**
741         * Create the date time selection string for the "select" clause
742         * of the ADDE URL.
743         *
744         * @return the select day and time strings
745         */
746        protected String getDayTimeSelectString() {
747            StringBuilder buf = new StringBuilder(1024);
748            if (getDoAbsoluteTimes()) {
749                List times = getSelectedAbsoluteTimes();
750    
751                // no time selection is permitted as a valid choice -
752                // will then use all times today by default.
753                if (times.isEmpty()) {
754                    return "";
755                }
756    
757                //check for the "no times available" message
758                if (times.get(0) instanceof String) {
759                    return "";
760                }
761    
762                if (archiveDay!=null) {
763                    logger.trace("archiveDay: {}", archiveDay);
764                    try {
765                        Date d = archiveDayFormatter.parse(archiveDay);
766                        DateTime dt = new DateTime(d);
767                        logger.trace("parsed to: {}", dt.toString());
768                        buf.append("day ").append(UtcDate.getIYD(dt)).append(';');
769                    } catch (Exception e) {
770                        logger.error("archiveDay parse error", e);
771                    }
772                }
773                else {
774                    logger.trace("archiveDay is null!");
775                }
776    
777                buf.append("time ");
778                for (int i = 0; i < times.size(); i++) {
779                    DateTime dt = (DateTime) times.get(i);
780                    buf.append(UtcDate.getHMS(dt));
781                    if (i != times.size() - 1) {
782                        buf.append(',');
783                    }
784                }
785            } else {
786                buf.append(AddeUtil.RELATIVE_TIME);
787            }
788            return buf.toString();
789        }
790    
791        /**
792         * Get the data type for this chooser
793         *
794         * @return  the type
795         */
796        @Override public String getDataType() {
797            return "POINT";
798        }
799    
800        /**
801         * Get the increment between times for relative time requests
802         *
803         * @return time increment (hours)
804         */
805        @Override public float getRelativeTimeIncrement() {
806            return relativeTimeIncrement;
807        }
808    
809        /**
810         * Set the increment between times for relative time requests
811         *
812         * @param increment time increment (hours)
813         */
814        public void setRelativeTimeIncrement(float increment) {
815            relativeTimeIncrement = increment;
816            if (relTimeIncBox != null) {
817                relTimeIncBox.setSelectedItem(relativeTimeIncrement);
818            }
819        }
820    
821        /**
822         * Update labels, enable widgets, etc.
823         */
824        @Override protected void updateStatus() {
825            super.updateStatus();
826            if (readTimesTask != null) {
827                if (taskOk(readTimesTask)) {
828                    setStatus("Reading available times from server");
829                }
830            } else if (getDoAbsoluteTimes() && !haveTimeSelected()) {
831                setStatus(MSG_TIMES);
832            }
833            enableWidgets();
834        }
835    
836                    
837        /**
838         * Get the descriptor widget label.
839         *
840         * @return  label for the descriptor  widget
841         */
842        @Override public String getDescriptorLabel() {
843            return "Point Type"; 
844        }
845        
846        /**
847         * get the adde server grup type to use
848         *
849         * @return group type
850         */
851        @Override protected String getGroupType() {
852            return AddeServer.TYPE_POINT;
853        }
854        
855        /**
856         * Make the UI for this selector.
857         * 
858         * @return The gui
859         */   
860        @Override public JComponent doMakeContents() {
861            JPanel myPanel = new JPanel();
862            
863            McVGuiUtils.setComponentWidth(descriptorComboBox, Width.DOUBLEDOUBLE);
864                    
865            JLabel stationLabel = McVGuiUtils.makeLabelRight("Station:");
866            addServerComp(stationLabel);
867            
868            JLabel timesLabel = McVGuiUtils.makeLabelRight("Times:");
869            addDescComp(timesLabel);
870            
871            JPanel timesPanel = makeTimesPanel();
872            timesPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
873            addDescComp(timesPanel);
874                    
875            GroupLayout layout = new GroupLayout(myPanel);
876            myPanel.setLayout(layout);
877            layout.setHorizontalGroup(
878                layout.createParallelGroup(LEADING)
879                .addGroup(layout.createSequentialGroup()
880                    .addGroup(layout.createParallelGroup(LEADING)
881                        .addGroup(layout.createSequentialGroup()
882                            .addComponent(descriptorLabel)
883                            .addGap(GAP_RELATED)
884                            .addComponent(descriptorComboBox))
885                        .addGroup(layout.createSequentialGroup()
886                            .addComponent(timesLabel)
887                            .addGap(GAP_RELATED)
888                            .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))))
889            );
890            layout.setVerticalGroup(
891                layout.createParallelGroup(LEADING)
892                .addGroup(layout.createSequentialGroup()
893                    .addGroup(layout.createParallelGroup(BASELINE)
894                        .addComponent(descriptorLabel)
895                        .addComponent(descriptorComboBox))
896                    .addPreferredGap(RELATED)
897                    .addGroup(layout.createParallelGroup(LEADING)
898                        .addComponent(timesLabel)
899                        .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)))
900            );
901            
902            setInnerPanel(myPanel);
903            return super.doMakeContents();
904        }
905    
906        public JComponent doMakeContents(boolean doesOverride) {
907            if (doesOverride) {
908                return super.doMakeContents();
909            } else {
910                return doMakeContents();
911            }
912        }
913    }