diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java | 1178 |
1 files changed, 0 insertions, 1178 deletions
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 deleted file mode 100644 index 03c6c3926..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java +++ /dev/null @@ -1,1178 +0,0 @@ -/* - * Copyright (C) 2009 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 static com.android.SdkConstants.FQCN_SPACE; -import static com.android.SdkConstants.FQCN_SPACE_V7; -import static com.android.SdkConstants.GESTURE_OVERLAY_VIEW; -import static com.android.SdkConstants.VIEW_MERGE; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.Margins; -import com.android.ide.common.api.Rect; -import com.android.ide.common.layout.GridLayoutRule; -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; -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.utils.Pair; - -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.ui.views.properties.IPropertyDescriptor; -import org.eclipse.ui.views.properties.IPropertySheetPage; -import org.eclipse.ui.views.properties.IPropertySource; -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. - * The only large difference is that we keep both the original bounds of the view info - * and we pre-compute the selection bounds which are absolute to the rendered image - * (whereas the original bounds are relative to the parent view.) - * <p/> - * Each view also knows its parent and children. - * <p/> - * We can't alter {@link ViewInfo} as it is part of the LayoutBridge and needs to - * have a fixed API. - * <p/> - * The view info also implements {@link IPropertySource}, which enables a linked - * {@link IPropertySheetPage} to display the attributes of the selected element. - * This class actually delegates handling of {@link IPropertySource} to the underlying - * {@link UiViewElementNode}, if any. - */ -public class CanvasViewInfo implements IPropertySource { - - /** - * Minimal size of the selection, in case an empty view or layout is selected. - */ - public static final int SELECTION_MIN_SIZE = 6; - - private final Rectangle mAbsRect; - private final Rectangle mSelectionRect; - private final String mName; - private final Object mViewObject; - private final UiViewElementNode mUiViewNode; - private CanvasViewInfo mParent; - private ViewInfo mViewInfo; - private final List<CanvasViewInfo> mChildren = new ArrayList<CanvasViewInfo>(); - - /** - * Is this view info an individually exploded view? This is the case for views - * that were specially inflated by the {@link UiElementPullParser} and assigned - * fixed padding because they were invisible and somebody requested visibility. - */ - 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, - Object viewObject, UiViewElementNode node, Rectangle absRect, - Rectangle selectionRect, ViewInfo viewInfo) { - mParent = parent; - mName = name; - mViewObject = viewObject; - mViewInfo = viewInfo; - 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 - */ - @NonNull - public Rectangle getAbsRect() { - return mAbsRect; - } - - /** - * Returns the absolute selection bounds of the view info as a rectangle. - * The selection bounds will always have a size greater or equal to - * {@link #SELECTION_MIN_SIZE}. - * The width/height is inclusive (i.e. width = right-left-1). - * This is in absolute "screen" coordinates (relative to the rendered bitmap). - * - * @return the absolute selection bounds - */ - @NonNull - public Rectangle getSelectionRect() { - return mSelectionRect; - } - - /** - * Returns the view node. Could be null, although unlikely. - * @return An {@link UiViewElementNode} that uniquely identifies the object in the XML model. - * @see ViewInfo#getCookie() - */ - @Nullable - public UiViewElementNode getUiViewNode() { - return mUiViewNode; - } - - /** - * 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 - */ - @Nullable - 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 - */ - @NonNull - public List<CanvasViewInfo> getChildren() { - return mChildren; - } - - /** - * 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 - */ - @Nullable - 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 - */ - @Nullable - public List<CanvasViewInfo> getNodeSiblings() { - return mNodeSiblings; - } - - /** - * Returns all the children of the canvas view info where each child corresponds to a - * unique node that the user can see and select. 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 - */ - @NonNull - public List<CanvasViewInfo> getUniqueChildren() { - boolean haveHidden = false; - - 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; - } - - haveHidden |= info.isHidden(); - } - - if (haveHidden) { - List<CanvasViewInfo> children = new ArrayList<CanvasViewInfo>(mChildren.size()); - for (CanvasViewInfo vi : mChildren) { - if (!vi.isHidden()) { - 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. - * - * @param potentialParent the view info to check - * @return true if the given info is a parent of this view - */ - public boolean isParent(@NonNull CanvasViewInfo potentialParent) { - CanvasViewInfo p = mParent; - while (p != null) { - if (p == potentialParent) { - return true; - } - p = p.getParent(); - } - return false; - } - - /** - * Returns the name of the {@link CanvasViewInfo}. - * Could be null, although unlikely. - * Experience shows this is the full qualified Java name of the View. - * TODO: Rename this method to getFqcn. - * - * @return the name of the view info - * - * @see ViewInfo#getClassName() - */ - @NonNull - public String getName() { - return mName; - } - - /** - * Returns the View object associated with the {@link CanvasViewInfo}. - * @return the view object or null. - */ - @Nullable - public Object getViewObject() { - return mViewObject; - } - - /** - * Returns the baseline of this object, or -1 if it does not support a baseline - * - * @return the baseline or -1 - */ - public int getBaseline() { - if (mViewInfo != null) { - int baseline = mViewInfo.getBaseLine(); - if (baseline != Integer.MIN_VALUE) { - return baseline; - } - } - - return -1; - } - - /** - * Returns the {@link Margins} for this {@link CanvasViewInfo} - * - * @return the {@link Margins} for this {@link CanvasViewInfo} - */ - @Nullable - public Margins getMargins() { - if (mViewInfo != null) { - int leftMargin = mViewInfo.getLeftMargin(); - int topMargin = mViewInfo.getTopMargin(); - int rightMargin = mViewInfo.getRightMargin(); - int bottomMargin = mViewInfo.getBottomMargin(); - return new Margins( - leftMargin != Integer.MIN_VALUE ? leftMargin : 0, - rightMargin != Integer.MIN_VALUE ? rightMargin : 0, - topMargin != Integer.MIN_VALUE ? topMargin : 0, - bottomMargin != Integer.MIN_VALUE ? bottomMargin : 0 - ); - } - - return null; - } - - // ---- Implementation of IPropertySource - // TODO: Get rid of this once the old propertysheet implementation is fully gone - - @Override - public Object getEditableValue() { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - return ((IPropertySource) uiView).getEditableValue(); - } - return null; - } - - @Override - public IPropertyDescriptor[] getPropertyDescriptors() { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - return ((IPropertySource) uiView).getPropertyDescriptors(); - } - return null; - } - - @Override - public Object getPropertyValue(Object id) { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - return ((IPropertySource) uiView).getPropertyValue(id); - } - return null; - } - - @Override - public boolean isPropertySet(Object id) { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - return ((IPropertySource) uiView).isPropertySet(id); - } - return false; - } - - @Override - public void resetPropertyValue(Object id) { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - ((IPropertySource) uiView).resetPropertyValue(id); - } - } - - @Override - public void setPropertyValue(Object id, Object value) { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - ((IPropertySource) uiView).setPropertyValue(id, value); - } - } - - /** - * Returns the XML node corresponding to this info, or null if there is no - * such XML node. - * - * @return The XML node corresponding to this info object, or null - */ - @Nullable - public Node getXmlNode() { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - return uiView.getXmlNode(); - } - - return null; - } - - /** - * Returns true iff this view info corresponds to a root element. - * - * @return True iff this is a root view info. - */ - public boolean isRoot() { - // Select the visual element -- unless it's the root. - // The root element is the one whose GRAND parent - // is null (because the parent will be a -document- - // node). - - // Special case: a gesture overlay is sometimes added as the root, but for all intents - // and purposes it is its layout child that is the real root so treat that one as the - // root as well (such that the whole layout canvas does not highlight as part of hovers - // etc) - if (mParent != null - && mParent.mName.endsWith(GESTURE_OVERLAY_VIEW) - && mParent.isRoot() - && mParent.mChildren.size() == 1) { - return true; - } - - return mUiViewNode == null || mUiViewNode.getUiParent() == null || - mUiViewNode.getUiParent().getUiParent() == null; - } - - /** - * Returns true if this {@link CanvasViewInfo} represents an invisible widget that - * should be highlighted when selected. This is the case for any layout that is less than the minimum - * threshold ({@link #SELECTION_MIN_SIZE}), or any other view that has -0- bounds. - * - * @return True if this is a tiny layout or invisible view - */ - public boolean isInvisible() { - if (isHidden()) { - // Don't expand and highlight hidden widgets - return false; - } - - if (mAbsRect.width < SELECTION_MIN_SIZE || mAbsRect.height < SELECTION_MIN_SIZE) { - return mUiViewNode != null && (mUiViewNode.getDescriptor().hasChildren() || - mAbsRect.width <= 0 || mAbsRect.height <= 0); - } - - return false; - } - - /** - * Returns true if this {@link CanvasViewInfo} represents a widget that should be - * hidden, such as a {@code <Space>} which are typically not manipulated by the user - * through dragging etc. - * - * @return true if this is a hidden view - */ - public boolean isHidden() { - if (GridLayoutRule.sDebugGridLayout) { - return false; - } - - return FQCN_SPACE.equals(mName) || FQCN_SPACE_V7.equals(mName); - } - - /** - * Is this {@link CanvasViewInfo} a view that has had its padding inflated in order to - * make it visible during selection or dragging? Note that this is NOT considered to - * be the case in the explode-all-views mode where all nodes have their padding - * increased; it's only used for views that individually exploded because they were - * requested visible and they returned true for {@link #isInvisible()}. - * - * @return True if this is an exploded node. - */ - public boolean isExploded() { - return mExploded; - } - - /** - * Mark this {@link CanvasViewInfo} as having been exploded or not. See the - * {@link #isExploded()} method for details on what this property means. - * - * @param exploded New value of the exploded property to mark this info with. - */ - void setExploded(boolean exploded) { - mExploded = exploded; - } - - /** - * Returns the info represented as a {@link SimpleElement}. - * - * @return A {@link SimpleElement} wrapping this info. - */ - @NonNull - SimpleElement toSimpleElement() { - - UiViewElementNode uiNode = getUiViewNode(); - - String fqcn = SimpleXmlTransfer.getFqcn(uiNode.getDescriptor()); - String parentFqcn = null; - Rect bounds = SwtUtils.toRect(getAbsRect()); - Rect parentBounds = null; - - UiElementNode uiParent = uiNode.getUiParent(); - if (uiParent != null) { - parentFqcn = SimpleXmlTransfer.getFqcn(uiParent.getDescriptor()); - } - if (getParent() != null) { - parentBounds = SwtUtils.toRect(getParent().getAbsRect()); - } - - SimpleElement e = new SimpleElement(fqcn, parentFqcn, bounds, parentBounds); - - for (UiAttributeNode attr : uiNode.getAllUiAttributes()) { - String value = attr.getCurrentValue(); - if (value != null && value.length() > 0) { - AttributeDescriptor attrDesc = attr.getDescriptor(); - SimpleAttribute a = new SimpleAttribute( - attrDesc.getNamespaceUri(), - attrDesc.getXmlLocalName(), - value); - e.addAttribute(a); - } - } - - for (CanvasViewInfo childVi : getChildren()) { - SimpleElement e2 = childVi.toSimpleElement(); - if (e2 != null) { - e.addInnerElement(e2); - } - } - - return e; - } - - /** - * Returns the layout url attribute value for the closest surrounding include or - * fragment element parent, or null if this {@link CanvasViewInfo} is not rendered as - * part of an include or fragment tag. - * - * @return the layout url attribute value for the surrounding include tag, or null if - * not applicable - */ - @Nullable - public String getIncludeUrl() { - CanvasViewInfo curr = this; - while (curr != null) { - if (curr.mUiViewNode != null) { - Node node = curr.mUiViewNode.getXmlNode(); - if (node != null && node.getNodeType() == Node.ELEMENT_NODE) { - String nodeName = node.getNodeName(); - if (node.getNamespaceURI() == null - && SdkConstants.VIEW_INCLUDE.equals(nodeName)) { - // Note: the layout attribute is NOT in the Android namespace - Element element = (Element) node; - String url = element.getAttribute(SdkConstants.ATTR_LAYOUT); - if (url.length() > 0) { - return url; - } - } else if (SdkConstants.VIEW_FRAGMENT.equals(nodeName)) { - String url = FragmentMenu.getFragmentLayout(node); - if (url != null) { - return url; - } - } - } - } - curr = curr.mParent; - } - - return null; - } - - /** Adds the given {@link CanvasViewInfo} as a new last child of this view */ - private void addChild(@NonNull CanvasViewInfo child) { - mChildren.add(child); - } - - /** Adds the given {@link CanvasViewInfo} as a child at the given index */ - private void addChildAt(int index, @NonNull 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(@NonNull CanvasViewInfo child) { - return mChildren.remove(child); - } - - @Override - public String toString() { - return "CanvasViewInfo [name=" + mName + ", node=" + mUiViewNode + "]"; - } - - // ---- 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 layoutlib5 if true, the {@link ViewInfo} hierarchy was created by layoutlib - * version 5 or higher, which means this algorithm can make certain assumptions - * (for example that {@code <merge>} siblings will provide {@link MergeCookie} - * references, so we don't have to search for them.) - * @param root the root {@link ViewInfo} to build from - * @return a {@link CanvasViewInfo} hierarchy - */ - @NonNull - public static Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root, boolean layoutlib5) { - return new Builder(layoutlib5).create(root); - } - - /** Builder object which walks over a tree of {@link ViewInfo} objects and builds - * up a corresponding {@link CanvasViewInfo} hierarchy. */ - private static class Builder { - public Builder(boolean layoutlib5) { - mLayoutLib5 = layoutlib5; - } - - /** - * The mapping from nodes that have a {@code <merge>} as a parent in the node - * model to their corresponding views - */ - private Map<UiViewElementNode, List<CanvasViewInfo>> mMergeNodeMap; - - /** - * Whether the ViewInfos are provided by a layout library that is version 5 or - * later, since that will allow us to take several shortcuts - */ - private boolean mLayoutLib5; - - /** - * Creates a hierarchy of {@link CanvasViewInfo} objects and merge bounding - * rectangles from the given {@link ViewInfo} hierarchy - */ - private 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.getNodeSiblings() == null || vi.isPrimaryNodeSibling()) { - includedBounds.add(vi.getAbsRect()); - } - } - - // 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); - } - } - - CanvasViewInfo mergeView = new CanvasViewInfo(rootView, VIEW_MERGE, null, - merge, absRect, absRect, null /* viewInfo */); - for (CanvasViewInfo view : merged) { - if (rootView.removeChild(view)) { - mergeView.addChild(view); - } - } - rootView.addChild(mergeView); - } - } - - 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(), - null /* viewInfo */); - // Insert the <merge> as the new real root - rootView.mParent = merge; - merge.addChild(rootView); - rootView = merge; - } - - return Pair.of(rootView, null); - } - } - - private boolean hasMergeParent(UiViewElementNode rootNode) { - UiElementNode rootParent = rootNode.getUiParent(); - return (rootParent instanceof UiViewElementNode - && VIEW_MERGE.equals(rootParent.getDescriptor().getXmlName())); - } - - /** 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); - } - - /** - * 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) { - - 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, root); - } - - /** 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); - // Bug workaround: Ensure that we never have a child node identical - // to its parent node: this can happen for example when rendering a - // ZoomControls view where the merge cookies point to the parent. - if (parent != null && view.mUiViewNode == parent.mUiViewNode) { - return null; - } - - // Process children: - parentX += viewInfo.getLeft(); - parentY += viewInfo.getTop(); - - List<ViewInfo> children = viewInfo.getChildren(); - - if (mLayoutLib5) { - for (ViewInfo child : children) { - Object cookie = child.getCookie(); - if (cookie instanceof UiViewElementNode || cookie instanceof MergeCookie) { - CanvasViewInfo childView = createSubtree(view, child, - parentX, parentY); - if (childView != null) { - view.addChild(childView); - } - } // else: null cookies, adapter item references, etc: No child views. - } - - return view; - } - - // See if we have any missing keys at this level - int missingNodes = 0; - int mergeNodes = 0; - 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)) { - 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) { - CanvasViewInfo childView = createSubtree(view, child, - parentX, parentY); - view.addChild(childView); - } - - // 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. - - UiViewElementNode uiViewNode = view.getUiViewNode(); - String containerName = uiViewNode != null - ? uiViewNode.getDescriptor().getXmlLocalName() : ""; //$NON-NLS-1$ - if (containerName.equals(SdkConstants.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>(); - if (uiViewNode != null) { - for (UiElementNode child : uiViewNode.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) { - CanvasViewInfo childView = createSubtree(view, child, - parentX, parentY); - view.addChild(childView); - } - } - } - } - } - - return view; - } - - /** - * 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); - if (childView != null) { - parentView.addChild(childView); - } - if (child.getCookie() instanceof UiViewElementNode) { - afterNode = (UiViewElementNode) child.getCookie(); - } - } else { - 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, null /* viewInfo */); - // 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, null /* viewInfo */); - parentView.addChild(v); - } - } - } - - 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; - } - - private boolean isAfter(UiViewElementNode afterNode, UiViewElementNode candidate) { - UiElementNode parent = candidate.getUiParent(); - if (parent != null) { - for (UiElementNode sibling : parent.getUiChildren()) { - if (sibling == afterNode) { - return true; - } else if (sibling == candidate) { - return false; - } - } - } - 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; - } - - /** 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 && subtree != null) { - parent.mChildren.add(subtree); - } - return subtree; - } else { - for (ViewInfo child : viewInfo.getChildren()) { - addKeyedSubtrees(parent, child, parentX + viewInfo.getLeft(), parentY - + viewInfo.getTop()); - } - - return null; - } - } - } -} |