001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2025 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 https://www.gnu.org/licenses/. 027 */ 028package edu.wisc.ssec.mcidasv.supportform; 029 030import static java.util.Objects.requireNonNull; 031 032import java.awt.EventQueue; 033 034import javax.swing.JFrame; 035import javax.swing.JPanel; 036import javax.swing.text.JTextComponent; 037 038import net.miginfocom.swing.MigLayout; 039 040import javax.swing.JFileChooser; 041import javax.swing.JLabel; 042import javax.swing.JOptionPane; 043import javax.swing.JScrollPane; 044import javax.swing.JTextField; 045import javax.swing.JTextArea; 046import javax.swing.JButton; 047import javax.swing.JCheckBox; 048import java.awt.event.MouseAdapter; 049import java.awt.event.MouseEvent; 050import java.awt.event.ActionListener; 051import java.awt.event.ActionEvent; 052 053import org.slf4j.Logger; 054import org.slf4j.LoggerFactory; 055import ucar.unidata.idv.IdvObjectStore; 056import ucar.unidata.idv.IntegratedDataViewer; 057import ucar.unidata.idv.ui.IdvUIManager; 058 059import edu.wisc.ssec.mcidasv.util.BackgroundTask; 060import edu.wisc.ssec.mcidasv.util.CollectionHelpers; 061import edu.wisc.ssec.mcidasv.util.FocusTraveller; 062 063import java.io.*; 064import java.nio.file.Files; 065import java.nio.file.Paths; 066import java.util.ArrayList; 067import java.util.List; 068import java.util.concurrent.ExecutorService; 069import java.util.concurrent.Executors; 070import java.util.zip.ZipEntry; 071import java.util.zip.ZipOutputStream; 072 073/** 074 * This class handles all the GUI elements of a McIDAS-V support request. 075 */ 076public class SupportForm extends JFrame { 077 078 private static final Logger logger = 079 LoggerFactory.getLogger(SupportForm.class); 080 081 public static final String PROP_SUPPORTREQ_BUNDLE = "mcv.supportreq.bundle"; 082 083 public static final String PROP_SUPPORTREQ_CC = "mcv.supportreq.cc"; 084 085 public static final String PROP_SUPPORTREQ_CONFIRMEMAIL = "mcv.supportreq.confirmedemail"; 086 087 private static final String HELP_ID = "idv.tools.supportrequestform"; 088 089 private static ExecutorService exec = Executors.newCachedThreadPool(); 090 091 private final IdvObjectStore store; 092 093 private final StateCollector collector; 094 095 private final CancelListener listener = new CancelListener(); 096 097 private JPanel contentPane; 098 private JTextField userField; 099 private JTextField emailField; 100 private JTextField confirmField; 101 private JTextField organizationField; 102 private JTextField subjectField; 103 private JTextField attachmentOneField; 104 private JTextField attachmentTwoField; 105 private JTextArea descriptionArea; 106 private JCheckBox bundleCheckBox; 107 private JCheckBox ccCheckBox; 108 private JButton sendButton; 109 private JButton cancelButton; 110 private JButton helpButton; 111 112 // TODO: McIDAS Inquiry #3159-3141 -> get a list of all file types that the wisc.edu email server 113 // TODO: does not allow and add it here 114 private static String[] unsupportedAttachments = {"py", "bat"}; // add more here 115 116 private static ArrayList<String> filesToDelete = new ArrayList<>(); 117 118 /** 119 * Creates a support request form that collects information about 120 * the current McIDAS-V session. 121 * 122 * @param store Storage for persisted user input. Should not be {@code null}. 123 * @param collector Collects information about the current session. 124 */ 125 public SupportForm(IdvObjectStore store, StateCollector collector) { 126 this.store = requireNonNull(store); 127 this.collector = requireNonNull(collector); 128 initComponents(); 129 unpersistInput(); 130 otherDoFocusThingNow(); 131 } 132 133 /** 134 * Saves 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 persistInput() { 141 store.put(IdvUIManager.PROP_HELP_NAME, getUser()); 142 store.put(IdvUIManager.PROP_HELP_EMAIL, getEmail()); 143 store.put(PROP_SUPPORTREQ_CONFIRMEMAIL, getConfirmedEmail()); 144 store.put(IdvUIManager.PROP_HELP_ORG, getOrganization()); 145 store.put(PROP_SUPPORTREQ_CC, getSendCopy()); 146 store.put(PROP_SUPPORTREQ_BUNDLE, getSendBundle()); 147 store.save(); 148 } 149 150 /** 151 * Loads user input for the following: name, email address, email address 152 * confirmation, organization, whether or not to CC the user a copy, and 153 * whether or not a {@literal "state"} bundle should be included. 154 * 155 * <p>You should initialize the GUI components before calling this method. 156 */ 157 private void unpersistInput() { 158 userField.setText(store.get(IdvUIManager.PROP_HELP_NAME, "")); 159 emailField.setText(store.get(IdvUIManager.PROP_HELP_EMAIL, "")); 160 confirmField.setText(store.get(PROP_SUPPORTREQ_CONFIRMEMAIL, "")); 161 organizationField.setText(store.get(IdvUIManager.PROP_HELP_ORG, "")); 162 ccCheckBox.setSelected(store.get(PROP_SUPPORTREQ_CC, true)); 163 bundleCheckBox.setSelected(store.get(PROP_SUPPORTREQ_BUNDLE, false)); 164 } 165 166 /** 167 * Create the frame. 168 */ 169 public void initComponents() { 170 setTitle("Request McIDAS-V Support"); 171 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 172 setBounds(100, 100, 682, 538); 173 contentPane = new JPanel(); 174 setContentPane(contentPane); 175 contentPane.setLayout(new MigLayout("", "[][grow]", "[][][][][][][grow][][][][][][]")); 176 177 JLabel nameLabel = new JLabel("Your Name:"); 178 contentPane.add(nameLabel, "cell 0 0,alignx right"); 179 180 userField = new JTextField(); 181 contentPane.add(userField, "cell 1 0,growx"); 182 userField.setName("user"); 183 userField.setColumns(10); 184 185 JLabel emailLabel = new JLabel("Your Email:"); 186 contentPane.add(emailLabel, "cell 0 1,alignx right"); 187 188 emailField = new JTextField(); 189 contentPane.add(emailField, "cell 1 1,growx"); 190 emailField.setName("email"); 191 emailField.setColumns(10); 192 193 JLabel confirmLabel = new JLabel("Confirm Email:"); 194 contentPane.add(confirmLabel, "cell 0 2,alignx right"); 195 196 confirmField = new JTextField(); 197 contentPane.add(confirmField, "cell 1 2,growx"); 198 confirmField.setName("confirm"); 199 confirmField.setColumns(10); 200 201 JLabel organizationLabel = new JLabel("Organization:"); 202 contentPane.add(organizationLabel, "cell 0 3,alignx right"); 203 204 organizationField = new JTextField(); 205 contentPane.add(organizationField, "cell 1 3,growx"); 206 organizationField.setName("organization"); 207 organizationField.setColumns(10); 208 209 JLabel subjectLabel = new JLabel("Subject:"); 210 contentPane.add(subjectLabel, "cell 0 4,alignx right"); 211 212 subjectField = new JTextField(); 213 contentPane.add(subjectField, "cell 1 4,growx"); 214 subjectField.setName("subject"); 215 subjectField.setColumns(10); 216 217 JLabel descriptiveLabel = new JLabel("Please provide a thorough description of the problem you encountered."); 218 contentPane.add(descriptiveLabel, "cell 1 5"); 219 220 JLabel descriptionLabel = new JLabel("Description:"); 221 contentPane.add(descriptionLabel, "cell 0 6,alignx right,aligny top"); 222 223 descriptionArea = new JTextArea(); 224 JScrollPane scrollPane = new JScrollPane(descriptionArea); 225 contentPane.add(scrollPane, "cell 1 6,grow"); 226 descriptionArea.setName("description"); 227 descriptionArea.setLineWrap(true); 228 descriptionArea.setWrapStyleWord(true); 229 descriptionArea.setColumns(20); 230 descriptionArea.setRows(6); 231 232 JLabel attachmentOneLabel = new JLabel("Attachment 1:"); 233 contentPane.add(attachmentOneLabel, "cell 0 7,alignx right"); 234 235 attachmentOneField = new JTextField(); 236 attachmentOneField.addMouseListener(new MouseAdapter() { 237 @Override public void mouseClicked(MouseEvent evt) { 238 attachmentOneFieldMouseClicked(evt); 239 } 240 }); 241 contentPane.add(attachmentOneField, "flowx,cell 1 7,growx"); 242 attachmentOneField.setName("attachment1"); 243 attachmentOneField.setColumns(10); 244 245 JButton attachmentOneButton = new JButton("Browse..."); 246 attachmentOneButton.addActionListener(new ActionListener() { 247 public void actionPerformed(ActionEvent evt) { 248 attachmentOneButtonActionPerformed(evt); 249 } 250 }); 251 contentPane.add(attachmentOneButton, "cell 1 7,alignx left"); 252 253 JLabel attachmentTwoLabel = new JLabel("Attachment 2:"); 254 contentPane.add(attachmentTwoLabel, "cell 0 8,alignx right"); 255 256 attachmentTwoField = new JTextField(); 257 attachmentTwoField.addMouseListener(new MouseAdapter() { 258 @Override public void mouseClicked(MouseEvent evt) { 259 attachmentTwoFieldMouseClicked(evt); 260 } 261 }); 262 contentPane.add(attachmentTwoField, "flowx,cell 1 8,growx"); 263 attachmentTwoField.setName("attachment2"); 264 attachmentTwoField.setColumns(10); 265 266 JButton attachmentTwoButton = new JButton("Browse..."); 267 attachmentTwoButton.addActionListener(new ActionListener() { 268 public void actionPerformed(ActionEvent evt) { 269 attachmentTwoButtonActionPerformed(evt); 270 } 271 }); 272 contentPane.add(attachmentTwoButton, "cell 1 8,alignx left"); 273 274 bundleCheckBox = new JCheckBox("Include current application state as a bundle."); 275 bundleCheckBox.setName("sendstate"); 276 contentPane.add(bundleCheckBox, "cell 1 9,alignx left"); 277 278 ccCheckBox = new JCheckBox("Send copy of support request to the email address I provided."); 279 ccCheckBox.setName("ccrequest"); 280 ccCheckBox.setSelected(true); 281 contentPane.add(ccCheckBox, "cell 1 10,alignx left"); 282 283 helpButton = new JButton("Help"); 284 helpButton.addActionListener(new ActionListener() { 285 public void actionPerformed(ActionEvent evt) { 286 ucar.unidata.ui.Help.getDefaultHelp().gotoTarget(HELP_ID); 287 } 288 }); 289 contentPane.add(helpButton, "flowx,cell 1 12,alignx right"); 290 291 cancelButton = new JButton("Cancel"); 292 cancelButton.addActionListener(listener); 293 contentPane.add(cancelButton, "cell 1 12,alignx right"); 294 295 sendButton = new JButton("Send Request"); 296 sendButton.addActionListener(new ActionListener() { 297 public void actionPerformed(ActionEvent evt) { 298 sendRequest(evt); 299 } 300 }); 301 contentPane.add(sendButton, "cell 1 12,alignx right"); 302 contentPane.setFocusTraversalPolicy(new FocusTraveller(userField, emailField, confirmField, organizationField, subjectField, descriptionArea, attachmentOneButton, attachmentTwoButton, bundleCheckBox, ccCheckBox, helpButton, cancelButton, sendButton)); 303 } 304 305 /** 306 * Checks {@link #emailField} and {@link #confirmField} to see if they 307 * match (case is ignored). 308 * 309 * @return {@code true} if there is a match, {@code false} otherwise. 310 */ 311 public boolean checkEmailAddresses() { 312 return emailField.getText().equalsIgnoreCase(confirmField.getText()); 313 } 314 315 /** 316 * Returns whatever occupies {@link #userField}. 317 * 318 * @return User's name. 319 */ 320 public String getUser() { 321 return userField.getText(); 322 } 323 324 /** 325 * Returns whatever currently lives in {@link #emailField}. 326 * 327 * @return User's email address. 328 */ 329 public String getEmail() { 330 return emailField.getText(); 331 } 332 333 /** 334 * Returns whatever currently lives in {@link #confirmField}. 335 * 336 * @return User's confirmed email address. 337 */ 338 public String getConfirmedEmail() { 339 return confirmField.getText(); 340 } 341 342 /** 343 * Returns whatever resides in {@link #subjectField}. 344 * 345 * @return Subject of the support request. 346 */ 347 public String getSubject() { 348 return subjectField.getText(); 349 } 350 351 /** 352 * Returns whatever has commandeered {@link #organizationField}. 353 * 354 * @return Organization to which the user belongs. 355 */ 356 public String getOrganization() { 357 return organizationField.getText(); 358 } 359 360 /** 361 * Returns whatever is ensconced inside {@link #descriptionArea}. 362 * 363 * @return Body of the user's email. 364 */ 365 public String getDescription() { 366 return descriptionArea.getText(); 367 } 368 369 /** 370 * Checks whether or not the user has attached a file in the 371 * {@literal "first file"} slot. 372 * 373 * @return {@code true} if there's a file, {@code false} otherwise. 374 */ 375 public boolean hasAttachmentOne() { 376 return new File(attachmentOneField.getText()).exists(); 377 } 378 379 /** 380 * Checks whether or not the user has attached a file in the 381 * {@literal "second file"} slot. 382 * 383 * @return {@code true} if there's a file, {@code false} otherwise. 384 */ 385 public boolean hasAttachmentTwo() { 386 return new File(attachmentTwoField.getText()).exists(); 387 } 388 389 /** 390 * Returns whatever file path has monopolized {@link #attachmentOneField}. 391 * 392 * @return Path to the first file attachment, or a blank string if no file 393 * has been selected. 394 */ 395 public String getAttachmentOne() { 396 return attachmentOneField.getText(); 397 } 398 399 /** 400 * Returns whatever file path has appeared within 401 * {@link #attachmentTwoField}. 402 * 403 * @return Path to the second file attachment, or a blank string if no 404 * file has been selected. 405 */ 406 public String getAttachmentTwo() { 407 return attachmentTwoField.getText(); 408 } 409 410 // TODO: javadocs! 411 public boolean getSendCopy() { 412 return ccCheckBox.isSelected(); 413 } 414 415 public boolean getSendBundle() { 416 return bundleCheckBox.isSelected(); 417 } 418 419 public byte[] getExtraState() { 420 return collector.getContents(); 421 } 422 423 public String getExtraStateName() { 424 return collector.getExtraAttachmentName(); 425 } 426 427 public boolean canBundleState() { 428 return collector.canBundleState(); 429 } 430 431 public byte[] getBundledState() { 432 return collector.getBundledState(); 433 } 434 435 public String getBundledStateName() { 436 return collector.getBundleAttachmentName(); 437 } 438 439 /** 440 * Determine if {@code mcidasv.log} can be sent to the Help Desk. 441 * 442 * @return {@code true} if {@code mcidasv.log} exists, {@code false} 443 * otherwise. 444 */ 445 public boolean canSendLog() { 446 String path = collector.getLogPath(); 447 if (path == null || path.isEmpty()) { 448 return false; 449 } 450 return new File(path).exists(); 451 } 452 453 /** 454 * Get path to where {@code mcidasv.log} <b>should</b> be located. 455 * 456 * @return String representing the full path to the user's 457 * {@code mcidasv.log}. Note: <b>the file may not exist!</b> 458 */ 459 public String getLogPath() { 460 return collector.getLogPath(); 461 } 462 463 /** 464 * Determine if {@code RESOLV.SRV} can be sent to the Help Desk. 465 * 466 * @return {@code true} if {@code RESOLV.SRV} exists, {@code false} 467 * otherwise. 468 */ 469 public boolean canSendResolvSrv() { 470 String path = collector.getResolvSrvPath(); 471 boolean result = false; 472 if ((path != null) && !path.isEmpty()) { 473 result = Files.exists(Paths.get(path)); 474 } 475 return result; 476 } 477 478 /** 479 * Get the path to where the user's {@code RESOLV.SRV} file <b>should</b> 480 * be located. 481 * 482 * @return Path to {@code RESOLV.SRV}. Note: <b>the file may not exist!</b> 483 */ 484 public String getResolvSrvPath() { 485 return collector.getResolvSrvPath(); 486 } 487 488 /** 489 * Get path to McV prefs file. 490 * 491 * @return 492 */ 493 public String getPrefsPath() { 494 return collector.getPrefsPath(); 495 } 496 497 // TODO: dialogs are bad news bears. 498 public void showSuccess() { 499 setVisible(false); 500 dispose(); 501 JOptionPane.showMessageDialog(null, "Support request sent successfully.", "Success", JOptionPane.DEFAULT_OPTION); 502 // delete compressed files, if any exist 503 clearFiles(); 504 } 505 506 // TODO: dialogs are bad news hares. 507 public void showFailure(final String reason) { 508 clearFiles(); 509 String msg = ""; 510 if (reason == null || reason.isEmpty()) { 511 msg = "Error sending request, could not determine cause."; 512 } else { 513 msg = "Error sending request:\n"+reason; 514 } 515 JOptionPane.showMessageDialog(this, msg, "Problem sending support request", JOptionPane.ERROR_MESSAGE); 516 if (sendButton != null) { 517 sendButton.setEnabled(true); 518 } 519 } 520 521 /** 522 * Checks to see if there is <i>anything</i> in the name, email, 523 * email confirmation, subject, and description. 524 * 525 * @return {@code true} if all of the required fields have some sort of 526 * input, {@code false} otherwise. 527 */ 528 private boolean validInput() { 529 if (userField.getText().isEmpty()) { 530 return false; 531 } 532 if (emailField.getText().isEmpty()) { 533 return false; 534 } 535 if (confirmField.getText().isEmpty()) { 536 return false; 537 } 538 if (subjectField.getText().isEmpty()) { 539 return false; 540 } 541 if (descriptionArea.getText().isEmpty()) { 542 return false; 543 } 544 return checkEmailAddresses(); 545 } 546 547 private void attachmentOneButtonActionPerformed(ActionEvent evt) { 548 attachFileToField(attachmentOneField); 549 } 550 551 private void attachmentTwoButtonActionPerformed(ActionEvent evt) { 552 attachFileToField(attachmentTwoField); 553 } 554 555 private void attachmentOneFieldMouseClicked(MouseEvent evt) { 556 if (attachmentOneField.getText().isEmpty()) { 557 attachFileToField(attachmentOneField); 558 } 559 } 560 561 private void attachmentTwoFieldMouseClicked(MouseEvent evt) { 562 if (attachmentTwoField.getText().isEmpty()) { 563 attachFileToField(attachmentTwoField); 564 } 565 } 566 567 private void showInvalidInputs() { 568 // how to display these? 569 JOptionPane.showMessageDialog(this, "You must provide at least your name, email address, subject, and description.", "Missing required input", JOptionPane.ERROR_MESSAGE); 570 } 571 572 private void sendRequest(ActionEvent evt) { 573 574 // check input validity 575 if (!validInput()) { 576 showInvalidInputs(); 577 return; 578 } 579 580 // zip files! 581 if (!(zipFile(attachmentOneField) && zipFile(attachmentTwoField))) return; 582 583 // disable the ability to send more requests until we get a status 584 // reply from the server. 585 if (sendButton != null) { 586 sendButton.setEnabled(false); 587 } 588 589 // persist things that need it. 590 persistInput(); 591 592 // create a background thread 593 listener.task = new Submitter(this); 594 595 // send the worker thread to the mines 596 exec.execute(listener.task); 597 } 598 599 /** 600 * Due to some fields persisting user input between McIDAS-V sessions we 601 * set the focus to be on the first of these fields <i>lacking</i> input. 602 */ 603 private void otherDoFocusThingNow() { 604 List<JTextComponent> comps = CollectionHelpers.list(userField, 605 emailField, confirmField, organizationField, subjectField, descriptionArea); 606 607 for (JTextComponent comp : comps) { 608 if (comp.getText().isEmpty()) { 609 comp.requestFocus(true); 610 break; 611 } 612 } 613 } 614 615 public static void clearFiles() { 616 for (int i = 0; i < filesToDelete.size(); i++) { 617 logger.warn(filesToDelete.get(i) + " will be deleted!"); 618 File toDelete = new File(filesToDelete.get(i)); 619 toDelete.delete(); 620 } 621 } 622 623 private static void attachFileToField(final JTextField field) { 624 String current = field.getText(); 625 JFileChooser jfc = new JFileChooser(current); 626 if (jfc.showOpenDialog(field) == JFileChooser.APPROVE_OPTION) { 627 field.setText(jfc.getSelectedFile().toString()); 628 } 629 } 630 631 private static String getFileExtensionSF(String fullName) { 632 String fileName = new File(fullName).getName(); 633 int dotIndex = fileName.lastIndexOf('.'); 634 return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1); 635 } 636 637 private static String getFileNameWithoutExt(File file) { 638 String tmp = file.getName(); 639 String[] arrTmp = tmp.split("\\."); 640 String out = ""; 641 642 for (int i = 0; i < arrTmp.length - 1; i++) { 643 out += arrTmp[i]; 644 } 645 return out; 646 } 647 648 649 /** 650 * McIDAS Inquiry #1690-3141 651 * Zip files before sending them through the support form 652 */ 653 private static boolean zipFile(final JTextField field) { 654 String path = field.getText(); 655 if (path.isEmpty() || path == null) { 656 // no file in the field, that's okay! 657 return true; 658 } 659 File file = new File(path); 660 String ext = getFileExtensionSF(path); 661 662 for (String e : unsupportedAttachments) { 663 logger.info(ext + " " + e + " " +ext.equalsIgnoreCase(e)); 664 if (ext.equalsIgnoreCase(e)) { 665 JOptionPane.showMessageDialog(null, file.getName() + " may not be attatched correctly!", "Potentially dangerous file!", JOptionPane.ERROR_MESSAGE); 666 // let it pass 667 } 668 } 669 670 String newName = getFileNameWithoutExt(file) + "_" + ext + "File"; 671 try { 672 FileInputStream inStream = new FileInputStream(file); 673 674 String newFilePath = newName + "_compressed.zip"; 675 String newFileName = newName + "_compressed.zip"; 676 677 FileOutputStream outStream = new FileOutputStream(newFilePath); 678 ZipOutputStream zipStream = new ZipOutputStream(outStream); 679 680 ZipEntry zipEntry = new ZipEntry(file.getName()); 681 zipStream.putNextEntry(zipEntry); 682 683 byte[] bytes = new byte[1024]; 684 int length; 685 while ((length = inStream.read(bytes)) >= 0) { 686 zipStream.write(bytes, 0, length); 687 } 688 689 zipStream.close(); 690 inStream.close(); 691 outStream.close(); 692 693 filesToDelete.add(newFilePath); 694 field.setText(newFilePath); 695 logger.info("1: " + newFilePath); 696 logger.info("2: " + newFileName); 697 return true; 698 } catch (Exception e) { 699 logger.error("Zipping the file failed", e); 700 JOptionPane.showMessageDialog(null, "Unable to attach " + file.getName() + " file", "File Error", JOptionPane.ERROR_MESSAGE); 701 return false; 702 } 703 } 704 705 private class CancelListener implements ActionListener { 706 BackgroundTask<?> task; 707 public void actionPerformed(ActionEvent e) { 708 if (task != null) { 709 task.cancel(true); 710 } 711 setVisible(false); 712 dispose(); 713 } 714 } 715 716 /** 717 * Launch a test of the Support Request Form. 718 * 719 * @param args Ignored. 720 */ 721 public static void main(String[] args) { 722 EventQueue.invokeLater(() -> { 723 try { 724 new SupportForm( 725 new IntegratedDataViewer().getStore(), 726 new SimpleStateCollector() 727 ).setVisible(true); 728 } catch (Exception e) { 729 logger.error("Problem creating support form", e); 730 } 731 }); 732 } 733}