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