• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
GestureManager(LayoutCanvas canvas)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      */
getCanvas()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      */
getCurrentGesture()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      */
paint(GC gc)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      */
registerListeners(DragSource dragSource, DropTarget dropTarget)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      */
unregisterListeners(DragSource dragSource, DropTarget dropTarget)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      */
startGesture(ControlPoint mousePos, Gesture gesture, int mask)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      */
updateMouse(ControlPoint mousePos, TypedEvent event)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      */
finishGesture(ControlPoint mousePos, boolean canceled)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      */
updateCursor(ControlPoint controlPoint)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      */
updateMessage(DropFeedback feedback)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      */
getCurrentControlPoint()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      */
getRuleModifierMask()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
mouseMove(MouseEvent e)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
mouseUp(MouseEvent e)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
mouseDown(MouseEvent e)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
mouseDoubleClick(MouseEvent e)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
keyPressed(KeyEvent e)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
keyReleased(KeyEvent e)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 {
CanvasDropListener()575         public CanvasDropListener() {
576         }
577 
578         /**
579          * The cursor has entered the drop target boundaries. {@inheritDoc}
580          */
581         @Override
dragEnter(DropTargetEvent event)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
dragOver(DropTargetEvent event)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
dragLeave(DropTargetEvent event)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
dropAccept(DropTargetEvent event)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
drop(final DropTargetEvent event)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
dragOperationChanged(DropTargetEvent event)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
dragStart(DragSourceEvent e)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
dragSetData(DragSourceEvent e)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
dragFinished(DragSourceEvent e)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