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