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