001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2016
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
029package edu.wisc.ssec.mcidasv.chooser.adde;
030
031import static javax.swing.GroupLayout.DEFAULT_SIZE;
032import static javax.swing.GroupLayout.PREFERRED_SIZE;
033import static javax.swing.GroupLayout.Alignment.BASELINE;
034import static javax.swing.GroupLayout.Alignment.LEADING;
035import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
036
037import java.util.Hashtable;
038import java.util.List;
039
040import java.awt.Component;
041import java.awt.event.ItemEvent;
042import java.awt.event.ItemListener;
043
044import javax.swing.BorderFactory;
045import javax.swing.GroupLayout;
046import javax.swing.JCheckBox;
047import javax.swing.JComponent;
048import javax.swing.JLabel;
049import javax.swing.JPanel;
050import javax.swing.JSlider;
051import javax.swing.JTabbedPane;
052import javax.swing.event.ChangeEvent;
053import javax.swing.event.ChangeListener;
054
055import org.w3c.dom.Element;
056
057import edu.wisc.ssec.mcidas.AreaDirectory;
058
059import ucar.unidata.data.DataSelection;
060import ucar.unidata.data.imagery.AddeImageDescriptor;
061import ucar.unidata.data.imagery.BandInfo;
062import ucar.unidata.data.imagery.ImageDataset;
063import ucar.unidata.idv.chooser.IdvChooserManager;
064import ucar.unidata.util.GuiUtils;
065import ucar.unidata.util.StringUtil;
066import ucar.unidata.util.TwoFacedObject;
067import ucar.unidata.xml.XmlObjectStore;
068
069import edu.wisc.ssec.mcidasv.Constants;
070import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
071
072/**
073 * Chooser that allows users to select images from a remote ADDE server.
074 *
075 * <p>Displays a list of the descriptors (names) of the image datasets
076 * available for a particular ADDE group on the remote server.</p>
077 */
078public class ImageChooser extends AddeImageChooser implements Constants {
079
080    /**
081     * Public keys for server, group, dataset, user, project.
082     */
083    public final static String SIZE_KEY = "size";
084    public final static String BAND_KEY = "band";
085    public final static String PLACE_KEY = "place";
086    public final static String LATLON_KEY = "latlon";
087    public final static String LINELE_KEY = "linele";
088    public final static String MAG_KEY = "mag";
089    public final static String UNIT_KEY = "unit";
090    public final static String PREVIEW_KEY = "preview";
091    public final static String NAVIGATION_KEY = "navigation";
092
093    /** Property for image default value unit */
094    protected static final String PROP_NAV = "NAV";
095
096    /** Property for image default value unit */
097    protected static final String PROP_UNIT = "UNIT";
098
099    /** Property for image default value band */
100    protected static final String PROP_BAND = "BAND";
101
102    /** Xml attr name for the defaults */
103    private static final String ATTR_NAV = "NAV";
104    private static final String ATTR_UNIT = "UNIT";
105    private static final String ATTR_BAND = "BAND";
106    private static final String ATTR_PLACE = "PLACE";
107    private static final String ATTR_SIZE = "SIZE";
108    private static final String ATTR_MAG = "MAG";
109    private static final String ATTR_LATLON = "LATLON";
110    private static final String ATTR_LINELE = "LINELE";
111
112    /** string for ALL */
113    private static final String ALL = "ALL";
114
115    private JCheckBox previewBox = null;
116
117
118    /**
119     * Construct an ADDE image selection widget.
120     *
121     * @param mgr Chooser manager.
122     * @param root Chooser XML node.
123     */
124    public ImageChooser(IdvChooserManager mgr, Element root) {
125        super(mgr, root);
126        //DAVEP: Hiding parameter set picker for now... revisit after 1.0
127//        showParameterButton();
128    }
129    
130    /**
131     * Return the parameter type associated with this chooser.
132     */
133    @Override protected String getParameterSetType() {
134        return "addeimagery";
135    }
136    
137    /**
138     * Return the data source ID. Used by extending classes.
139     */
140    @Override protected String getDataSourceId() {
141        return "ADDE.IMAGE";
142    }
143
144    /**
145     * Restore the selected parameter set using element attributes.
146     *
147     * @param restoreElement {@code Element} with the desired attributes.
148     * {@code null} values are permitted.
149     *
150     * @return {@code true} if the parameter set was restored, {@code false}
151     * otherwise.
152     */
153    @Override protected boolean restoreParameterSet(Element restoreElement) {
154        boolean okay = super.restoreParameterSet(restoreElement);
155        if (!okay) {
156            return okay;
157        }
158        
159        // Imagery specific restore
160        
161        // Restore nav
162        if (restoreElement.hasAttribute(ATTR_NAV)) {
163            String nav = restoreElement.getAttribute(ATTR_NAV);
164            TwoFacedObject tfo = new TwoFacedObject("Default", "X");
165            navComboBox.setSelectedItem(tfo);
166            if (nav.toUpperCase().equals("LALO")) {
167                tfo = new TwoFacedObject("Lat/Lon", "LALO");
168            }
169            navComboBox.setSelectedItem(tfo);
170        }
171        return true;
172    }
173    
174    /**
175     * Get the list of BandInfos for the current selected images.
176     *
177     * @return List of BandInfos.
178     */
179    public List<BandInfo> getSelectedBandInfos() {
180        return super.getBandInfos();
181    }
182
183    /**
184     * Get the value for the given property. This can either be the value
185     * supplied by the end user through the advanced GUI or is the default.
186     *
187     * @param prop The property.
188     * @param ad The AreaDirectory.
189     *
190     * @return Value of the property to use in the request string.
191     */
192    @Override protected String getPropValue(String prop, AreaDirectory ad) {
193        String propValue = super.getPropValue(prop, ad);
194        if (PROP_NAV.equals(prop)) {
195            propValue = TwoFacedObject.getIdString(navComboBox.getSelectedItem());
196        }
197        return propValue;
198    }
199
200    /**
201     * Get the default value for a key.
202     *
203     * @param property Property (key type).
204     * @param dflt Default value.
205     *
206     * @return Value for key or {@code dflt} if not found.
207     */
208    @Override protected String getDefault(String property, String dflt) {
209        String paramDefault = super.getDefault(property, dflt);
210        if (PROP_NAV.equals(property)) {
211            if (restoreElement != null) {
212                paramDefault = restoreElement.getAttribute(ATTR_NAV);
213            }
214        } else if (PROP_UNIT.equals(property)) {
215            paramDefault = "";
216        } else if (PROP_BAND.equals(property)) {
217            paramDefault = ALL;
218        } else if (PROP_PLACE.equals(property)) {
219            paramDefault = "";
220        }
221        return paramDefault;
222    }
223    
224    /**
225     * Get the DataSource properties.
226     * 
227     * @param ht Hashtable of properties.
228     */
229    @Override protected void getDataSourceProperties(Hashtable ht) {
230        super.getDataSourceProperties(ht);
231        if (restoreElement != null) {
232            if (restoreElement.hasAttribute(ATTR_BAND)) {
233                ht.put(BAND_KEY, restoreElement.getAttribute(ATTR_BAND));
234            }
235            if (restoreElement.hasAttribute(ATTR_LATLON)) {
236                ht.put(LATLON_KEY, restoreElement.getAttribute(ATTR_LATLON));
237            }
238            if (restoreElement.hasAttribute(ATTR_LINELE)) {
239                ht.put(LINELE_KEY, restoreElement.getAttribute(ATTR_LINELE));
240            }
241            if (restoreElement.hasAttribute(ATTR_MAG)) {
242                ht.put(MAG_KEY, restoreElement.getAttribute(ATTR_MAG));
243            }
244            if (restoreElement.hasAttribute(ATTR_PLACE)) {
245                ht.put(PLACE_KEY, restoreElement.getAttribute(ATTR_PLACE));
246            }
247            if (restoreElement.hasAttribute(ATTR_SIZE)) {
248                ht.put(SIZE_KEY, restoreElement.getAttribute(ATTR_SIZE));
249            }
250            if (restoreElement.hasAttribute(ATTR_UNIT)) {
251                ht.put(UNIT_KEY, restoreElement.getAttribute(ATTR_UNIT));
252            }
253        } else {
254            ht.put(NAVIGATION_KEY, getPropValue(PROP_NAV, null));
255        }
256        ht.put(PREVIEW_KEY, previewBox.isSelected());
257    }
258    
259    /**
260     * Should we use the user supplied property.
261     * 
262     * @param propId The property.
263     * 
264     * @return Should use the value from the advanced widget.
265     */
266    protected boolean usePropFromUser(String propId) {
267        boolean fromSuper = super.usePropFromUser(propId);
268        if (PROP_UNIT.equals(propId) || PROP_BAND.equals(propId)) {
269            fromSuper = false;
270        }
271        return fromSuper;
272    }
273    
274    /**
275     * Make the UI for this selector.
276     *
277     * @return The GUI.
278     */
279    @Override public JComponent doMakeContents() {
280        JPanel myPanel = new JPanel();
281
282        JLabel timesLabel = McVGuiUtils.makeLabelRight("Times:");
283        addDescComp(timesLabel);
284
285        JPanel timesPanel = makeTimesPanel();
286        timesPanel.setBorder(BorderFactory.createEtchedBorder());
287        addDescComp(timesPanel);
288
289        JLabel navigationLabel = McVGuiUtils.makeLabelRight("Navigation:");
290        addDescComp(navigationLabel);
291
292        // Use processPropertyComponents to build combo boxes that we rely on
293        processPropertyComponents();
294        addDescComp(navComboBox);
295        McVGuiUtils.setComponentWidth(navComboBox, McVGuiUtils.Width.DOUBLE);
296
297        // Preview checkbox
298        JLabel previewLabel = McVGuiUtils.makeLabelRight("Preview:");
299        addDescComp(previewLabel);
300        XmlObjectStore store = getIdv().getStore();
301        previewBox = new JCheckBox("Create preview image", store.get(Constants.PREF_IMAGE_PREVIEW, true));
302        previewBox.setToolTipText("Creating preview images takes extra time and network bandwidth");
303        previewBox.addItemListener(new ItemListener() {
304            public void itemStateChanged(ItemEvent e) {
305                XmlObjectStore store = getIdv().getStore();
306                store.put(Constants.PREF_IMAGE_PREVIEW, previewBox.isSelected());
307                store.save();
308            }
309        });
310        addDescComp(previewBox);
311
312        GroupLayout layout = new GroupLayout(myPanel);
313        myPanel.setLayout(layout);
314        layout.setHorizontalGroup(
315            layout.createParallelGroup(LEADING)
316                .addGroup(layout.createSequentialGroup()
317                    .addGroup(layout.createParallelGroup(LEADING)
318                        .addGroup(layout.createSequentialGroup()
319                            .addComponent(descriptorLabel)
320                            .addGap(GAP_RELATED)
321                            .addComponent(descriptorComboBox))
322                        .addGroup(layout.createSequentialGroup()
323                            .addComponent(timesLabel)
324                            .addGap(GAP_RELATED)
325                            .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
326                        .addGroup(layout.createSequentialGroup()
327                            .addComponent(navigationLabel)
328                            .addGap(GAP_RELATED)
329                            .addComponent(navComboBox))
330                        .addGroup(layout.createSequentialGroup()
331                            .addComponent(previewLabel)
332                            .addGap(GAP_RELATED)
333                            .addComponent(previewBox))))
334        );
335        layout.setVerticalGroup(
336            layout.createParallelGroup(LEADING)
337                .addGroup(layout.createSequentialGroup()
338                    .addGroup(layout.createParallelGroup(BASELINE)
339                        .addComponent(descriptorLabel)
340                        .addComponent(descriptorComboBox))
341                    .addPreferredGap(RELATED)
342                    .addGroup(layout.createParallelGroup(LEADING)
343                        .addComponent(timesLabel)
344                        .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
345                    .addPreferredGap(RELATED)
346                    .addGroup(layout.createParallelGroup(LEADING)
347                        .addComponent(navigationLabel)
348                        .addComponent(navComboBox))
349                    .addGroup(layout.createParallelGroup(LEADING)
350                        .addComponent(previewLabel)
351                        .addComponent(previewBox)))
352        );
353        setInnerPanel(myPanel);
354        return super.doMakeContents(true);
355    }
356
357    /**
358     * User said go, we go. Simply get the list of images from the Image Chooser
359     * and create the {@code ADDE.IMAGE} data source.
360     */
361    @Override public void doLoadInThread() {
362        if (!checkForValidValues()) {
363            return;
364        }
365        if (!getGoodToGo()) {
366            updateStatus();
367            return;
368        }
369
370        List imageList = getImageList();
371        if ((imageList == null) || imageList.isEmpty()) {
372            return;
373        }
374
375        // Check for size threshold
376        final int[] dim = { 0, 0 };
377        AddeImageDescriptor aid = (AddeImageDescriptor) imageList.get(0);
378        dim[0] = aid.getImageInfo().getElements();
379        dim[1] = aid.getImageInfo().getLines();
380        // System.err.println("dim:" + dim[0] + " x " + dim[1] + " # images:"
381        // + imageList.size());
382        int numPixels = dim[0] * dim[1] * imageList.size();
383        double megs = (4 * numPixels) / (double) 1000000;
384
385        //DAVEP: take this out--it should be warning in the data source, not the chooser
386        boolean doSizeCheck = false;
387        if ((megs > AddeImageChooser.SIZE_THRESHOLD) && doSizeCheck) {
388            final JCheckBox maintainSize = new JCheckBox(
389                "Maintain spatial extent", false);
390            final JLabel sizeLbl = new JLabel(StringUtil.padRight("  "
391                + ((double) ((int) megs * 100)) / 100.0 + " MB", 14));
392            GuiUtils.setFixedWidthFont(sizeLbl);
393            final List[] listHolder = { imageList };
394            final JSlider slider = new JSlider(2, (int) megs, (int) megs);
395            slider.setMajorTickSpacing((int) (megs - 2) / 10);
396            slider.setMinorTickSpacing((int) (megs - 2) / 10);
397            // slider.setPaintTicks(true);
398            slider.setSnapToTicks(true);
399            final long timeNow = System.currentTimeMillis();
400            ChangeListener sizeListener = new javax.swing.event.ChangeListener() {
401                public void stateChanged(ChangeEvent evt) {
402                    // A hack so we don't respond to the first event that we get
403                    // from the slider when
404                    // the dialog is first shown
405                    if ((System.currentTimeMillis() - timeNow) < 500) {
406                        return;
407                    }
408                    JSlider slider = (JSlider) evt.getSource();
409                    int pixelsPerImage = (1000000 * slider.getValue()) / listHolder[0].size() / 4;
410                    double aspect = dim[1] / (double) dim[0];
411                    int nx = (int) Math.sqrt(pixelsPerImage / aspect);
412                    int ny = (int) (aspect * nx);
413                    if (maintainSize.isSelected()) {
414                        // doesn't work
415                        lineMagSlider.setValue(getLineMagValue() - 1);
416                        lineMagSliderChanged(true);
417                    } else {
418                        numElementsFld.setText("" + nx);
419                        numLinesFld.setText("" + ny);
420                    }
421                    listHolder[0] = getImageList();
422                    AddeImageDescriptor aid = (AddeImageDescriptor) listHolder[0]
423                        .get(0);
424                    dim[0] = aid.getImageInfo().getElements();
425                    dim[1] = aid.getImageInfo().getLines();
426                    int numPixels = dim[0] * dim[1] * listHolder[0].size();
427                    double nmegs = (4 * numPixels) / (double) 1000000;
428                    sizeLbl.setText(StringUtil.padRight("  "
429                            + ((double) ((int) nmegs * 100)) / 100.0 + " MB",
430                        14));
431                }
432            };
433            slider.addChangeListener(sizeListener);
434
435            JComponent msgContents = GuiUtils
436                .vbox(
437                    new JLabel(
438                        "<html>You are about to load "
439                            + megs
440                            + " MB of imagery.<br>Are you sure you want to do this?<p><hr><p></html>"),
441                    GuiUtils.inset(GuiUtils.leftCenterRight(new JLabel(
442                            "Change Size: "),
443                        GuiUtils.inset(slider, 5), sizeLbl), 5));
444
445            if (!GuiUtils.askOkCancel("Image Size", msgContents)) {
446                return;
447            }
448            imageList = listHolder[0];
449        }
450
451        ImageDataset ids = new ImageDataset(getDatasetName(), imageList);
452        // make properties Hashtable to hand the station name
453        // to the AddeImageDataSource
454        Hashtable ht = new Hashtable();
455        ht.put(DataSelection.PROP_CHOOSERTIMEMATCHING, getDoTimeDrivers());
456        getDataSourceProperties(ht);
457        Object bandName = getSelectedBandName();
458        if ((bandName != null) && !bandName.equals(ALLBANDS.toString())) {
459            ht.put(DATA_NAME_KEY, bandName);
460        }
461        ht.put("allBands", bandDirs);
462        makeDataSource(ids, getDataSourceId(), ht);
463        saveServerState();
464        // uncheck the check box every time click the add source button
465        drivercbx.setSelected(false);
466        enableTimeWidgets();
467        setDoTimeDrivers(false);
468    }
469
470}