org.eclipse.emf.edit.command.DragAndDropCommand Maven / Gradle / Ivy
/**
* Copyright (c) 2002-2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* IBM - Initial API and implementation
*/
package org.eclipse.emf.edit.command;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.emf.common.command.AbstractCommand;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandWrapper;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.command.IdentityCommand;
import org.eclipse.emf.common.command.UnexecutableCommand;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.EMFEditPlugin;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
/**
* The drag and drop command logically acts upon an owner object onto which a collection of things is being dragged.
* The static create method delegates command creation to {@link EditingDomain#createCommand EditingDomain.createCommand},
* which may or may not result in the actual creation of an instance of this class.
*
* The implementation of this class is high-level and generic;
* it ultimately delegates all behaviour to other types of command,
* and is typically undoable as a result.
*/
public class DragAndDropCommand extends AbstractCommand implements DragAndDropFeedback
{
/**
* This class is used to encode the drag and drop arguments
* into an object that will be passed as the feature of a {@link CommandParameter}.
*/
public static class Detail
{
public float location;
public int operations;
public int operation;
public Detail(float location, int operations, int operation)
{
this.location = location;
this.operations = operations;
this.operation = operation;
}
}
/**
* This creates a command to perform a drag and drop operation upon the owner.
* See {@link DragAndDropCommand DragAndDropCommand} for a description of the arguments.
*/
public static Command create(EditingDomain domain, Object owner, float location, int operations, int operation, Collection> collection)
{
return
domain.createCommand
(DragAndDropCommand.class, new CommandParameter(owner, new Detail(location, operations, operation), collection));
}
/**
* This caches the label.
*/
protected static final String LABEL = EMFEditPlugin.INSTANCE.getString("_UI_DragAndDropCommand_label");
/**
* This caches the description.
*/
protected static final String DESCRIPTION = EMFEditPlugin.INSTANCE.getString("_UI_DragAndDropCommand_description");
/**
* This keeps track of the domain in which this command is created.
*/
protected EditingDomain domain;
/**
* This keeps track of the owner that is the target of the drag and drop.
*/
protected Object owner;
/**
* This keeps track of the location of the drag and drop.
*/
protected float location;
/**
* This keeps track of the lower range of locations in which the effect of this command remains unchanged.
*/
protected float lowerLocationBound;
/**
* This keeps track of the upper range of locations in which the effect of this command remains unchanged.
*/
protected float upperLocationBound;
/**
* This keeps track of the permitted operations.
*/
protected int operations;
/**
* This keeps track of the current operation that will be returned by {@link #getOperation}.
*/
protected int operation;
/**
* This keeps track of the feedback that will be returned by {@link #getFeedback}.
*/
protected int feedback;
/**
* This keeps track of the collection of dragged sources.
*/
protected Collection> collection;
/**
* This keeps track of the command that implements the drag side of the operation.
*/
protected Command dragCommand;
/**
* This keeps track of whether execute has been called on the {@link #dragCommand}.
*/
protected boolean isDragCommandExecuted;
/**
* This keeps track of the command that implements the drop side of the operation.
*/
protected Command dropCommand;
/**
* This controls whether or not to optimize the {@link #prepare}.
*/
protected boolean optimize;
/**
* This is to remember which owner to use for the drop command in the optimized mode.
*/
protected Object optimizedDropCommandOwner;
/**
* This creates and instance in the given domain and for the given information.
* The location should be in the range of 0.0 to 1.0,
* indicating the relative vertical location of the drag operation,
* where 0.0 is at the top and 1.0 is at the bottom.
* The operations is a bitwise mask of the DROP_* values.
* The operation is the desired operation as specified by a DROP_* value.
* And the collection contains the source objects being dragged.
*/
public DragAndDropCommand(EditingDomain domain, Object owner, float location, int operations, int operation, Collection> collection)
{
this(domain, owner, location, operations, operation, collection, domain == null ? false : domain.getOptimizeCopy());
}
public DragAndDropCommand
(EditingDomain domain, Object owner, float location, int operations, int operation, Collection> collection, boolean optimize)
{
super(LABEL, DESCRIPTION);
this.domain = domain;
this.owner = owner;
this.location = location;
this.operations = operations;
this.operation = operation;
this.collection = collection;
this.optimize = optimize;
// EATM Leave this disabled for now.
//
this.optimize = false;
}
protected boolean analyzeForNonContainment(Command command)
{
if (command instanceof AddCommand)
{
return isNonContainment(((AddCommand)command).getFeature());
}
else if (command instanceof SetCommand)
{
return isNonContainment(((SetCommand)command).getFeature());
}
else if (command instanceof CommandWrapper)
{
return analyzeForNonContainment(((CommandWrapper)command).getCommand());
}
else if (command instanceof CompoundCommand)
{
for (Command childCommand : ((CompoundCommand)command).getCommandList())
{
if (analyzeForNonContainment(childCommand))
{
return true;
}
}
}
return false;
}
protected boolean isNonContainment(EStructuralFeature feature)
{
return feature instanceof EReference && !((EReference)feature).isContainment();
}
public Object getOwner()
{
return owner;
}
public float getLocation()
{
return location;
}
public int getOperations()
{
return operations;
}
public Collection> getCollection()
{
return collection;
}
/**
* This implementation of prepare is called again to implement {@link #validate validate}.
* The method {@link #reset} will have been called before doing so.
*/
@Override
protected boolean prepare()
{
// We'll default to this.
//
boolean result = false;
// If there isn't something obviously wrong with the arguments...
//
if (owner != null && collection != null && operations != DROP_NONE && operation != DROP_NONE)
{
// If the location is near the boundary, we'll start by trying to do a drop insert.
//
if (location <= 0.20 || location >= 0.80)
{
// If we could do a drop insert operation...
//
result = prepareDropInsert();
if (result)
{
// Set the bounds so that we re-check when we are closer to the middle.
//
if (location <= 0.20)
{
lowerLocationBound = 0.0F;
upperLocationBound = 0.2F;
}
else
{
lowerLocationBound = 0.8F;
upperLocationBound = 1.0F;
}
}
else
{
// We can try to do a drop on instead.
//
reset();
result = prepareDropOn();
// Set the bounds so that we re-check when we get near the other end.
//
if (location <= 0.20)
{
lowerLocationBound = 0.0F;
upperLocationBound = 0.8F;
}
else
{
lowerLocationBound = 0.2F;
upperLocationBound = 1.0F;
}
}
}
// We are near the middle, so we'll start by trying to do a drop on.
//
else
{
// If we can do a drop on operation.
//
result = prepareDropOn();
if (result)
{
// Set the range so that we re-check when we get aren't in the middle.
//
lowerLocationBound = 0.2F;
upperLocationBound = 0.8F;
}
else
{
// We can reset and try a drop insert instead.
//
reset();
result = prepareDropInsert();
// Set the range so that we re-check when we get into the other half.
//
if (location <= 0.50)
{
lowerLocationBound = 0.0F;
upperLocationBound = 0.5F;
}
else
{
lowerLocationBound = 0.5F;
upperLocationBound = 1.0F;
}
}
}
}
else
{
// We'll always be wrong for these arguments, so don't bother re-checking.
//
lowerLocationBound = 0.0F;
upperLocationBound = 1.0F;
}
return result;
}
/**
* This can be overridden to determine the parent of an object; this implementation uses {@link EditingDomain#getParent}.
*/
protected Object getParent(Object object)
{
return domain.getParent(object);
}
/**
* This can be overridden to determine the children of an object; this implementation uses {@link EditingDomain#getChildren}.
*/
protected Collection> getChildren(Object object)
{
return domain.getChildren(object);
}
/**
* This attempts to prepare a drop insert operation.
*/
protected boolean prepareDropInsert()
{
// This will be the default return value.
//
boolean result = false;
// The feedback is set based on which half we are in.
// If the command isn't executable, these values won't be used.
//
feedback = location < 0.5 ? FEEDBACK_INSERT_BEFORE : FEEDBACK_INSERT_AFTER;
// If we can't determine the parent.
//
Object parent = getParent(owner);
if (parent == null)
{
dragCommand = UnexecutableCommand.INSTANCE;
dropCommand = UnexecutableCommand.INSTANCE;
}
else
{
// Iterate over the children to find the owner.
//
Collection> children = getChildren(parent);
int i = 0;
for (Object child : children)
{
// When we match the owner, we're done.
//
if (child == owner)
{
break;
}
++i;
}
// If the location indicates after, add one more.
//
if (location >= 0.5)
{
++i;
}
// Try to create a specific command based on the current desired operation.
//
switch (operation)
{
case DROP_MOVE:
{
result = prepareDropMoveInsert(parent, children, i);
break;
}
case DROP_COPY:
{
result = prepareDropCopyInsert(parent, children, i);
break;
}
case DROP_LINK:
{
result = prepareDropLinkInsert(parent, children, i);
break;
}
}
// If there isn't an executable command we should maybe try a copy operation, but only if we're allowed and not doing a link.
//
if (!result && operation != DROP_COPY && operation != DROP_LINK && (operations & DROP_COPY) != 0)
{
// Try again.
//
reset();
result = prepareDropCopyInsert(parent, children, i);
if (result)
{
// We've switch the operation!
//
operation = DROP_COPY;
}
}
// If there isn't an executable command we should maybe try a link operation, but only if we're allowed and not doing a link.
//
if (!result && operation != DROP_LINK && (operations & DROP_LINK) != 0)
{
// Try again.
//
reset();
result = prepareDropLinkInsert(parent, children, i);
if (result)
{
// We've switch the operation!
//
operation = DROP_LINK;
}
}
}
return result;
}
/**
* This attempts to prepare a drop move insert operation.
*/
protected boolean prepareDropMoveInsert(Object parent, Collection> children, int index)
{
// We don't want to move insert an object before or after itself...
//
if (collection.contains(owner))
{
dragCommand = IdentityCommand.INSTANCE;
dropCommand = UnexecutableCommand.INSTANCE;
}
// If the dragged objects share a parent...
//
else if (children.containsAll(collection))
{
dragCommand = IdentityCommand.INSTANCE;
// Create move commands for all the objects in the collection.
//
CompoundCommand compoundCommand = new CompoundCommand();
List