Home | History | Annotate | Download | only in gle2
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
     18 
     19 import com.android.ide.common.api.DropFeedback;
     20 import com.android.ide.common.api.IViewRule;
     21 import com.android.ide.common.api.Rect;
     22 import com.android.ide.common.api.SegmentType;
     23 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
     24 import com.android.sdklib.SdkConstants;
     25 import com.android.util.Pair;
     26 
     27 import org.eclipse.jface.action.IStatusLineManager;
     28 import org.eclipse.swt.SWT;
     29 import org.eclipse.swt.dnd.DND;
     30 import org.eclipse.swt.dnd.DragSource;
     31 import org.eclipse.swt.dnd.DragSourceEvent;
     32 import org.eclipse.swt.dnd.DragSourceListener;
     33 import org.eclipse.swt.dnd.DropTarget;
     34 import org.eclipse.swt.dnd.DropTargetEvent;
     35 import org.eclipse.swt.dnd.DropTargetListener;
     36 import org.eclipse.swt.dnd.TextTransfer;
     37 import org.eclipse.swt.events.KeyEvent;
     38 import org.eclipse.swt.events.KeyListener;
     39 import org.eclipse.swt.events.MouseEvent;
     40 import org.eclipse.swt.events.MouseListener;
     41 import org.eclipse.swt.events.MouseMoveListener;
     42 import org.eclipse.swt.events.TypedEvent;
     43 import org.eclipse.swt.graphics.Cursor;
     44 import org.eclipse.swt.graphics.Device;
     45 import org.eclipse.swt.graphics.GC;
     46 import org.eclipse.swt.graphics.Image;
     47 import org.eclipse.swt.graphics.ImageData;
     48 import org.eclipse.swt.graphics.Rectangle;
     49 import org.eclipse.swt.widgets.Display;
     50 import org.eclipse.ui.IEditorSite;
     51 
     52 import java.util.ArrayList;
     53 import java.util.List;
     54 
     55 /**
     56  * The {@link GestureManager} is is the central manager of gestures; it is responsible
     57  * for recognizing when particular gestures should begin and terminate. It
     58  * listens to the drag, mouse and keyboard systems to find out when to start
     59  * gestures and in order to update the gestures along the way.
     60  */
     61 public class GestureManager {
     62     /** The canvas which owns this GestureManager. */
     63     private final LayoutCanvas mCanvas;
     64 
     65     /** The currently executing gesture, or null. */
     66     private Gesture mCurrentGesture;
     67 
     68     /** A listener for drop target events. */
     69     private final DropTargetListener mDropListener = new CanvasDropListener();
     70 
     71     /** A listener for drag source events. */
     72     private final DragSourceListener mDragSourceListener = new CanvasDragSourceListener();
     73 
     74     /** Tooltip shown during the gesture, or null */
     75     private GestureToolTip mTooltip;
     76 
     77     /**
     78      * The list of overlays associated with {@link #mCurrentGesture}. Will be
     79      * null before it has been initialized lazily by the paint routine (the
     80      * initialized value can never be null, but it can be an empty collection).
     81      */
     82     private List<Overlay> mOverlays;
     83 
     84     /**
     85      * Most recently seen mouse position (x coordinate). We keep a copy of this
     86      * value since we sometimes need to know it when we aren't told about the
     87      * mouse position (such as when a keystroke is received, such as an arrow
     88      * key in order to tweak the current drop position)
     89      */
     90     protected int mLastMouseX;
     91 
     92     /**
     93      * Most recently seen mouse position (y coordinate). We keep a copy of this
     94      * value since we sometimes need to know it when we aren't told about the
     95      * mouse position (such as when a keystroke is received, such as an arrow
     96      * key in order to tweak the current drop position)
     97      */
     98     protected int mLastMouseY;
     99 
    100     /**
    101      * Most recently seen mouse mask. We keep a copy of this since in some
    102      * scenarios (such as on a drag gesture) we don't get access to it.
    103      */
    104     protected int mLastStateMask;
    105 
    106     /**
    107      * Listener for mouse motion, click and keyboard events.
    108      */
    109     private Listener mListener;
    110 
    111     /**
    112      * When we the drag leaves, we don't know if that's the last we'll see of
    113      * this drag or if it's just temporarily outside the canvas and it will
    114      * return. We want to restore it if it comes back. This is also necessary
    115      * because even on a drop we'll receive a
    116      * {@link DropTargetListener#dragLeave} right before the drop, and we need
    117      * to restore it in the drop. Therefore, when we lose a {@link DropGesture}
    118      * to a {@link DropTargetListener#dragLeave}, we store a reference to the
    119      * current gesture as a {@link #mZombieGesture}, since the gesture is dead
    120      * but might be brought back to life if we see a subsequent
    121      * {@link DropTargetListener#dragEnter} before another gesture begins.
    122      */
    123     private DropGesture mZombieGesture;
    124 
    125     /**
    126      * Flag tracking whether we've set a message or error message on the global status
    127      * line (since we only want to clear that message if we have set it ourselves).
    128      * This is the actual message rather than a boolean such that (if we can get our
    129      * hands on the global message) we can check to see if the current message is the
    130      * one we set and only in that case clear it when it is no longer applicable.
    131      */
    132     private String mDisplayingMessage;
    133 
    134     /**
    135      * Constructs a new {@link GestureManager} for the given
    136      * {@link LayoutCanvas}.
    137      *
    138      * @param canvas The canvas which controls this {@link GestureManager}
    139      */
    140     public GestureManager(LayoutCanvas canvas) {
    141         this.mCanvas = canvas;
    142     }
    143 
    144     /**
    145      * Returns the canvas associated with this GestureManager.
    146      *
    147      * @return The {@link LayoutCanvas} associated with this GestureManager.
    148      *         Never null.
    149      */
    150     public LayoutCanvas getCanvas() {
    151         return mCanvas;
    152     }
    153 
    154     /**
    155      * Returns the current gesture, if one is in progress, and otherwise returns
    156      * null.
    157      *
    158      * @return The current gesture or null.
    159      */
    160     public Gesture getCurrentGesture() {
    161         return mCurrentGesture;
    162     }
    163 
    164     /**
    165      * Paints the overlays associated with the current gesture, if any.
    166      *
    167      * @param gc The graphics object to paint into.
    168      */
    169     public void paint(GC gc) {
    170         if (mCurrentGesture == null) {
    171             return;
    172         }
    173 
    174         if (mOverlays == null) {
    175             mOverlays = mCurrentGesture.createOverlays();
    176             Device device = gc.getDevice();
    177             for (Overlay overlay : mOverlays) {
    178                 overlay.create(device);
    179             }
    180         }
    181         for (Overlay overlay : mOverlays) {
    182             overlay.paint(gc);
    183         }
    184     }
    185 
    186     /**
    187      * Registers all the listeners needed by the {@link GestureManager}.
    188      *
    189      * @param dragSource The drag source in the {@link LayoutCanvas} to listen
    190      *            to.
    191      * @param dropTarget The drop target in the {@link LayoutCanvas} to listen
    192      *            to.
    193      */
    194     public void registerListeners(DragSource dragSource, DropTarget dropTarget) {
    195         assert mListener == null;
    196         mListener = new Listener();
    197         mCanvas.addMouseMoveListener(mListener);
    198         mCanvas.addMouseListener(mListener);
    199         mCanvas.addKeyListener(mListener);
    200 
    201         if (dragSource != null) {
    202             dragSource.addDragListener(mDragSourceListener);
    203         }
    204         if (dropTarget != null) {
    205             dropTarget.addDropListener(mDropListener);
    206         }
    207     }
    208 
    209     /**
    210      * Unregisters all the listeners previously registered by
    211      * {@link #registerListeners}.
    212      *
    213      * @param dragSource The drag source in the {@link LayoutCanvas} to stop
    214      *            listening to.
    215      * @param dropTarget The drop target in the {@link LayoutCanvas} to stop
    216      *            listening to.
    217      */
    218     public void unregisterListeners(DragSource dragSource, DropTarget dropTarget) {
    219         if (mCanvas.isDisposed()) {
    220             // If the LayoutCanvas is already disposed, we shouldn't try to unregister
    221             // the listeners; they are already not active and an attempt to remove the
    222             // listener will throw a widget-is-disposed exception.
    223             mListener = null;
    224             return;
    225         }
    226 
    227         if (mListener != null) {
    228             mCanvas.removeMouseMoveListener(mListener);
    229             mCanvas.removeMouseListener(mListener);
    230             mCanvas.removeKeyListener(mListener);
    231             mListener = null;
    232         }
    233 
    234         if (dragSource != null) {
    235             dragSource.removeDragListener(mDragSourceListener);
    236         }
    237         if (dropTarget != null) {
    238             dropTarget.removeDropListener(mDropListener);
    239         }
    240     }
    241 
    242     /**
    243      * Starts the given gesture.
    244      *
    245      * @param mousePos The most recent mouse coordinate applicable to the new
    246      *            gesture, in control coordinates.
    247      * @param gesture The gesture to initiate
    248      */
    249     private void startGesture(ControlPoint mousePos, Gesture gesture, int mask) {
    250         if (mCurrentGesture != null) {
    251             finishGesture(mousePos, true);
    252             assert mCurrentGesture == null;
    253         }
    254 
    255         if (gesture != null) {
    256             mCurrentGesture = gesture;
    257             mCurrentGesture.begin(mousePos, mask);
    258         }
    259     }
    260 
    261     /**
    262      * Updates the current gesture, if any, for the given event.
    263      *
    264      * @param mousePos The most recent mouse coordinate applicable to the new
    265      *            gesture, in control coordinates.
    266      * @param event The event corresponding to this update. May be null. Don't
    267      *            make any assumptions about the type of this event - for
    268      *            example, it may not always be a MouseEvent, it could be a
    269      *            DragSourceEvent, etc.
    270      */
    271     private void updateMouse(ControlPoint mousePos, TypedEvent event) {
    272         if (mCurrentGesture != null) {
    273             mCurrentGesture.update(mousePos);
    274         }
    275     }
    276 
    277     /**
    278      * Finish the given gesture, either from successful completion or from
    279      * cancellation.
    280      *
    281      * @param mousePos The most recent mouse coordinate applicable to the new
    282      *            gesture, in control coordinates.
    283      * @param canceled True if and only if the gesture was canceled.
    284      */
    285     private void finishGesture(ControlPoint mousePos, boolean canceled) {
    286         if (mCurrentGesture != null) {
    287             mCurrentGesture.end(mousePos, canceled);
    288             if (mOverlays != null) {
    289                 for (Overlay overlay : mOverlays) {
    290                     overlay.dispose();
    291                 }
    292                 mOverlays = null;
    293             }
    294             mCurrentGesture = null;
    295             mZombieGesture = null;
    296             mLastStateMask = 0;
    297             updateMessage(null);
    298             updateCursor(mousePos);
    299             mCanvas.redraw();
    300         }
    301     }
    302 
    303     /**
    304      * Update the cursor to show the type of operation we expect on a mouse press:
    305      * <ul>
    306      * <li>Over a selection handle, show a directional cursor depending on the position of
    307      * the selection handle
    308      * <li>Over a widget, show a move (hand) cursor
    309      * <li>Otherwise, show the default arrow cursor
    310      * </ul>
    311      */
    312     void updateCursor(ControlPoint controlPoint) {
    313         // We don't hover on the root since it's not a widget per see and it is always there.
    314         SelectionManager selectionManager = mCanvas.getSelectionManager();
    315 
    316         if (!selectionManager.isEmpty()) {
    317             Display display = mCanvas.getDisplay();
    318             Pair<SelectionItem, SelectionHandle> handlePair =
    319                 selectionManager.findHandle(controlPoint);
    320             if (handlePair != null) {
    321                 SelectionHandle handle = handlePair.getSecond();
    322                 int cursorType = handle.getSwtCursorType();
    323                 Cursor cursor = display.getSystemCursor(cursorType);
    324                 if (cursor != mCanvas.getCursor()) {
    325                     mCanvas.setCursor(cursor);
    326                 }
    327                 return;
    328             }
    329 
    330             // See if it's over a selected view
    331             LayoutPoint layoutPoint = controlPoint.toLayout();
    332             for (SelectionItem item : selectionManager.getSelections()) {
    333                 if (item.getRect().contains(layoutPoint.x, layoutPoint.y)
    334                         && !item.isRoot()) {
    335                     Cursor cursor = display.getSystemCursor(SWT.CURSOR_HAND);
    336                     if (cursor != mCanvas.getCursor()) {
    337                         mCanvas.setCursor(cursor);
    338                     }
    339                     return;
    340                 }
    341             }
    342         }
    343 
    344         if (mCanvas.getCursor() != null) {
    345             mCanvas.setCursor(null);
    346         }
    347     }
    348 
    349     /**
    350      * Update the Eclipse status message with any feedback messages from the given
    351      * {@link DropFeedback} object, or clean up if there is no more feedback to process
    352      * @param feedback the feedback whose message we want to display, or null to clear the
    353      *            message if previously set
    354      */
    355     void updateMessage(DropFeedback feedback) {
    356         IEditorSite editorSite = mCanvas.getEditorDelegate().getEditor().getEditorSite();
    357         IStatusLineManager status = editorSite.getActionBars().getStatusLineManager();
    358         if (feedback == null) {
    359             if (mDisplayingMessage != null) {
    360                 status.setMessage(null);
    361                 status.setErrorMessage(null);
    362                 mDisplayingMessage = null;
    363             }
    364         } else if (feedback.errorMessage != null) {
    365             if (!feedback.errorMessage.equals(mDisplayingMessage)) {
    366                 mDisplayingMessage = feedback.errorMessage;
    367                 status.setErrorMessage(mDisplayingMessage);
    368             }
    369         } else if (feedback.message != null) {
    370             if (!feedback.message.equals(mDisplayingMessage)) {
    371                 mDisplayingMessage = feedback.message;
    372                 status.setMessage(mDisplayingMessage);
    373             }
    374         } else if (mDisplayingMessage != null) {
    375             // TODO: Can we check the existing message and only clear it if it's the
    376             // same as the one we set?
    377             mDisplayingMessage = null;
    378             status.setMessage(null);
    379             status.setErrorMessage(null);
    380         }
    381 
    382         // Tooltip
    383         if (feedback != null && feedback.tooltip != null) {
    384             Pair<Boolean,Boolean> position = mCurrentGesture.getTooltipPosition();
    385             boolean below = position.getFirst();
    386             if (feedback.tooltipY != null) {
    387                 below = feedback.tooltipY == SegmentType.BOTTOM;
    388             }
    389             boolean toRightOf = position.getSecond();
    390             if (feedback.tooltipX != null) {
    391                 toRightOf = feedback.tooltipX == SegmentType.RIGHT;
    392             }
    393             if (mTooltip == null) {
    394                 mTooltip = new GestureToolTip(mCanvas, below, toRightOf);
    395             }
    396             mTooltip.update(feedback.tooltip, below, toRightOf);
    397         } else if (mTooltip != null) {
    398             mTooltip.dispose();
    399             mTooltip = null;
    400         }
    401     }
    402 
    403     /**
    404      * Returns the current mouse position as a {@link ControlPoint}
    405      *
    406      * @return the current mouse position as a {@link ControlPoint}
    407      */
    408     public ControlPoint getCurrentControlPoint() {
    409         return ControlPoint.create(mCanvas, mLastMouseX, mLastMouseY);
    410     }
    411 
    412     /**
    413      * Returns the current SWT modifier key mask as an {@link IViewRule} modifier mask
    414      *
    415      * @return the current SWT modifier key mask as an {@link IViewRule} modifier mask
    416      */
    417     public int getRuleModifierMask() {
    418         int swtMask = mLastStateMask;
    419         int modifierMask = 0;
    420         if ((swtMask & SWT.MOD1) != 0) {
    421             modifierMask |= DropFeedback.MODIFIER1;
    422         }
    423         if ((swtMask & SWT.MOD2) != 0) {
    424             modifierMask |= DropFeedback.MODIFIER2;
    425         }
    426         if ((swtMask & SWT.MOD3) != 0) {
    427             modifierMask |= DropFeedback.MODIFIER3;
    428         }
    429         return modifierMask;
    430     }
    431 
    432     /**
    433      * Helper class which implements the {@link MouseMoveListener},
    434      * {@link MouseListener} and {@link KeyListener} interfaces.
    435      */
    436     private class Listener implements MouseMoveListener, MouseListener, KeyListener {
    437 
    438         // --- MouseMoveListener ---
    439 
    440         @Override
    441         public void mouseMove(MouseEvent e) {
    442             mLastMouseX = e.x;
    443             mLastMouseY = e.y;
    444             mLastStateMask = e.stateMask;
    445 
    446             if ((e.stateMask & SWT.BUTTON_MASK) != 0) {
    447                 if (mCurrentGesture != null) {
    448                     ControlPoint controlPoint = ControlPoint.create(mCanvas, e);
    449                     updateMouse(controlPoint, e);
    450                     mCanvas.redraw();
    451                 }
    452             } else {
    453                 updateCursor(ControlPoint.create(mCanvas, e));
    454                 mCanvas.hover(e);
    455             }
    456         }
    457 
    458         // --- MouseListener ---
    459 
    460         @Override
    461         public void mouseUp(MouseEvent e) {
    462             ControlPoint mousePos = ControlPoint.create(mCanvas, e);
    463             if (mCurrentGesture == null) {
    464                 // Just a click, select
    465                 Pair<SelectionItem, SelectionHandle> handlePair =
    466                     mCanvas.getSelectionManager().findHandle(mousePos);
    467                 if (handlePair == null) {
    468                     mCanvas.getSelectionManager().select(e);
    469                 }
    470             }
    471             if (mCurrentGesture == null) {
    472                 updateCursor(mousePos);
    473             } else if (mCurrentGesture instanceof DropGesture) {
    474                 // Mouse Up shouldn't be delivered in the middle of a drag & drop -
    475                 // but this can happen on some versions of Linux
    476                 // (see http://code.google.com/p/android/issues/detail?id=19057 )
    477                 // and if we process the mouseUp it will abort the remainder of
    478                 // the drag & drop operation, so ignore this event!
    479             } else {
    480                 finishGesture(mousePos, false);
    481             }
    482             mCanvas.redraw();
    483         }
    484 
    485         @Override
    486         public void mouseDown(MouseEvent e) {
    487             mLastMouseX = e.x;
    488             mLastMouseY = e.y;
    489             mLastStateMask = e.stateMask;
    490 
    491             // Not yet used. Should be, for Mac and Linux.
    492         }
    493 
    494         @Override
    495         public void mouseDoubleClick(MouseEvent e) {
    496             // SWT delivers a double click event even if you click two different buttons
    497             // in rapid succession. In any case, we only want to let you double click the
    498             // first button to warp to XML:
    499             if (e.button == 1) {
    500                 // Warp to the text editor and show the corresponding XML for the
    501                 // double-clicked widget
    502                 LayoutPoint p = ControlPoint.create(mCanvas, e).toLayout();
    503                 CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p);
    504                 if (vi != null) {
    505                     mCanvas.show(vi);
    506                 }
    507             }
    508         }
    509 
    510         // --- KeyListener ---
    511 
    512         @Override
    513         public void keyPressed(KeyEvent e) {
    514             mLastStateMask = e.stateMask;
    515             // Workaround for the fact that in keyPressed the current state
    516             // mask is not yet updated
    517             if (e.keyCode == SWT.SHIFT) {
    518                 mLastStateMask |= SWT.MOD2;
    519             }
    520             if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
    521                 if (e.keyCode == SWT.COMMAND) {
    522                     mLastStateMask |= SWT.MOD1;
    523                 }
    524             } else {
    525                 if (e.keyCode == SWT.CTRL) {
    526                     mLastStateMask |= SWT.MOD1;
    527                 }
    528             }
    529 
    530             // Give gestures a first chance to see and consume the key press
    531             if (mCurrentGesture != null) {
    532                 // unless it's "Escape", which cancels the gesture
    533                 if (e.keyCode == SWT.ESC) {
    534                     ControlPoint controlPoint = ControlPoint.create(mCanvas,
    535                             mLastMouseX, mLastMouseY);
    536                     finishGesture(controlPoint, true);
    537                     return;
    538                 }
    539 
    540                 if (mCurrentGesture.keyPressed(e)) {
    541                     return;
    542                 }
    543             }
    544 
    545             // Fall back to canvas actions for the key press
    546             mCanvas.handleKeyPressed(e);
    547         }
    548 
    549         @Override
    550         public void keyReleased(KeyEvent e) {
    551             mLastStateMask = e.stateMask;
    552             // Workaround for the fact that in keyPressed the current state
    553             // mask is not yet updated
    554             if (e.keyCode == SWT.SHIFT) {
    555                 mLastStateMask &= ~SWT.MOD2;
    556             }
    557             if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
    558                 if (e.keyCode == SWT.COMMAND) {
    559                     mLastStateMask &= ~SWT.MOD1;
    560                 }
    561             } else {
    562                 if (e.keyCode == SWT.CTRL) {
    563                     mLastStateMask &= ~SWT.MOD1;
    564                 }
    565             }
    566 
    567             if (mCurrentGesture != null) {
    568                 mCurrentGesture.keyReleased(e);
    569             }
    570         }
    571     }
    572 
    573     /** Listener for Drag &amp; Drop events. */
    574     private class CanvasDropListener implements DropTargetListener {
    575         public CanvasDropListener() {
    576         }
    577 
    578         /**
    579          * The cursor has entered the drop target boundaries. {@inheritDoc}
    580          */
    581         @Override
    582         public void dragEnter(DropTargetEvent event) {
    583             mCanvas.showInvisibleViews(true);
    584             mCanvas.getEditorDelegate().getGraphicalEditor().dismissHoverPalette();
    585 
    586             if (mCurrentGesture == null) {
    587                 Gesture newGesture = mZombieGesture;
    588                 if (newGesture == null) {
    589                     newGesture = new MoveGesture(mCanvas);
    590                 } else {
    591                     mZombieGesture = null;
    592                 }
    593                 startGesture(ControlPoint.create(mCanvas, event),
    594                         newGesture, 0);
    595             }
    596 
    597             if (mCurrentGesture instanceof DropGesture) {
    598                 ((DropGesture) mCurrentGesture).dragEnter(event);
    599             }
    600         }
    601 
    602         /**
    603          * The cursor is moving over the drop target. {@inheritDoc}
    604          */
    605         @Override
    606         public void dragOver(DropTargetEvent event) {
    607             if (mCurrentGesture instanceof DropGesture) {
    608                 ((DropGesture) mCurrentGesture).dragOver(event);
    609             }
    610         }
    611 
    612         /**
    613          * The cursor has left the drop target boundaries OR data is about to be
    614          * dropped. {@inheritDoc}
    615          */
    616         @Override
    617         public void dragLeave(DropTargetEvent event) {
    618             if (mCurrentGesture instanceof DropGesture) {
    619                 DropGesture dropGesture = (DropGesture) mCurrentGesture;
    620                 dropGesture.dragLeave(event);
    621                 finishGesture(ControlPoint.create(mCanvas, event), true);
    622                 mZombieGesture = dropGesture;
    623             }
    624 
    625             mCanvas.showInvisibleViews(false);
    626         }
    627 
    628         /**
    629          * The drop is about to be performed. The drop target is given a last
    630          * chance to change the nature of the drop. {@inheritDoc}
    631          */
    632         @Override
    633         public void dropAccept(DropTargetEvent event) {
    634             Gesture gesture = mCurrentGesture != null ? mCurrentGesture : mZombieGesture;
    635             if (gesture instanceof DropGesture) {
    636                 ((DropGesture) gesture).dropAccept(event);
    637             }
    638         }
    639 
    640         /**
    641          * The data is being dropped. {@inheritDoc}
    642          */
    643         @Override
    644         public void drop(final DropTargetEvent event) {
    645             // See if we had a gesture just prior to the drop (we receive a dragLeave
    646             // right before the drop which we don't know whether means the cursor has
    647             // left the canvas for good or just before a drop)
    648             Gesture gesture = mCurrentGesture != null ? mCurrentGesture : mZombieGesture;
    649             mZombieGesture = null;
    650 
    651             if (gesture instanceof DropGesture) {
    652                 ((DropGesture) gesture).drop(event);
    653 
    654                 finishGesture(ControlPoint.create(mCanvas, event), true);
    655             }
    656         }
    657 
    658         /**
    659          * The operation being performed has changed (e.g. modifier key).
    660          * {@inheritDoc}
    661          */
    662         @Override
    663         public void dragOperationChanged(DropTargetEvent event) {
    664             if (mCurrentGesture instanceof DropGesture) {
    665                 ((DropGesture) mCurrentGesture).dragOperationChanged(event);
    666             }
    667         }
    668     }
    669 
    670     /**
    671      * Our canvas {@link DragSourceListener}. Handles drag being started and
    672      * finished and generating the drag data.
    673      */
    674     private class CanvasDragSourceListener implements DragSourceListener {
    675 
    676         /**
    677          * The current selection being dragged. This may be a subset of the
    678          * canvas selection due to the "sanitize" pass. Can be empty but never
    679          * null.
    680          */
    681         private final ArrayList<SelectionItem> mDragSelection = new ArrayList<SelectionItem>();
    682 
    683         private SimpleElement[] mDragElements;
    684 
    685         /**
    686          * The user has begun the actions required to drag the widget.
    687          * <p/>
    688          * Initiate a drag only if there is one or more item selected. If
    689          * there's none, try to auto-select the one under the cursor.
    690          * {@inheritDoc}
    691          */
    692         @Override
    693         public void dragStart(DragSourceEvent e) {
    694             LayoutPoint p = LayoutPoint.create(mCanvas, e);
    695             ControlPoint controlPoint = ControlPoint.create(mCanvas, e);
    696             SelectionManager selectionManager = mCanvas.getSelectionManager();
    697 
    698             // See if the mouse is over a selection handle; if so, start a resizing
    699             // gesture.
    700             Pair<SelectionItem, SelectionHandle> handle =
    701                 selectionManager.findHandle(controlPoint);
    702             if (handle != null) {
    703                 startGesture(controlPoint, new ResizeGesture(mCanvas, handle.getFirst(),
    704                         handle.getSecond()), mLastStateMask);
    705                 e.detail = DND.DROP_NONE;
    706                 e.doit = false;
    707                 mCanvas.redraw();
    708                 return;
    709             }
    710 
    711             // We need a selection (simple or multiple) to do any transfer.
    712             // If there's a selection *and* the cursor is over this selection,
    713             // use all the currently selected elements.
    714             // If there is no selection or the cursor is not over a selected
    715             // element, *change* the selection to match the element under the
    716             // cursor and use that. If nothing can be selected, abort the drag
    717             // operation.
    718             List<SelectionItem> selections = selectionManager.getSelections();
    719             mDragSelection.clear();
    720 
    721             if (!selections.isEmpty()) {
    722                 // Is the cursor on top of a selected element?
    723                 boolean insideSelection = false;
    724 
    725                 for (SelectionItem cs : selections) {
    726                     if (!cs.isRoot() && cs.getRect().contains(p.x, p.y)) {
    727                         insideSelection = true;
    728                         break;
    729                     }
    730                 }
    731 
    732                 if (!insideSelection) {
    733                     CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p);
    734                     if (vi != null && !vi.isRoot() && !vi.isHidden()) {
    735                         selectionManager.selectSingle(vi);
    736                         insideSelection = true;
    737                     }
    738                 }
    739 
    740                 if (insideSelection) {
    741                     // We should now have a proper selection that matches the
    742                     // cursor. Let's use this one. We make a copy of it since
    743                     // the "sanitize" pass below might remove some of the
    744                     // selected objects.
    745                     if (selections.size() == 1) {
    746                         // You are dragging just one element - this might or
    747                         // might not be the root, but if it's the root that is
    748                         // fine since we will let you drag the root if it is the
    749                         // only thing you are dragging.
    750                         mDragSelection.addAll(selections);
    751                     } else {
    752                         // Only drag non-root items.
    753                         for (SelectionItem cs : selections) {
    754                             if (!cs.isRoot() && !cs.isHidden()) {
    755                                 mDragSelection.add(cs);
    756                             }
    757                         }
    758                     }
    759                 }
    760             }
    761 
    762             // If you are dragging a non-selected item, select it
    763             if (mDragSelection.isEmpty()) {
    764                 CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p);
    765                 if (vi != null && !vi.isRoot() && !vi.isHidden()) {
    766                     selectionManager.selectSingle(vi);
    767                     mDragSelection.addAll(selections);
    768                 }
    769             }
    770 
    771             SelectionManager.sanitize(mDragSelection);
    772 
    773             e.doit = !mDragSelection.isEmpty();
    774             int imageCount = mDragSelection.size();
    775             if (e.doit) {
    776                 mDragElements = SelectionItem.getAsElements(mDragSelection);
    777                 GlobalCanvasDragInfo.getInstance().startDrag(mDragElements,
    778                         mDragSelection.toArray(new SelectionItem[imageCount]),
    779                         mCanvas, new Runnable() {
    780                             @Override
    781                             public void run() {
    782                                 mCanvas.getClipboardSupport().deleteSelection("Remove",
    783                                         mDragSelection);
    784                             }
    785                         });
    786             }
    787 
    788             // If you drag on the -background-, we make that into a marquee
    789             // selection
    790             if (!e.doit || (imageCount == 1
    791                     && (mDragSelection.get(0).isRoot() || mDragSelection.get(0).isHidden()))) {
    792                 boolean toggle = (mLastStateMask & (SWT.CTRL | SWT.SHIFT | SWT.COMMAND)) != 0;
    793                 startGesture(controlPoint,
    794                         new MarqueeGesture(mCanvas, toggle), mLastStateMask);
    795                 e.detail = DND.DROP_NONE;
    796                 e.doit = false;
    797             } else {
    798                 // Otherwise, the drag means you are moving something
    799                 mCanvas.showInvisibleViews(true);
    800                 startGesture(controlPoint, new MoveGesture(mCanvas), 0);
    801 
    802                 // Render drag-images: Copy portions of the full screen render.
    803                 Image image = mCanvas.getImageOverlay().getImage();
    804                 if (image != null) {
    805                     /**
    806                      * Transparency of the dragged image ([0-255]). We're using 30%
    807                      * translucency to make the image faint and not obscure the drag
    808                      * feedback below it.
    809                      */
    810                     final byte DRAG_TRANSPARENCY = (byte) (0.3 * 255);
    811 
    812                     List<Rectangle> rectangles = new ArrayList<Rectangle>(imageCount);
    813                     if (imageCount > 0) {
    814                         ImageData data = image.getImageData();
    815                         Rectangle imageRectangle = new Rectangle(0, 0, data.width, data.height);
    816                         for (SelectionItem item : mDragSelection) {
    817                             Rectangle bounds = item.getRect();
    818                             // Some bounds can be outside the rendered rectangle (for
    819                             // example, in an absolute layout, you can have negative
    820                             // coordinates), so create the intersection of these bounds.
    821                             Rectangle clippedBounds = imageRectangle.intersection(bounds);
    822                             rectangles.add(clippedBounds);
    823                         }
    824                         Rectangle boundingBox = ImageUtils.getBoundingRectangle(rectangles);
    825                         double scale = mCanvas.getHorizontalTransform().getScale();
    826                         e.image = SwtUtils.drawRectangles(image, rectangles, boundingBox, scale,
    827                                 DRAG_TRANSPARENCY);
    828 
    829                         // Set the image offset such that we preserve the relative
    830                         // distance between the mouse pointer and the top left corner of
    831                         // the dragged view
    832                         int deltaX = (int) (scale * (boundingBox.x - p.x));
    833                         int deltaY = (int) (scale * (boundingBox.y - p.y));
    834                         e.offsetX = -deltaX;
    835                         e.offsetY = -deltaY;
    836 
    837                         // View rules may need to know it as well
    838                         GlobalCanvasDragInfo dragInfo = GlobalCanvasDragInfo.getInstance();
    839                         Rect dragBounds = null;
    840                         int width = (int) (scale * boundingBox.width);
    841                         int height = (int) (scale * boundingBox.height);
    842                         dragBounds = new Rect(deltaX, deltaY, width, height);
    843                         dragInfo.setDragBounds(dragBounds);
    844 
    845                         // Record the baseline such that we can perform baseline alignment
    846                         // on the node as it's dragged around
    847                         NodeProxy firstNode =
    848                             mCanvas.getNodeFactory().create(mDragSelection.get(0).getViewInfo());
    849                         dragInfo.setDragBaseline(firstNode.getBaseline());
    850                     }
    851                 }
    852             }
    853 
    854             // No hover during drag (since no mouse over events are delivered
    855             // during a drag to keep the hovers up to date anyway)
    856             mCanvas.clearHover();
    857 
    858             mCanvas.redraw();
    859         }
    860 
    861         /**
    862          * Callback invoked when data is needed for the event, typically right
    863          * before drop. The drop side decides what type of transfer to use and
    864          * this side must now provide the adequate data. {@inheritDoc}
    865          */
    866         @Override
    867         public void dragSetData(DragSourceEvent e) {
    868             if (TextTransfer.getInstance().isSupportedType(e.dataType)) {
    869                 e.data = SelectionItem.getAsText(mCanvas, mDragSelection);
    870                 return;
    871             }
    872 
    873             if (SimpleXmlTransfer.getInstance().isSupportedType(e.dataType)) {
    874                 e.data = mDragElements;
    875                 return;
    876             }
    877 
    878             // otherwise we failed
    879             e.detail = DND.DROP_NONE;
    880             e.doit = false;
    881         }
    882 
    883         /**
    884          * Callback invoked when the drop has been finished either way. On a
    885          * successful move, remove the originating elements.
    886          */
    887         @Override
    888         public void dragFinished(DragSourceEvent e) {
    889             // Clear the selection
    890             mDragSelection.clear();
    891             mDragElements = null;
    892             GlobalCanvasDragInfo.getInstance().stopDrag();
    893 
    894             finishGesture(ControlPoint.create(mCanvas, e), e.detail == DND.DROP_NONE);
    895             mCanvas.showInvisibleViews(false);
    896             mCanvas.redraw();
    897         }
    898     }
    899 }
    900