builtin
callback
callbacks
+carlo
checkbox
classloader
classpath
instanceof
instantiatable
int
+javac
javadoc
layoutlib
leaky
marquee
metadata
min
+monte
multi
multimap
multimaps
sdk
se
searchable
+selectable
semi
serializer
settable
public static final String LIST_VIEW = "ListView"; //$NON-NLS-1$
public static final String GALLERY = "Gallery"; //$NON-NLS-1$
public static final String GRID_VIEW = "GridView"; //$NON-NLS-1$
+ public static final String SCROLL_VIEW = "ScrollView"; //$NON-NLS-1$
public static final String EXPANDABLE_LIST_VIEW = "ExpandableListView";//$NON-NLS-1$
public static final String ATTR_TEXT = "text"; //$NON-NLS-1$
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.ide.common.api.Rect;
+import com.android.ide.common.rendering.api.Capability;
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;
import org.w3c.dom.Node;
import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
/**
* Maps a {@link ViewInfo} in a structure more adapted to our needs.
private boolean mExploded;
/**
- * Constructs a {@link CanvasViewInfo} hierarchy based on a given {@link ViewInfo}
- * hierarchy. This call is recursive and builds a full tree.
- *
- * @param viewInfo The root of the {@link ViewInfo} hierarchy.
+ * Constructs a {@link CanvasViewInfo} initialized with the given initial values.
*/
- public CanvasViewInfo(ViewInfo viewInfo) {
- this(viewInfo, null /*parent*/, 0 /*parentX*/, 0 /*parentY*/);
- }
-
- private CanvasViewInfo(ViewInfo viewInfo, CanvasViewInfo parent,
- int parentX, int parentY) {
+ private CanvasViewInfo(CanvasViewInfo parent, String name,
+ Object viewObject, UiViewElementNode node, Rectangle absRect,
+ Rectangle selectionRect) {
mParent = parent;
- mName = viewInfo.getClassName();
- mViewObject = viewInfo.getViewObject();
-
- // The ViewInfo#getViewKey() method returns a cookie uniquely identifying the object
- // they represent on this side of the API.
- // In this case, the parser is guaranteed to be an UiElementPullParser, which creates
- // cookies that are of type UiViewElementNode.
- // We'll simply crash if the type is not right, as this is not supposed to happen
- // and nothing could work if there's a type mismatch.
- mUiViewNode = (UiViewElementNode) viewInfo.getCookie();
-
- int x = viewInfo.getLeft();
- int y = viewInfo.getTop();
- int w = viewInfo.getRight() - x;
- int h = viewInfo.getBottom() - y;
-
- if (parent != null) {
- x += parentX;
- y += parentY;
- }
-
- mAbsRect = new Rectangle(x, y, w - 1, h - 1);
-
- if (viewInfo.getChildren() != null) {
- for (ViewInfo child : viewInfo.getChildren()) {
- // 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.
- if (child.getCookie() instanceof UiViewElementNode) {
- mChildren.add(new CanvasViewInfo(child, this, x, y));
- }
- }
- }
-
- // adjust selection bounds for views which are too small to select
-
- if (w < SELECTION_MIN_SIZE) {
- int d = (SELECTION_MIN_SIZE - w) / 2;
- x -= d;
- w += SELECTION_MIN_SIZE - w;
- }
-
- if (h < SELECTION_MIN_SIZE) {
- int d = (SELECTION_MIN_SIZE - h) / 2;
- y -= d;
- h += SELECTION_MIN_SIZE - h;
- }
-
- mSelectionRect = new Rectangle(x, y, w - 1, h - 1);
+ mName = name;
+ mViewObject = viewObject;
+ mUiViewNode = node;
+ mAbsRect = absRect;
+ mSelectionRect = selectionRect;
}
/**
* Returns the original {@link ViewInfo} bounds in absolute coordinates
* over the whole graphic.
+ *
+ * @return the bounding box in absolute coordinates
*/
public Rectangle getAbsRect() {
return mAbsRect;
/**
* Returns the parent {@link CanvasViewInfo}.
* It is null for the root and non-null for children.
+ *
+ * @return the parent {@link CanvasViewInfo}, which can be null
*/
public CanvasViewInfo getParent() {
return mParent;
* Returns the list of children of this {@link CanvasViewInfo}.
* The list is never null. It can be empty.
* By contract, this.getChildren().get(0..n-1).getParent() == this.
+ *
+ * @return the children, never null
*/
- public ArrayList<CanvasViewInfo> getChildren() {
+ public List<CanvasViewInfo> getChildren() {
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.
+ *
+ * @param potentialParent the view info to check
+ * @return true if the given info is a parent of this view
*/
public boolean isParent(CanvasViewInfo potentialParent) {
if (potentialParent == null) {
* Could be null, although unlikely.
* Experience shows this is the full qualified Java name of the View.
*
+ * @return the name of the view info, or null
+ *
* @see ViewInfo#getClassName()
*/
public String getName() {
return null;
}
+
+ // ---- Factory functionality ----
+
+ /**
+ * Creates a new {@link CanvasViewInfo} hierarchy based on the given {@link ViewInfo}
+ * hierarchy. Note that this will not necessarily create one {@link CanvasViewInfo}
+ * for each {@link ViewInfo}. It will generally only create {@link CanvasViewInfo}
+ * objects for {@link ViewInfo} objects that contain a reference to an
+ * {@link UiViewElementNode}, meaning that it corresponds to an element in the XML
+ * file for this layout file. This is not always the case, such as in the following
+ * scenarios:
+ * <ul>
+ * <li>we link to other layouts with {@code <include>}
+ * <li>the current view is rendered within another view ("Show Included In") such that
+ * the outer file does not correspond to elements in the current included XML layout
+ * <li>on older platforms that don't support {@link Capability#EMBEDDED_LAYOUT} there
+ * is no reference to the {@code <include>} tag
+ * <li>with the {@code <merge>} tag we don't get a reference to the corresponding
+ * element
+ * <ul>
+ * <p>
+ * This method will build up a set of {@link CanvasViewInfo} that corresponds to the
+ * actual <b>selectable</b> views (which are also shown in the Outline).
+ *
+ * @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);
+ }
+ }
+
+ /** 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);
+ }
+
+ /**
+ * 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) {
+
+ int x = root.getLeft();
+ int y = root.getTop();
+ int w = root.getRight() - x;
+ int h = root.getBottom() - y;
+
+ x += parentX;
+ y += parentY;
+
+ Rectangle absRect = new Rectangle(x, y, w - 1, h - 1);
+
+ if (w < SELECTION_MIN_SIZE) {
+ int d = (SELECTION_MIN_SIZE - w) / 2;
+ x -= d;
+ w += SELECTION_MIN_SIZE - w;
+ }
+
+ 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);
+ }
+
+ /** 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;
+
+ 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;
+ 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 (missingNodes == 0) {
+ // No missing nodes; this is the normal case, and we can just continue to
+ // recursively add our children
+ 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);
+ }
+ }
+ for (ViewInfo child : children) {
+ Object cookie = child.getCookie();
+ if (cookie != null) {
+ unused.remove(cookie);
+ }
+ }
+ 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
+ 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.
+ UiViewElementNode reference = unused.get(0);
+ addBoundingView(view, children, reference, parentX, parentY);
+ }
+ }
+ }
+ }
+
+ 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;
+ } else {
+ absRect = absRect.union(rect);
+ }
+ } 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);
+ }
+ }
+
+ /** 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);
+ if (parent != null) {
+ parent.mChildren.add(subtree);
+ }
+ return subtree;
+ } else {
+ for (ViewInfo child : viewInfo.getChildren()) {
+ addKeyedSubtrees(parent, child, parentX + viewInfo.getLeft(), parentY
+ + viewInfo.getTop());
+ }
+
+ 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 new child at the given index */
+ private void addChild(CanvasViewInfo child, int index) {
+ if (index < 0) {
+ index = mChildren.size();
+ }
+ mChildren.add(index, child);
+ }
}
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+import static com.android.ide.common.layout.LayoutConstants.SCROLL_VIEW;
import static com.android.ide.eclipse.adt.AndroidConstants.ANDROID_PKG;
import static com.android.sdklib.resources.Density.DEFAULT_DENSITY;
// FIXME set the rendering mode using ViewRule or something.
List<UiElementNode> children = model.getUiChildren();
if (children.size() > 0 &&
- children.get(0).getDescriptor().getXmlLocalName().equals("ScrollView")) {
+ children.get(0).getDescriptor().getXmlLocalName().equals(SCROLL_VIEW)) {
renderingMode = RenderingMode.V_SCROLL;
}
}
// set the Image Overlay as the image factory.
params.setImageFactory(getCanvasControl().getImageOverlay());
- return layoutLib.createSession(params);
+ try {
+ return layoutLib.createSession(params);
+ } catch (RuntimeException t) {
+ // Exceptions from the bridge
+ displayError(t.getLocalizedMessage());
+ throw t;
+ }
}
/**
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+import com.android.sdklib.annotations.VisibleForTesting;
+
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Rectangle;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
/**
@Override
public void paint(GC gc) {
- List<CanvasViewInfo> included = mCanvas.getViewHierarchy().getIncluded();
- if (included == null || included.size() != 1) {
+ ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy();
+ List<Rectangle> includedBounds = viewHierarchy.getIncludedBounds();
+ if (includedBounds == null || includedBounds.size() == 0) {
// We don't support multiple included children yet. When that works,
// this code should use a BSP tree to figure out which regions to paint
// to leave holes in the mask.
if (image == null) {
return;
}
- ImageData data = image.getImageData();
-
- Rectangle hole = included.get(0).getAbsRect();
int oldAlpha = gc.getAlpha();
gc.setAlpha(MASK_TRANSPARENCY);
Color bg = gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
gc.setBackground(bg);
- ControlPoint topLeft = LayoutPoint.create(mCanvas, hole.x, hole.y).toControl();
- ControlPoint bottomRight = LayoutPoint.create(mCanvas, hole.x + hole.width,
- hole.y + hole.height).toControl();
- CanvasTransform hi = mCanvas.getHorizontalTransform();
- CanvasTransform vi = mCanvas.getVerticalTransform();
- int deltaX = hi.translate(0);
- int deltaY = vi.translate(0);
- int x1 = topLeft.x;
- int y1 = topLeft.y;
- int x2 = bottomRight.x;
- int y2 = bottomRight.y;
- int width = data.width;
- int height = data.height;
-
- width = hi.getScalledImgSize();
- height = vi.getScalledImgSize();
-
- if (y1 > deltaX) {
- // Top
- gc.fillRectangle(deltaX, deltaY, width, y1 - deltaY);
- }
-
- if (y2 < height) {
- // Bottom
- gc.fillRectangle(deltaX, y2, width, height - y2 + deltaY);
+ CanvasViewInfo root = viewHierarchy.getRoot();
+ Rectangle whole = root.getAbsRect();
+ whole = new Rectangle(whole.x, whole.y, whole.width + 1, whole.height + 1);
+ Collection<Rectangle> masks = subtractRectangles(whole, includedBounds);
+
+ for (Rectangle mask : masks) {
+ ControlPoint topLeft = LayoutPoint.create(mCanvas, mask.x, mask.y).toControl();
+ ControlPoint bottomRight = LayoutPoint.create(mCanvas, mask.x + mask.width,
+ mask.y + mask.height).toControl();
+ int x1 = topLeft.x;
+ int y1 = topLeft.y;
+ int x2 = bottomRight.x;
+ int y2 = bottomRight.y;
+
+ gc.fillRectangle(x1, y1, x2 - x1, y2 - y1);
}
- if (x1 > deltaX) {
- // Left
- gc.fillRectangle(deltaX, y1, x1 - deltaX, y2 - y1);
- }
+ gc.setAlpha(oldAlpha);
+ }
- if (x2 < width) {
- // Right
- gc.fillRectangle(x2, y1, width - x2 + deltaX, y2 - y1);
+ /**
+ * Given a Rectangle, remove holes from it (specified as a collection of Rectangles) such
+ * that the result is a list of rectangles that cover everything that is not a hole.
+ *
+ * @param rectangle the rectangle to subtract from
+ * @param holes the holes to subtract from the rectangle
+ * @return a list of sub rectangles that remain after subtracting out the given list of holes
+ */
+ @VisibleForTesting
+ static Collection<Rectangle> subtractRectangles(
+ Rectangle rectangle, Collection<Rectangle> holes) {
+ List<Rectangle> result = new ArrayList<Rectangle>();
+ result.add(rectangle);
+
+ for (Rectangle hole : holes) {
+ List<Rectangle> tempResult = new ArrayList<Rectangle>();
+ for (Rectangle r : result) {
+ if (hole.intersects(r)) {
+ // Clip the hole to fit the rectangle bounds
+ Rectangle h = hole.intersection(r);
+
+ // Split the rectangle
+
+ // Above (includes the NW and NE corners)
+ if (h.y > r.y) {
+ tempResult.add(new Rectangle(r.x, r.y, r.width, h.y - r.y));
+ }
+
+ // Left (not including corners)
+ if (h.x > r.x) {
+ tempResult.add(new Rectangle(r.x, h.y, h.x - r.x, h.height));
+ }
+
+ int hx2 = h.x + h.width;
+ int hy2 = h.y + h.height;
+ int rx2 = r.x + r.width;
+ int ry2 = r.y + r.height;
+
+ // Below (includes the SW and SE corners)
+ if (hy2 < ry2) {
+ tempResult.add(new Rectangle(r.x, hy2, r.width, ry2 - hy2));
+ }
+
+ // Right (not including corners)
+ if (hx2 < rx2) {
+ tempResult.add(new Rectangle(hx2, h.y, rx2 - hx2, h.height));
+ }
+ } else {
+ tempResult.add(r);
+ }
+ }
+
+ result = tempResult;
}
- gc.setAlpha(oldAlpha);
+ return result;
}
}
}
}
if (element instanceof CanvasViewInfo) {
- ArrayList<CanvasViewInfo> children = ((CanvasViewInfo) element).getChildren();
+ List<CanvasViewInfo> children = ((CanvasViewInfo) element).getChildren();
if (children != null) {
return children.toArray();
}
public boolean hasChildren(Object element) {
if (element instanceof CanvasViewInfo) {
- ArrayList<CanvasViewInfo> children = ((CanvasViewInfo) element).getChildren();
+ List<CanvasViewInfo> children = ((CanvasViewInfo) element).getChildren();
if (children != null) {
return children.size() > 0;
}
private boolean mExplodedParents;
/**
- * List of included view infos in the current view hierarchy.
+ * Bounds of included views in the current view hierarchy when rendered in other context
*/
- private List<CanvasViewInfo> mIncluded;
+ private List<Rectangle> mIncludedBounds;
/** The render session for the current view hierarchy */
private RenderSession mSession;
mSession = session;
mIsResultValid = (session != null && session.getResult().isSuccess());
mExplodedParents = false;
- mIncluded = null;
+ mIncludedBounds = null;
if (mIsResultValid && session != null) {
ViewInfo root = session.getRootView();
if (root == null) {
mLastValidViewInfoRoot = null;
} else {
- mLastValidViewInfoRoot = new CanvasViewInfo(session.getRootView());
+ mLastValidViewInfoRoot = CanvasViewInfo.create(root);
}
updateNodeProxies(mLastValidViewInfoRoot, null);
if (key != null) {
mCanvas.getNodeFactory().create(vi);
- if (key != null && parentKey == null && vi.getParent() != null) {
+ if (parentKey == null && vi.getParent() != null) {
// This is an included view root
- if (mIncluded == null) {
- mIncluded = new ArrayList<CanvasViewInfo>();
+ if (mIncludedBounds == null) {
+ mIncludedBounds = new ArrayList<Rectangle>();
}
- mIncluded.add(vi);
+ mIncludedBounds.add(vi.getAbsRect());
}
}
}
/**
- * Returns true when the last {@link #setResult} provided a valid
- * {@link LayoutScene}.
+ * Returns true when the last {@link #setSession} provided a valid
+ * {@link RenderSession}.
* <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.
}
/**
- * Returns the list of included views in the current view hierarchy. Can be null
+ * Returns the list of bounds for included views in the current view hierarchy. Can be null
* when there are no included views.
*
- * @return a list of included views, or null
+ * @return a list of included view bounds, or null
*/
- public List<CanvasViewInfo> getIncluded() {
- return mIncluded;
+ public List<Rectangle> getIncludedBounds() {
+ return mIncludedBounds;
}
+
}
DescriptorsUtils.getFreeWidgetId(uiRoot, "LinearLayout"));
}
- private static ElementDescriptor createDesc(String name, String fqn, boolean hasChildren) {
+ private static ViewElementDescriptor createDesc(String name, String fqn, boolean hasChildren) {
if (hasChildren) {
return new ViewElementDescriptor(name, name, fqn, "", "", new AttributeDescriptor[0],
new AttributeDescriptor[0], new ElementDescriptor[1], false);
--- /dev/null
+/*
+ * Copyright (C) 2010 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.gle2;
+
+import com.android.ide.common.rendering.api.Capability;
+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 org.eclipse.swt.graphics.Rectangle;
+
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+public class CanvasViewInfoTest extends TestCase {
+
+ private static ViewElementDescriptor createDesc(String name, String fqn, boolean hasChildren) {
+ if (hasChildren) {
+ return new ViewElementDescriptor(name, name, fqn, "", "", new AttributeDescriptor[0],
+ new AttributeDescriptor[0], new ElementDescriptor[1], false);
+ } else {
+ return new ViewElementDescriptor(name, fqn);
+ }
+ }
+
+ private static UiViewElementNode createNode(UiViewElementNode parent, String fqn,
+ boolean hasChildren) {
+ String name = fqn.substring(fqn.lastIndexOf('.') + 1);
+ ViewElementDescriptor descriptor = createDesc(name, fqn, hasChildren);
+ if (parent != null) {
+ return (UiViewElementNode) parent.appendNewUiChild(descriptor);
+ } else {
+ return new UiViewElementNode(descriptor);
+ }
+ }
+
+ private static UiViewElementNode createNode(String fqn, boolean hasChildren) {
+ return createNode(null, fqn, hasChildren);
+ }
+
+ public void testNormalCreate() throws Exception {
+ // Normal view hierarchy, no null keys anywhere
+
+ 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("Button", child1Node, 0, 0, 50, 20);
+ UiViewElementNode child2Node = createNode(rootNode, "android.widget.Button", false);
+ ViewInfo child2 = new ViewInfo("Button", child2Node, 0, 20, 70, 25);
+ root.setChildren(Arrays.asList(child1, child2));
+
+ CanvasViewInfo rootView = CanvasViewInfo.create(root);
+ 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(rootView.getUiViewNode(), rootNode);
+ assertEquals(2, rootView.getChildren().size());
+ CanvasViewInfo childView1 = rootView.getChildren().get(0);
+ CanvasViewInfo childView2 = rootView.getChildren().get(1);
+
+ assertEquals("Button", 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("Button", childView2.getName());
+ assertSame(rootView, childView2.getParent());
+ assertEquals(new Rectangle(10, 30, 69, 4), childView2.getAbsRect());
+ assertEquals(new Rectangle(10, 30, 69, 5), childView2.getSelectionRect());
+ assertSame(childView2.getUiViewNode(), child2Node);
+ }
+
+ public void testShowIn() throws Exception {
+ // Test rendering of "Show Included In" (included content rendered
+ // within an outer content that has null keys)
+
+ ViewInfo root = new ViewInfo("LinearLayout", null, 10, 10, 100, 100);
+ ViewInfo child1 = new ViewInfo("CheckBox", null, 0, 0, 50, 20);
+ UiViewElementNode child2Node = createNode("android.widget.RelativeLayout", true);
+ ViewInfo child2 = new ViewInfo("RelativeLayout", child2Node, 0, 20, 70, 25);
+ root.setChildren(Arrays.asList(child1, child2));
+ UiViewElementNode child21Node = createNode("android.widget.Button", false);
+ ViewInfo child21 = new ViewInfo("RadioButton", child21Node, 0, 20, 70, 25);
+ child2.setChildren(Arrays.asList(child21));
+
+ CanvasViewInfo rootView = CanvasViewInfo.create(root);
+ 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());
+ assertNull(rootView.getUiViewNode());
+ assertEquals(1, rootView.getChildren().size());
+ CanvasViewInfo includedView = rootView.getChildren().get(0);
+
+ assertEquals("RelativeLayout", includedView.getName());
+ assertSame(rootView, includedView.getParent());
+ assertEquals(new Rectangle(10, 30, 69, 4), includedView.getAbsRect());
+ assertEquals(new Rectangle(10, 30, 69, 5), includedView.getSelectionRect());
+ assertSame(includedView.getUiViewNode(), child2Node);
+
+ CanvasViewInfo grandChild = includedView.getChildren().get(0);
+ assertNotNull(grandChild);
+ assertEquals("RadioButton", grandChild.getName());
+ assertSame(child21Node, grandChild.getUiViewNode());
+ assertEquals(new Rectangle(10, 50, 69, 4), grandChild.getAbsRect());
+ assertEquals(new Rectangle(10, 50, 69, 5), grandChild.getSelectionRect());
+ }
+
+ public void testIncludeTag() throws Exception {
+ // Test rendering of included views on layoutlib 5+ (e.g. has <include> tag)
+
+ 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 child2Node = createNode(rootNode, "include", true);
+ ViewInfo child2 = new ViewInfo("RelativeLayout", child2Node, 0, 20, 70, 25);
+ root.setChildren(Arrays.asList(child1, child2));
+ ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25);
+ child2.setChildren(Arrays.asList(child21));
+
+ CanvasViewInfo rootView = CanvasViewInfo.create(root);
+ 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(2, rootView.getChildren().size());
+
+ CanvasViewInfo childView1 = rootView.getChildren().get(0);
+ CanvasViewInfo includedView = rootView.getChildren().get(1);
+
+ 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", includedView.getName());
+ assertSame(rootView, includedView.getParent());
+ assertEquals(new Rectangle(10, 30, 69, 4), includedView.getAbsRect());
+ assertEquals(new Rectangle(10, 30, 69, 5), includedView.getSelectionRect());
+ assertSame(includedView.getUiViewNode(), child2Node);
+ assertEquals(0, includedView.getChildren().size());
+ }
+
+ public void testNoIncludeTag() throws Exception {
+ // Test rendering of included views on layoutlib 4- (e.g. no <include> tag cookie
+ // in
+ // view info)
+
+ 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 child2Node = createNode(rootNode, "include", true);
+ ViewInfo child2 = new ViewInfo("RelativeLayout", null /* layoutlib 4 */, 0, 20, 70, 25);
+ root.setChildren(Arrays.asList(child1, child2));
+ ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25);
+ child2.setChildren(Arrays.asList(child21));
+
+ CanvasViewInfo rootView = CanvasViewInfo.create(root);
+ 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(2, rootView.getChildren().size());
+
+ CanvasViewInfo childView1 = rootView.getChildren().get(0);
+ CanvasViewInfo includedView = rootView.getChildren().get(1);
+
+ 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", includedView.getName());
+ assertSame(rootView, includedView.getParent());
+ assertEquals(new Rectangle(10, 30, 69, 4), includedView.getAbsRect());
+ assertEquals(new Rectangle(10, 30, 69, 5), includedView.getSelectionRect());
+ assertSame(includedView.getUiViewNode(), child2Node);
+ assertEquals(0, includedView.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
+
+ 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 multiChildNode = createNode(rootNode, "foo", 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);
+ 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(2, rootView.getChildren().size());
+
+ CanvasViewInfo childView1 = rootView.getChildren().get(0);
+ CanvasViewInfo includedView = rootView.getChildren().get(1);
+
+ 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("foo", 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(0, includedView.getChildren().size());
+ assertSame(multiChildNode, includedView.getUiViewNode());
+ }
+
+ /**
+ * Dumps out the given {@link ViewInfo} hierarchy to standard out.
+ * Useful during development.
+ *
+ * @param graphicalEditor the editor associated with this hierarchy
+ * @param root the root of the {@link ViewInfo} hierarchy
+ */
+ public static void dump(GraphicalEditorPart graphicalEditor, ViewInfo root) {
+ System.out.println("\n\nRendering:");
+ boolean supportsEmbedding = graphicalEditor.renderingSupports(Capability.EMBEDDED_LAYOUT);
+ 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) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < depth; i++) {
+ sb.append(" ");
+ }
+ sb.append(info.getClassName());
+ sb.append(" [");
+ sb.append(info.getLeft());
+ sb.append(",");
+ sb.append(info.getTop());
+ sb.append(",");
+ sb.append(info.getRight());
+ sb.append(",");
+ sb.append(info.getBottom());
+ sb.append("] ");
+ Object cookie = info.getCookie();
+ if (cookie instanceof UiViewElementNode) {
+ sb.append(" ");
+ UiViewElementNode node = (UiViewElementNode) cookie;
+ sb.append("<");
+ sb.append(node.getXmlNode().getNodeName());
+ sb.append("> ");
+ } else if (cookie != null) {
+ sb.append(" cookie=" + cookie);
+ }
+
+ System.out.println(sb.toString());
+
+ for (ViewInfo child : info.getChildren()) {
+ dump(child, depth + 1);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2010 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.gle2;
+
+import org.eclipse.swt.graphics.Rectangle;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+public class IncludeOverlayTest extends TestCase {
+
+ public void testSubtractRectangles() throws Exception {
+ checkSubtract(new Rectangle(0, 0, 100, 80), Collections.<Rectangle> emptyList());
+
+ checkSubtract(new Rectangle(0, 0, 100, 80), Arrays.asList(new Rectangle(50, 50, 20, 20)));
+
+ checkSubtract(new Rectangle(0, 0, 100, 80), Arrays.asList(new Rectangle(50, 50, 20, 20),
+ new Rectangle(90, 90, 10, 10)));
+
+ checkSubtract(new Rectangle(0, 0, 100, 80), Arrays.asList(new Rectangle(50, 50, 20, 20),
+ new Rectangle(90, 90, 10, 10), new Rectangle(0, 0, 10, 10)));
+
+ }
+
+ private void checkSubtract(Rectangle rectangle, List<Rectangle> holes) {
+ Collection<Rectangle> result = IncludeOverlay.subtractRectangles(rectangle, holes);
+
+ // Do some Monte Carlo testing - pick random coordinates and check that if they
+ // are within one of the holes then they are not in the result list and vice versa
+ Random random = new Random(42L);
+ for (int i = 0; i < 1000; i++) {
+ int x = random.nextInt(rectangle.width);
+ int y = random.nextInt(rectangle.height);
+
+ boolean inHole = false;
+ for (Rectangle hole : holes) {
+ if (hole.contains(x, y)) {
+ inHole = true;
+ }
+ }
+
+ boolean inResult = false;
+ for (Rectangle r : result) {
+ if (r.contains(x, y)) {
+ inResult = true;
+ break;
+ }
+ }
+
+ if (inHole == inResult) {
+ fail("Wrong result at (" + x + "," + y + ") for rectangle=" + rectangle
+ + " and holes=" + holes + " where inHole=inResult="
+ + inResult);
+ }
+ }
+ }
+}
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 = new CanvasViewInfo(lvi);
+ CanvasViewInfo cvi = CanvasViewInfo.create(lvi);
// Create a NodeProxy.
NodeProxy proxy = m.create(cvi);
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 = new CanvasViewInfo(lvi);
+ CanvasViewInfo cvi = CanvasViewInfo.create(lvi);
// NodeProxies are cached. Creating the same one twice returns the same proxy.
NodeProxy proxy1 = m.create(cvi);
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 = new CanvasViewInfo(lvi);
+ CanvasViewInfo cvi = CanvasViewInfo.create(lvi);
// NodeProxies are cached. Creating the same one twice returns the same proxy.
NodeProxy proxy1 = m.create(cvi);