Home | History | Annotate | Download | only in grid
      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      */
     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      */
     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 }