diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java | 2384 |
1 files changed, 0 insertions, 2384 deletions
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); - } -} |