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 }