blob: 7cf3a647aab91114d6a4ec85979dfd7bf35ace69 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.ide.common.api.DropFeedback;
import com.android.ide.common.api.INode;
import com.android.ide.common.api.InsertType;
import com.android.ide.common.api.Point;
import com.android.ide.common.api.Rect;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode.NodeCreationListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.TransferData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.widgets.Display;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* The Move gesture provides the operation for moving widgets around in the canvas.
*/
public class MoveGesture extends DropGesture {
/** The associated {@link LayoutCanvas}. */
private LayoutCanvas mCanvas;
/** Overlay which paints the drag & drop feedback. */
private MoveOverlay mOverlay;
private static final boolean DEBUG = false;
/**
* The top view right under the drag'n'drop cursor.
* This can only be null during a drag'n'drop when there is no view under the cursor
* or after the state was all cleared.
*/
private CanvasViewInfo mCurrentView;
/**
* The elements currently being dragged. This will always be non-null for a valid
* drag'n'drop that happens within the same instance of Eclipse.
* <p/>
* In the event that the drag and drop happens between different instances of Eclipse
* this will remain null.
*/
private SimpleElement[] mCurrentDragElements;
/**
* The first view under the cursor that responded to onDropEnter is called the "target view".
* It can differ from mCurrentView, typically because a terminal View doesn't
* accept drag'n'drop so its parent layout became the target drag'n'drop receiver.
* <p/>
* The target node is the proxy node associated with the target view.
* This can be null if no view under the cursor accepted the drag'n'drop or if the node
* factory couldn't create a proxy for it.
*/
private NodeProxy mTargetNode;
/**
* The latest drop feedback returned by IViewRule.onDropEnter/Move.
*/
private DropFeedback mFeedback;
/**
* {@link #dragLeave(DropTargetEvent)} is unfortunately called right before data is
* about to be dropped (between the last {@link #dragOver(DropTargetEvent)} and the
* next {@link #dropAccept(DropTargetEvent)}). That means we can't just
* trash the current DropFeedback from the current view rule in dragLeave().
* Instead we preserve it in mLeaveTargetNode and mLeaveFeedback in case a dropAccept
* happens next.
*/
private NodeProxy mLeaveTargetNode;
/**
* @see #mLeaveTargetNode
*/
private DropFeedback mLeaveFeedback;
/**
* @see #mLeaveTargetNode
*/
private CanvasViewInfo mLeaveView;
/** Singleton used to keep track of drag selection in the same Eclipse instance. */
private final GlobalCanvasDragInfo mGlobalDragInfo;
/**
* Constructs a new {@link MoveGesture}, tied to the given canvas.
*
* @param canvas The canvas to associate the {@link MoveGesture} with.
*/
public MoveGesture(LayoutCanvas canvas) {
mCanvas = canvas;
mGlobalDragInfo = GlobalCanvasDragInfo.getInstance();
}
@Override
public List<Overlay> createOverlays() {
mOverlay = new MoveOverlay();
return Collections.<Overlay> singletonList(mOverlay);
}
@Override
public void begin(ControlPoint pos, int startMask) {
super.begin(pos, startMask);
// Hide selection overlays during a move drag
mCanvas.getSelectionOverlay().setHidden(true);
}
@Override
public void end(ControlPoint pos, boolean canceled) {
super.end(pos, canceled);
mCanvas.getSelectionOverlay().setHidden(false);
// Ensure that the outline is back to showing the current selection, since during
// a drag gesture we temporarily set it to show the current target node instead.
mCanvas.getSelectionManager().syncOutlineSelection();
}
/* TODO: Pass modifier mask to drag rules as well! This doesn't work yet since
the drag &amp; drop code seems to steal keyboard events.
@Override
public boolean keyPressed(KeyEvent event) {
update(mCanvas.getGestureManager().getCurrentControlPoint());
mCanvas.redraw();
return true;
}
@Override
public boolean keyReleased(KeyEvent event) {
update(mCanvas.getGestureManager().getCurrentControlPoint());
mCanvas.redraw();
return true;
}
*/
/*
* The cursor has entered the drop target boundaries.
* {@inheritDoc}
*/
@Override
public void dragEnter(DropTargetEvent event) {
if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drag enter", event);
// Make sure we don't have any residual data from an earlier operation.
clearDropInfo();
mLeaveTargetNode = null;
mLeaveFeedback = null;
mLeaveView = null;
// Get the dragged elements.
//
// The current transfered type can be extracted from the event.
// As described in dragOver(), this works basically works on Windows but
// not on Linux or Mac, in which case we can't get the type until we
// receive dropAccept/drop().
// For consistency we try to use the GlobalCanvasDragInfo instance first,
// and if it fails we use the event transfer type as a backup (but as said
// before it will most likely work only on Windows.)
// In any case this can be null even for a valid transfer.
mCurrentDragElements = mGlobalDragInfo.getCurrentElements();
if (mCurrentDragElements == null) {
SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance();
if (sxt.isSupportedType(event.currentDataType)) {
mCurrentDragElements = (SimpleElement[]) sxt.nativeToJava(event.currentDataType);
}
}
// if there is no data to transfer, invalidate the drag'n'drop.
// The assumption is that the transfer should have at least one element with a
// a non-null non-empty FQCN. Everything else is optional.
if (mCurrentDragElements == null ||
mCurrentDragElements.length == 0 ||
mCurrentDragElements[0] == null ||
mCurrentDragElements[0].getFqcn() == null ||
mCurrentDragElements[0].getFqcn().length() == 0) {
event.detail = DND.DROP_NONE;
}
dragOperationChanged(event);
}
/*
* The operation being performed has changed (e.g. modifier key).
* {@inheritDoc}
*/
@Override
public void dragOperationChanged(DropTargetEvent event) {
if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drag changed", event);
checkDataType(event);
recomputeDragType(event);
}
private void recomputeDragType(DropTargetEvent event) {
if (event.detail == DND.DROP_DEFAULT) {
// Default means we can now choose the default operation, either copy or move.
// If the drag comes from the same canvas we default to move, otherwise we
// default to copy.
if (mGlobalDragInfo.getSourceCanvas() == mCanvas &&
(event.operations & DND.DROP_MOVE) != 0) {
event.detail = DND.DROP_MOVE;
} else if ((event.operations & DND.DROP_COPY) != 0) {
event.detail = DND.DROP_COPY;
}
}
// We don't support other types than copy and move
if (event.detail != DND.DROP_COPY && event.detail != DND.DROP_MOVE) {
event.detail = DND.DROP_NONE;
}
}
/*
* The cursor has left the drop target boundaries OR data is about to be dropped.
* {@inheritDoc}
*/
@Override
public void dragLeave(DropTargetEvent event) {
if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drag leave");
// dragLeave is unfortunately called right before data is about to be dropped
// (between the last dropMove and the next dropAccept). That means we can't just
// trash the current DropFeedback from the current view rule, we need to preserve
// it in case a dropAccept happens next.
// See the corresponding kludge in dropAccept().
mLeaveTargetNode = mTargetNode;
mLeaveFeedback = mFeedback;
mLeaveView = mCurrentView;
clearDropInfo();
}
/*
* The cursor is moving over the drop target.
* {@inheritDoc}
*/
@Override
public void dragOver(DropTargetEvent event) {
processDropEvent(event);
}
/*
* The drop is about to be performed.
* The drop target is given a last chance to change the nature of the drop.
* {@inheritDoc}
*/
@Override
public void dropAccept(DropTargetEvent event) {
if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drop accept");
checkDataType(event);
// If we have a valid target node and it matches the one we saved in
// dragLeave then we restore the DropFeedback that we saved in dragLeave.
if (mLeaveTargetNode != null) {
mTargetNode = mLeaveTargetNode;
mFeedback = mLeaveFeedback;
mCurrentView = mLeaveView;
}
if (mFeedback != null && mFeedback.invalidTarget) {
// The script said we can't drop here.
event.detail = DND.DROP_NONE;
}
if (mLeaveTargetNode == null || event.detail == DND.DROP_NONE) {
clearDropInfo();
}
mLeaveTargetNode = null;
mLeaveFeedback = null;
mLeaveView = null;
}
/*
* The data is being dropped.
* {@inheritDoc}
*/
@Override
public void drop(final DropTargetEvent event) {
if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "dropped");
SimpleElement[] elements = null;
SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance();
if (sxt.isSupportedType(event.currentDataType)) {
if (event.data instanceof SimpleElement[]) {
elements = (SimpleElement[]) event.data;
}
}
if (elements == null || elements.length < 1) {
if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drop missing drop data");
return;
}
if (mCurrentDragElements != null && Arrays.equals(elements, mCurrentDragElements)) {
elements = mCurrentDragElements;
}
if (mTargetNode == null) {
ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy();
if (viewHierarchy.isValid() && viewHierarchy.isEmpty()) {
// There is no target node because the drop happens on an empty document.
// Attempt to create a root node accordingly.
createDocumentRoot(elements);
} else {
if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "dropped on null targetNode");
}
return;
}
updateDropFeedback(mFeedback, event);
final SimpleElement[] elementsFinal = elements;
final LayoutPoint canvasPoint = getDropLocation(event).toLayout();
String label = computeUndoLabel(mTargetNode, elements, event.detail);
// Create node listener which (during the drop) listens for node additions
// and stores the list of added node such that they can be selected afterwards.
final List<UiElementNode> added = new ArrayList<UiElementNode>();
// List of "index within parent" for each node
final List<Integer> indices = new ArrayList<Integer>();
NodeCreationListener listener = new NodeCreationListener() {
@Override
public void nodeCreated(UiElementNode parent, UiElementNode child, int index) {
if (parent == mTargetNode.getNode()) {
added.add(child);
// Adjust existing indices
for (int i = 0, n = indices.size(); i < n; i++) {
int idx = indices.get(i);
if (idx >= index) {
indices.set(i, idx + 1);
}
}
indices.add(index);
}
}
@Override
public void nodeDeleted(UiElementNode parent, UiElementNode child, int previousIndex) {
if (parent == mTargetNode.getNode()) {
// Adjust existing indices
for (int i = 0, n = indices.size(); i < n; i++) {
int idx = indices.get(i);
if (idx >= previousIndex) {
indices.set(i, idx - 1);
}
}
// Make sure we aren't removing the same nodes that are being added
// No, that can happen when canceling out of a drop handler such as
// when dropping an included layout, then canceling out of the
// resource chooser.
//assert !added.contains(child);
}
}
};
try {
UiElementNode.addNodeCreationListener(listener);
mCanvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel(label, new Runnable() {
@Override
public void run() {
InsertType insertType = getInsertType(event, mTargetNode);
mCanvas.getRulesEngine().callOnDropped(mTargetNode,
elementsFinal,
mFeedback,
new Point(canvasPoint.x, canvasPoint.y),
insertType);
mTargetNode.applyPendingChanges();
// Clean up drag if applicable
if (event.detail == DND.DROP_MOVE) {
GlobalCanvasDragInfo.getInstance().removeSource();
}
mTargetNode.applyPendingChanges();
}
});
} finally {
UiElementNode.removeNodeCreationListener(listener);
}
final List<INode> nodes = new ArrayList<INode>();
NodeFactory nodeFactory = mCanvas.getNodeFactory();
for (UiElementNode uiNode : added) {
if (uiNode instanceof UiViewElementNode) {
NodeProxy node = nodeFactory.create((UiViewElementNode) uiNode);
if (node != null) {
nodes.add(node);
}
}
}
// Select the newly dropped nodes:
// Find out which nodes were added, and look up their corresponding
// CanvasViewInfos.
final SelectionManager selectionManager = mCanvas.getSelectionManager();
// Don't use the indices to search for corresponding nodes yet, since a
// render may not have happened yet and we'd rather use an up to date
// view hierarchy than indices to look up the right view infos.
if (!selectionManager.selectDropped(nodes, null /* indices */)) {
// In some scenarios we can't find the actual view infos yet; this
// seems to happen when you drag from one canvas to another (see the
// related comment next to the setFocus() call below). In that case
// defer selection briefly until the view hierarchy etc is up to
// date.
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
selectionManager.selectDropped(nodes, indices);
}
});
}
clearDropInfo();
mCanvas.redraw();
// Request focus: This is *necessary* when you are dragging from one canvas editor
// to another, because without it, the redraw does not seem to be processed (the change
// is invisible until you click on the target canvas to give it focus).
mCanvas.setFocus();
}
/**
* Returns the right {@link InsertType} to use for the given drop target event and the
* given target node
*
* @param event the drop target event
* @param mTargetNode the node targeted by the drop
* @return the {link InsertType} to use for the drop
*/
public static InsertType getInsertType(DropTargetEvent event, NodeProxy mTargetNode) {
GlobalCanvasDragInfo dragInfo = GlobalCanvasDragInfo.getInstance();
if (event.detail == DND.DROP_MOVE) {
SelectionItem[] selection = dragInfo.getCurrentSelection();
if (selection != null) {
for (SelectionItem item : selection) {
if (item.getNode() != null
&& item.getNode().getParent() == mTargetNode) {
return InsertType.MOVE_WITHIN;
}
}
}
return InsertType.MOVE_INTO;
} else if (dragInfo.getSourceCanvas() != null) {
return InsertType.PASTE;
} else {
return InsertType.CREATE;
}
}
/**
* Computes a suitable Undo label to use for a drop operation, such as
* "Drop Button in LinearLayout" and "Move Widgets in RelativeLayout".
*
* @param targetNode The target of the drop
* @param elements The dragged widgets
* @param detail The DnD mode, as used in {@link DropTargetEvent#detail}.
* @return A string suitable as an undo-label for the drop event
*/
public static String computeUndoLabel(NodeProxy targetNode,
SimpleElement[] elements, int detail) {
// Decide whether it's a move or a copy; we'll label moves specifically
// as a move and consider everything else a "Drop"
String verb = (detail == DND.DROP_MOVE) ? "Move" : "Drop";
// Get the type of widget being dropped/moved, IF there is only one. If
// there is more than one, just reference it as "Widgets".
String object;
if (elements != null && elements.length == 1) {
object = getSimpleName(elements[0].getFqcn());
} else {
object = "Widgets";
}
String where = getSimpleName(targetNode.getFqcn());
// When we localize this: $1 is the verb (Move or Drop), $2 is the
// object (such as "Button"), and $3 is the place we are doing it (such
// as "LinearLayout").
return String.format("%1$s %2$s in %3$s", verb, object, where);
}
/**
* Returns simple name (basename, following last dot) of a fully qualified
* class name.
*
* @param fqcn The fqcn to reduce
* @return The base name of the fqcn
*/
public static String getSimpleName(String fqcn) {
// Note that the following works even when there is no dot, since
// lastIndexOf will return -1 so we get fcqn.substring(-1+1) =
// fcqn.substring(0) = fqcn
return fqcn.substring(fqcn.lastIndexOf('.') + 1);
}
/**
* Updates the {@link DropFeedback#isCopy} and {@link DropFeedback#sameCanvas} fields
* of the given {@link DropFeedback}. This is generally called right before invoking
* one of the callOnXyz methods of GRE to refresh the fields.
*
* @param df The current {@link DropFeedback}.
* @param event An optional event to determine if the current operation is copy or move.
*/
private void updateDropFeedback(DropFeedback df, DropTargetEvent event) {
if (event != null) {
df.isCopy = event.detail == DND.DROP_COPY;
}
df.sameCanvas = mCanvas == mGlobalDragInfo.getSourceCanvas();
df.invalidTarget = false;
df.dipScale = mCanvas.getEditorDelegate().getGraphicalEditor().getDipScale();
df.modifierMask = mCanvas.getGestureManager().getRuleModifierMask();
// Set the drag bounds, after converting it from control coordinates to
// layout coordinates
GlobalCanvasDragInfo dragInfo = GlobalCanvasDragInfo.getInstance();
Rect dragBounds = null;
Rect controlDragBounds = dragInfo.getDragBounds();
if (controlDragBounds != null) {
CanvasTransform ht = mCanvas.getHorizontalTransform();
CanvasTransform vt = mCanvas.getVerticalTransform();
double horizScale = ht.getScale();
double verticalScale = vt.getScale();
int x = (int) (controlDragBounds.x / horizScale);
int y = (int) (controlDragBounds.y / verticalScale);
int w = (int) (controlDragBounds.w / horizScale);
int h = (int) (controlDragBounds.h / verticalScale);
dragBounds = new Rect(x, y, w, h);
}
int baseline = dragInfo.getDragBaseline();
if (baseline != -1) {
df.dragBaseline = baseline;
}
df.dragBounds = dragBounds;
}
/**
* Verifies that event.currentDataType is of type {@link SimpleXmlTransfer}.
* If not, try to find a valid data type.
* Otherwise set the drop to {@link DND#DROP_NONE} to cancel it.
*
* @return True if the data type is accepted.
*/
private static boolean checkDataType(DropTargetEvent event) {
SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance();
TransferData current = event.currentDataType;
if (sxt.isSupportedType(current)) {
return true;
}
// We only support SimpleXmlTransfer and the current data type is not right.
// Let's see if we can find another one.
for (TransferData td : event.dataTypes) {
if (td != current && sxt.isSupportedType(td)) {
// We like this type better.
event.currentDataType = td;
return true;
}
}
// We failed to find any good transfer type.
event.detail = DND.DROP_NONE;
return false;
}
/**
* Returns the mouse location of the drop target event.
*
* @param event the drop target event
* @return a {@link ControlPoint} location corresponding to the top left corner
*/
private ControlPoint getDropLocation(DropTargetEvent event) {
return ControlPoint.create(mCanvas, event);
}
/**
* Called on both dragEnter and dragMove.
* Generates the onDropEnter/Move/Leave events depending on the currently
* selected target node.
*/
private void processDropEvent(DropTargetEvent event) {
if (!mCanvas.getViewHierarchy().isValid()) {
// We don't allow drop on an invalid layout, even if we have some obsolete
// layout info for it.
event.detail = DND.DROP_NONE;
clearDropInfo();
return;
}
LayoutPoint p = getDropLocation(event).toLayout();
// Is the mouse currently captured by a DropFeedback.captureArea?
boolean isCaptured = false;
if (mFeedback != null) {
Rect r = mFeedback.captureArea;
isCaptured = r != null && r.contains(p.x, p.y);
}
// We can't switch views/nodes when the mouse is captured
CanvasViewInfo vi;
if (isCaptured) {
vi = mCurrentView;
} else {
vi = mCanvas.getViewHierarchy().findViewInfoAt(p);
// When dragging into the canvas, if you are not over any other view, target
// the root element (since it may not "fill" the screen, e.g. if you have a linear
// layout but have layout_height wrap_content, then the layout will only extend
// to cover the children in the layout, not the whole visible screen area, which
// may be surprising
if (vi == null) {
vi = mCanvas.getViewHierarchy().getRoot();
}
}
boolean isMove = true;
boolean needRedraw = false;
if (vi != mCurrentView) {
// Current view has changed. Does that also change the target node?
// Note that either mCurrentView or vi can be null.
if (vi == null) {
// vi is null but mCurrentView is not, no view is a target anymore
// We don't need onDropMove in this case
isMove = false;
needRedraw = true;
event.detail = DND.DROP_NONE;
clearDropInfo(); // this will call callDropLeave.
} else {
// vi is a new current view.
// Query GRE for onDropEnter on the ViewInfo hierarchy, starting from the child
// towards its parent, till we find one that returns a non-null drop feedback.
DropFeedback df = null;
NodeProxy targetNode = null;
for (CanvasViewInfo targetVi = vi;
targetVi != null && df == null;
targetVi = targetVi.getParent()) {
targetNode = mCanvas.getNodeFactory().create(targetVi);
df = mCanvas.getRulesEngine().callOnDropEnter(targetNode,
targetVi.getViewObject(), mCurrentDragElements);
if (df != null) {
// We should also dispatch an onDropMove() call to the initial enter
// position, such that the view is notified of the position where
// we are within the node immediately (before we for example attempt
// to draw feedback). This is necessary since most views perform the
// guideline computations in onDropMove (since only onDropMove is handed
// the -position- of the mouse), and we want this computation to happen
// before we ask the view to draw its feedback.
updateDropFeedback(df, event);
df = mCanvas.getRulesEngine().callOnDropMove(targetNode,
mCurrentDragElements, df, new Point(p.x, p.y));
}
if (df != null &&
event.detail == DND.DROP_MOVE &&
mCanvas == mGlobalDragInfo.getSourceCanvas()) {
// You can't move an object into itself in the same canvas.
// E.g. case of moving a layout and the node under the mouse is the
// layout itself: a copy would be ok but not a move operation of the
// layout into himself.
SelectionItem[] selection = mGlobalDragInfo.getCurrentSelection();
if (selection != null) {
for (SelectionItem cs : selection) {
if (cs.getViewInfo() == targetVi) {
// The node that responded is one of the selection roots.
// Simply invalidate the drop feedback and move on the
// parent in the ViewInfo chain.
updateDropFeedback(df, event);
mCanvas.getRulesEngine().callOnDropLeave(
targetNode, mCurrentDragElements, df);
df = null;
targetNode = null;
}
}
}
}
}
if (df == null) {
// Provide visual feedback that we are refusing the drop
event.detail = DND.DROP_NONE;
clearDropInfo();
} else if (targetNode != mTargetNode) {
// We found a new target node for the drag'n'drop.
// Release the previous one, if any.
callDropLeave();
// And assign the new one
mTargetNode = targetNode;
mFeedback = df;
// We don't need onDropMove in this case
isMove = false;
}
}
mCurrentView = vi;
}
if (isMove && mTargetNode != null && mFeedback != null) {
// this is a move inside the same view
com.android.ide.common.api.Point p2 =
new com.android.ide.common.api.Point(p.x, p.y);
updateDropFeedback(mFeedback, event);
DropFeedback df = mCanvas.getRulesEngine().callOnDropMove(
mTargetNode, mCurrentDragElements, mFeedback, p2);
mCanvas.getGestureManager().updateMessage(mFeedback);
if (df == null) {
// The target is no longer interested in the drop move.
event.detail = DND.DROP_NONE;
callDropLeave();
} else if (df != mFeedback) {
mFeedback = df;
}
}
if (mFeedback != null) {
if (event.detail == DND.DROP_NONE && !mFeedback.invalidTarget) {
// If we previously provided visual feedback that we were refusing
// the drop, we now need to change it to mean we're accepting it.
event.detail = DND.DROP_DEFAULT;
recomputeDragType(event);
} else if (mFeedback.invalidTarget) {
// Provide visual feedback that we are refusing the drop
event.detail = DND.DROP_NONE;
}
}
if (needRedraw || (mFeedback != null && mFeedback.requestPaint)) {
mCanvas.redraw();
}
// Update outline to show the target node there
OutlinePage outline = mCanvas.getOutlinePage();
TreeSelection newSelection = TreeSelection.EMPTY;
if (mCurrentView != null && mTargetNode != null) {
// Find the view corresponding to the target node. The current view can be a leaf
// view whereas the target node is always a parent layout.
if (mCurrentView.getUiViewNode() != mTargetNode.getNode()) {
mCurrentView = mCurrentView.getParent();
}
if (mCurrentView != null && mCurrentView.getUiViewNode() == mTargetNode.getNode()) {
TreePath treePath = SelectionManager.getTreePath(mCurrentView);
newSelection = new TreeSelection(treePath);
}
}
ISelection currentSelection = outline.getSelection();
if (currentSelection == null || !currentSelection.equals(newSelection)) {
outline.setSelection(newSelection);
}
}
/**
* Calls onDropLeave on mTargetNode with the current mFeedback. <br/>
* Then clears mTargetNode and mFeedback.
*/
private void callDropLeave() {
if (mTargetNode != null && mFeedback != null) {
updateDropFeedback(mFeedback, null);
mCanvas.getRulesEngine().callOnDropLeave(mTargetNode, mCurrentDragElements, mFeedback);
}
mTargetNode = null;
mFeedback = null;
}
private void clearDropInfo() {
callDropLeave();
mCurrentView = null;
mCanvas.redraw();
}
/**
* Creates a root element in an empty document.
* Only the first element's FQCN of the dragged elements is used.
* <p/>
* Actual XML handling is done by {@link LayoutCanvas#createDocumentRoot(String)}.
*/
private void createDocumentRoot(SimpleElement[] elements) {
if (elements == null || elements.length < 1 || elements[0] == null) {
return;
}
mCanvas.createDocumentRoot(elements[0]);
}
/**
* An {@link Overlay} to paint the move feedback. This just delegates to the
* layout rules.
*/
private class MoveOverlay extends Overlay {
@Override
public void paint(GC gc) {
if (mTargetNode != null && mFeedback != null) {
RulesEngine rulesEngine = mCanvas.getRulesEngine();
rulesEngine.callDropFeedbackPaint(mCanvas.getGcWrapper(), mTargetNode, mFeedback);
mFeedback.requestPaint = false;
}
}
}
}