diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java | 771 |
1 files changed, 771 insertions, 0 deletions
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 new file mode 100644 index 000000000..d247e28d7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java @@ -0,0 +1,771 @@ +/* + * 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 static com.android.SdkConstants.ATTR_ID; +import static com.android.SdkConstants.VIEW_MERGE; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +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.utils.Pair; + +import org.eclipse.swt.graphics.Rectangle; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +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.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.RandomAccess; +import java.util.Set; + +/** + * The view hierarchy class manages a set of view info objects and performs find + * operations on this set. + */ +public class ViewHierarchy { + private static final boolean DUMP_INFO = false; + + private LayoutCanvas mCanvas; + + /** + * Constructs a new {@link ViewHierarchy} tied to the given + * {@link LayoutCanvas}. + * + * @param canvas The {@link LayoutCanvas} to create a {@link ViewHierarchy} + * for. + */ + public ViewHierarchy(LayoutCanvas canvas) { + mCanvas = canvas; + } + + /** + * 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 + * no root node. Null here does not mean the result was invalid, merely that the XML + * had no content to display -- we need to treat an empty document as valid so that + * we can drop new items in it. + */ + private CanvasViewInfo mLastValidViewInfoRoot; + + /** + * 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. + * <p/> + * Note that an empty document (with a null {@link #mLastValidViewInfoRoot}) is considered + * valid since it is an acceptable drop target. + */ + private boolean mIsResultValid; + + /** + * A list of invisible parents (see {@link CanvasViewInfo#isInvisible()} for + * details) in the current view hierarchy. + */ + private final List<CanvasViewInfo> mInvisibleParents = new ArrayList<CanvasViewInfo>(); + + /** + * A read-only view of {@link #mInvisibleParents}; note that this is NOT a copy so it + * reflects updates to the underlying {@link #mInvisibleParents} list. + */ + private final List<CanvasViewInfo> mInvisibleParentsReadOnly = + Collections.unmodifiableList(mInvisibleParents); + + /** + * Flag which records whether or not we have any exploded parent nodes in this + * view hierarchy. This is used to track whether or not we need to recompute the + * layout when we exit show-all-invisible-parents mode (see + * {@link LayoutCanvas#showInvisibleViews}). + */ + private boolean mExplodedParents; + + /** + * Bounds of included views in the current view hierarchy when rendered in other context + */ + private List<Rectangle> mIncludedBounds; + + /** The render session for the current view hierarchy */ + private RenderSession mSession; + + /** Map from nodes to canvas view infos */ + private Map<UiViewElementNode, CanvasViewInfo> mNodeToView = Collections.emptyMap(); + + /** Map from DOM nodes to canvas view infos */ + private Map<Node, CanvasViewInfo> mDomNodeToView = Collections.emptyMap(); + + /** + * Disposes the view hierarchy content. + */ + public void dispose() { + if (mSession != null) { + mSession.dispose(); + mSession = null; + } + } + + + /** + * Sets the result of the layout rendering. The result object indicates if the layout + * rendering succeeded. If it did, it contains a bitmap and the objects rectangles. + * + * Implementation detail: the bridge's computeLayout() method already returns a newly + * allocated ILayourResult. That means we can keep this result and hold on to it + * when it is valid. + * + * @param session The new session, either valid or not. + * @param explodedNodes The set of individual nodes the layout computer was asked to + * explode. Note that these are independent of the explode-all mode where + * all views are exploded; this is used only for the mode ( + * {@link LayoutCanvas#showInvisibleViews}) where individual invisible + * nodes are padded during certain interactions. + */ + /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes, + boolean layoutlib5) { + // replace the previous scene, so the previous scene must be disposed. + if (mSession != null) { + mSession.dispose(); + } + + mSession = session; + mIsResultValid = (session != null && session.getResult().isSuccess()); + mExplodedParents = false; + mNodeToView = new HashMap<UiViewElementNode, CanvasViewInfo>(50); + if (mIsResultValid && session != null) { + List<ViewInfo> rootList = session.getRootViews(); + + 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, layoutlib5); + } else { + infos = null; + } + } else { + if (rootList.size() > 1 && hasMergeRoot()) { + ViewInfo mergeRoot = createMergeInfo(session); + mergeRoot.setChildren(rootList); + infos = CanvasViewInfo.create(mergeRoot, layoutlib5); + } else { + ViewInfo root = rootList.get(0); + + if (root != null) { + infos = CanvasViewInfo.create(root, layoutlib5); + if (DUMP_INFO) { + dump(session, root, 0); + } + } else { + infos = null; + } + } + } + if (infos != null) { + mLastValidViewInfoRoot = infos.getFirst(); + mIncludedBounds = infos.getSecond(); + + if (mLastValidViewInfoRoot.getUiViewNode() == null && + mLastValidViewInfoRoot.getChildren().isEmpty()) { + GraphicalEditorPart editor = mCanvas.getEditorDelegate().getGraphicalEditor(); + if (editor.getIncludedWithin() != null) { + // Somehow, this view was supposed to be rendered within another + // view, yet this view was rendered as part of the other view. + // In that case, abort attempting to show included in; clear the + // include context and trigger a standalone re-render. + editor.showIn(null); + return; + } + } + + } else { + mLastValidViewInfoRoot = null; + mIncludedBounds = null; + } + + updateNodeProxies(mLastValidViewInfoRoot); + + // Update the data structures related to tracking invisible and exploded nodes. + // We need to find the {@link CanvasViewInfo} objects that correspond to + // the passed in {@link UiElementNode} keys that were re-rendered, and mark + // them as exploded and store them in a list for rendering. + mExplodedParents = false; + mInvisibleParents.clear(); + addInvisibleParents(mLastValidViewInfoRoot, explodedNodes); + + mDomNodeToView = new HashMap<Node, CanvasViewInfo>(mNodeToView.size()); + for (Map.Entry<UiViewElementNode, CanvasViewInfo> entry : mNodeToView.entrySet()) { + mDomNodeToView.put(entry.getKey().getXmlNode(), entry.getValue()); + } + + // Update the selection + mCanvas.getSelectionManager().sync(); + } else { + mIncludedBounds = null; + mInvisibleParents.clear(); + mDomNodeToView = Collections.emptyMap(); + } + } + + private ViewInfo createMergeInfo(RenderSession session) { + BufferedImage image = session.getImage(); + ControlPoint imageSize = ControlPoint.create(mCanvas, + mCanvas.getHorizontalTransform().getMargin() + image.getWidth(), + mCanvas.getVerticalTransform().getMargin() + image.getHeight()); + LayoutPoint layoutSize = imageSize.toLayout(); + UiDocumentNode model = mCanvas.getEditorDelegate().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.getEditorDelegate().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/> + * Since proxies are reused, this will update the bounds of an existing proxy when the + * canvas is refreshed and a view changes position or size. + * <p/> + * This is a recursive call that updates the whole hierarchy starting at the given + * view info. + */ + private void updateNodeProxies(CanvasViewInfo vi) { + if (vi == null) { + return; + } + + UiViewElementNode key = vi.getUiViewNode(); + + if (key != null) { + mCanvas.getNodeFactory().create(vi); + mNodeToView.put(key, vi); + } + + for (CanvasViewInfo child : vi.getChildren()) { + updateNodeProxies(child); + } + } + + /** + * Make a pass over the view hierarchy and look for two things: + * <ol> + * <li>Invisible parents. These are nodes that can hold children and have empty + * bounds. These are then added to the {@link #mInvisibleParents} list. + * <li>Exploded nodes. These are nodes that were previously marked as invisible, and + * subsequently rendered by a recomputed layout. They now no longer have empty bounds, + * but should be specially marked via {@link CanvasViewInfo#setExploded} such that we + * for example in selection operations can determine if we need to recompute the + * layout. + * </ol> + * + * @param vi + * @param invisibleNodes + */ + private void addInvisibleParents(CanvasViewInfo vi, Set<UiElementNode> invisibleNodes) { + if (vi == null) { + return; + } + + if (vi.isInvisible()) { + mInvisibleParents.add(vi); + } else if (invisibleNodes != null) { + UiViewElementNode key = vi.getUiViewNode(); + + if (key != null && invisibleNodes.contains(key)) { + vi.setExploded(true); + mExplodedParents = true; + mInvisibleParents.add(vi); + } + } + + for (CanvasViewInfo child : vi.getChildren()) { + addInvisibleParents(child, invisibleNodes); + } + } + + /** + * Returns the current {@link RenderSession}. + * @return the session or null if none have been set. + */ + public RenderSession getSession() { + return mSession; + } + + /** + * 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. + * <p/> + * Note that an empty document (with a null {@link #getRoot()}) is considered + * valid since it is an acceptable drop target. + * @return True when this {@link ViewHierarchy} contains a valid hierarchy of views. + */ + public boolean isValid() { + return mIsResultValid; + } + + /** + * Returns true if the last valid content of the canvas represents an empty document. + * @return True if the last valid content of the canvas represents an empty document. + */ + public boolean isEmpty() { + return mLastValidViewInfoRoot == null; + } + + /** + * Returns true if we have parents in this hierarchy that are invisible (e.g. because + * they have no children and zero layout bounds). + * + * @return True if we have invisible parents. + */ + public boolean hasInvisibleParents() { + return mInvisibleParents.size() > 0; + } + + /** + * Returns true if we have views that were exploded during rendering + * @return True if we have exploded parents + */ + public boolean hasExplodedParents() { + return mExplodedParents; + } + + /** Locates and return any views that overlap the given selection rectangle. + * @param topLeft The top left corner of the selection rectangle. + * @param bottomRight The bottom right corner of the selection rectangle. + * @return A collection of {@link CanvasViewInfo} objects that overlap the + * rectangle. + */ + public Collection<CanvasViewInfo> findWithin( + LayoutPoint topLeft, + LayoutPoint bottomRight) { + Rectangle selectionRectangle = new Rectangle(topLeft.x, topLeft.y, bottomRight.x + - topLeft.x, bottomRight.y - topLeft.y); + List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>(); + addWithin(mLastValidViewInfoRoot, selectionRectangle, infos); + return infos; + } + + /** + * Recursive internal version of {@link #findViewInfoAt(int, int)}. Please don't use directly. + * <p/> + * Tries to find the inner most child matching the given x,y coordinates in the view + * info sub-tree. This uses the potentially-expanded selection bounds. + * + * Returns null if not found. + */ + private void addWithin( + CanvasViewInfo canvasViewInfo, + Rectangle canvasRectangle, + List<CanvasViewInfo> infos) { + if (canvasViewInfo == null) { + return; + } + Rectangle r = canvasViewInfo.getSelectionRect(); + if (canvasRectangle.intersects(r)) { + + // try to find a matching child first + for (CanvasViewInfo child : canvasViewInfo.getChildren()) { + addWithin(child, canvasRectangle, infos); + } + + if (canvasViewInfo != mLastValidViewInfoRoot) { + infos.add(canvasViewInfo); + } + } + } + + /** + * Locates and returns the {@link CanvasViewInfo} corresponding to the given + * node, or null if it cannot be found. + * + * @param node The node we want to find a corresponding + * {@link CanvasViewInfo} for. + * @return The {@link CanvasViewInfo} corresponding to the given node, or + * null if no match was found. + */ + @Nullable + public CanvasViewInfo findViewInfoFor(@Nullable Node node) { + CanvasViewInfo vi = mDomNodeToView.get(node); + + if (vi == null) { + if (node == null) { + return null; + } else if (node.getNodeType() == Node.TEXT_NODE) { + return mDomNodeToView.get(node.getParentNode()); + } else if (node.getNodeType() == Node.ATTRIBUTE_NODE) { + return mDomNodeToView.get(((Attr) node).getOwnerElement()); + } else if (node.getNodeType() == Node.DOCUMENT_NODE) { + return mDomNodeToView.get(((Document) node).getDocumentElement()); + } + } + + return vi; + } + + /** + * Tries to find the inner most child matching the given x,y coordinates in + * the view info sub-tree, starting at the last know view info root. This + * uses the potentially-expanded selection bounds. + * <p/> + * Returns null if not found or if there's no view info root. + * + * @param p The point at which to look for the deepest match in the view + * hierarchy + * @return A {@link CanvasViewInfo} that intersects the given point, or null + * if nothing was found. + */ + public CanvasViewInfo findViewInfoAt(LayoutPoint p) { + if (mLastValidViewInfoRoot == null) { + return null; + } + + return findViewInfoAt_Recursive(p, mLastValidViewInfoRoot); + } + + /** + * Recursive internal version of {@link #findViewInfoAt(int, int)}. Please don't use directly. + * <p/> + * Tries to find the inner most child matching the given x,y coordinates in the view + * info sub-tree. This uses the potentially-expanded selection bounds. + * + * Returns null if not found. + */ + private CanvasViewInfo findViewInfoAt_Recursive(LayoutPoint p, CanvasViewInfo canvasViewInfo) { + if (canvasViewInfo == null) { + return null; + } + Rectangle r = canvasViewInfo.getSelectionRect(); + if (r.contains(p.x, p.y)) { + + // try to find a matching child first + // 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; + } + } + + // if no children matched, this is the view that we're looking for + return canvasViewInfo; + } + + return null; + } + + /** + * Returns a list of all the possible alternatives for a given view at the given + * position. This is used to build and manage the "alternate" selection that cycles + * around the parents or children of the currently selected element. + */ + /* package */ List<CanvasViewInfo> findAltViewInfoAt(LayoutPoint p) { + if (mLastValidViewInfoRoot != null) { + return findAltViewInfoAt_Recursive(p, mLastValidViewInfoRoot, null); + } + + return null; + } + + /** + * Internal recursive version of {@link #findAltViewInfoAt(int, int, CanvasViewInfo)}. + * Please don't use directly. + */ + private List<CanvasViewInfo> findAltViewInfoAt_Recursive( + LayoutPoint p, CanvasViewInfo parent, List<CanvasViewInfo> outList) { + Rectangle r; + + if (outList == null) { + outList = new ArrayList<CanvasViewInfo>(); + + if (parent != null) { + // add the parent root only once + r = parent.getSelectionRect(); + if (r.contains(p.x, p.y)) { + outList.add(parent); + } + } + } + + if (parent != null && !parent.getChildren().isEmpty()) { + // then add all children that match the position + for (CanvasViewInfo child : parent.getChildren()) { + r = child.getSelectionRect(); + if (r.contains(p.x, p.y)) { + outList.add(child); + } + } + + // finally recurse in the children + for (CanvasViewInfo child : parent.getChildren()) { + r = child.getSelectionRect(); + if (r.contains(p.x, p.y)) { + findAltViewInfoAt_Recursive(p, child, outList); + } + } + } + + return outList; + } + + /** + * Locates and returns the {@link CanvasViewInfo} corresponding to the given + * node, or null if it cannot be found. + * + * @param node The node we want to find a corresponding + * {@link CanvasViewInfo} for. + * @return The {@link CanvasViewInfo} corresponding to the given node, or + * null if no match was found. + */ + public CanvasViewInfo findViewInfoFor(INode node) { + return findViewInfoFor((NodeProxy) node); + } + + /** + * Tries to find a child with the same view key in the view info sub-tree. + * Returns null if not found. + * + * @param viewKey The view key that a matching {@link CanvasViewInfo} should + * have as its key. + * @return A {@link CanvasViewInfo} matching the given key, or null if not + * found. + */ + public CanvasViewInfo findViewInfoFor(UiElementNode viewKey) { + return mNodeToView.get(viewKey); + } + + /** + * Tries to find a child with the given node proxy as the view key. + * Returns null if not found. + * + * @param proxy The view key that a matching {@link CanvasViewInfo} should + * have as its key. + * @return A {@link CanvasViewInfo} matching the given key, or null if not + * found. + */ + @Nullable + public CanvasViewInfo findViewInfoFor(@Nullable NodeProxy proxy) { + if (proxy == null) { + return null; + } + return mNodeToView.get(proxy.getNode()); + } + + /** + * Returns a list of ALL ViewInfos (possibly excluding the root, depending + * on the parameter for that). + * + * @param includeRoot If true, include the root in the list, otherwise + * exclude it (but include all its children) + * @return A list of canvas view infos. + */ + public List<CanvasViewInfo> findAllViewInfos(boolean includeRoot) { + List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>(); + if (mIsResultValid && mLastValidViewInfoRoot != null) { + findAllViewInfos(infos, mLastValidViewInfoRoot, includeRoot); + } + + return infos; + } + + private void findAllViewInfos(List<CanvasViewInfo> result, CanvasViewInfo canvasViewInfo, + boolean includeRoot) { + if (canvasViewInfo != null) { + if (includeRoot || !canvasViewInfo.isRoot()) { + result.add(canvasViewInfo); + } + for (CanvasViewInfo child : canvasViewInfo.getChildren()) { + findAllViewInfos(result, child, true); + } + } + } + + /** + * Returns the root of the view hierarchy, if any (could be null, for example + * on rendering failure). + * + * @return The current view hierarchy, or null + */ + public CanvasViewInfo getRoot() { + return mLastValidViewInfoRoot; + } + + /** + * Returns a collection of views that have zero bounds and that correspond to empty + * parents. Note that the views may not actually have zero bounds; in particular, if + * they are exploded ({@link CanvasViewInfo#isExploded()}, then they will have the + * bounds of a shown invisible node. Therefore, this method returns the views that + * would be invisible in a real rendering of the scene. + * + * @return A collection of empty parent views. + */ + public List<CanvasViewInfo> getInvisibleViews() { + return mInvisibleParentsReadOnly; + } + + /** + * Returns the invisible nodes (the {@link UiElementNode} objects corresponding + * to the {@link CanvasViewInfo} objects returned from {@link #getInvisibleViews()}. + * We are pulling out the nodes since they preserve their identity across layout + * rendering, and in particular we return it as a set such that the layout renderer + * can perform quick identity checks when looking up attribute values during the + * rendering process. + * + * @return A set of the invisible nodes. + */ + public Set<UiElementNode> getInvisibleNodes() { + if (mInvisibleParents.size() == 0) { + return Collections.emptySet(); + } + + Set<UiElementNode> nodes = new HashSet<UiElementNode>(mInvisibleParents.size()); + for (CanvasViewInfo info : mInvisibleParents) { + UiViewElementNode node = info.getUiViewNode(); + if (node != null) { + nodes.add(node); + } + } + + return nodes; + } + + /** + * 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 view bounds, or null + */ + public List<Rectangle> getIncludedBounds() { + return mIncludedBounds; + } + + /** + * Returns a map of the default properties for the given view object in this session + * + * @param viewObject the object to look up the properties map for + * @return the map of properties, or null if not found + */ + @Nullable + public Map<String, String> getDefaultProperties(@NonNull Object viewObject) { + if (mSession != null) { + return mSession.getDefaultProperties(viewObject); + } + + return null; + } + + /** + * Dumps a {@link ViewInfo} hierarchy to stdout + * + * @param session the corresponding session, if any + * @param info the {@link ViewInfo} object to dump + * @param depth the depth to indent it to + */ + public static void dump(RenderSession session, ViewInfo info, int depth) { + if (DUMP_INFO) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < depth; i++) { + sb.append(" "); //$NON-NLS-1$ + } + sb.append(info.getClassName()); + sb.append(" ["); //$NON-NLS-1$ + sb.append(info.getLeft()); + sb.append(","); //$NON-NLS-1$ + sb.append(info.getTop()); + sb.append(","); //$NON-NLS-1$ + sb.append(info.getRight()); + sb.append(","); //$NON-NLS-1$ + sb.append(info.getBottom()); + sb.append("]"); //$NON-NLS-1$ + Object cookie = info.getCookie(); + if (cookie instanceof UiViewElementNode) { + sb.append(" "); //$NON-NLS-1$ + UiViewElementNode node = (UiViewElementNode) cookie; + sb.append("<"); //$NON-NLS-1$ + sb.append(node.getDescriptor().getXmlName()); + sb.append(">"); //$NON-NLS-1$ + + String id = node.getAttributeValue(ATTR_ID); + if (id != null && !id.isEmpty()) { + sb.append(" "); + sb.append(id); + } + } else if (cookie != null) { + sb.append(" " + cookie); //$NON-NLS-1$ + } + /* Display defaults? + if (info.getViewObject() != null) { + Map<String, String> defaults = session.getDefaultProperties(info.getCookie()); + sb.append(" - defaults: "); //$NON-NLS-1$ + sb.append(defaults); + sb.append('\n'); + } + */ + + System.out.println(sb.toString()); + + for (ViewInfo child : info.getChildren()) { + dump(session, child, depth + 1); + } + } + } +} |