/* * 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 & 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(); } } }