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      */
GestureManager(LayoutCanvas canvas)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      */
getCanvas()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      */
getCurrentGesture()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      */
paint(GC gc)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      */
registerListeners(DragSource dragSource, DropTarget dropTarget)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      */
unregisterListeners(DragSource dragSource, DropTarget dropTarget)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      */
startGesture(ControlPoint mousePos, Gesture gesture, int mask)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      */
updateMouse(ControlPoint mousePos, TypedEvent event)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      */
finishGesture(ControlPoint mousePos, boolean canceled)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      */
updateCursor(ControlPoint controlPoint)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      */
updateMessage(DropFeedback feedback)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      */
getCurrentControlPoint()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      */
getRuleModifierMask()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
mouseMove(MouseEvent e)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
mouseUp(MouseEvent e)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
mouseDown(MouseEvent e)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
mouseDoubleClick(MouseEvent e)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
mouseEnter(MouseEvent e)522         public void mouseEnter(MouseEvent e) {
523             ControlPoint mousePos = ControlPoint.create(mCanvas, e);
524             mCanvas.getPreviewManager().enter(mousePos);
525         }
526 
527         @Override
mouseExit(MouseEvent e)528         public void mouseExit(MouseEvent e) {
529             ControlPoint mousePos = ControlPoint.create(mCanvas, e);
530             mCanvas.getPreviewManager().exit(mousePos);
531         }
532 
533         @Override
mouseHover(MouseEvent e)534         public void mouseHover(MouseEvent e) {
535         }
536 
537         // --- KeyListener ---
538 
539         @Override
keyPressed(KeyEvent e)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
keyReleased(KeyEvent e)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 {
CanvasDropListener()602         public CanvasDropListener() {
603         }
604 
605         /**
606          * The cursor has entered the drop target boundaries. {@inheritDoc}
607          */
608         @Override
dragEnter(DropTargetEvent event)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
dragOver(DropTargetEvent event)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
dragLeave(DropTargetEvent event)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
dropAccept(DropTargetEvent event)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
drop(final DropTargetEvent event)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
dragOperationChanged(DropTargetEvent event)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
dragStart(DragSourceEvent e)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
dragSetData(DragSourceEvent e)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
dragFinished(DragSourceEvent e)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