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 }