/*******************************************************************************
* Copyright (c) 2006-2013 The RCP Company 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:
* The RCP Company - initial API and implementation
*******************************************************************************/
package com.rcpcompany.uibindings.internal.utils.dnd;
import java.util.Collection;
import java.util.Collections;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.edit.command.DragAndDropCommand;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
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 com.rcpcompany.uibindings.IContainerBinding;
import com.rcpcompany.uibindings.IContainerBinding.IContainerDropContext;
/**
* Drop adapter for use with {@link IContainerBinding}.
* <p>
* This is an adaption of {@link org.eclipse.emf.edit.ui.dnd.EditingDomainViewerDropAdapter}.
*
* @author Tonny Madsen, The RCP Company
*/
public class ContainerBindingDropAdapter extends DropTargetAdapter {
/**
* This indicates whether the current platform is motif, which needs special treatment, since it
* cannot do early data transfer, but doesn't cleanly return null either.
*/
protected final static boolean IS_MOTIF = "motif".equals(SWT.getPlatform());
/**
* This is the viewer for which this is a drop target listener.
*/
/**
* This is the collection of source objects being dragged.
*/
protected Collection<EObject> mySourceObjects;
/**
* This is the command created during dragging which provides the feedback and will carry out
* the action upon completion.
*/
protected ContainerDragAndDropCommand myDragAndDropCommand;
/**
* This records the object for which the {@link #myDragAndDropCommand} was created.
*/
protected Object myCommandTarget;
/**
* This keeps track of the original operation that the user requested, before we started
* changing the event.detail. We always try to create the command using this.
*/
protected int myDragEnterOperation;
/**
* This keeps track of the information used to create {@link #myDragAndDropCommand}, but does
* not need to be disposed. This allows us to dispose of the command in dragLeave, and then, if
* we need to execute it, recreate it in drop.
*
* TODO Factor this out!
*/
protected DragAndDropCommandInformation myDragAndDropCommandInformation;
/**
* The container of the adapter
*/
protected final IContainerBinding myContainer;
/**
* This creates an instance with the given container.
*
* @param binding the container
*/
public ContainerBindingDropAdapter(IContainerBinding binding) {
myContainer = binding;
}
/**
* This is called when the mouse first enters or starts dragging in the viewer.
*/
@Override
public void dragEnter(DropTargetEvent event) {
// Remember the requested operation.
myDragEnterOperation = event.detail;
helper(event);
}
@Override
public void dragLeave(DropTargetEvent event) {
if (myDragAndDropCommand != null) {
myDragAndDropCommand.dispose();
myDragAndDropCommand = null;
}
myCommandTarget = null;
mySourceObjects = null;
}
@Override
public void dragOperationChanged(DropTargetEvent event) {
// Remember the requested operation.
myDragEnterOperation = event.detail;
helper(event);
}
@Override
public void dragOver(DropTargetEvent event) {
helper(event);
}
@Override
public void dropAccept(DropTargetEvent event) {
helper(event);
}
@Override
public void drop(DropTargetEvent event) {
// A command was created if the source was available early, and the
// information used to create it was cached...
//
if (myDragAndDropCommandInformation == null) {
// Otherwise, the source should be available now as event.data, and we
// can create the command.
//
mySourceObjects = extractDragSourceObjects(event.data);
final IContainerDropContext dropContext = myContainer.getDropContext(event);
if (dropContext == null) // event.detail = DND.DROP_NONE;
return;
myDragAndDropCommandInformation = new DragAndDropCommandInformation(dropContext, event.operations,
myDragEnterOperation);
}
// Recreate the command.
//
myDragAndDropCommand = myDragAndDropCommandInformation.createCommand();
// If the command can execute...
//
if (myDragAndDropCommand != null && myDragAndDropCommand.canExecute()) {
// Execute it.
//
myContainer.getEditingDomain().getCommandStack().execute(myDragAndDropCommand);
} else {
// Otherwise, let's call the whole thing off.
//
event.detail = DND.DROP_NONE;
myDragAndDropCommand.dispose();
}
// Clean up the state.
//
myDragAndDropCommand = null;
myCommandTarget = null;
mySourceObjects = null;
myDragAndDropCommandInformation = null;
}
/**
* This method is called the same way for each of the
* {@link org.eclipse.swt.dnd.DropTargetListener} methods, except for leave and drop. If the
* source is available early, it creates or revalidates the {@link DragAndDropCommand}, and
* updates the event's detail (operation) and feedback (drag under effect), appropriately.
*/
protected void helper(DropTargetEvent event) {
// If we can't do anything else, we'll provide the default select feedback
// and enable auto-scroll and auto-expand effects.
event.feedback = DND.FEEDBACK_SELECT | getAutoFeedback();
// If we don't already have it, try to get the source early. We can't give
// feedback if it's not available yet (this is platform-dependent).
//
if (mySourceObjects == null) {
mySourceObjects = getDragSource(event);
if (mySourceObjects == null) {
// Clear out any old information from a previous drag.
//
myDragAndDropCommandInformation = null;
return;
}
}
// Get the target object from the item widget and the mouse location in it.
//
final IContainerDropContext dropContext = myContainer.getDropContext(event);
if (dropContext == null) {
event.detail = DND.DROP_NONE;
return;
}
final EObject target = dropContext.getDropTargetObject();
//
//
if (myDragAndDropCommand == null) {
/*
* If we don't have a previous cached command...
*/
// We'll need to keep track of the information we use to create the
// command, so that we can recreate it in drop.
myDragAndDropCommandInformation = new DragAndDropCommandInformation(dropContext, event.operations,
myDragEnterOperation);
// Remember the target; create the command and test if it is executable.
//
myCommandTarget = target;
myDragAndDropCommand = myDragAndDropCommandInformation.createCommand();
} else if (target == myCommandTarget) {
/*
* The target has not changed...
*
* re-validate the command.
*/
myDragAndDropCommand.revalidate(dropContext, event.operations, myDragEnterOperation);
// Keep track of any changes to the command information.
myDragAndDropCommandInformation = new DragAndDropCommandInformation(dropContext, event.operations,
myDragEnterOperation);
} else {
// If not, dispose the current command and create a new one.
//
myDragAndDropCommand.dispose();
myDragAndDropCommandInformation = new DragAndDropCommandInformation(dropContext, event.operations,
myDragEnterOperation);
myCommandTarget = target;
myDragAndDropCommand = myDragAndDropCommandInformation.createCommand();
}
if (!myDragAndDropCommand.canExecute()) {
event.detail = DND.DROP_NONE;
return;
}
event.detail = myDragAndDropCommand.getOperation();
event.feedback = myDragAndDropCommand.getFeedback() | getAutoFeedback();
}
/**
* This returns the bitwise OR'ed flags for desired auto-feedback effects. Drag under effect DND
* constants are always OR'ed with this to enable them. This implementation enables
* {@link DND#FEEDBACK_SCROLL auto-scroll} and {@link DND#FEEDBACK_EXPAND auto-expand} (hover).
*/
protected int getAutoFeedback() {
return DND.FEEDBACK_SCROLL | DND.FEEDBACK_EXPAND;
}
/**
* This attempts to extract the drag source from the event early, i.e., before the drop method.
*/
protected Collection<EObject> getDragSource(DropTargetEvent event) {
// Check whether the current data type can be transfered locally.
//
final BindingTransfer localTransfer = BindingTransfer.getInstance();
if (!localTransfer.isSupportedType(event.currentDataType)) {
// Iterate over the data types to see if there is a data type that supports a local
// transfer.
//
final TransferData[] dataTypes = event.dataTypes;
for (int i = 0; i < dataTypes.length; ++i) {
final TransferData transferData = dataTypes[i];
// If the local transfer supports this data type, switch to that data type
//
if (localTransfer.isSupportedType(transferData)) {
event.currentDataType = transferData;
}
}
}
// Motif kludge: we would get something random instead of null.
//
if (IS_MOTIF) return null;
// Transfer the data and, if non-null, extract it.
//
final Object object = localTransfer.nativeToJava(event.currentDataType);
return object == null ? null : extractDragSourceObjects(object);
}
/**
* Extracts a collection of dragged source objects from the given object retrieved from the
* transfer agent.
*
* @param object the object from the transfer type
* @return the collection of relevant objects
*/
protected Collection<EObject> extractDragSourceObjects(Object object) {
Collection<?> list = null;
if (object instanceof Collection) {
list = (Collection<?>) object;
}
if (object instanceof IStructuredSelection) {
list = ((IStructuredSelection) object).toList();
}
if (list == null) return Collections.EMPTY_LIST;
/*
* Check the elements in the list
*/
return (Collection<EObject>) list;
}
/**
* This holds all of the information used to create a {@link DragAndDropCommand}, but does not
* need to be disposed.
*/
protected class DragAndDropCommandInformation {
private final int operations;
private final int operation;
private final IContainerDropContext context;
public DragAndDropCommandInformation(IContainerDropContext context, int operations, int operation) {
this.context = context;
this.operations = operations;
this.operation = operation;
}
public ContainerDragAndDropCommand createCommand() {
return new ContainerDragAndDropCommand(myContainer, context, operations, operation, mySourceObjects);
}
}
}