1 /* 2 * $Id$ 3 * 4 * Copyright (c) 1996, 2009, Oracle and/or its affiliates. All rights reserved. 5 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 6 * 7 * This code is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License version 2 only, as 9 * published by the Free Software Foundation. Oracle designates this 10 * particular file as subject to the "Classpath" exception as provided 11 * by Oracle in the LICENSE file that accompanied this code. 12 * 13 * This code is distributed in the hope that it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16 * version 2 for more details (a copy is included in the LICENSE file that 17 * accompanied this code). 18 * 19 * You should have received a copy of the GNU General Public License version 20 * 2 along with this work; if not, write to the Free Software Foundation, 21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 22 * 23 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 24 * or visit www.oracle.com if you need additional information or have any 25 * questions. 26 */ 27 package com.sun.interview.wizard; 28 29 import java.awt.BorderLayout; 30 import java.awt.Dimension; 31 import java.awt.EventQueue; 32 import java.awt.Frame; 33 import java.awt.Insets; 34 import java.awt.Toolkit; 35 import java.awt.Window; 36 import java.awt.event.ActionEvent; 37 import java.awt.event.ActionListener; 38 import java.awt.event.WindowAdapter; 39 import java.awt.event.WindowEvent; 40 import java.io.*; 41 import java.lang.reflect.InvocationTargetException; 42 import java.lang.reflect.Method; 43 import java.net.URL; 44 import java.nio.charset.StandardCharsets; 45 import java.util.Map; 46 import java.util.Properties; 47 import javax.swing.BorderFactory; 48 import javax.swing.Box; 49 import javax.swing.Icon; 50 import javax.swing.ImageIcon; 51 import javax.swing.JButton; 52 import javax.swing.JComponent; 53 import javax.swing.JDialog; 54 import javax.swing.JFileChooser; 55 import javax.swing.JFrame; 56 import javax.swing.JMenu; 57 import javax.swing.JMenuBar; 58 import javax.swing.JMenuItem; 59 import javax.swing.JOptionPane; 60 import javax.swing.JPanel; 61 import javax.swing.JPopupMenu; 62 import javax.swing.JSplitPane; 63 import javax.swing.JToggleButton; 64 import javax.swing.JToolBar; 65 import javax.swing.KeyStroke; 66 import javax.swing.UIManager; 67 import javax.swing.WindowConstants; 68 import javax.swing.event.AncestorEvent; 69 import javax.swing.event.AncestorListener; 70 import javax.swing.event.PopupMenuEvent; 71 import javax.swing.event.PopupMenuListener; 72 import javax.swing.filechooser.FileFilter; 73 74 import com.sun.interview.Interview; 75 import com.sun.interview.Question; 76 import com.sun.interview.WizPrint; 77 import com.sun.javatest.tool.jthelp.HelpBroker; 78 import com.sun.javatest.tool.jthelp.HelpSet; 79 import com.sun.javatest.tool.jthelp.JTHelpBroker; 80 81 /** 82 * A wizard to present an {@link Interview interview} consisting of 83 * a series of {@link Question questions}. 84 * 85 * <p>The tool can be started as an application itself, 86 * by using the {@link #main main} 87 * method. This requires that the class name of the interview 88 * be supplied as the first argument; the class itself must be on 89 * the tool's class path. This technique allows any interview 90 * to be run by this tool. 91 * <p>An alternative technique is to provide a small default main method 92 * inside each interview, which creates an instance of the interview 93 * and starts up a tool such as this one to run the interview. 94 *<pre> 95 * import javasoft.sqe.wizard.Interview; 96 * import javasoft.sqe.wizard.swing.Wizard; 97 * 98 * public class Demo extends Interview { 99 * public static void main(String[] args) { 100 * Demo d = new Demo(); 101 * Wizard w = new Wizard(d); 102 * w.showInFrame(true); 103 * } 104 * } 105 *</pre> 106 */ 107 public class Wizard extends JComponent { 108 /** 109 * A minimal main program to invoke the wizard on a specified interview. 110 * @param args Only one argument is accepted: the name of a class which is 111 * a subtype of {@link Interview}. 112 */ 113 public static void main(String[] args) { 114 try { 115 UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); 116 117 Class ic = (Class.forName(args[0], true, ClassLoader.getSystemClassLoader())); 118 Interview i = (Interview)(ic.newInstance()); 119 Wizard w = new Wizard(i); 120 w.showInFrame(true); 121 } 122 catch (Throwable e) { 123 e.printStackTrace(); 124 System.exit(1); 125 } 126 } 127 128 /** 129 * Create a wizard to present an interview. 130 * @param i The interview to be presented. 131 */ 132 public Wizard(Interview i) { 133 this(i, null); 134 } 135 136 /** 137 * Create a wizard to present an interview. 138 * @param i The interview to be presented. 139 * @param e An array of exporters to which the interview can be exported. 140 */ 141 public Wizard(Interview i, Exporter[] e) { 142 interview = i; 143 exporters = e; 144 } 145 146 /** 147 * Open a file and load it into the interview for this wizard. 148 * This does not affect the name of the current file. 149 * @param f The file to be loaded. 150 * @throws IOException if any problems occur while reading the file. 151 * @throws Interview.Fault if the checksum is missing or incorrect in the file 152 * @see Interview#load 153 * @see #setFile 154 */ 155 public void open(File f) throws Interview.Fault, IOException { 156 try (InputStream in = new BufferedInputStream(new FileInputStream(f))) { 157 158 Map<String, String> stringProps = com.sun.javatest.util.Properties.load(in); 159 interview.load(stringProps); 160 interview.setEdited(false); 161 String info = stringProps.get("INFO"); 162 if (info == null) { 163 info = "true"; 164 } 165 initialInfoVisible = info.equals("true"); 166 } 167 } 168 169 /** 170 * Save the current responses to the interview's questions in a file.. 171 * @param f The file in which to save the responses. 172 * @throws IOException if any problems occur while reading the file. 173 * @see Interview#save 174 */ 175 public void save(File f) throws IOException { 176 try (OutputStream out = new BufferedOutputStream(new FileOutputStream(f))) { 177 Properties p = new Properties(); 178 if (infoPanel != null) 179 p.put("INFO", String.valueOf(infoPanel.isShowing())); 180 interview.save(com.sun.javatest.util.Properties.convertToStringProps(p)); 181 interview.setEdited(false); 182 p.save(out, "Wizard data file: " + interview.getTitle()); 183 } 184 } 185 186 /** 187 * Get the name of the current file associated with this interview. 188 * @return the file for the interview 189 * @see #setFile 190 */ 191 public File getFile() { 192 return currFile; 193 } 194 195 /** 196 * Set the name of the current file associated with this interview. 197 * The file may be used as a default in open/save operations. 198 * @param f The file to be associated with this interview. 199 * @see #getFile 200 * @see #setDefaultFile 201 */ 202 public void setFile(File f) { 203 currFile = new File(f.getAbsolutePath()); 204 if (window != null) 205 updateTitle(window); 206 } 207 208 /** 209 * Set the name of a default file associated with this interview. 210 * The default file is used for the name of the current value 211 * if the user performs a File>New operation. In addition, if the 212 * default file is set, and the current file matches the default file, 213 * it will not be shown in the title bar. 214 * @param f The default file to be associated with this interview. 215 */ 216 public void setDefaultFile(File f) { 217 defaultFile = f; 218 if (window != null) 219 updateTitle(window); 220 } 221 222 /** 223 * Set the help broker in which context sensitive help and default menu help 224 * is displayed. If not set, a default help broker will be created. 225 * @param helpBroker The help broker to use for context sensitive and menu help. 226 */ 227 public void setHelpBroker(HelpBroker helpBroker) { 228 helpHelpBroker = helpBroker; 229 } 230 231 /** 232 * Set the help set to be used for context sensitive help and the default menu help. 233 * If not set, the interview's help set will be used. 234 * @param helpSet The help set to use for context sensitive and menu help. 235 * 236 */ 237 public void setHelpSet(HelpSet helpSet) { 238 helpHelpSet = helpSet; 239 } 240 241 /** 242 * Set the prefix string for the help IDs for context sensitive help and default menu help. 243 * If not set, the default is "wizard.". 244 * @param helpPrefix A prefix to be used for all context sentive help and menu entries. 245 */ 246 public void setHelpSetPrefix(String helpPrefix) { 247 helpHelpPrefix = helpPrefix; 248 } 249 250 /** 251 * Set the help menu to be used on the wizard. If not set, the default is a menu 252 * containing a single "Help" entry. 253 * @param helpMenu The help menu to be used. 254 */ 255 public void setHelpMenu(JMenu helpMenu) { 256 this.helpMenu = helpMenu; 257 } 258 259 260 /** 261 * Show the wizard in a frame centered on the screen. 262 * @param exitOnClose Set to true if the JVM should be exited when the frame is closed. 263 */ 264 public void showInFrame(final boolean exitOnClose) { 265 if (window != null && !(window instanceof JFrame)) 266 throw new IllegalStateException(); 267 268 if (!EventQueue.isDispatchThread()) { 269 EventQueue.invokeLater(new Runnable() { 270 public void run() { 271 showInFrame(exitOnClose); 272 } 273 }); 274 return; 275 } 276 277 initGUI(); 278 okBtn.setVisible(false); 279 cancelBtn.setVisible(false); 280 281 final JFrame f = new JFrame(); 282 initMenuBar(f); 283 updateTitle(f); 284 f.setName("interview.wizard"); 285 f.setJMenuBar(menuBar); 286 f.setContentPane(main); 287 f.pack(); 288 289 f.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 290 f.addWindowListener(new WindowAdapter() { 291 public void windowClosing(WindowEvent e) { 292 if (interview.isEdited() && !okToContinue()) 293 return; 294 e.getWindow().dispose(); 295 } 296 297 public void windowClosed(WindowEvent e) { 298 if (exitOnClose) 299 System.exit(0); 300 } 301 }); 302 303 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 304 Dimension size = f.getSize(); 305 f.setLocation(screenSize.width/2 - size.width/2, screenSize.height/2 - size.height/2); 306 f.show(); 307 308 window = f; 309 } 310 311 /** 312 * Action command for the okListener for {@link #showInDialog}. 313 */ 314 public static final String OK = "OK"; 315 316 /** 317 * Show the wizard in a dialog. 318 * @param parent The parent frame for this dialog. 319 * @param okListener A listener to e notified when the dialog is dismissed. 320 */ 321 public void showInDialog(final Frame parent, final ActionListener okListener) { 322 if (window != null && !(window instanceof JDialog)) 323 throw new IllegalStateException(); 324 325 if (!EventQueue.isDispatchThread()) { 326 EventQueue.invokeLater(new Runnable() { 327 public void run() { 328 showInDialog(parent, okListener); 329 } 330 }); 331 return; 332 } 333 334 this.okListener = okListener; 335 336 initGUI(); 337 okBtn.setVisible(true); 338 okBtn.setEnabled(interview.isFinishable()); 339 cancelBtn.setVisible(true); 340 341 final JDialog d = new JDialog(parent); 342 initMenuBar(d); 343 updateTitle(d); 344 d.setJMenuBar(menuBar); 345 d.setContentPane(main); 346 d.pack(); 347 348 d.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 349 d.addWindowListener(new WindowAdapter() { 350 public void windowClosing(WindowEvent e) { 351 if (!interview.isEdited() || okToContinue()) 352 e.getWindow().dispose(); 353 } 354 355 public void windowClosed(WindowEvent e) { 356 } 357 }); 358 359 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 360 Dimension size = d.getSize(); 361 d.setLocation(screenSize.width/2 - size.width/2, screenSize.height/2 - size.height/2); 362 d.show(); 363 364 window = d; 365 } 366 367 /** 368 * Check if this object is being displayed on the screen. 369 * @return true if the wizard is currently being displayed, 370 * and false otherwise. 371 */ 372 public boolean isShowing() { 373 return (window != null && window.isShowing()); 374 } 375 376 /** 377 * Ensure that this object is showing in front of all other windows 378 * on the screen. If the object is not currently visible, the call 379 * has no effect. 380 */ 381 public void toFront() { 382 if (window != null) 383 window.toFront(); 384 } 385 386 /** 387 * Initialize the frame's GUI components 388 */ 389 private void initGUI() { 390 391 title = interview.getTitle(); 392 if (title == null || title.equals("")) 393 title = i18n.getString("wizard.defaultTitle"); 394 395 //main = new JPanel(new BorderLayout()); 396 setLayout(new BorderLayout()); 397 main = this; 398 399 questionPanel = new QuestionPanel(interview); 400 questionPanel.setBorder(BorderFactory.createLoweredBevelBorder()); 401 pathPanel = new PathPanel(questionPanel, interview); 402 403 if (interview.getHelpSet() != null) 404 infoPanel = new InfoPanel(interview); 405 406 buttonPanel = new JToolBar(); 407 buttonPanel.setFloatable(false); 408 //buttonPanel.setBorder(BorderFactory.createRaisedBevelBorder()); 409 410 buttonPanel.add(Box.createHorizontalGlue()); 411 backBtn = createButton("back", "performBack", performer); 412 buttonPanel.add(backBtn); 413 nextBtn = createButton("next", "performNext", performer); 414 buttonPanel.add(nextBtn); 415 buttonPanel.addSeparator(); 416 okBtn = createButton("ok", "performOk", performer); 417 buttonPanel.add(okBtn); 418 cancelBtn = createButton("cancel", "performCancel", performer); 419 buttonPanel.add(cancelBtn); 420 if (infoPanel != null) { 421 buttonPanel.addSeparator(); 422 infoBtn = createToggle("info", "performInfo", performer); 423 infoBtn.setSelected(initialInfoVisible); 424 buttonPanel.add(infoBtn); 425 } 426 buttonPanel.addAncestorListener(new Listener()); 427 428 body = new JPanel(new BorderLayout()); 429 body.add(pathPanel, BorderLayout.WEST); 430 body.add(questionPanel, BorderLayout.CENTER); 431 body.add(buttonPanel, BorderLayout.SOUTH); 432 433 body.registerKeyboardAction(performer, "performFindNext", KeyStroke.getKeyStroke("F3"), 434 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 435 436 if (helpHelpPrefix == null) 437 helpHelpPrefix = "wizard."; 438 439 if (helpHelpSet == null && infoPanel != null) 440 helpHelpSet = infoPanel.getHelpSet(); 441 442 if (helpHelpBroker == null && helpHelpSet != null) 443 helpHelpBroker = new JTHelpBroker(); 444 445 if (helpHelpBroker != null && helpHelpSet != null) 446 helpHelpBroker.enableHelpKey(main, helpHelpPrefix + "window.csh"); 447 if (infoPanel == null) 448 main.add(body); 449 else 450 update(infoBtn.isSelected()); 451 } 452 453 private void initMenuBar(Window w) { 454 menuBar = new JMenuBar(); 455 456 fileMenu = createMenu("file", fileMenuData, performer); 457 if (w instanceof JFrame) { 458 fileMenu.addSeparator(); 459 fileMenu.add(createMenuItem("file", "exit", "performExit", performer)); 460 } 461 else { 462 fileMenu.addSeparator(); 463 fileMenu.add(createMenuItem("file", "close", "performCancel", performer)); 464 } 465 466 467 if (exporters != null) { 468 // replace the default "export log" item with a full export submenu 469 for (int i = 0; i < fileMenu.getItemCount(); i++) { 470 JMenuItem mi = fileMenu.getItem(i); 471 if (mi != null && mi.getActionCommand().equals("performExportLog")) { 472 fileMenu.remove(i); 473 JMenu exportMenu = new ExportMenu(exporters); 474 exportMenu.add(createMenuItem("export", "log", "performExportLog", performer)); 475 fileMenu.insert(exportMenu, i); 476 break; 477 } 478 } 479 } 480 menuBar.add(fileMenu); 481 482 JMenu searchMenu = createMenu("search", searchMenuData, performer); 483 menuBar.add(searchMenu); 484 485 if (helpHelpBroker != null) { 486 if (helpMenu == null) 487 helpMenu = createMenu("help", helpMenuData, performer); 488 menuBar.add(helpMenu); 489 } 490 } 491 492 private void update(boolean showInfoPanel) { 493 Dimension bodySize = body.getSize(); 494 if (bodySize.width == 0) 495 bodySize = body.getPreferredSize(); 496 497 Dimension infoSize = infoPanel.getSize(); 498 if (infoSize.width == 0) 499 infoSize = infoPanel.getPreferredSize(); 500 // need to capture the next value before we remove everything from main 501 boolean infoPanelIsShowing = infoPanel.isShowing(); 502 503 main.removeAll(); 504 505 if (showInfoPanel) { 506 // body-help 507 JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, body, infoPanel); 508 sp.setDividerLocation(bodySize.width + 2); 509 main.add(sp); 510 infoPanel.setCurrentID(interview.getCurrentQuestion()); 511 } 512 else { 513 // body 514 main.add(body); 515 } 516 517 if (window != null) { 518 int divWidth = new JSplitPane().getDividerSize(); 519 Dimension winSize = window.getSize(); 520 int newWidth = winSize.width; 521 if (showInfoPanel != infoPanelIsShowing) 522 newWidth += (showInfoPanel ? +1 : -1) * (infoSize.width + divWidth + 4); 523 window.setSize(newWidth, winSize.height); 524 } 525 526 if (infoBtn.isSelected() != showInfoPanel) 527 infoBtn.setSelected(showInfoPanel); 528 } 529 530 private void updateTitle(Window w) { 531 String t; 532 if (currFile == null 533 || (defaultFile != null && currFile.equals(defaultFile))) 534 t = title; 535 else 536 t = i18n.getString("wizard.titleAndFile", new Object[] {title, currFile.getPath()}); 537 if (w instanceof JFrame) 538 ((JFrame) w).setTitle(t); 539 else 540 ((JDialog) w).setTitle(t); 541 } 542 543 /** 544 * Invoke a performXXX method via reflection 545 * @param s The name of the method to be invoked. 546 */ 547 private void perform(String s) { 548 try { 549 Method m = Wizard.class.getDeclaredMethod(s, new Class[] { }); 550 m.invoke(Wizard.this, new Object[] { }); 551 } 552 catch (IllegalAccessException ex) { 553 System.err.println(s); 554 ex.printStackTrace(); 555 } 556 catch (InvocationTargetException ex) { 557 System.err.println(s); 558 ex.getTargetException().printStackTrace(); 559 } 560 catch (NoSuchMethodException ex) { 561 System.err.println(s); 562 } 563 } 564 565 /** 566 * Handle the "back" action 567 */ 568 private void performBack() { 569 try { 570 questionPanel.saveCurrentResponse(); 571 interview.prev(); 572 } 573 catch (Interview.Fault e) { 574 // exception normally means no more questions 575 // e.printStackTrace(); 576 } 577 catch (RuntimeException e) { 578 // typically NumberFormatError 579 // SEE ALSO QuestionPanel.showInetAddressQuestion 580 // which wants to throw Interview.Fault from 581 // the value saver, but can't 582 questionPanel.getToolkit().beep(); 583 } 584 } 585 586 /** 587 * Handle the "cancel" action 588 */ 589 private void performCancel() { 590 questionPanel.saveCurrentResponse(); 591 if (interview.isEdited() && !okToContinue()) 592 return; 593 window.dispose(); 594 } 595 596 /** 597 * Handle the "exit" action 598 */ 599 private void performExit() { 600 questionPanel.saveCurrentResponse(); 601 if (interview.isEdited() && !okToContinue()) 602 return; 603 // setVisible(false); 604 System.exit(0); // uugh 605 } 606 607 /** 608 * Handle the "exportLog" action 609 */ 610 private void performExportLog() { 611 questionPanel.saveCurrentResponse(); 612 JFileChooser chooser = new JFileChooser(); 613 if (currFile != null) { 614 // setCurrentDirectory required 615 chooser.setCurrentDirectory(new File(currFile.getParent())); 616 int dot = currFile.getName().lastIndexOf("."); 617 if (dot != -1) { 618 File f = new File(currFile.getName().substring(0, dot) + ".html"); 619 chooser.setSelectedFile(f); 620 } 621 } 622 else { 623 chooser.setCurrentDirectory(getUserDir()); 624 } 625 chooser.setFileFilter(htmlFilter); 626 //chooser.addChoosableFileFilter(txtFilter); 627 int action = chooser.showDialog(main, i18n.getString("wizard.exportLog")); 628 if (action != JFileChooser.APPROVE_OPTION) 629 return; 630 631 File f = ensureExtn(chooser.getSelectedFile(), ".html"); 632 if (f.exists() && !okToOverwrite(f)) 633 return; 634 try { 635 Writer out = new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8); 636 WizPrint w = new WizPrint(interview, interview.getPath()); 637 w.setShowResponses(true); 638 w.write(out); 639 } 640 catch (FileNotFoundException e) { 641 JOptionPane.showMessageDialog(main, 642 i18n.getString("wizard.fileNotFound.txt", e.getMessage()), 643 i18n.getString("wizard.fileNotFound.title"), 644 JOptionPane.ERROR_MESSAGE); 645 } 646 catch (IOException e) { 647 JOptionPane.showMessageDialog(main, 648 i18n.getString("wizard.badFile.txt", e.getMessage()), 649 i18n.getString("wizard.badFile.title"), 650 JOptionPane.ERROR_MESSAGE); 651 } 652 } 653 654 /** 655 * Handle the "find" action 656 */ 657 private void performFind() { 658 if (searchDialog == null) 659 searchDialog = SearchDialog.create(window, interview, helpHelpBroker, helpHelpPrefix); 660 searchDialog.setVisible(true); 661 } 662 663 /** 664 * Handle the "find" action 665 */ 666 private void performFindNext() { 667 if (searchDialog == null) 668 searchDialog = SearchDialog.create(window, interview, helpHelpBroker, helpHelpPrefix); 669 searchDialog.find(); 670 } 671 672 /** 673 * Handle the "help" action 674 */ 675 private void performHelp() { 676 helpHelpBroker.displayCurrentID(helpHelpPrefix + "intro.csh"); 677 } 678 679 /** 680 * Handle the "info" action 681 */ 682 private void performInfo() { 683 boolean infoOn = infoBtn.isSelected(); 684 if (infoPanel.isShowing() != infoOn) { 685 update(infoOn); 686 window.validate(); 687 } 688 } 689 690 /** 691 * Handle the "new" action 692 */ 693 private void performNew() { 694 questionPanel.saveCurrentResponse(); 695 if (interview.isEdited() && !okToContinue()) 696 return; 697 interview.clear(); 698 interview.setEdited(false); 699 setFile(defaultFile); 700 } 701 702 /** 703 * Handle the "next" action 704 */ 705 private void performNext() { 706 try { 707 questionPanel.saveCurrentResponse(); 708 interview.next(); 709 } 710 catch (Interview.Fault e) { 711 // exception normally means no more questions 712 // e.printStackTrace(); 713 questionPanel.getToolkit().beep(); 714 } 715 catch (RuntimeException e) { 716 // typically NumberFormatError 717 questionPanel.getToolkit().beep(); 718 } 719 } 720 721 /** 722 * Handle the "ok" action 723 */ 724 private void performOk() { 725 try { 726 questionPanel.saveCurrentResponse(); 727 window.dispose(); 728 okListener.actionPerformed(new ActionEvent(this, 729 ActionEvent.ACTION_PERFORMED, 730 OK)); 731 } 732 catch (RuntimeException e) { 733 // typically NumberFormatError 734 questionPanel.getToolkit().beep(); 735 } 736 } 737 738 /** 739 * Handle the "open" action 740 */ 741 private void performOpen() { 742 questionPanel.saveCurrentResponse(); 743 if (interview.isEdited() && !okToContinue()) 744 return; 745 746 JFileChooser chooser = new JFileChooser(); 747 // set current directory from file or user.dir 748 if (currFile != null) { 749 // setCurrentDirectory required 750 chooser.setCurrentDirectory(new File(currFile.getParent())); 751 chooser.setSelectedFile(new File(currFile.getName())); 752 } 753 else { 754 chooser.setCurrentDirectory(getUserDir()); 755 } 756 chooser.setFileFilter(jtiFilter); 757 int action = chooser.showOpenDialog(main); 758 if (action != JFileChooser.APPROVE_OPTION) 759 return; 760 File f = ensureExtn(chooser.getSelectedFile(), ".jti"); 761 try { 762 open(f); 763 setFile(f); 764 } 765 catch (Interview.Fault e) { 766 JOptionPane.showMessageDialog(main, 767 i18n.getString("wizard.badInterview.txt", e.getMessage()), 768 i18n.getString("wizard.badInterview.title"), 769 JOptionPane.ERROR_MESSAGE); 770 } 771 catch (FileNotFoundException e) { 772 JOptionPane.showMessageDialog(main, 773 i18n.getString("wizard.fileNotFound.txt", e.getMessage()), 774 i18n.getString("wizard.fileNotFound.title"), 775 JOptionPane.ERROR_MESSAGE); 776 } 777 catch (IOException e) { 778 JOptionPane.showMessageDialog(main, 779 i18n.getString("wizard.badFile.txt", e.getMessage()), 780 i18n.getString("wizard.badFile.title"), 781 JOptionPane.ERROR_MESSAGE); 782 } 783 } 784 785 /** 786 * Handle the "save" action 787 */ 788 private void performSave() { 789 questionPanel.saveCurrentResponse(); 790 // save with current file 791 if (currFile == null) 792 performSaveAs(); 793 else 794 performSaveInternal(currFile); 795 } 796 797 /** 798 * Handle the "save as" action 799 */ 800 private void performSaveAs() { 801 questionPanel.saveCurrentResponse(); 802 JFileChooser chooser = new JFileChooser(); 803 if (currFile != null) { 804 // setCurrentDirectory required 805 chooser.setCurrentDirectory(new File(currFile.getParent())); 806 chooser.setSelectedFile(new File(currFile.getName())); 807 } 808 else { 809 chooser.setCurrentDirectory(getUserDir()); 810 } 811 chooser.setFileFilter(jtiFilter); 812 int action = chooser.showSaveDialog(main); 813 if (action != JFileChooser.APPROVE_OPTION) 814 return; 815 File f = ensureExtn(chooser.getSelectedFile(), ".jti"); 816 if (f.exists() && !okToOverwrite(f)) 817 return; 818 performSaveInternal(f); 819 } 820 821 /** 822 * Internal common routine for the save/saveAs actions 823 */ 824 private void performSaveInternal(File f) { 825 try { 826 save(f); 827 setFile(f); 828 } 829 catch (FileNotFoundException e) { 830 JOptionPane.showMessageDialog(main, 831 i18n.getString("wizard.fileNotFound.txt", e.getMessage()), 832 i18n.getString("wizard.fileNotFound.title"), 833 JOptionPane.ERROR_MESSAGE); 834 } 835 catch (IOException e) { 836 JOptionPane.showMessageDialog(main, 837 i18n.getString("wizard.badFile.txt", e.getMessage()), 838 i18n.getString("wizard.badFile.title"), 839 JOptionPane.ERROR_MESSAGE); 840 } 841 } 842 843 /** 844 * Get the user's current directory 845 */ 846 private File getUserDir() { 847 return new File(System.getProperty("user.dir")); 848 } 849 850 private JButton createButton(String uiKey, String actionCommand, ActionListener l) { 851 JButton b = new JButton(createIcon(uiKey)); 852 b.setToolTipText(i18n.getString("wizard." + uiKey + ".tip")); 853 b.setActionCommand(actionCommand); 854 b.addActionListener(l); 855 b.registerKeyboardAction(l, actionCommand, enterKey, JComponent.WHEN_FOCUSED); 856 return b; 857 } 858 859 private Icon createIcon(String uiKey) { 860 String iconResource = i18n.getString("wizard." + uiKey + ".icon"); 861 URL url = getClass().getResource(iconResource); 862 return (url == null ? null : new ImageIcon(url)); 863 } 864 865 /** 866 * Create a menu according to an array of data 867 * @title the title for the menu 868 * @menuData the data for the menu; one element per menu item; an element can be 869 * one of 870 * <dl> 871 * <dt> null 872 * <dd> a separator 873 * <dt> an array of two strings 874 * <dd> a menu item, whose name is the first string, and whose action is the second 875 * </dl> 876 */ 877 private JMenu createMenu(String uiKey, String[][] menuData, ActionListener l) { 878 JMenu m = new JMenu(i18n.getString("wizard." + uiKey + ".menu")); 879 m.setName("wizard." + uiKey); 880 m.setMnemonic(i18n.getString("wizard." + uiKey + ".mne").charAt(0)); 881 for (int i = 0; i < menuData.length; i++) { 882 String[] data = menuData[i]; 883 if (data == null) 884 m.addSeparator(); 885 else { 886 JMenuItem mi = createMenuItem(uiKey, data[0], data[1], l); 887 if (data.length > 2) { 888 KeyStroke accel = KeyStroke.getKeyStroke(data[2]); 889 mi.setAccelerator(accel); 890 } 891 m.add(mi); 892 } 893 } 894 return m; 895 } 896 897 private JMenuItem createMenuItem(String uiKey, String name, String actionCommand, ActionListener l) { 898 JMenuItem item = new JMenuItem(i18n.getString("wizard." + uiKey + "." + name + ".mit")); 899 item.setName(name); 900 item.setMnemonic(i18n.getString("wizard." + uiKey + "." + name + ".mne").charAt(0)); 901 item.setActionCommand(actionCommand); 902 item.addActionListener(l); 903 return item; 904 } 905 906 private JToggleButton createToggle(String uiKey, String actionCommand, ActionListener l) { 907 JToggleButton b = new JToggleButton(createIcon(uiKey)) { 908 public Insets getInsets() { 909 return (nextBtn == null ? super.getInsets() : nextBtn.getInsets()); // !! 910 } 911 }; 912 b.setToolTipText(i18n.getString("wizard." + uiKey + ".tip")); 913 b.setActionCommand(actionCommand); 914 b.addActionListener(l); 915 b.registerKeyboardAction(l, actionCommand, enterKey, JComponent.WHEN_FOCUSED); 916 return b; 917 } 918 919 private File ensureExtn(File f, String extn) { 920 if (f.getName().endsWith(extn)) 921 return f; 922 else 923 return new File(f.getPath() + extn); 924 } 925 926 private boolean okToContinue() { 927 int response = 928 JOptionPane.showConfirmDialog(main, 929 i18n.getString("wizard.unsavedAnswers.txt"), 930 i18n.getString("wizard.unsavedAnswers.title"), 931 JOptionPane.YES_NO_OPTION); 932 return (response == JOptionPane.YES_OPTION); 933 } 934 935 private boolean okToOverwrite(File f) { 936 int response = 937 JOptionPane.showConfirmDialog(main, 938 i18n.getString("wizard.overwrite.txt", f), 939 i18n.getString("wizard.overwrite.title"), 940 JOptionPane.YES_NO_OPTION); 941 return (response == JOptionPane.YES_OPTION); 942 } 943 944 private ActionListener performer = new ActionListener() { 945 public void actionPerformed(ActionEvent e) { 946 perform(e.getActionCommand()); 947 } 948 }; 949 950 private Interview interview; 951 private Exporter[] exporters; 952 private String title; 953 private JMenuBar menuBar; 954 private JMenu fileMenu; 955 //private JPanel main; 956 private JComponent main; 957 private JPanel body; 958 private PathPanel pathPanel; 959 private QuestionPanel questionPanel; 960 private InfoPanel infoPanel; 961 private JToolBar buttonPanel; 962 private JButton cancelBtn; 963 private JButton backBtn; 964 private JButton nextBtn; 965 private JButton okBtn; 966 private JToggleButton infoBtn; 967 private Window window; 968 private ActionListener okListener; 969 private SearchDialog searchDialog; 970 private boolean initialInfoVisible = true; 971 private Listener listener = new Listener(); 972 973 // help for Help menu and context sensitive help (F1) 974 private HelpSet helpHelpSet; 975 private HelpBroker helpHelpBroker; 976 private String helpHelpPrefix; 977 private JMenu helpMenu; 978 979 private File currFile; 980 private File defaultFile; 981 private boolean exitOnClose; 982 983 private final FileFilter jtiFilter = new ExtensionFileFilter(".jti"); 984 private final FileFilter htmlFilter = 985 new ExtensionFileFilter(new String[] {".htm", ".html"}); 986 987 private static final KeyStroke enterKey = KeyStroke.getKeyStroke("ENTER"); 988 989 private static final I18NResourceBundle i18n = I18NResourceBundle.getDefaultBundle(); 990 991 private static final String[][] fileMenuData = { 992 {"new", "performNew"}, 993 {"open", "performOpen"}, 994 {"save", "performSave"}, 995 {"saveAs", "performSaveAs"}, 996 null, 997 {"exportLog", "performExportLog"} 998 }; 999 1000 private static final String[][] helpMenuData = { 1001 {"help", "performHelp", "F1"} 1002 }; 1003 1004 private static final String[][] searchMenuData = { 1005 {"find", "performFind", "control F"}, 1006 {"findNext", "performFindNext", "F3"}, 1007 }; 1008 1009 private class ExtensionFileFilter extends FileFilter { 1010 ExtensionFileFilter(String extn) { 1011 this.extns = new String[] {extn}; 1012 } 1013 1014 ExtensionFileFilter(String[] extns) { 1015 this.extns = extns; 1016 } 1017 1018 1019 ExtensionFileFilter(String[] extns, String description) { 1020 this.extns = extns; 1021 this.description = description; 1022 } 1023 1024 public boolean accept(File f) { 1025 if (f.isDirectory()) 1026 return true; 1027 for (int i = 0; i < extns.length; i++) 1028 if (f.getName().endsWith(extns[i])) 1029 return true; 1030 return false; 1031 } 1032 1033 public String getDescription() { 1034 if (description == null) { 1035 StringBuffer sb = new StringBuffer("wizard.extn"); 1036 if (extns.length == 0) 1037 sb.append(".allFiles"); 1038 else { 1039 for (int i = 0; i < extns.length; i++) 1040 sb.append(extns[i]); 1041 } 1042 description = i18n.getString(sb.toString()); 1043 } 1044 return description; 1045 } 1046 1047 private String[] extns; 1048 private String description; 1049 } 1050 1051 private class ExportMenu extends JMenu implements ActionListener, PopupMenuListener { 1052 ExportMenu(Exporter[] exporters) { 1053 super(i18n.getString("wizard.export.menu")); 1054 setName("export"); 1055 setMnemonic(i18n.getString("wizard.export.mne").charAt(0)); 1056 for (int i = 0; i < exporters.length; i++) { 1057 JMenuItem mi = new JMenuItem(exporters[i].getName()); 1058 mi.putClientProperty("exporter", exporters[i]); 1059 mi.setActionCommand("performGenericExport"); 1060 mi.addActionListener(this); 1061 add(mi); 1062 } 1063 getPopupMenu().addPopupMenuListener(this); 1064 } 1065 1066 public void actionPerformed(ActionEvent ev) { 1067 questionPanel.saveCurrentResponse(); 1068 JMenuItem mi = (JMenuItem)(ev.getSource()); 1069 Exporter e = (Exporter)(mi.getClientProperty("exporter")); 1070 export(e); 1071 } 1072 1073 public void popupMenuCanceled(PopupMenuEvent e) { 1074 } 1075 1076 public void popupMenuWillBecomeInvisible(PopupMenuEvent ev) { 1077 } 1078 1079 public void popupMenuWillBecomeVisible(PopupMenuEvent ev) { 1080 JPopupMenu m = (JPopupMenu)(ev.getSource()); 1081 for (int i = 0; i < m.getComponentCount(); i++) { 1082 JMenuItem mi = (JMenuItem)(m.getComponent(i)); 1083 if (mi != null) { 1084 Exporter e = (Exporter)(mi.getClientProperty("exporter")); 1085 if (e != null) 1086 mi.setEnabled(e.isExportable()); 1087 } 1088 } 1089 } 1090 1091 private void export(Exporter e) { 1092 JFileChooser exportChooser = new JFileChooser(); 1093 if (currFile != null) { 1094 // setCurrentDirectory required 1095 exportChooser.setCurrentDirectory(new File(currFile.getParent())); 1096 String[] extns = e.getFileExtensions(); 1097 int dot = currFile.getName().lastIndexOf("."); 1098 if (dot != -1 && extns != null && extns.length > 0) { 1099 File f = new File(currFile.getName().substring(0, dot) + extns[0]); 1100 exportChooser.setSelectedFile(f); 1101 } 1102 } 1103 else { 1104 exportChooser.setCurrentDirectory(getUserDir()); 1105 } 1106 exportChooser.setApproveButtonText(i18n.getString("wizard.exportChooser.export")); 1107 String[] extns = e.getFileExtensions(); 1108 String desc = e.getFileDescription(); 1109 exportChooser.setFileFilter(new ExtensionFileFilter(extns, desc)); 1110 int action = exportChooser.showSaveDialog(main); 1111 if (action != JFileChooser.APPROVE_OPTION) 1112 return; 1113 try { 1114 File f = ensureExtn(exportChooser.getSelectedFile(), extns[0]); 1115 if (f.exists() && !okToOverwrite(f)) 1116 return; 1117 e.export(f); 1118 } 1119 catch (IOException ex) { 1120 JOptionPane.showMessageDialog(main, 1121 i18n.getString("wizard.exportError.txt", ex.getMessage()), 1122 i18n.getString("wizard.exportError.title"), 1123 JOptionPane.ERROR_MESSAGE); 1124 } 1125 catch (Interview.Fault ex) { 1126 JOptionPane.showMessageDialog(main, 1127 i18n.getString("wizard.exportError.txt", ex.getMessage()), 1128 i18n.getString("wizard.exportError.title"), 1129 JOptionPane.ERROR_MESSAGE); 1130 } 1131 } 1132 } 1133 1134 private class Listener implements AncestorListener, Interview.Observer 1135 { 1136 // ---------- from AncestorListener ----------- 1137 1138 public void ancestorAdded(AncestorEvent e) { 1139 interview.addObserver(this); 1140 pathUpdated(); 1141 currentQuestionChanged(interview.getCurrentQuestion()); 1142 } 1143 1144 public void ancestorMoved(AncestorEvent e) { } 1145 1146 public void ancestorRemoved(AncestorEvent e) { 1147 interview.removeObserver(this); 1148 } 1149 1150 //----- from Interview.Observer ----------- 1151 1152 public void pathUpdated() { 1153 okBtn.setEnabled(interview.isFinishable()); 1154 } 1155 1156 public void currentQuestionChanged(Question q) { 1157 backBtn.setEnabled(!interview.isFirst(q)); 1158 nextBtn.setEnabled(!interview.isLast(q)); 1159 } 1160 } 1161 }