diff options
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.java | 1633 |
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; - } - } -} |