001 /* 002 * $Id: LocalEntryShortcut.java,v 1.2 2012/02/19 17:35:49 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.servermanager; 031 032 import java.awt.Component; 033 import java.awt.Container; 034 import java.awt.event.ActionEvent; 035 import java.awt.event.ActionListener; 036 import java.io.File; 037 import java.util.Collections; 038 import java.util.Set; 039 040 import javax.swing.DefaultComboBoxModel; 041 import javax.swing.JButton; 042 import javax.swing.JComboBox; 043 import javax.swing.JFileChooser; 044 import javax.swing.JLabel; 045 import javax.swing.JList; 046 import javax.swing.JTextField; 047 import javax.swing.SwingUtilities; 048 import javax.swing.WindowConstants; 049 import javax.swing.plaf.basic.BasicComboBoxRenderer; 050 051 import net.miginfocom.swing.MigLayout; 052 053 import org.slf4j.Logger; 054 import org.slf4j.LoggerFactory; 055 056 import ucar.unidata.xml.XmlObjectStore; 057 058 import edu.wisc.ssec.mcidasv.McIDASV; 059 import edu.wisc.ssec.mcidasv.servermanager.LocalAddeEntry.AddeFormat; 060 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EditorAction; 061 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryStatus; 062 import edu.wisc.ssec.mcidasv.util.McVGuiUtils; 063 import edu.wisc.ssec.mcidasv.util.McVTextField; 064 065 /** 066 * A dialog that allows the user to define or modify {@link LocalAddeEntry}s. 067 * 068 * Temporary solution for adding entries via the adde choosers. 069 */ 070 @SuppressWarnings("serial") 071 public class LocalEntryShortcut extends javax.swing.JDialog { 072 073 private static final Logger logger = LoggerFactory.getLogger(LocalEntryShortcut.class); 074 075 /** Property ID for the last directory selected. */ 076 private static final String PROP_LAST_PATH = "mcv.localdata.lastpath"; 077 078 /** The valid local ADDE formats. */ 079 private static final DefaultComboBoxModel formats = new DefaultComboBoxModel(new Object[] { AddeFormat.MCIDAS_AREA, AddeFormat.AMSRE_L1B, AddeFormat.AMSRE_RAIN_PRODUCT, AddeFormat.GINI, AddeFormat.LRIT_GOES9, AddeFormat.LRIT_GOES10, AddeFormat.LRIT_GOES11, AddeFormat.LRIT_GOES12, AddeFormat.LRIT_MET5, AddeFormat.LRIT_MET7, AddeFormat.LRIT_MTSAT1R, AddeFormat.METEOSAT_OPENMTP, AddeFormat.METOP_AVHRR_L1B, AddeFormat.MODIS_L1B_MOD02, AddeFormat.MODIS_L2_MOD06, AddeFormat.MODIS_L2_MOD07, AddeFormat.MODIS_L2_MOD35, AddeFormat.MODIS_L2_MOD04, AddeFormat.MODIS_L2_MOD28, AddeFormat.MODIS_L2_MODR, AddeFormat.MSG_HRIT_FD, AddeFormat.MSG_HRIT_HRV, AddeFormat.MTSAT_HRIT, AddeFormat.NOAA_AVHRR_L1B, AddeFormat.SSMI, AddeFormat.TRMM, AddeFormat.MCIDAS_MD }); 080 081 /** The server manager GUI. Be aware that this can be {@code null}. */ 082 private final TabbedAddeManager managerController; 083 084 /** Reference back to the server manager. */ 085 private final EntryStore entryStore; 086 087 private final LocalAddeEntry currentEntry; 088 089 /** Either the path to an ADDE directory as selected by the user or an empty {@link String}. */ 090 private String selectedPath = ""; 091 092 /** The last dialog action performed by the user. */ 093 private EditorAction editorAction = EditorAction.INVALID; 094 095 private final String datasetText; 096 097 /** 098 * Creates a modal local ADDE data editor. It's pretty useful when adding 099 * from a chooser. 100 * 101 * @param entryStore The server manager. Should not be {@code null}. 102 * @param group Name of the group/dataset containing the desired data. Be aware that {@code null} is okay. 103 */ 104 public LocalEntryShortcut(final EntryStore entryStore, final String group) { 105 super((javax.swing.JDialog)null, true); 106 this.managerController = null; 107 this.entryStore = entryStore; 108 this.datasetText = group; 109 this.currentEntry = null; 110 SwingUtilities.invokeLater(new Runnable() { 111 @Override public void run() { 112 initComponents(LocalAddeEntry.INVALID_ENTRY); 113 } 114 }); 115 } 116 117 // TODO(jon): hold back on javadocs, this is likely to change 118 public LocalEntryShortcut(java.awt.Frame parent, boolean modal, final TabbedAddeManager manager, final EntryStore store) { 119 super(manager, modal); 120 this.managerController = manager; 121 this.entryStore = store; 122 this.datasetText = null; 123 this.currentEntry = null; 124 SwingUtilities.invokeLater(new Runnable() { 125 @Override public void run() { 126 initComponents(LocalAddeEntry.INVALID_ENTRY); 127 } 128 }); 129 } 130 131 // TODO(jon): hold back on javadocs, this is likely to change 132 public LocalEntryShortcut(java.awt.Frame parent, boolean modal, final TabbedAddeManager manager, final EntryStore store, final LocalAddeEntry entry) { 133 super(manager, modal); 134 this.managerController = manager; 135 this.entryStore = store; 136 this.datasetText = null; 137 this.currentEntry = entry; 138 SwingUtilities.invokeLater(new Runnable() { 139 @Override public void run() { 140 initComponents(entry); 141 } 142 }); 143 } 144 145 /** 146 * Creates the editor dialog and initializes the various GUI components. 147 * 148 * @param initEntry Use {@link LocalAddeEntry#INVALID_ENTRY} to specify 149 * that the user is creating a new entry; otherwise provide the actual 150 * entry that the user is editing. 151 */ 152 private void initComponents(final LocalAddeEntry initEntry) { 153 JLabel datasetLabel = new JLabel("Dataset (e.g. MYDATA):"); 154 datasetField = McVGuiUtils.makeTextFieldDeny("", 8, true, McVTextField.mcidasDeny); 155 datasetLabel.setLabelFor(datasetField); 156 datasetField.setColumns(20); 157 if (datasetText != null) { 158 datasetField.setText(datasetText); 159 } 160 161 JLabel typeLabel = new JLabel("Image Type (e.g. JAN 07 GOES):"); 162 typeField = new JTextField(); 163 typeLabel.setLabelFor(typeField); 164 typeField.setColumns(20); 165 166 JLabel formatLabel = new JLabel("Format:"); 167 formatComboBox = new JComboBox(); 168 formatComboBox.setRenderer(new TooltipComboBoxRenderer()); 169 formatComboBox.setModel(formats); 170 formatComboBox.setSelectedIndex(0); 171 formatLabel.setLabelFor(formatComboBox); 172 173 JLabel directoryLabel = new JLabel("Directory:"); 174 directoryField = new JTextField(); 175 directoryLabel.setLabelFor(directoryField); 176 directoryField.setColumns(20); 177 178 JButton browseButton = new JButton("Browse..."); 179 browseButton.addActionListener(new ActionListener() { 180 @Override public void actionPerformed(final ActionEvent evt) { 181 browseButtonActionPerformed(evt); 182 } 183 }); 184 185 JButton saveButton = new JButton("Add Dataset"); 186 saveButton.addActionListener(new ActionListener() { 187 @Override public void actionPerformed(final ActionEvent evt) { 188 if (initEntry == LocalAddeEntry.INVALID_ENTRY) { 189 saveButtonActionPerformed(evt); 190 } else { 191 editButtonActionPerformed(evt); 192 } 193 } 194 }); 195 196 JButton cancelButton = new JButton("Cancel"); 197 cancelButton.addActionListener(new ActionListener() { 198 @Override public void actionPerformed(final ActionEvent evt) { 199 cancelButtonActionPerformed(evt); 200 } 201 }); 202 203 if (initEntry == LocalAddeEntry.INVALID_ENTRY) { 204 setTitle("Add Local Dataset"); 205 } else { 206 setTitle("Edit Local Dataset"); 207 saveButton.setText("Save Changes"); 208 datasetField.setText(initEntry.getGroup()); 209 typeField.setText(initEntry.getName()); 210 directoryField.setText(EntryTransforms.demungeFileMask(initEntry.getFileMask())); 211 formatComboBox.setSelectedItem(initEntry.getFormat()); 212 } 213 214 setResizable(false); 215 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 216 Container c = getContentPane(); 217 c.setLayout(new MigLayout( 218 "", // general layout constraints; currently 219 // none are specified. 220 "[align right][fill]", // column constraints; defined two columns 221 // leftmost aligns the components right; 222 // rightmost simply fills the remaining space 223 "[][][][][][]")); // row constraints; possibly not needed in 224 // this particular example? 225 226 // done via WindowBuilder + Eclipse 227 // c.add(datasetLabel, "cell 0 0"); // row: 0; col: 0 228 // c.add(datasetField, "cell 1 0"); // row: 0; col: 1 229 // c.add(typeLabel, "cell 0 1"); // row: 1; col: 0 230 // c.add(typeField, "cell 1 1"); // row: 1; col: 1 231 // c.add(formatLabel, "cell 0 2"); // row: 2; col: 0 232 // c.add(formatComboBox, "cell 1 2"); // row: 2; col: 1 233 // c.add(directoryLabel, "cell 0 3"); // row: 3; col: 0 ... etc! 234 // c.add(directoryField, "flowx,cell 1 3"); 235 // c.add(browseButton, "cell 1 3,alignx right"); 236 // c.add(saveButton, "flowx,cell 1 5,alignx right,aligny top"); 237 // c.add(cancelButton, "cell 1 5,alignx right,aligny top"); 238 239 // another way to accomplish the above layout. 240 c.add(datasetLabel); 241 c.add(datasetField, "wrap"); // think "newline" or "new row" 242 c.add(typeLabel); 243 c.add(typeField, "wrap"); // think "newline" or "new row" 244 c.add(formatLabel); 245 c.add(formatComboBox, "wrap"); // think "newline" or "new row" 246 c.add(directoryLabel); 247 c.add(directoryField, "flowx, split 2"); // split this current cell 248 // into two "subcells"; this 249 // will cause browseButton to 250 // be grouped into the current 251 // cell. 252 c.add(browseButton, "alignx right, wrap"); 253 254 // skips "cell 0 5" causing this row to start in "cell 1 5"; splits 255 // the cell so that saveButton and cancelButton both occupy cell 1 5. 256 c.add(saveButton, "flowx, split 2, skip 1, alignx right, aligny top"); 257 c.add(cancelButton, "alignx right, aligny top"); 258 pack(); 259 }// </editor-fold> 260 261 /** 262 * Triggered when the {@literal "add"} button is clicked. 263 */ 264 private void saveButtonActionPerformed(final ActionEvent evt) { 265 addEntry(); 266 } 267 268 private void editButtonActionPerformed(final ActionEvent evt) { 269 editEntry(); 270 } 271 272 /** 273 * Triggered when the {@literal "file picker"} button is clicked. 274 */ 275 private void browseButtonActionPerformed(final ActionEvent evt) { 276 String lastPath = getLastPath(); 277 selectedPath = getDataDirectory(lastPath); 278 // yes, the "!=" is intentional! getDataDirectory(String) will return 279 // the exact String it is given if the user cancelled the file picker 280 if (selectedPath != lastPath) { 281 directoryField.setText(selectedPath); 282 setLastPath(selectedPath); 283 } 284 } 285 286 /** 287 * Returns the value of the {@link #PROP_LAST_PATH} McIDAS-V property. 288 * 289 * @return Either the {@code String} representation of the last path 290 * selected by the user, or an empty {@code String}. 291 */ 292 private String getLastPath() { 293 McIDASV mcv = McIDASV.getStaticMcv(); 294 String path = ""; 295 if (mcv != null) { 296 return mcv.getObjectStore().get(PROP_LAST_PATH, ""); 297 } 298 return path; 299 } 300 301 /** 302 * Sets the value of the {@link #PROP_LAST_PATH} McIDAS-V property to be 303 * the contents of {@code path}. 304 * 305 * @param path New value for {@link #PROP_LAST_PATH}. {@code null} will be 306 * converted to an empty {@code String}. 307 */ 308 public void setLastPath(final String path) { 309 String okayPath = (path != null) ? path : ""; 310 McIDASV mcv = McIDASV.getStaticMcv(); 311 if (mcv != null) { 312 XmlObjectStore store = mcv.getObjectStore(); 313 store.put(PROP_LAST_PATH, okayPath); 314 store.saveIfNeeded(); 315 } 316 } 317 318 /** 319 * Calls {@link #dispose} if the dialog is visible. 320 */ 321 private void cancelButtonActionPerformed(ActionEvent evt) { 322 if (isDisplayable()) { 323 dispose(); 324 } 325 } 326 327 /** 328 * Poll the various UI components and attempt to construct valid ADDE 329 * entries based upon the information provided by the user. 330 * 331 * @return {@link Set} of entries that represent the user's input, or an 332 * empty {@code Set} if the input was somehow invalid. 333 */ 334 private Set<LocalAddeEntry> pollWidgets() { 335 String group = datasetField.getText(); 336 String name = typeField.getText(); 337 String mask = getLastPath(); 338 if (mask.isEmpty() && !directoryField.getText().isEmpty()) { 339 mask = directoryField.getText(); 340 setLastPath(mask); 341 } 342 AddeFormat format = (AddeFormat)formatComboBox.getSelectedItem(); 343 LocalAddeEntry entry = new LocalAddeEntry.Builder(name, group, mask, format).status(EntryStatus.ENABLED).build(); 344 return Collections.singleton(entry); 345 } 346 347 /** 348 * Creates new {@link LocalAddeEntry}s based upon the contents of the dialog 349 * and adds {@literal "them"} to the managed servers. If the dialog is 350 * displayed, we call {@link #dispose()} and attempt to refresh the 351 * server manager GUI if it is available. 352 */ 353 private void addEntry() { 354 Set<LocalAddeEntry> addedEntries = pollWidgets(); 355 entryStore.addEntries(addedEntries); 356 if (isDisplayable()) { 357 dispose(); 358 } 359 if (managerController != null) { 360 managerController.refreshDisplay(); 361 } 362 } 363 364 private void editEntry() { 365 Set<LocalAddeEntry> newEntries = pollWidgets(); 366 Set<LocalAddeEntry> currentEntries = Collections.singleton(currentEntry); 367 entryStore.replaceEntries(currentEntries, newEntries); 368 if (isDisplayable()) { 369 dispose(); 370 } 371 if (managerController != null) { 372 managerController.refreshDisplay(); 373 } 374 } 375 376 /** 377 * Ask the user for a data directory from which to create a MASK= 378 * 379 * @param startDir If this is a valid path, then the file picker will 380 * (presumably) use that as its initial location. Should not be 381 * {@code null}? 382 * 383 * @return Either a path to a data directory or {@code startDir}. 384 */ 385 private String getDataDirectory(final String startDir) { 386 JFileChooser fileChooser = new JFileChooser(); 387 fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 388 fileChooser.setSelectedFile(new File(startDir)); 389 switch (fileChooser.showOpenDialog(this)) { 390 case JFileChooser.APPROVE_OPTION: 391 return fileChooser.getSelectedFile().getAbsolutePath(); 392 case JFileChooser.CANCEL_OPTION: 393 return startDir; 394 default: 395 return startDir; 396 } 397 } 398 399 /** 400 * @see #editorAction 401 */ 402 public EditorAction getEditorAction() { 403 return editorAction; 404 } 405 406 /** 407 * @see #editorAction 408 */ 409 private void setEditorAction(final EditorAction editorAction) { 410 this.editorAction = editorAction; 411 } 412 413 /** 414 * Dave's nice combobox tooltip renderer! 415 */ 416 private class TooltipComboBoxRenderer extends BasicComboBoxRenderer { 417 @Override public Component getListCellRendererComponent(JList list, 418 Object value, int index, boolean isSelected, boolean cellHasFocus) 419 { 420 if (isSelected) { 421 setBackground(list.getSelectionBackground()); 422 setForeground(list.getSelectionForeground()); 423 if (value != null && (value instanceof AddeFormat)) 424 list.setToolTipText(((AddeFormat)value).getTooltip()); 425 } else { 426 setBackground(list.getBackground()); 427 setForeground(list.getForeground()); 428 } 429 setFont(list.getFont()); 430 setText((value == null) ? "" : value.toString()); 431 return this; 432 } 433 } 434 435 // Variables declaration - do not modify 436 private JTextField datasetField; 437 private JTextField directoryField; 438 private JComboBox formatComboBox; 439 private JTextField typeField; 440 // End of variables declaration 441 }