/******************************************************************************* * CogTool Copyright Notice and Distribution Terms * CogTool 1.3, Copyright (c) 2005-2013 Carnegie Mellon University * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * CogTool is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * CogTool is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with CogTool; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * CogTool makes use of several third-party components, with the * following notices: * * Eclipse SWT version 3.448 * Eclipse GEF Draw2D version 3.2.1 * * Unless otherwise indicated, all Content made available by the Eclipse * Foundation is provided to you under the terms and conditions of the Eclipse * Public License Version 1.0 ("EPL"). A copy of the EPL is provided with this * Content and is also available at http://www.eclipse.org/legal/epl-v10.html. * * CLISP version 2.38 * * Copyright (c) Sam Steingold, Bruno Haible 2001-2006 * This software is distributed under the terms of the FSF Gnu Public License. * See COPYRIGHT file in clisp installation folder for more information. * * ACT-R 6.0 * * Copyright (c) 1998-2007 Dan Bothell, Mike Byrne, Christian Lebiere & * John R Anderson. * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * Apache Jakarta Commons-Lang 2.1 * * This product contains software developed by the Apache Software Foundation * (http://www.apache.org/) * * jopt-simple version 1.0 * * Copyright (c) 2004-2013 Paul R. Holser, Jr. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Mozilla XULRunner 1.9.0.5 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/. * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * The J2SE(TM) Java Runtime Environment version 5.0 * * Copyright 2009 Sun Microsystems, Inc., 4150 * Network Circle, Santa Clara, California 95054, U.S.A. All * rights reserved. U.S. * See the LICENSE file in the jre folder for more information. ******************************************************************************/ package edu.cmu.cs.hcii.cogtool.ui; import java.util.EventObject; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.events.ShellAdapter; import org.eclipse.swt.events.ShellEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import edu.cmu.cs.hcii.cogtool.CogTool; import edu.cmu.cs.hcii.cogtool.CogToolLID; import edu.cmu.cs.hcii.cogtool.FrameTemplateSupport; 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.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.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.uimodel.FrameEditorUIModel; import edu.cmu.cs.hcii.cogtool.uimodel.FrameEltGroupHalo; import edu.cmu.cs.hcii.cogtool.uimodel.FrameEltSelnFig; import edu.cmu.cs.hcii.cogtool.uimodel.FrameUIModel; import edu.cmu.cs.hcii.cogtool.uimodel.GraphicalChildWidget; import edu.cmu.cs.hcii.cogtool.uimodel.GraphicalContextMenu; import edu.cmu.cs.hcii.cogtool.uimodel.GraphicalGridButton; import edu.cmu.cs.hcii.cogtool.uimodel.GraphicalListItem; import edu.cmu.cs.hcii.cogtool.uimodel.GraphicalMenuHeader; import edu.cmu.cs.hcii.cogtool.uimodel.GraphicalMenuWidget; import edu.cmu.cs.hcii.cogtool.uimodel.GraphicalParentWidget; import edu.cmu.cs.hcii.cogtool.uimodel.GraphicalTraversableWidget; import edu.cmu.cs.hcii.cogtool.uimodel.GraphicalWidget; import edu.cmu.cs.hcii.cogtool.uimodel.GraphicalWidgetRenderer; import edu.cmu.cs.hcii.cogtool.util.ClipboardUtil; import edu.cmu.cs.hcii.cogtool.util.AlertHandler; import edu.cmu.cs.hcii.cogtool.util.IAttributed; import edu.cmu.cs.hcii.cogtool.util.L10N; import edu.cmu.cs.hcii.cogtool.util.ListenerIdentifier; import edu.cmu.cs.hcii.cogtool.util.ListenerIdentifierMap; import edu.cmu.cs.hcii.cogtool.util.MenuUtil; import edu.cmu.cs.hcii.cogtool.util.NameChangeAlert; import edu.cmu.cs.hcii.cogtool.util.NullSafe; import edu.cmu.cs.hcii.cogtool.util.OSUtils; import edu.cmu.cs.hcii.cogtool.util.PrecisionUtilities; import edu.cmu.cs.hcii.cogtool.util.SWTStringUtil; import edu.cmu.cs.hcii.cogtool.util.SWTTextEditor; import edu.cmu.cs.hcii.cogtool.util.StringUtil; import edu.cmu.cs.hcii.cogtool.util.TextEntryListener; import edu.cmu.cs.hcii.cogtool.util.UndoManager; import edu.cmu.cs.hcii.cogtool.util.WindowUtil; import edu.cmu.cs.hcii.cogtool.view.CogToolScalableFigure; import edu.cmu.cs.hcii.cogtool.view.FrameEditorView; import edu.cmu.cs.hcii.cogtool.view.InteractionDrawingEditor; import edu.cmu.cs.hcii.cogtool.view.InteractionFigure; import edu.cmu.cs.hcii.cogtool.view.MenuFactory; import edu.cmu.cs.hcii.cogtool.view.MoveHalo; import edu.cmu.cs.hcii.cogtool.view.PotentialFigure; import edu.cmu.cs.hcii.cogtool.view.RadioButtonSash; import edu.cmu.cs.hcii.cogtool.view.RemoteLinkage; import edu.cmu.cs.hcii.cogtool.view.ResizeThumb; import edu.cmu.cs.hcii.cogtool.view.ScalableInteractiveFigure; import edu.cmu.cs.hcii.cogtool.view.StandardDrawingEditor; import edu.cmu.cs.hcii.cogtool.view.View; /** * This class is responsible for dealing with all of the * Widgets that need to get displayed. * * Providing contextual menu support. * holding onto selection * * etc. * * @author alexeiser */ public class FrameEditorUI extends ZoomableUI { /** * preferred height of a label; used for editor for widget titles */ public static final int LABEL_HEIGHT = 15; /** * Hold onto the frame editor view. */ protected FrameEditorView view; /** * The interaction of the frame editor. * Used for displaying messages to the user. */ protected FrameEditorInteraction interaction; // Handle alerts from the model /** * Alert handler for changes in selected Widgets. */ protected AlertHandler widgetChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { updateView(); } }; /** * Hold onto selection for the standard state. */ protected FrameEditorSelectionState selection; /** * Hold onto context selection state. * Context is used specifically for the selection needed for contextual menu. */ protected FrameEditorSelectionState contextSelection; /** * Unions the given bds rectangle into the given base rectangle; * since the rectangle may get created, it always returns r (if not * null) or the new rectangle. */ protected Rectangle unionBounds(Rectangle r, Rectangle bds) { if (bds != null) { if (r == null) { r = new Rectangle(bds); } else { r.union(bds); } } return r; } public class ResizeHandles implements IUIFigure { /** * Top left will not be shown if the selection contains * an MenuItem, which anchors any resizing to the top-left */ protected boolean anchorTopLeft = false; /** * Resize thumb for Top Left */ protected ResizeThumb topLeftResize = null; /** * Resize thumb for Bottom Left */ protected ResizeThumb bottomLeftResize = null; /** * Resize thumb for Top Right */ protected ResizeThumb topRightResize = null; /** * Resize thumb for Bottom Right */ protected ResizeThumb bottomRightResize = null; public void setTopLeftAnchored(boolean anchored) { anchorTopLeft = anchored; } public boolean isTopLeftAnchored() { return anchorTopLeft; } /** * Get the complete area used by currently selected widgets, unzoomed. * * Can return null if no selected widgets. * @return */ protected Rectangle getSelectedElementFigureArea() { boolean anchored = false; // Use figures since they may be sized differently during dynamics // accumulate as unzoomed. Rectangle r = null; // Go through the list of selected widget figures, union the bounds. Iterator<FrameEltSelnFig<?>> iter = selection.getSelectedFigures(); while (iter.hasNext()) { FrameEltSelnFig<?> fig = iter.next(); if (fig instanceof GraphicalWidget<?>) { GraphicalWidget<?> gw = (GraphicalWidget<?>) fig; IWidget w = gw.getModel(); SimpleWidgetGroup wg = w.getParentGroup(); if (wg != null) { r = unionBounds(r, getGroupFigureBounds(wg)); anchored = anchored || (w instanceof ChildWidget); } else { r = unionBounds(r, gw.getBounds()); } } else if (fig instanceof FrameEltGroupHalo) { Rectangle haloBds = fig.getBounds(); // this is zoomed! double scale = frameUI.getZoom(); double right = (haloBds.x + haloBds.width) / scale; double bottom = (haloBds.y + haloBds.height) / scale; double left = haloBds.x / scale; double top = haloBds.y / scale; haloBds = PrecisionUtilities.getDraw2DRectangle(left, top, right - left, bottom - top); r = unionBounds(r, haloBds); } } setTopLeftAnchored(anchored); return r; } public void show() { // Only draw resize handles if there is one item selected or // if only child widgets with the same parent widget are selected. // This should improve performance, since a number of while loops // are avoided. if ((selection.getElementSelectionCount() != 1) && (selection.getSelectedWidgetsParent() == null)) { hide(); return; } // If the top left resize thumb is null, create all 4 here. if (topLeftResize == null) { topLeftResize = new ResizeThumb(FrameEditorUI.TOP_LEFT); topRightResize = new ResizeThumb(FrameEditorUI.TOP_RIGHT); bottomLeftResize = new ResizeThumb(FrameEditorUI.BOTTOM_LEFT); bottomRightResize = new ResizeThumb(FrameEditorUI.BOTTOM_RIGHT); // Add the thumbs to the interaction figure. InteractionDrawingEditor drawingEditor = view.getEditor(); drawingEditor.addInteractionFigure(topLeftResize); drawingEditor.addInteractionFigure(bottomLeftResize); drawingEditor.addInteractionFigure(topRightResize); drawingEditor.addInteractionFigure(bottomRightResize); } else if (! bottomRightResize.isVisible()) { topRightResize.setVisible(true); bottomLeftResize.setVisible(true); bottomRightResize.setVisible(true); } // this.anchorTopLeft gets set during getSelectedElementFigureArea() Rectangle r = getSelectedElementFigureArea(); // Top left will not be shown if the selection contains // an MenuItem, which anchors any resizing to the top-left; // computed by getSelectedWidgetFigureArea topLeftResize.setVisible(! anchorTopLeft); update(r); } /** * Given rectangle is unzoomed */ protected void update(Rectangle r) { if ((r != null) && (bottomRightResize != null) && bottomRightResize.isVisible()) { // Since the resize rects are not drawn in the zoomed area // calculate the positions in the unzoomed context double scale = frameUI.getZoom(); // Center on corners. These figures are not "scaled" they must // be positioned in SCALED location, not in 1:1 location. double right = r.x + r.width; double bottom = r.y + r.height; int x = PrecisionUtilities.round(r.x * scale); int y = PrecisionUtilities.round(r.y * scale); int width = PrecisionUtilities.round(right * scale) - x; int height = PrecisionUtilities.round(bottom * scale) - y; // Trigger a redraw on all handles. if (topLeftResize.isVisible()) { topLeftResize.centerAt(x, y); topLeftResize.repaint(); } bottomLeftResize.centerAt(x, y + height); bottomLeftResize.repaint(); topRightResize.centerAt(x + width, y); topRightResize.repaint(); bottomRightResize.centerAt(x + width, y + height); bottomRightResize.repaint(); } } public void update() { Rectangle r = getSelectedElementFigureArea(); update(r); } public void hide() { if (topLeftResize != null) { topLeftResize.setVisible(false); topRightResize.setVisible(false); bottomLeftResize.setVisible(false); bottomRightResize.setVisible(false); } } } /* * Each traversable widget potentially has an empty spot below and to the * right of it. Both, one, or none of these can be visible depending on * whether the menus are already filled out. */ public class PotentialFigures implements IUIFigure { /** * The figure that is selected that caused potential figures to be visible */ protected GraphicalTraversableWidget<?> potentialFigureOwner = null; /** * Potential next traversable widget to the right */ protected PotentialFigure rightPotentialFigure = new PotentialFigure(); /** * Potential next traversable widget below */ protected PotentialFigure bottomPotentialFigure = new PotentialFigure(); public void setFigureOwner(GraphicalTraversableWidget<?> gtw) { potentialFigureOwner = gtw; } public GraphicalTraversableWidget<?> getFigureOwner() { return potentialFigureOwner; } public PotentialFigure getRightFigure() { return rightPotentialFigure; } public PotentialFigure getBottomFigure() { return bottomPotentialFigure; } public void setSelection(IFigure selectFig) { if (selectFig == rightPotentialFigure) { rightPotentialFigure.setSelected(true); bottomPotentialFigure.setSelected(false); } else if (selectFig == bottomPotentialFigure) { rightPotentialFigure.setSelected(false); bottomPotentialFigure.setSelected(true); } else { rightPotentialFigure.setSelected(false); bottomPotentialFigure.setSelected(false); } } public void show() { if (potentialFigureOwner != null) { InteractionDrawingEditor editor = view.getEditor(); // Add as a scaled interaction figure so these size correctly // when the view is zoomed int pos = potentialFigureOwner.getPotentialFigures(); if ((pos & GraphicalTraversableWidget.DOWN) != 0) { bottomPotentialFigure.setVisible(true); bottomPotentialFigure.addToEditor(editor); } if ((pos & GraphicalTraversableWidget.RIGHT) != 0) { rightPotentialFigure.setVisible(true); rightPotentialFigure.addToEditor(editor); } update(); } else { hide(); } } // Extent is determined not by the selection but by the owner public void update() { if ((potentialFigureOwner != null) && (rightPotentialFigure.isVisible() || bottomPotentialFigure.isVisible())) { DoubleRectangle shapeBds = potentialFigureOwner.getModel().getEltBounds(); Rectangle bds = PrecisionUtilities.getDraw2DRectangle(shapeBds); if (rightPotentialFigure.isVisible()) { // Figure should appear to the right of the existing widget rightPotentialFigure.resetBounds(bds.x + bds.width, bds.y, bds.width, bds.height); } if (bottomPotentialFigure.isVisible()) { if (potentialFigureOwner instanceof GraphicalContextMenu) { bds.x = PrecisionUtilities.round(shapeBds.x + (0.5 * shapeBds.width)); bds.y = PrecisionUtilities.round(shapeBds.y + (0.5 * shapeBds.height)); bds.width = FrameEditorUI.MENU_ITEM_WIDTH; bds.height = FrameEditorUI.MENU_ITEM_HEIGHT; } else { if (potentialFigureOwner instanceof GraphicalMenuHeader) { bds.width = (int) Math.round(bds.width * FrameEditorUI.MENU_ITEM_RATIO); } // Figure should appear underneath existing widget bds.y += bds.height; IWidget owner = potentialFigureOwner.getModel(); Object isSep = owner.getAttribute(WidgetAttributes.IS_SEPARATOR_ATTR); if (NullSafe.equals(WidgetAttributes.IS_SEPARATOR, isSep)) { // current widget is a separator and the height of // the potential widget needs to be increased bds.height = (int) Math.round(bds.height * FrameEditorUI.SEPARATOR_RATIO); } } bottomPotentialFigure.resetBounds(bds); } } } public void hide() { rightPotentialFigure.setVisible(false); rightPotentialFigure.setSelected(false); bottomPotentialFigure.setVisible(false); bottomPotentialFigure.setSelected(false); } } public class GridSashes implements IUIFigure { protected GraphicalGridButton sashButton = null; /** * A sash for adding space above a grid button */ protected RadioButtonSash topGridSash = new RadioButtonSash(true); /** * A sash for adding space to the left of a grid button column */ protected RadioButtonSash leftGridSash = new RadioButtonSash(false); public void setSashButton(GraphicalGridButton button) { sashButton = button; } public GraphicalGridButton getSashButton() { return sashButton; } public void show() { if (sashButton != null) { InteractionDrawingEditor editor = view.getEditor(); GridButtonGroup gbg = (GridButtonGroup) sashButton.getModel().getParentGroup(); Rectangle r = sashButton.getBounds(); if (! PrecisionUtilities.withinEpsilon(r.y, gbg.getStartY(), GridButtonGroup.PIXEL_EPSILON)) { topGridSash.setActive(editor, r); } if (! PrecisionUtilities.withinEpsilon(r.x, gbg.getStartX(), GridButtonGroup.PIXEL_EPSILON)) { leftGridSash.setActive(editor, r); } } } // Extent is determined not by the selection but by the owner public void update() { if (sashButton != null) { Rectangle r = sashButton.getBounds(); if (topGridSash.isVisible()) { topGridSash.resetBounds(r); } if (leftGridSash.isVisible()) { leftGridSash.resetBounds(r); } } } public void hide() { topGridSash.setVisible(false); leftGridSash.setVisible(false); } } /* * Group selection finite state machine: * * For any given group and its members: * * UNGROUPED: multiple potential members selected * * NO group halo * - Group operation invoked > new state: GROUPED-SELECTED * * GROUPED-SELECTED: group is selected, * perhaps other elements selected too (no members though) * * YES group halo; SELECTED * * resize handles if the group is the only element selected * - Ungroup operation invoked > new state: UNGROUPED, all mbrs selected * - Group deselected > new state: GROUPED-UNSELECTED * - Member selected > new state: GROUPED-TENTATIVE * * GROUPED-UNSELECTED: group exists, but neither it nor a mbr is selected * * NO group halo * * NO resize handles * - Member selected > new state: GROUPED-TENTATIVE * * GROUPED-TENTATIVE: group exists, * it is not selected, but at least one of its mbrs is * * YES group halo; UNSELECTED (or TENTATIVE, if you prefer) * * NO resize handles (might be on the member!) * - All selected members deselected > new state: GROUPED-UNSELECTED * - Ungroup operation invoked > new state: UNGROUPED, all mbrs selected * - Group halo selected (SHIFT/CTRL allowed) > new state: GROUPED-SELECTED */ public class MoveHalos implements IUIFigure { /** * Stores the list of halos to be displayed. Either a widget * or widget group will map to a halo. */ protected Map<FrameElement, MoveHalo> moveHalos = new HashMap<FrameElement, MoveHalo>(); public MoveHalo getMoveHalo(IWidget w) { SimpleWidgetGroup group = w.getParentGroup(); FrameElement data = (group == null) ? w : group; Iterator<MoveHalo> halosIter = moveHalos.values().iterator(); while (halosIter.hasNext()) { MoveHalo halo = halosIter.next(); if (halo.getData() == data) { return halo; } } return null; } protected boolean setHaloExtent(MoveHalo halo, InteractionDrawingEditor drawingEditor) { FrameElement data = halo.getData(); Rectangle r; if (data instanceof IWidget) { // Use figure since it may be sized differently during dynamic ops GraphicalWidget<?> widgetFig = frameUI.getWidgetFigure((IWidget) data); r = widgetFig.getBounds(); } else if (data instanceof Association<?>) { r = getGroupFigureBounds((Association<?>) data); } else { r = null; } if (r != null) { halo.resetBounds(r); return true; } return false; } protected void setHaloVisible(MoveHalo halo, InteractionDrawingEditor drawingEditor) { if (setHaloExtent(halo, drawingEditor)) { halo.addToEditor(drawingEditor); } } protected void setHalosVisible() { InteractionDrawingEditor drawingEditor = view.getEditor(); Iterator<MoveHalo> halos = moveHalos.values().iterator(); while (halos.hasNext()) { MoveHalo halo = halos.next(); setHaloVisible(halo, drawingEditor); } } protected MoveHalo addHalo(FrameElement key) { MoveHalo halo = moveHalos.get(key); if (halo == null) { halo = new MoveHalo(key); moveHalos.put(key, halo); } return halo; } public MoveHalo ensureHalo(FrameElement key) { MoveHalo halo = addHalo(key); setHaloVisible(halo, view.getEditor()); return halo; } protected FrameEltGroupHalo addGroupHalo(FrameElementGroup eltGroup) { FrameEltGroupHalo halo = uiModel.getGroupHalo(eltGroup); moveHalos.put(eltGroup, halo); return halo; } // Halos are created for each selected item, not the selection as a whole public void show() { Iterator<FrameElement> elements = selection.getSelectedElementsIterator(); while (elements.hasNext()) { FrameElement elt = elements.next(); // For the moment, only works with selected widgets. if (elt instanceof IWidget) { IWidget w = (IWidget) elt; if (! (w instanceof ChildWidget)) { SimpleWidgetGroup group = w.getParentGroup(); FrameElement key = (group == null) ? w : group; addHalo(key); } } else if (elt instanceof FrameElementGroup) { FrameEltGroupHalo halo = addGroupHalo((FrameElementGroup) elt); halo.setSelected(true); } Iterator<FrameElementGroup> groups = elt.getRootElement().getEltGroups().iterator(); while (groups.hasNext()) { Association<?> assoc = groups.next(); if (assoc instanceof FrameElementGroup) { FrameEltGroupHalo halo = addGroupHalo((FrameElementGroup) assoc); halo.setSelected(selection.isElementSelected(assoc)); } } // Add frame element group halo for each selected remote label FrameElement owner = (FrameElement) elt.getAttribute(WidgetAttributes.REMOTE_LABEL_OWNER_ATTR); if ((owner != null) && (owner instanceof FrameElementGroup)) { FrameEltGroupHalo halo = addGroupHalo((FrameElementGroup) owner); halo.setSelected(selection.isElementSelected(owner)); } } setHalosVisible(); } public void update() { InteractionDrawingEditor drawingEditor = view.getEditor(); Iterator<MoveHalo> halos = moveHalos.values().iterator(); while (halos.hasNext()) { MoveHalo halo = halos.next(); setHaloExtent(halo, drawingEditor); } } public void hideHalo(MoveHalo halo) { view.getEditor().removeInteractionFigure(halo); moveHalos.remove(halo.getData()); } public void hide() { InteractionDrawingEditor drawingEditor = view.getEditor(); Iterator<MoveHalo> halos = moveHalos.values().iterator(); while (halos.hasNext()) { MoveHalo halo = halos.next(); drawingEditor.removeInteractionFigure(halo); } moveHalos.clear(); } } public class RemoteLabelLinkages implements IUIFigure { protected Map<FrameElement, RemoteLinkage> labelLinkages = new HashMap<FrameElement, RemoteLinkage>(); protected void showLinkages() { InteractionDrawingEditor drawingEditor = view.getEditor(); Iterator<RemoteLinkage> linkages = labelLinkages.values().iterator(); while (linkages.hasNext()) { RemoteLinkage linkage = linkages.next(); drawingEditor.getInteractionFigure().add(linkage, 2); } } public void show() { Iterator<FrameEltSelnFig<?>> elementFigs = selection.getSelectedFigures(); while (elementFigs.hasNext()) { FrameEltSelnFig<?> eltFig = elementFigs.next(); FrameElement elt = eltFig.getModel(); FrameElement owner = elt.getRemoteLabelOwner(); IWidget remoteLabel = null; GraphicalWidget<?> labelFig = null; // Recall that a remote label can never have a remote label if (owner != null) { remoteLabel = (IWidget) owner.getAttribute(WidgetAttributes.REMOTE_LABEL_ATTR); } else if (elt instanceof IWidget) { owner = (FrameElement) elt.getAttribute(WidgetAttributes.REMOTE_LABEL_OWNER_ATTR); if (owner != null) { remoteLabel = (IWidget) elt; labelFig = (GraphicalWidget<?>) eltFig; } } if (remoteLabel != null) { RemoteLinkage linkage = labelLinkages.get(owner); if (linkage == null) { IFigure ownerFig = null; if (labelFig == null) { labelFig = frameUI.getWidgetFigure(remoteLabel); } if (owner instanceof FrameElementGroup) { ownerFig = uiModel.getGroupHalo((FrameElementGroup) owner); } else if (owner instanceof SimpleWidgetGroup) { ownerFig = halosUIFig.ensureHalo(owner); } else if (owner instanceof IWidget) { if (owner == elt) { ownerFig = eltFig; } else { ownerFig = frameUI.getWidgetFigure((IWidget) owner); } } if (ownerFig != null) { linkage = new RemoteLinkage(ownerFig, owner, labelFig); labelLinkages.put(owner, linkage); } } } } showLinkages(); } // show public void update() { // nothing to do; they are connectors! } public void hide() { InteractionDrawingEditor drawingEditor = view.getEditor(); Iterator<RemoteLinkage> linkages = labelLinkages.values().iterator(); while (linkages.hasNext()) { RemoteLinkage linkage = linkages.next(); drawingEditor.getInteractionFigure().remove(linkage); } labelLinkages.clear(); } } protected ResizeHandles resizeHandlesUIFig = new ResizeHandles(); protected PotentialFigures potentialUIFig = new PotentialFigures(); protected GridSashes sashesUIFig = new GridSashes(); protected MoveHalos halosUIFig = new MoveHalos(); protected RemoteLabelLinkages linkagesUIFig = new RemoteLabelLinkages(); protected class WidgetTitleEditor extends SWTTextEditor { public WidgetTitleEditor() { super(view.getEditor().getSWTEditorSubstrate(), LEFT_JUSTIFY); } @Override public boolean confirm(int focusRule) { PotentialFigure bottomPotentialFigure = potentialUIFig.getBottomFigure(); confirmRenameFigure(); if ((focusRule == KEEP_FOCUS) && bottomPotentialFigure.isVisible()) { // Move selection to new potential child initiateRetitleFigure(bottomPotentialFigure); } else { cleanup(); } return true; } @Override protected TextEntryListener getEntryListener() { return new EntryListener() { @Override public void keyPressed(KeyEvent e) { PotentialFigure rightPotentialFigure = potentialUIFig.getRightFigure(); PotentialFigure bottomPotentialFigure = potentialUIFig.getBottomFigure(); if (e.keyCode == SWT.ARROW_LEFT) { // left/right always move cursor within the text area // Exception; figure is rightPotentialFigure and // the editor is empty. IFigure figure = (IFigure) getData(); if ((figure == rightPotentialFigure) && "".equals(getText())) { cleanup(); potentialUIFig.setSelection(null); } } else if (e.keyCode == SWT.ARROW_RIGHT) { // left and right always move cursor within the text area } else if (e.keyCode == SWT.ARROW_DOWN) { IFigure figure = (IFigure) getData(); // Commit current text editor changes. confirm(KEEP_FOCUS); if (figure instanceof GraphicalTraversableWidget<?>) { GraphicalTraversableWidget<?> travFigure = (GraphicalTraversableWidget<?>) figure; IFigure toSelect = travFigure.selectDown(); if (toSelect != travFigure) { if (toSelect == null) { toSelect = bottomPotentialFigure; setUpUISupport(travFigure); } else { selection.deselectSelnFig(travFigure); } selectFigure(toSelect); } } } else if (e.keyCode == SWT.ARROW_UP) { IFigure figure = (IFigure) getData(); // Commit current text editor changes. confirm(KEEP_FOCUS); if (figure == bottomPotentialFigure) { // up always returns to owner, if it's bottom cleanup(); potentialUIFig.setSelection(null); } else if (figure instanceof GraphicalTraversableWidget<?>) { GraphicalTraversableWidget<?> travFigure = (GraphicalTraversableWidget<?>) figure; IFigure toSelect = travFigure.selectUp(); // Nowhere to go if null returned; simply stay! if ((toSelect != null) && (toSelect != travFigure)) { selection.deselectSelnFig(travFigure); selectFigure(toSelect); } } } else { super.keyPressed(e); } delayedRepainting.doWork(); } }; } public void editTitle(IFigure widgetFigure) { String text = ""; if (widgetFigure instanceof GraphicalWidget<?>) { GraphicalWidget<?> widget = (GraphicalWidget<?>) widgetFigure; text = widget.getModel().getTitle(); } editText(text, widgetFigure, frameUI.getZoom()); } @Override public void editText(String initialText, IFigure data, double scale) { super.editText(initialText, data, scale); potentialUIFig.setSelection(data); } @Override protected int computeY(Rectangle bounds, double scale, int offset) { double o = ((bounds.height - LABEL_HEIGHT) * scale) / 2.0; return PrecisionUtilities.round((bounds.y * scale) + o); } // TODO this is a temporary kludge so stuff looks the right height // for Bonnie's next demo, but needs to be fixed better long // term (check with Mike!) @Override protected int computeHeight(Rectangle bounds, double scale, int offset) { return PrecisionUtilities.round((LABEL_HEIGHT + 2 /*+ (2 * offset) */) * scale); } @Override protected Font getFontToUse() { IFigure widgetFigure = (IFigure) getData(); return widgetFigure.getFont(); } } /** * Parameters used for changing string based properties of widgets. * Often passed to the controller as the parameter of an LID. * @author alexeiser * */ public static class ActionStringParameters { /** * The string to set for the selection */ public String newString; /** * the selection currently being changed. * Maybe standard or context. */ public FrameEditorSelectionState selection; /** * Indicating whether the new string is the separator indicator */ public boolean isSeparator; public ActionStringParameters(String str, FrameEditorSelectionState s) { this(str, s, false); } public ActionStringParameters(String str, FrameEditorSelectionState s, boolean isSep) { newString = str; selection = s; isSeparator = isSep; } } /** * Standard parameter used for passing information to the Controller. * Specifies an offset in doubles to move by. * @author alexeiser * */ public static class MoveParameters { /** * the X, Y values to move the currently selected objects by */ public double moveByX; public double moveByY; /** * The selection, may be context or default. */ public FrameEditorSelectionState selection; /** * If true, selected widgets in groups are moved as a group; otherwise, * only the widgets in selection are moved. */ public boolean moveAsGroup; /** * The constructor for the move command. * @param dx * @param dy * @param s */ public MoveParameters(double dx, double dy, FrameEditorSelectionState s) { this(dx, dy, s, true); } public MoveParameters(double dx, double dy, FrameEditorSelectionState s, boolean moveGrp) { moveByX = dx; moveByY = dy; selection = s; moveAsGroup = moveGrp; } } /** * Standard parameter used for passing information to the Controller. * Specifies information needed to move widgets in widget groups around. * @author rmyers */ public static class ReorderWidgetParameters { /** * The last clicked widget that was dragged */ public IWidget reorderWidget; /** * The group to insert the widget into */ public SimpleWidgetGroup widgetGroup; /** * The index in the group at which the widget should be added */ public int insertIndex; /** * If widget is a child widget, this will be its new parent */ public AParentWidget parent; public ReorderWidgetParameters() { this(null, null, -1, null); } /** * The constructor for the drag and drop command. */ public ReorderWidgetParameters(IWidget widget, SimpleWidgetGroup group, int index, AParentWidget newParent) { reorderWidget = widget; widgetGroup = group; insertIndex = index; parent = newParent; } public boolean requiresTarget() { return true; } } /** * A special case of the reorder interaction; the control-drag method of * duplicating widgets also needs much of the same information. If the * widget is dragged off into space as opposed to part of a parent group, * the change in x and y is required instead. * @author rmyers */ public static class InsertDuplicateParameters extends ReorderWidgetParameters { /** * the X, Y values to move the widget by */ public double moveByX = 0.0; public double moveByY = 0.0; public InsertDuplicateParameters() { super(); } public InsertDuplicateParameters(IWidget widget, SimpleWidgetGroup group, int index, AParentWidget newParent, double x, double y) { super(widget, group, index, newParent); moveByX = x; moveByY = y; } @Override public boolean requiresTarget() { return false; } } /** * The LID parameter object dictating a change in the rendered property */ public static class SetRenderSkinParameters { /** * Flag controlling if the selection should be set to rendered * or unrendered */ public boolean rendered; /** * The selection to effect. */ public FrameEditorSelectionState selection; public SetRenderSkinParameters(boolean renderSkin, FrameEditorSelectionState seln) { rendered = renderSkin; selection = seln; } } /** * Standard parameter used when duplicating widgets. * @author alexeiser * */ public static class DuplicateParameters { /** * the X, Y values to move the currently selected objects by */ public double moveByX; public double moveByY; /** * The selection, may be context or default. */ public FrameEditorSelectionState selection; /** * The constructor for the move command. * @param pt * @param s */ public DuplicateParameters(double dx, double dy, FrameEditorSelectionState s) { moveByX = dx; moveByY = dy; selection = s; } } /** * The resize Parameters defined to resize a widget in the controller. * The fixed corner specifies the resulting fixed point of the resize * The moved corner is the new position of one of the corners */ public static class ResizeParameters { /** * The old top left of the resize region */ public double oldX; public double oldY; /** * The new top left of the resize region */ public double newX; public double newY; /** * The ratios for the resized width/height */ public double ratioX; public double ratioY; /** * The selection object for the resize. */ public FrameEditorSelectionState selection; public ResizeParameters(double origX, double origY, double lastX, double lastY, double rX, double rY, FrameEditorSelectionState s) { oldX = origX; oldY = origY; newX = lastX; newY = lastY; ratioX = rX; ratioY = rY; selection = s; } } /** * The parameters used for shape changes. */ public static class ShapeChangeParameters { /** * The new shape type for the selection */ public ShapeType newShapeType; /** * The selection controlling which objects to change */ public FrameEditorSelectionState selection; public ShapeChangeParameters(ShapeType shapeType, FrameEditorSelectionState s) { newShapeType = shapeType; selection = s; } } // For renaming a FrameElementGroup public static class EltGroupRenameParameters { public FrameElementGroup eltGroup; public String newName; public EltGroupRenameParameters(FrameElementGroup groupToRename, String newEltGroupName) { eltGroup = groupToRename; newName = newEltGroupName; } } /** * The LID parameter class dictating changed parameter types. */ public static class TypeChangeParameters { /** * the new widget type for the selection */ public WidgetType newWidgetType; /** * selected Widgets */ public FrameEditorSelectionState selection; public TypeChangeParameters(WidgetType widgetType, FrameEditorSelectionState s) { newWidgetType = widgetType; selection = s; } } /** * Parameters needed to create new widgets */ public static class NewWidgetParameters { /** * The rectangle defining the shape of the widget */ public DoubleRectangle bounds; /** * Only applicable for child items; they need to have a reference to * their parent, or at least the list of items that they are a part of. */ public AParentWidget parent; /** * Only applicable for traversable items; this indicates the type of * the new widget to be created */ public WidgetType type; /** * true if widget should use traversable Widget subclasses, false if * it should use generic Widget class */ public boolean isAutomatic; /** * The widget title, may be null */ public String widgetTitle; /** * Only applicable for menu headers, list box items, and radio buttons; * this is the widget group that the new widget should add itself to. */ public SimpleWidgetGroup parentGroup; /** * If true, makes the new widget into a non-interactive separator. */ public boolean isSeparator; public NewWidgetParameters(DoubleRectangle shape, WidgetType wt, boolean auto) { this(shape, null, wt, auto, null, null, false); } public NewWidgetParameters(DoubleRectangle shape, AParentWidget parentWidget, WidgetType wt, boolean auto, String title, SimpleWidgetGroup group, boolean sep) { bounds = shape; parent = parentWidget; type = wt; isAutomatic = auto; widgetTitle = title; parentGroup = group; isSeparator = sep; } } public static class PasteBackgroundImageParms { public FrameEditorSelectionState selection; public byte[] imageData; public PasteBackgroundImageParms(FrameEditorSelectionState seln, byte[] imgData) { selection = seln; imageData = imgData; } } public static class CopyImageAsBackgroundParms { public byte[] imageData; public IWidget selectedWidget; public CopyImageAsBackgroundParms(byte[] imgData, IWidget w) { imageData = imgData; selectedWidget = w; } } public static class SetRemoteLabelTextParms { public String newText; public FrameElement selectedElement; public SetRemoteLabelTextParms(String text, FrameElement elt) { newText = text; selectedElement = elt; } } public static class SetRemoteLabelTypeParms { // Can be only: Text, Link, Checkbox, Noninteractive public WidgetType newType; public IWidget selectedRemoteLabel; public SetRemoteLabelTypeParms(WidgetType type, IWidget w) { newType = type; selectedRemoteLabel = w; } } protected WidgetTitleEditor editor = null; protected FrameEditorMouseState mouseState; protected FrameEditorUIModel uiModel; protected Frame frame; // cached from uiModel! protected Design design; // cached from uiModel! protected FrameUIModel frameUI; // cached from uiModel! protected DelayedSelection delayedWidgetSelection; // Constants for delayedRepainting; powers of 2! protected static final int REPAINT_SELECT_HANDLES = 1; protected static final int REPAINT_EDITOR = 2; protected static final int RESET_VISIBLE_AREA = 4; // protected static final int REPAINT_VIEW = 16; protected static final int ZOOM_REPAINT = REPAINT_SELECT_HANDLES | REPAINT_EDITOR; protected static final int SHAPE_CHANGE_REPAINT = REPAINT_SELECT_HANDLES | RESET_VISIBLE_AREA; protected static final int REPAINT_ALL = DelayedRepaint.REPAINT_ALL; protected DelayedRepaint delayedRepainting; public static final double SEPARATOR_RATIO = 3.5; public static final double MENU_ITEM_RATIO = 1.3; public static final int MENU_ITEM_HEIGHT = 30; // Constants for initial menu item size public static final int MENU_ITEM_WIDTH = 100; public static final int BOTTOM_RIGHT = 3; public static final int TOP_RIGHT = 2; public static final int BOTTOM_LEFT = 1; // Constants for GraphicalWidget resize thumb positions public static final int TOP_LEFT = 0; protected static final String FRAME_PREFIX = L10N.get("WT.FramePrefix", "Frame"); protected static final String WIDGET_LABEL = L10N.get("WT.WidgetLabel", "Widget"); protected static final String WIDGETS_LABEL = L10N.get("WT.WidgetsLabel", "Widgets"); protected static final String SELECT_ALL_WIDGETS = L10N.get("DE.SelectAllWidgets", "Select All Widgets"); protected static String buildWindowMenuLabel(Design design, Frame frame) { String designName = SWTStringUtil.insertEllipsis(design.getName(), StringUtil.EQUAL, SWTStringUtil.DEFAULT_FONT); String frameName = SWTStringUtil.insertEllipsis(frame.getName(), StringUtil.NO_FRONT, SWTStringUtil.DEFAULT_FONT); return FRAME_PREFIX + ": " + designName + " > " + frameName; } /** * Create an interactive FrameEditor View. Creates a window. */ public FrameEditorUI(Frame modelFrame, Design modelDesign, Project modelProject, UndoManager undoMgr) { super(modelProject, buildWindowMenuLabel(modelDesign, modelFrame), buildLeadItems(modelProject, modelDesign), undoMgr); // Create the selection listeners selection = new FrameEditorSelectionState(); contextSelection = new FrameEditorSelectionState(); delayedWidgetSelection = new DelayedSelection(selection) { @Override protected void selectItem(Object item) { FrameEltSelnFig<?> fig = (FrameEltSelnFig<?>) item; if (fig instanceof GraphicalWidget<?>) { GraphicalWidget<?> gw = (GraphicalWidget<?>) item; selection.selectSelnFig(fig); // TODO: Is this the right trigger for initiating rename? if (selection.getWidgetSelectionCount() == 1) { if (gw.getModel().getTitle() == "") { initiateRetitleFigure(gw); } } else { cleanupFigureLabelEditor(); } } if (fig instanceof FrameEltGroupHalo) { selection.selectSelnFig(fig); } } }; delayedRepainting = new DelayedRepaint() { @Override protected void performRepaint() { refreshUISupport(); // Update the SWT view with any changes as needed. // For example selection changes. if (! view.isDisposed()) { updateView(); } } @Override public void doWork() { super.doWork(); undoMgrViewHandler.resetView(undoManager); // Update the enabled items selection state. setViewEnabledState(selection, ListenerIdentifierMap.NORMAL); if (selection.getWidgetSelectionCount() == 0) { view.updateFrameProperties(frame, false); } } }; CogTool.selectionPhase.addDelayedWork(delayedWidgetSelection); CogTool.repaintPhase.addDelayedWork(delayedRepainting); uiModel = new FrameEditorUIModel(modelFrame, modelDesign, modelProject, getWindowZoom(modelFrame), widgetChangeHandler); frame = modelFrame; design = modelDesign; frameUI = uiModel.getFrameUI(); // Create the interaction stuff mouseState = new FrameEditorMouseState(this); // create the device types. int deviceTypes = DeviceType.buildDeviceSet(design.getDeviceTypes()); // Generate the frame editor view. view = new FrameEditorView(deviceTypes, lIDMap, this, menuData, uiModel, mouseState, mouseState, selection, this, getWindowLocation()); view.updateFrameProperties(frame, true); SelectionListener treeListener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent evt) { selection.deselectAll(); Tree t = (Tree) evt.getSource(); TreeItem[] items = t.getSelection(); for (TreeItem item : items) { Object itemData = item.getData(); if (itemData instanceof FrameElement) { FrameEltSelnFig<?> fig = getElementFigure((FrameElement) itemData); if (fig != null) { selection.selectSelnFig(fig); centerSelectedRegion(); } } } mouseState.cleanup(); } }; view.getFramePropertiesPane().setTreeListener(treeListener); view.getEltGroupPropertiesPane().setTreeListener(treeListener); SelectionListener selnListener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent evt) { if (selection.getElementSelectionCount() == 1) { FrameElement selected = selection.getSelectedIFrameElements()[0]; FrameElement remoteLabelOwner = (FrameElement) selected.getAttribute(WidgetAttributes.REMOTE_LABEL_OWNER_ATTR); // Check if the selected element is a remote label if (remoteLabelOwner != null) { FrameEltSelnFig<?> fig = getElementFigure(remoteLabelOwner); // If so, select the owner and re-center if (fig != null) { selection.setSelectedSelnFig(fig); centerSelectedRegion(); CogTool.delayedWorkMgr.doDelayedWork(true); } } else { remoteLabelOwner = selected.getRemoteLabelOwner(); // Should always be the case, but check to ensure // the selected element can have a remote label if (remoteLabelOwner != null) { IWidget remoteLabel = (IWidget) remoteLabelOwner.getAttribute(WidgetAttributes.REMOTE_LABEL_ATTR); if (remoteLabel != null) { FrameEltSelnFig<?> fig = frameUI.getWidgetFigure(remoteLabel); if (fig != null) { selection.setSelectedSelnFig(fig); centerSelectedRegion(); CogTool.delayedWorkMgr.doDelayedWork(true); } } } } } } }; view.getWidgetPropertiesPane().setSelectionListener(selnListener); view.getEltGroupPropertiesPane().setSelectionListener(selnListener); InteractionDrawingEditor drawingEditor = view.getEditor(); setZoomEditor(drawingEditor); // get the editor substrate: IE: the Draw2d canvas. Canvas editorSubstrate = drawingEditor.getSWTEditorSubstrate(); // Add menu detection link to mouseState editorSubstrate.addListener(SWT.MenuDetect, mouseState); setUpDropImageSupport(editorSubstrate); // Restore zoom level restoreZoom(); // Update the window title with the project & design updateTitle(); // create the Frame editor Interaction interaction = new FrameEditorInteraction(view); // Please make sure init is always the last call in the constructor init(); } // ctor protected FrameEltSelnFig<?> getElementFigure(FrameElement elt) { if (elt instanceof FrameElementGroup) { return uiModel.getGroupHalo((FrameElementGroup) elt); } if (elt instanceof IWidget) { return frameUI.getWidgetFigure((IWidget) elt); } if (elt instanceof SimpleWidgetGroup) { Iterator<IWidget> topLevelMbrs = ((SimpleWidgetGroup) elt).iterator(); if (topLevelMbrs.hasNext()) { return frameUI.getWidgetFigure(topLevelMbrs.next()); } } return null; } public void hideAllChildren() { frameUI.hideAllChildren(); } protected GraphicalParentWidget<?, ?> getAsParent(GraphicalTraversableWidget<?> gw) { if ((gw instanceof GraphicalParentWidget<?, ?>) && ((GraphicalParentWidget<?, ?>) gw).canHaveChildren()) { return (GraphicalParentWidget<?, ?>) gw; } if (gw instanceof GraphicalChildWidget<?, ?>) { return ((GraphicalChildWidget<?, ?>) gw).getParentFigure(); } return null; } protected GraphicalParentWidget<?, ?> getSelectedParentWidget() { if (selection.getWidgetSelectionCount() == 1) { Iterator<GraphicalWidget<?>> selectedParent = selection.getSelectedWidgetFigures(); if (selectedParent.hasNext()) { GraphicalWidget<?> gw = selectedParent.next(); if (gw instanceof GraphicalTraversableWidget<?>) { return getAsParent((GraphicalTraversableWidget<?>) gw); } } } return null; } protected void openChildren(GraphicalParentWidget<?, ?> parentFig) { if (parentFig != null) { parentFig.openChildren(); } } public void openChildren() { GraphicalParentWidget<?, ?> parentFig = getSelectedParentWidget(); openChildren(parentFig); } public void hideNonhaloSupport(boolean hidePotential, boolean hideChildren, GraphicalTraversableWidget<?> currentFig) { resizeHandlesUIFig.hide(); sashesUIFig.hide(); if (hidePotential) { potentialUIFig.hide(); } else if (currentFig != potentialUIFig.getFigureOwner()) { potentialUIFig.hide(); potentialUIFig.setFigureOwner(currentFig); potentialUIFig.show(); } if (hideChildren) { hideAllChildren(); } } public void hideNondynamicSupport(boolean hideSashes) { hideNondynamicSupport(hideSashes, true); } public void hideNondynamicSupport(boolean hideSashes, boolean hideChildren) { if (hideSashes) { sashesUIFig.hide(); } potentialUIFig.hide(); if (hideChildren) { hideAllChildren(); } } public void clearUISupport(boolean resetOwners) { resizeHandlesUIFig.hide(); halosUIFig.hide(); sashesUIFig.hide(); potentialUIFig.hide(); linkagesUIFig.hide(); if (resetOwners) { sashesUIFig.setSashButton(null); potentialUIFig.setFigureOwner(null); cleanupFigureLabelEditor(); } hideAllChildren(); } public void setUpUISupport(GraphicalTraversableWidget<?> selected) { potentialUIFig.setFigureOwner(selected); if (selected instanceof GraphicalGridButton) { sashesUIFig.setSashButton((GraphicalGridButton) selected); } else { sashesUIFig.setSashButton(null); } // Draw potential figures first, then sashes, halos, and resize handles potentialUIFig.show(); sashesUIFig.show(); halosUIFig.show(); resizeHandlesUIFig.show(); linkagesUIFig.show(); openChildren(getAsParent(selected)); repaintEditor(); resetVisibleArea(); } public void refreshUISupport() { clearUISupport(false); GraphicalTraversableWidget<?> selected = null; if (selection.getWidgetSelectionCount() == 1) { Iterator<GraphicalWidget<?>> selectedParent = selection.getSelectedWidgetFigures(); if (selectedParent.hasNext()) { GraphicalWidget<?> gw = selectedParent.next(); if (gw instanceof GraphicalTraversableWidget<?>) { selected = (GraphicalTraversableWidget<?>) gw; } } } setUpUISupport(selected); } // refreshUISupport public void updateUISupport() { view.getEditor().getLWS().getUpdateManager().performUpdate(); // Draw potential figures first, then sashes, halos, and resize handles potentialUIFig.update(); sashesUIFig.update(); halosUIFig.update(); linkagesUIFig.update(); if (! mouseState.isReordering()) { resizeHandlesUIFig.update(); } } protected void setUpDropImageSupport(final Canvas editorSubstrate) { ExternalImageDropTarget dropTarget = new ExternalImageDropTarget() { protected GraphicalWidget<?> dropTargetWidget = null; @Override protected void cancelHighlight(DropTargetEvent evt) { if (dropTargetWidget != null) { dropTargetWidget.dynamicHighlight(false); } } @Override protected void highlight(DropTargetEvent evt) { org.eclipse.swt.graphics.Point pt = editorSubstrate.toControl(evt.x, evt.y); GraphicalWidget<?> widgetFig = widgetLocatedAtXY(pt.x, pt.y); if (dropTargetWidget != widgetFig) { cancelHighlight(evt); if (widgetFig != null) { widgetFig.dynamicHighlight(true); } dropTargetWidget = widgetFig; } } @Override protected Object buildParameters(DropTargetEvent evt, byte[] imgData) { org.eclipse.swt.graphics.Point pt = editorSubstrate.toControl(evt.x, evt.y); GraphicalWidget<?> widgetFig = widgetLocatedAtXY(pt.x, pt.y); IWidget selectedWidget = (widgetFig != null) ? widgetFig.getModel() : null; return new FrameEditorUI.CopyImageAsBackgroundParms(imgData, selectedWidget); } }; setUpDropImage(editorSubstrate, dropTarget); } /** * Add the important event listeners to the contents. * * Includes both the Mouse & Mouse Motion listeners as well as the Keyboard * */ protected void addEventListeners() { // Add them to the contents. InteractionFigure interactionLayer = view.getEditor().getInteractionFigure(); // let mouseState handle delete key events interactionLayer.addKeyListener(mouseState); getShell().addShellListener(new ShellAdapter() { @Override public void shellDeactivated(ShellEvent e) { mouseState.cancelDynamicOperation(); } }); } /** * "Scrolls" the given group up (MUST REVIEW WHEN FINALLY DONE) */ public void scrollListBoxUp(SimpleWidgetGroup group) { Integer value = (Integer) group.getAttribute(WidgetAttributes.NUM_VISIBLE_ATTR); int num = value.intValue(); if (group.size() > num) { IWidget top = (IWidget) group.getAttribute(WidgetAttributes.FIRST_VISIBLE_ATTR); int index = group.indexOf(top); if (index == 0) { return; } frameUI.getWidgetFigure(top).setVisible(false); double height = top.getEltBounds().height; for (int i = index + 1; i < index + num; i++) { group.get(i).moveElement(0, -height); } } } /** * Add the selection Change event listeners. */ protected void addSelectionChangeListeners() { AlertHandler widgetSelectionHandler = new AlertHandler() { public void handleAlert(EventObject alert) { FrameEditorSelectionState.SelectionChange evt = (FrameEditorSelectionState.SelectionChange) alert; if (evt != null) { // Set the state of corresponding graphical widgets GraphicalWidget<?> gw; // Alter state of all widgets in selection if (evt.changedElement == null) { Iterator<GraphicalWidget<?>> widgetFigs = ((FrameEditorSelectionState) evt.getSource()).getSelectedWidgetFigures(); while (widgetFigs.hasNext()) { gw = widgetFigs.next(); gw.setSelected(evt.selected); } } else if (evt.changedElement instanceof GraphicalWidget<?>) { // If an object was passed on the event, set its // selected state to the event's selected state gw = (GraphicalWidget<?>) evt.changedElement; if (frame.getWidgets().contains(gw.getModel())) { gw.setSelected(evt.selected); } } } // Repaint the frame contents // This causes the resize handles to be redrawn. // Also causes the entire frame to be "refreshed" delayedRepainting.requestRepaint(REPAINT_ALL); // Add the following back when another window's actions // can cause changes to a Frame Editor's selection state // (such as something that deletes a widget) // updateView(); } // handleAlert }; selection.addHandler(this, FrameEditorSelectionState.SelectionChange.class, widgetSelectionHandler); } // addSelectionChangeListeners // TODO: Currently works only for IWidget protected void removeHalo(IWidget w) { MoveHalo halo = halosUIFig.getMoveHalo(w); if (halo != null) { SimpleWidgetGroup group = w.getParentGroup(); if (group != null) { IWidget[] widgets = selection.getSelectedIWidgets(); // Avoid removing if another sibling/cousin is selected for (IWidget widget : widgets) { if ((widget.getParentGroup() == group) && (widget != w)) { return; } } } halosUIFig.hideHalo(halo); } } /** * Registers a listener that enables/disables the * "remove background image" menu item as necessary. * * Also enable or disabled the capture Background button. * TODO: when capture background becomes a menu item, add to setEnabled */ protected void addBackgroundImageHandler() { AlertHandler backgroundEnabler = new AlertHandler() { public void handleAlert(EventObject alert) { // Checks to see if the frame has a background. // if so enable remove background and capture background // buttons. boolean enable = (frame.getBackgroundImage() == null) ? MenuUtil.DISABLED : MenuUtil.ENABLED; // Enable or disabled the remove background menu item setEnabled(CogToolLID.RemoveBackgroundImage, ListenerIdentifierMap.ALL, enable); // Enable or disabled the capture background button view.setIsBackgroundAvailable(enable); } }; frame.addHandler(this, Frame.BackgroundImageChange.class, backgroundEnabler); } /** * Registers a listener that adjusts selection state in response to * widget additions or removals */ protected void addFrameWidgetHandler() { AlertHandler frameWidgetHandler = new AlertHandler() { public void handleAlert(EventObject alert) { Frame.WidgetChange chg = (Frame.WidgetChange) alert; IWidget chgWidget = chg.getChangeElement(); if (chg != null) { // Check what action was performed switch (chg.action) { // Switch the selection to the newly-added widget case Frame.WidgetChange.ELEMENT_ADD: { GraphicalWidget<?> gw = frameUI.getWidgetFigure(chgWidget); gw.addChangeHandler(widgetChangeHandler, Widget.WidgetChange.class); gw.addChangeHandler(widgetChangeHandler, IAttributed.AttributeChange.class); gw.addChangeHandler(widgetChangeHandler, IAttributed.AuthorityChange.class); delayedWidgetSelection.addToSelection(chgWidget, gw); view.requestRename(); break; } // Reflect the change in selection state case Frame.WidgetChange.ELEMENT_DELETE: { // Cannot depend on the frameUI to have removed // this widget's graphical version. // Call deselect with the model itself. selection.deselectElement(chgWidget); delayedWidgetSelection.removeFromSelection(chgWidget); // call to clean up any resize handles. delayedRepainting.requestRepaint(REPAINT_SELECT_HANDLES); break; } } } } }; frame.addHandler(this, Frame.WidgetChange.class, frameWidgetHandler); AlertHandler frameEltHandler = new AlertHandler() { public void handleAlert(EventObject alert) { delayedRepainting.requestRepaint(REPAINT_SELECT_HANDLES); } }; // Do the above for both widget and association changes! frame.addHandler(this, Frame.ElementChange.class, frameEltHandler); AlertHandler frameAssocHandler = new AlertHandler() { public void handleAlert(EventObject alert) { Frame.FrameEltGrpChange chg = (Frame.FrameEltGrpChange) alert; if (chg.action == Frame.FrameEltGrpChange.ELEMENT_DELETE) { selection.deselectElement(chg.getChangeElement()); } else if (chg.action == Frame.FrameEltGrpChange.ELEMENT_ADD) { selection.setSelectedSelnFig(uiModel.getGroupHalo(chg.getChangeElement())); } } }; frame.addHandler(this, Frame.FrameEltGrpChange.class, frameAssocHandler); } public void resetVisibleArea() { // TODO:mlh at least on PC, performUpdate screws up previous locn assignment StandardDrawingEditor e = view.getEditor(); e.getLWS().getUpdateManager().performUpdate(); DoubleSize extent = frameUI.getPreferredSize(); e.setMinVisibleArea(PrecisionUtilities.round(extent.width), PrecisionUtilities.round(extent.height), false); } /** * Registers a listener that redraws the frame in response to changes in * the appearance of widgets * * When this event occurs, resize handles are redrawn, the window's size is * recomputed, and the display updated. * */ protected void addWidgetShapeChangeHandler() { AlertHandler widgetTitleChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { FrameUIModel.WidgetTitleChange chg = (FrameUIModel.WidgetTitleChange) alert; // chg.widget may be the remote label of the currently // selected widget if (selection.getElementSelectionCount() == 1) { FrameElement elt = selection.getSelectedIFrameElements()[0]; if (chg.widget == elt.getAttribute(WidgetAttributes.REMOTE_LABEL_ATTR)) { view.setRemoteLabelText(chg.widget.getTitle()); } } } }; frameUI.addHandler(this, FrameUIModel.WidgetTitleChange.class, widgetTitleChangeHandler); AlertHandler widgetShapeChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { delayedRepainting.requestRepaint(SHAPE_CHANGE_REPAINT); // a FULL repaint is probably not needed } }; frameUI.addHandler(this, FrameUIModel.WidgetShapeImageChange.class, widgetShapeChangeHandler); AlertHandler widgetGroupChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { FrameUIModel.WidgetGroupChange evt = (FrameUIModel.WidgetGroupChange) alert; if (evt.isAdd) { GraphicalWidget<?> gw = frameUI.getWidgetFigure(evt.widget); delayedWidgetSelection.addToSelection(evt.widget, gw); } else { delayedWidgetSelection.removeFromSelection(evt.widget); SimpleWidgetGroup group = evt.widget.getParentGroup(); if (group.size() == 0) { //evt.widget was the last widget in this group, //so hide the halo for it removeHalo(evt.widget); } } } }; frameUI.addHandler(this, FrameUIModel.WidgetGroupChange.class, widgetGroupChangeHandler); } /** * Set up this object for editing widgets. * This method should be called from within the constructor. */ private void init() { // Add listeners to the view addEventListeners(); addSelectionChangeListeners(); // Add listeners to the model addBackgroundImageHandler(); addFrameWidgetHandler(); addWidgetShapeChangeHandler(); project.addHandler(this, Project.DesignChange.class, new AlertHandler() { public void handleAlert(EventObject alert) { Project.DesignChange chg = (Project.DesignChange) alert; if ((! chg.isAdd) && (chg.element == design)) { closeOpenController(); } } }); design.addHandler(this, Design.FrameChange.class, new AlertHandler() { public void handleAlert(EventObject alert) { Design.FrameChange chg = (Design.FrameChange) alert; if ((! chg.isAdd) && (chg.element == frame)) { closeOpenController(); } } }); design.addHandler(this, Design.DeviceTypeChange.class, new AlertHandler() { public void handleAlert(EventObject alert) { Set<DeviceType> dts = design.getDeviceTypes(); int deviceTypes = DeviceType.buildDeviceSet(dts); view.resetDeviceTypes(deviceTypes); } }); // Add listeners to rename events on project and design frame.addHandler(this, NameChangeAlert.class, renameHandler); design.addHandler(this, NameChangeAlert.class, renameHandler); // Some items should always be enabled. // Should be the last call in the constructor setInitiallyEnabled(true); } /** * Update the window's title. * This sets the title to the project > Design > Frame notation. */ @Override protected void updateTitle() { String frameName = SWTStringUtil.insertEllipsis(frame.getName(), StringUtil.NO_FRONT, SWTStringUtil.DEFAULT_FONT); view.setWindowTitle(modificationFlag + FRAME_PREFIX + ": " + project.getName() + " > " + design.getName() + " > " + frameName + ((OSUtils.MACOSX) ? "" : UI.WINDOW_TITLE)); } @Override protected String buildWindowMenuLabel() { return buildWindowMenuLabel(design, frame); } /** * Recover any system resources being used to support this window/view. * * @author mlh */ @Override public void dispose() { // Call super dispose first so that window is closed, before // disposing of the frame. This prevents a crash because of // trying to dry a disposed image. // Should really be at the end of this function. TODO: super.dispose(); CogTool.selectionPhase.removeDelayedWork(delayedWidgetSelection); CogTool.repaintPhase.removeDelayedWork(delayedRepainting); uiModel.dispose(); frameUI.removeAllHandlers(this); frame.removeAllHandlers(this); design.removeAllHandlers(this); undoManager.removeAllHandlers(this); selection.removeAllHandlers(this); } /** * Return the model object for this UIModel. This returns an Frame. */ @Override protected Object getModelObject() { return frame; } /** * Return the interaction object for this frame editor. */ public FrameEditorInteraction getInteraction() { return interaction; } /** * Standard interaction needed by AController; * leaf subclasses must implement. * * @author mlh */ @Override public Interaction getStandardInteraction() { return interaction; } /** * Return the selection state for Frame Editors. */ public FrameEditorSelectionState getSelectionState() { return selection; } // Used by support classes only! public FrameUIModel getFrameUI() { return uiModel.getFrameUI(); } public FrameEditorUIModel getFrameEditorUI() { return uiModel; } public GraphicalWidget<?> widgetLocatedAtXY(int x, int y) { return uiModel.widgetLocatedAtXY(x, y); } /** * select all widgets in the model. * * Iterates through each widget and adds to selection individually. */ public void selectAllWidgets() { selection.deselectAll(); // Iterate through the list and select each item individually. Iterator<GraphicalWidget<?>> widgetFigures = frameUI.getFigureListIterator(); while (widgetFigures.hasNext()) { GraphicalWidget<?> widgetFigure = widgetFigures.next(); selection.selectSelnFig(widgetFigure); } } protected Rectangle getEltFigureBounds(FrameElement elt) { if (elt instanceof IWidget) { GraphicalWidget<?> gw = frameUI.getWidgetFigure((IWidget) elt); return gw.getBounds(); } if (elt instanceof Association<?>) { return getGroupFigureBounds((Association<?>) elt); } return null; } protected Rectangle getGroupFigureBounds(Association<?> group) { Rectangle r = null; Iterator<? extends FrameElement> iter = group.iterator(); // Use figures since they may be sized differently during dynamic ops while (iter.hasNext()) { r = unionBounds(r, getEltFigureBounds(iter.next())); } return r; } public DoubleRectangle getSelectedWidgetArea() { DoubleRectangle r = null; Iterator<FrameElement> selectedElts = selection.getSelectedElementsIterator(); while (selectedElts.hasNext()) { FrameElement elt = selectedElts.next(); DoubleRectangle bds = null; if (elt instanceof IWidget) { IWidget w = (IWidget) elt; SimpleWidgetGroup wg = w.getParentGroup(); bds = (wg != null) ? wg.getGroupBounds() : w.getEltBounds(); } else if (elt instanceof FrameElementGroup) { bds = ((FrameElementGroup) elt).getGroupBounds(); } if (bds != null) { if (r == null) { r = new DoubleRectangle(bds); } else { r = r.union(bds); } } } return r; } /** * The IUIModel's subclasses implementation of getView * Return the view for the Frame Editor. */ @Override public View getView() { return view; } // Really want a keyword for "package" visibility! protected InteractionDrawingEditor getViewEditor() { return view.getEditor(); } /** * Internal function to perform the zoom Exposed by IZoomable * Adds code to repaint the resize handles if an object is selected * * Stores the zoom setting to the registry * * @param zoom */ @Override public void setZoom(double zoom) { super.setZoom(zoom); delayedRepainting.requestRepaint(ZOOM_REPAINT); } /** * Sets the "always-enabled" widgets; * call this at the end of the subclass constructor! * * @author mlh */ @Override protected void setInitiallyEnabled(boolean forConstruction) { super.setInitiallyEnabled(forConstruction); setEnabled(CogToolLID.NewWidget, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.NewWidgetJustWarn, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.AddDesignDevices, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.ZoomToFit, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.ZoomNormal, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.ZoomIn, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.ZoomOut, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.SetBackgroundImage, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.SetWidgetColor, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.SkinNone, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.SkinWireFrame, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.SkinWinXP, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.SkinMacOSX, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.SkinPalm, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.RenderAll, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); setEnabled(CogToolLID.UnRender, ListenerIdentifierMap.ALL, MenuUtil.ENABLED); boolean enable = (frame.getBackgroundImage() == null) ? MenuUtil.DISABLED : MenuUtil.ENABLED; setEnabled(CogToolLID.RemoveBackgroundImage, ListenerIdentifierMap.ALL, enable); view.setIsBackgroundAvailable(enable); setViewEnabledState(selection, ListenerIdentifierMap.NORMAL); } public void setLIDEnabledState() { setViewEnabledState(selection, ListenerIdentifierMap.NORMAL); } /** * Enables or disables LIDs as appropriate * @param sel the selection state on which to base enabling/disabling * @param availability NORMAL or CONTEXT * @see ListenerIdentifierMap */ protected void setViewEnabledState(FrameEditorSelectionState sel, Boolean availability) { String label = ""; int widgetCount = sel.getWidgetSelectionCount(); int elementCount = sel.getElementSelectionCount(); IWidget selectedWidget = null; FrameElement selectedElement = null; if ((widgetCount > 0) || (elementCount > 0)) { if (widgetCount == 1) { label = " " + WIDGET_LABEL; selectedWidget = sel.getSelectedIWidgets()[0]; selectedElement = selectedWidget; } else { label = " " + WIDGETS_LABEL; if (elementCount == 1) { selectedElement = sel.getSelectedIFrameElements()[0]; } } } Text t = WindowUtil.getFocusedText(); boolean editing = ((editor != null) && editor.getVisible()); String cutCopyLabel = (editing || (t != null)) ? "" : label; // Turn on rename support if selection is exactly 1 boolean enabled = (widgetCount == 1); String renameString = MenuFactory.RENAME_STRING; String relabelString = MenuFactory.RELABEL_STRING; if (enabled) { renameString += label; relabelString += label; } setEnabled(CogToolLID.Rename, availability, enabled, renameString); setEnabled(FrameEditorLID.Relabel, availability, enabled, relabelString); setEnabled(CogToolLID.CopyPath, availability, enabled); setEnabled(FrameEditorLID.RemoveImageProperty, availability, (selectedWidget != null) && (selectedWidget.getImage() != null)); setEnabled(FrameEditorLID.ToggleRenderSkin, availability, enabled, MenuFactory.RENDER_WIDGET_SKIN_LABEL, (selectedWidget != null) && selectedWidget.isRendered()); if (enabled && (selectedWidget != null)) { enabled = selectedWidget.getAttribute(WidgetAttributes.REMOTE_LABEL_OWNER_ATTR) != null; } setEnabled(FrameEditorLID.SetRemoteLabelType, availability, enabled); if (elementCount == 1) { enabled = selectedElement.getRemoteLabelOwner() != null; } setEnabled(FrameEditorLID.SetRemoteLabelText, availability, enabled); // Turn on these LIDs if the selection is > 0 enabled = (widgetCount > 0); setEnabled(FrameEditorLID.SetImageProperty, availability, enabled); setEnabled(FrameEditorLID.CaptureImageProperty, availability, enabled && view.isBackgroundAvailable()); enabled = (elementCount > 0); String deleteString = MenuFactory.DELETE_STRING + label; setEnabled(CogToolLID.Delete, availability, enabled, deleteString); String dupString = MenuFactory.DUPLICATE_STRING + label; setEnabled(CogToolLID.Duplicate, availability, enabled, dupString); // For these, there must exist a non-IChildWidget selected widget int nonchildWidgetCount = sel.getNonchildSelectionCount(); enabled = (nonchildWidgetCount > 0); String cutString = MenuFactory.CUT_STRING + cutCopyLabel; setEnabled(CogToolLID.Cut, availability, enabled, cutString); String copyString = MenuFactory.COPY_STRING + cutCopyLabel; setEnabled(CogToolLID.Copy, availability, enabled, copyString); setEnabled(CogToolLID.SetFrameTemplate, availability, enabled); setEnabled(CogToolLID.NudgeLeft, availability, enabled); setEnabled(CogToolLID.NudgeRight, availability, enabled); setEnabled(CogToolLID.NudgeDown, availability, enabled); setEnabled(CogToolLID.NudgeUp, availability, enabled); setEnabled(CogToolLID.BringToFront, availability, enabled); setEnabled(CogToolLID.BringForward, availability, enabled); setEnabled(CogToolLID.SendBackward, availability, enabled); setEnabled(CogToolLID.SendToBack, availability, enabled); // Grouping enabled only if multiple unrelated IFrameElements are selected enabled = false; FrameElement firstSelectedElt = null; Iterator<FrameElement> selectedElts = sel.getSelectedElementsIterator(); while (selectedElts.hasNext()) { FrameElement elt = selectedElts.next(); if (firstSelectedElt == null) { firstSelectedElt = elt.getRootElement(); } else if (firstSelectedElt != elt.getRootElement()) { enabled = true; break; } } setEnabled(CogToolLID.Group, availability, enabled); enabled = false; selectedElts = sel.getSelectedElementsIterator(); while (selectedElts.hasNext()) { FrameElement elt = selectedElts.next(); enabled = enabled || (elt instanceof FrameElementGroup) || (elt.getRootElement().getEltGroups().size() > 0); } setEnabled(CogToolLID.Ungroup, availability, enabled); // don't allow alignment if any of the selected widgets are part of a // widget group (e.g. menu headers, list box items, grid buttons) // TODO might want to allow it for grid buttons, with extra work in the // controller to calculate horizontal and vertical space boolean groupWidgetSelected = false; IWidget[] widgets = sel.getSelectedIWidgets(); for (IWidget widget : widgets) { if (widget.getParentGroup() != null) { groupWidgetSelected = true; } } // Enable alignment items if multiple non-child widgets are selected enabled = (nonchildWidgetCount > 1) /* && ! groupWidgetSelected */; setEnabled(CogToolLID.AlignTop, availability, enabled); setEnabled(CogToolLID.AlignBottom, availability, enabled); setEnabled(CogToolLID.AlignLeft, availability, enabled); setEnabled(CogToolLID.AlignRight, availability, enabled); setEnabled(CogToolLID.AlignCenter, availability, enabled); setEnabled(CogToolLID.AlignHorizCenter, availability, enabled); setEnabled(CogToolLID.AlignVertCenter, availability, enabled); // Enable spacing items if at least 3 non-child widgets are selected enabled = (nonchildWidgetCount >= 3) && ! groupWidgetSelected; setEnabled(CogToolLID.SpaceVertically, availability, enabled); setEnabled(CogToolLID.SpaceHorizontally, availability, enabled); // If there is at least one widget in the model, enable these. enabled = (frame.getWidgets().size() > 0); setEnabled(CogToolLID.SelectAll, availability, enabled, SELECT_ALL_WIDGETS); // Draws the dot to indicate that the correct skin type is selected setSelected(CogToolLID.SkinNone, availability, false); setSelected(CogToolLID.SkinWireFrame, availability, false); setSelected(CogToolLID.SkinMacOSX, availability, false); setSelected(CogToolLID.SkinWinXP, availability, false); setSelected(CogToolLID.SkinPalm, availability, false); SkinType skin = design.getSkin(); CogToolLID id = null; if (skin == SkinType.None) { id = CogToolLID.SkinNone; } else if (skin == SkinType.WireFrame) { id = CogToolLID.SkinWireFrame; } else if (skin == SkinType.MacOSX) { id = CogToolLID.SkinMacOSX; } else if (skin == SkinType.WinXP) { id = CogToolLID.SkinWinXP; } else if (skin == SkinType.Palm) { id = CogToolLID.SkinPalm; } if (id != null) { setSelected(id, availability, true); } setEnabled(CogToolLID.ClearFrameTemplate, ListenerIdentifierMap.ALL, FrameTemplateSupport.hasFrameTemplate(design)); } // setViewEnabledState // LID Transmuter Stuff @Override public ListenerIdentifier transmute(ListenerIdentifier id, boolean isContextSelection) { ListenerIdentifier specificLID = super.transmute(id, isContextSelection); // Check if super has already specialized this if (specificLID != id) { return specificLID; } if ((id == CogToolLID.Paste) && ClipboardUtil.hasImageData()) { return CogToolLID.PasteBackgroundImage; } if (id == FrameEditorLID.ToggleRenderSkin) { return FrameEditorLID.SetRenderSkin; } return id; } /** * Do any set-up before <code>performAction</code> is invoked. * * @param id the transmuted key specifying the semantic nature of the * action to be performed */ @Override protected void setUpPerformAction(ListenerIdentifier id) { super.setUpPerformAction(id); int selectionMask = canIDCauseSelection(id); if (isSelectionFlagSet(selectionMask, CogToolLID.CAUSES_WIDGET_SELECTION)) { delayedWidgetSelection.setActive(true); } if (id == FrameEditorLID.Reorder) { delayedRepainting.requestRepaint(SHAPE_CHANGE_REPAINT); } } /** * The get parameters function returns the correct parameters needed for * a particular LID. */ @Override public Object getParameters(ListenerIdentifier originalLID, ListenerIdentifier transmutedLID, boolean isContextSelection) { // Check the general LID, if it is to restore the parent, then // return the super's get parameters Object parameters = super.getParameters(originalLID, transmutedLID, isContextSelection); if (parameters != UNSET) { return parameters; } setUpPerformAction(transmutedLID); FrameEditorSelectionState selnStateToUse = isContextSelection ? contextSelection : selection; if (transmutedLID == CogToolLID.PasteBackgroundImage) { return new FrameEditorUI.PasteBackgroundImageParms(selnStateToUse, ClipboardUtil.fetchImageData()); } if (transmutedLID == FrameEditorLID.ChangeShapeProperty) { return new FrameEditorUI.ShapeChangeParameters(view.getWidgetShape(), selnStateToUse); } if (transmutedLID == FrameEditorLID.ChangeTypeProperty) { return new FrameEditorUI.TypeChangeParameters(view.getWidgetType(), selnStateToUse); } if (transmutedLID == FrameEditorLID.ChangeTitleProperty) { String newTitle = view.getWidgetTitle(); boolean isSeparator = GraphicalWidgetRenderer.SEPARATOR_STRING.equals(newTitle); return new FrameEditorUI.ActionStringParameters(newTitle, selnStateToUse, isSeparator); } if (transmutedLID == FrameEditorLID.ChangeAuxTextProperty) { return new FrameEditorUI.ActionStringParameters(view.getElementAuxText(), selnStateToUse); } if (transmutedLID == FrameEditorLID.ChangeNameProperty) { return new FrameEditorUI.ActionStringParameters(view.getWidgetName(), selnStateToUse); } if (transmutedLID == FrameEditorLID.SetRenderSkin) { boolean renderState = view.getWidgetRendered(); boolean newRenderState = (originalLID == FrameEditorLID.ToggleRenderSkin) ^ renderState; return new FrameEditorUI.SetRenderSkinParameters(newRenderState, selnStateToUse); } if (transmutedLID == FrameEditorLID.NewWidget) { return new FrameEditorUI.NewWidgetParameters(new DoubleRectangle(Widget.DEFAULT_X, Widget.DEFAULT_Y, Widget.DEFAULT_WIDTH, Widget.DEFAULT_HEIGHT), null, getCurrentWidgetType(), view.isAutomaticCreation(), "", null, false); } if (transmutedLID == CogToolLID.Duplicate) { return new FrameEditorUI.DuplicateParameters(16.0, 16.0, selnStateToUse); } if (transmutedLID == FrameEditorLID.SetRemoteLabelText) { return new FrameEditorUI.SetRemoteLabelTextParms(view.getRemoteLabelText(), selnStateToUse.getSelectedIFrameElements()[0]); } // The following LID's simply use selection // FrameEditorLID.SetImageProperty // FrameEditorLID.CaptureImageProperty // FrameEditorLID.CopyPath return selnStateToUse; } /** * Allows the interfaces to clean up any feedback provided to the * user before and during a performAction. * * @param okToContinue the return value from performAction * @param menuHidden whether or not the context menu is dismissed * without selecting an operation to perform * @author mlh */ @Override public void cleanup(boolean okToContinue, boolean menuHidden) { if (menuHidden) { Iterator<FrameEltSelnFig<?>> figures; // If an element was contextually selected, clear the highlight if (contextSelection.getElementSelectionCount() > 0) { figures = contextSelection.getSelectedFigures(); while (figures.hasNext()) { FrameEltSelnFig<?> eltFig = figures.next(); eltFig.dynamicHighlight(false); } } // Check for selected widgets that are highlighted if (selection.getElementSelectionCount() > 0) { figures = selection.getSelectedFigures(); while (figures.hasNext()) { FrameEltSelnFig<?> eltFig = figures.next(); eltFig.dynamicHighlight(false); } } } super.cleanup(okToContinue, menuHidden); } /** * Initiate a rename of the specified widget. */ public void initiateWidgetRename(IWidget w) { view.setWidgetName(w.getName()); view.requestRename(); } /** * Initiate a relabel of the specified widget. */ public void initiateWidgetRetitle(IWidget w) { view.setWidgetTitle(w.getTitle()); initiateRetitleFigure(frameUI.getWidgetFigure(w)); } /** * Pass-through to FrameUI to temporarily set the graphical widget bounds. * Used during dynamic operations; the repaint request is *not* delayed! * * This change does not affect the model, only the visual representation. * * @param tempOriginX * @param tempOriginY * @param tempWidth * @param tempHeight * @param gw */ public void setGraphicalWidgetBounds(double tempOriginX, double tempOriginY, double tempWidth, double tempHeight, GraphicalWidget<?> gw) { frameUI.setGraphicalWidgetBounds(tempOriginX, tempOriginY, tempWidth, tempHeight, gw); } /** * Change the graphical widget origins. Used during dynamic operations, * thus the repaint request is *not* delayed! * * This change does not affect the model, only the visual representation. * * @param x * @param y * @param gw */ public void setGraphicalWidgetOrigin(double x, double y, GraphicalWidget<?> gw) { frameUI.setGraphicalWidgetOrigin(x, y, gw); } public void moveFrameElementGroup(double offsetX, double offsetY, Association<? extends FrameElement> eltGroup) { Point newOrigin = new Point(0, 0); for (FrameElement elt : eltGroup) { if (elt instanceof Association<?>) { moveFrameElementGroup(offsetX, offsetY, (Association<?>) elt); } else if (elt instanceof IWidget) { IWidget w = (IWidget) elt; DoublePoint p = w.getShape().getOrigin(); double deltaX = offsetX; double deltaY = offsetY; // Prevent the ability to move a widget to an origin less than 0,0 if (deltaX + p.x < 0.0) { deltaX = - p.x; } if (deltaY + p.y < 0.0) { deltaY = - p.y; } newOrigin.setLocation(PrecisionUtilities.round(p.x + deltaX), PrecisionUtilities.round(p.y + deltaY)); frameUI.getWidgetFigure(w).setLocation(newOrigin); } } } // moveFrameElementGroup /** * Assumes gw's model is a traversable widget but not a child widget. */ public void moveWidgetGroup(double offsetX, double offsetY, GraphicalTraversableWidget<?> gw) { // No easier way to do this (hard to make getTopHeader call generic) TraversableWidget groupWidget = null; if (gw instanceof GraphicalMenuWidget<?>) { groupWidget = ((AMenuWidget) gw.getModel()).getTopHeader(); } else if ((gw instanceof GraphicalListItem) || (gw instanceof GraphicalGridButton)) { groupWidget = (TraversableWidget) gw.getModel(); } else { return; } SimpleWidgetGroup headerGroup = groupWidget.getParentGroup(); // Ensure top-left header doesn't scroll off-screen groupWidget = (TraversableWidget) headerGroup.get(0); DoublePoint p = groupWidget.getShape().getOrigin(); // Prevent the ability to move a widget to an origin less than 0,0 if (offsetX + p.x < 0.0) { offsetX = - p.x; } if (offsetY + p.y < 0.0) { offsetY = - p.y; } Point newOrigin = new Point(0, 0); int index = 1; int headerCount = headerGroup.size(); do { newOrigin.setLocation(PrecisionUtilities.round(p.x + offsetX), PrecisionUtilities.round(p.y + offsetY)); frameUI.getWidgetFigure(groupWidget).setLocation(newOrigin); // Stop when we've moved the last header! if (index >= headerCount) { break; } groupWidget = (TraversableWidget) headerGroup.get(index++); p = groupWidget.getShape().getOrigin(); } while (true); } /** * either uses the selection state or initiateRenameFigure to select it */ protected void selectFigure(IFigure figure) { if (figure instanceof PotentialFigure) { initiateRetitleFigure(figure); } else if (figure instanceof GraphicalTraversableWidget<?>) { selection.selectSelnFig((GraphicalWidget<?>) figure); } } /** * Change the graphical widget's origin by a displacement. Used during * dynamic operations, thus the repaint request is *not* delayed! * * This change does not affect the model, only the visual representation. * * @param diffX * @param diffY * @param gw */ public void setGraphicalWidgetMove(double diffX, double diffY, GraphicalWidget<?> gw) { frameUI.setGraphicalWidgetMove(diffX, diffY, gw); } /** * Create a new temporary rectangular figure. Used during * dynamic operations, thus the repaint request is *not* delayed! * * Calls rectangle on scalable frame figure. used for dragging a new region * or drawing a selectable area. * * @param x * @param y * @param w * @param h * @param outline */ public void setTemporaryWidget(double x, double y, double w, double h, boolean outline) { // get the contents of the frame editor CogToolScalableFigure editorContents = view.getEditor().getContents(); // if it supports drawing the region, do so. if (editorContents instanceof ScalableInteractiveFigure) { ScalableInteractiveFigure contents = (ScalableInteractiveFigure) editorContents; // Use the Interaction Layers temporary figure to draw the rect. contents.setTemporaryFigure(PrecisionUtilities.round(x), PrecisionUtilities.round(y), PrecisionUtilities.round(w), PrecisionUtilities.round(h)); // specify if the new region is an outline. contents.setOutlineDrawing(outline); } else { throw new IllegalStateException("Attempted to call " + "setTemporaryFigure on a " + "IScalableFigure which does not " + "support it."); } } /** * Repaint function that ensures updates to visual components. * Updates resize widget status * repaints contents. * * Called infrequently. This repaint is not in the normal redraw loop. * called when notifications are received from elsewhere that objects have * changed. Called to update the contents of all items if changes have been * made. * * TODO: Call DrawWidgets each time? * */ protected void repaint() { // Update the contents if an update is needed. // Will only actually repaint if the contents are dirty. view.getEditor().getInteractionFigure().repaint(); } /** * Return the WidgetResize button under x, y if there is one. * * Since the resizeThumbs are in the InteractionFigure, they are not in a * zoomed context, therefore you don't modify x,y for zoom to find them. * * IE: do not adjust x, y for any zoom factors. * * Returns null if no resize thumb. * * @param x * @param y * @return */ public ResizeThumb widgetResizeUnderXY(int x, int y) { IFigure f = view.getEditor().getInteractionFigure().findFigureAt(x, y, ResizeThumb.class); if (f != null) { return (ResizeThumb) f; } return null; } /** * Find a potential graphical figure at x, y */ public PotentialFigure potentialWidgetUnderXY(int x, int y) { IFigure f = view.getEditor().getInteractionFigure().findFigureAt(x, y, PotentialFigure.class); if (f != null) { return (PotentialFigure) f; } return null; } /** * Find a move halo at x, y */ public MoveHalo moveHaloUnderXY(int x, int y) { MoveHalo halo = view.moveHaloUnderXY(x, y); if (halo != null) { GraphicalWidget<?> clickedFigure = widgetLocatedAtXY(x, y); if (clickedFigure != null) { SimpleWidgetGroup group; IWidget haloWidget; FrameElement data = halo.getData(); if (data instanceof IWidget) { haloWidget = (IWidget) data; group = haloWidget.getParentGroup(); } else if (data instanceof SimpleWidgetGroup) { group = (SimpleWidgetGroup) data; haloWidget = group.get(0); } else { return halo; } IWidget clickedWidget = clickedFigure.getModel(); if ((group != null) && (group.contains(clickedWidget))) { return null; } int haloLevel = haloWidget.getLevel(); int clickedLevel = clickedWidget.getLevel(); if (haloLevel < clickedLevel) { return null; } } return halo; } return null; } /** * Find a potential graphical figure at x, y */ public RadioButtonSash radioSashUnderXY(int x, int y) { IFigure f = view.getEditor().getInteractionFigure().findFigureAt(x, y, RadioButtonSash.class); if (f != null) { return (RadioButtonSash) f; } return null; } /** * Find a remote label linkage */ public RemoteLinkage linkageUnderXY(int x, int y) { IFigure f = view.getEditor().getInteractionFigure().findFigureAt(x, y, RemoteLinkage.class); if (f != null) { return (RemoteLinkage) f; } return null; } // Context menu stuff /** * Show the basic contextual menu, the boolean inContext is used * to determine if the context selection state should be used * later on in getParameters */ protected void showContextMenu(FrameEditorSelectionState seln, boolean context) { // Check the context menu enabled states setViewEnabledState(seln, ListenerIdentifierMap.CONTEXT); // Multiple selection? if (seln.getElementSelectionCount() > 0) { view.showSelectionMenu(context); } else { view.showBlankSpaceMenu(); } } /** * Show the basic context menu, * This shows the context menu based on the current selection. * This is often called by the contextual menu key on windows. */ @Override public void showContextMenu() { showContextMenu(selection, View.SELECTION); } /** * Show a context menu at a specific X,Y Used for contextual menu's on * unselected or selected objects. */ @Override public void showContextMenu(int x, int y) { // Check which region of the frame was hit FrameEltSelnFig<?> eltFig = (FrameEltSelnFig<?>) frameUI.getFigureAtXY(x, y, FrameUIModel.ONLY_GRAPHICAL_WIDGETS); // Check for appropriate MoveHalo if (eltFig == null) { MoveHalo halo = moveHaloUnderXY(x, y); if (halo instanceof FrameEltGroupHalo) { eltFig = (FrameEltGroupHalo) halo; } } // Invocation in empty space? if (eltFig == null) { contextSelection.deselectAll(); showContextMenu(contextSelection, View.CONTEXT); } // Invocation on a selection widget else if (eltFig.isSelected()) { showContextMenu(); Iterator<FrameEltSelnFig<?>> selectedFigs = selection.getSelectedFigures(); while (selectedFigs.hasNext()) { FrameEltSelnFig<?> selectedFig = selectedFigs.next(); selectedFig.dynamicHighlight(true); } // The mac requires an additional repaint to display the highlight if (OSUtils.MACOSX) { updateUISupport(); } } // Invocation on an unselected widget else { eltFig.dynamicHighlight(true); // Populate the context selection // This does not impact current selection, just the context one. contextSelection.setSelectedSelnFig(eltFig); // Display the correct context menu. showContextMenu(contextSelection, View.CONTEXT); // The mac requires an additional repaint to display highlighting if (OSUtils.MACOSX) { updateUISupport(); } } } /** * Support for centering selection when zooming */ @Override protected Rectangle getSelectedRegion() { // Go through all widgets and union their bounds. Iterator<FrameEltSelnFig<?>> selectedFigs = selection.getSelectedFigures(); return computeUnion(selectedFigs); } /** * Update the visual contents based on the selected widgets. */ public void updateView() { int selectionCount = selection.getElementSelectionCount(); // If only one item selected, enable & update the // text boxes and pull downs with values. if (selectionCount == 1) { // preserve selected state if a whole text field is selected Text text = WindowUtil.getFocusedText(); boolean restoreSel = false; if ((text != null) && (text.getSelectionCount() == text.getCharCount())) { restoreSel = true; } // get the selected item Iterator<FrameElement> iter = selection.getSelectedElementsIterator(); // For each selected object, which there is only one. FrameElement elt = iter.next(); if (elt instanceof IWidget) { IWidget widget = (IWidget) elt; view.useParameters(FrameEditorView.USE_SINGLE_SELECT); view.updateWidgetProperties(widget); } else if (elt instanceof FrameElementGroup) { FrameElementGroup eltGroup = (FrameElementGroup) elt; view.useParameters(FrameEditorView.USE_GROUP_SELECT); view.updateEltGroupProperties(eltGroup); } // Restore the selection state if (restoreSel) { text.selectAll(); } } else if (selectionCount > 1) { // TODO: on multi selection, some values are left enabled // We need to set these to something or disable them. view.useParameters(FrameEditorView.USE_MULTI_SELECT); } else { view.useParameters(FrameEditorView.USE_NONE); view.updateFrameProperties(frame, false); } } /** * Pass-through to frame view to turn off any temporary figure drawing. * Calls a repaint after to remove any repaint handles, and flush repaints */ public void stopDrawingTemporaryFigure() { CogToolScalableFigure editorContents = view.getEditor().getContents(); if (editorContents instanceof ScalableInteractiveFigure) { ScalableInteractiveFigure contents = (ScalableInteractiveFigure) editorContents; contents.stopDrawingTemporaryFigure(); } else { throw new IllegalStateException("Attempted to call " + "StopDrawingTemporaryFigure on an " + "IScalableFigure which does not " + "support it."); } } public WidgetType getCurrentWidgetType() { return view.getWidgetType(); } public void initiateRetitleFigure(IFigure widgetFigure) { if (widgetFigure instanceof GraphicalWidget<?>) { IWidget w = ((GraphicalWidget<?>) widgetFigure).getModel(); Object value = w.getAttribute(WidgetAttributes.IS_SEPARATOR_ATTR); if (NullSafe.equals(WidgetAttributes.IS_SEPARATOR, value)) { // don't allow renaming of separators return; } } // The editor control must be a child of an SWT object (the canvas) if (editor == null) { editor = new WidgetTitleEditor(); } cleanupFigureLabelEditor(); if (widgetFigure != null) { editor.editTitle(widgetFigure); } } protected void confirmRenameFigure() { if ((editor != null) && editor.inUse()) { String newTitle = editor.getText(); editor.cleanup(); // figure whose title is currently being edited IFigure figure = (IFigure) editor.getData(); if (figure instanceof PotentialFigure) { if ("".equals(newTitle)) { return; // nothing to do if empty string on potential item! } GraphicalTraversableWidget<?> parent = potentialUIFig.getFigureOwner(); TraversableWidget parentModel = (TraversableWidget) parent.getModel(); boolean isRightPotentialFigure = (figure == potentialUIFig.getRightFigure()); WidgetType type = null; AParentWidget header = null; SimpleWidgetGroup group = null; boolean isSeparator = false; if (parentModel instanceof MenuItem) { type = WidgetType.MenuItem; MenuItem parentItem = (MenuItem) parentModel; if (isRightPotentialFigure) { // position of selected figure is on the right // parent menuitem becomes a submenu; figure to be // created becomes a child of the submenu parentItem.setSubmenu(true); header = parentItem; } else { // if position is on the bottom and the parent is not a // header, put the new widget in the same menu as the // parent item header = parentItem.getParent(); // previous item should hide children if it's a submenu ((GraphicalParentWidget<?, ?>) parent).closeChildren(); } if (parentModel.getWidgetType() != WidgetType.Submenu) { isSeparator = GraphicalWidgetRenderer.SEPARATOR_STRING.equals(newTitle); } } else if (parentModel instanceof ContextMenu) { header = (ContextMenu) parentModel; type = WidgetType.MenuItem; isSeparator = GraphicalWidgetRenderer.SEPARATOR_STRING.equals(newTitle); } else if (parentModel instanceof MenuHeader) { // if pos is right, create a new header instead of an item // and hide children of the previous header if (isRightPotentialFigure) { type = WidgetType.Menu; ((GraphicalParentWidget<?, ?>) parent).closeChildren(); group = parentModel.getParentGroup(); } else { header = (MenuHeader) parentModel; type = WidgetType.MenuItem; isSeparator = GraphicalWidgetRenderer.SEPARATOR_STRING.equals(newTitle); } } else if (parentModel instanceof PullDownItem) { type = WidgetType.PullDownItem; if (! isRightPotentialFigure) { PullDownItem parentItem = (PullDownItem) parentModel; header = parentItem.getParent(); isSeparator = GraphicalWidgetRenderer.SEPARATOR_STRING.equals(newTitle); } } else if (parentModel instanceof PullDownHeader) { type = WidgetType.PullDownItem; if (! isRightPotentialFigure) { header = (PullDownHeader) parentModel; isSeparator = GraphicalWidgetRenderer.SEPARATOR_STRING.equals(newTitle); } } else if (parentModel instanceof ListItem) { type = WidgetType.ListBoxItem; group = parentModel.getParentGroup(); isSeparator = GraphicalWidgetRenderer.SEPARATOR_STRING.equals(newTitle); } else if (parentModel instanceof GridButton) { type = parentModel.getWidgetType(); group = parentModel.getParentGroup(); } Rectangle r = ((PotentialFigure) figure).getUnscaledBounds(); DoubleRectangle bounds = new DoubleRectangle(r.x, r.y, r.width, r.height); performAction(CogToolLID.NewWidget, new FrameEditorUI.NewWidgetParameters(bounds, header, type, view.isAutomaticCreation(), newTitle, group, isSeparator)); } else { boolean isSeparator = GraphicalWidgetRenderer.SEPARATOR_STRING.equals(newTitle); performAction(FrameEditorLID.ChangeTitleProperty, new FrameEditorUI.ActionStringParameters(newTitle, selection, isSeparator), true); } } } /** * Removes stale Text control */ protected void cleanupFigureLabelEditor() { if (editor != null) { editor.cleanup(); } } protected void repaintEditor() { if (editor != null) { editor.repaintTextEditor(frameUI.getZoom()); } } public boolean areCompatible(IWidget clickedWidget, IWidget targetWidget) { if (clickedWidget instanceof ChildWidget) { if (targetWidget instanceof ChildWidget) { if (((clickedWidget instanceof MenuItem) == (targetWidget instanceof MenuItem)) && ((clickedWidget instanceof PullDownItem) == (targetWidget instanceof PullDownItem))) { // NOTE: this works because MenuItem and PullDownItem // are currently the only interfaces that extend // IChildWidget. return true; } } else if (targetWidget instanceof AParentWidget) { if (((clickedWidget instanceof MenuItem) == (targetWidget instanceof AMenuWidget)) && ((clickedWidget instanceof PullDownItem) == (targetWidget instanceof PullDownHeader))) { // NOTE: this works because MenuItem and PullDownItem // are currently the only interfaces that extend // IChildWidget. return true; } } } else { if (((clickedWidget instanceof MenuHeader) == (targetWidget instanceof MenuHeader)) && ((clickedWidget instanceof ListItem) == (targetWidget instanceof ListItem))) { // NOTE: The only widgets that have free-floating parent groups // are currently menu headers and list items. return true; } } return false; } public GraphicalTraversableWidget<?> getPotentialFigureOwner() { return potentialUIFig.getFigureOwner(); } public void setPotentialFigureOwner(GraphicalTraversableWidget<?> owner) { potentialUIFig.setFigureOwner(owner); } }