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 }