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