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