/******************************************************************************* * 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.swt.SWT; import org.eclipse.swt.custom.TreeEditor; import org.eclipse.swt.dnd.ByteArrayTransfer; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DragSource; import org.eclipse.swt.dnd.DragSourceEvent; import org.eclipse.swt.dnd.DragSourceListener; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.dnd.TransferData; import org.eclipse.swt.dnd.TreeDragSourceEffect; import org.eclipse.swt.dnd.TreeDropTargetEffect; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.events.TreeEvent; import org.eclipse.swt.events.TreeListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; import edu.cmu.cs.hcii.cogtool.CogTool; import edu.cmu.cs.hcii.cogtool.CogToolLID; import edu.cmu.cs.hcii.cogtool.CogToolPref; import edu.cmu.cs.hcii.cogtool.ResultDisplayPolicy; import edu.cmu.cs.hcii.cogtool.model.ACTR6PredictionAlgo; import edu.cmu.cs.hcii.cogtool.model.APredictionResult; import edu.cmu.cs.hcii.cogtool.model.AUndertaking; import edu.cmu.cs.hcii.cogtool.model.CognitiveModelGenerator; import edu.cmu.cs.hcii.cogtool.model.Demonstration; import edu.cmu.cs.hcii.cogtool.model.Design; import edu.cmu.cs.hcii.cogtool.model.GroupNature; import edu.cmu.cs.hcii.cogtool.model.IPredictionAlgo; import edu.cmu.cs.hcii.cogtool.model.ISimilarityDictionary; import edu.cmu.cs.hcii.cogtool.model.Project; import edu.cmu.cs.hcii.cogtool.model.SNIFACTPredictionAlgo; import edu.cmu.cs.hcii.cogtool.model.Script; import edu.cmu.cs.hcii.cogtool.model.TaskApplication; import edu.cmu.cs.hcii.cogtool.model.TaskGroup; import edu.cmu.cs.hcii.cogtool.model.WidgetAttributes; import edu.cmu.cs.hcii.cogtool.ui.ProjectSelectionState.ProjectSelectionPredicate; import edu.cmu.cs.hcii.cogtool.ui.SWTTreeProjectSelectionState.ProjectSelectionChange; import edu.cmu.cs.hcii.cogtool.uimodel.ProjectUIModel; import edu.cmu.cs.hcii.cogtool.util.AlertHandler; 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.ListenerIdentifier; import edu.cmu.cs.hcii.cogtool.util.ListenerIdentifierMap; import edu.cmu.cs.hcii.cogtool.util.ManagedText; import edu.cmu.cs.hcii.cogtool.util.MenuUtil; import edu.cmu.cs.hcii.cogtool.util.NullSafe; import edu.cmu.cs.hcii.cogtool.util.OSUtils; import edu.cmu.cs.hcii.cogtool.util.SWTContextMenuUtil; import edu.cmu.cs.hcii.cogtool.util.UndoManager; import edu.cmu.cs.hcii.cogtool.util.WindowUtil; import edu.cmu.cs.hcii.cogtool.view.MenuFactory; import edu.cmu.cs.hcii.cogtool.view.ProjectView; import edu.cmu.cs.hcii.cogtool.view.View; // TODO: add showItem and showColumn calls as appropriate public class ProjectUI extends DefaultUI implements ProjectUIModel.TreeRowHook, ProjectUIModel.TreeColumnHook { private static String EXPORT_PROJECT_LABEL = L10N.get("PR.ExportAllXMLLabel", "Export Project to XML"); protected int treeOperationOccurred = 0; protected class TreeOpTimer implements Runnable { public void run() { treeOperationOccurred--; } } protected Tree tree; protected Rectangle overBox = null; protected ImageData curImgData = null; // TODO: Determine these colors dynamically! 60,60,60 is useless on a PC protected static RGB CONTEXT_RGB = new RGB(120, 120, 150); protected static ImageData CONTEXT_IMAGEDATA = new ImageData(1, 1, 1, new PaletteData(new RGB[] { CONTEXT_RGB })); protected static Color CONTEXT_COLOR = new Color(WindowUtil.GLOBAL_DISPLAY, 180, 180, 200); protected ProjectInteraction interaction; protected ProjectView view; protected SWTTreeProjectSelectionState selection; protected ProjectContextSelectionState contextSelection; protected TreeEditor editor; protected SelectionListener taskSelectListener = null; protected ProjectUIModel uiModel; protected AlertHandler columnReorderHandler = new AlertHandler() { public void handleAlert(EventObject alert) { ProjectUIModel.DesignReordering ro = (ProjectUIModel.DesignReordering) alert; if (performAction(ProjectLID.ReorderDesigns, ro.newDesignOrdering)) { delayedDesignSelection.addToSelection(ro.movedDesign.getData(), ro.movedDesign); } } }; protected AlertHandler taskAppChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { TaskApplication.TaskApplicationResultChange chg = (TaskApplication.TaskApplicationResultChange) alert; TaskApplication taskApp = project.getTaskApplication(chg.task, (Design) chg.getSource()); if (taskApp != null) { delayedCellSelection.addToSelection(taskApp); } else { delayedCellSelection.reset(true); } } }; protected AlertHandler designChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { Project.DesignChange chg = (Project.DesignChange) alert; if (chg != null) { Design design = (Design) chg.element; if (chg.isAdd) { design.addHandler(ProjectUI.this, Project.DesignChange.class, taskAppChangeHandler); } else { design.removeHandler(Project.DesignChange.class, taskAppChangeHandler); } } } }; protected Listener columnSelectionListener = new Listener() { public void handleEvent(Event evt) { // XXX: SWT 3.1 doesn't support full events on TreeColumns: // https://bugs.eclipse.org/bugs/show_bug.cgi?id=17871 // if (((evt.stateMask & SWT.SHIFT) == 0) && // ((evt.stateMask & MenuUtil.platformControlKey()) == 0)) // { if (evt.widget.getData() == null) { selection.selectAllTasks(); } else { selection.setSelectedColumn((TreeColumn) evt.widget); } // } // else { // selection.addSelectedColumn((TreeColumn) evt.widget); // } } }; protected Listener columnDefaultSelectionListener = new Listener() { public void handleEvent(Event evt) { TreeColumn col = (TreeColumn) evt.widget; if (col.getData() != null) { DesignSelectionState seln = new SingleDesignSelectionState((Design) col.getData()); performAction(ProjectLID.EditDesign, seln); } } }; // Need to talk to the view, so this can't be static final protected ControlAdapter onResizeColumn = new ControlAdapter() { @Override public void controlResized(ControlEvent evt) { // TODO: Remember settings somehow? Attribute on Design? } }; protected DelayedSelection delayedTaskSelection; protected DelayedSelection delayedDesignSelection; protected DelayedSelection delayedCellSelection; protected DelayedRepaint delayedRepainting; protected ProjectSelectionPredicate requiresRegenerationPredicate; protected ProjectSelectionPredicate hasComputableScriptsPredicate; protected ProjectSelectionPredicate hasComputedResultPredicate; protected ProjectSelectionPredicate hasResultStepsPredicate; protected ProjectSelectionPredicate hasScriptsPredicate; protected ProjectSelectionPredicate hasTracesPredicate; protected ProjectSelectionPredicate hasMultipleScriptsPredicate; protected static final String PROJECT_PREFIX = L10N.get("WT.ProjectPrefix", "Project"); protected static final String TASK_LABEL = L10N.get("WT.TaskLabel", "Task"); protected static final String TASKS_LABEL = L10N.get("WT.TasksLabel", "Tasks"); protected static final String TASK_GROUP_LABEL = L10N.get("WT.TaskGroupLabel", "Task Group"); protected static final String TASK_GROUPS_LABEL = L10N.get("WT.TaskGroupsLabel", "Task Groups"); protected static final String DESIGN_LABEL = L10N.get("WT.DesignLabel", "Design"); protected static final String SCRIPTS_LABEL = L10N.get("WT.ScriptsLabel", "Scripts"); protected static final String SCRIPT_LABEL = L10N.get("WT.ScriptLabel", "Script"); protected static final String VIEW_SCRIPTS = L10N.get("WT.ViewScripts", "View Scripts"); protected static final String regenerateTitle = L10N.get("PR.Regenerate", "Regenerate"); protected static final String recomputeTitle = L10N.get("PR.Recompute", "Recompute"); protected static String buildWindowMenuLabel(Project project) { return PROJECT_PREFIX + ": " + project.getName(); } protected class TreeToolTip extends WindowUtil.CustomToolTip { public TreeToolTip(Tree forTree) { super(forTree); } @Override protected boolean handleClick(Event evt) { TreeItem[] newSelection = new TreeItem[] { (TreeItem) toolTipLabel.getData() }; // Assuming Tree is single select, set the // selection as if the mouse down event went // through to the Tree tree.setSelection(newSelection); Event e = new Event(); e.item = newSelection[0]; tree.notifyListeners(SWT.Selection, e); return true; } @Override protected void setTipContents(int x, int y) { TreeItem item = tree.getItem(evtLocation); if (item == null) { if (toolTipWindow != null) { toolTipWindow.setVisible(false); } return; } TreeColumn col = findColumn(x); if (col == null) { if (toolTipWindow != null) { toolTipWindow.setVisible(false); } return; } Design design = (Design) col.getData(); if (design == null) { if (toolTipWindow != null) { toolTipWindow.setVisible(false); } return; } toolTipLabel.setData(item); AUndertaking task = (AUndertaking) item.getData(); toolTipText.setLength(0); toolTipText.append(" " + L10N.get("TT.Design", "Design") + ": " + design.getName()); toolTipText.append("\n " + L10N.get("TT.Task", "Task") + ": " + task.getFullName()); if (task.isTaskGroup()) { String groupNature = ((TaskGroup) task).getNature().getName(); toolTipText.append("\n " + groupNature); } else { TaskApplication ta = project.getTaskApplication(task, design); if (ta == null) { toolTipText.append("\n No Demonstration"); } else { CognitiveModelGenerator gen = ta.getFirstModelGenerator(); String resultDisplay = ResultDisplayPolicy.getTaskApplicationCell(project, ta, gen, false, ResultDisplayPolicy.WITH_SECS); toolTipText.append("\n " + resultDisplay); } } toolTipLabel.setText(toolTipText.toString()); if (toolTipWindow != null) { toolTipWindow.setVisible(true); } } // setTipContents } // TreeToolTip protected TreeToolTip toolTipSpt; protected TreeItem renameTaskItem = null; // Alias for constant protected static final int IS_TASK_CHANGE = ProjectSelectionChange.IS_TASK_CHANGE; protected TreeItem findLastDescendant(TreeItem item) { int itemChildCount = item.getItemCount(); while (itemChildCount > 0) { item = item.getItem(itemChildCount - 1); itemChildCount = item.getItemCount(); } return item; } protected TreeItem findPrevItem(TreeItem item) { TreeItem prevItem = null; TreeItem itemParent = item.getParentItem(); // If a root item, find previous root sibling, if one if (itemParent == null) { int index = tree.indexOf(item); // There must be a previous sibling; if so, find last descendant if (index > 0) { prevItem = findLastDescendant(tree.getItem(index - 1)); } // else nowhere to go! } else { // Find location within TaskGroup int index = itemParent.indexOf(item); // Check for previous sibling; if so, find last descendant if (index > 0) { prevItem = findLastDescendant(itemParent.getItem(index - 1)); } else { // No previous sibling, so up to the TaskGroup itself prevItem = itemParent; } } return prevItem; } protected TreeItem findAncestorSibling(TreeItem item) { TreeItem itemParent = item.getParentItem(); if (itemParent == null) { int index = tree.indexOf(item); if (index < tree.getItemCount() - 1) { return tree.getItem(index + 1); } return null; } int index = itemParent.indexOf(item); if (index < itemParent.getItemCount() - 1) { return itemParent.getItem(index + 1); } return findAncestorSibling(itemParent); } protected TreeItem findNextItem(TreeItem item) { TreeItem nextItem = null; // If item has children, return the first child if (item.getItemCount() > 0) { nextItem = item.getItem(0); } else { // Find next sibling, if one TreeItem itemParent = item.getParentItem(); // If a root item, find next root sibling, if one if (itemParent == null) { int index = tree.indexOf(item); if (index < tree.getItemCount() - 1) { nextItem = tree.getItem(index + 1); } // else, nowhere to go } else { int index = itemParent.indexOf(item); if (index < itemParent.getItemCount() - 1) { nextItem = itemParent.getItem(index + 1); } else { // Find next "ancestor" sibling, if one nextItem = findAncestorSibling(itemParent); } } } return nextItem; } public ProjectUI(Project proj, UndoManager undoMgr) { super(proj, buildWindowMenuLabel(proj), buildLeadItems(), undoMgr); createPredicates(); view = new ProjectView(lIDMap, this, menuData, getWindowLocation()); tree = view.getTree(); toolTipSpt = new TreeToolTip(tree); selection = new SWTTreeProjectSelectionState(project, tree); contextSelection = new ProjectContextSelectionState(project); delayedRepainting = new DelayedRepaint() { @Override public void doWork() { super.doWork(); setViewEnabledState(selection, ListenerIdentifierMap.NORMAL); undoMgrViewHandler.resetView(undoManager); if (renameTaskItem != null) { initiateTaskRename(renameTaskItem, true); renameTaskItem = null; } } }; delayedTaskSelection = new DelayedSelection(selection) { @Override protected void selectItem(Object item) { selection.addSelectedItem((TreeItem) item); } }; delayedDesignSelection = new DelayedSelection(selection) { @Override protected void selectItem(Object item) { selection.setSelectedColumn((TreeColumn) item); } }; delayedCellSelection = new DelayedSelection(selection) { protected TaskApplication taskApp = null; @Override protected void selectItem(Object item) { taskApp = (TaskApplication) item; TreeItem row = uiModel.getTaskTreeItem(taskApp.getTask()); TreeColumn column = uiModel.getDesignTreeColumn(taskApp.getDesign()); selection.setSelectedCell(row, column); } @Override public void removeFromSelection(Object selectionKey) { if (isActive() && (taskApp != null)) { if ((selectionKey == taskApp.getTask()) || (selectionKey == taskApp.getDesign())) { taskApp = null; itemsToSelect.clear(); } } } @Override public void reset(boolean notCanceled) { taskApp = null; super.reset(notCanceled); } }; CogTool.repaintPhase.addDelayedWork(delayedRepainting); CogTool.selectionPhase.addDelayedWork(delayedTaskSelection); CogTool.selectionPhase.addDelayedWork(delayedDesignSelection); CogTool.selectionPhase.addDelayedWork(delayedCellSelection); uiModel = new ProjectUIModel(proj, tree, this, this); uiModel.addHandler(this, ProjectUIModel.DesignReordering.class, columnReorderHandler); updateTitle(); editor = new TreeEditor(tree); tree.setHeaderVisible(true); tree.setLinesVisible(true); tree.addTreeListener(new TreeListener() { private void setTimer() { treeOperationOccurred++; WindowUtil.GLOBAL_DISPLAY.timerExec(500, new TreeOpTimer()); } public void treeCollapsed(TreeEvent evt) { setTimer(); } public void treeExpanded(TreeEvent evt) { setTimer(); } }); AlertHandler selectionChangeHandler = new AlertHandler() { public void handleAlert(EventObject evt) { ProjectSelectionChange chg = (ProjectSelectionChange) evt; if (editor.getEditor() != null) { commitRenameTask(chg.designColumn != IS_TASK_CHANGE); } if (chg.designColumn != IS_TASK_CHANGE) { uiModel.recolorTree(chg.designColumn, chg.cellTaskRow, chg.selected); overBox = null; } tree.redraw(); setViewEnabledState(selection, ListenerIdentifierMap.NORMAL); } }; selection.addHandler(this, ProjectSelectionChange.class, selectionChangeHandler); tree.addListener(SWT.Paint, createHighlightColumnPaintListener()); project.addHandler(this, Project.DesignChange.class, designChangeHandler); Iterator<Design> designs = project.getDesigns().iterator(); while (designs.hasNext()) { Design design = designs.next(); design.addHandler(this, TaskApplication.TaskApplicationResultChange.class, taskAppChangeHandler); } setUpDragAndDrop(); taskSelectListener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent evt) { // TreeItem (de)selected is evt.item TreeItem[] sel = tree.getSelection(); // System.out.println("Selected rows! " + Arrays.asList(sel)); // Work around for a bug in SWT which causes a superfluous // raised alert. // TODO: incorporate when fixed Bug Report: // https://bugs.eclipse.org/bugs/show_bug.cgi?id=122644 if ((sel.length == 0) && (selection.getSelectedTaskCount() == 0)) { // hack around the fact that when the first row is // selected, selecting a column seems to trigger this event return; } boolean selectionChanged = false; // Check to see if selection has actually changed. if (selection.getSelectedDesign() != null) { selectionChanged = true; } else if (sel.length != selection.getSelectedTaskCount()) { selectionChanged = true; } else { for (TreeItem element : sel) { AUndertaking task = (AUndertaking) element.getData(); if (! selection.isTaskSelected(task)) { selectionChanged = true; break; // only care if there is any difference } } } if (selectionChanged) { // Change of the selection state (was a row selected) // so deselect it selection.deselectAll(); for (TreeItem element : sel) { selection.addSelectedItem(element); } setViewEnabledState(selection, ListenerIdentifierMap.NORMAL); } } }; tree.addSelectionListener(taskSelectListener); tree.deselectAll(); // this.tree.addListener(SWT.MouseHover, createSetToolTipListener()); TreeColumn col = tree.getColumn(0); col.setWidth(250); interaction = new ProjectInteraction(view); CONTEXT_IMAGEDATA.setAlpha(0, 0, 140); tree.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent evt) { TreeItem item = null; if (selection.getSelectedTaskCount() == 1) { item = selection.getSelectedTaskItems()[0]; } Design selectedDesign = selection.getSelectedDesign(); if (evt.keyCode == SWT.ARROW_LEFT) { if (evt.stateMask == SWT.SHIFT) { if (selection.getSelectedTaskCount() > 0) { performAction(ProjectLID.PromoteTask, selection); } } else if (item != null) { int index; if (selectedDesign != null) { index = project.getDesigns().indexOf(selectedDesign); } else { index = 1; } if (index >= 1) { int[] columnOrdering = uiModel.getCurrentDesignOrdering(); int colIndex = columnOrdering[index]; selection.setSelectedCell(item, tree.getColumn(colIndex)); } // SWT will want to select the whole row, so don't let // the event continue evt.doit = false; } else if (selectedDesign != null) { int index = project.getDesigns().indexOf(selectedDesign); if (index >= 1) { int[] columnOrdering = uiModel.getCurrentDesignOrdering(); TreeColumn col = tree.getColumn(columnOrdering[index]); selection.setSelectedColumn(col); } // SWT will want to select the whole row, so don't let // the event continue evt.doit = false; } } else if (evt.keyCode == SWT.ARROW_RIGHT) { if (evt.stateMask == SWT.SHIFT) { if (selection.getSelectedTaskCount() > 0) { performAction(ProjectLID.DemoteTask, selection); } } else if (item != null) { List<Design> projectDesigns = project.getDesigns(); int index; if (selectedDesign != null) { index = projectDesigns.indexOf(selectedDesign) + 2; } else { index = projectDesigns.size(); } if (index <= projectDesigns.size()) { int[] columnOrdering = uiModel.getCurrentDesignOrdering(); int colIndex = columnOrdering[index]; selection.setSelectedCell(item, tree.getColumn(colIndex)); } // SWT will want to select the whole row, so don't let // the event continue evt.doit = false; } else if (selectedDesign != null) { List<Design> projectDesigns = project.getDesigns(); int index = projectDesigns.indexOf(selectedDesign); if (index < projectDesigns.size() - 1) { int[] columnOrdering = uiModel.getCurrentDesignOrdering(); TreeColumn col = tree.getColumn(columnOrdering[index + 2]); selection.setSelectedColumn(col); } // SWT will want to select the whole row, so don't let // the event continue evt.doit = false; } } else if (evt.keyCode == SWT.ARROW_UP) { if ((item != null) && (selectedDesign != null)) { TreeItem prevItem = findPrevItem(item); if (prevItem != null) { TreeColumn col = selection.getSelectedColumn(); selection.setSelectedCell(prevItem, col); } // SWT will want to select the whole row, so don't let // the event continue evt.doit = false; } else if (selectedDesign != null) { int treeItemCount = tree.getItemCount(); if (treeItemCount > 0) { TreeItem lastItem = findLastDescendant(tree.getItem(treeItemCount - 1)); TreeColumn col = selection.getSelectedColumn(); selection.setSelectedCell(lastItem, col); } // SWT will want to select the whole row, so don't let // the event continue evt.doit = false; } } else if (evt.keyCode == SWT.ARROW_DOWN) { if ((item != null) && (selectedDesign != null)) { TreeItem nextItem = findNextItem(item); if (nextItem != null) { TreeColumn col = selection.getSelectedColumn(); selection.setSelectedCell(nextItem, col); } // SWT will want to select the whole row, so don't let // the event continue evt.doit = false; } else if (selectedDesign != null) { int treeItemCount = tree.getItemCount(); if (treeItemCount > 0) { TreeItem firstItem = tree.getItem(0); TreeColumn col = selection.getSelectedColumn(); selection.setSelectedCell(firstItem, col); } // SWT will want to select the whole row, so don't let // the event continue evt.doit = false; } } else if (evt.character == SWT.CR) { // Enter key performs "edit" action on a design or script or // edit/rename on a task Design d = selection.getSelectedDesign(); if (selection.getSelectedTaskCount() == 1) { if (d == null) { performAction(ProjectLID.InitiateTaskRename, selection); } else { AUndertaking t = selection.getSelectedTask(); if (t.isTaskGroup()) { if (((TaskGroup) t).getNature() == GroupNature.SUM) { performAction(ProjectLID.ViewGroupScript, selection); } } else { performAction(ProjectLID.EditScript, selection); } } } else if (d != null) { performAction(ProjectLID.EditDesign, selection); } } } }); // Ensure the first item at the top of the view region is the first row if (tree.getItemCount() > 0) { TreeItem showItem = tree.getItems()[0]; if (showItem != null) { tree.showItem(showItem); } } // React to context menu invocation requests ProjectMouseState mouseState = new ProjectMouseState(this); SWTContextMenuUtil.addMenuListener(tree, mouseState); setInitiallyEnabled(true); } // ctor protected static class TaskDnDTransfer extends ByteArrayTransfer { /* * See comment for CogToolClipboard.CogToolTransfer as to why 4 letters */ // private static final String COGTOOL_TASK_NAME = // "edu.cmu.cs.hcii.cogtool.model.AUndertaking"; private static final String COGTOOL_TASK_NAME = "CgTk"; private static final int COGTOOL_TASK_ID = registerType(COGTOOL_TASK_NAME); private static TaskDnDTransfer ONLY = new TaskDnDTransfer(); private TaskDnDTransfer() { } public static TaskDnDTransfer getInstance() { return ONLY; } @Override protected int[] getTypeIds() { return new int[] { COGTOOL_TASK_ID }; } @Override protected String[] getTypeNames() { return new String[] { COGTOOL_TASK_NAME }; } @Override public void javaToNative(Object object, TransferData transferData) { // Since we are guaranteeing the same project, // we will use the selection state; no need to "transfer" anything! // SWT MAC BUG: We must ensure that transferData.data is not null! // It is worth noting that it appears that in the // forthcoming new version of SWT all this DND stuff changes // some more and TransferData may be going away altogether. At // which time all this will need to be refiddled further. if (OSUtils.MACOSX) { // What we want to do is // transferData.data = new byte[][] { new byte[4] }; // but because transferData's implementation is different // on different platforms we need to jump through hoops // to do this only on Macintosh OSUtils.getPlatform().initTransferData(transferData); } } @Override public Object nativeToJava(TransferData transferData) { // Since we are guaranteeing the same project, // we will use the selection state; no need to "transfer" anything! return null; } } protected static class TaskAppDnDTransfer extends ByteArrayTransfer { /* * See comment for CogToolClipboard.CogToolTransfer as to why 4 letters */ // private static final String COGTOOL_TASKAPP_NAME = // "edu.cmu.cs.hcii.cogtool.model.TaskApplication"; private static final String COGTOOL_TASKAPP_NAME = "CgTa"; private static final int COGTOOL_TASKAPP_ID = registerType(COGTOOL_TASKAPP_NAME); private static TaskAppDnDTransfer ONLY = new TaskAppDnDTransfer(); private TaskAppDnDTransfer() { } public static TaskAppDnDTransfer getInstance() { return ONLY; } @Override protected int[] getTypeIds() { return new int[] { COGTOOL_TASKAPP_ID }; } @Override protected String[] getTypeNames() { return new String[] { COGTOOL_TASKAPP_NAME }; } @Override public void javaToNative(Object object, TransferData transferData) { // Since we are guaranteeing the same project, // we will use the selection state; no need to "transfer" anything! // SWT MAC BUG: We must ensure that transferData.data is not null! // It is worth noting that it appears that in the // forthcoming new version of SWT all this DND stuff changes // some more and TransferData may be going away altogether. At // which time all this will need to be refiddled further. if (OSUtils.MACOSX) { // What we want to do is // transferData.data = new byte[][] { new byte[4] }; // but because transferData's implementation is different // on different platforms we need to jump through hoops // to do this only on Macintosh OSUtils.getPlatform().initTransferData(transferData); } } @Override public Object nativeToJava(TransferData transferData) { // Since we are guaranteeing the same project, // we will use the selection state; no need to "transfer" anything! return null; } } /** * Parameters for renaming a Task/TaskGroup. * <p> * Includes the undertaking to be renamed, the new name, and the * undertaking's parent (either an <code>TaskGroup</code> or * <code>null</code> to indicate a top-level task for the project. * * @author mlh */ public static class TaskRenameEvent { public AUndertaking task; public String newName; public TaskGroup parent; /** * Initialize the parameters for renaming an undertaking. * * @param taskToRename the task to rename * @param newTaskName the new name for the task * @param parentGroup the immediate parent task group of the * undertaking being renamed; <code>null</code> * means that the task is top-level and that * the project then acts as the name scope * for uniqueness testing * @author mlh */ public TaskRenameEvent(AUndertaking taskToRename, String newTaskName, TaskGroup parentGroup) { task = taskToRename; newName = newTaskName; parent = parentGroup; } } /** * Parameters for ChangeTaskPosition and DuplicateTaskFull */ public static class ChangeTaskPositionParms { public TaskSelectionState tasks; public AUndertaking placeBeforeTask; public boolean isDuplicate; public ChangeTaskPositionParms(TaskSelectionState seln, AUndertaking beforeTask, boolean duplicate) { tasks = seln; placeBeforeTask = beforeTask; isDuplicate = duplicate; } } /** * Parameters for MoveTaskApplication and DuplicateTaskApplication */ public static class MoveCopyTaskApplicationParms { public AUndertaking fromTask; public AUndertaking toTask; public Design design; public MoveCopyTaskApplicationParms(AUndertaking f, AUndertaking t, Design d) { fromTask = f; toTask = t; design = d; } } protected static Tree currentDnDSource = null; protected static int currentDnDColumn = -1; // 0 is Task, > 0 is TaskApp! protected static TreeItem currentDnDRow = null; protected static TreeItem currentDndTaskAppDropRow = null; // See http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html // for more documentation of SWT drag-and-drop support. protected void setUpDragAndDrop() { DragSource treeAsSource = new DragSource(tree, DND.DROP_MOVE | DND.DROP_COPY); TaskDnDTransfer taskTransfer = TaskDnDTransfer.getInstance(); TaskAppDnDTransfer taskAppTransfer = TaskAppDnDTransfer.getInstance(); Transfer[] types = new Transfer[] { taskTransfer, taskAppTransfer }; treeAsSource.setTransfer(types); // DropSourceEvent fields: // dataType: // the Transfer type of the data the target prefers to receive; // useful in dragSetData // detail: // the operation the target performed; one of: // DROP_MOVE - move from source to target; remove from source // DROP_COPY - copy the source to target; leave the source // DROP_LINK - create a link of the source at the target // useful in dragFinished in case the source needs to be removed // doit: // in dragStart, determines if the operation should proceed // in dragFinished, may be set to indicate if the operation succeeded // image: // may be set to the Image displayed during drag // x, y: position within the Tree DragSourceListener srcListener = new TreeDragSourceEffect(tree) { @Override public void dragStart(DragSourceEvent evt) { // If the Transfer type cannot be determined until the drag // starts, the setTransfer() call can be invoked here. // Set evt.doit to false here if action is inappropriate. // Reset, just in case no drag-and-drop should happen currentDnDSource = null; // Must be in first column! TreeColumn column = findColumn(evt.x); TreeItem row = tree.getItem(new Point(evt.x, evt.y)); if ((column != null) && (column.getData() == null)) { // Moving a task; there is no data associated with the // first column. if ((row != null) && (row.getData() != null)) { if (((AUndertaking) row.getData()).isSpawned()) { evt.doit = false; return; } } if (selection.getSelectedTaskCount() == 0) { if (row != null) { selection.setSelectedItem(row); currentDnDSource = tree; currentDnDColumn = 0; } } else { currentDnDSource = tree; currentDnDColumn = 0; } } else { // Must be in cell with a valid TaskApplication! if ((column != null) && (column.getData() != null)) { if ((row != null) && (row.getData() != null)) { Design design = (Design) column.getData(); AUndertaking task = (AUndertaking) row.getData(); TaskApplication taskApp = project.getTaskApplication(task, design); if (taskApp != null) { if (! taskApp.getDemonstration().isEditable()) { evt.doit = false; return; } // set some highlighting of the source cell selection.setSelectedCell(row, column); contextSelection.setSelectedDesign(design); contextSelection.addSelectedTask(task); currentDnDRow = row; currentDnDSource = tree; currentDnDColumn = tree.indexOf(column); return; // do not do superclass work! } } } evt.doit = false; } super.dragStart(evt); } @Override public void dragSetData(DragSourceEvent evt) { // Based on the requested Transfer data type, set evt.data // if (taskTransfer.isSupportedType(evt.dataType)) { // evt.data = "This is the requested data"; // } super.dragSetData(evt); } @Override public void dragFinished(DragSourceEvent evt) { // Operation was performed by the drop target; clean up // If needed, evt.detail should be the operation performed. super.dragFinished(evt); currentDnDSource = null; currentDnDColumn = -1; currentDnDRow = null; currentDndTaskAppDropRow = null; } }; treeAsSource.addDragListener(srcListener); DropTarget treeAsTarget = new DropTarget(tree, DND.DROP_MOVE | DND.DROP_COPY); treeAsTarget.setTransfer(types); // DropTargetEvent fields: // currentDataType: // the Transfer type of the data the target prefers to receive; // can be set -- see the method comments below // dataTypes: // the array of Transfer types the source can "send" // detail: // the operation the user is trying to perform; one of: // DROP_MOVE - move from source to target; remove from source // DROP_COPY - copy the source to target; leave the source // DROP_LINK - create a link of the source at the target // DROP_DEFAULT - indicator that target must choose operation // DROP_NONE - indicator that user is trying an unsupported op // may be set to the operation the target feels is correct // (thus, if initially DEFAULT, then the operation that would be // performed; if initially DEFAULT and not changed, it will appear // to the user as a MOVE -- also, set to NONE if target determines // operation is not permitted) // feedback: // bitwise OR'ing of feedback effects displayed to the user; // can be set using the following constants: // FEEDBACK_SELECT - item under cursor is selected // FEEDBACK_SCROLL - allows scrolling to make items visible // FEEDBACK_EXPAND - allows tree items to be expanded // FEEDBACK_INSERT_BEFORE - insertion mark before item under cursor // FEEDBACK_INSERT_AFTER - insertion mark after item under cursor // FEEDBACK_NONE - no feedback // item: // TreeItem or TableItem under the cursor, if applicable // operations: // bitwise OR'ing of the operations that the DragSource can support treeAsTarget.addDropListener(new TreeDropTargetEffect(tree) { protected static final int DRAG_FEEDBACK = DND.FEEDBACK_EXPAND | DND.FEEDBACK_INSERT_BEFORE | DND.FEEDBACK_SCROLL; protected static final int DRAG_APP_FEEDBACK = DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL; protected int requestedOp = DND.DROP_MOVE; @Override public void dragEnter(DropTargetEvent evt) { // Set evt.detail to DND.DROP_NONE when the operation is a no-op // or if the presented type is unacceptable. Other choices // that make sense: DND.DROP_MOVE, DND.DROP_COPY // evt.currentDataType is the type preferred by the target. // evt.dataTypes contains types provided by the source. super.dragEnter(evt); if (currentDnDSource != getControl()) { evt.detail = DND.DROP_NONE; } else { requestedOp = evt.detail; } } @Override public void dragLeave(DropTargetEvent evt) { // If any resources are allocated in dragEnter, free them here. // The dragLeave event also occurs if the user cancels the // Drag-and-Drop operation by hitting Escape; it is also called // just before a drop() is invoked. if (currentDndTaskAppDropRow != null) { currentDndTaskAppDropRow.setBackground(currentDnDColumn, ProjectUIModel.unselectedTaskBackgroundColor); } super.dragLeave(evt); } @Override public void dragOperationChanged(DropTargetEvent evt) { // User pressed a modifier key, possibly switching between // DROP_MOVE and DROP_COPY; as a result, // change evt.detail if desired here and // change evt.currentDataType if desired here. if ((evt.detail != DND.DROP_MOVE) && (evt.detail != DND.DROP_COPY)) { evt.detail = DND.DROP_NONE; } requestedOp = evt.detail; super.dragOperationChanged(evt); } @Override public void dragOver(DropTargetEvent evt) { // Can change evt.detail if desired here // Can change evt.currentDataType if desired here // Determine the feedback based on the item the evt is "over" // Can set evt.feedback to an OR of any of the following: // DND.FEEDBACK_SELECT, DND.FEEDBACK_SCROLL, // DND.FEEDBACK_EXPAND, DND.FEEDBACK_INSERT_BEFORE, and // DND.FEEDBACK_INSERT_AFTER if (currentDndTaskAppDropRow != null) { currentDndTaskAppDropRow.setBackground(currentDnDColumn, ProjectUIModel.unselectedTaskBackgroundColor); } Point toTreeEvtLoc = tree.toControl(evt.x, evt.y); //System.out.println("dragOver; set feedback here?"); if (currentDnDSource != getControl()) { evt.detail = DND.DROP_NONE; evt.feedback = DND.FEEDBACK_NONE; } else if (currentDnDColumn == 0) { // Moving tasks evt.feedback = DRAG_FEEDBACK; evt.detail = requestedOp; TreeItem row = tree.getItem(toTreeEvtLoc); if ((row != null) && (row.getData() != null)) { if (((AUndertaking) row.getData()).isSpawned()) { evt.detail = DND.DROP_NONE; evt.feedback = DND.FEEDBACK_NONE; } } } else { // Moving task applications evt.feedback = DRAG_APP_FEEDBACK; TreeColumn column = findColumn(toTreeEvtLoc.x); if (column == null) { evt.detail = DND.DROP_NONE; } else { Design design = (Design) column.getData(); if (design != contextSelection.getSelectedDesign()) { evt.detail = DND.DROP_NONE; } else { TreeItem row = tree.getItem(toTreeEvtLoc); if ((row == null) || (row.getData() == null)) { evt.detail = DND.DROP_NONE; } else { AUndertaking task = (AUndertaking) row.getData(); if (task.isTaskGroup() || task.isSpawned() || contextSelection.isTaskSelected(task)) { evt.detail = DND.DROP_NONE; } else { evt.detail = requestedOp; currentDndTaskAppDropRow = row; currentDndTaskAppDropRow.setBackground(currentDnDColumn, CONTEXT_COLOR); } } } } } super.dragOver(evt); } @Override public void dropAccept(DropTargetEvent evt) { // Can change evt.detail if desired here. // Provide one last chance to define the type of data that // will be returned in the drop event; thus, change // evt.currentDataType if desired here super.dropAccept(evt); } @Override public void drop(DropTargetEvent evt) { // When the drop operation is completed, update the // evt.detail field with the operation performed. // Do the operation! AUndertaking beforeTask = null; if (evt.item != null) { beforeTask = (AUndertaking) evt.item.getData(); } if (requestedOp == DND.DROP_COPY) { if (currentDnDColumn == 0) { ProjectUI.ChangeTaskPositionParms parms = new ProjectUI.ChangeTaskPositionParms(selection, beforeTask, true); if (performAction(ProjectLID.DuplicateTaskFull, parms, true)) { evt.detail = DND.DROP_COPY; } } else { AUndertaking fromTask = (AUndertaking) currentDnDRow.getData(); AUndertaking toTask = (AUndertaking) currentDndTaskAppDropRow.getData(); TreeColumn column = tree.getColumn(currentDnDColumn); Design design = (Design) column.getData(); ProjectUI.MoveCopyTaskApplicationParms parms = new ProjectUI.MoveCopyTaskApplicationParms(fromTask, toTask, design); selection.setSelectedCell(currentDndTaskAppDropRow, column); if (performAction(ProjectLID.DuplicateTaskApplication, parms, true)) { uiModel.redisplayAllResults(); evt.detail = DND.DROP_COPY; } } } else if (requestedOp == DND.DROP_MOVE) { if (currentDnDColumn == 0) { ProjectUI.ChangeTaskPositionParms parms = new ProjectUI.ChangeTaskPositionParms(selection, beforeTask, false); if (performAction(ProjectLID.ChangeTaskPosition, parms, true)) { evt.detail = DND.DROP_MOVE; } } else { AUndertaking fromTask = (AUndertaking) currentDnDRow.getData(); AUndertaking toTask = (AUndertaking) currentDndTaskAppDropRow.getData(); TreeColumn column = tree.getColumn(currentDnDColumn); Design design = (Design) column.getData(); ProjectUI.MoveCopyTaskApplicationParms parms = new ProjectUI.MoveCopyTaskApplicationParms(fromTask, toTask, design); selection.setSelectedCell(currentDndTaskAppDropRow, column); if (performAction(ProjectLID.MoveTaskApplication, parms, true)) { uiModel.redisplayAllResults(); evt.detail = DND.DROP_MOVE; } } } super.drop(evt); } }); } // setUpDragAndDrop protected void createPredicates() { requiresRegenerationPredicate = new ProjectSelectionPredicate(project) { @Override protected boolean isSatisfiedBy(Design design, AUndertaking t) { TaskApplication ta = project.getTaskApplication(t, design); if (ta != null) { Demonstration demo = ta.getDemonstration(); return demo.isObsolete() /*&& ! demo.isInvalid()*/; } return false; } }; hasComputableScriptsPredicate = new ProjectSelectionPredicate(project) { @Override protected boolean isSatisfiedBy(Design design, AUndertaking t) { TaskApplication taskApp = project.getTaskApplication(t, design); if (taskApp != null) { IPredictionAlgo activeAlg = taskApp.determineActiveAlgorithm(project); APredictionResult result = taskApp.getResult(taskApp.getFirstModelGenerator(), activeAlg); if ((result != null) && ! result.canBeRecomputed()) { return false; } return taskApp.hasComputableScript() && ! taskApp.getDemonstration().isInvalid(); } return project.getDefaultAlgo() == SNIFACTPredictionAlgo.ONLY; } }; hasComputedResultPredicate = new ProjectSelectionPredicate(project) { @Override protected boolean isSatisfiedBy(Design design, AUndertaking t) { TaskApplication taskApp = project.getTaskApplication(t, design); return ((taskApp != null) && taskApp.hasComputedResult()); } }; hasResultStepsPredicate = new ProjectSelectionPredicate(project) { @Override protected boolean isSatisfiedBy(Design design, AUndertaking t) { TaskApplication taskApp = project.getTaskApplication(t, design); return (taskApp != null) && taskApp.hasResultSteps(); } }; hasScriptsPredicate = new ProjectSelectionPredicate(project) { @Override protected boolean isSatisfiedBy(Design design, AUndertaking t) { TaskApplication taskApp = project.getTaskApplication(t, design); return (taskApp != null) && taskApp.hasScript(); } }; hasTracesPredicate = new ProjectSelectionPredicate(project) { @Override protected boolean isSatisfiedBy(Design design, AUndertaking t) { TaskApplication taskApp = project.getTaskApplication(t, design); return (taskApp != null) && taskApp.hasResultTraces(); } }; hasMultipleScriptsPredicate = new ProjectSelectionPredicate(project) { protected int numScripts = 0; @Override protected void resetState() { numScripts = 0; } @Override protected boolean isSatisfiedBy(Design design, AUndertaking t) { TaskApplication taskApp = project.getTaskApplication(t, design); if (taskApp != null) { Iterator<CognitiveModelGenerator> modelGens = taskApp.getModelGenerators(); while (modelGens.hasNext()) { CognitiveModelGenerator modelGen = modelGens.next(); Script script = taskApp.getScript(modelGen); if (script != null) { if (++numScripts > 1) { return true; } } } } return false; } }; } @Override protected Object getModelObject() { return project; } public ProjectSelectionState getSelectionState() { return selection; } public ProjectInteraction getInteraction() { return interaction; } /** * Standard interaction needed by AController; * leaf subclasses must implement. * * @author mlh */ @Override public Interaction getStandardInteraction() { return interaction; } @Override public View getView() { return view; } @Override protected String buildWindowMenuLabel() { return buildWindowMenuLabel(project); } @Override protected void updateTitle() { view.setWindowTitle(modificationFlag + PROJECT_PREFIX + ": " + project.getName() + (OSUtils.MACOSX ? "" : UI.WINDOW_TITLE)); } @Override protected void updateWindowMenus() { menuData.setNexusLabel(project.getName()); super.updateWindowMenus(); } /** * Recover any system resources being used to support this window/view. * * @author mlh */ @Override public void dispose() { CogTool.selectionPhase.removeDelayedWork(delayedTaskSelection); CogTool.selectionPhase.removeDelayedWork(delayedDesignSelection); CogTool.selectionPhase.removeDelayedWork(delayedCellSelection); CogTool.repaintPhase.removeDelayedWork(delayedRepainting); uiModel.removeAllHandlers(this); uiModel.dispose(); selection.removeAllHandlers(this); project.removeAllHandlers(this); Iterator<Design> designs = project.getDesigns().iterator(); while (designs.hasNext()) { Design design = designs.next(); design.removeAllHandlers(this); } if (taskSelectListener != null) { tree.removeSelectionListener(taskSelectListener); taskSelectListener = null; } super.dispose(); } protected boolean selectionRequiresRegeneration(ProjectSelectionState seln) { return requiresRegenerationPredicate.isSatisfiedBy(seln); } // selectionRequiresRegeneration protected boolean taskHasComputableScripts(ProjectSelectionState seln) { return hasComputableScriptsPredicate.isSatisfiedBy(seln); } // taskHasComputableScripts protected boolean selectionHasComputedResult(ProjectSelectionState seln) { return hasComputedResultPredicate.isSatisfiedBy(seln); } // selectionHasComputedResult protected boolean selectionHasResultSteps(ProjectSelectionState seln) { return hasResultStepsPredicate.isSatisfiedBy(seln); } protected boolean selectionHasScripts(ProjectSelectionState seln) { return hasScriptsPredicate.isSatisfiedBy(seln); } // selectionHasScripts protected boolean taskHasScripts(AUndertaking task, Design design) { return hasScriptsPredicate.checkSatisifies(design, task); } protected boolean selectionHasTraces(ProjectSelectionState seln) { return hasTracesPredicate.isSatisfiedBy(seln); } // selectionHasTraces /** * Sets the "always-enabled" widgets; * call this at the end of the subclass constructor! * * @author mlh */ @Override protected void setInitiallyEnabled(boolean forConstruction) { super.setInitiallyEnabled(forConstruction); setEnabled(ProjectLID.CopyResultsToClipboard, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.SelectAll, ListenerIdentifierMap.ALL, MenuUtil.ENABLED, L10N.get("PUI.SelectAllTasks", "Select All Tasks")); setEnabled(CogToolLID.NewDesign, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.NewTask, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.NewTaskGroup, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.NewDesign, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.ImportXML, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.ImportWebCrawl, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.ExportToXML, ListenerIdentifierMap.ALL, MenuUtil.ENABLED, EXPORT_PROJECT_LABEL); setEnabled(CogToolLID.ExportResultsToCSV, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); } public void setLIDEnabledState() { setViewEnabledState(selection, ListenerIdentifierMap.NORMAL); } protected boolean snifActTasksSelected(ProjectSelectionState sel, int flags) { AUndertaking[] tasks = sel.getSelectedTasks(flags); if (tasks.length == 0) { return false; } boolean allSnifAct = true; for (AUndertaking task : tasks) { AUndertaking parentGroup; if (flags == TaskSelectionState.TASK_GROUPS_ONLY) { parentGroup = task; } else { parentGroup = task.getTaskGroup(); } boolean taskIsSnifAct = ((parentGroup != null) && ! NullSafe.equals(WidgetAttributes.NO_CONTEXT, parentGroup.getAttribute(WidgetAttributes.SNIFACT_CONTEXT_ATTR))); allSnifAct = allSnifAct && taskIsSnifAct; } return allSnifAct; } /** * Enables or disables LIDs as appropriate * @param sel the selection state on which to base enabling/disabling * @param availability NORMAL or CONTEXT * @see ListenerIdentifierMap */ protected void setViewEnabledState(ProjectSelectionState sel, Boolean availability) { boolean hasDesign = sel.getSelectedDesign() != null; setEnabled(CogToolLID.AddDesignDevices, availability, hasDesign); String scriptLabel = ""; scriptLabel = hasMultipleScripts(sel) ? " " + SCRIPTS_LABEL : " " + SCRIPT_LABEL; String label = ""; int selectedTaskCount = sel.getSelectedTaskCount(); boolean isSnifActTask = snifActTasksSelected(sel, TaskSelectionState.PRUNE_SELECTION); boolean isSnifActGroup = snifActTasksSelected(sel, TaskSelectionState.TASK_GROUPS_ONLY); if (selectedTaskCount > 0) { AUndertaking tasks[] = sel.getSelectedTasks(TaskSelectionState.ORDER_SELECTION); boolean allGroups = true; for (int i = 0; i < tasks.length; i++) { if (! tasks[i].isTaskGroup()) { allGroups = false; } } if (allGroups) { label = (selectedTaskCount > 1) ? (" " + TASK_GROUPS_LABEL) : (" " + TASK_GROUP_LABEL); } else { label = (selectedTaskCount > 1) ? (" " + TASKS_LABEL) : (" " + TASK_LABEL); } } if (hasDesign) { label = " " + DESIGN_LABEL; setEnabled(CogToolLID.ExportToXML, ListenerIdentifierMap.ALL, MenuUtil.ENABLED, L10N.get("PR.ExportDesignXMLLabel", "Export Design to XML")); } else { setEnabled(CogToolLID.ExportToXML, ListenerIdentifierMap.ALL, MenuUtil.ENABLED, EXPORT_PROJECT_LABEL); } String cutCopyLabel = (editor.getEditor() != null) ? "" : label; String regenerateString = regenerateTitle; boolean requiresRegeneration = selectionRequiresRegeneration(sel); if (requiresRegeneration) { regenerateString += scriptLabel; } setEnabled(CogToolLID.RegenerateScript, availability, requiresRegeneration, regenerateString); AUndertaking[] tasks = sel.getSelectedTasks(TaskSelectionState.PRUNE_SELECTION); boolean singleTask = selectedTaskCount == 1; boolean cellSelected = hasDesign && singleTask; boolean anySelection = hasDesign || (selectedTaskCount > 0); setEnabled(CogToolLID.ExportDesignToHTML, availability, hasDesign); setEnabled(ProjectLID.ExportDictToCSV, availability, hasDesign); setEnabled(ProjectLID.ImportDict, availability, hasDesign); setEnabled(CogToolLID.CaptureBehavior, availability, true); if (cellSelected) { setEnabled(CogToolLID.Paste, availability, false); setEnabled(CogToolLID.Duplicate, availability, false, MenuFactory.DUPLICATE_STRING); setEnabled(CogToolLID.Rename, availability, false, MenuFactory.RENAME_STRING); setEnabled(CogToolLID.Cut, availability, false, MenuFactory.CUT_STRING); setEnabled(CogToolLID.Copy, availability, false, MenuFactory.COPY_STRING); setEnabled(CogToolLID.Delete, availability, false, MenuFactory.DELETE_STRING); String editLabel = MenuFactory.EDIT_STRING + " " + SCRIPT_LABEL; boolean editEnabled = true; if (tasks[0].isTaskGroup()) { if (GroupNature.SUM.equals(((TaskGroup) tasks[0]).getNature())) { editLabel = VIEW_SCRIPTS; } else { editLabel = MenuFactory.EDIT_STRING; editEnabled = false; } } setEnabled(CogToolLID.Edit, availability, editEnabled, editLabel); if (isSnifActTask) { // If it's a task generated by a run of SNIF-ACT (and therefore // put in a group that has that attribute), the algorithm in its // cell should never be changed. setEnabled(ProjectLID.SetAlgorithmACTR6, availability, false); setEnabled(ProjectLID.SetAlgorithmSNIFACT, availability, false); setEnabled(ProjectLID.SetAlgorithmDefault, availability, false); setEnabled(ProjectLID.SetAlgorithmHuman, availability, false); setEnabled(ProjectLID.SetBackgroundComputationDefault, availability, false); setEnabled(ProjectLID.SetBackgroundComputationFalse, availability, false); setEnabled(ProjectLID.SetBackgroundComputationTrue, availability, false); } } else if (isSnifActTask) { setEnabled(CogToolLID.Paste, availability, false); setEnabled(CogToolLID.Duplicate, availability, false, MenuFactory.DUPLICATE_STRING); setEnabled(CogToolLID.Cut, availability, false, MenuFactory.CUT_STRING); setEnabled(CogToolLID.Copy, availability, false, MenuFactory.COPY_STRING); setEnabled(CogToolLID.Rename, availability, hasDesign || singleTask, MenuFactory.RENAME_STRING + label); setEnabled(CogToolLID.NewTask, availability, false); setEnabled(CogToolLID.NewTaskGroup, availability, false); } else { setEnabled(CogToolLID.NewTask, availability, true); setEnabled(CogToolLID.NewTaskGroup, availability, true); setEnabled(CogToolLID.Paste, availability, true); String dupString = MenuFactory.DUPLICATE_STRING; if (anySelection) { dupString += label; } setEnabled(CogToolLID.Duplicate, availability, anySelection, dupString); // Edit enabled if only a single design selected String editString = MenuFactory.EDIT_STRING; if (hasDesign) { editString += label; } setEnabled(CogToolLID.Edit, availability, hasDesign, editString); // Rename enabled if a single selection boolean enabled = hasDesign || singleTask; String renameString = MenuFactory.RENAME_STRING; if (enabled) { renameString += label; } setEnabled(CogToolLID.Rename, availability, enabled, renameString); // Cut, Copy, Delete, DeselectAll should be enabled // if there is any selection (task or design) setEnabled(CogToolLID.Cut, availability, anySelection, MenuFactory.CUT_STRING + cutCopyLabel); setEnabled(CogToolLID.Copy, availability, anySelection, MenuFactory.COPY_STRING + cutCopyLabel); setEnabled(CogToolLID.Delete, availability, anySelection, MenuFactory.DELETE_STRING + label); setEnabled(CogToolLID.DeselectAll, availability, anySelection); } boolean showRecompute = anySelection && taskHasComputableScripts(sel); if (! showRecompute && hasDesign && sel.getSelectedTaskCount() == 1) { Design design = sel.getSelectedDesign(); TaskApplication taskApp = project.getTaskApplication(sel.getSelectedTask(), design); if (taskApp != null && taskApp.getActiveAlgorithm() instanceof SNIFACTPredictionAlgo) { showRecompute = true; } } String recomputeString = recomputeTitle; if (showRecompute) { recomputeString += scriptLabel; } // If the user wants to call recompute let them. setEnabled(CogToolLID.RecomputeScript, availability, showRecompute, recomputeString); // The export trace is only available when a script is // computed && there are traces to export. boolean hasComputedResult = selectionHasComputedResult(sel); boolean showExport = anySelection && //hasComputedResult && selectionHasTraces(sel); setEnabled(ProjectLID.ExportTraces, availability, showExport); setEnabled(ProjectLID.DisplayTraces, availability, showExport); setEnabled(ProjectLID.ExportForSanlab, availability, showExport); // Show Visualization should only be available when a script is // computed/valid && there are ResultSteps to visualize. boolean showVis = anySelection && hasComputedResult && selectionHasResultSteps(sel); setEnabled(ProjectLID.ShowModelVisualization, availability, showVis); // The export device and script lisp files // is only available when a script is valid // valid IE: not null, not invalid boolean showExportFiles = anySelection && selectionHasScripts(sel) && hasComputedResult; setEnabled(ProjectLID.ExportActrModelFile, availability, showExportFiles); // Default to off; fix if truly enabled setEnabled(ProjectLID.MoveTaskEarlier, ListenerIdentifierMap.ALL, false); setEnabled(ProjectLID.MoveTaskLater, ListenerIdentifierMap.ALL, false); setEnabled(ProjectLID.PromoteTask, ListenerIdentifierMap.ALL, false); setEnabled(ProjectLID.DemoteTask, ListenerIdentifierMap.ALL, false); // Allow "vertical" movement if effectively only one task is selected if ((tasks != null) && ! hasDesign) { if (singleTask) { boolean spawned = tasks[0].isSpawned(); TaskGroup parent = tasks[0].getTaskGroup(); List<AUndertaking> siblings; if (parent != null) { setEnabled(ProjectLID.PromoteTask, ListenerIdentifierMap.ALL, ! spawned); siblings = parent.getUndertakings(); } else { siblings = project.getUndertakings(); } int siblingCount = siblings.size(); int atIndex = siblings.indexOf(tasks[0]); if (siblingCount > 1) { boolean notFirstChild = (atIndex > 0); setEnabled(ProjectLID.DemoteTask, ListenerIdentifierMap.ALL, notFirstChild && ! spawned); setEnabled(ProjectLID.MoveTaskEarlier, ListenerIdentifierMap.ALL, notFirstChild && ! spawned); setEnabled(ProjectLID.MoveTaskLater, ListenerIdentifierMap.ALL, (atIndex < siblingCount - 1) && ! spawned); } } else if (tasks.length > 1) { // Too many tasks selected to check conditions; // let the controller handle the error cases. setEnabled(ProjectLID.PromoteTask, ListenerIdentifierMap.ALL, ! isSnifActTask); setEnabled(ProjectLID.DemoteTask, ListenerIdentifierMap.ALL, ! isSnifActTask); } } // Stuff that is enabled only when a script exists. boolean canExport = false; if (hasDesign) { Design design = sel.getSelectedDesign(); for (AUndertaking task : tasks) { if (taskHasScripts(task, design)) { canExport = true; } } } setEnabled(CogToolLID.ExportScriptToCSV, availability, canExport); // Enable "Show XXX" options if any task groups are selected boolean enabled = false; int numTaskGroups = 0; TaskGroup group = null; for (AUndertaking task : tasks) { if (task.isTaskGroup()) { enabled = true; numTaskGroups++; group = (TaskGroup) task; } } setEnabled(ProjectLID.Ungroup, availability, enabled && ! isSnifActGroup); setEnabled(CogToolLID.ShowSum, availability, enabled && ! isSnifActGroup); setEnabled(CogToolLID.ShowMean, availability, enabled); setEnabled(CogToolLID.ShowMin, availability, enabled); setEnabled(CogToolLID.ShowMax, availability, enabled); setSelected(CogToolLID.ShowSum, availability, false); setSelected(CogToolLID.ShowMean, availability, false); setSelected(CogToolLID.ShowMin, availability, false); setSelected(CogToolLID.ShowMax, availability, false); if (enabled) { if (numTaskGroups == 1) { GroupNature nature = group.getNature(); CogToolLID id = null; if (nature == GroupNature.SUM) { id = CogToolLID.ShowSum; } else if (nature == GroupNature.MEAN) { id = CogToolLID.ShowMean; } else if (nature == GroupNature.MIN) { id = CogToolLID.ShowMin; } else if (nature == GroupNature.MAX) { id = CogToolLID.ShowMax; } setSelected(id, availability, true); } } IPredictionAlgo defaultAlgo = project.getDefaultAlgo(); // enabled state for default algorithm settings setSelected(ProjectLID.SetProjDefaultAlgoACTR, availability, defaultAlgo == ACTR6PredictionAlgo.ONLY); setSelected(ProjectLID.SetProjDefaultAlgoSNIFACT, availability, defaultAlgo == SNIFACTPredictionAlgo.ONLY); setSelected(ProjectLID.SetProjExecBackground, availability, project.getDefaultRunInBackground()); setSelected(ProjectLID.SetProjExecForeground, availability, ! project.getDefaultRunInBackground()); // (TODO: same level/contiguous/subtrees for Group Tasks???) // if the user has named the task (created the group), he can export boolean canExportHCIPA = false; if ((tasks != null) && (tasks.length > 0)) { // Must have selected a top-level group for (AUndertaking task : tasks) { if (task.isTaskGroup() && (task.getTaskGroup() == null)) { canExportHCIPA = true; } } } else if (hasDesign) { // At least one top-level task must be a group Iterator<AUndertaking> allTasks = project.getUndertakings().iterator(); while (allTasks.hasNext()) { AUndertaking u = allTasks.next(); if (u.isTaskGroup()) { canExportHCIPA = true; } } } setEnabled(ProjectLID.ExportToHCIPA, availability, canExportHCIPA); if (hasDesign) { ISimilarityDictionary dict = (ISimilarityDictionary) sel.getSelectedDesign().getAttribute(WidgetAttributes.DICTIONARY_ATTR); String newLabel = NullSafe.equals(dict, WidgetAttributes.NO_DICTIONARY) ? L10N.get("WT.GenerateDictionary", "Generate Dictionary...") : L10N.get("WT.UpdateDictionary", "Update Dictionary..."); setEnabled(ProjectLID.GenerateDictionary, availability, ! isSnifActTask, newLabel); } else if (tasks.length > 0) { List<Design> designs = project.getDesigns(); String newLabel = designs.size() > 1 ? L10N.get("WT.UpdateDictionaries", "Update Dictionaries...") : L10N.get("WT.UpdateDictionary", "Update Dictionary..."); setEnabled(ProjectLID.GenerateDictionary, availability, ! isSnifActTask, newLabel); } } /** * Transforms an "object-oriented" <code>ListenerIdentifier</code> * into a more specific value representing an actual, concrete * application function, depending upon the internal state of the * application itself (such as, based on what application elements * are currently selected). * <p> * If there is no more specific value for the given <code>id</code>, * the input value should be returned unchanged. * * @param id the key specifying the semantic nature of the * action to be performed * @return the specific value representing an actual, concrete * application function, or, if none exists, the input value * @author mlh */ @Override public ListenerIdentifier transmute(ListenerIdentifier id, boolean isContextSelection) { ListenerIdentifier specificLID = super.transmute(id, isContextSelection); // Check if super has already specialized this if (specificLID != id) { return specificLID; } ProjectSelectionState sel; if (isContextSelection) { sel = contextSelection; } else { sel = selection; } // Give priority to design stuff if (sel.getSelectedDesign() != null) { if (sel.getSelectedTaskCount() == 1) { specificLID = ProjectLID.scriptLIDs.get(id); if (ProjectLID.EditScript.equals(specificLID)) { if (sel.getSelectedTaskCount() == 1) { AUndertaking t = sel.getSelectedTask(); if (sel.getSelectedTask().isTaskGroup()) { if (((TaskGroup) t).getNature() == GroupNature.SUM) { specificLID = ProjectLID.ViewGroupScript; } } } } } else { specificLID = ProjectLID.designLIDs.get(id); } } // Fallback to undertaking stuff if necessary else { specificLID = ProjectLID.taskLIDs.get(id); } return (specificLID != null) ? specificLID : id; } /** * 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); int selectionMask = canIDCauseSelection(id); if (isSelectionFlagSet(selectionMask, ProjectLID.CAUSES_TASK_SELECTION)) { delayedTaskSelection.setActive(true); } if (isSelectionFlagSet(selectionMask, ProjectLID.CAUSES_DESIGN_SELECTION)) { delayedDesignSelection.setActive(true); } if (isSelectionFlagSet(selectionMask, ProjectLID.CAUSES_CELL_SELECTION)) { delayedCellSelection.setActive(true); } } /** * Fetches the parameters needed by any <code>performAction</code> * invoked for the "specialized" <code>ListenerIdentifier</code>. * In some cases, the determination of the parameters requires * information from the original "general" LID subclass instance * (see, for example, SEDemoLID). * * @param originalLID the general LID value returned from a menu command * @param transmutedLID the specific value representing an actual, * concrete application function returned by * a call to <code>specialize()</code> * @param isContextSelection true if we should parameterize based on * the current contextual selection; * false to use the standard selection * @return the parameters the <code>IListenerAction</code> may require * to perform the requested semantic action * @author mlh */ @Override public Object getParameters(ListenerIdentifier originalLID, ListenerIdentifier transmutedLID, boolean isContextSelection) { Object parameters = super.getParameters(originalLID, transmutedLID, isContextSelection); if (parameters != UNSET) { return parameters; } setUpPerformAction(transmutedLID); if (isContextSelection) { return contextSelection; } return selection; } /** * Allows the interfaces to clean up any feedback provided to the * user before and during a performAction. * * @param okToContinue the return value from performAction * @param menuHidden whether or not the context menu is dismissed * without selecting an operation to perform * @author mlh */ @Override public void cleanup(boolean okToContinue, boolean menuHidden) { if (! tree.isDisposed()) { selectOverBox(null); } super.cleanup(okToContinue, menuHidden); } public void selectAllTasks() { selection.selectAllTasks(); } public void deselectAllTasks(){ selection.deselectAll(); } /** * Triggers the task-renaming functionality in the UI on the undertaking. */ public void initiateTaskRename(AUndertaking undertaking) { initiateTaskRename(undertaking, true); } /** * Triggers the task-renaming interaction in the UI on the * given undertaking. * * @param undertaking the task to rename * @param selectAll set to true if the entire string should be selected; * false implies that the caret should be at string end * @author mlh */ public void initiateTaskRename(AUndertaking undertaking, boolean selectAll) { // Cleanup may require re-establishment of the editor. renameTaskItem = uiModel.getTaskTreeItem(undertaking); // Occasionally, initiateTaskRename is not called with cleanup initiateTaskRename(renameTaskItem, selectAll); } // initiateTaskRename /** * Triggers the task-renaming functionality in the UI on the given * TreeItem's AUndertaking. */ public void initiateTaskRename(TreeItem taskToRenameItem, boolean selectAll) { // Can progress only if no other rename is already in progress if ((editor.getEditor() == null) && (taskToRenameItem != null)) { // Ensure the selection is set properly so it looks right when done selection.setSelectedItem(taskToRenameItem); final AUndertaking task = (AUndertaking) taskToRenameItem.getData(); // The editor must have the same size as the cell. editor.horizontalAlignment = SWT.LEFT; editor.grabHorizontal = true; // The control that will be the editor must be a child of the Tree ManagedText newEditor = new ManagedText(tree, SWT.SINGLE | SWT.LEFT, Keypad.FULL_KEYPAD) { @Override public boolean confirm(int focusRule) { return commitRenameTask(true); } @Override public void cancel() { cleanupTaskEditor(); } }; newEditor.setFocus(); String taskName = task.getName(); int nameLength = taskName.length(); newEditor.setText(taskName); if (selectAll) { newEditor.selectAll(); } else { newEditor.setSelection(nameLength, nameLength); } editor.setEditor(newEditor, taskToRenameItem); setViewEnabledState(selection, ListenerIdentifierMap.NORMAL); } } // initiateTaskRename /** * Reports a task renaming to the controller. * @param item */ protected boolean commitRenameTask(boolean activeCommit) { boolean success = true; if (editor.getEditor() != null) { Text text = (Text) editor.getEditor(); String newName = text.getText(); TreeItem item = editor.getItem(); // Get task & parent taskgroup from item, don't use this.selection! AUndertaking taskToRename = (AUndertaking) item.getData(); TreeItem parentItem = item.getParentItem(); TaskGroup parentGroup = (TaskGroup) ((parentItem != null) ? parentItem.getData() : null); cleanupTaskEditor(); success = performAction((CogToolPref.HCIPA.getBoolean()) ? ProjectLID.HCIPARenameTask : ProjectLID.RenameTask, new ProjectUI.TaskRenameEvent(taskToRename, newName, parentGroup), false); if (activeCommit) { renameTaskItem = null; } } return success; } /** * Removes stale Text control and selection listener. */ protected void cleanupTaskEditor() { // Remove and dispose the text box Control oldEditor = editor.getEditor(); if (oldEditor != null) { // We defer this disposal until the main event loop as otherwise // we crash mysteriously in Leopard (OS X 10.5). The oldEditor // is actually a subclass (suspect for SWT controls!), and my // conjecture is that some method in the superclass is still // depending upon the object not having been disposed at it is // unwinding the stack. Anyway, this appears to fix the problem, // and certainly should make the patient no worse. WindowUtil.deferDisposal(oldEditor); editor.setEditor(null); setViewEnabledState(selection, ListenerIdentifierMap.NORMAL); } } /** * Discovers how many levels of tree are currently expanded. * @return number of visible levels below this point */ protected int numExpandedLevels(TreeItem[] items) { if (items == null) { // base case -- leaves don't add any indent return 0; } // recursive case // find the max below this point, add 1 int max = 0; for (TreeItem item : items) { // prune anything not expanded if (item.getExpanded()) { int val = numExpandedLevels(item.getItems()); if (val > max) { max = val; } } } return max + 1; } protected TreeColumn findColumn(int x) { int initialOffset = 0; if (OSUtils.MACOSX) { // XXX: dirty hacks around SWT bugs initialOffset = (numExpandedLevels(tree.getItems()) - 1) * 24; // on the mac, X is not offset by the "origin" either x += tree.getHorizontalBar().getSelection(); } TreeColumn[] allCols = tree.getColumns(); int[] columnOrdering = uiModel.getCurrentDesignOrdering(); int numCols = tree.getColumnCount(); for (int i = 0; i < numCols; i++) { int w = allCols[columnOrdering[i]].getWidth(); // XXX: Hack for MacOSX where the initial column is "wider" as items are expanded if (i == 0) { x -= initialOffset; } if (x < w) { return allCols[columnOrdering[i]]; } x -= w; } return null; } protected Listener createSetToolTipListener() { return new Listener() { public void handleEvent(Event evt) { String toolTipText = null; TreeColumn col = findColumn(evt.x); if (col != null) { Design colData = (Design) col.getData(); if (colData != null) { toolTipText = colData.getName(); } } TreeItem row = tree.getItem(new Point(evt.x, evt.y)); if (row != null) { AUndertaking rowData = (AUndertaking) row.getData(); if (rowData != null) { if (toolTipText != null) { toolTipText += '@' + rowData.getName(); } else { toolTipText = rowData.getName(); } } } tree.setToolTipText(toolTipText); } }; } protected Listener createHighlightColumnPaintListener() { return new Listener() { public void handleEvent(Event evt) { if (overBox != null) { GraphicsUtil.drawOverlay(evt.gc, curImgData, overBox); } } }; } protected Rectangle computeSelectedColumnArea() { return computeColumnArea(selection.getSelectedColumn()); } protected Rectangle computeColumnArea(TreeColumn column) { if (column != null) { Point size = tree.getSize(); Rectangle treeArea = new Rectangle(0, 0, size.x, size.y); int[] columnOrdering = uiModel.getCurrentDesignOrdering(); int i = 0; TreeColumn nextCol = tree.getColumn(i++); while (nextCol != column) { treeArea.x += nextCol.getWidth(); // if (OSUtils.MACOSX) { // // XXX: dirty hacks around SWT bugs // treeArea.x += 30; // } nextCol = tree.getColumn(columnOrdering[i++]); } treeArea.width = column.getWidth(); if (OSUtils.MACOSX) { // XXX: dirty hacks around SWT bugs int off = ((numExpandedLevels(tree.getItems()) - 1) * 24); treeArea.x += off; // treeArea.width += 1; treeArea.y += 1; treeArea.height -= 16; treeArea.x += 4; } return treeArea; } return null; } protected void showContextMenu(ProjectSelectionState seln, boolean context) { setViewEnabledState(seln, ListenerIdentifierMap.CONTEXT); if (seln.getSelectedDesign() != null) { view.showContextMenuForDesign(context); } else { int selectedTaskCount = seln.getSelectedTaskCount(); if (selectedTaskCount > 0) { boolean isTaskGroup; if (selectedTaskCount == 1) { // no need to prune task list! isTaskGroup = seln.getSelectedTask().isTaskGroup(); } else { isTaskGroup = false; } view.showContextMenuForUndertaking(isTaskGroup, context); } else { view.showContextMenuForBlankSpace(); } } } protected void selectOverBox(Rectangle bounds) { overBox = bounds; curImgData = CONTEXT_IMAGEDATA; tree.redraw(); } @Override public void showContextMenu() { showContextMenu(selection, View.SELECTION); } // Override to set the correct context menu on the frame // item item-selected column design where selection to use //(a) ok n/a ok ok cell temporary cell //(b) ok yes ok null task normal selection //(c) ok no ok null task temporary task //(d) ok n/a null n/a right of cells temporary task //(e) null n/a ok ok design temporary design //(f) null n/a ok null bottom-left no selection //(g) null n/a null n/a bottom-right no selection // TODO: dfm -- make the code match the above!!!!! @Override public void showContextMenu(int x, int y) { // Clear any stale state contextSelection.deselectAll(); // Check which region of the frame was hit TreeItem item = tree.getItem(new Point(x, y)); TreeColumn column = findColumn(x); // See if the context invocation was made beyond all designs if (column == null) { if (item == null) { showContextMenu(); // see (g) above } else { // see (d) above selectOverBox(item.getBounds()); contextSelection.addSelectedTask((AUndertaking) item.getData()); showContextMenu(contextSelection, View.CONTEXT); } } // If not, the invocation occurred somewhere within the table else { Design design = (Design) column.getData(); // Detect a context invocation in the table header/footer if (item == null) { // Detect a context invocation under the "tasks" heading if (design == null) { // see (f) above showContextMenu(); } // Otherwise the invocation lies under a design heading else { // see (e) above // TODO: Really? What if something else was selected? selectOverBox(computeColumnArea(column)); contextSelection.setSelectedDesign(design); showContextMenu(contextSelection, View.CONTEXT); } } // Detect a context invocation inside the table body else { AUndertaking undertaking = (AUndertaking) item.getData(); // Check for context invocation under the "tasks" column if (design == null) { // Set up the contextual selection state as necessary if (selection.isTaskSelected(undertaking)) { showContextMenu(); // see (b) above } else { // see (c) above selectOverBox(item.getBounds()); contextSelection.addSelectedTask(undertaking); showContextMenu(contextSelection, View.CONTEXT); } } // Otherwise at the intersection of a task and a design else { // see (a) above selection.setSelectedCell(item, column); Rectangle bounds = item.getBounds(tree.indexOf(column)); if (OSUtils.MACOSX) { // XXX: DIRTY HACK TO fix SWT bug. bounds.y -= 1; bounds.height += 1; } selectOverBox(bounds); // TODO: instead of the following, pass undertaking and // design in to showContextMenuForIntersection() Menu contextMenu = view.getContextMenuForIntersection(project, undertaking, design); // Set up the contextual selection state as necessary contextSelection.setSelectedDesign(design); contextSelection.addSelectedTask(undertaking); setViewEnabledState(contextSelection, ListenerIdentifierMap.CONTEXT); contextMenu.setVisible(true); } } } } public void onRowCreation(TreeItem row) { delayedTaskSelection.addToSelection(row.getData(), row); } public void onRowDeletion(TreeItem row) { cleanupTaskEditor(); selection.deselectRow(row); delayedTaskSelection.removeFromSelection(row.getData()); delayedCellSelection.removeFromSelection(row.getData()); } public void onColumnCreation(TreeColumn column) { // When a resize occurs, update the scroll bars column.addControlListener(onResizeColumn); // Change column selection on click. column.addListener(SWT.Selection, columnSelectionListener); if (column.getData() != null) { // Edit design on double-click. column.addListener(SWT.DefaultSelection, columnDefaultSelectionListener); delayedDesignSelection.addToSelection(column.getData(), column); } } public void onColumnDeletion(TreeColumn column) { // It's ok if this test fails; might be a redo! if (selection.getSelectedColumn() == column) { selection.deselectColumn(column); } if (column.getData() != null) { delayedDesignSelection.removeFromSelection(column.getData()); delayedCellSelection.removeFromSelection(column.getData()); } } public boolean hasMultipleScripts(ProjectSelectionState seln) { return hasMultipleScriptsPredicate.isSatisfiedBy(seln); } }