/*******************************************************************************
* CogTool Copyright Notice and Distribution Terms
* CogTool 1.3, Copyright (c) 2005-2013 Carnegie Mellon University
* This software is distributed under the terms of the FSF Lesser
* Gnu Public License (see LGPL.txt).
*
* CogTool is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* CogTool 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CogTool; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* CogTool makes use of several third-party components, with the
* following notices:
*
* Eclipse SWT version 3.448
* Eclipse GEF Draw2D version 3.2.1
*
* Unless otherwise indicated, all Content made available by the Eclipse
* Foundation is provided to you under the terms and conditions of the Eclipse
* Public License Version 1.0 ("EPL"). A copy of the EPL is provided with this
* Content and is also available at http://www.eclipse.org/legal/epl-v10.html.
*
* CLISP version 2.38
*
* Copyright (c) Sam Steingold, Bruno Haible 2001-2006
* This software is distributed under the terms of the FSF Gnu Public License.
* See COPYRIGHT file in clisp installation folder for more information.
*
* ACT-R 6.0
*
* Copyright (c) 1998-2007 Dan Bothell, Mike Byrne, Christian Lebiere &
* John R Anderson.
* This software is distributed under the terms of the FSF Lesser
* Gnu Public License (see LGPL.txt).
*
* Apache Jakarta Commons-Lang 2.1
*
* This product contains software developed by the Apache Software Foundation
* (http://www.apache.org/)
*
* jopt-simple version 1.0
*
* Copyright (c) 2004-2013 Paul R. Holser, Jr.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Mozilla XULRunner 1.9.0.5
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/.
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The J2SE(TM) Java Runtime Environment version 5.0
*
* Copyright 2009 Sun Microsystems, Inc., 4150
* Network Circle, Santa Clara, California 95054, U.S.A. All
* rights reserved. U.S.
* See the LICENSE file in the jre folder for more information.
******************************************************************************/
package edu.cmu.cs.hcii.cogtool.ui;
import java.io.File;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.ColorDialog;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import edu.cmu.cs.hcii.cogtool.CogToolFileTypes;
import edu.cmu.cs.hcii.cogtool.model.Design;
import edu.cmu.cs.hcii.cogtool.model.DeviceType;
import edu.cmu.cs.hcii.cogtool.model.Project;
import edu.cmu.cs.hcii.cogtool.util.Cancelable;
import edu.cmu.cs.hcii.cogtool.util.DisplaySubprocessTrace;
import edu.cmu.cs.hcii.cogtool.util.EnableDisable;
import edu.cmu.cs.hcii.cogtool.util.ErrorDialog;
import edu.cmu.cs.hcii.cogtool.util.GraphicsUtil;
import edu.cmu.cs.hcii.cogtool.util.Keypad;
import edu.cmu.cs.hcii.cogtool.util.L10N;
import edu.cmu.cs.hcii.cogtool.util.ManagedText;
import edu.cmu.cs.hcii.cogtool.util.OSUtils;
import edu.cmu.cs.hcii.cogtool.util.Pausable;
import edu.cmu.cs.hcii.cogtool.util.PausableProgessBar;
import edu.cmu.cs.hcii.cogtool.util.RcvrUIException;
import edu.cmu.cs.hcii.cogtool.util.Stoppable;
import edu.cmu.cs.hcii.cogtool.util.StoppableProgressBar;
import edu.cmu.cs.hcii.cogtool.util.ThreadProgressBar;
import edu.cmu.cs.hcii.cogtool.util.WindowUtil;
import edu.cmu.cs.hcii.cogtool.view.PersistenceView;
import edu.cmu.cs.hcii.cogtool.view.View;
/**
* Default implementation of the standard user interactions for
* CogTool windows.
*
* @author mlh
*/
public class DefaultInteraction implements Interaction
{
/*
* List of commonly used strings. Stored statically here for more easily
* readable code later.
*/
protected static final String errorTitle =
L10N.get("DEFINT.Error",
"File Error");
protected static final String genericErrorTitle =
L10N.get("DEFINT.GenericError", "Error");
protected static final String unreadableFileMsg =
L10N.get("DEFINT.CannotReadFile",
"The selected file cannot be read.");
protected static final String invalidImageFileMsg =
L10N.get("DEFINT.InvalidImageFile",
"The selected file does not contain a valid image.");
protected static final String fileNotFoundMsg =
L10N.get("DEFINT.FileNotFound",
"File not found:") + " ";
protected static final String selectWebPageExportDialogTitle =
L10N.get("PM.webpageExportDialogTitle",
"Select a Directory to Export the Design Into");
protected static final String selectWebPageExportDialogMessage =
L10N.get("PM.webpageExportDialogMessage",
"The exported design will be added to this directory. " +
"Existing files with conflicting names will be replaced.");
protected static final String projectPropertiesTitle =
L10N.get("DI.ProjectPropertiesTitle", "Project Properties");
protected static final String buildVersionLabel =
L10N.get("DI.BuildVersionLabel",
"Project file last written by CogTool version");
protected static final String unknownBuildVersion =
L10N.get("DI.UnknownBuildVersion", "Unknown build version");
protected static final String unsavedProject =
L10N.get("DI.UnsavedProject", "Unsaved project");
protected static final String fileInUse =
L10N.get("DI.FileInUse",
"The file selected is being used by another project being edited:");
protected static final String containsObsoleteWait =
L10N.get("DI.ObsoleteWaits",
"The design(s) contain(s) an old form of system wait that is no longer supported, and so the script(s) cannot be computed. " +
"Please recast the design(s) with the system waits attached to transitions.");
protected static final String traceVersionTooOld =
L10N.get("DI.TraceVersionTooOld",
"The version of CogTool used to compute this task was too old for this version to visualize. Please recompute the task.");
protected static final String traceVersionTooNew =
L10N.get("DI.TraceVersionTooNew",
"The version of CogTool used to compute this task was newer than this version, and the result cannot be visualized by this version. Please recompute the task.");
protected static final String nameCollisionMsg =
L10N.get("FE.WidgetNameCollision",
"The name you have chosen is already in use.\n" +
"Please choose a different name.");
protected static final String nameEmptyMsg =
L10N.get("FE.WidgetNameEmpty",
"A name cannot be empty.\n" +
"Please provide a name.");
protected View view;
protected Shell window;
protected PersistenceView persistView;
protected int csvNum = 1;
/**
* Initialize interaction with the given window that pop-up dialog
* boxes are modal to. Also initializes the CogTool functionality
* for requesting file names for opening and saving projects.
*
* @param win target window for dialog box modality
* @author mlh
*/
public DefaultInteraction(View v)
{
view = v;
window = v.getShell();
persistView = new PersistenceView(window);
}
/**
* Report a warning in a pop-up window with an 'ok' button.
*
* @param title the window title
* @param msg the message reporting the warning
* @author mlh
*/
public void reportWarning(String title, String msg)
{
try {
WindowUtil.presentWarningDialog(window, title, msg);
}
catch (SWTException ex) {
throw new RcvrUIException("Creation of dialog box failed.", ex);
}
}
/**
* Report a set of warnings in a pop-up window with an 'ok' button.
*
* @param title the window title
* @param messages the messages reporting the warnings
* @author mlh
*/
public void reportWarnings(String title, List<String> messages)
{
StringBuilder msgBuffer = new StringBuilder();
String separator = "";
Iterator<String> warnings = messages.iterator();
while (warnings.hasNext()) {
msgBuffer.append(separator);
msgBuffer.append(warnings.next());
separator = "\n";
}
reportWarning(title, msgBuffer.toString());
}
/**
* Report a problem in a pop-up window with an 'ok' button.
*
* @param title the window title
* @param msg the message reporting the problem
* @author mlh
*/
public void reportProblem(String title, String msg)
{
try {
WindowUtil.presentErrorDialog(window, title, msg);
}
catch (SWTException ex) {
throw new RcvrUIException("Creation of dialog box failed.", ex);
}
}
/**
* Report a set of problems in a pop-up window with an 'ok' button.
*
* @param title the window title
* @param messages the messages reporting the problems
* @author mlh
*/
public void reportProblems(String title, List<String> messages)
{
StringBuilder msgBuffer = new StringBuilder();
String separator = "";
Iterator<String> problems = messages.iterator();
while (problems.hasNext()) {
msgBuffer.append(separator);
msgBuffer.append(problems.next());
separator = "\n";
}
reportProblem(title, msgBuffer.toString());
}
/**
* Report a problem in a pop-up window and allow the user to either
* retry or cancel the affected operation; thus, buttons include
* 'retry' and 'cancel'.
*
* @param title the window title
* @param msg the message reporting the problem
* @return true if retry was selected, false if cancel was selected
* @author mlh
*/
public boolean reportAndRetry(String title, String msg)
{
return SWT.RETRY == WindowUtil.presentErrorAbortDialog(window,
title,
msg);
}
/**
* Request one or more files from the user in a pop-up dialog box.
*
* @return an array of abstract file descriptions representing the selected
* files
* @author mlh
*/
public File[] selectFileSources()
{
return persistView.selectFileSources();
}
/**
* Request a location to save a CogTool project.
*
* @param projectName the name of the project being saved, which
* acts as part of the initial file name
* @return the abstract file description of where the project should
* be saved
* @author mlh
*/
public File selectFileDest(String projectName)
{
// Specify the extension for projects.
return selectExportLocation(projectName,
CogToolFileTypes.COGTOOL_FILE_EXT);
}
protected static String OVERWRITE = L10N.get("DI.Overwrite", "Overwrite");
protected static String SELECT_AGAIN =
L10N.get("DI.SelectAgain", "Select another file");
protected static String CANCEL_SAVE = L10N.get("DI.Cancel", "Cancel");
protected class BeingEditedDialog extends WindowUtil.SimpleDialog
{
protected File destination;
public BeingEditedDialog(File saveFileAttempt)
{
super(window, errorTitle, SWT.PRIMARY_MODAL, SWT.DIALOG_TRIM);
destination = saveFileAttempt;
}
protected void addLabel(String text, int span, int width)
{
Label element = new Label(dialog, SWT.NONE);
if (text != null) {
element.setText(text);
if (textFont != null) {
element.setFont(textFont);
}
}
GridData eltLayout = new GridData();
if (span > 0) {
eltLayout.grabExcessHorizontalSpace = true;
eltLayout.horizontalSpan = span;
}
else {
eltLayout.widthHint = width;
}
element.setLayoutData(eltLayout);
}
protected void addButton(final String label)
{
Button nextButton = new Button(dialog, SWT.PUSH);
nextButton.setText(label);
if (buttonFont != null) {
nextButton.setFont(buttonFont);
}
GridData buttonLayout =
new GridData(GridData.HORIZONTAL_ALIGN_END);
buttonLayout.horizontalSpan = 2;
nextButton.setLayoutData(buttonLayout);
nextButton.addListener(SWT.Selection,
new Listener() {
public void handleEvent(Event evt)
{
userResponse = label;
dialog.close();
}
});
}
@Override
protected void buildDialog()
{
// Four columns
GridLayout layout = new GridLayout(8, false);
layout.marginLeft = 19;
layout.marginRight = 13;
layout.marginTop = 7;
layout.marginBottom = 12;
dialog.setLayout(layout);
addLabel(fileInUse, 8, 0);
addLabel(null, 0, 35);
addLabel(destination.getName(), 7, 0);
addLabel(null, 8, 0);
addLabel(null, 0, 35);
// Add "Overwrite" button
addButton(OVERWRITE);
addButton(SELECT_AGAIN);
addButton(CANCEL_SAVE);
}
}
/**
* Protest that the specified file is currently open for a different
* project.
*
* @param saveFileAttempt the file selected by the user to save a project
* that happens to already be in use for a different
* project being edited
* @return NO_SAVE if retry was selected, SAVE if overwrite was selected,
* and CANCEL if cancel was selected
*/
public int protestBeingEdited(File saveFileAttempt)
{
BeingEditedDialog dialog = new BeingEditedDialog(saveFileAttempt);
Object response = dialog.open();
if (response == OVERWRITE) {
return SAVE;
}
if (response == SELECT_AGAIN) {
return NO_SAVE;
}
return CANCEL; // must be CANCEL_SAVE
}
/**
* Ask the user to save a modified project having the given name
* before actually closing it, especially during application exit.
*
* @param projectName the name of the project to be saved, which
* acts as part of the initial file name
* @return the code indicating the user's decision (one of
* <code>SAVE</code>, <code>NO_SAVE</code>, or <code>CANCEL</code>)
* @author mlh
*/
public int askSaveBeforeClose(String projectName)
{
MessageBox saveBeforeCloseDialog =
WindowUtil.createYesNoCancelDialog
(window,
L10N.get("PM.SaveBeforeCloseTitle",
"Save Changes?"),
L10N.get("PM.SaveBeforeClose",
"Save project before closing?\n"
+ "Unsaved changes in project:"
) + "\n " + projectName);
switch (saveBeforeCloseDialog.open()) {
case SWT.YES: {
return SAVE;
}
case SWT.NO: {
return NO_SAVE;
}
case SWT.CANCEL: {
return CANCEL;
}
}
return CANCEL; // TODO: exception instead; should never get here
}
/**
* The user has tried to open a file that is already open and modified, so
* ask if they want to revert to the saved version
*
* @param projectName the name of the project in question
* @return the code indicating the user's decision (either
* <code>SWT.OK</code> or <code>SWT.CANCEL</code>\)
* @author rmyers
*/
public boolean askRevertBeforeOpen(String projectName)
{
MessageBox revertBeforeOpenDialog =
WindowUtil.createConfirmDialog
(window,
L10N.get("PM.RevertBeforeOpenTitle",
"Revert?"),
L10N.get("PM.RevertBeforeOpen",
projectName + " " + "is already open "
+ "and has been modified since "
+ "it was last saved.\n"
+ "Revert changes?"));
return revertBeforeOpenDialog.open() == SWT.OK;
}
/**
* Open up a widget color wheel with the specified title.
* Should be platform specific.
* the specified integer should be in the following format.
* lowest order 8 bits red
* middle order 8 bits green
* highest order 8 bits blue
* ie: xxxxxxxx xxxxxxxx xxxxxxxx
* BLUE GREEN RED
*/
public Integer selectColor(int oldColor, String dialogTitle)
{
RGB old = GraphicsUtil.getRGBFromColor(oldColor);
// Open the platform specific color chooser
ColorDialog dialog = new ColorDialog(window);
dialog.setRGB(old);
if (dialogTitle != null)
{
dialog.setText(dialogTitle);
}
RGB newColor = dialog.open();
if (newColor != null) {
return new Integer(GraphicsUtil.getColorFromRGB(newColor));
}
return null;
}
/**
* Open up a widget color wheel.
* Should be platfrom specific.
* the specfied integer should be in the following format.
* lowest order 8 bits red
* middle order 8 bits green
* highest order 8 bits blue
* ie: xxxxxxxx xxxxxxxx xxxxxxxx
* BLUE GREEN RED
*/
public Integer selectColor(int oldColor)
{
return selectColor(oldColor, null);
}
/**
* Open up a widget color wheel.
* Should be platorm specific.
*/
public Integer selectColor()
{
return selectColor(0);
}
/**
* A file IO error: The selected file was unreadable
*/
public void protestUnreadableFile()
{
reportProblem(errorTitle, unreadableFileMsg);
}
/**
* The selected file is not a supported image type
*/
public void protestInvalidImageFile()
{
reportProblem(errorTitle, invalidImageFileMsg);
}
public void protestFileNotFound(String file)
{
reportProblem(errorTitle, fileNotFoundMsg + file);
}
/**
* Returns null if canceled.
*/
protected FileDialog selectFileName(int mode,
String[] filterNames,
String[] extensions)
{
return selectFileName(mode, filterNames, extensions, null);
}
protected FileDialog selectFileName(int mode,
String[] filterNames,
String[] extensions,
String def)
{
FileDialog dialog = new FileDialog(window, mode);
if (def != null) {
dialog.setFileName(def);
}
dialog.setFilterNames(filterNames);
// Windows wild cards
dialog.setFilterExtensions(extensions);
if (dialog.open() != null) {
return dialog;
}
return null; // canceled!
}
/**
* A file selection dialog for selecting images.
* It filters the list based on JPG, GIF, PNG, BMP and ICO files
* An option is also available to show all.
*/
public String selectImageFile()
{
// TODO: provide an "All Images" option when supported by SWT
FileDialog dialog =
selectFileName(SWT.OPEN,
new String[] { "All Readable (*.jpg;*.gif;*.png;*.bmp;*.ico)",
"JPG Images (*.jpg)",
"GIF Images (*.gif)",
"PNG Images (*.png)",
"BMP Images (*.bmp)",
"ICO Icon Files (*.ico)",
"All Files (*.*)" },
new String[] { "*.jpg;*.gif;*.png;*.bmp;*.ico",
"*.jpg",
"*.gif",
"*.png",
"*.bmp",
"*.ico",
"*.*" });
if (dialog != null) {
return dialog.getFilterPath() + File.separator
+ dialog.getFileName();
}
return null;
}
/**
* Selects a csv file
*
* @see edu.cmu.cs.hcii.cogtool.ui.Interaction#selectCSVFile(java.lang.String)
*/
public File selectCSVFile()
{
FileDialog dialog =
selectFileName(SWT.OPEN,
new String[] { "CSV Files (*.csv)" },
new String[] { "*.csv" });
if (dialog == null) {
return null;
}
File path = new File(dialog.getFilterPath());
return new File(path, dialog.getFileName());
}
public File selectXMLFile(boolean forInput, String def)
{
FileDialog dialog =
selectFileName(forInput ? SWT.OPEN : SWT.SAVE,
new String[] { "XML Files (*.xml)" },
new String[] { "*.xml"},
def);
if (dialog == null) {
return null;
}
File path = new File(dialog.getFilterPath());
return new File(path, dialog.getFileName());
}
public File selectFile(boolean forInput, String def, String[] allowedExtensions)
{
FileDialog dialog =
selectFileName(forInput ? SWT.OPEN : SWT.SAVE,
new String[] { "XML Files (*.xml)" },
allowedExtensions,
def);
if (dialog == null) {
return null;
}
File path = new File(dialog.getFilterPath());
return new File(path, dialog.getFileName());
}
protected static final String exportCanceledMsg =
L10N.get("DEFINT.ExportCanceled", "Export canceled.");
/**
* A file selection dialog for selecting .csv files
* to write to.
*/
public File selectCSVFileDest(String defaultFileName)
{
FileDialog dialog =
selectFileName(SWT.SAVE,
new String[] { "CSV Files (*.csv)" },
new String[] { "*.csv" },
(defaultFileName + ".csv"));
if (dialog == null) {
setStatusMessage(exportCanceledMsg);
return null;
}
File path = new File(dialog.getFilterPath());
String name = dialog.getFileName();
if (! name.endsWith(".csv")) {
name += ".csv";
}
File dest = new File(path, name);
if (dest.exists() && OSUtils.WINDOWS) {
int choice = shouldReplaceFile(dest);
if (choice == SWT.NO) {
return selectCSVFileDest(defaultFileName);
}
if (choice == SWT.CANCEL) {
setStatusMessage(exportCanceledMsg);
return null;
}
}
return dest;
}
/**
* Request a location to store result trace lines, etc.
*
* @return the abstract file location for storing some kind of result
* @author mlh/alex
*/
public File selectExportLocation(String defaultFilename,
String defaultExtension)
{
return persistView.selectFileDest(defaultFilename,
defaultExtension);
}
protected int shouldReplaceFile(File file)
{
MessageBox replaceBox =
new MessageBox(window, SWT.YES | SWT.NO | SWT.CANCEL
| SWT.ICON_QUESTION | SWT.PRIMARY_MODAL);
replaceBox.setMessage(L10N.get("DEFINT.ReplaceFile",
"File exists. Replace file?\n")
+ file.getAbsolutePath());
return replaceBox.open();
}
// TODO This whole mishmash of different flavors of progress bars, and
// Cancelables/Stoppables/Pausables is a mess. We shouldn't be using
// the type hierarchy to fiddle this stuff. Instead we should have a
// single interface for control of a background operation, subsuming
// all of Cancelable, Stoppable and Pausable, and a single ProgressBar
// type that takes a bunch of flags indicating what buttons it should
// display.
/**
* Provide a visual progress bar, optionally with a "Cancel" button and
* optionally with space for status text.
*
* @param windowTitle the window title for the dialog box
* @param cancelable the "process" that may be canceled using the cancel
* button; no cancel button if <code>null</code>
* @param initialStatusText if not null, the initial text to display as
* status; no status text if <code>null</code>
* @param style true for SMOOTH and false for INDETERMINATE
*/
public ProgressBar createProgressBar(String windowTitle,
Cancelable cancelable,
String initialStatusText,
boolean style)
{
return createProgressBar(windowTitle,
null,
null,
cancelable,
initialStatusText,
style,
null);
}
/**
* Provide a visual progress bar, optionally with a "Cancel" button and
* optionally with space for status text.
*
* @param windowTitle the window title for the dialog box
* @param cancelable the "process" that may be canceled using the cancel
* button; no cancel button if <code>null</code>
* @param initialStatusText if not null, the initial text to display as
* status; no status text if <code>null</code>
* @param style true for SMOOTH and false for INDETERMINATE
* @param ratio the ratio desired for inserting an ellipsis into the
* status string (see StringUtil constants)
*/
public ProgressBar createProgressBar(String windowTitle,
Cancelable cancelable,
String initialStatusText,
boolean style,
double ratio)
{
return createProgressBar(windowTitle,
null,
null,
cancelable,
initialStatusText,
style,
null,
ratio);
}
public ProgressBar createProgressBar(String windowTitle,
Pausable pausable,
Stoppable stoppable,
Cancelable cancelable,
String initialStatusText,
boolean style,
double ratio)
{
return createProgressBar(windowTitle,
pausable,
stoppable,
cancelable,
initialStatusText,
style,
null,
ratio);
}
/**
* Provide a visual progress bar, optionally with a "Cancel" button and
* optionally with space for status text.
* <p>
* IMPORTANT: Caller is responsible for invoking dispose() on the
* returned value
*
* @param windowTitle the window title for the dialog box
* @param pausable the "process" may be paused using the pause button;
* none if <code>null</code>
* @param cancelable the "process" that may be canceled using the cancel
* button; no cancel button if <code>null</code>
* @param initialStatusText if not null, the initial text to display as
* status; no status text if <code>null</code>
* @param style true for SMOOTH and false for INDETERMINATE
* @param win which window (if any) to be "modal" behavior against
*/
public ProgressBar createProgressBar(String windowTitle,
Pausable pausable,
Stoppable stoppable,
Cancelable cancelable,
String initialStatusText,
boolean style,
Shell win)
{
return createProgressBar(windowTitle,
pausable,
stoppable,
cancelable,
initialStatusText,
style,
win,
-1.0);
}
public ProgressBar createProgressBar(String windowTitle,
Pausable pausable,
Stoppable stoppable,
Cancelable cancelable,
String initialStatusText,
boolean style,
Shell win,
double ratio)
{
if (pausable != null && stoppable != null) {
throw new IllegalStateException(
"ProgressBars that are both Pausable and Stoppable are not implemented");
}
ThreadProgressBar pb = null;
if (pausable != null) {
pb = new PausableProgessBar(win,
windowTitle,
pausable,
cancelable,
initialStatusText,
style ? SWT.SMOOTH
: SWT.INDETERMINATE);
} else if (stoppable != null) {
pb = new StoppableProgressBar(win,
windowTitle,
stoppable,
cancelable,
initialStatusText,
style ? SWT.SMOOTH
: SWT.INDETERMINATE);
} else {
pb = new ThreadProgressBar(win,
windowTitle,
cancelable,
initialStatusText,
style ? SWT.SMOOTH : SWT.INDETERMINATE,
ratio);
}
final ThreadProgressBar progressBar = pb;
progressBar.buildAndDisplay();
return new ProgressBar()
{
public void dispose()
{
progressBar.dispose();
}
public EnableDisable getDisabler()
{
return progressBar.getDisabler();
}
public void updateProgress(double progress, String status)
{
progressBar.updateProgress(progress, status);
}
};
}
/**
* Provide a trace window, optionally with a "Cancel" button and
* optionally with space for status text.
* <p>
* IMPORTANT: Caller is responsible for invoking dispose() on the
* returned value
*
* @param windowTitle the window title for the dialog box
* @param cancelable the "process" that may be canceled using the cancel
* button; no cancel button if <code>null</code>
* @param initialStatusText if not null, the initial text to display as
* status; no status text if <code>null</code>
*/
public ITraceWindow createTraceWindow(String windowTitle,
Cancelable cancelable,
String initialStatusText)
{
return createTraceWindow(windowTitle, cancelable, initialStatusText,
null);
}
public ITraceWindow createTraceWindow(String windowTitle,
Cancelable cancelable,
String initialStatusText,
Shell parentWindow)
{
final DisplaySubprocessTrace traceWindow =
new DisplaySubprocessTrace(parentWindow,
windowTitle,
900,
700,
initialStatusText,
cancelable);
traceWindow.display();
return new ITraceWindow() {
public EnableDisable getDisabler()
{
return traceWindow.getDisabler();
}
public void updateProgress(double progress, String status)
{
traceWindow.updateProgress(progress, status);
}
public void dispose()
{
traceWindow.dispose();
}
public void appendOutputLine(String outputLine)
{
traceWindow.appendOutputLine(outputLine);
}
public void appendOutputLines(List<String> outputLines)
{
traceWindow.appendOutputLines(outputLines);
}
public void appendErrorLine(String errorLine)
{
traceWindow.appendErrorLine(errorLine);
}
public void appendErrorLines(List<String> errorLines)
{
traceWindow.appendErrorLines(errorLines);
}
public void scrollToTop()
{
traceWindow.scrollToTop();
}
};
}
/**
* Creates a directory chooser dialog, with the appropriate messages
* about what the user should be trying to do.
*/
public String askUserForDirectory()
{
return askUserForDirectory(selectWebPageExportDialogTitle,selectWebPageExportDialogMessage);
}
/**TODO
* Creates a directory chooser dialog, with the appropriate messages
* about what the user should be trying to do.
*/
public String askUserForDirectory(String title, String message)
{
DirectoryDialog dialog = new DirectoryDialog(window);
dialog.setText(title);
dialog.setMessage(message);
return dialog.open();
}
/**
* Pop a dialog box with the given project's properties (e.g., name and
* build version).
*
* @param project the project whose properties to display
*/
public void showProjectProperties(Project project)
{
String buildVersion = project.getBuildVersion();
if (buildVersion == Project.NOT_YET_SAVED) {
buildVersion = unsavedProject;
}
else if (buildVersion == Project.UNKNOWN_BUILD_VERSION) {
buildVersion = unknownBuildVersion;
}
// TODO the following won't localize cleanly!
WindowUtil.presentInformationDialog(window,
projectPropertiesTitle
+ ": "
+ project.getName(),
buildVersionLabel
+ ": "
+ buildVersion);
}
public void setStatusMessage(String message)
{
view.setStatusMessage(message);
}
public void setStatusMessage(String message, int duration)
{
view.setStatusMessage(message, duration);
}
public File selectDirectory(String message)
{
DirectoryDialog saveDialog = new DirectoryDialog(window);
saveDialog.setMessage(message);
String path = saveDialog.open();
if (path != null) {
return new File(path);
}
return null;
}
public void protestObsoleteWaits()
{
reportProblem(genericErrorTitle, containsObsoleteWait);
}
public void protestTraceVersion(boolean tooOld)
{
if (tooOld) {
reportProblem(genericErrorTitle, traceVersionTooOld);
} else {
reportProblem(genericErrorTitle, traceVersionTooNew);
}
}
/* (non-Javadoc)
* @see edu.cmu.cs.hcii.cogtool.ui.IInteraction#reportException(java.lang.String, java.lang.String, java.lang.Exception)
*/
public void reportException(String title, String msg, Exception e)
{
ErrorDialog dlog = new ErrorDialog(title, msg, e);
dlog.open();
}
public boolean editPreferences()
{
PreferencesDialog dialog = new PreferencesDialog(window, this);
return dialog.open() != null;
}
protected static final Color WHITE =
WindowUtil.GLOBAL_DISPLAY.getSystemColor(SWT.COLOR_WHITE);
/**
* Dialog box specific to soliciting information for a design
* (name, device type set).
*
* @author afaaborg
*/
public static class NewDesignDialog extends WindowUtil.CustomDialog
{
protected static final String NEW_DESIGN_FOR_TITLE =
L10N.get("PI.NewDesignForTitle", "New Design for");
protected static final String NEW_DESIGN_TITLE =
L10N.get("PI.NewDesignTitle", "New Design");
protected static final String ADD_DEVICES_TITLE =
L10N.get("PI.AddDevicesTitle", "Add Devices to Design");
protected static final String CALLED_UNTIL_SAVED_MSG =
L10N.get("PI.CalledUntilSavedMsg",
"Until you save it, the new project will be called:");
protected static final String PLEASE_ENTER_NAME_MSG =
L10N.get("PI.PleaseEnterNameMsg",
"Because each project must have at least one design,\nplease enter the name of an initial design:");
protected static final String NAME_FOR_NEW_DESIGN =
L10N.get("PI.NameForNewDesign", "Name for the new design") + ":";
protected static final String ACTION_CANNOT_BE_UNDONE =
L10N.get("PI.ActionCannotBeUndone",
"Please note that adding devices cannot be undone.");
protected static final String DESIGN_NAME_FOR_ADDED_DEVICES =
L10N.get("PI.DesignNameForAddedDevices",
"Design to which to add new devices") + ":";
protected static final String INPUT_DEVICES_TO_USE =
L10N.get("PI.InputDevicesToUse",
"Input devices used in this design (at least one must be selected):");
protected static final String INPUT_DEVICES_TO_ADD =
L10N.get("PI.InputDevicesToAdd",
"Input devices to add to this design:");
protected static final String OUTPUT_DEVICES_TO_USE =
L10N.get("PI.OutputDevicesToUse",
"Output devices used in this design (at least one must be selected):");
protected static final String OUTPUT_DEVICES_TO_ADD =
L10N.get("PI.OutputDevicesToAdd",
"Output devices to add to this design:");
protected DesignRequestData requestData;
protected boolean newProject;
protected Project userProject;
protected Design existingDesign;
//user input
protected ManagedText designNameInput;
protected Composite inputDeviceComposite;
protected Composite outputDeviceComposite;
protected String promptResponse;
protected boolean importDialog;
/**
* Initialize dialog box modal to the given parent window
* and with initial values specified by the given design request data.
*
* @param parentWin window the dialog box should be modal to
* @param data the initial values for the design information
* @author afaaborg
*/
public NewDesignDialog(Shell parentWin,
DesignRequestData data,
boolean newProjectMode,
Project project)
{
super(parentWin,
newProjectMode
? (NEW_DESIGN_FOR_TITLE + " " + project.getName())
: NEW_DESIGN_TITLE,
SWT.PRIMARY_MODAL);
newProject = newProjectMode;
userProject = project;
existingDesign = null;
requestData = data;
//Set default devices:
//Iterate through all of the designs in the project and
//see if they have the same set of devices. If so, default to
//that set of devices
List<Design> designs = userProject.getDesigns();
Set<DeviceType> initialDevices = new HashSet<DeviceType>();
boolean defaultDevices = true;
for (int i = 0; i < designs.size(); i++) {
Design design = designs.get(i);
Set<DeviceType> devices = design.getDeviceTypes();
// The devices set to compare future device sets against
if (i == 0) {
initialDevices.addAll(devices);
}
else {
// Compare this set to the first
if (! devices.equals(initialDevices)) {
defaultDevices = false;
}
}
}
if (defaultDevices) {
requestData.deviceTypes = initialDevices;
}
}
public NewDesignDialog(Shell parentWin,
DesignRequestData data,
boolean newProjectMode,
Project project, boolean changeDesignName)
{
super(parentWin,
newProjectMode
? (NEW_DESIGN_FOR_TITLE + " " + project.getName())
: NEW_DESIGN_TITLE,
SWT.PRIMARY_MODAL);
newProject = newProjectMode;
userProject = project;
existingDesign = null;
requestData = data;
importDialog = true;
}
public NewDesignDialog(Shell parentWin,
DesignRequestData data,
Project project,
Design design)
{
super(parentWin, ADD_DEVICES_TITLE, SWT.PRIMARY_MODAL);
requestData = data;
newProject = false;
existingDesign = design;
userProject = project;
// The deviceTypes in requestData get the known devices from design
requestData.deviceTypes = new HashSet<DeviceType>(design.getDeviceTypes());
requestData.designName = design.getName();
}
public String getPromptResponse()
{
return promptResponse;
}
@Override
protected void onOK()
{
promptResponse = designNameInput.getText();
requestData.designName = promptResponse;
super.onOK();
}
protected Composite createUI()
{
Composite userInterface = new Composite(dialog, SWT.NONE);
userInterface.setLayout(new FormLayout());
Composite newDesignUI;
Composite newProjectUI;
FormData formData;
if (newProject) {
newProjectUI = new Composite(userInterface, SWT.NONE);
newProjectUI.setLayout(new FillLayout(SWT.VERTICAL));
formData = new FormData();
formData.right = new FormAttachment(100, 0);
formData.left = new FormAttachment(0, 0);
newProjectUI.setLayoutData(formData);
Label initialDesignLabel = new Label(newProjectUI, SWT.WRAP);
//initialDesignLabel.setText("This initial design will be added to your new project");
initialDesignLabel.setText(CALLED_UNTIL_SAVED_MSG + " '"
+ userProject.getName()
+ "'");
newDesignUI = new Composite(userInterface, SWT.NONE);
newDesignUI.setLayout(new FormLayout());
formData = new FormData();
formData.bottom = new FormAttachment(100, 0);
formData.right = new FormAttachment(100, 0);
formData.left = new FormAttachment(0, 0);
formData.top = new FormAttachment(newProjectUI, 30, SWT.TOP);
newDesignUI.setLayoutData(formData);
}
else {
newDesignUI = new Composite(userInterface, SWT.NONE);
newDesignUI.setLayout(new FormLayout());
formData = new FormData();
formData.bottom = new FormAttachment(100, 0);
formData.right = new FormAttachment(100, 0);
formData.left = new FormAttachment(0, 0);
newDesignUI.setLayoutData(formData);
}
Label designNameLabel = new Label(newDesignUI, SWT.NONE);
formData = new FormData();
formData.top = new FormAttachment(0, 0);
formData.left = new FormAttachment(0, 0);
designNameLabel.setLayoutData(formData);
if (newProject) {
designNameLabel.setText(PLEASE_ENTER_NAME_MSG);
}
else if (existingDesign == null) {
designNameLabel.setText(NAME_FOR_NEW_DESIGN);
}
else {
designNameLabel.setText(ACTION_CANNOT_BE_UNDONE
+ "\n"
+ DESIGN_NAME_FOR_ADDED_DEVICES);
}
int designNameStyle = SWT.BORDER;
if (existingDesign != null) {
designNameStyle |= SWT.READ_ONLY;
}
//Code in order to disable the input text
/* if(importDialog && requestData.designName != null)
{
designNameStyle |= SWT.READ_ONLY;
}*/
designNameInput = new ManagedText(newDesignUI,
designNameStyle,
Keypad.FULL_KEYPAD)
{
@Override
protected void onModify()
{
enableOKButtonCheck();
}
@Override
public void selectAll()
{
if (existingDesign == null) {
super.selectAll();
}
}
};
if (existingDesign != null) {
// this.designNameInput.setEnabled(false);
designNameInput.setBackground(WHITE);
}
if(importDialog && requestData.designName != null)
{
//this.designNameInput.setEnabled(false);
designNameInput.setText(requestData.designName);
}
formData = new FormData();
formData.right = new FormAttachment(100, -20);
formData.top =
new FormAttachment(designNameLabel, 10, SWT.DEFAULT);
//formData_4.left = new FormAttachment(0, 80);
formData.left = new FormAttachment(0, 20);
formData.width = 200;
designNameInput.setLayoutData(formData);
Label devicesLabel = new Label(newDesignUI, SWT.NONE);
devicesLabel.setText((existingDesign == null)
? INPUT_DEVICES_TO_USE
: INPUT_DEVICES_TO_ADD);
formData = new FormData();
formData.top =
new FormAttachment(designNameInput.getOuter(), 32, SWT.DEFAULT);
formData.left = new FormAttachment(0, 0);
devicesLabel.setLayoutData(formData);
inputDeviceComposite = new Composite(newDesignUI, SWT.BORDER);
inputDeviceComposite.setLayout(new GridLayout());
formData = new FormData();
formData.right = new FormAttachment(100, -20);
formData.top = new FormAttachment(devicesLabel, 10, SWT.DEFAULT);
formData.left = new FormAttachment(0, 20);
inputDeviceComposite.setLayoutData(formData);
devicesLabel = new Label(newDesignUI, SWT.NONE);
devicesLabel.setText((existingDesign == null)
? OUTPUT_DEVICES_TO_USE
: OUTPUT_DEVICES_TO_ADD);
formData = new FormData();
formData.top =
new FormAttachment(inputDeviceComposite, 15, SWT.DEFAULT);
formData.left = new FormAttachment(0, 0);
devicesLabel.setLayoutData(formData);
outputDeviceComposite = new Composite(newDesignUI, SWT.BORDER);
outputDeviceComposite.setLayout(new GridLayout());
formData = new FormData();
formData.bottom = new FormAttachment(100, -10);
formData.right = new FormAttachment(100, -20);
formData.top = new FormAttachment(devicesLabel, 10, SWT.DEFAULT);
formData.left = new FormAttachment(0, 20);
outputDeviceComposite.setLayoutData(formData);
GridData g = new GridData();
g.horizontalSpan = 4;
userInterface.setLayoutData(g);
// Set default design name
if (existingDesign != null) {
designNameInput.setText(existingDesign.getName());
}
else if(importDialog) {
designNameInput.setText(requestData.designName);
}
else {
int designCount = userProject.getDesigns().size() + 1;
designNameInput.setText("Design " + designCount);
// Select the text of the design name so that it is easy to change
designNameInput.selectAll();
}
return userInterface;
} // createUI
/**
* Add a device checkbox to the table
* @param table the table to add the device to
* @param dt the device
* @param isSelected if the device is selected
* @param optionListener the action listener to associate with the device
*/
protected void addDevice(DeviceType dt,
boolean isSelected,
SelectionListener action)
{
Composite parent = (dt.getNature() == DeviceType.OUTPUT_ONLY)
? outputDeviceComposite
: inputDeviceComposite;
Button button = new Button(parent, SWT.CHECK);
button.setSelection((dt == DeviceType.Display) || isSelected);
button.setData(dt);
button.setText(dt.toString());
button.addSelectionListener(action);
if(importDialog && isSelected)
{
button.setEnabled(false);
}
if ((dt == DeviceType.Display) ||
(isSelected && (existingDesign != null)))
{
button.setEnabled(false);
}
}
/**
* Maintain the request's set of selected device types;
* toggle whether the set includes the given type.
*
* @param devType the device type to toggle set presence of
*/
protected void toggleDeviceType(DeviceType devType)
{
if (requestData.deviceTypes.contains(devType)) {
requestData.deviceTypes.remove(devType);
}
else {
requestData.deviceTypes.add(devType);
}
enableOKButtonCheck();
}
/**
* Populate the dialog box.
*/
@Override
protected void addMoreFields()
{
super.addMoreFields();
createUI();
addDevice(DeviceType.Keyboard,
requestData.deviceTypes.contains(DeviceType.Keyboard),
new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0)
{
toggleDeviceType(DeviceType.Keyboard);
}
});
addDevice(DeviceType.Mouse,
requestData.deviceTypes.contains(DeviceType.Mouse),
new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent evt)
{
toggleDeviceType(DeviceType.Mouse);
}
});
addDevice(DeviceType.Touchscreen,
requestData.deviceTypes.contains(DeviceType.Touchscreen),
new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent evt)
{
toggleDeviceType(DeviceType.Touchscreen);
}
});
addDevice(DeviceType.Voice,
requestData.deviceTypes.contains(DeviceType.Voice),
new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent evt)
{
toggleDeviceType(DeviceType.Voice);
}
});
// addDevice(DeviceType.Physical,
// this.requestData.deviceTypes.contains(DeviceType.Physical),
// new SelectionAdapter() {
// public void widgetSelected(SelectionEvent evt)
// {
// toggleDeviceType(DeviceType.Physical);
// }
// });
addDevice(DeviceType.Display,
requestData.deviceTypes.contains(DeviceType.Display),
new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent evt)
{
toggleDeviceType(DeviceType.Display);
}
});
addDevice(DeviceType.Speaker,
requestData.deviceTypes.contains(DeviceType.Speaker),
new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent evt)
{
toggleDeviceType(DeviceType.Speaker);
}
});
}
@Override
protected void addMoreButtons()
{
super.addMoreButtons();
enableOKButtonCheck();
}
/**
* Determine if the OK button should be set enabled or disabled.
*/
protected void enableOKButtonCheck()
{
if (okButton != null) {
boolean enableOk = false;
if (existingDesign != null) {
enableOk =
! requestData.deviceTypes.equals(existingDesign.getDeviceTypes());
}
// Check that the new design name is not ""
else if (designNameInput.getText().length() > 0) {
Iterator<DeviceType> requestedDevices =
requestData.deviceTypes.iterator();
// Check that at least one input device is selected
while (requestedDevices.hasNext()) {
DeviceType device = requestedDevices.next();
if (device.getNature() != DeviceType.OUTPUT_ONLY) {
enableOk = true;
}
}
}
okButton.setEnabled(enableOk);
}
}
} // NewDesignDialog
/**
* Request new information (name, device type set) about a new or
* existing design. The given data initializes the values for
* the interaction.
*
* @param requestData the initial values for the dialog box
* @return true if the user indicated that the operation should proceed;
* false indicates a desire to cancel the operation
* @author mlh
*/
public boolean requestNewDesignName(DesignRequestData requestData,
boolean newProjectMode,
Project project)
{
NewDesignDialog askName = new NewDesignDialog(window,
requestData,
newProjectMode,
project);
Object response = askName.open();
if ((response != null) && response.equals(WindowUtil.PromptDialog.OK))
{
requestData.designName = askName.getPromptResponse();
return true;
}
return false;
}
public boolean requestNewDesignName(DesignRequestData requestData,
boolean newProjectMode,
Project project, boolean changeDesignName)
{
NewDesignDialog askName = new NewDesignDialog(window,
requestData,
newProjectMode,
project, changeDesignName);
Object response = askName.open();
if ((response != null) && response.equals(WindowUtil.PromptDialog.OK))
{
requestData.designName = askName.getPromptResponse();
return true;
}
return false;
}
/**
* Request new devices for the given design
*
* @param requestData to hold the return values for the dialog box
* @param project the user's project
* @param design the design to which to add devices
* @return true if the user indicated that the operation should proceed;
* false indicates a desire to cancel the operation
*/
public boolean requestAddDesignDevices(DesignRequestData requestData,
Project project,
Design design)
{
NewDesignDialog askName = new NewDesignDialog(window,
requestData,
project,
design);
Object response = askName.open();
if ((response != null) && response.equals(WindowUtil.PromptDialog.OK))
{
requestData.designName = askName.getPromptResponse();
return true;
}
return false;
}
/**
* A message explaining that the new widget name collides with
* an existing one.
*/
public String protestNameCollision(String type)
{
WindowUtil.PromptDialog dialog =
new WindowUtil.PromptDialog(window,
errorTitle,
SWT.PRIMARY_MODAL,
type + " " + L10N.get("CT.NameLabel",
"name:"),
nameCollisionMsg);
Object response = dialog.open();
return
(response != null) && response.equals(WindowUtil.PromptDialog.OK)
? dialog.getPromptResponse()
: null;
}
/**
* A message explaining that the new widget name cannot be empty.
*/
public String protestNameCannotBeEmpty(String type)
{
WindowUtil.PromptDialog dialog =
new WindowUtil.PromptDialog(window,
errorTitle,
SWT.PRIMARY_MODAL,
type + " " + L10N.get("CT.NameLabel",
"name:"),
nameEmptyMsg);
Object response = dialog.open();
return
(response != null) && response.equals(WindowUtil.PromptDialog.OK)
? dialog.getPromptResponse()
: null;
}
/**
* Complain that one or more frames contains a hidden button. (Hidden hot-spot)
*
* @author khaledziyaeen@gmail.com
*/
public boolean protestHiddenButtons(String message)
{
return SWT.OK == WindowUtil.presentConfirmDialog(window,
L10N.get("CT.HiddenButtons",
"Hidden Buttons"),
message);
}
public Boolean confirmNoTracing() {
switch (WindowUtil.presentYesNoCancelDialog(window,
L10N.get("PM.confirmNoTracingTitle", "Turn Tracing On?"),
L10N.get("PM.confirmNoTracingMsg",
"ACT-R tracing is currently suppressed, which means " +
"visualization and the generation of script steps in " +
"novice exploration will not work. Would you like to " +
"turn ACT-R tracing back on again?"))) {
case SWT.YES:
return Boolean.TRUE;
case SWT.NO:
return Boolean.FALSE;
default:
return null;
}
}
}