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