/*
* $Id$
*
* Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.javatest.exec;
import com.sun.interview.Help;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.AWTEvent;
import java.awt.FocusTraversalPolicy;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JSplitPane;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import com.sun.interview.Interview;
import com.sun.interview.Question;
import com.sun.interview.wizard.QuestionRenderer;
import com.sun.javatest.InterviewParameters;
import com.sun.javatest.TestSuite;
import com.sun.javatest.WorkDirectory;
import com.sun.javatest.exec.WorkDirChooseTool.ExecModelStub;
import com.sun.javatest.tool.FileChooser;
import com.sun.javatest.tool.FileHistory;
import com.sun.javatest.tool.HelpLink;
import com.sun.javatest.tool.Preferences;
import com.sun.javatest.tool.ToolDialog;
import com.sun.javatest.tool.UIFactory;
import com.sun.javatest.tool.jthelp.HelpID;
import com.sun.javatest.tool.jthelp.JHelpContentViewer;
import com.sun.javatest.util.Debug;
import java.util.ArrayList;
import java.util.List;
/**
* Dialog to edit InterviewParameters object.
*
* InterviewEditor keeps reference to the main InterviewParameters object,
* but never change it.
*
* Before editing interview the main InterviewParameters object is synced
* with the view object.
*
* When view object is loaded or saved, all registered observers are notified.
*
*/
public class InterviewEditor extends ToolDialog {
public InterviewEditor(JComponent parent, UIFactory uif,
InterviewParameters ip) {
super(parent, uif, "ce", ToolDialog.MODAL_DOCUMENT);
WorkDirectory wd = ip.getWorkDirectory();
if (wd == null) {
throw new IllegalArgumentException(uif.getI18NString("ce.wdNull.err"));
}
mainConfig = ip;
try {
//this.key = key;
this.ext = getExtention();
viewConfig = ip.getTestSuite().createInterview();
viewConfig.setWorkDirectory(ip.getWorkDirectory());
history = FileHistory.getFileHistory(wd, getHistoryFileName());
} catch (TestSuite.Fault e) {
// ignore, for now; should not happen
}
}
public InterviewEditor(JComponent parent, UIFactory uif,
InterviewParameters ip, ContextManager cm) {
this(parent, uif, ip);
setContextManager(cm);
}
/**
* Sets contextManager to the passed value.
* @param cm - ContextManager to use
*/
void setContextManager(ContextManager cm) {
this.contextManager = cm;
}
/**
* Returns previously created or set ContextManager. If not set, creates
* the new instance.
*/
ContextManager getContextManager() {
if (contextManager == null) {
try {
contextManager = (ContextManager) ((Class.forName(
"com.sun.javatest.exec.ContextManager")).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
return contextManager;
}
/**
* Returns extension for files to be saved. Subclasses like TemplateEditor
* might override this method.
*
* @return default extension
*/
protected String getExtention() {
return CONFIG_EXTENSION;
}
/**
* Returns file name to store history of configuration files.
* This implementation returns "configHistory.jtl". Subclasses might
* override this method to return alternative value.
*/
protected String getHistoryFileName() {
return CONFIG_HISTORY;
}
private void setRestorer(CE_View view) {
getRestorer().setRestorePolicy(Restorer.RESTORE_ALL);
getRestorer().setWindowKey(getRestorerWindowKey(view == fullView));
}
protected String getRestorerWindowKey(boolean isFullView) {
return "confEdit.config" + (isFullView ? ".f" : ".s");
}
/**
* Adds passed file to the history.
* @param f - file to be added.
*/
void addToHistory(File f) {
if (history == null) {
WorkDirectory wd = viewConfig.getWorkDirectory();
if (wd == null) {
return;
}
history = FileHistory.getFileHistory(wd, getHistoryFileName());
}
history.add(f);
}
/**
* Syncs mainConfig and viewConfig
*/
private void sync() {
try {
copy(mainConfig, viewConfig);
} catch (Interview.Fault e) {
uif.showError("ce.show.error", e.getMessage());
}
}
/**
* Starts editing new config. Supposed to be called outside.
*/
public void newConfig() {
try {
InterviewParameters newConfig = viewConfig.getTestSuite().createInterview();
copy(newConfig, viewConfig);
} catch (TestSuite.Fault e) {
uif.showError("ce.show.error", e.getMessage());
} catch (Interview.Fault e) {
uif.showError("ce.show.error", e.getMessage());
}
show(FULL_MODE);
}
/**
* Start editing empty configuration.
*/
private void newConfigAsk() {
if (askAndSave("ce.clear.warn")) {
newConfig();
}
}
/**
* Show dialog.
*/
public void edit(int mode) {
sync();
show(mode);
}
/**
* @return mode that will be used by WorkDirChooseTool to select file.
*/
public int getFileChooserMode() {
return WorkDirChooseTool.LOAD_CONFIG;
}
/**
* Show choose file dialog and then load new file.
* Supposed to be invoked from outside of editor.
* Doesn't expect that viewConfig can be changed.
*/
public void loadConfig() {
loadConfig0(false);
}
/**
* Show choose file dialog and then load new file.
* The dialog depends on fileChooserMode setting. It can be either
* simple JFileChooser or "advanced" home made file chooser.
* @param ask if true, dialog asking whether to save changes will appear
* in case of unsaved changes.
*/
protected void loadConfig0(boolean ask) {
TestSuite ts = viewConfig.getTestSuite();
ExecModelStub em = new ExecModelStub(ts, contextManager);
try {
em.setWorkDir(mainConfig.getWorkDirectory(), true);
} catch (Exception ignore) {
// stub never throws exceptions
}
WorkDirChooseTool fc = WorkDirChooseTool.getTool((JComponent)parent,
uif, em, getFileChooserMode(), ts, true);
WorkDirChooseTool.ChosenFileHandler cfh =
new WorkDirChooseTool.ChosenFileHandler();
fc.setChosenFileHandler(cfh);
fc.doTool();
File f = cfh.file;
if (f != null && (!ask || askAndSave("ce.load.warn"))) {
loadConfigFromFile(f);
if (cfh.isEditConfig && !isVisible()) {
edit(FULL_MODE);
}
}
}
/**
* Works similar to loadConfig(), but asks to save changes if any
* before reload.
*/
private void loadConfigAsk() {
loadConfig0(true);
}
/**
*
* @param f
*/
public void loadAndEdit(File f) {
if (f == null) {
return;
}
loadConfigFromFile(f);
notifyObservers();
show(FULL_MODE);
}
/**
* Shows file chooser dialog.
* @return chosen file or null.
*/
private File chooseConfigFile() {
File mainConfigFile = mainConfig.getFile();
FileChooser fileChooser = getFileChooser();
if (mainConfigFile != null)
fileChooser.setCurrentDirectory(mainConfigFile.getParentFile());
return loadConfigFile(getContextManager(), parent, uif, fileChooser);
}
/**
* Updates viewConfig, notifies observers of the change.
* @param file File to load.
*/
public void loadConfigFromFile(File file) {
if (file == null) {
return;
}
try {
viewConfig.load(file);
if (currView != null && currView.isShowing())
currView.load();
addToHistory(file);
updateTitle();
notifyObservers();
} catch (FileNotFoundException e) {
uif.showError("ce.load.cantFindFile", file);
} catch (IOException e) {
uif.showError("ce.load.error", new Object[] { file, e } );
} catch (Interview.Fault e) {
uif.showError("ce.load.error", new Object[] { file, e.getMessage() } );
}
}
public void save() {
save0();
}
// return true if saved, false if cancelled/error
private boolean save0() {
// Use the filename saved in the viewConfig.
// By default, this will have been copied from the mainConfig,
// but it may have been cleared if clear() has been called, thereby
// making "save" behave as "saveAs".
return save0(viewConfig.getFile());
}
public void saveAs() {
save0(null);
}
// return true if saved, false if cancelled/error
private boolean save0(File file) {
if (file == null) {
File mainConfigFile = mainConfig.getFile();
File mainConfigDir = (mainConfigFile == null ? null : mainConfigFile.getParentFile());
file = getSaveFile(mainConfigDir);
if (file == null)
return false; // exit without saving
}
try {
if (currView != null) {
currView.save();
}
viewConfig.setFile(file); // for subsequent use
doSave(file);
addToHistory(file);
updateTitle();
notifyObservers();
return true;
}
catch (IOException e) {
if (!file.canWrite())
uif.showError("ce.save.cantWriteFile", file);
else if (e instanceof FileNotFoundException)
uif.showError("ce.save.cantFindFile", file);
else
uif.showError("ce.save.error", new Object[] { file, e } );
}
catch (Interview.Fault e) {
uif.showError("ce.save.error", new Object[] { file, e.getMessage() } );
}
return false;
}
/**
* Does actual save work. should be overriden, when needed.
*/
protected void doSave(File file) throws Interview.Fault, IOException {
viewConfig.save(file);
}
private File getSaveFile(File dir) {
FileChooser fileChooser = getFileChooser();
fileChooser.setDialogTitle(uif.getI18NString("ce.save.title"));
return saveConfigFile(getContextManager(), parent, uif, fileChooser, dir,
this.templateMode);
}
private FileChooser getFileChooser() {
FileChooser fileChooser = new FileChooser(true);
fileChooser.addChoosableExtension(ext, uif.getI18NString("ce.jtiFiles"));
return fileChooser;
}
/**
* In viewConfig differs from mainConfig asks user whether save changes
* or not. Save changes in case of positive answer.
* @param que
* @return false iff user said "cancel".
*/
private boolean askAndSave(String question) {
if (isEdited()) {
int rc = uif.showYesNoCancelDialog(question);
switch (rc) {
case JOptionPane.YES_OPTION: {
save();
return true;}
case JOptionPane.NO_OPTION: return true;
default: return false;
}
} else {
return true;
}
}
public void revert() {
if (!isEdited())
return;
int rc = uif.showOKCancelDialog("ce.revert.warn");
if (rc != JOptionPane.OK_OPTION)
return;
try {
copy(mainConfig, viewConfig);
if (currView != null && currView.isShowing())
currView.load();
updateTitle();
} catch (Interview.Fault e) {
uif.showError("ce.revert", e.getMessage());
}
}
public void setRunPending(boolean b) {
runPending = b;
}
public boolean isRunPending() {
return runPending;
}
public void show() {
if (stdView == null)
initGUI();
show(DEFAULT_MODE);
}
public void updateMenu() {
FileHistory h = FileHistory.getFileHistory(viewConfig.getWorkDirectory(), getHistoryFileName());
recentConfigMenu.setEnabled(h.getLatestEntry() != null);
}
public void show(int mode) {
if (stdView == null) {
initGUI();
}
updateTitle();
updateMenu();
switch (mode) {
case DEFAULT_MODE:
show(getDefaultView());
break;
case FULL_MODE:
show(fullView);
break;
case STD_MODE:
show(stdView);
break;
case STD_TESTS_MODE:
stdView.showTab(CE_StdView.TESTS_PANE);
show(stdView);
break;
case STD_EXCLUDE_LIST_MODE:
stdView.showTab(CE_StdView.EXCLUDE_LIST_PANE);
show(stdView);
break;
case STD_KEYWORDS_MODE:
stdView.showTab(CE_StdView.KEYWORDS_PANE);
show(stdView);
break;
case STD_KFL_MODE:
stdView.showTab(CE_StdView.KFL_PANE);
show(stdView);
break;
case STD_PRIOR_STATUS_MODE:
stdView.showTab(CE_StdView.PRIOR_STATUS_PANE);
show(stdView);
break;
case STD_ENVIRONMENT_MODE:
stdView.showTab(CE_StdView.ENVIRONMENT_PANE);
show(stdView);
break;
case STD_CONCURRENCY_MODE:
stdView.showTab(CE_StdView.CONCURRENCY_PANE);
show(stdView);
break;
case STD_TIMEOUT_FACTOR_MODE:
stdView.showTab(CE_StdView.TIMEOUT_FACTOR_PANE);
show(stdView);
break;
default:
throw new IllegalArgumentException();
}
}
public void show(ActionListener closeListener) {
this.closeListener = closeListener;
show();
}
public void show(int mode, ActionListener closeListener, boolean isTemplateMode) {
this.closeListener = closeListener;
show(mode);
}
public static final int DEFAULT_MODE = 0;
public static final int FULL_MODE = 1;
public static final int STD_MODE = 2;
public static final int STD_TESTS_MODE = 3;
public static final int STD_EXCLUDE_LIST_MODE = 4;
public static final int STD_KEYWORDS_MODE = 5;
public static final int STD_PRIOR_STATUS_MODE = 6;
public static final int STD_ENVIRONMENT_MODE = 7;
public static final int STD_CONCURRENCY_MODE = 8;
public static final int STD_TIMEOUT_FACTOR_MODE = 9;
public static final int TEMPLATE_FULL_MODE = 10;
public static final int STD_KFL_MODE = 11;
private void show(CE_View newView) {
// update viewConfig from currView (if showing) else mainConfig
if (currView != null && currView.isShowing()) {
currView.save();
}
setRestorer(newView);
setView(newView);
setVisible(true);
}
@Override
public void setVisible(boolean isVisible) {
super.setVisible(isVisible);
notifyObserversOfVisibility(isVisible);
}
private void setView(CE_View newView) {
if (newView == null)
throw new NullPointerException();
if (currView != null && currView == newView) {
currView.load();
}
if (currView != newView) {
// note whether the focus is in the current view
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
Component fo = kfm.getPermanentFocusOwner();
boolean focusInView = (fo != null && currView != null && currView.isAncestorOf(fo));
currView = newView;
// update currView from viewConfig
currView.load();
// set up the appropriate view and controls
((CardLayout)(views.getLayout())).show(views, currView.getName());
// The following is a workaround for what may be a JDK bug.
// As a result of changing the view, the permanent focus owner may no longer
// be showing, and therefore not accepting any keyboard input.
if (focusInView) {
Container fcr = (currView.isFocusCycleRoot() ? currView : currView.getFocusCycleRootAncestor());
FocusTraversalPolicy ftp = fcr.getFocusTraversalPolicy();
Component c = ftp.getDefaultComponent(fcr);
if (c != null)
c.requestFocusInWindow();
}
boolean currIsFull = (currView == fullView);
markerMenu.setEnabled(currIsFull);
searchMenu.setEnabled(currIsFull);
(currIsFull ? viewFullBtn : viewStdBtn).setSelected(true);
viewTagCheckBox.setEnabled(currIsFull);
if (detailsBrowser != null)
detailsBrowser.setQuestionInfoEnabled(currIsFull);
updateTitle();
}
}
public void close() {
if (canInterruptTemplateCreation()) {
doClose();
} else {
uif.showError("ce.force_close");
}
}
public void doClose() {
if (currView != null && !currView.isOKToClose()) {
if (afterCloseCommand != null) {
afterCloseCommand.run();
afterCloseCommand = null;
}
return;
}
close(true);
}
protected void windowClosingAction(AWTEvent e) {
if (!canInterruptTemplateCreation()) {
uif.showError("ce.force_close");
return;
}
if(fullView.isVisible()) {
fullView.prepareClosing();
}
doClose();
}
private void close(boolean checkIfEdited) {
if (currView == null)
return;
if (!isShowing())
return;
if (checkIfEdited && isEdited()) {
int rc = uif.showYesNoCancelDialog("ce.close.warn");
switch (rc) {
case JOptionPane.YES_OPTION:
if (save0()) {
break;
} else {
if (afterCloseCommand != null) {
afterCloseCommand.run();
afterCloseCommand = null;
}
return;
}
case JOptionPane.NO_OPTION:
break;
default:
if (afterCloseCommand != null) {
afterCloseCommand.run();
afterCloseCommand = null;
}
return;
}
}
setVisible(false);
// closeListener may have been set by show(ActionListener)
if (closeListener != null) {
ActionEvent e = new ActionEvent(this,
ActionEvent.ACTION_PERFORMED,
CLOSE);
closeListener.actionPerformed(e);
closeListener = null;
}
if (afterCloseCommand != null) {
afterCloseCommand.run();
afterCloseCommand = null;
}
}
public void setCheckExcludeListListener(ActionListener l) {
if (stdView == null)
initGUI();
stdView.setCheckExcludeListListener(l);
}
boolean isCurrentQuestionChanged() {
if (currView != null && currView.isShowing())
currView.save();
Question mq = mainConfig.getCurrentQuestion();
Question vq = viewConfig.getCurrentQuestion();
return !equal(mq.getTag(), vq.getTag());
}
boolean isEdited() {
if (currView != null && currView.isShowing())
currView.save();
return !equal(mainConfig, viewConfig);
}
/**
* Compares two InterviewParameters objects for equivalence.
* Two interview are equivalent when they both provide the same set
* of questions and all corresponding questions have the same values.
*
* @param a first interview
* @param b second interview
* @return true, iff two interviews are equivalent.
*/
public static boolean equal(InterviewParameters a, InterviewParameters b) {
if (a == b) {
return true;
}
if (a.isTemplate() != b.isTemplate()) {
return false;
}
// do ez checks first
if (a.getMarkersEnabled() != b.getMarkersEnabled()
|| a.getMarkersFilterEnabled() != b.getMarkersFilterEnabled()) {
return false;
}
Map aQuestions = a.getAllQuestions();
Map bQuestions = b.getAllQuestions();
Set keys = new TreeSet<>();
keys.addAll(aQuestions.keySet());
keys.addAll(bQuestions.keySet());
for (Iterator iter = keys.iterator(); iter.hasNext(); ) {
String key = (iter.next());
Question aq = aQuestions.get(key);
Question bq = bQuestions.get(key);
if (aq == null || bq == null) {
return false;
}
if (!aq.equals(bq)) {
String empty = "";
boolean eq = (aq.getStringValue() == null &&
empty.equals(bq.getStringValue())) ||
(empty.equals(aq.getStringValue()) &&
bq.getStringValue() == null);
if(!eq) {
/*
Hopefully, question reloading is not required anymore,
not because questions are equal now, but because
the unexpected dialog no longer appears...
// if aq is not set, it will be set to the default value
// (if the default value had been specified for aq before)
aq.reload();
if (!aq.equals(bq)) {
return false;
}
*/
return false;
}
}
}
// Checking external values
Set aKeys = a.getPropertyKeys();
Set bKeys = b.getPropertyKeys();
if (aKeys == null || bKeys == null) {
return aKeys == bKeys;
}
if (aKeys.size() != bKeys.size()) {
return false;
}
for (Iterator iter = aKeys.iterator(); iter.hasNext(); ) {
String key = iter.next();
if (!bKeys.contains(key)) {
return false;
}
String aProp = a.retrieveProperty(key);
String bProp = b.retrieveProperty(key);
if (!equal(aProp, bProp)) {
return false;
}
}
return true;
}
/**
* Registers new observer
* @param o - observer to be added to the list
*/
public void addObserver(Observer o) {
if (o != null && !observers.contains(o)) {
observers.add(o);
}
}
/**
* Removes observer from the list
* @param o - observer to be removed from the list
*/
public void removeObserver(Observer o) {
if (o != null) {
observers.remove(o);
}
}
/**
* Notifies registered observers of the change happened to viewConfig
*/
protected void notifyObservers() {
for (Observer obs: observers) {
obs.changed(viewConfig);
}
}
/**
* Notifies registered observers of setVisible() method has been called.
*/
protected void notifyObserversOfVisibility(boolean isVisible) {
for (Observer obs: observers) {
obs.changedVisibility(isVisible, this);
}
}
private static boolean equal(String a, String b) {
return (a == null || b == null ? a == b : a.equals(b));
}
@Override
public void dispose() {
if (viewConfig != null) {
viewConfig.dispose();
viewConfig = null;
}
super.dispose();
}
protected void initGUI() {
setHelp("confEdit.window.csh");
listener = new Listener();
updateTitle();
if (viewConfig.getHelpSet() != null) {
// would prefer that the helpset came from the test suite
infoPanel = new JHelpContentViewer(Help.getHelpSet(viewConfig));
infoPanel.setName("info");
int dpi = uif.getDotsPerInch();
infoPanel.setPreferredSize(new Dimension(4*dpi, 3*dpi));
infoPanel.putClientProperty(HelpLink.HELPBROKER_FOR_HELPLINK, uif.getHelpBroker());
}
fullView = new CE_FullView(viewConfig, infoPanel, uif, listener);
if (customRenderersMap != null) {
fullView.setCustomRenderers(customRenderersMap);
}
stdView = new CE_StdView(viewConfig, infoPanel, uif, listener);
stdView.setParentToolDialog(this);
initMenuBar();
views = uif.createPanel("ce.views", new CardLayout(), false);
views.add(fullView, fullView.getName());
views.add(stdView, stdView.getName());
if (infoPanel == null) {
viewInfoCheckBox.setEnabled(false);
viewInfoCheckBox.setSelected(false);
}
else {
Preferences p = Preferences.access();
boolean prefMoreInfo = p.getPreference(MORE_INFO_PREF, "true").equals("true");
viewInfoCheckBox.setEnabled(true);
viewInfoCheckBox.setSelected(prefMoreInfo);
}
// set body to be views+info or views
if (viewInfoCheckBox.isSelected()) {
views.setBorder(null);
JSplitPane sp = uif.createSplitPane(JSplitPane.HORIZONTAL_SPLIT, views, infoPanel);
sp.setDividerLocation(views.getPreferredSize().width + sp.getDividerSize());
body = sp;
}
else {
views.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
body = views;
}
// Don't register "shift alt D" on body, because body might change
// if the more info is opened/closed.
// Instead, register it on views and infoPanel
views.registerKeyboardAction(listener, DETAILS, detailsKey,
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
if (infoPanel != null)
infoPanel.registerKeyboardAction(listener, DETAILS, detailsKey,
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
setBody(body);
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
}
protected JMenu createFileMenu() {
String[] fileMenuItems = new String[] { SAVE, SAVE_AS, REVERT, null,
NEW, LOAD, null, CLOSE };
JMenu fileMenu = uif.createMenu("ce.file", fileMenuItems, listener);
FileHistory h = FileHistory.getFileHistory(viewConfig.getWorkDirectory(), getHistoryFileName());
FileHistory.Listener l = new FileHistory.Listener(h, 0, listener);
recentConfigMenu = uif.createMenu("ce.history");
recentConfigMenu.setEnabled(h.getLatestEntry() != null);
recentConfigMenu.addMenuListener(l);
fileMenu.insert(recentConfigMenu, 4);
return fileMenu;
}
private void initMenuBar() {
listener = new Listener();
JMenuBar menuBar = uif.createMenuBar("ce.menub");
JMenu fileMenu = createFileMenu();
menuBar.add(fileMenu);
// marker menu
markerMenu = fullView.getMarkerMenu();
menuBar.add(markerMenu);
// search menu
searchMenu = fullView.getSearchMenu();
menuBar.add(searchMenu);
// view menu
viewMenu = uif.createMenu("ce.view");
viewMenu.addMenuListener(listener);
ButtonGroup viewGroup = new ButtonGroup();
viewFullBtn = uif.createRadioButtonMenuItem("ce.view", CE_View.FULL);
viewFullBtn.setSelected(true);
viewFullBtn.setActionCommand(CE_View.FULL);
viewFullBtn.addActionListener(listener);
viewGroup.add(viewFullBtn);
viewMenu.add(viewFullBtn);
viewStdBtn = uif.createRadioButtonMenuItem("ce.view", CE_View.STD);
viewStdBtn.setActionCommand(CE_View.STD);
viewStdBtn.addActionListener(listener);
viewGroup.add(viewStdBtn);
viewMenu.add(viewStdBtn);
viewMenu.addSeparator();
viewInfoCheckBox = uif.createCheckBoxMenuItem("ce.view", "info", false);
viewInfoCheckBox.addChangeListener(listener);
viewMenu.add(viewInfoCheckBox);
viewTagCheckBox = uif.createCheckBoxMenuItem("ce.view", "tag", false);
viewTagCheckBox.setAccelerator(KeyStroke.getKeyStroke("control T"));
viewTagCheckBox.addChangeListener(listener);
viewMenu.add(viewTagCheckBox);
viewMenu.addSeparator();
viewRefreshItem = uif.createMenuItem("ce.view", "refresh", listener);
viewRefreshItem.setAccelerator(KeyStroke.getKeyStroke("F5"));
viewMenu.add(viewRefreshItem);
menuBar.add(viewMenu);
menuBar.add(uif.createHorizontalGlue("ce.pad"));
// help menu
JMenu helpMenu = uif.createMenu("ce.help");
// config editor help
JMenuItem mainItem = uif.createHelpMenuItem("ce.help.main", "confEdit.window.csh");
helpMenu.add(mainItem);
/**
// template editor help
mainItem = uif.createHelpMenuItem("ce.help.maint", "confEdit.templateDialog.csh");
helpMenu.add(mainItem);
*/
JMenuItem fullItem = uif.createHelpMenuItem("ce.help.full", "confEdit.fullView.csh");
helpMenu.add(fullItem);
JMenuItem stdItem = uif.createHelpMenuItem("ce.help.std", "confEdit.stdView.csh");
helpMenu.add(stdItem);
menuBar.add(helpMenu);
setJMenuBar(menuBar);
}
protected void updateTitle() {
File f = viewConfig.getFile();
setI18NTitle("ce.title",
new Object[] { new Integer(currView == fullView ? 0 : 1),
new Integer(f == null ? 0 : 1), f });
}
private boolean isInfoVisible() {
return (body instanceof JSplitPane);
}
private void setInfoVisible(boolean b) {
// verify there is an infoPanel to be made visible
if (infoPanel == null)
throw new IllegalStateException();
// check if already set as desired
if (b == isInfoVisible())
return;
// get dimensions of views and info panel
Dimension viewsSize = views.getSize();
if (viewsSize.width == 0)
viewsSize = views.getPreferredSize();
Dimension infoSize = infoPanel.getSize();
if (infoSize.width == 0)
infoSize = infoPanel.getPreferredSize();
if (b) {
// set body to views+info; remove border, because JSplitPane adds in
// its own padding
views.setBorder(null);
JSplitPane sp = uif.createSplitPane(JSplitPane.HORIZONTAL_SPLIT, views, infoPanel);
sp.setDividerLocation(viewsSize.width + sp.getDividerSize());
body = sp;
showInfoForQuestion(viewConfig.getCurrentQuestion());
}
else {
// set body to views; add a border to stand in for the padding
// that JSplitPane would otherwise give
views.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
body = views;
}
setBody(body);
if (isShowing()) {
// adjust the size of the window up or down as appropriate
Dimension winSize = getSize();
int divWidth = new JSplitPane().getDividerSize();
int newWidth = winSize.width;
newWidth += (b ? +1 : -1) * (infoSize.width + divWidth);
setSize(newWidth, winSize.height);
}
}
private void showInfoForQuestion(Question q) {
HelpID helpId = Help.getHelpID(q);
// uugh
if (helpId == null)
System.err.println("WARNING: no help for " + q.getKey());
else
infoPanel.setCurrentID(helpId);
}
private CE_View getDefaultView() {
if (currView != null)
return currView;
Preferences p = Preferences.access();
String prefView = p.getPreference(VIEW_PREF, CE_View.FULL);
if (prefView.equals(CE_View.STD))
return stdView;
else
return fullView;
}
protected void perform(String cmd) {
if (cmd.equals(NEW))
newConfigAsk();
else if (cmd.equals(LOAD))
loadConfigAsk();
/*
else if (cmd.equals(LOADT))
load(true);
else if (cmd.equals(NEWT))
clear(true);
*/
else if (cmd.equals(SAVE))
save();
else if (cmd.equals(SAVE_AS))
saveAs();
else if (cmd.equals(REVERT))
revert();
else if (cmd.equals(CE_View.FULL))
show(fullView);
else if (cmd.equals(CE_View.STD))
show(stdView);
else if (cmd.equals(CLOSE)) {
close();
}
else if (cmd.equals(DONE)) {
if (currView != null && !currView.isOKToClose())
return;
if (!canInterruptTemplateCreation() && !viewConfig.isFinishable()) {
uif.showError("ce.force_close");
return;
}
currView.save();
if (!viewConfig.isFinishable()) {
Integer rp = new Integer(runPending ? 1 : 0);
int rc = uif.showOKCancelDialog("ce.okToClose", rp);
if (rc != JOptionPane.OK_OPTION)
return;
}
if (isEdited() || isCurrentQuestionChanged())
saveRequired = true;
if (saveRequired) {
if (!save0()) {
// save failed, stay in CE, don't clear saveRequired flag
return;
}
// save succeeded, so safe to clear saveRequired flag
saveRequired = false;
}
close(false);
}
else if (cmd.equals(REFRESH)) {
if (currView != null)
currView.refresh();
}
else if (cmd.equals(DETAILS)) {
if (detailsBrowser == null) {
detailsBrowser = new DetailsBrowser(body, viewConfig, infoPanel);
detailsBrowser.setQuestionInfoEnabled(currView == fullView);
}
detailsBrowser.setVisible(true);
}
else
throw new IllegalArgumentException(cmd);
}
private boolean canInterruptTemplateCreation () {
/** fa
ContextManager cm = getContextManager();
String wdTmpl = TemplateUtilities.getTemplatePath(model.getWorkDirectory());
if (mainConfig.isTemplate() &&
!cm.getFeatureManager().isEnabled(FeatureManager.WD_WITHOUT_TEMPLATE) &&
wdTmpl == null) {
return false;
}
*/
return true;
}
public static void copy(InterviewParameters from, InterviewParameters to)
throws Interview.Fault
{
copy(from, to, true); // copy filename as well, by default
}
private static void copy(InterviewParameters from, InterviewParameters to,
boolean copyFile)
throws Interview.Fault
{
//System.err.println("CE.copy from " + (from==mainConfig?"main":from==viewConfig?"view":from.toString()) + " to " + (to==mainConfig?"main":to==viewConfig?"view":to.toString()));
Map data = new HashMap<>();
from.save(data);
to.load(data, false);
to.setTemplate(from.isTemplate());
if (copyFile)
to.setFile(from.getFile());
if (debug) {
Debug.println("InterviewEditor: equal(b,a) " + equal(to,from));
}
}
/**
* Checks default settings relate to config file load fron the default location
* @param cm ContextManager
object defining current harness' context. The following methods
* affect this method functionality:
*
* getDefaultConfigLoadPath()
* getAllowConfigLoadOutsideDefault()
*
* @throws IllegalArgumentException
if the following configuration errors found:
*
* -
getDefaultConfigLoadPath()
returns null
when getAllowConfigLoadOutsideDefault()
returns false
* -
getDefaultConfigLoadPath()
returns not absolute path
* -
getDefaultConfigLoadPath()
returns a file (not a directory)
*
* @see ContextManager#setDefaultConfigLoadPath(java.io.File)
* @see ContextManager#setAllowConfigLoadOutsideDefault(boolean state)
* @see ContextManager#getDefaultConfigLoadPath()
* @see ContextManager#getAllowConfigLoadOutsideDefault()
*/
public static File checkLoadConfigFileDefaults(ContextManager cm) {
if (cm == null)
return null;
File defaultConfigLoadPath = cm.getDefaultConfigLoadPath();
boolean allowConfigLoadOutsideDefault = cm.getAllowConfigLoadOutsideDefault();
if (defaultConfigLoadPath == null && !allowConfigLoadOutsideDefault)
throw new IllegalArgumentException("Default directory not specified for " +
"load operation when allowConfigLoadOutsideDefault is false");
if (defaultConfigLoadPath != null) {
if (!defaultConfigLoadPath.isAbsolute())
throw new IllegalArgumentException("Relative paths not " +
"currently supported. The following setting is incorrect: " +
"\"" + defaultConfigLoadPath.getPath() + "\" selected for " +
"load operation");
if (defaultConfigLoadPath.isFile())
throw new IllegalArgumentException("Filename selected unexpectedly " +
"as a default directory: " +
"\"" + defaultConfigLoadPath.getPath() + "\" for " +
"load operation");
}
return defaultConfigLoadPath;
}
static File loadConfigFile(ContextManager cm, Component parent, UIFactory uif, String ext, String key) {
FileChooser fileChooser = new FileChooser(true);
fileChooser.addChoosableExtension(ext, uif.getI18NString("ce.jtiFiles" + key));
return loadConfigFile(cm, parent, uif, fileChooser);
}
/**
* Provides capabilities for configuration file loading. Method takes into
* account context settings relating to default locations for configuration
* files loading and behaves according to them.
* @param cm ContextManager
object defining current harness' context. The following methods
* affect this method functionality:
* getDefaultConfigLoadPath()
* getAllowConfigLoadOutsideDefault()
*
* @param parent A parent frame to be used for fileChooser
/warning dialogs
* @param uif The UIFactory used to for configuration file loading operation
* @param fileChooser The FileChooser
used for configuration file loading
* @return The configuration file selected by user if this file loading is allowed by
* harness' contest settings
* @see ContextManager#setDefaultConfigLoadPath(java.io.File)
* @see ContextManager#setAllowConfigLoadOutsideDefault(boolean state)
* @see ContextManager#getDefaultConfigLoadPath()
* @see ContextManager#getAllowConfigLoadOutsideDefault()
*/
static File loadConfigFile(ContextManager cm, Component parent, UIFactory uif, FileChooser fileChooser) {
if (cm == null)
return null;
File defaultConfigLoadPath = checkLoadConfigFileDefaults(cm);
boolean allowConfigLoadOutsideDefault = cm.getAllowConfigLoadOutsideDefault();
File file = null;
fileChooser.setDialogTitle(uif.getI18NString("ce.load.title"));
if (defaultConfigLoadPath != null) {
if (!allowConfigLoadOutsideDefault) {
if (!(new File(defaultConfigLoadPath.getAbsolutePath())).canRead()) {
uif.showError("ce.load.defDirNotExists", defaultConfigLoadPath);
return null;
}
fileChooser.enableDirectories(false);
} else
fileChooser.enableDirectories(true);
fileChooser.setCurrentDirectory(defaultConfigLoadPath);
}
boolean isMatch = true;
while (file == null) {
int rc = fileChooser.showDialog(parent, uif.getI18NString("ce.load.btn"));
if (rc != JFileChooser.APPROVE_OPTION)
return null;
file = fileChooser.getSelectedFile();
if (!allowConfigLoadOutsideDefault) {
if (defaultConfigLoadPath == null)
return null;
File f = new File(file.getAbsolutePath().substring(0, file.getAbsolutePath().lastIndexOf(File.separator)));
try {
isMatch = (f.getCanonicalPath().indexOf((defaultConfigLoadPath.getCanonicalPath())) == 0);
} catch ( IOException ioe) {
ioe.printStackTrace(System.err);
return null;
}
if (!isMatch) {
uif.showError("ce.load.notAllowedDir", defaultConfigLoadPath);
file = null;
fileChooser.setCurrentDirectory(defaultConfigLoadPath);
continue; // choose another file
}
}
}
if (file != null) {
String path = file.getPath();
String ext = fileChooser.getChosenExtension();
if (ext == null) {
ext = CONFIG_EXTENSION;
}
if (!path.endsWith(ext))
file = new File(path + ext);
}
return file;
}
/**
* Provides as the user with a dialog to chooser where to save a config. Method takes into account
* context settings relating to default locations for configuration files saving and behaves
* according to them.
* @param cm ContextManager
object defining current harness' context. The following methods
* affect this method functionality:
*
* getDefaultConfigSavePath()
* getAllowConfigSaveOutsideDefault()
*
* @param parent A parent frame to be used for fileChooser
/warning dialogs
* @param uif The UIFactory used to for configuration file saving operation
* @param fileChooser The FileChooser
used for configuration file saving
* @return The configuration file selected by user if this file saving is allowed by
* harness' contest settings
* @throws IllegalArgumentException
if the following configuration errors found:
*
* -
getDefaultConfigSavePath()
returns null
when getAllowConfigSaveOutsideDefault()
returns false
* -
getDefaultConfigSavePath()
returns not absolute path
* -
getDefaultConfigSavePath()
returns a file (not a directory)
*
* @see ContextManager#setDefaultConfigSavePath(java.io.File)
* @see ContextManager#setAllowConfigSaveOutsideDefault(boolean state)
* @see ContextManager#getDefaultConfigSavePath()
* @see ContextManager#getAllowConfigSaveOutsideDefault()
*/
static File saveConfigFile(ContextManager cm, Component parent, UIFactory uif, FileChooser fileChooser, File dir,
boolean isTemplate) {
if (cm == null)
return null;
File defaultSavePath;
if (isTemplate) {
defaultSavePath = cm.getDefaultTemplateSavePath();
} else {
defaultSavePath = cm.getDefaultConfigSavePath();
}
boolean allowSaveOutsideDefault;
if (isTemplate) {
allowSaveOutsideDefault = cm.getAllowTemplateSaveOutsideDefault();
} else {
allowSaveOutsideDefault = cm.getAllowConfigSaveOutsideDefault();
}
if (defaultSavePath == null && !allowSaveOutsideDefault)
throw new IllegalArgumentException("Default directory not specified for " +
"save operation when allowConfigSaveOutsideDefault is false");
if (defaultSavePath != null) {
if (!defaultSavePath.isAbsolute())
throw new IllegalArgumentException("Relative paths not " +
"currently supported. The following setting is incorrect: " +
"\"" + defaultSavePath.getPath() + "\" selected for " +
"save operation");
if (defaultSavePath.isFile())
throw new IllegalArgumentException("Filename selected unexpectedly " +
"as a default directory: " +
"\"" + defaultSavePath.getPath() + "\" for " +
"save operation");
if (!allowSaveOutsideDefault) {
if (!defaultSavePath.canWrite()) {
uif.showError("ce.save.defDirNotExists", defaultSavePath);
return null;
}
fileChooser.enableDirectories(false);
} else
fileChooser.enableDirectories(true);
fileChooser.setCurrentDirectory(defaultSavePath);
} else
if (dir != null)
fileChooser.setCurrentDirectory(dir);
File file = null;
boolean isMatch = true;
while (file == null) {
int rc = fileChooser.showDialog(parent, uif.getI18NString("ce.save.btn"));
if (rc != JFileChooser.APPROVE_OPTION)
// user has canceled or closed the chooser
return null;
file = fileChooser.getSelectedFile();
if (file == null) // just making sure
continue;
File f = new File(file.getAbsolutePath().substring(0, file.getAbsolutePath().lastIndexOf(File.separator)));
if (!allowSaveOutsideDefault) {
if (defaultSavePath == null)
return null;
try {
isMatch = defaultSavePath.getCanonicalPath().equals(f.getCanonicalPath());
} catch ( IOException ioe) {
ioe.printStackTrace(System.err);
return null;
}
if (!isMatch) {
uif.showError("ce.save.notAllowedDir", defaultSavePath);
file = null;
fileChooser.setCurrentDirectory(defaultSavePath);
continue; // choose another file
}
}
if (file.isDirectory()) {
uif.showError("ce.save.fileIsDir", file);
file = null;
continue; // choose another file
}
File parentFile = file.getParentFile();
if (parentFile != null) {
if (parentFile.exists() && !parentFile.isDirectory()) {
uif.showError("ce.save.parentNotADir", parentFile);
file = null;
continue; // choose another file
} else if (!parentFile.exists()) {
rc = uif.showYesNoDialog("ce.save.createParentDir",
parentFile);
if (rc == JOptionPane.YES_OPTION) {
if (!parentFile.mkdirs()) {
uif.showError("ce.save.cantCreateParentDir",
parentFile);
file = null;
continue; // choose another file
}
} else {
file = null;
continue; // choose another file
}
}
}
// if file exists, leave well enough alone;
// otherwise, make sure it ends with .jti or .jtm
if (!file.exists()) {
String path = file.getPath();
String ext = fileChooser.getChosenExtension();
if (ext != null && !path.endsWith(ext))
file = new File(path + ext);
}
// if file exists, make sure user wants to overwrite it
if (file.exists()) {
rc = uif.showYesNoDialog("ce.save.warn");
switch (rc) {
case JOptionPane.YES_OPTION:
break; // use this file
case JOptionPane.NO_OPTION:
fileChooser.setSelectedFile(null);
file = null;
continue; // choose another file
}
}
}
return file;
}
void setAfterCloseCommand(Runnable runnable) {
afterCloseCommand = runnable;
}
private Runnable afterCloseCommand;
/**
* Will be eliminated in the next release.
* @deprecated
*/
@Deprecated
protected boolean templateMode = false;
protected ContextManager contextManager = null;
protected InterviewParameters mainConfig;
protected InterviewParameters viewConfig;
private FileHistory history;
private boolean saveRequired;
//protected String key;
private boolean runPending;
private static boolean debug = Debug.getBoolean(InterviewEditor.class);
private JMenu recentConfigMenu;
private JMenu markerMenu;
private JMenu searchMenu;
private JMenu viewMenu;
private JRadioButtonMenuItem viewFullBtn;
private JRadioButtonMenuItem viewStdBtn;
private JCheckBoxMenuItem viewInfoCheckBox;
private JCheckBoxMenuItem viewTagCheckBox;
private JMenuItem viewRefreshItem;
private JComponent body;
private JPanel views;
private JHelpContentViewer infoPanel;
private CE_FullView fullView;
private CE_StdView stdView;
private CE_View currView;
private Listener listener;
//private TemplatesUI templatesUI;
private Map, QuestionRenderer> customRenderersMap;
private ActionListener closeListener;
//private ExecModel model;
private final List observers = new ArrayList();
private DetailsBrowser detailsBrowser;
private static final KeyStroke detailsKey = KeyStroke.getKeyStroke("shift alt D");
protected String ext;
// XXX this isn't the right class to define these in
// do not make more public than package private
static final String CONFIG_EXTENSION = ".jti";
static final String CONFIG_HISTORY = "configHistory.jtl";
private static final String NEW = "new";
private static final String LOAD = "load";
//private static final String NEWT = "newt";
//private static final String LOADT = "loadt";
private static final String SAVE = "save";
private static final String SAVE_AS = "saveAs";
private static final String REVERT = "revert";
private static final String DONE = "done";
private static final String REFRESH = "refresh";
private static final String DETAILS = "details";
static final String CLOSE = "close";
static final String MORE_INFO_PREF = "exec.config.moreInfo";
static final String VIEW_PREF = "exec.config.view";
public void setCustomRenderers(Map, QuestionRenderer> renderersMap) {
customRenderersMap = renderersMap;
if (fullView != null) {
fullView.setCustomRenderers(customRenderersMap);
}
}
private class Listener
implements ActionListener, ChangeListener, MenuListener
{
// ---------- from ActionListener -----------
public void actionPerformed(ActionEvent e) {
Object src = e.getSource();
if (src instanceof JMenuItem) {
JMenuItem mi = (JMenuItem) src;
File f = (File) (mi.getClientProperty(FileHistory.FILE));
if (f != null && askAndSave("ce.load.warn")) {
loadConfigFromFile(f);
return;
}
}
perform(e.getActionCommand());
}
// ---------- from ChangeListener -----------
public void stateChanged(ChangeEvent e) {
Object src = e.getSource();
if (src == viewInfoCheckBox && infoPanel != null)
setInfoVisible(viewInfoCheckBox.isSelected());
else if (src == viewTagCheckBox)
fullView.setTagVisible(viewTagCheckBox.isSelected());
}
// ---------- from MenuListener -----------
public void menuSelected(MenuEvent e) {
Object src = e.getSource();
if (src == viewMenu)
viewTagCheckBox.setSelected(fullView.isTagVisible());
}
public void menuDeselected(MenuEvent e) {
}
public void menuCanceled(MenuEvent e) {
}
};
/**
* For private communication with SessionControl, not for broadcast outside
* of core JT.
*/
public interface Observer {
/**
* Invoked when value of interview parameters has been changed
* @param p object with updated value (viewConfig)
*/
public void changed(InterviewParameters p);
/**
* Invoked when setVisible() method is invoked on InterviewEditor object
* @param isVisible argument passed to setVisible() method
* @param source editor that changed the state
*/
public void changedVisibility(boolean isVisible, InterviewEditor source);
}
}