blob: 98bc25e37b79ff68dfb96f0257cf947f30e4ca9c [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (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.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.SdkConstants;
import com.android.ide.common.api.DropFeedback;
import com.android.ide.common.api.IViewRule;
import com.android.ide.common.api.Rect;
import com.android.ide.common.api.SegmentType;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.utils.Pair;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.TypedEvent;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorSite;
import java.util.ArrayList;
import java.util.List;
/**
* The {@link GestureManager} is is the central manager of gestures; it is responsible
* for recognizing when particular gestures should begin and terminate. It
* listens to the drag, mouse and keyboard systems to find out when to start
* gestures and in order to update the gestures along the way.
*/
public class GestureManager {
/** The canvas which owns this GestureManager. */
private final LayoutCanvas mCanvas;
/** The currently executing gesture, or null. */
private Gesture mCurrentGesture;
/** A listener for drop target events. */
private final DropTargetListener mDropListener = new CanvasDropListener();
/** A listener for drag source events. */
private final DragSourceListener mDragSourceListener = new CanvasDragSourceListener();
/** Tooltip shown during the gesture, or null */
private GestureToolTip mTooltip;
/**
* The list of overlays associated with {@link #mCurrentGesture}. Will be
* null before it has been initialized lazily by the paint routine (the
* initialized value can never be null, but it can be an empty collection).
*/
private List<Overlay> mOverlays;
/**
* Most recently seen mouse position (x coordinate). We keep a copy of this
* value since we sometimes need to know it when we aren't told about the
* mouse position (such as when a keystroke is received, such as an arrow
* key in order to tweak the current drop position)
*/
protected int mLastMouseX;
/**
* Most recently seen mouse position (y coordinate). We keep a copy of this
* value since we sometimes need to know it when we aren't told about the
* mouse position (such as when a keystroke is received, such as an arrow
* key in order to tweak the current drop position)
*/
protected int mLastMouseY;
/**
* Most recently seen mouse mask. We keep a copy of this since in some
* scenarios (such as on a drag gesture) we don't get access to it.
*/
protected int mLastStateMask;
/**
* Listener for mouse motion, click and keyboard events.
*/
private Listener mListener;
/**
* When we the drag leaves, we don't know if that's the last we'll see of
* this drag or if it's just temporarily outside the canvas and it will
* return. We want to restore it if it comes back. This is also necessary
* because even on a drop we'll receive a
* {@link DropTargetListener#dragLeave} right before the drop, and we need
* to restore it in the drop. Therefore, when we lose a {@link DropGesture}
* to a {@link DropTargetListener#dragLeave}, we store a reference to the
* current gesture as a {@link #mZombieGesture}, since the gesture is dead
* but might be brought back to life if we see a subsequent
* {@link DropTargetListener#dragEnter} before another gesture begins.
*/
private DropGesture mZombieGesture;
/**
* Flag tracking whether we've set a message or error message on the global status
* line (since we only want to clear that message if we have set it ourselves).
* This is the actual message rather than a boolean such that (if we can get our
* hands on the global message) we can check to see if the current message is the
* one we set and only in that case clear it when it is no longer applicable.
*/
private String mDisplayingMessage;
/**
* Constructs a new {@link GestureManager} for the given
* {@link LayoutCanvas}.
*
* @param canvas The canvas which controls this {@link GestureManager}
*/
public GestureManager(LayoutCanvas canvas) {
mCanvas = canvas;
}
/**
* Returns the canvas associated with this GestureManager.
*
* @return The {@link LayoutCanvas} associated with this GestureManager.
* Never null.
*/
public LayoutCanvas getCanvas() {
return mCanvas;
}
/**
* Returns the current gesture, if one is in progress, and otherwise returns
* null.
*
* @return The current gesture or null.
*/
public Gesture getCurrentGesture() {
return mCurrentGesture;
}
/**
* Paints the overlays associated with the current gesture, if any.
*
* @param gc The graphics object to paint into.
*/
public void paint(GC gc) {
if (mCurrentGesture == null) {
return;
}
if (mOverlays == null) {
mOverlays = mCurrentGesture.createOverlays();
Device device = gc.getDevice();
for (Overlay overlay : mOverlays) {
overlay.create(device);
}
}
for (Overlay overlay : mOverlays) {
overlay.paint(gc);
}
}
/**
* Registers all the listeners needed by the {@link GestureManager}.
*
* @param dragSource The drag source in the {@link LayoutCanvas} to listen
* to.
* @param dropTarget The drop target in the {@link LayoutCanvas} to listen
* to.
*/
public void registerListeners(DragSource dragSource, DropTarget dropTarget) {
assert mListener == null;
mListener = new Listener();
mCanvas.addMouseMoveListener(mListener);
mCanvas.addMouseListener(mListener);
mCanvas.addKeyListener(mListener);
if (dragSource != null) {
dragSource.addDragListener(mDragSourceListener);
}
if (dropTarget != null) {
dropTarget.addDropListener(mDropListener);
}
}
/**
* Unregisters all the listeners previously registered by
* {@link #registerListeners}.
*
* @param dragSource The drag source in the {@link LayoutCanvas} to stop
* listening to.
* @param dropTarget The drop target in the {@link LayoutCanvas} to stop
* listening to.
*/
public void unregisterListeners(DragSource dragSource, DropTarget dropTarget) {
if (mCanvas.isDisposed()) {
// If the LayoutCanvas is already disposed, we shouldn't try to unregister
// the listeners; they are already not active and an attempt to remove the
// listener will throw a widget-is-disposed exception.
mListener = null;
return;
}
if (mListener != null) {
mCanvas.removeMouseMoveListener(mListener);
mCanvas.removeMouseListener(mListener);
mCanvas.removeKeyListener(mListener);
mListener = null;
}
if (dragSource != null) {
dragSource.removeDragListener(mDragSourceListener);
}
if (dropTarget != null) {
dropTarget.removeDropListener(mDropListener);
}
}
/**
* Starts the given gesture.
*
* @param mousePos The most recent mouse coordinate applicable to the new
* gesture, in control coordinates.
* @param gesture The gesture to initiate
*/
private void startGesture(ControlPoint mousePos, Gesture gesture, int mask) {
if (mCurrentGesture != null) {
finishGesture(mousePos, true);
assert mCurrentGesture == null;
}
if (gesture != null) {
mCurrentGesture = gesture;
mCurrentGesture.begin(mousePos, mask);
}
}
/**
* Updates the current gesture, if any, for the given event.
*
* @param mousePos The most recent mouse coordinate applicable to the new
* gesture, in control coordinates.
* @param event The event corresponding to this update. May be null. Don't
* make any assumptions about the type of this event - for
* example, it may not always be a MouseEvent, it could be a
* DragSourceEvent, etc.
*/
private void updateMouse(ControlPoint mousePos, TypedEvent event) {
if (mCurrentGesture != null) {
mCurrentGesture.update(mousePos);
}
}
/**
* Finish the given gesture, either from successful completion or from
* cancellation.
*
* @param mousePos The most recent mouse coordinate applicable to the new
* gesture, in control coordinates.
* @param canceled True if and only if the gesture was canceled.
*/
private void finishGesture(ControlPoint mousePos, boolean canceled) {
if (mCurrentGesture != null) {
mCurrentGesture.end(mousePos, canceled);
if (mOverlays != null) {
for (Overlay overlay : mOverlays) {
overlay.dispose();
}
mOverlays = null;
}
mCurrentGesture = null;
mZombieGesture = null;
mLastStateMask = 0;
updateMessage(null);
updateCursor(mousePos);
mCanvas.redraw();
}
}
/**
* Update the cursor to show the type of operation we expect on a mouse press:
* <ul>
* <li>Over a selection handle, show a directional cursor depending on the position of
* the selection handle
* <li>Over a widget, show a move (hand) cursor
* <li>Otherwise, show the default arrow cursor
* </ul>
*/
void updateCursor(ControlPoint controlPoint) {
// We don't hover on the root since it's not a widget per see and it is always there.
SelectionManager selectionManager = mCanvas.getSelectionManager();
if (!selectionManager.isEmpty()) {
Display display = mCanvas.getDisplay();
Pair<SelectionItem, SelectionHandle> handlePair =
selectionManager.findHandle(controlPoint);
if (handlePair != null) {
SelectionHandle handle = handlePair.getSecond();
int cursorType = handle.getSwtCursorType();
Cursor cursor = display.getSystemCursor(cursorType);
if (cursor != mCanvas.getCursor()) {
mCanvas.setCursor(cursor);
}
return;
}
// See if it's over a selected view
LayoutPoint layoutPoint = controlPoint.toLayout();
for (SelectionItem item : selectionManager.getSelections()) {
if (item.getRect().contains(layoutPoint.x, layoutPoint.y)
&& !item.isRoot()) {
Cursor cursor = display.getSystemCursor(SWT.CURSOR_HAND);
if (cursor != mCanvas.getCursor()) {
mCanvas.setCursor(cursor);
}
return;
}
}
}
if (mCanvas.getCursor() != null) {
mCanvas.setCursor(null);
}
}
/**
* Update the Eclipse status message with any feedback messages from the given
* {@link DropFeedback} object, or clean up if there is no more feedback to process
* @param feedback the feedback whose message we want to display, or null to clear the
* message if previously set
*/
void updateMessage(DropFeedback feedback) {
IEditorSite editorSite = mCanvas.getEditorDelegate().getEditor().getEditorSite();
IStatusLineManager status = editorSite.getActionBars().getStatusLineManager();
if (feedback == null) {
if (mDisplayingMessage != null) {
status.setMessage(null);
status.setErrorMessage(null);
mDisplayingMessage = null;
}
} else if (feedback.errorMessage != null) {
if (!feedback.errorMessage.equals(mDisplayingMessage)) {
mDisplayingMessage = feedback.errorMessage;
status.setErrorMessage(mDisplayingMessage);
}
} else if (feedback.message != null) {
if (!feedback.message.equals(mDisplayingMessage)) {
mDisplayingMessage = feedback.message;
status.setMessage(mDisplayingMessage);
}
} else if (mDisplayingMessage != null) {
// TODO: Can we check the existing message and only clear it if it's the
// same as the one we set?
mDisplayingMessage = null;
status.setMessage(null);
status.setErrorMessage(null);
}
// Tooltip
if (feedback != null && feedback.tooltip != null) {
Pair<Boolean,Boolean> position = mCurrentGesture.getTooltipPosition();
boolean below = position.getFirst();
if (feedback.tooltipY != null) {
below = feedback.tooltipY == SegmentType.BOTTOM;
}
boolean toRightOf = position.getSecond();
if (feedback.tooltipX != null) {
toRightOf = feedback.tooltipX == SegmentType.RIGHT;
}
if (mTooltip == null) {
mTooltip = new GestureToolTip(mCanvas, below, toRightOf);
}
mTooltip.update(feedback.tooltip, below, toRightOf);
} else if (mTooltip != null) {
mTooltip.dispose();
mTooltip = null;
}
}
/**
* Returns the current mouse position as a {@link ControlPoint}
*
* @return the current mouse position as a {@link ControlPoint}
*/
public ControlPoint getCurrentControlPoint() {
return ControlPoint.create(mCanvas, mLastMouseX, mLastMouseY);
}
/**
* Returns the current SWT modifier key mask as an {@link IViewRule} modifier mask
*
* @return the current SWT modifier key mask as an {@link IViewRule} modifier mask
*/
public int getRuleModifierMask() {
int swtMask = mLastStateMask;
int modifierMask = 0;
if ((swtMask & SWT.MOD1) != 0) {
modifierMask |= DropFeedback.MODIFIER1;
}
if ((swtMask & SWT.MOD2) != 0) {
modifierMask |= DropFeedback.MODIFIER2;
}
if ((swtMask & SWT.MOD3) != 0) {
modifierMask |= DropFeedback.MODIFIER3;
}
return modifierMask;
}
/**
* Helper class which implements the {@link MouseMoveListener},
* {@link MouseListener} and {@link KeyListener} interfaces.
*/
private class Listener implements MouseMoveListener, MouseListener, MouseTrackListener,
KeyListener {
// --- MouseMoveListener ---
@Override
public void mouseMove(MouseEvent e) {
mLastMouseX = e.x;
mLastMouseY = e.y;
mLastStateMask = e.stateMask;
ControlPoint controlPoint = ControlPoint.create(mCanvas, e);
if ((e.stateMask & SWT.BUTTON_MASK) != 0) {
if (mCurrentGesture != null) {
updateMouse(controlPoint, e);
mCanvas.redraw();
}
} else {
updateCursor(controlPoint);
mCanvas.hover(e);
mCanvas.getPreviewManager().moved(controlPoint);
}
}
// --- MouseListener ---
@Override
public void mouseUp(MouseEvent e) {
ControlPoint mousePos = ControlPoint.create(mCanvas, e);
if (mCurrentGesture == null) {
// If clicking on a configuration preview, just process it there
if (mCanvas.getPreviewManager().click(mousePos)) {
return;
}
// Just a click, select
Pair<SelectionItem, SelectionHandle> handlePair =
mCanvas.getSelectionManager().findHandle(mousePos);
if (handlePair == null) {
mCanvas.getSelectionManager().select(e);
}
}
if (mCurrentGesture == null) {
updateCursor(mousePos);
} else if (mCurrentGesture instanceof DropGesture) {
// Mouse Up shouldn't be delivered in the middle of a drag & drop -
// but this can happen on some versions of Linux
// (see http://code.google.com/p/android/issues/detail?id=19057 )
// and if we process the mouseUp it will abort the remainder of
// the drag & drop operation, so ignore this event!
} else {
finishGesture(mousePos, false);
}
mCanvas.redraw();
}
@Override
public void mouseDown(MouseEvent e) {
mLastMouseX = e.x;
mLastMouseY = e.y;
mLastStateMask = e.stateMask;
// Not yet used. Should be, for Mac and Linux.
}
@Override
public void mouseDoubleClick(MouseEvent e) {
// SWT delivers a double click event even if you click two different buttons
// in rapid succession. In any case, we only want to let you double click the
// first button to warp to XML:
if (e.button == 1) {
// Warp to the text editor and show the corresponding XML for the
// double-clicked widget
LayoutPoint p = ControlPoint.create(mCanvas, e).toLayout();
CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p);
if (vi != null) {
mCanvas.show(vi);
}
}
}
// --- MouseTrackListener ---
@Override
public void mouseEnter(MouseEvent e) {
ControlPoint mousePos = ControlPoint.create(mCanvas, e);
mCanvas.getPreviewManager().enter(mousePos);
}
@Override
public void mouseExit(MouseEvent e) {
ControlPoint mousePos = ControlPoint.create(mCanvas, e);
mCanvas.getPreviewManager().exit(mousePos);
}
@Override
public void mouseHover(MouseEvent e) {
}
// --- KeyListener ---
@Override
public void keyPressed(KeyEvent e) {
mLastStateMask = e.stateMask;
// Workaround for the fact that in keyPressed the current state
// mask is not yet updated
if (e.keyCode == SWT.SHIFT) {
mLastStateMask |= SWT.MOD2;
}
if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
if (e.keyCode == SWT.COMMAND) {
mLastStateMask |= SWT.MOD1;
}
} else {
if (e.keyCode == SWT.CTRL) {
mLastStateMask |= SWT.MOD1;
}
}
// Give gestures a first chance to see and consume the key press
if (mCurrentGesture != null) {
// unless it's "Escape", which cancels the gesture
if (e.keyCode == SWT.ESC) {
ControlPoint controlPoint = ControlPoint.create(mCanvas,
mLastMouseX, mLastMouseY);
finishGesture(controlPoint, true);
return;
}
if (mCurrentGesture.keyPressed(e)) {
return;
}
}
// Fall back to canvas actions for the key press
mCanvas.handleKeyPressed(e);
}
@Override
public void keyReleased(KeyEvent e) {
mLastStateMask = e.stateMask;
// Workaround for the fact that in keyPressed the current state
// mask is not yet updated
if (e.keyCode == SWT.SHIFT) {
mLastStateMask &= ~SWT.MOD2;
}
if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
if (e.keyCode == SWT.COMMAND) {
mLastStateMask &= ~SWT.MOD1;
}
} else {
if (e.keyCode == SWT.CTRL) {
mLastStateMask &= ~SWT.MOD1;
}
}
if (mCurrentGesture != null) {
mCurrentGesture.keyReleased(e);
}
}
}
/** Listener for Drag &amp; Drop events. */
private class CanvasDropListener implements DropTargetListener {
public CanvasDropListener() {
}
/**
* The cursor has entered the drop target boundaries. {@inheritDoc}
*/
@Override
public void dragEnter(DropTargetEvent event) {
mCanvas.showInvisibleViews(true);
mCanvas.getEditorDelegate().getGraphicalEditor().dismissHoverPalette();
if (mCurrentGesture == null) {
Gesture newGesture = mZombieGesture;
if (newGesture == null) {
newGesture = new MoveGesture(mCanvas);
} else {
mZombieGesture = null;
}
startGesture(ControlPoint.create(mCanvas, event),
newGesture, 0);
}
if (mCurrentGesture instanceof DropGesture) {
((DropGesture) mCurrentGesture).dragEnter(event);
}
}
/**
* The cursor is moving over the drop target. {@inheritDoc}
*/
@Override
public void dragOver(DropTargetEvent event) {
if (mCurrentGesture instanceof DropGesture) {
((DropGesture) mCurrentGesture).dragOver(event);
}
}
/**
* The cursor has left the drop target boundaries OR data is about to be
* dropped. {@inheritDoc}
*/
@Override
public void dragLeave(DropTargetEvent event) {
if (mCurrentGesture instanceof DropGesture) {
DropGesture dropGesture = (DropGesture) mCurrentGesture;
dropGesture.dragLeave(event);
finishGesture(ControlPoint.create(mCanvas, event), true);
mZombieGesture = dropGesture;
}
mCanvas.showInvisibleViews(false);
}
/**
* The drop is about to be performed. The drop target is given a last
* chance to change the nature of the drop. {@inheritDoc}
*/
@Override
public void dropAccept(DropTargetEvent event) {
Gesture gesture = mCurrentGesture != null ? mCurrentGesture : mZombieGesture;
if (gesture instanceof DropGesture) {
((DropGesture) gesture).dropAccept(event);
}
}
/**
* The data is being dropped. {@inheritDoc}
*/
@Override
public void drop(final DropTargetEvent event) {
// See if we had a gesture just prior to the drop (we receive a dragLeave
// right before the drop which we don't know whether means the cursor has
// left the canvas for good or just before a drop)
Gesture gesture = mCurrentGesture != null ? mCurrentGesture : mZombieGesture;
mZombieGesture = null;
if (gesture instanceof DropGesture) {
((DropGesture) gesture).drop(event);
finishGesture(ControlPoint.create(mCanvas, event), true);
}
}
/**
* The operation being performed has changed (e.g. modifier key).
* {@inheritDoc}
*/
@Override
public void dragOperationChanged(DropTargetEvent event) {
if (mCurrentGesture instanceof DropGesture) {
((DropGesture) mCurrentGesture).dragOperationChanged(event);
}
}
}
/**
* Our canvas {@link DragSourceListener}. Handles drag being started and
* finished and generating the drag data.
*/
private class CanvasDragSourceListener implements DragSourceListener {
/**
* The current selection being dragged. This may be a subset of the
* canvas selection due to the "sanitize" pass. Can be empty but never
* null.
*/
private final ArrayList<SelectionItem> mDragSelection = new ArrayList<SelectionItem>();
private SimpleElement[] mDragElements;
/**
* The user has begun the actions required to drag the widget.
* <p/>
* Initiate a drag only if there is one or more item selected. If
* there's none, try to auto-select the one under the cursor.
* {@inheritDoc}
*/
@Override
public void dragStart(DragSourceEvent e) {
LayoutPoint p = LayoutPoint.create(mCanvas, e);
ControlPoint controlPoint = ControlPoint.create(mCanvas, e);
SelectionManager selectionManager = mCanvas.getSelectionManager();
// See if the mouse is over a selection handle; if so, start a resizing
// gesture.
Pair<SelectionItem, SelectionHandle> handle =
selectionManager.findHandle(controlPoint);
if (handle != null) {
startGesture(controlPoint, new ResizeGesture(mCanvas, handle.getFirst(),
handle.getSecond()), mLastStateMask);
e.detail = DND.DROP_NONE;
e.doit = false;
mCanvas.redraw();
return;
}
// We need a selection (simple or multiple) to do any transfer.
// If there's a selection *and* the cursor is over this selection,
// use all the currently selected elements.
// If there is no selection or the cursor is not over a selected
// element, *change* the selection to match the element under the
// cursor and use that. If nothing can be selected, abort the drag
// operation.
List<SelectionItem> selections = selectionManager.getSelections();
mDragSelection.clear();
SelectionItem primary = null;
if (!selections.isEmpty()) {
// Is the cursor on top of a selected element?
boolean insideSelection = false;
for (SelectionItem cs : selections) {
if (!cs.isRoot() && cs.getRect().contains(p.x, p.y)) {
primary = cs;
insideSelection = true;
break;
}
}
if (!insideSelection) {
CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p);
if (vi != null && !vi.isRoot() && !vi.isHidden()) {
primary = selectionManager.selectSingle(vi);
insideSelection = true;
}
}
if (insideSelection) {
// We should now have a proper selection that matches the
// cursor. Let's use this one. We make a copy of it since
// the "sanitize" pass below might remove some of the
// selected objects.
if (selections.size() == 1) {
// You are dragging just one element - this might or
// might not be the root, but if it's the root that is
// fine since we will let you drag the root if it is the
// only thing you are dragging.
mDragSelection.addAll(selections);
} else {
// Only drag non-root items.
for (SelectionItem cs : selections) {
if (!cs.isRoot() && !cs.isHidden()) {
mDragSelection.add(cs);
} else if (cs == primary) {
primary = null;
}
}
}
}
}
// If you are dragging a non-selected item, select it
if (mDragSelection.isEmpty()) {
CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p);
if (vi != null && !vi.isRoot() && !vi.isHidden()) {
primary = selectionManager.selectSingle(vi);
mDragSelection.addAll(selections);
}
}
SelectionManager.sanitize(mDragSelection);
e.doit = !mDragSelection.isEmpty();
int imageCount = mDragSelection.size();
if (e.doit) {
mDragElements = SelectionItem.getAsElements(mDragSelection, primary);
GlobalCanvasDragInfo.getInstance().startDrag(mDragElements,
mDragSelection.toArray(new SelectionItem[imageCount]),
mCanvas, new Runnable() {
@Override
public void run() {
mCanvas.getClipboardSupport().deleteSelection("Remove",
mDragSelection);
}
});
}
// If you drag on the -background-, we make that into a marquee
// selection
if (!e.doit || (imageCount == 1
&& (mDragSelection.get(0).isRoot() || mDragSelection.get(0).isHidden()))) {
boolean toggle = (mLastStateMask & (SWT.CTRL | SWT.SHIFT | SWT.COMMAND)) != 0;
startGesture(controlPoint,
new MarqueeGesture(mCanvas, toggle), mLastStateMask);
e.detail = DND.DROP_NONE;
e.doit = false;
} else {
// Otherwise, the drag means you are moving something
mCanvas.showInvisibleViews(true);
startGesture(controlPoint, new MoveGesture(mCanvas), 0);
// Render drag-images: Copy portions of the full screen render.
Image image = mCanvas.getImageOverlay().getImage();
if (image != null) {
/**
* Transparency of the dragged image ([0-255]). We're using 30%
* translucency to make the image faint and not obscure the drag
* feedback below it.
*/
final byte DRAG_TRANSPARENCY = (byte) (0.3 * 255);
List<Rectangle> rectangles = new ArrayList<Rectangle>(imageCount);
if (imageCount > 0) {
ImageData data = image.getImageData();
Rectangle imageRectangle = new Rectangle(0, 0, data.width, data.height);
for (SelectionItem item : mDragSelection) {
Rectangle bounds = item.getRect();
// Some bounds can be outside the rendered rectangle (for
// example, in an absolute layout, you can have negative
// coordinates), so create the intersection of these bounds.
Rectangle clippedBounds = imageRectangle.intersection(bounds);
rectangles.add(clippedBounds);
}
Rectangle boundingBox = ImageUtils.getBoundingRectangle(rectangles);
double scale = mCanvas.getHorizontalTransform().getScale();
e.image = SwtUtils.drawRectangles(image, rectangles, boundingBox, scale,
DRAG_TRANSPARENCY);
// Set the image offset such that we preserve the relative
// distance between the mouse pointer and the top left corner of
// the dragged view
int deltaX = (int) (scale * (boundingBox.x - p.x));
int deltaY = (int) (scale * (boundingBox.y - p.y));
e.offsetX = -deltaX;
e.offsetY = -deltaY;
// View rules may need to know it as well
GlobalCanvasDragInfo dragInfo = GlobalCanvasDragInfo.getInstance();
Rect dragBounds = null;
int width = (int) (scale * boundingBox.width);
int height = (int) (scale * boundingBox.height);
dragBounds = new Rect(deltaX, deltaY, width, height);
dragInfo.setDragBounds(dragBounds);
// Record the baseline such that we can perform baseline alignment
// on the node as it's dragged around
NodeProxy firstNode =
mCanvas.getNodeFactory().create(mDragSelection.get(0).getViewInfo());
dragInfo.setDragBaseline(firstNode.getBaseline());
}
}
}
// No hover during drag (since no mouse over events are delivered
// during a drag to keep the hovers up to date anyway)
mCanvas.clearHover();
mCanvas.redraw();
}
/**
* Callback invoked when data is needed for the event, typically right
* before drop. The drop side decides what type of transfer to use and
* this side must now provide the adequate data. {@inheritDoc}
*/
@Override
public void dragSetData(DragSourceEvent e) {
if (TextTransfer.getInstance().isSupportedType(e.dataType)) {
e.data = SelectionItem.getAsText(mCanvas, mDragSelection);
return;
}
if (SimpleXmlTransfer.getInstance().isSupportedType(e.dataType)) {
e.data = mDragElements;
return;
}
// otherwise we failed
e.detail = DND.DROP_NONE;
e.doit = false;
}
/**
* Callback invoked when the drop has been finished either way. On a
* successful move, remove the originating elements.
*/
@Override
public void dragFinished(DragSourceEvent e) {
// Clear the selection
mDragSelection.clear();
mDragElements = null;
GlobalCanvasDragInfo.getInstance().stopDrag();
finishGesture(ControlPoint.create(mCanvas, e), e.detail == DND.DROP_NONE);
mCanvas.showInvisibleViews(false);
mCanvas.redraw();
}
}
}