| /* |
| * Copyright (c) 2002 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 - Initial API and implementation |
| * Jens Lukowski/Innoopract - initial renaming/restructuring |
| * |
| */ |
| package org.eclipse.wst.common.ui.dnd; |
| |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Iterator; |
| |
| import org.eclipse.jface.viewers.AbstractTreeViewer; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.swt.custom.TableTreeItem; |
| import org.eclipse.swt.dnd.DND; |
| import org.eclipse.swt.dnd.DropTargetAdapter; |
| import org.eclipse.swt.dnd.DropTargetEvent; |
| import org.eclipse.swt.dnd.TransferData; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.ScrollBar; |
| import org.eclipse.swt.widgets.Table; |
| import org.eclipse.swt.widgets.TableItem; |
| import org.eclipse.swt.widgets.Tree; |
| import org.eclipse.swt.widgets.TreeItem; |
| import org.eclipse.swt.widgets.Widget; |
| |
| |
| /** |
| * This implementation of a drop target listener |
| * is designed to turn a drag and drop operation into a {@link Command} based on the model objects of an {@link EditingDomain} |
| * and created by {@link DragAndDropManager#create}. |
| * It is designed to do early data transfer so the the enablement and feedback of the drag and drop interaction |
| * can intimately depend on the state of the model objects involed. |
| * <p> |
| * The base implementation of this class should be sufficient for most applications. |
| * Any change in behaviour is typically accomplished by overriding |
| * {@link ItemProviderAdapter}.createDragAndDropCommand |
| * to return a derived implementation of {@link DragAndDropCommand}. |
| * This is how one these adapters is typically hooked up: |
| * <pre> |
| * viewer.addDropSupport |
| * (DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_LINK, |
| * new Transfer [] { ObjectTransfer.getInstance() }, |
| * EditingDomainViewerDropAdapter(viewer)); |
| * </pre> |
| * <p> |
| * This implementation prefers to use a {@link ObjectTransfer}, |
| * which short-circuits the transfer process for simple transfers within the workbench, |
| * the method {@link #getDragSource} can be overriden to change the behaviour. |
| * The implementation also only handles an {@link IStructuredSelection}, |
| * but the method {@link #extractDragSource} can be overriden to change the behaviour. |
| * <p> |
| * You can call {@link #setHoverThreshold} to set the amount of time, in milliseconds, |
| * to hover over an item before {@link #hover} is called; |
| * the default is 1500 milliseconds. |
| */ |
| public class ViewerDropAdapter extends DropTargetAdapter |
| { |
| public static final String copyright = "(c) Copyright IBM Corporation 2000, 2002."; |
| /** |
| * This is the viewer for which this is a drop target listener. |
| */ |
| protected Viewer viewer; |
| |
| /** |
| * This is the collection of source objects being dragged. |
| */ |
| protected Collection source; |
| |
| /** |
| * This is the command created during dragging which provides the feedback and will carry out the action upon completion. |
| */ |
| // protected Command command; |
| protected DragAndDropCommand command; |
| |
| /** |
| * This records the object for which the {@link #command} was created. |
| */ |
| protected Object commandTarget; |
| |
| /** |
| * The amount of time to hover over a tree item before expanding it |
| */ |
| protected int hoverThreshold = 1500; |
| |
| /** |
| * The is the time the mouse first started hovering over the current item. |
| */ |
| protected long hoverStart = 0; |
| |
| /** |
| * This keeps track of the most recent item for the {@link #hoverStart}. |
| */ |
| protected Widget previousItem; |
| |
| /** |
| * This keeps track of the original operation that was in effect before we set the event.detail in here. |
| */ |
| protected int originalOperation; |
| |
| /** |
| * This keeps track of the information used to create the current command. |
| */ |
| protected DragAndDropCommandInformation dragAndDropCommandInformation; |
| |
| protected DragAndDropManager dragAndDropManager; |
| |
| /** |
| * This creates and instance of the given domain and viewer. |
| */ |
| public ViewerDropAdapter(Viewer viewer, DragAndDropManager dragAndDropManager) |
| { |
| this.viewer = viewer; |
| this.dragAndDropManager = dragAndDropManager; |
| } |
| |
| /** |
| * This is called when the mouse first enters or starts dragging in the viewer. |
| */ |
| public void dragEnter(DropTargetEvent event) |
| { |
| originalOperation = event.detail; |
| helper(event); |
| } |
| |
| /** |
| * This is called when the mouse leaves or stops dragging in the viewer |
| */ |
| public void dragLeave(DropTargetEvent event) |
| { |
| // Clean up the command if there is one. |
| // |
| if (command != null) |
| { |
| // command.dispose(); |
| command = null; |
| commandTarget = null; |
| } |
| |
| // Reset the other values. |
| // |
| previousItem = null; |
| hoverStart = 0; |
| source = null; |
| } |
| |
| /** |
| * This is called when the operation has changed in some way, typically because the user changes keyboard modifiers. |
| */ |
| public void dragOperationChanged(DropTargetEvent event) |
| { |
| originalOperation = event.detail; |
| helper(event); |
| } |
| |
| /** |
| * This is called repeated when the mouse over the viewer. |
| */ |
| public void dragOver(DropTargetEvent event) |
| { |
| helper(event); |
| } |
| |
| /** |
| * This is called just as the mouse is released over the viewer to initiate a drop. |
| */ |
| public void dropAccept(DropTargetEvent event) |
| { |
| // There seems to be a bug in SWT that the view may have scrolled. |
| // helper(event); |
| } |
| |
| /** |
| * This is called to indate that the drop action should be invoked. |
| */ |
| public void drop(DropTargetEvent event) |
| { |
| // There seems to be a bug in SWT that the view may have scrolled. |
| // helper(event); |
| if (dragAndDropCommandInformation != null) |
| { |
| command = dragAndDropCommandInformation.createCommand(); |
| |
| // Execute the command |
| command.execute(); |
| |
| // Clean up the state. |
| // |
| command = null; |
| commandTarget = null; |
| previousItem = null; |
| hoverStart = 0; |
| source = null; |
| } |
| } |
| |
| /** |
| * This method is called the same way for each of the {@link org.eclipse.swt.dnd.DropTargetListener} methods, except during leave. |
| */ |
| protected void helper(DropTargetEvent event) |
| { |
| // Try to get the source if there isn't one. |
| // |
| if (source == null) |
| { |
| source = getDragSource(event); |
| } |
| else if (event.currentDataType == null) |
| { |
| setCurrentDataType(event); |
| } |
| |
| // If there's still no source, wait until the next time to try again. |
| // |
| if (source == null) |
| { |
| event.detail = DND.DROP_NONE; |
| event.feedback = DND.FEEDBACK_SELECT; |
| } |
| // Otherwise, if we need to scroll... |
| // |
| else if (scrollIfNeeded(event)) |
| { |
| // In the case that we scroll, we just do all the work on the next event and only just scroll now. |
| // |
| event.feedback = DND.FEEDBACK_SELECT; |
| } |
| else |
| { |
| // Get the data from the item, if there is one. |
| // |
| Object target = event.item == null ? null : event.item.getData(); |
| if (target instanceof TableTreeItem) |
| { |
| target = ((TableTreeItem)target).getData(); |
| } |
| |
| // Do the logic to determine the hover information. |
| // If we're over a new item from before. |
| // |
| if (event.item != previousItem) |
| { |
| // Remember the item and the time. |
| // |
| previousItem = event.item; |
| hoverStart = event.time; |
| } |
| else if (target != null) |
| { |
| if (event.time - hoverStart > hoverThreshold) |
| { |
| hover(target); |
| |
| // We don't need to hover over this guy again. |
| // |
| hoverStart = Integer.MAX_VALUE; |
| } |
| } |
| |
| // Determine if we can create a valid command at the current mouse location. |
| // |
| boolean valid = false; |
| |
| // If we don't have a previous cached command... |
| // |
| if (command == null) |
| { |
| // Create the command and test if it is executable. |
| // |
| commandTarget = target; |
| command = dragAndDropManager.createCommand(target, getLocation(event), event.operations, event.detail, source); |
| if (command != null) |
| { |
| valid = command.canExecute(); |
| } |
| } |
| else |
| { |
| int operation = originalOperation != event.detail ? originalOperation : event.detail; |
| |
| // Check if the cached command is able to provide drag and drop feedback. |
| // |
| if (target == commandTarget)// && command instanceof DragAndDropFeedback) |
| { |
| float location = getLocation(event); |
| |
| dragAndDropCommandInformation = |
| new DragAndDropCommandInformation(target, location, event.operations, operation, source); |
| |
| // If so, revalidate the command. |
| // |
| command.reinitialize(target, location, event.operations, operation, source); |
| if (command != null) |
| { |
| valid = command.canExecute(); |
| } |
| } |
| else |
| { |
| // If not, dispose the current command and create a new one. |
| // |
| // command.dispose(); |
| commandTarget = target; |
| |
| dragAndDropCommandInformation = |
| new DragAndDropCommandInformation(target, getLocation(event), event.operations, operation, source); |
| |
| // DragAndDropManager.create(domain, target, getLocation(event), event.operations, operation, source); |
| // |
| command = dragAndDropCommandInformation.createCommand(); |
| |
| if (command != null) |
| { |
| valid = command.canExecute(); |
| } |
| } |
| } |
| |
| // If this command can provide detailed drag and drop feedback... |
| // |
| //if (command instanceof DragAndDropCommand) |
| if (command != null) |
| { |
| // Use the feedback for the operation and mouse point from the command. |
| // |
| event.detail = command.getOperation(); |
| event.feedback = command.getFeedback(); |
| } |
| else if (valid) |
| { |
| // All we can know is to provide selection feedback. |
| // |
| event.feedback = DND.FEEDBACK_SELECT; |
| } |
| else |
| { |
| // There is no executable command, so we'd better nix the whole deal. |
| // |
| event.detail = DND.DROP_NONE; |
| event.feedback = DND.FEEDBACK_SELECT; |
| } |
| } |
| } |
| |
| |
| protected void setCurrentDataType(DropTargetEvent event) |
| { |
| ObjectTransfer objectTransfer = ObjectTransfer.getInstance(); |
| TransferData [] dataTypes = event.dataTypes; |
| for (int i = 0; i < dataTypes.length; ++i) |
| { |
| TransferData transferData = dataTypes[i]; |
| // If the local tansfer supports this datatype, switch to that data type |
| // |
| if (objectTransfer.isSupportedType(transferData)) |
| { |
| event.currentDataType = transferData; |
| } |
| } |
| } |
| |
| /** |
| * This attempts to extract the drag source from the event early, i.e., before the drop method. |
| * This implementation tries to use a {@link org.eclipse.wst.common.ui.dnd.ObjectTransfer}. |
| */ |
| protected Collection getDragSource(DropTargetEvent event) |
| { |
| // Check whether the current data type can be transfered locally. |
| // |
| ObjectTransfer objectTransfer = ObjectTransfer.getInstance(); |
| if (!objectTransfer.isSupportedType(event.currentDataType)) |
| { |
| // Iterate over the data types to see if there is a datatype that supports a local transfer. |
| // |
| setCurrentDataType(event); |
| return null; |
| } |
| else |
| { |
| // Transfer the data and extract it. |
| // |
| Object object = objectTransfer.nativeToJava(event.currentDataType); |
| if (object == null) |
| { |
| return null; |
| } |
| else |
| { |
| return extractDragSource(object); |
| } |
| } |
| } |
| |
| /** |
| * This extracts a collection of dragged source objects from the given object retrieved from the transfer agent. |
| * This default implementation converts a structured selection into a collection of elements. |
| */ |
| protected Collection extractDragSource(Object object) |
| { |
| // Transfer the data and convert the structured selection to a collection of objects. |
| // |
| if (object instanceof IStructuredSelection) |
| { |
| Collection result = new ArrayList(); |
| for (Iterator elements = ((IStructuredSelection)object).iterator(); elements.hasNext(); ) |
| { |
| result.add(elements.next()); |
| } |
| return result; |
| } |
| else |
| { |
| return Collections.EMPTY_LIST; |
| } |
| } |
| |
| /** |
| * This gets the amount of time, in milliseconds, to hover over an item before {@link #hover} is called. |
| */ |
| public int getHoverThreshold() |
| { |
| return hoverThreshold; |
| } |
| |
| /** |
| * This set the amount of time, in milliseconds, to hover over an item before {@link #hover} is called. |
| */ |
| public void setHoverThreshold(int hoverThreshold) |
| { |
| this.hoverThreshold = hoverThreshold; |
| } |
| |
| /** |
| * This is called when the cursor has hovered over the given target for longer than {@link #hoverThreshold}. |
| */ |
| protected void hover(Object target) |
| { |
| if (viewer instanceof AbstractTreeViewer) |
| { |
| ((AbstractTreeViewer)viewer).expandToLevel(target, 1); |
| } |
| } |
| |
| /** |
| * This returns whether a scroll was performed based on the given drag coordinates. |
| */ |
| protected boolean scrollIfNeeded(DropTargetEvent event) |
| { |
| // By default we'll not scroll |
| // |
| boolean result = false; |
| |
| // We only handle a tree item right now. |
| // |
| if (event.item instanceof TreeItem) |
| { |
| // Tree items have special data that will help. |
| // |
| TreeItem treeItem = (TreeItem)event.item; |
| |
| // We need need the point in the coordinates of the control and the control's bounds. |
| // |
| Tree tree = treeItem.getParent(); |
| Point point = tree.toControl(new Point(event.x, event.y)); |
| Rectangle bounds = tree.getClientArea(); |
| |
| // This is the distance in pixels from the top or bottom that will cause scrolling. |
| // |
| int scrollEpsilon = Math.min(treeItem.getBounds().height, bounds.height / 3); |
| |
| // This will be the item that should be scrolled into the view. |
| // |
| TreeItem scrollTreeItem = null; |
| |
| // If we should scroll up. |
| // |
| if (point.y < scrollEpsilon) |
| { |
| // Determine the parent to find the sibling. |
| // |
| TreeItem parent = treeItem.getParentItem(); |
| // Walk through the siblings. |
| // |
| TreeItem [] children = parent == null ? tree.getItems() : parent.getItems(); |
| for (int i = 0; i < children.length; ++i) |
| { |
| // Is this a match. |
| // |
| if (children[i] == treeItem) |
| { |
| // If there is a previous sibling... |
| // |
| if (i > 0) |
| { |
| scrollTreeItem = children[i - 1]; |
| |
| // Get the last deepest descendent of this previous sibling. |
| // |
| for (;;) |
| { |
| children = scrollTreeItem.getItems(); |
| if (children != null && children.length != 0 && scrollTreeItem.getExpanded()) |
| { |
| scrollTreeItem = children[children.length - 1]; |
| } |
| else |
| { |
| break; |
| } |
| } |
| } |
| else |
| { |
| // The parent must be the previous. |
| // |
| scrollTreeItem = parent; |
| } |
| |
| // We're done after the match. |
| // |
| break; |
| } |
| } |
| } |
| // If we should scroll down... |
| // |
| else if (bounds.height - point.y < scrollEpsilon) |
| { |
| // If this thing has visible children, then the first child must be next. |
| // |
| TreeItem [] children = treeItem.getItems(); |
| if (children != null && children.length != 0 && treeItem.getExpanded()) |
| { |
| scrollTreeItem = children[0]; |
| } |
| else |
| { |
| // We need the parent to determine siblings and will walk up the tree if we are the last sibling. |
| // |
| while (scrollTreeItem == null) |
| { |
| // If there's no parent, we're done. |
| // |
| TreeItem parent = treeItem.getParentItem(); |
| // Walk the children. |
| // |
| children = parent == null ? tree.getItems() : parent.getItems(); |
| for (int i = 0; i < children.length; ++i) |
| { |
| // When we find the child. |
| // |
| if (children[i] == treeItem) |
| { |
| // If the next index is a valid index... |
| // |
| if (++i < children.length) |
| { |
| // We've found the item. |
| // |
| scrollTreeItem = children[i]; |
| } |
| |
| // We're done with this parent. |
| // |
| break; |
| } |
| } |
| |
| if (parent == null) |
| { |
| break; |
| } |
| |
| // Walk up. |
| // |
| treeItem = parent; |
| } |
| } |
| } |
| |
| // If we should scroll. |
| // |
| if (scrollTreeItem != null) |
| { |
| // Only scroll if we're on an item for a while. |
| // |
| if (previousItem != null && event.time - hoverStart > 200) |
| { |
| ScrollBar verticalScrollBar = tree.getVerticalBar(); |
| if (verticalScrollBar != null) |
| { |
| int before = verticalScrollBar.getSelection(); |
| |
| // Make sure the item is scrolled in place. |
| // |
| tree.showItem(scrollTreeItem); |
| |
| // Make sure we don't scroll again quickly. |
| // |
| previousItem = null; |
| |
| // Indicate that we've done a scroll and nothing else should be done. |
| // |
| result = before != verticalScrollBar.getSelection(); |
| } |
| } |
| else |
| { |
| // If the item changes, reset the timer information. |
| // |
| if (event.item != previousItem) |
| { |
| previousItem = event.item; |
| hoverStart = event.time; |
| } |
| } |
| } |
| } |
| else if (event.item instanceof TableItem) |
| { |
| // Table items have special data that will help. |
| // |
| TableItem tableItem = (TableItem)event.item; |
| |
| // We need need the point in the coordinates of the control and the control's bounds. |
| // |
| Table table = tableItem.getParent(); |
| Point point = table.toControl(new Point(event.x, event.y)); |
| Rectangle bounds = table.getClientArea(); |
| if (table.getHeaderVisible()) |
| { |
| int offset = table.getItemHeight(); |
| bounds.y += offset; |
| bounds.height -= offset; |
| point.y -= offset; |
| } |
| |
| // The position of this item. |
| // |
| int index = table.indexOf(tableItem); |
| |
| // This is the distance in pixels from the top or bottom that will cause scrolling. |
| // |
| int scrollEpsilon = Math.min(tableItem.getBounds(0).height, bounds.height / 3); |
| |
| // This will be the item that should be scrolled into the view. |
| // |
| TableItem scrollTableItem = null; |
| |
| // If we should scroll up. |
| // |
| if (point.y < scrollEpsilon) |
| { |
| if (index > 0) |
| { |
| scrollTableItem = table.getItems()[index - 1]; |
| } |
| } |
| // If we should scroll down... |
| // |
| else if (bounds.height - point.y < scrollEpsilon) |
| { |
| if (index + 1 < table.getItems().length) |
| { |
| scrollTableItem = table.getItems()[index + 1]; |
| } |
| } |
| |
| // If we should scroll. |
| // |
| if (scrollTableItem != null) |
| { |
| // Only scroll if we're on an item for a while. |
| // |
| if (previousItem != null && event.time - hoverStart > 200) |
| { |
| ScrollBar verticalScrollBar = table.getVerticalBar(); |
| if (verticalScrollBar != null) |
| { |
| int before = verticalScrollBar.getSelection(); |
| |
| // Make sure the item is scrolled in place. |
| // |
| table.showItem(scrollTableItem); |
| |
| // Make sure we don't scroll again quickly. |
| // |
| previousItem = null; |
| |
| // Indicate that we've done a scroll and nothing else should be done. |
| // |
| result = before != verticalScrollBar.getSelection(); |
| } |
| } |
| else |
| { |
| // If the item changes, reset the timer information. |
| // |
| if (event.item != previousItem) |
| { |
| previousItem = event.item; |
| hoverStart = event.time; |
| } |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| protected static float getLocation(DropTargetEvent event) |
| { |
| if (event.item instanceof TreeItem) |
| { |
| TreeItem treeItem = (TreeItem)event.item; |
| Control control = treeItem.getParent(); |
| Point point = control.toControl(new Point(event.x, event.y)); |
| Rectangle bounds = treeItem.getBounds(); |
| return (float)(point.y - bounds.y) / (float)bounds.height; |
| } |
| else if (event.item instanceof TableItem) |
| { |
| TableItem tableItem = (TableItem)event.item; |
| Control control = tableItem.getParent(); |
| Point point = control.toControl(new Point(event.x, event.y)); |
| Rectangle bounds = tableItem.getBounds(0); |
| return (float)(point.y - bounds.y) / (float)bounds.height; |
| } |
| else |
| { |
| return 0.0F; |
| } |
| } |
| |
| protected class DragAndDropCommandInformation |
| { |
| // protected EditingDomain domain; |
| protected Object target; |
| protected float location; |
| protected int operations; |
| protected int operation; |
| protected Collection source; |
| public DragAndDropCommandInformation |
| (Object target, float location, int operations, int operation, Collection source) |
| { |
| this.target = target; |
| this.location = location; |
| this.operations = operations; |
| this.operation = operation; |
| this.source = new ArrayList(source); |
| } |
| |
| public DragAndDropCommand createCommand() |
| { |
| return dragAndDropManager.createCommand(target, location, operations, operation, source); |
| } |
| } |
| } |