/** * */ package ie.dcu.apps.ist.views; import ie.dcu.apps.ist.AppWindow; import ie.dcu.apps.ist.event.*; import ie.dcu.apps.ist.exp.*; import ie.dcu.apps.ist.widgets.SwtTimer; import ie.dcu.segment.annotate.*; import java.io.IOException; import java.util.Properties; import java.util.logging.*; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.*; import org.eclipse.swt.widgets.*; /** * @author Kevin McGuinness */ public class ExperimentPanel extends Composite { // Some property keys private static final String DESCRIPTION_TITLE = "description.title"; private static final String TIMEOUT_MESSAGE = "timeout-message"; private static final String EXPERIMENT_COMPLETE_MESSAGE = "experiment-complete-message"; // Type of button in use private static enum ButtonType { Start, Finish }; // App window and experiment private final AppWindow window; private final Experiment experiment; private final Logger log; // Controls private final Group group; private final Button button; private final SwtTimer timer; private final Text text; // Shell if the panel is inside a shell private Shell shell; // Experiment manager private final ExperimentManager manager; private final EvaluationListener evaluator; public ExperimentPanel(AppWindow window, Composite parent, int style) { super(parent, style); // Set app window this.window = window; this.experiment = window.getExperiment(); this.log = Logger.getLogger("Experiment"); // Create controls timer = new SwtTimer(this, SWT.NONE); group = new Group(this, SWT.DEFAULT); button = new Button(this, SWT.PUSH); text = new Text(group, SWT.READ_ONLY | SWT.WRAP); // Create manager and evaluator manager = new ExperimentManager(window); evaluator = new EvaluationListener(manager, timer); // Setup controls setupControls(); layoutControls(); addListeners(); // Begin experiment beginExperiment(); } public static void open(AppWindow win) { Shell app = win.getShell(); // Style bits int style = SWT.MODELESS | SWT.RESIZE | SWT.BORDER | SWT.TITLE | SWT.CLOSE; // Construct shell Shell shell = new Shell(app, style); // Determine shell bounds Rectangle bounds = new Rectangle(0,0,180,280); // Move shell to an appropriate place (right of app window) Rectangle rect = app.getBounds(); bounds.x = rect.x + rect.width; bounds.y = rect.y + rect.height / 2 - bounds.height / 2; // Move back if outside the screen Rectangle screen = shell.getDisplay().getBounds(); if (bounds.x + bounds.width > screen.width) { bounds.x -= bounds.x + bounds.width - screen.width; } // Set bounds shell.setBounds(bounds); // Set layout shell.setLayout(new FillLayout()); // Create experiment panel final ExperimentPanel panel = new ExperimentPanel(win, shell, SWT.NONE); panel.shell = shell; panel.updateTitle(); // Add shell close button listener shell.addShellListener(new ShellAdapter() { public void shellClosed(ShellEvent e) { if (!panel.manager.isComplete()) { e.doit = panel.cancelExperiment(); } } }); // Open experiment panel shell.open(); } private void setupControls() { group.setText(property(DESCRIPTION_TITLE)); text.setEnabled(false); enableButton(null); } private void layoutControls() { GridLayout gl; GridData gd; // Setup layout manager for this gl = new GridLayout(); gl.marginTop = 5; gl.verticalSpacing = 5; this.setLayout(gl); // Setup layout manager for group gl = new GridLayout(); group.setLayout(gl); // Layout timer gd = new GridData(SWT.FILL, SWT.FILL, true, false); timer.setLayoutData(gd); // Layout group gd = new GridData(SWT.FILL, SWT.FILL, true, true); group.setLayoutData(gd); // Layout button gd = new GridData(SWT.FILL, SWT.FILL, true, false); button.setLayoutData(gd); // Layout text gd = new GridData(SWT.FILL, SWT.FILL, true, true); text.setLayoutData(gd); } private void addListeners() { button.addListener(SWT.Selection, buttonListener); timer.addTimeoutListener(timeListener); } /** * Function to call if something goes wrong with the experiment. Shows an * error message and gracefully returns the application to normal mode. * * @param reason * Short description of the problem. */ private void abortExperiment(String reason) { timer.clear(); evaluator.detach(); // Show abort message error("Aborting experiment!%n%s", reason); // Tell manager of abort manager.abortExperiment(); // Exit experiment mode exitExperimentMode(); } /** * Does what is necessary to exit experiment mode. */ private void exitExperimentMode() { // Remove experiment view window.setExperiment(null); // Re-enable interactions window.getView().setEnabled(true); // Kill the shell closeShell(); } /** * Closes the encompassing (non-application) shell, if any. */ private void closeShell() { if (shell != null) { shell.close(); shell.dispose(); shell = null; } } private boolean cancelExperiment() { // Make sure the user knows what they're doing boolean cancel = MessageDialog.openQuestion(window.getShell(), "Confirm", "Are you sure you want to cancel the experiment?"); if (cancel) { // Guess we better do what the user says, grumble grumble :-/ timer.clear(); evaluator.detach(); manager.abortExperiment(); window.setExperiment(null); window.getView().setEnabled(true); } return cancel; } /** * Start the experiment. */ private void beginExperiment() { try { manager.beginExperiment(experiment); } catch (IOException e) { log.log(Level.SEVERE, "Error starting experiment", e); abortExperiment(e.getMessage()); } // Begin first task beginTask(); } /** * Finish the experiment. */ private void endExperiment() { try { manager.endExperiment(); } catch (IOException e) { log.log(Level.SEVERE, "Error ending experiment", e); abortExperiment(e.getMessage()); } // Tell user experiment is complete notifyExperimentComplete(); // Stop experiment mode exitExperimentMode(); } /** * Load the task. */ private void beginTask() { // Set timer timer.set(experiment.getTime()); // Begin task try { manager.beginTask(); } catch (IOException e) { log.log(Level.SEVERE, "Error starting task", e); abortExperiment(e.getMessage()); } // Disable interactions window.getView().setEnabled(false); // Update description text.setText(manager.getTaskDescription()); // Update title text updateTitle(); // Setup start button enableButton(ButtonType.Start); // Attach evaluator evaluator.attach(); } private void updateTitle() { if (shell != null) { int ntasks = manager.getTaskCount(); int progress = manager.getProgress(); shell.setText(String.format("Task [%d/%d]", progress+1, ntasks)); } } /** * Start the task. */ private void startTask() { // Enable interactions window.getView().setEnabled(true); // Start clock timer.start(); // Enable finish button enableButton(ButtonType.Finish); } /** * End the task. */ private void endTask() { // Detach evaluator evaluator.detach(); // Evaluate final result manager.evaluate(timer.getElapsed()); // End task try { manager.endTask(); } catch (IOException e) { log.log(Level.SEVERE, "Error ending task", e); abortExperiment(e.getMessage()); } // Stop and clear clock timer.clear(); } /** * End the current task and start the next. * End the experiment if finished. */ private void nextTask() { endTask(); if (manager.isComplete()) { endExperiment(); } else { beginTask(); } } private void enableButton(ButtonType type) { if (type != null) { String name = type.toString().toLowerCase(); button.setText(property("button.text." + name)); button.setData(type); button.setEnabled(true); button.setFocus(); } else { button.setText(property("button.text.start")); button.setEnabled(false); button.setData(null); } } private ButtonType getEnabledButton() { return (ButtonType) button.getData(); } private void handleButtonClicked() { ButtonType btn = getEnabledButton(); switch (btn) { case Start: startTask(); break; case Finish: // Prevent clicking finish by accident if (manager.getActiveContext().getAnnotations().count() == 0) { getDisplay().beep(); } else { nextTask(); } break; } } private void handleTimeout() { // Finish task endTask(); // Tell user notifyTimeoutOccurred(); // Next task if (manager.isComplete()) { endExperiment(); } else { beginTask(); } } private void notifyExperimentComplete() { message(EXPERIMENT_COMPLETE_MESSAGE); } private void notifyTimeoutOccurred() { // BEEP BEEP!! time up :-) getDisplay().beep(); message(TIMEOUT_MESSAGE); } private void message(String key) { Shell main = window.getShell(); MessageDialog.openInformation(main, "Information", property(key)); } private void error(String message, Object ... args) { Shell main = window.getShell(); MessageDialog.openError(main, "Error", String.format(message, args)); } private String property(String key) { return property(key, key); } private String property(String key, String def) { Properties p = window.getProperties(); return p.getProperty(String.format("ExperimentPanel.%s", key), def); } private final Listener buttonListener = new Listener() { public void handleEvent(Event event) { handleButtonClicked(); } }; private final TimeoutListener timeListener = new TimeoutListener() { public void timeoutOccured(TimeoutEvent evt) { handleTimeout(); } }; } class EvaluationListener implements AnnotationListener { private final ExperimentManager manager; private final SwtTimer timer; private final Logger log; public EvaluationListener(ExperimentManager manager, SwtTimer timer) { this.manager = manager; this.timer = timer; this.log = Logger.getLogger("Experiment"); } public void attach() { manager.getActiveContext().addAnnotationListener(this); } public void detach() { manager.getActiveContext().removeAnnotationListener(this); } private void annotationsChanged(AnnotationEvent e) { // Return if there is nothing to evaluate yet if (e.manager.count() == 0) { return; } // Time the change occurred int time = timer.getElapsed(); manager.evaluate(time); // Store if necessary try { manager.store(time); } catch (IOException ex) { log.log(Level.SEVERE, "Error storing intermediate segmentation mask", e); } } public void annotationPerformed(AnnotationEvent e) { annotationsChanged(e); } public void annotationRedone(AnnotationEvent e) { annotationsChanged(e); } public void annotationUndone(AnnotationEvent e) { annotationsChanged(e); } public void annotationsCleared(AnnotationEvent e) { annotationsChanged(e); } }