/******************************************************************************* * 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.controller; import java.io.BufferedWriter; import java.io.File; import java.io.FileFilter; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; import edu.cmu.cs.hcii.cogtool.CogToolClipboard; import edu.cmu.cs.hcii.cogtool.CogToolLID; import edu.cmu.cs.hcii.cogtool.CogToolPref; import edu.cmu.cs.hcii.cogtool.CogToolWorkThread; import edu.cmu.cs.hcii.cogtool.FrameTemplateSupport; import edu.cmu.cs.hcii.cogtool.model.AAction; import edu.cmu.cs.hcii.cogtool.model.Design; import edu.cmu.cs.hcii.cogtool.model.DesignUtil; import edu.cmu.cs.hcii.cogtool.model.DeviceType; import edu.cmu.cs.hcii.cogtool.model.DoublePoint; import edu.cmu.cs.hcii.cogtool.model.DoubleRectangle; import edu.cmu.cs.hcii.cogtool.model.ExportCogToolXML; import edu.cmu.cs.hcii.cogtool.model.Frame; import edu.cmu.cs.hcii.cogtool.model.InputDevice; import edu.cmu.cs.hcii.cogtool.model.Project; import edu.cmu.cs.hcii.cogtool.model.Project.ITaskDesign; import edu.cmu.cs.hcii.cogtool.model.SkinType; import edu.cmu.cs.hcii.cogtool.model.TaskApplication; import edu.cmu.cs.hcii.cogtool.model.Transition; import edu.cmu.cs.hcii.cogtool.model.TransitionSource; import edu.cmu.cs.hcii.cogtool.model.WidgetAttributes; import edu.cmu.cs.hcii.cogtool.ui.ActionProperties; import edu.cmu.cs.hcii.cogtool.ui.DesignEditorInteraction; import edu.cmu.cs.hcii.cogtool.ui.DesignEditorLID; import edu.cmu.cs.hcii.cogtool.ui.DesignEditorSelectionState; import edu.cmu.cs.hcii.cogtool.ui.DesignEditorUI; import edu.cmu.cs.hcii.cogtool.ui.FrameSelectionState; import edu.cmu.cs.hcii.cogtool.ui.Interaction.ProgressBar; import edu.cmu.cs.hcii.cogtool.ui.ProjectLID; import edu.cmu.cs.hcii.cogtool.ui.RcvrExceptionHandler; import edu.cmu.cs.hcii.cogtool.ui.SelectionState; import edu.cmu.cs.hcii.cogtool.ui.UI; import edu.cmu.cs.hcii.cogtool.ui.ZoomableUI; import edu.cmu.cs.hcii.cogtool.util.AListenerAction; import edu.cmu.cs.hcii.cogtool.util.AUndoableEdit; import edu.cmu.cs.hcii.cogtool.util.ClipboardUtil; import edu.cmu.cs.hcii.cogtool.util.CompoundUndoableEdit; import edu.cmu.cs.hcii.cogtool.util.FileUtil; import edu.cmu.cs.hcii.cogtool.util.GraphicsUtil; import edu.cmu.cs.hcii.cogtool.util.IListenerAction; import edu.cmu.cs.hcii.cogtool.util.IUndoableEdit; import edu.cmu.cs.hcii.cogtool.util.IUndoableEditSequence; import edu.cmu.cs.hcii.cogtool.util.L10N; import edu.cmu.cs.hcii.cogtool.util.NamedObjectUtil; import edu.cmu.cs.hcii.cogtool.util.ObjectSaver; import edu.cmu.cs.hcii.cogtool.util.RcvrClipboardException; import edu.cmu.cs.hcii.cogtool.util.RcvrIOException; import edu.cmu.cs.hcii.cogtool.util.RcvrIOSaveException; import edu.cmu.cs.hcii.cogtool.util.RcvrIllegalStateException; import edu.cmu.cs.hcii.cogtool.util.RcvrImageException; import edu.cmu.cs.hcii.cogtool.util.RcvrOutOfMemoryException; import edu.cmu.cs.hcii.cogtool.util.RcvrUIException; import edu.cmu.cs.hcii.cogtool.util.RecoverableException; import edu.cmu.cs.hcii.cogtool.util.ThreadManager; import edu.cmu.cs.hcii.cogtool.util.UndoManager; /** * The Controller for semantic actions specific to the Project editor window. * <p> * Semantic actions and parameters: * Undo <no parameters> * Redo <no parameters> * SelectAll <no parameters> (selects all frames) * DeselectAll ISelectionState (objects to be deselected) * * Paste <no parameters> * CopyFrame IFrameSelectionState (what to copy) * CutFrame IFrameSelectionState (what to cut) * * ExportDesignToHTML <no parameters> * SkinNone/SkinWireFrame/SkinMacOSX/SkinWinXP/SkinPalm <no parameters> * * AddDesignDevices <no parameters> * NewFrame <no parameters> * DuplicateFrame IFrameSelectionState (what to duplicate) * InitiateFrameRename IFrameSelectionState (what to rename) * RenameFrame FrameRenameParameters (what to rename, new name) * EditFrame IFrameSelectionState (what to edit) * DeleteFrame IFrameSelectionState (what to delete) * MoveFrames MoveParameters (what to move, by how much) * NudgeLeft/Right/Up/Down IFrameSelectionState (what to move) * SetBackgroundImage IFrameSelectionState (which frames to set bkg) * RemoveBackgroundImage IFrameSelectionState (which frames to set bkg) * SetWidgetColor IFrameSelectionState (frames to set color) * ImportImageDirectory <no parameters> * * NewTransition NewTransitionParameters (src, frame, new frame loc) * EditTransition DesignEditorSelectionState (what to edit) * DeleteTransition DesignEditorSelectionState (what to delete) * ChangeTarget ChangeTargetParameters (transition, new target) * ChangeSource ChangeSourceParameters (transition, new source) * ChangeWidgetAction ChangeActionParameters (transitions, action prms) * ChangeDeviceAction ChangeActionParameters (transitions, action prms) */ public class DesignEditorController extends ZoomableController { protected static final String DEFAULT_FRAME_PREFIX = L10N.get("DE.FrameNamePrefix", "Frame"); public static final int INITIAL_FRAME_SUFFIX = 1; public static final String INITIAL_FRAME_NAME = DEFAULT_FRAME_PREFIX + " " + INITIAL_FRAME_SUFFIX; protected static final String moveFrames = L10N.get("UNDO.DE.MoveFrames", "Move Frames"); protected static final String moveFrame = L10N.get("UNDO.DE.MoveFrame", "Move Frame"); protected static final String renameFrame = L10N.get("UNDO.DE.RenameFrame", "Rename Frame"); protected static final String renameDesign = L10N.get("UNDO.DE.RenameFrame", "Rename Design"); protected static final String deleteFrames = L10N.get("UNDO.DE.DeleteFrames", "Delete Frames"); protected static final String deleteFrame = L10N.get("UNDO.DE.DeleteFrame", "Delete Frame"); protected static final String deleteTransitions = L10N.get("UNDO.DE.DeleteTransitions", "Delete Transitions"); protected static final String deleteTransition = L10N.get("UNDO.DE.DeleteTransition", "Delete Transition"); protected static final String selectWidgetColor = L10N.get("DESIGN.WIDGET.selectColor", "Select Widget Color"); protected static final String changeTransitionSource = L10N.get("UNDO.DE.ChangeSource", "Change Transition Source"); protected static final String changeAction = L10N.get("UNDO.DE.ChangeAction", "Change Action"); protected static final String duplicateFrames = L10N.get("UNDO.DE.DuplicateFrames", "Duplicate Frames"); protected static final String duplicateFrame = L10N.get("UNDO.DE.DuplicateFrame", "Duplicate Frame"); protected static final String nothingPasted = L10N.get("DE.NothingPasted", "Nothing pasted"); protected static final String pasteComplete = L10N.get("DE.PasteComplete", "object(s) pasted"); protected static final String removeBackgroundImage = L10N.get("UNDO.FE.RemoveBackgroundImage", "Remove Frame Background Image"); protected static final String setBackgroundImage = L10N.get("UNDO.FE.SetBackgroundImage", "Set Frame Background Image"); protected static final String setWidgetColor = L10N.get("UNDO.FE.SetWidgetColor", "Set Widget Color"); protected static final String importBackgroundImages = L10N.get("UNDO.DE.ImportBackgroundImages", "Import Background Images"); protected static final String changeSkin = L10N.get("UNDO.DE.ChangeSkin", "Change Skin"); protected static final String changeDelay = L10N.get("UNDO.DE.ChangeDelay", "Change Transition Delay"); protected static final Pattern IMAGE_FILE_NAME = Pattern.compile("(.+)\\.(jpe?g|gif|png)", Pattern.CASE_INSENSITIVE); protected static final String FRAME_COPIED = L10N.get("DE.FrameCopied", "Frame copied to the clipboard"); protected static final String FRAMES_COPIED = L10N.get("DE.FramesCopied", "Frames copied to the clipboard"); protected Design design; protected DesignEditorUI ui; protected DemoStateManager demoStateMgr; protected DesignEditorInteraction interaction; protected int nextNewFrameSuffix = INITIAL_FRAME_SUFFIX; /** * The local properties variable. Used to prevent instantiating extra * action properties on the fly. Set the value to UNSET, since the * which tab to use is always "fetched" before it is used. */ protected ActionProperties properties = new ActionProperties(ActionProperties.UNSET); protected class FrameAlignmentAction extends AlignmentAction { public FrameAlignmentAction(int alignAction) { super(alignAction); } /** * Set the expected parameter class. * The parameter is a map from each frame that is selected to the * bounds of its figure (including the name label and device box). */ public Class<?> getParameterClass() { return Map.class; } @SuppressWarnings("unchecked") public boolean performAction(Object actionParms) { Map<Frame, DoubleRectangle> frameMap = (Map<Frame, DoubleRectangle>) actionParms; CompoundUndoableEdit editSequence = new CompoundUndoableEdit(getAlignmentName(), LIDS[action]); // Reset for new computation reset(); // Get the list of widgets to align Iterator<DoubleRectangle> values = frameMap.values().iterator(); // Determine the reference widget while (values.hasNext()) { DoubleRectangle r = values.next(); // Get reference dimension we are aligning on for comparison computeReference(r); } Iterator<Map.Entry<Frame, DoubleRectangle>> entries = frameMap.entrySet().iterator(); while (entries.hasNext()) { Map.Entry<Frame, DoubleRectangle> entry = entries.next(); final Frame f = entry.getKey(); // Get the bounds DoubleRectangle bounds = entry.getValue(); final double oldX = bounds.x; final double oldY = bounds.y; // Compute where the bounds should be based on the reference computeNewOrigin(bounds); final double new_X = newX; final double new_Y = newY; // Move the widget to the new location f.setFrameOrigin(new_X, new_Y); IUndoableEdit edit = new AUndoableEdit(LIDS[action]) { @Override public String getPresentationName() { return getAlignmentName(); } @Override public void redo() { super.redo(); // Move f.setFrameOrigin(new_X, new_Y); } @Override public void undo() { super.undo(); // move to old location f.setFrameOrigin(oldX, oldY); } }; editSequence.addEdit(edit); } editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } interaction.setStatusMessage(getAlignmentName()); return true; } } public DesignEditorController(Design designToEdit, Project designProject) { super(designProject); design = designToEdit; undoMgr = UndoManager.getUndoManager(design, project); demoStateMgr = DemoStateManager.getStateManager(project, design); ui = new DesignEditorUI(design, project, undoMgr); interaction = ui.getInteraction(); assignActions(); ui.setVisible(true); } @Override protected Object getModelObject() { return getModel(); } /** * Accessor. * @return the Design model being managed by this controller */ public Design getModel() { return design; } @Override public UI getUI() { return ui; } @Override protected ZoomableUI getZoomableUI() { return ui; } @Override public void assignActions() { super.assignActions(); ui.setAction(DesignEditorLID.ExportDesignToHTML, createExportDesignToHTMLAction()); ui.setAction(DesignEditorLID.ExportToXML, createExportDesignToXMLAction()); ui.setAction(DesignEditorLID.Undo, new UndoController.UndoAction(undoMgr, interaction)); ui.setAction(DesignEditorLID.Redo, new UndoController.RedoAction(undoMgr, interaction)); ui.setAction(DesignEditorLID.Paste, createPasteAction()); ui.setAction(DesignEditorLID.CopyFrame, createCopyFrameAction()); ui.setAction(DesignEditorLID.CutFrame, createCutFrameAction()); ui.setAction(DesignEditorLID.ClearFrameTemplate, new AListenerAction() { public boolean performAction(Object prms) { FrameTemplateSupport.clearFrameTemplate(design); return true; } }); ui.setAction(DesignEditorLID.DeselectAll, new IListenerAction() { public Class<?> getParameterClass() { return SelectionState.class; } public boolean performAction(Object prms) { SelectionState selection = (SelectionState) prms; selection.deselectAll(); return true; } }); ui.setAction(DesignEditorLID.MoveFrames, new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorUI.MoveParameters.class; } public boolean performAction(Object prms) { DesignEditorUI.MoveParameters movePrms = (DesignEditorUI.MoveParameters) prms; if (movePrms != null) { return moveFrames(movePrms.dx, movePrms.dy, movePrms.selection); } else { throw new RcvrUIException("Cannot move frames without parameters."); } } }); ui.setAction(DesignEditorLID.AddDesignDevices, createAddDevicesAction()); ui.setAction(DesignEditorLID.NewFrame, createNewFrameAction()); // Align selected frames ui.setAction(DesignEditorLID.AlignTop, new FrameAlignmentAction(AlignmentAction.TOP)); ui.setAction(DesignEditorLID.AlignBottom, new FrameAlignmentAction(AlignmentAction.BOTTOM)); ui.setAction(DesignEditorLID.AlignLeft, new FrameAlignmentAction(AlignmentAction.LEFT)); ui.setAction(DesignEditorLID.AlignRight, new FrameAlignmentAction(AlignmentAction.RIGHT)); ui.setAction(DesignEditorLID.AlignCenter, new FrameAlignmentAction(AlignmentAction.CENTER)); ui.setAction(DesignEditorLID.AlignHorizCenter, new FrameAlignmentAction(AlignmentAction.HORIZ_CENTER)); ui.setAction(DesignEditorLID.AlignVertCenter, new FrameAlignmentAction(AlignmentAction.VERT_CENTER)); // Space selected frames equally ui.setAction(DesignEditorLID.SpaceVertically, new IListenerAction() { public Class<?> getParameterClass() { return Map.class; } @SuppressWarnings("unchecked") public boolean performAction(Object prms) { Map<Frame, DoubleRectangle> frameMap = (Map<Frame, DoubleRectangle>) prms; // Equally space the widgets in the // vertical axis. final boolean VERTICAL = true; return spaceFramesEqually(frameMap, VERTICAL); } }); ui.setAction(DesignEditorLID.SpaceHorizontally, new IListenerAction() { public Class<?> getParameterClass() { return Map.class; } @SuppressWarnings("unchecked") public boolean performAction(Object prms) { Map<Frame, DoubleRectangle> frameMap = (Map<Frame, DoubleRectangle>) prms; // Equally space the widgets in the // horizontal axis. final boolean HORIZONTAL = false; return spaceFramesEqually(frameMap, HORIZONTAL); } }); ui.setAction(DesignEditorLID.InitiateFrameRename, createInitiateFrameRenameAction()); ui.setAction(DesignEditorLID.RenameFrame, new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorUI.FrameRenameParameters.class; } public boolean performAction(Object prms) { DesignEditorUI.FrameRenameParameters evt = (DesignEditorUI.FrameRenameParameters) prms; return renameFrame(evt.frame, evt.newName); } }); ui.setAction(ProjectLID.RenameDesign, new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorUI.DesignRenameParameters.class; } public boolean performAction(Object prms) { DesignEditorUI.DesignRenameParameters evt = (DesignEditorUI.DesignRenameParameters) prms; return renameDesign(evt.design, evt.newText); } }); ui.setAction(DesignEditorLID.NewTransition, createNewTransitionAction()); ui.setAction(DesignEditorLID.EditFrame, createEditFrameAction()); ui.setAction(DesignEditorLID.EditTransition, createEditTransitionAction()); ui.setAction(DesignEditorLID.DeleteFrame, createDeleteFrameAction()); ui.setAction(DesignEditorLID.DeleteTransition, createDeleteTransitionAction()); // Nudge selected frame(s) ui.setAction(DesignEditorLID.NudgeLeft, new IListenerAction() { public Class<?> getParameterClass() { return FrameSelectionState.class; } public boolean performAction(Object prms) { FrameSelectionState selection = (FrameSelectionState) prms; double dx = -1.0 / ui.getZoom(); return moveFrames(dx, 0.0, selection); } }); ui.setAction(DesignEditorLID.NudgeRight, new IListenerAction() { public Class<?> getParameterClass() { return FrameSelectionState.class; } public boolean performAction(Object prms) { FrameSelectionState selection = (FrameSelectionState) prms; double dx = 1.0 / ui.getZoom(); return moveFrames(dx, 0.0, selection); } }); ui.setAction(DesignEditorLID.NudgeUp, new IListenerAction() { public Class<?> getParameterClass() { return FrameSelectionState.class; } public boolean performAction(Object prms) { FrameSelectionState selection = (FrameSelectionState) prms; double dy = -1.0 / ui.getZoom(); return moveFrames(0.0, dy, selection); } }); ui.setAction(DesignEditorLID.NudgeDown, new IListenerAction() { public Class<?> getParameterClass() { return FrameSelectionState.class; } public boolean performAction(Object prms) { FrameSelectionState selection = (FrameSelectionState) prms; double dy = 1.0 / ui.getZoom(); return moveFrames(0.0, dy, selection); } }); ui.setAction(DesignEditorLID.SelectAll, new AListenerAction() { public boolean performAction(Object prms) { ui.selectAllFrames(); return true; } }); ui.setAction(DesignEditorLID.ChangeTarget, createChangeTargetAction()); ui.setAction(DesignEditorLID.ChangeSource, createChangeSourceAction()); // The following 6 are also functionality in FrameEditorController ui.setAction(DesignEditorLID.SetBackgroundImage, createSetBackgroundImageAction()); ui.setAction(DesignEditorLID.RemoveBackgroundImage, createRemoveBackgroundImageAction()); ui.setAction(DesignEditorLID.CopyImageAsBackground, createCopyImageAsBkgAction()); ui.setAction(DesignEditorLID.PasteBackgroundImage, createPasteBackgroundImageAction()); ui.setAction(DesignEditorLID.SetWidgetColor, createSetWidgetColorAction()); // Skins! ui.setAction(DesignEditorLID.SkinWireFrame, createSetSkinAction(SkinType.WireFrame, DesignEditorLID.SkinWireFrame)); ui.setAction(DesignEditorLID.SkinMacOSX, createSetSkinAction(SkinType.MacOSX, DesignEditorLID.SkinMacOSX)); ui.setAction(DesignEditorLID.SkinWinXP, createSetSkinAction(SkinType.WinXP, DesignEditorLID.SkinWinXP)); ui.setAction(DesignEditorLID.SkinPalm, createSetSkinAction(SkinType.Palm, DesignEditorLID.SkinPalm)); ui.setAction(CogToolLID.RenderAll, createRenderAllAction(true, CogToolLID.RenderAll)); ui.setAction(CogToolLID.UnRender, createRenderAllAction(false, CogToolLID.UnRender)); ui.setAction(DesignEditorLID.ImportImageDirectory, createImportImageDirectory()); IListenerAction action = createChangeActionAction(); ui.setAction(DesignEditorLID.ChangeWidgetAction, action); ui.setAction(DesignEditorLID.ChangeDeviceAction, action); ui.setAction(DesignEditorLID.DuplicateFrame, createDuplicateFrameAction()); ui.setAction(DesignEditorLID.ChangeDelay, createChangeDelayAction()); } /** * Update the design's name. * The name must be unique. * * @param design * @param newName * @return */ protected boolean renameDesign(final Design design, String tryName) { final String oldName = design.getName(); boolean notDone = true; do { if (tryName.length() == 0) { tryName = interaction.protestNameCannotBeEmpty("Design"); // If canceled, indicate so; otherwise, test again if (tryName == null) { return false; } } else { Design designForName = project.getDesign(tryName); // If the widget for the given name is the same, // then no change is necessary! if (designForName == design) { notDone = false; } else if (designForName != null) { // A non-null design for the tryName indicates a collision tryName = interaction.protestNameCollision("Design"); // If canceled, indicate so; otherwise, test again if (tryName == null) { return false; } } else { // Not canceled, not empty, and not a collision notDone = false; final String newName = tryName; design.setName(newName); IUndoableEdit edit = new AUndoableEdit(ProjectLID.RenameDesign) { @Override public String getPresentationName() { return renameDesign; } @Override public void redo() { super.redo(); design.setName(newName); makeDesignNameUnique(design); } @Override public void undo() { super.undo(); design.setName(oldName); makeDesignNameUnique(design); } }; undoMgr.addEdit(edit); } } } while (notDone); return true; } /** * Used to determine the horizontal relationship of 2 frames, * based on a map from the frame to its bounds. * This is used in the calculation of alignment, and spacing. * Frame1 to the left of Frame2 returns a < 0 number */ protected static Comparator<Map.Entry<Frame, DoubleRectangle>> frameHorizontalComparator = new Comparator<Map.Entry<Frame, DoubleRectangle>>() { public int compare(Map.Entry<Frame, DoubleRectangle> l, Map.Entry<Frame, DoubleRectangle> r) { // Compare the left & right for ordering DoubleRectangle left = l.getValue(); DoubleRectangle right = r.getValue(); double diff = left.x - right.x; return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0); } }; /** * Used to determine the Vertical relationship of 2 frames, * based on a map from the frame to its bounds. * This is used in the calculation of alignment, and spacing. * Frame1 above Frame2 returns a < 0 number */ protected static Comparator<Map.Entry<Frame, DoubleRectangle>> frameVerticalComparator = new Comparator<Map.Entry<Frame, DoubleRectangle>>() { public int compare(Map.Entry<Frame, DoubleRectangle> u, Map.Entry<Frame, DoubleRectangle> l) { // Compare up and down for ordering. DoubleRectangle upper = u.getValue(); DoubleRectangle lower = l.getValue(); double diff = upper.y - lower.y; return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0); } }; /** * Spaces the selected frames equally along a certain axis * @param vertical true for vertical axis; false for horizontal */ protected boolean spaceFramesEqually(Map<Frame, DoubleRectangle> frameMap, final boolean vertical) { final String undoRedoLabel = vertical ? SPACE_VERTICALLY : SPACE_HORIZONTALLY; CogToolLID lid = vertical ? DesignEditorLID.SpaceVertically : DesignEditorLID.SpaceHorizontally; CompoundUndoableEdit editSequence = new CompoundUndoableEdit(undoRedoLabel, lid); // Order the widgets according to location // (either from the left or from the top) Comparator<Map.Entry<Frame, DoubleRectangle>> c = vertical ? frameVerticalComparator : frameHorizontalComparator; @SuppressWarnings("unchecked") Map.Entry<Frame, DoubleRectangle>[] entries = new Map.Entry[frameMap.size()]; frameMap.entrySet().toArray(entries); Arrays.sort(entries, c); // Calculate the spacing between widgets double sum = 0; double min = Double.MAX_VALUE; double max = Double.MIN_VALUE; // Go through each frame that is selected // Determine the size, min & max of the region. // this can then be used to do spacing. for (Entry<Frame, DoubleRectangle> entrie : entries) { DoubleRectangle bounds = entrie.getValue(); double size = vertical ? bounds.height : bounds.width; double position = vertical ? bounds.y : bounds.x; sum += size; min = Math.min(min, position); max = Math.max(max, size + position); } // Get the spacing to use between each item. double spacing = ((max - min) - sum) / (entries.length - 1); // Adjust the spacings to the correct values for (Entry<Frame, DoubleRectangle> entrie : entries) { // go through each frame and set the frame's origin final Frame f = entrie.getKey(); final DoubleRectangle bounds = entrie.getValue(); // TODO (dfm) this is probably the place to walk over the design pre-flighting // problems we want to warn the user about, and give the opportunity // to skip the export if desired // Determine the old point... final double p_old = vertical ? bounds.y : bounds.x; final double p_new = min; // Set the new location f.setFrameOrigin(vertical ? bounds.x : p_new, vertical ? p_new : bounds.y); // Advance the pointer to the next location min += spacing + (vertical ? bounds.height : bounds.width); IUndoableEdit edit = new AUndoableEdit(lid) { @Override public String getPresentationName() { return undoRedoLabel; } @Override public void redo() { super.redo(); f.setFrameOrigin(vertical ? bounds.x : p_new, vertical ? p_new : bounds.y); } @Override public void undo() { super.undo(); f.setFrameOrigin(vertical ? bounds.x : p_old, vertical ? p_old : bounds.y); } }; editSequence.addEdit(edit); } editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } // TODO this should be merged with ProjectController.createWebPages in // some way, probably by moving to DefaultController (sigh) protected IListenerAction createExportDesignToHTMLAction() { return new AListenerAction() { public boolean performAction(Object actionParms) { return ExportToHTMLWorkThread.exportDesignToHTML(design, interaction); } }; } protected IListenerAction createExportDesignToXMLAction() { return new AListenerAction() { public boolean performAction(Object actionParms) { String defaultFilename = design.getName() + ".xml"; File exportFile = interaction.selectXMLFile(false, defaultFilename); if (exportFile == null) { return false; } OutputStream oStream = null; try { try { oStream = new FileOutputStream(exportFile); Writer xmlWriter = new BufferedWriter(new OutputStreamWriter(oStream, "UTF-8")); Map<ITaskDesign, TaskApplication> designTAs = project.taskApplicationsForDesign(design); ExportCogToolXML.exportXML(design, designTAs, xmlWriter, "UTF-8"); xmlWriter.flush(); } finally { if (oStream != null) { oStream.close(); } } } catch (IllegalArgumentException e) { throw new RcvrIllegalStateException("Invalid argument exporting to XML", e); } catch (IllegalStateException e) { throw new RcvrIllegalStateException("Exporting to XML", e); } catch (IOException e) { throw new RcvrIOSaveException("Exporting to XML", e); } catch (Exception e) { throw new RecoverableException("Exporting to XML", e); } interaction.setStatusMessage(L10N.get("PC.DesignExportedToXML", "Design exported to XML:") + " " + exportFile.getName()); return true; } }; } protected void makeFrameNameUnique(Frame frame, Collection<Frame> pendingAdds) { frame.setName(NamedObjectUtil.makeNameUnique(frame.getName(), design.getFrames(), pendingAdds)); } protected void makeFrameNameUnique(Frame frame) { makeFrameNameUnique(frame, null); } protected void makeDesignNameUnique(Design design) { design.setName(NamedObjectUtil.makeNameUnique(design.getName(), project.getDesigns())); } /** * Create a ListenerAction to handle removing frame background image on * multiple frames */ protected IListenerAction createRemoveBackgroundImageAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameSelectionState.class; } public boolean performAction(Object prms) { // Get selected frames from parameters FrameSelectionState seln = (FrameSelectionState) prms; Frame[] frames = seln.getSelectedFrames(); setBackgroundImageOnFrames(frames, null, WidgetAttributes.NO_IMAGE, DesignEditorLID.RemoveBackgroundImage); return true; } }; } /** * Create a ListenerAction to handle setting frame background image on * multiple frames */ protected IListenerAction createSetBackgroundImageAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameSelectionState.class; } public boolean performAction(Object prms) { // Get selected frames from parameters FrameSelectionState seln = (FrameSelectionState) prms; Frame[] frames = seln.getSelectedFrames(); // this code is from FrameEditorController // load image // Pull up interaction to select a file String imageURL = interaction.selectImageFile(); // Check if the dialog was cancelled if (imageURL != null) { byte[] imageData; try { // Set the background to new image imageData = GraphicsUtil.loadImageFromFile(imageURL); setBackgroundImageOnFrames(frames, imageData, imageURL, DesignEditorLID.SetBackgroundImage); return true; } catch (IOException e) { interaction.protestUnreadableFile(); imageData = null; } } return false; } }; } protected IListenerAction createPasteBackgroundImageAction() { return new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorUI.PasteBackgroundImageParms.class; } public boolean performAction(Object prms) { // Get selected frames from parameters DesignEditorUI.PasteBackgroundImageParms parms = (DesignEditorUI.PasteBackgroundImageParms) prms; Frame[] frames = parms.selection.getSelectedFrames(); if ((frames == null) || (frames.length == 0)) { interaction.protestNoSelection(); return false; } setBackgroundImageOnFrames(frames, parms.imageData, WidgetAttributes.NO_IMAGE, DesignEditorLID.PasteBackgroundImage); return true; } }; } /** * Create a ListenerAction to handle setting widget color on multiple frames */ protected IListenerAction createSetWidgetColorAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameSelectionState.class; } public boolean performAction(Object prms) { // Get selected frames from parameters FrameSelectionState seln = (FrameSelectionState) prms; Frame[] frames = seln.getSelectedFrames(); // TODO: Which color to use as old widget color? int oldColor = (frames.length > 0) ? frames[0].getWidgetColor() : 128; // Pull up interaction to select a file Integer newColorChoice = interaction.selectColor(oldColor, selectWidgetColor); // Check if the dialog was canceled if (newColorChoice != null) { setWidgetColorForFrames(frames, newColorChoice.intValue()); return true; } return false; } }; } protected IListenerAction createPasteAction() { return new AListenerAction() { public boolean performAction(Object prms) { try { Collection<Object> objects = CogToolClipboard.fetchCogToolObjects(); if ((objects != null) && (objects.size() > 0)) { CompoundUndoableEdit editSequence = new CompoundUndoableEdit(L10N.get("UNDO.Paste", "Paste"), DesignEditorLID.Paste); Set<DeviceType> devTypes = design.getDeviceTypes(); int numPasted = 0; Iterator<Object> objIt = objects.iterator(); while (objIt.hasNext()) { Object o = objIt.next(); if (o instanceof Frame) { Frame frame = (Frame) o; makeFrameNameUnique(frame); // Find an unoccupied starting position // by cascading. DoublePoint origin = frame.getFrameOrigin(); DesignUtil.findDistinctOrigin(design, origin, 16.0, 16.0); // Union devices Iterator<InputDevice> frameDevices = frame.getInputDevices().iterator(); while (frameDevices.hasNext()) { InputDevice inputDevice = frameDevices.next(); DeviceType devType = inputDevice.getDeviceType(); if (! devTypes.contains(devType)) { DesignCmd.addDevice(design, devType); } } Iterator<DeviceType> designDevTypes = devTypes.iterator(); while (designDevTypes.hasNext()) { DeviceType devType = designDevTypes.next(); if (frame.getInputDevice(devType) == null) { frame.addInputDevice(devType); } } addFrame(frame, editSequence); numPasted++; } else if (o instanceof Transition) { Transition t = (Transition) o; DeviceType device = t.getAction().getDefaultDeviceType(); if (! devTypes.contains(device)) { DesignCmd.addDevice(design, device); } IUndoableEdit edit = DesignEditorCmd.addTransition(demoStateMgr, t); editSequence.addEdit(edit); numPasted++; } } editSequence.end(); undoMgr.addEdit(editSequence); interaction.setStatusMessage(numPasted + " " + pasteComplete); } else { interaction.setStatusMessage(nothingPasted); } } catch (IOException e) { throw new RcvrClipboardException(e); } catch (ParserConfigurationException e) { throw new RcvrClipboardException(e); } catch (SAXException e) { throw new RcvrClipboardException(e); } catch (ClipboardUtil.ClipboardException e) { throw new RcvrClipboardException(e); } return true; } }; } // createPasteAction protected IListenerAction createCopyImageAsBkgAction() { return new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorUI.CopyBackgroundImageParms.class; } public boolean performAction(Object actionParms) { DesignEditorUI.CopyBackgroundImageParms prms = (DesignEditorUI.CopyBackgroundImageParms) actionParms; if (prms.selectedFrame == null) { interaction.protestNoSelection(); return false; } setBackgroundImageOnFrames(new Frame[] { prms.selectedFrame }, prms.imageData, WidgetAttributes.NO_IMAGE, CogToolLID.CopyImageAsBackground); return true; } }; } protected IListenerAction createCopyFrameAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameSelectionState.class; } public boolean performAction(Object prms) { FrameSelectionState seln = (FrameSelectionState) prms; Frame[] frames = seln.getSelectedFrames(); if ((frames != null) && (frames.length > 0)) { copyFrames(frames); if (frames.length == 1) { interaction.setStatusMessage(FRAME_COPIED); } else { interaction.setStatusMessage(FRAMES_COPIED); } return true; } interaction.protestNoSelection(); return false; } }; } protected void copyFrames(Frame[] frames) { try { // Passing the set of frames as part of purpose allows us // to copy only those transitions that link those frames. ObjectSaver s = CogToolClipboard.startClipboardSave(CogToolClipboard.CopyFrames, frames, CogToolClipboard.SAVE_TO_CLIPBOARD); for (Frame frame : frames) { s.saveObject(frame); } s.finish(); } catch (IOException e) { throw new RcvrClipboardException(e); } catch (IllegalStateException e) { throw new RcvrClipboardException(e); } catch (OutOfMemoryError error) { throw new RcvrOutOfMemoryException("Copying Frames", error); } } protected IListenerAction createCutFrameAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameSelectionState.class; } public boolean performAction(Object prms) { FrameSelectionState seln = (FrameSelectionState) prms; Frame[] frames = seln.getSelectedFrames(); if ((frames != null) && (frames.length > 0)) { copyFrames(frames); deleteFrames(frames); return true; } interaction.protestNoSelection(); return false; } }; } protected IListenerAction createAddDevicesAction() { return new AListenerAction() { public boolean performAction(Object prms) { return DesignCmd.addDevices(project, design, interaction); } }; } protected IListenerAction createNewFrameAction() { return new AListenerAction() { public boolean performAction(Object prms) { // Find an unoccupied starting position by cascading. DoublePoint origin = new DoublePoint(10.0, 10.0); DesignUtil.findDistinctOrigin(design, origin, 16.0, 16.0); Frame frame = createNewFrame(origin.x, origin.y); ui.initiateFrameRename(frame); return true; } }; } protected Frame createNewFrame(double x, double y) { return createNewFrame(x, y, null); } protected void addFrame(Frame frame, IUndoableEditSequence editSequence) { DesignEditorCmd.addFrame(project, design, demoStateMgr, frame, editSequence); } // addFrame protected Frame createFrame(String frameName, double x, double y) { Frame frame = new Frame(frameName, design.getDeviceTypes()); frame.setFrameOrigin(x, y); return frame; } protected Frame createNewFrame(double x, double y, IUndoableEditSequence editSequence) { String frameNamePrefix = DEFAULT_FRAME_PREFIX + " "; String frameName = frameNamePrefix + nextNewFrameSuffix; while (design.getFrame(frameName) != null) { frameName = frameNamePrefix + ++nextNewFrameSuffix; } Frame frame = createFrame(frameName, x, y); addFrame(frame, (editSequence != null) ? editSequence : undoMgr); return frame; } protected boolean moveFrames(double dx, double dy, FrameSelectionState selection) { Frame[] frames = selection.getSelectedFrames(); if ((frames != null) && (frames.length > 0)) { String editLabel; if (frames.length > 1) { editLabel = moveFrames; } else { editLabel = moveFrame; } CompoundUndoableEdit editSequence = new CompoundUndoableEdit(editLabel, DesignEditorLID.MoveFrames); for (Frame frame : frames) { moveFrame(frame, dx, dy, editSequence); } editSequence.end(); undoMgr.addEdit(editSequence); } return true; } protected void moveFrame(final Frame frame, final double dx, final double dy, IUndoableEditSequence editSequence) { // Create a new point, so we can modify it without side-effects final DoublePoint oldP = new DoublePoint(frame.getFrameOrigin()); final DoublePoint p = new DoublePoint(oldP); // Compute the new location. p.x += dx; p.y += dy; // Ensure that it is not less then 0,0 if (p.x < 0) { p.x = 0; } if (p.y < 0) { p.y = 0; } frame.setFrameOrigin(p); editSequence.addEdit(new AUndoableEdit(DesignEditorLID.MoveFrames) { @Override public String getPresentationName() { return moveFrame; } @Override public void redo() { super.redo(); frame.setFrameOrigin(p); } @Override public void undo() { super.undo(); frame.setFrameOrigin(oldP); } }); } protected IListenerAction createEditFrameAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameSelectionState.class; } public boolean performAction(Object prms) { FrameSelectionState selection = (FrameSelectionState) prms; Frame[] frames = selection.getSelectedFrames(); if ((frames == null) || (frames.length == 0)) { interaction.protestNoSelection(); return false; } for (Frame frame : frames) { try { FrameEditorController.openController(frame, design, project); } catch (GraphicsUtil.ImageException ex) { interaction.protestInvalidImageFile(); } } return true; } }; } // createEditDesignAction protected IListenerAction createEditTransitionAction() { return new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorUI.EditTransitionParameters.class; } public boolean performAction(Object prms) { DesignEditorUI.EditTransitionParameters parms = (DesignEditorUI.EditTransitionParameters) prms; Transition[] transitions = parms.selection.getSelectedTransitions(); // Probably only one transition selected if ((transitions != null) && (transitions.length > 0)) { if (transitions.length == 1) { TransitionSource source = transitions[0].getSource(); AAction action = transitions[0].getAction(); int limitMode = ActionProperties.determineChangeActionMode(source); int deviceTypes = DeviceType.buildDeviceSet(design.getDeviceTypes()); if (! interaction.determineNewAction(transitions[0], properties, parms.useWhichParts, deviceTypes, limitMode, L10N.get("DE.ChangeActionType", "Change Action Type"))) { return false; } action = EditActionCmd.buildActionFromProperties(properties, deviceTypes, limitMode, interaction); if (action == null) { return false; } action = EditActionCmd.ensureActionIsUnique(source, action, properties, deviceTypes, limitMode, transitions[0], interaction); if (action == null) { return false; } return changeTransitionsAction(transitions, action, properties.delayInSecs, properties.delayLabel); } else { interaction.protestMultipleTransitionSelection(); } } else { interaction.protestNoSelection(); } return false; } }; } // createEditTransitionAction protected IListenerAction createInitiateFrameRenameAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameSelectionState.class; } public boolean performAction(Object prms) { FrameSelectionState selection = (FrameSelectionState) prms; int selectedFrameCount = selection.getSelectedFrameCount(); if (selectedFrameCount == 1) { ui.initiateFrameRename(selection); return true; } if (selectedFrameCount > 1) { interaction.protestMultipleFrameSelection(); return false; } interaction.protestNoSelection(); return false; } }; } protected boolean isFrameNameUnique(String newFrameName, String oldFrameName) { if (newFrameName.equals(oldFrameName)) { return true; } return design.getFrame(newFrameName) == null; } protected boolean renameFrame(final Frame renamedFrame, String tryName) { final String oldName = renamedFrame.getName(); boolean notDone = true; do { if (tryName.length() == 0) { tryName = interaction.protestNameCannotBeEmpty(DEFAULT_FRAME_PREFIX); // If canceled, indicate so; otherwise, test again if (tryName == null) { return false; } } else { Frame frameForName = design.getFrame(tryName); // If the widget for the given name is the same, // then no change is necessary! if (frameForName == renamedFrame) { notDone = false; } else if (frameForName != null) { // A non-null frame for the tryName indicates a collision tryName = interaction.protestNameCollision(DEFAULT_FRAME_PREFIX); // If canceled, indicate so; otherwise, test again if (tryName == null) { return false; } } else { // Not canceled, not empty, and not a collision notDone = false; final String newName = tryName; renamedFrame.setName(newName); IUndoableEdit edit = new AUndoableEdit(DesignEditorLID.RenameFrame) { @Override public String getPresentationName() { return renameFrame; } @Override public void redo() { super.redo(); Frame testFrame = design.getFrame(newName); renamedFrame.setName(newName); if (testFrame != null) { makeFrameNameUnique(renamedFrame); } } @Override public void undo() { super.undo(); Frame testFrame = design.getFrame(oldName); renamedFrame.setName(oldName); if (testFrame != null) { makeFrameNameUnique(renamedFrame); } } }; undoMgr.addEdit(edit); } } } while (notDone); return true; } // renameFrame protected IListenerAction createNewTransitionAction() { return new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorUI.NewTransitionParameters.class; } public boolean performAction(Object prms) { DesignEditorUI.NewTransitionParameters newPrms = (DesignEditorUI.NewTransitionParameters) prms; if (newPrms != null) { CompoundUndoableEdit editSequence = null; if (newPrms.target == null) { editSequence = new CompoundUndoableEdit(DesignEditorCmd.NEW_TRANSITION, DesignEditorLID.NewTransition); newPrms.target = createNewFrame(newPrms.x, newPrms.y, editSequence); } IUndoableEdit edit = createNewTransition(newPrms.source, newPrms.target); // null is returned if the operation is canceled. if (edit != null) { if (editSequence != null) { editSequence.addEdit(edit); editSequence.end(); edit = editSequence; } undoMgr.addEdit(edit); if (editSequence != null) { ui.initiateFrameRename(newPrms.target); } return true; } else if (editSequence != null) { editSequence.end(); editSequence.undo(); } return false; } else { throw new RcvrUIException("Cannot create transition without parameters."); } } }; } /** * Pass-through using controller's resources * @param source * @param target * @return */ protected AAction createDefaultAction(TransitionSource source) { ui.getDefaultProperties(source, properties); // Determine desired transition type from the UI (palette setting) return EditActionCmd.createDefaultAction(design, source, ui.getCurrentActionType(), properties); } // Return null to indicate cancel protected IUndoableEdit createNewTransition(final TransitionSource source, Frame target) { int deviceTypes = DeviceType.buildDeviceSet(design.getDeviceTypes()); // Helps control which options are available for editing the action int limitMode = ActionProperties.determineChangeActionMode(source); AAction action = createDefaultAction(source); if (action == null) { if (! interaction.determineNewAction(properties, deviceTypes, limitMode, L10N.get("DE.SetActionType", "Set Action Type"))) { return null; // Indicate that the user canceled } action = EditActionCmd.buildActionFromProperties(properties, deviceTypes, limitMode, interaction); if (action == null) { return null; } } // action is not null at this point; no transition yet for this action! action = EditActionCmd.ensureActionIsUnique(source, action, properties, deviceTypes, limitMode, null, interaction); if (action == null) { return null; } Transition newTransition = new Transition(source, target, action); newTransition.setDelayInfo(properties.delayInSecs, properties.delayLabel); IUndoableEdit edit = DesignEditorCmd.addTransition(demoStateMgr, newTransition); interaction.setTransitionStatusMessage(newTransition); return edit; } // createNewTransition protected IListenerAction createDeleteFrameAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameSelectionState.class; } public boolean performAction(Object prms) { FrameSelectionState selection = (FrameSelectionState) prms; Frame[] frames = selection.getSelectedFrames(); if ((frames != null) && (frames.length > 0)) { if (interaction.confirmDeleteFrames(frames)) { deleteFrames(frames); return true; } } else { interaction.protestNoSelection(); } return false; } }; } // createDeleteFrameAction // Currently, used by import images as frames protected void deleteFrame(Frame frame, CogToolLID lid, IUndoableEditSequence editSequence) { DesignEditorCmd.deleteFrame(project, design, demoStateMgr, frame, lid, editSequence); } // deleteFrame protected void deleteFrames(final Frame[] frames) { if ((frames != null) && (frames.length > 0)) { final Transition[][] incidentTransitions = new Transition[frames.length][]; for (int i = 0; i < frames.length; i++) { // Remove Transition instances that target this Frame incidentTransitions[i] = frames[i].removeIncidentTransitions(); } for (Frame frame : frames) { design.removeFrame(frame); } DemoStateManager.IDesignUndoableEdit edit = new DemoStateManager.InvalidatingEdit(DesignEditorLID.DeleteFrame, demoStateMgr) { protected boolean recoverMgrs = true; @Override public String getPresentationName() { return (frames.length > 1) ? deleteFrames : deleteFrame; } @Override public void redo() { super.redo(); recoverMgrs = true; for (Frame frame : frames) { frame.removeIncidentTransitions(); } for (Frame frame : frames) { design.removeFrame(frame); } stateMgr.noteFramesEdit(frames, this); } @Override public void undo() { super.undo(); recoverMgrs = false; for (int i = frames.length - 1; i >= 0; i--) { design.addFrame(frames[i]); } for (int i = frames.length - 1; i >= 0; i--) { DesignEditorCmd.addIncidentTransitions(incidentTransitions[i]); } stateMgr.noteFramesEdit(frames, this); } @Override public void die() { super.die(); if (recoverMgrs) { for (Frame frame : frames) { recoverManagers(frame); } } } }; demoStateMgr.noteFramesEdit(frames, edit); undoMgr.addEdit(edit); } } // deleteFrames protected IListenerAction createDeleteTransitionAction() { return new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorSelectionState.class; } public boolean performAction(Object prms) { DesignEditorSelectionState selection = (DesignEditorSelectionState) prms; Transition[] transitions = selection.getSelectedTransitions(); if ((transitions != null) && (transitions.length > 0)) { if (interaction.confirmDeleteTransitions(transitions)) { deleteTransitions(transitions); return true; } } else { interaction.protestNoSelection(); } return false; } }; } // createDeleteTransitionAction protected IListenerAction createChangeTargetAction() { return new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorUI.ChangeTargetParameters.class; } public boolean performAction(Object prms) { DesignEditorUI.ChangeTargetParameters chgPrms = (DesignEditorUI.ChangeTargetParameters) prms; DesignEditorCmd.changeTransitionTarget(demoStateMgr, chgPrms.transition, chgPrms.newDestination, undoMgr); return true; } }; } protected IListenerAction createChangeSourceAction() { return new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorUI.ChangeSourceParameters.class; } public boolean performAction(Object prms) { DesignEditorUI.ChangeSourceParameters chgPrms = (DesignEditorUI.ChangeSourceParameters) prms; return changeTransitionSource(chgPrms.transition, chgPrms.newSource); } }; } protected void deleteTransitions(final Transition[] transitions) { // For redo, must save the corresponding source for each Transition if ((transitions != null) && (transitions.length > 0)) { final TransitionSource[] sources = new TransitionSource[transitions.length]; for (int i = 0; i < transitions.length; i++) { Transition t = transitions[i]; sources[i] = t.getSource(); sources[i].removeTransition(t); } DemoStateManager.IDesignUndoableEdit edit = new DemoStateManager.InvalidatingEdit(DesignEditorLID.DeleteTransition, demoStateMgr) { @Override public String getPresentationName() { return (transitions.length > 1) ? deleteTransitions : deleteTransition; } @Override public void redo() { super.redo(); for (int i = 0; i < transitions.length; i++) { Transition t = transitions[i]; sources[i].removeTransition(t); } stateMgr.noteTransitionsEdit(transitions, this); } @Override public void undo() { super.undo(); for (int i = 0; i < transitions.length; i++) { Transition t = transitions[i]; sources[i].addTransition(t); } stateMgr.noteTransitionsEdit(transitions, this); } }; demoStateMgr.noteTransitionsEdit(transitions, edit); undoMgr.addEdit(edit); } } // deleteTransitions protected boolean changeTransitionSource(final Transition transition, final TransitionSource newSource) { final TransitionSource oldSource = transition.getSource(); if (newSource != oldSource) { AAction action = transition.getAction(); // Check that the new source is consistent the transition's action if (! newSource.canAccept(action)) { interaction.protestInconsistentSource(); return false; } while (newSource.getTransition(action) != null) { //TODO: allow interaction to change? Must handle in undo then! if (! interaction.protestNotUniqueAction(action, newSource, false)) { return false; } } oldSource.removeTransition(transition); transition.setSource(newSource); newSource.addTransition(transition); final boolean invalidating = (newSource.getFrame() != oldSource.getFrame()); DemoStateManager.IDesignUndoableEdit edit = new DemoStateManager.DesignUndoableEdit(DesignEditorLID.ChangeSource, demoStateMgr) { @Override public String getPresentationName() { return changeTransitionSource; } @Override public Boolean getEditNature() { return invalidating ? DemoStateManager.INVALIDATING : DemoStateManager.OBSOLETING; } @Override public void redo() { super.redo(); oldSource.removeTransition(transition); transition.setSource(newSource); newSource.addTransition(transition); stateMgr.noteTransitionEdit(transition, this); if (! invalidating && CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } @Override public void undo() { super.undo(); newSource.removeTransition(transition); transition.setSource(oldSource); oldSource.addTransition(transition); stateMgr.noteTransitionEdit(transition, this); if (! invalidating && CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } }; demoStateMgr.noteTransitionEdit(transition, edit); undoMgr.addEdit(edit); if (! invalidating && CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } return true; } protected IListenerAction createChangeActionAction() { return new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorUI.ChangeActionParameters.class; } public boolean performAction(Object prms) { DesignEditorUI.ChangeActionParameters chgPrms = (DesignEditorUI.ChangeActionParameters) prms; if (chgPrms != null) { Transition[] transitions = chgPrms.selection.getSelectedTransitions(); if ((transitions != null) && (transitions.length > 0)) { properties.copyValues(chgPrms.properties); // TODO: Assume one for the moment; ui enforces it! TransitionSource source = transitions[0].getSource(); int deviceTypes = DeviceType.buildDeviceSet(design.getDeviceTypes()); int limitMode = ActionProperties.determineChangeActionMode(source); AAction newAction = EditActionCmd.buildActionFromProperties(properties, deviceTypes, limitMode, interaction); if (newAction == null) { return false; } newAction = EditActionCmd.ensureActionIsUnique(source, newAction, properties, deviceTypes, limitMode, transitions[0], interaction); if (newAction == null) { return false; } return changeTransitionsAction(transitions, newAction, NO_DELAY_CHANGE, null); // TODO: If the given action properties are unusable // (e.g., not unique from the source or empty string) // and we should therefore return false so that an // action that commits property changes can be // canceled, assign the ensureActionIsUnique result // to a new variable uniqueAction and return the AND of // the changeTransitionsAction call (first) with // (newAction == uniqueAction). } } return false; } }; } /** * Negative delays are not allowed, thus use -1.0 to indicate no chg */ protected static final double NO_DELAY_CHANGE = -1.0; protected boolean changeTransitionsAction(Transition[] transitions, AAction newAction, double delayInSecs, String delayLabel) { if ((transitions != null) && (transitions.length > 0)) { CompoundUndoableEdit editSequence = new CompoundUndoableEdit(changeAction, DesignEditorLID.ChangeWidgetAction); for (Transition transition : transitions) { changeTransitionAction(transition, newAction, delayInSecs, delayLabel, editSequence); } editSequence.end(); // Only add the compound edit if it contains something if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } } return true; } protected void changeTransitionAction(final Transition transition, final AAction newAction, final double delayInSecs, final String delayLabel, IUndoableEditSequence editSequence) { final AAction oldAction = transition.getAction(); final double oldDelayInSecs = transition.getDelayInSecs(); final String oldDelayLabel = transition.getDelayLabel(); // Must check that the transition's source can accept the new action if ((! oldAction.equals(newAction)) || ((delayInSecs != NO_DELAY_CHANGE) && ((delayInSecs != oldDelayInSecs) || ! oldDelayLabel.equals(delayLabel)))) { transition.setAction(newAction); if (delayInSecs != NO_DELAY_CHANGE) { transition.setDelayInfo(delayInSecs, delayLabel); } DemoStateManager.IDesignUndoableEdit edit = new DemoStateManager.ObsoletingEdit(DesignEditorLID.ChangeWidgetAction, demoStateMgr) { @Override public String getPresentationName() { return changeAction; } @Override public void redo() { super.redo(); transition.setAction(newAction); if (delayInSecs != NO_DELAY_CHANGE) { transition.setDelayInfo(delayInSecs, delayLabel); } stateMgr.noteTransitionEdit(transition, this); if (CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } @Override public void undo() { super.undo(); transition.setAction(oldAction); if (delayInSecs != NO_DELAY_CHANGE) { transition.setDelayInfo(oldDelayInSecs, oldDelayLabel); } stateMgr.noteTransitionEdit(transition, this); if (CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } }; demoStateMgr.noteTransitionEdit(transition, edit); editSequence.addEdit(edit); if (CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } } protected class FrameSubsetDuplicator implements Frame.IFrameDuplicator { protected Set<Frame> framesToCopy = new HashSet<Frame>(); protected Map<Frame, Frame> copiedFrames = new HashMap<Frame, Frame>(); // old frame -> frame copy public FrameSubsetDuplicator(Frame[] frames) { for (Frame frame : frames) { framesToCopy.add(frame); } } /** * Only duplicate frames that are in the selection must be copied; * otherwise, return the given frame itself. */ public Frame getOrDuplicate(Frame frameToCopy) { Frame frame = copiedFrames.get(frameToCopy); if (frame == null) { if (framesToCopy.contains(frameToCopy)) { frame = frameToCopy.duplicate(frameToCopy.getName(), this); // Warning: it is important that each frame be added // to the design *before* we make the next frame name // unique, or we can end up with non-unique names. makeFrameNameUnique(frame, copiedFrames.values()); } else { frame = frameToCopy; } } return frame; } public void recordDuplicateFrame(Frame originalFrame, Frame frameDuplicate) { copiedFrames.put(originalFrame, frameDuplicate); } } protected IListenerAction createDuplicateFrameAction() { return new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorUI.DuplicateParameters.class; } public boolean performAction(Object prms) { // From DesignEditorUI DesignEditorUI.DuplicateParameters parameters = (DesignEditorUI.DuplicateParameters) prms; Frame[] framesToDuplicate = parameters.selection.getSelectedFrames(); if ((framesToDuplicate != null) && (framesToDuplicate.length > 0)) { FrameSubsetDuplicator duplicator = new FrameSubsetDuplicator(framesToDuplicate); Frame[] duplicatedFrames = new Frame[framesToDuplicate.length]; for (int i = 0; i < framesToDuplicate.length; i++) { Frame duplicatedFrame = duplicator.getOrDuplicate(framesToDuplicate[i]); duplicatedFrames[i] = duplicatedFrame; duplicatedFrame.moveFrameOrigin(parameters.dx, parameters.dy); } design.addFrames(duplicatedFrames); undoMgr.addEdit(createDuplicateFramesEdit(duplicatedFrames)); return true; } interaction.protestNoSelection(); return false; } }; } protected IUndoableEdit createDuplicateFramesEdit(final Frame[] frames) { // Based on the code in deleteFrames return new DemoStateManager.InvalidatingEdit(DesignEditorLID.DuplicateFrame, demoStateMgr) { protected Transition[][] incidentTransitions = null; protected boolean recoverMgrs = false; @Override public String getPresentationName() { return (frames.length > 1) ? duplicateFrames : duplicateFrame; } @Override public void redo() { super.redo(); recoverMgrs = false; for (int i = frames.length - 1; i >= 0; i--) { design.addFrame(frames[i]); } for (int i = frames.length - 1; i >= 0; i--) { DesignEditorCmd.addIncidentTransitions(incidentTransitions[i]); } stateMgr.noteFramesEdit(frames, this); } @Override public void undo() { super.undo(); recoverMgrs = true; if (incidentTransitions == null) { incidentTransitions = new Transition[frames.length][]; for (int i = 0; i < frames.length; i++) { // Remove Transition instances for each Frame incidentTransitions[i] = frames[i].removeIncidentTransitions(); } for (Frame frame : frames) { design.removeFrame(frame); } } else { for (Frame frame : frames) { frame.removeIncidentTransitions(); } for (Frame frame : frames) { design.removeFrame(frame); } } stateMgr.noteFramesEdit(frames, this); } @Override public void die() { super.die(); if (recoverMgrs) { for (Frame frame : frames) { recoverManagers(frame); } } } }; } // createDuplicateFramesEdit /** * Internal class for storing image data as a byte array * along with its associated bounding rectangle * * @author jcorn */ protected static class ImageData { public DoubleRectangle bounds; public byte[] data; public String imageURL; public ImageData(DoubleRectangle bds, byte[] imgData, String url) { bounds = bds; data = imgData; imageURL = url; } } /** * Returns a Map mapping IFrames to their current background image * and bounds in an ImageData object * * @param frames an array of IFrames * @return a Hashtable mapping IFrames to their current background images */ protected Map<Frame, ImageData> getBackgroundImageData(Frame[] frames) { Map<Frame, ImageData> previousImagesData = new HashMap<Frame, ImageData>(); for (Frame f : frames) { String imgPath = (String) f.getAttribute(WidgetAttributes.IMAGE_PATH_ATTR); // Save previous data for undo operation previousImagesData.put(f, new ImageData(f.getBackgroundBounds(), f.getBackgroundImage(), imgPath)); } return previousImagesData; } /** * Sets the background image for the array of frames * * @param frames an array of IFrames * @param imageData the new image, or null if none * @param imageURL the file path of the new image, or * <code>WidgetAttributes.NO_IMAGE</code> */ protected void setBackgroundImageOnFrames(final Frame[] frames, final byte[] imageData, final String imageURL, CogToolLID editLID) { try { // compute the size of the new image. final DoubleRectangle imageSize = GraphicsUtil.getImageBounds(imageData); // save previous data for undo/redo final Map<Frame, ImageData> previousImageData = getBackgroundImageData(frames); // do operation on all frames for (Frame frame : frames) { frame.setBackgroundImage(imageData, imageSize); frame.setAttribute(WidgetAttributes.IMAGE_PATH_ATTR, imageURL); } // Add the undo edit IUndoableEdit edit = new AUndoableEdit(editLID) { @Override public String getPresentationName() { return (imageData == null) ? removeBackgroundImage : setBackgroundImage; } @Override public void redo() { super.redo(); // Set background image to new image data for all // selected frames try { for (Frame frame : frames) { frame.setBackgroundImage(imageData, imageSize); frame.setAttribute(WidgetAttributes.IMAGE_PATH_ATTR, imageURL); } } catch (GraphicsUtil.ImageException ex) { throw new RcvrImageException("Redo set background image failed", ex); } } @Override public void undo() { super.undo(); try { // iterate through frames Iterator<Entry<Frame, ImageData>> imageEntryIterator = previousImageData.entrySet().iterator(); while (imageEntryIterator.hasNext()) { Entry<Frame, ImageData> imageEntry = imageEntryIterator.next(); Frame f = imageEntry.getKey(); ImageData id = imageEntry.getValue(); f.setBackgroundImage(id.data, id.bounds); f.setAttribute(WidgetAttributes.IMAGE_PATH_ATTR, id.imageURL); } } catch (GraphicsUtil.ImageException ex) { throw new RcvrImageException("Undo set background image failed", ex); } } }; undoMgr.addEdit(edit); for (Frame frame : frames) { UndoManager frameMgr = UndoManager.getUndoManager(frame, project); frameMgr.addEdit(edit); } } catch (GraphicsUtil.ImageException e) { // setBackgroundImage and GraphicsUtil.getImageBounds may // throw ImageException, translating any other exception. interaction.protestInvalidImageFile(); } } /** * Returns a Map mapping IFrames to their current widget color * * @param frames an array of IFrames * @return Map mapping IFrames to their current widget color */ protected Map<Frame, Integer> getWidgetColorData(Frame[] frames) { Map<Frame, Integer> widgetColorData = new HashMap<Frame, Integer>(); for (Frame frame : frames) { widgetColorData.put(frame, new Integer(frame.getWidgetColor())); } return widgetColorData; } /** * Sets the widget color on all widgets contained within the given frames * * @param frames an array of IFrames * @param color integer containing new widget color */ protected void setWidgetColorForFrames(final Frame[] frames, final int color) { // save previous data for undo/redo final Map<Frame, Integer> previousWidgetColorData = getWidgetColorData(frames); // do operation on all frames for (Frame frame : frames) { frame.setWidgetColor(color); } // Add the undo edit IUndoableEdit edit = new AUndoableEdit(DesignEditorLID.SetWidgetColor) { @Override public String getPresentationName() { return setWidgetColor; } @Override public void redo() { super.redo(); // Set background image to new image data for all // selected frames for (Frame frame : frames) { frame.setWidgetColor(color); } } @Override public void undo() { super.undo(); // Iterate through frames, resetting widget colors // to old values Iterator<Entry<Frame, Integer>> colorEntryIterator = previousWidgetColorData.entrySet().iterator(); while (colorEntryIterator.hasNext()) { Entry<Frame, Integer> colorEntry = colorEntryIterator.next(); Frame f = colorEntry.getKey(); int oldColor = colorEntry.getValue().intValue(); f.setWidgetColor(oldColor); } } }; undoMgr.addEdit(edit); for (Frame frame : frames) { UndoManager frameMgr = UndoManager.getUndoManager(frame, project); frameMgr.addEdit(edit); } } /** * Support for sorting image file names alphabetically */ protected static final Comparator<File> fileNameAlphabetizer = new Comparator<File>() { public int compare(File f1, File f2) { String name0 = f1.getName(); String name1 = f2.getName(); return name0.compareTo(name1); } }; /** * A threaded object which is responsible for generating * loading of images and creating frames with the images as background. */ protected class ImportImageDirThread extends CogToolWorkThread { protected final String title = L10N.get("WDX.ImportingImageDirectory", "Importing Images ..."); protected ProgressBar progressBar = interaction.createProgressBar(title, this, title, ProgressBar.SMOOTH); protected File[] imageList; protected Frame[] createdFrames; protected double frameX; protected double frameY; // If doWork() fails to complete by throwing an Exception, we don't want // to try to install the frames, so we use finished to say whether or not // doWork() completed normally. protected boolean finished = false; protected Frame frameToDelete; protected CompoundUndoableEdit editSequence = new CompoundUndoableEdit(importBackgroundImages, DesignEditorLID.ImportImageDirectory); // Used for computing progress percentage protected double completedCount = 0.0; public ImportImageDirThread(File[] images, double originX, double originY, Frame frameBeingDeleted) { super(); if (images == null) { throw new RcvrImageException("Required images array was null"); } imageList = images; frameX = originX; frameY = originY; // Sort the images by name Arrays.sort(imageList, fileNameAlphabetizer); createdFrames = new Frame[imageList.length]; frameToDelete = frameBeingDeleted; setProgressCallback(progressBar, true); setDisabler(progressBar.getDisabler()); } public void doWork() { // Performed by child thread byte[] imageData; DoubleRectangle imageSize; int i = 0; Set<DeviceType> designDeviceTypes = design.getDeviceTypes(); int minFrameWidth = DesignUtil.getFrameMinWidth(); int minFrameHeight = DesignUtil.getFrameMinHeight(); double frameScale = DesignUtil.getFrameScaleFactor(); DesignUtil.IFrameSituator frameSituator = new DesignUtil.ByRowFrameSituator(frameX, frameY, 16.0, 16.0, minFrameWidth, minFrameHeight, CogToolPref.FRAMES_PER_ROW.getInt(), frameScale); while ((! isCanceled()) && (i < imageList.length)) { try { File imgFile = imageList[i]; // Set the background to new image imageData = GraphicsUtil.loadImageFromFile(imgFile.getAbsolutePath()); imageSize = GraphicsUtil.getImageBounds(imageData); // Strip extension off the filename to get the frame name String fileName = imgFile.getName(); Matcher imageFileNameMatcher = IMAGE_FILE_NAME.matcher(fileName); if (! imageFileNameMatcher.matches()) { throw new RcvrImageException( "Formerly matching image file name no longer matches: " + fileName); } // Create new frame & add it to design Frame frame = new Frame(imageFileNameMatcher.group(1), designDeviceTypes); frame.setBackgroundImage(imageData, imageSize); frameSituator.situateNextFrame(frame); createdFrames[i] = frame; // Update current progress completedCount += 1.0; setProgress(completedCount / imageList.length, fileName); // Prepare for next frame i++; } catch (GraphicsUtil.ImageException e) { addWorkException(new RcvrImageException(e)); } catch (IOException e) { addWorkException(new RcvrIOException(e)); } } finished = true; } @Override public void doneCallback() { // Performed by the main UI thread // If canceled or failed to complete normally, don't // make the changes if ((! isCanceled()) && finished && (createdFrames.length > 0)) { if (frameToDelete != null) { deleteFrame(frameToDelete, DesignEditorLID.ImportImageDirectory, editSequence); } double maxY = 0.0; Frame lastFrame = null; Iterator<Frame> existingFrames = design.getFrames().iterator(); while (existingFrames.hasNext()) { Frame f = existingFrames.next(); double curY = ui.getFrameDisplayBounds(f).y; if (curY > maxY) { maxY = curY; lastFrame = f; } } if (lastFrame != null) { maxY += ui.getFrameDisplayBounds(lastFrame).height + 16.0; } for (Frame createdFrame : createdFrames) { makeFrameNameUnique(createdFrame); addFrame(createdFrame, editSequence); if (maxY > 0.0) { createdFrame.moveFrameOrigin(0.0, maxY); } } editSequence.end(); undoMgr.addEdit(editSequence); } RcvrExceptionHandler.recoverWorkThread(this, interaction); progressBar.dispose(); } } protected IListenerAction createImportImageDirectory() { return new AListenerAction() { public boolean performAction(Object actionParms) { // Ask for a directory of images String filePath = interaction.askForImageDir(); if (filePath != null) { File dir = new File(filePath); if (dir.isDirectory()) { final File[] files = dir.listFiles(new FileFilter() { public boolean accept(File pathname) { String name = pathname.getName(); Matcher imageFileNameMatcher = IMAGE_FILE_NAME.matcher(name); if (imageFileNameMatcher.matches()) { // Test for Mac resource forks if (name.startsWith("._")) { String realName = name.substring(2); String fullPath = pathname.getParent() + FileUtil.FILE_SEP + realName; return ! new File(fullPath).exists(); } return true; } return false; } }); // Find an unoccupied starting position by cascading. DoublePoint origin = new DoublePoint(10.0, 10.0); // Check if only the default frame is present; // if so, and it hasn't been significantly modified or // used, indicate that it should be deleted when // the images are imported. Frame frameToDelete = design.getFrame(DesignEditorController.INITIAL_FRAME_NAME); if ((frameToDelete == null) || (design.getFrames().size() > 1) || (frameToDelete.getWidgets().size() > 0) || (frameToDelete.getBackgroundImage() != null)) { frameToDelete = null; } ImportImageDirThread workThread = new ImportImageDirThread(files, origin.x, origin.y, frameToDelete); ThreadManager.startNewThread(workThread); } } return true; } }; } protected IListenerAction createSetSkinAction(final SkinType newSkin, final CogToolLID lid) { return new AListenerAction() { public boolean performAction(Object prms) { final SkinType oldSkin = design.getSkin(); design.setSkin(newSkin); undoMgr.addEdit(new AUndoableEdit(lid) { @Override public String getPresentationName() { return changeSkin; } @Override public void redo() { super.redo(); design.setSkin(newSkin); } @Override public void undo() { super.undo(); design.setSkin(oldSkin); } }); return true; } }; } protected IListenerAction createRenderAllAction(final boolean render, final CogToolLID lid) { return new AListenerAction() { public boolean performAction(Object prms) { FrameEditorController.renderUnRenderAll(design, render, lid, DesignEditorController.this.undoMgr); return true; } }; } protected IListenerAction createChangeDelayAction() { return new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorUI.ChangeDelayParameters.class; } public boolean performAction(Object actionParms) { DesignEditorUI.ChangeDelayParameters prm = (DesignEditorUI.ChangeDelayParameters) actionParms; int numTransitions = prm.selection.getSelectedTransitionCount(); if (numTransitions == 0) { interaction.protestNoSelection(); return false; } if (numTransitions > 1) { interaction.protestMultipleTransitionSelection(); return false; } final Transition transition = prm.selection.getSelectedTransitions()[0]; final double oldDelayInSecs = transition.getDelayInSecs(); final String oldDelayLabel = transition.getDelayLabel(); if ((oldDelayInSecs != prm.delayInSecs) || ! oldDelayLabel.equals(prm.delayLabel)) { final double newDelayInSecs = prm.delayInSecs; final String newDelayLabel = prm.delayLabel; transition.setDelayInfo(newDelayInSecs, newDelayLabel); DemoStateManager.IDesignUndoableEdit edit = new DemoStateManager.ObsoletingEdit(DesignEditorLID.ChangeDelay, demoStateMgr) { @Override public String getPresentationName() { return changeDelay; } @Override public void redo() { super.redo(); transition.setDelayInfo(newDelayInSecs, newDelayLabel); stateMgr.noteTransitionEdit(transition, this); if (CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } @Override public void undo() { super.undo(); transition.setDelayInfo(oldDelayInSecs, oldDelayLabel); stateMgr.noteTransitionEdit(transition, this); if (CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } }; demoStateMgr.noteTransitionEdit(transition, edit); undoMgr.addEdit(edit); if (CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } return true; } }; } /** * Creates a new DesignEditorController instance for editing an existing * Design instance. * * @param design the Design instance to edit. * @param project the Project containing the design * @return the Controller instance for editing the given Design instance * @author mlh */ public static DesignEditorController openController(Design design, Project project) { DesignEditorController controller = (DesignEditorController) ControllerRegistry.ONLY.findOpenController(design); // If already open, just bring it to front if (controller != null) { controller.takeFocus(); } else { controller = new DesignEditorController(design, project); ControllerRegistry.ONLY.addOpenController(controller); } return controller; } }