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.getLayoutEditor().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 mouseMove(MouseEvent e)440 public void mouseMove(MouseEvent e) { 441 mLastMouseX = e.x; 442 mLastMouseY = e.y; 443 mLastStateMask = e.stateMask; 444 445 if ((e.stateMask & SWT.BUTTON_MASK) != 0) { 446 if (mCurrentGesture != null) { 447 ControlPoint controlPoint = ControlPoint.create(mCanvas, e); 448 updateMouse(controlPoint, e); 449 mCanvas.redraw(); 450 } 451 } else { 452 updateCursor(ControlPoint.create(mCanvas, e)); 453 mCanvas.hover(e); 454 } 455 } 456 457 // --- MouseListener --- 458 mouseUp(MouseEvent e)459 public void mouseUp(MouseEvent e) { 460 ControlPoint mousePos = ControlPoint.create(mCanvas, e); 461 if (mCurrentGesture == null) { 462 // Just a click, select 463 Pair<SelectionItem, SelectionHandle> handlePair = 464 mCanvas.getSelectionManager().findHandle(mousePos); 465 if (handlePair == null) { 466 mCanvas.getSelectionManager().select(e); 467 } 468 } 469 if (mCurrentGesture == null) { 470 updateCursor(mousePos); 471 } else if (mCurrentGesture instanceof DropGesture) { 472 // Mouse Up shouldn't be delivered in the middle of a drag & drop - 473 // but this can happen on some versions of Linux 474 // (see http://code.google.com/p/android/issues/detail?id=19057 ) 475 // and if we process the mouseUp it will abort the remainder of 476 // the drag & drop operation, so ignore this event! 477 } else { 478 finishGesture(mousePos, false); 479 } 480 mCanvas.redraw(); 481 } 482 mouseDown(MouseEvent e)483 public void mouseDown(MouseEvent e) { 484 mLastMouseX = e.x; 485 mLastMouseY = e.y; 486 mLastStateMask = e.stateMask; 487 488 // Not yet used. Should be, for Mac and Linux. 489 } 490 mouseDoubleClick(MouseEvent e)491 public void mouseDoubleClick(MouseEvent e) { 492 // SWT delivers a double click event even if you click two different buttons 493 // in rapid succession. In any case, we only want to let you double click the 494 // first button to warp to XML: 495 if (e.button == 1) { 496 // Warp to the text editor and show the corresponding XML for the 497 // double-clicked widget 498 LayoutPoint p = ControlPoint.create(mCanvas, e).toLayout(); 499 CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p); 500 if (vi != null) { 501 mCanvas.show(vi); 502 } 503 } 504 } 505 506 // --- KeyListener --- 507 keyPressed(KeyEvent e)508 public void keyPressed(KeyEvent e) { 509 mLastStateMask = e.stateMask; 510 // Workaround for the fact that in keyPressed the current state 511 // mask is not yet updated 512 if (e.keyCode == SWT.SHIFT) { 513 mLastStateMask |= SWT.MOD2; 514 } 515 if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) { 516 if (e.keyCode == SWT.COMMAND) { 517 mLastStateMask |= SWT.MOD1; 518 } 519 } else { 520 if (e.keyCode == SWT.CTRL) { 521 mLastStateMask |= SWT.MOD1; 522 } 523 } 524 525 // Give gestures a first chance to see and consume the key press 526 if (mCurrentGesture != null) { 527 // unless it's "Escape", which cancels the gesture 528 if (e.keyCode == SWT.ESC) { 529 ControlPoint controlPoint = ControlPoint.create(mCanvas, 530 mLastMouseX, mLastMouseY); 531 finishGesture(controlPoint, true); 532 return; 533 } 534 535 if (mCurrentGesture.keyPressed(e)) { 536 return; 537 } 538 } 539 540 // Fall back to canvas actions for the key press 541 mCanvas.handleKeyPressed(e); 542 } 543 keyReleased(KeyEvent e)544 public void keyReleased(KeyEvent e) { 545 mLastStateMask = e.stateMask; 546 // Workaround for the fact that in keyPressed the current state 547 // mask is not yet updated 548 if (e.keyCode == SWT.SHIFT) { 549 mLastStateMask &= ~SWT.MOD2; 550 } 551 if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) { 552 if (e.keyCode == SWT.COMMAND) { 553 mLastStateMask &= ~SWT.MOD1; 554 } 555 } else { 556 if (e.keyCode == SWT.CTRL) { 557 mLastStateMask &= ~SWT.MOD1; 558 } 559 } 560 561 if (mCurrentGesture != null) { 562 mCurrentGesture.keyReleased(e); 563 } 564 } 565 } 566 567 /** Listener for Drag & Drop events. */ 568 private class CanvasDropListener implements DropTargetListener { CanvasDropListener()569 public CanvasDropListener() { 570 } 571 572 /** 573 * The cursor has entered the drop target boundaries. {@inheritDoc} 574 */ dragEnter(DropTargetEvent event)575 public void dragEnter(DropTargetEvent event) { 576 mCanvas.showInvisibleViews(true); 577 578 if (mCurrentGesture == null) { 579 Gesture newGesture = mZombieGesture; 580 if (newGesture == null) { 581 newGesture = new MoveGesture(mCanvas); 582 } else { 583 mZombieGesture = null; 584 } 585 startGesture(ControlPoint.create(mCanvas, event), 586 newGesture, 0); 587 } 588 589 if (mCurrentGesture instanceof DropGesture) { 590 ((DropGesture) mCurrentGesture).dragEnter(event); 591 } 592 } 593 594 /** 595 * The cursor is moving over the drop target. {@inheritDoc} 596 */ dragOver(DropTargetEvent event)597 public void dragOver(DropTargetEvent event) { 598 if (mCurrentGesture instanceof DropGesture) { 599 ((DropGesture) mCurrentGesture).dragOver(event); 600 } 601 } 602 603 /** 604 * The cursor has left the drop target boundaries OR data is about to be 605 * dropped. {@inheritDoc} 606 */ dragLeave(DropTargetEvent event)607 public void dragLeave(DropTargetEvent event) { 608 if (mCurrentGesture instanceof DropGesture) { 609 DropGesture dropGesture = (DropGesture) mCurrentGesture; 610 dropGesture.dragLeave(event); 611 finishGesture(ControlPoint.create(mCanvas, event), true); 612 mZombieGesture = dropGesture; 613 } 614 615 mCanvas.showInvisibleViews(false); 616 } 617 618 /** 619 * The drop is about to be performed. The drop target is given a last 620 * chance to change the nature of the drop. {@inheritDoc} 621 */ dropAccept(DropTargetEvent event)622 public void dropAccept(DropTargetEvent event) { 623 Gesture gesture = mCurrentGesture != null ? mCurrentGesture : mZombieGesture; 624 if (gesture instanceof DropGesture) { 625 ((DropGesture) gesture).dropAccept(event); 626 } 627 } 628 629 /** 630 * The data is being dropped. {@inheritDoc} 631 */ drop(final DropTargetEvent event)632 public void drop(final DropTargetEvent event) { 633 // See if we had a gesture just prior to the drop (we receive a dragLeave 634 // right before the drop which we don't know whether means the cursor has 635 // left the canvas for good or just before a drop) 636 Gesture gesture = mCurrentGesture != null ? mCurrentGesture : mZombieGesture; 637 mZombieGesture = null; 638 639 if (gesture instanceof DropGesture) { 640 ((DropGesture) gesture).drop(event); 641 642 finishGesture(ControlPoint.create(mCanvas, event), true); 643 } 644 } 645 646 /** 647 * The operation being performed has changed (e.g. modifier key). 648 * {@inheritDoc} 649 */ dragOperationChanged(DropTargetEvent event)650 public void dragOperationChanged(DropTargetEvent event) { 651 if (mCurrentGesture instanceof DropGesture) { 652 ((DropGesture) mCurrentGesture).dragOperationChanged(event); 653 } 654 } 655 } 656 657 /** 658 * Our canvas {@link DragSourceListener}. Handles drag being started and 659 * finished and generating the drag data. 660 */ 661 private class CanvasDragSourceListener implements DragSourceListener { 662 663 /** 664 * The current selection being dragged. This may be a subset of the 665 * canvas selection due to the "sanitize" pass. Can be empty but never 666 * null. 667 */ 668 private final ArrayList<SelectionItem> mDragSelection = new ArrayList<SelectionItem>(); 669 670 private SimpleElement[] mDragElements; 671 672 /** 673 * The user has begun the actions required to drag the widget. 674 * <p/> 675 * Initiate a drag only if there is one or more item selected. If 676 * there's none, try to auto-select the one under the cursor. 677 * {@inheritDoc} 678 */ dragStart(DragSourceEvent e)679 public void dragStart(DragSourceEvent e) { 680 LayoutPoint p = LayoutPoint.create(mCanvas, e); 681 ControlPoint controlPoint = ControlPoint.create(mCanvas, e); 682 SelectionManager selectionManager = mCanvas.getSelectionManager(); 683 684 // See if the mouse is over a selection handle; if so, start a resizing 685 // gesture. 686 Pair<SelectionItem, SelectionHandle> handle = 687 selectionManager.findHandle(controlPoint); 688 if (handle != null) { 689 startGesture(controlPoint, new ResizeGesture(mCanvas, handle.getFirst(), 690 handle.getSecond()), mLastStateMask); 691 e.detail = DND.DROP_NONE; 692 e.doit = false; 693 mCanvas.redraw(); 694 return; 695 } 696 697 // We need a selection (simple or multiple) to do any transfer. 698 // If there's a selection *and* the cursor is over this selection, 699 // use all the currently selected elements. 700 // If there is no selection or the cursor is not over a selected 701 // element, *change* the selection to match the element under the 702 // cursor and use that. If nothing can be selected, abort the drag 703 // operation. 704 List<SelectionItem> selections = selectionManager.getSelections(); 705 mDragSelection.clear(); 706 707 if (!selections.isEmpty()) { 708 // Is the cursor on top of a selected element? 709 boolean insideSelection = false; 710 711 for (SelectionItem cs : selections) { 712 if (!cs.isRoot() && cs.getRect().contains(p.x, p.y)) { 713 insideSelection = true; 714 break; 715 } 716 } 717 718 if (!insideSelection) { 719 CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p); 720 if (vi != null && !vi.isRoot() && !vi.isHidden()) { 721 selectionManager.selectSingle(vi); 722 insideSelection = true; 723 } 724 } 725 726 if (insideSelection) { 727 // We should now have a proper selection that matches the 728 // cursor. Let's use this one. We make a copy of it since 729 // the "sanitize" pass below might remove some of the 730 // selected objects. 731 if (selections.size() == 1) { 732 // You are dragging just one element - this might or 733 // might not be the root, but if it's the root that is 734 // fine since we will let you drag the root if it is the 735 // only thing you are dragging. 736 mDragSelection.addAll(selections); 737 } else { 738 // Only drag non-root items. 739 for (SelectionItem cs : selections) { 740 if (!cs.isRoot() && !cs.isHidden()) { 741 mDragSelection.add(cs); 742 } 743 } 744 } 745 } 746 } 747 748 // If you are dragging a non-selected item, select it 749 if (mDragSelection.isEmpty()) { 750 CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p); 751 if (vi != null && !vi.isRoot() && !vi.isHidden()) { 752 selectionManager.selectSingle(vi); 753 mDragSelection.addAll(selections); 754 } 755 } 756 757 SelectionManager.sanitize(mDragSelection); 758 759 e.doit = !mDragSelection.isEmpty(); 760 int imageCount = mDragSelection.size(); 761 if (e.doit) { 762 mDragElements = SelectionItem.getAsElements(mDragSelection); 763 GlobalCanvasDragInfo.getInstance().startDrag(mDragElements, 764 mDragSelection.toArray(new SelectionItem[imageCount]), 765 mCanvas, new Runnable() { 766 public void run() { 767 mCanvas.getClipboardSupport().deleteSelection("Remove", 768 mDragSelection); 769 } 770 }); 771 } 772 773 // If you drag on the -background-, we make that into a marquee 774 // selection 775 if (!e.doit || (imageCount == 1 776 && (mDragSelection.get(0).isRoot() || mDragSelection.get(0).isHidden()))) { 777 boolean toggle = (mLastStateMask & (SWT.CTRL | SWT.SHIFT | SWT.COMMAND)) != 0; 778 startGesture(controlPoint, 779 new MarqueeGesture(mCanvas, toggle), mLastStateMask); 780 e.detail = DND.DROP_NONE; 781 e.doit = false; 782 } else { 783 // Otherwise, the drag means you are moving something 784 mCanvas.showInvisibleViews(true); 785 startGesture(controlPoint, new MoveGesture(mCanvas), 0); 786 787 // Render drag-images: Copy portions of the full screen render. 788 Image image = mCanvas.getImageOverlay().getImage(); 789 if (image != null) { 790 /** 791 * Transparency of the dragged image ([0-255]). We're using 30% 792 * translucency to make the image faint and not obscure the drag 793 * feedback below it. 794 */ 795 final byte DRAG_TRANSPARENCY = (byte) (0.3 * 255); 796 797 List<Rectangle> rectangles = new ArrayList<Rectangle>(imageCount); 798 if (imageCount > 0) { 799 ImageData data = image.getImageData(); 800 Rectangle imageRectangle = new Rectangle(0, 0, data.width, data.height); 801 for (SelectionItem item : mDragSelection) { 802 Rectangle bounds = item.getRect(); 803 // Some bounds can be outside the rendered rectangle (for 804 // example, in an absolute layout, you can have negative 805 // coordinates), so create the intersection of these bounds. 806 Rectangle clippedBounds = imageRectangle.intersection(bounds); 807 rectangles.add(clippedBounds); 808 } 809 Rectangle boundingBox = ImageUtils.getBoundingRectangle(rectangles); 810 double scale = mCanvas.getHorizontalTransform().getScale(); 811 e.image = SwtUtils.drawRectangles(image, rectangles, boundingBox, scale, 812 DRAG_TRANSPARENCY); 813 814 // Set the image offset such that we preserve the relative 815 // distance between the mouse pointer and the top left corner of 816 // the dragged view 817 int deltaX = (int) (scale * (boundingBox.x - p.x)); 818 int deltaY = (int) (scale * (boundingBox.y - p.y)); 819 e.offsetX = -deltaX; 820 e.offsetY = -deltaY; 821 822 // View rules may need to know it as well 823 GlobalCanvasDragInfo dragInfo = GlobalCanvasDragInfo.getInstance(); 824 Rect dragBounds = null; 825 int width = (int) (scale * boundingBox.width); 826 int height = (int) (scale * boundingBox.height); 827 dragBounds = new Rect(deltaX, deltaY, width, height); 828 dragInfo.setDragBounds(dragBounds); 829 830 // Record the baseline such that we can perform baseline alignment 831 // on the node as it's dragged around 832 NodeProxy firstNode = 833 mCanvas.getNodeFactory().create(mDragSelection.get(0).getViewInfo()); 834 dragInfo.setDragBaseline(firstNode.getBaseline()); 835 } 836 } 837 } 838 839 // No hover during drag (since no mouse over events are delivered 840 // during a drag to keep the hovers up to date anyway) 841 mCanvas.clearHover(); 842 843 mCanvas.redraw(); 844 } 845 846 /** 847 * Callback invoked when data is needed for the event, typically right 848 * before drop. The drop side decides what type of transfer to use and 849 * this side must now provide the adequate data. {@inheritDoc} 850 */ dragSetData(DragSourceEvent e)851 public void dragSetData(DragSourceEvent e) { 852 if (TextTransfer.getInstance().isSupportedType(e.dataType)) { 853 e.data = SelectionItem.getAsText(mCanvas, mDragSelection); 854 return; 855 } 856 857 if (SimpleXmlTransfer.getInstance().isSupportedType(e.dataType)) { 858 e.data = mDragElements; 859 return; 860 } 861 862 // otherwise we failed 863 e.detail = DND.DROP_NONE; 864 e.doit = false; 865 } 866 867 /** 868 * Callback invoked when the drop has been finished either way. On a 869 * successful move, remove the originating elements. 870 */ dragFinished(DragSourceEvent e)871 public void dragFinished(DragSourceEvent e) { 872 // Clear the selection 873 mDragSelection.clear(); 874 mDragElements = null; 875 GlobalCanvasDragInfo.getInstance().stopDrag(); 876 877 finishGesture(ControlPoint.create(mCanvas, e), e.detail == DND.DROP_NONE); 878 mCanvas.showInvisibleViews(false); 879 mCanvas.redraw(); 880 } 881 } 882 } 883