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    }