/******************************************************************************* * 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.util.EventObject; import java.util.Iterator; import java.util.List; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableItem; import edu.cmu.cs.hcii.cogtool.CogTool; import edu.cmu.cs.hcii.cogtool.CogToolLID; import edu.cmu.cs.hcii.cogtool.model.CognitiveModelGenerator; import edu.cmu.cs.hcii.cogtool.model.DefaultModelGeneratorState; import edu.cmu.cs.hcii.cogtool.model.Demonstration; import edu.cmu.cs.hcii.cogtool.model.Design; import edu.cmu.cs.hcii.cogtool.model.DeviceType; import edu.cmu.cs.hcii.cogtool.model.DoubleRectangle; import edu.cmu.cs.hcii.cogtool.model.Frame; import edu.cmu.cs.hcii.cogtool.model.HandLocation; import edu.cmu.cs.hcii.cogtool.model.Script; import edu.cmu.cs.hcii.cogtool.model.AScriptStep; import edu.cmu.cs.hcii.cogtool.model.Task; import edu.cmu.cs.hcii.cogtool.model.TaskApplication; import edu.cmu.cs.hcii.cogtool.model.TaskGroup; import edu.cmu.cs.hcii.cogtool.model.TransitionSource; import edu.cmu.cs.hcii.cogtool.model.AUndertaking; import edu.cmu.cs.hcii.cogtool.model.IWidget; import edu.cmu.cs.hcii.cogtool.model.Project; import edu.cmu.cs.hcii.cogtool.model.Project.ITaskDesign; import edu.cmu.cs.hcii.cogtool.uimodel.DefaultSEUIModel; import edu.cmu.cs.hcii.cogtool.uimodel.ScriptViewerUIModel; import edu.cmu.cs.hcii.cogtool.uimodel.GroupScriptUIModel.GroupScriptIterator; import edu.cmu.cs.hcii.cogtool.util.GraphicsUtil; import edu.cmu.cs.hcii.cogtool.util.AlertHandler; import edu.cmu.cs.hcii.cogtool.util.ListenerIdentifier; import edu.cmu.cs.hcii.cogtool.util.ListenerIdentifierMap; import edu.cmu.cs.hcii.cogtool.util.PrecisionUtilities; import edu.cmu.cs.hcii.cogtool.util.RcvrImageException; import edu.cmu.cs.hcii.cogtool.util.SWTContextMenuUtil; import edu.cmu.cs.hcii.cogtool.util.UndoManager; import edu.cmu.cs.hcii.cogtool.view.SWTList; import edu.cmu.cs.hcii.cogtool.view.SWTListGroupScript; import edu.cmu.cs.hcii.cogtool.view.ScriptView; import edu.cmu.cs.hcii.cogtool.view.ScriptViewerView; import edu.cmu.cs.hcii.cogtool.view.View; public class ScriptViewerUI extends SEDefaultUI { protected ITaskDesign taskDesign; protected ScriptViewerView view; protected ScriptViewerMouseState seMouseState; protected SEDemoSelectionState selection; protected SEDemoContextSelectionState contextSelection; protected SEDemoInteraction interaction; protected SelectionListener SWTselectionChangeHandler; protected ScriptViewerUIModel uiModel; protected DelayedRepaint delayedRepainting; protected DelayedSelection delayedStateSelection; // cannot pass in null or SWT will complain protected SWTContextMenuUtil.MenuListener contextMenuListener = new SWTContextMenuUtil.MenuListenerAdapter(); protected SWTContextMenuUtil.MenuListener frameContextListener = new SWTContextMenuUtil.MenuListenerAdapter() { @Override public void handleEvent(Event evt) { showFrameContextMenu((Label) evt.widget); } }; @Override protected DefaultSEUIModel getDefaultUIModel() { return getUIModel(); } protected TaskApplication.TaskApplicationResultChange designAlert; public ScriptViewerUI(ITaskDesign td, Project scriptProject, UndoManager undoMgr) { super(td.getTask(), td.getDesign(), scriptProject, undoMgr, true); designAlert = new TaskApplication.TaskApplicationResultChange(design, task); uiModel = new ScriptViewerUIModel(td, scriptProject, frameLabelListener, inputDeviceChangeHandler); uiModel.setWidgetShapeChangeHandler(handleWidgetShapeChange); // Cache model values taskDesign = td; seMouseState = new ScriptViewerMouseState(this); int deviceTypes = DeviceType.buildDeviceSet(design.getDeviceTypes()); ScriptViewerView v = new ScriptViewerView(deviceTypes, lIDMap, this, menuData, uiModel, null, seMouseState, contextMenuListener, frameContextListener, this, getWindowLocation()); setViewUIModel(v, uiModel); view = v; setZoomEditor(view.getEditor()); int index = 0; TaskGroup group = (TaskGroup) taskDesign.getTask(); AUndertaking firstTask = group.getUndertakings().get(index++); while (! (firstTask instanceof Task) && (index < group.getUndertakings().size())) { firstTask = group.getUndertakings().get(index++); } // TODO check for task app also? boolean hasTask = firstTask instanceof Task; if (! hasTask) { return; } TaskApplication ta = scriptProject.getTaskApplication(firstTask, taskDesign.getDesign()); final Demonstration firstDemo = ta.getDemonstration(); DefaultModelGeneratorState initialState = firstDemo.getInitialState(); boolean userMouseHand = firstDemo.getMouseHand(); HandLocation mouseHandLoc = initialState.getHandLocation(userMouseHand); view.setStartMouseHandLocation(mouseHandLoc); view.setUserMouseHand(userMouseHand); Table historyTable = view.getHistoryTable(); selection = new SEDemoSelectionState(historyTable); contextSelection = new SEDemoContextSelectionState(); interaction = new SEDemoInteraction(getView()); updateTitle(); AlertHandler designFrameChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { Design.FrameChange evt = (Design.FrameChange) alert; addRemoveFrameNameChgHandler((Frame) evt.element, evt.isAdd); } }; design.addHandler(this, Design.FrameChange.class, designFrameChangeHandler); // Listen to Menu events on the History table. // Used to detect up-context click on the selected row. historyTable.addListener(SWT.MenuDetect, new Listener() { public void handleEvent(Event evt) { //xyzzy TODO: must select/deselect by pos! for Mac (unless selection occurs before menudetect! showSelectionContextMenu(evt.x, evt.y); } }); if (hasTask) { addSelectionChangeListeners(); Iterator<AUndertaking> tasks = group.getUndertakings().iterator(); while (tasks.hasNext()) { AUndertaking t = tasks.next(); TaskApplication tApp = project.getTaskApplication(t, design); if (tApp == null) { continue; } final Script script = tApp.getOnlyScript(); AlertHandler stepStateChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { updateView(); DefaultModelGeneratorState currentOverride = uiModel.getCurrentOverrideState(); if ((currentOverride != null) && (script == uiModel.getCurrentOverrideScript())) { AScriptStep selectionOwner = currentOverride.getScriptStep().getOwner(); delayedStateSelection.addToSelection(selectionOwner); } } }; script.addHandler(this, Script.StepStateChange.class, stepStateChangeHandler); delayedStateSelection = new DelayedSelection(selection) { @Override protected void selectItem(Object item) { int selectIndex = script.getStepStateIndex((AScriptStep) item); if (selectIndex >= script.getStepStateCount()) { // Select the "last" item selection.setSelectedState(null); } else { selection.setSelectedStateIndex(selectIndex); } } }; script.getDemonstration().addHandler(this, Demonstration.ScriptStepChange.class, createScriptChangeAlert(script)); } } AlertHandler initialStateChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { Demonstration.InitialStateChange chg = (Demonstration.InitialStateChange) alert; DefaultModelGeneratorState initialState = firstDemo.getInitialState(); boolean mouseHand = initialState.getMouseHand(); if (chg.involvesChange(Demonstration.InitialStateChange.MOUSE_HAND)) { view.setUserMouseHand(mouseHand); } if (chg.involvesChange(Demonstration.InitialStateChange.GENERATOR_STATE)) { view.setStartMouseHandLocation(initialState.getHandLocation(mouseHand)); } } }; firstDemo.addHandler(this, Demonstration.InitialStateChange.class, initialStateChangeHandler); delayedRepainting = new DelayedRepaint() { @Override protected void performRepaint() { updateView(); } @Override public void doWork() { needsRepaint = REPAINT_ALL; super.doWork(); undoMgrViewHandler.resetView(undoManager); // Update the enabled items selection state. setViewEnabledState(selection, ListenerIdentifierMap.NORMAL); } }; CogTool.selectionPhase.addDelayedWork(delayedStateSelection); CogTool.repaintPhase.addDelayedWork(delayedRepainting); updateView(); // Set the initial selection to the LAST element in the filtered list. selectLastHistoryItem(); // Restore zoom level restoreZoom(); setInitiallyEnabled(true); } // ctor protected void showFrameContextMenu(Label frameLabel) { view.showFrameMenu(); } @Override public void dispose() { CogTool.selectionPhase.removeDelayedWork(delayedStateSelection); CogTool.repaintPhase.removeDelayedWork(delayedRepainting); uiModel.dispose(); TaskGroup group = (TaskGroup) taskDesign.getTask(); Iterator<AUndertaking> tasks = group.getUndertakings().iterator(); while (tasks.hasNext()) { AUndertaking t = tasks.next(); TaskApplication tApp = project.getTaskApplication(t, design); if (tApp == null) { continue; } Script script = tApp.getOnlyScript(); script.removeAllHandlers(this); script.getDemonstration().removeAllHandlers(this); } selection.removeAllHandlers(this); view.removeSWTListSelectionHandler(SWTselectionChangeHandler); super.dispose(); } /** * @return Interaction Object */ public SEDemoInteraction getInteraction() { return interaction; } @Override public Interaction getStandardInteraction() { return interaction; } public SEDemoSelectionState getSelection() { return selection; } /** * Set the last item in the history list to be highlighted. */ protected void selectLastHistoryItem() { // To select the last item in the list, simply // set selection to null! selection.setSelectedState(null); } @Override public View getView() { return view; } protected ScriptView getScriptView() { return view; } // For use solely by mouse state and tests public ScriptViewerUIModel getUIModel() { return uiModel; } @Override protected Object getModelObject() { return taskDesign; } public void setCurrentFrame(Frame frame) { if (uiModel.setCurrentFrame(frame)) { view.getEditor().setContents(uiModel.getCurrentFrame().getContents()); } } protected Script getSelectedScript(DefaultModelGeneratorState stepState) { SWTList swtList = view.getScriptEditorList(); if (stepState == null) { TaskGroup group = (TaskGroup) task; List<AUndertaking> tasks = group.getUndertakings(); int numTasks = tasks.size(); for (int i = numTasks - 1; i >= 0; i--) { AUndertaking t = tasks.get(i); TaskApplication ta = project.getTaskApplication(t, design); if (ta != null) { return ta.getOnlyScript(); } } } TableItem item = swtList.getItemList().get(stepState); return (Script) item.getData(SWTListGroupScript.SCRIPT_DATA_KEY); } /** * Add the selection Change event listeners. */ protected void addSelectionChangeListeners() { SWTselectionChangeHandler = new SelectionListener() { public void widgetSelected(SelectionEvent evt) { SWTList swtList = view.getScriptEditorList(); TableItem[] selectedItems = swtList.getSelectionObject(); for (TableItem selectedItem : selectedItems) { // TODO: Currently supports only single selection. Object data = selectedItem.getData(); if (data instanceof Frame) { selection.setSelectedState(null); } else { selection.setSelectedState(selectedItem); } } setViewEnabledState(selection, ListenerIdentifierMap.NORMAL); // Let selection change handle changing the frame } public void widgetDefaultSelected(SelectionEvent evt) { SWTList swtList = view.getScriptEditorList(); TableItem[] selectedItems = swtList.getSelectionObject(); // TODO: Currently supports only single selection. for (TableItem selectedItem : selectedItems) { Object data = selectedItem.getData(); if (data instanceof DefaultModelGeneratorState) { Script s = (Script) selectedItem.getData(SWTListGroupScript.SCRIPT_DATA_KEY); DefaultModelGeneratorState stepState = (DefaultModelGeneratorState) data; // In case we need this; not a context selection // in truth, but we can re-use the structure. contextSelection.setSelectedState(stepState); performAction(ProjectLID.EditScript, s); } } } }; view.addSWTListSelectionHandler(SWTselectionChangeHandler); AlertHandler selectionChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { SEDemoSelectionState.StepStateSelectionChange event = (SEDemoSelectionState.StepStateSelectionChange) alert; if (event != null) { if (event.selected) { DefaultModelGeneratorState stepState = event.changedState; Frame resultFrame = null; Script script = getSelectedScript(stepState); AUndertaking t = script.getDemonstration().getTaskApplication().getTask(); if (stepState != null) { resultFrame = stepState.getScriptStep().getCurrentFrame(); } else if (script != null) { resultFrame = script.getDemonstration().getResultFrame(); } DefaultModelGeneratorState overrideState = script.getPreviousState(stepState); if (overrideState == null) { TaskGroup group = (TaskGroup) task; List<AUndertaking> siblingTasks = group.getUndertakings(); int currentTaskIndex = siblingTasks.indexOf(t); CognitiveModelGenerator modelGen = script.getModelGenerator(); while ((overrideState == null) && (0 <= --currentTaskIndex)) { AUndertaking prevSibling = siblingTasks.get(currentTaskIndex); TaskApplication ta = project.getTaskApplication(prevSibling, design); if (ta != null) { script = ta.getScript(modelGen); if (script != null) { overrideState = script.getLastState(); } } } } uiModel.setCurrentOverride(script, overrideState); try { setCurrentFrame(resultFrame); } catch (GraphicsUtil.ImageException ex) { throw new RcvrImageException("Changing current demonstration frame", ex); } } } } // handleAlert }; selection.addHandler(this, SEDemoSelectionState.StepStateSelectionChange.class, selectionChangeHandler); } protected AlertHandler createScriptChangeAlert(final Script script) { return new AlertHandler() { public void handleAlert(EventObject alert) { Demonstration.ScriptStepChange evt = (Demonstration.ScriptStepChange) alert; boolean computableChange = determineNextSelection(script, evt.scriptSteps, evt.action, evt.index, delayedStateSelection); if (computableChange) { design.raiseAlert(designAlert); } } }; } // createScriptChangeAlert @Override public void showContextMenu() { view.showStandardMenu(); } /** * Sets the "always-enabled" widgets; * call this at the end of the subclass constructor! */ @Override protected void setInitiallyEnabled(boolean forConstruction) { super.setInitiallyEnabled(forConstruction); setViewEnabledState(selection, ListenerIdentifierMap.NORMAL); } public void setLIDEnabledState() { setViewEnabledState(selection, ListenerIdentifierMap.NORMAL); } /** * Enables or disables LIDs as appropriate * * Primarily used to enable Edit Think Duration or edit Delay Duration. * * @param selnState the selection state on which to base enabling/disabling * @param availability NORMAL or CONTEXT * @see ListenerIdentifierMap */ protected void setViewEnabledState(SEDemoSelectionState selnState, Boolean availability) { setEnabled(SEDemoLID.RecomputeScript, availability, false); setEnabled(SEDemoLID.RegenerateScript, availability, false); setEnabled(SEDemoLID.ChangeWaitProperties, availability, false); setEnabled(SEDemoLID.ChangeThinkProperties, availability, false); setEnabled(CogToolLID.Delete, availability, false); setEnabled(SEDemoLID.InsertThink, availability, false); setEnabled(SEDemoLID.InsertLookAt, availability, false); setEnabled(SEDemoLID.InsertDelay, availability, false); DefaultModelGeneratorState state = (availability == ListenerIdentifierMap.CONTEXT) ? contextSelection.getSelectedState() : selection.getSelectedState(); setEnabled(ProjectLID.EditScript, availability, (state != null)); } /** * Mouse event occurred on the table, use the selected item to * display an appropriate menu. * */ public void showSelectionContextMenu(int x, int y) { contextSelection.deselectAll(); Table historyTable = view.getHistoryTable(); org.eclipse.swt.graphics.Point atPoint = historyTable.toControl(x, y); TableItem ti = historyTable.getItem(atPoint); if ((ti != null) && (ti.getData() instanceof DefaultModelGeneratorState)) { contextSelection.setSelectedState((DefaultModelGeneratorState) ti.getData()); } else { // this is the result Step, so no selection historyTable.select(historyTable.getItemCount() - 1); } showContextMenu(contextSelection, true); } protected void showContextMenu(SEDemoSelectionState selnState, boolean context) { setViewEnabledState(selnState, (context) ? ListenerIdentifierMap.CONTEXT : ListenerIdentifierMap.NORMAL); view.showScriptStepSelectionMenu(); } @Override public Object getParameters(ListenerIdentifier originalLID, ListenerIdentifier transmutedLID, boolean isContextSelection) { Object parameters = super.getParameters(originalLID, transmutedLID, isContextSelection); if (parameters != UNSET) { return parameters; } if (ProjectLID.EditScript.equals(originalLID)) { DefaultModelGeneratorState stepState = contextSelection.getSelectedState(); TableItem item = view.getScriptEditorList().getItemList().get(stepState); return item.getData(SWTListGroupScript.SCRIPT_DATA_KEY); } if (DesignEditorLID.EditFrame.equals(originalLID)) { return uiModel.getCurrentFrame().getFrame(); } return selection; } /** * Do any set-up before <code>performAction</code> is invoked. * * @param id the transmuted key specifying the semantic nature of the * action to be performed */ @Override protected void setUpPerformAction(ListenerIdentifier id) { super.setUpPerformAction(id); delayedStateSelection.setActive(true); } /** * Support for centering selection when zooming */ @Override protected Rectangle getSelectedRegion() { DefaultModelGeneratorState selectedState = selection.getSelectedState(); if (selectedState != null) { TransitionSource src = selectedState.getScriptStep().getStepFocus(); if (src instanceof IWidget) { DoubleRectangle widgetBds = ((IWidget) src).getEltBounds(); return PrecisionUtilities.getDraw2DRectangle(widgetBds); } } return super.getSelectedRegion(); } @Override protected boolean centerSelectedRegion() { if (! super.centerSelectedRegion()) { setScrollOrigin(0, 0); return false; } return true; } /** * Update the view: Set the View's List with the current ScriptSteps */ @Override protected void updateView() { GroupScriptIterator groupSteps = uiModel.getScriptUIModel().getGroupSteps(); SWTListGroupScript swtList = view.getScriptEditorList(); swtList.setGroupListContents(groupSteps); swtList.addListItem(swtList.getDestinationFrame()); } }