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