Home | History | Annotate | Download | only in gle2
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
     17 
     18 import com.android.ide.common.api.DropFeedback;
     19 import com.android.ide.common.api.INode;
     20 import com.android.ide.common.api.InsertType;
     21 import com.android.ide.common.api.Point;
     22 import com.android.ide.common.api.Rect;
     23 import com.android.ide.eclipse.adt.AdtPlugin;
     24 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory;
     25 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
     26 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
     27 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
     28 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
     29 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode.NodeCreationListener;
     30 
     31 import org.eclipse.jface.viewers.ISelection;
     32 import org.eclipse.jface.viewers.TreePath;
     33 import org.eclipse.jface.viewers.TreeSelection;
     34 import org.eclipse.swt.dnd.DND;
     35 import org.eclipse.swt.dnd.DropTargetEvent;
     36 import org.eclipse.swt.dnd.TransferData;
     37 import org.eclipse.swt.graphics.GC;
     38 import org.eclipse.swt.widgets.Display;
     39 
     40 import java.util.ArrayList;
     41 import java.util.Arrays;
     42 import java.util.Collections;
     43 import java.util.List;
     44 
     45 /**
     46  * The Move gesture provides the operation for moving widgets around in the canvas.
     47  */
     48 public class MoveGesture extends DropGesture {
     49     /** The associated {@link LayoutCanvas}. */
     50     private LayoutCanvas mCanvas;
     51 
     52     /** Overlay which paints the drag & drop feedback. */
     53     private MoveOverlay mOverlay;
     54 
     55     private static final boolean DEBUG = false;
     56 
     57     /**
     58      * The top view right under the drag'n'drop cursor.
     59      * This can only be null during a drag'n'drop when there is no view under the cursor
     60      * or after the state was all cleared.
     61      */
     62     private CanvasViewInfo mCurrentView;
     63 
     64     /**
     65      * The elements currently being dragged. This will always be non-null for a valid
     66      * drag'n'drop that happens within the same instance of Eclipse.
     67      * <p/>
     68      * In the event that the drag and drop happens between different instances of Eclipse
     69      * this will remain null.
     70      */
     71     private SimpleElement[] mCurrentDragElements;
     72 
     73     /**
     74      * The first view under the cursor that responded to onDropEnter is called the "target view".
     75      * It can differ from mCurrentView, typically because a terminal View doesn't
     76      * accept drag'n'drop so its parent layout became the target drag'n'drop receiver.
     77      * <p/>
     78      * The target node is the proxy node associated with the target view.
     79      * This can be null if no view under the cursor accepted the drag'n'drop or if the node
     80      * factory couldn't create a proxy for it.
     81      */
     82     private NodeProxy mTargetNode;
     83 
     84     /**
     85      * The latest drop feedback returned by IViewRule.onDropEnter/Move.
     86      */
     87     private DropFeedback mFeedback;
     88 
     89     /**
     90      * {@link #dragLeave(DropTargetEvent)} is unfortunately called right before data is
     91      * about to be dropped (between the last {@link #dragOver(DropTargetEvent)} and the
     92      * next {@link #dropAccept(DropTargetEvent)}). That means we can't just
     93      * trash the current DropFeedback from the current view rule in dragLeave().
     94      * Instead we preserve it in mLeaveTargetNode and mLeaveFeedback in case a dropAccept
     95      * happens next.
     96      */
     97     private NodeProxy mLeaveTargetNode;
     98 
     99     /**
    100      * @see #mLeaveTargetNode
    101      */
    102     private DropFeedback mLeaveFeedback;
    103 
    104     /**
    105      * @see #mLeaveTargetNode
    106      */
    107     private CanvasViewInfo mLeaveView;
    108 
    109     /** Singleton used to keep track of drag selection in the same Eclipse instance. */
    110     private final GlobalCanvasDragInfo mGlobalDragInfo;
    111 
    112     /**
    113      * Constructs a new {@link MoveGesture}, tied to the given canvas.
    114      *
    115      * @param canvas The canvas to associate the {@link MoveGesture} with.
    116      */
    117     public MoveGesture(LayoutCanvas canvas) {
    118         mCanvas = canvas;
    119         mGlobalDragInfo = GlobalCanvasDragInfo.getInstance();
    120     }
    121 
    122     @Override
    123     public List<Overlay> createOverlays() {
    124         mOverlay = new MoveOverlay();
    125         return Collections.<Overlay> singletonList(mOverlay);
    126     }
    127 
    128     @Override
    129     public void begin(ControlPoint pos, int startMask) {
    130         super.begin(pos, startMask);
    131 
    132         // Hide selection overlays during a move drag
    133         mCanvas.getSelectionOverlay().setHidden(true);
    134     }
    135 
    136     @Override
    137     public void end(ControlPoint pos, boolean canceled) {
    138         super.end(pos, canceled);
    139 
    140         mCanvas.getSelectionOverlay().setHidden(false);
    141 
    142         // Ensure that the outline is back to showing the current selection, since during
    143         // a drag gesture we temporarily set it to show the current target node instead.
    144         mCanvas.getSelectionManager().syncOutlineSelection();
    145     }
    146 
    147     /* TODO: Pass modifier mask to drag rules as well! This doesn't work yet since
    148        the drag &amp; drop code seems to steal keyboard events.
    149     @Override
    150     public boolean keyPressed(KeyEvent event) {
    151         update(mCanvas.getGestureManager().getCurrentControlPoint());
    152         mCanvas.redraw();
    153         return true;
    154     }
    155 
    156     @Override
    157     public boolean keyReleased(KeyEvent event) {
    158         update(mCanvas.getGestureManager().getCurrentControlPoint());
    159         mCanvas.redraw();
    160         return true;
    161     }
    162     */
    163 
    164     /*
    165      * The cursor has entered the drop target boundaries.
    166      * {@inheritDoc}
    167      */
    168     @Override
    169     public void dragEnter(DropTargetEvent event) {
    170         if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drag enter", event);
    171 
    172         // Make sure we don't have any residual data from an earlier operation.
    173         clearDropInfo();
    174         mLeaveTargetNode = null;
    175         mLeaveFeedback = null;
    176         mLeaveView = null;
    177 
    178         // Get the dragged elements.
    179         //
    180         // The current transfered type can be extracted from the event.
    181         // As described in dragOver(), this works basically works on Windows but
    182         // not on Linux or Mac, in which case we can't get the type until we
    183         // receive dropAccept/drop().
    184         // For consistency we try to use the GlobalCanvasDragInfo instance first,
    185         // and if it fails we use the event transfer type as a backup (but as said
    186         // before it will most likely work only on Windows.)
    187         // In any case this can be null even for a valid transfer.
    188 
    189         mCurrentDragElements = mGlobalDragInfo.getCurrentElements();
    190 
    191         if (mCurrentDragElements == null) {
    192             SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance();
    193             if (sxt.isSupportedType(event.currentDataType)) {
    194                 mCurrentDragElements = (SimpleElement[]) sxt.nativeToJava(event.currentDataType);
    195             }
    196         }
    197 
    198         // if there is no data to transfer, invalidate the drag'n'drop.
    199         // The assumption is that the transfer should have at least one element with a
    200         // a non-null non-empty FQCN. Everything else is optional.
    201         if (mCurrentDragElements == null ||
    202                 mCurrentDragElements.length == 0 ||
    203                 mCurrentDragElements[0] == null ||
    204                 mCurrentDragElements[0].getFqcn() == null ||
    205                 mCurrentDragElements[0].getFqcn().length() == 0) {
    206             event.detail = DND.DROP_NONE;
    207         }
    208 
    209         dragOperationChanged(event);
    210     }
    211 
    212     /*
    213      * The operation being performed has changed (e.g. modifier key).
    214      * {@inheritDoc}
    215      */
    216     @Override
    217     public void dragOperationChanged(DropTargetEvent event) {
    218         if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drag changed", event);
    219 
    220         checkDataType(event);
    221         recomputeDragType(event);
    222     }
    223 
    224     private void recomputeDragType(DropTargetEvent event) {
    225         if (event.detail == DND.DROP_DEFAULT) {
    226             // Default means we can now choose the default operation, either copy or move.
    227             // If the drag comes from the same canvas we default to move, otherwise we
    228             // default to copy.
    229 
    230             if (mGlobalDragInfo.getSourceCanvas() == mCanvas &&
    231                     (event.operations & DND.DROP_MOVE) != 0) {
    232                 event.detail = DND.DROP_MOVE;
    233             } else if ((event.operations & DND.DROP_COPY) != 0) {
    234                 event.detail = DND.DROP_COPY;
    235             }
    236         }
    237 
    238         // We don't support other types than copy and move
    239         if (event.detail != DND.DROP_COPY && event.detail != DND.DROP_MOVE) {
    240             event.detail = DND.DROP_NONE;
    241         }
    242     }
    243 
    244     /*
    245      * The cursor has left the drop target boundaries OR data is about to be dropped.
    246      * {@inheritDoc}
    247      */
    248     @Override
    249     public void dragLeave(DropTargetEvent event) {
    250         if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drag leave");
    251 
    252         // dragLeave is unfortunately called right before data is about to be dropped
    253         // (between the last dropMove and the next dropAccept). That means we can't just
    254         // trash the current DropFeedback from the current view rule, we need to preserve
    255         // it in case a dropAccept happens next.
    256         // See the corresponding kludge in dropAccept().
    257         mLeaveTargetNode = mTargetNode;
    258         mLeaveFeedback = mFeedback;
    259         mLeaveView = mCurrentView;
    260 
    261         clearDropInfo();
    262     }
    263 
    264     /*
    265      * The cursor is moving over the drop target.
    266      * {@inheritDoc}
    267      */
    268     @Override
    269     public void dragOver(DropTargetEvent event) {
    270         processDropEvent(event);
    271     }
    272 
    273     /*
    274      * The drop is about to be performed.
    275      * The drop target is given a last chance to change the nature of the drop.
    276      * {@inheritDoc}
    277      */
    278     @Override
    279     public void dropAccept(DropTargetEvent event) {
    280         if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drop accept");
    281 
    282         checkDataType(event);
    283 
    284         // If we have a valid target node and it matches the one we saved in
    285         // dragLeave then we restore the DropFeedback that we saved in dragLeave.
    286         if (mLeaveTargetNode != null) {
    287             mTargetNode = mLeaveTargetNode;
    288             mFeedback = mLeaveFeedback;
    289             mCurrentView = mLeaveView;
    290         }
    291 
    292         if (mFeedback != null && mFeedback.invalidTarget) {
    293             // The script said we can't drop here.
    294             event.detail = DND.DROP_NONE;
    295         }
    296 
    297         if (mLeaveTargetNode == null || event.detail == DND.DROP_NONE) {
    298             clearDropInfo();
    299         }
    300 
    301         mLeaveTargetNode = null;
    302         mLeaveFeedback = null;
    303         mLeaveView = null;
    304     }
    305 
    306     /*
    307      * The data is being dropped.
    308      * {@inheritDoc}
    309      */
    310     @Override
    311     public void drop(final DropTargetEvent event) {
    312         if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "dropped");
    313 
    314         SimpleElement[] elements = null;
    315 
    316         SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance();
    317 
    318         if (sxt.isSupportedType(event.currentDataType)) {
    319             if (event.data instanceof SimpleElement[]) {
    320                 elements = (SimpleElement[]) event.data;
    321             }
    322         }
    323 
    324         if (elements == null || elements.length < 1) {
    325             if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drop missing drop data");
    326             return;
    327         }
    328 
    329         if (mCurrentDragElements != null && Arrays.equals(elements, mCurrentDragElements)) {
    330             elements = mCurrentDragElements;
    331         }
    332 
    333         if (mTargetNode == null) {
    334             ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy();
    335             if (viewHierarchy.isValid() && viewHierarchy.isEmpty()) {
    336                 // There is no target node because the drop happens on an empty document.
    337                 // Attempt to create a root node accordingly.
    338                 createDocumentRoot(elements);
    339             } else {
    340                 if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "dropped on null targetNode");
    341             }
    342             return;
    343         }
    344 
    345         updateDropFeedback(mFeedback, event);
    346 
    347         final SimpleElement[] elementsFinal = elements;
    348         final LayoutPoint canvasPoint = getDropLocation(event).toLayout();
    349         String label = computeUndoLabel(mTargetNode, elements, event.detail);
    350 
    351         // Create node listener which (during the drop) listens for node additions
    352         // and stores the list of added node such that they can be selected afterwards.
    353         final List<UiElementNode> added = new ArrayList<UiElementNode>();
    354         // List of "index within parent" for each node
    355         final List<Integer> indices = new ArrayList<Integer>();
    356         NodeCreationListener listener = new NodeCreationListener() {
    357             @Override
    358             public void nodeCreated(UiElementNode parent, UiElementNode child, int index) {
    359                 if (parent == mTargetNode.getNode()) {
    360                     added.add(child);
    361 
    362                     // Adjust existing indices
    363                     for (int i = 0, n = indices.size(); i < n; i++) {
    364                         int idx = indices.get(i);
    365                         if (idx >= index) {
    366                             indices.set(i, idx + 1);
    367                         }
    368                     }
    369 
    370                     indices.add(index);
    371                 }
    372             }
    373 
    374             @Override
    375             public void nodeDeleted(UiElementNode parent, UiElementNode child, int previousIndex) {
    376                 if (parent == mTargetNode.getNode()) {
    377                     // Adjust existing indices
    378                     for (int i = 0, n = indices.size(); i < n; i++) {
    379                         int idx = indices.get(i);
    380                         if (idx >= previousIndex) {
    381                             indices.set(i, idx - 1);
    382                         }
    383                     }
    384 
    385                     // Make sure we aren't removing the same nodes that are being added
    386                     // No, that can happen when canceling out of a drop handler such as
    387                     // when dropping an included layout, then canceling out of the
    388                     // resource chooser.
    389                     //assert !added.contains(child);
    390                 }
    391             }
    392         };
    393 
    394         try {
    395             UiElementNode.addNodeCreationListener(listener);
    396             mCanvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel(label, new Runnable() {
    397                 @Override
    398                 public void run() {
    399                     InsertType insertType = getInsertType(event, mTargetNode);
    400                     mCanvas.getRulesEngine().callOnDropped(mTargetNode,
    401                             elementsFinal,
    402                             mFeedback,
    403                             new Point(canvasPoint.x, canvasPoint.y),
    404                             insertType);
    405                     mTargetNode.applyPendingChanges();
    406                     // Clean up drag if applicable
    407                     if (event.detail == DND.DROP_MOVE) {
    408                         GlobalCanvasDragInfo.getInstance().removeSource();
    409                     }
    410                     mTargetNode.applyPendingChanges();
    411                 }
    412             });
    413         } finally {
    414             UiElementNode.removeNodeCreationListener(listener);
    415         }
    416 
    417         final List<INode> nodes = new ArrayList<INode>();
    418         NodeFactory nodeFactory = mCanvas.getNodeFactory();
    419         for (UiElementNode uiNode : added) {
    420             if (uiNode instanceof UiViewElementNode) {
    421                 NodeProxy node = nodeFactory.create((UiViewElementNode) uiNode);
    422                 if (node != null) {
    423                     nodes.add(node);
    424                 }
    425             }
    426         }
    427 
    428         // Select the newly dropped nodes:
    429         // Find out which nodes were added, and look up their corresponding
    430         // CanvasViewInfos.
    431         final SelectionManager selectionManager = mCanvas.getSelectionManager();
    432         // Don't use the indices to search for corresponding nodes yet, since a
    433         // render may not have happened yet and we'd rather use an up to date
    434         // view hierarchy than indices to look up the right view infos.
    435         if (!selectionManager.selectDropped(nodes, null /* indices */)) {
    436             // In some scenarios we can't find the actual view infos yet; this
    437             // seems to happen when you drag from one canvas to another (see the
    438             // related comment next to the setFocus() call below). In that case
    439             // defer selection briefly until the view hierarchy etc is up to
    440             // date.
    441             Display.getDefault().asyncExec(new Runnable() {
    442                 @Override
    443                 public void run() {
    444                     selectionManager.selectDropped(nodes, indices);
    445                 }
    446             });
    447         }
    448 
    449         clearDropInfo();
    450         mCanvas.redraw();
    451         // Request focus: This is *necessary* when you are dragging from one canvas editor
    452         // to another, because without it, the redraw does not seem to be processed (the change
    453         // is invisible until you click on the target canvas to give it focus).
    454         mCanvas.setFocus();
    455     }
    456 
    457     /**
    458      * Returns the right {@link InsertType} to use for the given drop target event and the
    459      * given target node
    460      *
    461      * @param event the drop target event
    462      * @param mTargetNode the node targeted by the drop
    463      * @return the {link InsertType} to use for the drop
    464      */
    465     public static InsertType getInsertType(DropTargetEvent event, NodeProxy mTargetNode) {
    466         GlobalCanvasDragInfo dragInfo = GlobalCanvasDragInfo.getInstance();
    467         if (event.detail == DND.DROP_MOVE) {
    468             SelectionItem[] selection = dragInfo.getCurrentSelection();
    469             if (selection != null) {
    470                 for (SelectionItem item : selection) {
    471                     if (item.getNode() != null
    472                             && item.getNode().getParent() == mTargetNode) {
    473                         return InsertType.MOVE_WITHIN;
    474                     }
    475                 }
    476             }
    477 
    478             return InsertType.MOVE_INTO;
    479         } else if (dragInfo.getSourceCanvas() != null) {
    480             return InsertType.PASTE;
    481         } else {
    482             return InsertType.CREATE;
    483         }
    484     }
    485 
    486     /**
    487      * Computes a suitable Undo label to use for a drop operation, such as
    488      * "Drop Button in LinearLayout" and "Move Widgets in RelativeLayout".
    489      *
    490      * @param targetNode The target of the drop
    491      * @param elements The dragged widgets
    492      * @param detail The DnD mode, as used in {@link DropTargetEvent#detail}.
    493      * @return A string suitable as an undo-label for the drop event
    494      */
    495     public static String computeUndoLabel(NodeProxy targetNode,
    496             SimpleElement[] elements, int detail) {
    497         // Decide whether it's a move or a copy; we'll label moves specifically
    498         // as a move and consider everything else a "Drop"
    499         String verb = (detail == DND.DROP_MOVE) ? "Move" : "Drop";
    500 
    501         // Get the type of widget being dropped/moved, IF there is only one. If
    502         // there is more than one, just reference it as "Widgets".
    503         String object;
    504         if (elements != null && elements.length == 1) {
    505             object = getSimpleName(elements[0].getFqcn());
    506         } else {
    507             object = "Widgets";
    508         }
    509 
    510         String where = getSimpleName(targetNode.getFqcn());
    511 
    512         // When we localize this: $1 is the verb (Move or Drop), $2 is the
    513         // object (such as "Button"), and $3 is the place we are doing it (such
    514         // as "LinearLayout").
    515         return String.format("%1$s %2$s in %3$s", verb, object, where);
    516     }
    517 
    518     /**
    519      * Returns simple name (basename, following last dot) of a fully qualified
    520      * class name.
    521      *
    522      * @param fqcn The fqcn to reduce
    523      * @return The base name of the fqcn
    524      */
    525     public static String getSimpleName(String fqcn) {
    526         // Note that the following works even when there is no dot, since
    527         // lastIndexOf will return -1 so we get fcqn.substring(-1+1) =
    528         // fcqn.substring(0) = fqcn
    529         return fqcn.substring(fqcn.lastIndexOf('.') + 1);
    530     }
    531 
    532     /**
    533      * Updates the {@link DropFeedback#isCopy} and {@link DropFeedback#sameCanvas} fields
    534      * of the given {@link DropFeedback}. This is generally called right before invoking
    535      * one of the callOnXyz methods of GRE to refresh the fields.
    536      *
    537      * @param df The current {@link DropFeedback}.
    538      * @param event An optional event to determine if the current operation is copy or move.
    539      */
    540     private void updateDropFeedback(DropFeedback df, DropTargetEvent event) {
    541         if (event != null) {
    542             df.isCopy = event.detail == DND.DROP_COPY;
    543         }
    544         df.sameCanvas = mCanvas == mGlobalDragInfo.getSourceCanvas();
    545         df.invalidTarget = false;
    546         df.dipScale = mCanvas.getEditorDelegate().getGraphicalEditor().getDipScale();
    547         df.modifierMask = mCanvas.getGestureManager().getRuleModifierMask();
    548 
    549         // Set the drag bounds, after converting it from control coordinates to
    550         // layout coordinates
    551         GlobalCanvasDragInfo dragInfo = GlobalCanvasDragInfo.getInstance();
    552         Rect dragBounds = null;
    553         Rect controlDragBounds = dragInfo.getDragBounds();
    554         if (controlDragBounds != null) {
    555             CanvasTransform ht = mCanvas.getHorizontalTransform();
    556             CanvasTransform vt = mCanvas.getVerticalTransform();
    557             double horizScale = ht.getScale();
    558             double verticalScale = vt.getScale();
    559             int x = (int) (controlDragBounds.x / horizScale);
    560             int y = (int) (controlDragBounds.y / verticalScale);
    561             int w = (int) (controlDragBounds.w / horizScale);
    562             int h = (int) (controlDragBounds.h / verticalScale);
    563             dragBounds = new Rect(x, y, w, h);
    564         }
    565         int baseline = dragInfo.getDragBaseline();
    566         if (baseline != -1) {
    567             df.dragBaseline = baseline;
    568         }
    569         df.dragBounds = dragBounds;
    570     }
    571 
    572     /**
    573      * Verifies that event.currentDataType is of type {@link SimpleXmlTransfer}.
    574      * If not, try to find a valid data type.
    575      * Otherwise set the drop to {@link DND#DROP_NONE} to cancel it.
    576      *
    577      * @return True if the data type is accepted.
    578      */
    579     private static boolean checkDataType(DropTargetEvent event) {
    580 
    581         SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance();
    582 
    583         TransferData current = event.currentDataType;
    584 
    585         if (sxt.isSupportedType(current)) {
    586             return true;
    587         }
    588 
    589         // We only support SimpleXmlTransfer and the current data type is not right.
    590         // Let's see if we can find another one.
    591 
    592         for (TransferData td : event.dataTypes) {
    593             if (td != current && sxt.isSupportedType(td)) {
    594                 // We like this type better.
    595                 event.currentDataType = td;
    596                 return true;
    597             }
    598         }
    599 
    600         // We failed to find any good transfer type.
    601         event.detail = DND.DROP_NONE;
    602         return false;
    603     }
    604 
    605     /**
    606      * Returns the mouse location of the drop target event.
    607      *
    608      * @param event the drop target event
    609      * @return a {@link ControlPoint} location corresponding to the top left corner
    610      */
    611     private ControlPoint getDropLocation(DropTargetEvent event) {
    612         return ControlPoint.create(mCanvas, event);
    613     }
    614 
    615     /**
    616      * Called on both dragEnter and dragMove.
    617      * Generates the onDropEnter/Move/Leave events depending on the currently
    618      * selected target node.
    619      */
    620     private void processDropEvent(DropTargetEvent event) {
    621         if (!mCanvas.getViewHierarchy().isValid()) {
    622             // We don't allow drop on an invalid layout, even if we have some obsolete
    623             // layout info for it.
    624             event.detail = DND.DROP_NONE;
    625             clearDropInfo();
    626             return;
    627         }
    628 
    629         LayoutPoint p = getDropLocation(event).toLayout();
    630 
    631         // Is the mouse currently captured by a DropFeedback.captureArea?
    632         boolean isCaptured = false;
    633         if (mFeedback != null) {
    634             Rect r = mFeedback.captureArea;
    635             isCaptured = r != null && r.contains(p.x, p.y);
    636         }
    637 
    638         // We can't switch views/nodes when the mouse is captured
    639         CanvasViewInfo vi;
    640         if (isCaptured) {
    641             vi = mCurrentView;
    642         } else {
    643             vi = mCanvas.getViewHierarchy().findViewInfoAt(p);
    644 
    645             // When dragging into the canvas, if you are not over any other view, target
    646             // the root element (since it may not "fill" the screen, e.g. if you have a linear
    647             // layout but have layout_height wrap_content, then the layout will only extend
    648             // to cover the children in the layout, not the whole visible screen area, which
    649             // may be surprising
    650             if (vi == null) {
    651                 vi = mCanvas.getViewHierarchy().getRoot();
    652             }
    653         }
    654 
    655         boolean isMove = true;
    656         boolean needRedraw = false;
    657 
    658         if (vi != mCurrentView) {
    659             // Current view has changed. Does that also change the target node?
    660             // Note that either mCurrentView or vi can be null.
    661 
    662             if (vi == null) {
    663                 // vi is null but mCurrentView is not, no view is a target anymore
    664                 // We don't need onDropMove in this case
    665                 isMove = false;
    666                 needRedraw = true;
    667                 event.detail = DND.DROP_NONE;
    668                 clearDropInfo(); // this will call callDropLeave.
    669 
    670             } else {
    671                 // vi is a new current view.
    672                 // Query GRE for onDropEnter on the ViewInfo hierarchy, starting from the child
    673                 // towards its parent, till we find one that returns a non-null drop feedback.
    674 
    675                 DropFeedback df = null;
    676                 NodeProxy targetNode = null;
    677 
    678                 for (CanvasViewInfo targetVi = vi;
    679                      targetVi != null && df == null;
    680                      targetVi = targetVi.getParent()) {
    681                     targetNode = mCanvas.getNodeFactory().create(targetVi);
    682                     df = mCanvas.getRulesEngine().callOnDropEnter(targetNode,
    683                             targetVi.getViewObject(), mCurrentDragElements);
    684 
    685                     if (df != null) {
    686                         // We should also dispatch an onDropMove() call to the initial enter
    687                         // position, such that the view is notified of the position where
    688                         // we are within the node immediately (before we for example attempt
    689                         // to draw feedback). This is necessary since most views perform the
    690                         // guideline computations in onDropMove (since only onDropMove is handed
    691                         // the -position- of the mouse), and we want this computation to happen
    692                         // before we ask the view to draw its feedback.
    693                         updateDropFeedback(df, event);
    694                         df = mCanvas.getRulesEngine().callOnDropMove(targetNode,
    695                                 mCurrentDragElements, df, new Point(p.x, p.y));
    696                     }
    697 
    698                     if (df != null &&
    699                             event.detail == DND.DROP_MOVE &&
    700                             mCanvas == mGlobalDragInfo.getSourceCanvas()) {
    701                         // You can't move an object into itself in the same canvas.
    702                         // E.g. case of moving a layout and the node under the mouse is the
    703                         // layout itself: a copy would be ok but not a move operation of the
    704                         // layout into himself.
    705 
    706                         SelectionItem[] selection = mGlobalDragInfo.getCurrentSelection();
    707                         if (selection != null) {
    708                             for (SelectionItem cs : selection) {
    709                                 if (cs.getViewInfo() == targetVi) {
    710                                     // The node that responded is one of the selection roots.
    711                                     // Simply invalidate the drop feedback and move on the
    712                                     // parent in the ViewInfo chain.
    713 
    714                                     updateDropFeedback(df, event);
    715                                     mCanvas.getRulesEngine().callOnDropLeave(
    716                                             targetNode, mCurrentDragElements, df);
    717                                     df = null;
    718                                     targetNode = null;
    719                                 }
    720                             }
    721                         }
    722                     }
    723                 }
    724 
    725                 if (df == null) {
    726                     // Provide visual feedback that we are refusing the drop
    727                     event.detail = DND.DROP_NONE;
    728                     clearDropInfo();
    729 
    730                 } else if (targetNode != mTargetNode) {
    731                     // We found a new target node for the drag'n'drop.
    732                     // Release the previous one, if any.
    733                     callDropLeave();
    734 
    735                     // And assign the new one
    736                     mTargetNode = targetNode;
    737                     mFeedback = df;
    738 
    739                     // We don't need onDropMove in this case
    740                     isMove = false;
    741                 }
    742             }
    743 
    744             mCurrentView = vi;
    745         }
    746 
    747         if (isMove && mTargetNode != null && mFeedback != null) {
    748             // this is a move inside the same view
    749             com.android.ide.common.api.Point p2 =
    750                 new com.android.ide.common.api.Point(p.x, p.y);
    751             updateDropFeedback(mFeedback, event);
    752             DropFeedback df = mCanvas.getRulesEngine().callOnDropMove(
    753                     mTargetNode, mCurrentDragElements, mFeedback, p2);
    754             mCanvas.getGestureManager().updateMessage(mFeedback);
    755 
    756             if (df == null) {
    757                 // The target is no longer interested in the drop move.
    758                 event.detail = DND.DROP_NONE;
    759                 callDropLeave();
    760 
    761             } else if (df != mFeedback) {
    762                 mFeedback = df;
    763             }
    764         }
    765 
    766         if (mFeedback != null) {
    767             if (event.detail == DND.DROP_NONE && !mFeedback.invalidTarget) {
    768                 // If we previously provided visual feedback that we were refusing
    769                 // the drop, we now need to change it to mean we're accepting it.
    770                 event.detail = DND.DROP_DEFAULT;
    771                 recomputeDragType(event);
    772 
    773             } else if (mFeedback.invalidTarget) {
    774                 // Provide visual feedback that we are refusing the drop
    775                 event.detail = DND.DROP_NONE;
    776             }
    777         }
    778 
    779         if (needRedraw || (mFeedback != null && mFeedback.requestPaint)) {
    780             mCanvas.redraw();
    781         }
    782 
    783         // Update outline to show the target node there
    784         OutlinePage outline = mCanvas.getOutlinePage();
    785         TreeSelection newSelection = TreeSelection.EMPTY;
    786         if (mCurrentView != null && mTargetNode != null) {
    787             // Find the view corresponding to the target node. The current view can be a leaf
    788             // view whereas the target node is always a parent layout.
    789             if (mCurrentView.getUiViewNode() != mTargetNode.getNode()) {
    790                 mCurrentView = mCurrentView.getParent();
    791             }
    792             if (mCurrentView != null && mCurrentView.getUiViewNode() == mTargetNode.getNode()) {
    793                 TreePath treePath = SelectionManager.getTreePath(mCurrentView);
    794                 newSelection = new TreeSelection(treePath);
    795             }
    796         }
    797 
    798         ISelection currentSelection = outline.getSelection();
    799         if (currentSelection == null || !currentSelection.equals(newSelection)) {
    800             outline.setSelection(newSelection);
    801         }
    802     }
    803 
    804     /**
    805      * Calls onDropLeave on mTargetNode with the current mFeedback. <br/>
    806      * Then clears mTargetNode and mFeedback.
    807      */
    808     private void callDropLeave() {
    809         if (mTargetNode != null && mFeedback != null) {
    810             updateDropFeedback(mFeedback, null);
    811             mCanvas.getRulesEngine().callOnDropLeave(mTargetNode, mCurrentDragElements, mFeedback);
    812         }
    813 
    814         mTargetNode = null;
    815         mFeedback = null;
    816     }
    817 
    818     private void clearDropInfo() {
    819         callDropLeave();
    820         mCurrentView = null;
    821         mCanvas.redraw();
    822     }
    823 
    824     /**
    825      * Creates a root element in an empty document.
    826      * Only the first element's FQCN of the dragged elements is used.
    827      * <p/>
    828      * Actual XML handling is done by {@link LayoutCanvas#createDocumentRoot(String)}.
    829      */
    830     private void createDocumentRoot(SimpleElement[] elements) {
    831         if (elements == null || elements.length < 1 || elements[0] == null) {
    832             return;
    833         }
    834 
    835         mCanvas.createDocumentRoot(elements[0]);
    836     }
    837 
    838     /**
    839      * An {@link Overlay} to paint the move feedback. This just delegates to the
    840      * layout rules.
    841      */
    842     private class MoveOverlay extends Overlay {
    843         @Override
    844         public void paint(GC gc) {
    845             if (mTargetNode != null && mFeedback != null) {
    846                 RulesEngine rulesEngine = mCanvas.getRulesEngine();
    847                 rulesEngine.callDropFeedbackPaint(mCanvas.getGcWrapper(), mTargetNode, mFeedback);
    848                 mFeedback.requestPaint = false;
    849             }
    850         }
    851     }
    852 }
    853