OSDN Git Service

Use GridLayout state to determine exact row and column boundaries
authorTor Norbye <tnorbye@google.com>
Wed, 24 Aug 2011 03:05:32 +0000 (20:05 -0700)
committerTor Norbye <tnorbye@google.com>
Wed, 24 Aug 2011 03:26:59 +0000 (20:26 -0700)
This changeset updates the GridLayout support to consider the state of
a rendered GridLayout when deciding where the rows and columns
are. This information is already available in the GridLayout object,
so if one is provided use that data rather than inferring it from the
bounds of the views in each row and column.

This required changing the view rules API a bit to pass the view
objects in to the key entry points (drawing selection, drag & drop and
resize).

Change-Id: If8484f7f7181c65d0a2fdf629ffd515edd05448b

25 files changed:
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AbsoluteLayoutRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AdapterViewRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FrameLayoutRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/IgnoredLayoutRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RelativeLayoutRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ResizeState.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableRowRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridLayoutPainter.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ResizeGesture.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionOverlay.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java
eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java
eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LinearLayoutRuleTest.java
eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/ZoomControlsRuleTest.java
eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/grid/GridModelTest.java
rule_api/src/com/android/ide/common/api/IViewRule.java

index 9a789c6..6f6fb87 100644 (file)
@@ -56,7 +56,8 @@ public class AbsoluteLayoutRule extends BaseLayoutRule {
     // The AbsoluteLayout accepts any drag'n'drop anywhere on its surface.
 
     @Override
-    public DropFeedback onDropEnter(final INode targetNode, final IDragElement[] elements) {
+    public DropFeedback onDropEnter(INode targetNode, Object targetView,
+            final IDragElement[] elements) {
 
         if (elements.length == 0) {
             return null;
index 018d32d..1de09b8 100644 (file)
@@ -27,7 +27,7 @@ import com.android.ide.common.api.Rect;
 /** Rule for AdapterView subclasses that don't have more specific rules */
 public class AdapterViewRule extends BaseLayoutRule {
     @Override
-    public DropFeedback onDropEnter(INode targetNode, IDragElement[] elements) {
+    public DropFeedback onDropEnter(INode targetNode, Object targetView, IDragElement[] elements) {
         // You are not allowed to insert children into AdapterViews; you must
         // use the dedicated addView methods etc dynamically
         DropFeedback dropFeedback = new DropFeedback(null,  new IFeedbackPainter() {
index c0a8f53..55667fe 100644 (file)
@@ -235,15 +235,14 @@ public class BaseLayoutRule extends BaseViewRule {
      * The default behavior for pasting in a layout is to simulate a drop in the
      * top-left corner of the view.
      * <p/>
-     * Note that we explicitly do not call super() here -- the BasView.onPaste
+     * Note that we explicitly do not call super() here -- the BaseViewRule.onPaste handler
      * will call onPasteBeforeChild() instead.
      * <p/>
      * Derived layouts should override this behavior if not appropriate.
      */
     @Override
-    public void onPaste(INode targetNode, IDragElement[] elements) {
-
-        DropFeedback feedback = onDropEnter(targetNode, elements);
+    public void onPaste(INode targetNode, Object targetView, IDragElement[] elements) {
+        DropFeedback feedback = onDropEnter(targetNode, targetView, elements);
         if (feedback != null) {
             Point p = targetNode.getBounds().getTopLeft();
             feedback = onDropMove(targetNode, elements, feedback, p);
@@ -263,12 +262,13 @@ public class BaseLayoutRule extends BaseViewRule {
      * a hint to paste "before" it.
      *
      * @param parentNode the parent node we're pasting into
+     * @param parentView the view object for the parent layout, or null
      * @param targetNode the first selected node
      * @param elements the elements being pasted
      */
-    public void onPasteBeforeChild(INode parentNode, INode targetNode, IDragElement[] elements) {
-
-        DropFeedback feedback = onDropEnter(parentNode, elements);
+    public void onPasteBeforeChild(INode parentNode, Object parentView, INode targetNode,
+            IDragElement[] elements) {
+        DropFeedback feedback = onDropEnter(parentNode, parentView, elements);
         if (feedback != null) {
             Point parentP = parentNode.getBounds().getTopLeft();
             Point targetP = targetNode.getBounds().getTopLeft();
@@ -625,14 +625,15 @@ public class BaseLayoutRule extends BaseViewRule {
     // ---- Resizing ----
 
     /** Creates a new {@link ResizeState} object to track resize state */
-    protected ResizeState createResizeState(INode layout, INode node) {
-        return new ResizeState(this, layout, node);
+    protected ResizeState createResizeState(INode layout, Object layoutView, INode node) {
+        return new ResizeState(this, layout, layoutView, node);
     }
 
     @Override
     public DropFeedback onResizeBegin(INode child, INode parent,
-            SegmentType horizontalEdge, SegmentType verticalEdge) {
-        ResizeState state = createResizeState(parent, child);
+            SegmentType horizontalEdge, SegmentType verticalEdge,
+            Object childView, Object parentView) {
+        ResizeState state = createResizeState(parent, parentView, child);
         state.horizontalEdgeType = horizontalEdge;
         state.verticalEdgeType = verticalEdge;
 
index dcf0f14..8e84904 100644 (file)
@@ -866,7 +866,7 @@ public class BaseViewRule implements IViewRule {
     // ==== Drag'n'drop support ====
 
     // By default Views do not accept drag'n'drop.
-    public DropFeedback onDropEnter(INode targetNode, IDragElement[] elements) {
+    public DropFeedback onDropEnter(INode targetNode, Object targetView, IDragElement[] elements) {
         return null;
     }
 
@@ -894,7 +894,7 @@ public class BaseViewRule implements IViewRule {
      * this case, defer the call to the parent layout and use the target node as
      * an indication of where to paste.
      */
-    public void onPaste(INode targetNode, IDragElement[] elements) {
+    public void onPaste(INode targetNode, Object targetView, IDragElement[] elements) {
         //
         INode parent = targetNode.getParent();
         if (parent != null) {
@@ -902,7 +902,8 @@ public class BaseViewRule implements IViewRule {
             IViewRule parentRule = mRulesEngine.loadRule(parentFqcn);
 
             if (parentRule instanceof BaseLayoutRule) {
-                ((BaseLayoutRule) parentRule).onPasteBeforeChild(parent, targetNode, elements);
+                ((BaseLayoutRule) parentRule).onPasteBeforeChild(parent, targetView, targetNode,
+                        elements);
             }
         }
     }
@@ -1010,13 +1011,13 @@ public class BaseViewRule implements IViewRule {
     }
 
     public void paintSelectionFeedback(IGraphics graphics, INode parentNode,
-            List<? extends INode> childNodes) {
+            List<? extends INode> childNodes, Object view) {
     }
 
     // ---- Resizing ----
 
     public DropFeedback onResizeBegin(INode child, INode parent, SegmentType horizontalEdge,
-            SegmentType verticalEdge) {
+            SegmentType verticalEdge, Object childView, Object parentView) {
         return null;
     }
 
index 884034f..090b9b3 100755 (executable)
@@ -29,12 +29,12 @@ import com.android.ide.common.api.IGraphics;
 import com.android.ide.common.api.INode;
 import com.android.ide.common.api.INodeHandler;
 import com.android.ide.common.api.IViewMetadata;
+import com.android.ide.common.api.IViewMetadata.FillPreference;
 import com.android.ide.common.api.IViewRule;
 import com.android.ide.common.api.InsertType;
-import com.android.ide.common.api.RuleAction;
 import com.android.ide.common.api.Point;
 import com.android.ide.common.api.Rect;
-import com.android.ide.common.api.IViewMetadata.FillPreference;
+import com.android.ide.common.api.RuleAction;
 import com.android.util.Pair;
 
 import java.util.List;
@@ -50,7 +50,8 @@ public class FrameLayoutRule extends BaseLayoutRule {
     // The FrameLayout accepts any drag'n'drop anywhere on its surface.
 
     @Override
-    public DropFeedback onDropEnter(INode targetNode, final IDragElement[] elements) {
+    public DropFeedback onDropEnter(INode targetNode, Object targetView,
+            final IDragElement[] elements) {
         if (elements.length == 0) {
             return null;
         }
index 47ea3d9..8b22bbf 100644 (file)
@@ -31,10 +31,10 @@ import com.android.ide.common.api.IMenuCallback;
 import com.android.ide.common.api.INode;
 import com.android.ide.common.api.INodeHandler;
 import com.android.ide.common.api.IViewRule;
-import com.android.ide.common.api.RuleAction;
-import com.android.ide.common.api.RuleAction.Choices;
 import com.android.ide.common.api.Point;
 import com.android.ide.common.api.Rect;
+import com.android.ide.common.api.RuleAction;
+import com.android.ide.common.api.RuleAction.Choices;
 import com.android.ide.common.api.SegmentType;
 import com.android.ide.common.layout.grid.GridDropHandler;
 import com.android.ide.common.layout.grid.GridLayoutPainter;
@@ -177,7 +177,7 @@ public class GridLayoutRule extends BaseLayoutRule {
                             return;
                         }
 
-                        GridModel grid = new GridModel(mRulesEngine, parentNode);
+                        GridModel grid = new GridModel(mRulesEngine, parentNode, null);
                         if (id.equals(ACTION_ADD_ROW)) {
                             grid.addRow(children);
                         } else if (id.equals(ACTION_REMOVE_ROW)) {
@@ -236,8 +236,8 @@ public class GridLayoutRule extends BaseLayoutRule {
     }
 
     @Override
-    public DropFeedback onDropEnter(INode targetNode, final IDragElement[] elements) {
-        GridDropHandler userData = new GridDropHandler(this, targetNode);
+    public DropFeedback onDropEnter(INode targetNode, Object targetView, IDragElement[] elements) {
+        GridDropHandler userData = new GridDropHandler(this, targetNode, targetView);
         IFeedbackPainter painter = GridLayoutPainter.createDropFeedbackPainter(this, elements);
         return new DropFeedback(userData, painter);
     }
@@ -292,7 +292,7 @@ public class GridLayoutRule extends BaseLayoutRule {
 
         // Attempt to clean up spacer objects for any newly-empty rows or columns
         // as the result of this deletion
-        GridModel grid = new GridModel(mRulesEngine, parent);
+        GridModel grid = new GridModel(mRulesEngine, parent, null);
         for (INode child : deleted) {
             // We don't care about deletion of spacers
             if (child.getFqcn().equals(FQCN_SPACE)) {
@@ -333,7 +333,7 @@ public class GridLayoutRule extends BaseLayoutRule {
     private GridModel getGrid(ResizeState resizeState) {
         GridModel grid = (GridModel) resizeState.clientData;
         if (grid == null) {
-            grid = new GridModel(mRulesEngine, resizeState.layout);
+            grid = new GridModel(mRulesEngine, resizeState.layout, resizeState.layoutView);
             resizeState.clientData = grid;
         }
 
@@ -412,16 +412,21 @@ public class GridLayoutRule extends BaseLayoutRule {
 
     @Override
     public void paintSelectionFeedback(IGraphics graphics, INode parentNode,
-            List<? extends INode> childNodes) {
-        super.paintSelectionFeedback(graphics, parentNode, childNodes);
+            List<? extends INode> childNodes, Object view) {
+        super.paintSelectionFeedback(graphics, parentNode, childNodes, view);
 
         if (sShowStructure) {
             // TODO: Cache the grid
-            GridLayoutPainter.paintStructure(DrawingStyle.GUIDELINE_DASHED,
-                    parentNode, graphics, new GridModel(mRulesEngine, parentNode));
+            if (view != null) {
+                GridLayoutPainter.paintStructure(view, DrawingStyle.GUIDELINE_DASHED,
+                        parentNode, graphics);
+            } else {
+                GridLayoutPainter.paintStructure(DrawingStyle.GUIDELINE_DASHED,
+                        parentNode, graphics, new GridModel(mRulesEngine, parentNode, view));
+            }
         } else if (sDebugGridLayout) {
             GridLayoutPainter.paintStructure(DrawingStyle.GRID,
-                    parentNode, graphics, new GridModel(mRulesEngine, parentNode));
+                    parentNode, graphics, new GridModel(mRulesEngine, parentNode, view));
         }
 
         // TBD: Highlight the cells around the selection, and display easy controls
index a0b56d2..999c6a0 100644 (file)
@@ -32,7 +32,7 @@ import com.android.ide.common.api.INode;
  */
 public abstract class IgnoredLayoutRule extends BaseLayoutRule {
     @Override
-    public DropFeedback onDropEnter(INode targetNode, IDragElement[] elements) {
+    public DropFeedback onDropEnter(INode targetNode, Object targetView, IDragElement[] elements) {
         // Do nothing; this layout rule corresponds to a layout that
         // should not be handled as a layout by the visual editor - usually
         // because some widget is extending a layout for implementation purposes
index 3639648..38b3db4 100644 (file)
@@ -264,7 +264,8 @@ public class LinearLayoutRule extends BaseLayoutRule {
     // ==== Drag'n'drop support ====
 
     @Override
-    public DropFeedback onDropEnter(final INode targetNode, final IDragElement[] elements) {
+    public DropFeedback onDropEnter(final INode targetNode, Object targetView,
+            final IDragElement[] elements) {
 
         if (elements.length == 0) {
             return null;
@@ -764,8 +765,9 @@ public class LinearLayoutRule extends BaseLayoutRule {
         /** List of nodes which should have their weights cleared */
         public List<INode> mClearWeights;
 
-        private LinearResizeState(BaseLayoutRule rule, INode layout, INode node) {
-            super(rule, layout, node);
+        private LinearResizeState(BaseLayoutRule rule, INode layout, Object layoutView,
+                INode node) {
+            super(rule, layout, layoutView, node);
 
             unweightedSizes = mRulesEngine.measureChildren(layout,
                     new IClientRulesEngine.AttributeFilter() {
@@ -843,8 +845,8 @@ public class LinearLayoutRule extends BaseLayoutRule {
     }
 
     @Override
-    protected ResizeState createResizeState(INode layout, INode node) {
-        return new LinearResizeState(this, layout, node);
+    protected ResizeState createResizeState(INode layout, Object layoutView, INode node) {
+        return new LinearResizeState(this, layout, layoutView, node);
     }
 
     protected void updateResizeState(LinearResizeState resizeState, final INode node, INode layout,
index 90952c9..841c590 100755 (executable)
@@ -50,9 +50,9 @@ import com.android.ide.common.api.INode.IAttribute;
 import com.android.ide.common.api.INodeHandler;
 import com.android.ide.common.api.IViewRule;
 import com.android.ide.common.api.InsertType;
-import com.android.ide.common.api.RuleAction;
 import com.android.ide.common.api.Point;
 import com.android.ide.common.api.Rect;
+import com.android.ide.common.api.RuleAction;
 import com.android.ide.common.api.SegmentType;
 import com.android.ide.common.layout.relative.ConstraintPainter;
 import com.android.ide.common.layout.relative.GuidelinePainter;
@@ -132,8 +132,8 @@ public class RelativeLayoutRule extends BaseLayoutRule {
 
     @Override
     public void paintSelectionFeedback(IGraphics graphics, INode parentNode,
-            List<? extends INode> childNodes) {
-        super.paintSelectionFeedback(graphics, parentNode, childNodes);
+            List<? extends INode> childNodes, Object view) {
+        super.paintSelectionFeedback(graphics, parentNode, childNodes, view);
 
         boolean showDependents = true;
         if (sShowStructure) {
@@ -150,7 +150,7 @@ public class RelativeLayoutRule extends BaseLayoutRule {
     // ==== Drag'n'drop support ====
 
     @Override
-    public DropFeedback onDropEnter(INode targetNode, final IDragElement[] elements) {
+    public DropFeedback onDropEnter(INode targetNode, Object targetView, IDragElement[] elements) {
         return new DropFeedback(new MoveHandler(targetNode, elements, mRulesEngine),
                 new GuidelinePainter());
     }
@@ -286,7 +286,8 @@ public class RelativeLayoutRule extends BaseLayoutRule {
 
     @Override
     public DropFeedback onResizeBegin(INode child, INode parent,
-            SegmentType horizontalEdgeType, SegmentType verticalEdgeType) {
+            SegmentType horizontalEdgeType, SegmentType verticalEdgeType,
+            Object childView, Object parentView) {
         ResizeHandler state = new ResizeHandler(parent, child, mRulesEngine,
                 horizontalEdgeType, verticalEdgeType);
         return new DropFeedback(state, new GuidelinePainter());
index d67a77c..e95957a 100644 (file)
@@ -78,17 +78,25 @@ class ResizeState {
     public int modifierMask;
 
     /**
+     * The actual view object for the layout containing the resizing operation,
+     * or null if not known
+     */
+    public Object layoutView;
+
+    /**
      * Constructs a new {@link ResizeState}
      *
      * @param rule the associated rule
      * @param layout the parent layout containing the resized node
+     * @param layoutView the actual View instance for the layout, or null if not known
      * @param node the node being resized
      */
-    ResizeState(BaseLayoutRule rule, INode layout, INode node) {
+    ResizeState(BaseLayoutRule rule, INode layout, Object layoutView, INode node) {
         mRule = rule;
 
         this.layout = layout;
         this.node = node;
+        this.layoutView = layoutView;
     }
 
     /**
index 4ae31b7..b5987c0 100644 (file)
@@ -181,7 +181,7 @@ public class TableLayoutRule extends LinearLayoutRule {
 
     @Override
     public DropFeedback onResizeBegin(INode child, INode parent, SegmentType horizontalEdge,
-            SegmentType verticalEdge) {
+            SegmentType verticalEdge, Object childView, Object parentView) {
         // Children of a table layout cannot set their widths (it is controlled by column
         // settings on the table). They can set their heights (though for TableRow, the
         // height is always wrap_content).
@@ -195,6 +195,7 @@ public class TableLayoutRule extends LinearLayoutRule {
         }
 
         // Allow resizing heights only
-        return super.onResizeBegin(child, parent, horizontalEdge, null /*verticalEdge*/);
+        return super.onResizeBegin(child, parent, horizontalEdge, null /*verticalEdge*/,
+                childView, parentView);
     }
 }
index dad71ed..f372866 100644 (file)
@@ -66,7 +66,7 @@ public class TableRowRule extends LinearLayoutRule {
 
     @Override
     public DropFeedback onResizeBegin(INode child, INode parent, SegmentType horizontalEdge,
-            SegmentType verticalEdge) {
+            SegmentType verticalEdge, Object childView, Object parentView) {
         // No resizing in TableRows; the width is *always* match_parent and the height is
         // *always* wrap_content.
         return null;
index 6274838..e09b7e3 100644 (file)
@@ -63,10 +63,11 @@ public class GridDropHandler {
      * Creates a new {@link GridDropHandler} for
      * @param gridLayoutRule the corresponding {@link GridLayoutRule}
      * @param layout the GridLayout node
+     * @param view the view instance of the grid layout receiving the drop
      */
-    public GridDropHandler(GridLayoutRule gridLayoutRule, INode layout) {
+    public GridDropHandler(GridLayoutRule gridLayoutRule, INode layout, Object view) {
         mRule = gridLayoutRule;
-        mGrid = new GridModel(mRule.getRulesEngine(), layout);
+        mGrid = new GridModel(mRule.getRulesEngine(), layout, view);
     }
 
     /**
index cf4e21b..add8706 100644 (file)
@@ -28,6 +28,7 @@ import com.android.ide.common.api.INode;
 import com.android.ide.common.api.Rect;
 import com.android.ide.common.api.SegmentType;
 import com.android.ide.common.layout.GridLayoutRule;
+import com.android.util.Pair;
 
 /**
  * Painter which paints feedback during drag, drop and resizing operations, as well as
@@ -298,4 +299,31 @@ public class GridLayoutPainter {
             mRule.drawElement(gc, first, offsetX, offsetY);
         }
     }
+
+    /**
+     * Paints the structure (the row and column boundaries) of the given GridLayout
+     *
+     * @param view the instance of the GridLayout whose structure should be painted
+     * @param style the drawing style to use for the cell boundaries
+     * @param layout the layout element
+     * @param gc the graphics context
+     */
+    public static void paintStructure(Object view, DrawingStyle style, INode layout,
+            IGraphics gc) {
+        Pair<int[],int[]> cellBounds = GridModel.getAxisBounds(view);
+        if (cellBounds != null) {
+            int[] xs = cellBounds.getFirst();
+            int[] ys = cellBounds.getSecond();
+            Rect b = layout.getBounds();
+            gc.useStyle(style);
+            for (int row = 0; row < ys.length; row++) {
+                int y = ys[row] + b.y;
+                gc.drawLine(b.x, y, b.x2(), y);
+            }
+            for (int column = 0; column < xs.length; column++) {
+                int x = xs[column] + b.x;
+                gc.drawLine(x, b.y, x, b.y2());
+            }
+        }
+    }
 }
index 787eda5..38d53bd 100644 (file)
@@ -44,10 +44,12 @@ import com.android.ide.common.api.IViewMetadata;
 import com.android.ide.common.api.Margins;
 import com.android.ide.common.api.Rect;
 import com.android.ide.common.layout.GridLayoutRule;
+import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.util.Pair;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -137,14 +139,21 @@ public class GridModel {
     private boolean stale;
 
     /**
+     * An actual instance of a GridLayout object that this grid model corresponds to.
+     */
+    private Object mViewObject;
+
+    /**
      * Constructs a {@link GridModel} for the given layout
      *
      * @param rulesEngine the associated rules engine
      * @param node the GridLayout node
+     * @param viewObject an actual GridLayout instance, or null
      */
-    public GridModel(IClientRulesEngine rulesEngine, INode node) {
+    public GridModel(IClientRulesEngine rulesEngine, INode node, Object viewObject) {
         mRulesEngine = rulesEngine;
         layout = node;
+        mViewObject = viewObject;
         loadFromXml();
     }
 
@@ -317,20 +326,8 @@ public class GridModel {
                 declaredRowCount == UNDEFINED ? children.length : declaredRowCount,
                 declaredColumnCount == UNDEFINED ? children.length : declaredColumnCount);
 
-        // Compute the actualColumnCount and actualRowCount. This -should- be
-        // as easy as declaredColumnCount + extraColumnsMap.size(),
-        // but the user doesn't *have* to declare a column count (or a row count)
-        // and we need both, so go and find the actual row and column maximums.
-        int maxColumn = 0;
-        int maxRow = 0;
-        for (ViewData view : mChildViews) {
-            maxColumn = max(maxColumn, view.column);
-            maxRow = max(maxRow, view.row);
-        }
-        actualColumnCount = maxColumn + 1;
-        actualRowCount = maxRow + 1;
-
         assignCellBounds();
+
         for (int i = 0; i <= actualRowCount; i++) {
             mBaselines[i] = UNDEFINED;
         }
@@ -498,21 +495,77 @@ public class GridModel {
     }
 
     /**
+     * Computes the positions of the column and row boundaries
+     */
+    private void assignCellBounds() {
+        if (!assignCellBoundsFromView()) {
+            assignCellBoundsFromBounds();
+        }
+        initializeMaxBounds();
+        mBaselines = new int[actualRowCount + 1];
+    }
+
+    /**
+     * Computes the positions of the column and row boundaries, using actual
+     * layout data from the associated GridLayout instance (stored in
+     * {@link #mViewObject})
+     */
+    private boolean assignCellBoundsFromView() {
+        if (mViewObject != null) {
+            Pair<int[], int[]> cellBounds = GridModel.getAxisBounds(mViewObject);
+            if (cellBounds != null) {
+                int[] xs = cellBounds.getFirst();
+                int[] ys = cellBounds.getSecond();
+
+                actualColumnCount = xs.length - 1;
+                actualRowCount = ys.length - 1;
+
+                Rect layoutBounds = layout.getBounds();
+                int layoutBoundsX = layoutBounds.x;
+                int layoutBoundsY = layoutBounds.y;
+                mLeft = new int[xs.length];
+                mTop = new int[ys.length];
+                for (int i = 0; i < xs.length; i++) {
+                    mLeft[i] = xs[i] + layoutBoundsX;
+                }
+                for (int i = 0; i < ys.length; i++) {
+                    mTop[i] = ys[i] + layoutBoundsY;
+                }
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
      * Computes the boundaries of the rows and columns by considering the bounds of the
      * children.
      */
-    private void assignCellBounds() {
+    private void assignCellBoundsFromBounds() {
         Rect layoutBounds = layout.getBounds();
+
+        // Compute the actualColumnCount and actualRowCount. This -should- be
+        // as easy as declaredColumnCount + extraColumnsMap.size(),
+        // but the user doesn't *have* to declare a column count (or a row count)
+        // and we need both, so go and find the actual row and column maximums.
+        int maxColumn = 0;
+        int maxRow = 0;
+        for (ViewData view : mChildViews) {
+            maxColumn = max(maxColumn, view.column);
+            maxRow = max(maxRow, view.row);
+        }
+        actualColumnCount = maxColumn + 1;
+        actualRowCount = maxRow + 1;
+
         mLeft = new int[actualColumnCount + 1];
-        mMaxRight = new int[actualColumnCount + 1];
         for (int i = 1; i < actualColumnCount; i++) {
             mLeft[i] = UNDEFINED;
         }
         mLeft[0] = layoutBounds.x;
         mLeft[actualColumnCount] = layoutBounds.x2();
         mTop = new int[actualRowCount + 1];
-        mMaxBottom = new int[actualRowCount + 1];
-        mBaselines = new int[actualRowCount + 1];
         for (int i = 1; i < actualRowCount; i++) {
             mTop[i] = UNDEFINED;
         }
@@ -537,27 +590,6 @@ public class GridModel {
             } else {
                 mTop[row] = Math.min(bounds.y, mTop[row]);
             }
-
-            if (!view.isSpacer()) {
-                int x2 = bounds.x2();
-                int y2 = bounds.y2();
-                int targetColumn = min(actualColumnCount - 1, column + view.columnSpan - 1);
-                int targetRow = min(actualRowCount - 1, row + view.rowSpan - 1);
-                IViewMetadata metadata = mRulesEngine.getMetadata(view.node.getFqcn());
-                if (metadata != null) {
-                    Margins insets = metadata.getInsets();
-                    if (insets != null) {
-                        x2 -= insets.right;
-                        y2 -= insets.bottom;
-                    }
-                }
-                if (mMaxRight[targetColumn] < x2) {
-                    mMaxRight[targetColumn] = x2;
-                }
-                if (mMaxBottom[targetRow] < y2) {
-                    mMaxBottom[targetRow] = y2;
-                }
-            }
         }
 
         // Ensure that any empty columns/rows have a valid boundary value; for now,
@@ -600,6 +632,78 @@ public class GridModel {
     }
 
     /**
+     * Determine, for each row and column, what the largest x and y edges are
+     * within that row or column. This is used to find a natural split point to
+     * suggest when adding something "to the right of" or "below" another view.
+     */
+    private void initializeMaxBounds() {
+        mMaxRight = new int[actualColumnCount + 1];
+        mMaxBottom = new int[actualRowCount + 1];
+
+        for (ViewData view : mChildViews) {
+            Rect bounds = view.node.getBounds();
+            if (!bounds.isValid()) {
+                continue;
+            }
+
+            if (!view.isSpacer()) {
+                int x2 = bounds.x2();
+                int y2 = bounds.y2();
+                int column = view.column;
+                int row = view.row;
+                int targetColumn = min(actualColumnCount - 1,
+                        column + view.columnSpan - 1);
+                int targetRow = min(actualRowCount - 1, row + view.rowSpan - 1);
+                IViewMetadata metadata = mRulesEngine.getMetadata(view.node.getFqcn());
+                if (metadata != null) {
+                    Margins insets = metadata.getInsets();
+                    if (insets != null) {
+                        x2 -= insets.right;
+                        y2 -= insets.bottom;
+                    }
+                }
+                if (mMaxRight[targetColumn] < x2) {
+                    mMaxRight[targetColumn] = x2;
+                }
+                if (mMaxBottom[targetRow] < y2) {
+                    mMaxBottom[targetRow] = y2;
+                }
+            }
+        }
+    }
+
+    /**
+     * Looks up the x[] and y[] locations of the columns and rows in the given GridLayout
+     * instance.
+     *
+     * @param view the GridLayout object, which should already have performed layout
+     * @return a pair of x[] and y[] integer arrays, or null if it could not be found
+     */
+    public static Pair<int[], int[]> getAxisBounds(Object view) {
+        try {
+            Class<?> clz = view.getClass();
+            Field horizontalAxis = clz.getDeclaredField("mHorizontalAxis"); //$NON-NLS-1$
+            Field verticalAxis = clz.getDeclaredField("mVerticalAxis"); //$NON-NLS-1$
+            horizontalAxis.setAccessible(true);
+            verticalAxis.setAccessible(true);
+            Object horizontal = horizontalAxis.get(view);
+            Object vertical = verticalAxis.get(view);
+            Field locations = horizontal.getClass().getDeclaredField("locations"); //$NON-NLS-1$
+            assert locations.getType().isArray() : locations.getType();
+            locations.setAccessible(true);
+            Object horizontalLocations = locations.get(horizontal);
+            Object verticalLocations = locations.get(vertical);
+            int[] xs = (int[]) horizontalLocations;
+            int[] ys = (int[]) verticalLocations;
+            return Pair.of(xs, ys);
+        } catch (Throwable t) {
+            AdtPlugin.log(t, null); // TODO: Add to API!
+        }
+
+        return null;
+    }
+
+    /**
      * Add a new column.
      *
      * @param selectedChildren if null or empty, add the column at the end of the grid,
index c778271..2c91b74 100644 (file)
@@ -279,7 +279,7 @@ public class ClipboardSupport {
 
         NodeProxy targetNode = mCanvas.getNodeFactory().create(target);
 
-        mCanvas.getRulesEngine().callOnPaste(targetNode, pasted);
+        mCanvas.getRulesEngine().callOnPaste(targetNode, target.getViewObject(), pasted);
     }
 
     /**
index 43bb3ff..805e6f6 100644 (file)
@@ -626,7 +626,7 @@ public class MoveGesture extends DropGesture {
                      targetVi = targetVi.getParent()) {
                     targetNode = mCanvas.getNodeFactory().create(targetVi);
                     df = mCanvas.getRulesEngine().callOnDropEnter(targetNode,
-                                                                  mCurrentDragElements);
+                            targetVi.getViewObject(), mCurrentDragElements);
 
                     if (df != null) {
                         // We should also dispatch an onDropMove() call to the initial enter
index ec6c7c6..623c3a2 100644 (file)
@@ -78,8 +78,13 @@ public class ResizeGesture extends Gesture {
 
         RulesEngine rulesEngine = mCanvas.getRulesEngine();
         Rect newBounds = getNewBounds(pos);
+        ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy();
+        CanvasViewInfo childInfo = viewHierarchy.findViewInfoFor(mChildNode);
+        CanvasViewInfo parentInfo = viewHierarchy.findViewInfoFor(mParentNode);
+        Object childView = childInfo != null ? childInfo.getViewObject() : null;
+        Object parentView = parentInfo != null ? parentInfo.getViewObject() : null;
         mFeedback = rulesEngine.callOnResizeBegin(mChildNode, mParentNode, newBounds,
-                mHorizontalEdge, mVerticalEdge);
+                mHorizontalEdge, mVerticalEdge, childView, parentView);
         update(pos);
         mCanvas.getGestureManager().updateMessage(mFeedback);
     }
index 0186212..ff84e79 100644 (file)
@@ -99,7 +99,7 @@ public class SelectionOverlay extends Overlay {
                 if (root != null) {
                     NodeProxy parent = mCanvas.getNodeFactory().create(root);
                     rulesEngine.callPaintSelectionFeedback(gcWrapper,
-                            parent, Collections.<INode>emptyList());
+                            parent, Collections.<INode>emptyList(), root.getViewObject());
                 }
             }
 
@@ -114,7 +114,7 @@ public class SelectionOverlay extends Overlay {
             if (root != null) {
                 NodeProxy parent = mCanvas.getNodeFactory().create(root);
                 rulesEngine.callPaintSelectionFeedback(gcWrapper,
-                        parent, Collections.<INode>emptyList());
+                        parent, Collections.<INode>emptyList(), root.getViewObject());
             }
         }
     }
@@ -167,6 +167,7 @@ public class SelectionOverlay extends Overlay {
                 parents.add(parentNode);
             }
         }
+        ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy();
         for (INode parent : parents) {
             List<INode> children = new ArrayList<INode>();
             for (INode node : nodes) {
@@ -175,8 +176,11 @@ public class SelectionOverlay extends Overlay {
                     children.add(node);
                 }
             }
+            CanvasViewInfo viewInfo = viewHierarchy.findViewInfoFor((NodeProxy) parent);
+            Object view = viewInfo != null ? viewInfo.getViewObject() : null;
+
             rulesEngine.callPaintSelectionFeedback(gcWrapper,
-                    (NodeProxy) parent, children);
+                    (NodeProxy) parent, children, view);
         }
     }
 
index 9cf683a..e6848cb 100755 (executable)
@@ -25,9 +25,9 @@ import com.android.ide.common.api.IGraphics;
 import com.android.ide.common.api.INode;
 import com.android.ide.common.api.IViewRule;
 import com.android.ide.common.api.InsertType;
-import com.android.ide.common.api.RuleAction;
 import com.android.ide.common.api.Point;
 import com.android.ide.common.api.Rect;
+import com.android.ide.common.api.RuleAction;
 import com.android.ide.common.api.SegmentType;
 import com.android.ide.common.layout.ViewRule;
 import com.android.ide.eclipse.adt.AdtPlugin;
@@ -298,13 +298,13 @@ public class RulesEngine {
     }
 
     public void callPaintSelectionFeedback(GCWrapper gcWrapper, NodeProxy parentNode,
-            List<? extends INode> childNodes) {
+            List<? extends INode> childNodes, Object view) {
         // try to find a rule for this element's FQCN
         IViewRule rule = loadRule(parentNode.getNode());
 
         if (rule != null) {
             try {
-                rule.paintSelectionFeedback(gcWrapper, parentNode, childNodes);
+                rule.paintSelectionFeedback(gcWrapper, parentNode, childNodes, view);
 
             } catch (Exception e) {
                 AdtPlugin.log(e, "%s.callPaintSelectionFeedback() failed: %s",
@@ -321,13 +321,13 @@ public class RulesEngine {
      * Followed by a paint.
      */
     public DropFeedback callOnDropEnter(NodeProxy targetNode,
-            IDragElement[] elements) {
+            Object targetView, IDragElement[] elements) {
         // try to find a rule for this element's FQCN
         IViewRule rule = loadRule(targetNode.getNode());
 
         if (rule != null) {
             try {
-                return rule.onDropEnter(targetNode, elements);
+                return rule.onDropEnter(targetNode, targetView, elements);
 
             } catch (Exception e) {
                 AdtPlugin.log(e, "%s.onDropEnter() failed: %s",
@@ -430,16 +430,18 @@ public class RulesEngine {
      * Called when pasting elements in an existing document on the selected target.
      *
      * @param targetNode The first node selected.
+     * @param targetView The view object for the target node, or null if not known
      * @param pastedElements The elements being pasted.
      */
-    public void callOnPaste(NodeProxy targetNode, SimpleElement[] pastedElements) {
+    public void callOnPaste(NodeProxy targetNode, Object targetView,
+            SimpleElement[] pastedElements) {
         // try to find a rule for this element's FQCN
         IViewRule rule = loadRule(targetNode.getNode());
 
         if (rule != null) {
             try {
                 mInsertType = InsertType.PASTE;
-                rule.onPaste(targetNode, pastedElements);
+                rule.onPaste(targetNode, targetView, pastedElements);
 
             } catch (Exception e) {
                 AdtPlugin.log(e, "%s.onPaste() failed: %s",
@@ -452,12 +454,14 @@ public class RulesEngine {
     // ---- Resize operations ----
 
     public DropFeedback callOnResizeBegin(NodeProxy child, NodeProxy parent, Rect newBounds,
-            SegmentType horizontalEdge, SegmentType verticalEdge) {
+            SegmentType horizontalEdge, SegmentType verticalEdge, Object childView,
+            Object parentView) {
         IViewRule rule = loadRule(parent.getNode());
 
         if (rule != null) {
             try {
-                return rule.onResizeBegin(child, parent, horizontalEdge, verticalEdge);
+                return rule.onResizeBegin(child, parent, horizontalEdge, verticalEdge,
+                        childView, parentView);
             } catch (Exception e) {
                 AdtPlugin.log(e, "%s.onResizeBegin() failed: %s", rule.getClass().getSimpleName(),
                         e.toString());
index c325a40..4454e1b 100644 (file)
@@ -73,7 +73,7 @@ public class LayoutTestBase extends TestCase {
                 "android.widget.Button", dragBounds).id(draggedButtonId));
 
         // Enter target
-        DropFeedback feedback = rule.onDropEnter(targetNode, elements);
+        DropFeedback feedback = rule.onDropEnter(targetNode, null/*targetView*/, elements);
         assertNotNull(feedback);
         assertFalse(feedback.invalidTarget);
         assertNotNull(feedback.painter);
index 4a0fc6e..a703a6f 100644 (file)
@@ -57,7 +57,7 @@ public class LinearLayoutRuleTest extends LayoutTestBase {
                 "android.widget.Button", dragBounds).id("@+id/Button01"));
 
         // Enter target
-        DropFeedback feedback = rule.onDropEnter(targetNode, elements);
+        DropFeedback feedback = rule.onDropEnter(targetNode, null/*targetView*/, elements);
         assertNotNull(feedback);
         assertFalse(feedback.invalidTarget);
         assertNotNull(feedback.painter);
index c20f550..daa487e 100644 (file)
@@ -43,7 +43,7 @@ public class ZoomControlsRuleTest extends LayoutTestBase {
         ZoomControlsRule rule = new ZoomControlsRule();
 
         // Enter target
-        DropFeedback feedback = rule.onDropEnter(layout, elements);
+        DropFeedback feedback = rule.onDropEnter(layout, null/*targetView*/, elements);
         // Zoom controls don't respond to drags
         assertNull(feedback);
     }
index 44eb443..91533b0 100644 (file)
@@ -38,7 +38,7 @@ public class GridModelTest extends LayoutTestBase {
         TestNode targetNode = TestNode.create("android.widget.GridLayout").id("@+id/GridLayout1")
                 .bounds(new Rect(0, 0, 240, 480)).set(ANDROID_URI, ATTR_COLUMN_COUNT, "3");
 
-        GridModel model = new GridModel(null, targetNode);
+        GridModel model = new GridModel(null, targetNode, null);
         assertEquals(3, model.declaredColumnCount);
         assertEquals(1, model.actualColumnCount);
         assertEquals(1, model.actualRowCount);
@@ -48,7 +48,7 @@ public class GridModelTest extends LayoutTestBase {
         targetNode.add(TestNode.create(FQCN_BUTTON).id("@+id/Button3"));
         targetNode.add(TestNode.create(FQCN_BUTTON).id("@+id/Button4"));
 
-        model = new GridModel(null, targetNode);
+        model = new GridModel(null, targetNode, null);
         assertEquals(3, model.declaredColumnCount);
         assertEquals(3, model.actualColumnCount);
         assertEquals(2, model.actualRowCount);
index d29ef71..c11f500 100755 (executable)
@@ -134,26 +134,44 @@ public interface IViewRule {
      * @param graphics the graphics context to paint into
      * @param parentNode the parent layout node
      * @param childNodes the child nodes selected in the parent layout
+     * @param view An instance of the view to be painted (may be null)
      */
     void paintSelectionFeedback(IGraphics graphics, INode parentNode,
-            List<? extends INode> childNodes);
+            List<? extends INode> childNodes, Object view);
 
     // ==== Drag'n'drop support ====
 
     /**
-     * Called when the d'n'd starts dragging over the target node.
-     * If interested, returns a DropFeedback passed to onDrop/Move/Leave/Paint.
-     * If not interested in drop, return null.
-     * Followed by a paint.
+     * Called when the d'n'd starts dragging over the target node. If
+     * interested, returns a DropFeedback passed to onDrop/Move/Leave/Paint. If
+     * not interested in drop, return null. Followed by a paint.
+     *
+     * @param targetNode the {@link INode} for the target layout receiving a
+     *            drop event
+     * @param targetView the corresponding View object for the target layout, or
+     *            null if not known
+     * @param elements an array of {@link IDragElement} element descriptors for
+     *            the dragged views
+     * @return a {@link DropFeedback} object with drop state (which will be
+     *         supplied to a follow-up {@link #onDropMove} call), or null if the
+     *         drop should be ignored
      */
-    DropFeedback onDropEnter(INode targetNode,
-            IDragElement[] elements);
+    DropFeedback onDropEnter(INode targetNode, Object targetView, IDragElement[] elements);
 
     /**
-     * Called after onDropEnter.
-     * Returns a DropFeedback passed to onDrop/Move/Leave/Paint (typically same
-     * as input one).
-     * Returning null will invalidate the drop workflow.
+     * Called after onDropEnter. Returns a DropFeedback passed to
+     * onDrop/Move/Leave/Paint (typically same as input one). Returning null
+     * will invalidate the drop workflow.
+     *
+     * @param targetNode the {@link INode} for the target layout receiving a
+     *            drop event
+     * @param elements an array of {@link IDragElement} element descriptors for
+     *            the dragged views
+     * @param feedback the {@link DropFeedback} object created by
+     *            {@link #onDropEnter(INode, Object, IDragElement[])}
+     * @param where the current mouse drag position
+     * @return a {@link DropFeedback} (which is usually just the same one passed
+     *         into this method)
      */
     DropFeedback onDropMove(INode targetNode,
             IDragElement[] elements,
@@ -176,6 +194,12 @@ public interface IViewRule {
      * <i>...user leaves canvas...</i>
      * - onDropLeave(node2, feedback2)
      * </pre>
+     * @param targetNode the {@link INode} for the target layout receiving a
+     *            drop event
+     * @param elements an array of {@link IDragElement} element descriptors for
+     *            the dragged views
+     * @param feedback the {@link DropFeedback} object created by
+     *            {@link #onDropEnter(INode, Object, IDragElement[])}
      */
     void onDropLeave(INode targetNode,
             IDragElement[] elements,
@@ -186,7 +210,15 @@ public interface IViewRule {
      * <p>
      * TODO: Document that this method will be called under an edit lock so you can
      * directly manipulate the nodes without wrapping it in an
-     * {@link INode#editXml(String, INodeHandler)} call
+     * {@link INode#editXml(String, INodeHandler)} call.
+     *
+     * @param targetNode the {@link INode} for the target layout receiving a
+     *            drop event
+     * @param elements an array of {@link IDragElement} element descriptors for
+     *            the dragged views
+     * @param feedback the {@link DropFeedback} object created by
+     *            {@link #onDropEnter(INode, Object, IDragElement[])}
+     * @param where the mouse drop position
      */
     void onDropped(INode targetNode,
             IDragElement[] elements,
@@ -197,9 +229,11 @@ public interface IViewRule {
      * Called when pasting elements in an existing document on the selected target.
      *
      * @param targetNode The first node selected.
+     * @param targetView the corresponding View object for the target layout, or
+     *            null if not known
      * @param pastedElements The elements being pasted.
      */
-    void onPaste(INode targetNode, IDragElement[] pastedElements);
+    void onPaste(INode targetNode, Object targetView, IDragElement[] pastedElements);
 
     // ==== XML Creation ====
 
@@ -261,11 +295,15 @@ public interface IViewRule {
      * @param parent the layout containing the child
      * @param horizEdge The horizontal edge being resized, or null
      * @param verticalEdge the vertical edge being resized, or null
+     * @param childView an instance of the resized node view, or null if not known
+     * @param parentView an instance of the parent layout view object, or null if not known
      * @return a {@link DropFeedback} object which performs an update painter callback
      *         etc.
      */
-    DropFeedback onResizeBegin(INode child, INode parent,
-            SegmentType horizEdge, SegmentType verticalEdge);
+    DropFeedback onResizeBegin(
+            INode child, INode parent,
+            SegmentType horizEdge, SegmentType verticalEdge,
+            Object childView, Object parentView);
 
     /**
      * Called by the IDE on the parent layout when a child widget is being resized. This