aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MergeRule.java37
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java733
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeOverlay.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java9
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionOverlay.java16
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java100
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfoTest.java335
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactoryTest.java6
11 files changed, 1033 insertions, 222 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MergeRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MergeRule.java
new file mode 100644
index 0000000..77f5c22
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MergeRule.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.layout;
+
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.MenuAction;
+
+import java.util.List;
+
+/**
+ * Drop handler for the {@code <merge>} tag
+ */
+public class MergeRule extends FrameLayoutRule {
+ // The <merge> tag behaves a lot like the FrameLayout; all children are added
+ // on top of each other at (0,0)
+
+ @Override
+ public List<MenuAction> getContextMenu(INode selectedNode) {
+ // Deliberately ignore super.getContextMenu(); we don't want to attempt to list
+ // properties for the <merge> tag
+ return null;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java
index 84647bd..0585f4f 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java
@@ -16,8 +16,11 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_MERGE;
+
import com.android.ide.common.api.Rect;
import com.android.ide.common.rendering.api.Capability;
+import com.android.ide.common.rendering.api.MergeCookie;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;
@@ -25,6 +28,7 @@ import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDes
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.util.Pair;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
@@ -34,8 +38,11 @@ import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
/**
* Maps a {@link ViewInfo} in a structure more adapted to our needs.
@@ -66,7 +73,7 @@ public class CanvasViewInfo implements IPropertySource {
private final String mName;
private final Object mViewObject;
private final UiViewElementNode mUiViewNode;
- private final CanvasViewInfo mParent;
+ private CanvasViewInfo mParent;
private final ArrayList<CanvasViewInfo> mChildren = new ArrayList<CanvasViewInfo>();
/**
@@ -77,6 +84,17 @@ public class CanvasViewInfo implements IPropertySource {
private boolean mExploded;
/**
+ * Node sibling. This is usually null, but it's possible for a single node in the
+ * model to have <b>multiple</b> separate views in the canvas, for example
+ * when you {@code <include>} a view that has multiple widgets inside a
+ * {@code <merge>} tag. In this case, all the views have the same node model,
+ * the include tag, and selecting the include should highlight all the separate
+ * views that are linked to this node. That's what this field is all about: it is
+ * a <b>circular</b> list of all the siblings that share the same node.
+ */
+ private List<CanvasViewInfo> mNodeSiblings;
+
+ /**
* Constructs a {@link CanvasViewInfo} initialized with the given initial values.
*/
private CanvasViewInfo(CanvasViewInfo parent, String name,
@@ -142,6 +160,77 @@ public class CanvasViewInfo implements IPropertySource {
}
/**
+ * For nodes that have multiple views rendered from a single node, such as the
+ * children of a {@code <merge>} tag included into a separate layout, return the
+ * "primary" view, the first view that is rendered
+ */
+ private CanvasViewInfo getPrimaryNodeSibling() {
+ if (mNodeSiblings == null || mNodeSiblings.size() == 0) {
+ return null;
+ }
+
+ return mNodeSiblings.get(0);
+ }
+
+ /**
+ * Returns true if this view represents one view of many linked to a single node, and
+ * where this is the primary view. The primary view is the one that will be shown
+ * in the outline for example (since we only show nodes, not views, in the outline,
+ * and therefore don't want repetitions when a view has more than one view info.)
+ *
+ * @return true if this is the primary view among more than one linked to a single
+ * node
+ */
+ private boolean isPrimaryNodeSibling() {
+ return getPrimaryNodeSibling() == this;
+ }
+
+ /**
+ * Returns the list of node sibling of this view (which <b>will include this
+ * view</b>). For most views this is going to be null, but for views that share a
+ * single node (such as widgets inside a {@code <merge>} tag included into another
+ * layout), this will provide all the views that correspond to the node.
+ *
+ * @return a non-empty list of siblings (including this), or null
+ */
+ public List<CanvasViewInfo> getNodeSiblings() {
+ return mNodeSiblings;
+ }
+
+ /**
+ * Returns all the children of the canvas view info where each child corresponds to a
+ * unique node. This is intended for use by the outline for example, where only the
+ * actual nodes are displayed, not the views themselves.
+ * <p>
+ * Most views have their own nodes, so this is generally the same as
+ * {@link #getChildren}, except in the case where you for example include a view that
+ * has multiple widgets inside a {@code <merge>} tag, where all these widgets have the
+ * same node (the {@code <merge>} tag).
+ *
+ * @return list of {@link CanvasViewInfo} objects that are children of this view,
+ * never null
+ */
+ public List<CanvasViewInfo> getUniqueChildren() {
+ for (CanvasViewInfo info : mChildren) {
+ if (info.mNodeSiblings != null) {
+ // We have secondary children; must create a new collection containing
+ // only non-secondary children
+ List<CanvasViewInfo> children = new ArrayList<CanvasViewInfo>();
+ for (CanvasViewInfo vi : mChildren) {
+ if (vi.mNodeSiblings == null) {
+ children.add(vi);
+ } else if (vi.isPrimaryNodeSibling()) {
+ children.add(vi);
+ }
+ }
+ return children;
+ }
+ }
+
+ return mChildren;
+ }
+
+ /**
* Returns true if the specific {@link CanvasViewInfo} is a parent
* of this {@link CanvasViewInfo}. It can be a direct parent or any
* grand-parent higher in the hierarchy.
@@ -376,6 +465,32 @@ public class CanvasViewInfo implements IPropertySource {
return null;
}
+ /** Adds the given {@link CanvasViewInfo} as a new last child of this view */
+ private void addChild(CanvasViewInfo child) {
+ mChildren.add(child);
+ }
+
+ /** Adds the given {@link CanvasViewInfo} as a child at the given index */
+ private void addChildAt(int index, CanvasViewInfo child) {
+ mChildren.add(index, child);
+ }
+
+ /**
+ * Removes the given {@link CanvasViewInfo} from the child list of this view, and
+ * returns true if it was successfully removed
+ *
+ * @param child the child to be removed
+ * @return true if it was a child and was removed
+ */
+ public boolean removeChild(CanvasViewInfo child) {
+ return mChildren.remove(child);
+ }
+
+ @Override
+ public String toString() {
+ return "CanvasViewInfo [name=" + mName + ", node=" + mUiViewNode + "]";
+ }
+
// ---- Factory functionality ----
/**
@@ -402,234 +517,488 @@ public class CanvasViewInfo implements IPropertySource {
* @param root the root {@link ViewInfo} to build from
* @return a {@link CanvasViewInfo} hierarchy
*/
- public static CanvasViewInfo create(ViewInfo root) {
- if (root.getCookie() == null) {
- // Special case: If the root-most view does not have a view cookie,
- // then we are rendering some outer layout surrounding this layout, and in
- // that case we must search down the hierarchy for the (possibly multiple)
- // sub-roots that correspond to elements in this layout, and place them inside
- // an outer view that has no node. In the outline this item will be used to
- // show the inclusion-context.
- CanvasViewInfo rootView = createView(null, root, 0, 0);
- addKeyedSubtrees(rootView, root, 0, 0);
- return rootView;
- } else {
- // We have a view key at the top, so just go and create {@link CanvasViewInfo}
- // objects for each {@link ViewInfo} until we run into a null key.
- return addKeyedSubtrees(null, root, 0, 0);
- }
+ public static Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root) {
+ return new Builder().create(root);
}
- /** Creates a {@link CanvasViewInfo} for a given {@link ViewInfo} but does not recurse */
- private static CanvasViewInfo createView(CanvasViewInfo parent, ViewInfo root, int parentX,
- int parentY) {
- Object cookie = root.getCookie();
- UiViewElementNode node = null;
- if (cookie instanceof UiViewElementNode) {
- node = (UiViewElementNode) cookie;
- }
-
- return createView(parent, root, parentX, parentY, node);
- }
+ /** Builder object which walks over a tree of {@link ViewInfo} objects and builds
+ * up a corresponding {@link CanvasViewInfo} hierarchy. */
+ private static class Builder {
+ private Map<UiViewElementNode,List<CanvasViewInfo>> mMergeNodeMap;
+
+ public Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root) {
+ Object cookie = root.getCookie();
+ if (cookie == null) {
+ // Special case: If the root-most view does not have a view cookie,
+ // then we are rendering some outer layout surrounding this layout, and in
+ // that case we must search down the hierarchy for the (possibly multiple)
+ // sub-roots that correspond to elements in this layout, and place them inside
+ // an outer view that has no node. In the outline this item will be used to
+ // show the inclusion-context.
+ CanvasViewInfo rootView = createView(null, root, 0, 0);
+ addKeyedSubtrees(rootView, root, 0, 0);
+
+ List<Rectangle> includedBounds = new ArrayList<Rectangle>();
+ for (CanvasViewInfo vi : rootView.getChildren()) {
+ if (vi.isPrimaryNodeSibling()) {
+ includedBounds.add(vi.getAbsRect());
+ }
+ }
- /**
- * Creates a {@link CanvasViewInfo} for a given {@link ViewInfo} but does not recurse.
- * This method specifies an explicit {@link UiViewElementNode} to use rather than
- * relying on the view cookie in the info object.
- */
- private static CanvasViewInfo createView(CanvasViewInfo parent, ViewInfo root, int parentX,
- int parentY, UiViewElementNode node) {
+ // There are <merge> nodes here; see if we can insert it into the hierarchy
+ if (mMergeNodeMap != null) {
+ // Locate all the nodes that have a <merge> as a parent in the node model,
+ // and where the view sits at the top level inside the include-context node.
+ UiViewElementNode merge = null;
+ List<CanvasViewInfo> merged = new ArrayList<CanvasViewInfo>();
+ for (Map.Entry<UiViewElementNode, List<CanvasViewInfo>> entry : mMergeNodeMap
+ .entrySet()) {
+ UiViewElementNode node = entry.getKey();
+ if (!hasMergeParent(node)) {
+ continue;
+ }
+ List<CanvasViewInfo> views = entry.getValue();
+ assert views.size() > 0;
+ CanvasViewInfo view = views.get(0); // primary
+ if (view.getParent() != rootView) {
+ continue;
+ }
+ UiElementNode parent = node.getUiParent();
+ if (merge != null && parent != merge) {
+ continue;
+ }
+ merge = (UiViewElementNode) parent;
+ merged.add(view);
+ }
+ if (merged.size() > 0) {
+ // Compute a bounding box for the merged views
+ Rectangle absRect = null;
+ for (CanvasViewInfo child : merged) {
+ Rectangle rect = child.getAbsRect();
+ if (absRect == null) {
+ absRect = rect;
+ } else {
+ absRect = absRect.union(rect);
+ }
+ }
- int x = root.getLeft();
- int y = root.getTop();
- int w = root.getRight() - x;
- int h = root.getBottom() - y;
+ CanvasViewInfo mergeView = new CanvasViewInfo(rootView, VIEW_MERGE, null,
+ merge, absRect, absRect);
+ for (CanvasViewInfo view : merged) {
+ if (rootView.removeChild(view)) {
+ mergeView.addChild(view);
+ }
+ }
+ rootView.addChild(mergeView);
+ }
+ }
- x += parentX;
- y += parentY;
+ return Pair.of(rootView, includedBounds);
+ } else {
+ // We have a view key at the top, so just go and create {@link CanvasViewInfo}
+ // objects for each {@link ViewInfo} until we run into a null key.
+ CanvasViewInfo rootView = addKeyedSubtrees(null, root, 0, 0);
+
+ // Special case: look to see if the root element is really a <merge>, and if so,
+ // manufacture a view for it such that we can target this root element
+ // in drag & drop operations, such that we can show it in the outline, etc
+ if (rootView != null && hasMergeParent(rootView.getUiViewNode())) {
+ CanvasViewInfo merge = new CanvasViewInfo(null, VIEW_MERGE, null,
+ (UiViewElementNode) rootView.getUiViewNode().getUiParent(),
+ rootView.getAbsRect(), rootView.getSelectionRect());
+ // Insert the <merge> as the new real root
+ rootView.mParent = merge;
+ merge.addChild(rootView);
+ rootView = merge;
+ }
- Rectangle absRect = new Rectangle(x, y, w - 1, h - 1);
+ return Pair.of(rootView, null);
+ }
+ }
- if (w < SELECTION_MIN_SIZE) {
- int d = (SELECTION_MIN_SIZE - w) / 2;
- x -= d;
- w += SELECTION_MIN_SIZE - w;
+ private boolean hasMergeParent(UiViewElementNode rootNode) {
+ UiElementNode rootParent = rootNode.getUiParent();
+ return (rootParent instanceof UiViewElementNode
+ && VIEW_MERGE.equals(rootParent.getDescriptor().getXmlName()));
}
- if (h < SELECTION_MIN_SIZE) {
- int d = (SELECTION_MIN_SIZE - h) / 2;
- y -= d;
- h += SELECTION_MIN_SIZE - h;
+ /** Creates a {@link CanvasViewInfo} for a given {@link ViewInfo} but does not recurse */
+ private CanvasViewInfo createView(CanvasViewInfo parent, ViewInfo root, int parentX,
+ int parentY) {
+ Object cookie = root.getCookie();
+ UiViewElementNode node = null;
+ if (cookie instanceof UiViewElementNode) {
+ node = (UiViewElementNode) cookie;
+ } else if (cookie instanceof MergeCookie) {
+ cookie = ((MergeCookie) cookie).getCookie();
+ if (cookie instanceof UiViewElementNode) {
+ node = (UiViewElementNode) cookie;
+ CanvasViewInfo view = createView(parent, root, parentX, parentY, node);
+ if (root.getCookie() instanceof MergeCookie && view.mNodeSiblings == null) {
+ List<CanvasViewInfo> v = mMergeNodeMap == null ?
+ null : mMergeNodeMap.get(node);
+ if (v != null) {
+ v.add(view);
+ } else {
+ v = new ArrayList<CanvasViewInfo>();
+ v.add(view);
+ if (mMergeNodeMap == null) {
+ mMergeNodeMap =
+ new HashMap<UiViewElementNode, List<CanvasViewInfo>>();
+ }
+ mMergeNodeMap.put(node, v);
+ }
+ view.mNodeSiblings = v;
+ }
+
+ return view;
+ }
+ }
+
+ return createView(parent, root, parentX, parentY, node);
}
- Rectangle selectionRect = new Rectangle(x, y, w - 1, h - 1);
+ /**
+ * Creates a {@link CanvasViewInfo} for a given {@link ViewInfo} but does not recurse.
+ * This method specifies an explicit {@link UiViewElementNode} to use rather than
+ * relying on the view cookie in the info object.
+ */
+ private CanvasViewInfo createView(CanvasViewInfo parent, ViewInfo root, int parentX,
+ int parentY, UiViewElementNode node) {
- return new CanvasViewInfo(parent, root.getClassName(), root.getViewObject(), node, absRect,
- selectionRect);
- }
+ int x = root.getLeft();
+ int y = root.getTop();
+ int w = root.getRight() - x;
+ int h = root.getBottom() - y;
- /** Create a subtree recursively until you run out of keys */
- private static CanvasViewInfo createSubtree(CanvasViewInfo parent, ViewInfo viewInfo,
- int parentX, int parentY) {
- assert viewInfo.getCookie() != null;
+ x += parentX;
+ y += parentY;
- CanvasViewInfo view = createView(parent, viewInfo, parentX, parentY);
+ Rectangle absRect = new Rectangle(x, y, w - 1, h - 1);
- // Process children:
- parentX += viewInfo.getLeft();
- parentY += viewInfo.getTop();
+ if (w < SELECTION_MIN_SIZE) {
+ int d = (SELECTION_MIN_SIZE - w) / 2;
+ x -= d;
+ w += SELECTION_MIN_SIZE - w;
+ }
- // See if we have any missing keys at this level
- int missingNodes = 0;
- List<ViewInfo> children = viewInfo.getChildren();
- for (ViewInfo child : children) {
- // Only use children which have a ViewKey of the correct type.
- // We can't interact with those when they have a null key or
- // an incompatible type.
- Object cookie = child.getCookie();
- if (!(cookie instanceof UiViewElementNode)) {
- missingNodes++;
+ if (h < SELECTION_MIN_SIZE) {
+ int d = (SELECTION_MIN_SIZE - h) / 2;
+ y -= d;
+ h += SELECTION_MIN_SIZE - h;
}
+
+ Rectangle selectionRect = new Rectangle(x, y, w - 1, h - 1);
+
+ return new CanvasViewInfo(parent, root.getClassName(), root.getViewObject(), node,
+ absRect, selectionRect);
}
- if (missingNodes == 0) {
- // No missing nodes; this is the normal case, and we can just continue to
- // recursively add our children
+ /** Create a subtree recursively until you run out of keys */
+ private CanvasViewInfo createSubtree(CanvasViewInfo parent, ViewInfo viewInfo,
+ int parentX, int parentY) {
+ assert viewInfo.getCookie() != null;
+
+ CanvasViewInfo view = createView(parent, viewInfo, parentX, parentY);
+
+ // Process children:
+ parentX += viewInfo.getLeft();
+ parentY += viewInfo.getTop();
+
+ // See if we have any missing keys at this level
+ int missingNodes = 0;
+ int mergeNodes = 0;
+ List<ViewInfo> children = viewInfo.getChildren();
for (ViewInfo child : children) {
- CanvasViewInfo childView = createSubtree(view, child, parentX, parentY);
- view.addChild(childView);
- }
- } else {
- // We don't have keys for one or more of the ViewInfos. There are many
- // possible causes: we are on an SDK platform that does not support
- // embedded_layout rendering, or we are including a view with a <merge>
- // as the root element.
-
- String containerName = view.getUiViewNode().getDescriptor().getXmlLocalName();
- if (containerName.equals(LayoutDescriptors.VIEW_INCLUDE)) {
- // This is expected -- we don't WANT to get node keys for the content
- // of an include since it's in a different file and should be treated
- // as a single unit that cannot be edited (hence, no CanvasViewInfo
- // children)
- } else {
- // We are getting children with null keys where we don't expect it;
- // this usually means that we are dealing with an Android platform
- // that does not support {@link Capability#EMBEDDED_LAYOUT}, or
- // that there are <merge> tags which are doing surprising things
- // to the view hierarchy
- LinkedList<UiViewElementNode> unused = new LinkedList<UiViewElementNode>();
- for (UiElementNode child : view.getUiViewNode().getUiChildren()) {
- if (child instanceof UiViewElementNode) {
- unused.addLast((UiViewElementNode) child);
+ // Only use children which have a ViewKey of the correct type.
+ // We can't interact with those when they have a null key or
+ // an incompatible type.
+ Object cookie = child.getCookie();
+ if (!(cookie instanceof UiViewElementNode)) {
+ if (cookie instanceof MergeCookie) {
+ mergeNodes++;
+ } else {
+ missingNodes++;
}
}
+ }
+
+ if (missingNodes == 0 && mergeNodes == 0) {
+ // No missing nodes; this is the normal case, and we can just continue to
+ // recursively add our children
for (ViewInfo child : children) {
- Object cookie = child.getCookie();
- if (cookie != null) {
- unused.remove(cookie);
- }
+ CanvasViewInfo childView = createSubtree(view, child,
+ parentX, parentY);
+ view.addChild(childView);
}
- if (unused.size() > 0) {
- if (unused.size() == missingNodes) {
- // The number of unmatched elements and ViewInfos are identical;
- // it's very likely that they match one to one, so just use these
+
+ // TBD: Emit placeholder views for keys that have no views?
+ } else {
+ // We don't have keys for one or more of the ViewInfos. There are many
+ // possible causes: we are on an SDK platform that does not support
+ // embedded_layout rendering, or we are including a view with a <merge>
+ // as the root element.
+
+ String containerName = view.getUiViewNode().getDescriptor().getXmlLocalName();
+ if (containerName.equals(LayoutDescriptors.VIEW_INCLUDE)) {
+ // This is expected -- we don't WANT to get node keys for the content
+ // of an include since it's in a different file and should be treated
+ // as a single unit that cannot be edited (hence, no CanvasViewInfo
+ // children)
+ } else {
+ // We are getting children with null keys where we don't expect it;
+ // this usually means that we are dealing with an Android platform
+ // that does not support {@link Capability#EMBEDDED_LAYOUT}, or
+ // that there are <merge> tags which are doing surprising things
+ // to the view hierarchy
+ LinkedList<UiViewElementNode> unused = new LinkedList<UiViewElementNode>();
+ for (UiElementNode child : view.getUiViewNode().getUiChildren()) {
+ if (child instanceof UiViewElementNode) {
+ unused.addLast((UiViewElementNode) child);
+ }
+ }
+ for (ViewInfo child : children) {
+ Object cookie = child.getCookie();
+ if (mergeNodes > 0 && cookie instanceof MergeCookie) {
+ cookie = ((MergeCookie) cookie).getCookie();
+ }
+ if (cookie != null) {
+ unused.remove(cookie);
+ }
+ }
+
+ if (unused.size() > 0 || mergeNodes > 0) {
+ if (unused.size() == missingNodes) {
+ // The number of unmatched elements and ViewInfos are identical;
+ // it's very likely that they match one to one, so just use these
+ for (ViewInfo child : children) {
+ if (child.getCookie() == null) {
+ // Only create a flat (non-recursive) view
+ CanvasViewInfo childView = createView(view, child, parentX,
+ parentY, unused.removeFirst());
+ view.addChild(childView);
+ } else {
+ CanvasViewInfo childView = createSubtree(view, child, parentX,
+ parentY);
+ view.addChild(childView);
+ }
+ }
+ } else {
+ // We have an uneven match. In this case we might be dealing
+ // with <merge> etc.
+ // We have no way to associate elements back with the
+ // corresponding <include> tags if there are more than one of
+ // them. That's not a huge tragedy since visually you are not
+ // allowed to edit these anyway; we just need to make a visual
+ // block for these for selection and outline purposes.
+ addMismatched(view, parentX, parentY, children, unused);
+ }
+ } else {
+ // No unused keys, but there are views without keys.
+ // We can't represent these since all views must have node keys
+ // such that you can operate on them. Just ignore these.
for (ViewInfo child : children) {
- if (child.getCookie() == null) {
- // Only create a flat (non-recursive) view
- CanvasViewInfo childView = createView(view, child, parentX,
- parentY, unused.removeFirst());
- view.addChild(childView);
- } else {
- CanvasViewInfo childView = createSubtree(view, child, parentX,
- parentY);
+ if (child.getCookie() != null) {
+ CanvasViewInfo childView = createSubtree(view, child,
+ parentX, parentY);
view.addChild(childView);
}
}
- } else {
- // We have an uneven match. In this case we might be dealing
- // with <merge> etc.
- // We have no way to associate elements back with the
- // corresponding <include> tags if there are more than one of
- // them. That's not a huge tragedy since visually you are not
- // allowed to edit these anyway; we just need to make a visual
- // block for these for selection and outline purposes.
- UiViewElementNode reference = unused.get(0);
- addBoundingView(view, children, reference, parentX, parentY);
}
}
}
- }
- return view;
- }
+ return view;
+ }
- /**
- * Add a single bounding view for all the non-keyed children with dimensions that span
- * the bounding rectangle of all these children, and associate it with the given node
- * reference. Keyed children are added in the normal way.
- */
- private static void addBoundingView(CanvasViewInfo parentView, List<ViewInfo> children,
- UiViewElementNode reference, int parentX, int parentY) {
- Rectangle absRect = null;
- int insertIndex = -1;
- for (int index = 0, size = children.size(); index < size; index++) {
- ViewInfo child = children.get(index);
- if (child.getCookie() == null) {
- int x = child.getLeft();
- int y = child.getTop();
- int width = child.getRight() - x;
- int height = child.getBottom() - y;
- Rectangle rect = new Rectangle(x, y, width, height);
- if (absRect == null) {
- absRect = rect;
- insertIndex = index;
+ /**
+ * We have various {@link ViewInfo} children with null keys, and/or nodes in
+ * the corresponding UI model that are not referenced by any of the {@link ViewInfo}
+ * objects. This method attempts to account for this, by matching the views in
+ * the right order.
+ */
+ private void addMismatched(CanvasViewInfo parentView, int parentX, int parentY,
+ List<ViewInfo> children, LinkedList<UiViewElementNode> unused) {
+ UiViewElementNode afterNode = null;
+ UiViewElementNode beforeNode = null;
+ // We have one important clue we can use when matching unused nodes
+ // with views: if we have a view V1 with node N1, and a view V2 with node N2,
+ // then we can only match unknown node UN with unknown node UV if
+ // V1 < UV < V2 and N1 < UN < N2.
+ // We can use these constraints to do the matching, for example by
+ // a simple DAG traversal. However, since the number of unmatched nodes
+ // will typically be very small, we'll just do a simple algorithm here
+ // which checks forwards/backwards whether a match is valid.
+ for (int index = 0, size = children.size(); index < size; index++) {
+ ViewInfo child = children.get(index);
+ if (child.getCookie() != null) {
+ CanvasViewInfo childView = createSubtree(parentView, child, parentX, parentY);
+ parentView.addChild(childView);
+ if (child.getCookie() instanceof UiViewElementNode) {
+ afterNode = (UiViewElementNode) child.getCookie();
+ }
} else {
- absRect = absRect.union(rect);
+ beforeNode = nextViewNode(children, index);
+
+ // Find first eligible node from unused
+ // TOD: What if there are more eligible? We need to process ALL views
+ // and all nodes in one go here
+
+ UiViewElementNode matching = null;
+ for (UiViewElementNode candidate : unused) {
+ if (afterNode == null || isAfter(afterNode, candidate)) {
+ if (beforeNode == null || isBefore(beforeNode, candidate)) {
+ matching = candidate;
+ break;
+ }
+ }
+ }
+
+ if (matching != null) {
+ unused.remove(matching);
+ CanvasViewInfo childView = createView(parentView, child, parentX, parentY,
+ matching);
+ parentView.addChild(childView);
+ afterNode = matching;
+ } else {
+ // We have no node for the view -- what do we do??
+ // Nothing - we only represent stuff in the outline that is in the
+ // source model, not in the render
+ }
+ }
+ }
+
+ // Add zero-bounded boxes for all remaining nodes since they need to show
+ // up in the outline, need to be selectable so you can press Delete, etc.
+ if (unused.size() > 0) {
+ Map<UiViewElementNode, Integer> rankMap =
+ new HashMap<UiViewElementNode, Integer>();
+ Map<UiViewElementNode, CanvasViewInfo> infoMap =
+ new HashMap<UiViewElementNode, CanvasViewInfo>();
+ UiElementNode parent = unused.get(0).getUiParent();
+ if (parent != null) {
+ int index = 0;
+ for (UiElementNode child : parent.getUiChildren()) {
+ UiViewElementNode node = (UiViewElementNode) child;
+ rankMap.put(node, index++);
+ }
+ for (CanvasViewInfo child : parentView.getChildren()) {
+ infoMap.put(child.getUiViewNode(), child);
+ }
+ List<Integer> usedIndexes = new ArrayList<Integer>();
+ for (UiViewElementNode node : unused) {
+ Integer rank = rankMap.get(node);
+ if (rank != null) {
+ usedIndexes.add(rank);
+ }
+ }
+ Collections.sort(usedIndexes);
+ for (int i = usedIndexes.size() - 1; i >= 0; i--) {
+ Integer rank = usedIndexes.get(i);
+ UiViewElementNode found = null;
+ for (UiViewElementNode node : unused) {
+ if (rankMap.get(node) == rank) {
+ found = node;
+ break;
+ }
+ }
+ if (found != null) {
+ Rectangle absRect = new Rectangle(parentX, parentY, 0, 0);
+ String name = found.getDescriptor().getXmlLocalName();
+ CanvasViewInfo v = new CanvasViewInfo(parentView, name, null, found,
+ absRect, absRect);
+ // Find corresponding index in the parent view
+ List<CanvasViewInfo> siblings = parentView.getChildren();
+ int insertPosition = siblings.size();
+ for (int j = siblings.size() - 1; j >= 0; j--) {
+ CanvasViewInfo sibling = siblings.get(j);
+ UiViewElementNode siblingNode = sibling.getUiViewNode();
+ if (siblingNode != null) {
+ Integer siblingRank = rankMap.get(siblingNode);
+ if (siblingRank != null && siblingRank < rank) {
+ insertPosition = j + 1;
+ break;
+ }
+ }
+ }
+ parentView.addChildAt(insertPosition, v);
+ unused.remove(found);
+ }
+ }
+ }
+ // Add in any remaining
+ for (UiViewElementNode node : unused) {
+ Rectangle absRect = new Rectangle(parentX, parentY, 0, 0);
+ String name = node.getDescriptor().getXmlLocalName();
+ CanvasViewInfo v = new CanvasViewInfo(parentView, name, null, node, absRect,
+ absRect);
+ parentView.addChild(v);
}
- } else {
- CanvasViewInfo childView = createSubtree(parentView, child, parentX, parentY);
- parentView.addChild(childView);
}
}
- if (absRect != null) {
- absRect.x += parentX;
- absRect.y += parentY;
- String name = reference.getDescriptor().getXmlLocalName();
- CanvasViewInfo childView = new CanvasViewInfo(parentView, name, null, reference,
- absRect, absRect);
- parentView.addChild(childView, insertIndex);
+
+ private boolean isBefore(UiViewElementNode beforeNode, UiViewElementNode candidate) {
+ UiElementNode parent = candidate.getUiParent();
+ if (parent != null) {
+ for (UiElementNode sibling : parent.getUiChildren()) {
+ if (sibling == beforeNode) {
+ return false;
+ } else if (sibling == candidate) {
+ return true;
+ }
+ }
+ }
+ return false;
}
- }
- /** Search for a subtree with valid keys and add those subtrees */
- private static CanvasViewInfo addKeyedSubtrees(CanvasViewInfo parent, ViewInfo viewInfo,
- int parentX, int parentY) {
- if (viewInfo.getCookie() != null) {
- CanvasViewInfo subtree = createSubtree(parent, viewInfo, parentX, parentY);
+ private boolean isAfter(UiViewElementNode afterNode, UiViewElementNode candidate) {
+ UiElementNode parent = candidate.getUiParent();
if (parent != null) {
- parent.mChildren.add(subtree);
+ for (UiElementNode sibling : parent.getUiChildren()) {
+ if (sibling == afterNode) {
+ return true;
+ } else if (sibling == candidate) {
+ return false;
+ }
+ }
}
- return subtree;
- } else {
- for (ViewInfo child : viewInfo.getChildren()) {
- addKeyedSubtrees(parent, child, parentX + viewInfo.getLeft(), parentY
- + viewInfo.getTop());
+ return false;
+ }
+
+ private UiViewElementNode nextViewNode(List<ViewInfo> children, int index) {
+ int size = children.size();
+ for (; index < size; index++) {
+ ViewInfo child = children.get(index);
+ if (child.getCookie() instanceof UiViewElementNode) {
+ return (UiViewElementNode) child.getCookie();
+ }
}
return null;
}
- }
- /** Adds the given {@link CanvasViewInfo} as a new last child of this view */
- private void addChild(CanvasViewInfo child) {
- mChildren.add(child);
- }
+ /** Search for a subtree with valid keys and add those subtrees */
+ private CanvasViewInfo addKeyedSubtrees(CanvasViewInfo parent, ViewInfo viewInfo,
+ int parentX, int parentY) {
+ // We don't include MergeCookies when searching down for the first non-null key,
+ // since this means we are in a "Show Included In" context, and the include tag itself
+ // (which the merge cookie is pointing to) is still in the including-document rather
+ // than the included document. Therefore, we only accept real UiViewElementNodes here,
+ // not MergeCookies.
+ if (viewInfo.getCookie() != null) {
+ CanvasViewInfo subtree = createSubtree(parent, viewInfo, parentX, parentY);
+ if (parent != null) {
+ parent.mChildren.add(subtree);
+ }
+ return subtree;
+ } else {
+ for (ViewInfo child : viewInfo.getChildren()) {
+ addKeyedSubtrees(parent, child, parentX + viewInfo.getLeft(), parentY
+ + viewInfo.getTop());
+ }
- /** Adds the given {@link CanvasViewInfo} as a new child at the given index */
- private void addChild(CanvasViewInfo child, int index) {
- if (index < 0) {
- index = mChildren.size();
+ return null;
+ }
}
- mChildren.add(index, child);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index 8e94358..cb5c849 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -1760,6 +1760,9 @@ public class GraphicalEditorPart extends EditorPart
* Called when the file changes triggered a redraw of the layout
*/
public void reloadLayout(final ChangeFlags flags, final boolean libraryChanged) {
+ if (mConfigComposite.isDisposed()) {
+ return;
+ }
Display display = mConfigComposite.getDisplay();
display.asyncExec(new Runnable() {
public void run() {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeOverlay.java
index 84f3e01..66adad8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeOverlay.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeOverlay.java
@@ -35,7 +35,7 @@ import java.util.List;
*/
public class IncludeOverlay extends Overlay {
/** Mask transparency - 0 is transparent, 255 is opaque */
- private static final int MASK_TRANSPARENCY = 208;
+ private static final int MASK_TRANSPARENCY = 160;
/** The associated {@link LayoutCanvas}. */
private LayoutCanvas mCanvas;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java
index 16f6fba..07ab41c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java
@@ -521,6 +521,15 @@ public class MoveGesture extends DropGesture {
vi = mCurrentView;
} else {
vi = mCanvas.getViewHierarchy().findViewInfoAt(p);
+
+ // When dragging into the canvas, if you are not over any other view, target
+ // the root element (since it may not "fill" the screen, e.g. if you have a linear
+ // layout but have layout_height wrap_content, then the layout will only extend
+ // to cover the children in the layout, not the whole visible screen area, which
+ // may be surprising
+ if (vi == null) {
+ vi = mCanvas.getViewHierarchy().getRoot();
+ }
}
boolean isMove = true;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java
index d7bc412..02f98b1 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java
@@ -407,7 +407,7 @@ public class OutlinePage extends ContentOutlinePage
}
}
if (element instanceof CanvasViewInfo) {
- List<CanvasViewInfo> children = ((CanvasViewInfo) element).getChildren();
+ List<CanvasViewInfo> children = ((CanvasViewInfo) element).getUniqueChildren();
if (children != null) {
return children.toArray();
}
@@ -497,6 +497,8 @@ public class OutlinePage extends ContentOutlinePage
element = vi.getUiViewNode();
}
+ Image image = getImage(element);
+
if (element instanceof UiElementNode) {
UiElementNode node = (UiElementNode) element;
styledString = node.getStyledDescription();
@@ -549,6 +551,7 @@ public class OutlinePage extends ContentOutlinePage
if (includedWithin != null) {
styledString = new StyledString();
styledString.append(includedWithin.getDisplayName(), QUALIFIER_STYLER);
+ image = IconFactory.getInstance().getIcon(LayoutDescriptors.VIEW_INCLUDE);
}
}
@@ -559,7 +562,7 @@ public class OutlinePage extends ContentOutlinePage
cell.setText(styledString.toString());
cell.setStyleRanges(styledString.getStyleRanges());
- cell.setImage(getImage(element));
+ cell.setImage(image);
super.update(cell);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionOverlay.java
index 90aeebf..817f2f9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionOverlay.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionOverlay.java
@@ -63,7 +63,7 @@ public class SelectionOverlay extends Overlay {
NodeProxy node = s.getNode();
if (node != null) {
String name = s.getName();
- paintSelection(gcWrapper, node, name, isMultipleSelection);
+ paintSelection(gcWrapper, s.getViewInfo(), node, name, isMultipleSelection);
}
}
@@ -121,7 +121,7 @@ public class SelectionOverlay extends Overlay {
}
/** Called by the canvas when a view is being selected. */
- private void paintSelection(IGraphics gc, INode selectedNode, String displayName,
+ private void paintSelection(IGraphics gc, CanvasViewInfo view, INode selectedNode, String displayName,
boolean isMultipleSelection) {
Rect r = selectedNode.getBounds();
@@ -133,6 +133,18 @@ public class SelectionOverlay extends Overlay {
gc.fillRect(r);
gc.drawRect(r);
+ // Paint sibling rectangles, if applicable
+ List<CanvasViewInfo> siblings = view.getNodeSiblings();
+ if (siblings != null) {
+ for (CanvasViewInfo sibling : siblings) {
+ if (sibling != view) {
+ r = SwtUtils.toRect(sibling.getSelectionRect());
+ gc.fillRect(r);
+ gc.drawRect(r);
+ }
+ }
+ }
+
/* Label hidden pending selection visual design
if (displayName == null || isMultipleSelection) {
return;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
index 738da30..01487b9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
@@ -16,21 +16,27 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_MERGE;
+
import com.android.ide.common.api.INode;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.util.Pair;
import org.eclipse.swt.graphics.Rectangle;
import org.w3c.dom.Node;
+import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.RandomAccess;
import java.util.Set;
/**
@@ -52,7 +58,7 @@ public class ViewHierarchy {
}
/**
- * The CanvasViewInfo root created by the last call to {@link #setResult}
+ * The CanvasViewInfo root created by the last call to {@link #setSession}
* with a valid layout.
* <p/>
* This <em>can</em> be null to indicate we're dealing with an empty document with
@@ -63,7 +69,7 @@ public class ViewHierarchy {
private CanvasViewInfo mLastValidViewInfoRoot;
/**
- * True when the last {@link #setResult} provided a valid {@link LayoutScene}.
+ * True when the last {@link #setSession} provided a valid {@link LayoutScene}.
* <p/>
* When false this means the canvas is displaying an out-dated result image & bounds and some
* features should be disabled accordingly such a drag'n'drop.
@@ -137,21 +143,42 @@ public class ViewHierarchy {
mSession = session;
mIsResultValid = (session != null && session.getResult().isSuccess());
mExplodedParents = false;
- mIncludedBounds = null;
-
if (mIsResultValid && session != null) {
- ViewInfo root = null;
-
List<ViewInfo> rootList = session.getRootViews();
- if (rootList != null && rootList.size() > 0) {
- root = rootList.get(0);
+ Pair<CanvasViewInfo,List<Rectangle>> infos = null;
+
+ if (rootList == null || rootList.size() == 0) {
+ // Special case: Look to see if this is really an empty <merge> view,
+ // which shows up without any ViewInfos in the merge. In that case we
+ // want to manufacture an empty view, such that we can target the view
+ // via drag & drop, etc.
+ if (hasMergeRoot()) {
+ ViewInfo mergeRoot = createMergeInfo(session);
+ infos = CanvasViewInfo.create(mergeRoot);
+ } else {
+ infos = null;
+ }
+ } else {
+ if (rootList.size() > 1 && hasMergeRoot()) {
+ ViewInfo mergeRoot = createMergeInfo(session);
+ mergeRoot.setChildren(rootList);
+ infos = CanvasViewInfo.create(mergeRoot);
+ } else {
+ ViewInfo root = rootList.get(0);
+ if (root != null) {
+ infos = CanvasViewInfo.create(root);
+ } else {
+ infos = null;
+ }
+ }
}
-
- if (root == null) {
- mLastValidViewInfoRoot = null;
+ if (infos != null) {
+ mLastValidViewInfoRoot = infos.getFirst();
+ mIncludedBounds = infos.getSecond();
} else {
- mLastValidViewInfoRoot = CanvasViewInfo.create(root);
+ mLastValidViewInfoRoot = null;
+ mIncludedBounds = null;
}
updateNodeProxies(mLastValidViewInfoRoot, null);
@@ -167,10 +194,41 @@ public class ViewHierarchy {
// Update the selection
mCanvas.getSelectionManager().sync(mLastValidViewInfoRoot);
} else {
+ mIncludedBounds = null;
mInvisibleParents.clear();
}
}
+ private ViewInfo createMergeInfo(RenderSession session) {
+ BufferedImage image = session.getImage();
+ ControlPoint imageSize = ControlPoint.create(mCanvas,
+ ICanvasTransform.IMAGE_MARGIN + image.getWidth(),
+ ICanvasTransform.IMAGE_MARGIN + image.getHeight());
+ LayoutPoint layoutSize = imageSize.toLayout();
+ UiDocumentNode model = mCanvas.getLayoutEditor().getUiRootNode();
+ List<UiElementNode> children = model.getUiChildren();
+ return new ViewInfo(VIEW_MERGE, children.get(0), 0, 0, layoutSize.x, layoutSize.y);
+ }
+
+ /**
+ * Returns true if this view hierarchy corresponds to an editor that has a {@code
+ * <merge>} tag at the root
+ *
+ * @return true if there is a {@code <merge>} at the root of this editor's document
+ */
+ private boolean hasMergeRoot() {
+ UiDocumentNode model = mCanvas.getLayoutEditor().getUiRootNode();
+ if (model != null) {
+ List<UiElementNode> children = model.getUiChildren();
+ if (children != null && children.size() > 0
+ && VIEW_MERGE.equals(children.get(0).getDescriptor().getXmlName())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* Creates or updates the node proxy for this canvas view info.
* <p/>
@@ -189,14 +247,6 @@ public class ViewHierarchy {
if (key != null) {
mCanvas.getNodeFactory().create(vi);
-
- if (parentKey == null && vi.getParent() != null) {
- // This is an included view root
- if (mIncludedBounds == null) {
- mIncludedBounds = new ArrayList<Rectangle>();
- }
- mIncludedBounds.add(vi.getAbsRect());
- }
}
for (CanvasViewInfo child : vi.getChildren()) {
@@ -411,7 +461,15 @@ public class ViewHierarchy {
if (r.contains(p.x, p.y)) {
// try to find a matching child first
- for (CanvasViewInfo child : canvasViewInfo.getChildren()) {
+ // Iterate in REVERSE z order such that siblings on top
+ // are checked before earlier siblings (this matters in layouts like
+ // FrameLayout and in <merge> contexts where the views are sitting on top
+ // of each other and we want to select the same view as the one drawn
+ // on top of the others
+ List<CanvasViewInfo> children = canvasViewInfo.getChildren();
+ assert children instanceof RandomAccess;
+ for (int i = children.size() - 1; i >= 0; i--) {
+ CanvasViewInfo child = children.get(i);
CanvasViewInfo v = findViewInfoAt_Recursive(p, child);
if (v != null) {
return v;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java
index c5dbec5..fa05c37 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java
@@ -16,6 +16,8 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gre;
+import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_MERGE;
+
import com.android.ide.common.api.DropFeedback;
import com.android.ide.common.api.IClientRulesEngine;
import com.android.ide.common.api.IDragElement;
@@ -614,6 +616,7 @@ public class RulesEngine {
String ruleClassName;
ClassLoader classLoader;
if (realFqcn.startsWith("android.") || //$NON-NLS-1$
+ realFqcn.equals(VIEW_MERGE) ||
// FIXME: Remove this special case as soon as we pull
// the MapViewRule out of this code base and bundle it
// with the add ons
@@ -628,6 +631,10 @@ public class RulesEngine {
classLoader = RulesEngine.class.getClassLoader();
int dotIndex = realFqcn.lastIndexOf('.');
String baseName = realFqcn.substring(dotIndex+1);
+ // Capitalize rule class name to match naming conventions, if necessary (<merge>)
+ if (Character.isLowerCase(baseName.charAt(0))) {
+ baseName = Character.toUpperCase(baseName.charAt(0)) + baseName.substring(1);
+ }
ruleClassName = packageName + "." + //$NON-NLS-1$
baseName + "Rule"; //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfoTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfoTest.java
index 657a7f8..547db8b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfoTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfoTest.java
@@ -17,15 +17,23 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.ide.common.rendering.api.Capability;
+import com.android.ide.common.rendering.api.MergeCookie;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+import com.android.util.Pair;
import org.eclipse.swt.graphics.Rectangle;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
import junit.framework.TestCase;
@@ -66,7 +74,7 @@ public class CanvasViewInfoTest extends TestCase {
ViewInfo child2 = new ViewInfo("Button", child2Node, 0, 20, 70, 25);
root.setChildren(Arrays.asList(child1, child2));
- CanvasViewInfo rootView = CanvasViewInfo.create(root);
+ CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst();
assertNotNull(rootView);
assertEquals("LinearLayout", rootView.getName());
assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
@@ -103,7 +111,7 @@ public class CanvasViewInfoTest extends TestCase {
ViewInfo child21 = new ViewInfo("RadioButton", child21Node, 0, 20, 70, 25);
child2.setChildren(Arrays.asList(child21));
- CanvasViewInfo rootView = CanvasViewInfo.create(root);
+ CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst();
assertNotNull(rootView);
assertEquals("LinearLayout", rootView.getName());
assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
@@ -140,7 +148,7 @@ public class CanvasViewInfoTest extends TestCase {
ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25);
child2.setChildren(Arrays.asList(child21));
- CanvasViewInfo rootView = CanvasViewInfo.create(root);
+ CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst();
assertNotNull(rootView);
assertEquals("LinearLayout", rootView.getName());
assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
@@ -181,7 +189,7 @@ public class CanvasViewInfoTest extends TestCase {
ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25);
child2.setChildren(Arrays.asList(child21));
- CanvasViewInfo rootView = CanvasViewInfo.create(root);
+ CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst();
assertNotNull(rootView);
assertEquals("LinearLayout", rootView.getName());
assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
@@ -207,6 +215,57 @@ public class CanvasViewInfoTest extends TestCase {
assertEquals(0, includedView.getChildren().size());
}
+ public void testMergeMatching() throws Exception {
+ // Test rendering of MULTIPLE included views or when there is no simple match
+ // between view info and ui element node children
+
+ UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
+ ViewInfo root = new ViewInfo("LinearLayout", rootNode, 10, 10, 100, 100);
+ UiViewElementNode child1Node = createNode(rootNode, "android.widget.Button", false);
+ ViewInfo child1 = new ViewInfo("CheckBox", child1Node, 0, 0, 50, 20);
+ UiViewElementNode multiChildNode1 = createNode(rootNode, "foo", true);
+ UiViewElementNode multiChildNode2 = createNode(rootNode, "bar", true);
+ ViewInfo child2 = new ViewInfo("RelativeLayout", null, 0, 20, 70, 25);
+ ViewInfo child3 = new ViewInfo("AbsoluteLayout", null, 10, 40, 50, 15);
+ root.setChildren(Arrays.asList(child1, child2, child3));
+ ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25);
+ child2.setChildren(Arrays.asList(child21));
+
+ CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst();
+ assertNotNull(rootView);
+ assertEquals("LinearLayout", rootView.getName());
+ assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
+ assertEquals(new Rectangle(10, 10, 89, 89), rootView.getSelectionRect());
+ assertNull(rootView.getParent());
+ assertSame(rootNode, rootView.getUiViewNode());
+ assertEquals(3, rootView.getChildren().size());
+
+ CanvasViewInfo childView1 = rootView.getChildren().get(0);
+ CanvasViewInfo includedView1 = rootView.getChildren().get(1);
+ CanvasViewInfo includedView2 = rootView.getChildren().get(2);
+
+ assertEquals("CheckBox", childView1.getName());
+ assertSame(rootView, childView1.getParent());
+ assertEquals(new Rectangle(10, 10, 49, 19), childView1.getAbsRect());
+ assertEquals(new Rectangle(10, 10, 49, 19), childView1.getSelectionRect());
+ assertSame(childView1.getUiViewNode(), child1Node);
+
+ assertEquals("RelativeLayout", includedView1.getName());
+ assertSame(multiChildNode1, includedView1.getUiViewNode());
+ assertEquals("foo", includedView1.getUiViewNode().getDescriptor().getXmlName());
+ assertSame(multiChildNode2, includedView2.getUiViewNode());
+ assertEquals("AbsoluteLayout", includedView2.getName());
+ assertEquals("bar", includedView2.getUiViewNode().getDescriptor().getXmlName());
+ assertSame(rootView, includedView1.getParent());
+ assertSame(rootView, includedView2.getParent());
+ assertEquals(new Rectangle(10, 30, 69, 4), includedView1.getAbsRect());
+ assertEquals(new Rectangle(10, 30, 69, 5), includedView1.getSelectionRect());
+ assertEquals(new Rectangle(20, 50, 39, -26), includedView2.getAbsRect());
+ assertEquals(new Rectangle(20, 35, 39, 5), includedView2.getSelectionRect());
+ assertEquals(0, includedView1.getChildren().size());
+ assertEquals(0, includedView2.getChildren().size());
+ }
+
public void testMerge() throws Exception {
// Test rendering of MULTIPLE included views or when there is no simple match
// between view info and ui element node children
@@ -222,7 +281,7 @@ public class CanvasViewInfoTest extends TestCase {
ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25);
child2.setChildren(Arrays.asList(child21));
- CanvasViewInfo rootView = CanvasViewInfo.create(root);
+ CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst();
assertNotNull(rootView);
assertEquals("LinearLayout", rootView.getName());
assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
@@ -240,14 +299,249 @@ public class CanvasViewInfoTest extends TestCase {
assertEquals(new Rectangle(10, 10, 49, 19), childView1.getSelectionRect());
assertSame(childView1.getUiViewNode(), child1Node);
- assertEquals("foo", includedView.getName());
+ assertEquals("RelativeLayout", includedView.getName());
assertSame(rootView, includedView.getParent());
- assertEquals(new Rectangle(10, 30, 70, 5), includedView.getAbsRect());
- assertEquals(new Rectangle(10, 30, 70, 5), includedView.getSelectionRect());
+ assertEquals(new Rectangle(10, 30, 69, 4), includedView.getAbsRect());
+ assertEquals(new Rectangle(10, 30, 69, 5), includedView.getSelectionRect());
assertEquals(0, includedView.getChildren().size());
assertSame(multiChildNode, includedView.getUiViewNode());
}
+ public void testInsertMerge() throws Exception {
+ // Test rendering of MULTIPLE included views or when there is no simple match
+ // between view info and ui element node children
+
+ UiViewElementNode mergeNode = createNode("merge", true);
+ UiViewElementNode rootNode = createNode(mergeNode, "android.widget.Button", false);
+ ViewInfo root = new ViewInfo("Button", rootNode, 10, 10, 100, 100);
+
+ CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst();
+ assertNotNull(rootView);
+ assertEquals("merge", rootView.getName());
+ assertSame(rootView.getUiViewNode(), mergeNode);
+ assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
+ assertEquals(new Rectangle(10, 10, 89, 89), rootView.getSelectionRect());
+ assertNull(rootView.getParent());
+ assertSame(mergeNode, rootView.getUiViewNode());
+ assertEquals(1, rootView.getChildren().size());
+
+ CanvasViewInfo childView1 = rootView.getChildren().get(0);
+
+ assertEquals("Button", childView1.getName());
+ assertSame(rootView, childView1.getParent());
+ assertEquals(new Rectangle(10, 10, 89, 89), childView1.getAbsRect());
+ assertEquals(new Rectangle(10, 10, 89, 89), childView1.getSelectionRect());
+ assertSame(childView1.getUiViewNode(), rootNode);
+ }
+
+ public void testUnmatchedMissing() throws Exception {
+ UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
+ ViewInfo root = new ViewInfo("LinearLayout", rootNode, 0, 0, 100, 100);
+ List<ViewInfo> children = new ArrayList<ViewInfo>();
+ // Should be matched up with corresponding node:
+ Set<Integer> missingKeys = new HashSet<Integer>();
+ // Should not be matched with any views, but should get view created:
+ Set<Integer> extraKeys = new HashSet<Integer>();
+ // Should not be matched with any nodes
+ Set<Integer> extraViews = new HashSet<Integer>();
+ int numViews = 30;
+ missingKeys.add(0);
+ missingKeys.add(4);
+ missingKeys.add(14);
+ missingKeys.add(29);
+ extraKeys.add(9);
+ extraKeys.add(20);
+ extraKeys.add(22);
+ extraViews.add(18);
+ extraViews.add(24);
+
+ List<String> expectedViewNames = new ArrayList<String>();
+ List<String> expectedNodeNames = new ArrayList<String>();
+
+ for (int i = 0; i < numViews; i++) {
+ UiViewElementNode childNode = null;
+ if (!extraViews.contains(i)) {
+ childNode = createNode(rootNode, "childNode" + i, false);
+ }
+ Object cookie = missingKeys.contains(i) || extraViews.contains(i) ? null : childNode;
+ ViewInfo childView = new ViewInfo("childView" + i, cookie,
+ 0, i * 20, 50, (i + 1) * 20);
+ children.add(childView);
+
+ if (!extraViews.contains(i)) {
+ expectedViewNames.add("childView" + i);
+ expectedNodeNames.add("childNode" + i);
+ }
+
+ if (extraKeys.contains(i)) {
+ createNode(rootNode, "extraNodeAt" + i, false);
+
+ expectedViewNames.add("extraNodeAt" + i);
+ expectedNodeNames.add("extraNodeAt" + i);
+ }
+ }
+ root.setChildren(children);
+
+ CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst();
+ assertNotNull(rootView);
+
+ // dump(root, 0);
+ // dump(rootView, 0);
+
+ assertEquals("LinearLayout", rootView.getName());
+ assertNull(rootView.getParent());
+ assertSame(rootNode, rootView.getUiViewNode());
+ assertEquals(numViews + extraKeys.size() - extraViews.size(), rootNode.getUiChildren()
+ .size());
+ assertEquals(numViews + extraKeys.size() - extraViews.size(),
+ rootView.getChildren().size());
+ assertEquals(expectedViewNames.size(), rootView.getChildren().size());
+ for (int i = 0, n = rootView.getChildren().size(); i < n; i++) {
+ CanvasViewInfo childView = rootView.getChildren().get(i);
+ String expectedViewName = expectedViewNames.get(i);
+ String expectedNodeName = expectedNodeNames.get(i);
+ assertEquals(expectedViewName, childView.getName());
+ assertNotNull(childView.getUiViewNode());
+ assertEquals(expectedNodeName, childView.getUiViewNode().getDescriptor().getXmlName());
+ }
+ }
+
+ public void testMergeCookies() throws Exception {
+ UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
+ ViewInfo root = new ViewInfo("LinearLayout", rootNode, 0, 0, 100, 100);
+
+ // Create the merge cookies in the opposite order to ensure that we don't
+ // apply our own logic when matching up views with nodes
+ LinkedList<MergeCookie> cookies = new LinkedList<MergeCookie>();
+ for (int i = 0; i < 10; i++) {
+ UiViewElementNode node = createNode(rootNode, "childNode" + i, false);
+ cookies.addFirst(new MergeCookie(node));
+ }
+ Iterator<MergeCookie> it = cookies.iterator();
+ ArrayList<ViewInfo> children = new ArrayList<ViewInfo>();
+ for (int i = 0; i < 10; i++) {
+ ViewInfo childView = new ViewInfo("childView" + i, it.next(), 0, i * 20, 50,
+ (i + 1) * 20);
+ children.add(childView);
+ }
+ root.setChildren(children);
+
+ CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst();
+ assertNotNull(rootView);
+
+ assertEquals("LinearLayout", rootView.getName());
+ assertNull(rootView.getParent());
+ assertSame(rootNode, rootView.getUiViewNode());
+ for (int i = 0, n = rootView.getChildren().size(); i < n; i++) {
+ CanvasViewInfo childView = rootView.getChildren().get(i);
+ assertEquals("childView" + i, childView.getName());
+ assertEquals("childNode" + (9 - i), childView.getUiViewNode().getDescriptor()
+ .getXmlName());
+ }
+ }
+
+ public void testMergeCookies2() throws Exception {
+ UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
+ ViewInfo root = new ViewInfo("LinearLayout", rootNode, 0, 0, 100, 100);
+
+ UiViewElementNode node1 = createNode(rootNode, "childNode1", false);
+ UiViewElementNode node2 = createNode(rootNode, "childNode2", false);
+ MergeCookie cookie1 = new MergeCookie(node1);
+ MergeCookie cookie2 = new MergeCookie(node2);
+
+ // Sets alternating merge cookies and checks whether the node sibling lists are
+ // okay and merged correctly
+
+ ArrayList<ViewInfo> children = new ArrayList<ViewInfo>();
+ for (int i = 0; i < 10; i++) {
+ Object cookie = (i % 2) == 0 ? cookie1 : cookie2;
+ ViewInfo childView = new ViewInfo("childView" + i, cookie, 0, i * 20, 50, (i + 1) * 20);
+ children.add(childView);
+ }
+ root.setChildren(children);
+
+ Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root);
+ CanvasViewInfo rootView = result.getFirst();
+ List<Rectangle> bounds = result.getSecond();
+ assertNull(bounds);
+ assertNotNull(rootView);
+
+ assertEquals("LinearLayout", rootView.getName());
+ assertNull(rootView.getParent());
+ assertSame(rootNode, rootView.getUiViewNode());
+ assertEquals(10, rootView.getChildren().size());
+ assertEquals(2, rootView.getUniqueChildren().size());
+ for (int i = 0, n = rootView.getChildren().size(); i < n; i++) {
+ CanvasViewInfo childView = rootView.getChildren().get(i);
+ assertEquals("childView" + i, childView.getName());
+ Object cookie = (i % 2) == 0 ? node1 : node2;
+ assertSame(cookie, childView.getUiViewNode());
+ List<CanvasViewInfo> nodeSiblings = childView.getNodeSiblings();
+ assertEquals(5, nodeSiblings.size());
+ }
+ List<CanvasViewInfo> nodeSiblings = rootView.getChildren().get(0).getNodeSiblings();
+ for (int j = 0; j < 5; j++) {
+ assertEquals("childView" + (j * 2), nodeSiblings.get(j).getName());
+ }
+ nodeSiblings = rootView.getChildren().get(1).getNodeSiblings();
+ for (int j = 0; j < 5; j++) {
+ assertEquals("childView" + (j * 2 + 1), nodeSiblings.get(j).getName());
+ }
+ }
+
+ public void testIncludeBounds() throws Exception {
+ UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
+ ViewInfo root = new ViewInfo("included", null, 0, 0, 100, 100);
+
+ UiViewElementNode node1 = createNode(rootNode, "childNode1", false);
+ UiViewElementNode node2 = createNode(rootNode, "childNode2", false);
+ MergeCookie cookie1 = new MergeCookie(node1);
+ MergeCookie cookie2 = new MergeCookie(node2);
+
+ // Sets alternating merge cookies and checks whether the node sibling lists are
+ // okay and merged correctly
+
+ ArrayList<ViewInfo> children = new ArrayList<ViewInfo>();
+ for (int i = 0; i < 10; i++) {
+ Object cookie = (i % 2) == 0 ? cookie1 : cookie2;
+ ViewInfo childView = new ViewInfo("childView" + i, cookie, 0, i * 20, 50, (i + 1) * 20);
+ children.add(childView);
+ }
+ root.setChildren(children);
+
+ Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root);
+ CanvasViewInfo rootView = result.getFirst();
+ List<Rectangle> bounds = result.getSecond();
+ assertNotNull(rootView);
+
+ assertEquals("included", rootView.getName());
+ assertNull(rootView.getParent());
+ assertNull(rootView.getUiViewNode());
+ assertEquals(10, rootView.getChildren().size());
+ assertEquals(2, rootView.getUniqueChildren().size());
+ for (int i = 0, n = rootView.getChildren().size(); i < n; i++) {
+ CanvasViewInfo childView = rootView.getChildren().get(i);
+ assertEquals("childView" + i, childView.getName());
+ Object cookie = (i % 2) == 0 ? node1 : node2;
+ assertSame(cookie, childView.getUiViewNode());
+ List<CanvasViewInfo> nodeSiblings = childView.getNodeSiblings();
+ assertEquals(5, nodeSiblings.size());
+ }
+ List<CanvasViewInfo> nodeSiblings = rootView.getChildren().get(0).getNodeSiblings();
+ for (int j = 0; j < 5; j++) {
+ assertEquals("childView" + (j * 2), nodeSiblings.get(j).getName());
+ }
+ nodeSiblings = rootView.getChildren().get(1).getNodeSiblings();
+ for (int j = 0; j < 5; j++) {
+ assertEquals("childView" + (j * 2 + 1), nodeSiblings.get(j).getName());
+ }
+
+ // Only show the primary bounds as included
+ assertEquals(2, bounds.size());
+ assertEquals(new Rectangle(0, 0, 49, 19), bounds.get(0));
+ assertEquals(new Rectangle(0, 20, 49, 19), bounds.get(1));
+ }
+
/**
* Dumps out the given {@link ViewInfo} hierarchy to standard out.
* Useful during development.
@@ -261,11 +555,10 @@ public class CanvasViewInfoTest extends TestCase {
System.out.println("Supports Embedded Layout=" + supportsEmbedding);
System.out.println("Rendering context=" + graphicalEditor.getIncludedWithin());
dump(root, 0);
-
}
/** Helper for {@link #dump(GraphicalEditorPart, ViewInfo)} */
- private static void dump(ViewInfo info, int depth) {
+ public static void dump(ViewInfo info, int depth) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append(" ");
@@ -285,7 +578,7 @@ public class CanvasViewInfoTest extends TestCase {
sb.append(" ");
UiViewElementNode node = (UiViewElementNode) cookie;
sb.append("<");
- sb.append(node.getXmlNode().getNodeName());
+ sb.append(node.getDescriptor().getXmlName());
sb.append("> ");
} else if (cookie != null) {
sb.append(" cookie=" + cookie);
@@ -297,4 +590,24 @@ public class CanvasViewInfoTest extends TestCase {
dump(child, depth + 1);
}
}
+
+ /** Helper for {@link #dump(GraphicalEditorPart, ViewInfo)} */
+ public static void dump(CanvasViewInfo info, int depth) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < depth; i++) {
+ sb.append(" ");
+ }
+ sb.append(info.getName());
+ sb.append(" [");
+ sb.append(info.getAbsRect());
+ sb.append("], node=");
+ sb.append(info.getUiViewNode());
+
+ System.out.println(sb.toString());
+
+ for (CanvasViewInfo child : info.getChildren()) {
+ dump(child, depth + 1);
+ }
+ }
+
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactoryTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactoryTest.java
index 08f191a..277089f 100755
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactoryTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactoryTest.java
@@ -48,7 +48,7 @@ public class NodeFactoryTest extends TestCase {
ViewElementDescriptor ved = new ViewElementDescriptor("xml", "com.example.MyJavaClass");
UiViewElementNode uiv = new UiViewElementNode(ved);
ViewInfo lvi = new ViewInfo("name", uiv, 10, 12, 110, 120);
- CanvasViewInfo cvi = CanvasViewInfo.create(lvi);
+ CanvasViewInfo cvi = CanvasViewInfo.create(lvi).getFirst();
// Create a NodeProxy.
NodeProxy proxy = m.create(cvi);
@@ -95,7 +95,7 @@ public class NodeFactoryTest extends TestCase {
ViewElementDescriptor ved = new ViewElementDescriptor("xml", "com.example.MyJavaClass");
UiViewElementNode uiv = new UiViewElementNode(ved);
ViewInfo lvi = new ViewInfo("name", uiv, 10, 12, 110, 120);
- CanvasViewInfo cvi = CanvasViewInfo.create(lvi);
+ CanvasViewInfo cvi = CanvasViewInfo.create(lvi).getFirst();
// NodeProxies are cached. Creating the same one twice returns the same proxy.
NodeProxy proxy1 = m.create(cvi);
@@ -107,7 +107,7 @@ public class NodeFactoryTest extends TestCase {
ViewElementDescriptor ved = new ViewElementDescriptor("xml", "com.example.MyJavaClass");
UiViewElementNode uiv = new UiViewElementNode(ved);
ViewInfo lvi = new ViewInfo("name", uiv, 10, 12, 110, 120);
- CanvasViewInfo cvi = CanvasViewInfo.create(lvi);
+ CanvasViewInfo cvi = CanvasViewInfo.create(lvi).getFirst();
// NodeProxies are cached. Creating the same one twice returns the same proxy.
NodeProxy proxy1 = m.create(cvi);