/******************************************************************************* * 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.IOException; import java.util.ArrayList; 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.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.Stack; import java.util.TreeSet; 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.FrameTemplateSupport; import edu.cmu.cs.hcii.cogtool.controller.DemoStateManager.IDesignUndoableEdit; import edu.cmu.cs.hcii.cogtool.model.AMenuWidget; import edu.cmu.cs.hcii.cogtool.model.AParentWidget; import edu.cmu.cs.hcii.cogtool.model.Association; import edu.cmu.cs.hcii.cogtool.model.CheckBox; import edu.cmu.cs.hcii.cogtool.model.ChildWidget; import edu.cmu.cs.hcii.cogtool.model.ContextMenu; import edu.cmu.cs.hcii.cogtool.model.Design; 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.DoubleSize; import edu.cmu.cs.hcii.cogtool.model.Frame; import edu.cmu.cs.hcii.cogtool.model.FrameElement; import edu.cmu.cs.hcii.cogtool.model.FrameElementGroup; import edu.cmu.cs.hcii.cogtool.model.GridButton; import edu.cmu.cs.hcii.cogtool.model.GridButtonGroup; import edu.cmu.cs.hcii.cogtool.model.IWidget; import edu.cmu.cs.hcii.cogtool.model.ListItem; import edu.cmu.cs.hcii.cogtool.model.MenuHeader; import edu.cmu.cs.hcii.cogtool.model.MenuItem; import edu.cmu.cs.hcii.cogtool.model.Project; import edu.cmu.cs.hcii.cogtool.model.PullDownHeader; import edu.cmu.cs.hcii.cogtool.model.PullDownItem; import edu.cmu.cs.hcii.cogtool.model.RadioButton; import edu.cmu.cs.hcii.cogtool.model.RadioButtonGroup; import edu.cmu.cs.hcii.cogtool.model.ShapeType; import edu.cmu.cs.hcii.cogtool.model.SimpleWidgetGroup; import edu.cmu.cs.hcii.cogtool.model.SkinType; import edu.cmu.cs.hcii.cogtool.model.TraversableWidget; import edu.cmu.cs.hcii.cogtool.model.Widget; import edu.cmu.cs.hcii.cogtool.model.WidgetAttributes; import edu.cmu.cs.hcii.cogtool.model.WidgetType; import edu.cmu.cs.hcii.cogtool.ui.DesignEditorLID; import edu.cmu.cs.hcii.cogtool.ui.DesignEditorUI; import edu.cmu.cs.hcii.cogtool.ui.FrameEditorInteraction; import edu.cmu.cs.hcii.cogtool.ui.FrameEditorLID; import edu.cmu.cs.hcii.cogtool.ui.FrameEditorSelectionState; import edu.cmu.cs.hcii.cogtool.ui.FrameEditorUI; 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.EmptyIterator; import edu.cmu.cs.hcii.cogtool.util.GraphicsUtil; import edu.cmu.cs.hcii.cogtool.util.IAttributed; 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.ListenerIdentifier; import edu.cmu.cs.hcii.cogtool.util.NamedObjectUtil; import edu.cmu.cs.hcii.cogtool.util.NullSafe; import edu.cmu.cs.hcii.cogtool.util.PrecisionUtilities; import edu.cmu.cs.hcii.cogtool.util.RcvrClipboardException; import edu.cmu.cs.hcii.cogtool.util.RcvrImageException; import edu.cmu.cs.hcii.cogtool.util.ReadOnlyList; import edu.cmu.cs.hcii.cogtool.util.UndoManager; //TODO: Use exit point in a mouse out, as a mouse drag. //TODO: Use entry point in a mouse in, as a mouse drag. /** * @author alexeiser * Controls the various functions for the frame view. * */ public class FrameEditorController extends ZoomableController { /** * Frame editor UI for this controller. */ private FrameEditorUI ui; /** * Demonstration state manager (manages invalidating/obsoleting * state changes to demonstration and script steps based on * frame and widget edits). */ private DemoStateManager demoStateMgr; /** * Local reference to the interaction held by the UI. */ private FrameEditorInteraction interaction; /** * The model object for this controller. A Frame. */ private Frame model; /** * The parent object for this model. the design. * Used in order to preserve the relationships and to invalidating scripts. */ private Design design; private static final String DEFAULT_WIDGET_PREFIX = L10N.get("FE.WidgetNamePrefix", "Widget"); private static final String DEFAULT_GROUP_PREFIX = L10N.get("FE.GroupNamePrefix", "Group"); /** * Holds the current suffix to auto insert on new widgets */ private int widgetNameSuffix = 1; private static final String PASTE = DesignEditorCmd.PASTE; private static final String WIDGET_COPIED = L10N.get("FE.WidgetCopied", "Widget copied to the clipboard"); private static final String WIDGETS_COPIED = L10N.get("FE.WidgetsCopied", "Widgets copied to the clipboard"); private static final String SELECT_WIDGET_COLOR = L10N.get("FRAME.WIDGET.selectColor", "Select Widget Color"); private static final String BRING_TO_FRONT = L10N.get("UNDO.FE.BringToFront", "Bring to Front"); private static final String BRING_FORWARD = L10N.get("UNDO.FE.BringForward", "Bring Forward"); private static final String SEND_BACKWARD = L10N.get("UNDO.FE.SendBackward", "Send Backward"); private static final String SEND_TO_BACK = L10N.get("UNDO.FE.SendToBack", "Send to Back"); private static final String CHG_DISPLAYED_LABEL = L10N.get("UNDO.FE.ChangeWidgetTitle", "Change Displayed Label"); private static final String CHG_AUX_TEXT = L10N.get("UNDO.FE.ChangeWidgetAuxText", "Change Auxiliary Text"); private static final String SET_REMOTE_LABEL = L10N.get("UNDO.FE.SetRemoteLabel", "Set Remote Label"); private static final String CAPTURE_BKG_IMG = L10N.get("UNDO.FE.CaptureBackgroundImage", "Capture Background"); private static final String CHG_WIDGET_SHAPE = L10N.get("UNDO.FE.ChangeWidgetShape", "Change Widget Shape"); private static final String CHG_WIDGET_TYPE = L10N.get("UNDO.FE.ChangeWidgetType", "Change Widget Type"); private static final String CHG_WIDGET_RENDERED = L10N.get("UNDO.FE.ChangeRenderSkin", "Change Widget Rendered"); private static final String SET_WIDGET_COLOR = L10N.get("UNDO.FE.SetWidgetColor", "Set Widget Color"); private static final String REMOVE_BKG_IMG = L10N.get("UNDO.FE.RemoveBackgroundImage", "Remove Frame Background Image"); private static final String SET_BKG_IMG = L10N.get("UNDO.FE.SetBackgroundImage", "Set Frame Background Image"); private static final String NEW_WIDGET = L10N.get("UNDO.FE.NewWidget", "New Widget"); private static final String DELETE_WIDGETS = L10N.get("UNDO.FE.DeleteWidgets", "Delete Widgets"); private static final String DELETE_WIDGET = L10N.get("UNDO.FE.DeleteWidget", "Delete Widget"); private static final String DELETE_GROUP = L10N.get("UNDO.FE.DeleteGroup", "Delete Group"); private static final String RESIZE_WIDGETS = L10N.get("UNDO.FE.ResizeWidgets", "Resize Widgets"); private static final String RESIZE_WIDGET = L10N.get("UNDO.FE.ResizeWidget", "Resize Widget"); private static final String MOVE_WIDGETS = L10N.get("UNDO.FE.MoveWidgets", "Move Widgets"); private static final String MOVE_WIDGET = L10N.get("UNDO.FE.MoveWidget", "Move Widget"); private static final String REORDER_WIDGET = L10N.get("UNDO.FE.ReorderWidget", "Reorder Widget"); private static final String CHG_WIDGET_NAME = L10N.get("UNDO.FE.ChangeWidgetName", "Change Widget Name"); private static final String CHG_FRAME_NAME = L10N.get("UNDO.FE.ChangeFrameName", "Change Frame Name"); private static final String DUPLICATE_WIDGET = L10N.get("UNDO.FE.DuplicateWidget", "Duplicate Widget"); private static final String DUPLICATE_WIDGETS = L10N.get("UNDO.FE.DuplicateWidgets", "Duplicate Widgets"); private static final String REMOVE_WIDGET_IMG = L10N.get("UNDO.FE.RemoveWidgetImage", "Remove Widget Image"); private static final String SET_WIDGET_IMG = L10N.get("UNDO.FE.SetWidgetImage", "Set Widget Image"); private static final String CHANGE_SKIN = L10N.get("UNDO.DE.ChangeSkin", "Change Skin"); private static final String RENDER_ALL = L10N.get("UNDO.DE.RenderAll", "Render Design's Widgets"); private static final String UN_RENDER = L10N.get("UNDO.DE.UnRender", "UnRender Design's Widgets"); private static final String CHG_WIDGET_LEVEL = L10N.get("UNDO.FE.ChangeWidgetLevel", "Level Change"); private static final String SET_SPEAKER_TEXT = L10N.get("UNDO.FE.SetSpeakerText", "Set Speaker Text"); private static final String SET_SPEAKER_DURATION = L10N.get("UNDO.FE.SetSpeakerDuration", "Set Speaker Duration"); private static final String GROUP_ELEMENTS = L10N.get("UNDO.FE.GroupElements", "Group"); private static final String UNGROUP_ELEMENTS = L10N.get("UNDO.FE.UngroupElements", "Ungroup"); private static final String nothingPasted = L10N.get("FE.NothingPasted", "Nothing pasted"); private static final String pasteComplete = L10N.get("FE.PasteComplete", "object(s) pasted"); private static final String templateCreated = L10N.get("FE.TemplateCreated", "Frame Template created"); private static final String noTemplateWidgets = L10N.get("FE.NoTemplateWidgets", "No widgets are available to define Frame Template"); private static final String frameTemplateComplete = L10N.get("FE.FrameTemplateComplete", "widget(s) comprise the new Frame Template"); /** * "Duplicator" support for duplicating widgets; simply returns the given * frame. */ private static Frame.IFrameDuplicator lookupFrameDuplicator = new Frame.IFrameDuplicator() { public Frame getOrDuplicate(Frame frameToCopy) { return frameToCopy; } public void recordDuplicateFrame(Frame originalFrame, Frame frameDuplicate) { // Do nothing } }; protected static class ElementAllWidgetIterator implements Iterator<IWidget> { private Stack<Iterator<? extends FrameElement>> mbrIterators = new Stack<Iterator<? extends FrameElement>>(); private IWidget nextWidget; public ElementAllWidgetIterator(Association<?> association) { mbrIterators.push(association.iterator()); nextWidget = null; } public ElementAllWidgetIterator(IWidget widget) { nextWidget = widget; pushParentWidgetIterator(); } private void pushParentWidgetIterator() { if (nextWidget instanceof AParentWidget) { SimpleWidgetGroup groupChildren = ((AParentWidget) nextWidget).getChildren(); if (groupChildren != null) { mbrIterators.push(groupChildren.iterator()); } } } public boolean hasNext() { if (nextWidget == null) { while (mbrIterators.size() > 0) { if (mbrIterators.peek().hasNext()) { FrameElement nextMbr = mbrIterators.peek().next(); if (nextMbr instanceof IWidget) { nextWidget = (IWidget) nextMbr; pushParentWidgetIterator(); return true; } if (nextMbr instanceof Association<?>) { Iterator<? extends FrameElement> members = ((Association<?>) nextMbr).iterator(); mbrIterators.push(members); } // else unknown member type (currently none exist!) } else { mbrIterators.pop(); } } return false; } return true; } public IWidget next() { if (hasNext()) { IWidget result = nextWidget; nextWidget = null; return result; } throw new NoSuchElementException(); } public void remove() { throw new UnsupportedOperationException(); } } protected static class ElementAllWidgets implements Iterable<IWidget> { private FrameElement elt; public ElementAllWidgets(FrameElement element) { elt = element; } public Iterator<IWidget> iterator() { if (elt instanceof IWidget) { return new ElementAllWidgetIterator((IWidget) elt); } if (elt instanceof Association<?>) { return new ElementAllWidgetIterator((Association<?>) elt); } return new EmptyIterator<IWidget>(); } } /** * Combines a noteWidgetEdit and a check for automatic regeneration */ private void noteEditCheckRegenerate(DemoStateManager.ObsoletingEdit edit) { demoStateMgr.noteFrameEdit(model, edit); if (CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } private void noteEditCheckRegenerate(FrameElement elt, DemoStateManager.ObsoletingEdit edit) { demoStateMgr.noteWidgetsEdit(new ElementAllWidgets(elt), edit); if (CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } private void noteEditCheckRegenerate(IWidget widget, DemoStateManager.ObsoletingEdit edit) { demoStateMgr.noteWidgetEdit(widget, edit); if (CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } private void noteEditCheckRegenerate(Iterable<? extends IWidget> widgets, DemoStateManager.ObsoletingEdit edit) { demoStateMgr.noteWidgetsEdit(widgets, edit); if (CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } private void noteEditCheckRegenerate(SimpleWidgetGroup group1, SimpleWidgetGroup group2, DemoStateManager.ObsoletingEdit edit) { demoStateMgr.noteReorderEdit(group1, group2, edit); if (CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } } /** * Undo/redo edit for moving a widget to a new origin */ protected class WidgetMoveUndoRedo extends DemoStateManager.ObsoletingEdit { private String presentationLabel; private IWidget widget; private double newX; private double newY; private double oldX; private double oldY; public WidgetMoveUndoRedo(ListenerIdentifier listenerID, String presentation, IWidget w, double nx, double ny, double ox, double oy) { super(listenerID, demoStateMgr); presentationLabel = presentation; widget = w; newX = nx; newY = ny; oldX = ox; oldY = oy; } @Override public String getPresentationName() { return presentationLabel; } @Override public void redo() { super.redo(); widget.setWidgetOrigin(newX, newY); noteEditCheckRegenerate(widget, this); } @Override public void undo() { super.undo(); widget.setWidgetOrigin(oldX, oldY); noteEditCheckRegenerate(widget, this); } } /** * The listenerAction to handle setting alignment of frame elements * * Subclass of listener action, which all alignment actions use. * TODO: generalize and move to external utility class. * * @author alexeiser * */ protected class ElementAlignmentAction extends AlignmentAction { protected class ElementAlignmentUndoRedo extends DemoStateManager.ObsoletingEdit { private FrameElement elt; private double deltaX; private double deltaY; public ElementAlignmentUndoRedo(ListenerIdentifier listenerID, FrameElement e, double dx, double dy) { super(listenerID, demoStateMgr); elt = e; deltaX = dx; deltaY = dy; } @Override public String getPresentationName() { return getAlignmentName(); } @Override public void redo() { super.redo(); // Move elt.moveElement(deltaX, deltaY); noteEditCheckRegenerate(elt, this); } @Override public void undo() { super.undo(); // move to old location elt.moveElement(- deltaX, - deltaY); noteEditCheckRegenerate(elt, this); } } public ElementAlignmentAction(int alignAction) { super(alignAction); } /** * Set the expected parameter class. * use Selection. (Expects multi select) */ public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; // Reset for new computation reset(); // Check that there are two or more items to align. // Items that can align are elements that are not descendants // of a selected FrameElementGroup, such as: // (1) A selected widget that doesn't have a parent group // (thus, by definition, not an IChildWidget) // (2) The parent group of a selected non-IChildWidget widget // (3) A selected FrameElementGroup // For optimization, keep track of the bounds for each item // that will be aligned. Map<FrameElement, DoubleRectangle> allowedToAlign = new HashMap<FrameElement, DoubleRectangle>(); Iterator<FrameElement> selectedElts = selection.getSelectedElementsIterator(); // Determine which elements can participate and the // reference location while (selectedElts.hasNext()) { FrameElement elt = selectedElts.next(); if ((! (elt instanceof ChildWidget)) && ! isMemberOfSelectedGroup(elt, selection)) { DoubleRectangle bds; if (elt instanceof IWidget) { SimpleWidgetGroup parentGroup = ((IWidget) elt).getParentGroup(); if (parentGroup == null) { bds = elt.getEltBounds(); allowedToAlign.put(elt, bds); } else { bds = parentGroup.getEltBounds(); allowedToAlign.put(parentGroup, bds); } } else { bds = elt.getEltBounds(); allowedToAlign.put(elt, bds); } computeReference(bds); } } // Complain if not at least two participants if (allowedToAlign.size() < 2) { interaction.protestTooFewElements(); return false; } CompoundUndoableEdit editSequence = new CompoundUndoableEdit(getAlignmentName(), LIDS[action]); // Adjust all the elements relative to the reference location Iterator<Map.Entry<FrameElement, DoubleRectangle>> alignEntries = allowedToAlign.entrySet().iterator(); while (alignEntries.hasNext()) { Map.Entry<FrameElement, DoubleRectangle> alignEntry = alignEntries.next(); // Get the bounds DoubleRectangle bounds = alignEntry.getValue(); // Compute where the bounds should be based on the reference; // the new origin is available in this.newX and this.newY computeNewOrigin(bounds); FrameElement elt = alignEntry.getKey(); // Move to the new location double dx = newX - bounds.x; double dy = newY - bounds.y; elt.moveElement(dx, dy); DemoStateManager.ObsoletingEdit edit = new ElementAlignmentUndoRedo(LIDS[action], elt, dx, dy); noteEditCheckRegenerate(elt, edit); editSequence.addEdit(edit); } editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } interaction.setStatusMessage(getAlignmentName()); return true; } // performAction } /** * Constructor for the FrameEditorController. * Takes a Frame as the input model. * * Creates the FrameEditorUI and the view. */ public FrameEditorController(Frame f, Design d, Project p) { super(p); model = f; design = d; // Get the Undo Manager from the factory. // Opening a new window for this controller will keep the undo from the // previous one. undoMgr = UndoManager.getUndoManager(model, project); demoStateMgr = DemoStateManager.getStateManager(project, design); // Generate the UI Model. This uses a factory, but it's not really // necessary. The design is passed in mostly for renaming. // Similar with project. ui = new FrameEditorUI(model, design, project, undoMgr); // Get the UI's interaction support. interaction = ui.getInteraction(); // Set up the list of Perform Action operations. assignActions(); // Set the view as visible, and let the dogs fly! :D ui.setVisible(true); } /** * Generic accessor method from default controller. * Accessor method * @return model object. */ @Override protected Object getModelObject() { return getModel(); } /** * Accessor for the specific model * @return the Frame model being managed by this controller */ public Frame getModel() { return model; } /** * Add the listeners for Menu Items, and other LID listeners */ @Override public void assignActions() { // get the default actions from Default controller super.assignActions(); // Enable undo & redo ui.setAction(FrameEditorLID.Undo, new UndoController.UndoAction(undoMgr, interaction)); ui.setAction(FrameEditorLID.Redo, new UndoController.RedoAction(undoMgr, interaction)); // Enable cut copy and paste ui.setAction(FrameEditorLID.Paste, createPasteAction()); ui.setAction(FrameEditorLID.SetFrameTemplate, createSetFrameTemplateAction()); ui.setAction(FrameEditorLID.ClearFrameTemplate, new AListenerAction() { public boolean performAction(Object prms) { FrameTemplateSupport.clearFrameTemplate(design); return true; } }); ui.setAction(FrameEditorLID.Copy, createCopyWidgetAction()); ui.setAction(FrameEditorLID.Cut, createCutWidgetAction()); ui.setAction(CogToolLID.CopyPath, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState seln = (FrameEditorSelectionState) prms; if (seln.getWidgetSelectionCount() != 1) { return false; } IWidget w = seln.getSelectedIWidgets()[0]; Object pathObj = w.getAttribute(WidgetAttributes.IMAGE_PATH_ATTR); if (! NullSafe.equals(WidgetAttributes.NO_IMAGE, pathObj)) { ClipboardUtil.copyTextData((String) pathObj); } return true; } }); ui.setAction(FrameEditorLID.Rename, createInitiateRenameAction()); ui.setAction(FrameEditorLID.Relabel, createInitiateRelabelAction()); // Select all. ui.setAction(FrameEditorLID.SelectAll, new AListenerAction() { public boolean performAction(Object prms) { ui.selectAllWidgets(); return true; } }); // Creating a new widget // May be called from the menu item, or from mouseState ui.setAction(FrameEditorLID.NewWidget, createNewWidgetAction()); ui.setAction(FrameEditorLID.NewWidgetJustWarn, createNewWidgetExplanationAction()); // Delete an item. // Requires a selection state ui.setAction(FrameEditorLID.Delete, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; // Delete the item / add undo return deleteElements(selection); } }); // Adjust image/color properties ui.setAction(FrameEditorLID.SetBackgroundImage, createSetBackgroundImageAction()); // Clear the background of an image. ui.setAction(FrameEditorLID.RemoveBackgroundImage, new AListenerAction() { public boolean performAction(Object prms) { // Clear background, by saying use // "no" image. setBackgroundImage(null, WidgetAttributes.NO_IMAGE); return true; } }); ui.setAction(FrameEditorLID.CopyImageAsBackground, createCopyImageAsBkgAction()); ui.setAction(FrameEditorLID.PasteBackgroundImage, createPasteBackgroundImageAction()); // Set the color of the widgets. ui.setAction(FrameEditorLID.SetWidgetColor, newSetWidgetColorAction()); // Skins! ui.setAction(FrameEditorLID.SkinWireFrame, createSetSkinAction(SkinType.WireFrame, FrameEditorLID.SkinWireFrame)); ui.setAction(FrameEditorLID.SkinMacOSX, createSetSkinAction(SkinType.MacOSX, FrameEditorLID.SkinMacOSX)); ui.setAction(FrameEditorLID.SkinWinXP, createSetSkinAction(SkinType.WinXP, FrameEditorLID.SkinWinXP)); ui.setAction(FrameEditorLID.SkinPalm, createSetSkinAction(SkinType.Palm, FrameEditorLID.SkinPalm)); ui.setAction(CogToolLID.RenderAll, createRenderAllAction(true, CogToolLID.RenderAll)); ui.setAction(CogToolLID.UnRender, createRenderAllAction(false, CogToolLID.UnRender)); // Nudge selected widget(s) // requires selection ui.setAction(FrameEditorLID.NudgeLeft, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; // Displace left by 1 pixel scaled by // the zoom double dx = -1.0 / ui.getZoom(); // Move by the point return moveElements(selection, dx, 0.0); } }); ui.setAction(FrameEditorLID.NudgeRight, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; // Move by 1 pixel scaled by zoom right. double dx = 1.0 / ui.getZoom(); return moveElements(selection, dx, 0.0); } }); ui.setAction(FrameEditorLID.NudgeUp, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; // Move up 1 pixel scaled by zoom double dy = -1.0 / ui.getZoom(); return moveElements(selection, 0.0, dy); } }); ui.setAction(FrameEditorLID.NudgeDown, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; // move down by 1 pixel scaled by zoom double dy = 1.0 / ui.getZoom(); return moveElements(selection, 0.0, dy); } }); // Align selected widgets ui.setAction(FrameEditorLID.AlignTop, new ElementAlignmentAction(AlignmentAction.TOP)); ui.setAction(FrameEditorLID.AlignBottom, new ElementAlignmentAction(AlignmentAction.BOTTOM)); ui.setAction(FrameEditorLID.AlignLeft, new ElementAlignmentAction(AlignmentAction.LEFT)); ui.setAction(FrameEditorLID.AlignRight, new ElementAlignmentAction(AlignmentAction.RIGHT)); ui.setAction(FrameEditorLID.AlignCenter, new ElementAlignmentAction(AlignmentAction.CENTER)); ui.setAction(FrameEditorLID.AlignHorizCenter, new ElementAlignmentAction(AlignmentAction.HORIZ_CENTER)); ui.setAction(FrameEditorLID.AlignVertCenter, new ElementAlignmentAction(AlignmentAction.VERT_CENTER)); // Space selected widgets equally ui.setAction(FrameEditorLID.SpaceVertically, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; // Equally space the widgets in the // vertical axis. final boolean VERTICAL = true; return spaceElementsEqually(selection, VERTICAL); } }); ui.setAction(FrameEditorLID.SpaceHorizontally, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; // Equally space the widgets in the // horizontal axis. final boolean HORIZONTAL = false; return spaceElementsEqually(selection, HORIZONTAL); } }); // Adjust ordering of selected widget(s) ui.setAction(FrameEditorLID.BringToFront, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { CompoundUndoableEdit editSequence = new CompoundUndoableEdit(BRING_TO_FRONT, FrameEditorLID.BringToFront); FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; // Get the list of selected widgets // Keep them in the same level ordering // they started in IWidget[] selected = getSelectedWidgets(selection, Widget.WidgetLevelComparator.ONLY); // Now, iterate through all widgets and // set the level. for (IWidget element : selected) { // use MAX Value here // adjustWidgetLevel will tell the frame // and it will change MAX_VALUE to // be the correct number adjustWidgetLevel(Integer.MAX_VALUE, element, editSequence, editSequence.getLID()); } editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } }); ui.setAction(FrameEditorLID.BringForward, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { CompoundUndoableEdit editSequence = new CompoundUndoableEdit(BRING_FORWARD, FrameEditorLID.BringForward); FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; // Get the list of selected widgets // Keep them in the same level ordering // they started in IWidget[] selected = getSelectedWidgets(selection, Widget.WidgetLevelComparator.ONLY); // Fix corner case where all the widgets are // stacked downward from the top // IE: Prevent the current top most item // from moving. int maxLevel = model.getWidgets().size() - 1; // Traverse the list sorted by level in reverse order for (int i = selected.length - 1; i >= 0; i--, maxLevel--) { IWidget w = selected[i]; int widgetLevel = w.getLevel(); // If the level is less then the max, // try to increase it one. if (widgetLevel < maxLevel) { adjustWidgetLevel(widgetLevel + 1, w, editSequence, editSequence.getLID()); } } editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } }); ui.setAction(FrameEditorLID.SendBackward, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { CompoundUndoableEdit editSequence = new CompoundUndoableEdit(SEND_BACKWARD, FrameEditorLID.SendBackward); FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; // Get the list of selected widgets // Keep them in the same level ordering // they started in IWidget[] selected = getSelectedWidgets(selection, Widget.WidgetLevelComparator.ONLY); // Fix corner case where all the widgets are // stacked upward from level 0 int minLevel = 0; for (int i = 0; i < selected.length; i++, minLevel++) { IWidget w = selected[i]; int widgetLevel = w.getLevel(); // move the level down by 1 if (widgetLevel > minLevel) { adjustWidgetLevel(widgetLevel - 1, w, editSequence, editSequence.getLID()); } } editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } }); ui.setAction(FrameEditorLID.SendToBack, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { CompoundUndoableEdit editSequence = new CompoundUndoableEdit(SEND_TO_BACK, FrameEditorLID.SendToBack); FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; // Get the list of selected widgets // Keep them in the same level ordering // they started in IWidget[] selected = getSelectedWidgets(selection, Widget.WidgetLevelComparator.ONLY); // Traverse in reverse so they get set to 0 // in the correct order for (int i = selected.length - 1; i >= 0; i--) { // try to move all towards 0. adjustWidgetLevel(0, selected[i], editSequence, editSequence.getLID()); } editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } }); // Mouse operations on widgets ui.setAction(FrameEditorLID.MoveWidgets, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.MoveParameters.class; } public boolean performAction(Object prms) { // Get the move parameters. FrameEditorUI.MoveParameters movePrms = (FrameEditorUI.MoveParameters) prms; if (movePrms != null) { return moveElements(movePrms.selection, movePrms.moveByX, movePrms.moveByY, movePrms.moveAsGroup); } return false; } }); ui.setAction(FrameEditorLID.Reorder, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.ReorderWidgetParameters.class; } public boolean performAction(Object prms) { // Get the parameters. FrameEditorUI.ReorderWidgetParameters rPrms = (FrameEditorUI.ReorderWidgetParameters) prms; if (rPrms != null) { if (rPrms.parent == null) { return reorderWidget(rPrms.reorderWidget, rPrms.widgetGroup, rPrms.insertIndex); } return reorderChildWidget((ChildWidget) rPrms.reorderWidget, rPrms.widgetGroup, rPrms.insertIndex, rPrms.parent); } return false; } }); ui.setAction(FrameEditorLID.InsertDuplicate, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.InsertDuplicateParameters.class; } public boolean performAction(Object prms) { // Get the parameters. FrameEditorUI.InsertDuplicateParameters iPrms = (FrameEditorUI.InsertDuplicateParameters) prms; if (iPrms != null) { return insertDuplicateWidget(iPrms.reorderWidget, iPrms.widgetGroup, iPrms.insertIndex, iPrms.parent, iPrms.moveByX, iPrms.moveByY); } return false; } }); ui.setAction(FrameEditorLID.ResizeWidgets, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.ResizeParameters.class; } public boolean performAction(Object prms) { FrameEditorUI.ResizeParameters resizePrms = (FrameEditorUI.ResizeParameters) prms; if (prms != null) { // Resize the frame elements based on prms return resizeElements(resizePrms.oldX, resizePrms.oldY, resizePrms.newX, resizePrms.newY, resizePrms.ratioX, resizePrms.ratioY, resizePrms.selection); } return false; } }); // Change properties of selected widget(s) ui.setAction(FrameEditorLID.ChangeShapeProperty, createChangeShapeAction()); // Change the widget title. // The title is non unique. ui.setAction(FrameEditorLID.ChangeTitleProperty, createChangeTitlePropertyAction()); ui.setAction(FrameEditorLID.ChangeAuxTextProperty, createChangeAuxTextPropertyAction()); // Change the name. This requires a UNIQUE name. // Only one selected widget is expected. ui.setAction(DesignEditorLID.RenameFrame, new IListenerAction() { public Class<?> getParameterClass() { return DesignEditorUI.FrameRenameParameters.class; } public boolean performAction(Object prms) { DesignEditorUI.FrameRenameParameters p = (DesignEditorUI.FrameRenameParameters) prms; return updateFrameName(p.frame, p.newName); } }); // Change the name. This requires a UNIQUE name. // Only one selected widget is expected. ui.setAction(FrameEditorLID.ChangeNameProperty, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.ActionStringParameters.class; } public boolean performAction(Object prms) { FrameEditorUI.ActionStringParameters p = (FrameEditorUI.ActionStringParameters) prms; // Check selection count int numWidgets = p.selection.getWidgetSelectionCount(); if (numWidgets == 0) { interaction.protestNoSelection(); } else if (numWidgets > 1) { interaction.protestTooManyWidgets(); } else { IWidget[] selectedWidget = p.selection.getSelectedIWidgets(); // Update widget's name return updateWidgetName(selectedWidget[0], p.newString); } return false; } }); // Change the type of the widget. // TODO: this needs to be extended to invalidate transitions (e.g., if changed to Noninteractive) ui.setAction(FrameEditorLID.ChangeTypeProperty, createChangeTypeAction()); ui.setAction(FrameEditorLID.SetRenderSkin, createSetRenderSkinAction()); // Set the widget image property. ui.setAction(FrameEditorLID.SetImageProperty, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; // Apply to all selected items. Iterator<IWidget> selected = selection.getSelectedWidgetsIterator(); // Prompt user for the new file String imageURL = interaction.selectImageFile(); // Check if the user canceled the dialog if (imageURL != null) { try { byte[] imageData = GraphicsUtil.loadImageFromFile(imageURL); setWidgetImages(selected, imageData, imageURL); return true; } catch (IOException e) { // Tell the user if there was a // problem reading the file. interaction.protestUnreadableFile(); } } return false; } }); // Clear the image stored on a widget. ui.setAction(FrameEditorLID.RemoveImageProperty, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; Iterator<IWidget> selected = selection.getSelectedWidgetsIterator(); // Remove all the images setWidgetImages(selected, null, WidgetAttributes.NO_IMAGE); return true; } }); // capture the image under a widget.. // IE: get its background. ui.setAction(FrameEditorLID.CaptureImageProperty, captureImageAction()); ui.setAction(FrameEditorLID.Duplicate, duplicateWidgetsAction()); ui.setAction(CogToolLID.SetAttribute, new IListenerAction() { public Class<?> getParameterClass() { return UI.SetAttributeParameters.class; } public boolean performAction(Object prms) { UI.SetAttributeParameters saprms = (UI.SetAttributeParameters) prms; return frameSetAttribute(saprms.target, saprms.attrName, saprms.value, undoMgr); } }); ui.setAction(FrameEditorLID.SetSpeakerText, new IListenerAction() { public Class<?> getParameterClass() { return String.class; } public boolean performAction(Object prms) { return setSpeakerText((String) prms); } }); ui.setAction(FrameEditorLID.SetSpeakerTime, new IListenerAction() { public Class<?> getParameterClass() { return Double.class; } public boolean performAction(Object prms) { double newTime = ((Double) prms).doubleValue(); return setSpeakerDuration(newTime); } }); ui.setAction(FrameEditorLID.AddDesignDevices, new AListenerAction() { public boolean performAction(Object prms) { return DesignCmd.addDevices(project, design, interaction); } }); ui.setAction(FrameEditorLID.Group, createGroupElementsAction()); ui.setAction(FrameEditorLID.Ungroup, createUngroupElementsAction()); ui.setAction(FrameEditorLID.RenameEltGroup, new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.EltGroupRenameParameters.class; } public boolean performAction(Object prms) { FrameEditorUI.EltGroupRenameParameters p = (FrameEditorUI.EltGroupRenameParameters) prms; return renameEltGroup(p.eltGroup, p.newName); } }); ui.setAction(FrameEditorLID.SetRemoteLabelText, createSetRemoteLabelTextAction()); ui.setAction(FrameEditorLID.SetRemoteLabelType, createSetRemoteLabelTypeAction()); } // assignActions private boolean setAsSeparator(final IWidget widget, final Object value, final ChildWidget oldSelectedItem, IUndoableEditSequence editSeq) { final String IS_SEP_ATTR = WidgetAttributes.IS_SEPARATOR_ATTR; final Object oldValue = widget.getAttribute(IS_SEP_ATTR); final DoubleRectangle bounds = widget.getEltBounds(); final double oldHeight = bounds.height; final double newHeight = WidgetAttributes.IS_SEPARATOR.equals(value) ? (oldHeight / FrameEditorUI.SEPARATOR_RATIO) : (oldHeight * FrameEditorUI.SEPARATOR_RATIO); final boolean isAuto = widget.isStandard(); final SimpleWidgetGroup group = widget.getParentGroup(); final AParentWidget pullDown = (oldSelectedItem != null) ? oldSelectedItem.getParent() : null; if (widget.equals(oldSelectedItem)) { // the currently selected widget in the pull down // has changed to a separator; deselect it pullDown.setAttribute(WidgetAttributes.SELECTION_ATTR, WidgetAttributes.NONE_SELECTED); } widget.setAttribute(IS_SEP_ATTR, value); if (isAuto) { widget.setWidgetSize(bounds.width, newHeight); DesignEditorCmd.repositionChildren(widget.getParentGroup()); } if (editSeq != null) { DemoStateManager.IDesignUndoableEdit edit = new DemoStateManager.InvalidatingEdit(CogToolLID.SetAttribute, demoStateMgr) { @Override public String getPresentationName() { return DefaultCmd.SET_ATTRIBUTE; } @Override public void redo() { super.redo(); if (widget.equals(oldSelectedItem)) { pullDown.setAttribute(WidgetAttributes.SELECTION_ATTR, WidgetAttributes.NONE_SELECTED); } widget.setAttribute(IS_SEP_ATTR, value); if (isAuto) { widget.setWidgetSize(bounds.width, newHeight); DesignEditorCmd.repositionChildren(group); } stateMgr.noteWidgetEdit(widget, this); } @Override public void undo() { super.undo(); if (widget.equals(oldSelectedItem)) { pullDown.setAttribute(WidgetAttributes.SELECTION_ATTR, oldSelectedItem); } widget.setAttribute(IS_SEP_ATTR, oldValue); if (isAuto) { widget.setWidgetSize(bounds.width, oldHeight); DesignEditorCmd.repositionChildren(group); } stateMgr.noteWidgetEdit(widget, this); } }; demoStateMgr.noteWidgetEdit(widget, edit); editSeq.addEdit(edit); } return true; } // setAsSeparator // xxy for remote label, do we set both directions (here??) private boolean frameSetAttribute(IAttributed target, final String attrName, final Object value, IUndoableEditSequence editSeq) { if (attrName.equals(WidgetAttributes.IS_SEPARATOR_ATTR)) { ChildWidget selectedItem = null; if (target instanceof PullDownItem) { AParentWidget pullDown = ((PullDownItem) target).getParent(); selectedItem = (ChildWidget) pullDown.getAttribute(WidgetAttributes.SELECTION_ATTR); } return setAsSeparator((IWidget) target, value, selectedItem, editSeq); } return DefaultCmd.setAttribute(target, demoStateMgr, attrName, value, interaction, editSeq); } private void insertDuplicateEdit(final IWidget duplicatedWidget, final ReadOnlyList<? extends IWidget> widgetCopies, final SimpleWidgetGroup group, final int index, final AParentWidget parent, final double startPosX, final double startPosY, IUndoableEditSequence editSeq) { DemoStateManager.IDesignUndoableEdit edit = new DemoStateManager.InvalidatingEdit(FrameEditorLID.Duplicate, demoStateMgr) { @Override public String getPresentationName() { return DUPLICATE_WIDGET; } @Override public void redo() { super.redo(); if (duplicatedWidget instanceof ChildWidget) { ChildWidget child = (ChildWidget) duplicatedWidget; AParentWidget curParent = child.getParent(); curParent.addItem(index, child); } else if (index >= 0) { group.add(index, duplicatedWidget); } else { group.add(duplicatedWidget); } model.addWidget(duplicatedWidget); stateMgr.noteWidgetsEdit(widgetCopies, this); if (parent != null) { DesignEditorCmd.repositionChildren(parent); } else if (group != null) { DesignEditorCmd.repositionChildren(group, startPosX, startPosY); } } @Override public void undo() { super.undo(); if (duplicatedWidget instanceof ChildWidget) { ChildWidget child = (ChildWidget) duplicatedWidget; AParentWidget curParent = child.getParent(); curParent.removeItem(child); } else if (duplicatedWidget.getParentGroup() != null) { SimpleWidgetGroup parentGroup = duplicatedWidget.getParentGroup(); parentGroup.remove(duplicatedWidget); } model.removeWidget(duplicatedWidget); stateMgr.noteWidgetsEdit(widgetCopies, this); if (parent != null) { DesignEditorCmd.repositionChildren(parent); } else if (group != null) { DesignEditorCmd.repositionChildren(group, startPosX, startPosY); } } }; editSeq.addEdit(edit); } /** * If group or parent is non-null, duplicate the widget within the given * group. If they are both null, the widget was dragged to empty space, so * give it a new group. */ private boolean insertDuplicateWidget(IWidget widget, SimpleWidgetGroup group, int index, AParentWidget parent, double moveByX, double moveByY) { Map<IWidget, IWidget> widgetCopies = new LinkedHashMap<IWidget, IWidget>(); double startPosX = 0.0; double startPosY = 0.0; DoubleSize newSize; if (parent != null) { newSize = getNewWidgetSize(parent); newSize.height *= getHeightFactor(widget, parent.getChildren()); } else if ((group == null) || (group.size() == 0)) { newSize = widget.getShape().getSize(); } else { DoublePoint startPos = group.get(0).getShape().getOrigin(); startPosX = startPos.x; startPosY = startPos.y; newSize = group.get(0).getShape().getSize(); if (widget instanceof ListItem) { newSize.height *= getHeightFactor(widget, group); } } widgetSituator.reset(widgetCopies, null); IWidget newWidget = null; if (parent != null) { if (widget instanceof ChildWidget) { newWidget = ((ChildWidget) widget).duplicate(parent, lookupFrameDuplicator, widgetSituator, index); newWidget.setWidgetSize(newSize.width, newSize.height); if (newWidget instanceof AParentWidget) { resizeChildren(newWidget); DesignEditorCmd.repositionChildren((AParentWidget) newWidget); } DesignEditorCmd.repositionChildren(parent); } } else if (group != null) { if (widget instanceof MenuHeader) { newWidget = ((MenuHeader) widget).duplicate(group, lookupFrameDuplicator, widgetSituator, index); } else if (widget instanceof ListItem) { newWidget = ((ListItem) widget).duplicate(group, lookupFrameDuplicator, index); } newWidget.setWidgetSize(newSize.width, newSize.height); resizeChildren(newWidget); widgetSituator.placeInContext(widget, newWidget); DesignEditorCmd.repositionChildren(group, startPosX, startPosY); } else { // Duplicating into space if ((widget instanceof MenuHeader) || (widget instanceof ListItem)) { SimpleWidgetGroup newGroup = null; if (widget instanceof MenuHeader) { newGroup = new SimpleWidgetGroup(SimpleWidgetGroup.HORIZONTAL); newWidget = ((MenuHeader) widget).duplicate(newGroup, lookupFrameDuplicator, widgetSituator); } else { // (widget instanceof ListItem) newGroup = new SimpleWidgetGroup(SimpleWidgetGroup.VERTICAL); newWidget = ((ListItem) widget).duplicate(newGroup, lookupFrameDuplicator); } group = newGroup; widgetSituator.placeInContext(widget, newWidget); newWidget.moveElement(moveByX, moveByY); group.setAttribute(WidgetAttributes.IS_RENDERED_ATTR, Boolean.valueOf(widget.isRendered())); } } widgetSituator.completeWork(); Collection<IWidget> duplicateWidgets = widgetCopies.values(); Iterator<IWidget> copies = duplicateWidgets.iterator(); while (copies.hasNext()) { IWidget widgetCopy = copies.next(); // Warning: it is important that each widget be added // to the frame *before* we make the next widget name // unique, or we can end up with non-unique names. makeWidgetNameUnique(widgetCopy); model.addWidget(widgetCopy); } SimpleWidgetGroup newGroup = (group != null) ? group : newWidget.getParentGroup(); Object rendered = newGroup.getAttribute(WidgetAttributes.IS_RENDERED_ATTR); boolean groupRendered = ((Boolean) rendered).booleanValue(); boolean widgetRendered = widget.isRendered(); if (groupRendered != widgetRendered) { newWidget.setRendered(groupRendered); } insertDuplicateEdit(newWidget, new ReadOnlyList<IWidget>(new ArrayList<IWidget>(duplicateWidgets)), group, index, parent, startPosX, startPosY, undoMgr); return true; } private void reorderGroupWidget(IWidget widget, double newWidth, double newHeight, int index, SimpleWidgetGroup prevGroup, SimpleWidgetGroup newGroup, double prevGroupStartX, double prevGroupStartY, double newGroupStartX, double newGroupStartY) { widget.setWidgetSize(newWidth, newHeight); resizeChildren(widget); prevGroup.remove(widget); newGroup.add(index, widget); DesignEditorCmd.repositionChildren(newGroup, newGroupStartX, newGroupStartY); if (prevGroup != newGroup) { DesignEditorCmd.repositionChildren(prevGroup, prevGroupStartX, prevGroupStartY); } } private boolean reorderWidget(final IWidget widget, final SimpleWidgetGroup newGroup, final int newIndex) { final SimpleWidgetGroup prevGroup = widget.getParentGroup(); final int prevIndex = prevGroup.indexOf(widget); int index = newIndex; if (prevGroup == newGroup) { if (prevIndex < newIndex) { index--; } if (index == prevIndex) { return true; } } DoublePoint prevGroupStartPos = prevGroup.get(0).getShape().getOrigin(); final double prevGroupStartX = prevGroupStartPos.x; final double prevGroupStartY = prevGroupStartPos.y; DoubleSize prevSize = widget.getShape().getSize(); final double prevWidth = prevSize.width; final double prevHeight = prevSize.height; DoubleRectangle newBds = newGroup.get(0).getEltBounds(); double heightFactor = (widget instanceof ListItem) ? getHeightFactor(widget, newGroup) : 1.0; final double newWidth = newBds.width; final double newHeight = newBds.height * heightFactor; final double newGroupStartX = newBds.x; final double newGroupStartY = newBds.y; Object rendered = newGroup.getAttribute(WidgetAttributes.IS_RENDERED_ATTR); final boolean groupRendered = ((Boolean) rendered).booleanValue(); final boolean widgetRendered = widget.isRendered(); reorderGroupWidget(widget, newWidth, newHeight, index, prevGroup, newGroup, prevGroupStartX, prevGroupStartY, newGroupStartX, newGroupStartY); if (widgetRendered != groupRendered) { widget.setRendered(groupRendered); } DemoStateManager.ObsoletingEdit edit = new DemoStateManager.ObsoletingEdit(FrameEditorLID.Reorder, demoStateMgr) { @Override public String getPresentationName() { return REORDER_WIDGET; } @Override public void redo() { super.redo(); int index = newIndex; if (prevGroup == newGroup) { if (prevIndex < newIndex) { index--; } } reorderGroupWidget(widget, newWidth, newHeight, index, prevGroup, newGroup, prevGroupStartX, prevGroupStartY, newGroupStartX, newGroupStartY); if (widgetRendered != groupRendered) { widget.setRendered(groupRendered); } noteEditCheckRegenerate(prevGroup, newGroup, this); } @Override public void undo() { super.undo(); reorderGroupWidget(widget, prevWidth, prevHeight, prevIndex, newGroup, prevGroup, newGroupStartX, newGroupStartY, prevGroupStartX, prevGroupStartY); if (widgetRendered != groupRendered) { widget.setRendered(widgetRendered); } noteEditCheckRegenerate(newGroup, prevGroup, this); } }; noteEditCheckRegenerate(prevGroup, newGroup, edit); undoMgr.addEdit(edit); return true; } private void reorderChildWidget(ChildWidget child, double newWidth, double newHeight, int index, AParentWidget prevParent, AParentWidget newParent) { child.setWidgetSize(newWidth, newHeight); resizeChildren(child); prevParent.removeItem(child); newParent.addItem(index, child); DesignEditorCmd.repositionChildren(prevParent); if (newParent != prevParent) { child.setParent(newParent); DesignEditorCmd.repositionChildren(newParent); } } private boolean reorderChildWidget(final ChildWidget widget, final SimpleWidgetGroup newGroup, final int newIndex, final AParentWidget newParent) { final SimpleWidgetGroup prevGroup = widget.getParentGroup(); final int prevIndex = prevGroup.indexOf(widget); int index = newIndex; if (prevGroup == newGroup) { if (prevIndex < newIndex) { index--; } if (index == prevIndex) { return true; } } final AParentWidget prevParent = widget.getParent(); DoubleSize prevSize = widget.getShape().getSize(); final double prevWidth = prevSize.width; final double prevHeight = prevSize.height; DoubleSize newSize = getNewWidgetSize(newParent); final double newWidth = newSize.width; final double newHeight = newSize.height * getHeightFactor(widget, newGroup); final boolean widgetRendered = widget.isRendered(); reorderChildWidget(widget, newWidth, newHeight, index, prevParent, newParent); Object rendered = widget.getParentGroup().getAttribute(WidgetAttributes.IS_RENDERED_ATTR); final boolean groupRendered = ((Boolean) rendered).booleanValue(); if (widgetRendered != groupRendered) { widget.setRendered(groupRendered); } DemoStateManager.ObsoletingEdit edit = new DemoStateManager.ObsoletingEdit(FrameEditorLID.Reorder, demoStateMgr) { private ChildWidget child = widget; @Override public String getPresentationName() { return REORDER_WIDGET; } @Override public void redo() { super.redo(); int index = newIndex; if (prevGroup == newGroup) { if (prevIndex < newIndex) { index--; } } reorderChildWidget(child, newWidth, newHeight, index, prevParent, newParent); if (widgetRendered != groupRendered) { widget.setRendered(groupRendered); } noteEditCheckRegenerate(prevParent.getChildren(), newParent.getChildren(), this); } @Override public void undo() { super.undo(); reorderChildWidget(child, prevWidth, prevHeight, prevIndex, newParent, prevParent); if (widgetRendered != groupRendered) { widget.setRendered(widgetRendered); } noteEditCheckRegenerate(newParent.getChildren(), prevParent.getChildren(), this); } }; noteEditCheckRegenerate(prevParent.getChildren(), newParent.getChildren(), edit); undoMgr.addEdit(edit); if (CogToolPref.REGENERATE_AUTOMATICALLY.getBoolean()) { DemoScriptCmd.regenerateDesignScripts(project, design, interaction); } return true; } private double getHeightFactor(IWidget widget, SimpleWidgetGroup newGroup) { double heightFactor = 1.0; IWidget newWidget = null; if (newGroup != null) { if (newGroup.size() > 0) { newWidget = newGroup.get(0); } if (newWidget != null){ Object isSep = newWidget.getAttribute(WidgetAttributes.IS_SEPARATOR_ATTR); if (NullSafe.equals(WidgetAttributes.IS_SEPARATOR, isSep)) { heightFactor *= FrameEditorUI.SEPARATOR_RATIO; } } } Object isSep = widget.getAttribute(WidgetAttributes.IS_SEPARATOR_ATTR); if (NullSafe.equals(WidgetAttributes.IS_SEPARATOR, isSep)) { heightFactor *= 1.0 / FrameEditorUI.SEPARATOR_RATIO; } return heightFactor; } private void resizeChildren(IWidget widget) { if (widget instanceof AParentWidget) { AParentWidget parent = (AParentWidget) widget; DoubleSize size = parent.getShape().getSize(); if (parent instanceof MenuHeader) { size.width *= FrameEditorUI.MENU_ITEM_RATIO; } int numItems = parent.itemCount(); for (int i = 0; i < numItems; i++) { ChildWidget child = parent.getItem(i); double h = size.height; Object isSep = child.getAttribute(WidgetAttributes.IS_SEPARATOR_ATTR); if (NullSafe.equals(WidgetAttributes.IS_SEPARATOR, isSep)) { h /= FrameEditorUI.SEPARATOR_RATIO; } child.setWidgetSize(size.width, h); resizeChildren(child); } } } private DoubleSize getNewWidgetSize(AParentWidget newParent) { if (newParent.hasChildren()) { return newParent.getItem(0).getShape().getSize(); } DoubleSize size = newParent.getShape().getSize(); if (newParent instanceof MenuHeader) { size.width *= 1.3; } return size; } /** * Create a ListenerAction to handle creating a new Widget. * */ private IListenerAction createNewWidgetAction() { return new AListenerAction() { public boolean performAction(Object prms) { // TODO: Should we provide more input to this default widget? // Dialog box with option? // Current/last palette setting // TODO: Bonnie wanted to have new widgets show up under the // mouse. To do that we need to do something like // getCursorLocation() (from display) // offset based on the (0,0) of the window // Might want to get that based on GetCursorControl(). // If working with the control, you need to walk the tree to // get the actual offset from 0,0, in the display. // // Alternatively, in specialize, get the mouse pointer position // from mousestate (but that would require tracking the mouse // state -- on hover at least). // Instantiate the appropriate widget. If the class was passed // a prms, use that to dictate what to do. Widget widget = null; CompoundUndoableEdit editSequence = new CompoundUndoableEdit(NEW_WIDGET, FrameEditorLID.NewWidget); if (prms instanceof FrameEditorUI.NewWidgetParameters) { FrameEditorUI.NewWidgetParameters nwp = (FrameEditorUI.NewWidgetParameters) prms; // If parent is non-null, the new widget will be a child // widget if (nwp.parent != null) { if (nwp.type == WidgetType.MenuItem){ // Create menu item; may become a submenu through // user interaction later widget = new MenuItem((AMenuWidget) nwp.parent, nwp.bounds, nwp.widgetTitle); } else if (nwp.type == WidgetType.PullDownItem) { widget = new PullDownItem((PullDownHeader) nwp.parent, nwp.bounds, nwp.widgetTitle); boolean rendered = nwp.parent.isRendered(); SimpleWidgetGroup group = widget.getParentGroup(); group.setAttribute(WidgetAttributes.IS_RENDERED_ATTR, Boolean.valueOf(rendered)); } } // If a parent group is specified, the new widget will be // a menu header, list item, or radio button; these are the // only widgets that are created as part of a free-standing // widget group else if (nwp.parentGroup != null) { if (nwp.type == WidgetType.Menu) { widget = new MenuHeader(nwp.parentGroup, nwp.bounds, nwp.widgetTitle); } else if (nwp.type == WidgetType.ListBoxItem) { widget = new ListItem(nwp.parentGroup, nwp.bounds, nwp.widgetTitle); } else if (nwp.type == WidgetType.Radio) { widget = new RadioButton((RadioButtonGroup) nwp.parentGroup, nwp.bounds, nwp.widgetTitle); } else if (nwp.type == WidgetType.Check) { widget = new CheckBox((GridButtonGroup) nwp.parentGroup, nwp.bounds, nwp.widgetTitle); } } else { widget = createWidget(nwp.type, nwp.bounds, nwp.widgetTitle, nwp.isAutomatic); } if (nwp.isSeparator) { frameSetAttribute(widget, WidgetAttributes.IS_SEPARATOR_ATTR, WidgetAttributes.IS_SEPARATOR, editSequence); } } // if (widget.getWidgetType() == WidgetType.TextBox) { // widget.setAttribute(WidgetAttributes.IS_STANDARD_ATTR, // WidgetAttributes.IS_CUSTOM); // } // Auto-generate a unique name for the widget widget.setName(generateUniqueWidgetName()); // Build the widget: check for uniqueness and add to the frame boolean result = addCreatedWidget(widget, editSequence); editSequence.end(); undoMgr.addEdit(editSequence); return result; } }; } private IListenerAction createNewWidgetExplanationAction() { return new AListenerAction() { public boolean performAction(Object prms) { interaction.newWidgetExplanation(); return false; } }; } private Widget createWidget(WidgetType defaultType, DoubleRectangle bounds, String widgetTitle, boolean isStandard) { Widget widget; // The given parameter bounds must be valid, thus // the user specified actual bounds interactively if (isStandard) { if (defaultType == WidgetType.Menu) { SimpleWidgetGroup newMenuHeaderGroup = new SimpleWidgetGroup(SimpleWidgetGroup.HORIZONTAL); widget = new MenuHeader(newMenuHeaderGroup, bounds, widgetTitle); } else if (defaultType == WidgetType.PullDownList) { widget = new PullDownHeader(bounds, widgetTitle); } else if (defaultType == WidgetType.ContextMenu) { widget = new ContextMenu(bounds, widgetTitle); // The default value for CONTEXT_MENU_ACTION_ATTR // is RIGHT_CLICK; must change to TAP_HOLD if the devices // contain a Touchscreen but not a Mouse Set<DeviceType> deviceTypes = design.getDeviceTypes(); if (deviceTypes.contains(DeviceType.Touchscreen) && ! deviceTypes.contains(DeviceType.Mouse)) { widget.setAttribute(WidgetAttributes.CONTEXT_MENU_ACTION_ATTR, WidgetAttributes.TAP_HOLD); } } else if (defaultType == WidgetType.ListBoxItem) { SimpleWidgetGroup newListItemGroup = new SimpleWidgetGroup(SimpleWidgetGroup.VERTICAL); widget = new ListItem(newListItemGroup, bounds, widgetTitle); newListItemGroup.setAttribute(WidgetAttributes.FIRST_VISIBLE_ATTR, widget); } else if (defaultType == WidgetType.Radio) { RadioButtonGroup newRadioGroup = new RadioButtonGroup(); widget = new RadioButton(newRadioGroup, bounds, widgetTitle); } else if (defaultType == WidgetType.Check) { GridButtonGroup newCheckGroup = new GridButtonGroup(); widget = new CheckBox(newCheckGroup, bounds, widgetTitle); } else { // Create new widget in specified location // Note: could be a child widget; // if so, the user is managing the hierarchy! widget = new Widget(bounds, defaultType); } widget.setAttribute(WidgetAttributes.IS_STANDARD_ATTR, WidgetAttributes.IS_STANDARD); } else { // Create new widget in specified location // Note: could be a child widget; // if so, the user is managing the hierarchy! widget = new Widget(bounds, defaultType); widget.setAttribute(WidgetAttributes.IS_STANDARD_ATTR, WidgetAttributes.IS_CUSTOM); } return widget; } /** * Create a ListenerAction to handle setting frame background image * */ private IListenerAction createSetBackgroundImageAction() { return new AListenerAction() { public boolean performAction(Object prms) { // Pull up interaction to select a file String imageURL = interaction.selectImageFile(); // Check if the dialog was cancelled if (imageURL != null) { try { // Set the background to new image setBackgroundImage(GraphicsUtil.loadImageFromFile(imageURL), imageURL); return true; } catch (IOException e) { interaction.protestUnreadableFile(); } } return false; } }; } private IListenerAction createPasteBackgroundImageAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.PasteBackgroundImageParms.class; } public boolean performAction(Object prms) { FrameEditorUI.PasteBackgroundImageParms p = (FrameEditorUI.PasteBackgroundImageParms) prms; if (p.selection.getWidgetSelectionCount() > 0) { setWidgetImages(p.selection.getSelectedWidgetsIterator(), p.imageData, WidgetAttributes.NO_IMAGE); } else { // Set background image with clipboard data setBackgroundImage(p.imageData, WidgetAttributes.NO_IMAGE); } return true; } }; } private void changeTitleProperty(ListenerIdentifier lid, final String presentation, final IWidget widget, final String newTitle, boolean isSeparator, IUndoableEditSequence editSequence) { final String oldTitle = widget.getTitle(); if (! oldTitle.equals(newTitle)) { if (isSeparator && ((widget instanceof MenuItem) || (widget instanceof ListItem) || (widget instanceof PullDownItem))) { frameSetAttribute(widget, WidgetAttributes.IS_SEPARATOR_ATTR, WidgetAttributes.IS_SEPARATOR, editSequence); } widget.setTitle(newTitle); DemoStateManager.ObsoletingEdit edit = new DemoStateManager.ObsoletingEdit(lid, demoStateMgr) { @Override public String getPresentationName() { return presentation; } @Override public void redo() { // Redo the set title super.redo(); widget.setTitle(newTitle); noteEditCheckRegenerate(widget, this); } @Override public void undo() { // Go back to old title super.undo(); widget.setTitle(oldTitle); noteEditCheckRegenerate(widget, this); } }; noteEditCheckRegenerate(widget, edit); editSequence.addEdit(edit); } } // changeTitleProperty private IListenerAction createChangeTitlePropertyAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.ActionStringParameters.class; } public boolean performAction(Object prms) { FrameEditorUI.ActionStringParameters p = (FrameEditorUI.ActionStringParameters) prms; // While the UI should suppress // multiple selection for setting // titles, the selection supports it Iterator<IWidget> selected = p.selection.getSelectedWidgetsIterator(); CompoundUndoableEdit editSequence = new CompoundUndoableEdit(CHG_DISPLAYED_LABEL, FrameEditorLID.ChangeTitleProperty); String newTitle = p.newString; // Loop through each item and set the title while (selected.hasNext()) { IWidget widget = selected.next(); changeTitleProperty(FrameEditorLID.ChangeTitleProperty, CHG_DISPLAYED_LABEL, widget, newTitle, p.isSeparator, editSequence); } editSequence.end(); // Don't add empty edits! if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } }; } // createChangeTitlePropertyAction private void changeAuxTextProperty(ListenerIdentifier lid, final String presentation, FrameElement elt, final String newText, IUndoableEditSequence editSequence) { if (elt instanceof IWidget) { final IWidget widget = (IWidget) elt; final String oldText = widget.getAuxiliaryText(); if (! oldText.equals(newText)) { widget.setAuxiliaryText(newText); DemoStateManager.ObsoletingEdit edit = new DemoStateManager.ObsoletingEdit(lid, demoStateMgr) { @Override public String getPresentationName() { return presentation; } @Override public void redo() { // Redo the set auxiliary text super.redo(); widget.setAuxiliaryText(newText); noteEditCheckRegenerate(widget, this); } @Override public void undo() { // Go back to old auxiliary text super.undo(); widget.setAuxiliaryText(oldText); noteEditCheckRegenerate(widget, this); } }; noteEditCheckRegenerate(widget, edit); editSequence.addEdit(edit); } } else if (elt instanceof FrameElementGroup) { final FrameElementGroup eltGroup = (FrameElementGroup) elt; final String oldText = eltGroup.getAuxiliaryText(); if (! oldText.equals(newText)) { eltGroup.setAuxiliaryText(newText); IUndoableEdit edit = new DemoStateManager.ObsoletingEdit(lid, demoStateMgr) { @Override public String getPresentationName() { return presentation; } @Override public void redo() { // Redo the set auxiliary text super.redo(); eltGroup.setAuxiliaryText(newText); } @Override public void undo() { // Go back to old auxiliary text super.undo(); eltGroup.setAuxiliaryText(oldText); } }; editSequence.addEdit(edit); } } } // changeAuxTextProperty private IListenerAction createChangeAuxTextPropertyAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.ActionStringParameters.class; } public boolean performAction(Object prms) { FrameEditorUI.ActionStringParameters p = (FrameEditorUI.ActionStringParameters) prms; // While the UI should suppress // multiple selection for setting // titles, the selection supports it Iterator<FrameElement> selected = p.selection.getSelectedElementsIterator(); CompoundUndoableEdit editSequence = new CompoundUndoableEdit(CHG_AUX_TEXT, FrameEditorLID.ChangeAuxTextProperty); String newTitle = p.newString; // Loop through each item and set the title while (selected.hasNext()) { FrameElement elt = selected.next(); changeAuxTextProperty(FrameEditorLID.ChangeAuxTextProperty, CHG_AUX_TEXT, elt, newTitle, editSequence); } editSequence.end(); // Don't add empty edits! if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } }; } // createChangeAuxTextPropertyAction /** * Create a ListenerAction which will handle capturing a background image. * @return */ private IListenerAction captureImageAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { CompoundUndoableEdit editSequence = new CompoundUndoableEdit(CAPTURE_BKG_IMG, FrameEditorLID.CaptureImageProperty); // Get the selection. FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; Iterator<IWidget> selected = selection.getSelectedWidgetsIterator(); // Iterate over every selected widget while (selected.hasNext()) { final IWidget w = selected.next(); DoubleRectangle bounds = w.getEltBounds(); // Get the image from the background, and crop to shape final byte[] bg = GraphicsUtil.cropImage(model.getBackgroundImage(), bounds.x, bounds.y, bounds.width, bounds.height); // Get the old image, could be null. final byte[] old = w.getImage(); final String previousImagePath = (String) w.getAttribute(WidgetAttributes.IMAGE_PATH_ATTR); w.setImage(bg); w.setAttribute(WidgetAttributes.IMAGE_PATH_ATTR, WidgetAttributes.NO_IMAGE); editSequence.addEdit(new AUndoableEdit(FrameEditorLID.CaptureImageProperty) { @Override public String getPresentationName() { return CAPTURE_BKG_IMG; } @Override public void redo() { super.redo(); w.setImage(bg); w.setAttribute(WidgetAttributes.IMAGE_PATH_ATTR, WidgetAttributes.NO_IMAGE); } @Override public void undo() { super.undo(); w.setImage(old); w.setAttribute(WidgetAttributes.IMAGE_PATH_ATTR, previousImagePath); } }); } editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } }; } /** * Create a listener for changing the shape of a widget. * * @return */ private IListenerAction createChangeShapeAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.ShapeChangeParameters.class; } public boolean performAction(Object prms) { // Get the parameters to change the shape into final FrameEditorUI.ShapeChangeParameters p = (FrameEditorUI.ShapeChangeParameters) prms; Iterator<IWidget> selected = p.selection.getSelectedWidgetsIterator(); CompoundUndoableEdit editSequence = new CompoundUndoableEdit(CHG_WIDGET_SHAPE, FrameEditorLID.ChangeShapeProperty); // Loop through all selected widgets. while (selected.hasNext()) { final IWidget w = selected.next(); final ShapeType oldShapeType = w.getShape().getShapeType(); final ShapeType newShapeType = p.newShapeType; // Don't make a non-changing edit! if (! oldShapeType.equals(newShapeType)) { w.setShapeType(newShapeType); DemoStateManager.ObsoletingEdit edit = new DemoStateManager.ObsoletingEdit(FrameEditorLID.ChangeShapeProperty, demoStateMgr) { @Override public String getPresentationName() { return CHG_WIDGET_SHAPE; } @Override public void redo() { super.redo(); w.setShapeType(p.newShapeType); noteEditCheckRegenerate(w, this); } @Override public void undo() { super.undo(); w.setShapeType(oldShapeType); noteEditCheckRegenerate(w, this); } }; noteEditCheckRegenerate(w, edit); editSequence.addEdit(edit); } } editSequence.end(); // Don't add empty edits! if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } }; } private void changeWidgetType(ListenerIdentifier lid, final String presentation, final IWidget widget, final WidgetType newWidgetType, IUndoableEditSequence editSequence) { final WidgetType oldWidgetType = widget.getWidgetType(); // Don't make a non-changing edit! if (! oldWidgetType.equals(newWidgetType)) { // Set the new widget type widget.setWidgetType(newWidgetType); DemoStateManager.ObsoletingEdit edit = new DemoStateManager.ObsoletingEdit(lid, demoStateMgr) { @Override public String getPresentationName() { return presentation; } @Override public void redo() { super.redo(); widget.setWidgetType(newWidgetType); noteEditCheckRegenerate(widget, this); } @Override public void undo() { super.undo(); widget.setWidgetType(oldWidgetType); noteEditCheckRegenerate(widget, this); } }; noteEditCheckRegenerate(widget, edit); editSequence.addEdit(edit); } } /** * Create a listener action for changing the type. * Changing the type can be a dangerous option. * If you change the type and thus change what kind of * transition you can use, it may play havoc on scripts, and design view. * @return */ private IListenerAction createChangeTypeAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.TypeChangeParameters.class; } public boolean performAction(Object prms) { FrameEditorUI.TypeChangeParameters p = (FrameEditorUI.TypeChangeParameters) prms; Iterator<IWidget> selected = p.selection.getSelectedWidgetsIterator(); CompoundUndoableEdit editSequence = new CompoundUndoableEdit(CHG_WIDGET_TYPE, FrameEditorLID.ChangeTypeProperty); while (selected.hasNext()) { IWidget widget = selected.next(); // Check if the widget types match // TODO: deal with the following // WidgetType.compatibleTransitions(oldWidgetType, // p.newWidgetType) // if it returns false, show interaction.. // auto delete transition? changeWidgetType(FrameEditorLID.ChangeTypeProperty, CHG_WIDGET_TYPE, widget, p.newWidgetType, editSequence); } editSequence.end(); // Don't add empty edits! if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } }; } private IListenerAction createSetRenderSkinAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.SetRenderSkinParameters.class; } public boolean performAction(Object prms) { FrameEditorUI.SetRenderSkinParameters p = (FrameEditorUI.SetRenderSkinParameters) prms; // Iterate through selected objects. Iterator<IWidget> selected = p.selection.getSelectedWidgetsIterator(); CompoundUndoableEdit editSeq = new CompoundUndoableEdit(CHG_WIDGET_RENDERED, FrameEditorLID.SetRenderSkin); while (selected.hasNext()) { IWidget w = selected.next(); if (w instanceof TraversableWidget) { AParentWidget parent = null; if (w instanceof MenuItem) { parent = ((MenuItem) w).getTopHeader(); if (parent == null) { // parent is a context menu parent = ((MenuItem) w).getParent(); } } else if (w instanceof ChildWidget) { parent = ((ChildWidget) w).getParent(); } else if (w instanceof AParentWidget) { parent = (AParentWidget) w; } if (parent != null) { SimpleWidgetGroup group = parent.getParentGroup(); if (group != null) { //menu header renderGroup(group, p.rendered, parent.isRendered(), editSeq); } else { //pull down header renderWidget(parent, p.rendered, parent.isRendered(), editSeq); renderChildren(parent, p.rendered, parent.isRendered(), editSeq); } } else if (w.getParentGroup() != null) { //list box item or radio button renderGroup(w.getParentGroup(), p.rendered, w.isRendered(), editSeq); } } else { renderWidget(w, p.rendered, w.isRendered(), editSeq); } } editSeq.end(); // Only add this edit if it is significant if (editSeq.isSignificant()) { undoMgr.addEdit(editSeq); } return true; } }; } // createSetRenderSkinAction public static void renderWidget(final IWidget w, final boolean rendered, final boolean oldRendered, CompoundUndoableEdit edit) { w.setRendered(rendered); edit.addEdit(new AUndoableEdit(FrameEditorLID.SetRenderSkin) { @Override public String getPresentationName() { return CHG_WIDGET_RENDERED; } @Override public void redo() { super.redo(); w.setRendered(rendered); } @Override public void undo() { super.undo(); w.setRendered(oldRendered); } }); } public static void renderUnRenderAll(Design d, boolean render, CogToolLID lid, UndoManager mgr) { CompoundUndoableEdit edits = new CompoundUndoableEdit((render ? RENDER_ALL : UN_RENDER), lid); for (Frame f : d.getFrames()) { for (IWidget w : f.getWidgets()) { renderWidget(w, render, w.isRendered(), edits); } } edits.end(); if (edits.isSignificant()) { mgr.addEdit(edits); } } private void renderGroup(SimpleWidgetGroup group, boolean rendered, boolean oldRendered, CompoundUndoableEdit edit) { Iterator<IWidget> children = group.iterator(); while (children.hasNext()) { IWidget w = children.next(); if (w instanceof AParentWidget) { renderChildren((AParentWidget) w, rendered, oldRendered, edit); } renderWidget(w, rendered, oldRendered, edit); } } private void renderChildren(AParentWidget parent, boolean rendered, boolean oldRendered, CompoundUndoableEdit edit) { if (parent.hasChildren()) { Iterator<IWidget> children = parent.getChildren().iterator(); while (children.hasNext()) { IWidget w = children.next(); if (w instanceof AParentWidget) { renderChildren((AParentWidget) w, rendered, oldRendered, edit); } renderWidget(w, rendered, oldRendered, edit); } } } private IListenerAction createRenderAllAction(final boolean render, final CogToolLID lid) { return new AListenerAction() { public boolean performAction(Object prms) { renderUnRenderAll(design, render, lid, FrameEditorController.this.undoMgr); return true; } }; } /** * Set up the COPY widget code. * Uses the persistence store to generate XML for the copy action. * @return */ private IListenerAction createCopyWidgetAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState seln = (FrameEditorSelectionState) prms; // If selection is non-zero, copy frame elements int elementCount = seln.getElementSelectionCount(); if (elementCount > 0) { copyElements(seln, DesignEditorCmd.SAVE_TO_CLIPBOARD); if (elementCount == 1) { interaction.setStatusMessage(WIDGET_COPIED); } else { interaction.setStatusMessage(WIDGETS_COPIED); } return true; } // tell user to select something. interaction.protestNoSelection(); return false; } }; } /** * Actually perform the copy widgets operation with the selection * Use persistence to generate the needed XML. * Saves either to the clipboard or to the given Design's Frame template. */ private void copyElements(FrameEditorSelectionState seln, boolean saveToClipboard) { // Sort by level so pasted widgets share level relationships of // the originals Set<FrameElement> sortedSelection = getSelectedElements(seln, elementLevelComparator); DesignEditorCmd.copyElements(design, seln.getSelectedIFrameElements(), sortedSelection.iterator(), saveToClipboard); } /** * Set up cut action, tests to ensure a cut is valid, and then * calls cut method. * @return */ private IListenerAction createCutWidgetAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState seln = (FrameEditorSelectionState) prms; // if non zero selected items copy them, then delete. if (seln.getElementSelectionCount() > 0) { // Copy the widgets, then delete them to perform a cut copyElements(seln, DesignEditorCmd.SAVE_TO_CLIPBOARD); return deleteElements(seln); } // Tell the user nothing was selected interaction.protestNoSelection(); return false; } }; } private IListenerAction createInitiateRenameAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; int selectedWidgetCount = selection.getWidgetSelectionCount(); if (selectedWidgetCount == 1) { IWidget w = selection.getSelectedIWidgets()[0]; ui.initiateWidgetRename(w); return true; } interaction.protestNoSelection(); return false; } }; } private IListenerAction createInitiateRelabelAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; int selectedWidgetCount = selection.getWidgetSelectionCount(); if (selectedWidgetCount == 1) { IWidget w = selection.getSelectedIWidgets()[0]; ui.initiateWidgetRetitle(w); return true; } interaction.protestNoSelection(); return false; } }; } /** * Ensure that the new name specified is UNIQUE. * Do this by adding a [X] on the end. mostly used in copy and paste. * * See bug report #133 * * @param widget */ private void makeWidgetNameUnique(IWidget widget) { widget.setName(NamedObjectUtil.makeNameUnique(widget.getName(), model.getWidgets())); } private void makeEltGroupNameUnique(FrameElementGroup eltGroup, String initialName) { eltGroup.setName(NamedObjectUtil.makeNameUnique(initialName, model.getEltGroups())); } private void makeEltGroupNameUnique(FrameElementGroup eltGroup) { makeEltGroupNameUnique(eltGroup, eltGroup.getName()); } private void assignUniqueEltGroupName(FrameElementGroup eltGroup) { makeEltGroupNameUnique(eltGroup, DEFAULT_GROUP_PREFIX + " [1]"); } /** * Ensure that the new name specified is UNIQUE. * Do this by adding a [X] on the end. mostly used in copy and paste. * * See bug report #133 * * @param widget */ private void makeFrameNameUnique(Frame frame) { frame.setName(NamedObjectUtil.makeNameUnique(frame.getName(), model.getDesign().getFrames())); } private void addChildWidgets(SimpleWidgetGroup widgetGroup, IUndoableEditSequence editSeq) { if (widgetGroup != null) { Iterator<IWidget> children = widgetGroup.iterator(); while (children.hasNext()) { IWidget child = children.next(); makeWidgetNameUnique(child); editSeq.addEdit(addWidget(child)); if (child instanceof MenuItem) { MenuItem item = (MenuItem) child; if (item.isSubmenu()) { addChildWidgets(item.getChildren(), editSeq); } } } } } /** * Record the Widgets in the clipboard as the template to use * when creating new Frames. */ private IListenerAction createSetFrameTemplateAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState seln = (FrameEditorSelectionState) prms; // If selection is non zero, copy widgets if (seln.getElementSelectionCount() > 0) { copyElements(seln, DesignEditorCmd.SAVE_TO_TEMPLATE); interaction.setStatusMessage(templateCreated); return true; } // tell user to select something. interaction.setStatusMessage(noTemplateWidgets); interaction.protestNoSelection(); return false; } }; } // createSetFrameTemplateAction /** * The paste action for inserting copied information. * Currently no checks are made to ensure that the paste is valid XML. * @return */ private IListenerAction createPasteAction() { return new AListenerAction() { public boolean performAction(Object prms) { try { if (CogToolClipboard.hasCogToolObjects()) { // Get the XML text from the clipboard Collection<Object> objects = CogToolClipboard.fetchCogToolObjects(); if ((objects != null) && (objects.size() > 0)) { CompoundUndoableEdit editSequence = new CompoundUndoableEdit(PASTE, FrameEditorLID.Paste); int numPasted = DesignEditorCmd.pasteElements(design, model, objects, demoStateMgr, editSequence); if (numPasted > 0) { editSequence.end(); undoMgr.addEdit(editSequence); interaction.setStatusMessage(numPasted + " " + pasteComplete); } else { interaction.setStatusMessage(nothingPasted); } } return true; } 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 false; } }; } // createPasteAction private IListenerAction createCopyImageAsBkgAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.CopyImageAsBackgroundParms.class; } public boolean performAction(Object actionParms) { FrameEditorUI.CopyImageAsBackgroundParms prms = (FrameEditorUI.CopyImageAsBackgroundParms) actionParms; if (prms.selectedWidget != null) { setWidgetImage(prms.selectedWidget, prms.imageData, WidgetAttributes.NO_IMAGE, FrameEditorLID.CopyImageAsBackground, SET_WIDGET_IMG, undoMgr); } else { // Set background image with clipboard data setBackgroundImage(prms.imageData, WidgetAttributes.NO_IMAGE); } return true; } }; } /** * Returns a new anonymous IListenerAction for widget color changes. * @return */ private IListenerAction newSetWidgetColorAction() { return new AListenerAction() { public boolean performAction(Object prms) { final int oldColor = model.getWidgetColor(); // Ask the user to get the color. Integer newColorChoice = interaction.selectColor(oldColor, SELECT_WIDGET_COLOR); // if the color is not the flag value, set it and add the undo. if (newColorChoice != null) { final int newColor = newColorChoice.intValue(); model.setWidgetColor(newColor); undoMgr.addEdit(new AUndoableEdit(FrameEditorLID.SetWidgetColor) { @Override public String getPresentationName() { return SET_WIDGET_COLOR; } @Override public void redo() { super.redo(); model.setWidgetColor(newColor); } @Override public void undo() { super.undo(); model.setWidgetColor(oldColor); } }); return true; } return false; } }; } /** * Sets the background image for the frame * * @param imageData the new image, or null if none */ private void setBackgroundImage(final byte[] imageData, final String imagePath) { try { // compute the size of the new image. final DoubleRectangle imageSize = GraphicsUtil.getImageBounds(imageData); // Get existing image final byte[] previousImageData = model.getBackgroundImage(); final DoubleRectangle previmageSize = model.getBackgroundBounds(); final String oldPath = (String) model.getAttribute(WidgetAttributes.IMAGE_PATH_ATTR); // Perform operation model.setBackgroundImage(imageData, imageSize); model.setAttribute(WidgetAttributes.IMAGE_PATH_ATTR, imagePath); CogToolLID lid = (imageData == null) ? FrameEditorLID.RemoveBackgroundImage : FrameEditorLID.SetBackgroundImage; // Add the undo edit IUndoableEdit edit = new AUndoableEdit(lid) { @Override public String getPresentationName() { return (imageData == null) ? REMOVE_BKG_IMG : SET_BKG_IMG; } @Override public void redo() { super.redo(); try { model.setBackgroundImage(imageData, imageSize); model.setAttribute(WidgetAttributes.IMAGE_PATH_ATTR, imagePath); } catch (GraphicsUtil.ImageException ex) { throw new RcvrImageException("Redo set background image failed", ex); } } @Override public void undo() { super.undo(); try { model.setBackgroundImage(previousImageData, previmageSize); model.setAttribute(WidgetAttributes.IMAGE_PATH_ATTR, oldPath); } catch (GraphicsUtil.ImageException ex) { throw new RcvrImageException("Undo set background image failed", ex); } } }; undoMgr.addEdit(edit); } catch (GraphicsUtil.ImageException e) { interaction.protestInvalidImageFile(); } } /** * Add a widget to the frame. * Returns an UndoableEdit which can later be undone. * @param w * @return */ private IUndoableEdit addWidget(IWidget widget) { model.addWidget(widget); IDesignUndoableEdit edit = DesignEditorCmd.addWidgetUndoableEdit(model, widget, demoStateMgr); demoStateMgr.noteWidgetEdit(widget, edit); return edit; } /** * Take a newly created widget and add it to the model, and then select it. * Prompts the user if the widget's name matches that of an existing widget. * * Currently this method is called in a single place, and the provided name * is guaranteed unique. * * @param w new widget */ private boolean addCreatedWidget(final IWidget w, IUndoableEditSequence seq) { // If we have a name collision, we prompt the user to enter a new name // until we find one that does not collide. String name = w.getName(); if (model.isWidgetNameTaken(name)) { do { // Ask until the new widget name is specified. name = interaction.askWidgetName(name, generateUniqueWidgetName()); if (name == null) { return false; } } while (model.isWidgetNameTaken(name)); // Once the name is unique, set it. w.setName(name); } // Add the new widget to the undo system and to the model seq.addEdit(addWidget(w)); return true; } /** * Delete currently selected widgets. */ protected boolean deleteElements(FrameEditorSelectionState selection) { String editLabel = (selection.getElementSelectionCount() > 1) ? DELETE_WIDGETS : DELETE_WIDGET; CompoundUndoableEdit editSequence = new CompoundUndoableEdit(editLabel, FrameEditorLID.Delete); FrameElement[] selectedElts = selection.getSelectedIFrameElements(); // Check to see if any items are selected if (selectedElts.length > 0) { if (interaction.confirmDeleteElements(selectedElts)) { Set<IWidget> frameWidgets = model.getWidgets(); Set<FrameElementGroup> frameAssocs = model.getEltGroups(); for (FrameElement selectedElt : selectedElts) { if (selectedElt instanceof FrameElementGroup) { // We need to check that the current selected group // hasn't already been deleted because it is a // component of another group if (frameAssocs.contains(selectedElt)) { deleteFrameEltGroup((FrameElementGroup) selectedElt, null, editSequence); } } else if (selectedElt instanceof IWidget) { // We need to check that the current selected widget // hasn't already been deleted because it is a component // of another widget (e.g., menu item part of a menu) if (frameWidgets.contains(selectedElt)) { deleteWidget((IWidget) selectedElt, true, null, editSequence); } } } editSequence.end(); undoMgr.addEdit(editSequence); return true; } } else { interaction.protestNoSelection(); } return false; } // deleteWidgets /** * This class is just for delete widget edits. It's not an anonymous class * due to the two abstract functions, undoHelper and redoHelper. * @author cmj */ protected class DeleteWidgetUndoableEdit extends DemoStateManager.InvalidatingEdit { protected IWidget widget; protected SimpleWidgetGroup parentGroup; protected int atIndex; protected double deltaX; protected double deltaY; public DeleteWidgetUndoableEdit(IWidget w, SimpleWidgetGroup pg, int index, double dx, double dy) { super(FrameEditorLID.Delete, demoStateMgr); widget = w; parentGroup = pg; atIndex = index; deltaX = dx; deltaY = dy; } @Override public String getPresentationName() { return DELETE_WIDGET; } /** * This function should be overridden to remove the widget from its * parent group. */ protected void redoHelper() { /* empty */ } /** * This function should be overridden to add the widget to its * parent group, at the appropriate index level. */ protected void undoHelper() { /* empty */ } @Override public void redo() { super.redo(); if (parentGroup != null) { int numWidgets = parentGroup.size(); for (int i = atIndex + 1; i < numWidgets; i++) { IWidget curWidget = parentGroup.get(i); curWidget.moveElement(deltaX, deltaY); } redoHelper(); } model.removeWidget(widget); stateMgr.noteWidgetEdit(widget, this); } @Override public void undo() { super.undo(); if (parentGroup != null) { undoHelper(); int numWidgets = parentGroup.size(); for (int i = atIndex + 1; i < numWidgets; i++) { IWidget curWidget = parentGroup.get(i); curWidget.moveElement(- deltaX, - deltaY); } } model.addWidget(widget); stateMgr.noteWidgetEdit(widget, this); } } private void deleteGroupMember(FrameElement elt, FrameElementGroup fromGroup, IUndoableEditSequence editSequence) { if (elt instanceof IWidget) { deleteWidget((IWidget) elt, false, fromGroup, editSequence); } else if (elt instanceof FrameElementGroup) { deleteFrameEltGroup((FrameElementGroup) elt, fromGroup, editSequence); } else if (elt instanceof SimpleWidgetGroup) { SimpleWidgetGroup parentGroup = (SimpleWidgetGroup) elt; while (parentGroup.size() > 0) { IWidget child = parentGroup.get(0); // If the widget group is itself a remote label owner, // its remote label will be deleted in the first call here. deleteWidget(child, false, fromGroup, editSequence); } } } private void deleteFrameEltGroup(final FrameElementGroup grp, FrameElementGroup fromGroup, IUndoableEditSequence editSequence) { // Check if this element group is a remote label owner; if so, delete // the remote label as well deleteRemoteLabel(grp, editSequence); Iterator<FrameElement> members = grp.iterator(); while (members.hasNext()) { deleteGroupMember(members.next(), grp, editSequence); } model.removeEltGroup(grp); removeRootElement(DELETE_GROUP, grp, fromGroup, editSequence); DemoStateManager.IDesignUndoableEdit edit = new DemoStateManager.InvalidatingEdit(FrameEditorLID.Delete, demoStateMgr) { @Override public String getPresentationName() { return DELETE_GROUP; } @Override public void redo() { super.redo(); model.removeEltGroup(grp); } @Override public void undo() { super.undo(); model.addEltGroup(grp); } }; editSequence.addEdit(edit); } private void removeRootElement(final String presentationName, final FrameElement rootElt, final FrameElementGroup fromGrp, IUndoableEditSequence editSequence) { Set<FrameElementGroup> rootEltGrps = rootElt.getEltGroups(); int numGrps = rootEltGrps.size(); if (numGrps > 0) { final List<FrameElementGroup> deleteGrps = new ArrayList<FrameElementGroup>(rootEltGrps); final int[] atIndexes = new int[numGrps]; Iterator<FrameElementGroup> eltGrps = deleteGrps.iterator(); int i = 0; while (eltGrps.hasNext()) { FrameElementGroup grp = eltGrps.next(); atIndexes[i++] = grp.indexOf(rootElt); if (grp != fromGrp) { grp.remove(rootElt); if (grp.size() == 1) { ungroup(grp, editSequence); removeRootElement(presentationName, grp, fromGrp, editSequence); } } } if (editSequence != null) { DemoStateManager.IDesignUndoableEdit edit = new DemoStateManager.InvalidatingEdit(FrameEditorLID.Delete, demoStateMgr) { @Override public String getPresentationName() { return presentationName; } @Override public void redo() { super.redo(); Iterator<FrameElementGroup> eltGrps = deleteGrps.iterator(); while (eltGrps.hasNext()) { FrameElementGroup grp = eltGrps.next(); if (grp != fromGrp) { grp.remove(rootElt); if (grp.size() == 1) { ungroup(grp, null); } } } } @Override public void undo() { super.undo(); Iterator<FrameElementGroup> eltGrps = deleteGrps.iterator(); int i = 0; while (eltGrps.hasNext()) { FrameElementGroup grp = eltGrps.next(); if (grp.size() == 1) { regroup(grp); } grp.add(atIndexes[i++], rootElt); } } }; editSequence.addEdit(edit); } } } /** * Delete the associated remote label if this element has one. */ private void deleteRemoteLabel(FrameElement elt, IUndoableEditSequence editSequence) { FrameElement asLabelOwner = elt.getRemoteLabelOwner(); if (asLabelOwner != null) { IWidget remoteLabel = (IWidget) asLabelOwner.getAttribute(WidgetAttributes.REMOTE_LABEL_ATTR); // If this element has a remote label, delete it if (remoteLabel != null) { deleteWidget(remoteLabel, false, null, editSequence); } } } /** * This deletes a widget from the model and adds the action to the undo list. * * @param w is the widget to delete. * @param moveSiblings is a flag - if it is true, the other widgets in the * group will be moved to close up the space left by the deleted widget. * @param editSequence is the compound edit to add this delete operation to. */ private void deleteWidget(IWidget w, boolean moveSiblings, FrameElementGroup fromGroup, IUndoableEditSequence editSequence) { // If this is a remote label, delete the attribute indicating so // from the point of view of its owner. FrameElement remoteLabelOwner = (FrameElement) w.getAttribute(WidgetAttributes.REMOTE_LABEL_OWNER_ATTR); if (remoteLabelOwner != null) { DefaultCmd.unsetAttribute(remoteLabelOwner, demoStateMgr, WidgetAttributes.REMOTE_LABEL_ATTR, interaction, editSequence); } else { // Check if this widget is a remote label owner; if so, delete // the remote label as well deleteRemoteLabel(w, editSequence); } SimpleWidgetGroup parentGroup = w.getParentGroup(); int atIndex = (parentGroup != null) ? parentGroup.indexOf(w) : -1; double deltaX = 0; double deltaY = 0; // If a parent widget with children (for example, submenu or menu // header), delete those children too. The moveSiblings flag is // false because there is no need to shuffle the child menus around // since we know every item in them will be deleted. if (w instanceof AParentWidget) { AParentWidget pw = (AParentWidget) w; while (pw.itemCount() > 0) { deleteWidget(pw.getItem(0), false, fromGroup, editSequence); } } // If the widget is the last object of the its root element, // then the root element must be removed from any containing groups // and, if the root element is the second to last member of any // group, then the group should be removed as well. // This must be done before the removeWidget call. FrameElement rootElt = w.getRootElement(); // If the widget is its own root element, remove it from // containing associations if (rootElt == w) { removeRootElement(DELETE_WIDGET, rootElt, fromGroup, editSequence); } else if (rootElt instanceof SimpleWidgetGroup) { // Otherwise, need to do the same only if the root element // is a widget group that will become empty. SimpleWidgetGroup rootGroup = (SimpleWidgetGroup) rootElt; // If the widget group will become empty, // remove it from containing associations if (rootGroup.size() == 1) { removeRootElement(DELETE_WIDGET, rootElt, fromGroup, editSequence); } } model.removeWidget(w); if (parentGroup != null) { // Check if this parent group is a remote label owner; if so, delete // the remote label as well deleteRemoteLabel(parentGroup, editSequence); if (moveSiblings) { int myGroupNum = parentGroup.size(); if (parentGroup.getOrientation() == SimpleWidgetGroup.HORIZONTAL) { deltaX = - w.getEltBounds().width; } else if (parentGroup.getOrientation() == SimpleWidgetGroup.VERTICAL) { deltaY = - w.getEltBounds().height; } // Move all widgets later than this one in the group. // This loop will be ineffective for grid buttons for (int i = atIndex + 1; i < myGroupNum; i++) { IWidget curWidget = parentGroup.get(i); curWidget.moveElement(deltaX, deltaY); } } // If it is a child widget, remove this item from its parent. // Otherwise, remove it from its parent group. if (w instanceof ChildWidget) { ChildWidget child = (ChildWidget) w; AParentWidget itemParent = child.getParent(); itemParent.removeItem(child); } else { parentGroup.remove(w); } if (parentGroup instanceof GridButtonGroup) { GridButtonGroup gbg = (GridButtonGroup) parentGroup; GridButton gb = (GridButton) w; DoubleRectangle b = w.getEltBounds(); double x = b.x + b.width; double y = b.y + b.height; double newHoriz = gb.getHorizSpace(); double newVert = gb.getVertSpace(); ReadOnlyList<GridButton> movedButtons = null; GridButton top = null; double dx = 0.0; double dy = 0.0; if (gbg.getColumn(gb).size() == 0) { // w was the only widget in the column; need to move next // column over movedButtons = gbg.getMovedButtons(false, x, b.y); if (movedButtons.size() > 0) { top = movedButtons.get(0); DoublePoint p = top.getShape().getOrigin(); dx = b.x - p.x; } } else { // need to move lower widgets up to fill the hole movedButtons = gbg.getMovedButtons(true, b.x, y); if (movedButtons.size() > 0) { top = movedButtons.get(0); DoublePoint p = top.getShape().getOrigin(); dy = b.y - p.y; } } if (top != null) { moveGridButton(FrameEditorLID.Delete, DELETE_WIDGET, top, dx, dy, newHoriz, newVert, editSequence); } } } DemoStateManager.IDesignUndoableEdit edit; // Add the deletion to the undoable history if (w instanceof ChildWidget) { ChildWidget child = (ChildWidget) w; final AParentWidget itemParent = child.getParent(); edit = new DeleteWidgetUndoableEdit(w, parentGroup, atIndex, deltaX, deltaY) { @Override public void redoHelper() { itemParent.removeItem((ChildWidget) widget); } @Override public void undoHelper() { itemParent.addItem(atIndex, (ChildWidget) widget); } }; } else { edit = new DeleteWidgetUndoableEdit(w, parentGroup, atIndex, deltaX, deltaY) { @Override public void redoHelper() { parentGroup.remove(widget); } @Override public void undoHelper() { parentGroup.add(atIndex, widget); } }; } demoStateMgr.noteWidgetEdit(w, edit); editSequence.addEdit(edit); } // deleteWidget private void resizeElement(FrameElement elt, double oldResizeX, double oldResizeY, double newResizeX, double newResizeY, double ratioX, double ratioY, Set<SimpleWidgetGroup> resizedGroups, boolean childrenToo, CompoundUndoableEdit editSequence) { if (elt instanceof IWidget) { IWidget widget = (IWidget) elt; SimpleWidgetGroup group = widget.getParentGroup(); if ((widget instanceof TraversableWidget) && (group != null)) { if (! resizedGroups.contains(group)) { resizeGroup(group, oldResizeX, oldResizeY, newResizeX, newResizeY, ratioX, ratioY, childrenToo, editSequence); resizedGroups.add(group); } } else { // Resize the actual widget. resizeWidget(widget, oldResizeX, oldResizeY, newResizeX, newResizeY, ratioX, ratioY, childrenToo, editSequence); } } else if (elt instanceof SimpleWidgetGroup) { Iterator<IWidget> widgets = ((SimpleWidgetGroup) elt).iterator(); while (widgets.hasNext()) { resizeElement(widgets.next(), oldResizeX, oldResizeY, newResizeX, newResizeY, ratioX, ratioY, resizedGroups, childrenToo, editSequence); } } else if (elt instanceof FrameElementGroup) { Iterator<FrameElement> members = ((FrameElementGroup) elt).iterator(); while (members.hasNext()) { resizeElement(members.next(), oldResizeX, oldResizeY, newResizeX, newResizeY, ratioX, ratioY, resizedGroups, true, editSequence); } } } /** * Resize the selected set of elements based on where the mouse was released * and the fixed point in the resize. * * While it supports multiple selection, its behavior is correct if called * with more then one selected widget. */ private boolean resizeElements(double oldResizeX, double oldResizeY, double newResizeX, double newResizeY, double ratioX, double ratioY, FrameEditorSelectionState selection) { Iterator<FrameElement> selected = selection.getSelectedElementsIterator(); CompoundUndoableEdit editSequence = new CompoundUndoableEdit((selection.getWidgetSelectionCount() != 1) ? RESIZE_WIDGETS : RESIZE_WIDGET, FrameEditorLID.ResizeWidgets); Set<SimpleWidgetGroup> resizedGroups = new HashSet<SimpleWidgetGroup>(); // Loop through selected widgets while (selected.hasNext()) { FrameElement elt = selected.next(); resizeElement(elt, oldResizeX, oldResizeY, newResizeX, newResizeY, ratioX, ratioY, resizedGroups, false, editSequence); } editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } private void resizeGroup(SimpleWidgetGroup group, double oldResizeX, double oldResizeY, double newResizeX, double newResizeY, double ratioX, double ratioY, boolean childrenToo, CompoundUndoableEdit editSequence) { Iterator<IWidget> groupElts = group.iterator(); while (groupElts.hasNext()) { IWidget groupElt = groupElts.next(); resizeWidget(groupElt, oldResizeX, oldResizeY, newResizeX, newResizeY, ratioX, ratioY, childrenToo, editSequence); } } /** * Implementation on a per widget bases for resize. * Calculates the new size be the difference between the fixed point and the * released point. * * @param widget The widget to resize * @param mouseReleased The point where the mouse was let up * @param fixedPoint The point which will not move after resize * @param edit The Compound undoable edit */ private void resizeWidget(final IWidget widget, double oldResizeX, double oldResizeY, double newResizeX, double newResizeY, final double ratioX, final double ratioY, final boolean childrenToo, IUndoableEditSequence editSequence) { // Get the old shape bounds for the undo DoubleRectangle oldBounds = widget.getEltBounds(); final double oldWidgetX = oldBounds.x; final double oldWidgetY = oldBounds.y; final double oldWidth = oldBounds.width; final double oldHeight = oldBounds.height; final double newWidgetX = ratioX * (oldBounds.x - oldResizeX) + newResizeX; final double newWidgetY = ratioY * (oldBounds.y - oldResizeY) + newResizeY; final double newWidth = Math.max(ratioX * oldBounds.width, 1.0); final double newHeight = Math.max(ratioY * oldBounds.height, 1.0); final double oldHoriz = (widget instanceof GridButton) ? ((GridButton) widget).getHorizSpace() : 0.0; final double oldVert = (widget instanceof GridButton) ? ((GridButton) widget).getVertSpace() : 0.0; // Actually make the changes to the model. widget.setWidgetOrigin(newWidgetX, newWidgetY); widget.setWidgetSize(newWidth, newHeight); if (widget instanceof GridButton) { GridButton gb = (GridButton) widget; gb.setHorizSpace(oldHoriz * ratioX); gb.setVertSpace(oldVert * ratioY); } else if (widget instanceof AParentWidget) { if (childrenToo) { resizeChildren(widget); } DesignEditorCmd.repositionChildren((AParentWidget) widget); } // Add the undo support DemoStateManager.ObsoletingEdit edit = new DemoStateManager.ObsoletingEdit(FrameEditorLID.ResizeWidgets, demoStateMgr) { @Override public String getPresentationName() { return RESIZE_WIDGET; } @Override public void redo() { super.redo(); widget.setWidgetOrigin(newWidgetX, newWidgetY); widget.setWidgetSize(newWidth, newHeight); if (widget instanceof GridButton) { GridButton gb = (GridButton) widget; gb.setHorizSpace(oldHoriz * ratioX); gb.setVertSpace(oldVert * ratioY); } else if (widget instanceof AParentWidget) { if (childrenToo) { resizeChildren(widget); } DesignEditorCmd.repositionChildren((AParentWidget) widget); } noteEditCheckRegenerate(widget, this); } @Override public void undo() { super.undo(); widget.setWidgetOrigin(oldWidgetX, oldWidgetY); widget.setWidgetSize(oldWidth, oldHeight); if (widget instanceof GridButton) { GridButton gb = (GridButton) widget; gb.setHorizSpace(oldHoriz); gb.setVertSpace(oldVert); } else if (widget instanceof AParentWidget) { if (childrenToo) { // technically, this won't restore the sizes if they were changed manuallythes resizeChildren(widget); } DesignEditorCmd.repositionChildren((AParentWidget) widget); } noteEditCheckRegenerate(widget, this); } }; noteEditCheckRegenerate(widget, edit); editSequence.addEdit(edit); } // resizeWidget /** * Move the list of selected Widgets and set up undo support for that. * Uses a compound Undo * * @param selection Iterator of items to move */ private boolean moveElements(FrameEditorSelectionState selection, double widgetMoveByX, double widgetMoveByY) { return moveElements(selection, widgetMoveByX, widgetMoveByY, true); } private void moveWidget(ListenerIdentifier lid, String presentationLabel, IWidget widget, double widgetMoveByX, double widgetMoveByY, boolean moveAsGroup, Set<SimpleWidgetGroup> movedGroups, Set<IWidget> movedWidgets, IUndoableEditSequence editSequence) { if (movedWidgets.contains(widget)) { return; } movedWidgets.add(widget); SimpleWidgetGroup parentGroup = widget.getParentGroup(); if ((parentGroup == null) || (! moveAsGroup)) { if (widget instanceof GridButton) { GridButton gb = (GridButton) widget; moveGridButton(lid, presentationLabel, gb, widgetMoveByX, widgetMoveByY, gb.getHorizSpace() + widgetMoveByX, gb.getVertSpace() + widgetMoveByY, editSequence); } else { moveWidget(lid, presentationLabel, widget, widgetMoveByX, widgetMoveByY, editSequence); } } else if (! (widget instanceof ChildWidget)) { // If widget is part of a group that hasn't been moved yet, // move entire group if (! movedGroups.contains(parentGroup)) { moveWidgetGroup(lid, presentationLabel, parentGroup, widgetMoveByX, widgetMoveByY, editSequence); movedGroups.add(parentGroup); } } } private void moveElement(ListenerIdentifier lid, String presentationLabel, FrameElement frameElt, double widgetMoveByX, double widgetMoveByY, boolean moveAsGroup, Set<SimpleWidgetGroup> movedGroups, Set<IWidget> movedWidgets, IUndoableEditSequence editSequence) { if (frameElt instanceof IWidget) { moveWidget(lid, presentationLabel, (IWidget) frameElt, widgetMoveByX, widgetMoveByY, moveAsGroup, movedGroups, movedWidgets, editSequence); } else if (frameElt instanceof SimpleWidgetGroup) { for (IWidget w : (SimpleWidgetGroup)frameElt) { moveWidget(lid, presentationLabel, w, widgetMoveByX, widgetMoveByY, moveAsGroup, movedGroups, movedWidgets, editSequence); } } else if (frameElt instanceof FrameElementGroup) { for (FrameElement fe : (FrameElementGroup)frameElt) { moveElement(lid, presentationLabel, fe, widgetMoveByX, widgetMoveByY, moveAsGroup, movedGroups, movedWidgets, editSequence); } } } private boolean isMemberOfSelectedGroup(FrameElement elt, FrameEditorSelectionState seln) { FrameElement rootElt = elt.getRootElement(); Iterator<FrameElementGroup> grps = rootElt.getEltGroups().iterator(); // If a selected item is a member of an FrameElementGroup // that is also selected, skip it while (grps.hasNext()) { if (seln.isElementSelected(grps.next())) { return true; } } return false; } private boolean moveElements(FrameEditorSelectionState selection, double moveByX, double moveByY, boolean moveAsGroup) { String editLabel; if (selection.getWidgetSelectionCount() == 1) { editLabel = MOVE_WIDGET; } else { editLabel = MOVE_WIDGETS; } CompoundUndoableEdit editSequence = new CompoundUndoableEdit(editLabel, FrameEditorLID.MoveWidgets); FrameElement[] selected = selection.getSelectedIFrameElements(); // Avoid moving a group more than once Set<SimpleWidgetGroup> movedGroups = new HashSet<SimpleWidgetGroup>(); Set<IWidget> movedWidgets = new HashSet<IWidget>(); // Move all selected widgets by the specified amount for (FrameElement eltToMove : selected) { if (! isMemberOfSelectedGroup(eltToMove, selection)) { moveElement(FrameEditorLID.MoveWidgets, editLabel, eltToMove, moveByX, moveByY, moveAsGroup, movedGroups, movedWidgets, editSequence); } } editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } /** * Special case of moveWidget; moves all buttons below or to the right of * gb in its group and updates the horizontal and vertical offsets as * necessary. */ private void moveGridButton(ListenerIdentifier lid, String presentationLabel, GridButton gb, double widgetMoveByX, double widgetMoveByY, double newHoriz, double newVert, IUndoableEditSequence editSequence) { // move the list of buttons affected DoublePoint oldLocation = gb.getShape().getOrigin(); double oldX = oldLocation.x; double oldY = oldLocation.y; GridButtonGroup gbg = (GridButtonGroup) gb.getParentGroup(); boolean vertical = (widgetMoveByX == 0); double oldHoriz = gb.getHorizSpace(); double oldVert = gb.getVertSpace(); ReadOnlyList<? extends GridButton> movedButtons = gbg.getMovedButtons(vertical, oldX, oldY); for (int i = 0; i < movedButtons.size(); i++) { GridButton g = movedButtons.get(i); DoublePoint p = g.getShape().getOrigin(); double tempX = Math.max(p.x + widgetMoveByX, 0.0); double tempY = Math.max(p.y + widgetMoveByY, 0.0); g.setWidgetOrigin(tempX, tempY); } if (vertical) { gb.setVertSpace(newVert); } else { List<GridButton> column = gbg.getColumn(gb); for (int i = 0; i < column.size(); i++) { GridButton g = column.get(i); g.setHorizSpace(newHoriz); } } DemoStateManager.IDesignUndoableEdit edit = moveGridButtonsEdit(lid, presentationLabel, movedButtons, widgetMoveByX, widgetMoveByY, oldHoriz, oldVert, newHoriz, newVert, gb); editSequence.addEdit(edit); } /** * Move a single widget. * Add the move to the list of undoable edits using compound edit passed in * * Enforce that the move does not move the origin past 0,0 * * @param w * @param e */ private void moveWidget(ListenerIdentifier lid, String presentationLabel, IWidget w, double widgetMoveByX, double widgetMoveByY, IUndoableEditSequence editSequence) { // Keep track of old and new locations, ensuring not less than zero DoublePoint oldLocation = w.getShape().getOrigin(); double oldX = oldLocation.x; double oldY = oldLocation.y; double newX = Math.max(oldX + widgetMoveByX, 0.0); double newY = Math.max(oldY + widgetMoveByY, 0.0); // Change model and create undo. w.setWidgetOrigin(newX, newY); DemoStateManager.ObsoletingEdit edit = new WidgetMoveUndoRedo(lid, presentationLabel, w, newX, newY, oldX, oldY); noteEditCheckRegenerate(w, edit); editSequence.addEdit(edit); } // moveWidget private void moveWidgetGroup(ListenerIdentifier lid, final String presentationLabel, final SimpleWidgetGroup group, double widgetMoveByX, double widgetMoveByY, IUndoableEditSequence editSequence) { // Keep track of old and new locations, ensuring not less than zero IWidget firstChildWidget = group.get(0); DoublePoint oldLocation = firstChildWidget.getShape().getOrigin(); final double deltaX = Math.max(widgetMoveByX, - oldLocation.x); final double deltaY = Math.max(widgetMoveByY, - oldLocation.y); final int numWidgets = group.size(); List<IWidget> groupWidgets = new ArrayList<IWidget>(); // Change model and create undo. for (int i = 0; i < numWidgets; i++) { IWidget widget = group.get(i); groupWidgets.add(widget); widget.moveElement(deltaX, deltaY); } final ReadOnlyList<? extends IWidget> roGroupWidgets = new ReadOnlyList<IWidget>(groupWidgets); DemoStateManager.ObsoletingEdit edit = new DemoStateManager.ObsoletingEdit(lid, demoStateMgr) { @Override public String getPresentationName() { return presentationLabel; } @Override public void redo() { super.redo(); for (int i = 0; i < numWidgets; i++) { IWidget w = roGroupWidgets.get(i); w.moveElement(deltaX, deltaY); } noteEditCheckRegenerate(roGroupWidgets, this); } @Override public void undo() { super.undo(); for (int i = 0; i < numWidgets; i++) { IWidget w = roGroupWidgets.get(i); w.moveElement(- deltaX, - deltaY); } noteEditCheckRegenerate(roGroupWidgets, this); } }; noteEditCheckRegenerate(roGroupWidgets, edit); editSequence.addEdit(edit); } // moveWidgetGroup private IDesignUndoableEdit moveGridButtonsEdit(ListenerIdentifier lid, final String presentationLabel, final ReadOnlyList<? extends GridButton> movedButtons, final double widgetMoveByX, final double widgetMoveByY, final double oldHoriz, final double oldVert, final double newHoriz, final double newVert, final GridButton gb) { final GridButtonGroup gbg = (GridButtonGroup) gb.getParentGroup(); DemoStateManager.ObsoletingEdit edit = new DemoStateManager.ObsoletingEdit(lid, demoStateMgr) { @Override public String getPresentationName() { return presentationLabel; } @Override public void redo() { super.redo(); for (int i = 0; i < movedButtons.size(); i++) { GridButton g = movedButtons.get(i); DoublePoint p = g.getShape().getOrigin(); double tempX = Math.max(p.x + widgetMoveByX, 0.0); double tempY = Math.max(p.y + widgetMoveByY, 0.0); g.setWidgetOrigin(tempX, tempY); } if (widgetMoveByX == 0) { gb.setVertSpace(newVert); } else { List<GridButton> column = gbg.getColumn(gb); for (int i = 0; i < column.size(); i++) { GridButton g = column.get(i); g.setHorizSpace(newHoriz); } } noteEditCheckRegenerate(movedButtons, this); } @Override public void undo() { super.undo(); for (int i = 0; i < movedButtons.size(); i++) { GridButton g = movedButtons.get(i); DoublePoint p = g.getShape().getOrigin(); double tempX = Math.max(p.x - widgetMoveByX, 0.0); double tempY = Math.max(p.y - widgetMoveByY, 0.0); g.setWidgetOrigin(tempX, tempY); } if (widgetMoveByX == 0) { gb.setVertSpace(oldVert); } else { List<GridButton> column = gbg.getColumn(gb); for (int i = 0; i < column.size(); i++) { GridButton g = column.get(i); g.setHorizSpace(oldHoriz); } } noteEditCheckRegenerate(movedButtons, this); } }; noteEditCheckRegenerate(movedButtons, edit); return edit; } /** * Changes the level of the selected widget * * @param newLevel the desired level for the widget */ private void adjustWidgetLevel(final int newLevel, final IWidget widget, IUndoableEditSequence editSequence, ListenerIdentifier lid) { // Get the old widget level final int oldLevel = widget.getLevel(); // Update the model model.setWidgetLevel(newLevel, widget); // add undo support DemoStateManager.ObsoletingEdit edit = new DemoStateManager.ObsoletingEdit(lid, demoStateMgr) { @Override public String getPresentationName() { return CHG_WIDGET_LEVEL; } @Override public void redo() { super.redo(); model.setWidgetLevel(newLevel, widget); noteEditCheckRegenerate(widget, this); } @Override public void undo() { super.undo(); model.setWidgetLevel(oldLevel, widget); noteEditCheckRegenerate(widget, this); } }; noteEditCheckRegenerate(widget, edit); editSequence.addEdit(edit); } /** * Used to create a set of selected frame elements sorted by * "widget" level, where each frame element group is assigned the maximum * level of the widgets it contains. */ private static Comparator<FrameElement> elementLevelComparator = new Comparator<FrameElement>() { protected int getMaxLevel(FrameElementGroup g) { int level = 0; Iterator<FrameElement> elements = g.iterator(); while (elements.hasNext()) { FrameElement elt = elements.next(); int compareLevel = 0; if (elt instanceof IWidget) { compareLevel = ((IWidget) elt).getLevel(); } else if (elt instanceof SimpleWidgetGroup) { SimpleWidgetGroup wg = (SimpleWidgetGroup) elt; if (wg.size() > 0) { compareLevel = wg.get(0).getLevel(); } } else if (elt instanceof FrameElementGroup) { compareLevel = getMaxLevel((FrameElementGroup) elt); } if (level < compareLevel) { level = compareLevel; } } return level; } public int compare(FrameElement l, FrameElement r) { if (l instanceof IWidget) { IWidget lw = (IWidget) l; if (r instanceof IWidget) { IWidget rw = (IWidget) r; return Widget.WidgetLevelComparator.ONLY.compare(lw, rw); } if (r instanceof FrameElementGroup) { return lw.getLevel() - getMaxLevel((FrameElementGroup) r); } } else if (l instanceof FrameElementGroup) { int lLevel = getMaxLevel((FrameElementGroup) l); if (r instanceof IWidget) { return lLevel - ((IWidget) r).getLevel(); } if (r instanceof FrameElementGroup) { return lLevel - getMaxLevel((FrameElementGroup) r); } } return 0; // shouldn't get here, but what else to do? } }; /** * Used to determine the horizontal relationship of 2 widgets * This is used in the calculation of alignment, and spacing. * Widget1 to the left of Widget2 returns a < 0 number */ private static Comparator<FrameElement> elementHorizontalComparator = new Comparator<FrameElement>() { public int compare(FrameElement l, FrameElement r) { // Compare the left & right for ordering DoubleRectangle left = l.getEltBounds(); DoubleRectangle right = r.getEltBounds(); double diff = left.x - right.x; return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0); } }; /** * Used to determine the Vertical relationship of 2 widgets * This is used in the calculation of alignment, and spacing. * Widget1 above Widget2 returns a < 0 number */ private static Comparator<FrameElement> elementVerticalComparator = new Comparator<FrameElement>() { public int compare(FrameElement u, FrameElement l) { // Compare up and down for ordering. DoubleRectangle upper = u.getEltBounds(); DoubleRectangle lower = l.getEltBounds(); double diff = upper.y - lower.y; return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0); } }; /** * Used to sort group members by location (by column and row). */ private static Comparator<FrameElement> groupMemberComparator = new Comparator<FrameElement>() { public int compare(FrameElement lhs, FrameElement rhs) { if (lhs == rhs) { return 0; } DoubleRectangle lhsBds = lhs.getEltBounds(); DoubleRectangle rhsBds = rhs.getEltBounds(); // lhs comes before rhs if its column is to the left OR // it is in the same column and it comes above if ((lhsBds.x < rhsBds.x) || ((lhsBds.y < rhsBds.y) && PrecisionUtilities.withinEpsilon(lhsBds.x, rhsBds.x, GridButtonGroup.PIXEL_EPSILON))) { return -1; } return 1; } }; /** * Spaces the selected frame elements equally along a certain axis * @param vertical true for vertical axis; false for horizontal */ private boolean spaceElementsEqually(FrameEditorSelectionState selection, boolean vertical) { // Order the widgets according to location // (either from the left or from the top) Comparator<FrameElement> c = vertical ? elementVerticalComparator : elementHorizontalComparator; Set<FrameElement> elements = getSelectedElements(selection, c); if (elements.size() <= 2) { interaction.protestTooFewElements(); return false; } // Calculate the spacing between widgets double sum = 0; double min = Double.MAX_VALUE; double max = Double.MIN_VALUE; // Go through each element that is selected // Determine the size, min & max of the region. // this can then be used to do spacing. Iterator<FrameElement> eltIter = elements.iterator(); while (eltIter.hasNext()) { DoubleRectangle bounds = eltIter.next().getEltBounds(); 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) / (elements.size() - 1); String undoRedoLabel = vertical ? SPACE_VERTICALLY : SPACE_HORIZONTALLY; CogToolLID lid = vertical ? FrameEditorLID.SpaceVertically : FrameEditorLID.SpaceHorizontally; CompoundUndoableEdit editSequence = new CompoundUndoableEdit(undoRedoLabel, lid); // Avoid moving a group more than once Set<SimpleWidgetGroup> movedGroups = new HashSet<SimpleWidgetGroup>(); Set<IWidget> movedWidgets = new HashSet<IWidget>(); // Adjust the spacings to the correct values and go through // each element performing the appropriate move. eltIter = elements.iterator(); while (eltIter.hasNext()) { FrameElement elt = eltIter.next(); DoubleRectangle bounds = elt.getEltBounds(); // Determine the amount to move each element double deltaX = vertical ? 0.0 : (min - bounds.x); double deltaY = vertical ? (min - bounds.y) : 0.0; // Set the new location, adding the undoable edit moveElement(lid, undoRedoLabel, elt, deltaX, deltaY, true, movedGroups, movedWidgets, editSequence); // Advance the pointer to the next location min += spacing + (vertical ? bounds.height : bounds.width); } editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } /** * Update the widget's name. * The name must be unique. * * @param widget * @param newName * @return */ private boolean updateWidgetName(final IWidget widget, String tryName) { final String oldName = widget.getName(); boolean notDone = true; do { if (tryName.length() == 0) { tryName = interaction.protestNameCannotBeEmpty(DEFAULT_WIDGET_PREFIX); // If canceled, indicate so; otherwise, test again if (tryName == null) { return false; } } else { IWidget widgetForName = model.getWidget(tryName); // If the widget for the given name is the same, // then no change is necessary! if (widgetForName == widget) { notDone = false; } else if (widgetForName != null) { // A non-null widget for the tryName indicates a collision tryName = interaction.protestNameCollision(DEFAULT_WIDGET_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; model.setWidgetName(newName, widget); DemoStateManager.ObsoletingEdit edit = new DemoStateManager.ObsoletingEdit(FrameEditorLID.ChangeNameProperty, demoStateMgr) { @Override public String getPresentationName() { return CHG_WIDGET_NAME; } @Override public void redo() { super.redo(); model.setWidgetName(newName, widget); noteEditCheckRegenerate(widget, this); } @Override public void undo() { super.undo(); model.setWidgetName(oldName, widget); noteEditCheckRegenerate(widget, this); } }; noteEditCheckRegenerate(widget, edit); undoMgr.addEdit(edit); } } } while (notDone); return true; } private boolean renameEltGroup(final FrameElementGroup eltGroup, String tryName) { final String oldName = eltGroup.getName(); boolean notDone = true; do { if (tryName.length() == 0) { tryName = interaction.protestNameCannotBeEmpty(DEFAULT_GROUP_PREFIX); // If canceled, indicate so; otherwise, test again if (tryName == null) { return false; } } else { FrameElementGroup groupForName = model.getEltGroup(tryName); // If the group for the given name is the same, // then no change is necessary! if (groupForName == eltGroup) { notDone = false; } else if (groupForName != null) { // A non-null widget for the tryName indicates a collision tryName = interaction.protestNameCollision(DEFAULT_GROUP_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; model.setEltGroupName(newName, eltGroup); IUndoableEdit edit = new AUndoableEdit(FrameEditorLID.ChangeNameProperty) { @Override public String getPresentationName() { return CHG_WIDGET_NAME; } @Override public void redo() { super.redo(); model.setEltGroupName(newName, eltGroup); } @Override public void undo() { super.undo(); model.setEltGroupName(oldName, eltGroup); } }; undoMgr.addEdit(edit); } } } while (notDone); return true; } // renameEltGroup /** * Update the frame's name. * The name must be unique. * * @param frame * @param newName * @return */ private boolean updateFrameName(final Frame frame, String tryName) { final String oldName = frame.getName(); boolean notDone = true; do { if (tryName.length() == 0) { tryName = interaction.protestNameCannotBeEmpty("Frame"); // If canceled, indicate so; otherwise, test again if (tryName == null) { return false; } } else { Frame frameForName = model.getDesign().getFrame(tryName); // If the widget for the given name is the same, // then no change is necessary! if (frameForName == frame) { notDone = false; } else if (frameForName != null) { // A non-null widget for the tryName indicates a collision tryName = interaction.protestNameCollision("Frame"); // 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; frame.setName(newName); IUndoableEdit edit = new AUndoableEdit(DesignEditorLID.RenameFrame) { @Override public String getPresentationName() { return CHG_FRAME_NAME; } @Override public void redo() { super.redo(); Frame testFrame = design.getFrame(newName); frame.setName(newName); if (testFrame != null) { makeFrameNameUnique(frame); } } @Override public void undo() { super.undo(); Frame testFrame = design.getFrame(oldName); frame.setName(oldName); if (testFrame != null) { makeFrameNameUnique(frame); } } }; undoMgr.addEdit(edit); } } } while (notDone); return true; } protected static class DuplicateWidgetSituator extends SimpleWidgetGroup.AWidgetDuplicator { protected Map<IWidget, IWidget> widgetCopies; protected Map<FrameElementGroup, FrameElementGroup> groupCopies; public void placeInContext(IWidget origWidget, IWidget widgetCopy) { widgetCopies.put(origWidget, widgetCopy); } public IWidget getDuplicate(IWidget origWidget) { return widgetCopies.get(origWidget); } public void reset(Map<IWidget, IWidget> wCopies, Map<FrameElementGroup, FrameElementGroup> gCopies) { reset(); widgetCopies = wCopies; groupCopies = gCopies; } public void setGroupDuplicate(FrameElementGroup original, FrameElementGroup copy) { groupCopies.put(original, copy); } @Override public void completeWork() { super.completeWork(); // Avoid leak widgetSituator.reset(null, null); } } private static DuplicateWidgetSituator widgetSituator = new DuplicateWidgetSituator(); private Iterator<IWidget> sortSelectedMbrs(SimpleWidgetGroup group, FrameEditorSelectionState s) { Set<IWidget> sortingSet = new TreeSet<IWidget>(groupMemberComparator); Iterator<IWidget> groupMbrs = group.iterator(); while (groupMbrs.hasNext()) { IWidget member = groupMbrs.next(); if ((s == null) || s.isElementSelected(member)) { sortingSet.add(member); } } return sortingSet.iterator(); } // Assumes that widget is an instance of MenuHeader, ListItem, or // GridButton private IWidget duplicateGroupMember(IWidget widget, FrameEditorSelectionState selection, double moveByX, double moveByY) { SimpleWidgetGroup existingGroup = widget.getParentGroup(); SimpleWidgetGroup newGroup = widgetSituator.getGroup(existingGroup); // If the first time this group has been seen, populate. if (newGroup.size() == 0) { Iterator<IWidget> widgets = sortSelectedMbrs(existingGroup, selection); while (widgets.hasNext()) { Frame.duplicateWidget(widgets.next(), lookupFrameDuplicator, widgetSituator, moveByX, moveByY); } DesignEditorCmd.repositionChildren(newGroup); } return widgetSituator.getDuplicate(widget); } // duplicateGroupMember private IWidget duplicateWidget(IWidget widget, FrameEditorSelectionState selection, double moveByX, double moveByY) { // If already copied, simply return the copy. IWidget widgetCopy = widgetSituator.getDuplicate(widget); if (widgetCopy != null) { return widgetCopy; } // Avoid children // IChildWidget returns null; this is ok for now since the only // invocation that cares about the return value is the recursive // call for the remote "label" in duplicateRemoteLabel(). if (widget instanceof ChildWidget) { return null; } // Test this first to catch MenuHeader // (also gets GridButton and ListItem) if (widget.getParentGroup() != null) { widgetCopy = duplicateGroupMember(widget, selection, moveByX, moveByY); } else { // This gets PullDownHeader and ContextMenu (but not // MenuHeader/Item) and all else but MenuItem and PullDownItem // (i.e., children) // TODO: Is there any reasonable semantics to // duplicating menu/pull-down items? widgetCopy = Frame.duplicateWidget(widget, lookupFrameDuplicator, widgetSituator, moveByX, moveByY); } // If this is a remote label, then check if the owner is also being // duplicated; if so, keep the remote label as a remote label. // If not, remote the attribute on the copy. FrameElement remoteLabelOwner = (FrameElement) widget.getAttribute(WidgetAttributes.REMOTE_LABEL_OWNER_ATTR); if (remoteLabelOwner != null) { if ((selection != null) && ! selection.isElementSelected(remoteLabelOwner)) { // Remove "label"-ness widgetCopy.unsetAttribute(WidgetAttributes.REMOTE_LABEL_OWNER_ATTR); } // otherwise, when the owner gets duplicated, it will replace // the owner of this label with its copy; see duplicateRemoteLabel! } else { Frame.duplicateRemoteLabel(widget, widgetCopy, lookupFrameDuplicator, widgetSituator, moveByX, moveByY); } return widgetCopy; } // duplicateWidget private FrameElementGroup duplicateFrameEltGroup(FrameElementGroup grp, double moveByX, double moveByY) { // Temporarily assign the same name; when added to the Frame // we will ensure the name is then unique FrameElementGroup newEltGrp = grp.twin(); Iterator<FrameElement> eltsToDup = grp.iterator(); while (eltsToDup.hasNext()) { FrameElement elt = eltsToDup.next(); FrameElement eltCopy = null; if (elt instanceof IWidget) { IWidget widget = (IWidget) elt; duplicateWidget(widget, null, moveByX, moveByY); eltCopy = widgetSituator.getDuplicate(widget); } else if (elt instanceof SimpleWidgetGroup) { SimpleWidgetGroup group = (SimpleWidgetGroup) elt; SimpleWidgetGroup groupCopy = widgetSituator.getGroup(group); Iterator<IWidget> groupWidgets = group.iterator(); while (groupWidgets.hasNext()) { duplicateGroupMember(groupWidgets.next(), null, moveByX, moveByY); } Frame.duplicateRemoteLabel(group, groupCopy, lookupFrameDuplicator, widgetSituator, moveByX, moveByY); eltCopy = groupCopy; } else if (elt instanceof FrameElementGroup) { eltCopy = duplicateFrameEltGroup((FrameElementGroup) elt, moveByX, moveByY); } if (eltCopy != null) { newEltGrp.add(eltCopy); } } widgetSituator.setGroupDuplicate(grp, newEltGrp); Frame.duplicateRemoteLabel(grp, newEltGrp, lookupFrameDuplicator, widgetSituator, moveByX, moveByY); return newEltGrp; } // duplicateFrameEltGroup private IListenerAction duplicateWidgetsAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.DuplicateParameters.class; } public boolean performAction(Object prms) { FrameEditorUI.DuplicateParameters parameters = (FrameEditorUI.DuplicateParameters) prms; int elementCount = parameters.selection.getElementSelectionCount(); // If selection is non zero, copy widgets if (elementCount > 0) { Map<IWidget, IWidget> widgetCopies = new LinkedHashMap<IWidget, IWidget>(); final Map<FrameElementGroup, FrameElementGroup> groupCopies = new LinkedHashMap<FrameElementGroup, FrameElementGroup>(); widgetSituator.reset(widgetCopies, groupCopies); Set<FrameElement> selectedElements = getSelectedElements(parameters.selection, elementLevelComparator); Iterator<FrameElement> elements = selectedElements.iterator(); // Iterate through the widgets and duplicate. while (elements.hasNext()) { FrameElement elt = elements.next(); if (elt instanceof IWidget) { duplicateWidget((IWidget) elt, parameters.selection, parameters.moveByX, parameters.moveByY); } else if (elt instanceof FrameElementGroup) { duplicateFrameEltGroup((FrameElementGroup) elt, parameters.moveByX, parameters.moveByY); } } widgetSituator.completeWork(); final Iterable<? extends IWidget> widgetDuplicates = new ReadOnlyList<IWidget>(new ArrayList<IWidget>(widgetCopies.values())); Iterator<? extends IWidget> copiedWidgets = widgetDuplicates.iterator(); Set<GridButtonGroup> dupGridGroups = new HashSet<GridButtonGroup>(); while (copiedWidgets.hasNext()) { IWidget widgetCopy = copiedWidgets.next(); // Warning: it is important that each widget be added // to the frame *before* we make the next widget name // unique or we can end up with non-unique names. makeWidgetNameUnique(widgetCopy); model.addWidget(widgetCopy); if (widgetCopy instanceof GridButton) { GridButtonGroup gbg = (GridButtonGroup) widgetCopy.getParentGroup(); // Avoid recalculating offsets more than once // for each grid button group if (! dupGridGroups.contains(gbg)) { gbg.recalculateOffsets(); dupGridGroups.add(gbg); } } } Iterator<FrameElementGroup> copiedGroups = groupCopies.values().iterator(); while (copiedGroups.hasNext()) { FrameElementGroup copiedGroup = copiedGroups.next(); // Ensure name is unique, then add to frame immediately // Otherwise, it would be possible to generate presumed // unique names that weren't. makeEltGroupNameUnique(copiedGroup); model.addEltGroup(copiedGroup); } DemoStateManager.IDesignUndoableEdit edit = new DemoStateManager.InvalidatingEdit(FrameEditorLID.Duplicate, demoStateMgr) { @Override public String getPresentationName() { return DUPLICATE_WIDGETS; } @Override public void redo() { super.redo(); Iterator<? extends IWidget> addCopies = widgetDuplicates.iterator(); while (addCopies.hasNext()) { IWidget widgetCopy = addCopies.next(); model.addWidget(widgetCopy); } Iterator<FrameElementGroup> copiedGroups = groupCopies.values().iterator(); while (copiedGroups.hasNext()) { model.addEltGroup(copiedGroups.next()); } demoStateMgr.noteWidgetsEdit(widgetDuplicates, this); } @Override public void undo() { super.undo(); Iterator<? extends IWidget> removeCopies = widgetDuplicates.iterator(); while (removeCopies.hasNext()) { IWidget widgetCopy = removeCopies.next(); model.removeWidget(widgetCopy); } Iterator<FrameElementGroup> copiedGroups = groupCopies.values().iterator(); while (copiedGroups.hasNext()) { model.removeEltGroup(copiedGroups.next()); } demoStateMgr.noteWidgetsEdit(widgetDuplicates, this); } }; demoStateMgr.noteWidgetsEdit(widgetDuplicates, edit); undoMgr.addEdit(edit); return true; } // tell user to select something. interaction.protestNoSelection(); return false; } }; } /** * Get the UI. * In this case it's a FrameEditorUI */ @Override public UI getUI() { return ui; } @Override protected ZoomableUI getZoomableUI() { return ui; } /** * Helper method which returns a list of models instead of the * SelectionState returned Figures * @param selection * @return */ private Set<FrameElement> getSelectedElements(FrameEditorSelectionState seln, Comparator<? super FrameElement> c) { Set<FrameElement> elements = new TreeSet<FrameElement>(c); Iterator<FrameElement> selectedElts = seln.getSelectedElementsIterator(); // Determine which elements can participate while (selectedElts.hasNext()) { FrameElement elt = selectedElts.next(); if (! isMemberOfSelectedGroup(elt, seln)) { elements.add(elt); } } return elements; } /** * Fetch all widgets referenced by the given selection sorted by * the given comparison function. */ private IWidget[] getSelectedWidgets(FrameEditorSelectionState seln, Comparator<? super IWidget> c) { IWidget[] widgets; Set<IWidget> allWidgets = new HashSet<IWidget>(); Iterator<FrameElement> s = seln.getSelectedElementsIterator(); while (s.hasNext()) { FrameElement elt = s.next(); if (elt instanceof IWidget) { IWidget w = (IWidget) elt; SimpleWidgetGroup group = w.getParentGroup(); if (group != null) { Iterator<IWidget> mbrs = group.iterator(); while (mbrs.hasNext()) { allWidgets.add(mbrs.next()); } } else { allWidgets.add(w); } } else if (elt instanceof FrameElementGroup) { Iterator<IWidget> eltGrpWidgets = new ElementAllWidgetIterator((FrameElementGroup) elt); while (eltGrpWidgets.hasNext()) { allWidgets.add(eltGrpWidgets.next()); } } } widgets = new IWidget[allWidgets.size()]; allWidgets.toArray(widgets); Arrays.sort(widgets, c); return widgets; } /** * Generates a unique widget name * * See bug report #133 * * @return the generated name */ private String generateUniqueWidgetName() { String name = null; do { name = DEFAULT_WIDGET_PREFIX + " " + widgetNameSuffix++; } while (model.isWidgetNameTaken(name)); return name; } private void setWidgetImage(final IWidget w, final byte[] imageData, final String imageURL, CogToolLID lid, final String undoRedoLabel, IUndoableEditSequence editSequence) { // Get existing image final byte[] previousImageData = w.getImage(); final String previousImagePath = (String) w.getAttribute(WidgetAttributes.IMAGE_PATH_ATTR); // Perform operation w.setImage(imageData); w.setAttribute(WidgetAttributes.IMAGE_PATH_ATTR, imageURL); // Add the undo edit editSequence.addEdit(new AUndoableEdit(lid) { @Override public String getPresentationName() { return undoRedoLabel; } @Override public void redo() { super.redo(); w.setImage(imageData); w.setAttribute(WidgetAttributes.IMAGE_PATH_ATTR, imageURL); } @Override public void undo() { super.undo(); w.setImage(previousImageData); w.setAttribute(WidgetAttributes.IMAGE_PATH_ATTR, previousImagePath); } }); } /** * Sets the images on a bunch of widgets * @param selected an iterator containing the IWidgets * @param imageData the new image, or null of none */ private void setWidgetImages(Iterator<IWidget> selected, final byte[] imageData, final String imageURL) { String undoRedoLabel = (imageData == null) ? REMOVE_WIDGET_IMG : SET_WIDGET_IMG; CogToolLID lid = (imageData == null) ? FrameEditorLID.RemoveImageProperty : FrameEditorLID.SetImageProperty; // Create the compound undo CompoundUndoableEdit editSequence = new CompoundUndoableEdit(undoRedoLabel, lid); // Change the images on all the selected widgets while (selected.hasNext()) { setWidgetImage(selected.next(), imageData, imageURL, lid, undoRedoLabel, editSequence); } // Commit the edit editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } } private AListenerAction createSetSkinAction(final SkinType newSkin, final CogToolLID lid) { return new AListenerAction() { public boolean performAction(Object prms) { final SkinType oldSkin = design.getSkin(); design.setSkin(newSkin); IUndoableEdit edit = new AUndoableEdit(lid) { @Override public String getPresentationName() { return CHANGE_SKIN; } @Override public void redo() { super.redo(); design.setSkin(newSkin); } @Override public void undo() { super.undo(); design.setSkin(oldSkin); } }; UndoManager designUndoMgr = UndoManager.getUndoManager(design, project); designUndoMgr.addEdit(edit); undoMgr.addEdit(edit); return true; } }; } private boolean setSpeakerText(final String newText) { final String oldText = model.getSpeakerText(); if (oldText.equals(newText)) { return true; } model.setSpeakerText(newText); DemoScriptCmd.resetComputations(project, design); DemoStateManager.ObsoletingEdit edit = new DemoStateManager.ObsoletingEdit(FrameEditorLID.SetSpeakerText, demoStateMgr) { @Override public String getPresentationName() { return SET_SPEAKER_TEXT; } @Override public void redo() { super.redo(); model.setSpeakerText(newText); // DemoScriptCmd.redoAllChanges(computeUndoRedos); noteEditCheckRegenerate(this); } @Override public void undo() { super.undo(); model.setSpeakerText(oldText); // DemoScriptCmd.undoAllChanges(computeUndoRedos); noteEditCheckRegenerate(this); } }; noteEditCheckRegenerate(edit); undoMgr.addEdit(edit); return true; } private boolean setSpeakerDuration(final double newTimeInSecs) { final double oldTimeInSecs = model.getListenTimeInSecs(); if (oldTimeInSecs == newTimeInSecs) { return true; } model.setListenTimeInSecs(newTimeInSecs); DemoScriptCmd.resetComputations(project, design); DemoStateManager.ObsoletingEdit edit = new DemoStateManager.ObsoletingEdit(FrameEditorLID.SetSpeakerTime, demoStateMgr) { @Override public String getPresentationName() { return SET_SPEAKER_DURATION; } @Override public void redo() { super.redo(); model.setListenTimeInSecs(newTimeInSecs); // DemoScriptCmd.redoAllChanges(computeUndoRedos); noteEditCheckRegenerate(this); } @Override public void undo() { super.undo(); model.setListenTimeInSecs(oldTimeInSecs); // DemoScriptCmd.undoAllChanges(computeUndoRedos); noteEditCheckRegenerate(this); } }; noteEditCheckRegenerate(edit); undoMgr.addEdit(edit); return true; } private IListenerAction createGroupElementsAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; int selectedEltCount = selection.getElementSelectionCount(); if (selectedEltCount > 1) { final FrameElementGroup newGroup = new FrameElementGroup(); assignUniqueEltGroupName(newGroup); Iterator<FrameElement> selectedElts = selection.getSelectedElementsIterator(); while (selectedElts.hasNext()) { FrameElement rootElt = selectedElts.next().getRootElement(); // Add element to the group, which also records // that the element is a member of the group if (! newGroup.contains(rootElt)) { newGroup.add(rootElt); } } model.addEltGroup(newGroup); IUndoableEdit edit = new AUndoableEdit(FrameEditorLID.Group) { @Override public String getPresentationName() { return GROUP_ELEMENTS; } @Override public void redo() { super.redo(); Iterator<FrameElement> groupElts = newGroup.iterator(); // Since undo simply removed the group as an // association from the element, we only need // to add it back before adding the association // back to the frame while (groupElts.hasNext()) { groupElts.next().addToEltGroup(newGroup); } model.addEltGroup(newGroup); } @Override public void undo() { super.undo(); model.removeEltGroup(newGroup); Iterator<FrameElement> groupElts = newGroup.iterator(); // Since the group is no longer part of the // frame, no need to physically remove its // members; just remove the group as an // association from the element. while (groupElts.hasNext()) { groupElts.next().removeFromEltGroup(newGroup); } } }; undoMgr.addEdit(edit); return true; } if (selectedEltCount == 1) { interaction.protestTooFewWidgets(); } else { interaction.protestNoSelection(); } return false; } }; } // createGroupElementsAction private void regroup(FrameElementGroup group) { Iterator<? extends FrameElement> groupElts = group.iterator(); while (groupElts.hasNext()) { groupElts.next().addToEltGroup(group); } model.addEltGroup(group); } private void ungroup(FrameElementGroup group, IUndoableEditSequence editSequence) { model.removeEltGroup(group); Iterator<? extends FrameElement> groupElts = group.iterator(); while (groupElts.hasNext()) { groupElts.next().removeFromEltGroup(group); } deleteRemoteLabel(group, editSequence); } private IListenerAction createUngroupElementsAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorSelectionState.class; } public boolean performAction(Object prms) { FrameEditorSelectionState selection = (FrameEditorSelectionState) prms; Iterator<FrameElement> selectedElts = selection.getSelectedElementsIterator(); final Set<FrameElementGroup> selectedGroups = new HashSet<FrameElementGroup>(); while (selectedElts.hasNext()) { FrameElement elt = selectedElts.next(); if (elt instanceof FrameElementGroup) { selectedGroups.add((FrameElementGroup) elt); } else { selectedGroups.addAll(elt.getRootElement().getEltGroups()); } } if (selectedGroups.size() > 0) { CompoundUndoableEdit editSequence = new CompoundUndoableEdit(UNGROUP_ELEMENTS, FrameEditorLID.Ungroup); Iterator<FrameElementGroup> groups = selectedGroups.iterator(); while (groups.hasNext()) { FrameElementGroup group = groups.next(); ungroup(group, editSequence); removeRootElement(UNGROUP_ELEMENTS, group, null, editSequence); } IUndoableEdit edit = new AUndoableEdit(FrameEditorLID.Ungroup) { @Override public String getPresentationName() { return UNGROUP_ELEMENTS; } @Override public void redo() { super.redo(); Iterator<FrameElementGroup> groups = selectedGroups.iterator(); while (groups.hasNext()) { FrameElementGroup group = groups.next(); ungroup(group, null); } } @Override public void undo() { super.undo(); Iterator<FrameElementGroup> groups = selectedGroups.iterator(); while (groups.hasNext()) { FrameElementGroup group = groups.next(); regroup(group); } } }; editSequence.addEdit(edit); // Commit the edit editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } return true; } interaction.protestNoSelection(); return false; } }; } // createUngroupElementsAction private IListenerAction createSetRemoteLabelTextAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.SetRemoteLabelTextParms.class; } public boolean performAction(Object prms) { FrameEditorUI.SetRemoteLabelTextParms p = (FrameEditorUI.SetRemoteLabelTextParms) prms; // Check p.selectedElement; must be an FrameElementGroup // or an IWidget of WidgetType Button, PullDownList, Graffiti, // or TextBox (whether IS_STANDARD or IS_CUSTOM) or an IWidget // of WidgetType Radio, Check, or ListBoxItem if IS_STANDARD // (in which case, the label belongs to the parent SimpleWidgetGroup) FrameElement owningElt = p.selectedElement.getRemoteLabelOwner(); if (owningElt == null) { interaction.protestUnsupportedLabelOwnerType(); return false; } IWidget remoteLabel = (IWidget) owningElt.getAttribute(WidgetAttributes.REMOTE_LABEL_ATTR); // Already has a label; simply update if (remoteLabel != null) { changeTitleProperty(FrameEditorLID.SetRemoteLabelText, SET_REMOTE_LABEL, remoteLabel, p.newText, false, // not a separator! undoMgr); } else if ((p.newText != null) && ! p.newText.equals("")) { CompoundUndoableEdit editSequence = new CompoundUndoableEdit(SET_REMOTE_LABEL, FrameEditorLID.SetRemoteLabelText); final double INITIAL_WIDTH = 100.0; final double INITIAL_HEIGHT = 16.0; final double MIN_MARGIN = 5.0; final double EXTRA_MARGIN = 10.0; DoubleRectangle eltBds = owningElt.getEltBounds(); double maxY = eltBds.y - INITIAL_HEIGHT - MIN_MARGIN; DoubleRectangle labelBds; if (0.0 <= maxY) { labelBds = new DoubleRectangle(eltBds.x, Math.max(0.0, maxY - EXTRA_MARGIN), INITIAL_WIDTH, INITIAL_HEIGHT); } else { double maxX = Math.max(0.0, eltBds.x - INITIAL_WIDTH - MIN_MARGIN); labelBds = new DoubleRectangle(Math.max(0.0, maxX - EXTRA_MARGIN), eltBds.y, INITIAL_WIDTH, INITIAL_HEIGHT); } remoteLabel = new Widget(labelBds, WidgetType.Text); DefaultCmd.setAttribute(owningElt, demoStateMgr, WidgetAttributes.REMOTE_LABEL_ATTR, remoteLabel, interaction, editSequence); remoteLabel.setAttribute(WidgetAttributes.IS_STANDARD_ATTR, WidgetAttributes.IS_CUSTOM); remoteLabel.setAttribute(WidgetAttributes.REMOTE_LABEL_OWNER_ATTR, owningElt); remoteLabel.setName(generateUniqueWidgetName()); remoteLabel.setTitle(p.newText); // Add the new widget to the undo system and to the model editSequence.addEdit(addWidget(remoteLabel)); // Commit the edit editSequence.end(); // Only add this edit if it is significant if (editSequence.isSignificant()) { undoMgr.addEdit(editSequence); } } return true; } }; } // createSetRemoteLabelTextAction private IListenerAction createSetRemoteLabelTypeAction() { return new IListenerAction() { public Class<?> getParameterClass() { return FrameEditorUI.SetRemoteLabelTypeParms.class; } public boolean performAction(Object prms) { FrameEditorUI.SetRemoteLabelTypeParms p = (FrameEditorUI.SetRemoteLabelTypeParms) prms; if (! p.newType.canBeARemoteLabel()) { interaction.protestUnsupportedRemoteLabelType(); return false; } // TODO: xxy If changed to Noninteractive, remove transitions(!?) changeWidgetType(FrameEditorLID.SetRemoteLabelType, CHG_WIDGET_TYPE, p.selectedRemoteLabel, p.newType, undoMgr); return true; } }; } // createSetRemoteLabelTypeAction /** * Creates a new FrameEditorController instance for editing an existing * Frame instance. * * @param frame the Frame instance to edit. * @param design the Design containing the frame * @param project the Project containing the design * @return the Controller instance for editing the given Frame instance * @author mlh */ public static FrameEditorController openController(Frame frame, Design design, Project project) { FrameEditorController controller = (FrameEditorController) ControllerRegistry.ONLY.findOpenController(frame); // If already open, just bring it to front if (controller != null) { controller.takeFocus(); } else { controller = new FrameEditorController(frame, design, project); ControllerRegistry.ONLY.addOpenController(controller); } return controller; } }