001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2015
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 */
028
029package edu.wisc.ssec.mcidasv.chooser;
030
031import static javax.swing.GroupLayout.DEFAULT_SIZE;
032import static javax.swing.GroupLayout.Alignment.LEADING;
033import static javax.swing.GroupLayout.Alignment.TRAILING;
034
035import java.awt.Component;
036import java.awt.Dimension;
037import java.io.File;
038import java.util.ArrayList;
039import java.util.Hashtable;
040import java.util.List;
041
042import javax.swing.GroupLayout;
043import javax.swing.JCheckBox;
044import javax.swing.JComponent;
045import javax.swing.JFileChooser;
046import javax.swing.JLabel;
047import javax.swing.JPanel;
048import javax.swing.JRadioButton;
049import javax.swing.JTextField;
050
051import org.w3c.dom.Element;
052
053import ucar.unidata.data.DataSource;
054import ucar.unidata.idv.chooser.IdvChooserManager;
055import ucar.unidata.util.FileManager;
056import ucar.unidata.util.GuiUtils;
057import ucar.unidata.util.Misc;
058import ucar.unidata.util.PollingInfo;
059import ucar.unidata.xml.XmlUtil;
060
061import edu.wisc.ssec.mcidasv.ArgumentManager;
062import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
063
064/**
065 * A class for choosing files that can be polled.
066 *
067 * @author IDV development team
068 */
069public class PollingFileChooser extends FileChooser {
070
071    /** Any initial file system path to start with */
072    public static final String ATTR_DIRECTORY = "directory";
073
074    /** Polling interval */
075    public static final String ATTR_INTERVAL = "interval";
076
077    /** The title attribute */
078    public static final String ATTR_TITLE = "title";
079
080    /** polling info */
081    private PollingInfo pollingInfo;
082    
083    /** file path widget accessible to everyone */
084    private JTextField filePathWidget;
085    
086    /** file pattern widget accessible to everyone */
087    private JTextField filePatternWidget;
088    
089    /** Keep track of what was selected and update status accordingly */
090    boolean isDirectory = false;
091    int directoryCount = 0;
092    int fileCount = 0;
093    
094    /**
095     * Create the PollingFileChooser, passing in the manager and the xml element
096     * from choosers.xml
097     *
098     * @param mgr The manager
099     * @param root The xml root
100     *
101     */
102    public PollingFileChooser(IdvChooserManager mgr, Element root) {
103        super(mgr, root);
104        
105        Element chooserNode = getXmlNode();
106        
107        pollingInfo = (PollingInfo) idv.getPreference(PREF_POLLINGINFO + "." + getId());
108        if (pollingInfo == null) {
109            pollingInfo = new PollingInfo();
110            pollingInfo.setMode(PollingInfo.MODE_COUNT);
111            pollingInfo.setName("Directory");
112            pollingInfo.setFilePattern(getAttribute(ATTR_FILEPATTERN, ""));
113            pollingInfo.setFilePaths(Misc.newList(getAttribute(ATTR_DIRECTORY, "")));
114            pollingInfo.setIsActive(XmlUtil.getAttribute(chooserNode, ATTR_POLLON, true));
115
116            pollingInfo.setInterval((long) (XmlUtil.getAttribute(chooserNode, ATTR_INTERVAL, 5.0) * 60 * 1000));
117            int fileCount = 1;
118            String s = XmlUtil.getAttribute(chooserNode, ATTR_FILECOUNT, "1");
119            s = s.trim();
120            if (s.equals("all")) {
121                fileCount = Integer.MAX_VALUE;
122            } else {
123                fileCount = new Integer(s).intValue();
124            }
125            pollingInfo.setFileCount(fileCount);
126        }
127        filePathWidget = pollingInfo.getFilePathWidget();
128        filePatternWidget = pollingInfo.getPatternWidget();
129
130    }
131    
132    /**
133     * An extension of JFileChooser
134     *
135     * @author IDV Development Team
136     * @version $Revision$
137     */
138    public class MyDirectoryChooser extends MyFileChooser {
139
140        /**
141         * Create the file chooser
142         *
143         * @param path   the initial path
144         */
145        public MyDirectoryChooser(String path) {
146            super(path);
147            setMultiSelectionEnabled(getAllowMultiple());
148            setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
149        }
150        
151        /**
152         * Set the selected directory
153         *
154         * @param selectedDirectory  the selected directory
155         */
156        public void setCurrentDirectory(File selectedDirectory) {
157            super.setCurrentDirectory(selectedDirectory);
158            setSelectedFiles(null);
159        }
160        
161        /**
162         * Set the selected files
163         *
164         * @param selectedFiles  the selected files
165         */
166        public void setSelectedFiles(File[] selectedFiles) {
167            fileCount=0;
168            directoryCount=0;
169            if (selectedFiles == null || selectedFiles.length == 0) {
170                isDirectory = true;
171                if (filePathWidget!=null) {
172                    filePathWidget.setText(this.getCurrentDirectory().getAbsolutePath());
173                }
174            }
175            else {
176                isDirectory = false;
177                for (File selectedFile : selectedFiles) {
178                    if (selectedFile.isFile()) fileCount++;
179                    if (selectedFile.isDirectory()) {
180                        directoryCount++;
181                        if (directoryCount==1 && filePathWidget!=null) {
182                            filePathWidget.setText(selectedFile.getAbsolutePath());
183                        }
184                    }
185                }
186            }
187            super.setSelectedFiles(selectedFiles);
188            
189            // Disable load button if we arrived here by typing a directory or file name
190            if (directoryCount > 0 ||
191                    directoryCount == 0 && fileCount == 0 && !isDirectory) {
192                setHaveData(false);
193            }
194            else {
195                setHaveData(true);
196            }
197
198            updateStatus();
199        }
200    
201    }
202
203    /**
204     * Make the file chooser
205     *
206     * @param path   the initial path
207     *
208     * @return  the file chooser
209     */
210    protected JFileChooser doMakeDirectoryChooser(String path) {
211        return new MyDirectoryChooser(path);
212    }
213
214    /**
215     * Override the base class method to catch the do load
216     * This directly handles loading directories and passes off files to selectFiles() and selectFilesInner()
217     */
218    public void doLoadInThread() {
219        Element chooserNode = getXmlNode();
220
221        idv.getStateManager().writePreference(PREF_POLLINGINFO + "." + getId(), pollingInfo);
222        idv.getStateManager().writePreference(PREF_DEFAULTDIR + getId(), pollingInfo.getFile());
223        
224//        userMessage("doLoadInThread: fileCount: " + fileCount + ", directoryCount: " + directoryCount + ", isDirectory: " + isDirectory + ", getHaveData: " + getHaveData() + ", buttonPressed: " + buttonPressed);
225
226        // If a user types in a directory (on Windows) do not try to load that directory.
227        // If the load button was pressed, go for it!
228        if (fileCount == 0 && !buttonPressed) return;
229        
230        // If this is file(s) only, use FileChooser.doLoadInThread()
231        if (fileCount > 0) {
232            super.doLoadInThread();
233            return;
234        }
235        
236        Hashtable properties = new Hashtable();
237        if ( !pollingInfo.applyProperties()) {
238            return;
239        }
240        
241        String title = basename(pollingInfo.getFile());
242        title += "/" + ((JTextField)pollingInfo.getPatternWidget()).getText();
243        pollingInfo.setName(title);
244        properties.put(DataSource.PROP_TITLE, title);
245        properties.put(DataSource.PROP_POLLINFO, pollingInfo.cloneMe());
246
247        String dataSourceId;
248        if (XmlUtil.hasAttribute(chooserNode, ATTR_DATASOURCEID)) {
249            dataSourceId = XmlUtil.getAttribute(chooserNode, ATTR_DATASOURCEID);
250        } else {
251            dataSourceId = getDataSourceId();
252        }
253        
254        makeDataSource(pollingInfo.getFiles(), dataSourceId, properties);
255    }
256
257    /**
258     * Handle the selection of the set of files
259     * Copy from IDV FileChooser, add ability to name and poll
260     */
261    protected boolean selectFilesInner(File[] files, File directory)
262            throws Exception {
263        Element chooserNode = getXmlNode();
264        
265        if ((files == null) || (files.length == 0)) {
266            userMessage("Please select a file");
267            return false;
268        }
269        FileManager.addToHistory(files[0]);
270        List    selectedFiles      = new ArrayList();
271        String  fileNotExistsError = "";
272        boolean didXidv            = false;
273
274        for (int i = 0; i < files.length; i++) {
275            if ( !files[i].exists()) {
276                fileNotExistsError += "File does not exist: " + files[i] + "\n";
277            } else {
278                String filename = files[i].toString();
279                //Check for the bundle or jnlp file
280                if (((ArgumentManager)idv.getArgsManager()).isBundle(filename)
281                        || idv.getArgsManager().isJnlpFile(filename)) {
282                    didXidv = idv.handleAction(filename, null);
283                } else {
284                    selectedFiles.add(filename);
285                }
286            }
287        }
288
289        if (didXidv) {
290            closeChooser();
291            return true;
292        }
293
294        if (selectedFiles.size() == 0) {
295            return false;
296        }
297
298        if (fileNotExistsError.length() > 0) {
299            userMessage(fileNotExistsError);
300            return false;
301        }
302
303        Object definingObject = selectedFiles;
304        String title = selectedFiles.size() + " files";
305        if (selectedFiles.size() == 1) {
306            definingObject = selectedFiles.get(0);
307            title = basename(definingObject.toString());
308        }
309        
310        String dataSourceId;
311        if (XmlUtil.hasAttribute(chooserNode, ATTR_DATASOURCEID)) {
312            dataSourceId = XmlUtil.getAttribute(chooserNode, ATTR_DATASOURCEID);
313        } else {
314            dataSourceId = getDataSourceId();
315        }
316        
317        Hashtable   properties  = new Hashtable();
318        
319        // TODO: I disabled file polling on purpose:
320        // The control for this is in the Directory options and is grayed out
321        //  when selecting single files.  Is this something people want?
322        PollingInfo newPollingInfo = new PollingInfo(false);
323        String pattern = getFilePattern();
324        if ((pattern != null) && (pattern.length() > 0)) {
325            newPollingInfo.setFilePattern(pattern);
326        }
327        newPollingInfo.setName(title);
328        properties.put(DataSource.PROP_TITLE, title);
329        properties.put(DataSource.PROP_POLLINFO, newPollingInfo);
330
331        // explicitly denote whether or not this was a "bulk load". these data
332        // sources require a little extra attention when being unpersisted.
333        properties.put("bulk.load", (selectedFiles.size() > 1));
334        return makeDataSource(definingObject, dataSourceId, properties);
335    }
336    
337    /**
338     * Emulate basename()
339     */
340    private String basename(String path) {
341        if (path.lastIndexOf('/') > 0)
342            path = path.substring(path.lastIndexOf('/'));
343        else if (path.lastIndexOf('\\') > 0)
344            path = path.substring(path.lastIndexOf('\\'));
345        if (path.length() > 1)
346            path = path.substring(1);
347        return path;
348    }
349    
350    /**
351     * Get the tooltip for the load button
352     *
353     * @return The tooltip for the load button
354     */
355    protected String getLoadToolTip() {
356        return "Load the file(s) or directory";
357    }
358
359    /**
360     * Override the base class method to catch the do update.
361     */
362    @Override public void doUpdate() {
363        fileChooser.rescanCurrentDirectory();
364    }
365
366    /**
367     * Process PollingInfo GUI components based on their label and properties
368     * Turn it into a nicely-formatted labeled panel
369     */
370    private JPanel processPollingOption(JLabel label, JPanel panel) {
371        String string = label.getText().trim();
372        
373        // File Pattern
374        if (string.equals("File Pattern:")) {
375            Component panel1 = panel.getComponent(0);
376            if (panel1 instanceof JPanel) {
377                Component[] comps = ((JPanel)panel1).getComponents();
378                if (comps.length == 2) {
379                    List newComps1 = new ArrayList();
380                    List newComps2 = new ArrayList();
381                    if (comps[0] instanceof JPanel) {
382                        Component[] comps2 = ((JPanel)comps[0]).getComponents();
383                        if (comps2.length==1 &&
384                                comps2[0] instanceof JPanel)
385                            comps2=((JPanel)comps2[0]).getComponents();
386                        if (comps2.length == 2) {
387                            if (comps2[0] instanceof JTextField) {
388                                McVGuiUtils.setComponentWidth((JTextField) comps2[0], McVGuiUtils.Width.SINGLE);
389                            }
390                            newComps1.add(comps2[0]);
391                            newComps2.add(comps2[1]);
392                        }
393                    }
394                    newComps1.add(comps[1]);
395                    panel = GuiUtils.vbox(
396                            GuiUtils.left(GuiUtils.hbox(newComps1)),
397                            GuiUtils.left(GuiUtils.hbox(newComps2))
398                    );
399                }
400            }
401        }
402        
403        // Files
404        if (string.equals("Files:")) {
405            Component panel1 = panel.getComponent(0);
406            if (panel1 instanceof JPanel) {
407                Component[] comps = ((JPanel)panel1).getComponents();
408                if (comps.length == 6) {
409                    List newComps1 = new ArrayList();
410                    List newComps2 = new ArrayList();
411                    if (comps[3] instanceof JRadioButton) {
412                        String text = ((JRadioButton) comps[3]).getText().trim();
413                        if (text.equals("All files in last:")) text="All files in last";
414                        ((JRadioButton) comps[3]).setText(text);
415                    }
416                    if (comps[4] instanceof JTextField) {
417                        McVGuiUtils.setComponentWidth((JTextField) comps[4], McVGuiUtils.Width.HALF);
418                    }
419                    if (comps[5] instanceof JLabel) {
420                        String text = ((JLabel) comps[5]).getText().trim();
421                        ((JLabel) comps[5]).setText(text);
422                    }
423                    newComps1.add(comps[0]);
424                    newComps1.add(comps[1]);
425                    newComps2.add(comps[3]);
426                    newComps2.add(comps[4]);
427                    newComps2.add(comps[5]);
428                    panel = GuiUtils.vbox(
429                            GuiUtils.left(GuiUtils.hbox(newComps1)),
430                            GuiUtils.left(GuiUtils.hbox(newComps2))
431                    );
432                }
433            }
434        }
435        
436        // Polling
437        if (string.equals("Polling:")) {
438            Component panel1 = panel.getComponent(0);
439            if (panel1 instanceof JPanel) {
440                Component[] comps = ((JPanel)panel1).getComponents();
441                if (comps.length == 4) {
442                    List newComps = new ArrayList();
443                    if (comps[0] instanceof JCheckBox) {
444                        ((JCheckBox) comps[0]).setText("");
445                    }
446                    if (comps[1] instanceof JLabel) {
447                        String text = ((JLabel) comps[1]).getText().trim();
448                        if (text.equals("Check every:")) text="Refresh every";
449                        ((JLabel) comps[1]).setText(text);
450                    }
451                    if (comps[2] instanceof JTextField) {
452                        McVGuiUtils.setComponentWidth((JTextField) comps[2], McVGuiUtils.Width.HALF);
453                    }
454                    if (comps[3] instanceof JLabel) {
455                        String text = ((JLabel) comps[3]).getText().trim();
456                        ((JLabel) comps[3]).setText(text);
457                    }
458                    newComps.add(comps[0]);
459                    newComps.add(comps[1]);
460                    newComps.add(comps[2]);
461                    newComps.add(comps[3]);
462                    string="";
463                    panel = GuiUtils.left(GuiUtils.hbox(newComps));
464                }
465            }
466        }
467        
468        return McVGuiUtils.makeLabeledComponent(string, panel);
469    }
470    
471    /**
472     * Turn PollingInfo options into a nicely-formatted panel
473     */
474    private JPanel processPollingOptions(List comps) {
475        List newComps = new ArrayList();
476        newComps = new ArrayList();
477        if (comps.size() == 4) {
478//          newComps.add(comps.get(0));
479            
480            // Put Recent and Pattern panels next to each other and make them bordered
481            Component[] labelPanel1 = ((JPanel)comps.get(2)).getComponents();
482            Component[] labelPanel2 = ((JPanel)comps.get(1)).getComponents();
483            if (labelPanel1[1] instanceof JPanel && labelPanel2[1] instanceof JPanel) {
484                JPanel recentPanel = (JPanel)labelPanel1[1];
485                JPanel patternPanel = (JPanel)labelPanel2[1];
486                recentPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Recent Files"));
487                patternPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("File Pattern"));
488                
489                // Make the container panel
490                JPanel filePanel = new JPanel();
491                
492                GroupLayout layout = new GroupLayout(filePanel);
493                filePanel.setLayout(layout);
494                layout.setHorizontalGroup(
495                    layout.createParallelGroup(LEADING)
496                    .addGroup(layout.createSequentialGroup()
497                        .addComponent(recentPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
498                        .addGap(GAP_RELATED)
499                        .addComponent(patternPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
500                        )
501                );
502                layout.setVerticalGroup(
503                    layout.createParallelGroup(LEADING)
504                    .addGroup(layout.createSequentialGroup()
505                        .addGroup(layout.createParallelGroup(TRAILING)
506                            .addComponent(recentPanel, LEADING, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
507                            .addComponent(patternPanel, LEADING, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
508                            )
509                );
510
511                newComps.add(McVGuiUtils.makeLabeledComponent("Directory:", filePanel));
512            }
513            else {
514                newComps.add(comps.get(1));
515                newComps.add(comps.get(2));
516            }
517            newComps.add(comps.get(3));
518        }
519        else {
520            newComps = comps;
521        }
522        return GuiUtils.top(GuiUtils.vbox(newComps));
523    }
524    
525    /**
526     * Set the status message appropriately
527     */
528    protected void updateStatus() {
529        super.updateStatus();
530        String selectedReference = "the selected data";
531        
532        if(!getHaveData()) {
533            setStatus("Select zero, one, or multiple files");
534            GuiUtils.enableTree(bottomPanel, false);
535            return;
536        }
537        
538        if (isDirectory) {
539            selectedReference = "all files in this directory";
540        }
541        else {
542            if (fileCount > 0) {
543                if (fileCount > 1) selectedReference = "the selected files";
544                else selectedReference = "the selected file";
545            }
546            if (directoryCount > 0) {
547                selectedReference = "the selected directory";
548            }
549        }
550        GuiUtils.enableTree(bottomPanel, isDirectory || directoryCount > 0);
551        setStatus("Press \"" + CMD_LOAD + "\" to load " + selectedReference, "buttons");
552    }
553        
554    /**
555     * Get the top panel for the chooser
556     * @return the top panel
557     */
558//    protected JPanel getTopPanel() {
559//      return McVGuiUtils.makeLabeledComponent("Source Name:", pollingInfo.getNameWidget());
560//    }
561    
562    /**
563     * Get the center panel for the chooser
564     * @return the center panel
565     */
566    protected JPanel getCenterPanel() {
567        fileChooser = doMakeDirectoryChooser(path);
568        fileChooser.setPreferredSize(new Dimension(300, 300));
569        fileChooser.setMultiSelectionEnabled(getAllowMultiple());
570
571        JPanel centerPanel;
572        JComponent accessory = getAccessory();
573        if (accessory == null) {
574            centerPanel = GuiUtils.center(fileChooser);
575        } else {
576            centerPanel = GuiUtils.centerRight(fileChooser, GuiUtils.top(accessory));
577        }
578        centerPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
579        return McVGuiUtils.makeLabeledComponent("Files:", centerPanel);
580    }
581    
582    /**
583     * Get the bottom panel for the chooser
584     * @return the bottom panel
585     */
586    protected JPanel getBottomPanel() {
587
588        // Pull apart the PollingInfo components and rearrange them
589        // Don't want to override PollingInfo because it isn't something the user sees
590        // Arranged like: Label, Panel; Label, Panel; Label, Panel; etc...
591        List comps = new ArrayList();
592        List newComps = new ArrayList();
593        pollingInfo.getPropertyComponents(comps, false, pollingInfo.getFileCount()>0);
594        for (int i=0; i<comps.size()-1; i++) {
595            JComponent compLabel = (JComponent)comps.get(i);
596            if (compLabel instanceof JLabel) {
597                i++;
598                JComponent compPanel = (JComponent)comps.get(i);
599                if (compPanel instanceof JPanel) {
600                    newComps.add(processPollingOption((JLabel)compLabel, (JPanel)compPanel));
601                }
602            }
603        }
604        
605        JPanel pollingPanel = processPollingOptions(newComps);
606        return pollingPanel;
607    }
608
609}
610