blob: f22759ea245e0145abbc6ba96ee6ab34715b027e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2010 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.gef.dnd;
import java.util.Collection;
import java.util.Collections;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.dnd.TransferData;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.AutoexposeHelper;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
/**
* An abstract implementation of TransferDropTargetListener for use with
* {@link EditPartViewer}s. The Viewer's <code>Control</code> should be the Drop
* target. In order to communicate with EditParts in a consistent way,
* DropTargetEvents are processed into {@link Request Requests}.
* <P>
* Dropping is inherently a targeting interaction. This class handles
* calculating the <i>target</i> EditPart. It also handles common targeting
* behavior, such as interacting with the target EditPart or its ancestors to
* achieve things like auto-scroll/auto-expose.
*/
public abstract class AbstractTransferDropTargetListener implements
TransferDropTargetListener {
private DropTargetEvent currentEvent;
private AutoexposeHelper exposeHelper;
private boolean hovering = false;
private boolean askForCommand;
private long hoverStartTime = -1;
private Point prevMouseLoc;
private Request request;
private boolean showingFeedback;
private EditPart target;
private Transfer transfer;
private EditPartViewer viewer;
/**
* Constructs a new AbstractTransferDropTargetListener and sets the
* EditPartViewer. If you use this constructor, you must set the Transfer
* yourself using {@link #setTransfer(Transfer)}.
*
* @param viewer
* the EditPartViewer
*/
public AbstractTransferDropTargetListener(EditPartViewer viewer) {
setViewer(viewer);
}
/**
* Constructs a new AbstractTransferDropTargetListener and sets the
* EditPartViewer and Transfer. The Viewer's Control should be the Drop
* target.
*
* @param viewer
* the EditPartViewer
* @param xfer
* the Transfer
*/
public AbstractTransferDropTargetListener(EditPartViewer viewer,
Transfer xfer) {
setViewer(viewer);
setTransfer(xfer);
}
private EditPart calculateTargetEditPart() {
EditPart ep = getViewer().findObjectAtExcluding(getDropLocation(),
getExclusionSet(), new EditPartViewer.Conditional() {
public boolean evaluate(EditPart editpart) {
return editpart.getTargetEditPart(getTargetRequest()) != null;
}
});
if (ep != null)
ep = ep.getTargetEditPart(getTargetRequest());
return ep;
}
/**
* Creates and returns the <code>Request</code> that will be sent to the
* targeted EditPart. Subclasses can override to create specialized
* requests.
*
* @return the <code>Request</code> to be used with the <i>target</i>
* EditPart
*/
protected Request createTargetRequest() {
return new Request();
}
/**
* Stores the information about the current DropTargetEvent. This method may
* not be called on the listener, because the listener may not be made
* active until after the mouse has entered the drop target.
*
* @see DropTargetListener#dragEnter(DropTargetEvent)
*/
public void dragEnter(DropTargetEvent event) {
resetHover();
setCurrentEvent(event);
}
/**
* Stores the information about the current DropTargetEvent and then calls
* <code>unload()</code>. Subclasses should override {@link #unload()} to
* perform actions for this event. For some reason, SWT also calls
* <code>dragLeave()</code> when the actual drop is performed, even though
* the mouse has not left the drop target.
*
* @see DropTargetListener#dragLeave(DropTargetEvent)
*/
public void dragLeave(DropTargetEvent event) {
setCurrentEvent(event);
unload();
}
/**
* Stores the information about the current DropTargetEvent and then calls
* <code>handleDragOperationChanged()</code>. Subclasses should override
* {@link #handleDragOperationChanged()} to perform actions for this event.
*
* @see DropTargetListener#dragOperationChanged(DropTargetEvent)
*/
public void dragOperationChanged(DropTargetEvent event) {
resetHover();
setCurrentEvent(event);
handleDragOperationChanged();
}
/**
* Stores the information about the current DropTargetEvent and then calls
* <code>handleDragOver()</code>. Subclasses should override
* {@link #handleDragOver()} to perform actions for this event.
*
* @see DropTargetListener#dragOver(org.eclipse.swt.dnd.DropTargetEvent)
*/
public void dragOver(DropTargetEvent event) {
setCurrentEvent(event);
handleDragOver();
if (testAndSet(event)) {
resetHover();
} else {
if (hovering)
return;
long currentTime = event.time;
if (hoverStartTime == -1) {
hoverStartTime = currentTime;
} else if (currentTime - hoverStartTime > 400) {
handleHover();
hovering = true;
}
}
}
/**
* Stores the information about the current DropTargetEvent and then calls
* {@link #handleDrop()}, followed by {@link #unload()}. Subclasses should
* override these methods to perform actions for this event.
*
* @see DropTargetListener#drop(DropTargetEvent)
*/
public void drop(DropTargetEvent event) {
setCurrentEvent(event);
eraseTargetFeedback();
handleDrop();
unload();
}
/**
* Stores the current <code>DropTargetEvent</code> and does nothing. By
* default, the drop is accepted.
*
* @see DropTargetListener#dropAccept(DropTargetEvent)
*/
public void dropAccept(DropTargetEvent event) {
setCurrentEvent(event);
}
/**
* Calls <code>eraseTargetFeedback(Request)</code> on the current
* <i>target</i>, using the target Request. Does nothing if there is no
* target, or if the target has not been requested to show target feedback.
*/
protected void eraseTargetFeedback() {
if (getTargetEditPart() != null && showingFeedback) {
showingFeedback = false;
getTargetEditPart().eraseTargetFeedback(getTargetRequest());
}
}
/**
* Returns the current command from the target EditPart.
*
* @return The current command from the target EditPart
*/
protected Command getCommand() {
return getTargetEditPart().getCommand(getTargetRequest());
}
/**
* Returns the current <code>DropTargetEvent</code>.
*
* @return the current event
*/
public DropTargetEvent getCurrentEvent() {
return currentEvent;
}
/**
* Returns the current mouse location, as a {@link Point}. The location is
* relative to the control's client area.
*
* @return the drop location
*/
protected Point getDropLocation() {
org.eclipse.swt.graphics.Point swt;
swt = new org.eclipse.swt.graphics.Point(getCurrentEvent().x,
getCurrentEvent().y);
DropTarget target = (DropTarget) getCurrentEvent().widget;
swt = target.getControl().toControl(swt);
return new Point(swt.x, swt.y);
}
/**
* Returns a Collection of {@link EditPart EditParts} that are to be
* excluded when searching for the target EditPart.
*
* @return A Collection of EditParts to be excluded
*/
protected Collection getExclusionSet() {
return Collections.EMPTY_LIST;
}
/**
* Returns the current <i>target</i> <code>EditPart</code>.
*
* @return the target EditPart
*/
protected EditPart getTargetEditPart() {
return target;
}
/**
* Returns the target <code>Request</code>. If the target Request is
* <code>null</code>, {@link #createTargetRequest()} is called and the newly
* created Request is returned.
*
* @return the target Request
*/
protected Request getTargetRequest() {
if (request == null)
request = createTargetRequest();
return request;
}
/**
* @see TransferDropTargetListener#getTransfer()
*/
public Transfer getTransfer() {
return transfer;
}
/**
* Returns the <code>EditPartViewer</code> that is the target of the drop.
*
* @return the EditPartViewer
*/
protected EditPartViewer getViewer() {
return viewer;
}
/**
* Called when the user changes the Drag operation. By default, target
* feedback is erased. The target Request and target EditPart are updated,
* and target feedback is re-displayed on the new target.
*/
protected void handleDragOperationChanged() {
// Erase any old feedback now, in case the request changes substantially
eraseTargetFeedback();
// Update request based on the new operation type
updateTargetRequest();
// Update the target based on the updated request
updateTargetEditPart();
}
/**
* Called whenever the User drags over the target. By default, the target
* Request and target EditPart are updated, feedback is shown, and
* auto-expose occurs.
*/
protected void handleDragOver() {
updateTargetRequest();
updateTargetEditPart();
showTargetFeedback();
if (exposeHelper != null) {
// If the expose helper does not wish to continue, set helper to
// null.
if (!exposeHelper.step(getDropLocation()))
exposeHelper = null;
}
}
/**
* Updates the target Request and target EditPart, and performs the drop. By
* default, the drop is performed by asking the target EditPart for a
* Command using the target Request. This Command is then executed on the
* CommandStack.
* <P>
* If there is no target EditPart or no executable Command, the event's
* <code>detail</code> field is set to <code>DND.DROP_NONE</code>.
*/
protected void handleDrop() {
updateTargetRequest();
updateTargetEditPart();
if (getTargetEditPart() != null) {
Command command = getCommand();
if (command != null && command.canExecute())
getViewer().getEditDomain().getCommandStack().execute(command);
else
getCurrentEvent().detail = DND.DROP_NONE;
} else
getCurrentEvent().detail = DND.DROP_NONE;
}
/**
* Called when a new target EditPart has been entered. By default, the new
* target is asked to show feedback.
*/
protected void handleEnteredEditPart() {
}
/**
* Called as the current target EditPart is being exited. By default, the
* target is asked to erase feedback.
*/
protected void handleExitingEditPart() {
eraseTargetFeedback();
}
/**
* Called when the mouse hovers during drag and drop.
*/
protected void handleHover() {
updateAutoexposeHelper();
}
/**
* Called when the mouse resumes motion after having hovered.
*/
protected void handleHoverStop() {
}
/**
* Returns <code>true</code> if this TransferDropTargetListener is enabled
* for the specified <code>DropTargetEvent</code>. By default, this is
* calculated by comparing the event's {@link DropTargetEvent#dataTypes
* dataTypes} with the <code>Transfer's</code> supported types (
* {@link Transfer#isSupportedType(TransferData)}). If a dataType is
* supported, an attempt is made to find a <i>target</i>
* <code>EditPart</code> at the current drop location. If a target
* <code>EditPart</code> is found, <code>true</code> is returned, and the
* DropTargetEvent's {@link DropTargetEvent#currentDataType} is set to the
* dataType that matched.
*
* @param event
* the DropTargetEvent
* @return <code>true</code> if this TransferDropTargetListener is enabled
* for the given DropTargetEvent
*/
public boolean isEnabled(DropTargetEvent event) {
for (int i = 0; i < event.dataTypes.length; i++) {
if (getTransfer().isSupportedType(event.dataTypes[i])) {
setCurrentEvent(event);
event.currentDataType = event.dataTypes[i];
updateTargetRequest();
EditPart oldTarget = target;
updateTargetEditPart();
boolean result;
if (target == null)
result = false;
else if (askForCommand) {
Command command = getCommand();
result = command != null && command.canExecute();
} else
result = true;
request = null;
target = oldTarget;
return result;
}
}
return false;
}
/**
* Returns <code>true</code> if {@link #isEnabled(DropTargetEvent)} is
* determined by asking the potential target for a Command.
*
* @return <code>true</code> if the target will be queried for a
* <code>Command</code>
* @since 3.1
*/
protected boolean isEnablementDeterminedByCommand() {
return askForCommand;
}
private void resetHover() {
if (hovering) {
handleHoverStop();
hovering = false;
hoverStartTime = -1;
prevMouseLoc = null;
}
}
/**
* Sets the current autoexpose helper.
*
* @param helper
* the autoexpose helper
*/
protected void setAutoexposeHelper(AutoexposeHelper helper) {
exposeHelper = helper;
}
/**
* Determines if the target editpart should be asked for a Command during
* {@link #isEnabled(DropTargetEvent)}. For most DND operations, the data is
* not available, thus asking for a command would not make sense. The
* default value is <code>false</code>.
*
* @param value
* <code>true</code> if a
* @since 3.1
*/
protected void setEnablementDeterminedByCommand(boolean value) {
askForCommand = value;
}
/**
* Sets the current DropTargetEvent.
*
* @param currentEvent
* the DropTargetEvent
*/
public void setCurrentEvent(DropTargetEvent currentEvent) {
this.currentEvent = currentEvent;
}
/**
* Sets the <i>target</i> <code>EditPart</code>. If the target is changing,
* {@link #handleExitingEditPart()} is called before the target changes, and
* {@link #handleEnteredEditPart()} is called afterwards.
*
* @param ep
* the new target EditPart
*/
protected void setTargetEditPart(EditPart ep) {
if (ep != target) {
if (target != null)
handleExitingEditPart();
target = ep;
if (target != null)
handleEnteredEditPart();
}
}
/**
* Sets the Tranfer type that this listener can handle.
*
* @param xfer
* the Transfer
*/
protected void setTransfer(Transfer xfer) {
transfer = xfer;
}
/**
* Sets the EditPartViewer.
*
* @param viewer
* the EditPartViewer
*/
protected void setViewer(EditPartViewer viewer) {
this.viewer = viewer;
}
/**
* Asks the target <code>EditPart</code> to show target feedback if it is
* not <code>null</code>.
*
* @see EditPart#showTargetFeedback(Request)
*/
protected void showTargetFeedback() {
if (getTargetEditPart() != null) {
showingFeedback = true;
getTargetEditPart().showTargetFeedback(getTargetRequest());
}
}
/**
* Tests whether the given event's location is different than the previous
* event's location, and sets the remembered location to the current event's
* location.
*
* @param event
* @return boolean
*/
private boolean testAndSet(DropTargetEvent event) {
boolean result = prevMouseLoc == null
|| !(prevMouseLoc.x == event.x && prevMouseLoc.y == event.y);
if (prevMouseLoc == null)
prevMouseLoc = new Point();
prevMouseLoc.x = event.x;
prevMouseLoc.y = event.y;
return result;
}
/**
* Erases target feedback and sets the request to <code>null</code>.
*/
protected void unload() {
resetHover();
eraseTargetFeedback();
request = null;
setTargetEditPart(null);
setCurrentEvent(null);
setAutoexposeHelper(null);
}
/**
* Updates the active {@link AutoexposeHelper}. Does nothing if there is
* still an active helper. Otherwise, obtains a new helper (possible
* <code>null</code>) at the current mouse location and calls
* {@link #setAutoexposeHelper(AutoexposeHelper)}.
*/
protected void updateAutoexposeHelper() {
if (exposeHelper != null)
return;
AutoexposeHelper.Search search;
search = new AutoexposeHelper.Search(getDropLocation());
getViewer().findObjectAtExcluding(getDropLocation(),
Collections.EMPTY_LIST, search);
setAutoexposeHelper(search.result);
}
/**
* Updates the target EditPart.
*/
protected void updateTargetEditPart() {
setTargetEditPart(calculateTargetEditPart());
}
/**
* Subclasses must implement this to update the target Request.
*/
protected abstract void updateTargetRequest();
}