| /******************************************************************************* |
| * Copyright (c) 2004, 2006 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-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.PlatformUI; |
| 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); |
| } |
| } |
| |
| /** |
| * Return the list of 'IDragOverListener' elements associated with |
| * the given control. If there's a 'global' listener then always |
| * return it. |
| * |
| * @param control |
| * @return |
| */ |
| 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. Note that all 'Shell' controls are expected to be 'top level' |
| * so DO NOT do the origin offset for them. |
| * |
| * @param draggedItem |
| * @param boundsControl |
| * @return |
| */ |
| public static Rectangle getDisplayBounds(Control boundsControl) { |
| Control parent = boundsControl.getParent(); |
| if (parent == null || boundsControl instanceof Shell) { |
| return boundsControl.getBounds(); |
| } |
| |
| return Geometry.toDisplay(parent, boundsControl.getBounds()); |
| } |
| |
| 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(); |
| |
| // If the target can handle a 'finished' notification then send one |
| if (target!= null && target instanceof IDropTarget2) { |
| ((IDropTarget2)target).dragFinished(true); |
| } |
| |
| 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.getCurrent(); |
| |
| // Testing...immediately 'drop' onto the test target |
| if (forcedDropTarget != null) { |
| Point location = forcedDropTarget.getLocation(); |
| |
| Control currentControl = SwtUtil.findControl(forcedDropTarget.getShells(), 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() { |
| // Get the curslor location as a point |
| Point location = new Point(event.x, event.y); |
| |
| // Select a drop target; use the global one by default |
| IDropTarget target = null; |
| |
| Control targetControl = display.getCursorControl(); |
| |
| // Get the drop target for this location |
| target = getDropTarget(targetControl, |
| draggedItem, location, |
| tracker.getRectangles()[0]); |
| |
| // Set up the tracker feedback based on the target |
| Rectangle snapTarget = null; |
| if (target != null) { |
| snapTarget = target.getSnapRectangle(); |
| |
| tracker.setCursor(target.getCursor()); |
| } else { |
| tracker.setCursor(DragCursors |
| .getCursor(DragCursors.INVALID)); |
| } |
| |
| // If snapping then reset the tracker's rectangle based on the current drop target |
| 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 }); |
| } |
| } |
| } |
| }); |
| } |
| }); |
| |
| // Setup...when the drag starts we might already be over a valid target, check this... |
| // If there is a 'global' target then skip the check |
| IDropTarget target = null; |
| Control startControl = display.getCursorControl(); |
| |
| if (startControl != null && allowSnapping) { |
| target = getDropTarget(startControl, |
| draggedItem, initialLocation, |
| sourceBounds); |
| } |
| |
| // Set up an initial tracker rectangle |
| Rectangle startRect = sourceBounds; |
| if (target != null) { |
| Rectangle rect = target.getSnapRectangle(); |
| |
| if (rect != null) { |
| startRect = rect; |
| } |
| |
| tracker.setCursor(target.getCursor()); |
| } |
| |
| if (startRect != null) { |
| tracker.setRectangles(new Rectangle[] { Geometry.copy(startRect)}); |
| } |
| |
| // Tracking Loop...tracking is preformed on the 'SWT.Move' listener registered |
| // against the tracker. |
| |
| // HACK: |
| // Some control needs to capture the mouse during the drag or other |
| // controls will interfere with the cursor |
| Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(); |
| if (shell != null) { |
| shell.setCapture(true); |
| } |
| |
| // Run tracker until mouse up occurs or escape key pressed. |
| boolean trackingOk = tracker.open(); |
| |
| // HACK: |
| // Release the mouse now |
| if (shell != null) { |
| shell.setCapture(false); |
| } |
| |
| // Done tracking... |
| |
| // Get the current drop target |
| IDropTarget dropTarget = null; |
| Point finalLocation = display.getCursorLocation(); |
| Control targetControl = display.getCursorControl(); |
| dropTarget = getDropTarget(targetControl, draggedItem, |
| finalLocation, tracker.getRectangles()[0]); |
| |
| // Cleanup... |
| tracker.dispose(); |
| |
| // if we're going to perform a 'drop' then delay the issuing of the 'finished' |
| // callback until after it's done... |
| if (trackingOk) { |
| return dropTarget; |
| } |
| else if (dropTarget!= null && dropTarget instanceof IDropTarget2) { |
| // If the target can handle a 'finished' notification then send one |
| ((IDropTarget2)dropTarget).dragFinished(false); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * 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) { |
| // Search for a listener by walking the control's parent hierarchy |
| 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)); |
| } |
| |
| } |