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