blob: ec0a2e1ecc6cda7ede8b1dfe183a9cc8fc614a3e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2017 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.dltk.internal.ui.dnd;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.Viewer;
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.Item;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.TreeItem;
/**
* This adapter class provides generic drag-and-drop support for a viewer.
* <p>
* Subclasses must implement the following methods:
* <ul>
* <li><code>validateDrop</code> - identifies valid drop targets in viewer</li>
* <li><code>performDrop</code> - carries out a drop into a viewer</li>
* </ul>
* The <code>setFeedbackEnabled</code> method can be called to turn on and off
* visual insertion feedback (on by default).
* </p>
* <p>
* THIS IS (ALMOST) A ONE-TO-ONE COPY OF PLATFORMS ViewerDropAdapter REFERE
* THERE FIRST IF YOU NEED TO FIX SOMETHING.
* </p>
*/
public abstract class DLTKViewerDropAdapter extends DropTargetAdapter {
/**
* Constant describing the position of the cursor relative to the target
* object. This means the mouse is positioned slightly before the target.
*
* @see #getCurrentLocation()
*/
public static final int LOCATION_BEFORE = 1;
/**
* Constant describing the position of the cursor relative to the target
* object. This means the mouse is positioned slightly after the target.
*
* @see #getCurrentLocation()
*/
public static final int LOCATION_AFTER = 2;
/**
* Constant describing the position of the cursor relative to the target
* object. This means the mouse is positioned directly on the target.
*
* @see #getCurrentLocation()
*/
public static final int LOCATION_ON = 3;
/**
* Constant describing the position of the cursor relative to the target
* object. This means the mouse is not positioned over or near any valid
* target.
*
* @see #getCurrentLocation()
*/
public static final int LOCATION_NONE = 4;
/**
* The viewer to which this drop support has been added.
*/
private Viewer viewer;
/**
* The current operation.
*/
private int currentOperation = DND.DROP_NONE;
/**
* The last valid operation.
*/
private int lastValidOperation = DND.DROP_NONE;
/**
* The data item currently under the mouse.
*/
private Object currentTarget;
/**
* Information about the position of the mouse relative to the target
* (before, on, or after the target. Location is one of the
* <code>LOCATION_* </code> constants defined in this type.
*/
private int currentLocation;
/**
* A flag that allows adapter users to turn the insertion feedback on or
* off. Default is <code>true</code>.
*/
private boolean feedbackEnabled = true;
/**
* A flag that allows adapter users to turn auto scrolling on or off.
* Default is <code>true</code>.
*/
private boolean scrollEnabled = true;
/**
* A flag that allows adapter users to turn auto expanding on or off.
* Default is <code>true</code>.
*/
private boolean expandEnabled = true;
/**
* A flag that allows adapter users to turn selection feedback on or off.
* Default is <code>true</code>.
*/
private boolean selectFeedbackEnabled = true;
/**
* Creates a new drop adapter for the given viewer.
*
* @param viewer
* the viewer
*/
protected DLTKViewerDropAdapter(Viewer viewer) {
this.viewer = viewer;
}
/**
* Returns the position of the given event's coordinates relative to its
* target. The position is determined to be before, after, or on the item,
* based on some threshold value.
*
* @param event
* the event
* @return one of the <code>LOCATION_* </code>constants defined in this
* class
*/
protected int determineLocation(DropTargetEvent event) {
if (!(event.item instanceof Item)) {
return DLTKViewerDropAdapter.LOCATION_NONE;
}
Item item = (Item) event.item;
Point coordinates = new Point(event.x, event.y);
coordinates = viewer.getControl().toControl(coordinates);
if (item != null) {
Rectangle bounds = getBounds(item);
if (bounds == null) {
return DLTKViewerDropAdapter.LOCATION_NONE;
}
if ((coordinates.y - bounds.y) < 5) {
return DLTKViewerDropAdapter.LOCATION_BEFORE;
}
if ((bounds.y + bounds.height - coordinates.y) < 5) {
return DLTKViewerDropAdapter.LOCATION_AFTER;
}
}
return DLTKViewerDropAdapter.LOCATION_ON;
}
/**
* Returns the target item of the given drop event.
*
* @param event
* the event
* @return The target of the drop, may be <code>null</code>.
*/
protected Object determineTarget(DropTargetEvent event) {
return event.item == null ? null : event.item.getData();
}
/*
* (non-Javadoc) Method declared on DropTargetAdapter. The mouse has moved
* over the drop target. If the target item has changed, notify the action
* and check that it is still enabled.
*/
private void doDropValidation(DropTargetEvent event) {
currentOperation = determineOperation(currentTarget, lastValidOperation,
event.currentDataType, event.operations);
event.detail = currentOperation;
setFeedback(event, currentLocation);
}
/*
* (non-Javadoc) Method declared on DropTargetAdapter. The drag has entered
* this widget's region. See if the drop should be allowed.
*/
@Override
public void dragEnter(DropTargetEvent event) {
currentTarget = determineTarget(event);
currentLocation = determineLocation(event);
lastValidOperation = event.detail;
doDropValidation(event);
}
/*
* (non-Javadoc) Method declared on DropTargetAdapter. The drop operation
* has changed, see if the action should still be enabled.
*/
@Override
public void dragOperationChanged(DropTargetEvent event) {
currentTarget = determineTarget(event);
lastValidOperation = event.detail;
doDropValidation(event);
}
/*
* (non-Javadoc) Method declared on DropTargetAdapter. The mouse has moved
* over the drop target. If the target item has changed, notify the action
* and check that it is still enabled.
*/
@Override
public void dragOver(DropTargetEvent event) {
// use newly revealed item as target if scrolling occurs
Object target = determineTarget(event);
// set the location feedback
int oldLocation = currentLocation;
currentLocation = determineLocation(event);
setFeedback(event, currentLocation);
// see if anything has really changed before doing validation.
if (target != currentTarget || currentLocation != oldLocation) {
currentTarget = target;
doDropValidation(event);
}
}
/*
* (non-Javadoc) Method declared on DropTargetAdapter. The user has dropped
* something on the desktop viewer.
*/
@Override
public void drop(DropTargetEvent event) {
currentLocation = determineLocation(event);
// perform the drop behavior
if (!performDrop(event.data)) {
event.detail = DND.DROP_NONE;
}
currentOperation = event.detail;
}
/*
* (non-Javadoc) Method declared on DropTargetAdapter. Last chance for the
* action to disable itself
*/
@Override
public void dropAccept(DropTargetEvent event) {
event.detail = determineOperation(currentTarget, event.detail,
event.currentDataType, event.operations);
}
/**
* Returns the bounds of the given SWT tree or table item.
*
* @param item
* the SWT Item
* @return the bounds, or <code>null</code> if it is not a known type of
* item
*/
protected Rectangle getBounds(Item item) {
if (item instanceof TreeItem) {
return ((TreeItem) item).getBounds();
}
if (item instanceof TableItem) {
return ((TableItem) item).getBounds(0);
}
return null;
}
/**
* Returns a constant describing the position of the mouse relative to the
* target (before, on, or after the target.
*
* @return one of the <code>LOCATION_* </code> constants defined in this
* type
*/
protected int getCurrentLocation() {
return currentLocation;
}
/**
* Returns the current operation.
*
* @return a <code>DROP_*</code> constant from class <code>DND</code>
*
* @see DND#DROP_COPY
* @see DND#DROP_MOVE
* @see DND#DROP_LINK
* @see DND#DROP_NONE
*/
protected int getCurrentOperation() {
return currentOperation;
}
/**
* Returns the target object currently under the mouse.
*
* @return the current target object
*/
protected Object getCurrentTarget() {
return currentTarget;
}
/**
* Returns whether visible insertion feedback should be presented to the
* user.
* <p>
* Typical insertion feedback is the horizontal insertion bars that appear
* between adjacent items while dragging.
* </p>
*
* @return <code>true</code> if visual feedback is desired, and
* <code>false</code> if not
*/
public boolean getFeedbackEnabled() {
return feedbackEnabled;
}
/**
* Returns the object currently selected by the viewer.
*
* @return the selected object, or <code>null</code> if either no object or
* multiple objects are selected
*/
protected Object getSelectedObject() {
ISelection selection = viewer.getSelection();
if (selection instanceof IStructuredSelection && !selection.isEmpty()) {
IStructuredSelection structured = (IStructuredSelection) selection;
return structured.getFirstElement();
}
return null;
}
/**
* @return the viewer to which this drop support has been added.
*/
protected Viewer getViewer() {
return viewer;
}
/**
* Performs any work associated with the drop.
* <p>
* Subclasses must implement this method to provide drop behavior.
* </p>
*
* @param data
* the drop data
* @return <code>true</code> if the drop was successful, and
* <code>false</code> otherwise
*/
public abstract boolean performDrop(Object data);
/*
* (non-Javadoc) Method declared on DropTargetAdapter. The mouse has moved
* over the drop target. If the target item has changed, notify the action
* and check that it is still enabled.
*/
private void setFeedback(DropTargetEvent event, int location) {
if (feedbackEnabled) {
switch (location) {
case LOCATION_BEFORE:
event.feedback = DND.FEEDBACK_INSERT_BEFORE;
break;
case LOCATION_AFTER:
event.feedback = DND.FEEDBACK_INSERT_AFTER;
break;
case LOCATION_ON:
default:
event.feedback = DND.FEEDBACK_SELECT;
break;
}
}
// Explicitly inhibit SELECT feedback if desired
if (!selectFeedbackEnabled) {
event.feedback &= ~DND.FEEDBACK_SELECT;
}
if (expandEnabled) {
event.feedback |= DND.FEEDBACK_EXPAND;
}
if (scrollEnabled) {
event.feedback |= DND.FEEDBACK_SCROLL;
}
}
/**
* Sets whether visible insertion feedback should be presented to the user.
* <p>
* Typical insertion feedback is the horizontal insertion bars that appear
* between adjacent items while dragging.
* </p>
*
* @param value
* <code>true</code> if visual feedback is desired, and
* <code>false</code> if not
*/
public void setFeedbackEnabled(boolean value) {
feedbackEnabled = value;
}
/**
* Sets whether selection feedback should be provided during dragging.
*
* @param value
* <code>true</code> if selection feedback is desired, and
* <code>false</code> if not
*
* @since 3.2
*/
public void setSelectionFeedbackEnabled(boolean value) {
selectFeedbackEnabled = value;
}
/**
* Sets whether auto scrolling and expanding should be provided during
* dragging.
*
* @param value
* <code>true</code> if scrolling and expanding is desired, and
* <code>false</code> if not
* @since 2.0
*/
public void setScrollExpandEnabled(boolean value) {
expandEnabled = value;
scrollEnabled = value;
}
/**
* Sets whether auto expanding should be provided during dragging.
*
* @param value
* <code>true</code> if expanding is desired, and
* <code>false</code> if not
* @since 3.4
*/
public void setExpandEnabled(boolean value) {
expandEnabled = value;
}
/**
* Sets whether auto scrolling should be provided during dragging.
*
* @param value
* <code>true</code> if scrolling is desired, and
* <code>false</code> if not
* @since 3.4
*/
public void setScrollEnabled(boolean value) {
scrollEnabled = value;
}
/**
* Validates dropping on the given object. This method is called whenever
* some aspect of the drop operation changes.
* <p>
* Subclasses must implement this method to define which drops make sense.
* </p>
*
* @param target
* the object that the mouse is currently hovering over, or
* <code>null</code> if the mouse is hovering over empty space
* @param operation
* the current drag operation (copy, move, etc.)
* @param transferType
* the current transfer type
* @return <code>true</code> if the drop is valid, and <code>false</code>
* otherwise
*/
public abstract boolean validateDrop(Object target, int operation,
TransferData transferType);
/**
* Determine the operation which should be executed given the target and the
* operation requested by the user. This method is called whenever some
* aspect of the drop operation changes. The operation is one of
* DND#DROP_DEFAULT, DND#DROP_COPY, DND#DROP_MOVE, DND#DROP_LINK.
* <p>
* The method returns the operation valid in the given context. The result
* is one of DND#DROP_NONE, DND#DROP_COPY, DND#DROP_MOVE, DND#DROP_LINK,
* DND#DROP_DEFAULT.
* </p>
* <p>
* Subclasses can overwrite this method to define which operation does make
* sense in the given context.
* </p>
*
* @param target
* the object that the mouse is currently hovering over, or
* <code>null</code> if the mouse is hovering over empty space
* @param operation
* the current drag operation (copy, move, etc.)
* @param transferType
* the current transfer type
* @param operations
* a bitwise OR'ing of the operations that the DragSource can
* support
* @return the operation which will be executed if no modifier key is
* pressed by the user
*
* @see DND#DROP_NONE
* @see DND#DROP_MOVE
* @see DND#DROP_COPY
* @see DND#DROP_LINK
*/
protected int determineOperation(Object target, int operation,
TransferData transferType, int operations) {
if (!validateDrop(target, operation, transferType)) {
return DND.DROP_NONE;
}
return operation;
}
}