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