/*******************************************************************************
* 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.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.InputEvent;
import org.eclipse.draw2d.KeyEvent;
import org.eclipse.draw2d.RectangleFigure;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Cursor;
import edu.cmu.cs.hcii.cogtool.CogToolLID;
import edu.cmu.cs.hcii.cogtool.model.AParentWidget;
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.FrameElement;
import edu.cmu.cs.hcii.cogtool.model.FrameElementGroup;
import edu.cmu.cs.hcii.cogtool.model.GridButton;
import edu.cmu.cs.hcii.cogtool.model.Association;
import edu.cmu.cs.hcii.cogtool.model.ChildWidget;
import edu.cmu.cs.hcii.cogtool.model.GridButtonGroup;
import edu.cmu.cs.hcii.cogtool.model.MenuHeader;
import edu.cmu.cs.hcii.cogtool.model.MenuItem;
import edu.cmu.cs.hcii.cogtool.model.TraversableWidget;
import edu.cmu.cs.hcii.cogtool.model.IWidget;
import edu.cmu.cs.hcii.cogtool.model.SimpleWidgetGroup;
import edu.cmu.cs.hcii.cogtool.model.WidgetAttributes;
import edu.cmu.cs.hcii.cogtool.uimodel.FrameEltGroupHalo;
import edu.cmu.cs.hcii.cogtool.uimodel.FrameEltSelnFig;
import edu.cmu.cs.hcii.cogtool.uimodel.GraphicalChildWidget;
import edu.cmu.cs.hcii.cogtool.uimodel.GraphicalMenuItem;
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.util.ReadOnlyList;
import edu.cmu.cs.hcii.cogtool.util.NullSafe;
import edu.cmu.cs.hcii.cogtool.util.PrecisionUtilities;
import edu.cmu.cs.hcii.cogtool.util.WindowUtil;
import edu.cmu.cs.hcii.cogtool.view.InteractionDrawingEditor;
import edu.cmu.cs.hcii.cogtool.view.InteractionFigure;
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.StandardDrawingEditor;
/**
*
* This class is responsible for mouse events and related operations
* for the Frame/Widget Editor.
*
* States
*
* MouseUp:
* Down in space deselects others;
* enters PotentialCreatingWidget
* Down on an unselected Widget deselects others and selects Widget;
* enters PotentialMovingWidget
* Down on a selected Widget;
* enters PotentialMovingSelection
* Shift-down on a Widget;
* enters PotentialTogglingSelection
* Shift-down in space;
* enters PotentialTogglingSelection
* Down on a resize handle remembers;
* enters PotentialResizingWidget
*
* PotentialCreatingWidget
* Drag creates dynamic Widget box;
* enters CreatingWidget
* Up/ESC cancels;
* enters MouseUp
*
* CreatingWidget
* Drag redraws dynamic Widget box
* ESC cancels creation;
* enters MouseUp
* Up creates real Widget;
* enters MouseUp
*
* PotentialMovingWidget
* Drag moves selected Widget
* enters MovingWidgets
* Up/ESC cancels;
* enters MouseUp
*
* PotentialMovingSelection
* Drag moves selected Widgets
* enters MovingWidgets
* Up deselects others and selects Widget
* enters MouseUp
* ESC cancels;
* enters MouseUp
*
* * PotentialDuplicatingSelection
* Drag duplicates selected Widgets and moves new Widgets
* enters DuplicatingWidgets
* Up cancels;
* enters MouseUp
* ESC cancels;
* enters MouseUp
*
* DuplicatingWidgets
* Drag (potentially) moves new Widgets
* Up (actually) moves new Widgets;
* enters MouseUp
* ESC cancels move;
* enters MouseUp
*
* MovingWidgets
* Drag (potentially) moves selected Frames
* Up (actually) moves selected Frames;
* enters MouseUp
* ESC cancels move;
* enters MouseUp
*
* PotentialTogglingSelection
* Drag creates dynamic toggling box;
* enters TogglingSelection
* Up/ESC cancels;
* enters MouseUp
*
* TogglingSelection
* Drag (potentially) toggles Widgets
* Up (actually) toggles Widgets;
* enters MouseUp
* ESC cancels toggling;
* enters MouseUp
*
* PotentialResizingWidget
* Drag (potentially) resizes Widget(s);
* enters ResizingWidget
* Up/ESC cancels resizing;
* enters MouseUp
*
* ResizingWidget
* Drag (potentially) resizes Widget(s)
* Up (actually) resizes Widget(s);
* enters MouseUp
* ESC cancels resizing;
* enters MouseUp
*
*/
public class FrameEditorMouseState extends Draw2DMouseState
{
// For mouse state
public static final int MouseUp = 0;
public static final int PotentialMovingWidget = 1;
public static final int PotentialMovingSelection = 2;
public static final int MovingWidgets = 3;
public static final int PotentialCreatingWidget = 4;
public static final int CreatingWidget = 5;
public static final int PotentialTogglingSelection = 6;
public static final int TogglingSelection = 7;
public static final int PotentialResizingWidget = 8;
public static final int ResizingWidget = 9;
public static final int PotentialDuplicatingWidget = 10;
public static final int DuplicatingWidgets = 11;
public static final int PotentialReorderWidget = 12;
public static final int ReorderWidget = 13;
public static final int PotentialInsertDuplicateWidget = 14;
public static final int InsertDuplicateWidget = 15;
public static final int PotentialMovingGridButtons = 16;
public static final int MovingGridButtons = 17;
protected static final Cursor SELECT_CURSOR =
WindowUtil.getCursor(WindowUtil.SELECT_CURSOR);
protected static final Cursor NOT_ALLOWED_CURSOR =
WindowUtil.getCursor(WindowUtil.NOT_ALLOWED_CURSOR);
protected static final Cursor DRAW_CURSOR =
WindowUtil.getCursor(WindowUtil.DRAW_CURSOR);
protected static final Cursor MOVE_HORIZ_CURSOR =
WindowUtil.getCursor(WindowUtil.RESIZE_WE_CURSOR);
protected static final Cursor MOVE_VERT_CURSOR =
WindowUtil.getCursor(WindowUtil.RESIZE_NS_CURSOR);
protected static final int REORDER_DISTANCE = 100;
/**
* The state the mouse FSM is in. See the FSM comment in the class def.
*/
protected int mouseListenerState = MouseUp;
/**
* Hold the UI access. Used for any interaction needed.
* Also important for accessing the interaction layer and adjusting the
* visual interface.
*/
protected FrameEditorUI ui;
/**
* Hold on to the selection state to change what widgets are selected.
*/
protected FrameEditorSelectionState selection;
/**
* A reference to the last widget clicked on; used for reordering
* widgets that are part of widget groups.
*/
protected IWidget lastClickedWidget = null;
/**
* The last widget or potential figure hovered over during
* the reorder interaction.
*/
protected GraphicalWidget<?> lastHoveredWidget = null;
/**
* The dynamic box used for the reordering widget interaction
*/
protected RectangleFigure reorderFigure = new RectangleFigure();
/**
* A thick line that indicates where the lastClickedWidget will be moved to
*/
protected RectangleFigure dividerLine = new RectangleFigure();
/**
* The direction the radio buttons will be moved; see
* RadioButtonSash.vertical
*/
protected boolean moveIsVertical = false;
/**
* The list of radio buttons that need to be moved
*/
protected ReadOnlyList<GridButton> movedGridButtons = null;
/**
* Empty list when radio buttons aren't actually being moved.
*/
protected static final ReadOnlyList<GridButton> NO_GRID_BUTTONS =
new ReadOnlyList<GridButton>(new ArrayList<GridButton>());
/**
* Minimum x and y while rearranging radio buttons
*/
protected double minX;
protected double minY;
/**
* If true, in the middle of the reorder interaction
*/
protected boolean isReordering = false;
/**
* A boolean to indicate that the mouse drag timer should be stopped.
*/
protected boolean stopMouseDragTimer = true;
/**
* A timer task used for mouse drag events. The type of the class is below.
*/
protected MouseDragTimerTask mouseDragTask = new MouseDragTimerTask();
/**
* Root interaction figure for call to mouseDragged in MouseDragTimerTask
*/
protected IFigure rootFigure = null;
/**
* The location in 1:1 for the down press
*/
protected double scaledMouseDownX;
protected double scaledMouseDownY;
/**
* The original down mouse location in window coordinates
* (for hysteresis checking).
*/
protected int mouseDownX;
protected int mouseDownY;
/**
* Whether the mouseDown information is current.
*/
protected boolean isMouseDownValid = false;
/**
* The position in 1:1 which defines the RESIZE fixed anchor.
*/
protected double mouseFixedResizeX;
protected double mouseFixedResizeY;
protected int currentResizeHandleType = FrameEditorUI.TOP_LEFT;
/**
* The area in 1:1 of the currently selected widgets during RESIZE
*/
protected DoubleRectangle initialResizeArea;
// Holds rectangle figures for duplicating via ctrl + drag
protected List<RectangleFigure> duplicatingDynamic = null;
protected int lastX = 0;
protected int lastY = 0;
/**
* Parameters for reordering a widget
*/
protected FrameEditorUI.ReorderWidgetParameters reorderParms =
new FrameEditorUI.ReorderWidgetParameters();
/**
* Parameters for inserting a duplicate of a widget
*/
protected FrameEditorUI.InsertDuplicateParameters insertDuplicateParms =
new FrameEditorUI.InsertDuplicateParameters();
/**
* If the cursor needs to be changed, save the previous one to revert it
*/
protected Cursor prevCursor;
// Hysteresis for dragging
protected final int HYSTERESIS = 3;
/**
* The MouseDragTimerTask is an event which simple moves the mouse cursor
* in the interface. this is done so that when the mouse hovers in an
* area it can be treated as an automatic drag event.
*/
protected class MouseDragTimerTask implements Runnable
{
/**
* The next location of the mouse. IE: where it should move when
* task runs
*/
protected int mouseLocX;
protected int mouseLocY;
/**
* The mouse button that was pressed when timer was created
*/
protected int mouseButton;
/**
* Event's mouse state
*/
protected int mouseState;
/**
* Loop and fire the mouseDrag function updating the mouse
* cursor as needed.
* Stop, when stopMouseDrag flag is set true.
*/
public void run()
{
if (stopMouseDragTimer) {
// Do nothing.
}
else {
// Call the MouseDragged function specifying the new location,
// button and state
FrameEditorMouseState.this.mouseDragged(rootFigure,
mouseButton,
mouseLocX,
mouseLocY,
mouseState);
}
}
/**
* Set the next position to use on when run.
*/
public void setNextMousePosition(int mousePosX,
int mousePosY,
int button,
int state)
{
mouseLocX = mousePosX;
mouseLocY = mousePosY;
mouseButton = button;
mouseState = state;
}
}
/**
* Simple constructor. Takes a FrameEditorUIModel and assigns the local
* look ups; selection and the uimodel itsself.
* @param frameUI
*/
public FrameEditorMouseState(FrameEditorUI frameUI)
{
super(frameUI);
ui = frameUI;
selection = ui.selection;
}
protected void resetPrevCursor()
{
InteractionFigure drawLayer =
ui.getViewEditor().getInteractionFigure();
drawLayer.setCursor(prevCursor);
}
public boolean isReordering()
{
return isReordering;
}
/**
* Set the mouseDownPointer. This should be a DoublePoint, with the X,Y
* being in 1:1 space. Returns the current zoom factor.
*
* @param x
* @param y
*/
protected double setMouseDown(int x, int y)
{
mouseDownX = x;
mouseDownY = y;
double zoom = ui.getZoom();
scaledMouseDownX = x / zoom;
scaledMouseDownY = y / zoom;
isMouseDownValid = true;
return zoom;
}
/**
* Set the mouse State.
* Used to remember what the user is doing.
* Mouse Down, Resize, etc
*/
protected void setMouseState(int newState)
{
ui.getViewEditor().captureMouseEvents(newState != MouseUp);
mouseListenerState = newState;
}
/**
* Get the mouse state
* @return
*/
protected int getMouseState()
{
return mouseListenerState;
}
@Override
protected boolean dealWithMouseDoubleClicked(IFigure figure,
int button,
int x,
int y,
int state)
{
boolean goForward =
super.dealWithMouseDoubleClicked(figure, button, x, y, state);
GraphicalWidget<?> target = ui.widgetLocatedAtXY(x, y);
if (goForward && (target != null)) {
selection.setSelectedSelnFig(target);
}
ui.confirmRenameFigure();
if (goForward) {
ui.initiateRetitleFigure(target);
}
setMouseState(MouseUp);
cleanup();
return goForward;
} // dealWithMouseDoubleClicked
protected FrameEltSelnFig<?> isSomeElementSelected(FrameElement owner)
{
if (owner instanceof IWidget) {
if (selection.isElementSelected(owner)) {
return ui.getFrameUI().getWidgetFigure((IWidget) owner);
}
}
else if (owner instanceof FrameElementGroup) {
if (selection.isElementSelected(owner)) {
return ui.getFrameEditorUI().getGroupHalo((FrameElementGroup) owner);
}
}
else if (owner instanceof SimpleWidgetGroup) {
Iterator<IWidget> members = ((SimpleWidgetGroup) owner).iterator();
while (members.hasNext()) {
IWidget member = members.next();
if (selection.isElementSelected(member)) {
return ui.getFrameUI().getWidgetFigure(member);
}
}
}
return null;
} // isSomeElementSelected
protected FrameEltSelnFig<?> getRepresentativeElt(FrameElement owner)
{
if (owner instanceof IWidget) {
return ui.getFrameUI().getWidgetFigure((IWidget) owner);
}
if (owner instanceof FrameElementGroup) {
return ui.getFrameEditorUI().getGroupHalo((FrameElementGroup) owner);
}
if (owner instanceof SimpleWidgetGroup) {
Iterator<IWidget> members = ((SimpleWidgetGroup) owner).iterator();
if (members.hasNext()) {
return ui.getFrameUI().getWidgetFigure(members.next());
}
}
return null;
}
/**
* Called on mouse presses
* Determines if the mouse pressed on a resize widget.
* Raises a resizeAlert or a mouseOperations alert.
*
* Alerts contain X,Y coordinates in 1:1 scale.
*/
protected void handleMousePressed(int x, int y, int state)
{
ui.view.getEditor().getSWTEditorSubstrate().setFocus();
// Check to see if the pressed event is on the resize handlers.
// Use the ZOOMED scale because the resize widgets are in the
// interaction layer and they're not affected by zoom.
ResizeThumb resize = ui.widgetResizeUnderXY(x, y);
PotentialFigure pWidget = ui.potentialWidgetUnderXY(x, y);
RadioButtonSash sash = ui.radioSashUnderXY(x, y);
// Check to see if the mouse press happened on a resize widget
if (resize != null) {
// Need this because we want the actual AREA,
// not the center of the thumb.
initialResizeArea = ui.getSelectedWidgetArea();
// Set the fixed point of the resize
double fixedX = 0.0;
double fixedY = 0.0;
// Move through the possible types and set the fixed point.
currentResizeHandleType = resize.thumbType;
switch (resize.thumbType) {
case FrameEditorUI.TOP_LEFT:
fixedX =
initialResizeArea.x + initialResizeArea.width;
fixedY =
initialResizeArea.y + initialResizeArea.height;
break;
case FrameEditorUI.BOTTOM_LEFT:
fixedX =
initialResizeArea.x + initialResizeArea.width;
fixedY = initialResizeArea.y;
break;
case FrameEditorUI.TOP_RIGHT:
fixedX = initialResizeArea.x;
fixedY =
initialResizeArea.y + initialResizeArea.height;
break;
case FrameEditorUI.BOTTOM_RIGHT:
fixedX = initialResizeArea.x;
fixedY = initialResizeArea.y;
break;
default:
// If the thumbType is unknown, throw an error.
// Should only happen if a thumb type was added
// and this code wasn't modified.
throw new IllegalStateException("Unknown type of Resize Thumb selected");
}
// Specify the fixed resize point.
mouseFixedResizeX = fixedX;
mouseFixedResizeY = fixedY;
// Set the state to "resize" as the next action.
// TODO: Should we implement the other types of resizing?
int nextState = PotentialResizingWidget;
setMouseState(nextState);
// Switch to fast mode rendering for graphical widgets
setWidgetFastRenderMode(true);
}
else if (sash != null) {
moveIsVertical = sash.isVertical();
setMouseState(PotentialMovingGridButtons);
}
else if (pWidget != null) {
ui.initiateRetitleFigure(pWidget);
}
else {
// Get whatever graphical widget under x,y
GraphicalWidget<?> wf = ui.widgetLocatedAtXY(x, y);
MoveHalo halo = ui.moveHaloUnderXY(x, y);
RemoteLinkage linkage = ui.linkageUnderXY(x, y);
GraphicalParentWidget<?, ?> currentParent = null;
// If SHIFT is held, always treat as start of a drag toggle select.
if ((state & InputEvent.SHIFT) != 0) {
setMouseState(PotentialTogglingSelection);
}
else if (wf != null) {
IWidget widget = wf.getModel();
SimpleWidgetGroup group = widget.getParentGroup();
lastClickedWidget = widget;
if ((state & platformDuplicateModifierKey()) != 0) {
if (! selection.isElementSelected(widget)) {
selection.setSelectedSelnFig(wf);
}
if ((group != null) &&
(group.getOrientation() != SimpleWidgetGroup.FREEFORM) &&
(selection.getWidgetSelectionCount() == 1))
{
setMouseState(PotentialInsertDuplicateWidget);
}
else {
setMouseState(PotentialDuplicatingWidget);
}
}
else {
if ((group != null) &&
(group.getOrientation() != SimpleWidgetGroup.FREEFORM))
{
if (! selection.isElementSelected(widget)) {
selection.setSelectedSelnFig(wf);
}
setMouseState(PotentialReorderWidget);
}
// Mouse down on a selected widget indicates possible move.
// Mouse down on a unselected widget indicates deselect
// other widgets, select that widget, and prepare for
// possible move.
else if (selection.isSelectionFigureSelected(wf)) {
setMouseState(PotentialMovingSelection);
}
else {
selection.setSelectedSelnFig(wf);
setMouseState(PotentialMovingWidget);
}
}
if (wf instanceof GraphicalMenuItem) {
GraphicalMenuItem menuItemFig = (GraphicalMenuItem) wf;
if (menuItemFig.isSubmenu()) {
currentParent = menuItemFig;
}
else {
currentParent = menuItemFig.getParentFigure();
}
}
else if (wf instanceof GraphicalParentWidget<?, ?>) {
currentParent =
(GraphicalParentWidget<?, ?>) wf;
}
else if (wf instanceof GraphicalChildWidget<?, ?>) {
currentParent =
((GraphicalChildWidget<?, ?>) wf).getParentFigure();
}
}
else if (halo != null) {
if (halo instanceof FrameEltGroupHalo) {
FrameEltGroupHalo groupHalo = (FrameEltGroupHalo) halo;
if ((state & platformDuplicateModifierKey()) != 0) {
if (! selection.isElementSelected(halo.getData()))
{
selection.setSelectedSelnFig(groupHalo);
}
setMouseState(PotentialDuplicatingWidget);
}
else {
selection.setSelectedSelnFig(groupHalo);
setMouseState(PotentialMovingSelection);
}
}
else {
/* if (wf != null) {
IWidget widget = wf.getWidgetModel();
SimpleWidgetGroup group = widget.getParentGroup();
if ((state & platformDuplicateModifierKey()) != 0) {
if (! this.selection.isWidgetSelected(widget)) {
this.selection.setSelectedWidget(wf);
}
if ((group != null) &&
(group.getOrientation() != SimpleWidgetGroup.FREEFORM) &&
(this.selection.getWidgetSelectionCount() == 1))
{
setMouseState(PotentialInsertDuplicateWidget);
}
else {
setMouseState(PotentialDuplicatingWidget);
}
}
else {
setMouseState(PotentialMovingSelection);
}
}
else { */
setMouseState(PotentialMovingSelection);
}
// }
}
else if (linkage != null) {
FrameElement owner = linkage.getOwner();
IWidget remoteLabel = linkage.getRemoteLabel();
// Ensure both are selected; if not yet, then set them
// both selected.
FrameEltSelnFig<?> ownerRepresentative =
isSomeElementSelected(owner);
if ((! selection.isElementSelected(remoteLabel)) ||
(ownerRepresentative == null))
{
if (ownerRepresentative == null) {
ownerRepresentative = getRepresentativeElt(owner);
}
selection.setSelectedSelnFig(linkage.getRemoteLabelFigure());
if (ownerRepresentative != null) {
selection.selectSelnFig(ownerRepresentative);
}
}
setMouseState(PotentialMovingSelection);
}
// Otherwise, out in space and SHIFT not held; deselect all and
// prepare to create
else {
selection.deselectAll();
setMouseState(PotentialCreatingWidget);
}
ui.hideAllChildren();
if (currentParent != null) {
currentParent.openChildren();
ui.resetVisibleArea();
}
}
} // handleMousePressed
protected void showSelectionArea(RectangleFigure rf, Rectangle bds)
{
rf.setBounds(bds);
rf.setOutline(true);
rf.setFill(false);
//add new rectangle object to the array list
duplicatingDynamic.add(rf);
ui.getViewEditor().addInteractionFigure(rf);
}
@Override
protected boolean dealWithMousePressed(IFigure figure,
int button, int x, int y, int state)
{
boolean goForward =
super.dealWithMousePressed(figure, button, x, y, state);
ui.confirmRenameFigure();
if (goForward) {
// Record the mouse down position in 1:1 coordinates
setMouseDown(x, y);
switch (getMouseState()) {
case MouseUp: {
handleMousePressed(x, y, state);
break;
} // case MouseUp:
default: {
// TODO: Throw exception?
break;
}
} // switch (getMouseState())
}
cleanup(); //XYZZY call updateDynamic() instead? only if not MouseUp?
lastX = x;
lastY = y;
return goForward;
} // mousePressed
protected void clearRectFigures()
{
for (int i = 0; i < duplicatingDynamic.size(); i++) {
RectangleFigure rf = duplicatingDynamic.get(i);
ui.getViewEditor().removeInteractionFigure(rf);
}
duplicatingDynamic.clear();
}
/**
* A mouse up event was called.
* Checks for context selection, then performs actions as dictated
* by the FSM.
*/
@Override
protected boolean dealWithMouseReleased(IFigure figure,
int button, int x, int y, int state)
{
boolean goForward =
super.dealWithMouseReleased(figure, button, x, y, state);
// Clear any mouse drag timer, that may be running
stopMouseDragTimer = true;
if (goForward && isMouseDownValid) {
// Record the mouse down position
double zoom = ui.getZoom();
// The current mouse down position (scaled)
double currentScaledX = x / zoom;
double currentScaledY = y / zoom;
switch (getMouseState()) {
case PotentialCreatingWidget:
case PotentialMovingWidget:
case PotentialResizingWidget: {
// Nothing to do; any action necessary was taken on "down".
break;
}
case PotentialReorderWidget:
case PotentialMovingSelection: {
// Get whatever graphical widget under original down x,y
GraphicalWidget<?> wf =
ui.widgetLocatedAtXY(mouseDownX,
mouseDownY);
MoveHalo halo = null;
FrameElement data = null;
if (wf == null) {
halo = ui.moveHaloUnderXY(mouseDownX,
mouseDownY);
if (halo != null) {
data = halo.getData();
if (data instanceof SimpleWidgetGroup) {
IWidget[] widgets =
selection.getSelectedIWidgets();
SimpleWidgetGroup group = (SimpleWidgetGroup) data;
for (IWidget widget : widgets) {
if (widget.getParentGroup() != group) {
selection.deselectElement(widget);
}
}
break;
}
if (data instanceof IWidget) {
wf =
ui.frameUI.getWidgetFigure((IWidget) data);
}
}
}
if (wf != null) {
selection.setSelectedSelnFig(wf);
}
else {
if (halo == null) {
halo = ui.moveHaloUnderXY(mouseDownX,
mouseDownY);
if (halo != null) {
data = halo.getData();
}
}
if ((data != null) &&
(data instanceof FrameElementGroup))
{
selection.setSelectedSelnFig((FrameEltGroupHalo) halo);
}
}
break;
}
case PotentialTogglingSelection: {
// If mouse down on a widget, toggle selection.
GraphicalWidget<?> wf =
ui.widgetLocatedAtXY(mouseDownX,
mouseDownY);
MoveHalo halo = ui.moveHaloUnderXY(mouseDownX,
mouseDownY);
FrameElement data = null;
if (halo != null) {
data = halo.getData();
}
if (wf == null) {
if (data instanceof IWidget) {
wf =
ui.frameUI.getWidgetFigure((IWidget) data);
}
}
if (wf != null) {
// If the widget is already selected, unselect it.
if (selection.isSelectionFigureSelected(wf)) {
selection.deselectSelnFig(wf);
}
else {
selection.selectSelnFig(wf);
}
}
else if (data instanceof SimpleWidgetGroup) {
Iterator<IWidget> widgets =
((SimpleWidgetGroup) data).iterator();
while (widgets.hasNext()) {
IWidget w = widgets.next();
selection.deselectElement(w);
}
}
else if ((halo instanceof FrameEltGroupHalo) &&
(data instanceof FrameElementGroup))
{
FrameEltGroupHalo groupHalo = (FrameEltGroupHalo) halo;
if (selection.isElementSelected(data)) {
selection.deselectSelnFig(groupHalo);
}
else {
selection.selectSelnFig(groupHalo);
}
}
else {
selection.deselectAll();
}
break;
}
// Move is complete, so apply changes to the model.
case MovingWidgets: {
// Get selection, and use the difference between current
// and start location.
double moveByX = currentScaledX - scaledMouseDownX;
double moveByY = currentScaledY - scaledMouseDownY;
FrameEditorUI.MoveParameters prms =
new FrameEditorUI.MoveParameters(moveByX, moveByY, selection);
ui.performAction(CogToolLID.MoveWidgets, prms);
break;
}
case ReorderWidget: {
boolean reorder = reorderWidget(x, y, reorderParms);
ui.clearUISupport(true);
isReordering = false;
if (reorder) {
ui.performAction(FrameEditorLID.Reorder,
reorderParms);
}
else {
selection.deselectElement(lastClickedWidget);
}
InteractionDrawingEditor editor = ui.getViewEditor();
editor.removeInteractionFigure(reorderFigure);
editor.removeInteractionFigure(dividerLine);
break;
}
// Perform resize operation. More complex then move,
// since the user may have flipped the orientation.
case ResizingWidget: {
// Switch to quality mode rendering for graphical widgets
setWidgetFastRenderMode(false);
if (currentScaledX < 0.0) {
currentScaledX = 0.0;
}
if (currentScaledY < 0.0) {
currentScaledY = 0.0;
}
// Deal with any anchoring issues
if (ui.resizeHandlesUIFig.isTopLeftAnchored()) {
switch (currentResizeHandleType) {
case FrameEditorUI.TOP_RIGHT: {
// Cannot change Y position
currentScaledY = initialResizeArea.y;
break;
}
case FrameEditorUI.BOTTOM_LEFT: {
// Cannot change X position
currentScaledX = initialResizeArea.x;
break;
}
default: {
break;
}
}
// Cannot move left of top-left
if (currentScaledX < initialResizeArea.x) {
currentScaledX = initialResizeArea.x;
}
// Cannot move above of top-left
if (currentScaledY < initialResizeArea.y) {
currentScaledY = initialResizeArea.y;
}
}
double width =
Math.abs(currentScaledX - mouseFixedResizeX);
double height =
Math.abs(currentScaledY - mouseFixedResizeY);
FrameEditorUI.ResizeParameters prms =
new FrameEditorUI.ResizeParameters(initialResizeArea.x,
initialResizeArea.y,
Math.min(currentScaledX,
mouseFixedResizeX),
Math.min(currentScaledY,
mouseFixedResizeY),
width / initialResizeArea.width,
height / initialResizeArea.height,
selection);
ui.performAction(CogToolLID.ResizeWidgets, prms);
break;
}
// Finished a mouse drag operation to create a new widget
case CreatingWidget: {
// Get the difference between mouse down and mouse up,
// create a new widget in that rectangle.
if (currentScaledX < 0.0) {
currentScaledX = 0.0;
}
if (currentScaledY < 0.0) {
currentScaledY = 0.0;
}
double width =
Math.abs(scaledMouseDownX - currentScaledX);
double height =
Math.abs(scaledMouseDownY - currentScaledY);
double leftX = (scaledMouseDownX > currentScaledX)
? currentScaledX
: scaledMouseDownX;
double topY = (scaledMouseDownY > currentScaledY)
? currentScaledY
: scaledMouseDownY;
// Turn off the bounding box drawn.
ui.stopDrawingTemporaryFigure();
// Create a rectangle for the new region.
DoubleRectangle region =
new DoubleRectangle(leftX, topY, width, height);
// Don't create a new widget with either a height or
// width of 0. Probably this should be less then 2.
if ((region.width != 0.0) && (region.height != 0.0)) {
ui.performAction(CogToolLID.NewWidget,
new FrameEditorUI.NewWidgetParameters(region,
ui.getCurrentWidgetType(),
ui.view.isAutomaticCreation()));
}
break;
}
// Finished a mouse drag operation to select a set of widgets
case TogglingSelection: {
// Get the total area selected
double width =
Math.abs(scaledMouseDownX - currentScaledX);
double height =
Math.abs(scaledMouseDownY - currentScaledY);
// Get top left point.
double leftX = (scaledMouseDownX > currentScaledX)
? currentScaledX
: scaledMouseDownX;
double topY = (scaledMouseDownY > currentScaledY)
? currentScaledY
: scaledMouseDownY;
// Turn off the bounding box drawn.
ui.stopDrawingTemporaryFigure();
// Create the final region's area
DoubleRectangle region =
new DoubleRectangle(leftX, topY, width, height);
// Loop through all figures and check for intersections
Iterator<GraphicalWidget<?>> gwFigures =
ui.getFrameUI().getFigureListIterator();
while (gwFigures.hasNext()) {
GraphicalWidget<?> gw = gwFigures.next();
if (! (gw instanceof GraphicalChildWidget<?, ?>)) {
Rectangle bounds = gw.getBounds();
if (region.intersects(bounds.x,
bounds.y,
bounds.width,
bounds.height))
{
// If the widget is already selected, deselect it.
if (selection.isSelectionFigureSelected(gw))
{
selection.deselectSelnFig(gw);
}
else {
selection.selectSelnFig(gw);
}
}
}
}
break;
}
case PotentialInsertDuplicateWidget:
case PotentialDuplicatingWidget: {
GraphicalWidget<?> widgetFig =
ui.widgetLocatedAtXY(mouseDownX,
mouseDownY);
selection.setSelectedSelnFig(widgetFig);
break;
}
case DuplicatingWidgets: {
double dx = currentScaledX - scaledMouseDownX;
double dy = currentScaledY - scaledMouseDownY;
// Remove all the rectangle figures from the display, clear the list
clearRectFigures();
ui.performAction(FrameEditorLID.Duplicate,
new FrameEditorUI.DuplicateParameters(dx, dy,
selection));
break;
}
case InsertDuplicateWidget: {
double dx = currentScaledX - scaledMouseDownX;
double dy = currentScaledY - scaledMouseDownY;
isReordering = false;
if (reorderWidget(x, y, insertDuplicateParms)) {
insertDuplicateParms.moveByX = dx;
insertDuplicateParms.moveByY = dy;
ui.performAction(FrameEditorLID.InsertDuplicate,
insertDuplicateParms);
}
else {
selection.deselectElement(lastClickedWidget);
}
InteractionDrawingEditor editor = ui.getViewEditor();
editor.removeInteractionFigure(reorderFigure);
editor.removeInteractionFigure(dividerLine);
break;
}
case PotentialMovingGridButtons: {
if (movedGridButtons != null) {
movedGridButtons = NO_GRID_BUTTONS;
}
break;
}
case MovingGridButtons: {
// Get selection, and use the difference between current
// and start location.
double moveByX;
double moveByY;
GraphicalWidget<?> gw = ui.getPotentialFigureOwner();
GridButton gb = (GridButton) gw.getModel();
DoublePoint start = gb.getShape().getOrigin();
if (moveIsVertical) {
moveByX = 0.0;
if (currentScaledY < minY) {
moveByY = (minY - start.y);
}
else {
moveByY = currentScaledY - scaledMouseDownY;
}
}
else {
moveByY = 0.0;
if (currentScaledX < minX) {
moveByX = (minX - start.x);
}
else {
moveByX = currentScaledX - scaledMouseDownX;
}
}
if ((moveByX != 0.0) || (moveByY != 0.0)) {
FrameEditorUI.MoveParameters prms =
new FrameEditorUI.MoveParameters(moveByX,
moveByY,
selection,
false);
ui.performAction(CogToolLID.MoveWidgets, prms);
}
break;
}
}
// Clear the values used.
lastClickedWidget = null;
isMouseDownValid = false;
setMouseState(MouseUp);
}
cleanup();
return goForward;
} // mouseReleased
@Override
public void cleanup()
{
resetPrevCursor();
super.cleanup();
}
protected boolean reorderAllowed(IWidget targetWidget)
{
// Ensure the targetWidget is not a child of the last clicked widget
while (targetWidget instanceof ChildWidget) {
IWidget parent = ((ChildWidget) targetWidget).getParent();
if (parent == lastClickedWidget) {
return false;
}
targetWidget = parent;
}
return true;
}
protected boolean reorderWidget(int x, int y, FrameEditorUI.ReorderWidgetParameters prms)
{
GraphicalWidget<?> wf = ui.widgetLocatedAtXY(x, y);
if ((wf == null) && withinReorder(x, y)) {
wf = lastHoveredWidget;
}
prms.widgetGroup = null;
prms.insertIndex = -1;
prms.parent = null;
prms.reorderWidget = lastClickedWidget;
if (wf != null) {
IWidget targetWidget = wf.getModel();
if (! ui.areCompatible(lastClickedWidget, targetWidget)) {
return false;
}
if (! reorderAllowed(targetWidget)) {
return false;
}
prms.insertIndex = getInsertIndex(targetWidget, x, y);
if (prms.insertIndex < 0) {
return false;
}
if (lastClickedWidget instanceof ChildWidget) {
if (targetWidget instanceof ChildWidget) {
prms.widgetGroup = targetWidget.getParentGroup();
prms.parent = ((ChildWidget) targetWidget).getParent();
}
else if (targetWidget instanceof AParentWidget) {
// "else if" because only MenuItems are both, and for them
// we want the ChildWidget behavior (to add the item to
// a submenu, drag it over the potential figure)
prms.parent = (AParentWidget) targetWidget;
prms.widgetGroup = prms.parent.getChildren();
prms.insertIndex = 0;
}
}
else {
// moving around menu headers or list items
prms.widgetGroup = targetWidget.getParentGroup();
}
return true;
}
PotentialFigure pf = ui.potentialWidgetUnderXY(x, y);
if (pf != null) {
GraphicalTraversableWidget<?> owner =
ui.getPotentialFigureOwner();
if (owner instanceof GraphicalMenuItem) {
if (pf == ui.potentialUIFig.getRightFigure()) {
prms.parent = (MenuItem) owner.getModel();
prms.widgetGroup = prms.parent.getChildren();
prms.insertIndex = 0;
return true;
}
}
else {
if ((owner instanceof GraphicalParentWidget<?, ?>) &&
(pf == ui.potentialUIFig.getBottomFigure()))
{
prms.parent = (AParentWidget) owner.getModel();
prms.widgetGroup = prms.parent.getChildren();
prms.insertIndex = 0;
}
else {
IWidget widget = owner.getModel();
prms.widgetGroup = widget.getParentGroup();
prms.insertIndex = prms.widgetGroup.indexOf(widget) + 1;
}
return true;
}
}
// out in space
return ((! prms.requiresTarget()) &&
! (lastClickedWidget instanceof ChildWidget));
}
protected int getInsertIndex(IWidget widgetModel, int x, int y)
{
double zoom = ui.getZoom();
DoubleRectangle widgetBds = widgetModel.getEltBounds();
double centerX = widgetBds.x + (widgetBds.width / 2);
double centerY = widgetBds.y + (widgetBds.height / 2);
int originX = PrecisionUtilities.round(centerX * zoom);
int originY = PrecisionUtilities.round(centerY * zoom);
SimpleWidgetGroup group = widgetModel.getParentGroup();
int index = 0;
if (group != null) {
index = group.indexOf(widgetModel);
switch (group.getOrientation()) {
case SimpleWidgetGroup.HORIZONTAL: {
if (x > originX) {
index++;
}
break;
}
case SimpleWidgetGroup.VERTICAL: {
if (y > originY) {
index++;
}
break;
}
default: {
index++;
}
}
}
return index;
}
protected boolean withinHysteresis(int eventX, int eventY)
{
return (Math.abs(eventX - mouseDownX) < HYSTERESIS) &&
(Math.abs(eventY - mouseDownY) < HYSTERESIS);
}
/**
* Support for dynamic drawing of a widget as feedback for toggling
* selection or creating a new widget.
*
*/
protected void redrawTemporaryWidget(double currentScaledX,
double currentScaledY,
boolean asOutline)
{
double originDownX = scaledMouseDownX;
double originDownY = scaledMouseDownY;
// Prevent the ability to create a widget to
// an origin less then 0.0,0.0
if (currentScaledX < 0.0) {
currentScaledX = 0.0;
}
if (currentScaledY < 0.0) {
currentScaledY = 0.0;
}
// Create the size
double width = Math.abs(originDownX - currentScaledX);
double height = Math.abs(originDownY - currentScaledY);
if (currentScaledX < originDownX) {
originDownX = currentScaledX;
}
if (currentScaledY < originDownY) {
originDownY = currentScaledY;
}
ui.setTemporaryWidget(originDownX, originDownY, width, height,
asOutline);
}
protected void updateDynamicDuplicate(int x, int y)
{
int dx = x - lastX;
int dy = y - lastY;
Iterator<RectangleFigure> frameFigs =
duplicatingDynamic.iterator();
while (frameFigs.hasNext()) {
RectangleFigure frameFig = frameFigs.next();
Point frameOrigin = frameFig.getLocation();
frameOrigin.x += dx;
frameOrigin.y += dy;
frameFig.setLocation(frameOrigin);
}
}
/**
* Utility to help with dynamic move of selected widgets.
*
* @param selectedWFs the set of selected graphical widget figures
* @param currentScaledX scaled X location of the current mouse position
* @param currentScaledY scaled Y location of the current mouse position
* @author mlh/alex
*/
protected void dynamicMoveWidgets(Iterator<FrameEltSelnFig<?>> selectedFigs,
double currentScaledX,
double currentScaledY)
{
ui.hideNondynamicSupport(true);
// Iterate through list of selected objects
while (selectedFigs.hasNext()) {
FrameEltSelnFig<?> fig = selectedFigs.next();
// Determine movement amount from initial points.
double offsetX = currentScaledX - scaledMouseDownX;
double offsetY = currentScaledY - scaledMouseDownY;
if (fig instanceof GraphicalChildWidget<?, ?>) {
continue;
}
else if (fig instanceof FrameEltGroupHalo) {
ui.moveFrameElementGroup(offsetX,
offsetY,
((FrameEltGroupHalo) fig).getModel());
}
else if (fig instanceof GraphicalWidget<?>) {
GraphicalWidget<?> gw = (GraphicalWidget<?>) fig;
if ((gw instanceof GraphicalTraversableWidget<?>) &&
(gw.getModel().getParentGroup() != null))
{
ui.moveWidgetGroup(offsetX,
offsetY,
(GraphicalTraversableWidget<?>) gw);
}
else {
DoublePoint p = gw.getModel().getShape().getOrigin();
double tempX = offsetX + p.x;
double tempY = offsetY + p.y;
// Prevent moving a widget to an origin less than 0.0,0.0
if (tempX < 0.0) {
tempX = 0.0;
}
if (tempY < 0.0) {
tempY = 0.0;
}
// Temporarily move the widget origin to new location.
ui.setGraphicalWidgetOrigin(tempX, tempY, gw);
}
}
}
} // dynamicMoveWidgets
protected void setDividerBounds(GraphicalWidget<?> widget,
int x,
int y)
{
double zoom = ui.getZoom();
Rectangle bounds = widget.getBounds();
int originX = bounds.x + (bounds.width / 2);
int originY = bounds.y + (bounds.height / 2);
originX = PrecisionUtilities.round(originX * zoom);
originY = PrecisionUtilities.round(originY * zoom);
double newX = bounds.x;
double newY = bounds.y;
double newH = bounds.height;
double newW = bounds.width;
int heightInc = 0;
int widthInc = 0;
if ((lastClickedWidget instanceof ChildWidget) &&
(widget instanceof GraphicalParentWidget<?, ?>) &&
(! (widget instanceof GraphicalMenuItem)))
{
AParentWidget parent = (AParentWidget) widget.getModel();
int childrenLoc = parent.getChildrenLocation();
switch (childrenLoc) {
case AParentWidget.CHILDREN_BELOW:
case AParentWidget.CHILDREN_CENTER: {
if (childrenLoc == AParentWidget.CHILDREN_CENTER) {
newX += newW / 2.0;
newY += newH / 2.0;
}
else {
newY += newH;
}
if (parent.hasChildren()) {
IWidget child = parent.getItem(0);
DoubleSize childSize = child.getShape().getSize();
newW = childSize.width;
newH = childSize.height;
Object value =
child.getAttribute(WidgetAttributes.IS_SEPARATOR_ATTR);
if (NullSafe.equals(WidgetAttributes.IS_SEPARATOR, value))
{
newH *= FrameEditorUI.SEPARATOR_RATIO;
}
}
else if (parent instanceof MenuHeader) {
newW *= FrameEditorUI.MENU_ITEM_RATIO;
}
newH /= 10.0;
newY -= newH / 2.0;
heightInc = 1;
break;
}
case AParentWidget.CHILDREN_RIGHT: {
newX += newW;
if (parent.hasChildren()) {
DoubleSize childSize =
parent.getItem(0).getShape().getSize();
newW = childSize.width;
newH = childSize.height;
}
newW /= 20.0;
newX -= newW / 2.0;
widthInc = 1;
break;
}
}
}
else {
switch (widget.getModel().getParentGroup().getOrientation()) {
case SimpleWidgetGroup.HORIZONTAL: {
if (x > originX) {
newX += newW;
}
newW /= 20.0;
newX -= newW / 2.0;
widthInc = 1;
break;
}
case SimpleWidgetGroup.VERTICAL: {
if (y > originY) {
newY += newH;
}
newH /= 10.0;
Object value =
widget.getModel().getAttribute(WidgetAttributes.IS_SEPARATOR_ATTR);
if (WidgetAttributes.IS_SEPARATOR.equals(value)) {
newH *= FrameEditorUI.SEPARATOR_RATIO;
}
newY -= newH / 2.0;
heightInc = 1;
break;
}
}
}
newX *= zoom;
newY *= zoom;
double rightX = newX + (newW + widthInc) * zoom;
double bottomY = newY + (newH + heightInc) * zoom;
newW = PrecisionUtilities.round(rightX - newX);
newH = PrecisionUtilities.round(bottomY - newY);
newX = PrecisionUtilities.round(newX);
newY = PrecisionUtilities.round(newY);
dividerLine.setBounds(new Rectangle((int) newX, (int) newY,
(int) newW, (int) newH));
dividerLine.setVisible(true);
}
protected void dynamicReorderWidget(int x, int y, boolean duplicate)
{
int dx = x - lastX;
int dy = y - lastY;
Point frameOrigin = reorderFigure.getLocation();
frameOrigin.x += dx;
frameOrigin.y += dy;
reorderFigure.setLocation(frameOrigin);
GraphicalWidget<?> curWidgetFig = ui.widgetLocatedAtXY(x, y);
PotentialFigure potential = ui.potentialWidgetUnderXY(x, y);
ui.resizeHandlesUIFig.hide();
dividerLine.setVisible(false);
if ((curWidgetFig != null) &&
ui.areCompatible(lastClickedWidget,
curWidgetFig.getModel()) &&
(duplicate || reorderAllowed(curWidgetFig.getModel())))
{
boolean isChildWidget =
lastClickedWidget instanceof ChildWidget;
boolean changedWidget =
curWidgetFig.getModel() != lastClickedWidget;
lastHoveredWidget = curWidgetFig;
// If the dragged widget is not a child, it won't make sense to
// drag it onto a potential figure
// MLHQ: How do you know this cast will succeed?
ui.hideNonhaloSupport(! isChildWidget,
changedWidget,
(GraphicalTraversableWidget<?>) curWidgetFig);
setDividerBounds(curWidgetFig, x, y);
if (lastClickedWidget instanceof ChildWidget) {
if (curWidgetFig instanceof GraphicalChildWidget<?, ?>) {
GraphicalChildWidget<?, ?> child =
(GraphicalChildWidget<?, ?>) curWidgetFig;
child.getParentFigure().openChildren();
}
if (curWidgetFig instanceof GraphicalParentWidget<?, ?>) {
if (changedWidget) {
((GraphicalParentWidget<?, ?>) curWidgetFig).openChildren();
}
}
}
}
else if (potential != null) {
GraphicalTraversableWidget<?> owner =
ui.getPotentialFigureOwner();
if (owner instanceof GraphicalChildWidget<?, ?>) {
((GraphicalChildWidget<?, ?>) owner).getParentFigure().openChildren();
}
ui.potentialUIFig.setSelection(potential);
}
else {
if (withinReorder(x, y)) {
setDividerBounds(lastHoveredWidget, x, y);
if (lastHoveredWidget instanceof GraphicalChildWidget<?, ?>) {
GraphicalChildWidget<?, ?> gcw =
(GraphicalChildWidget<?, ?>) lastHoveredWidget;
gcw.getParentFigure().openChildren();
}
}
else if ((lastClickedWidget instanceof ChildWidget) ||
! duplicate)
{
ui.hideNondynamicSupport(true, false);
InteractionFigure drawLayer =
ui.getViewEditor().getInteractionFigure();
drawLayer.setCursor(NOT_ALLOWED_CURSOR);
}
}
} // dynamicReorderWidget
protected boolean withinReorder(int x, int y)
{
double zoom = ui.getZoom();
if (lastHoveredWidget != null) {
IWidget lastWidget = lastHoveredWidget.getModel();
SimpleWidgetGroup lastGroup = lastWidget.getParentGroup();
if (lastGroup != null) {
if (lastGroup.indexOf(lastWidget) == lastGroup.size() - 1)
{
Rectangle bounds = ui.getGroupFigureBounds(lastGroup);
Rectangle reorderBds = new Rectangle();
Rectangle widgetBds = lastHoveredWidget.getBounds();
reorderBds.width = widgetBds.width + 20;
reorderBds.height = widgetBds.height + 20;
if (lastGroup.getOrientation() == SimpleWidgetGroup.VERTICAL) {
reorderBds.x = bounds.x;
reorderBds.y = bounds.y + bounds.height;
}
else if (lastGroup.getOrientation() == SimpleWidgetGroup.HORIZONTAL)
{
reorderBds.x = bounds.x + bounds.width;
reorderBds.y = bounds.y;
}
reorderBds.x =
PrecisionUtilities.round(reorderBds.x * zoom);
reorderBds.y =
PrecisionUtilities.round(reorderBds.y * zoom);
reorderBds.width =
PrecisionUtilities.round(reorderBds.width * zoom);
reorderBds.height =
PrecisionUtilities.round(reorderBds.height * zoom);
return reorderBds.contains(x, y);
}
}
}
return false;
}
protected void dynamicResizeWidget(IWidget widget,
GraphicalWidget<?> gw,
double ratioX,
double ratioY,
double newLeft,
double newTop)
{
DoubleRectangle bds = widget.getEltBounds();
double newX =
ratioX * (bds.x - initialResizeArea.x) + newLeft;
double newY =
ratioY * (bds.y - initialResizeArea.y) + newTop;
ui.setGraphicalWidgetBounds(newX,
newY,
ratioX * bds.width,
ratioY * bds.height,
gw);
}
protected void dynamicResizeGroup(Association<?> group,
double ratioX,
double ratioY,
double newLeft,
double newTop,
boolean childrenToo)
{
Iterator<? extends FrameElement> groupElts = group.iterator();
while (groupElts.hasNext()) {
FrameElement elt = groupElts.next();
if (elt instanceof IWidget) {
IWidget w = (IWidget) elt;
GraphicalWidget<?> gw = ui.frameUI.getWidgetFigure(w);
dynamicResizeWidget(w, gw, ratioX, ratioY, newLeft, newTop);
if (childrenToo && (w instanceof AParentWidget)) {
AParentWidget pw = (AParentWidget) w;
SimpleWidgetGroup children = pw.getChildren();
if (children != null) {
dynamicResizeGroup(children,
ratioX, ratioY, newLeft, newTop,
childrenToo);
}
}
}
else if (elt instanceof Association<?>) {
dynamicResizeGroup((Association<?>) elt,
ratioX, ratioY, newLeft, newTop,
childrenToo);
}
}
}
/**
* Utility to help with dynamic resize of selected widgets.
*
* @param selectedWFs the set of selected graphical widget figures
* @param currentScaledX scaled X location of the current mouse position
* @param currentScaledY scaled Y location of the current mouse position
* @author mlh/alex
*/
protected void dynamicResizeWidgets(Iterator<FrameEltSelnFig<?>> selectedWFs,
double currentScaledX,
double currentScaledY)
{
// Prevent the ability to resize a widget to
// an origin less then 0.0,0.0
if (currentScaledX < 0.0) {
currentScaledX = 0.0;
}
if (currentScaledY < 0.0) {
currentScaledY = 0.0;
}
// Deal with any anchoring issues
if (ui.resizeHandlesUIFig.isTopLeftAnchored()) {
switch (currentResizeHandleType) {
case FrameEditorUI.TOP_RIGHT: {
// Cannot change Y position
currentScaledY = initialResizeArea.y;
break;
}
case FrameEditorUI.BOTTOM_LEFT: {
// Cannot change X position
currentScaledX = initialResizeArea.x;
break;
}
default: {
break;
}
}
// Cannot move left of top-left
if (currentScaledX < initialResizeArea.x) {
currentScaledX = initialResizeArea.x;
}
// Cannot move above of top-left
if (currentScaledY < initialResizeArea.y) {
currentScaledY = initialResizeArea.y;
}
}
double newWidth = Math.abs(currentScaledX - mouseFixedResizeX);
double newHeight = Math.abs(currentScaledY - mouseFixedResizeY);
double newLeft = Math.min(currentScaledX, mouseFixedResizeX);
double newTop = Math.min(currentScaledY, mouseFixedResizeY);
double ratioX = newWidth / initialResizeArea.width;
double ratioY = newHeight / initialResizeArea.height;
// Iterate through selected widgets
while (selectedWFs.hasNext()) {
FrameEltSelnFig<?> f = selectedWFs.next();
if (f instanceof GraphicalWidget<?>) {
GraphicalWidget<?> gw = (GraphicalWidget<?>) f;
IWidget w = gw.getModel();
SimpleWidgetGroup group = w.getParentGroup();
if ((w instanceof TraversableWidget) && (group != null)) {
dynamicResizeGroup(group,
ratioX, ratioY, newLeft, newTop, false);
}
else {
dynamicResizeWidget(w, gw, ratioX, ratioY, newLeft, newTop);
}
}
else if (f instanceof FrameEltGroupHalo) {
dynamicResizeGroup(((FrameEltGroupHalo) f).getModel(),
ratioX, ratioY, newLeft, newTop, true);
}
}
} // dynamicResizeWidgets
/**
* Only needed below in mouseDragged!
*/
protected org.eclipse.swt.graphics.Point updateDelta =
new org.eclipse.swt.graphics.Point(0, 0);
/**
* Parameterized mouse drags so they can be scaled programatically.
*
* Handles mouse drags: moves the viewable area as needed
*
* @param figure ignored
* @param mouseX the location the mouse is pointing to
* @param mouseY the location the mouse is pointing to
* @param button the buttons pressed
* @param state the modifiers being held down
*/
@Override
protected boolean dealWithMouseDragged(IFigure figure,
int button,
int mouseX,
int mouseY,
int state)
{
boolean goForward =
super.dealWithMouseDragged(figure, button, mouseX, mouseY, state);
if (goForward && isMouseDownValid) {
// Get the StandardEditor for handling visuals & scrolling
StandardDrawingEditor editor = ui.getViewEditor();
// Update VIEW to ensure mouse point is visible.
// If outside of the visible canvas, up-click should cancel!
stopMouseDragTimer = true;
stopDynamic =
editor.movePointNearEdge(mouseX, mouseY, updateDelta);
double zoom = ui.getZoom();
// The current mouse down position (scaled)
double currentScaledX = mouseX / zoom;
double currentScaledY = mouseY / zoom;
Iterator<FrameEltSelnFig<?>> selectedEltFigs =
selection.getSelectedFigures();
InteractionFigure drawLayer =
ui.getViewEditor().getInteractionFigure();
// If the initial point of the mouse move was a widget,
// then this is a move operation.
// Else this is a new widget
switch (getMouseState()) {
// Perform move-related states
case PotentialMovingWidget:
case PotentialMovingSelection: {
if (withinHysteresis(mouseX, mouseY)) {
return true;
}
ui.hideNondynamicSupport(false);
prevCursor = drawLayer.getCursor();
drawLayer.setCursor(SELECT_CURSOR);
setMouseState(MovingWidgets);
// fall through!
}
case MovingWidgets: {
dynamicMoveWidgets(selectedEltFigs,
currentScaledX,
currentScaledY);
ui.repaintEditor();
break;
}
case PotentialReorderWidget: {
if (withinHysteresis(mouseX, mouseY)) {
return true;
}
prevCursor = drawLayer.getCursor();
ui.hideNondynamicSupport(false, false);
setUpReorderFigures();
prevCursor = drawLayer.getCursor();
drawLayer.setCursor(SELECT_CURSOR);
setMouseState(ReorderWidget);
// fall through!
}
case ReorderWidget: {
dynamicReorderWidget(mouseX, mouseY, false);
break;
}
case PotentialResizingWidget: {
if (withinHysteresis(mouseX, mouseY)) {
return true;
}
ui.hideNondynamicSupport(true);
// Iterate through selected widgets XYZZY openChildren in a loop???
while (selectedEltFigs.hasNext()) {
FrameEltSelnFig<?> gw = selectedEltFigs.next();
if (gw instanceof GraphicalTraversableWidget<?>) {
if (gw instanceof GraphicalChildWidget<?, ?>) {
((GraphicalChildWidget<?, ?>) gw).getParentFigure().openChildren();
}
}
}
setMouseState(ResizingWidget);
// fall through!
}
// Perform a drag resize of selected widget
case ResizingWidget: {
dynamicResizeWidgets(selectedEltFigs,
currentScaledX,
currentScaledY);
ui.repaintEditor();
break;
}
// Create a new widget
case PotentialCreatingWidget: {
if (withinHysteresis(mouseX, mouseY)) {
return true;
}
setMouseState(CreatingWidget);
// fall through!
}
case CreatingWidget: {
redrawTemporaryWidget(currentScaledX,
currentScaledY,
false);
break;
}
// Sweeping a region for toggling selection
case PotentialTogglingSelection: {
if (withinHysteresis(mouseX, mouseY)) {
return true;
}
setMouseState(TogglingSelection);
// fall through!
}
case TogglingSelection: {
redrawTemporaryWidget(currentScaledX,
currentScaledY,
true);
break;
}
case PotentialDuplicatingWidget: {
if (withinHysteresis(mouseX, mouseY)) {
return true;
}
ui.hideNondynamicSupport(false, false);
duplicatingDynamic = new ArrayList<RectangleFigure>();
Iterator<FrameEltSelnFig<?>> selectedFigs =
selection.getSelectedFigures();
while (selectedFigs.hasNext()) {
FrameEltSelnFig<?> currFig = selectedFigs.next();
if (currFig instanceof FrameEltGroupHalo) {
showSelectionArea(new RectangleFigure(),
currFig.getBounds());
}
else if (! (currFig instanceof GraphicalChildWidget<?, ?>)) {
Rectangle widgetBds =
new Rectangle(currFig.getBounds());
// Must scale widget figure's bounds to zoom factor
widgetBds.height =
PrecisionUtilities.round(widgetBds.height * zoom);
widgetBds.width =
PrecisionUtilities.round(widgetBds.width * zoom);
widgetBds.x =
PrecisionUtilities.round(widgetBds.x * zoom);
widgetBds.y =
PrecisionUtilities.round(widgetBds.y * zoom);
//display the new rectangle object
showSelectionArea(new RectangleFigure(), widgetBds);
}
}
setMouseState(DuplicatingWidgets);
// fall through!
}
case DuplicatingWidgets: {
updateDynamicDuplicate(mouseX, mouseY);
break;
}
case PotentialInsertDuplicateWidget: {
if (withinHysteresis(mouseX, mouseY)) {
return true;
}
ui.hideNondynamicSupport(false, false);
prevCursor = drawLayer.getCursor();
setUpReorderFigures();
prevCursor = drawLayer.getCursor();
drawLayer.setCursor(DRAW_CURSOR);
setMouseState(InsertDuplicateWidget);
// fall through!
}
case InsertDuplicateWidget: {
dynamicReorderWidget(mouseX, mouseY, true);
break;
}
case PotentialMovingGridButtons: {
if (withinHysteresis(mouseX, mouseY)) {
return true;
}
GraphicalWidget<?> gw = ui.getPotentialFigureOwner();
GridButton owner = (GridButton) gw.getModel();
DoublePoint start = owner.getShape().getOrigin();
GridButtonGroup gbg =
(GridButtonGroup) owner.getParentGroup();
movedGridButtons =
gbg.getMovedButtons(moveIsVertical,
start.x,
start.y);
minX = start.x - owner.getHorizSpace();
minY = start.y - owner.getVertSpace();
prevCursor = drawLayer.getCursor();
if (moveIsVertical) {
drawLayer.setCursor(MOVE_VERT_CURSOR);
}
else {
drawLayer.setCursor(MOVE_HORIZ_CURSOR);
}
setMouseState(MovingGridButtons);
// fall through!
}
case MovingGridButtons: {
dynamicMoveGridButtons(currentScaledX, currentScaledY);
break;
}
}
// Repeating timer for causing events to repeat.
if ((updateDelta.x != 0) || (updateDelta.y != 0)) {
if (rootFigure == null) {
rootFigure =
ui.getViewEditor().getInteractionFigure();
}
stopMouseDragTimer = false;
// Determine the new point the mouse "moved" too.
mouseDragTask.setNextMousePosition(mouseX + updateDelta.x,
mouseY + updateDelta.y,
button,
state);
// Queue the event for 0.1 sec.
WindowUtil.GLOBAL_DISPLAY.timerExec(100, mouseDragTask);
}
}
lastX = mouseX;
lastY = mouseY;
updateDynamic();
return goForward;
} // mouseDragged
protected void dynamicMoveGridButtons(double currentScaledX,
double currentScaledY)
{
GraphicalWidget<?> gw = ui.getPotentialFigureOwner();
GridButton gb = (GridButton) gw.getModel();
DoublePoint start = gb.getShape().getOrigin();
double moveX = 0.0;
double moveY = 0.0;
double offsetX = currentScaledX - scaledMouseDownX;
double offsetY = currentScaledY - scaledMouseDownY;
ui.hideNondynamicSupport(true);
if (moveIsVertical) {
moveY =
(currentScaledY < minY) ? (minY - start.y) : offsetY;
}
else {
moveX =
(currentScaledX < minX) ? (minX - start.x) : offsetX;
}
// Iterate through list of affected radio buttons
for (int i = 0; i < movedGridButtons.size(); i++) {
GridButton w = movedGridButtons.get(i);
GraphicalWidget<?> wf = ui.frameUI.getWidgetFigure(w);
DoublePoint p = w.getShape().getOrigin();
// Temporarily move the widget origin to new location.
wf.setLocation(new Point(PrecisionUtilities.round(p.x + moveX),
PrecisionUtilities.round(p.y + moveY)));
// this.ui.setGraphicalWidgetOrigin(p.x + moveX, p.y + moveY, wf);
}
}
protected void setUpReorderFigures()
{
double zoom = ui.getZoom();
//set size of new rectangle object
DoubleRectangle bds = lastClickedWidget.getEltBounds();
Rectangle frameSize = new Rectangle();
frameSize.height = PrecisionUtilities.round(bds.height * zoom);
frameSize.width = PrecisionUtilities.round(bds.width * zoom);
frameSize.x = PrecisionUtilities.round(bds.x * zoom);
frameSize.y = PrecisionUtilities.round(bds.y * zoom);
InteractionDrawingEditor drawEditor = ui.getViewEditor();
reorderFigure.setBounds(frameSize);
reorderFigure.setOutline(true);
reorderFigure.setFill(false);
drawEditor.addInteractionFigure(reorderFigure);
dividerLine.setOutline(false);
dividerLine.setFill(true);
dividerLine.setBackgroundColor(ColorConstants.black);
drawEditor.addInteractionFigure(dividerLine);
dividerLine.setVisible(false);
isReordering = true;
}
/**
* Not interested in this event
* But stop the drag timer so if a mouse up occurs, the widget doesn't keep
* moving since we don't see those events.
*/
@Override
protected boolean dealWithMouseExited(IFigure figure,
int x, int y, int state)
{
boolean goForward = super.dealWithMouseExited(figure, x, y, state);
// Stop the MouseDragTimerTask
stopMouseDragTimer = true;
return goForward;
}
@Override
protected void updateDynamic()
{
super.updateDynamic();
ui.updateUISupport();
ui.repaint();
}
@Override
protected void cancelDynamicOperation()
{
super.cancelDynamicOperation();
switch (getMouseState()) {
case MovingWidgets: {
Iterator<FrameEltSelnFig<?>> selectedEltFigs =
selection.getSelectedFigures();
dynamicMoveWidgets(selectedEltFigs,
scaledMouseDownX,
scaledMouseDownY);
break;
}
case ReorderWidget:
case InsertDuplicateWidget: {
reorderFigure.setVisible(false);
dividerLine.setVisible(false);
isReordering = false;
break;
}
case DuplicatingWidgets: {
clearRectFigures();
break;
}
case CreatingWidget: {
ui.stopDrawingTemporaryFigure();
break;
}
case TogglingSelection: {
ui.stopDrawingTemporaryFigure();
break;
}
case ResizingWidget: {
setWidgetFastRenderMode(false);
Iterator<FrameEltSelnFig<?>> selectedWFs =
selection.getSelectedFigures();
dynamicResizeWidgets(selectedWFs,
scaledMouseDownX,
scaledMouseDownY);
break;
}
case MovingGridButtons: {
dynamicMoveGridButtons(scaledMouseDownX,
scaledMouseDownY);
break;
}
}
lastClickedWidget = null;
setMouseState(MouseUp);
ui.confirmRenameFigure();
resetPrevCursor();
ui.refreshUISupport();
} // cancelDynamicOperation
/**
* Detects key presses in order to move widgets
* @param ke contains details of the key press
*/
@Override
protected boolean dealWithKeyPressed(KeyEvent ke)
{
if (! super.dealWithKeyPressed(ke)) {
return false;
}
GraphicalTraversableWidget<?> figure = null;
boolean nudge = true;
if (selection.getWidgetSelectionCount() == 1) {
GraphicalWidget<?> gw =
selection.getSelectedWidgetFigures().next();
if (gw instanceof GraphicalTraversableWidget<?>) {
figure = (GraphicalTraversableWidget<?>) gw;
nudge = false;
}
}
switch (ke.keycode) {
case '-': {
if (getMouseState() == MouseUp) {
if ((ke.getState() & CTRL_SHIFT_MASK) == CTRL_SHIFT_MASK) {
ui.performAction(CogToolLID.ZoomOut);
}
}
break;
}
case '=': {
if (getMouseState() == MouseUp) {
if ((ke.getState() & CTRL_SHIFT_MASK) == CTRL_SHIFT_MASK) {
ui.performAction(CogToolLID.ZoomIn);
}
}
break;
}
case SWT.ESC: {
cancelDynamicOperation();
break;
}
case SWT.ARROW_UP: {
if (getMouseState() == MouseUp) {
if (nudge) {
ui.performAction(CogToolLID.NudgeUp);
}
else {
changeSelection(figure, figure.selectUp());
}
}
break;
}
case SWT.ARROW_DOWN: {
if (getMouseState() == MouseUp) {
if (nudge) {
ui.performAction(CogToolLID.NudgeDown);
}
else {
IFigure toSelect = figure.selectDown();
if ((toSelect == null) &&
(figure == ui.potentialUIFig.getFigureOwner()))
{
PotentialFigure bottomPotentialFigure =
ui.potentialUIFig.getBottomFigure();
if (bottomPotentialFigure.isVisible()) {
toSelect = bottomPotentialFigure;
}
}
changeSelection(figure, toSelect);
}
}
break;
}
case SWT.ARROW_LEFT: {
if (getMouseState() == MouseUp) {
if (nudge) {
ui.performAction(CogToolLID.NudgeLeft);
}
else {
changeSelection(figure, figure.selectLeft());
}
}
break;
}
case SWT.ARROW_RIGHT: {
if (getMouseState() == MouseUp) {
if (nudge) {
ui.performAction(CogToolLID.NudgeRight);
}
else {
IFigure toSelect = figure.selectRight();
if ((toSelect == null) &&
(figure == ui.potentialUIFig.getFigureOwner()))
{
PotentialFigure rightPotentialFigure =
ui.potentialUIFig.getRightFigure();
if (rightPotentialFigure.isVisible()) {
toSelect = rightPotentialFigure;
}
}
changeSelection(figure, toSelect);
}
}
break;
}
case SWT.CR: {
if ((getMouseState() == MouseUp) &&
(selection.getWidgetSelectionCount() == 1))
{
Iterator<GraphicalWidget<?>> figs =
selection.getSelectedWidgetFigures();
GraphicalWidget<?> widget = figs.next();
ui.initiateRetitleFigure(widget);
}
break;
}
// Handle delete key & backspace keys when the item is selected,
// and the text area is not. TODO: Not a full solution.
case SWT.DEL:
case SWT.BS: { // Backspace
if (getMouseState() == MouseUp) {
ui.performAction(CogToolLID.Delete);
}
break;
}
}
return true;
} // dealWithKeyPressed
protected void changeSelection(GraphicalWidget<?> wasSelected,
IFigure nowSelected)
{
if ((nowSelected != null) && (nowSelected != wasSelected)) {
if (nowSelected instanceof GraphicalTraversableWidget<?>) {
selection.deselectSelnFig(wasSelected);
selection.selectSelnFig((GraphicalWidget<?>) nowSelected);
cleanup();
}
else {
ui.initiateRetitleFigure(nowSelected);
}
ui.potentialUIFig.setSelection(nowSelected);
}
}
/**
* Switch the widget to use Fast render mode (based on passed boolean)
* This controls if the widget should be drawn completely, or with a simple
* outline.
*
* @param fast
*/
protected void setWidgetFastRenderMode(boolean fast)
{
// Tell selected widgets widgets to go into fast mode
Iterator<GraphicalWidget<?>> widgetFigs =
selection.getSelectedWidgetFigures();
while (widgetFigs.hasNext()) {
widgetFigs.next();
// widgetFigure.setFastMode(fast);
}
// Tell the widget to redraw itself
ui.frameUI.drawWidgets();
}
}