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 }