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