org.eclipse.jface.viewers.ViewerDropAdapter Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2000, 2019 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.jface.viewers;
import org.eclipse.core.runtime.Assert;
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.
*
* Subclasses must implement the following methods:
*
*
* validateDrop
- identifies valid drop targets in viewer
* performDrop
- carries out a drop into a viewer
*
* The setFeedbackEnabled
method can be called to turn on and off
* visual insertion feedback (on by default).
*/
public abstract class ViewerDropAdapter 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. We need to remember the last good operation
* in the case where the current operation temporarily is not valid (drag over
* someplace you can't drop).
*/
private int lastValidOperation;
/**
* This is used because we allow the operation
* to be temporarily overridden (for example a move to a copy) for a drop that
* happens immediately after the operation is overridden.
*/
private int overrideOperation = -1;
/**
* The current DropTargetEvent, used only during validateDrop()
*/
private DropTargetEvent currentEvent;
/**
* 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 LOCATION_*
constants defined in this type.
*/
private int currentLocation;
/**
* A flag that allows adapter users to turn the insertion
* feedback on or off. Default is true
.
*/
private boolean feedbackEnabled = true;
/**
* A flag that allows adapter users to turn auto scrolling
* on or off. Default is true
.
*/
private boolean scrollEnabled = true;
/**
* A flag that allows adapter users to turn auto
* expanding on or off. Default is true
.
*/
private boolean expandEnabled = true;
/**
* A flag that allows adapter users to turn selection feedback
* on or off. Default is true
.
*/
private boolean selectFeedbackEnabled = true;
/**
* Creates a new drop adapter for the given viewer.
*
* @param viewer the viewer
*/
protected ViewerDropAdapter(Viewer viewer) {
this.viewer = viewer;
}
/**
* Clears internal state of this drop adapter. This method can be called
* when no DnD operation is underway, to clear internal state from previous
* drop operations.
*
* @since 3.5
*/
protected void clearState() {
this.currentTarget = null;
this.lastValidOperation = DND.DROP_NONE;
this.overrideOperation = -1;
}
/**
* 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 LOCATION_*
constants defined in this class
*/
protected int determineLocation(DropTargetEvent event) {
if (!(event.item instanceof Item)) {
return LOCATION_NONE;
}
Item item = (Item) event.item;
Point coordinates = new Point(event.x, event.y);
coordinates = viewer.getControl().toControl(coordinates);
Rectangle bounds = getBounds(item);
if (bounds == null) {
return LOCATION_NONE;
}
if ((coordinates.y - bounds.y) < getThreshold()) {
return LOCATION_BEFORE;
}
if ((bounds.y + bounds.height - coordinates.y) < getThreshold()) {
return LOCATION_AFTER;
}
return LOCATION_ON;
}
/**
* Get the threshold to determine whether to have before/after feedback or
* select feedback. Threshold shall not be bigger than itemHeight/2.
*
* @return amount of pixels from top/bottom of element that trigger before/after
* behavior.
* @since 3.16
*/
protected int getThreshold() {
// fixed default threshold provided up to v3.16
return 5;
}
/**
* Returns the target item of the given drop event.
*
* @param event the event
* @return The target of the drop, may be null
.
*/
protected Object determineTarget(DropTargetEvent event) {
return event.item == null ? null : event.item.getData();
}
private void doDropValidation(DropTargetEvent event) {
//always remember what was previously requested, but not if it
//was overridden
if (event.detail != DND.DROP_NONE && overrideOperation == -1)
lastValidOperation = event.detail;
currentOperation = lastValidOperation;
currentEvent = event;
overrideOperation = -1;
if (!validateDrop(currentTarget, currentOperation, event.currentDataType)) {
currentOperation = DND.DROP_NONE;
}
//give the right feedback for the override
if (overrideOperation != -1)
event.detail = overrideOperation;
else
event.detail = currentOperation;
currentEvent = null;
}
@Override
public void dragEnter(DropTargetEvent event) {
clearState(); // should this be removed and better be done in client code?
currentTarget = determineTarget(event);
doDropValidation(event);
}
@Override
public void dragOperationChanged(DropTargetEvent event) {
currentTarget = determineTarget(event);
doDropValidation(event);
}
@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);
}
}
@Override
public void drop(DropTargetEvent event) {
currentLocation = determineLocation(event);
currentEvent = event;
if (overrideOperation != -1)
currentOperation = overrideOperation;
//perform the drop behavior
if (!performDrop(event.data)) {
event.detail = DND.DROP_NONE;
}
//reset for next time
currentOperation = DND.DROP_NONE;
currentEvent = null;
}
@Override
public void dropAccept(DropTargetEvent event) {
currentEvent = event;
if (!validateDrop(currentTarget, event.detail, event.currentDataType)) {
currentOperation = event.detail = DND.DROP_NONE;
}
currentEvent = null;
}
/**
* Returns the bounds of the given SWT tree or table item.
*
* @param item the SWT Item
* @return the bounds, or null
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 LOCATION_*
constants defined in this type
*/
protected int getCurrentLocation() {
return currentLocation;
}
/**
* Returns the current operation.
*
* @return a DROP_*
constant from class DND
*
* @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 the current {@link DropTargetEvent}.
*
* This may be called only inside of the {@link #validateDrop(Object, int, TransferData)}
* or {@link #performDrop(Object)} methods.
* @return the DropTargetEvent
* @since 3.5
*/
protected DropTargetEvent getCurrentEvent() {
Assert.isTrue(currentEvent != null);
return currentEvent;
}
/**
* Returns whether visible insertion feedback should be presented to the user.
*
* Typical insertion feedback is the horizontal insertion bars that appear
* between adjacent items while dragging.
*
*
* @return true
if visual feedback is desired, and false
if not
*/
public boolean getFeedbackEnabled() {
return feedbackEnabled;
}
/**
* Returns the object currently selected by the viewer.
*
* @return the selected object or the first element in current selection,
* and null if no 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;
}
/**
* @deprecated this method should not be used. Exception handling has been
* removed from DropTargetAdapter methods overridden by this class.
* Handles any exception that occurs during callback, including
* rethrowing behavior.
*
* [Issue: Implementation prints stack trace and eats exception to avoid
* crashing VA/J.
* Consider conditionalizing the implementation to do one thing in VAJ
* and something more reasonable in other operating environments.
* ]
*
*
* @param exception the exception
* @param event the event
*/
@Deprecated
protected void handleException(Throwable exception, DropTargetEvent event) {
// Currently we never rethrow because VA/Java crashes if an SWT
// callback throws anything. Generally catching Throwable is bad, but in
// this cases it's better than hanging the image.
exception.printStackTrace();
event.detail = DND.DROP_NONE;
}
/**
* Performs any work associated with the drop.
*
* Subclasses must implement this method to provide drop behavior.
*
*
* @param data the drop data
* @return true
if the drop was successful, and
* false
otherwise
*/
public abstract boolean performDrop(Object data);
/**
* Overrides the current operation for a drop that happens immediately
* after the current validateDrop.
*
* This maybe called only from within a
* {@link #validateDrop(Object, int, TransferData)} method
*
*
* @param operation
* the operation to be used for the drop.
*
* @see DND#DROP_COPY
* @see DND#DROP_MOVE
* @see DND#DROP_LINK
* @see DND#DROP_NONE
*
* @since 3.5
*/
protected void overrideOperation(int operation) {
overrideOperation = operation;
}
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.
*
* Typical insertion feedback is the horizontal insertion bars that appear
* between adjacent items while dragging.
*
*
* @param value
* true
if visual feedback is desired, and
* false
if not
*/
public void setFeedbackEnabled(boolean value) {
feedbackEnabled = value;
}
/**
* Sets whether selection feedback should be provided during dragging.
*
* @param value true
if selection feedback is desired, and
* false
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 true
if scrolling and expanding is desired, and
* false
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 true
if expanding is desired, and
* false
if not
* @since 3.4
*/
public void setExpandEnabled(boolean value) {
expandEnabled = value;
}
/**
* Sets whether auto scrolling should be provided during dragging.
*
* @param value true
if scrolling is desired, and
* false
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.
*
* Subclasses must implement this method to define which drops make sense.
*
*
* @param target the object that the mouse is currently hovering over, or
* null
if the mouse is hovering over empty space
* @param operation the current drag operation (copy, move, etc.)
* @param transferType the current transfer type
* @return true
if the drop is valid, and false
* otherwise
*/
public abstract boolean validateDrop(Object target, int operation,
TransferData transferType);
}