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