001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2017
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028package edu.wisc.ssec.mcidasv.data;
029
030import static javax.swing.BorderFactory.createTitledBorder;
031import static javax.swing.GroupLayout.Alignment.BASELINE;
032import static javax.swing.GroupLayout.Alignment.LEADING;
033import static javax.swing.GroupLayout.Alignment.TRAILING;
034import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
035
036import java.awt.Container;
037import java.awt.Dimension;
038import java.awt.EventQueue;
039import java.awt.Font;
040import java.awt.event.WindowEvent;
041import java.awt.event.WindowAdapter;
042import java.io.ByteArrayInputStream;
043import java.io.ByteArrayOutputStream;
044import java.io.File;
045import java.io.IOException;
046
047import java.util.ArrayList;
048import java.util.List;
049import java.util.stream.Collectors;
050
051import javax.swing.BoxLayout;
052import javax.swing.GroupLayout;
053import javax.swing.JButton;
054import javax.swing.JComboBox;
055import javax.swing.JDialog;
056import javax.swing.JEditorPane;
057import javax.swing.JFrame;
058import javax.swing.JLabel;
059import javax.swing.JOptionPane;
060import javax.swing.JPanel;
061import javax.swing.JRadioButton;
062import javax.swing.JScrollPane;
063import javax.swing.JSeparator;
064import javax.swing.JTextField;
065import javax.swing.JTextPane;
066import javax.swing.JToolBar;
067import javax.swing.ScrollPaneConstants;
068import javax.swing.UIManager;
069import javax.swing.WindowConstants;
070import javax.swing.border.EmptyBorder;
071import javax.swing.event.HyperlinkEvent;
072import javax.swing.text.PlainDocument;
073
074import org.slf4j.Logger;
075import org.slf4j.LoggerFactory;
076
077import net.miginfocom.swing.MigLayout;
078
079import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
080import edu.wisc.ssec.mcidasv.util.McVGuiUtils.IconPanel;
081import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Prefer;
082import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width;
083import edu.wisc.ssec.mcidasv.util.WebBrowser;
084import edu.wisc.ssec.mcidasv.Constants;
085
086import ucar.ma2.Array;
087import ucar.nc2.Variable;
088import ucar.nc2.dataset.NetcdfDataset;
089import ucar.nc2.ncml.NcMLReader;
090import ucar.nc2.dt.grid.GridDataset;
091
092import ucar.unidata.util.FileManager;
093import ucar.unidata.util.GuiUtils;
094import ucar.unidata.util.IOUtil;
095import ucar.unidata.util.LayoutUtil;
096
097import ucar.unidata.data.DataSourceDescriptor;
098import ucar.unidata.data.grid.GeoGridDataSource;
099import ucar.unidata.idv.IntegratedDataViewer;
100
101import visad.ConstantMap;
102import visad.Display;
103import visad.FlatField;
104import visad.python.JPythonMethods;
105import visad.ss.BasicSSCell;
106import visad.ss.FancySSCell;
107
108/**
109 * GUI widget that allows users to attempt to specify an {@literal "NCML"}
110 * if a given NetCDF is not CF-compliant.
111 */
112public class BadNetCDFWidget implements Constants {
113    
114    private static final Logger logger =
115        LoggerFactory.getLogger(BadNetCDFWidget.class);
116    
117    private IntegratedDataViewer idv;
118    
119    private NetcdfDataset ncFile;
120    private List<Variable> varList;
121    private List<String> varNames;
122    
123    // For NcML Editor
124    private JEditorPane NcMLeditor;
125    
126    // For variable display
127    BasicSSCell display;
128    ConstantMap[] cmaps;
129    
130    // For nav specification
131    private JRadioButton radioLatLonVars = new JRadioButton("Variables", true);
132    private JRadioButton radioLatLonBounds = new JRadioButton("Bounds", false);
133    
134    private JComboBox refComboBox = new JComboBox();
135    private JComboBox latComboBox = new JComboBox();
136    private JComboBox lonComboBox = new JComboBox();
137
138    private JPanel panelLatLonVars = new JPanel();
139    private JPanel panelLatLonBounds = new JPanel();
140
141    private JTextField textLatUL = new JTextField();
142    private JTextField textLonUL = new JTextField();
143    private JTextField textLatLR = new JTextField();
144    private JTextField textLonLR = new JTextField();
145
146    /**
147     * Handles problems from {@code openDataset}.
148     *
149     * @param ncFile NetCDF that caused a problem. Cannot be {@code null}.
150     * @param idv Reference to the IDV. Cannot be {@code null}.
151     */
152    public BadNetCDFWidget(NetcdfDataset ncFile, IntegratedDataViewer idv) {
153        this.idv = idv;
154        this.ncFile = ncFile;
155        varList = ncFile.getVariables();
156        varNames = new ArrayList<>(varList.size());
157        varNames.addAll(varList.stream().map(Variable::getFullName).collect(Collectors.toList()));
158    }
159
160    /**
161     * Passes through any exception from openDataset - this function
162     * doesn't provide an IDV and should only be used for testing.
163     *
164     * (Some functionality using the rest of the IDV won't work.)
165     *
166     * @param filepath Path to a NetCDF file.
167     *
168     * @throws IOException if there was a problem.
169     */
170    public BadNetCDFWidget(String filepath) throws IOException {
171        this(NetcdfDataset.openDataset(filepath), null);
172    }
173
174    /**
175     * Displays our "main menu" of choices to fix the given file. Everything
176     * else needed can get called from here.
177     */
178    public void showChoices() {
179        EventQueue.invokeLater(() -> {
180            try {
181                BadNetCDFDialog dialog = new BadNetCDFDialog();
182                dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
183                dialog.setVisible(true);
184                dialog.toFront();
185            } catch (Exception e) {
186                logger.error("Could not show choices", e);
187            }
188        });
189    }
190
191    /**
192     * Creates an editor for NcML and displays it in a window. This includes
193     * buttons for saving just the NcML and the full NetCDF file with the
194     * changes made.
195     */
196    private void showNcMLEditor() {
197        NcMLeditor = new JEditorPane();
198        
199        // We use this to store the actual ncml - 10000 is just the number
200        // toolsUI used
201        ByteArrayOutputStream bos = new ByteArrayOutputStream(10000);
202        try {
203            ncFile.writeNcML(bos, null);
204            NcMLeditor.setText(bos.toString());
205            NcMLeditor.setCaretPosition(0);
206        } catch (IOException ioe) {
207            logger.error("Could not write ncml", ioe);
208            // DataSourceImpl - doesn't work if we're not a data source
209            // setInError(true, false, "");
210            return;
211        }
212        
213        NcMLeditor.setEditable(true);
214
215        // Set the font style.
216        NcMLeditor.setFont(new Font("Courier", Font.PLAIN, 12));
217        
218        // Set the tab size
219        NcMLeditor.getDocument().putProperty(PlainDocument.tabSizeAttribute, 2);
220
221        // Button to save NcML as text, 
222        // popup allows them to specify where.
223        JButton saveNcMLBtn = new JButton("Save NcML as text");
224        saveNcMLBtn.addActionListener(e -> {
225            // Begin with getting the filename we want to write to.
226            String ncLocation = ncFile.getLocation();
227
228            if (ncLocation == null) {
229                ncLocation = "test";
230            }
231            int pos = ncLocation.lastIndexOf(".");
232            if (pos > 0) {
233                ncLocation = ncLocation.substring(0, pos);
234            }
235            String filename = FileManager.getWriteFile(ncLocation + ".ncml");
236            if (filename == null) {
237                return;
238            }
239
240            // Once we have that, we can actually write to the file!
241            try {
242                IOUtil.writeFile(new File(filename), NcMLeditor.getText());
243            } catch (Exception exc) {
244                logger.error("Could not write to '"+filename+'\'', exc);
245            }
246        });
247
248        // Button to merge the NcML with NetCDF 
249        // a'la ToolsUI and write it back out as NetCDF3.
250        JButton saveNetCDFBtn = new JButton("Merge and save NetCDF");
251        saveNetCDFBtn.addActionListener(e -> {
252            // Begin with getting the filename we want to write to.
253            String ncLocation = ncFile.getLocation();
254
255            if (ncLocation == null) {
256                ncLocation = "test";
257            }
258            int pos = ncLocation.lastIndexOf(".");
259            if (pos > 0) {
260                ncLocation = ncLocation.substring(0, pos);
261            }
262            String filename = FileManager.getWriteFile(ncLocation + ".nc");
263            if (filename == null) {
264                return;
265            }
266
267            // Once we have that, we can actually write to the file!
268            try {
269                ByteArrayInputStream bis =
270                    new ByteArrayInputStream(NcMLeditor.getText().getBytes());
271                NcMLReader.writeNcMLToFile(bis, filename);
272            } catch (Exception exc) {
273                logger.error("Could not write to '"+filename+'\'', exc);
274            }
275        });
276        
277        // Button to load this data into McV from NcML
278        JButton sendToMcVBtn = new JButton("Attempt to load with this NcML");
279        sendToMcVBtn.addActionListener(ae -> {
280            // TODO: save the current NcML into the NetcdfDataSource
281            createIDVdisplay();
282        });
283
284        JToolBar toolbar = new JToolBar("NcML Editor Controls");
285
286        toolbar.add(saveNcMLBtn);
287        toolbar.add(saveNetCDFBtn);
288        toolbar.add(sendToMcVBtn);
289
290        JScrollPane scrollPane =
291            new JScrollPane(NcMLeditor,
292                ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
293                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
294
295        // TODO: PREFERRED SIZE?
296        scrollPane.setPreferredSize(new Dimension(600, 600));
297        
298        JPanel panel = LayoutUtil.topCenter(toolbar, scrollPane);
299        JFrame editorWindow =
300            GuiUtils.makeWindow("NcML Editor", LayoutUtil.inset(panel, 10), 0, 0);
301        editorWindow.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
302        editorWindow.setVisible(true);
303        editorWindow.toFront();
304    }
305
306    /**
307     * Takes our ncFile and puts it back into IDV (McV).
308     */
309    private void createIDVdisplay() {
310
311        // Make a NetcdfDataset from our NcML
312        ByteArrayInputStream bis =
313            new ByteArrayInputStream(NcMLeditor.getText().getBytes());
314
315        try {
316            ncFile = NcMLReader.readNcML(bis, null);
317        } catch (IOException e1) {
318            logger.error("Could not read ncml", e1);
319            return;
320        }
321
322        // Now try to turn that NetcdfDataset into a legitimate DataSource!
323        GridDataset gd;
324
325        try {
326            gd = new GridDataset(ncFile);
327        } catch (IOException e) {
328            logger.error("could not create grid dataset from netcdf file", e);
329            return;
330        }
331        
332        ncFile.getLocation();
333        DataSourceDescriptor dsd = new DataSourceDescriptor();
334        dsd.setLabel("NcML DS Label");
335        GeoGridDataSource ggds =
336            new GeoGridDataSource(dsd, gd, "NcML Data Source", ncFile.getLocation());
337        ggds.initAfterCreation();
338        idv.getDataManager().addDataSource(ggds);
339    }
340
341    /**
342     * Shows a window that gives a choice of variables.
343     */
344    private void showVarPicker() {
345        final JComboBox<String> varDD = new JComboBox<>();
346        GuiUtils.setListData(varDD, varNames);
347
348        varDD.addActionListener(ae -> {
349            JComboBox<String> cb = (JComboBox<String>)ae.getSource();
350            Variable plotVar = varList.get(cb.getSelectedIndex());
351            String varName = (String) cb.getSelectedItem();
352
353            float [] varVals;
354            try {
355                // TODO: Is there a better way to convert this?
356                // Is there another function like reshape?
357                Array varArray = plotVar.read();
358                varVals  = (float[])varArray.get1DJavaArray(float.class);
359            } catch (IOException IOexe) {
360                logger.error("error while reading from variable '"+plotVar+'\'', IOexe);
361                return;
362            }
363
364            int size = plotVar.getDimensions().size();
365            if( size != 2) {
366                JOptionPane.showMessageDialog(null,
367                    ("<html>Variables must have 2 dimensions to be displayed here.<br><br>\"" + varName + "\" has " + size + ".</html>"),
368                    "Invalid Dimensions",
369                    JOptionPane.ERROR_MESSAGE);
370                return;
371            }
372
373            int xdim = plotVar.getDimensions().get(0).getLength();
374            int ydim = plotVar.getDimensions().get(1).getLength();
375
376            float[][] var2D = reshape(varVals, ydim, xdim);
377
378            //JPythonMethods.plot(varVals);
379            //JPythonMethods.plot(var2D);
380
381            try {
382                FlatField varField = JPythonMethods.field(var2D);
383
384                ConstantMap[] cmaps1 = {
385                    new ConstantMap(1.0, Display.Red),
386                    new ConstantMap(1.0, Display.Green),
387                    new ConstantMap(1.0, Display.Blue)
388                };
389
390                // Clear out the display or we get some weird stuff going on.
391                display.clearCell();
392                display.clearMaps();
393                display.clearDisplay();
394
395                display.addData(varField, cmaps1);
396            } catch (Exception exe) {
397                logger.error("problem encountered", exe);
398            }
399        });
400        
401        //BasicSSCell display = new FancySSCell("Variable!");
402        //display.setDimension(BasicSSCell.JAVA3D_3D);
403
404        try {
405            // Heavily borrowed from VISAD's JPythonMethods
406            display = new FancySSCell("Variable Viewer");
407            display.setDimension(BasicSSCell.JAVA3D_3D);
408            display.setPreferredSize(new Dimension(256, 256));
409            JFrame frame = new JFrame("Variable Viewer");
410            JPanel pane = new JPanel();
411            pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
412            frame.setContentPane(pane);
413            pane.add(varDD);
414            pane.add(display);
415            JButton controls = new JButton("Controls");
416            JPanel buttons = new JPanel();
417            buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS));
418            buttons.add(controls);
419            pane.add(buttons);
420            final FancySSCell fdisp = (FancySSCell) display;
421            fdisp.setAutoShowControls(false);
422            
423            controls.addActionListener(e -> fdisp.showWidgetFrame());
424            
425            frame.pack();
426            frame.setVisible(true);
427            frame.toFront();
428        } catch (Exception exe) {
429            logger.error("problem displaying", exe);
430        }
431    }
432
433    /**
434     * Reshape a 1D float array into a 2D float array.
435     *
436     * @param arr Array to reshape.
437     * @param m First dimension.
438     * @param n Second dimension.
439     *
440     * @return {@code arr} reshaped into a new 2D array.
441     */
442    private static float[][] reshape(float[] arr, int m, int n) {
443        float[][] newArr = new float[m][n];
444        int index = 0;
445        for (int i = 0; i < n; i++) {
446            for (int j = 0; j< m; j++) {
447                newArr[j][i] = arr[index++];
448            }
449        }
450        return newArr;
451    }
452
453    /**
454     * Shows a window that gives the opportunity to either define coordinate
455     * variables or specify corner points.
456     *
457     * Borrowed heavily from FlatFileChooser's makeNavigationPanel for style.
458     */
459    private void showNavChooser() {
460        JPanel midPanel = new JPanel();
461        midPanel.setBorder(createTitledBorder("Navigation"));
462
463        GuiUtils.setListData(refComboBox, varNames);
464        GuiUtils.setListData(latComboBox, varNames);
465        GuiUtils.setListData(lonComboBox, varNames);
466        
467        McVGuiUtils.setComponentWidth(latComboBox, Width.QUADRUPLE);
468        McVGuiUtils.setComponentWidth(lonComboBox, Width.QUADRUPLE);
469        McVGuiUtils.setComponentWidth(refComboBox, Width.QUADRUPLE);
470
471        panelLatLonVars =
472            McVGuiUtils.topBottom(
473                McVGuiUtils.makeLabeledComponent("Latitude:",latComboBox),
474                McVGuiUtils.makeLabeledComponent("Longitude:",lonComboBox),
475                Prefer.NEITHER);
476
477        GuiUtils.buttonGroup(radioLatLonVars, radioLatLonBounds);
478
479        // Images to make the bounds more clear
480        IconPanel urPanel =
481            new IconPanel("/edu/wisc/ssec/mcidasv/images/upper_right.gif");
482        IconPanel llPanel =
483            new IconPanel("/edu/wisc/ssec/mcidasv/images/lower_left.gif");
484        
485        McVGuiUtils.setComponentWidth(textLatUL);
486        McVGuiUtils.setComponentWidth(textLonUL);
487        McVGuiUtils.setComponentWidth(textLatLR);
488        McVGuiUtils.setComponentWidth(textLonLR);
489        panelLatLonBounds = McVGuiUtils.topBottom(
490            McVGuiUtils.makeLabeledComponent("UL Lat/Lon:", LayoutUtil.leftRight(LayoutUtil.hbox(textLatUL, textLonUL), urPanel)),
491            McVGuiUtils.makeLabeledComponent("LR Lat/Lon:", LayoutUtil.leftRight(llPanel, LayoutUtil.hbox(textLatLR, textLonLR))),
492            Prefer.NEITHER);
493        
494        panelLatLonBounds =
495            McVGuiUtils.topBottom(
496                panelLatLonBounds,
497                McVGuiUtils.makeLabeledComponent("Reference:", refComboBox),
498                Prefer.NEITHER);
499
500        McVGuiUtils.setComponentWidth(radioLatLonVars);
501        McVGuiUtils.setComponentWidth(radioLatLonBounds);
502        
503        // Add a bit of a buffer to both
504        panelLatLonVars = LayoutUtil.inset(panelLatLonVars, 5);
505        panelLatLonBounds = LayoutUtil.inset(panelLatLonBounds, 5);
506        
507        GroupLayout layout = new GroupLayout(midPanel);
508        midPanel.setLayout(layout);
509        layout.setHorizontalGroup(
510            layout.createParallelGroup(LEADING)
511            .addGroup(layout.createSequentialGroup()
512                .addContainerGap()
513                .addGroup(layout.createParallelGroup(LEADING)
514                    .addGroup(layout.createSequentialGroup()
515                        .addComponent(radioLatLonVars)
516                        .addGap(GAP_RELATED)
517                        .addComponent(panelLatLonVars))
518                    .addGroup(layout.createSequentialGroup()
519                        .addComponent(radioLatLonBounds)
520                        .addGap(GAP_RELATED)
521                        .addComponent(panelLatLonBounds)))
522                .addContainerGap())
523        );
524        layout.setVerticalGroup(
525            layout.createParallelGroup(LEADING)
526            .addGroup(TRAILING, layout.createSequentialGroup()
527                .addContainerGap()
528                .addGroup(layout.createParallelGroup(BASELINE)
529                    .addComponent(radioLatLonVars)
530                    .addComponent(panelLatLonVars))
531                .addPreferredGap(RELATED)
532                .addGroup(layout.createParallelGroup(BASELINE)
533                    .addComponent(radioLatLonBounds)
534                    .addComponent(panelLatLonBounds))
535                .addPreferredGap(RELATED)
536                .addContainerGap())
537        );
538 
539
540        
541        radioLatLonVars.addActionListener(e -> checkSetLatLon());
542        
543        radioLatLonBounds.addActionListener(e -> checkSetLatLon());
544        
545        JButton goBtn = new JButton("Go!");
546        goBtn.addActionListener(ae -> {
547            boolean isVar = radioLatLonVars.isSelected();
548            if (isVar) {
549                navVarAction();
550            } else {
551                navCornersAction();
552            }
553        });
554
555        JPanel wholePanel =
556            McVGuiUtils.topBottom(midPanel, goBtn, Prefer.NEITHER);
557
558        JFrame myWindow =
559            GuiUtils.makeWindow(
560                "Pick Your Navigation!",
561                LayoutUtil.inset(wholePanel, 10),
562                0, 0);
563
564        checkSetLatLon();
565        myWindow.setVisible(true);
566        myWindow.toFront();
567    }
568    
569    
570    /**
571     * Enable or disable widgets for navigation.
572     */
573    private void checkSetLatLon() {
574        boolean isVar = radioLatLonVars.isSelected();
575        GuiUtils.enableTree(panelLatLonVars, isVar);
576        GuiUtils.enableTree(panelLatLonBounds, !isVar);
577    }
578
579    /**
580     * One of the two workhorses of our nav chooser, it alters the chosen 
581     * (existing) variables so they can be used as lat/lon pairs.
582     */
583    private void navVarAction() {
584    }
585
586    /**
587     * One of the two workhorses of our nav chooser, it creates new 
588     * variables for lat/lon based on the specified cornerpoints and
589     * reference variable (for dimensions).
590     */
591    private void navCornersAction() {
592    }
593    
594    public class BadNetCDFDialog extends JDialog {
595
596        /**
597         * Create the dialog.
598         */
599        public BadNetCDFDialog() {
600            setTitle("Non-Compliant NetCDF Tool");
601            setMinimumSize(new Dimension(705, 320));
602            setBounds(100, 100, 705, 320);
603            Container contentPane = getContentPane();
604            
605            JLabel headerLabel =
606                new JLabel("McIDAS-V is unable to read your file.");
607            headerLabel.setFont(UIManager.getFont("OptionPane.font"));
608            headerLabel.setBorder(new EmptyBorder(0, 0, 4, 0));
609            
610            JTextPane messageTextPane = new JTextPane();
611            Font textPaneFont = UIManager.getFont("TextPane.font");
612            String fontCss =
613                String.format("style=\"font-family: '%s'; font-size: %d;\"", textPaneFont.getFamily(), textPaneFont.getSize());
614            messageTextPane.setBackground(UIManager.getColor("Label.background"));
615            messageTextPane.setContentType("text/html");
616            messageTextPane.setDragEnabled(false);
617            messageTextPane.setText("<html>\n<body "+
618                fontCss + 
619                ">To verify if your file is CF-compliant, you can run your file through an online compliance checker " + 
620                "(<a href=\"http://cfconventions.org/compliance-checker.html\">example CF-compliance utility</a>). " + 
621                "<br/><br/> \n\nIf the checker indicates that your file is not compliant you can attempt to fix it using " + 
622                "the NcML Editor provided in this window.<br/><br/>\n\nIn a future release of McIDAS-V, this interface will " +  
623                "present you with choices for the variables necessary for McIDAS-V to display your data.<br/></font></body></html>");
624            messageTextPane.setEditable(false);
625            messageTextPane.addHyperlinkListener(e -> {
626                HyperlinkEvent.EventType type = e.getEventType();
627                if (HyperlinkEvent.EventType.ACTIVATED.equals(type)) {
628                    String url = (e.getURL() == null)
629                               ? e.getDescription()
630                               : e.getURL().toString();
631                    WebBrowser.browse(url);
632                }
633            });
634
635            JSeparator separator = new JSeparator();
636            // seems pretty dumb to have to do this sizing business in order
637            // to get the separator to appear, right?
638            // check out the following:
639            // http://docs.oracle.com/javase/tutorial/uiswing/components/separator.html
640            //
641            // "Separators have almost no API and are extremely easy to use as 
642            // long as you keep one thing in mind: In most implementations, 
643            // a vertical separator has a preferred height of 0, and a 
644            // horizontal separator has a preferred width of 0. This means a 
645            // separator is not visible unless you either set its preferred 
646            // size or put it in under the control of a layout manager such as 
647            // BorderLayout or BoxLayout that stretches it to fill its 
648            // available display area."
649            // WHO ON EARTH DECIDED THAT WAS SENSIBLE DEFAULT BEHAVIOR FOR A
650            // SEPARATOR WIDGET!?
651            separator.setMinimumSize(new Dimension(1, 12));
652            separator.setPreferredSize(new Dimension(1, 12));
653            
654            JLabel editorLabel =
655                new JLabel("Open the file in the NcML editor:");
656            
657            JButton editorButton = new JButton("NcML Editor");
658            editorButton.addActionListener(e -> showNcMLEditor());
659            
660            JLabel viewLabel =
661                new JLabel("I just want to view one of the variables:");
662            
663            JButton viewButton = new JButton("View Variable");
664            viewButton.addActionListener(e -> showVarPicker());
665            
666            JLabel noncompliantLabel =
667                new JLabel("I have navigation variables, they just aren't CF-compliant: (FEATURE INCOMPLETE)");
668            
669            JButton noncompliantButton = new JButton("Choose Nav");
670            noncompliantButton.addActionListener(e -> showNavChooser());
671            this.addWindowListener(new WindowAdapter() {
672                @Override public void windowClosing(WindowEvent e) {
673                    BadNetCDFDialog.this.dispose();
674                }
675            });
676
677            contentPane.setLayout(new MigLayout(
678                "", 
679                "[grow][]", 
680                "[][grow][][][][][][]"));
681            contentPane.add(headerLabel,        "spanx 2, alignx left, aligny top, wrap");
682            contentPane.add(messageTextPane,    "spanx 2, grow, wrap");
683            contentPane.add(separator,          "spanx 2, growx, aligny top, wrap");
684            contentPane.add(editorLabel,        "alignx left, aligny baseline");
685            contentPane.add(editorButton,       "growx, aligny baseline, wrap");
686            contentPane.add(viewLabel,          "alignx left, aligny baseline");
687            contentPane.add(viewButton,         "growx, aligny baseline, wrap");
688            contentPane.add(noncompliantLabel,  "alignx left, aligny baseline");
689            contentPane.add(noncompliantButton, "growx, aligny baseline, wrap");
690            
691        }
692    }
693
694    /**
695     * Tester function to pick a file and send it through the paces.
696     *
697     * @param args Incoming arguments.
698     */
699    public static void main(String... args) {
700        String testfile = FileManager.getReadFile();
701        BadNetCDFWidget bfReader;
702        try {
703            bfReader = new BadNetCDFWidget(testfile);
704            bfReader.showChoices();
705            //bfReader.showNavChooser();
706        } catch (Exception exe) {
707            logger.error("Could not read '"+testfile+'\'', exe);
708        }
709    }
710}