diff options
Diffstat (limited to 'plugins/infra/core/org.eclipse.papyrus.infra.core.sasheditor/src/org/eclipse/papyrus/infra/core/sasheditor/internal/eclipsecopy/DragUtil.java')
-rwxr-xr-x | plugins/infra/core/org.eclipse.papyrus.infra.core.sasheditor/src/org/eclipse/papyrus/infra/core/sasheditor/internal/eclipsecopy/DragUtil.java | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core.sasheditor/src/org/eclipse/papyrus/infra/core/sasheditor/internal/eclipsecopy/DragUtil.java b/plugins/infra/core/org.eclipse.papyrus.infra.core.sasheditor/src/org/eclipse/papyrus/infra/core/sasheditor/internal/eclipsecopy/DragUtil.java new file mode 100755 index 00000000000..68c01d478f4 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core.sasheditor/src/org/eclipse/papyrus/infra/core/sasheditor/internal/eclipsecopy/DragUtil.java @@ -0,0 +1,417 @@ +/******************************************************************************* + * Copyright (c) 2004, 2015 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.papyrus.infra.core.sasheditor.internal.eclipsecopy; + +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.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 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, event -> display.syncExec(() -> { + // 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[] { Geometry.copy(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)); + } + +} |