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