1 /* 2 * $Id$ 3 * 4 * Copyright (c) 2010, 2012, 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.javatest.exec; 28 29 import com.sun.interview.Help; 30 import java.awt.CardLayout; 31 import java.awt.Component; 32 import java.awt.Container; 33 import java.awt.Dimension; 34 import java.awt.AWTEvent; 35 import java.awt.FocusTraversalPolicy; 36 import java.awt.KeyboardFocusManager; 37 import java.awt.event.ActionEvent; 38 import java.awt.event.ActionListener; 39 import java.io.File; 40 import java.io.FileNotFoundException; 41 import java.io.IOException; 42 import java.util.HashMap; 43 import java.util.Iterator; 44 import java.util.Map; 45 import java.util.Set; 46 import java.util.TreeSet; 47 import javax.swing.BorderFactory; 48 import javax.swing.ButtonGroup; 49 import javax.swing.JCheckBoxMenuItem; 50 import javax.swing.JComponent; 51 import javax.swing.JFileChooser; 52 import javax.swing.JMenu; 53 import javax.swing.JMenuBar; 54 import javax.swing.JMenuItem; 55 import javax.swing.JOptionPane; 56 import javax.swing.JPanel; 57 import javax.swing.JRadioButtonMenuItem; 58 import javax.swing.JSplitPane; 59 import javax.swing.KeyStroke; 60 import javax.swing.WindowConstants; 61 import javax.swing.event.ChangeEvent; 62 import javax.swing.event.ChangeListener; 63 import javax.swing.event.MenuEvent; 64 import javax.swing.event.MenuListener; 65 66 import com.sun.interview.Interview; 67 import com.sun.interview.Question; 68 import com.sun.interview.wizard.QuestionRenderer; 69 import com.sun.javatest.InterviewParameters; 70 import com.sun.javatest.TestSuite; 71 import com.sun.javatest.WorkDirectory; 72 import com.sun.javatest.exec.WorkDirChooseTool.ExecModelStub; 73 import com.sun.javatest.tool.FileChooser; 74 import com.sun.javatest.tool.FileHistory; 75 import com.sun.javatest.tool.HelpLink; 76 import com.sun.javatest.tool.Preferences; 77 import com.sun.javatest.tool.ToolDialog; 78 import com.sun.javatest.tool.UIFactory; 79 import com.sun.javatest.tool.jthelp.HelpID; 80 import com.sun.javatest.tool.jthelp.JHelpContentViewer; 81 import com.sun.javatest.util.Debug; 82 import java.util.ArrayList; 83 import java.util.List; 84 85 /** 86 * Dialog to edit InterviewParameters object. 87 * 88 * InterviewEditor keeps reference to the main InterviewParameters object, 89 * but never change it. 90 * <br> 91 * Before editing interview the main InterviewParameters object is synced 92 * with the view object. 93 * <br> 94 * When view object is loaded or saved, all registered observers are notified. 95 * 96 */ 97 public class InterviewEditor extends ToolDialog { 98 99 public InterviewEditor(JComponent parent, UIFactory uif, 100 InterviewParameters ip) { 101 102 super(parent, uif, "ce", ToolDialog.MODAL_DOCUMENT); 103 WorkDirectory wd = ip.getWorkDirectory(); 104 if (wd == null) { 105 throw new IllegalArgumentException(uif.getI18NString("ce.wdNull.err")); 106 } 107 mainConfig = ip; 108 try { 109 //this.key = key; 110 this.ext = getExtention(); 111 viewConfig = ip.getTestSuite().createInterview(); 112 viewConfig.setWorkDirectory(ip.getWorkDirectory()); 113 history = FileHistory.getFileHistory(wd, getHistoryFileName()); 114 } catch (TestSuite.Fault e) { 115 // ignore, for now; should not happen 116 } 117 } 118 public InterviewEditor(JComponent parent, UIFactory uif, 119 InterviewParameters ip, ContextManager cm) { 120 this(parent, uif, ip); 121 setContextManager(cm); 122 } 123 124 /** 125 * Sets contextManager to the passed value. 126 * @param cm - ContextManager to use 127 */ 128 void setContextManager(ContextManager cm) { 129 this.contextManager = cm; 130 } 131 132 /** 133 * Returns previously created or set ContextManager. If not set, creates 134 * the new instance. 135 */ 136 ContextManager getContextManager() { 137 if (contextManager == null) { 138 try { 139 contextManager = (ContextManager) ((Class.forName( 140 "com.sun.javatest.exec.ContextManager")).newInstance()); 141 } catch (Exception e) { 142 e.printStackTrace(); 143 } 144 } 145 return contextManager; 146 } 147 148 149 /** 150 * Returns extension for files to be saved. Subclasses like TemplateEditor 151 * might override this method. 152 * 153 * @return default extension 154 */ 155 protected String getExtention() { 156 return CONFIG_EXTENSION; 157 } 158 159 /** 160 * Returns file name to store history of configuration files. 161 * This implementation returns "configHistory.jtl". Subclasses might 162 * override this method to return alternative value. 163 */ 164 protected String getHistoryFileName() { 165 return CONFIG_HISTORY; 166 } 167 168 private void setRestorer(CE_View view) { 169 getRestorer().setRestorePolicy(Restorer.RESTORE_ALL); 170 getRestorer().setWindowKey(getRestorerWindowKey(view == fullView)); 171 } 172 173 protected String getRestorerWindowKey(boolean isFullView) { 174 return "confEdit.config" + (isFullView ? ".f" : ".s"); 175 } 176 177 /** 178 * Adds passed file to the history. 179 * @param f - file to be added. 180 */ 181 void addToHistory(File f) { 182 if (history == null) { 183 WorkDirectory wd = viewConfig.getWorkDirectory(); 184 if (wd == null) { 185 return; 186 } 187 history = FileHistory.getFileHistory(wd, getHistoryFileName()); 188 } 189 history.add(f); 190 } 191 192 /** 193 * Syncs mainConfig and viewConfig 194 */ 195 private void sync() { 196 try { 197 copy(mainConfig, viewConfig); 198 } catch (Interview.Fault e) { 199 uif.showError("ce.show.error", e.getMessage()); 200 } 201 } 202 203 /** 204 * Starts editing new config. Supposed to be called outside. 205 */ 206 public void newConfig() { 207 try { 208 InterviewParameters newConfig = viewConfig.getTestSuite().createInterview(); 209 copy(newConfig, viewConfig); 210 } catch (TestSuite.Fault e) { 211 uif.showError("ce.show.error", e.getMessage()); 212 } catch (Interview.Fault e) { 213 uif.showError("ce.show.error", e.getMessage()); 214 } 215 show(FULL_MODE); 216 } 217 218 /** 219 * Start editing empty configuration. 220 */ 221 private void newConfigAsk() { 222 if (askAndSave("ce.clear.warn")) { 223 newConfig(); 224 } 225 } 226 /** 227 * Show dialog. 228 */ 229 public void edit(int mode) { 230 sync(); 231 show(mode); 232 } 233 234 /** 235 * @return mode that will be used by WorkDirChooseTool to select file. 236 */ 237 public int getFileChooserMode() { 238 return WorkDirChooseTool.LOAD_CONFIG; 239 } 240 241 /** 242 * Show choose file dialog and then load new file. 243 * Supposed to be invoked from outside of editor. 244 * Doesn't expect that viewConfig can be changed. 245 */ 246 public void loadConfig() { 247 loadConfig0(false); 248 } 249 250 /** 251 * Show choose file dialog and then load new file. 252 * The dialog depends on fileChooserMode setting. It can be either 253 * simple JFileChooser or "advanced" home made file chooser. 254 * @param ask if true, dialog asking whether to save changes will appear 255 * in case of unsaved changes. 256 */ 257 protected void loadConfig0(boolean ask) { 258 TestSuite ts = viewConfig.getTestSuite(); 259 ExecModelStub em = new ExecModelStub(ts, contextManager); 260 try { 261 em.setWorkDir(mainConfig.getWorkDirectory(), true); 262 } catch (Exception ignore) { 263 // stub never throws exceptions 264 } 265 WorkDirChooseTool fc = WorkDirChooseTool.getTool((JComponent)parent, 266 uif, em, getFileChooserMode(), ts, true); 267 WorkDirChooseTool.ChosenFileHandler cfh = 268 new WorkDirChooseTool.ChosenFileHandler(); 269 fc.setChosenFileHandler(cfh); 270 fc.doTool(); 271 272 File f = cfh.file; 273 if (f != null && (!ask || askAndSave("ce.load.warn"))) { 274 loadConfigFromFile(f); 275 if (cfh.isEditConfig && !isVisible()) { 276 edit(FULL_MODE); 277 } 278 } 279 } 280 281 /** 282 * Works similar to loadConfig(), but asks to save changes if any 283 * before reload. 284 */ 285 private void loadConfigAsk() { 286 loadConfig0(true); 287 } 288 289 /** 290 * 291 * @param f 292 */ 293 public void loadAndEdit(File f) { 294 if (f == null) { 295 return; 296 } 297 loadConfigFromFile(f); 298 notifyObservers(); 299 show(FULL_MODE); 300 } 301 302 /** 303 * Shows file chooser dialog. 304 * @return chosen file or null. 305 */ 306 private File chooseConfigFile() { 307 File mainConfigFile = mainConfig.getFile(); 308 FileChooser fileChooser = getFileChooser(); 309 if (mainConfigFile != null) 310 fileChooser.setCurrentDirectory(mainConfigFile.getParentFile()); 311 return loadConfigFile(getContextManager(), parent, uif, fileChooser); 312 } 313 314 /** 315 * Updates viewConfig, notifies observers of the change. 316 * @param file File to load. 317 */ 318 public void loadConfigFromFile(File file) { 319 if (file == null) { 320 return; 321 } 322 try { 323 viewConfig.load(file); 324 if (currView != null && currView.isShowing()) 325 currView.load(); 326 addToHistory(file); 327 updateTitle(); 328 notifyObservers(); 329 } catch (FileNotFoundException e) { 330 uif.showError("ce.load.cantFindFile", file); 331 } catch (IOException e) { 332 uif.showError("ce.load.error", new Object[] { file, e } ); 333 } catch (Interview.Fault e) { 334 uif.showError("ce.load.error", new Object[] { file, e.getMessage() } ); 335 } 336 } 337 338 339 public void save() { 340 save0(); 341 } 342 343 // return true if saved, false if cancelled/error 344 private boolean save0() { 345 // Use the filename saved in the viewConfig. 346 // By default, this will have been copied from the mainConfig, 347 // but it may have been cleared if clear() has been called, thereby 348 // making "save" behave as "saveAs". 349 return save0(viewConfig.getFile()); 350 } 351 352 public void saveAs() { 353 save0(null); 354 } 355 356 // return true if saved, false if cancelled/error 357 private boolean save0(File file) { 358 if (file == null) { 359 File mainConfigFile = mainConfig.getFile(); 360 File mainConfigDir = (mainConfigFile == null ? null : mainConfigFile.getParentFile()); 361 file = getSaveFile(mainConfigDir); 362 if (file == null) 363 return false; // exit without saving 364 } 365 366 try { 367 if (currView != null) { 368 currView.save(); 369 } 370 viewConfig.setFile(file); // for subsequent use 371 doSave(file); 372 addToHistory(file); 373 374 updateTitle(); 375 notifyObservers(); 376 377 return true; 378 } 379 catch (IOException e) { 380 if (!file.canWrite()) 381 uif.showError("ce.save.cantWriteFile", file); 382 else if (e instanceof FileNotFoundException) 383 uif.showError("ce.save.cantFindFile", file); 384 else 385 uif.showError("ce.save.error", new Object[] { file, e } ); 386 } 387 catch (Interview.Fault e) { 388 uif.showError("ce.save.error", new Object[] { file, e.getMessage() } ); 389 } 390 391 return false; 392 } 393 394 /** 395 * Does actual save work. should be overriden, when needed. 396 */ 397 protected void doSave(File file) throws Interview.Fault, IOException { 398 viewConfig.save(file); 399 } 400 401 private File getSaveFile(File dir) { 402 FileChooser fileChooser = getFileChooser(); 403 fileChooser.setDialogTitle(uif.getI18NString("ce.save.title")); 404 return saveConfigFile(getContextManager(), parent, uif, fileChooser, dir, 405 this.templateMode); 406 } 407 408 private FileChooser getFileChooser() { 409 FileChooser fileChooser = new FileChooser(true); 410 fileChooser.addChoosableExtension(ext, uif.getI18NString("ce.jtiFiles")); 411 return fileChooser; 412 } 413 414 /** 415 * In viewConfig differs from mainConfig asks user whether save changes 416 * or not. Save changes in case of positive answer. 417 * @param que 418 * @return false iff user said "cancel". 419 */ 420 private boolean askAndSave(String question) { 421 if (isEdited()) { 422 int rc = uif.showYesNoCancelDialog(question); 423 424 switch (rc) { 425 case JOptionPane.YES_OPTION: { 426 save(); 427 return true;} 428 case JOptionPane.NO_OPTION: return true; 429 default: return false; 430 } 431 } else { 432 return true; 433 } 434 } 435 436 public void revert() { 437 if (!isEdited()) 438 return; 439 440 int rc = uif.showOKCancelDialog("ce.revert.warn"); 441 if (rc != JOptionPane.OK_OPTION) 442 return; 443 444 try { 445 copy(mainConfig, viewConfig); 446 if (currView != null && currView.isShowing()) 447 currView.load(); 448 updateTitle(); 449 } catch (Interview.Fault e) { 450 uif.showError("ce.revert", e.getMessage()); 451 } 452 } 453 454 public void setRunPending(boolean b) { 455 runPending = b; 456 } 457 458 public boolean isRunPending() { 459 return runPending; 460 } 461 462 public void show() { 463 if (stdView == null) 464 initGUI(); 465 466 show(DEFAULT_MODE); 467 } 468 469 public void updateMenu() { 470 FileHistory h = FileHistory.getFileHistory(viewConfig.getWorkDirectory(), getHistoryFileName()); 471 recentConfigMenu.setEnabled(h.getLatestEntry() != null); 472 } 473 474 public void show(int mode) { 475 if (stdView == null) { 476 initGUI(); 477 } 478 updateTitle(); 479 updateMenu(); 480 switch (mode) { 481 case DEFAULT_MODE: 482 show(getDefaultView()); 483 break; 484 485 case FULL_MODE: 486 show(fullView); 487 break; 488 489 case STD_MODE: 490 show(stdView); 491 break; 492 493 case STD_TESTS_MODE: 494 stdView.showTab(CE_StdView.TESTS_PANE); 495 show(stdView); 496 break; 497 498 case STD_EXCLUDE_LIST_MODE: 499 stdView.showTab(CE_StdView.EXCLUDE_LIST_PANE); 500 show(stdView); 501 break; 502 503 case STD_KEYWORDS_MODE: 504 stdView.showTab(CE_StdView.KEYWORDS_PANE); 505 show(stdView); 506 break; 507 508 case STD_KFL_MODE: 509 stdView.showTab(CE_StdView.KFL_PANE); 510 show(stdView); 511 break; 512 513 case STD_PRIOR_STATUS_MODE: 514 stdView.showTab(CE_StdView.PRIOR_STATUS_PANE); 515 show(stdView); 516 break; 517 518 case STD_ENVIRONMENT_MODE: 519 stdView.showTab(CE_StdView.ENVIRONMENT_PANE); 520 show(stdView); 521 break; 522 523 case STD_CONCURRENCY_MODE: 524 stdView.showTab(CE_StdView.CONCURRENCY_PANE); 525 show(stdView); 526 break; 527 528 case STD_TIMEOUT_FACTOR_MODE: 529 stdView.showTab(CE_StdView.TIMEOUT_FACTOR_PANE); 530 show(stdView); 531 break; 532 533 default: 534 throw new IllegalArgumentException(); 535 } 536 } 537 538 public void show(ActionListener closeListener) { 539 this.closeListener = closeListener; 540 show(); 541 } 542 543 public void show(int mode, ActionListener closeListener, boolean isTemplateMode) { 544 this.closeListener = closeListener; 545 show(mode); 546 } 547 548 public static final int DEFAULT_MODE = 0; 549 public static final int FULL_MODE = 1; 550 public static final int STD_MODE = 2; 551 public static final int STD_TESTS_MODE = 3; 552 public static final int STD_EXCLUDE_LIST_MODE = 4; 553 public static final int STD_KEYWORDS_MODE = 5; 554 public static final int STD_PRIOR_STATUS_MODE = 6; 555 public static final int STD_ENVIRONMENT_MODE = 7; 556 public static final int STD_CONCURRENCY_MODE = 8; 557 public static final int STD_TIMEOUT_FACTOR_MODE = 9; 558 public static final int TEMPLATE_FULL_MODE = 10; 559 public static final int STD_KFL_MODE = 11; 560 561 private void show(CE_View newView) { 562 563 // update viewConfig from currView (if showing) else mainConfig 564 if (currView != null && currView.isShowing()) { 565 currView.save(); 566 } 567 setRestorer(newView); 568 setView(newView); 569 setVisible(true); 570 } 571 572 @Override 573 public void setVisible(boolean isVisible) { 574 super.setVisible(isVisible); 575 notifyObserversOfVisibility(isVisible); 576 } 577 578 private void setView(CE_View newView) { 579 if (newView == null) 580 throw new NullPointerException(); 581 582 if (currView != null && currView == newView) { 583 currView.load(); 584 } 585 586 if (currView != newView) { 587 // note whether the focus is in the current view 588 KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); 589 Component fo = kfm.getPermanentFocusOwner(); 590 boolean focusInView = (fo != null && currView != null && currView.isAncestorOf(fo)); 591 592 currView = newView; 593 594 // update currView from viewConfig 595 currView.load(); 596 597 // set up the appropriate view and controls 598 ((CardLayout)(views.getLayout())).show(views, currView.getName()); 599 600 // The following is a workaround for what may be a JDK bug. 601 // As a result of changing the view, the permanent focus owner may no longer 602 // be showing, and therefore not accepting any keyboard input. 603 if (focusInView) { 604 Container fcr = (currView.isFocusCycleRoot() ? currView : currView.getFocusCycleRootAncestor()); 605 FocusTraversalPolicy ftp = fcr.getFocusTraversalPolicy(); 606 Component c = ftp.getDefaultComponent(fcr); 607 if (c != null) 608 c.requestFocusInWindow(); 609 } 610 611 boolean currIsFull = (currView == fullView); 612 markerMenu.setEnabled(currIsFull); 613 searchMenu.setEnabled(currIsFull); 614 (currIsFull ? viewFullBtn : viewStdBtn).setSelected(true); 615 viewTagCheckBox.setEnabled(currIsFull); 616 617 if (detailsBrowser != null) 618 detailsBrowser.setQuestionInfoEnabled(currIsFull); 619 620 updateTitle(); 621 } 622 } 623 624 public void close() { 625 if (canInterruptTemplateCreation()) { 626 doClose(); 627 } else { 628 uif.showError("ce.force_close"); 629 } 630 } 631 632 public void doClose() { 633 if (currView != null && !currView.isOKToClose()) { 634 if (afterCloseCommand != null) { 635 afterCloseCommand.run(); 636 afterCloseCommand = null; 637 } 638 return; 639 } 640 641 close(true); 642 } 643 644 protected void windowClosingAction(AWTEvent e) { 645 if (!canInterruptTemplateCreation()) { 646 uif.showError("ce.force_close"); 647 return; 648 } 649 650 if(fullView.isVisible()) { 651 fullView.prepareClosing(); 652 } 653 doClose(); 654 } 655 656 657 private void close(boolean checkIfEdited) { 658 if (currView == null) 659 return; 660 661 if (!isShowing()) 662 return; 663 664 if (checkIfEdited && isEdited()) { 665 int rc = uif.showYesNoCancelDialog("ce.close.warn"); 666 switch (rc) { 667 case JOptionPane.YES_OPTION: 668 if (save0()) { 669 break; 670 } else { 671 if (afterCloseCommand != null) { 672 afterCloseCommand.run(); 673 afterCloseCommand = null; 674 } 675 return; 676 } 677 678 case JOptionPane.NO_OPTION: 679 break; 680 default: 681 if (afterCloseCommand != null) { 682 afterCloseCommand.run(); 683 afterCloseCommand = null; 684 } 685 return; 686 } 687 } 688 689 setVisible(false); 690 691 // closeListener may have been set by show(ActionListener) 692 if (closeListener != null) { 693 ActionEvent e = new ActionEvent(this, 694 ActionEvent.ACTION_PERFORMED, 695 CLOSE); 696 closeListener.actionPerformed(e); 697 closeListener = null; 698 } 699 700 if (afterCloseCommand != null) { 701 afterCloseCommand.run(); 702 afterCloseCommand = null; 703 } 704 705 } 706 707 public void setCheckExcludeListListener(ActionListener l) { 708 if (stdView == null) 709 initGUI(); 710 711 stdView.setCheckExcludeListListener(l); 712 } 713 714 boolean isCurrentQuestionChanged() { 715 if (currView != null && currView.isShowing()) 716 currView.save(); 717 718 Question mq = mainConfig.getCurrentQuestion(); 719 Question vq = viewConfig.getCurrentQuestion(); 720 return !equal(mq.getTag(), vq.getTag()); 721 } 722 723 724 boolean isEdited() { 725 if (currView != null && currView.isShowing()) 726 currView.save(); 727 728 return !equal(mainConfig, viewConfig); 729 } 730 731 /** 732 * Compares two InterviewParameters objects for equivalence. 733 * Two interview are equivalent when they both provide the same set 734 * of questions and all corresponding questions have the same values. 735 * 736 * @param a first interview 737 * @param b second interview 738 * @return true, iff two interviews are equivalent. 739 */ 740 public static boolean equal(InterviewParameters a, InterviewParameters b) { 741 if (a == b) { 742 return true; 743 } 744 745 if (a.isTemplate() != b.isTemplate()) { 746 return false; 747 } 748 // do ez checks first 749 if (a.getMarkersEnabled() != b.getMarkersEnabled() 750 || a.getMarkersFilterEnabled() != b.getMarkersFilterEnabled()) { 751 return false; 752 } 753 754 Map<String, Question> aQuestions = a.getAllQuestions(); 755 Map<String, Question> bQuestions = b.getAllQuestions(); 756 757 Set<String> keys = new TreeSet<>(); 758 keys.addAll(aQuestions.keySet()); 759 keys.addAll(bQuestions.keySet()); 760 761 for (Iterator iter = keys.iterator(); iter.hasNext(); ) { 762 String key = (String) (iter.next()); 763 Question aq = aQuestions.get(key); 764 Question bq = bQuestions.get(key); 765 if (aq == null || bq == null) { 766 return false; 767 } 768 if (!aq.equals(bq)) { 769 String empty = ""; 770 boolean eq = (aq.getStringValue() == null && 771 empty.equals(bq.getStringValue())) || 772 (empty.equals(aq.getStringValue()) && 773 bq.getStringValue() == null); 774 if(!eq) { 775 /* 776 Hopefully, question reloading is not required anymore, 777 not because questions are equal now, but because 778 the unexpected dialog no longer appears... 779 780 // if aq is not set, it will be set to the default value 781 // (if the default value had been specified for aq before) 782 aq.reload(); 783 if (!aq.equals(bq)) { 784 return false; 785 } 786 */ 787 return false; 788 } 789 } 790 } 791 // Checking external values 792 Set aKeys = a.getPropertyKeys(); 793 Set bKeys = b.getPropertyKeys(); 794 795 if (aKeys == null || bKeys == null) { 796 return aKeys == bKeys; 797 } 798 799 if (aKeys.size() != bKeys.size()) { 800 return false; 801 } 802 803 for (Iterator iter = aKeys.iterator(); iter.hasNext(); ) { 804 String key = (String)iter.next(); 805 if (!bKeys.contains(key)) { 806 return false; 807 } 808 809 String aProp = a.retrieveProperty(key); 810 String bProp = b.retrieveProperty(key); 811 if (!equal(aProp, bProp)) { 812 return false; 813 } 814 } 815 816 817 return true; 818 } 819 820 /** 821 * Registers new observer 822 * @param o - observer to be added to the list 823 */ 824 public void addObserver(Observer o) { 825 if (o != null && !observers.contains(o)) { 826 observers.add(o); 827 } 828 } 829 /** 830 * Removes observer from the list 831 * @param o - observer to be removed from the list 832 */ 833 public void removeObserver(Observer o) { 834 if (o != null) { 835 observers.remove(o); 836 } 837 } 838 /** 839 * Notifies registered observers of the change happened to viewConfig 840 */ 841 protected void notifyObservers() { 842 for (Observer obs: observers) { 843 obs.changed(viewConfig); 844 } 845 } 846 /** 847 * Notifies registered observers of setVisible() method has been called. 848 */ 849 protected void notifyObserversOfVisibility(boolean isVisible) { 850 for (Observer obs: observers) { 851 obs.changedVisibility(isVisible, this); 852 } 853 } 854 855 private static boolean equal(String a, String b) { 856 return (a == null || b == null ? a == b : a.equals(b)); 857 } 858 859 @Override 860 public void dispose() { 861 if (viewConfig != null) { 862 viewConfig.dispose(); 863 viewConfig = null; 864 } 865 super.dispose(); 866 } 867 868 protected void initGUI() { 869 setHelp("confEdit.window.csh"); 870 listener = new Listener(); 871 872 updateTitle(); 873 874 if (viewConfig.getHelpSet() != null) { 875 // would prefer that the helpset came from the test suite 876 infoPanel = new JHelpContentViewer(Help.getHelpSet(viewConfig)); 877 infoPanel.setName("info"); 878 int dpi = uif.getDotsPerInch(); 879 infoPanel.setPreferredSize(new Dimension(4*dpi, 3*dpi)); 880 infoPanel.putClientProperty(HelpLink.HELPBROKER_FOR_HELPLINK, uif.getHelpBroker()); 881 } 882 883 fullView = new CE_FullView(viewConfig, infoPanel, uif, listener); 884 if (customRenderersMap != null) { 885 fullView.setCustomRenderers(customRenderersMap); 886 } 887 888 stdView = new CE_StdView(viewConfig, infoPanel, uif, listener); 889 stdView.setParentToolDialog(this); 890 891 initMenuBar(); 892 893 views = uif.createPanel("ce.views", new CardLayout(), false); 894 views.add(fullView, fullView.getName()); 895 views.add(stdView, stdView.getName()); 896 897 if (infoPanel == null) { 898 viewInfoCheckBox.setEnabled(false); 899 viewInfoCheckBox.setSelected(false); 900 } 901 else { 902 Preferences p = Preferences.access(); 903 boolean prefMoreInfo = p.getPreference(MORE_INFO_PREF, "true").equals("true"); 904 viewInfoCheckBox.setEnabled(true); 905 viewInfoCheckBox.setSelected(prefMoreInfo); 906 } 907 908 // set body to be views+info or views 909 if (viewInfoCheckBox.isSelected()) { 910 views.setBorder(null); 911 JSplitPane sp = uif.createSplitPane(JSplitPane.HORIZONTAL_SPLIT, views, infoPanel); 912 sp.setDividerLocation(views.getPreferredSize().width + sp.getDividerSize()); 913 body = sp; 914 } 915 else { 916 views.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); 917 body = views; 918 } 919 920 // Don't register "shift alt D" on body, because body might change 921 // if the more info is opened/closed. 922 // Instead, register it on views and infoPanel 923 views.registerKeyboardAction(listener, DETAILS, detailsKey, 924 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 925 if (infoPanel != null) 926 infoPanel.registerKeyboardAction(listener, DETAILS, detailsKey, 927 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 928 929 setBody(body); 930 931 setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 932 } 933 934 protected JMenu createFileMenu() { 935 String[] fileMenuItems = new String[] { SAVE, SAVE_AS, REVERT, null, 936 NEW, LOAD, null, CLOSE }; 937 JMenu fileMenu = uif.createMenu("ce.file", fileMenuItems, listener); 938 939 FileHistory h = FileHistory.getFileHistory(viewConfig.getWorkDirectory(), getHistoryFileName()); 940 FileHistory.Listener l = new FileHistory.Listener(h, 0, listener); 941 recentConfigMenu = uif.createMenu("ce.history"); 942 recentConfigMenu.setEnabled(h.getLatestEntry() != null); 943 recentConfigMenu.addMenuListener(l); 944 fileMenu.insert(recentConfigMenu, 4); 945 946 return fileMenu; 947 } 948 949 private void initMenuBar() { 950 listener = new Listener(); 951 JMenuBar menuBar = uif.createMenuBar("ce.menub"); 952 953 JMenu fileMenu = createFileMenu(); 954 menuBar.add(fileMenu); 955 956 // marker menu 957 markerMenu = fullView.getMarkerMenu(); 958 menuBar.add(markerMenu); 959 960 // search menu 961 searchMenu = fullView.getSearchMenu(); 962 menuBar.add(searchMenu); 963 964 // view menu 965 viewMenu = uif.createMenu("ce.view"); 966 viewMenu.addMenuListener(listener); 967 ButtonGroup viewGroup = new ButtonGroup(); 968 969 viewFullBtn = uif.createRadioButtonMenuItem("ce.view", CE_View.FULL); 970 viewFullBtn.setSelected(true); 971 viewFullBtn.setActionCommand(CE_View.FULL); 972 viewFullBtn.addActionListener(listener); 973 viewGroup.add(viewFullBtn); 974 viewMenu.add(viewFullBtn); 975 976 viewStdBtn = uif.createRadioButtonMenuItem("ce.view", CE_View.STD); 977 viewStdBtn.setActionCommand(CE_View.STD); 978 viewStdBtn.addActionListener(listener); 979 viewGroup.add(viewStdBtn); 980 viewMenu.add(viewStdBtn); 981 982 viewMenu.addSeparator(); 983 984 viewInfoCheckBox = uif.createCheckBoxMenuItem("ce.view", "info", false); 985 viewInfoCheckBox.addChangeListener(listener); 986 viewMenu.add(viewInfoCheckBox); 987 988 viewTagCheckBox = uif.createCheckBoxMenuItem("ce.view", "tag", false); 989 viewTagCheckBox.setAccelerator(KeyStroke.getKeyStroke("control T")); 990 viewTagCheckBox.addChangeListener(listener); 991 viewMenu.add(viewTagCheckBox); 992 993 viewMenu.addSeparator(); 994 995 viewRefreshItem = uif.createMenuItem("ce.view", "refresh", listener); 996 viewRefreshItem.setAccelerator(KeyStroke.getKeyStroke("F5")); 997 viewMenu.add(viewRefreshItem); 998 999 menuBar.add(viewMenu); 1000 1001 menuBar.add(uif.createHorizontalGlue("ce.pad")); 1002 1003 // help menu 1004 JMenu helpMenu = uif.createMenu("ce.help"); 1005 // config editor help 1006 JMenuItem mainItem = uif.createHelpMenuItem("ce.help.main", "confEdit.window.csh"); 1007 helpMenu.add(mainItem); 1008 1009 /** 1010 // template editor help 1011 mainItem = uif.createHelpMenuItem("ce.help.maint", "confEdit.templateDialog.csh"); 1012 helpMenu.add(mainItem); 1013 */ 1014 JMenuItem fullItem = uif.createHelpMenuItem("ce.help.full", "confEdit.fullView.csh"); 1015 helpMenu.add(fullItem); 1016 JMenuItem stdItem = uif.createHelpMenuItem("ce.help.std", "confEdit.stdView.csh"); 1017 helpMenu.add(stdItem); 1018 menuBar.add(helpMenu); 1019 1020 setJMenuBar(menuBar); 1021 } 1022 1023 protected void updateTitle() { 1024 File f = viewConfig.getFile(); 1025 setI18NTitle("ce.title", 1026 new Object[] { new Integer(currView == fullView ? 0 : 1), 1027 new Integer(f == null ? 0 : 1), f }); 1028 } 1029 1030 private boolean isInfoVisible() { 1031 return (body instanceof JSplitPane); 1032 } 1033 1034 private void setInfoVisible(boolean b) { 1035 // verify there is an infoPanel to be made visible 1036 if (infoPanel == null) 1037 throw new IllegalStateException(); 1038 1039 // check if already set as desired 1040 if (b == isInfoVisible()) 1041 return; 1042 1043 // get dimensions of views and info panel 1044 Dimension viewsSize = views.getSize(); 1045 if (viewsSize.width == 0) 1046 viewsSize = views.getPreferredSize(); 1047 1048 Dimension infoSize = infoPanel.getSize(); 1049 if (infoSize.width == 0) 1050 infoSize = infoPanel.getPreferredSize(); 1051 1052 1053 if (b) { 1054 // set body to views+info; remove border, because JSplitPane adds in 1055 // its own padding 1056 views.setBorder(null); 1057 JSplitPane sp = uif.createSplitPane(JSplitPane.HORIZONTAL_SPLIT, views, infoPanel); 1058 sp.setDividerLocation(viewsSize.width + sp.getDividerSize()); 1059 body = sp; 1060 showInfoForQuestion(viewConfig.getCurrentQuestion()); 1061 } 1062 else { 1063 // set body to views; add a border to stand in for the padding 1064 // that JSplitPane would otherwise give 1065 views.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); 1066 body = views; 1067 } 1068 1069 setBody(body); 1070 if (isShowing()) { 1071 // adjust the size of the window up or down as appropriate 1072 Dimension winSize = getSize(); 1073 int divWidth = new JSplitPane().getDividerSize(); 1074 int newWidth = winSize.width; 1075 newWidth += (b ? +1 : -1) * (infoSize.width + divWidth); 1076 setSize(newWidth, winSize.height); 1077 } 1078 } 1079 1080 private void showInfoForQuestion(Question q) { 1081 HelpID helpId = Help.getHelpID(q); 1082 // uugh 1083 if (helpId == null) 1084 System.err.println("WARNING: no help for " + q.getKey()); 1085 else 1086 infoPanel.setCurrentID(helpId); 1087 } 1088 1089 1090 private CE_View getDefaultView() { 1091 if (currView != null) 1092 return currView; 1093 Preferences p = Preferences.access(); 1094 String prefView = p.getPreference(VIEW_PREF, CE_View.FULL); 1095 if (prefView.equals(CE_View.STD)) 1096 return stdView; 1097 else 1098 return fullView; 1099 } 1100 1101 protected void perform(String cmd) { 1102 if (cmd.equals(NEW)) 1103 newConfigAsk(); 1104 else if (cmd.equals(LOAD)) 1105 loadConfigAsk(); 1106 /* 1107 else if (cmd.equals(LOADT)) 1108 load(true); 1109 else if (cmd.equals(NEWT)) 1110 clear(true); 1111 */ 1112 else if (cmd.equals(SAVE)) 1113 save(); 1114 else if (cmd.equals(SAVE_AS)) 1115 saveAs(); 1116 else if (cmd.equals(REVERT)) 1117 revert(); 1118 else if (cmd.equals(CE_View.FULL)) 1119 show(fullView); 1120 else if (cmd.equals(CE_View.STD)) 1121 show(stdView); 1122 else if (cmd.equals(CLOSE)) { 1123 close(); 1124 } 1125 else if (cmd.equals(DONE)) { 1126 if (currView != null && !currView.isOKToClose()) 1127 return; 1128 1129 if (!canInterruptTemplateCreation() && !viewConfig.isFinishable()) { 1130 uif.showError("ce.force_close"); 1131 return; 1132 } 1133 1134 currView.save(); 1135 if (!viewConfig.isFinishable()) { 1136 Integer rp = new Integer(runPending ? 1 : 0); 1137 int rc = uif.showOKCancelDialog("ce.okToClose", rp); 1138 if (rc != JOptionPane.OK_OPTION) 1139 return; 1140 } 1141 1142 if (isEdited() || isCurrentQuestionChanged()) 1143 saveRequired = true; 1144 1145 if (saveRequired) { 1146 if (!save0()) { 1147 // save failed, stay in CE, don't clear saveRequired flag 1148 return; 1149 } 1150 1151 // save succeeded, so safe to clear saveRequired flag 1152 saveRequired = false; 1153 } 1154 1155 close(false); 1156 } 1157 else if (cmd.equals(REFRESH)) { 1158 if (currView != null) 1159 currView.refresh(); 1160 } 1161 else if (cmd.equals(DETAILS)) { 1162 if (detailsBrowser == null) { 1163 detailsBrowser = new DetailsBrowser(body, viewConfig, infoPanel); 1164 detailsBrowser.setQuestionInfoEnabled(currView == fullView); 1165 } 1166 1167 detailsBrowser.setVisible(true); 1168 } 1169 else 1170 throw new IllegalArgumentException(cmd); 1171 } 1172 1173 private boolean canInterruptTemplateCreation () { 1174 /** fa 1175 ContextManager cm = getContextManager(); 1176 String wdTmpl = TemplateUtilities.getTemplatePath(model.getWorkDirectory()); 1177 if (mainConfig.isTemplate() && 1178 !cm.getFeatureManager().isEnabled(FeatureManager.WD_WITHOUT_TEMPLATE) && 1179 wdTmpl == null) { 1180 return false; 1181 } 1182 */ 1183 return true; 1184 } 1185 1186 public static void copy(InterviewParameters from, InterviewParameters to) 1187 throws Interview.Fault 1188 { 1189 copy(from, to, true); // copy filename as well, by default 1190 } 1191 1192 private static void copy(InterviewParameters from, InterviewParameters to, 1193 boolean copyFile) 1194 throws Interview.Fault 1195 { 1196 //System.err.println("CE.copy from " + (from==mainConfig?"main":from==viewConfig?"view":from.toString()) + " to " + (to==mainConfig?"main":to==viewConfig?"view":to.toString())); 1197 Map<String, String> data = new HashMap<>(); 1198 from.save(data); 1199 to.load(data, false); 1200 to.setTemplate(from.isTemplate()); 1201 1202 if (copyFile) 1203 to.setFile(from.getFile()); 1204 if (debug) { 1205 Debug.println("InterviewEditor: equal(b,a) " + equal(to,from)); 1206 } 1207 1208 } 1209 1210 /** 1211 * Checks default settings relate to config file load fron the default location 1212 * @param cm <code>ContextManager</code> object defining current harness' context. The following methods 1213 * affect this method functionality: 1214 * <ul> 1215 * <li><code>getDefaultConfigLoadPath()</code> 1216 * <li><code>getAllowConfigLoadOutsideDefault()</code> 1217 * </ul> 1218 * @throws <code>IllegalArgumentException</code> if the following configuration errors found: 1219 * <ul> 1220 * <li> <code>getDefaultConfigLoadPath()</code> returns <code>null</code> when <code>getAllowConfigLoadOutsideDefault()</code> returns <code>false</code> 1221 * <li> <code>getDefaultConfigLoadPath()</code> returns not absolute path 1222 * <li> <code>getDefaultConfigLoadPath()</code> returns a file (not a directory) 1223 * </ul> 1224 * @see ContextManager#setDefaultConfigLoadPath(java.io.File) 1225 * @see ContextManager#setAllowConfigLoadOutsideDefault(boolean state) 1226 * @see ContextManager#getDefaultConfigLoadPath() 1227 * @see ContextManager#getAllowConfigLoadOutsideDefault() 1228 */ 1229 1230 public static File checkLoadConfigFileDefaults(ContextManager cm) { 1231 if (cm == null) 1232 return null; 1233 1234 File defaultConfigLoadPath = cm.getDefaultConfigLoadPath(); 1235 boolean allowConfigLoadOutsideDefault = cm.getAllowConfigLoadOutsideDefault(); 1236 1237 if (defaultConfigLoadPath == null && !allowConfigLoadOutsideDefault) 1238 throw new IllegalArgumentException("Default directory not specified for " + 1239 "load operation when allowConfigLoadOutsideDefault is false"); 1240 1241 if (defaultConfigLoadPath != null) { 1242 if (!defaultConfigLoadPath.isAbsolute()) 1243 throw new IllegalArgumentException("Relative paths not " + 1244 "currently supported. The following setting is incorrect: " + 1245 "\"" + defaultConfigLoadPath.getPath() + "\" selected for " + 1246 "load operation"); 1247 1248 if (defaultConfigLoadPath.isFile()) 1249 throw new IllegalArgumentException("Filename selected unexpectedly " + 1250 "as a default directory: " + 1251 "\"" + defaultConfigLoadPath.getPath() + "\" for " + 1252 "load operation"); 1253 } 1254 1255 return defaultConfigLoadPath; 1256 } 1257 1258 static File loadConfigFile(ContextManager cm, Component parent, UIFactory uif, String ext, String key) { 1259 FileChooser fileChooser = new FileChooser(true); 1260 fileChooser.addChoosableExtension(ext, uif.getI18NString("ce.jtiFiles" + key)); 1261 return loadConfigFile(cm, parent, uif, fileChooser); 1262 } 1263 1264 /** 1265 * Provides capabilities for configuration file loading. Method takes into 1266 * account context settings relating to default locations for configuration 1267 * files loading and behaves according to them. 1268 * @param cm <code>ContextManager</code> object defining current harness' context. The following methods 1269 * affect this method functionality: 1270 * <li><code>getDefaultConfigLoadPath()</code> 1271 * <li><code>getAllowConfigLoadOutsideDefault()</code> 1272 * </ul> 1273 * @param parent A parent frame to be used for <code>fileChooser</code>/warning dialogs 1274 * @param uif The UIFactory used to for configuration file loading operation 1275 * @param fileChooser The <code>FileChooser</code> used for configuration file loading 1276 * @return The configuration file selected by user if this file loading is allowed by 1277 * harness' contest settings 1278 * @see ContextManager#setDefaultConfigLoadPath(java.io.File) 1279 * @see ContextManager#setAllowConfigLoadOutsideDefault(boolean state) 1280 * @see ContextManager#getDefaultConfigLoadPath() 1281 * @see ContextManager#getAllowConfigLoadOutsideDefault() 1282 */ 1283 1284 static File loadConfigFile(ContextManager cm, Component parent, UIFactory uif, FileChooser fileChooser) { 1285 1286 if (cm == null) 1287 return null; 1288 1289 File defaultConfigLoadPath = checkLoadConfigFileDefaults(cm); 1290 boolean allowConfigLoadOutsideDefault = cm.getAllowConfigLoadOutsideDefault(); 1291 1292 File file = null; 1293 1294 fileChooser.setDialogTitle(uif.getI18NString("ce.load.title")); 1295 1296 if (defaultConfigLoadPath != null) { 1297 if (!allowConfigLoadOutsideDefault) { 1298 if (!(new File(defaultConfigLoadPath.getAbsolutePath())).canRead()) { 1299 uif.showError("ce.load.defDirNotExists", defaultConfigLoadPath); 1300 return null; 1301 } 1302 fileChooser.enableDirectories(false); 1303 } else 1304 fileChooser.enableDirectories(true); 1305 fileChooser.setCurrentDirectory(defaultConfigLoadPath); 1306 } 1307 1308 boolean isMatch = true; 1309 1310 while (file == null) { 1311 int rc = fileChooser.showDialog(parent, uif.getI18NString("ce.load.btn")); 1312 if (rc != JFileChooser.APPROVE_OPTION) 1313 return null; 1314 1315 file = fileChooser.getSelectedFile(); 1316 1317 if (!allowConfigLoadOutsideDefault) { 1318 if (defaultConfigLoadPath == null) 1319 return null; 1320 1321 File f = new File(file.getAbsolutePath().substring(0, file.getAbsolutePath().lastIndexOf(File.separator))); 1322 1323 try { 1324 isMatch = (f.getCanonicalPath().indexOf((defaultConfigLoadPath.getCanonicalPath())) == 0); 1325 } catch ( IOException ioe) { 1326 ioe.printStackTrace(System.err); 1327 return null; 1328 } 1329 1330 if (!isMatch) { 1331 uif.showError("ce.load.notAllowedDir", defaultConfigLoadPath); 1332 1333 1334 file = null; 1335 fileChooser.setCurrentDirectory(defaultConfigLoadPath); 1336 continue; // choose another file 1337 } 1338 } 1339 } 1340 1341 if (file != null) { 1342 String path = file.getPath(); 1343 String ext = fileChooser.getChosenExtension(); 1344 if (ext == null) { 1345 ext = CONFIG_EXTENSION; 1346 } 1347 if (!path.endsWith(ext)) 1348 file = new File(path + ext); 1349 } 1350 1351 return file; 1352 } 1353 1354 /** 1355 * Provides as the user with a dialog to chooser where to save a config. Method takes into account 1356 * context settings relating to default locations for configuration files saving and behaves 1357 * according to them. 1358 * @param cm <code>ContextManager</code> object defining current harness' context. The following methods 1359 * affect this method functionality: 1360 * <ul> 1361 * <li><code>getDefaultConfigSavePath()</code> 1362 * <li><code>getAllowConfigSaveOutsideDefault()</code> 1363 * </ul> 1364 * @param parent A parent frame to be used for <code>fileChooser</code>/warning dialogs 1365 * @param uif The UIFactory used to for configuration file saving operation 1366 * @param fileChooser The <code>FileChooser</code> used for configuration file saving 1367 * @return The configuration file selected by user if this file saving is allowed by 1368 * harness' contest settings 1369 * @throws <code>IllegalArgumentException</code> if the following configuration errors found: 1370 * <ul> 1371 * <li> <code>getDefaultConfigSavePath()</code> returns <code>null</code> when <code>getAllowConfigSaveOutsideDefault()</code> returns <code>false</code> 1372 * <li> <code>getDefaultConfigSavePath()</code> returns not absolute path 1373 * <li> <code>getDefaultConfigSavePath()</code> returns a file (not a directory) 1374 * </ul> 1375 * @see ContextManager#setDefaultConfigSavePath(java.io.File) 1376 * @see ContextManager#setAllowConfigSaveOutsideDefault(boolean state) 1377 * @see ContextManager#getDefaultConfigSavePath() 1378 * @see ContextManager#getAllowConfigSaveOutsideDefault() 1379 */ 1380 1381 static File saveConfigFile(ContextManager cm, Component parent, UIFactory uif, FileChooser fileChooser, File dir, 1382 boolean isTemplate) { 1383 if (cm == null) 1384 return null; 1385 1386 File defaultSavePath; 1387 if (isTemplate) { 1388 defaultSavePath = cm.getDefaultTemplateSavePath(); 1389 } else { 1390 defaultSavePath = cm.getDefaultConfigSavePath(); 1391 } 1392 boolean allowSaveOutsideDefault; 1393 if (isTemplate) { 1394 allowSaveOutsideDefault = cm.getAllowTemplateSaveOutsideDefault(); 1395 } else { 1396 allowSaveOutsideDefault = cm.getAllowConfigSaveOutsideDefault(); 1397 } 1398 1399 1400 if (defaultSavePath == null && !allowSaveOutsideDefault) 1401 throw new IllegalArgumentException("Default directory not specified for " + 1402 "save operation when allowConfigSaveOutsideDefault is false"); 1403 1404 if (defaultSavePath != null) { 1405 if (!defaultSavePath.isAbsolute()) 1406 throw new IllegalArgumentException("Relative paths not " + 1407 "currently supported. The following setting is incorrect: " + 1408 "\"" + defaultSavePath.getPath() + "\" selected for " + 1409 "save operation"); 1410 1411 if (defaultSavePath.isFile()) 1412 throw new IllegalArgumentException("Filename selected unexpectedly " + 1413 "as a default directory: " + 1414 "\"" + defaultSavePath.getPath() + "\" for " + 1415 "save operation"); 1416 1417 if (!allowSaveOutsideDefault) { 1418 if (!defaultSavePath.canWrite()) { 1419 uif.showError("ce.save.defDirNotExists", defaultSavePath); 1420 return null; 1421 } 1422 fileChooser.enableDirectories(false); 1423 } else 1424 fileChooser.enableDirectories(true); 1425 1426 fileChooser.setCurrentDirectory(defaultSavePath); 1427 } else 1428 if (dir != null) 1429 fileChooser.setCurrentDirectory(dir); 1430 1431 File file = null; 1432 boolean isMatch = true; 1433 1434 while (file == null) { 1435 int rc = fileChooser.showDialog(parent, uif.getI18NString("ce.save.btn")); 1436 if (rc != JFileChooser.APPROVE_OPTION) 1437 // user has canceled or closed the chooser 1438 return null; 1439 1440 file = fileChooser.getSelectedFile(); 1441 if (file == null) // just making sure 1442 continue; 1443 1444 File f = new File(file.getAbsolutePath().substring(0, file.getAbsolutePath().lastIndexOf(File.separator))); 1445 1446 if (!allowSaveOutsideDefault) { 1447 if (defaultSavePath == null) 1448 return null; 1449 1450 try { 1451 isMatch = defaultSavePath.getCanonicalPath().equals(f.getCanonicalPath()); 1452 } catch ( IOException ioe) { 1453 ioe.printStackTrace(System.err); 1454 return null; 1455 } 1456 1457 if (!isMatch) { 1458 uif.showError("ce.save.notAllowedDir", defaultSavePath); 1459 file = null; 1460 fileChooser.setCurrentDirectory(defaultSavePath); 1461 continue; // choose another file 1462 } 1463 } 1464 1465 if (file.isDirectory()) { 1466 uif.showError("ce.save.fileIsDir", file); 1467 file = null; 1468 continue; // choose another file 1469 } 1470 1471 File parentFile = file.getParentFile(); 1472 if (parentFile != null) { 1473 if (parentFile.exists() && !parentFile.isDirectory()) { 1474 uif.showError("ce.save.parentNotADir", parentFile); 1475 file = null; 1476 continue; // choose another file 1477 } else if (!parentFile.exists()) { 1478 rc = uif.showYesNoDialog("ce.save.createParentDir", 1479 parentFile); 1480 if (rc == JOptionPane.YES_OPTION) { 1481 if (!parentFile.mkdirs()) { 1482 uif.showError("ce.save.cantCreateParentDir", 1483 parentFile); 1484 file = null; 1485 continue; // choose another file 1486 } 1487 } else { 1488 file = null; 1489 continue; // choose another file 1490 } 1491 } 1492 } 1493 1494 // if file exists, leave well enough alone; 1495 // otherwise, make sure it ends with .jti or .jtm 1496 if (!file.exists()) { 1497 String path = file.getPath(); 1498 String ext = fileChooser.getChosenExtension(); 1499 if (ext != null && !path.endsWith(ext)) 1500 file = new File(path + ext); 1501 } 1502 1503 // if file exists, make sure user wants to overwrite it 1504 if (file.exists()) { 1505 rc = uif.showYesNoDialog("ce.save.warn"); 1506 switch (rc) { 1507 case JOptionPane.YES_OPTION: 1508 break; // use this file 1509 1510 case JOptionPane.NO_OPTION: 1511 fileChooser.setSelectedFile(null); 1512 file = null; 1513 continue; // choose another file 1514 } 1515 } 1516 } 1517 return file; 1518 } 1519 1520 void setAfterCloseCommand(Runnable runnable) { 1521 afterCloseCommand = runnable; 1522 } 1523 1524 private Runnable afterCloseCommand; 1525 1526 /** 1527 * Will be eliminated in the next release. 1528 * @deprecated 1529 */ 1530 @Deprecated 1531 protected boolean templateMode = false; 1532 protected ContextManager contextManager = null; 1533 protected InterviewParameters mainConfig; 1534 protected InterviewParameters viewConfig; 1535 private FileHistory history; 1536 private boolean saveRequired; 1537 //protected String key; 1538 1539 private boolean runPending; 1540 private static boolean debug = Debug.getBoolean(InterviewEditor.class); 1541 1542 1543 1544 private JMenu recentConfigMenu; 1545 private JMenu markerMenu; 1546 private JMenu searchMenu; 1547 private JMenu viewMenu; 1548 private JRadioButtonMenuItem viewFullBtn; 1549 private JRadioButtonMenuItem viewStdBtn; 1550 private JCheckBoxMenuItem viewInfoCheckBox; 1551 private JCheckBoxMenuItem viewTagCheckBox; 1552 private JMenuItem viewRefreshItem; 1553 private JComponent body; 1554 private JPanel views; 1555 private JHelpContentViewer infoPanel; 1556 private CE_FullView fullView; 1557 private CE_StdView stdView; 1558 private CE_View currView; 1559 private Listener listener; 1560 //private TemplatesUI templatesUI; 1561 1562 private Map<Class<? extends Question>, QuestionRenderer> customRenderersMap; 1563 private ActionListener closeListener; 1564 //private ExecModel model; 1565 private final List<Observer> observers = new ArrayList<Observer>(); 1566 1567 private DetailsBrowser detailsBrowser; 1568 private static final KeyStroke detailsKey = KeyStroke.getKeyStroke("shift alt D"); 1569 protected String ext; 1570 1571 // XXX this isn't the right class to define these in 1572 // do not make more public than package private 1573 static final String CONFIG_EXTENSION = ".jti"; 1574 static final String CONFIG_HISTORY = "configHistory.jtl"; 1575 1576 private static final String NEW = "new"; 1577 private static final String LOAD = "load"; 1578 //private static final String NEWT = "newt"; 1579 //private static final String LOADT = "loadt"; 1580 private static final String SAVE = "save"; 1581 private static final String SAVE_AS = "saveAs"; 1582 private static final String REVERT = "revert"; 1583 private static final String DONE = "done"; 1584 private static final String REFRESH = "refresh"; 1585 private static final String DETAILS = "details"; 1586 static final String CLOSE = "close"; 1587 1588 static final String MORE_INFO_PREF = "exec.config.moreInfo"; 1589 static final String VIEW_PREF = "exec.config.view"; 1590 1591 1592 public void setCustomRenderers(Map<Class<? extends Question>, QuestionRenderer> renderersMap) { 1593 customRenderersMap = renderersMap; 1594 if (fullView != null) { 1595 fullView.setCustomRenderers(customRenderersMap); 1596 } 1597 } 1598 1599 private class Listener 1600 implements ActionListener, ChangeListener, MenuListener 1601 { 1602 // ---------- from ActionListener ----------- 1603 1604 public void actionPerformed(ActionEvent e) { 1605 Object src = e.getSource(); 1606 if (src instanceof JMenuItem) { 1607 JMenuItem mi = (JMenuItem) src; 1608 File f = (File) (mi.getClientProperty(FileHistory.FILE)); 1609 if (f != null && askAndSave("ce.load.warn")) { 1610 loadConfigFromFile(f); 1611 return; 1612 } 1613 } 1614 perform(e.getActionCommand()); 1615 } 1616 1617 // ---------- from ChangeListener ----------- 1618 1619 public void stateChanged(ChangeEvent e) { 1620 Object src = e.getSource(); 1621 if (src == viewInfoCheckBox && infoPanel != null) 1622 setInfoVisible(viewInfoCheckBox.isSelected()); 1623 else if (src == viewTagCheckBox) 1624 fullView.setTagVisible(viewTagCheckBox.isSelected()); 1625 } 1626 1627 // ---------- from MenuListener ----------- 1628 1629 public void menuSelected(MenuEvent e) { 1630 Object src = e.getSource(); 1631 if (src == viewMenu) 1632 viewTagCheckBox.setSelected(fullView.isTagVisible()); 1633 } 1634 1635 public void menuDeselected(MenuEvent e) { 1636 } 1637 1638 public void menuCanceled(MenuEvent e) { 1639 } 1640 1641 }; 1642 1643 /** 1644 * For private communication with SessionControl, not for broadcast outside 1645 * of core JT. 1646 */ 1647 public interface Observer { 1648 /** 1649 * Invoked when value of interview parameters has been changed 1650 * @param p object with updated value (viewConfig) 1651 */ 1652 public void changed(InterviewParameters p); 1653 1654 /** 1655 * Invoked when setVisible() method is invoked on InterviewEditor object 1656 * @param isVisible argument passed to setVisible() method 1657 * @param source editor that changed the state 1658 */ 1659 public void changedVisibility(boolean isVisible, InterviewEditor source); 1660 } 1661 }