diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid')
4 files changed, 0 insertions, 3748 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java deleted file mode 100644 index 8bdb56bfe..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java +++ /dev/null @@ -1,840 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.common.layout.grid; - -import static com.android.SdkConstants.ATTR_COLUMN_COUNT; -import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN; -import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN_SPAN; -import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY; -import static com.android.SdkConstants.ATTR_LAYOUT_ROW; -import static com.android.SdkConstants.ATTR_LAYOUT_ROW_SPAN; -import static com.android.ide.common.layout.GravityHelper.getGravity; -import static com.android.ide.common.layout.GridLayoutRule.GRID_SIZE; -import static com.android.ide.common.layout.GridLayoutRule.MARGIN_SIZE; -import static com.android.ide.common.layout.GridLayoutRule.MAX_CELL_DIFFERENCE; -import static com.android.ide.common.layout.GridLayoutRule.SHORT_GAP_DP; -import static com.android.ide.common.layout.grid.GridModel.UNDEFINED; -import static java.lang.Math.abs; - -import com.android.ide.common.api.DropFeedback; -import com.android.ide.common.api.IDragElement; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewMetadata; -import com.android.ide.common.api.Margins; -import com.android.ide.common.api.Point; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.SegmentType; -import com.android.ide.common.layout.BaseLayoutRule; -import com.android.ide.common.layout.GravityHelper; -import com.android.ide.common.layout.GridLayoutRule; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -/** - * The {@link GridDropHandler} handles drag and drop operations into and within a - * GridLayout, computing guidelines, handling drops to edit the grid model, and so on. - */ -public class GridDropHandler { - private final GridModel mGrid; - private final GridLayoutRule mRule; - private GridMatch mColumnMatch; - private GridMatch mRowMatch; - - /** - * Creates a new {@link GridDropHandler} for - * @param gridLayoutRule the corresponding {@link GridLayoutRule} - * @param layout the GridLayout node - * @param view the view instance of the grid layout receiving the drop - */ - public GridDropHandler(GridLayoutRule gridLayoutRule, INode layout, Object view) { - mRule = gridLayoutRule; - mGrid = GridModel.get(mRule.getRulesEngine(), layout, view); - } - - /** - * Computes the best horizontal and vertical matches for a drag to the given position. - * - * @param feedback a {@link DropFeedback} object containing drag state like the drag - * bounds and the drag baseline - * @param p the mouse position - */ - public void computeMatches(DropFeedback feedback, Point p) { - mRowMatch = mColumnMatch = null; - feedback.tooltip = null; - - Rect bounds = mGrid.layout.getBounds(); - int x1 = p.x; - int y1 = p.y; - - Rect dragBounds = feedback.dragBounds; - int w = dragBounds != null ? dragBounds.w : 0; - int h = dragBounds != null ? dragBounds.h : 0; - if (!GridLayoutRule.sGridMode) { - if (dragBounds != null) { - // Sometimes the items are centered under the mouse so - // offset by the top left corner distance - x1 += dragBounds.x; - y1 += dragBounds.y; - } - - int x2 = x1 + w; - int y2 = y1 + h; - - if (x2 < bounds.x || y2 < bounds.y || x1 > bounds.x2() || y1 > bounds.y2()) { - return; - } - - List<GridMatch> columnMatches = new ArrayList<GridMatch>(); - List<GridMatch> rowMatches = new ArrayList<GridMatch>(); - int max = BaseLayoutRule.getMaxMatchDistance(); - - // Column matches: - addLeftSideMatch(x1, columnMatches, max); - addRightSideMatch(x2, columnMatches, max); - addCenterColumnMatch(bounds, x1, y1, x2, y2, columnMatches, max); - - // Row matches: - int row = (mGrid.getViewCount() == 0) ? 0 : mGrid.getClosestRow(y1); - int rowY = mGrid.getRowY(row); - addTopMatch(y1, rowMatches, max, row, rowY); - addBaselineMatch(feedback.dragBaseline, y1, rowMatches, max, row, rowY); - addBottomMatch(y2, rowMatches, max); - - // Look for gap-matches: Predefined spacing between widgets. - // TODO: Make this use metadata for predefined spacing between - // pairs of types of components. For example, buttons have certain - // inserts in their 9-patch files (depending on the theme) that should - // be considered and subtracted from the overall proposed distance! - addColumnGapMatch(bounds, x1, x2, columnMatches, max); - addRowGapMatch(bounds, y1, y2, rowMatches, max); - - // Fallback: Split existing cell. Also do snap-to-grid. - if (GridLayoutRule.sSnapToGrid) { - x1 = ((x1 - MARGIN_SIZE - bounds.x) / GRID_SIZE) * GRID_SIZE - + MARGIN_SIZE + bounds.x; - y1 = ((y1 - MARGIN_SIZE - bounds.y) / GRID_SIZE) * GRID_SIZE - + MARGIN_SIZE + bounds.y; - x2 = x1 + w; - y2 = y1 + h; - } - - - if (columnMatches.size() == 0 && x1 >= bounds.x) { - // Split the current cell since we have no matches - // TODO: Decide whether it should be gravity left or right... - columnMatches.add(new GridMatch(SegmentType.LEFT, 0, x1, mGrid.getColumn(x1), - true /* createCell */, UNDEFINED)); - } - if (rowMatches.size() == 0 && y1 >= bounds.y) { - rowMatches.add(new GridMatch(SegmentType.TOP, 0, y1, mGrid.getRow(y1), - true /* createCell */, UNDEFINED)); - } - - // Pick best matches - Collections.sort(rowMatches); - Collections.sort(columnMatches); - - mColumnMatch = null; - mRowMatch = null; - String columnDescription = null; - String rowDescription = null; - if (columnMatches.size() > 0) { - mColumnMatch = columnMatches.get(0); - columnDescription = mColumnMatch.getDisplayName(mGrid.layout); - } - if (rowMatches.size() > 0) { - mRowMatch = rowMatches.get(0); - rowDescription = mRowMatch.getDisplayName(mGrid.layout); - } - - if (columnDescription != null && rowDescription != null) { - feedback.tooltip = columnDescription + '\n' + rowDescription; - } - - feedback.invalidTarget = mColumnMatch == null || mRowMatch == null; - } else { - // Find which cell we're inside. - - // TODO: Find out where within the cell we are, and offer to tweak the gravity - // based on the position. - int column = mGrid.getColumn(x1); - int row = mGrid.getRow(y1); - - int leftDistance = mGrid.getColumnDistance(column, x1); - int rightDistance = mGrid.getColumnDistance(column + 1, x1); - int topDistance = mGrid.getRowDistance(row, y1); - int bottomDistance = mGrid.getRowDistance(row + 1, y1); - - int SLOP = 2; - int radius = mRule.getNewCellSize(); - if (rightDistance < radius + SLOP) { - column = Math.min(column + 1, mGrid.actualColumnCount); - leftDistance = rightDistance; - } - if (bottomDistance < radius + SLOP) { - row = Math.min(row + 1, mGrid.actualRowCount); - topDistance = bottomDistance; - } - - boolean createColumn = leftDistance < radius + SLOP; - boolean createRow = topDistance < radius + SLOP; - if (x1 >= bounds.x2()) { - createColumn = true; - } - if (y1 >= bounds.y2()) { - createRow = true; - } - - int cellWidth = leftDistance + rightDistance; - int cellHeight = topDistance + bottomDistance; - SegmentType horizontalType = SegmentType.LEFT; - SegmentType verticalType = SegmentType.TOP; - int minDistance = 10; // Don't center or right/bottom align in tiny cells - if (!createColumn && leftDistance > minDistance - && dragBounds != null && dragBounds.w < cellWidth - 10) { - if (rightDistance < leftDistance) { - horizontalType = SegmentType.RIGHT; - } - - int centerDistance = Math.abs(cellWidth / 2 - leftDistance); - if (centerDistance < leftDistance / 2 && centerDistance < rightDistance / 2) { - horizontalType = SegmentType.CENTER_HORIZONTAL; - } - } - if (!createRow && topDistance > minDistance - && dragBounds != null && dragBounds.h < cellHeight - 10) { - if (bottomDistance < topDistance) { - verticalType = SegmentType.BOTTOM; - } - int centerDistance = Math.abs(cellHeight / 2 - topDistance); - if (centerDistance < topDistance / 2 && centerDistance < bottomDistance / 2) { - verticalType = SegmentType.CENTER_VERTICAL; - } - } - - mColumnMatch = new GridMatch(horizontalType, 0, x1, column, createColumn, 0); - mRowMatch = new GridMatch(verticalType, 0, y1, row, createRow, 0); - - StringBuilder description = new StringBuilder(50); - String rowString = Integer.toString(mColumnMatch.cellIndex + 1); - String columnString = Integer.toString(mRowMatch.cellIndex + 1); - if (mRowMatch.createCell && mRowMatch.cellIndex < mGrid.actualRowCount) { - description.append(String.format("Shift row %1$d down", mRowMatch.cellIndex + 1)); - description.append('\n'); - } - if (mColumnMatch.createCell && mColumnMatch.cellIndex < mGrid.actualColumnCount) { - description.append(String.format("Shift column %1$d right", - mColumnMatch.cellIndex + 1)); - description.append('\n'); - } - description.append(String.format("Insert into cell (%1$s,%2$s)", - rowString, columnString)); - description.append('\n'); - description.append(String.format("Align %1$s, %2$s", - horizontalType.name().toLowerCase(Locale.US), - verticalType.name().toLowerCase(Locale.US))); - feedback.tooltip = description.toString(); - } - } - - /** - * Adds a match to align the left edge with some other edge. - */ - private void addLeftSideMatch(int x1, List<GridMatch> columnMatches, int max) { - int column = (mGrid.getViewCount() == 0) ? 0 : mGrid.getClosestColumn(x1); - int columnX = mGrid.getColumnX(column); - int distance = abs(columnX - x1); - if (distance <= max) { - columnMatches.add(new GridMatch(SegmentType.LEFT, distance, columnX, column, - false, UNDEFINED)); - } - } - - /** - * Adds a match to align the right edge with some other edge. - */ - private void addRightSideMatch(int x2, List<GridMatch> columnMatches, int max) { - // TODO: Only match the right hand side if the drag bounds fit fully within the - // cell! Ditto for match below. - int columnRight = (mGrid.getViewCount() == 0) ? 0 : mGrid.getClosestColumn(x2); - int rightDistance = mGrid.getColumnDistance(columnRight, x2); - if (rightDistance < max) { - int columnX = mGrid.getColumnX(columnRight); - if (columnX > mGrid.layout.getBounds().x) { - columnMatches.add(new GridMatch(SegmentType.RIGHT, rightDistance, columnX, - columnRight, false, UNDEFINED)); - } - } - } - - /** - * Adds a horizontal match with the center axis of the GridLayout - */ - private void addCenterColumnMatch(Rect bounds, int x1, int y1, int x2, int y2, - List<GridMatch> columnMatches, int max) { - Collection<INode> intersectsRow = mGrid.getIntersectsRow(y1, y2); - if (intersectsRow.size() == 0) { - // Offer centering on this row since there isn't anything there - int matchedLine = bounds.centerX(); - int distance = abs((x1 + x2) / 2 - matchedLine); - if (distance <= 2 * max) { - boolean createCell = false; // always just put in column 0 - columnMatches.add(new GridMatch(SegmentType.CENTER_HORIZONTAL, distance, - matchedLine, 0 /* column */, createCell, UNDEFINED)); - } - } - } - - /** - * Adds a match to align the top edge with some other edge. - */ - private void addTopMatch(int y1, List<GridMatch> rowMatches, int max, int row, int rowY) { - int distance = mGrid.getRowDistance(row, y1); - if (distance <= max) { - rowMatches.add(new GridMatch(SegmentType.TOP, distance, rowY, row, false, - UNDEFINED)); - } - } - - /** - * Adds a match to align the bottom edge with some other edge. - */ - private void addBottomMatch(int y2, List<GridMatch> rowMatches, int max) { - int rowBottom = (mGrid.getViewCount() == 0) ? 0 : mGrid.getClosestRow(y2); - int distance = mGrid.getRowDistance(rowBottom, y2); - if (distance < max) { - int rowY = mGrid.getRowY(rowBottom); - if (rowY > mGrid.layout.getBounds().y) { - rowMatches.add(new GridMatch(SegmentType.BOTTOM, distance, rowY, - rowBottom, false, UNDEFINED)); - } - } - } - - /** - * Adds a baseline match, if applicable. - */ - private void addBaselineMatch(int dragBaseline, int y1, List<GridMatch> rowMatches, int max, - int row, int rowY) { - int dragBaselineY = y1 + dragBaseline; - int rowBaseline = mGrid.getBaseline(row); - if (rowBaseline != -1) { - int rowBaselineY = rowY + rowBaseline; - int distance = abs(dragBaselineY - rowBaselineY); - if (distance < max) { - rowMatches.add(new GridMatch(SegmentType.BASELINE, distance, rowBaselineY, row, - false, UNDEFINED)); - } - } - } - - /** - * Computes a horizontal "gap" match - a preferred distance from the nearest edge, - * including margin edges - */ - private void addColumnGapMatch(Rect bounds, int x1, int x2, List<GridMatch> columnMatches, - int max) { - if (x1 < bounds.x + MARGIN_SIZE + max) { - int matchedLine = bounds.x + MARGIN_SIZE; - int distance = abs(matchedLine - x1); - if (distance <= max) { - boolean createCell = mGrid.getColumnX(mGrid.getColumn(matchedLine)) != matchedLine; - columnMatches.add(new GridMatch(SegmentType.LEFT, distance, matchedLine, - 0, createCell, MARGIN_SIZE)); - } - } else if (x2 > bounds.x2() - MARGIN_SIZE - max) { - int matchedLine = bounds.x2() - MARGIN_SIZE; - int distance = abs(matchedLine - x2); - if (distance <= max) { - // This does not yet work properly; we need to use columnWeights to achieve this - //boolean createCell = mGrid.getColumnX(mGrid.getColumn(matchedLine)) != matchedLine; - //columnMatches.add(new GridMatch(SegmentType.RIGHT, distance, matchedLine, - // mGrid.actualColumnCount - 1, createCell, MARGIN_SIZE)); - } - } else { - int columnRight = mGrid.getColumn(x1 - SHORT_GAP_DP); - int columnX = mGrid.getColumnMaxX(columnRight); - int matchedLine = columnX + SHORT_GAP_DP; - int distance = abs(matchedLine - x1); - if (distance <= max) { - boolean createCell = mGrid.getColumnX(mGrid.getColumn(matchedLine)) != matchedLine; - columnMatches.add(new GridMatch(SegmentType.LEFT, distance, matchedLine, - columnRight, createCell, SHORT_GAP_DP)); - } - - // Add a column directly adjacent (no gap) - columnRight = mGrid.getColumn(x1); - columnX = mGrid.getColumnMaxX(columnRight); - matchedLine = columnX; - distance = abs(matchedLine - x1); - - // Let's say you have this arrangement: - // [button1][button2] - // This is two columns, where the right hand side edge of column 1 is - // flush with the left side edge of column 2, because in fact the width of - // button1 is what defines the width of column 1, and that in turn is what - // defines the left side position of column 2. - // - // In this case we don't want to consider inserting a new column at the - // right hand side of button1 a better match than matching left on column 2. - // Therefore, to ensure that this doesn't happen, we "penalize" right column - // matches such that they don't get preferential treatment when the matching - // line is on the left side of the column. - distance += 2; - - if (distance <= max) { - boolean createCell = mGrid.getColumnX(mGrid.getColumn(matchedLine)) != matchedLine; - columnMatches.add(new GridMatch(SegmentType.LEFT, distance, matchedLine, - columnRight, createCell, 0)); - } - } - } - - /** - * Computes a vertical "gap" match - a preferred distance from the nearest edge, - * including margin edges - */ - private void addRowGapMatch(Rect bounds, int y1, int y2, List<GridMatch> rowMatches, int max) { - if (y1 < bounds.y + MARGIN_SIZE + max) { - int matchedLine = bounds.y + MARGIN_SIZE; - int distance = abs(matchedLine - y1); - if (distance <= max) { - boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine; - rowMatches.add(new GridMatch(SegmentType.TOP, distance, matchedLine, - 0, createCell, MARGIN_SIZE)); - } - } else if (y2 > bounds.y2() - MARGIN_SIZE - max) { - int matchedLine = bounds.y2() - MARGIN_SIZE; - int distance = abs(matchedLine - y2); - if (distance <= max) { - // This does not yet work properly; we need to use columnWeights to achieve this - //boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine; - //rowMatches.add(new GridMatch(SegmentType.BOTTOM, distance, matchedLine, - // mGrid.actualRowCount - 1, createCell, MARGIN_SIZE)); - } - } else { - int rowBottom = mGrid.getRow(y1 - SHORT_GAP_DP); - int rowY = mGrid.getRowMaxY(rowBottom); - int matchedLine = rowY + SHORT_GAP_DP; - int distance = abs(matchedLine - y1); - if (distance <= max) { - boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine; - rowMatches.add(new GridMatch(SegmentType.TOP, distance, matchedLine, - rowBottom, createCell, SHORT_GAP_DP)); - } - - // Add a row directly adjacent (no gap) - rowBottom = mGrid.getRow(y1); - rowY = mGrid.getRowMaxY(rowBottom); - matchedLine = rowY; - distance = abs(matchedLine - y1); - distance += 2; // See explanation in addColumnGapMatch - if (distance <= max) { - boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine; - rowMatches.add(new GridMatch(SegmentType.TOP, distance, matchedLine, - rowBottom, createCell, 0)); - } - - } - } - - /** - * Called when a node is dropped in free-form mode. This will insert the dragged - * element into the grid and returns the newly created node. - * - * @param targetNode the GridLayout node - * @param element the dragged element - * @return the newly created {@link INode} - */ - public INode handleFreeFormDrop(INode targetNode, IDragElement element) { - assert mRowMatch != null; - assert mColumnMatch != null; - - String fqcn = element.getFqcn(); - - INode newChild = null; - - Rect bounds = element.getBounds(); - int row = mRowMatch.cellIndex; - int column = mColumnMatch.cellIndex; - - if (targetNode.getChildren().length == 0) { - // - // Set up the initial structure: - // - // - // Fixed Fixed - // Size Size - // Column Expanding Column Column - // +-----+-------------------------------+-----+ - // | | | | - // | 0,0 | 0,1 | 0,2 | Fixed Size Row - // | | | | - // +-----+-------------------------------+-----+ - // | | | | - // | | | | - // | | | | - // | 1,0 | 1,1 | 1,2 | Expanding Row - // | | | | - // | | | | - // | | | | - // +-----+-------------------------------+-----+ - // | | | | - // | 2,0 | 2,1 | 2,2 | Fixed Size Row - // | | | | - // +-----+-------------------------------+-----+ - // - // This is implemented in GridLayout by the following grid, where - // SC1 has columnWeight=1 and SR1 has rowWeight=1. - // (SC=Space for Column, SR=Space for Row) - // - // +------+-------------------------------+------+ - // | | | | - // | SCR0 | SC1 | SC2 | - // | | | | - // +------+-------------------------------+------+ - // | | | | - // | | | | - // | | | | - // | SR1 | | | - // | | | | - // | | | | - // | | | | - // +------+-------------------------------+------+ - // | | | | - // | SR2 | | | - // | | | | - // +------+-------------------------------+------+ - // - // Note that when we split columns and rows here, if splitting the expanding - // row or column then the row or column weight should be moved to the right or - // bottom half! - - - //int columnX = mGrid.getColumnX(column); - //int rowY = mGrid.getRowY(row); - - mGrid.setGridAttribute(targetNode, ATTR_COLUMN_COUNT, 2); - //mGrid.setGridAttribute(targetNode, ATTR_COLUMN_COUNT, 3); - //INode scr0 = addSpacer(targetNode, -1, 0, 0, 1, 1); - //INode sc1 = addSpacer(targetNode, -1, 0, 1, 0, 0); - //INode sc2 = addSpacer(targetNode, -1, 0, 2, 1, 0); - //INode sr1 = addSpacer(targetNode, -1, 1, 0, 0, 0); - //INode sr2 = addSpacer(targetNode, -1, 2, 0, 0, 1); - //mGrid.setGridAttribute(sc1, ATTR_LAYOUT_GRAVITY, VALUE_FILL_HORIZONTAL); - //mGrid.setGridAttribute(sr1, ATTR_LAYOUT_GRAVITY, VALUE_FILL_VERTICAL); - // - //mGrid.loadFromXml(); - //column = mGrid.getColumn(columnX); - //row = mGrid.getRow(rowY); - } - - int startX, endX; - if (mColumnMatch.type == SegmentType.RIGHT) { - endX = mColumnMatch.matchedLine - 1; - startX = endX - bounds.w; - column = mGrid.getColumn(startX); - } else { - startX = mColumnMatch.matchedLine; // TODO: What happens on type=RIGHT? - endX = startX + bounds.w; - } - int startY, endY; - if (mRowMatch.type == SegmentType.BOTTOM) { - endY = mRowMatch.matchedLine - 1; - startY = endY - bounds.h; - row = mGrid.getRow(startY); - } else if (mRowMatch.type == SegmentType.BASELINE) { - // TODO: The rowSpan should always be 1 for baseline alignments, since - // otherwise the alignment won't work! - startY = endY = mRowMatch.matchedLine; - } else { - startY = mRowMatch.matchedLine; - endY = startY + bounds.h; - } - int endColumn = mGrid.getColumn(endX); - int endRow = mGrid.getRow(endY); - int columnSpan = endColumn - column + 1; - int rowSpan = endRow - row + 1; - - // Make sure my math was right: - assert mRowMatch.type != SegmentType.BASELINE || rowSpan == 1 : rowSpan; - - // If the item almost fits into the row (at most N % bigger) then just enlarge - // the row; don't add a rowspan since that will defeat baseline alignment etc - if (!mRowMatch.createCell && bounds.h <= MAX_CELL_DIFFERENCE * mGrid.getRowHeight( - mRowMatch.type == SegmentType.BOTTOM ? endRow : row, 1)) { - if (mRowMatch.type == SegmentType.BOTTOM) { - row += rowSpan - 1; - } - rowSpan = 1; - } - if (!mColumnMatch.createCell && bounds.w <= MAX_CELL_DIFFERENCE * mGrid.getColumnWidth( - mColumnMatch.type == SegmentType.RIGHT ? endColumn : column, 1)) { - if (mColumnMatch.type == SegmentType.RIGHT) { - column += columnSpan - 1; - } - columnSpan = 1; - } - - if (mColumnMatch.type == SegmentType.CENTER_HORIZONTAL) { - column = 0; - columnSpan = mGrid.actualColumnCount; - } - - // Temporary: Ensure we don't get in trouble with implicit positions - mGrid.applyPositionAttributes(); - - // Split cells to make a new column - if (mColumnMatch.createCell) { - int columnWidthPx = mGrid.getColumnDistance(column, mColumnMatch.matchedLine); - //assert columnWidthPx == columnMatch.distance; // TBD? IF so simplify - int columnWidthDp = mRule.getRulesEngine().pxToDp(columnWidthPx); - - int maxX = mGrid.getColumnMaxX(column); - boolean insertMarginColumn = false; - if (mColumnMatch.margin == 0) { - columnWidthDp = 0; - } else if (mColumnMatch.margin != UNDEFINED) { - int distance = abs(mColumnMatch.matchedLine - (maxX + mColumnMatch.margin)); - insertMarginColumn = column > 0 && distance < 2; - if (insertMarginColumn) { - int margin = mColumnMatch.margin; - if (ViewMetadataRepository.INSETS_SUPPORTED) { - IViewMetadata metadata = mRule.getRulesEngine().getMetadata(fqcn); - if (metadata != null) { - Margins insets = metadata.getInsets(); - if (insets != null) { - // TODO: - // Consider left or right side attachment - // TODO: Also consider inset of element on cell to the left - margin -= insets.left; - } - } - } - - columnWidthDp = mRule.getRulesEngine().pxToDp(margin); - } - } - - column++; - mGrid.splitColumn(column, insertMarginColumn, columnWidthDp, mColumnMatch.matchedLine); - if (insertMarginColumn) { - column++; - } - } - - // Split cells to make a new row - if (mRowMatch.createCell) { - int rowHeightPx = mGrid.getRowDistance(row, mRowMatch.matchedLine); - //assert rowHeightPx == rowMatch.distance; // TBD? If so simplify - int rowHeightDp = mRule.getRulesEngine().pxToDp(rowHeightPx); - - int maxY = mGrid.getRowMaxY(row); - boolean insertMarginRow = false; - if (mRowMatch.margin == 0) { - rowHeightDp = 0; - } else if (mRowMatch.margin != UNDEFINED) { - int distance = abs(mRowMatch.matchedLine - (maxY + mRowMatch.margin)); - insertMarginRow = row > 0 && distance < 2; - if (insertMarginRow) { - int margin = mRowMatch.margin; - IViewMetadata metadata = mRule.getRulesEngine().getMetadata(element.getFqcn()); - if (metadata != null) { - Margins insets = metadata.getInsets(); - if (insets != null) { - // TODO: - // Consider left or right side attachment - // TODO: Also consider inset of element on cell to the left - margin -= insets.top; - } - } - - rowHeightDp = mRule.getRulesEngine().pxToDp(margin); - } - } - - row++; - mGrid.splitRow(row, insertMarginRow, rowHeightDp, mRowMatch.matchedLine); - if (insertMarginRow) { - row++; - } - } - - // Figure out where to insert the new child - - int index = mGrid.getInsertIndex(row, column); - if (index == -1) { - // Couldn't find a later place to insert - newChild = targetNode.appendChild(fqcn); - } else { - GridModel.ViewData next = mGrid.getView(index); - - newChild = targetNode.insertChildAt(fqcn, index); - - // Must also apply positions to the following child to ensure - // that the new child doesn't affect the implicit numbering! - // TODO: We can later check whether the implied number is equal to - // what it already is such that we don't need this - next.applyPositionAttributes(); - } - - // Set the cell position (gravity) of the new widget - int gravity = 0; - if (mColumnMatch.type == SegmentType.RIGHT) { - gravity |= GravityHelper.GRAVITY_RIGHT; - } else if (mColumnMatch.type == SegmentType.CENTER_HORIZONTAL) { - gravity |= GravityHelper.GRAVITY_CENTER_HORIZ; - } - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN, column); - if (mRowMatch.type == SegmentType.BASELINE) { - // There *is* no baseline gravity constant, instead, leave the - // vertical gravity unspecified and GridLayout will treat it as - // baseline alignment - //gravity |= GravityHelper.GRAVITY_BASELINE; - } else if (mRowMatch.type == SegmentType.BOTTOM) { - gravity |= GravityHelper.GRAVITY_BOTTOM; - } else if (mRowMatch.type == SegmentType.CENTER_VERTICAL) { - gravity |= GravityHelper.GRAVITY_CENTER_VERT; - } - // Ensure that we have at least one horizontal and vertical constraint, otherwise - // the new item will be fixed. As an example, if we have a single button in the - // table which we inserted *without* a gravity, and we then insert a button - // above it with a vertical gravity, then only the top column would be considered - // stretchable, and it will fill all available vertical space and the previous - // button will jump to the bottom. - if (!GravityHelper.isConstrainedHorizontally(gravity)) { - gravity |= GravityHelper.GRAVITY_LEFT; - } - /* This causes problems: Try placing two buttons vertically from the top of the layout. - We need to solve the free column/free row problem first. - if (!GravityHelper.isConstrainedVertically(gravity) - // There is no baseline constant, so we have to leave it unconstrained instead - && mRowMatch.type != SegmentType.BASELINE - // You also can't baseline align one element with another that has vertical - // alignment top or bottom, so when we first "freely" place views (e.g. - // at a particular y location), also place it freely (no constraint). - && !mRowMatch.createCell) { - gravity |= GravityHelper.GRAVITY_TOP; - } - */ - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_GRAVITY, getGravity(gravity)); - - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_ROW, row); - - // Apply spans to ensure that the widget can fit without pushing columns - if (columnSpan > 1) { - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN_SPAN, columnSpan); - } - if (rowSpan > 1) { - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_ROW_SPAN, rowSpan); - } - - // Ensure that we don't store columnCount=0 - if (mGrid.actualColumnCount == 0) { - mGrid.setGridAttribute(mGrid.layout, ATTR_COLUMN_COUNT, Math.max(1, column + 1)); - } - - return newChild; - } - - /** - * Called when a drop is completed and we're in grid-editing mode. This will insert - * the dragged element into the target cell. - * - * @param targetNode the GridLayout node - * @param element the dragged element - * @return the newly created node - */ - public INode handleGridModeDrop(INode targetNode, IDragElement element) { - String fqcn = element.getFqcn(); - INode newChild = targetNode.appendChild(fqcn); - - int column = mColumnMatch.cellIndex; - if (mColumnMatch.createCell) { - mGrid.addColumn(column, - newChild, UNDEFINED, false, UNDEFINED, UNDEFINED); - } - int row = mRowMatch.cellIndex; - if (mRowMatch.createCell) { - mGrid.addRow(row, newChild, UNDEFINED, false, UNDEFINED, UNDEFINED); - } - - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN, column); - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_ROW, row); - - int gravity = 0; - if (mColumnMatch.type == SegmentType.RIGHT) { - gravity |= GravityHelper.GRAVITY_RIGHT; - } else if (mColumnMatch.type == SegmentType.CENTER_HORIZONTAL) { - gravity |= GravityHelper.GRAVITY_CENTER_HORIZ; - } - if (mRowMatch.type == SegmentType.BASELINE) { - // There *is* no baseline gravity constant, instead, leave the - // vertical gravity unspecified and GridLayout will treat it as - // baseline alignment - //gravity |= GravityHelper.GRAVITY_BASELINE; - } else if (mRowMatch.type == SegmentType.BOTTOM) { - gravity |= GravityHelper.GRAVITY_BOTTOM; - } else if (mRowMatch.type == SegmentType.CENTER_VERTICAL) { - gravity |= GravityHelper.GRAVITY_CENTER_VERT; - } - if (!GravityHelper.isConstrainedHorizontally(gravity)) { - gravity |= GravityHelper.GRAVITY_LEFT; - } - if (!GravityHelper.isConstrainedVertically(gravity)) { - gravity |= GravityHelper.GRAVITY_TOP; - } - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_GRAVITY, getGravity(gravity)); - - if (mGrid.declaredColumnCount == UNDEFINED || mGrid.declaredColumnCount < column + 1) { - mGrid.setGridAttribute(mGrid.layout, ATTR_COLUMN_COUNT, column + 1); - } - - return newChild; - } - - /** - * Returns the best horizontal match - * - * @return the best horizontal match, or null if there is no match - */ - public GridMatch getColumnMatch() { - return mColumnMatch; - } - - /** - * Returns the best vertical match - * - * @return the best vertical match, or null if there is no match - */ - public GridMatch getRowMatch() { - return mRowMatch; - } - - /** - * Returns the grid used by the drop handler - * - * @return the grid used by the drop handler, never null - */ - public GridModel getGrid() { - return mGrid; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridLayoutPainter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridLayoutPainter.java deleted file mode 100644 index 7e2d3a799..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridLayoutPainter.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.common.layout.grid; - -import static com.android.ide.common.layout.GridLayoutRule.GRID_SIZE; -import static com.android.ide.common.layout.GridLayoutRule.MARGIN_SIZE; -import static com.android.ide.common.layout.grid.GridModel.UNDEFINED; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.DrawingStyle; -import com.android.ide.common.api.DropFeedback; -import com.android.ide.common.api.IDragElement; -import com.android.ide.common.api.IFeedbackPainter; -import com.android.ide.common.api.IGraphics; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.SegmentType; -import com.android.ide.common.layout.GridLayoutRule; -import com.android.utils.Pair; - -/** - * Painter which paints feedback during drag, drop and resizing operations, as well as - * static selection feedback - */ -public class GridLayoutPainter { - - /** - * Creates a painter for drop feedback - * - * @param rule the corresponding {@link GridLayoutRule} - * @param elements the dragged elements - * @return a {@link IFeedbackPainter} which can paint the drop feedback - */ - public static IFeedbackPainter createDropFeedbackPainter(GridLayoutRule rule, - IDragElement[] elements) { - return new DropFeedbackPainter(rule, elements); - } - - /** - * Paints the structure (the grid model) of the given GridLayout. - * - * @param style the drawing style to use to paint the structure lines - * @param layout the grid layout node - * @param gc the graphics context to paint into - * @param grid the grid model to be visualized - */ - public static void paintStructure(DrawingStyle style, INode layout, IGraphics gc, - GridModel grid) { - Rect b = layout.getBounds(); - - gc.useStyle(style); - for (int row = 0; row < grid.actualRowCount; row++) { - int y = grid.getRowY(row); - gc.drawLine(b.x, y, b.x2(), y); - } - for (int column = 0; column < grid.actualColumnCount; column++) { - int x = grid.getColumnX(column); - gc.drawLine(x, b.y, x, b.y2()); - } - } - - /** - * Paints a regular grid according to the {@link GridLayoutRule#GRID_SIZE} and - * {@link GridLayoutRule#MARGIN_SIZE} dimensions. These are the same lines that - * snap-to-grid will align with. - * - * @param layout the GridLayout node - * @param gc the graphics context to paint the grid into - */ - public static void paintGrid(INode layout, IGraphics gc) { - Rect b = layout.getBounds(); - - int oldAlpha = gc.getAlpha(); - gc.useStyle(DrawingStyle.GUIDELINE); - gc.setAlpha(128); - - int y1 = b.y + MARGIN_SIZE; - int y2 = b.y2() - MARGIN_SIZE; - for (int y = y1; y < y2; y += GRID_SIZE) { - int x1 = b.x + MARGIN_SIZE; - int x2 = b.x2() - MARGIN_SIZE; - for (int x = x1; x < x2; x += GRID_SIZE) { - gc.drawPoint(x, y); - } - } - gc.setAlpha(oldAlpha); - } - - /** - * Paint resizing feedback (which currently paints the grid model faintly.) - * - * @param gc the graphics context - * @param layout the GridLayout - * @param grid the grid model - */ - public static void paintResizeFeedback(IGraphics gc, INode layout, GridModel grid) { - paintStructure(DrawingStyle.GRID, layout, gc, grid); - } - - /** - * A painter which can paint the drop feedback for elements being dragged into or - * within a GridLayout. - */ - private static class DropFeedbackPainter implements IFeedbackPainter { - private final GridLayoutRule mRule; - private final IDragElement[] mElements; - - /** Constructs a new {@link GridLayoutPainter} bound to the given {@link GridLayoutRule} - * @param rule the corresponding rule - * @param elements the elements to draw */ - public DropFeedbackPainter(GridLayoutRule rule, IDragElement[] elements) { - mRule = rule; - mElements = elements; - } - - // Implements IFeedbackPainter - @Override - public void paint(@NonNull IGraphics gc, @NonNull INode node, - @NonNull DropFeedback feedback) { - Rect b = node.getBounds(); - if (!b.isValid()) { - return; - } - - // Highlight the receiver - gc.useStyle(DrawingStyle.DROP_RECIPIENT); - gc.drawRect(b); - GridDropHandler data = (GridDropHandler) feedback.userData; - - if (!GridLayoutRule.sGridMode) { - paintFreeFormDropFeedback(gc, node, feedback, b, data); - } else { - paintGridModeDropFeedback(gc, b, data); - } - } - - /** - * Paints the drag feedback for a free-form mode drag - */ - private void paintFreeFormDropFeedback(IGraphics gc, INode node, DropFeedback feedback, - Rect b, GridDropHandler data) { - GridModel grid = data.getGrid(); - if (GridLayoutRule.sSnapToGrid) { - GridLayoutPainter.paintGrid(node, gc); - } - GridLayoutPainter.paintStructure(DrawingStyle.GRID, node, gc, grid); - - GridMatch rowMatch = data.getRowMatch(); - GridMatch columnMatch = data.getColumnMatch(); - - if (rowMatch == null || columnMatch == null) { - return; - } - - IDragElement first = mElements[0]; - Rect dragBounds = first.getBounds(); - int offsetX = 0; - int offsetY = 0; - if (rowMatch.type == SegmentType.BOTTOM) { - offsetY -= dragBounds.h; - } else if (rowMatch.type == SegmentType.BASELINE) { - offsetY -= feedback.dragBaseline; - } - if (columnMatch.type == SegmentType.RIGHT) { - offsetX -= dragBounds.w; - } else if (columnMatch.type == SegmentType.CENTER_HORIZONTAL) { - offsetX -= dragBounds.w / 2; - } - - // Draw guidelines for matches - int y = rowMatch.matchedLine; - int x = columnMatch.matchedLine; - Rect bounds = first.getBounds(); - - // Draw margin - if (rowMatch.margin != UNDEFINED && rowMatch.margin > 0) { - gc.useStyle(DrawingStyle.DISTANCE); - int centerX = bounds.w / 2 + offsetX + x; - int y1; - int y2; - if (rowMatch.type == SegmentType.TOP) { - y1 = offsetY + y - 1; - y2 = rowMatch.matchedLine - rowMatch.margin; - } else { - assert rowMatch.type == SegmentType.BOTTOM; - y1 = bounds.h + offsetY + y - 1; - y2 = rowMatch.matchedLine + rowMatch.margin; - } - gc.drawLine(b.x, y1, b.x2(), y1); - gc.drawLine(b.x, y2, b.x2(), y2); - gc.drawString(Integer.toString(rowMatch.margin), - centerX - 3, y1 + (y2 - y1 - 16) / 2); - } else { - gc.useStyle(rowMatch.margin == 0 ? DrawingStyle.DISTANCE - : rowMatch.createCell ? DrawingStyle.GUIDELINE_DASHED - : DrawingStyle.GUIDELINE); - gc.drawLine(b.x, y, b.x2(), y ); - } - - if (columnMatch.margin != UNDEFINED && columnMatch.margin > 0) { - gc.useStyle(DrawingStyle.DISTANCE); - int centerY = bounds.h / 2 + offsetY + y; - int x1; - int x2; - if (columnMatch.type == SegmentType.LEFT) { - x1 = offsetX + x - 1; - x2 = columnMatch.matchedLine - columnMatch.margin; - } else { - assert columnMatch.type == SegmentType.RIGHT; - x1 = bounds.w + offsetX + x - 1; - x2 = columnMatch.matchedLine + columnMatch.margin; - } - gc.drawLine(x1, b.y, x1, b.y2()); - gc.drawLine(x2, b.y, x2, b.y2()); - gc.drawString(Integer.toString(columnMatch.margin), - x1 + (x2 - x1 - 16) / 2, centerY - 3); - } else { - gc.useStyle(columnMatch.margin == 0 ? DrawingStyle.DISTANCE - : columnMatch.createCell ? DrawingStyle.GUIDELINE_DASHED - : DrawingStyle.GUIDELINE); - gc.drawLine(x, b.y, x, b.y2()); - } - - // Draw preview rectangles for all the dragged elements - gc.useStyle(DrawingStyle.DROP_PREVIEW); - offsetX += x - bounds.x; - offsetY += y - bounds.y; - - for (IDragElement element : mElements) { - if (element == first) { - mRule.drawElement(gc, first, offsetX, offsetY); - // Preview baseline as well - if (feedback.dragBaseline != -1) { - int x1 = dragBounds.x + offsetX; - int y1 = dragBounds.y + offsetY + feedback.dragBaseline; - gc.drawLine(x1, y1, x1 + dragBounds.w, y1); - } - } else { - b = element.getBounds(); - if (b.isValid()) { - gc.drawRect(b.x + offsetX, b.y + offsetY, - b.x + offsetX + b.w, b.y + offsetY + b.h); - } - } - } - } - - /** - * Paints the drag feedback for a grid-mode drag - */ - private void paintGridModeDropFeedback(IGraphics gc, Rect b, GridDropHandler data) { - int radius = mRule.getNewCellSize(); - GridModel grid = data.getGrid(); - - gc.useStyle(DrawingStyle.GUIDELINE); - // Paint grid - for (int row = 1; row < grid.actualRowCount; row++) { - int y = grid.getRowY(row); - gc.drawLine(b.x, y - radius, b.x2(), y - radius); - gc.drawLine(b.x, y + radius, b.x2(), y + radius); - - } - for (int column = 1; column < grid.actualColumnCount; column++) { - int x = grid.getColumnX(column); - gc.drawLine(x - radius, b.y, x - radius, b.y2()); - gc.drawLine(x + radius, b.y, x + radius, b.y2()); - } - gc.drawRect(b.x, b.y, b.x2(), b.y2()); - gc.drawRect(b.x + 2 * radius, b.y + 2 * radius, - b.x2() - 2 * radius, b.y2() - 2 * radius); - - GridMatch columnMatch = data.getColumnMatch(); - GridMatch rowMatch = data.getRowMatch(); - int column = columnMatch.cellIndex; - int row = rowMatch.cellIndex; - boolean createColumn = columnMatch.createCell; - boolean createRow = rowMatch.createCell; - - Rect cellBounds = grid.getCellBounds(row, column, 1, 1); - - IDragElement first = mElements[0]; - Rect dragBounds = first.getBounds(); - int offsetX = cellBounds.x - dragBounds.x; - int offsetY = cellBounds.y - dragBounds.y; - - gc.useStyle(DrawingStyle.DROP_ZONE_ACTIVE); - if (createColumn) { - gc.fillRect(new Rect(cellBounds.x - radius, - cellBounds.y + (createRow ? -radius : radius), - 2 * radius + 1, cellBounds.h - (createRow ? 0 : 2 * radius))); - offsetX -= radius + dragBounds.w / 2; - } - if (createRow) { - gc.fillRect(new Rect(cellBounds.x + radius, cellBounds.y - radius, - cellBounds.w - 2 * radius, 2 * radius + 1)); - offsetY -= radius + dragBounds.h / 2; - } else if (!createColumn) { - // Choose this cell - gc.fillRect(new Rect(cellBounds.x + radius, cellBounds.y + radius, - cellBounds.w - 2 * radius, cellBounds.h - 2 * radius)); - } - - gc.useStyle(DrawingStyle.DROP_PREVIEW); - - Rect bounds = first.getBounds(); - int x = offsetX; - int y = offsetY; - if (columnMatch.type == SegmentType.RIGHT) { - x += cellBounds.w - bounds.w; - } else if (columnMatch.type == SegmentType.CENTER_HORIZONTAL) { - x += cellBounds.w / 2 - bounds.w / 2; - } - if (rowMatch.type == SegmentType.BOTTOM) { - y += cellBounds.h - bounds.h; - } else if (rowMatch.type == SegmentType.CENTER_VERTICAL) { - y += cellBounds.h / 2 - bounds.h / 2; - } - - mRule.drawElement(gc, first, x, y); - } - } - - /** - * Paints the structure (the row and column boundaries) of the given - * GridLayout - * - * @param view the instance of the GridLayout whose structure should be - * painted - * @param style the drawing style to use for the cell boundaries - * @param layout the layout element - * @param gc the graphics context - * @return true if the structure was successfully inferred from the view and - * painted - */ - public static boolean paintStructure(Object view, DrawingStyle style, INode layout, - IGraphics gc) { - Pair<int[],int[]> cellBounds = GridModel.getAxisBounds(view); - if (cellBounds != null) { - int[] xs = cellBounds.getFirst(); - int[] ys = cellBounds.getSecond(); - Rect b = layout.getBounds(); - gc.useStyle(style); - for (int row = 0; row < ys.length; row++) { - int y = ys[row] + b.y; - gc.drawLine(b.x, y, b.x2(), y); - } - for (int column = 0; column < xs.length; column++) { - int x = xs[column] + b.x; - gc.drawLine(x, b.y, x, b.y2()); - } - - return true; - } else { - return false; - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridMatch.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridMatch.java deleted file mode 100644 index 9bee34345..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridMatch.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.common.layout.grid; - -import static com.android.ide.common.layout.grid.GridModel.UNDEFINED; - -import com.android.ide.common.api.INode; -import com.android.ide.common.api.SegmentType; - -/** - * A match for a drag within a GridLayout, corresponding to an alignment with another - * edge, or a margin, or centering, or a gap distance from another edge and so on. - */ -class GridMatch implements Comparable<GridMatch> { - /** The distance to the matched edge - used to pick best matches */ - public final int distance; - - /** Type of edge that was matched (this refers to the edge on the dragged node, - * not on the matched node/row/cell etc) */ - public final SegmentType type; - - /** Row or column for the match */ - public int cellIndex; - - /** If true, create a new row/column */ - public boolean createCell; - - /** The actual x or y position of the matched segment */ - public int matchedLine; - - /** Amount of margin between the matched edges */ - public int margin; - - /** - * Constructs a match. - * - * @param type the edge of the dragged element that was matched - * @param distance the absolute distance from the ideal match - used to find the best - * match - * @param matchedLine the actual X or Y location of the ideal match - * @param cellIndex the index of the row or column we matched with - * @param createCell if true, create a new cell by splitting the existing cell at the - * matchedLine position - * @param margin a margin distance to add to the actual location from the matched line - */ - GridMatch(SegmentType type, int distance, int matchedLine, int cellIndex, - boolean createCell, int margin) { - super(); - this.type = type; - this.distance = distance; - this.matchedLine = matchedLine; - this.cellIndex = cellIndex; - this.createCell = createCell; - this.margin = margin; - } - - // Implements Comparable<GridMatch> - @Override - public int compareTo(GridMatch o) { - // Pick closest matches first - if (distance != o.distance) { - return distance - o.distance; - } - - // Prefer some types of matches over other matches - return getPriority() - o.getPriority(); - } - - /** - * Describes the match for the user - * - * @param layout the GridLayout containing the match - * @return a short description for the user of the match - */ - public String getDisplayName(INode layout) { - switch (type) { - case BASELINE: - return String.format("Align baseline in row %1$d", cellIndex + 1); - case CENTER_HORIZONTAL: - return "Center horizontally"; - case LEFT: - if (!createCell) { - return String.format("Insert into column %1$d", cellIndex + 1); - } - if (margin != UNDEFINED) { - if (cellIndex == 0 && margin != 0) { - return "Add one margin distance from the left"; - } - return String.format("Add next to column %1$d", cellIndex + 1); - } - return String.format("Align left at x=%1$d", matchedLine - layout.getBounds().x); - case RIGHT: - if (!createCell) { - return String.format("Insert right-aligned into column %1$d", cellIndex + 1); - } - return String.format("Align right at x=%1$d", matchedLine - layout.getBounds().x); - case TOP: - if (!createCell) { - return String.format("Insert into row %1$d", cellIndex + 1); - } - if (margin != UNDEFINED) { - if (cellIndex == 0 && margin != 0) { - return "Add one margin distance from the top"; - } - return String.format("Add below row %1$d", cellIndex + 1); - } - return String.format("Align top at y=%1d", matchedLine - layout.getBounds().y); - case BOTTOM: - if (!createCell) { - return String.format("Insert into bottom of row %1$d", cellIndex + 1); - } - return String.format("Align bottom at y=%1d", matchedLine - layout.getBounds().y); - case CENTER_VERTICAL: - return "Center vertically"; - case UNKNOWN: - default: - return null; - } - } - - /** - * Computes the sorting priority of this match, giving baseline matches higher - * precedence than centering which in turn is ordered before external edge matches - */ - private int getPriority() { - switch (type) { - case BASELINE: - return 0; - case CENTER_HORIZONTAL: - case CENTER_VERTICAL: - return 1; - case BOTTOM: - case LEFT: - case RIGHT: - case TOP: - return 2; - } - - return 3; - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java deleted file mode 100644 index 46770e82c..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java +++ /dev/null @@ -1,2384 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.common.layout.grid; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_COLUMN_COUNT; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN; -import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN_SPAN; -import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY; -import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_ROW; -import static com.android.SdkConstants.ATTR_LAYOUT_ROW_SPAN; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_ORIENTATION; -import static com.android.SdkConstants.ATTR_ROW_COUNT; -import static com.android.SdkConstants.FQCN_GRID_LAYOUT; -import static com.android.SdkConstants.FQCN_SPACE; -import static com.android.SdkConstants.FQCN_SPACE_V7; -import static com.android.SdkConstants.GRID_LAYOUT; -import static com.android.SdkConstants.NEW_ID_PREFIX; -import static com.android.SdkConstants.SPACE; -import static com.android.SdkConstants.VALUE_BOTTOM; -import static com.android.SdkConstants.VALUE_CENTER_VERTICAL; -import static com.android.SdkConstants.VALUE_N_DP; -import static com.android.SdkConstants.VALUE_TOP; -import static com.android.SdkConstants.VALUE_VERTICAL; -import static com.android.ide.common.layout.GravityHelper.GRAVITY_BOTTOM; -import static com.android.ide.common.layout.GravityHelper.GRAVITY_CENTER_HORIZ; -import static com.android.ide.common.layout.GravityHelper.GRAVITY_CENTER_VERT; -import static com.android.ide.common.layout.GravityHelper.GRAVITY_RIGHT; -import static java.lang.Math.abs; -import static java.lang.Math.max; -import static java.lang.Math.min; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.IClientRulesEngine; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewMetadata; -import com.android.ide.common.api.Margins; -import com.android.ide.common.api.Rect; -import com.android.ide.common.layout.GravityHelper; -import com.android.ide.common.layout.GridLayoutRule; -import com.android.utils.Pair; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.lang.ref.WeakReference; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** Models a GridLayout */ -public class GridModel { - /** Marker value used to indicate values (rows, columns, etc) which have not been set */ - static final int UNDEFINED = Integer.MIN_VALUE; - - /** The size of spacers in the dimension that they are not defining */ - static final int SPACER_SIZE_DP = 1; - - /** Attribute value used for {@link #SPACER_SIZE_DP} */ - private static final String SPACER_SIZE = String.format(VALUE_N_DP, SPACER_SIZE_DP); - - /** Width assigned to a newly added column with the Add Column action */ - private static final int DEFAULT_CELL_WIDTH = 100; - - /** Height assigned to a newly added row with the Add Row action */ - private static final int DEFAULT_CELL_HEIGHT = 15; - - /** The GridLayout node, never null */ - public final INode layout; - - /** True if this is a vertical layout, and false if it is horizontal (the default) */ - public boolean vertical; - - /** The declared count of rows (which may be {@link #UNDEFINED} if not specified) */ - public int declaredRowCount; - - /** The declared count of columns (which may be {@link #UNDEFINED} if not specified) */ - public int declaredColumnCount; - - /** The actual count of rows found in the grid */ - public int actualRowCount; - - /** The actual count of columns found in the grid */ - public int actualColumnCount; - - /** - * Array of positions (indexed by column) of the left edge of table cells; this - * corresponds to the column positions in the grid - */ - private int[] mLeft; - - /** - * Array of positions (indexed by row) of the top edge of table cells; this - * corresponds to the row positions in the grid - */ - private int[] mTop; - - /** - * Array of positions (indexed by column) of the maximum right hand side bounds of a - * node in the given column; this represents the visual edge of a column even when the - * actual column is wider - */ - private int[] mMaxRight; - - /** - * Array of positions (indexed by row) of the maximum bottom bounds of a node in the - * given row; this represents the visual edge of a row even when the actual row is - * taller - */ - private int[] mMaxBottom; - - /** - * Array of baselines computed for the rows. This array is populated lazily and should - * not be accessed directly; call {@link #getBaseline(int)} instead. - */ - private int[] mBaselines; - - /** List of all the view data for the children in this layout */ - private List<ViewData> mChildViews; - - /** The {@link IClientRulesEngine} */ - private final IClientRulesEngine mRulesEngine; - - /** - * An actual instance of a GridLayout object that this grid model corresponds to. - */ - private Object mViewObject; - - /** The namespace to use for attributes */ - private String mNamespace; - - /** - * Constructs a {@link GridModel} for the given layout - * - * @param rulesEngine the associated rules engine - * @param node the GridLayout node - * @param viewObject an actual GridLayout instance, or null - */ - private GridModel(IClientRulesEngine rulesEngine, INode node, Object viewObject) { - mRulesEngine = rulesEngine; - layout = node; - mViewObject = viewObject; - loadFromXml(); - } - - // Factory cache for most recent item (used primarily because during paints and drags - // the grid model is called repeatedly for the same view object.) - private static WeakReference<Object> sCachedViewObject = new WeakReference<Object>(null); - private static WeakReference<GridModel> sCachedViewModel; - - /** - * Factory which returns a grid model for the given node. - * - * @param rulesEngine the associated rules engine - * @param node the GridLayout node - * @param viewObject an actual GridLayout instance, or null - * @return a new model - */ - @NonNull - public static GridModel get( - @NonNull IClientRulesEngine rulesEngine, - @NonNull INode node, - @Nullable Object viewObject) { - if (viewObject != null && viewObject == sCachedViewObject.get()) { - GridModel model = sCachedViewModel.get(); - if (model != null) { - return model; - } - } - - GridModel model = new GridModel(rulesEngine, node, viewObject); - sCachedViewModel = new WeakReference<GridModel>(model); - sCachedViewObject = new WeakReference<Object>(viewObject); - return model; - } - - /** - * Returns the {@link ViewData} for the child at the given index - * - * @param index the position of the child node whose view we want to look up - * @return the corresponding {@link ViewData} - */ - public ViewData getView(int index) { - return mChildViews.get(index); - } - - /** - * Returns the {@link ViewData} for the given child node. - * - * @param node the node for which we want the view info - * @return the view info for the node, or null if not found - */ - public ViewData getView(INode node) { - for (ViewData view : mChildViews) { - if (view.node == node) { - return view; - } - } - - return null; - } - - /** - * Computes the index (among the children nodes) to insert a new node into which - * should be positioned at the given row and column. This will skip over any nodes - * that have implicit positions earlier than the given node, and will also ensure that - * all nodes are placed before the spacer nodes. - * - * @param row the target row of the new node - * @param column the target column of the new node - * @return the insert position to use or -1 if no preference is found - */ - public int getInsertIndex(int row, int column) { - if (vertical) { - for (ViewData view : mChildViews) { - if (view.column > column || view.column == column && view.row >= row) { - return view.index; - } - } - } else { - for (ViewData view : mChildViews) { - if (view.row > row || view.row == row && view.column >= column) { - return view.index; - } - } - } - - // Place it before the first spacer - for (ViewData view : mChildViews) { - if (view.isSpacer()) { - return view.index; - } - } - - return -1; - } - - /** - * Returns the baseline of the given row, or -1 if none is found. This looks for views - * in the row which have baseline vertical alignment and also define their own - * baseline, and returns the first such match. - * - * @param row the row to look up a baseline for - * @return the baseline relative to the row position, or -1 if not defined - */ - public int getBaseline(int row) { - if (row < 0 || row >= mBaselines.length) { - return -1; - } - - int baseline = mBaselines[row]; - if (baseline == UNDEFINED) { - baseline = -1; - - // TBD: Consider stringing together row information in the view data - // so I can quickly identify the views in a given row instead of searching - // among all? - for (ViewData view : mChildViews) { - // We only count baselines for views with rowSpan=1 because - // baseline alignment doesn't work for cell spanning views - if (view.row == row && view.rowSpan == 1) { - baseline = view.node.getBaseline(); - if (baseline != -1) { - // Even views that do have baselines do not count towards a row - // baseline if they have a vertical gravity - String gravity = getGridAttribute(view.node, ATTR_LAYOUT_GRAVITY); - if (gravity == null - || !(gravity.contains(VALUE_TOP) - || gravity.contains(VALUE_BOTTOM) - || gravity.contains(VALUE_CENTER_VERTICAL))) { - // Compute baseline relative to the row, not the view itself - baseline += view.node.getBounds().y - getRowY(row); - break; - } - } - } - } - mBaselines[row] = baseline; - } - - return baseline; - } - - /** Applies the row and column values into the XML */ - void applyPositionAttributes() { - for (ViewData view : mChildViews) { - view.applyPositionAttributes(); - } - - // Also fix the columnCount - if (getGridAttribute(layout, ATTR_COLUMN_COUNT) != null && - declaredColumnCount > actualColumnCount) { - setGridAttribute(layout, ATTR_COLUMN_COUNT, actualColumnCount); - } - } - - /** - * Sets the given GridLayout attribute (rowCount, layout_row, etc) to the - * given value. This automatically handles using the right XML namespace - * based on whether the GridLayout is the android.widget.GridLayout, or the - * support library GridLayout, and whether it's in a library project or not - * etc. - * - * @param node the node to apply the attribute to - * @param name the local name of the attribute - * @param value the integer value to set the attribute to - */ - public void setGridAttribute(INode node, String name, int value) { - setGridAttribute(node, name, Integer.toString(value)); - } - - /** - * Sets the given GridLayout attribute (rowCount, layout_row, etc) to the - * given value. This automatically handles using the right XML namespace - * based on whether the GridLayout is the android.widget.GridLayout, or the - * support library GridLayout, and whether it's in a library project or not - * etc. - * - * @param node the node to apply the attribute to - * @param name the local name of the attribute - * @param value the string value to set the attribute to, or null to clear - * it - */ - public void setGridAttribute(INode node, String name, String value) { - node.setAttribute(getNamespace(), name, value); - } - - /** - * Returns the namespace URI to use for GridLayout-specific attributes, such - * as columnCount, layout_column, layout_column_span, layout_gravity etc. - * - * @return the namespace, never null - */ - public String getNamespace() { - if (mNamespace == null) { - mNamespace = ANDROID_URI; - - String fqcn = layout.getFqcn(); - if (!fqcn.equals(GRID_LAYOUT) && !fqcn.equals(FQCN_GRID_LAYOUT)) { - mNamespace = mRulesEngine.getAppNameSpace(); - } - } - - return mNamespace; - } - - /** Removes the given flag from a flag attribute value and returns the result */ - static String removeFlag(String flag, String value) { - if (value.equals(flag)) { - return null; - } - // Handle spaces between pipes and flag are a prefix, suffix and interior occurrences - int index = value.indexOf(flag); - if (index != -1) { - int pipe = value.lastIndexOf('|', index); - int endIndex = index + flag.length(); - if (pipe != -1) { - value = value.substring(0, pipe).trim() + value.substring(endIndex).trim(); - } else { - pipe = value.indexOf('|', endIndex); - if (pipe != -1) { - value = value.substring(0, index).trim() + value.substring(pipe + 1).trim(); - } else { - value = value.substring(0, index).trim() + value.substring(endIndex).trim(); - } - } - } - - return value; - } - - /** - * Loads a {@link GridModel} from the XML model. - */ - private void loadFromXml() { - INode[] children = layout.getChildren(); - - declaredRowCount = getGridAttribute(layout, ATTR_ROW_COUNT, UNDEFINED); - declaredColumnCount = getGridAttribute(layout, ATTR_COLUMN_COUNT, UNDEFINED); - // Horizontal is the default, so if no value is specified it is horizontal. - vertical = VALUE_VERTICAL.equals(getGridAttribute(layout, ATTR_ORIENTATION)); - - mChildViews = new ArrayList<ViewData>(children.length); - int index = 0; - for (INode child : children) { - ViewData view = new ViewData(child, index++); - mChildViews.add(view); - } - - // Assign row/column positions to all cells that do not explicitly define them - if (!assignRowsAndColumnsFromViews(mChildViews)) { - assignRowsAndColumnsFromXml( - declaredRowCount == UNDEFINED ? children.length : declaredRowCount, - declaredColumnCount == UNDEFINED ? children.length : declaredColumnCount); - } - - assignCellBounds(); - - for (int i = 0; i <= actualRowCount; i++) { - mBaselines[i] = UNDEFINED; - } - } - - private Pair<Map<Integer, Integer>, Map<Integer, Integer>> findCellsOutsideDeclaredBounds() { - // See if we have any (row,column) pairs that fall outside the declared - // bounds; for these we identify the number of unique values and assign these - // consecutive values - Map<Integer, Integer> extraColumnsMap = null; - Map<Integer, Integer> extraRowsMap = null; - if (declaredRowCount != UNDEFINED) { - Set<Integer> extraRows = null; - for (ViewData view : mChildViews) { - if (view.row >= declaredRowCount) { - if (extraRows == null) { - extraRows = new HashSet<Integer>(); - } - extraRows.add(view.row); - } - } - if (extraRows != null && declaredRowCount != UNDEFINED) { - List<Integer> rows = new ArrayList<Integer>(extraRows); - Collections.sort(rows); - int row = declaredRowCount; - extraRowsMap = new HashMap<Integer, Integer>(); - for (Integer declared : rows) { - extraRowsMap.put(declared, row++); - } - } - } - if (declaredColumnCount != UNDEFINED) { - Set<Integer> extraColumns = null; - for (ViewData view : mChildViews) { - if (view.column >= declaredColumnCount) { - if (extraColumns == null) { - extraColumns = new HashSet<Integer>(); - } - extraColumns.add(view.column); - } - } - if (extraColumns != null && declaredColumnCount != UNDEFINED) { - List<Integer> columns = new ArrayList<Integer>(extraColumns); - Collections.sort(columns); - int column = declaredColumnCount; - extraColumnsMap = new HashMap<Integer, Integer>(); - for (Integer declared : columns) { - extraColumnsMap.put(declared, column++); - } - } - } - - return Pair.of(extraRowsMap, extraColumnsMap); - } - - /** - * Figure out actual row and column numbers for views that do not specify explicit row - * and/or column numbers - * TODO: Consolidate with the algorithm in GridLayout to ensure we get the - * exact same results! - */ - private void assignRowsAndColumnsFromXml(int rowCount, int columnCount) { - Pair<Map<Integer, Integer>, Map<Integer, Integer>> p = findCellsOutsideDeclaredBounds(); - Map<Integer, Integer> extraRowsMap = p.getFirst(); - Map<Integer, Integer> extraColumnsMap = p.getSecond(); - - if (!vertical) { - // Horizontal GridLayout: this is the default. Row and column numbers - // are assigned by assuming that the children are assigned successive - // column numbers until we get to the column count of the grid, at which - // point we jump to the next row. If any cell specifies either an explicit - // row number of column number, we jump to the next available position. - // Note also that if there are any rowspans on the current row, then the - // next row we jump to is below the largest such rowspan - in other words, - // the algorithm does not fill holes in the middle! - - // TODO: Ensure that we don't run into trouble if a later element specifies - // an earlier number... find out what the layout does in that case! - int row = 0; - int column = 0; - int nextRow = 1; - for (ViewData view : mChildViews) { - int declaredColumn = view.column; - if (declaredColumn != UNDEFINED) { - if (declaredColumn >= columnCount) { - assert extraColumnsMap != null; - declaredColumn = extraColumnsMap.get(declaredColumn); - view.column = declaredColumn; - } - if (declaredColumn < column) { - // Must jump to the next row to accommodate the new row - assert nextRow > row; - //row++; - row = nextRow; - } - column = declaredColumn; - } else { - view.column = column; - } - if (view.row != UNDEFINED) { - // TODO: Should this adjust the column number too? (If so must - // also update view.column since we've already processed the local - // column number) - row = view.row; - } else { - view.row = row; - } - - nextRow = Math.max(nextRow, view.row + view.rowSpan); - - // Advance - column += view.columnSpan; - if (column >= columnCount) { - column = 0; - assert nextRow > row; - //row++; - row = nextRow; - } - } - } else { - // Vertical layout: successive children are assigned to the same column in - // successive rows. - int row = 0; - int column = 0; - int nextColumn = 1; - for (ViewData view : mChildViews) { - int declaredRow = view.row; - if (declaredRow != UNDEFINED) { - if (declaredRow >= rowCount) { - declaredRow = extraRowsMap.get(declaredRow); - view.row = declaredRow; - } - if (declaredRow < row) { - // Must jump to the next column to accommodate the new column - assert nextColumn > column; - column = nextColumn; - } - row = declaredRow; - } else { - view.row = row; - } - if (view.column != UNDEFINED) { - // TODO: Should this adjust the row number too? (If so must - // also update view.row since we've already processed the local - // row number) - column = view.column; - } else { - view.column = column; - } - - nextColumn = Math.max(nextColumn, view.column + view.columnSpan); - - // Advance - row += view.rowSpan; - if (row >= rowCount) { - row = 0; - assert nextColumn > column; - //row++; - column = nextColumn; - } - } - } - } - - private static boolean sAttemptSpecReflection = true; - - private boolean assignRowsAndColumnsFromViews(List<ViewData> views) { - if (!sAttemptSpecReflection) { - return false; - } - - try { - // Lazily initialized reflection methods - Field spanField = null; - Field rowSpecField = null; - Field colSpecField = null; - Field minField = null; - Field maxField = null; - Method getLayoutParams = null; - - for (ViewData view : views) { - // TODO: If the element *specifies* anything in XML, use that instead - Object child = mRulesEngine.getViewObject(view.node); - if (child == null) { - // Fallback to XML model - return false; - } - - if (getLayoutParams == null) { - getLayoutParams = child.getClass().getMethod("getLayoutParams"); //$NON-NLS-1$ - } - Object layoutParams = getLayoutParams.invoke(child); - if (rowSpecField == null) { - Class<? extends Object> layoutParamsClass = layoutParams.getClass(); - rowSpecField = layoutParamsClass.getDeclaredField("rowSpec"); //$NON-NLS-1$ - colSpecField = layoutParamsClass.getDeclaredField("columnSpec"); //$NON-NLS-1$ - rowSpecField.setAccessible(true); - colSpecField.setAccessible(true); - } - assert colSpecField != null; - - Object rowSpec = rowSpecField.get(layoutParams); - Object colSpec = colSpecField.get(layoutParams); - if (spanField == null) { - spanField = rowSpec.getClass().getDeclaredField("span"); //$NON-NLS-1$ - spanField.setAccessible(true); - } - assert spanField != null; - Object rowInterval = spanField.get(rowSpec); - Object colInterval = spanField.get(colSpec); - if (minField == null) { - Class<? extends Object> intervalClass = rowInterval.getClass(); - minField = intervalClass.getDeclaredField("min"); //$NON-NLS-1$ - maxField = intervalClass.getDeclaredField("max"); //$NON-NLS-1$ - minField.setAccessible(true); - maxField.setAccessible(true); - } - assert maxField != null; - - int row = minField.getInt(rowInterval); - int col = minField.getInt(colInterval); - int rowEnd = maxField.getInt(rowInterval); - int colEnd = maxField.getInt(colInterval); - - view.column = col; - view.row = row; - view.columnSpan = colEnd - col; - view.rowSpan = rowEnd - row; - } - - return true; - - } catch (Throwable e) { - sAttemptSpecReflection = false; - return false; - } - } - - /** - * Computes the positions of the column and row boundaries - */ - private void assignCellBounds() { - if (!assignCellBoundsFromView()) { - assignCellBoundsFromBounds(); - } - initializeMaxBounds(); - mBaselines = new int[actualRowCount + 1]; - } - - /** - * Computes the positions of the column and row boundaries, using actual - * layout data from the associated GridLayout instance (stored in - * {@link #mViewObject}) - */ - private boolean assignCellBoundsFromView() { - if (mViewObject != null) { - Pair<int[], int[]> cellBounds = GridModel.getAxisBounds(mViewObject); - if (cellBounds != null) { - int[] xs = cellBounds.getFirst(); - int[] ys = cellBounds.getSecond(); - Rect layoutBounds = layout.getBounds(); - - // Handle "blank" grid layouts: insert a fake grid of CELL_COUNT^2 cells - // where the user can do initial placement - if (actualColumnCount <= 1 && actualRowCount <= 1 && mChildViews.isEmpty()) { - final int CELL_COUNT = 1; - xs = new int[CELL_COUNT + 1]; - ys = new int[CELL_COUNT + 1]; - int cellWidth = layoutBounds.w / CELL_COUNT; - int cellHeight = layoutBounds.h / CELL_COUNT; - - for (int i = 0; i <= CELL_COUNT; i++) { - xs[i] = i * cellWidth; - ys[i] = i * cellHeight; - } - } - - actualColumnCount = xs.length - 1; - actualRowCount = ys.length - 1; - - int layoutBoundsX = layoutBounds.x; - int layoutBoundsY = layoutBounds.y; - mLeft = new int[xs.length]; - mTop = new int[ys.length]; - for (int i = 0; i < xs.length; i++) { - mLeft[i] = xs[i] + layoutBoundsX; - } - for (int i = 0; i < ys.length; i++) { - mTop[i] = ys[i] + layoutBoundsY; - } - - return true; - } - } - - return false; - } - - /** - * Computes the boundaries of the rows and columns by considering the bounds of the - * children. - */ - private void assignCellBoundsFromBounds() { - Rect layoutBounds = layout.getBounds(); - - // Compute the actualColumnCount and actualRowCount. This -should- be - // as easy as declaredColumnCount + extraColumnsMap.size(), - // but the user doesn't *have* to declare a column count (or a row count) - // and we need both, so go and find the actual row and column maximums. - int maxColumn = 0; - int maxRow = 0; - for (ViewData view : mChildViews) { - maxColumn = max(maxColumn, view.column); - maxRow = max(maxRow, view.row); - } - actualColumnCount = maxColumn + 1; - actualRowCount = maxRow + 1; - - mLeft = new int[actualColumnCount + 1]; - for (int i = 1; i < actualColumnCount; i++) { - mLeft[i] = UNDEFINED; - } - mLeft[0] = layoutBounds.x; - mLeft[actualColumnCount] = layoutBounds.x2(); - mTop = new int[actualRowCount + 1]; - for (int i = 1; i < actualRowCount; i++) { - mTop[i] = UNDEFINED; - } - mTop[0] = layoutBounds.y; - mTop[actualRowCount] = layoutBounds.y2(); - - for (ViewData view : mChildViews) { - Rect bounds = view.node.getBounds(); - if (!bounds.isValid()) { - continue; - } - int column = view.column; - int row = view.row; - - if (mLeft[column] == UNDEFINED) { - mLeft[column] = bounds.x; - } else { - mLeft[column] = Math.min(bounds.x, mLeft[column]); - } - if (mTop[row] == UNDEFINED) { - mTop[row] = bounds.y; - } else { - mTop[row] = Math.min(bounds.y, mTop[row]); - } - } - - // Ensure that any empty columns/rows have a valid boundary value; for now, - for (int i = actualColumnCount - 1; i >= 0; i--) { - if (mLeft[i] == UNDEFINED) { - if (i == 0) { - mLeft[i] = layoutBounds.x; - } else if (i < actualColumnCount - 1) { - mLeft[i] = mLeft[i + 1] - 1; - if (mLeft[i - 1] != UNDEFINED && mLeft[i] < mLeft[i - 1]) { - mLeft[i] = mLeft[i - 1]; - } - } else { - mLeft[i] = layoutBounds.x2(); - } - } - } - for (int i = actualRowCount - 1; i >= 0; i--) { - if (mTop[i] == UNDEFINED) { - if (i == 0) { - mTop[i] = layoutBounds.y; - } else if (i < actualRowCount - 1) { - mTop[i] = mTop[i + 1] - 1; - if (mTop[i - 1] != UNDEFINED && mTop[i] < mTop[i - 1]) { - mTop[i] = mTop[i - 1]; - } - } else { - mTop[i] = layoutBounds.y2(); - } - } - } - - // The bounds should be in ascending order now - if (false && GridLayoutRule.sDebugGridLayout) { - for (int i = 1; i < actualRowCount; i++) { - assert mTop[i + 1] >= mTop[i]; - } - for (int i = 0; i < actualColumnCount; i++) { - assert mLeft[i + 1] >= mLeft[i]; - } - } - } - - /** - * Determine, for each row and column, what the largest x and y edges are - * within that row or column. This is used to find a natural split point to - * suggest when adding something "to the right of" or "below" another view. - */ - private void initializeMaxBounds() { - mMaxRight = new int[actualColumnCount + 1]; - mMaxBottom = new int[actualRowCount + 1]; - - for (ViewData view : mChildViews) { - Rect bounds = view.node.getBounds(); - if (!bounds.isValid()) { - continue; - } - - if (!view.isSpacer()) { - int x2 = bounds.x2(); - int y2 = bounds.y2(); - int column = view.column; - int row = view.row; - int targetColumn = min(actualColumnCount - 1, - column + view.columnSpan - 1); - int targetRow = min(actualRowCount - 1, row + view.rowSpan - 1); - IViewMetadata metadata = mRulesEngine.getMetadata(view.node.getFqcn()); - if (metadata != null) { - Margins insets = metadata.getInsets(); - if (insets != null) { - x2 -= insets.right; - y2 -= insets.bottom; - } - } - if (mMaxRight[targetColumn] < x2 - && ((view.gravity & (GRAVITY_CENTER_HORIZ | GRAVITY_RIGHT)) == 0)) { - mMaxRight[targetColumn] = x2; - } - if (mMaxBottom[targetRow] < y2 - && ((view.gravity & (GRAVITY_CENTER_VERT | GRAVITY_BOTTOM)) == 0)) { - mMaxBottom[targetRow] = y2; - } - } - } - } - - /** - * Looks up the x[] and y[] locations of the columns and rows in the given GridLayout - * instance. - * - * @param view the GridLayout object, which should already have performed layout - * @return a pair of x[] and y[] integer arrays, or null if it could not be found - */ - public static Pair<int[], int[]> getAxisBounds(Object view) { - try { - Class<?> clz = view.getClass(); - String verticalAxisName = "verticalAxis"; - Field horizontalAxis; - try { - horizontalAxis = clz.getDeclaredField("horizontalAxis"); //$NON-NLS-1$ - } catch (NoSuchFieldException e) { - // Field names changed in KitKat - horizontalAxis = clz.getDeclaredField("mHorizontalAxis"); //$NON-NLS-1$ - verticalAxisName = "mVerticalAxis"; - } - Field verticalAxis = clz.getDeclaredField(verticalAxisName); - horizontalAxis.setAccessible(true); - verticalAxis.setAccessible(true); - Object horizontal = horizontalAxis.get(view); - Object vertical = verticalAxis.get(view); - Field locations = horizontal.getClass().getDeclaredField("locations"); //$NON-NLS-1$ - assert locations.getType().isArray() : locations.getType(); - locations.setAccessible(true); - Object horizontalLocations = locations.get(horizontal); - Object verticalLocations = locations.get(vertical); - int[] xs = (int[]) horizontalLocations; - int[] ys = (int[]) verticalLocations; - return Pair.of(xs, ys); - } catch (Throwable t) { - // Probably trying to show a GridLayout on a platform that does not support it. - // Return null to indicate that the grid bounds must be computed from view bounds. - return null; - } - } - - /** - * Add a new column. - * - * @param selectedChildren if null or empty, add the column at the end of the grid, - * and otherwise add it before the column of the first selected child - * @return the newly added column spacer - */ - public INode addColumn(List<? extends INode> selectedChildren) { - // Determine insert index - int newColumn = actualColumnCount; - if (selectedChildren != null && selectedChildren.size() > 0) { - INode first = selectedChildren.get(0); - ViewData view = getView(first); - newColumn = view.column; - } - - INode newView = addColumn(newColumn, null, UNDEFINED, false, UNDEFINED, UNDEFINED); - if (newView != null) { - mRulesEngine.select(Collections.singletonList(newView)); - } - - return newView; - } - - /** - * Adds a new column. - * - * @param newColumn the column index to insert before - * @param newView the {@link INode} to insert as the column spacer, which may be null - * (in which case a spacer is automatically created) - * @param columnWidthDp the width, in device independent pixels, of the column to be - * added (which may be {@link #UNDEFINED} - * @param split if true, split the existing column into two at the given x position - * @param row the row to add the newView to - * @param x the x position of the column we're inserting - * @return the column spacer - */ - public INode addColumn(int newColumn, INode newView, int columnWidthDp, - boolean split, int row, int x) { - // Insert a new column - actualColumnCount++; - if (declaredColumnCount != UNDEFINED) { - declaredColumnCount++; - setGridAttribute(layout, ATTR_COLUMN_COUNT, declaredColumnCount); - } - - boolean isLastColumn = true; - for (ViewData view : mChildViews) { - if (view.column >= newColumn) { - isLastColumn = false; - break; - } - } - - for (ViewData view : mChildViews) { - boolean columnSpanSet = false; - - int endColumn = view.column + view.columnSpan; - if (view.column >= newColumn || endColumn == newColumn) { - if (view.column == newColumn || endColumn == newColumn) { - //if (view.row == 0) { - if (newView == null && !isLastColumn) { - // Insert a new spacer - int index = getChildIndex(layout.getChildren(), view.node); - assert view.index == index; // TODO: Get rid of getter - if (endColumn == newColumn) { - // This cell -ends- at the desired position: insert it after - index++; - } - - ViewData newViewData = addSpacer(layout, index, - split ? row : UNDEFINED, - split ? newColumn - 1 : UNDEFINED, - columnWidthDp != UNDEFINED ? columnWidthDp : DEFAULT_CELL_WIDTH, - DEFAULT_CELL_HEIGHT); - newViewData.column = newColumn - 1; - newViewData.row = row; - newView = newViewData.node; - } - - // Set the actual row number on the first cell on the new row. - // This means we don't really need the spacer above to imply - // the new row number, but we use the spacer to assign the row - // some height. - if (view.column == newColumn) { - view.column++; - setGridAttribute(view.node, ATTR_LAYOUT_COLUMN, view.column); - } // else: endColumn == newColumn: handled below - } else if (getGridAttribute(view.node, ATTR_LAYOUT_COLUMN) != null) { - view.column++; - setGridAttribute(view.node, ATTR_LAYOUT_COLUMN, view.column); - } - } else if (endColumn > newColumn) { - view.columnSpan++; - setColumnSpanAttribute(view.node, view.columnSpan); - columnSpanSet = true; - } - - if (split && !columnSpanSet && view.node.getBounds().x2() > x) { - if (view.node.getBounds().x < x) { - view.columnSpan++; - setColumnSpanAttribute(view.node, view.columnSpan); - } - } - } - - // Hardcode the row numbers if the last column is a new column such that - // they don't jump back to backfill the previous row's new last cell - if (isLastColumn) { - for (ViewData view : mChildViews) { - if (view.column == 0 && view.row > 0) { - setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); - } - } - if (split) { - assert newView == null; - addSpacer(layout, -1, row, newColumn -1, - columnWidthDp != UNDEFINED ? columnWidthDp : DEFAULT_CELL_WIDTH, - SPACER_SIZE_DP); - } - } - - return newView; - } - - /** - * Removes the columns containing the given selection - * - * @param selectedChildren a list of nodes whose columns should be deleted - */ - public void removeColumns(List<? extends INode> selectedChildren) { - if (selectedChildren.size() == 0) { - return; - } - - // Figure out which columns should be removed - Set<Integer> removeColumns = new HashSet<Integer>(); - Set<ViewData> removedViews = new HashSet<ViewData>(); - for (INode child : selectedChildren) { - ViewData view = getView(child); - removedViews.add(view); - removeColumns.add(view.column); - } - // Sort them in descending order such that we can process each - // deletion independently - List<Integer> removed = new ArrayList<Integer>(removeColumns); - Collections.sort(removed, Collections.reverseOrder()); - - for (int removedColumn : removed) { - // Remove column. - // First, adjust column count. - // TODO: Don't do this if the column being deleted is outside - // the declared column range! - // TODO: Do this under a write lock? / editXml lock? - actualColumnCount--; - if (declaredColumnCount != UNDEFINED) { - declaredColumnCount--; - } - - // Remove any elements that begin in the deleted columns... - // If they have colspan > 1, then we must insert a spacer instead. - // For any other elements that overlap, we need to subtract from the span. - - for (ViewData view : mChildViews) { - if (view.column == removedColumn) { - int index = getChildIndex(layout.getChildren(), view.node); - assert view.index == index; // TODO: Get rid of getter - if (view.columnSpan > 1) { - // Make a new spacer which is the width of the following - // columns - int columnWidth = getColumnWidth(removedColumn, view.columnSpan) - - getColumnWidth(removedColumn, 1); - int columnWidthDip = mRulesEngine.pxToDp(columnWidth); - ViewData spacer = addSpacer(layout, index, UNDEFINED, UNDEFINED, - columnWidthDip, SPACER_SIZE_DP); - spacer.row = 0; - spacer.column = removedColumn; - } - layout.removeChild(view.node); - } else if (view.column < removedColumn - && view.column + view.columnSpan > removedColumn) { - // Subtract column span to skip this item - view.columnSpan--; - setColumnSpanAttribute(view.node, view.columnSpan); - } else if (view.column > removedColumn) { - view.column--; - if (getGridAttribute(view.node, ATTR_LAYOUT_COLUMN) != null) { - setGridAttribute(view.node, ATTR_LAYOUT_COLUMN, view.column); - } - } - } - } - - // Remove children from child list! - if (removedViews.size() <= 2) { - mChildViews.removeAll(removedViews); - } else { - List<ViewData> remaining = - new ArrayList<ViewData>(mChildViews.size() - removedViews.size()); - for (ViewData view : mChildViews) { - if (!removedViews.contains(view)) { - remaining.add(view); - } - } - mChildViews = remaining; - } - - //if (declaredColumnCount != UNDEFINED) { - setGridAttribute(layout, ATTR_COLUMN_COUNT, actualColumnCount); - //} - - } - - /** - * Add a new row. - * - * @param selectedChildren if null or empty, add the row at the bottom of the grid, - * and otherwise add it before the row of the first selected child - * @return the newly added row spacer - */ - public INode addRow(List<? extends INode> selectedChildren) { - // Determine insert index - int newRow = actualRowCount; - if (selectedChildren.size() > 0) { - INode first = selectedChildren.get(0); - ViewData view = getView(first); - newRow = view.row; - } - - INode newView = addRow(newRow, null, UNDEFINED, false, UNDEFINED, UNDEFINED); - if (newView != null) { - mRulesEngine.select(Collections.singletonList(newView)); - } - - return newView; - } - - /** - * Adds a new column. - * - * @param newRow the row index to insert before - * @param newView the {@link INode} to insert as the row spacer, which may be null (in - * which case a spacer is automatically created) - * @param rowHeightDp the height, in device independent pixels, of the row to be added - * (which may be {@link #UNDEFINED} - * @param split if true, split the existing row into two at the given y position - * @param column the column to add the newView to - * @param y the y position of the row we're inserting - * @return the row spacer - */ - public INode addRow(int newRow, INode newView, int rowHeightDp, boolean split, - int column, int y) { - actualRowCount++; - if (declaredRowCount != UNDEFINED) { - declaredRowCount++; - setGridAttribute(layout, ATTR_ROW_COUNT, declaredRowCount); - } - - boolean added = false; - for (ViewData view : mChildViews) { - if (view.row >= newRow) { - // Adjust the column count - if (view.row == newRow && view.column == 0) { - // Insert a new spacer - if (newView == null) { - int index = getChildIndex(layout.getChildren(), view.node); - assert view.index == index; // TODO: Get rid of getter - if (declaredColumnCount != UNDEFINED && !split) { - setGridAttribute(layout, ATTR_COLUMN_COUNT, declaredColumnCount); - } - ViewData newViewData = addSpacer(layout, index, - split ? newRow - 1 : UNDEFINED, - split ? column : UNDEFINED, - SPACER_SIZE_DP, - rowHeightDp != UNDEFINED ? rowHeightDp : DEFAULT_CELL_HEIGHT); - newViewData.column = column; - newViewData.row = newRow - 1; - newView = newViewData.node; - } - - // Set the actual row number on the first cell on the new row. - // This means we don't really need the spacer above to imply - // the new row number, but we use the spacer to assign the row - // some height. - view.row++; - setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); - - added = true; - } else if (getGridAttribute(view.node, ATTR_LAYOUT_ROW) != null) { - view.row++; - setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); - } - } else { - int endRow = view.row + view.rowSpan; - if (endRow > newRow) { - view.rowSpan++; - setRowSpanAttribute(view.node, view.rowSpan); - } else if (split && view.node.getBounds().y2() > y) { - if (view.node.getBounds().y < y) { - view.rowSpan++; - setRowSpanAttribute(view.node, view.rowSpan); - } - } - } - } - - if (!added) { - // Append a row at the end - if (newView == null) { - ViewData newViewData = addSpacer(layout, -1, UNDEFINED, UNDEFINED, - SPACER_SIZE_DP, - rowHeightDp != UNDEFINED ? rowHeightDp : DEFAULT_CELL_HEIGHT); - newViewData.column = column; - // TODO: MAke sure this row number is right! - newViewData.row = split ? newRow - 1 : newRow; - newView = newViewData.node; - } - if (declaredColumnCount != UNDEFINED && !split) { - setGridAttribute(layout, ATTR_COLUMN_COUNT, declaredColumnCount); - } - if (split) { - setGridAttribute(newView, ATTR_LAYOUT_ROW, newRow - 1); - setGridAttribute(newView, ATTR_LAYOUT_COLUMN, column); - } - } - - return newView; - } - - /** - * Removes the rows containing the given selection - * - * @param selectedChildren a list of nodes whose rows should be deleted - */ - public void removeRows(List<? extends INode> selectedChildren) { - if (selectedChildren.size() == 0) { - return; - } - - // Figure out which rows should be removed - Set<ViewData> removedViews = new HashSet<ViewData>(); - Set<Integer> removedRows = new HashSet<Integer>(); - for (INode child : selectedChildren) { - ViewData view = getView(child); - removedViews.add(view); - removedRows.add(view.row); - } - // Sort them in descending order such that we can process each - // deletion independently - List<Integer> removed = new ArrayList<Integer>(removedRows); - Collections.sort(removed, Collections.reverseOrder()); - - for (int removedRow : removed) { - // Remove row. - // First, adjust row count. - // TODO: Don't do this if the row being deleted is outside - // the declared row range! - actualRowCount--; - if (declaredRowCount != UNDEFINED) { - declaredRowCount--; - setGridAttribute(layout, ATTR_ROW_COUNT, declaredRowCount); - } - - // Remove any elements that begin in the deleted rows... - // If they have colspan > 1, then we must hardcode a new row number - // instead. - // For any other elements that overlap, we need to subtract from the span. - - for (ViewData view : mChildViews) { - if (view.row == removedRow) { - // We don't have to worry about a rowSpan > 1 here, because even - // if it is, those rowspans are not used to assign default row/column - // positions for other cells -// TODO: Check this; it differs from the removeColumns logic! - layout.removeChild(view.node); - } else if (view.row > removedRow) { - view.row--; - if (getGridAttribute(view.node, ATTR_LAYOUT_ROW) != null) { - setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); - } - } else if (view.row < removedRow - && view.row + view.rowSpan > removedRow) { - // Subtract row span to skip this item - view.rowSpan--; - setRowSpanAttribute(view.node, view.rowSpan); - } - } - } - - // Remove children from child list! - if (removedViews.size() <= 2) { - mChildViews.removeAll(removedViews); - } else { - List<ViewData> remaining = - new ArrayList<ViewData>(mChildViews.size() - removedViews.size()); - for (ViewData view : mChildViews) { - if (!removedViews.contains(view)) { - remaining.add(view); - } - } - mChildViews = remaining; - } - } - - /** - * Returns the row containing the given y line - * - * @param y the vertical position - * @return the row containing the given line - */ - public int getRow(int y) { - int row = Arrays.binarySearch(mTop, y); - if (row == -1) { - // Smaller than the first element; just use the first row - return 0; - } else if (row < 0) { - row = -(row + 2); - } - - return row; - } - - /** - * Returns the column containing the given x line - * - * @param x the horizontal position - * @return the column containing the given line - */ - public int getColumn(int x) { - int column = Arrays.binarySearch(mLeft, x); - if (column == -1) { - // Smaller than the first element; just use the first column - return 0; - } else if (column < 0) { - column = -(column + 2); - } - - return column; - } - - /** - * Returns the closest row to the given y line. This is - * either the row containing the line, or the row below it. - * - * @param y the vertical position - * @return the closest row - */ - public int getClosestRow(int y) { - int row = Arrays.binarySearch(mTop, y); - if (row == -1) { - // Smaller than the first element; just use the first column - return 0; - } else if (row < 0) { - row = -(row + 2); - } - - if (getRowDistance(row, y) < getRowDistance(row + 1, y)) { - return row; - } else { - return row + 1; - } - } - - /** - * Returns the closest column to the given x line. This is - * either the column containing the line, or the column following it. - * - * @param x the horizontal position - * @return the closest column - */ - public int getClosestColumn(int x) { - int column = Arrays.binarySearch(mLeft, x); - if (column == -1) { - // Smaller than the first element; just use the first column - return 0; - } else if (column < 0) { - column = -(column + 2); - } - - if (getColumnDistance(column, x) < getColumnDistance(column + 1, x)) { - return column; - } else { - return column + 1; - } - } - - /** - * Returns the distance between the given x position and the beginning of the given column - * - * @param column the column - * @param x the x position - * @return the distance between the two - */ - public int getColumnDistance(int column, int x) { - return abs(getColumnX(column) - x); - } - - /** - * Returns the actual width of the given column. This returns the difference between - * the rightmost edge of the views (not including spacers) and the left edge of the - * column. - * - * @param column the column - * @return the actual width of the non-spacer views in the column - */ - public int getColumnActualWidth(int column) { - return getColumnMaxX(column) - getColumnX(column); - } - - /** - * Returns the distance between the given y position and the top of the given row - * - * @param row the row - * @param y the y position - * @return the distance between the two - */ - public int getRowDistance(int row, int y) { - return abs(getRowY(row) - y); - } - - /** - * Returns the y position of the top of the given row - * - * @param row the target row - * @return the y position of its top edge - */ - public int getRowY(int row) { - return mTop[min(mTop.length - 1, max(0, row))]; - } - - /** - * Returns the bottom-most edge of any of the non-spacer children in the given row - * - * @param row the target row - * @return the bottom-most edge of any of the non-spacer children in the row - */ - public int getRowMaxY(int row) { - return mMaxBottom[min(mMaxBottom.length - 1, max(0, row))]; - } - - /** - * Returns the actual height of the given row. This returns the difference between - * the bottom-most edge of the views (not including spacers) and the top edge of the - * row. - * - * @param row the row - * @return the actual height of the non-spacer views in the row - */ - public int getRowActualHeight(int row) { - return getRowMaxY(row) - getRowY(row); - } - - /** - * Returns a list of all the nodes that intersects the rows in the range - * {@code y1 <= y <= y2}. - * - * @param y1 the starting y, inclusive - * @param y2 the ending y, inclusive - * @return a list of nodes intersecting the given rows, never null but possibly empty - */ - public Collection<INode> getIntersectsRow(int y1, int y2) { - List<INode> nodes = new ArrayList<INode>(); - - for (ViewData view : mChildViews) { - if (!view.isSpacer()) { - Rect bounds = view.node.getBounds(); - if (bounds.y2() >= y1 && bounds.y <= y2) { - nodes.add(view.node); - } - } - } - - return nodes; - } - - /** - * Returns the height of the given row or rows (if the rowSpan is greater than 1) - * - * @param row the target row - * @param rowSpan the row span - * @return the height in pixels of the given rows - */ - public int getRowHeight(int row, int rowSpan) { - return getRowY(row + rowSpan) - getRowY(row); - } - - /** - * Returns the x position of the left edge of the given column - * - * @param column the target column - * @return the x position of its left edge - */ - public int getColumnX(int column) { - return mLeft[min(mLeft.length - 1, max(0, column))]; - } - - /** - * Returns the rightmost edge of any of the non-spacer children in the given row - * - * @param column the target column - * @return the rightmost edge of any of the non-spacer children in the column - */ - public int getColumnMaxX(int column) { - return mMaxRight[min(mMaxRight.length - 1, max(0, column))]; - } - - /** - * Returns the width of the given column or columns (if the columnSpan is greater than 1) - * - * @param column the target column - * @param columnSpan the column span - * @return the width in pixels of the given columns - */ - public int getColumnWidth(int column, int columnSpan) { - return getColumnX(column + columnSpan) - getColumnX(column); - } - - /** - * Returns the bounds of the cell at the given row and column position, with the given - * row and column spans. - * - * @param row the target row - * @param column the target column - * @param rowSpan the row span - * @param columnSpan the column span - * @return the bounds, in pixels, of the given cell - */ - public Rect getCellBounds(int row, int column, int rowSpan, int columnSpan) { - return new Rect(getColumnX(column), getRowY(row), - getColumnWidth(column, columnSpan), - getRowHeight(row, rowSpan)); - } - - /** - * Produces a display of view contents along with the pixel positions of each - * row/column, like the following (used for diagnostics only) - * - * <pre> - * |0 |49 |143 |192 |240 - * 36| | |button2 | - * 72| |radioButton1 |button2 | - * 74|button1 |radioButton1 |button2 | - * 108|button1 | |button2 | - * 110| | |button2 | - * 149| | | | - * 320 - * </pre> - */ - @Override - public String toString() { - // Dump out the view table - int cellWidth = 25; - - List<List<List<ViewData>>> rowList = new ArrayList<List<List<ViewData>>>(mTop.length); - for (int row = 0; row < mTop.length; row++) { - List<List<ViewData>> columnList = new ArrayList<List<ViewData>>(mLeft.length); - for (int col = 0; col < mLeft.length; col++) { - columnList.add(new ArrayList<ViewData>(4)); - } - rowList.add(columnList); - } - for (ViewData view : mChildViews) { - for (int i = 0; i < view.rowSpan; i++) { - if (view.row + i > mTop.length) { // Guard against bogus span values - break; - } - if (rowList.size() <= view.row + i) { - break; - } - for (int j = 0; j < view.columnSpan; j++) { - List<List<ViewData>> columnList = rowList.get(view.row + i); - if (columnList.size() <= view.column + j) { - break; - } - columnList.get(view.column + j).add(view); - } - } - } - - StringWriter stringWriter = new StringWriter(); - PrintWriter out = new PrintWriter(stringWriter); - out.printf("%" + cellWidth + "s", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - for (int col = 0; col < actualColumnCount + 1; col++) { - out.printf("|%-" + (cellWidth - 1) + "d", mLeft[col]); //$NON-NLS-1$ //$NON-NLS-2$ - } - out.printf("\n"); //$NON-NLS-1$ - for (int row = 0; row < actualRowCount + 1; row++) { - out.printf("%" + cellWidth + "d", mTop[row]); //$NON-NLS-1$ //$NON-NLS-2$ - if (row == actualRowCount) { - break; - } - for (int col = 0; col < actualColumnCount; col++) { - List<ViewData> views = rowList.get(row).get(col); - - StringBuilder sb = new StringBuilder(); - for (ViewData view : views) { - String id = view != null ? view.getId() : ""; //$NON-NLS-1$ - if (id.startsWith(NEW_ID_PREFIX)) { - id = id.substring(NEW_ID_PREFIX.length()); - } - if (id.length() > cellWidth - 2) { - id = id.substring(0, cellWidth - 2); - } - if (sb.length() > 0) { - sb.append(','); - } - sb.append(id); - } - String cellString = sb.toString(); - if (cellString.contains(",") && cellString.length() > cellWidth - 2) { //$NON-NLS-1$ - cellString = cellString.substring(0, cellWidth - 6) + "...,"; //$NON-NLS-1$ - } - out.printf("|%-" + (cellWidth - 2) + "s ", cellString); //$NON-NLS-1$ //$NON-NLS-2$ - } - out.printf("\n"); //$NON-NLS-1$ - } - - out.flush(); - return stringWriter.toString(); - } - - /** - * Split a cell into two or three columns. - * - * @param newColumn The column number to insert before - * @param insertMarginColumn If false, then the cell at newColumn -1 is split with the - * left part taking up exactly columnWidthDp dips. If true, then the column - * is split twice; the left part is the implicit width of the column, the - * new middle (margin) column is exactly the columnWidthDp size and the - * right column is the remaining space of the old cell. - * @param columnWidthDp The width of the column inserted before the new column (or if - * insertMarginColumn is false, then the width of the margin column) - * @param x the x coordinate of the new column - */ - public void splitColumn(int newColumn, boolean insertMarginColumn, int columnWidthDp, int x) { - actualColumnCount++; - - // Insert a new column - if (declaredColumnCount != UNDEFINED) { - declaredColumnCount++; - if (insertMarginColumn) { - declaredColumnCount++; - } - setGridAttribute(layout, ATTR_COLUMN_COUNT, declaredColumnCount); - } - - // Are we inserting a new last column in the grid? That requires some special handling... - boolean isLastColumn = true; - for (ViewData view : mChildViews) { - if (view.column >= newColumn) { - isLastColumn = false; - break; - } - } - - // Hardcode the row numbers if the last column is a new column such that - // they don't jump back to backfill the previous row's new last cell: - // TODO: Only do this for horizontal layouts! - if (isLastColumn) { - for (ViewData view : mChildViews) { - if (view.column == 0 && view.row > 0) { - if (getGridAttribute(view.node, ATTR_LAYOUT_ROW) == null) { - setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); - } - } - } - } - - // Find the spacer which marks this column, and if found, mark it as a split - ViewData prevColumnSpacer = null; - for (ViewData view : mChildViews) { - if (view.column == newColumn - 1 && view.isColumnSpacer()) { - prevColumnSpacer = view; - break; - } - } - - // Process all existing grid elements: - // * Increase column numbers for all columns that have a hardcoded column number - // greater than the new column - // * Set an explicit column=0 where needed (TODO: Implement this) - // * Increase the columnSpan for all columns that overlap the newly inserted column edge - // * Split the spacer which defined the size of this column into two - // (and if not found, create a new spacer) - // - for (ViewData view : mChildViews) { - if (view == prevColumnSpacer) { - continue; - } - - INode node = view.node; - int column = view.column; - if (column > newColumn || (column == newColumn && view.node.getBounds().x2() > x)) { - // ALWAYS set the column, because - // (1) if it has been set, it needs to be corrected - // (2) if it has not been set, it needs to be set to cause this column - // to skip over the new column (there may be no views for the new - // column on this row). - // TODO: Enhance this such that we only set the column to a skip number - // where necessary, e.g. only on the FIRST view on this row following the - // skipped column! - - //if (getGridAttribute(node, ATTR_LAYOUT_COLUMN) != null) { - view.column += insertMarginColumn ? 2 : 1; - setGridAttribute(node, ATTR_LAYOUT_COLUMN, view.column); - //} - } else if (!view.isSpacer()) { - // Adjust the column span? We must increase it if - // (1) the new column is inside the range [column, column + columnSpan] - // (2) the new column is within the last cell in the column span, - // and the exact X location of the split is within the horizontal - // *bounds* of this node (provided it has gravity=left) - // (3) the new column is within the last cell and the cell has gravity - // right or gravity center - int endColumn = column + view.columnSpan; - if (endColumn > newColumn - || endColumn == newColumn && (view.node.getBounds().x2() > x - || GravityHelper.isConstrainedHorizontally(view.gravity) - && !GravityHelper.isLeftAligned(view.gravity))) { - // This cell spans the new insert position, so increment the column span - view.columnSpan += insertMarginColumn ? 2 : 1; - setColumnSpanAttribute(node, view.columnSpan); - } - } - } - - // Insert new spacer: - if (prevColumnSpacer != null) { - int px = getColumnWidth(newColumn - 1, 1); - if (insertMarginColumn || columnWidthDp == 0) { - px -= getColumnActualWidth(newColumn - 1); - } - int dp = mRulesEngine.pxToDp(px); - int remaining = dp - columnWidthDp; - if (remaining > 0) { - prevColumnSpacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, - String.format(VALUE_N_DP, remaining)); - prevColumnSpacer.column = insertMarginColumn ? newColumn + 1 : newColumn; - setGridAttribute(prevColumnSpacer.node, ATTR_LAYOUT_COLUMN, - prevColumnSpacer.column); - } - } - - if (columnWidthDp > 0) { - int index = prevColumnSpacer != null ? prevColumnSpacer.index : -1; - - addSpacer(layout, index, 0, insertMarginColumn ? newColumn : newColumn - 1, - columnWidthDp, SPACER_SIZE_DP); - } - } - - /** - * Split a cell into two or three rows. - * - * @param newRow The row number to insert before - * @param insertMarginRow If false, then the cell at newRow -1 is split with the above - * part taking up exactly rowHeightDp dips. If true, then the row is split - * twice; the top part is the implicit height of the row, the new middle - * (margin) row is exactly the rowHeightDp size and the bottom column is - * the remaining space of the old cell. - * @param rowHeightDp The height of the row inserted before the new row (or if - * insertMarginRow is false, then the height of the margin row) - * @param y the y coordinate of the new row - */ - public void splitRow(int newRow, boolean insertMarginRow, int rowHeightDp, int y) { - actualRowCount++; - - // Insert a new row - if (declaredRowCount != UNDEFINED) { - declaredRowCount++; - if (insertMarginRow) { - declaredRowCount++; - } - setGridAttribute(layout, ATTR_ROW_COUNT, declaredRowCount); - } - - // Find the spacer which marks this row, and if found, mark it as a split - ViewData prevRowSpacer = null; - for (ViewData view : mChildViews) { - if (view.row == newRow - 1 && view.isRowSpacer()) { - prevRowSpacer = view; - break; - } - } - - // Se splitColumn() for details - for (ViewData view : mChildViews) { - if (view == prevRowSpacer) { - continue; - } - - INode node = view.node; - int row = view.row; - if (row > newRow || (row == newRow && view.node.getBounds().y2() > y)) { - //if (getGridAttribute(node, ATTR_LAYOUT_ROW) != null) { - view.row += insertMarginRow ? 2 : 1; - setGridAttribute(node, ATTR_LAYOUT_ROW, view.row); - //} - } else if (!view.isSpacer()) { - int endRow = row + view.rowSpan; - if (endRow > newRow - || endRow == newRow && (view.node.getBounds().y2() > y - || GravityHelper.isConstrainedVertically(view.gravity) - && !GravityHelper.isTopAligned(view.gravity))) { - // This cell spans the new insert position, so increment the row span - view.rowSpan += insertMarginRow ? 2 : 1; - setRowSpanAttribute(node, view.rowSpan); - } - } - } - - // Insert new spacer: - if (prevRowSpacer != null) { - int px = getRowHeight(newRow - 1, 1); - if (insertMarginRow || rowHeightDp == 0) { - px -= getRowActualHeight(newRow - 1); - } - int dp = mRulesEngine.pxToDp(px); - int remaining = dp - rowHeightDp; - if (remaining > 0) { - prevRowSpacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, - String.format(VALUE_N_DP, remaining)); - prevRowSpacer.row = insertMarginRow ? newRow + 1 : newRow; - setGridAttribute(prevRowSpacer.node, ATTR_LAYOUT_ROW, prevRowSpacer.row); - } - } - - if (rowHeightDp > 0) { - int index = prevRowSpacer != null ? prevRowSpacer.index : -1; - addSpacer(layout, index, insertMarginRow ? newRow : newRow - 1, - 0, SPACER_SIZE_DP, rowHeightDp); - } - } - - /** - * Data about a view in a table; this is not the same as a cell because multiple views - * can share a single cell, and a view can span many cells. - */ - public class ViewData { - public final INode node; - public final int index; - public int row; - public int column; - public int rowSpan; - public int columnSpan; - public int gravity; - - ViewData(INode n, int index) { - node = n; - this.index = index; - - column = getGridAttribute(n, ATTR_LAYOUT_COLUMN, UNDEFINED); - columnSpan = getGridAttribute(n, ATTR_LAYOUT_COLUMN_SPAN, 1); - row = getGridAttribute(n, ATTR_LAYOUT_ROW, UNDEFINED); - rowSpan = getGridAttribute(n, ATTR_LAYOUT_ROW_SPAN, 1); - gravity = GravityHelper.getGravity(getGridAttribute(n, ATTR_LAYOUT_GRAVITY), 0); - } - - /** Applies the column and row fields into the XML model */ - void applyPositionAttributes() { - setGridAttribute(node, ATTR_LAYOUT_COLUMN, column); - setGridAttribute(node, ATTR_LAYOUT_ROW, row); - } - - /** Returns the id of this node, or makes one up for display purposes */ - String getId() { - String id = node.getStringAttr(ANDROID_URI, ATTR_ID); - if (id == null) { - id = "<unknownid>"; //$NON-NLS-1$ - String fqn = node.getFqcn(); - fqn = fqn.substring(fqn.lastIndexOf('.') + 1); - id = fqn + "-" - + Integer.toString(System.identityHashCode(node)).substring(0, 3); - } - - return id; - } - - /** Returns true if this {@link ViewData} represents a spacer */ - boolean isSpacer() { - return isSpace(node.getFqcn()); - } - - /** - * Returns true if this {@link ViewData} represents a column spacer - */ - boolean isColumnSpacer() { - return isSpacer() && - // Any spacer not found in column 0 is a column spacer since we - // place all horizontal spacers in column 0 - ((column > 0) - // TODO: Find a cleaner way. Maybe set ids on the elements in (0,0) and - // for column distinguish by id. Or at least only do this for column 0! - || !SPACER_SIZE.equals(node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_WIDTH))); - } - - /** - * Returns true if this {@link ViewData} represents a row spacer - */ - boolean isRowSpacer() { - return isSpacer() && - // Any spacer not found in row 0 is a row spacer since we - // place all vertical spacers in row 0 - ((row > 0) - // TODO: Find a cleaner way. Maybe set ids on the elements in (0,0) and - // for column distinguish by id. Or at least only do this for column 0! - || !SPACER_SIZE.equals(node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_HEIGHT))); - } - } - - /** - * Sets the column span of the given node to the given value (or if the value is 1, - * removes it) - * - * @param node the target node - * @param span the new column span - */ - public void setColumnSpanAttribute(INode node, int span) { - setGridAttribute(node, ATTR_LAYOUT_COLUMN_SPAN, span > 1 ? Integer.toString(span) : null); - } - - /** - * Sets the row span of the given node to the given value (or if the value is 1, - * removes it) - * - * @param node the target node - * @param span the new row span - */ - public void setRowSpanAttribute(INode node, int span) { - setGridAttribute(node, ATTR_LAYOUT_ROW_SPAN, span > 1 ? Integer.toString(span) : null); - } - - /** Returns the index of the given target node in the given child node array */ - static int getChildIndex(INode[] children, INode target) { - int index = 0; - for (INode child : children) { - if (child == target) { - return index; - } - index++; - } - - return -1; - } - - /** - * Update the model to account for the given nodes getting deleted. The nodes - * are not actually deleted by this method; that is assumed to be performed by the - * caller. Instead this method performs whatever model updates are necessary to - * preserve the grid structure. - * - * @param nodes the nodes to be deleted - */ - public void onDeleted(@NonNull List<INode> nodes) { - if (nodes.size() == 0) { - return; - } - - // Attempt to clean up spacer objects for any newly-empty rows or columns - // as the result of this deletion - - Set<INode> deleted = new HashSet<INode>(); - - for (INode child : nodes) { - // We don't care about deletion of spacers - String fqcn = child.getFqcn(); - if (fqcn.equals(FQCN_SPACE) || fqcn.equals(FQCN_SPACE_V7)) { - continue; - } - deleted.add(child); - } - - Set<Integer> usedColumns = new HashSet<Integer>(actualColumnCount); - Set<Integer> usedRows = new HashSet<Integer>(actualRowCount); - Multimap<Integer, ViewData> columnSpacers = ArrayListMultimap.create(actualColumnCount, 2); - Multimap<Integer, ViewData> rowSpacers = ArrayListMultimap.create(actualRowCount, 2); - Set<ViewData> removedViews = new HashSet<ViewData>(); - - for (ViewData view : mChildViews) { - if (deleted.contains(view.node)) { - removedViews.add(view); - } else if (view.isColumnSpacer()) { - columnSpacers.put(view.column, view); - } else if (view.isRowSpacer()) { - rowSpacers.put(view.row, view); - } else { - usedColumns.add(Integer.valueOf(view.column)); - usedRows.add(Integer.valueOf(view.row)); - } - } - - if (usedColumns.size() == 0 || usedRows.size() == 0) { - // No more views - just remove all the spacers - for (ViewData spacer : columnSpacers.values()) { - layout.removeChild(spacer.node); - } - for (ViewData spacer : rowSpacers.values()) { - layout.removeChild(spacer.node); - } - mChildViews.clear(); - actualColumnCount = 0; - declaredColumnCount = 2; - actualRowCount = 0; - declaredRowCount = UNDEFINED; - setGridAttribute(layout, ATTR_COLUMN_COUNT, 2); - - return; - } - - // Determine columns to introduce spacers into: - // This is tricky; I should NOT combine spacers if there are cells tied to - // individual ones - - // TODO: Invalidate column sizes too! Otherwise repeated updates might get confused! - // Similarly, inserts need to do the same! - - // Produce map of old column numbers to new column numbers - // Collapse regions of consecutive space and non-space ranges together - int[] columnMap = new int[actualColumnCount + 1]; // +1: Easily handle columnSpans as well - int newColumn = 0; - boolean prevUsed = usedColumns.contains(0); - for (int column = 1; column < actualColumnCount; column++) { - boolean used = usedColumns.contains(column); - if (used || prevUsed != used) { - newColumn++; - prevUsed = used; - } - columnMap[column] = newColumn; - } - newColumn++; - columnMap[actualColumnCount] = newColumn; - assert columnMap[0] == 0; - - int[] rowMap = new int[actualRowCount + 1]; // +1: Easily handle rowSpans as well - int newRow = 0; - prevUsed = usedRows.contains(0); - for (int row = 1; row < actualRowCount; row++) { - boolean used = usedRows.contains(row); - if (used || prevUsed != used) { - newRow++; - prevUsed = used; - } - rowMap[row] = newRow; - } - newRow++; - rowMap[actualRowCount] = newRow; - assert rowMap[0] == 0; - - - // Adjust column and row numbers to account for deletions: for a given cell, if it - // is to the right of a deleted column, reduce its column number, and if it only - // spans across the deleted column, reduce its column span. - for (ViewData view : mChildViews) { - if (removedViews.contains(view)) { - continue; - } - int newColumnStart = columnMap[Math.min(columnMap.length - 1, view.column)]; - // Gracefully handle rogue/invalid columnSpans in the XML - int newColumnEnd = columnMap[Math.min(columnMap.length - 1, - view.column + view.columnSpan)]; - if (newColumnStart != view.column) { - view.column = newColumnStart; - setGridAttribute(view.node, ATTR_LAYOUT_COLUMN, view.column); - } - - int columnSpan = newColumnEnd - newColumnStart; - if (columnSpan != view.columnSpan) { - if (columnSpan >= 1) { - view.columnSpan = columnSpan; - setColumnSpanAttribute(view.node, view.columnSpan); - } // else: merging spacing columns together - } - - - int newRowStart = rowMap[Math.min(rowMap.length - 1, view.row)]; - int newRowEnd = rowMap[Math.min(rowMap.length - 1, view.row + view.rowSpan)]; - if (newRowStart != view.row) { - view.row = newRowStart; - setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); - } - - int rowSpan = newRowEnd - newRowStart; - if (rowSpan != view.rowSpan) { - if (rowSpan >= 1) { - view.rowSpan = rowSpan; - setRowSpanAttribute(view.node, view.rowSpan); - } // else: merging spacing rows together - } - } - - // Merge spacers (and add spacers for newly empty columns) - int start = 0; - while (start < actualColumnCount) { - // Find next unused span - while (start < actualColumnCount && usedColumns.contains(start)) { - start++; - } - if (start == actualColumnCount) { - break; - } - assert !usedColumns.contains(start); - // Find the next span of unused columns and produce a SINGLE - // spacer for that range (unless it's a zero-sized columns) - int end = start + 1; - for (; end < actualColumnCount; end++) { - if (usedColumns.contains(end)) { - break; - } - } - - // Add up column sizes - int width = getColumnWidth(start, end - start); - - // Find all spacers: the first one found should be moved to the start column - // and assigned to the full height of the columns, and - // the column count reduced by the corresponding amount - - // TODO: if width = 0, fully remove - - boolean isFirstSpacer = true; - for (int column = start; column < end; column++) { - Collection<ViewData> spacers = columnSpacers.get(column); - if (spacers != null && !spacers.isEmpty()) { - // Avoid ConcurrentModificationException since we're inserting into the - // map within this loop (always at a different index, but the map doesn't - // know that) - spacers = new ArrayList<ViewData>(spacers); - for (ViewData spacer : spacers) { - if (isFirstSpacer) { - isFirstSpacer = false; - spacer.column = columnMap[start]; - setGridAttribute(spacer.node, ATTR_LAYOUT_COLUMN, spacer.column); - if (end - start > 1) { - // Compute a merged width for all the spacers (not needed if - // there's just one spacer; it should already have the correct width) - int columnWidthDp = mRulesEngine.pxToDp(width); - spacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, - String.format(VALUE_N_DP, columnWidthDp)); - } - columnSpacers.put(start, spacer); - } else { - removedViews.add(spacer); // Mark for model removal - layout.removeChild(spacer.node); - } - } - } - } - - if (isFirstSpacer) { - // No spacer: create one - int columnWidthDp = mRulesEngine.pxToDp(width); - addSpacer(layout, -1, UNDEFINED, columnMap[start], columnWidthDp, DEFAULT_CELL_HEIGHT); - } - - start = end; - } - actualColumnCount = newColumn; -//if (usedColumns.contains(newColumn)) { -// // TODO: This may be totally wrong for right aligned content! -// actualColumnCount++; -//} - - // Merge spacers for rows - start = 0; - while (start < actualRowCount) { - // Find next unused span - while (start < actualRowCount && usedRows.contains(start)) { - start++; - } - if (start == actualRowCount) { - break; - } - assert !usedRows.contains(start); - // Find the next span of unused rows and produce a SINGLE - // spacer for that range (unless it's a zero-sized rows) - int end = start + 1; - for (; end < actualRowCount; end++) { - if (usedRows.contains(end)) { - break; - } - } - - // Add up row sizes - int height = getRowHeight(start, end - start); - - // Find all spacers: the first one found should be moved to the start row - // and assigned to the full height of the rows, and - // the row count reduced by the corresponding amount - - // TODO: if width = 0, fully remove - - boolean isFirstSpacer = true; - for (int row = start; row < end; row++) { - Collection<ViewData> spacers = rowSpacers.get(row); - if (spacers != null && !spacers.isEmpty()) { - // Avoid ConcurrentModificationException since we're inserting into the - // map within this loop (always at a different index, but the map doesn't - // know that) - spacers = new ArrayList<ViewData>(spacers); - for (ViewData spacer : spacers) { - if (isFirstSpacer) { - isFirstSpacer = false; - spacer.row = rowMap[start]; - setGridAttribute(spacer.node, ATTR_LAYOUT_ROW, spacer.row); - if (end - start > 1) { - // Compute a merged width for all the spacers (not needed if - // there's just one spacer; it should already have the correct height) - int rowHeightDp = mRulesEngine.pxToDp(height); - spacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, - String.format(VALUE_N_DP, rowHeightDp)); - } - rowSpacers.put(start, spacer); - } else { - removedViews.add(spacer); // Mark for model removal - layout.removeChild(spacer.node); - } - } - } - } - - if (isFirstSpacer) { - // No spacer: create one - int rowWidthDp = mRulesEngine.pxToDp(height); - addSpacer(layout, -1, rowMap[start], UNDEFINED, DEFAULT_CELL_WIDTH, rowWidthDp); - } - - start = end; - } - actualRowCount = newRow; -// if (usedRows.contains(newRow)) { -// actualRowCount++; -// } - - // Update the model: remove removed children from the view data list - if (removedViews.size() <= 2) { - mChildViews.removeAll(removedViews); - } else { - List<ViewData> remaining = - new ArrayList<ViewData>(mChildViews.size() - removedViews.size()); - for (ViewData view : mChildViews) { - if (!removedViews.contains(view)) { - remaining.add(view); - } - } - mChildViews = remaining; - } - - // Update the final column and row declared attributes - if (declaredColumnCount != UNDEFINED) { - declaredColumnCount = actualColumnCount; - setGridAttribute(layout, ATTR_COLUMN_COUNT, actualColumnCount); - } - if (declaredRowCount != UNDEFINED) { - declaredRowCount = actualRowCount; - setGridAttribute(layout, ATTR_ROW_COUNT, actualRowCount); - } - } - - /** - * Adds a spacer to the given parent, at the given index. - * - * @param parent the GridLayout - * @param index the index to insert the spacer at, or -1 to append - * @param row the row to add the spacer to (or {@link #UNDEFINED} to not set a row yet - * @param column the column to add the spacer to (or {@link #UNDEFINED} to not set a - * column yet - * @param widthDp the width in device independent pixels to assign to the spacer - * @param heightDp the height in device independent pixels to assign to the spacer - * @return the newly added spacer - */ - ViewData addSpacer(INode parent, int index, int row, int column, - int widthDp, int heightDp) { - INode spacer; - - String tag = FQCN_SPACE; - String gridLayout = parent.getFqcn(); - if (!gridLayout.equals(GRID_LAYOUT) && gridLayout.length() > GRID_LAYOUT.length()) { - String pkg = gridLayout.substring(0, gridLayout.length() - GRID_LAYOUT.length()); - tag = pkg + SPACE; - } - if (index != -1) { - spacer = parent.insertChildAt(tag, index); - } else { - spacer = parent.appendChild(tag); - } - - ViewData view = new ViewData(spacer, index != -1 ? index : mChildViews.size()); - mChildViews.add(view); - - if (row != UNDEFINED) { - view.row = row; - setGridAttribute(spacer, ATTR_LAYOUT_ROW, row); - } - if (column != UNDEFINED) { - view.column = column; - setGridAttribute(spacer, ATTR_LAYOUT_COLUMN, column); - } - if (widthDp > 0) { - spacer.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, - String.format(VALUE_N_DP, widthDp)); - } - if (heightDp > 0) { - spacer.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, - String.format(VALUE_N_DP, heightDp)); - } - - // Temporary hack - if (GridLayoutRule.sDebugGridLayout) { - //String id = NEW_ID_PREFIX + "s"; - //if (row == 0) { - // id += "c"; - //} - //if (column == 0) { - // id += "r"; - //} - //if (row > 0) { - // id += Integer.toString(row); - //} - //if (column > 0) { - // id += Integer.toString(column); - //} - String id = NEW_ID_PREFIX + "spacer_" //$NON-NLS-1$ - + Integer.toString(System.identityHashCode(spacer)).substring(0, 3); - spacer.setAttribute(ANDROID_URI, ATTR_ID, id); - } - - - return view; - } - - /** - * Returns the string value of the given attribute, or null if it does not - * exist. This only works for attributes that are GridLayout specific, such - * as columnCount, layout_column, layout_row_span, etc. - * - * @param node the target node - * @param name the attribute name (which must be in the android: namespace) - * @return the attribute value or null - */ - - public String getGridAttribute(INode node, String name) { - return node.getStringAttr(getNamespace(), name); - } - - /** - * Returns the integer value of the given attribute, or the given defaultValue if the - * attribute was not set. This only works for attributes that are GridLayout specific, - * such as columnCount, layout_column, layout_row_span, etc. - * - * @param node the target node - * @param attribute the attribute name (which must be in the android: namespace) - * @param defaultValue the default value to use if the value is not set - * @return the attribute integer value - */ - private int getGridAttribute(INode node, String attribute, int defaultValue) { - String valueString = node.getStringAttr(getNamespace(), attribute); - if (valueString != null) { - try { - return Integer.decode(valueString); - } catch (NumberFormatException nufe) { - // Ignore - error in user's XML - } - } - - return defaultValue; - } - - /** - * Returns the number of children views in the GridLayout - * - * @return the number of children views in the GridLayout - */ - public int getViewCount() { - return mChildViews.size(); - } - - /** - * Returns true if the given class name represents a spacer - * - * @param fqcn the fully qualified class name - * @return true if this is a spacer - */ - public static boolean isSpace(String fqcn) { - return FQCN_SPACE.equals(fqcn) || FQCN_SPACE_V7.equals(fqcn); - } -} |