001 /*
002 * $Id: FileChooser.java,v 1.13 2012/02/19 17:35:36 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 package edu.wisc.ssec.mcidasv.chooser;
031
032 import static javax.swing.GroupLayout.DEFAULT_SIZE;
033 import static javax.swing.GroupLayout.Alignment.BASELINE;
034 import static javax.swing.GroupLayout.Alignment.LEADING;
035 import static javax.swing.GroupLayout.Alignment.TRAILING;
036 import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
037 import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED;
038
039 import java.awt.Dimension;
040 import java.awt.Insets;
041 import java.awt.event.ActionEvent;
042 import java.awt.event.ActionListener;
043 import java.util.ArrayList;
044 import java.util.HashMap;
045 import java.util.List;
046 import java.util.Map;
047
048 import javax.swing.GroupLayout;
049 import javax.swing.JButton;
050 import javax.swing.JComboBox;
051 import javax.swing.JComponent;
052 import javax.swing.JFileChooser;
053 import javax.swing.JLabel;
054 import javax.swing.JPanel;
055 import javax.swing.filechooser.FileFilter;
056
057 import org.w3c.dom.Element;
058
059 import ucar.unidata.idv.IntegratedDataViewer;
060 import ucar.unidata.idv.chooser.IdvChooserManager;
061 import ucar.unidata.util.FileManager;
062 import ucar.unidata.util.GuiUtils;
063 import ucar.unidata.util.Misc;
064 import ucar.unidata.util.PatternFileFilter;
065 import ucar.unidata.util.TwoFacedObject;
066 import ucar.unidata.xml.XmlUtil;
067 import edu.wisc.ssec.mcidasv.Constants;
068 import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
069 import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Position;
070 import edu.wisc.ssec.mcidasv.util.McVGuiUtils.TextColor;
071 import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width;
072
073 /**
074 * {@code FileChooser} is another {@literal "UI nicety"} extension. The main
075 * difference is that this class allows {@code choosers.xml} to specify a
076 * boolean attribute, {@code "selectdatasourceid"}. If disabled or not present,
077 * a {@code FileChooser} will behave exactly like a standard
078 * {@link FileChooser}.
079 *
080 * <p>If the attribute is present and enabled, the {@code FileChooser}'s
081 * data source type will automatically select the
082 * {@link ucar.unidata.data.DataSource} corresponding to the chooser's
083 * {@code "datasourceid"} attribute.
084 */
085 public class FileChooser extends ucar.unidata.idv.chooser.FileChooser implements Constants {
086
087 /**
088 * Chooser attribute that controls selecting the default data source.
089 * @see #selectDefaultDataSource
090 */
091 public static final String ATTR_SELECT_DSID = "selectdatasourceid";
092
093 /** Default data source ID for this chooser. Defaults to {@code null}. */
094 private final String defaultDataSourceId;
095
096 /**
097 * Whether or not to select the data source corresponding to
098 * {@link #defaultDataSourceId} within the {@link JComboBox} returned by
099 * {@link #getDataSourcesComponent()}. Defaults to {@code false}.
100 */
101 private final boolean selectDefaultDataSource;
102
103 /**
104 * If there is a default data source ID, get the combo box display value
105 */
106 private String defaultDataSourceName;
107
108 /** Different subclasses can use the combobox of data source ids */
109 private JComboBox sourceComboBox;
110
111 /**
112 * Get a handle on the actual file chooser
113 */
114 protected JFileChooser fileChooser;
115
116 /**
117 * Extending classes may need to manipulate the path
118 */
119 protected String path;
120
121 /**
122 * The panels that might need to be enabled/disabled
123 */
124 protected JPanel topPanel = new JPanel();
125 protected JPanel centerPanel = new JPanel();
126 protected JPanel bottomPanel = new JPanel();
127
128 /**
129 * Boolean to tell if the load was initiated from the load button
130 * (as opposed to typing in a filename... we need to capture that)
131 */
132 protected Boolean buttonPressed = false;
133
134 /**
135 * Get a handle on the IDV
136 */
137 protected IntegratedDataViewer idv = getIdv();
138
139 /**
140 * Creates a {@code FileChooser} and bubbles up {@code mgr} and
141 * {@code root} to {@link FileChooser}.
142 *
143 * @param mgr Global IDV chooser manager.
144 * @param root XML representing this chooser.
145 */
146 public FileChooser(final IdvChooserManager mgr, final Element root) {
147 super(mgr, root);
148
149 String id = XmlUtil.getAttribute(root, ATTR_DATASOURCEID, (String)null);
150 defaultDataSourceId = (id != null) ? id.toLowerCase() : id;
151
152 selectDefaultDataSource =
153 XmlUtil.getAttribute(root, ATTR_SELECT_DSID, false);
154
155 }
156
157 /**
158 * Label for getDataSourcesComponent selector
159 * @return
160 */
161 protected String getDataSourcesLabel() {
162 return "Data Type:";
163 }
164
165 /**
166 * Overridden so that McIDAS-V can attempt auto-selecting the default data
167 * source type.
168 */
169 @Override
170 protected JComboBox getDataSourcesComponent() {
171 sourceComboBox = getDataSourcesComponent(true);
172 if (selectDefaultDataSource && defaultDataSourceId != null) {
173 Map<String, Integer> ids = comboBoxContents(sourceComboBox);
174 if (ids.containsKey(defaultDataSourceId)) {
175 sourceComboBox.setSelectedIndex(ids.get(defaultDataSourceId));
176 defaultDataSourceName = sourceComboBox.getSelectedItem().toString();
177 sourceComboBox.setVisible(false);
178 }
179 }
180 return sourceComboBox;
181 }
182
183 /**
184 * Maps data source IDs to their index within {@code box}. This method is
185 * only applicable to {@link JComboBox}es created for {@link FileChooser}s.
186 *
187 * @param box Combo box containing relevant data source IDs and indices.
188 *
189 * @return A mapping of data source IDs to their offset within {@code box}.
190 */
191 private static Map<String, Integer> comboBoxContents(final JComboBox box) {
192 assert box != null;
193 Map<String, Integer> map = new HashMap<String, Integer>();
194 for (int i = 0; i < box.getItemCount(); i++) {
195 Object o = box.getItemAt(i);
196 if (!(o instanceof TwoFacedObject))
197 continue;
198 TwoFacedObject tfo = (TwoFacedObject)o;
199 map.put(TwoFacedObject.getIdString(tfo), i);
200 }
201 return map;
202 }
203
204 /**
205 * If the dataSources combo box is non-null then
206 * return the data source id the user selected.
207 * Else, return null
208 *
209 * @return Data source id
210 */
211 protected String getDataSourceId() {
212 return getDataSourceId(sourceComboBox);
213 }
214
215 /**
216 * Get the accessory component
217 *
218 * @return the component
219 */
220 protected JComponent getAccessory() {
221 return GuiUtils.left(
222 GuiUtils.inset(
223 FileManager.makeDirectoryHistoryComponent(
224 fileChooser, false), new Insets(13, 0, 0, 0)));
225 }
226
227 /**
228 * Override the base class method to catch the do load
229 */
230 public void doLoadInThread() {
231 selectFiles(fileChooser.getSelectedFiles(),
232 fileChooser.getCurrentDirectory());
233 }
234
235 /**
236 * Override the base class method to catch the do update
237 */
238 public void doUpdate() {
239 fileChooser.rescanCurrentDirectory();
240 }
241
242 /**
243 * Allow multiple file selection. Override if necessary.
244 */
245 protected boolean getAllowMultiple() {
246 return true;
247 }
248
249 /**
250 * Set whether the user has made a selection that contains data.
251 *
252 * @param have true to set the haveData property. Enables the
253 * loading button
254 */
255 public void setHaveData(boolean have) {
256 super.setHaveData(have);
257 updateStatus();
258 }
259
260 /**
261 * Set the status message appropriately
262 */
263 protected void updateStatus() {
264 super.updateStatus();
265 if(!getHaveData()) {
266 if (getAllowMultiple())
267 setStatus("Select one or more files");
268 else
269 setStatus("Select a file");
270 }
271 }
272
273 /**
274 * Get the top components for the chooser
275 *
276 * @param comps the top component
277 */
278 protected void getTopComponents(List comps) {
279 Element chooserNode = getXmlNode();
280
281 // Force ATTR_DSCOMP to be false before calling super.getTopComponents
282 // We call getDataSourcesComponent later on
283 boolean dscomp = XmlUtil.getAttribute(chooserNode, ATTR_DSCOMP, true);
284 XmlUtil.setAttributes(chooserNode, new String[] { ATTR_DSCOMP, "false" });
285 super.getTopComponents(comps);
286 if (dscomp) XmlUtil.setAttributes(chooserNode, new String[] { ATTR_DSCOMP, "true" });
287 }
288
289 /**
290 * Get the top panel for the chooser
291 * @return the top panel
292 */
293 protected JPanel getTopPanel() {
294 List topComps = new ArrayList();
295 getTopComponents(topComps);
296 if (topComps.size() == 0) return null;
297 JPanel topPanel = GuiUtils.left(GuiUtils.doLayout(topComps, 0, GuiUtils.WT_N, GuiUtils.WT_N));
298 topPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
299
300 return McVGuiUtils.makeLabeledComponent("Options:", topPanel);
301 }
302
303 /**
304 * Get the bottom panel for the chooser
305 * @return the bottom panel
306 */
307 protected JPanel getBottomPanel() {
308 return null;
309 }
310
311 /**
312 * Get the center panel for the chooser
313 * @return the center panel
314 */
315 protected JPanel getCenterPanel() {
316 Element chooserNode = getXmlNode();
317
318 fileChooser = doMakeFileChooser(path);
319 fileChooser.setPreferredSize(new Dimension(300, 300));
320 fileChooser.setMultiSelectionEnabled(getAllowMultiple());
321
322 List filters = new ArrayList();
323 String filterString = XmlUtil.getAttribute(chooserNode, ATTR_FILTERS, (String) null);
324
325 filters.addAll(getDataManager().getFileFilters());
326 if (filterString != null) {
327 filters.addAll(PatternFileFilter.createFilters(filterString));
328 }
329
330 if ( !filters.isEmpty()) {
331 for (int i = 0; i < filters.size(); i++) {
332 fileChooser.addChoosableFileFilter((FileFilter) filters.get(i));
333 }
334 fileChooser.setFileFilter(fileChooser.getAcceptAllFileFilter());
335 }
336
337 JPanel centerPanel;
338 JComponent accessory = getAccessory();
339 if (accessory == null) {
340 centerPanel = GuiUtils.center(fileChooser);
341 } else {
342 centerPanel = GuiUtils.centerRight(fileChooser, GuiUtils.top(accessory));
343 }
344 centerPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
345 setHaveData(false);
346 return McVGuiUtils.makeLabeledComponent("Files:", centerPanel);
347 }
348
349 private JLabel statusLabel = new JLabel("Status");
350
351 @Override
352 public void setStatus(String statusString, String foo) {
353 if (statusString == null)
354 statusString = "";
355 statusLabel.setText(statusString);
356 }
357
358 /**
359 * Create a more McIDAS-V-like GUI layout
360 */
361 protected JComponent doMakeContents() {
362 // Run super.doMakeContents()
363 // It does some initialization on private components that we can't get at
364 JComponent parentContents = super.doMakeContents();
365 Element chooserNode = getXmlNode();
366
367 path = (String) idv.getPreference(PREF_DEFAULTDIR + getId());
368 if (path == null) {
369 path = XmlUtil.getAttribute(chooserNode, ATTR_PATH, (String) null);
370 }
371
372 JComponent typeComponent = new JPanel();
373 if (XmlUtil.getAttribute(chooserNode, ATTR_DSCOMP, true)) {
374 typeComponent = getDataSourcesComponent();
375 }
376 if (defaultDataSourceName != null) {
377 typeComponent = new JLabel(defaultDataSourceName);
378 McVGuiUtils.setLabelBold((JLabel)typeComponent, true);
379 McVGuiUtils.setComponentHeight(typeComponent, new JComboBox());
380 }
381
382 // Create the different panels... extending classes can override these
383 topPanel = getTopPanel();
384 centerPanel = getCenterPanel();
385 bottomPanel = getBottomPanel();
386
387 JPanel innerPanel = centerPanel;
388 if (topPanel!=null && bottomPanel!=null)
389 innerPanel = McVGuiUtils.topCenterBottom(topPanel, centerPanel, bottomPanel);
390 else if (topPanel!=null)
391 innerPanel = McVGuiUtils.topBottom(topPanel, centerPanel, McVGuiUtils.Prefer.BOTTOM);
392 else if (bottomPanel!=null)
393 innerPanel = McVGuiUtils.topBottom(centerPanel, bottomPanel, McVGuiUtils.Prefer.TOP);
394
395 // Start building the whole thing here
396 JPanel outerPanel = new JPanel();
397
398 JLabel typeLabel = McVGuiUtils.makeLabelRight(getDataSourcesLabel());
399
400 JLabel statusLabelLabel = McVGuiUtils.makeLabelRight("");
401
402 McVGuiUtils.setLabelPosition(statusLabel, Position.RIGHT);
403 McVGuiUtils.setComponentColor(statusLabel, TextColor.STATUS);
404
405 JButton helpButton = McVGuiUtils.makeImageButton(ICON_HELP, "Show help");
406 helpButton.setActionCommand(GuiUtils.CMD_HELP);
407 helpButton.addActionListener(this);
408
409 JButton refreshButton = McVGuiUtils.makeImageButton(ICON_REFRESH, "Refresh");
410 refreshButton.setActionCommand(GuiUtils.CMD_UPDATE);
411 refreshButton.addActionListener(this);
412
413 McVGuiUtils.setButtonImage(loadButton, ICON_ACCEPT_SMALL);
414 McVGuiUtils.setComponentWidth(loadButton, Width.DOUBLE);
415
416 // This is how we know if the action was initiated by a button press
417 loadButton.addActionListener(new ActionListener() {
418 public void actionPerformed(ActionEvent e) {
419 buttonPressed = true;
420 Misc.runInABit(1000, new Runnable() {
421 public void run() {
422 buttonPressed = false;
423 }
424 });
425 }
426 }
427 );
428
429 GroupLayout layout = new GroupLayout(outerPanel);
430 outerPanel.setLayout(layout);
431 layout.setHorizontalGroup(
432 layout.createParallelGroup(LEADING)
433 .addGroup(TRAILING, layout.createSequentialGroup()
434 .addGroup(layout.createParallelGroup(TRAILING)
435 .addGroup(layout.createSequentialGroup()
436 .addContainerGap()
437 .addComponent(helpButton)
438 .addGap(GAP_RELATED)
439 .addComponent(refreshButton)
440 .addPreferredGap(RELATED)
441 .addComponent(loadButton))
442 .addGroup(LEADING, layout.createSequentialGroup()
443 .addContainerGap()
444 .addGroup(layout.createParallelGroup(LEADING)
445 .addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
446 .addGroup(layout.createSequentialGroup()
447 .addComponent(typeLabel)
448 .addGap(GAP_RELATED)
449 .addComponent(typeComponent))
450 .addGroup(layout.createSequentialGroup()
451 .addComponent(statusLabelLabel)
452 .addGap(GAP_RELATED)
453 .addComponent(statusLabel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)))))
454 .addContainerGap())
455 );
456 layout.setVerticalGroup(
457 layout.createParallelGroup(LEADING)
458 .addGroup(layout.createSequentialGroup()
459 .addContainerGap()
460 .addGroup(layout.createParallelGroup(BASELINE)
461 .addComponent(typeLabel)
462 .addComponent(typeComponent))
463 .addPreferredGap(UNRELATED)
464 .addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
465 .addPreferredGap(UNRELATED)
466 .addGroup(layout.createParallelGroup(BASELINE)
467 .addComponent(statusLabelLabel)
468 .addComponent(statusLabel))
469 .addPreferredGap(UNRELATED)
470 .addGroup(layout.createParallelGroup(BASELINE)
471 .addComponent(loadButton)
472 .addComponent(refreshButton)
473 .addComponent(helpButton))
474 .addContainerGap())
475 );
476
477 return outerPanel;
478
479 }
480
481 }