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