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 }