001    /*
002     * $Id: PollingFileChooser.java,v 1.19 2012/02/19 17:35:37 davep Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
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    
031    package edu.wisc.ssec.mcidasv.chooser;
032    
033    import static javax.swing.GroupLayout.DEFAULT_SIZE;
034    import static javax.swing.GroupLayout.Alignment.LEADING;
035    import static javax.swing.GroupLayout.Alignment.TRAILING;
036    
037    import java.awt.Component;
038    import java.awt.Dimension;
039    import java.io.File;
040    import java.util.ArrayList;
041    import java.util.Hashtable;
042    import java.util.List;
043    
044    import javax.swing.GroupLayout;
045    import javax.swing.JCheckBox;
046    import javax.swing.JComponent;
047    import javax.swing.JFileChooser;
048    import javax.swing.JLabel;
049    import javax.swing.JPanel;
050    import javax.swing.JRadioButton;
051    import javax.swing.JTextField;
052    
053    import org.w3c.dom.Element;
054    
055    import ucar.unidata.data.DataSource;
056    import ucar.unidata.idv.chooser.IdvChooserManager;
057    import ucar.unidata.util.FileManager;
058    import ucar.unidata.util.GuiUtils;
059    import ucar.unidata.util.Misc;
060    import ucar.unidata.util.PollingInfo;
061    import ucar.unidata.xml.XmlUtil;
062    
063    import edu.wisc.ssec.mcidasv.ArgumentManager;
064    import 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     */
071    public 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.19 $
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