aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java1633
1 files changed, 0 insertions, 1633 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java
deleted file mode 100644
index e0d6313bf..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java
+++ /dev/null
@@ -1,1633 +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.eclipse.adt.internal.editors.layout.refactoring;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_BACKGROUND;
-import static com.android.SdkConstants.ATTR_BASELINE_ALIGNED;
-import static com.android.SdkConstants.ATTR_LAYOUT_ABOVE;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BASELINE;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BOTTOM;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_TOP;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING;
-import static com.android.SdkConstants.ATTR_LAYOUT_BELOW;
-import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_HORIZONTAL;
-import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_VERTICAL;
-import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_TOP;
-import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_WEIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
-import static com.android.SdkConstants.ATTR_ORIENTATION;
-import static com.android.SdkConstants.ID_PREFIX;
-import static com.android.SdkConstants.LINEAR_LAYOUT;
-import static com.android.SdkConstants.NEW_ID_PREFIX;
-import static com.android.SdkConstants.RELATIVE_LAYOUT;
-import static com.android.SdkConstants.VALUE_FALSE;
-import static com.android.SdkConstants.VALUE_N_DP;
-import static com.android.SdkConstants.VALUE_TRUE;
-import static com.android.SdkConstants.VALUE_VERTICAL;
-import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
-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_FILL_HORIZ;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_FILL_VERT;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_LEFT;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_RIGHT;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_TOP;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_VERT_MASK;
-
-import com.android.ide.common.layout.GravityHelper;
-import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
-import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
-import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
-import com.android.utils.Pair;
-
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.text.edits.MultiTextEdit;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Helper class which performs the bulk of the layout conversion to relative layout
- * <p>
- * Future enhancements:
- * <ul>
- * <li>Render the layout at multiple screen sizes and analyze how the widgets move and
- * stretch and use that to add in additional constraints
- * <li> Adapt the LinearLayout analysis code to work with TableLayouts and TableRows as well
- * (just need to tweak the "isVertical" interpretation to account for the different defaults,
- * and perhaps do something about column size properties.
- * <li> We need to take into account existing margins and clear/update them
- * </ul>
- */
-class RelativeLayoutConversionHelper {
- private final MultiTextEdit mRootEdit;
- private final boolean mFlatten;
- private final Element mLayout;
- private final ChangeLayoutRefactoring mRefactoring;
- private final CanvasViewInfo mRootView;
- private List<Element> mDeletedElements;
-
- RelativeLayoutConversionHelper(ChangeLayoutRefactoring refactoring,
- Element layout, boolean flatten, MultiTextEdit rootEdit, CanvasViewInfo rootView) {
- mRefactoring = refactoring;
- mLayout = layout;
- mFlatten = flatten;
- mRootEdit = rootEdit;
- mRootView = rootView;
- }
-
- /** Performs conversion from any layout to a RelativeLayout */
- public void convertToRelative() {
- if (mRootView == null) {
- return;
- }
-
- // Locate the view for the layout
- CanvasViewInfo layoutView = findViewForElement(mRootView, mLayout);
- if (layoutView == null || layoutView.getChildren().size() == 0) {
- // No children. THAT was an easy conversion!
- return;
- }
-
- // Study the layout and get information about how to place individual elements
- List<View> views = analyzeLayout(layoutView);
-
- // Create/update relative layout constraints
- createAttachments(views);
- }
-
- /** Returns the elements that were deleted, or null */
- List<Element> getDeletedElements() {
- return mDeletedElements;
- }
-
- /**
- * Analyzes the given view hierarchy and produces a list of {@link View} objects which
- * contain placement information for each element
- */
- private List<View> analyzeLayout(CanvasViewInfo layoutView) {
- EdgeList edgeList = new EdgeList(layoutView);
- mDeletedElements = edgeList.getDeletedElements();
- deleteRemovedElements(mDeletedElements);
-
- List<Integer> columnOffsets = edgeList.getColumnOffsets();
- List<Integer> rowOffsets = edgeList.getRowOffsets();
-
- // Compute x/y offsets for each row/column index
- int[] left = new int[columnOffsets.size()];
- int[] top = new int[rowOffsets.size()];
-
- Map<Integer, Integer> xToCol = new HashMap<Integer, Integer>();
- int columnIndex = 0;
- for (Integer offset : columnOffsets) {
- left[columnIndex] = offset;
- xToCol.put(offset, columnIndex++);
- }
- Map<Integer, Integer> yToRow = new HashMap<Integer, Integer>();
- int rowIndex = 0;
- for (Integer offset : rowOffsets) {
- top[rowIndex] = offset;
- yToRow.put(offset, rowIndex++);
- }
-
- // Create a complete list of view objects
- List<View> views = createViews(edgeList, columnOffsets);
- initializeSpans(edgeList, columnOffsets, rowOffsets, xToCol, yToRow);
-
- // Sanity check
- for (View view : views) {
- assert view.getLeftEdge() == left[view.mCol];
- assert view.getTopEdge() == top[view.mRow];
- assert view.getRightEdge() == left[view.mCol+view.mColSpan];
- assert view.getBottomEdge() == top[view.mRow+view.mRowSpan];
- }
-
- // Ensure that every view has a proper id such that it can be referred to
- // with a constraint
- initializeIds(edgeList, views);
-
- // Attempt to lay the views out in a grid with constraints (though not that widgets
- // can overlap as well)
- Grid grid = new Grid(views, left, top);
- computeKnownConstraints(views, edgeList);
- computeHorizontalConstraints(grid);
- computeVerticalConstraints(grid);
-
- return views;
- }
-
- /** Produces a list of {@link View} objects from an {@link EdgeList} */
- private List<View> createViews(EdgeList edgeList, List<Integer> columnOffsets) {
- List<View> views = new ArrayList<View>();
- for (Integer offset : columnOffsets) {
- List<View> leftEdgeViews = edgeList.getLeftEdgeViews(offset);
- if (leftEdgeViews == null) {
- // must have been a right edge
- continue;
- }
- for (View view : leftEdgeViews) {
- views.add(view);
- }
- }
- return views;
- }
-
- /** Removes any elements targeted for deletion */
- private void deleteRemovedElements(List<Element> delete) {
- if (mFlatten && delete.size() > 0) {
- for (Element element : delete) {
- mRefactoring.removeElementTags(mRootEdit, element, delete,
- !AdtPrefs.getPrefs().getFormatGuiXml() /*changeIndentation*/);
- }
- }
- }
-
- /** Ensures that every element has an id such that it can be referenced from a constraint */
- private void initializeIds(EdgeList edgeList, List<View> views) {
- // Ensure that all views have a valid id
- for (View view : views) {
- String id = mRefactoring.ensureHasId(mRootEdit, view.mElement, null);
- edgeList.setIdAttributeValue(view, id);
- }
- }
-
- /**
- * Initializes the column and row indices, as well as any column span and row span
- * values
- */
- private void initializeSpans(EdgeList edgeList, List<Integer> columnOffsets,
- List<Integer> rowOffsets, Map<Integer, Integer> xToCol, Map<Integer, Integer> yToRow) {
- // Now initialize table view row, column and spans
- for (Integer offset : columnOffsets) {
- List<View> leftEdgeViews = edgeList.getLeftEdgeViews(offset);
- if (leftEdgeViews == null) {
- // must have been a right edge
- continue;
- }
- for (View view : leftEdgeViews) {
- Integer col = xToCol.get(view.getLeftEdge());
- assert col != null;
- Integer end = xToCol.get(view.getRightEdge());
- assert end != null;
-
- view.mCol = col;
- view.mColSpan = end - col;
- }
- }
-
- for (Integer offset : rowOffsets) {
- List<View> topEdgeViews = edgeList.getTopEdgeViews(offset);
- if (topEdgeViews == null) {
- // must have been a bottom edge
- continue;
- }
- for (View view : topEdgeViews) {
- Integer row = yToRow.get(view.getTopEdge());
- assert row != null;
- Integer end = yToRow.get(view.getBottomEdge());
- assert end != null;
-
- view.mRow = row;
- view.mRowSpan = end - row;
- }
- }
- }
-
- /**
- * Creates refactoring edits which adds or updates constraints for the given list of
- * views
- */
- private void createAttachments(List<View> views) {
- // Make the attachments
- String namespace = mRefactoring.getAndroidNamespacePrefix();
- for (View view : views) {
- for (Pair<String, String> constraint : view.getHorizConstraints()) {
- mRefactoring.setAttribute(mRootEdit, view.mElement, ANDROID_URI,
- namespace, constraint.getFirst(), constraint.getSecond());
- }
- for (Pair<String, String> constraint : view.getVerticalConstraints()) {
- mRefactoring.setAttribute(mRootEdit, view.mElement, ANDROID_URI,
- namespace, constraint.getFirst(), constraint.getSecond());
- }
- }
- }
-
- /**
- * Analyzes the existing layouts and layout parameter objects in the document to infer
- * constraints for layout types that we know about - such as LinearLayout baseline
- * alignment, weights, gravity, etc.
- */
- private void computeKnownConstraints(List<View> views, EdgeList edgeList) {
- // List of parent layout elements we've already processed. We iterate through all
- // the -children-, and we ask each for its element parent (which won't have a view)
- // and we look at the parent's layout attributes and its children layout constraints,
- // and then we stash away constraints that we can infer. This means that we will
- // encounter the same parent for every sibling, so that's why there's a map to
- // prevent duplicate work.
- Set<Node> seen = new HashSet<Node>();
-
- for (View view : views) {
- Element element = view.getElement();
- Node parent = element.getParentNode();
- if (seen.contains(parent)) {
- continue;
- }
- seen.add(parent);
-
- if (parent.getNodeType() != Node.ELEMENT_NODE) {
- continue;
- }
- Element layout = (Element) parent;
- String layoutName = layout.getTagName();
-
- if (LINEAR_LAYOUT.equals(layoutName)) {
- analyzeLinearLayout(edgeList, layout);
- } else if (RELATIVE_LAYOUT.equals(layoutName)) {
- analyzeRelativeLayout(edgeList, layout);
- } else {
- // Some other layout -- add more conditional handling here
- // for framelayout, tables, etc.
- }
- }
- }
-
- /**
- * Returns the layout weight of of the given child of a LinearLayout, or 0.0 if it
- * does not define a weight
- */
- private float getWeight(Element linearLayoutChild) {
- String weight = linearLayoutChild.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT);
- if (weight != null && weight.length() > 0) {
- try {
- return Float.parseFloat(weight);
- } catch (NumberFormatException nfe) {
- AdtPlugin.log(nfe, "Invalid weight %1$s", weight);
- }
- }
-
- return 0.0f;
- }
-
- /**
- * Returns the sum of all the layout weights of the children in the given LinearLayout
- *
- * @param linearLayout the layout to compute the total sum for
- * @return the total sum of all the layout weights in the given layout
- */
- private float getWeightSum(Element linearLayout) {
- float sum = 0;
- for (Element child : DomUtilities.getChildren(linearLayout)) {
- sum += getWeight(child);
- }
-
- return sum;
- }
-
- /**
- * Analyzes the given LinearLayout and updates the constraints to reflect
- * relationships it can infer - based on baseline alignment, gravity, order and
- * weights. This method also removes "0dip" as a special width/height used in
- * LinearLayouts with weight distribution.
- */
- private void analyzeLinearLayout(EdgeList edgeList, Element layout) {
- boolean isVertical = VALUE_VERTICAL.equals(layout.getAttributeNS(ANDROID_URI,
- ATTR_ORIENTATION));
- View baselineRef = null;
- if (!isVertical &&
- !VALUE_FALSE.equals(layout.getAttributeNS(ANDROID_URI, ATTR_BASELINE_ALIGNED))) {
- // Baseline alignment. Find the tallest child and set it as the baseline reference.
- int tallestHeight = 0;
- View tallest = null;
- for (Element child : DomUtilities.getChildren(layout)) {
- View view = edgeList.getView(child);
- if (view != null && view.getHeight() > tallestHeight) {
- tallestHeight = view.getHeight();
- tallest = view;
- }
- }
- if (tallest != null) {
- baselineRef = tallest;
- }
- }
-
- float weightSum = getWeightSum(layout);
- float cumulativeWeight = 0;
-
- List<Element> children = DomUtilities.getChildren(layout);
- String prevId = null;
- boolean isFirstChild = true;
- boolean linkBackwards = true;
- boolean linkForwards = false;
-
- for (int index = 0, childCount = children.size(); index < childCount; index++) {
- Element child = children.get(index);
-
- View childView = edgeList.getView(child);
- if (childView == null) {
- // Could be a nested layout that is being removed etc
- prevId = null;
- isFirstChild = false;
- continue;
- }
-
- // Look at the layout_weight attributes and determine whether we should be
- // attached on the bottom/right or on the top/left
- if (weightSum > 0.0f) {
- float weight = getWeight(child);
-
- // We can't emulate a LinearLayout where multiple children have positive
- // weights. However, we CAN support the common scenario where a single
- // child has a non-zero weight, and all children after it are pushed
- // to the end and the weighted child fills the remaining space.
- if (cumulativeWeight == 0 && weight > 0) {
- // See if we have a bottom/right edge to attach the forwards link to
- // (at the end of the forwards chains). Only if so can we link forwards.
- View referenced;
- if (isVertical) {
- referenced = edgeList.getSharedBottomEdge(layout);
- } else {
- referenced = edgeList.getSharedRightEdge(layout);
- }
- if (referenced != null) {
- linkForwards = true;
- }
- } else if (cumulativeWeight > 0) {
- linkBackwards = false;
- }
-
- cumulativeWeight += weight;
- }
-
- analyzeGravity(edgeList, layout, isVertical, child, childView);
- convert0dipToWrapContent(child);
-
- // Chain elements together in the flow direction of the linear layout
- if (prevId != null) { // No constraint for first child
- if (linkBackwards) {
- if (isVertical) {
- childView.addVerticalConstraint(ATTR_LAYOUT_BELOW, prevId);
- } else {
- childView.addHorizConstraint(ATTR_LAYOUT_TO_RIGHT_OF, prevId);
- }
- }
- } else if (isFirstChild) {
- assert linkBackwards;
-
- // First element; attach it to the parent if we can
- if (isVertical) {
- View referenced = edgeList.getSharedTopEdge(layout);
- if (referenced != null) {
- if (isAncestor(referenced.getElement(), child)) {
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_PARENT_TOP,
- VALUE_TRUE);
- } else {
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_TOP,
- referenced.getId());
- }
- }
- } else {
- View referenced = edgeList.getSharedLeftEdge(layout);
- if (referenced != null) {
- if (isAncestor(referenced.getElement(), child)) {
- childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_PARENT_LEFT,
- VALUE_TRUE);
- } else {
- childView.addHorizConstraint(
- ATTR_LAYOUT_ALIGN_LEFT, referenced.getId());
- }
- }
- }
- }
-
- if (linkForwards) {
- if (index < (childCount - 1)) {
- Element nextChild = children.get(index + 1);
- String nextId = mRefactoring.ensureHasId(mRootEdit, nextChild, null);
- if (nextId != null) {
- if (isVertical) {
- childView.addVerticalConstraint(ATTR_LAYOUT_ABOVE, nextId);
- } else {
- childView.addHorizConstraint(ATTR_LAYOUT_TO_LEFT_OF, nextId);
- }
- }
- } else {
- // Attach to right/bottom edge of the layout
- if (isVertical) {
- View referenced = edgeList.getSharedBottomEdge(layout);
- if (referenced != null) {
- if (isAncestor(referenced.getElement(), child)) {
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM,
- VALUE_TRUE);
- } else {
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_BOTTOM,
- referenced.getId());
- }
- }
- } else {
- View referenced = edgeList.getSharedRightEdge(layout);
- if (referenced != null) {
- if (isAncestor(referenced.getElement(), child)) {
- childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_PARENT_RIGHT,
- VALUE_TRUE);
- } else {
- childView.addHorizConstraint(
- ATTR_LAYOUT_ALIGN_RIGHT, referenced.getId());
- }
- }
- }
- }
- }
-
- if (baselineRef != null && baselineRef.getId() != null
- && !baselineRef.getId().equals(childView.getId())) {
- assert !isVertical;
- // Only align if they share the same gravity
- if ((childView.getGravity() & GRAVITY_VERT_MASK) ==
- (baselineRef.getGravity() & GRAVITY_VERT_MASK)) {
- childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_BASELINE, baselineRef.getId());
- }
- }
-
- prevId = mRefactoring.ensureHasId(mRootEdit, child, null);
- isFirstChild = false;
- }
- }
-
- /**
- * Checks the layout "gravity" value for the given child and updates the constraints
- * to account for the gravity
- */
- private int analyzeGravity(EdgeList edgeList, Element layout, boolean isVertical,
- Element child, View childView) {
- // Use gravity to constrain elements in the axis orthogonal to the
- // direction of the layout
- int gravity = childView.getGravity();
- if (isVertical) {
- if ((gravity & GRAVITY_RIGHT) != 0) {
- View referenced = edgeList.getSharedRightEdge(layout);
- if (referenced != null) {
- if (isAncestor(referenced.getElement(), child)) {
- childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_PARENT_RIGHT,
- VALUE_TRUE);
- } else {
- childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_RIGHT,
- referenced.getId());
- }
- }
- } else if ((gravity & GRAVITY_CENTER_HORIZ) != 0) {
- View referenced1 = edgeList.getSharedLeftEdge(layout);
- View referenced2 = edgeList.getSharedRightEdge(layout);
- if (referenced1 != null && referenced2 == referenced1) {
- if (isAncestor(referenced1.getElement(), child)) {
- childView.addHorizConstraint(ATTR_LAYOUT_CENTER_HORIZONTAL,
- VALUE_TRUE);
- }
- }
- } else if ((gravity & GRAVITY_FILL_HORIZ) != 0) {
- View referenced1 = edgeList.getSharedLeftEdge(layout);
- View referenced2 = edgeList.getSharedRightEdge(layout);
- if (referenced1 != null && referenced2 == referenced1) {
- if (isAncestor(referenced1.getElement(), child)) {
- childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_PARENT_LEFT,
- VALUE_TRUE);
- childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_PARENT_RIGHT,
- VALUE_TRUE);
- } else {
- childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_LEFT,
- referenced1.getId());
- childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_RIGHT,
- referenced2.getId());
- }
- }
- } else if ((gravity & GRAVITY_LEFT) != 0) {
- View referenced = edgeList.getSharedLeftEdge(layout);
- if (referenced != null) {
- if (isAncestor(referenced.getElement(), child)) {
- childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_PARENT_LEFT,
- VALUE_TRUE);
- } else {
- childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_LEFT,
- referenced.getId());
- }
- }
- }
- } else {
- // Handle horizontal layout: perform vertical gravity attachments
- if ((gravity & GRAVITY_BOTTOM) != 0) {
- View referenced = edgeList.getSharedBottomEdge(layout);
- if (referenced != null) {
- if (isAncestor(referenced.getElement(), child)) {
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM,
- VALUE_TRUE);
- } else {
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_BOTTOM,
- referenced.getId());
- }
- }
- } else if ((gravity & GRAVITY_CENTER_VERT) != 0) {
- View referenced1 = edgeList.getSharedTopEdge(layout);
- View referenced2 = edgeList.getSharedBottomEdge(layout);
- if (referenced1 != null && referenced2 == referenced1) {
- if (isAncestor(referenced1.getElement(), child)) {
- childView.addVerticalConstraint(ATTR_LAYOUT_CENTER_VERTICAL,
- VALUE_TRUE);
- }
- }
- } else if ((gravity & GRAVITY_FILL_VERT) != 0) {
- View referenced1 = edgeList.getSharedTopEdge(layout);
- View referenced2 = edgeList.getSharedBottomEdge(layout);
- if (referenced1 != null && referenced2 == referenced1) {
- if (isAncestor(referenced1.getElement(), child)) {
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_PARENT_TOP,
- VALUE_TRUE);
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM,
- VALUE_TRUE);
- } else {
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_TOP,
- referenced1.getId());
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_BOTTOM,
- referenced2.getId());
- }
- }
- } else if ((gravity & GRAVITY_TOP) != 0) {
- View referenced = edgeList.getSharedTopEdge(layout);
- if (referenced != null) {
- if (isAncestor(referenced.getElement(), child)) {
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_PARENT_TOP,
- VALUE_TRUE);
- } else {
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_TOP,
- referenced.getId());
- }
- }
- }
- }
- return gravity;
- }
-
- /** Converts 0dip values in layout_width and layout_height to wrap_content instead */
- private void convert0dipToWrapContent(Element child) {
- // Must convert layout_height="0dip" to layout_height="wrap_content".
- // 0dip is a special trick used in linear layouts in the presence of
- // weights where 0dip ensures that the height of the view is not taken
- // into account when distributing the weights. However, when converted
- // to RelativeLayout this will instead cause the view to actually be assigned
- // 0 height.
- String height = child.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT);
- // 0dip, 0dp, 0px, etc
- if (height != null && height.startsWith("0")) { //$NON-NLS-1$
- mRefactoring.setAttribute(mRootEdit, child, ANDROID_URI,
- mRefactoring.getAndroidNamespacePrefix(), ATTR_LAYOUT_HEIGHT,
- VALUE_WRAP_CONTENT);
- }
- String width = child.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH);
- if (width != null && width.startsWith("0")) { //$NON-NLS-1$
- mRefactoring.setAttribute(mRootEdit, child, ANDROID_URI,
- mRefactoring.getAndroidNamespacePrefix(), ATTR_LAYOUT_WIDTH,
- VALUE_WRAP_CONTENT);
- }
- }
-
- /**
- * Analyzes an embedded RelativeLayout within a layout hierarchy and updates the
- * constraints in the EdgeList with those relationships which can continue in the
- * outer single RelativeLayout.
- */
- private void analyzeRelativeLayout(EdgeList edgeList, Element layout) {
- NodeList children = layout.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node node = children.item(i);
- if (node.getNodeType() == Node.ELEMENT_NODE) {
- Element child = (Element) node;
- View childView = edgeList.getView(child);
- if (childView == null) {
- // Could be a nested layout that is being removed etc
- continue;
- }
-
- NamedNodeMap attributes = child.getAttributes();
- for (int j = 0, m = attributes.getLength(); j < m; j++) {
- Attr attribute = (Attr) attributes.item(j);
- String name = attribute.getLocalName();
- String value = attribute.getValue();
- if (name.equals(ATTR_LAYOUT_WIDTH)
- || name.equals(ATTR_LAYOUT_HEIGHT)) {
- // Ignore these for now
- } else if (name.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)
- && ANDROID_URI.equals(attribute.getNamespaceURI())) {
- // Determine if the reference is to a known edge
- String id = getIdBasename(value);
- if (id != null) {
- View referenced = edgeList.getView(id);
- if (referenced != null) {
- // This is a valid reference, so preserve
- // the attribute
- if (name.equals(ATTR_LAYOUT_BELOW) ||
- name.equals(ATTR_LAYOUT_ABOVE) ||
- name.equals(ATTR_LAYOUT_ALIGN_TOP) ||
- name.equals(ATTR_LAYOUT_ALIGN_BOTTOM) ||
- name.equals(ATTR_LAYOUT_ALIGN_BASELINE)) {
- // Vertical constraint
- childView.addVerticalConstraint(name, value);
- } else if (name.equals(ATTR_LAYOUT_ALIGN_LEFT) ||
- name.equals(ATTR_LAYOUT_TO_LEFT_OF) ||
- name.equals(ATTR_LAYOUT_TO_RIGHT_OF) ||
- name.equals(ATTR_LAYOUT_ALIGN_RIGHT)) {
- // Horizontal constraint
- childView.addHorizConstraint(name, value);
- } else {
- // We don't expect this
- assert false : name;
- }
- } else {
- // Reference to some layout that is not included here.
- // TODO: See if the given layout has an edge
- // that corresponds to one of our known views
- // so we can adjust the constraints and keep it after all.
- }
- } else {
- // It's a parent-relative constraint (such
- // as aligning with a parent edge, or centering
- // in the parent view)
- boolean remove = true;
- if (name.equals(ATTR_LAYOUT_ALIGN_PARENT_LEFT)) {
- View referenced = edgeList.getSharedLeftEdge(layout);
- if (referenced != null) {
- if (isAncestor(referenced.getElement(), child)) {
- childView.addHorizConstraint(name, VALUE_TRUE);
- } else {
- childView.addHorizConstraint(
- ATTR_LAYOUT_ALIGN_LEFT, referenced.getId());
- }
- remove = false;
- }
- } else if (name.equals(ATTR_LAYOUT_ALIGN_PARENT_RIGHT)) {
- View referenced = edgeList.getSharedRightEdge(layout);
- if (referenced != null) {
- if (isAncestor(referenced.getElement(), child)) {
- childView.addHorizConstraint(name, VALUE_TRUE);
- } else {
- childView.addHorizConstraint(
- ATTR_LAYOUT_ALIGN_RIGHT, referenced.getId());
- }
- remove = false;
- }
- } else if (name.equals(ATTR_LAYOUT_ALIGN_PARENT_TOP)) {
- View referenced = edgeList.getSharedTopEdge(layout);
- if (referenced != null) {
- if (isAncestor(referenced.getElement(), child)) {
- childView.addVerticalConstraint(name, VALUE_TRUE);
- } else {
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_TOP,
- referenced.getId());
- }
- remove = false;
- }
- } else if (name.equals(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM)) {
- View referenced = edgeList.getSharedBottomEdge(layout);
- if (referenced != null) {
- if (isAncestor(referenced.getElement(), child)) {
- childView.addVerticalConstraint(name, VALUE_TRUE);
- } else {
- childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_BOTTOM,
- referenced.getId());
- }
- remove = false;
- }
- }
-
- boolean alignWithParent =
- name.equals(ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING);
- if (remove && alignWithParent) {
- // TODO - look for this one AFTER we have processed
- // everything else, and then set constraints as necessary
- // IF there are no other conflicting constraints!
- }
-
- // Otherwise it's some kind of centering which we don't support
- // yet.
-
- // TODO: Find a way to determine whether we have
- // a corresponding edge for the parent (e.g. if
- // the ViewInfo bounds match our outer parent or
- // some other edge) and if so, substitute for that
- // id.
- // For example, if this element was centered
- // horizontally in a RelativeLayout that actually
- // occupies the entire width of our outer layout,
- // then it can be preserved after all!
-
- if (remove) {
- if (name.startsWith("layout_margin")) { //$NON-NLS-1$
- continue;
- }
-
- // Remove unknown attributes?
- // It's too early to do this, because we may later want
- // to *set* this value and it would result in an overlapping edits
- // exception. Therefore, we need to RECORD which attributes should
- // be removed, which lines should have its indentation adjusted
- // etc and finally process it all at the end!
- //mRefactoring.removeAttribute(mRootEdit, child,
- // attribute.getNamespaceURI(), name);
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * Given {@code @id/foo} or {@code @+id/foo}, returns foo. Note that given foo it will
- * return null.
- */
- private static String getIdBasename(String id) {
- if (id.startsWith(NEW_ID_PREFIX)) {
- return id.substring(NEW_ID_PREFIX.length());
- } else if (id.startsWith(ID_PREFIX)) {
- return id.substring(ID_PREFIX.length());
- }
-
- return null;
- }
-
- /** Returns true if the given second argument is a descendant of the first argument */
- private static boolean isAncestor(Node ancestor, Node node) {
- while (node != null) {
- if (node == ancestor) {
- return true;
- }
- node = node.getParentNode();
- }
- return false;
- }
-
- /**
- * Computes horizontal constraints for the views in the grid for any remaining views
- * that do not have constraints (as the result of the analysis of known layouts). This
- * will look at the rendered layout coordinates and attempt to connect elements based
- * on a spatial layout in the grid.
- */
- private void computeHorizontalConstraints(Grid grid) {
- int columns = grid.getColumns();
-
- String attachLeftProperty = ATTR_LAYOUT_ALIGN_PARENT_LEFT;
- String attachLeftValue = VALUE_TRUE;
- int marginLeft = 0;
- for (int col = 0; col < columns; col++) {
- if (!grid.colContainsTopLeftCorner(col)) {
- // Just accumulate margins for the next column
- marginLeft += grid.getColumnWidth(col);
- } else {
- // Add horizontal attachments
- String firstId = null;
- for (View view : grid.viewsStartingInCol(col, true)) {
- assert view.getId() != null;
- if (firstId == null) {
- firstId = view.getId();
- if (view.isConstrainedHorizontally()) {
- // Nothing to do -- we already have an accurate position for
- // this view
- } else if (attachLeftProperty != null) {
- view.addHorizConstraint(attachLeftProperty, attachLeftValue);
- if (marginLeft > 0) {
- view.addHorizConstraint(ATTR_LAYOUT_MARGIN_LEFT,
- String.format(VALUE_N_DP, marginLeft));
- marginLeft = 0;
- }
- } else {
- assert false;
- }
- } else if (!view.isConstrainedHorizontally()) {
- view.addHorizConstraint(ATTR_LAYOUT_ALIGN_LEFT, firstId);
- }
- }
- }
-
- // Figure out edge for the next column
- View view = grid.findRightEdgeView(col);
- if (view != null) {
- assert view.getId() != null;
- attachLeftProperty = ATTR_LAYOUT_TO_RIGHT_OF;
- attachLeftValue = view.getId();
-
- marginLeft = 0;
- } else if (marginLeft == 0) {
- marginLeft = grid.getColumnWidth(col);
- }
- }
- }
-
- /**
- * Performs vertical layout just like the {@link #computeHorizontalConstraints} method
- * did horizontally
- */
- private void computeVerticalConstraints(Grid grid) {
- int rows = grid.getRows();
-
- String attachTopProperty = ATTR_LAYOUT_ALIGN_PARENT_TOP;
- String attachTopValue = VALUE_TRUE;
- int marginTop = 0;
- for (int row = 0; row < rows; row++) {
- if (!grid.rowContainsTopLeftCorner(row)) {
- // Just accumulate margins for the next column
- marginTop += grid.getRowHeight(row);
- } else {
- // Add horizontal attachments
- String firstId = null;
- for (View view : grid.viewsStartingInRow(row, true)) {
- assert view.getId() != null;
- if (firstId == null) {
- firstId = view.getId();
- if (view.isConstrainedVertically()) {
- // Nothing to do -- we already have an accurate position for
- // this view
- } else if (attachTopProperty != null) {
- view.addVerticalConstraint(attachTopProperty, attachTopValue);
- if (marginTop > 0) {
- view.addVerticalConstraint(ATTR_LAYOUT_MARGIN_TOP,
- String.format(VALUE_N_DP, marginTop));
- marginTop = 0;
- }
- } else {
- assert false;
- }
- } else if (!view.isConstrainedVertically()) {
- view.addVerticalConstraint(ATTR_LAYOUT_ALIGN_TOP, firstId);
- }
- }
- }
-
- // Figure out edge for the next row
- View view = grid.findBottomEdgeView(row);
- if (view != null) {
- assert view.getId() != null;
- attachTopProperty = ATTR_LAYOUT_BELOW;
- attachTopValue = view.getId();
- marginTop = 0;
- } else if (marginTop == 0) {
- marginTop = grid.getRowHeight(row);
- }
- }
- }
-
- /**
- * Searches a view hierarchy and locates the {@link CanvasViewInfo} for the given
- * {@link Element}
- *
- * @param info the root {@link CanvasViewInfo} to search below
- * @param element the target element
- * @return the {@link CanvasViewInfo} which corresponds to the given element
- */
- private CanvasViewInfo findViewForElement(CanvasViewInfo info, Element element) {
- if (getElement(info) == element) {
- return info;
- }
-
- for (CanvasViewInfo child : info.getChildren()) {
- CanvasViewInfo result = findViewForElement(child, element);
- if (result != null) {
- return result;
- }
- }
-
- return null;
- }
-
- /** Returns the {@link Element} for the given {@link CanvasViewInfo} */
- private static Element getElement(CanvasViewInfo info) {
- Node node = info.getUiViewNode().getXmlNode();
- if (node instanceof Element) {
- return (Element) node;
- }
-
- return null;
- }
-
- /**
- * A grid of cells which can contain views, used to infer spatial relationships when
- * computing constraints. Note that a view can appear in than one cell; they will
- * appear in all cells that their bounds overlap with!
- */
- private class Grid {
- private final int[] mLeft;
- private final int[] mTop;
- // A list from row to column to cell, where a cell is a list of views
- private final List<List<List<View>>> mRowList;
- private int mRowCount;
- private int mColCount;
-
- Grid(List<View> views, int[] left, int[] top) {
- mLeft = left;
- mTop = top;
-
- // The left/top arrays should include the ending point too
- mColCount = left.length - 1;
- mRowCount = top.length - 1;
-
- // Using nested lists rather than arrays to avoid lack of typed arrays
- // (can't create List<View>[row][column] arrays)
- mRowList = new ArrayList<List<List<View>>>(top.length);
- for (int row = 0; row < top.length; row++) {
- List<List<View>> columnList = new ArrayList<List<View>>(left.length);
- for (int col = 0; col < left.length; col++) {
- columnList.add(new ArrayList<View>(4));
- }
- mRowList.add(columnList);
- }
-
- for (View view : views) {
- // Get rid of the root view; we don't want that in the attachments logic;
- // it was there originally such that it would contribute the outermost
- // edges.
- if (view.mElement == mLayout) {
- continue;
- }
-
- for (int i = 0; i < view.mRowSpan; i++) {
- for (int j = 0; j < view.mColSpan; j++) {
- mRowList.get(view.mRow + i).get(view.mCol + j).add(view);
- }
- }
- }
- }
-
- /**
- * Returns the number of rows in the grid
- *
- * @return the row count
- */
- public int getRows() {
- return mRowCount;
- }
-
- /**
- * Returns the number of columns in the grid
- *
- * @return the column count
- */
- public int getColumns() {
- return mColCount;
- }
-
- /**
- * Returns the list of views overlapping the given cell
- *
- * @param row the row of the target cell
- * @param col the column of the target cell
- * @return a list of views overlapping the given column
- */
- public List<View> get(int row, int col) {
- return mRowList.get(row).get(col);
- }
-
- /**
- * Returns true if the given column contains a top left corner of a view
- *
- * @param column the column to check
- * @return true if one or more views have their top left corner in this column
- */
- public boolean colContainsTopLeftCorner(int column) {
- for (int row = 0; row < mRowCount; row++) {
- View view = getTopLeftCorner(row, column);
- if (view != null) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Returns true if the given row contains a top left corner of a view
- *
- * @param row the row to check
- * @return true if one or more views have their top left corner in this row
- */
- public boolean rowContainsTopLeftCorner(int row) {
- for (int col = 0; col < mColCount; col++) {
- View view = getTopLeftCorner(row, col);
- if (view != null) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Returns a list of views (optionally sorted by increasing row index) that have
- * their left edge starting in the given column
- *
- * @param col the column to look up views for
- * @param sort whether to sort the result in increasing row order
- * @return a list of views starting in the given column
- */
- public List<View> viewsStartingInCol(int col, boolean sort) {
- List<View> views = new ArrayList<View>();
- for (int row = 0; row < mRowCount; row++) {
- View view = getTopLeftCorner(row, col);
- if (view != null) {
- views.add(view);
- }
- }
-
- if (sort) {
- View.sortByRow(views);
- }
-
- return views;
- }
-
- /**
- * Returns a list of views (optionally sorted by increasing column index) that have
- * their top edge starting in the given row
- *
- * @param row the row to look up views for
- * @param sort whether to sort the result in increasing column order
- * @return a list of views starting in the given row
- */
- public List<View> viewsStartingInRow(int row, boolean sort) {
- List<View> views = new ArrayList<View>();
- for (int col = 0; col < mColCount; col++) {
- View view = getTopLeftCorner(row, col);
- if (view != null) {
- views.add(view);
- }
- }
-
- if (sort) {
- View.sortByColumn(views);
- }
-
- return views;
- }
-
- /**
- * Returns the pixel width of the given column
- *
- * @param col the column to look up the width of
- * @return the width of the column
- */
- public int getColumnWidth(int col) {
- return mLeft[col + 1] - mLeft[col];
- }
-
- /**
- * Returns the pixel height of the given row
- *
- * @param row the row to look up the height of
- * @return the height of the row
- */
- public int getRowHeight(int row) {
- return mTop[row + 1] - mTop[row];
- }
-
- /**
- * Returns the first view found that has its top left corner in the cell given by
- * the row and column indexes, or null if not found.
- *
- * @param row the row of the target cell
- * @param col the column of the target cell
- * @return a view with its top left corner in the given cell, or null if not found
- */
- View getTopLeftCorner(int row, int col) {
- List<View> views = get(row, col);
- if (views.size() > 0) {
- for (View view : views) {
- if (view.mRow == row && view.mCol == col) {
- return view;
- }
- }
- }
-
- return null;
- }
-
- public View findRightEdgeView(int col) {
- for (int row = 0; row < mRowCount; row++) {
- List<View> views = get(row, col);
- if (views.size() > 0) {
- List<View> result = new ArrayList<View>();
- for (View view : views) {
- // Ends on the right edge of this column?
- if (view.mCol + view.mColSpan == col + 1) {
- result.add(view);
- }
- }
- if (result.size() > 1) {
- View.sortByColumn(result);
- }
- if (result.size() > 0) {
- return result.get(0);
- }
- }
- }
-
- return null;
- }
-
- public View findBottomEdgeView(int row) {
- for (int col = 0; col < mColCount; col++) {
- List<View> views = get(row, col);
- if (views.size() > 0) {
- List<View> result = new ArrayList<View>();
- for (View view : views) {
- // Ends on the bottom edge of this column?
- if (view.mRow + view.mRowSpan == row + 1) {
- result.add(view);
- }
- }
- if (result.size() > 1) {
- View.sortByRow(result);
- }
- if (result.size() > 0) {
- return result.get(0);
- }
-
- }
- }
-
- return null;
- }
-
- /**
- * 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 = 20;
-
- 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 < mColCount + 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 < mRowCount + 1; row++) {
- out.printf("%" + cellWidth + "d", mTop[row]); //$NON-NLS-1$ //$NON-NLS-2$
- if (row == mRowCount) {
- break;
- }
- for (int col = 0; col < mColCount; col++) {
- List<View> views = get(row, col);
- StringBuilder sb = new StringBuilder();
- for (View 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();
- }
- }
-
- /** Holds layout information about an individual view. */
- private static class View {
- private final Element mElement;
- private int mRow = -1;
- private int mCol = -1;
- private int mRowSpan = -1;
- private int mColSpan = -1;
- private CanvasViewInfo mInfo;
- private String mId;
- private List<Pair<String, String>> mHorizConstraints =
- new ArrayList<Pair<String, String>>(4);
- private List<Pair<String, String>> mVerticalConstraints =
- new ArrayList<Pair<String, String>>(4);
- private int mGravity;
-
- public View(CanvasViewInfo view, Element element) {
- mInfo = view;
- mElement = element;
- mGravity = GravityHelper.getGravity(element);
- }
-
- public int getHeight() {
- return mInfo.getAbsRect().height;
- }
-
- public int getGravity() {
- return mGravity;
- }
-
- public String getId() {
- return mId;
- }
-
- public Element getElement() {
- return mElement;
- }
-
- public List<Pair<String, String>> getHorizConstraints() {
- return mHorizConstraints;
- }
-
- public List<Pair<String, String>> getVerticalConstraints() {
- return mVerticalConstraints;
- }
-
- public boolean isConstrainedHorizontally() {
- return mHorizConstraints.size() > 0;
- }
-
- public boolean isConstrainedVertically() {
- return mVerticalConstraints.size() > 0;
- }
-
- public void addHorizConstraint(String property, String value) {
- assert property != null && value != null;
- // TODO - look for duplicates?
- mHorizConstraints.add(Pair.of(property, value));
- }
-
- public void addVerticalConstraint(String property, String value) {
- assert property != null && value != null;
- mVerticalConstraints.add(Pair.of(property, value));
- }
-
- public int getLeftEdge() {
- return mInfo.getAbsRect().x;
- }
-
- public int getTopEdge() {
- return mInfo.getAbsRect().y;
- }
-
- public int getRightEdge() {
- Rectangle bounds = mInfo.getAbsRect();
- // +1: make the bounds overlap, so the right edge is the same as the
- // left edge of the neighbor etc. Otherwise we end up with lots of 1-pixel wide
- // columns between adjacent items.
- return bounds.x + bounds.width + 1;
- }
-
- public int getBottomEdge() {
- Rectangle bounds = mInfo.getAbsRect();
- return bounds.y + bounds.height + 1;
- }
-
- @Override
- public String toString() {
- return "View [mId=" + mId + "]"; //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- public static void sortByRow(List<View> views) {
- Collections.sort(views, new ViewComparator(true/*rowSort*/));
- }
-
- public static void sortByColumn(List<View> views) {
- Collections.sort(views, new ViewComparator(false/*rowSort*/));
- }
-
- /** Comparator to help sort views by row or column index */
- private static class ViewComparator implements Comparator<View> {
- boolean mRowSort;
-
- public ViewComparator(boolean rowSort) {
- mRowSort = rowSort;
- }
-
- @Override
- public int compare(View view1, View view2) {
- if (mRowSort) {
- return view1.mRow - view2.mRow;
- } else {
- return view1.mCol - view2.mCol;
- }
- }
- }
- }
-
- /**
- * An edge list takes a hierarchy of elements and records the bounds of each element
- * into various lists such that it can answer queries about shared edges, about which
- * particular pixels occur as a boundary edge, etc.
- */
- private class EdgeList {
- private final Map<Element, View> mElementToViewMap = new HashMap<Element, View>(100);
- private final Map<String, View> mIdToViewMap = new HashMap<String, View>(100);
- private final Map<Integer, List<View>> mLeft = new HashMap<Integer, List<View>>();
- private final Map<Integer, List<View>> mTop = new HashMap<Integer, List<View>>();
- private final Map<Integer, List<View>> mRight = new HashMap<Integer, List<View>>();
- private final Map<Integer, List<View>> mBottom = new HashMap<Integer, List<View>>();
- private final Map<Element, Element> mSharedLeftEdge = new HashMap<Element, Element>();
- private final Map<Element, Element> mSharedTopEdge = new HashMap<Element, Element>();
- private final Map<Element, Element> mSharedRightEdge = new HashMap<Element, Element>();
- private final Map<Element, Element> mSharedBottomEdge = new HashMap<Element, Element>();
- private final List<Element> mDelete = new ArrayList<Element>();
-
- EdgeList(CanvasViewInfo view) {
- analyze(view, true);
- mDelete.remove(getElement(view));
- }
-
- public void setIdAttributeValue(View view, String id) {
- assert id.startsWith(NEW_ID_PREFIX) || id.startsWith(ID_PREFIX);
- view.mId = id;
- mIdToViewMap.put(getIdBasename(id), view);
- }
-
- public View getView(Element element) {
- return mElementToViewMap.get(element);
- }
-
- public View getView(String id) {
- return mIdToViewMap.get(id);
- }
-
- public List<View> getTopEdgeViews(Integer topOffset) {
- return mTop.get(topOffset);
- }
-
- public List<View> getLeftEdgeViews(Integer leftOffset) {
- return mLeft.get(leftOffset);
- }
-
- void record(Map<Integer, List<View>> map, Integer edge, View info) {
- List<View> list = map.get(edge);
- if (list == null) {
- list = new ArrayList<View>();
- map.put(edge, list);
- }
- list.add(info);
- }
-
- private List<Integer> getOffsets(Set<Integer> first, Set<Integer> second) {
- Set<Integer> joined = new HashSet<Integer>(first.size() + second.size());
- joined.addAll(first);
- joined.addAll(second);
- List<Integer> unique = new ArrayList<Integer>(joined);
- Collections.sort(unique);
-
- return unique;
- }
-
- public List<Element> getDeletedElements() {
- return mDelete;
- }
-
- public List<Integer> getColumnOffsets() {
- return getOffsets(mLeft.keySet(), mRight.keySet());
- }
- public List<Integer> getRowOffsets() {
- return getOffsets(mTop.keySet(), mBottom.keySet());
- }
-
- private View analyze(CanvasViewInfo view, boolean isRoot) {
- View added = null;
- if (!mFlatten || !isRemovableLayout(view)) {
- added = add(view);
- if (!isRoot) {
- return added;
- }
- } else {
- mDelete.add(getElement(view));
- }
-
- Element parentElement = getElement(view);
- Rectangle parentBounds = view.getAbsRect();
-
- // Build up a table model of the view
- for (CanvasViewInfo child : view.getChildren()) {
- Rectangle childBounds = child.getAbsRect();
- Element childElement = getElement(child);
-
- // See if this view shares the edge with the removed
- // parent layout, and if so, record that such that we can
- // later handle attachments to the removed parent edges
- if (parentBounds.x == childBounds.x) {
- mSharedLeftEdge.put(childElement, parentElement);
- }
- if (parentBounds.y == childBounds.y) {
- mSharedTopEdge.put(childElement, parentElement);
- }
- if (parentBounds.x + parentBounds.width == childBounds.x + childBounds.width) {
- mSharedRightEdge.put(childElement, parentElement);
- }
- if (parentBounds.y + parentBounds.height == childBounds.y + childBounds.height) {
- mSharedBottomEdge.put(childElement, parentElement);
- }
-
- if (mFlatten && isRemovableLayout(child)) {
- // When flattening, we want to disregard all layouts and instead
- // add their children!
- for (CanvasViewInfo childView : child.getChildren()) {
- analyze(childView, false);
-
- Element childViewElement = getElement(childView);
- Rectangle childViewBounds = childView.getAbsRect();
-
- // See if this view shares the edge with the removed
- // parent layout, and if so, record that such that we can
- // later handle attachments to the removed parent edges
- if (parentBounds.x == childViewBounds.x) {
- mSharedLeftEdge.put(childViewElement, parentElement);
- }
- if (parentBounds.y == childViewBounds.y) {
- mSharedTopEdge.put(childViewElement, parentElement);
- }
- if (parentBounds.x + parentBounds.width == childViewBounds.x
- + childViewBounds.width) {
- mSharedRightEdge.put(childViewElement, parentElement);
- }
- if (parentBounds.y + parentBounds.height == childViewBounds.y
- + childViewBounds.height) {
- mSharedBottomEdge.put(childViewElement, parentElement);
- }
- }
- mDelete.add(childElement);
- } else {
- analyze(child, false);
- }
- }
-
- return added;
- }
-
- public View getSharedLeftEdge(Element element) {
- return getSharedEdge(element, mSharedLeftEdge);
- }
-
- public View getSharedRightEdge(Element element) {
- return getSharedEdge(element, mSharedRightEdge);
- }
-
- public View getSharedTopEdge(Element element) {
- return getSharedEdge(element, mSharedTopEdge);
- }
-
- public View getSharedBottomEdge(Element element) {
- return getSharedEdge(element, mSharedBottomEdge);
- }
-
- private View getSharedEdge(Element element, Map<Element, Element> sharedEdgeMap) {
- Element original = element;
-
- while (element != null) {
- View view = getView(element);
- if (view != null) {
- assert isAncestor(element, original);
- return view;
- }
- element = sharedEdgeMap.get(element);
- }
-
- return null;
- }
-
- private View add(CanvasViewInfo info) {
- Rectangle bounds = info.getAbsRect();
- Element element = getElement(info);
- View view = new View(info, element);
- mElementToViewMap.put(element, view);
- record(mLeft, Integer.valueOf(bounds.x), view);
- record(mTop, Integer.valueOf(bounds.y), view);
- record(mRight, Integer.valueOf(view.getRightEdge()), view);
- record(mBottom, Integer.valueOf(view.getBottomEdge()), view);
- return view;
- }
-
- /**
- * Returns true if the given {@link CanvasViewInfo} represents an element we
- * should remove in a flattening conversion. We don't want to remove non-layout
- * views, or layout views that for example contain drawables on their own.
- */
- private boolean isRemovableLayout(CanvasViewInfo child) {
- // The element being converted is NOT removable!
- Element element = getElement(child);
- if (element == mLayout) {
- return false;
- }
-
- ElementDescriptor descriptor = child.getUiViewNode().getDescriptor();
- String name = descriptor.getXmlLocalName();
- if (name.equals(LINEAR_LAYOUT) || name.equals(RELATIVE_LAYOUT)) {
- // Don't delete layouts that provide a background image or gradient
- if (element.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND)) {
- AdtPlugin.log(IStatus.WARNING,
- "Did not flatten layout %1$s because it defines a '%2$s' attribute",
- VisualRefactoring.getId(element), ATTR_BACKGROUND);
- return false;
- }
-
- return true;
- }
-
- return false;
- }
- }
-}