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