diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java | 930 |
1 files changed, 930 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java new file mode 100644 index 000000000..98bc25e37 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java @@ -0,0 +1,930 @@ +/* + * 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(); + } + } +} |