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