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.supportform;
029    
030    import java.awt.EventQueue;
031    
032    import javax.swing.JFrame;
033    import javax.swing.JPanel;
034    import javax.swing.text.JTextComponent;
035    
036    import net.miginfocom.swing.MigLayout;
037    
038    import javax.swing.JFileChooser;
039    import javax.swing.JLabel;
040    import javax.swing.JOptionPane;
041    import javax.swing.JScrollPane;
042    import javax.swing.JTextField;
043    import javax.swing.JTextArea;
044    import javax.swing.JButton;
045    import javax.swing.JCheckBox;
046    import java.awt.event.MouseAdapter;
047    import java.awt.event.MouseEvent;
048    import java.awt.event.ActionListener;
049    import java.awt.event.ActionEvent;
050    
051    import ucar.unidata.idv.IdvObjectStore;
052    import ucar.unidata.idv.IntegratedDataViewer;
053    import ucar.unidata.idv.ui.IdvUIManager;
054    
055    import edu.wisc.ssec.mcidasv.util.BackgroundTask;
056    import edu.wisc.ssec.mcidasv.util.CollectionHelpers;
057    import edu.wisc.ssec.mcidasv.util.Contract;
058    import edu.wisc.ssec.mcidasv.util.FocusTraveller;
059    
060    import java.io.File;
061    import java.util.List;
062    import java.util.concurrent.ExecutorService;
063    import java.util.concurrent.Executors;
064    
065    /**
066     * This class handles all the GUI elements of a McIDAS-V support request.
067     */
068    public class SupportForm extends JFrame {
069        
070        public static final String PROP_SUPPORTREQ_BUNDLE = "mcv.supportreq.bundle";
071        
072        public static final String PROP_SUPPORTREQ_CC = "mcv.supportreq.cc";
073        
074        public static final String PROP_SUPPORTREQ_CONFIRMEMAIL = "mcv.supportreq.confirmedemail";
075        
076        private static final String HELP_ID = "idv.tools.supportrequestform";
077        
078        private static ExecutorService exec = Executors.newCachedThreadPool();
079        
080        private final IdvObjectStore store;
081        
082        private final StateCollector collector;
083        
084        private final CancelListener listener = new CancelListener();
085        
086        private JPanel contentPane;
087        private JTextField userField;
088        private JTextField emailField;
089        private JTextField confirmField;
090        private JTextField organizationField;
091        private JTextField subjectField;
092        private JTextField attachmentOneField;
093        private JTextField attachmentTwoField;
094        private JTextArea descriptionArea;
095        private JCheckBox bundleCheckBox;
096        private JCheckBox ccCheckBox;
097        private JButton sendButton;
098        private JButton cancelButton;
099        private JButton helpButton;
100        
101        /**
102         * Creates a support request form that collects information about
103         * the current McIDAS-V session.
104         * 
105         * @param store Storage for persisted user input. Should not be {@code null}.
106         * @param collector Collects information about the current session.
107         */
108        public SupportForm(IdvObjectStore store, StateCollector collector) {
109            this.store = Contract.notNull(store);
110            this.collector = Contract.notNull(collector);
111            initComponents();
112            unpersistInput();
113            otherDoFocusThingNow();
114        }
115        
116        /**
117         * Saves user input for the following: name, email address, email address
118         * confirmation, organization, whether or not to CC the user a copy, and 
119         * whether or not a {@literal "state"} bundle should be included.
120         * 
121         * <p>You should initialize the GUI components before calling this method.
122         */
123        private void persistInput() {
124            store.put(IdvUIManager.PROP_HELP_NAME, getUser());
125            store.put(IdvUIManager.PROP_HELP_EMAIL, getEmail());
126            store.put(PROP_SUPPORTREQ_CONFIRMEMAIL, getConfirmedEmail());
127            store.put(IdvUIManager.PROP_HELP_ORG, getOrganization());
128            store.put(PROP_SUPPORTREQ_CC, getSendCopy());
129            store.put(PROP_SUPPORTREQ_BUNDLE, getSendBundle());
130            store.save();
131        }
132        
133        /**
134         * Loads user input for the following: name, email address, email address
135         * confirmation, organization, whether or not to CC the user a copy, and 
136         * whether or not a {@literal "state"} bundle should be included.
137         * 
138         * <p>You should initialize the GUI components before calling this method.
139         */
140        private void unpersistInput() {
141            userField.setText(store.get(IdvUIManager.PROP_HELP_NAME, ""));
142            emailField.setText(store.get(IdvUIManager.PROP_HELP_EMAIL, ""));
143            confirmField.setText(store.get(PROP_SUPPORTREQ_CONFIRMEMAIL, ""));
144            organizationField.setText(store.get(IdvUIManager.PROP_HELP_ORG, ""));
145            ccCheckBox.setSelected(store.get(PROP_SUPPORTREQ_CC, true));
146            bundleCheckBox.setSelected(store.get(PROP_SUPPORTREQ_BUNDLE, false));
147        }
148        
149        /**
150         * Create the frame.
151         */
152        public void initComponents() {
153            setTitle("Request McIDAS-V Support");
154            setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
155            setBounds(100, 100, 682, 538);
156            contentPane = new JPanel();
157            setContentPane(contentPane);
158            contentPane.setLayout(new MigLayout("", "[][grow]", "[][][][][][][grow][][][][][][]"));
159            
160            JLabel nameLabel = new JLabel("Your Name:");
161            contentPane.add(nameLabel, "cell 0 0,alignx right");
162            
163            userField = new JTextField();
164            contentPane.add(userField, "cell 1 0,growx");
165            userField.setName("user");
166            userField.setColumns(10);
167            
168            JLabel emailLabel = new JLabel("Your Email:");
169            contentPane.add(emailLabel, "cell 0 1,alignx right");
170            
171            emailField = new JTextField();
172            contentPane.add(emailField, "cell 1 1,growx");
173            emailField.setName("email");
174            emailField.setColumns(10);
175            
176            JLabel confirmLabel = new JLabel("Confirm Email:");
177            contentPane.add(confirmLabel, "cell 0 2,alignx right");
178            
179            confirmField = new JTextField();
180            contentPane.add(confirmField, "cell 1 2,growx");
181            confirmField.setName("confirm");
182            confirmField.setColumns(10);
183            
184            JLabel organizationLabel = new JLabel("Organization:");
185            contentPane.add(organizationLabel, "cell 0 3,alignx right");
186            
187            organizationField = new JTextField();
188            contentPane.add(organizationField, "cell 1 3,growx");
189            organizationField.setName("organization");
190            organizationField.setColumns(10);
191            
192            JLabel subjectLabel = new JLabel("Subject:");
193            contentPane.add(subjectLabel, "cell 0 4,alignx right");
194            
195            subjectField = new JTextField();
196            contentPane.add(subjectField, "cell 1 4,growx");
197            subjectField.setName("subject");
198            subjectField.setColumns(10);
199            
200            JLabel descriptiveLabel = new JLabel("Please provide a thorough description of the problem you encountered.");
201            contentPane.add(descriptiveLabel, "cell 1 5");
202            
203            JLabel descriptionLabel = new JLabel("Description:");
204            contentPane.add(descriptionLabel, "cell 0 6,alignx right,aligny top");
205            
206            descriptionArea = new JTextArea();
207            JScrollPane scrollPane = new JScrollPane(descriptionArea);
208            contentPane.add(scrollPane, "cell 1 6,grow");
209            descriptionArea.setName("description");
210            descriptionArea.setLineWrap(true);
211            descriptionArea.setWrapStyleWord(true);
212            descriptionArea.setColumns(20);
213            descriptionArea.setRows(6);
214            
215            JLabel attachmentOneLabel = new JLabel("Attachment 1:");
216            contentPane.add(attachmentOneLabel, "cell 0 7,alignx right");
217            
218            attachmentOneField = new JTextField();
219            attachmentOneField.addMouseListener(new MouseAdapter() {
220                @Override public void mouseClicked(MouseEvent evt) {
221                    attachmentOneFieldMouseClicked(evt);
222                }
223            });
224            contentPane.add(attachmentOneField, "flowx,cell 1 7,growx");
225            attachmentOneField.setName("attachment1");
226            attachmentOneField.setColumns(10);
227            
228            JButton attachmentOneButton = new JButton("Browse...");
229            attachmentOneButton.addActionListener(new ActionListener() {
230                public void actionPerformed(ActionEvent evt) {
231                    attachmentOneButtonActionPerformed(evt);
232                }
233            });
234            contentPane.add(attachmentOneButton, "cell 1 7,alignx left");
235            
236            JLabel attachmentTwoLabel = new JLabel("Attachment 2:");
237            contentPane.add(attachmentTwoLabel, "cell 0 8,alignx right");
238            
239            attachmentTwoField = new JTextField();
240            attachmentTwoField.addMouseListener(new MouseAdapter() {
241                @Override public void mouseClicked(MouseEvent evt) {
242                    attachmentTwoFieldMouseClicked(evt);
243                }
244            });
245            contentPane.add(attachmentTwoField, "flowx,cell 1 8,growx");
246            attachmentTwoField.setName("attachment2");
247            attachmentTwoField.setColumns(10);
248            
249            JButton attachmentTwoButton = new JButton("Browse...");
250            attachmentTwoButton.addActionListener(new ActionListener() {
251                public void actionPerformed(ActionEvent evt) {
252                    attachmentTwoButtonActionPerformed(evt);
253                }
254            });
255            contentPane.add(attachmentTwoButton, "cell 1 8,alignx left");
256            
257            bundleCheckBox = new JCheckBox("Include current application state as a bundle.");
258            bundleCheckBox.setName("sendstate");
259            contentPane.add(bundleCheckBox, "cell 1 9,alignx left");
260            
261            ccCheckBox = new JCheckBox("Send copy of support request to the email address I provided.");
262            ccCheckBox.setName("ccrequest");
263            ccCheckBox.setSelected(true);
264            contentPane.add(ccCheckBox, "cell 1 10,alignx left");
265            
266            helpButton = new JButton("Help");
267            helpButton.addActionListener(new ActionListener() {
268                public void actionPerformed(ActionEvent evt) {
269                    ucar.unidata.ui.Help.getDefaultHelp().gotoTarget(HELP_ID);
270                }
271            });
272            contentPane.add(helpButton, "flowx,cell 1 12,alignx right");
273            
274            cancelButton = new JButton("Cancel");
275            cancelButton.addActionListener(listener);
276            contentPane.add(cancelButton, "cell 1 12,alignx right");
277            
278            sendButton = new JButton("Send Request");
279            sendButton.addActionListener(new ActionListener() {
280                public void actionPerformed(ActionEvent evt) {
281                    sendRequest(evt);
282                }
283            });
284            contentPane.add(sendButton, "cell 1 12,alignx right");
285            contentPane.setFocusTraversalPolicy(new FocusTraveller(userField, emailField, confirmField, organizationField, subjectField, descriptionArea, attachmentOneButton, attachmentTwoButton, bundleCheckBox, ccCheckBox, helpButton, cancelButton, sendButton));
286        }
287        
288        /**
289         * Checks {@link #emailField} and {@link #confirmField} to see if they 
290         * match (case is ignored).
291         * 
292         * @return {@code true} if there is a match, {@code false} otherwise.
293         */
294        public boolean checkEmailAddresses() {
295            return emailField.getText().equalsIgnoreCase(confirmField.getText());
296        }
297        
298        /**
299         * Returns whatever occupies {@link #userField}.
300         * 
301         * @return User's name.
302         */
303        public String getUser() {
304            return userField.getText();
305        }
306        
307        /**
308         * Returns whatever currently lives in {@link #emailField}.
309         * 
310         * @return User's email address.
311         */
312        public String getEmail() {
313            return emailField.getText();
314        }
315        
316        /**
317         * Returns whatever currently lives in {@link #confirmField}.
318         * 
319         * @return User's confirmed email address.
320         */
321        public String getConfirmedEmail() {
322            return confirmField.getText();
323        }
324        
325        /**
326         * Returns whatever resides in {@link #subjectField}.
327         * 
328         * @return Subject of the support request.
329         */
330        public String getSubject() {
331            return subjectField.getText();
332        }
333        
334        /**
335         * Returns whatever has commandeered {@link #organizationField}.
336         * 
337         * @return Organization to which the user belongs.
338         */
339        public String getOrganization() {
340            return organizationField.getText();
341        }
342        
343        /**
344         * Returns whatever is ensconced inside {@link #descriptionArea}.
345         * 
346         * @return Body of the user's email.
347         */
348        public String getDescription() {
349            return descriptionArea.getText();
350        }
351        
352        /**
353         * Checks whether or not the user has attached a file in the 
354         * {@literal "first file"} slot.
355         * 
356         * @return {@code true} if there's a file, {@code false} otherwise.
357         */
358        public boolean hasAttachmentOne() {
359            return new File(attachmentOneField.getText()).exists();
360        }
361        
362        /**
363         * Checks whether or not the user has attached a file in the 
364         * {@literal "second file"} slot.
365         * 
366         * @return {@code true} if there's a file, {@code false} otherwise.
367         */
368        public boolean hasAttachmentTwo() {
369            return new File(attachmentTwoField.getText()).exists();
370        }
371        
372        /**
373         * Returns whatever file path has monopolized {@link #attachmentOneField}.
374         * 
375         * @return Path to the first file attachment, or a blank string if no file
376         * has been selected.
377         */
378        public String getAttachmentOne() {
379            return attachmentOneField.getText();
380        }
381        
382        /**
383         * Returns whatever file path has appeared within 
384         * {@link #attachmentTwoField}.
385         * 
386         * @return Path to the second file attachment, or a blank string if no 
387         * file has been selected.
388         */
389        public String getAttachmentTwo() {
390            return attachmentTwoField.getText();
391        }
392        
393        // TODO: javadocs!
394        public boolean getSendCopy() {
395            return ccCheckBox.isSelected();
396        }
397        
398        public boolean getSendBundle() {
399            return bundleCheckBox.isSelected();
400        }
401        
402        public byte[] getExtraState() {
403            return collector.getContents();
404        }
405        
406        public String getExtraStateName() {
407            return collector.getExtraAttachmentName();
408        }
409        
410        public boolean canBundleState() {
411            return collector.canBundleState();
412        }
413        
414        public byte[] getBundledState() {
415            return collector.getBundledState();
416        }
417        
418        public String getBundledStateName() {
419            return collector.getBundleAttachmentName();
420        }
421        
422        public boolean canSendLog() {
423            String path = collector.getLogPath();
424            if (path == null || path.isEmpty()) {
425                return false;
426            }
427            return new File(path).exists();
428        }
429        
430        public String getLogPath() {
431            return collector.getLogPath();
432        }
433        
434        // TODO: dialogs are bad news bears.
435        public void showSuccess() {
436            setVisible(false);
437            dispose();
438            JOptionPane.showMessageDialog(null, "Support request sent successfully.", "Success", JOptionPane.DEFAULT_OPTION);
439        }
440        
441        // TODO: dialogs are bad news hares.
442        public void showFailure(final String reason) {
443            String msg = "";
444            if (reason == null || reason.isEmpty()) {
445                msg = "Error sending request, could not determine cause.";
446            } else {
447                msg = "Error sending request:\n"+reason;
448            }
449            JOptionPane.showMessageDialog(this, msg, "Problem sending support request", JOptionPane.ERROR_MESSAGE);
450            if (sendButton != null) {
451                sendButton.setEnabled(true);
452            }
453        }
454        
455        /**
456         * Checks to see if there is <i>anything</i> in the name, email, 
457         * email confirmation, subject, and description.
458         * 
459         * @return {@code true} if all of the required fields have some sort of 
460         * input, {@code false} otherwise.
461         */
462        private boolean validInput() {
463            if (userField.getText().isEmpty()) {
464                return false;
465            }
466            if (emailField.getText().isEmpty()) {
467                return false;
468            }
469            if (confirmField.getText().isEmpty()) {
470                return false;
471            }
472            if (subjectField.getText().isEmpty()) {
473                return false;
474            }
475            if (descriptionArea.getText().isEmpty()) {
476                return false;
477            }
478            return checkEmailAddresses();
479        }
480        
481        private void attachmentOneButtonActionPerformed(ActionEvent evt) {
482            attachFileToField(attachmentOneField);
483        }
484        
485        private void attachmentTwoButtonActionPerformed(ActionEvent evt) {
486            attachFileToField(attachmentTwoField);
487        }
488        
489        private void attachmentOneFieldMouseClicked(MouseEvent evt) {
490            if (attachmentOneField.getText().isEmpty()) {
491                attachFileToField(attachmentOneField);
492            }
493        }
494        
495        private void attachmentTwoFieldMouseClicked(MouseEvent evt) {
496            if (attachmentTwoField.getText().isEmpty()) {
497                attachFileToField(attachmentTwoField);
498            }
499        }
500        
501        private void showInvalidInputs() {
502            // how to display these?
503            JOptionPane.showMessageDialog(this, "You must provide at least your name, email address, subject, and description.", "Missing required input", JOptionPane.ERROR_MESSAGE);
504        }
505        
506        private void sendRequest(ActionEvent evt) {
507            // check input validity
508            if (!validInput()) {
509                showInvalidInputs();
510                return;
511            }
512            
513            // disable the ability to send more requests until we get a status
514            // reply from the server.
515            if (sendButton != null) {
516                sendButton.setEnabled(false);
517            }
518            
519            // persist things that need it.
520            persistInput();
521            
522            // create a background thread
523            listener.task = new Submitter(this);
524            
525            // send the worker thread to the mines
526            exec.execute(listener.task);
527        }
528        
529        /**
530         * Due to some fields persisting user input between McIDAS-V sessions we
531         * set the focus to be on the first of these fields <i>lacking</i> input.
532         */
533        private void otherDoFocusThingNow() {
534            List<JTextComponent> comps = CollectionHelpers.list(userField, 
535                emailField, confirmField, organizationField, subjectField, descriptionArea);
536            
537            for (JTextComponent comp : comps) {
538                if (comp.getText().isEmpty()) {
539                    comp.requestFocus(true);
540                    break;
541                }
542            }
543        }
544        
545        private static void attachFileToField(final JTextField field) {
546            String current = field.getText();
547            JFileChooser jfc = new JFileChooser(current);
548            if (jfc.showOpenDialog(field) == JFileChooser.APPROVE_OPTION) {
549                field.setText(jfc.getSelectedFile().toString());
550            }
551        }
552        
553        private class CancelListener implements ActionListener {
554            BackgroundTask<?> task;
555            public void actionPerformed(ActionEvent e) {
556                if (task != null) {
557                    task.cancel(true);
558                }
559                setVisible(false);
560                dispose();
561            }
562        }
563        
564        /**
565         * Launch a test of the Support Request Form.
566         * 
567         * @param args Ignored.
568         */
569        public static void main(String[] args) {
570            EventQueue.invokeLater(new Runnable() {
571                public void run() {
572                    try {
573                        new SupportForm(
574                            new IntegratedDataViewer().getStore(), 
575                            new SimpleStateCollector()
576                        ).setVisible(true);
577                    } catch (Exception e) {
578                        e.printStackTrace();
579                    }
580                }
581            });
582        }
583    }