diff options
author | Bob Badour <bbadour@google.com> | 2020-05-06 15:09:12 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-05-06 15:09:12 +0000 |
commit | f1a59c98333d28b04b74772f204bcc1df6e83634 (patch) | |
tree | fd845444b59dfc72656b7781596e0b1a0662c4c7 /eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java | |
parent | 14a008655dafe4ca1fc7d014a16a1c401761675c (diff) | |
parent | d58f8ba3b1869530926bd5f167103dfa161787a1 (diff) | |
download | sdk-f1a59c98333d28b04b74772f204bcc1df6e83634.tar.gz |
Merge "Revert "Remove unused project."" am: fc7cda06f5 am: d3c69fa48e am: d58f8ba3b1
Change-Id: I297730ce4f9da2bf30dde696439ab825cd642b00
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java | 852 |
1 files changed, 852 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java new file mode 100644 index 000000000..7cf3a647a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java @@ -0,0 +1,852 @@ +/* + * 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 & 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; + } + } + } +} |