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 java.awt.Component;
032import java.io.File;
033import java.nio.file.Files;
034import java.nio.file.Path;
035import java.util.Collections;
036import java.util.Hashtable;
037import java.util.Iterator;
038import java.util.List;
039import java.util.Vector;
040
041import javax.swing.JCheckBox;
042import javax.swing.JComboBox;
043import javax.swing.JComponent;
044import javax.swing.JFileChooser;
045import javax.swing.JLabel;
046import javax.swing.JPanel;
047import javax.swing.JTextField;
048
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051import org.w3c.dom.Element;
052
053import ucar.unidata.data.DataSource;
054import ucar.unidata.data.radar.Level2RadarDataSource;
055import ucar.unidata.idv.chooser.IdvChooserManager;
056import ucar.unidata.idv.control.DisplayControlBase;
057import ucar.unidata.metdata.NamedStation;
058import ucar.unidata.metdata.NamedStationTable;
059import ucar.unidata.util.FileManager;
060import ucar.unidata.util.GuiUtils;
061import ucar.unidata.util.Misc;
062import ucar.unidata.util.PatternFileFilter;
063import ucar.unidata.util.PollingInfo;
064import ucar.unidata.util.TwoFacedObject;
065
066import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
067
068/**
069 * A chooser for Level II NEXRAD data. This loads in
070 * files from the file system. Since (right now) the
071 * data does not contain the station we rely on
072 * the heuristic  of looking at the directory path
073 * name to see if it contains a station name.
074 * The user can also specify the station from the GUI
075 *
076 *
077 * @author IDV development team
078 * @version $Revision$Date: 2011/03/24 16:06:31 $
079 */
080public class Level2RadarChooser extends FileChooser {
081
082    /** Holds the predefined list of nexrad stations */
083    private JComboBox stationsCbx;
084
085    /** List of predefined nexrad stations */
086    private List nexradStations;
087
088    /** Label used in the widgets to show an unknown station */
089    private static String UNKNOWN_STATION = "I'm Feeling Lucky";
090
091    /**
092     * The data source id we pass the files to.
093     * This is the oone defined in idv/resources/datasources.xml
094     */
095    private static String DATA_TYPE = "FILE.LEVEL2RADAR";
096
097    /** the type for the CDM radar */
098    private static String CDM_DATA_TYPE = "FILE.RADAR";
099
100    /** checkbox for switching data types */
101    private JCheckBox typeCbx;
102
103    private static final Logger logger =
104            LoggerFactory.getLogger(Level2RadarChooser.class);
105
106    /**
107     * Create the chooser with the given chooser manager
108     * and xml root (from the xml that defines this chooser).
109     *
110     * @param mgr The manager
111     * @param root The xml
112     *
113     */
114    public Level2RadarChooser(IdvChooserManager mgr, Element root) {
115        super(mgr, root);
116    }
117
118    /**
119     * Label for {@link #getDataSourcesComponent()} selector.
120     *
121     * @return {@code String} to use as the label for data sources selector.
122     */
123    protected String getDataSourcesLabel() {
124        return "Station: ";
125    }
126
127    /**
128     * Overridden so that McIDAS-V can attempt auto-selecting the default data
129     * source type.
130     */
131    @Override protected JComboBox getDataSourcesComponent() {
132        stationsCbx = new JComboBox();
133        List stations = Misc.newList(UNKNOWN_STATION);
134        stations.addAll(nexradStations = getStations());
135        DisplayControlBase.setStations(stations, stationsCbx, false);
136        return stationsCbx;
137    }
138    
139    /**
140     * Get the tooltip for the load button
141     *
142     * @return The tooltip for the load button
143     */
144    protected String getLoadToolTip() {
145        return "Load the selected Level II radar files";
146    }
147
148    /**
149     * Make the file chooser
150     *
151     * @param path  the initial path
152     *
153     * @return the JFileChooser
154     */
155    protected JFileChooser doMakeFileChooser(String path) {
156        MyFileChooser fileChooser = new Level2RadarFileChooser(this, path);
157        fileChooser.addChoosableFileFilter(new PatternFileFilter(".*\\.raw$", "Raw files"));
158//        fileChooser.setApproveButtonText(ChooserPanel.CMD_LOAD);
159        fileChooser.setFileFilter(fileChooser.getAcceptAllFileFilter());
160        if (path != null) {
161            fileChooser.setCurrentDirectory(new File(path));
162        }
163        return fileChooser;
164    }
165    
166    /**
167     * Process the set of selected files
168     *
169     * @param files Array of files
170     * @param directory The last directory  chosen
171     *
172     * @return true if successful
173     */
174    protected boolean selectFilesInner(File[] files, final File directory) {
175        final Object selected =
176            ((TwoFacedObject) stationsCbx.getSelectedItem()).getId();
177
178        if (selected.equals(UNKNOWN_STATION)
179                && ((typeCbx != null) && !typeCbx.isSelected())) {
180            userMessage("Unknown location of selected files, "
181                        + "please select from list");
182            return false;
183        }
184
185        int recentCnt = getFileCount();
186        if (recentCnt <= 0) {
187            if ((files == null) || (files.length == 0)) {
188                userMessage("Please select one or more files");
189                return false;
190            }
191        }
192        if ((files != null) && (files.length > 0)) {
193            FileManager.addToHistory(files[0]);
194        }
195
196        String[] tmpDataLoc = getFileNames(((recentCnt <= 0)
197                                            ? files
198                                            : null));
199        if (recentCnt <= 0) {
200            if (tmpDataLoc == null) {
201                return false;
202            }
203        }
204
205        final Hashtable properties =
206            Misc.newHashtable(Level2RadarDataSource.STATION_LOCATION,
207                              selected);
208        String pattern = getFilePattern();
209        if ((pattern != null) && (pattern.length() > 0)) {
210            properties.put(DataSource.PROP_FILEPATTERN,
211                           pattern.toLowerCase());
212        } else {
213            pattern = null;
214        }
215
216        if (recentCnt > 0) {
217            properties.put(DataSource.MOST_RECENT, Integer.valueOf(recentCnt));
218            tmpDataLoc = new String[] { directory.toString() };
219            PollingInfo pollingInfo = new PollingInfo(directory.toString(),
220                                          60000, pattern, false, false);
221            pollingInfo.setMode(PollingInfo.MODE_COUNT);
222            pollingInfo.setFileCount(recentCnt);
223            properties.put(DataSource.PROP_POLLINFO, pollingInfo);
224        }
225
226        String dataType = ((typeCbx != null) && !typeCbx.isSelected())
227                          ? DATA_TYPE
228                          : CDM_DATA_TYPE;
229        // System.out.println("dataType = " + dataType);
230        makeDataSource(tmpDataLoc, dataType, properties);
231        return true;
232    }
233
234    /**
235     * Read in the nexrad stations from the
236     * idv/resources/nexradstns.xml resource
237     *
238     * @return List of of {@link ucar.unidata.metdata.NamedStation}-s
239     */
240    private List getStations() {
241        if (nexradStations == null) {
242            nexradStations = new Vector();
243            List radarLocations =
244                getIdv().getResourceManager().findLocationsByType("radar");
245            for (int i = 0; i < radarLocations.size(); i++) {
246                NamedStationTable nexrTable =
247                    (NamedStationTable) radarLocations.get(i);
248                nexradStations.addAll(nexrTable.values());
249            }
250            Collections.sort(nexradStations);
251        }
252        return nexradStations;
253    }
254
255    /**
256     * Try to guess at the station of the selected
257     * file based on directory name.
258     *
259     * @param file The selected file
260     */
261    protected void guessAtStation(File file) {
262
263        if ((file == null) || !file.isDirectory()) {
264            return;
265        }
266
267        // McIDAS Inquiry #2739-3141
268        if (!Files.isRegularFile(Path.of(file.getPath()))) {
269            // if the path is NOT a file, don't attempt to guess
270            return;
271        }
272
273        if ((nexradStations == null) || nexradStations.isEmpty()) {
274            return;
275        }
276        File tmpFile = file;
277
278        //Walk up the directory tree, looking at the names of each file
279
280        //Use the  dirLevel so we only do the println on the first check.
281        //Though  we could use it to only check one or two directory levels
282        int     dirLevel = 0;
283        boolean found    = false;
284        while ((tmpFile != null) && (found == false)) {
285            String name = tmpFile.getName().toLowerCase();
286            for (Iterator iter =
287                    nexradStations.iterator(); iter.hasNext(); ) {
288                NamedStation station = (NamedStation) iter.next();
289                if (station == null) {
290                    continue;
291                }
292
293                String id = station.getIdentifier();
294                //Do a .equals - perhaps we do want to do the .indexOf check??
295                //Though that might mean some odd matches.
296                if (name.indexOf(id.toLowerCase()) >= 0) {
297                    stationsCbx.setSelectedItem(
298                        DisplayControlBase.createStationTfo(station));
299                    found = true;
300                    break;
301                }
302            }
303            dirLevel++;
304            tmpFile = tmpFile.getParentFile();
305        }
306        if ( !found) {
307            stationsCbx.setSelectedItem(UNKNOWN_STATION);
308        }
309    }
310
311    /**
312     * This class allows us to add in our own functionality
313     * to the file chooser. It has a hook to support the guessing
314     * of the station from the directory name and passes through
315     * to the chooser the select and cancel events
316     *
317     * @author IDV development team
318     */
319    public class Level2RadarFileChooser extends FileChooser.MyFileChooser {
320
321        /** my chooser */
322        Level2RadarChooser myChooser;
323
324        /** Keeps track of the last directory the user chose */
325        File lastDirectory = null;
326
327        /**
328         * Create the special file chooser
329         *
330         *
331         * @param chooser the chooser to relate to
332         * @param path  path to start with
333         */
334        public Level2RadarFileChooser(Level2RadarChooser chooser,
335                                      String path) {
336            super(path);
337            myChooser = chooser;
338        }
339
340        /**
341         * Try to guess at the  station name
342         *
343         * @param file The currently selected dir
344         */
345        public void setCurrentDirectory(File file) {
346            super.setCurrentDirectory(file);
347            if ( !Misc.equals(file, lastDirectory)) {
348                if (myChooser != null) {
349                    myChooser.guessAtStation(file);
350                }
351                lastDirectory = file;
352            }
353        }
354    }
355
356    /**
357     * Get the bottom panel for the chooser
358     * @return the bottom panel
359     */
360    protected JPanel getBottomPanel() {       
361        // do this because the original check is made before the list is inited
362        if (getFileChooser() != null) {
363            guessAtStation(getFileChooser().getCurrentDirectory());
364        }
365        JComponent recentComponent = getRecentFilesComponent();
366        Component [] components = recentComponent.getComponents();
367        if (components != null) {
368                for (int i = 0; i < components.length; i++) {
369                        if (components[i] instanceof JLabel) {
370                                McVGuiUtils.setComponentWidth((JLabel)components[i], McVGuiUtils.Width.SINGLE);
371                                McVGuiUtils.setLabelPosition((JLabel)components[i], McVGuiUtils.Position.RIGHT);
372                        }
373                        else if (components[i] instanceof JComboBox) {
374                                McVGuiUtils.setComponentWidth((JComboBox)components[i], McVGuiUtils.Width.DOUBLE);
375                        }
376                        else if (components[i] instanceof JTextField) {
377                                McVGuiUtils.setComponentWidth((JTextField)components[i], McVGuiUtils.Width.SINGLE);
378                        }
379                }
380                recentComponent = GuiUtils.left(GuiUtils.hbox(components));
381        }
382        return McVGuiUtils.makeLabeledComponent("Times:", recentComponent);
383    }
384    
385}
386