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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 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 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 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 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 & Drop events. */ 574 private class CanvasDropListener implements DropTargetListener { 575 public CanvasDropListener() { 576 } 577 578 /** 579 * The cursor has entered the drop target boundaries. {@inheritDoc} 580 */ 581 @Override 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 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 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 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 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 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 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 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 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