• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.common.layout.grid;
17 
18 import static com.android.ide.common.layout.GridLayoutRule.GRID_SIZE;
19 import static com.android.ide.common.layout.GridLayoutRule.MARGIN_SIZE;
20 import static com.android.ide.common.layout.GridLayoutRule.MAX_CELL_DIFFERENCE;
21 import static com.android.ide.common.layout.GridLayoutRule.SHORT_GAP_DP;
22 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
23 import static com.android.ide.common.layout.LayoutConstants.ATTR_COLUMN_COUNT;
24 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_COLUMN;
25 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_COLUMN_SPAN;
26 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_GRAVITY;
27 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW;
28 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW_SPAN;
29 import static com.android.ide.common.layout.LayoutConstants.VALUE_1;
30 import static com.android.ide.common.layout.LayoutConstants.VALUE_BOTTOM;
31 import static com.android.ide.common.layout.LayoutConstants.VALUE_CENTER_HORIZONTAL;
32 import static com.android.ide.common.layout.LayoutConstants.VALUE_RIGHT;
33 import static com.android.ide.common.layout.grid.GridModel.UNDEFINED;
34 import static java.lang.Math.abs;
35 
36 import com.android.ide.common.api.DropFeedback;
37 import com.android.ide.common.api.IDragElement;
38 import com.android.ide.common.api.INode;
39 import com.android.ide.common.api.IViewMetadata;
40 import com.android.ide.common.api.Margins;
41 import com.android.ide.common.api.Point;
42 import com.android.ide.common.api.Rect;
43 import com.android.ide.common.api.SegmentType;
44 import com.android.ide.common.layout.BaseLayoutRule;
45 import com.android.ide.common.layout.GridLayoutRule;
46 import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
47 
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.List;
52 
53 /**
54  * The {@link GridDropHandler} handles drag and drop operations into and within a
55  * GridLayout, computing guidelines, handling drops to edit the grid model, and so on.
56  */
57 public class GridDropHandler {
58     private final GridModel mGrid;
59     private final GridLayoutRule mRule;
60     private GridMatch mColumnMatch;
61     private GridMatch mRowMatch;
62 
63     /**
64      * Creates a new {@link GridDropHandler} for
65      * @param gridLayoutRule the corresponding {@link GridLayoutRule}
66      * @param layout the GridLayout node
67      * @param view the view instance of the grid layout receiving the drop
68      */
GridDropHandler(GridLayoutRule gridLayoutRule, INode layout, Object view)69     public GridDropHandler(GridLayoutRule gridLayoutRule, INode layout, Object view) {
70         mRule = gridLayoutRule;
71         mGrid = new GridModel(mRule.getRulesEngine(), layout, view);
72     }
73 
74     /**
75      * Computes the best horizontal and vertical matches for a drag to the given position.
76      *
77      * @param feedback a {@link DropFeedback} object containing drag state like the drag
78      *            bounds and the drag baseline
79      * @param p the mouse position
80      */
computeMatches(DropFeedback feedback, Point p)81     public void computeMatches(DropFeedback feedback, Point p) {
82         mRowMatch = mColumnMatch = null;
83 
84         Rect bounds = mGrid.layout.getBounds();
85         int x1 = p.x;
86         int y1 = p.y;
87 
88         if (!GridLayoutRule.sGridMode) {
89             Rect dragBounds = feedback.dragBounds;
90             if (dragBounds != null) {
91                 // Sometimes the items are centered under the mouse so
92                 // offset by the top left corner distance
93                 x1 += dragBounds.x;
94                 y1 += dragBounds.y;
95             }
96 
97             int w = dragBounds != null ? dragBounds.w : 0;
98             int h = dragBounds != null ? dragBounds.h : 0;
99             int x2 = x1 + w;
100             int y2 = y1 + h;
101 
102             if (x2 < bounds.x || y2 < bounds.y || x1 > bounds.x2() || y1 > bounds.y2()) {
103                 return;
104             }
105 
106             List<GridMatch> columnMatches = new ArrayList<GridMatch>();
107             List<GridMatch> rowMatches = new ArrayList<GridMatch>();
108             int max = BaseLayoutRule.getMaxMatchDistance();
109 
110             // Column matches:
111             addLeftSideMatch(x1, columnMatches, max);
112             addRightSideMatch(x2, columnMatches, max);
113             addCenterColumnMatch(bounds, x1, y1, x2, y2, columnMatches, max);
114 
115             // Row matches:
116             int row = mGrid.getClosestRow(y1);
117             int rowY = mGrid.getRowY(row);
118             addTopMatch(y1, rowMatches, max, row, rowY);
119             addBaselineMatch(feedback.dragBaseline, y1, rowMatches, max, row, rowY);
120             addBottomMatch(y2, rowMatches, max);
121 
122             // Look for gap-matches: Predefined spacing between widgets.
123             // TODO: Make this use metadata for predefined spacing between
124             // pairs of types of components. For example, buttons have certain
125             // inserts in their 9-patch files (depending on the theme) that should
126             // be considered and subtracted from the overall proposed distance!
127             addColumnGapMatch(bounds, x1, x2, columnMatches, max);
128             addRowGapMatch(bounds, y1, y2, rowMatches, max);
129 
130             // Fallback: Split existing cell. Also do snap-to-grid.
131             if (GridLayoutRule.sSnapToGrid) {
132                 x1 = ((x1 - MARGIN_SIZE - bounds.x) / GRID_SIZE) * GRID_SIZE
133                         + MARGIN_SIZE + bounds.x;
134                 y1 = ((y1 - MARGIN_SIZE - bounds.y) / GRID_SIZE) * GRID_SIZE
135                         + MARGIN_SIZE + bounds.y;
136                 x2 = x1 + w;
137                 y2 = y1 + h;
138             }
139 
140 
141             if (columnMatches.size() == 0) {
142                 // Split the current cell since we have no matches
143                 // TODO: Decide whether it should be gravity left or right...
144                 columnMatches.add(new GridMatch(SegmentType.LEFT, 0, x1, mGrid.getColumn(x1),
145                         true /* createCell */, UNDEFINED));
146             }
147             if (rowMatches.size() == 0) {
148                 rowMatches.add(new GridMatch(SegmentType.TOP, 0, y1, mGrid.getRow(y1),
149                         true /* createCell */, UNDEFINED));
150             }
151 
152             // Pick best matches
153             Collections.sort(rowMatches);
154             Collections.sort(columnMatches);
155 
156             mColumnMatch = null;
157             mRowMatch = null;
158             String columnDescription = null;
159             String rowDescription = null;
160             if (columnMatches.size() > 0) {
161                 mColumnMatch = columnMatches.get(0);
162                 columnDescription = mColumnMatch.getDisplayName(mGrid.layout);
163             }
164             if (rowMatches.size() > 0) {
165                 mRowMatch = rowMatches.get(0);
166                 rowDescription = mRowMatch.getDisplayName(mGrid.layout);
167             }
168             if (columnDescription != null) {
169                 if (rowDescription != null) {
170                     feedback.tooltip = columnDescription + '\n' + rowDescription;
171                 } else {
172                     feedback.tooltip = columnDescription;
173                 }
174             } else if (rowDescription != null) {
175                 feedback.tooltip = rowDescription;
176             } else {
177                 feedback.tooltip = null;
178             }
179 
180             feedback.invalidTarget = mColumnMatch == null || mRowMatch == null;
181         } else {
182             // Find which cell we're inside.
183 
184             // TODO: Find out where within the cell we are, and offer to tweak the gravity
185             // based on the position.
186             int column = mGrid.getColumn(x1);
187             int row = mGrid.getRow(y1);
188 
189             int leftDistance = mGrid.getColumnDistance(column, x1);
190             int rightDistance = mGrid.getColumnDistance(column + 1, x1);
191             int topDistance = mGrid.getRowDistance(row, y1);
192             int bottomDistance = mGrid.getRowDistance(row + 1, y1);
193 
194             int SLOP = 2;
195             int radius = mRule.getNewCellSize();
196             if (rightDistance < radius + SLOP) {
197                 column++;
198                 leftDistance = rightDistance;
199             }
200             if (bottomDistance < radius + SLOP) {
201                 row++;
202                 topDistance = bottomDistance;
203             }
204 
205             boolean matchLeft = leftDistance < radius + SLOP;
206             boolean matchTop = topDistance < radius + SLOP;
207 
208             mColumnMatch = new GridMatch(SegmentType.LEFT, 0, x1, column, matchLeft, 0);
209             mRowMatch = new GridMatch(SegmentType.TOP, 0, y1, row, matchTop, 0);
210         }
211     }
212 
213     /**
214      * Adds a match to align the left edge with some other edge.
215      */
216     private void addLeftSideMatch(int x1, List<GridMatch> columnMatches, int max) {
217         int column = mGrid.getClosestColumn(x1);
218         int columnX = mGrid.getColumnX(column);
219         int distance = abs(columnX - x1);
220         if (distance <= max) {
221             columnMatches.add(new GridMatch(SegmentType.LEFT, distance, columnX, column,
222                     false, UNDEFINED));
223         }
224     }
225 
226     /**
227      * Adds a match to align the right edge with some other edge.
228      */
229     private void addRightSideMatch(int x2, List<GridMatch> columnMatches, int max) {
230         // TODO: Only match the right hand side if the drag bounds fit fully within the
231         // cell! Ditto for match below.
232         int columnRight = mGrid.getClosestColumn(x2);
233         int rightDistance = mGrid.getColumnDistance(columnRight, x2);
234         if (rightDistance < max) {
235             columnMatches.add(new GridMatch(SegmentType.RIGHT, rightDistance,
236                     mGrid.getColumnX(columnRight), columnRight, false, UNDEFINED));
237         }
238     }
239 
240     /**
241      * Adds a horizontal match with the center axis of the GridLayout
242      */
243     private void addCenterColumnMatch(Rect bounds, int x1, int y1, int x2, int y2,
244             List<GridMatch> columnMatches, int max) {
245         Collection<INode> intersectsRow = mGrid.getIntersectsRow(y1, y2);
246         if (intersectsRow.size() == 0) {
247             // Offer centering on this row since there isn't anything there
248             int matchedLine = bounds.centerX();
249             int distance = abs((x1 + x2) / 2 - matchedLine);
250             if (distance <= 2 * max) {
251                 boolean createCell = false; // always just put in column 0
252                 columnMatches.add(new GridMatch(SegmentType.CENTER_HORIZONTAL, distance,
253                         matchedLine, 0 /* column */, createCell, UNDEFINED));
254             }
255         }
256     }
257 
258     /**
259      * Adds a match to align the top edge with some other edge.
260      */
261     private void addTopMatch(int y1, List<GridMatch> rowMatches, int max, int row, int rowY) {
262         int distance = mGrid.getRowDistance(row, y1);
263         if (distance <= max) {
264             rowMatches.add(new GridMatch(SegmentType.TOP, distance, rowY, row, false,
265                     UNDEFINED));
266         }
267     }
268 
269     /**
270      * Adds a match to align the bottom edge with some other edge.
271      */
272     private void addBottomMatch(int y2, List<GridMatch> rowMatches, int max) {
273         int rowBottom = mGrid.getClosestRow(y2);
274         int distance = mGrid.getRowDistance(rowBottom, y2);
275         if (distance < max) {
276             int rowY = mGrid.getRowY(rowBottom);
277             rowMatches.add(new GridMatch(SegmentType.BOTTOM, distance, rowY,
278                     rowBottom, false, UNDEFINED));
279         }
280     }
281 
282     /**
283      * Adds a baseline match, if applicable.
284      */
285     private void addBaselineMatch(int dragBaseline, int y1, List<GridMatch> rowMatches, int max,
286             int row, int rowY) {
287         int dragBaselineY = y1 + dragBaseline;
288         int rowBaseline = mGrid.getBaseline(row);
289         if (rowBaseline != -1) {
290             int rowBaselineY = rowY + rowBaseline;
291             int distance = abs(dragBaselineY - rowBaselineY);
292             if (distance < max) {
293                 rowMatches.add(new GridMatch(SegmentType.BASELINE, distance, rowBaselineY, row,
294                         false, UNDEFINED));
295             }
296         }
297     }
298 
299     /**
300      * Computes a horizontal "gap" match - a preferred distance from the nearest edge,
301      * including margin edges
302      */
303     private void addColumnGapMatch(Rect bounds, int x1, int x2, List<GridMatch> columnMatches,
304             int max) {
305         if (x1 < bounds.x + MARGIN_SIZE + max) {
306             int matchedLine = bounds.x + MARGIN_SIZE;
307             int distance = abs(matchedLine - x1);
308             if (distance <= max) {
309                 boolean createCell = mGrid.getColumnX(mGrid.getColumn(matchedLine)) != matchedLine;
310                 columnMatches.add(new GridMatch(SegmentType.LEFT, distance, matchedLine,
311                         0, createCell, MARGIN_SIZE));
312             }
313         } else if (x2 > bounds.x2() - MARGIN_SIZE - max) {
314             int matchedLine = bounds.x2() - MARGIN_SIZE;
315             int distance = abs(matchedLine - x2);
316             if (distance <= max) {
317                 // This does not yet work properly; we need to use columnWeights to achieve this
318                 //boolean createCell = mGrid.getColumnX(mGrid.getColumn(matchedLine)) != matchedLine;
319                 //columnMatches.add(new GridMatch(SegmentType.RIGHT, distance, matchedLine,
320                 //        mGrid.actualColumnCount - 1, createCell, MARGIN_SIZE));
321             }
322         } else {
323             int columnRight = mGrid.getColumn(x1 - SHORT_GAP_DP);
324             int columnX = mGrid.getColumnMaxX(columnRight);
325             int matchedLine = columnX + SHORT_GAP_DP;
326             int distance = abs(matchedLine - x1);
327             if (distance <= max) {
328                 boolean createCell = mGrid.getColumnX(mGrid.getColumn(matchedLine)) != matchedLine;
329                 columnMatches.add(new GridMatch(SegmentType.LEFT, distance, matchedLine,
330                         columnRight, createCell, SHORT_GAP_DP));
331             }
332 
333             // Add a column directly adjacent (no gap)
334             columnRight = mGrid.getColumn(x1);
335             columnX = mGrid.getColumnMaxX(columnRight);
336             matchedLine = columnX;
337             distance = abs(matchedLine - x1);
338 
339             // Let's say you have this arrangement:
340             //     [button1][button2]
341             // This is two columns, where the right hand side edge of column 1 is
342             // flush with the left side edge of column 2, because in fact the width of
343             // button1 is what defines the width of column 1, and that in turn is what
344             // defines the left side position of column 2.
345             //
346             // In this case we don't want to consider inserting a new column at the
347             // right hand side of button1 a better match than matching left on column 2.
348             // Therefore, to ensure that this doesn't happen, we "penalize" right column
349             // matches such that they don't get preferential treatment when the matching
350             // line is on the left side of the column.
351             distance += 2;
352 
353             if (distance <= max) {
354                 boolean createCell = mGrid.getColumnX(mGrid.getColumn(matchedLine)) != matchedLine;
355                 columnMatches.add(new GridMatch(SegmentType.LEFT, distance, matchedLine,
356                         columnRight, createCell, 0));
357             }
358         }
359     }
360 
361     /**
362      * Computes a vertical "gap" match - a preferred distance from the nearest edge,
363      * including margin edges
364      */
365     private void addRowGapMatch(Rect bounds, int y1, int y2, List<GridMatch> rowMatches, int max) {
366         if (y1 < bounds.y + MARGIN_SIZE + max) {
367             int matchedLine = bounds.y + MARGIN_SIZE;
368             int distance = abs(matchedLine - y1);
369             if (distance <= max) {
370                 boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine;
371                 rowMatches.add(new GridMatch(SegmentType.TOP, distance, matchedLine,
372                         0, createCell, MARGIN_SIZE));
373             }
374         } else if (y2 > bounds.y2() - MARGIN_SIZE - max) {
375             int matchedLine = bounds.y2() - MARGIN_SIZE;
376             int distance = abs(matchedLine - y2);
377             if (distance <= max) {
378                 // This does not yet work properly; we need to use columnWeights to achieve this
379                 //boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine;
380                 //rowMatches.add(new GridMatch(SegmentType.BOTTOM, distance, matchedLine,
381                 //        mGrid.actualRowCount - 1, createCell, MARGIN_SIZE));
382             }
383         } else {
384             int rowBottom = mGrid.getRow(y1 - SHORT_GAP_DP);
385             int rowY = mGrid.getRowMaxY(rowBottom);
386             int matchedLine = rowY + SHORT_GAP_DP;
387             int distance = abs(matchedLine - y1);
388             if (distance <= max) {
389                 boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine;
390                 rowMatches.add(new GridMatch(SegmentType.TOP, distance, matchedLine,
391                         rowBottom, createCell, SHORT_GAP_DP));
392             }
393 
394             // Add a row directly adjacent (no gap)
395             rowBottom = mGrid.getRow(y1);
396             rowY = mGrid.getRowMaxY(rowBottom);
397             matchedLine = rowY;
398             distance = abs(matchedLine - y1);
399             distance += 2; // See explanation in addColumnGapMatch
400             if (distance <= max) {
401                 boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine;
402                 rowMatches.add(new GridMatch(SegmentType.TOP, distance, matchedLine,
403                         rowBottom, createCell, 0));
404             }
405 
406         }
407     }
408 
409     /**
410      * Called when a node is dropped in free-form mode. This will insert the dragged
411      * element into the grid and returns the newly created node.
412      *
413      * @param targetNode the GridLayout node
414      * @param element the dragged element
415      * @return the newly created {@link INode}
416      */
417     public INode handleFreeFormDrop(INode targetNode, IDragElement element) {
418         assert mRowMatch != null;
419         assert mColumnMatch != null;
420 
421         String fqcn = element.getFqcn();
422 
423         INode newChild = null;
424 
425         Rect bounds = element.getBounds();
426         int row = mRowMatch.cellIndex;
427         int column = mColumnMatch.cellIndex;
428 
429         if (targetNode.getChildren().length == 0) {
430             //
431             // Set up the initial structure:
432             //
433             //
434             //    Fixed                                 Fixed
435             //     Size                                  Size
436             //    Column       Expanding Column         Column
437             //   +-----+-------------------------------+-----+
438             //   |     |                               |     |
439             //   | 0,0 |              0,1              | 0,2 | Fixed Size Row
440             //   |     |                               |     |
441             //   +-----+-------------------------------+-----+
442             //   |     |                               |     |
443             //   |     |                               |     |
444             //   |     |                               |     |
445             //   | 1,0 |              1,1              | 1,2 | Expanding Row
446             //   |     |                               |     |
447             //   |     |                               |     |
448             //   |     |                               |     |
449             //   +-----+-------------------------------+-----+
450             //   |     |                               |     |
451             //   | 2,0 |              2,1              | 2,2 | Fixed Size Row
452             //   |     |                               |     |
453             //   +-----+-------------------------------+-----+
454             //
455             // This is implemented in GridLayout by the following grid, where
456             // SC1 has columnWeight=1 and SR1 has rowWeight=1.
457             // (SC=Space for Column, SR=Space for Row)
458             //
459             //   +------+-------------------------------+------+
460             //   |      |                               |      |
461             //   | SCR0 |             SC1               | SC2  |
462             //   |      |                               |      |
463             //   +------+-------------------------------+------+
464             //   |      |                               |      |
465             //   |      |                               |      |
466             //   |      |                               |      |
467             //   | SR1  |                               |      |
468             //   |      |                               |      |
469             //   |      |                               |      |
470             //   |      |                               |      |
471             //   +------+-------------------------------+------+
472             //   |      |                               |      |
473             //   | SR2  |                               |      |
474             //   |      |                               |      |
475             //   +------+-------------------------------+------+
476             //
477             // Note that when we split columns and rows here, if splitting the expanding
478             // row or column then the row or column weight should be moved to the right or
479             // bottom half!
480 
481 
482             int columnX = mGrid.getColumnX(column);
483             int rowY = mGrid.getRowY(row);
484 
485             targetNode.setAttribute(ANDROID_URI, ATTR_COLUMN_COUNT, VALUE_1);
486             //targetNode.setAttribute(ANDROID_URI, ATTR_COLUMN_COUNT, "3");
487             //INode scr0 = addSpacer(targetNode, -1, 0, 0, 1, 1);
488             //INode sc1 = addSpacer(targetNode, -1, 0, 1, 0, 0);
489             //INode sc2 = addSpacer(targetNode, -1, 0, 2, 1, 0);
490             //INode sr1 = addSpacer(targetNode, -1, 1, 0, 0, 0);
491             //INode sr2 = addSpacer(targetNode, -1, 2, 0, 0, 1);
492             //sc1.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN_WEIGHT, VALUE_1);
493             //sr1.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW_WEIGHT, VALUE_1);
494             //sc1.setAttribute(ANDROID_URI, ATTR_LAYOUT_GRAVITY, VALUE_FILL_HORIZONTAL);
495             //sr1.setAttribute(ANDROID_URI, ATTR_LAYOUT_GRAVITY, VALUE_FILL_VERTICAL);
496 
497             mGrid.loadFromXml();
498             column = mGrid.getColumn(columnX);
499             row = mGrid.getRow(rowY);
500         }
501 
502         int startX, endX;
503         if (mColumnMatch.type == SegmentType.RIGHT) {
504             endX = mColumnMatch.matchedLine - 1;
505             startX = endX - bounds.w;
506             column = mGrid.getColumn(startX);
507         } else {
508             startX = mColumnMatch.matchedLine; // TODO: What happens on type=RIGHT?
509             endX = startX + bounds.w;
510         }
511         int startY, endY;
512         if (mRowMatch.type == SegmentType.BOTTOM) {
513             endY = mRowMatch.matchedLine - 1;
514             startY = endY - bounds.h;
515             row = mGrid.getRow(startY);
516         } else if (mRowMatch.type == SegmentType.BASELINE) {
517             // TODO: The rowSpan should always be 1 for baseline alignments, since
518             // otherwise the alignment won't work!
519             startY = endY = mRowMatch.matchedLine;
520         } else {
521             startY = mRowMatch.matchedLine;
522             endY = startY + bounds.h;
523         }
524         int endColumn = mGrid.getColumn(endX);
525         int endRow = mGrid.getRow(endY);
526         int columnSpan = endColumn - column + 1;
527         int rowSpan = endRow - row + 1;
528 
529         // Make sure my math was right:
530         if (mRowMatch.type == SegmentType.BASELINE) {
531             assert rowSpan == 1 : rowSpan;
532         }
533 
534         // If the item almost fits into the row (at most N % bigger) then just enlarge
535         // the row; don't add a rowspan since that will defeat baseline alignment etc
536         if (!mRowMatch.createCell && bounds.h <= MAX_CELL_DIFFERENCE * mGrid.getRowHeight(
537                 mRowMatch.type == SegmentType.BOTTOM ? endRow : row, 1)) {
538             if (mRowMatch.type == SegmentType.BOTTOM) {
539                 row += rowSpan - 1;
540             }
541             rowSpan = 1;
542         }
543         if (!mColumnMatch.createCell && bounds.w <= MAX_CELL_DIFFERENCE * mGrid.getColumnWidth(
544                 mColumnMatch.type == SegmentType.RIGHT ? endColumn : column, 1)) {
545             if (mColumnMatch.type == SegmentType.RIGHT) {
546                 column += columnSpan - 1;
547             }
548             columnSpan = 1;
549         }
550 
551         if (mColumnMatch.type == SegmentType.CENTER_HORIZONTAL) {
552             column = 0;
553             columnSpan = mGrid.actualColumnCount;
554         }
555 
556         // Temporary: Ensure we don't get in trouble with implicit positions
557         mGrid.applyPositionAttributes();
558 
559         // Split cells to make a new column
560         if (mColumnMatch.createCell) {
561             int columnWidthPx = mGrid.getColumnDistance(column, mColumnMatch.matchedLine);
562             //assert columnWidthPx == columnMatch.distance; // TBD? IF so simplify
563             int columnWidthDp = mRule.getRulesEngine().pxToDp(columnWidthPx);
564 
565             int maxX = mGrid.getColumnMaxX(column);
566             boolean insertMarginColumn = false;
567             if (mColumnMatch.margin == 0) {
568                 columnWidthDp = 0;
569             } else if (mColumnMatch.margin != UNDEFINED) {
570                 int distance = abs(mColumnMatch.matchedLine - (maxX + mColumnMatch.margin));
571                 insertMarginColumn = column > 0 && distance < 2;
572                 if (insertMarginColumn) {
573                     int margin = mColumnMatch.margin;
574                     if (ViewMetadataRepository.INSETS_SUPPORTED) {
575                         IViewMetadata metadata = mRule.getRulesEngine().getMetadata(fqcn);
576                         if (metadata != null) {
577                             Margins insets = metadata.getInsets();
578                             if (insets != null) {
579                                 // TODO:
580                                 // Consider left or right side attachment
581                                 // TODO: Also consider inset of element on cell to the left
582                                 margin -= insets.left;
583                             }
584                         }
585                     }
586 
587                     columnWidthDp = mRule.getRulesEngine().pxToDp(margin);
588                 }
589             }
590 
591             column++;
592             mGrid.splitColumn(column, insertMarginColumn, columnWidthDp, mColumnMatch.matchedLine);
593             if (insertMarginColumn) {
594                 column++;
595             }
596             // TODO: This grid refresh is a little risky because we may have added a new
597             // child (spacer) which has no bounds yet!
598             mGrid.loadFromXml();
599         }
600 
601         // Split cells to make a new  row
602         if (mRowMatch.createCell) {
603             int rowHeightPx = mGrid.getRowDistance(row, mRowMatch.matchedLine);
604             //assert rowHeightPx == rowMatch.distance; // TBD? If so simplify
605             int rowHeightDp = mRule.getRulesEngine().pxToDp(rowHeightPx);
606 
607             int maxY = mGrid.getRowMaxY(row);
608             boolean insertMarginRow = false;
609             if (mRowMatch.margin == 0) {
610                 rowHeightDp = 0;
611             } else if (mRowMatch.margin != UNDEFINED) {
612                 int distance = abs(mRowMatch.matchedLine - (maxY + mRowMatch.margin));
613                 insertMarginRow = row > 0 && distance < 2;
614                 if (insertMarginRow) {
615                     int margin = mRowMatch.margin;
616                     IViewMetadata metadata = mRule.getRulesEngine().getMetadata(element.getFqcn());
617                     if (metadata != null) {
618                         Margins insets = metadata.getInsets();
619                         if (insets != null) {
620                             // TODO:
621                             // Consider left or right side attachment
622                             // TODO: Also consider inset of element on cell to the left
623                             margin -= insets.top;
624                         }
625                     }
626 
627                     rowHeightDp = mRule.getRulesEngine().pxToDp(margin);
628                 }
629             }
630 
631             row++;
632             mGrid.splitRow(row, insertMarginRow, rowHeightDp, mRowMatch.matchedLine);
633             if (insertMarginRow) {
634                 row++;
635             }
636             mGrid.loadFromXml();
637         }
638 
639         // Figure out where to insert the new child
640 
641         int index = mGrid.getInsertIndex(row, column);
642         if (index == -1) {
643             // Couldn't find a later place to insert
644             newChild = targetNode.appendChild(fqcn);
645         } else {
646             GridModel.ViewData next = mGrid.getView(index);
647 
648             newChild = targetNode.insertChildAt(fqcn, index);
649 
650             // Must also apply positions to the following child to ensure
651             // that the new child doesn't affect the implicit numbering!
652             // TODO: We can later check whether the implied number is equal to
653             // what it already is such that we don't need this
654             next.applyPositionAttributes();
655         }
656 
657         // Set the cell position of the new widget
658         if (mColumnMatch.type == SegmentType.RIGHT) {
659             newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_GRAVITY, VALUE_RIGHT);
660         } else if (mColumnMatch.type == SegmentType.CENTER_HORIZONTAL) {
661             newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_GRAVITY, VALUE_CENTER_HORIZONTAL);
662         }
663         newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, Integer.toString(column));
664         if (mRowMatch.type == SegmentType.BOTTOM) {
665             String value = VALUE_BOTTOM;
666             if (mColumnMatch.type == SegmentType.RIGHT) {
667                 value = value + '|' + VALUE_RIGHT;
668             }
669             newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_GRAVITY, value);
670         }
671         newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, Integer.toString(row));
672 
673         // Apply spans to ensure that the widget can fit without pushing columns
674         if (columnSpan > 1) {
675             newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN_SPAN,
676                     Integer.toString(columnSpan));
677         }
678         if (rowSpan > 1) {
679             newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW_SPAN, Integer.toString(rowSpan));
680         }
681 
682         // Ensure that we don't store columnCount=0
683         if (mGrid.actualColumnCount == 0) {
684             mGrid.layout.setAttribute(ANDROID_URI, ATTR_COLUMN_COUNT, VALUE_1);
685         }
686 
687         return newChild;
688     }
689 
690     /**
691      * Called when a drop is completed and we're in grid-editing mode. This will insert
692      * the dragged element into the target cell.
693      *
694      * @param targetNode the GridLayout node
695      * @param element the dragged element
696      * @return the newly created node
697      */
698     public INode handleGridModeDrop(INode targetNode, IDragElement element) {
699         String fqcn = element.getFqcn();
700         INode newChild = targetNode.appendChild(fqcn);
701 
702         if (mColumnMatch.createCell) {
703             mGrid.addColumn(mColumnMatch.cellIndex,
704                     newChild, UNDEFINED, false, UNDEFINED, UNDEFINED);
705         }
706         if (mRowMatch.createCell) {
707             mGrid.loadFromXml();
708             mGrid.addRow(mRowMatch.cellIndex, newChild, UNDEFINED, false, UNDEFINED, UNDEFINED);
709         }
710 
711         newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN,
712                 Integer.toString(mColumnMatch.cellIndex));
713         newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW,
714                 Integer.toString(mRowMatch.cellIndex));
715 
716         return newChild;
717     }
718 
719     /**
720      * Returns the best horizontal match
721      *
722      * @return the best horizontal match, or null if there is no match
723      */
724     public GridMatch getColumnMatch() {
725         return mColumnMatch;
726     }
727 
728     /**
729      * Returns the best vertical match
730      *
731      * @return the best vertical match, or null if there is no match
732      */
733     public GridMatch getRowMatch() {
734         return mRowMatch;
735     }
736 
737     /**
738      * Returns the grid used by the drop handler
739      *
740      * @return the grid used by the drop handler, never null
741      */
742     public GridModel getGrid() {
743         return mGrid;
744     }
745 }