| /******************************************************************************* |
| * Copyright (c) 2004 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ui.internal.dnd; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import org.eclipse.jface.util.Geometry; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.swt.widgets.Tracker; |
| import org.eclipse.ui.internal.DragCursors; |
| |
| |
| /** |
| * Provides the methods for attaching drag-and-drop listeners to SWT controls. |
| */ |
| public class DragUtil { |
| private static final String DROP_TARGET_ID = "org.eclipse.ui.internal.dnd.dropTarget"; //$NON-NLS-1$ |
| |
| /** |
| * The location where all drags will end. If this is non-null, then |
| * all user input is ignored in drag/drop. If null, we use user input |
| * to determine where objects should be dropped. |
| */ |
| private static TestDropLocation forcedDropTarget = null; |
| |
| /** |
| * List of IDragOverListener |
| */ |
| private static List defaultTargets = new ArrayList(); |
| |
| /** |
| * Sets the drop target for the given control. It is possible to add one or more |
| * targets for a "null" control. This becomes a default target that is used if no |
| * other targets are found (for example, when dragging objects off the application |
| * window). |
| * |
| * @param control the control that should be treated as a drag target, or null |
| * to indicate the default target |
| * @param target the drag target to handle the given control |
| */ |
| public static void addDragTarget(Control control, IDragOverListener target) { |
| if (control == null) { |
| defaultTargets.add(target); |
| } else { |
| List targetList = getTargetList(control); |
| |
| if (targetList == null) { |
| targetList = new ArrayList(1); |
| } |
| targetList.add(target); |
| control.setData(DROP_TARGET_ID, targetList); |
| } |
| } |
| |
| private static List getTargetList(Control control) { |
| List result = (List)control.getData(DROP_TARGET_ID); |
| return result; |
| } |
| |
| /** |
| * Removes a drop target from the given control. |
| * |
| * @param control |
| * @param target |
| */ |
| public static void removeDragTarget(Control control, IDragOverListener target) { |
| if (control == null) { |
| defaultTargets.remove(target); |
| } else { |
| List targetList = getTargetList(control); |
| if (targetList != null) { |
| targetList.remove(target); |
| if (targetList.isEmpty()) { |
| control.setData(DROP_TARGET_ID, null); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Shorthand method. Returns the bounding rectangle for the given control, in |
| * display coordinates. |
| * |
| * @param draggedItem |
| * @param boundsControl |
| * @return |
| */ |
| public static Rectangle getDisplayBounds(Control boundsControl) { |
| Control parent = boundsControl.getParent(); |
| if (parent == null) { |
| return boundsControl.getBounds(); |
| } |
| |
| return Geometry.toDisplay(parent, boundsControl.getBounds()); |
| } |
| |
| public static boolean performDrag(final Object draggedItem, Rectangle sourceBounds) { |
| return performDrag(draggedItem, sourceBounds, |
| Display.getDefault().getCursorLocation(), false); |
| } |
| |
| public static boolean performDrag(final Object draggedItem, Rectangle sourceBounds, |
| Point initialLocation, boolean allowSnapping) { |
| |
| IDropTarget target = dragToTarget(draggedItem, sourceBounds, |
| initialLocation, allowSnapping); |
| |
| if (target == null) { |
| return false; |
| } |
| |
| target.drop(); |
| |
| return true; |
| } |
| |
| |
| /** |
| * Drags the given item to the given location (in display coordinates). This |
| * method is intended for use by test suites. |
| * |
| * @param draggedItem object being dragged |
| * @param finalLocation location being dragged to |
| * @return true iff the drop was accepted |
| */ |
| public static boolean dragTo(Display display, Object draggedItem, Point finalLocation, Rectangle dragRectangle) { |
| Control currentControl = SwtUtil.findControl(display, finalLocation); |
| |
| IDropTarget target = getDropTarget(currentControl, draggedItem, finalLocation, dragRectangle); |
| |
| if (target == null) { |
| return false; |
| } |
| |
| target.drop(); |
| |
| return true; |
| } |
| |
| /** |
| * Forces all drags to end at the given position (display coordinates). Intended |
| * for use by test suites. If this method is called, then all subsequent calls |
| * to performDrag will terminate immediately and behave as though the object were |
| * dragged to the given location. Calling this method with null cancels this |
| * behavior and causes performDrag to behave normally. |
| * |
| * @param forcedLocation location where objects will be dropped (or null to |
| * cause drag/drop to behave normally). |
| */ |
| public static void forceDropLocation(TestDropLocation forcedLocation) { |
| forcedDropTarget = forcedLocation; |
| } |
| |
| /** |
| * Drags the given item, given an initial bounding rectangle in display coordinates. |
| * Due to a quirk in the Tracker class, changing the tracking rectangle when using the |
| * keyboard will also cause the mouse cursor to move. Since "snapping" causes the tracking |
| * rectangle to change based on the position of the mouse cursor, it is impossible to do |
| * drag-and-drop with the keyboard when snapping is enabled. |
| * |
| * @param draggedItem object being dragged |
| * @param sourceBounds initial bounding rectangle for the dragged item |
| * @param initialLocation initial position of the mouse cursor |
| * @param allowSnapping true iff the rectangle should snap to the drop location. This must |
| * be false if the user might be doing drag-and-drop using the keyboard. |
| * |
| * @return |
| */ |
| static IDropTarget dragToTarget(final Object draggedItem, final Rectangle sourceBounds, |
| final Point initialLocation, final boolean allowSnapping) { |
| final Display display = Display.getDefault(); |
| |
| if (forcedDropTarget != null) { |
| |
| Point location = forcedDropTarget.getLocation(); |
| Control currentControl = SwtUtil.findControl(display, location); |
| |
| return getDropTarget(currentControl, draggedItem, location, sourceBounds); |
| } |
| |
| // Create a tracker. This is just an XOR rect on the screen. |
| // As it moves we notify the drag listeners. |
| final Tracker tracker = new Tracker(display, SWT.NULL); |
| |
| tracker.setStippled(true); |
| |
| tracker.addListener(SWT.Move, new Listener() { |
| public void handleEvent(final Event event) { |
| display.syncExec(new Runnable() { |
| public void run() { |
| Point location = new Point(event.x, event.y); |
| |
| Control targetControl = display.getCursorControl(); |
| |
| IDropTarget target = getDropTarget(targetControl, draggedItem, location, |
| tracker.getRectangles()[0]); |
| |
| Rectangle snapTarget = null; |
| |
| if (target != null) { |
| snapTarget = target.getSnapRectangle(); |
| |
| tracker.setCursor(target.getCursor()); |
| } else { |
| tracker.setCursor(DragCursors.getCursor(DragCursors.INVALID)); |
| } |
| |
| if (allowSnapping) { |
| |
| if (snapTarget == null) { |
| snapTarget = new Rectangle(sourceBounds.x + location.x - initialLocation.x, |
| sourceBounds.y + location.y - initialLocation.y, |
| sourceBounds.width, |
| sourceBounds.height); |
| } |
| |
| // Try to prevent flicker: don't change the rectangles if they're already in |
| // the right location |
| |
| Rectangle[] currentRectangles = tracker.getRectangles(); |
| |
| if (!(currentRectangles.length == 1 && currentRectangles[0].equals(snapTarget))) { |
| tracker.setRectangles(new Rectangle[] {snapTarget}); |
| } |
| } |
| } |
| }); |
| } |
| }); |
| |
| if (sourceBounds != null) { |
| tracker.setRectangles(new Rectangle[] { new Rectangle(sourceBounds.x, sourceBounds.y, sourceBounds.width, sourceBounds.height) }); |
| } |
| |
| // HACK: |
| // Some control needs to capture the mouse during the drag or other |
| // controls will interfere with the cursor |
| Control startControl = display.getCursorControl(); |
| if (startControl != null) { |
| startControl.setCapture(true); |
| } |
| |
| // Run tracker until mouse up occurs or escape key pressed. |
| boolean trackingOk = tracker.open(); |
| |
| // HACK: |
| // Release the mouse now |
| if (startControl != null) { |
| startControl.setCapture(false); |
| } |
| |
| Point finalLocation = display.getCursorLocation(); |
| |
| IDropTarget dropTarget = null; |
| if (trackingOk) { |
| Control targetControl = display.getCursorControl(); |
| |
| dropTarget = getDropTarget(targetControl, draggedItem, finalLocation, tracker.getRectangles()[0]); |
| } |
| |
| // Cleanup. |
| tracker.dispose(); |
| |
| return dropTarget; |
| } |
| |
| /** |
| * Given a list of IDragOverListeners and a description of what is being dragged, it returns |
| * a IDropTarget for the current drop. |
| * |
| * @param toSearch |
| * @param mostSpecificControl |
| * @param draggedObject |
| * @param position |
| * @param dragRectangle |
| * @return |
| */ |
| private static IDropTarget getDropTarget(List toSearch, Control mostSpecificControl, Object draggedObject, Point position, Rectangle dragRectangle) { |
| if (toSearch == null) { |
| return null; |
| } |
| |
| Iterator iter = toSearch.iterator(); |
| while (iter.hasNext()) { |
| IDragOverListener next = (IDragOverListener)iter.next(); |
| |
| IDropTarget dropTarget = next.drag(mostSpecificControl, draggedObject, position, dragRectangle); |
| |
| if (dropTarget != null) { |
| return dropTarget; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns the drag target for the given control or null if none. |
| * |
| * @param toSearch |
| * @param e |
| * @return |
| */ |
| public static IDropTarget getDropTarget(Control toSearch, Object draggedObject, Point position, Rectangle dragRectangle) { |
| |
| for (Control current = toSearch; current != null; current = current.getParent()) { |
| IDropTarget dropTarget = getDropTarget(getTargetList(current), toSearch, draggedObject, position, dragRectangle); |
| |
| if (dropTarget != null) { |
| return dropTarget; |
| } |
| |
| // Don't look to parent shells for drop targets |
| if (current instanceof Shell) { |
| break; |
| } |
| } |
| |
| // No controls could handle this event -- check for default targets |
| return getDropTarget(defaultTargets, toSearch, draggedObject, position, dragRectangle); |
| } |
| |
| /** |
| * Returns the location of the given event, in display coordinates |
| * @return |
| */ |
| public static Point getEventLoc(Event event) { |
| Control ctrl = (Control)event.widget; |
| return ctrl.toDisplay(new Point(event.x, event.y)); |
| } |
| |
| } |