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;
030
031import static edu.wisc.ssec.mcidasv.util.McVGuiUtils.*;
032import static javax.swing.BorderFactory.*;
033import static javax.swing.GroupLayout.DEFAULT_SIZE;
034import static javax.swing.GroupLayout.Alignment.BASELINE;
035import static javax.swing.GroupLayout.Alignment.LEADING;
036import static javax.swing.GroupLayout.Alignment.TRAILING;
037import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
038import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED;
039import static ucar.unidata.util.IOUtil.hasSuffix;
040
041import java.awt.Component;
042import java.awt.Image;
043import java.awt.MediaTracker;
044import java.awt.Toolkit;
045import java.awt.event.FocusEvent;
046import java.awt.event.FocusListener;
047import java.io.File;
048import java.io.FileNotFoundException;
049import java.io.FileReader;
050import java.io.IOException;
051import java.io.InputStream;
052import java.io.Reader;
053import java.util.ArrayList;
054import java.util.Collections;
055import java.util.Hashtable;
056import java.util.List;
057
058import javax.swing.AbstractButton;
059import javax.swing.GroupLayout;
060import javax.swing.JButton;
061import javax.swing.JCheckBox;
062import javax.swing.JComboBox;
063import javax.swing.JComponent;
064import javax.swing.JFileChooser;
065import javax.swing.JLabel;
066import javax.swing.JPanel;
067import javax.swing.JRadioButton;
068import javax.swing.JTextField;
069import javax.swing.JToggleButton;
070import javax.swing.text.JTextComponent;
071
072import edu.wisc.ssec.mcidasv.util.CollectionHelpers;
073import org.slf4j.Logger;
074import org.slf4j.LoggerFactory;
075import org.w3c.dom.Element;
076
077import ucar.unidata.idv.IntegratedDataViewer;
078import ucar.unidata.idv.chooser.IdvChooser;
079import ucar.unidata.idv.chooser.IdvChooserManager;
080import ucar.unidata.util.GuiUtils;
081import ucar.unidata.util.IOUtil;
082import ucar.unidata.util.LayoutUtil;
083import ucar.unidata.util.TwoFacedObject;
084import ucar.unidata.xml.XmlUtil;
085import visad.util.ImageHelper;
086import edu.wisc.ssec.mcidasv.Constants;
087import edu.wisc.ssec.mcidasv.data.AxformInfo;
088import edu.wisc.ssec.mcidasv.data.EnviInfo;
089import edu.wisc.ssec.mcidasv.data.HeaderInfo;
090import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Position;
091import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Prefer;
092import edu.wisc.ssec.mcidasv.util.McVGuiUtils.TextColor;
093import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width;
094import edu.wisc.ssec.mcidasv.util.McVGuiUtils.IconPanel;
095
096public class FlatFileChooser extends IdvChooser implements Constants {
097
098    private static final long serialVersionUID = 1L;
099    private static final Logger logger = LoggerFactory.getLogger(FlatFileChooser.class);
100
101    /** Set default stride to keep dimensions within this */
102    private final int maxDefDim = 1000;
103    
104    // Properties associated with the button selector
105    private File dataFile;
106    private final JTextField dataFileText = new JTextField();
107    private JButton dataFileButton = new JButton();
108    private final JLabel dataFileDescription = new JLabel();
109    private final JLabel textDescription = new JLabel();
110    
111    // Dimensions
112    // elements, lines, bands
113    private final JTextComponent textElements = new JTextField();
114    private final JTextComponent textLines = new JTextField();
115    private final JTextComponent textBands = new JTextField();
116    private final JTextComponent textUnit = new JTextField();
117    private final JTextComponent textStride = new JTextField();
118    private final AbstractButton checkTranspose = new JCheckBox("Transpose elements/lines");
119    private final List<String> bandNames = new ArrayList<>(20);
120    private final List<String> bandFiles = new ArrayList<>(20);
121
122    // Navigation
123    // lat/lon files or bounds
124    private final JToggleButton radioLatLonFiles = new JRadioButton("Files", true);
125    private final JToggleButton radioLatLonBounds = new JRadioButton("Bounds", false);
126    private File latFile;
127    private File lonFile;
128    private final JLabel textLatFile = new JLabel();
129    private JButton buttonLatFile = new JButton();
130    private final JLabel textLonFile = new JLabel();
131    private JButton buttonLonFile = new JButton();
132    private JPanel panelLatLonFiles = new JPanel();
133    private final JTextComponent textLatUL = new JTextField();
134    private final JTextComponent textLonUL = new JTextField();
135    private final JTextComponent textLatLR = new JTextField();
136    private final JTextComponent textLonLR = new JTextField();
137    private JPanel panelLatLonBounds = new JPanel();
138    private final JTextComponent textLatLonScale = new JTextField();
139    private final AbstractButton checkEastPositive = new JCheckBox("East positive");
140
141
142    // Properties associated with the data file
143    // bytes/pixel, ASCII delimiter, endianness, interleave, offset, missing
144    private final JToggleButton radioBinary = new JRadioButton("Binary", true);
145    private final JToggleButton radioASCII = new JRadioButton("ASCII", false);
146    private final JToggleButton radioImage = new JRadioButton("Image", false);
147    private final JToggleButton radioEndianLittle = new JRadioButton("Little", true);
148    private final JToggleButton radioEndianBig = new JRadioButton("Big", false);
149    private final JComboBox<TwoFacedObject> comboByteFormat = new JComboBox<>();
150    private final JComboBox<TwoFacedObject> comboInterleave = new JComboBox<>();
151    private final JTextComponent textOffset = new JTextField("0");
152    private JPanel panelBinary = new JPanel();
153    private final JTextComponent textDelimiter = new JTextField();
154    private JPanel panelASCII = new JPanel();
155    private JPanel panelImage = new JPanel();
156    private final JTextComponent textMissing = new JTextField();
157    
158    private final List<TwoFacedObject> listByteFormat = CollectionHelpers.list(
159        new TwoFacedObject("1-byte unsigned integer", HeaderInfo.kFormat1ByteUInt),
160        new TwoFacedObject("2-byte signed integer", HeaderInfo.kFormat2ByteSInt),
161        new TwoFacedObject("4-byte signed integer", HeaderInfo.kFormat4ByteSInt),
162        new TwoFacedObject("4-byte float", HeaderInfo.kFormat4ByteFloat),
163        new TwoFacedObject("8-byte double", HeaderInfo.kFormat8ByteDouble),
164        new TwoFacedObject("2x8-byte complex number", HeaderInfo.kFormat2x8Byte),
165        new TwoFacedObject("2-byte unsigned integer", HeaderInfo.kFormat2ByteUInt)
166    );
167
168    private final List<TwoFacedObject> listInterleave = CollectionHelpers.list(
169        new TwoFacedObject("Sequential", HeaderInfo.kInterleaveSequential),
170        new TwoFacedObject("By line", HeaderInfo.kInterleaveByLine),
171        new TwoFacedObject("By pixel", HeaderInfo.kInterleaveByPixel)
172    );
173
174    private final JLabel statusLabel = new JLabel("Status");
175
176    /** Handle to the IDV. */
177    protected IntegratedDataViewer idv = getIdv();
178    
179    /**
180     * Super setStatus() takes a second string to enable "simple" mode
181     * which highlights the required component.  We don't really care
182     * about that feature, and we don't want getStatusLabel() to
183     * change the label background color.
184     */
185    @Override
186    public void setStatus(String statusString, String foo) {
187        if (statusString == null) {
188            statusString = "";
189        }
190        statusLabel.setText(statusString);
191    }
192
193    /**
194     * Create the FileChooser, passing in the manager and the xml element
195     * from choosers.xml
196     *
197     * @param mgr The manager
198     * @param root The xml root
199     */
200    public FlatFileChooser(IdvChooserManager mgr, Element root) {
201        super(mgr, root);
202        
203        loadButton = makeImageTextButton(ICON_ACCEPT_SMALL, getLoadCommandName());
204        loadButton.setActionCommand(getLoadCommandName());
205        loadButton.addActionListener(this);
206
207        dataFileButton = makeImageButton(ICON_OPEN, "Open file");
208        dataFileButton.addActionListener(e -> {
209            dataFile = getDataFile(dataFile);
210            if (dataFile != null) {
211                dataFileText.setText(dataFile.getAbsolutePath());
212                inspectDataFile(dataFile);
213            }
214        });
215        dataFileText.addActionListener(e -> {
216            dataFile = new File(dataFileText.getText());
217            inspectDataFile(dataFile);
218        });
219        dataFileText.addFocusListener(new FocusListener() {
220            @Override public void focusGained(FocusEvent e) {}
221            @Override public void focusLost(FocusEvent e) {
222                dataFile = new File(dataFileText.getText());
223                inspectDataFile(dataFile);
224            }
225        });
226        
227        radioLatLonFiles.addActionListener(e -> checkSetLatLon());
228        radioLatLonBounds.addActionListener(e -> checkSetLatLon());
229
230        buttonLatFile = makeImageButton(ICON_OPEN, "Select latitude file");
231        buttonLatFile.addActionListener(e -> {
232            latFile = getDataFile(latFile);
233            if (latFile != null) {
234                textLatFile.setText(latFile.getName());
235            }
236        });
237        buttonLonFile = makeImageButton(ICON_OPEN, "Select longitude file");
238        buttonLonFile.addActionListener(e -> {
239            lonFile = getDataFile(lonFile);
240            if (lonFile != null) {
241                textLonFile.setText(lonFile.getName());
242            }
243        });
244        GuiUtils.buttonGroup(radioLatLonFiles, radioLatLonBounds);
245        
246        radioBinary.addActionListener(e -> checkSetBinaryASCIIImage());
247        radioASCII.addActionListener(e -> checkSetBinaryASCIIImage());
248        radioImage.addActionListener(e -> checkSetBinaryASCIIImage());
249
250        GuiUtils.buttonGroup(radioBinary, radioASCII, radioImage);
251        GuiUtils.buttonGroup(radioEndianLittle, radioEndianBig);
252
253        GuiUtils.setListData(comboByteFormat, listByteFormat);
254        GuiUtils.setListData(comboInterleave, listInterleave);
255
256        setHaveData(false);
257    }
258    
259    /**
260     * enable/disable widgets for navigation
261     */
262    private void checkSetLatLon() {
263        boolean isFile = radioLatLonFiles.isSelected();
264        GuiUtils.enableTree(panelLatLonFiles, isFile);
265        GuiUtils.enableTree(panelLatLonBounds, !isFile);
266    }
267
268    /**
269     * enable/disable widgets for binary/ASCII
270     */
271    private void checkSetBinaryASCIIImage() {
272        GuiUtils.enableTree(panelBinary, radioBinary.isSelected());
273        GuiUtils.enableTree(panelASCII, radioASCII.isSelected());
274        GuiUtils.enableTree(panelImage, radioImage.isSelected());
275    }
276
277    /**
278     * Set whether the user has made a selection that contains data.
279     *
280     * @param have   true to set the haveData property.  Enables the
281     *               loading button
282     */
283    @Override public void setHaveData(boolean have) {
284        super.setHaveData(have);
285        updateStatus();
286    }
287    
288    /**
289     * Set the status message appropriately
290     */
291    @Override protected void updateStatus() {
292        super.updateStatus();
293        checkSetLatLon();
294        checkSetBinaryASCIIImage();
295        if (!getHaveData()) {
296            setStatus("Select a file"); 
297        }
298    }
299
300    private static boolean isImage(File f) {
301        String s = f.getName();
302        return hasSuffix(s, ".gif") || hasSuffix(s, ".jpg") || hasSuffix(s, ".png");
303    }
304
305    private static boolean isXml(File f) {
306        String s = f.getName();
307        return hasSuffix(s, ".xml") || hasSuffix(s, ".ximg");
308    }
309
310    private void inspectDataFile(File thisFile) {
311        if ((thisFile == null) || thisFile.getName().isEmpty()) {
312            dataFileDescription.setText("");
313            setHaveData(false);
314        } else if (!thisFile.exists()) {
315            dataFileDescription.setText("File does not exist");
316            setHaveData(false);
317        } else {
318            try (Reader fr = new FileReader(thisFile)) {
319                char[] first80c = new char[80];
320                fr.read(first80c, 0, 80);
321                String first80 = new String(first80c);
322
323                clearValues();
324
325                boolean doStride = false;
326
327                if (isImage(thisFile)) {
328                    dataFileDescription.setText("Image file");
329                    processImageFile(thisFile);
330                } else if (isXml(thisFile)) {
331                    dataFileDescription.setText("XML image header file");
332                    processXmlHeaderFile(thisFile);
333                } else if (first80.contains("                     Space Science & Engineering Center")) {
334                    dataFileDescription.setText("McIDAS-X AXFORM header file");
335                    processAxformHeaderFile(thisFile);
336                    doStride = true;
337                } else if (isENVI()) {
338                    dataFileDescription.setText("ENVI Data File");
339                    logger.trace("Found ENVI file, about to process header...");
340                    processEnviHeaderFile(new File(dataFile.getAbsolutePath().replace(".img", ".hdr")));
341                    doStride = true;
342                } else {
343                    dataFileDescription.setText("Binary, ASCII or Image data");
344                    processGenericFile(thisFile);
345                    doStride = true;
346                }
347
348                // Default the stride
349                int newStride = 1;
350                if (doStride) {
351                    String textLinesText = textLines.getText();
352                    String textElementsText = textElements.getText();
353                    if (!(textLinesText.isEmpty() || textElementsText.isEmpty())) {
354                        int myLines = Integer.parseInt(textLinesText);
355                        int myElements = Integer.parseInt(textElementsText);
356                        if ((myLines > maxDefDim) || (myElements > maxDefDim)) {
357                            newStride = Math.max((int) Math.ceil((float) myLines / (float) maxDefDim), (int) Math.ceil((float) myElements / (float) maxDefDim));
358                        }
359                    }
360                }
361                textStride.setText(Integer.toString(newStride));
362                setHaveData(true);
363            } catch (Exception e) {
364                logger.error("error inspecting file '" + thisFile + '\'', e);
365            }
366        }
367    }
368        
369    private boolean isENVI() {
370        // look for a corresponding header file
371        // filename.replace(".hdr", ".img");
372        boolean result = false;
373        File f = new File(dataFile.getAbsolutePath().replace(".img", ".hdr"));
374//        if (!f.exists()) {
375//            return false;
376//        }
377        if (f.exists()) {
378            try (Reader fr = new FileReader(f)) {
379                char[] first80c = new char[80];
380                fr.read(first80c, 0, 80);
381                fr.close();
382                String first80 = new String(first80c);
383                if (first80.contains("ENVI")) {
384                    result = true;
385                }
386            } catch (FileNotFoundException fnfe) {
387                logger.error("could not locate file", fnfe);
388            } catch (IOException ioe) {
389                logger.error("could not read file", ioe);
390            }
391        }
392        return result;
393    }
394
395        /**
396     * Special processing for a known data type.
397     *
398     * <p>This deals specifically with AXFORM header files.</p>
399     *
400     * @param thisFile AXFORM header file.
401     */
402    private void processAxformHeaderFile(File thisFile) {
403        bandNames.clear();
404        bandFiles.clear();
405
406        try {
407            AxformInfo axformInfo = new AxformInfo(thisFile);
408            
409            // Set the properties in the GUI
410            textDescription.setText(axformInfo.getParameter(HeaderInfo.DESCRIPTION, ""));
411            textElements.setText(axformInfo.getParameter(HeaderInfo.ELEMENTS, 0).toString());
412            textLines.setText(axformInfo.getParameter(HeaderInfo.LINES, 0).toString());
413            textUnit.setText(axformInfo.getParameter(HeaderInfo.UNIT, ""));
414            bandNames.addAll(axformInfo.getParameter(HeaderInfo.BANDNAMES, Collections.emptyList()));
415            bandFiles.addAll(axformInfo.getParameter(HeaderInfo.BANDFILES, Collections.emptyList()));
416            textBands.setText(Integer.toString(bandNames.size()));
417            textOffset.setText(axformInfo.getParameter(HeaderInfo.OFFSET, 0).toString());
418            textMissing.setText(axformInfo.getParameter(HeaderInfo.MISSINGVALUE, (float)0).toString());
419            
420            Integer dataType = axformInfo.getParameter(HeaderInfo.DATATYPE, HeaderInfo.kFormatUnknown);
421            Boolean bigEndian = axformInfo.getParameter(HeaderInfo.BIGENDIAN, Boolean.FALSE);
422            if (dataType == HeaderInfo.kFormatASCII) {
423                radioASCII.setSelected(true);
424            } else if (dataType == HeaderInfo.kFormatImage) {
425                radioImage.setSelected(true);
426            } else {
427                radioBinary.setSelected(true);
428                TwoFacedObject tfo = TwoFacedObject.findId(dataType.intValue(), listByteFormat);
429                if (tfo != null) {
430                    comboByteFormat.setSelectedItem(tfo);
431                }
432                tfo = TwoFacedObject.findId(HeaderInfo.kInterleaveSequential, listInterleave);
433                if (tfo != null) {
434                    comboInterleave.setSelectedItem(tfo);
435                }
436            }
437            
438            radioEndianLittle.setSelected(!bigEndian);
439            radioEndianBig.setSelected(bigEndian);
440
441            List<String> latlonFiles = axformInfo.getParameter(HeaderInfo.NAVFILES, new ArrayList<String>(4));
442            if (latlonFiles.size() == 2) {
443                latFile = new File(latlonFiles.get(0));
444                lonFile = new File(latlonFiles.get(1));
445            }
446
447            if ((latFile == null) || (lonFile == null)) {
448                radioLatLonBounds.setSelected(true);
449            } else {
450                textLatFile.setText(latFile.getName());
451                textLonFile.setText(lonFile.getName());
452                radioLatLonFiles.setSelected(true);
453            }
454            
455            textLatLonScale.setText("100");
456            checkEastPositive.setSelected(true);
457        } catch (Exception e) {
458            logger.error("error processing AXFORM header file", e);
459        }
460    }
461
462    /**
463     * Special processing for a known data type.
464     *
465     * <p>This deals specifically with ENVI header files.</p>
466     *
467     * @param thisFile ENVI header file.
468     */
469    private void processEnviHeaderFile(File thisFile) {
470        try {
471            EnviInfo enviInfo = new EnviInfo(thisFile);
472            
473            // Set the properties in the GUI
474            textDescription.setText(enviInfo.getParameter(HeaderInfo.DESCRIPTION, ""));
475            textElements.setText(enviInfo.getParameter(HeaderInfo.ELEMENTS, 0).toString());
476            textLines.setText(enviInfo.getParameter(HeaderInfo.LINES, 0).toString());
477            textUnit.setText(enviInfo.getParameter(HeaderInfo.UNIT, ""));
478            bandNames.addAll(enviInfo.getParameter(HeaderInfo.BANDNAMES, Collections.emptyList()));
479            bandFiles.addAll(enviInfo.getParameter(HeaderInfo.BANDFILES, Collections.emptyList()));
480            textBands.setText(Integer.toString(bandNames.size()));
481            textOffset.setText(enviInfo.getParameter(HeaderInfo.OFFSET, 0).toString());
482            textMissing.setText(enviInfo.getParameter(HeaderInfo.MISSINGVALUE, (float)0).toString());
483
484            Integer dataType = enviInfo.getParameter(HeaderInfo.DATATYPE, HeaderInfo.kFormatUnknown);
485            String interleaveType = enviInfo.getParameter(HeaderInfo.INTERLEAVE, HeaderInfo.kInterleaveSequential);
486            Boolean bigEndian = enviInfo.getParameter(HeaderInfo.BIGENDIAN, Boolean.FALSE);
487            radioBinary.setSelected(true);
488            TwoFacedObject tfo = TwoFacedObject.findId(dataType.intValue(), listByteFormat);
489            if (tfo != null) {
490                comboByteFormat.setSelectedItem(tfo);
491            }
492            tfo = TwoFacedObject.findId(interleaveType, listInterleave);
493            if (tfo != null) {
494                comboInterleave.setSelectedItem(tfo);
495            }
496            
497            radioEndianLittle.setSelected(!bigEndian);
498            radioEndianBig.setSelected(bigEndian);
499
500            // Look for a geo.hdr file that contains Latitude and Longitude bands
501            String parent = thisFile.getParent();
502            if (parent == null) {
503                parent = ".";
504            }
505            String navFile = thisFile.getName().replace(".hdr", "");
506            int lastDot = navFile.lastIndexOf('.');
507            if (lastDot >= 0) {
508                navFile = navFile.substring(0, lastDot) + ".geo.hdr";
509            }
510            navFile = parent + '/' + navFile;
511            EnviInfo navInfo = new EnviInfo(navFile);
512            if (navInfo.isNavHeader()) {
513                latFile = new File(navFile);
514                lonFile = new File(navFile);
515            }
516            
517            if ((latFile == null) || (lonFile == null)) {
518                radioLatLonBounds.setSelected(true);
519            }
520            else {
521                textLatFile.setText(latFile.getName());
522                textLonFile.setText(lonFile.getName());
523                radioLatLonFiles.setSelected(true);
524            }
525            
526            // fill in Lat/Lon bounds if we can
527            if (enviInfo.isHasBounds()) {
528                textLatUL.setText(enviInfo.getParameter("BOUNDS.ULLAT", ""));
529                textLonUL.setText(enviInfo.getParameter("BOUNDS.ULLON", ""));
530                textLatLR.setText(enviInfo.getParameter("BOUNDS.LRLAT", ""));
531                textLonLR.setText(enviInfo.getParameter("BOUNDS.LRLON", ""));
532            }
533            
534            textLatLonScale.setText("1");
535            checkEastPositive.setSelected(false);
536        } catch (Exception e) {
537            logger.error("error processing ENVI header file", e);
538        }
539    }
540
541    /**
542     * Special processing for a known data type.
543     *
544     * <p>This deals specifically with XML header files.</p>
545     *
546     * @param thisFile XML header file.
547     */
548    private void processXmlHeaderFile(File thisFile) {
549        try {
550            
551            bandFiles.clear();
552            bandNames.clear();
553
554            Element root = XmlUtil.getRoot(thisFile.getAbsolutePath(), getClass());
555            if (!"image".equals(root.getTagName())) {
556                processGenericFile(thisFile);
557                return;
558            }
559            
560            String description = XmlUtil.getAttribute(root, "name", (String)null);
561            String url = "";
562            if (XmlUtil.hasAttribute(root, "url")) {
563                url = XmlUtil.getAttribute(root, "url");
564                if (description.isEmpty()) {
565                    description = url;
566                }
567                String parent = thisFile.getParent();
568                if (parent == null) {
569                    parent = ".";
570                }
571                url = parent + '/' + url;
572            }
573            else {
574                processGenericFile(thisFile);
575                return;
576            }
577            if (XmlUtil.hasAttribute(root, "ullat")) {
578                radioLatLonBounds.setSelected(true);
579                textLatUL.setText(XmlUtil.getAttribute(root, "ullat"));
580            }
581            if (XmlUtil.hasAttribute(root, "ullon")) {
582                radioLatLonBounds.setSelected(true);
583                textLonUL.setText(XmlUtil.getAttribute(root, "ullon"));
584            }
585            if (XmlUtil.hasAttribute(root, "lrlat")) {
586                radioLatLonBounds.setSelected(true);
587                textLatLR.setText(XmlUtil.getAttribute(root, "lrlat"));
588            }
589            if (XmlUtil.hasAttribute(root, "lrlon")) {
590                radioLatLonBounds.setSelected(true);
591                textLonLR.setText(XmlUtil.getAttribute(root, "lrlon"));
592            }
593
594            // Try to read the referenced image to get lines and elements
595            setStatus("Loading image");
596            InputStream is   = null;
597            is = IOUtil.getInputStream(url, getClass());
598            byte[] imageContent = IOUtil.readBytes(is);
599            Image image = Toolkit.getDefaultToolkit().createImage(imageContent);
600            MediaTracker tracker = new MediaTracker(this); 
601            tracker.addImage(image, 0);
602            try { 
603                tracker.waitForAll(); 
604            } catch(InterruptedException e) {}
605            ImageHelper ih = new ImageHelper();
606            image.getWidth(ih);
607            if (ih.badImage) {
608                throw new IllegalStateException("Bad image: " + url);
609            }
610            int elements = image.getWidth(ih);
611            int lines = image.getHeight(ih);
612            
613            // Bands
614            bandFiles.add(url);
615            bandNames.add("XML image file");
616            
617            // Set the properties in the GUI
618            textDescription.setText(description);
619            textElements.setText(Integer.toString(elements));
620            textLines.setText(Integer.toString(lines));
621            textBands.setText(Integer.toString(bandNames.size()));
622
623            radioImage.setSelected(true);
624            textMissing.setText(Float.toString(-1.0F));
625            
626            textLatLonScale.setText("1");
627            checkEastPositive.setSelected(false);
628        } catch (Exception e) {
629            logger.error("error processing XML header file", e);
630        }
631    }
632
633    /**
634     * Special processing for a known data type.
635     *
636     * <p>This deals specifically with {@literal "image"} files.</p>
637     *
638     * @param thisFile Image file.
639     */
640    private void processImageFile(File thisFile) {
641        try {
642
643            bandFiles.clear();
644            bandNames.clear();
645
646            String description = thisFile.getName();
647            String url = thisFile.getAbsolutePath();
648
649            // Try to read the referenced image to get lines and elements
650            setStatus("Loading image");
651            InputStream is   = null;
652            is = IOUtil.getInputStream(url, getClass());
653            byte[] imageContent = IOUtil.readBytes(is);
654            Image image = Toolkit.getDefaultToolkit().createImage(imageContent);
655            MediaTracker tracker = new MediaTracker(this); 
656            tracker.addImage(image, 0);
657            try { 
658                tracker.waitForAll(); 
659            } catch (InterruptedException e) {}
660            ImageHelper ih = new ImageHelper();
661            image.getWidth(ih);
662            if (ih.badImage) {
663                throw new IllegalStateException("Bad image: " + url);
664            }
665
666            // Bands
667            bandFiles.add(url);
668            bandNames.add("Image file");
669
670            // Set the properties in the GUI
671            textDescription.setText(description);
672            textElements.setText(Integer.toString(image.getWidth(ih)));
673            textLines.setText(Integer.toString(image.getHeight(ih)));
674            textBands.setText(Integer.toString(bandNames.size()));
675            
676            radioImage.setSelected(true);
677            textMissing.setText(Float.toString(-1.0F));
678            
679            textLatLonScale.setText("1");
680            checkEastPositive.setSelected(false);
681        }
682        catch (Exception e) {
683            logger.error("error processing image file", e);
684        }
685    }
686
687    /**
688     * Special processing for an unknown data type.
689     *
690     * <p>Can we glean anything about the file by inspecting it more?</p>
691     *
692     * @param thisFile Unknown file type.
693     */
694    private void processGenericFile(File thisFile) {
695
696        clearValues();
697
698        // Set appropriate defaults
699        // Bands
700        bandFiles.clear();
701        bandFiles.add(thisFile.getAbsolutePath());
702        bandNames.clear();
703        bandNames.add("Flat data");
704
705        // Set the properties in the GUI
706        textDescription.setText(thisFile.getName());
707//      textElements.setText(Integer.toString(elements));
708//      textLines.setText(Integer.toString(lines));
709        textBands.setText("1");
710        
711        radioBinary.setSelected(true);
712        
713        textLatLonScale.setText("1");
714        checkEastPositive.setSelected(false);
715
716    }
717    
718    /**
719     * Clear out any data values presented to the user
720     */
721    private void clearValues() {
722        textDescription.setText("");
723        textElements.setText("");
724        textLines.setText("");
725        textBands.setText("");
726        textUnit.setText("");
727        textStride.setText("");
728        checkTranspose.setSelected(false);
729
730        textLatFile.setText("");
731        textLonFile.setText("");
732        textLatUL.setText("");
733        textLonUL.setText("");
734        textLatLR.setText("");
735        textLonLR.setText("");
736
737        textLatLonScale.setText("");
738        checkEastPositive.setSelected(false);
739
740        textOffset.setText("0");
741        textMissing.setText("");
742    }
743    
744    /**
745     * Ask the user for a data file.
746     *
747     * @param thisFile File or directory to use as initial location.
748     *
749     * @return Selected data file.
750     */
751    private static File getDataFile(File thisFile) {
752        JFileChooser fileChooser = new JFileChooser(thisFile);
753        fileChooser.setMultiSelectionEnabled(false);
754        int status = fileChooser.showOpenDialog(null);
755        if (status == JFileChooser.APPROVE_OPTION) {
756            thisFile = fileChooser.getSelectedFile();
757        }
758        return thisFile;
759    }
760    
761    /**
762     * Get the name of the dataset.
763     *
764     * @return descriptive name of the dataset.
765     */
766    public static String getDatasetName() {
767        return "Data Set Name";
768    }
769    
770    /**
771     * Get the properties from the datasource
772     *
773     * @param ht  a Hashtable of properties
774     */
775    @Override protected void getDataSourceProperties(Hashtable ht) {
776        super.getDataSourceProperties(ht);
777        ht.put("FLAT.NAME", textDescription.getText());
778        ht.put("FLAT.ELEMENTS", textElements.getText());
779        ht.put("FLAT.LINES", textLines.getText());
780        ht.put("FLAT.BANDNAMES", bandNames);
781        ht.put("FLAT.BANDFILES", bandFiles);
782        ht.put("FLAT.UNIT", textUnit.getText());
783        ht.put("FLAT.STRIDE", textStride.getText());
784        ht.put("FLAT.TRANSPOSE", checkTranspose.isSelected());
785        ht.put("FLAT.MISSING", textMissing.getText());
786        
787        // Navigation
788        if (radioLatLonFiles.isSelected()) {
789            ht.put("NAV.TYPE", "FILES");
790            ht.put("FILE.LAT", latFile.getAbsolutePath());
791            ht.put("FILE.LON", lonFile.getAbsolutePath());
792        } else if (radioLatLonBounds.isSelected()) {
793            ht.put("NAV.TYPE", "BOUNDS");
794            ht.put("BOUNDS.ULLAT", textLatUL.getText());
795            ht.put("BOUNDS.ULLON", textLonUL.getText());
796            ht.put("BOUNDS.LRLAT", textLatLR.getText());
797            ht.put("BOUNDS.LRLON", textLonLR.getText());
798        } else {
799            ht.put("NAV.TYPE", "UNKNOWN");
800        }
801        ht.put("NAV.SCALE", textLatLonScale.getText());
802        ht.put("NAV.EASTPOS", checkEastPositive.isSelected());
803
804        // Data type
805        if (radioBinary.isSelected()) {
806            TwoFacedObject format = (TwoFacedObject) comboByteFormat.getSelectedItem();
807            TwoFacedObject interleave = (TwoFacedObject) comboInterleave.getSelectedItem();
808            ht.put("FORMAT.TYPE", "BINARY");
809            ht.put("BINARY.FORMAT", format.getId());
810            ht.put("BINARY.INTERLEAVE", interleave.getId());
811            ht.put("BINARY.BIGENDIAN", radioEndianBig.isSelected());
812            ht.put("BINARY.OFFSET", textOffset.getText());
813        } else if (radioASCII.isSelected()) {
814            ht.put("FORMAT.TYPE", "ASCII");
815            ht.put("ASCII.DELIMITER", textDelimiter.getText());
816        } else if (radioImage.isSelected()) {
817            ht.put("FORMAT.TYPE", "IMAGE");
818        } else {
819            ht.put("FORMAT.TYPE", "UNKNOWN");
820        }
821
822    }
823        
824    /**
825     * User said go, so we go.
826     *
827     * <p>Simply get the list of images from the imageChooser and create the
828     * {@code FILE.FLAT} {@code DataSource}.</p>
829     */
830    public void doLoadInThread() {
831        String definingObject = dataFileText.getText();
832        String dataType = "FILE.FLAT";
833        
834        Hashtable properties = new Hashtable();
835        getDataSourceProperties(properties);
836        
837        makeDataSource(definingObject, dataType, properties);
838    }
839    
840    /**
841     * Creates the dimensions inner panel.
842     *
843     * @return The {@literal "dimensions"} panel.
844     */
845    protected JPanel makeDimensionsPanel() {
846        JPanel myPanel = new JPanel();
847        myPanel.setBorder(createTitledBorder("Dimensions"));
848        
849        JLabel elementsLabel = makeLabelRight("Elements:");
850        setComponentWidth(textElements);
851        
852        JLabel linesLabel = makeLabelRight("Lines:");
853        setComponentWidth(textLines);
854        
855        JLabel bandsLabel = makeLabelRight("Bands:");
856        setComponentWidth(textBands);
857
858        JLabel unitLabel = makeLabelRight("Units:");
859        setComponentWidth(textUnit);
860
861        JLabel strideLabel = makeLabelRight("Sampling:");
862        setComponentWidth(textStride);
863
864//      JLabel transposeLabel = McVGuiUtils.makeLabelRight("");
865        
866        GroupLayout layout = new GroupLayout(myPanel);
867        myPanel.setLayout(layout);
868        layout.setHorizontalGroup(
869            layout.createParallelGroup(LEADING)
870            .addGroup(layout.createSequentialGroup()
871                .addContainerGap()
872                .addGroup(layout.createParallelGroup(LEADING)
873                    .addGroup(layout.createSequentialGroup()
874                        .addComponent(elementsLabel)
875                        .addGap(GAP_RELATED)
876                        .addComponent(textElements))
877                    .addGroup(layout.createSequentialGroup()
878                        .addComponent(linesLabel)
879                        .addGap(GAP_RELATED)
880                        .addComponent(textLines))
881                    .addGroup(layout.createSequentialGroup()
882                        .addComponent(bandsLabel)
883                        .addGap(GAP_RELATED)
884                        .addComponent(textBands))
885                    .addGroup(layout.createSequentialGroup()
886                        .addComponent(unitLabel)
887                        .addGap(GAP_RELATED)
888                        .addComponent(textUnit))
889                    .addGroup(layout.createSequentialGroup()
890                        .addComponent(strideLabel)
891                        .addGap(GAP_RELATED)
892                        .addComponent(textStride)))
893                .addContainerGap())
894        );
895        layout.setVerticalGroup(
896            layout.createParallelGroup(LEADING)
897            .addGroup(TRAILING, layout.createSequentialGroup()
898                .addContainerGap()
899                .addGroup(layout.createParallelGroup(BASELINE)
900                    .addComponent(textElements)
901                    .addComponent(elementsLabel))
902                .addPreferredGap(RELATED)
903                .addGroup(layout.createParallelGroup(BASELINE)
904                    .addComponent(textLines)
905                    .addComponent(linesLabel))
906                .addPreferredGap(RELATED)
907                .addGroup(layout.createParallelGroup(BASELINE)
908                    .addComponent(textBands)
909                    .addComponent(bandsLabel))
910                .addPreferredGap(RELATED)
911                .addGroup(layout.createParallelGroup(BASELINE)
912                    .addComponent(textUnit)
913                    .addComponent(unitLabel))
914                .addPreferredGap(RELATED)
915                .addGroup(layout.createParallelGroup(BASELINE)
916                    .addComponent(textStride)
917                    .addComponent(strideLabel))
918                .addContainerGap())
919        );
920        
921        return myPanel;
922    }
923
924    /**
925     * Creates the navigation inner panel.
926     *
927     * @return The {@literal "navigation"} panel.
928     */
929    protected JPanel makeNavigationPanel() {
930        JPanel myPanel = new JPanel();
931        myPanel.setBorder(createTitledBorder("Navigation"));
932        
933        setComponentWidth(textLatFile, Width.DOUBLE);
934        setComponentWidth(textLonFile, Width.DOUBLE);
935        panelLatLonFiles = topBottom(
936            LayoutUtil.leftRight(makeLabeledComponent("Latitude:", textLatFile), buttonLatFile),
937            LayoutUtil.leftRight(makeLabeledComponent("Longitude:", textLonFile), buttonLonFile),
938            Prefer.NEITHER);
939        
940        // Images to make the bounds more clear
941        Component urPanel = new IconPanel("/edu/wisc/ssec/mcidasv/images/upper_right.gif");
942        Component llPanel = new IconPanel("/edu/wisc/ssec/mcidasv/images/lower_left.gif");
943        
944        setComponentWidth(textLatUL);
945        setComponentWidth(textLonUL);
946        setComponentWidth(textLatLR);
947        setComponentWidth(textLonLR);
948        panelLatLonBounds = topBottom(
949            makeLabeledComponent("UL Lat/Lon:", LayoutUtil.leftRight(GuiUtils.hbox(textLatUL, textLonUL), urPanel)),
950            makeLabeledComponent("LR Lat/Lon:", LayoutUtil.leftRight(llPanel, GuiUtils.hbox(textLatLR, textLonLR))),
951            Prefer.NEITHER);
952        
953        setComponentWidth(radioLatLonFiles);
954        setComponentWidth(radioLatLonBounds);
955        
956        JLabel labelScale = makeLabelRight("Scale:");
957        setComponentWidth(textLatLonScale);
958        
959        JPanel panelScaleEastPositive = LayoutUtil.hbox(textLatLonScale, checkEastPositive);
960        
961        GroupLayout layout = new GroupLayout(myPanel);
962        myPanel.setLayout(layout);
963        layout.setHorizontalGroup(
964            layout.createParallelGroup(LEADING)
965            .addGroup(layout.createSequentialGroup()
966                .addContainerGap()
967                .addGroup(layout.createParallelGroup(LEADING)
968                    .addGroup(layout.createSequentialGroup()
969                        .addComponent(radioLatLonFiles)
970                        .addGap(GAP_RELATED)
971                        .addComponent(panelLatLonFiles))
972                    .addGroup(layout.createSequentialGroup()
973                        .addComponent(radioLatLonBounds)
974                        .addGap(GAP_RELATED)
975                        .addComponent(panelLatLonBounds))
976                    .addGroup(layout.createSequentialGroup()
977                        .addComponent(labelScale)
978                        .addGap(GAP_RELATED)
979                        .addComponent(panelScaleEastPositive)))
980                .addContainerGap())
981        );
982        layout.setVerticalGroup(
983            layout.createParallelGroup(LEADING)
984            .addGroup(TRAILING, layout.createSequentialGroup()
985                .addContainerGap()
986                .addGroup(layout.createParallelGroup(BASELINE)
987                    .addComponent(radioLatLonFiles)
988                    .addComponent(panelLatLonFiles))
989                .addPreferredGap(RELATED)
990                .addGroup(layout.createParallelGroup(BASELINE)
991                    .addComponent(radioLatLonBounds)
992                    .addComponent(panelLatLonBounds))
993                .addPreferredGap(RELATED)
994                .addGroup(layout.createParallelGroup(BASELINE)
995                    .addComponent(labelScale)
996                    .addComponent(panelScaleEastPositive))
997                .addContainerGap())
998        );
999        return myPanel;
1000    }
1001
1002    /**
1003     * Creates the format inner panel.
1004     *
1005     * @return The {@literal "format"} panel.
1006     */
1007    protected JPanel makeFormatPanel() {
1008        JPanel myPanel = new JPanel();
1009        myPanel.setBorder(createTitledBorder("Format"));
1010
1011        setComponentWidth(radioBinary);
1012        setComponentWidth(radioASCII);
1013        setComponentWidth(radioImage);
1014        setComponentWidth(radioEndianLittle);
1015        setComponentWidth(radioEndianBig);
1016
1017        setComponentWidth(comboByteFormat, Width.TRIPLE);
1018        setComponentWidth(comboInterleave, Width.DOUBLE);
1019        setComponentWidth(textOffset, Width.HALF);
1020
1021        panelBinary = topBottom(
1022            topBottom(
1023                makeLabeledComponent("Byte format:", comboByteFormat),
1024                makeLabeledComponent("Interleave:", comboInterleave),
1025                Prefer.NEITHER),
1026            topBottom(
1027                makeLabeledComponent("Endian:", GuiUtils.hbox(radioEndianLittle, radioEndianBig, GAP_RELATED)),
1028                makeLabeledComponent("Offset:", makeComponentLabeled(textOffset, "bytes")),
1029                Prefer.NEITHER),
1030            Prefer.NEITHER);
1031        
1032        setComponentWidth(textDelimiter, Width.HALF);
1033        panelASCII = makeLabeledComponent("Delimiter:", textDelimiter);
1034        panelImage = new JPanel();
1035        
1036        JLabel missingLabel = makeLabelRight("Missing value:");
1037        setComponentWidth(textMissing);
1038        JPanel missingPanel = makeComponentLabeled(textMissing, "");
1039
1040        GroupLayout layout = new GroupLayout(myPanel);
1041        myPanel.setLayout(layout);
1042        layout.setHorizontalGroup(
1043            layout.createParallelGroup(LEADING)
1044            .addGroup(layout.createSequentialGroup()
1045                .addContainerGap()
1046                .addGroup(layout.createParallelGroup(LEADING)
1047                    .addGroup(layout.createSequentialGroup()
1048                        .addComponent(radioBinary)
1049                        .addGap(GAP_RELATED)
1050                        .addComponent(panelBinary))
1051                    .addGroup(layout.createSequentialGroup()
1052                        .addComponent(radioASCII)
1053                        .addGap(GAP_RELATED)
1054                        .addComponent(panelASCII))
1055                    .addGroup(layout.createSequentialGroup()
1056                        .addComponent(radioImage)
1057                        .addGap(GAP_RELATED)
1058                        .addComponent(panelImage))
1059                    .addGroup(layout.createSequentialGroup()
1060                        .addComponent(missingLabel)
1061                        .addGap(GAP_RELATED)
1062                        .addComponent(missingPanel)))
1063                .addContainerGap())
1064        );
1065        layout.setVerticalGroup(
1066            layout.createParallelGroup(LEADING)
1067            .addGroup(TRAILING, layout.createSequentialGroup()
1068                .addContainerGap()
1069                .addGroup(layout.createParallelGroup(BASELINE)
1070                    .addComponent(radioBinary)
1071                    .addComponent(panelBinary))
1072                .addPreferredGap(RELATED)
1073                .addGroup(layout.createParallelGroup(BASELINE)
1074                    .addComponent(radioASCII)
1075                    .addComponent(panelASCII))
1076                .addPreferredGap(RELATED)
1077                .addGroup(layout.createParallelGroup(BASELINE)
1078                    .addComponent(radioImage)
1079                    .addComponent(panelImage))
1080                .addPreferredGap(RELATED)
1081                .addGroup(layout.createParallelGroup(BASELINE)
1082                    .addComponent(missingLabel)
1083                    .addComponent(missingPanel))
1084                .addContainerGap())
1085        );
1086        return myPanel;
1087    }
1088
1089    /**
1090     * Creates the main panel properties panel.
1091     *
1092     * @return The {@literal "properties"} panel.
1093     */
1094    protected JPanel makePropertiesPanel() {
1095        JPanel topPanel = sideBySide(makeDimensionsPanel(), makeNavigationPanel());
1096        JPanel bottomPanel = makeFormatPanel();
1097        return topBottom(topPanel, bottomPanel, Prefer.NEITHER);
1098    }
1099    
1100    /**
1101     * Builds the GUI of the flat file chooser.
1102     *
1103     * @return GUI of this chooser.
1104     */
1105    protected JComponent doMakeContents() {
1106
1107        Element chooserNode = getXmlNode();
1108        String path = (String)idv.getPreference(PREF_DEFAULTDIR + getId());
1109        if (path == null) {
1110            path = XmlUtil.getAttribute(chooserNode, "path", (String)null);
1111        }
1112
1113        JPanel myPanel = new JPanel();
1114        
1115        // File
1116        JLabel fileLabel = makeLabelRight("File:");
1117        setComponentWidth(dataFileText, Width.DOUBLEDOUBLE);
1118        
1119        JLabel typeLabel = makeLabelRight("Type:");
1120        setLabelBold(dataFileDescription, true);
1121
1122        JLabel descriptionLabel = makeLabelRight("Description:");
1123        setLabelBold(textDescription, true);
1124
1125        JLabel propertiesLabel = makeLabelRight("Properties:");
1126        JPanel propertiesPanel = makePropertiesPanel();
1127        
1128        JLabel statusLabelLabel = makeLabelRight("");
1129        setLabelPosition(statusLabel, Position.RIGHT);
1130        setComponentColor(statusLabel, TextColor.STATUS);
1131                
1132        JButton helpButton = makeImageButton(ICON_HELP, "Show help");
1133        helpButton.setActionCommand(GuiUtils.CMD_HELP);
1134        helpButton.addActionListener(this);
1135        
1136        setComponentWidth(loadButton, Width.DOUBLE);
1137        
1138        GroupLayout layout = new GroupLayout(myPanel);
1139        myPanel.setLayout(layout);
1140        layout.setHorizontalGroup(
1141            layout.createParallelGroup(LEADING)
1142            .addGroup(layout.createSequentialGroup()
1143                .addContainerGap()
1144                .addGroup(layout.createParallelGroup(LEADING)
1145                    .addGroup(layout.createSequentialGroup()
1146                        .addComponent(fileLabel)
1147                        .addGap(GAP_RELATED)
1148                        .addComponent(dataFileText)
1149                        .addGap(GAP_RELATED)
1150                        .addComponent(dataFileButton))
1151                    .addGroup(layout.createSequentialGroup()
1152                        .addComponent(typeLabel)
1153                        .addGap(GAP_RELATED)
1154                        .addComponent(dataFileDescription))
1155                    .addGroup(layout.createSequentialGroup()
1156                        .addComponent(descriptionLabel)
1157                        .addGap(GAP_RELATED)
1158                        .addComponent(textDescription))
1159                    .addGroup(layout.createSequentialGroup()
1160                        .addComponent(propertiesLabel)
1161                        .addGap(GAP_RELATED)
1162                        .addComponent(propertiesPanel))
1163                    .addGroup(TRAILING, layout.createSequentialGroup()
1164                        .addComponent(helpButton)
1165                        .addPreferredGap(RELATED)
1166                        .addComponent(loadButton))
1167                    .addGroup(layout.createSequentialGroup()
1168                        .addComponent(statusLabelLabel)
1169                        .addGap(GAP_RELATED)
1170                        .addComponent(statusLabel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)))
1171                .addContainerGap())
1172        );
1173        layout.setVerticalGroup(
1174            layout.createParallelGroup(LEADING)
1175            .addGroup(TRAILING, layout.createSequentialGroup()
1176                .addContainerGap()
1177                .addGroup(layout.createParallelGroup(BASELINE)
1178                    .addComponent(dataFileButton)
1179                    .addComponent(dataFileText)
1180                    .addComponent(fileLabel))
1181                .addPreferredGap(RELATED)
1182                .addGroup(layout.createParallelGroup(BASELINE)
1183                    .addComponent(dataFileDescription)
1184                    .addComponent(typeLabel))
1185                .addPreferredGap(RELATED)
1186                .addGroup(layout.createParallelGroup(BASELINE)
1187                    .addComponent(textDescription)
1188                    .addComponent(descriptionLabel))
1189                .addPreferredGap(RELATED)
1190                .addGroup(layout.createParallelGroup(BASELINE)
1191                    .addComponent(propertiesPanel)
1192                    .addComponent(propertiesLabel))
1193                .addPreferredGap(RELATED, DEFAULT_SIZE, Short.MAX_VALUE)
1194                .addGroup(layout.createParallelGroup(BASELINE)
1195                    .addComponent(statusLabelLabel)
1196                    .addComponent(statusLabel))
1197                .addPreferredGap(UNRELATED)
1198                .addGroup(layout.createParallelGroup(BASELINE)
1199                    .addComponent(loadButton)
1200                    .addComponent(helpButton))
1201                .addContainerGap())
1202        );
1203        return myPanel;
1204    }
1205}
1206