1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.editors.layout.gle2; 18 19 import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_MERGE; 20 21 import com.android.ide.common.api.INode; 22 import com.android.ide.common.rendering.api.RenderSession; 23 import com.android.ide.common.rendering.api.ViewInfo; 24 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; 25 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; 26 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; 27 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; 28 import com.android.util.Pair; 29 30 import org.eclipse.swt.graphics.Rectangle; 31 import org.w3c.dom.Node; 32 33 import java.awt.image.BufferedImage; 34 import java.util.ArrayList; 35 import java.util.Collection; 36 import java.util.Collections; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.RandomAccess; 42 import java.util.Set; 43 44 /** 45 * The view hierarchy class manages a set of view info objects and performs find 46 * operations on this set. 47 */ 48 public class ViewHierarchy { 49 private static final boolean DUMP_INFO = false; 50 51 private LayoutCanvas mCanvas; 52 53 /** 54 * Constructs a new {@link ViewHierarchy} tied to the given 55 * {@link LayoutCanvas}. 56 * 57 * @param canvas The {@link LayoutCanvas} to create a {@link ViewHierarchy} 58 * for. 59 */ 60 public ViewHierarchy(LayoutCanvas canvas) { 61 this.mCanvas = canvas; 62 } 63 64 /** 65 * The CanvasViewInfo root created by the last call to {@link #setSession} 66 * with a valid layout. 67 * <p/> 68 * This <em>can</em> be null to indicate we're dealing with an empty document with 69 * no root node. Null here does not mean the result was invalid, merely that the XML 70 * had no content to display -- we need to treat an empty document as valid so that 71 * we can drop new items in it. 72 */ 73 private CanvasViewInfo mLastValidViewInfoRoot; 74 75 /** 76 * True when the last {@link #setSession} provided a valid {@link LayoutScene}. 77 * <p/> 78 * When false this means the canvas is displaying an out-dated result image & bounds and some 79 * features should be disabled accordingly such a drag'n'drop. 80 * <p/> 81 * Note that an empty document (with a null {@link #mLastValidViewInfoRoot}) is considered 82 * valid since it is an acceptable drop target. 83 */ 84 private boolean mIsResultValid; 85 86 /** 87 * A list of invisible parents (see {@link CanvasViewInfo#isInvisible()} for 88 * details) in the current view hierarchy. 89 */ 90 private final List<CanvasViewInfo> mInvisibleParents = new ArrayList<CanvasViewInfo>(); 91 92 /** 93 * A read-only view of {@link #mInvisibleParents}; note that this is NOT a copy so it 94 * reflects updates to the underlying {@link #mInvisibleParents} list. 95 */ 96 private final List<CanvasViewInfo> mInvisibleParentsReadOnly = 97 Collections.unmodifiableList(mInvisibleParents); 98 99 /** 100 * Flag which records whether or not we have any exploded parent nodes in this 101 * view hierarchy. This is used to track whether or not we need to recompute the 102 * layout when we exit show-all-invisible-parents mode (see 103 * {@link LayoutCanvas#showInvisibleViews}). 104 */ 105 private boolean mExplodedParents; 106 107 /** 108 * Bounds of included views in the current view hierarchy when rendered in other context 109 */ 110 private List<Rectangle> mIncludedBounds; 111 112 /** The render session for the current view hierarchy */ 113 private RenderSession mSession; 114 115 /** Map from nodes to canvas view infos */ 116 private Map<UiViewElementNode, CanvasViewInfo> mNodeToView = Collections.emptyMap(); 117 118 /** 119 * Disposes the view hierarchy content. 120 */ 121 public void dispose() { 122 if (mSession != null) { 123 mSession.dispose(); 124 mSession = null; 125 } 126 } 127 128 129 /** 130 * Sets the result of the layout rendering. The result object indicates if the layout 131 * rendering succeeded. If it did, it contains a bitmap and the objects rectangles. 132 * 133 * Implementation detail: the bridge's computeLayout() method already returns a newly 134 * allocated ILayourResult. That means we can keep this result and hold on to it 135 * when it is valid. 136 * 137 * @param session The new session, either valid or not. 138 * @param explodedNodes The set of individual nodes the layout computer was asked to 139 * explode. Note that these are independent of the explode-all mode where 140 * all views are exploded; this is used only for the mode ( 141 * {@link LayoutCanvas#showInvisibleViews}) where individual invisible 142 * nodes are padded during certain interactions. 143 */ 144 /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes, 145 boolean layoutlib5) { 146 // replace the previous scene, so the previous scene must be disposed. 147 if (mSession != null) { 148 mSession.dispose(); 149 } 150 151 mSession = session; 152 mIsResultValid = (session != null && session.getResult().isSuccess()); 153 mExplodedParents = false; 154 mNodeToView = new HashMap<UiViewElementNode, CanvasViewInfo>(50); 155 if (mIsResultValid && session != null) { 156 List<ViewInfo> rootList = session.getRootViews(); 157 158 Pair<CanvasViewInfo,List<Rectangle>> infos = null; 159 160 if (rootList == null || rootList.size() == 0) { 161 // Special case: Look to see if this is really an empty <merge> view, 162 // which shows up without any ViewInfos in the merge. In that case we 163 // want to manufacture an empty view, such that we can target the view 164 // via drag & drop, etc. 165 if (hasMergeRoot()) { 166 ViewInfo mergeRoot = createMergeInfo(session); 167 infos = CanvasViewInfo.create(mergeRoot, layoutlib5); 168 } else { 169 infos = null; 170 } 171 } else { 172 if (rootList.size() > 1 && hasMergeRoot()) { 173 ViewInfo mergeRoot = createMergeInfo(session); 174 mergeRoot.setChildren(rootList); 175 infos = CanvasViewInfo.create(mergeRoot, layoutlib5); 176 } else { 177 ViewInfo root = rootList.get(0); 178 179 if (root != null) { 180 infos = CanvasViewInfo.create(root, layoutlib5); 181 if (DUMP_INFO) { 182 dump(root, 0); 183 } 184 } else { 185 infos = null; 186 } 187 } 188 } 189 if (infos != null) { 190 mLastValidViewInfoRoot = infos.getFirst(); 191 mIncludedBounds = infos.getSecond(); 192 193 if (mLastValidViewInfoRoot.getUiViewNode() == null && 194 mLastValidViewInfoRoot.getChildren().isEmpty()) { 195 GraphicalEditorPart editor = mCanvas.getLayoutEditor().getGraphicalEditor(); 196 if (editor.getIncludedWithin() != null) { 197 // Somehow, this view was supposed to be rendered within another 198 // view, yet this view was rendered as part of the other view. 199 // In that case, abort attempting to show included in; clear the 200 // include context and trigger a standalone re-render. 201 editor.showIn(null); 202 return; 203 } 204 } 205 206 } else { 207 mLastValidViewInfoRoot = null; 208 mIncludedBounds = null; 209 } 210 211 updateNodeProxies(mLastValidViewInfoRoot); 212 213 // Update the data structures related to tracking invisible and exploded nodes. 214 // We need to find the {@link CanvasViewInfo} objects that correspond to 215 // the passed in {@link UiElementNode} keys that were re-rendered, and mark 216 // them as exploded and store them in a list for rendering. 217 mExplodedParents = false; 218 mInvisibleParents.clear(); 219 addInvisibleParents(mLastValidViewInfoRoot, explodedNodes); 220 221 // Update the selection 222 mCanvas.getSelectionManager().sync(); 223 } else { 224 mIncludedBounds = null; 225 mInvisibleParents.clear(); 226 } 227 } 228 229 private ViewInfo createMergeInfo(RenderSession session) { 230 BufferedImage image = session.getImage(); 231 ControlPoint imageSize = ControlPoint.create(mCanvas, 232 mCanvas.getHorizontalTransform().getMargin() + image.getWidth(), 233 mCanvas.getVerticalTransform().getMargin() + image.getHeight()); 234 LayoutPoint layoutSize = imageSize.toLayout(); 235 UiDocumentNode model = mCanvas.getLayoutEditor().getUiRootNode(); 236 List<UiElementNode> children = model.getUiChildren(); 237 return new ViewInfo(VIEW_MERGE, children.get(0), 0, 0, layoutSize.x, layoutSize.y); 238 } 239 240 /** 241 * Returns true if this view hierarchy corresponds to an editor that has a {@code 242 * <merge>} tag at the root 243 * 244 * @return true if there is a {@code <merge>} at the root of this editor's document 245 */ 246 private boolean hasMergeRoot() { 247 UiDocumentNode model = mCanvas.getLayoutEditor().getUiRootNode(); 248 if (model != null) { 249 List<UiElementNode> children = model.getUiChildren(); 250 if (children != null && children.size() > 0 251 && VIEW_MERGE.equals(children.get(0).getDescriptor().getXmlName())) { 252 return true; 253 } 254 } 255 256 return false; 257 } 258 259 /** 260 * Creates or updates the node proxy for this canvas view info. 261 * <p/> 262 * Since proxies are reused, this will update the bounds of an existing proxy when the 263 * canvas is refreshed and a view changes position or size. 264 * <p/> 265 * This is a recursive call that updates the whole hierarchy starting at the given 266 * view info. 267 */ 268 private void updateNodeProxies(CanvasViewInfo vi) { 269 if (vi == null) { 270 return; 271 } 272 273 UiViewElementNode key = vi.getUiViewNode(); 274 275 if (key != null) { 276 mCanvas.getNodeFactory().create(vi); 277 mNodeToView.put(key, vi); 278 } 279 280 for (CanvasViewInfo child : vi.getChildren()) { 281 updateNodeProxies(child); 282 } 283 } 284 285 /** 286 * Make a pass over the view hierarchy and look for two things: 287 * <ol> 288 * <li>Invisible parents. These are nodes that can hold children and have empty 289 * bounds. These are then added to the {@link #mInvisibleParents} list. 290 * <li>Exploded nodes. These are nodes that were previously marked as invisible, and 291 * subsequently rendered by a recomputed layout. They now no longer have empty bounds, 292 * but should be specially marked via {@link CanvasViewInfo#setExploded} such that we 293 * for example in selection operations can determine if we need to recompute the 294 * layout. 295 * </ol> 296 * 297 * @param vi 298 * @param invisibleNodes 299 */ 300 private void addInvisibleParents(CanvasViewInfo vi, Set<UiElementNode> invisibleNodes) { 301 if (vi == null) { 302 return; 303 } 304 305 if (vi.isInvisible()) { 306 mInvisibleParents.add(vi); 307 } else if (invisibleNodes != null) { 308 UiViewElementNode key = vi.getUiViewNode(); 309 310 if (key != null && invisibleNodes.contains(key)) { 311 vi.setExploded(true); 312 mExplodedParents = true; 313 mInvisibleParents.add(vi); 314 } 315 } 316 317 for (CanvasViewInfo child : vi.getChildren()) { 318 addInvisibleParents(child, invisibleNodes); 319 } 320 } 321 322 /** 323 * Returns the current {@link RenderSession}. 324 * @return the session or null if none have been set. 325 */ 326 public RenderSession getSession() { 327 return mSession; 328 } 329 330 /** 331 * Returns true when the last {@link #setSession} provided a valid 332 * {@link RenderSession}. 333 * <p/> 334 * When false this means the canvas is displaying an out-dated result image & bounds and some 335 * features should be disabled accordingly such a drag'n'drop. 336 * <p/> 337 * Note that an empty document (with a null {@link #getRoot()}) is considered 338 * valid since it is an acceptable drop target. 339 * @return True when this {@link ViewHierarchy} contains a valid hierarchy of views. 340 */ 341 public boolean isValid() { 342 return mIsResultValid; 343 } 344 345 /** 346 * Returns true if the last valid content of the canvas represents an empty document. 347 * @return True if the last valid content of the canvas represents an empty document. 348 */ 349 public boolean isEmpty() { 350 return mLastValidViewInfoRoot == null; 351 } 352 353 /** 354 * Returns true if we have parents in this hierarchy that are invisible (e.g. because 355 * they have no children and zero layout bounds). 356 * 357 * @return True if we have invisible parents. 358 */ 359 public boolean hasInvisibleParents() { 360 return mInvisibleParents.size() > 0; 361 } 362 363 /** 364 * Returns true if we have views that were exploded during rendering 365 * @return True if we have exploded parents 366 */ 367 public boolean hasExplodedParents() { 368 return mExplodedParents; 369 } 370 371 /** Locates and return any views that overlap the given selection rectangle. 372 * @param topLeft The top left corner of the selection rectangle. 373 * @param bottomRight The bottom right corner of the selection rectangle. 374 * @return A collection of {@link CanvasViewInfo} objects that overlap the 375 * rectangle. 376 */ 377 public Collection<CanvasViewInfo> findWithin( 378 LayoutPoint topLeft, 379 LayoutPoint bottomRight) { 380 Rectangle selectionRectangle = new Rectangle(topLeft.x, topLeft.y, bottomRight.x 381 - topLeft.x, bottomRight.y - topLeft.y); 382 List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>(); 383 addWithin(mLastValidViewInfoRoot, selectionRectangle, infos); 384 return infos; 385 } 386 387 /** 388 * Recursive internal version of {@link #findViewInfoAt(int, int)}. Please don't use directly. 389 * <p/> 390 * Tries to find the inner most child matching the given x,y coordinates in the view 391 * info sub-tree. This uses the potentially-expanded selection bounds. 392 * 393 * Returns null if not found. 394 */ 395 private void addWithin( 396 CanvasViewInfo canvasViewInfo, 397 Rectangle canvasRectangle, 398 List<CanvasViewInfo> infos) { 399 if (canvasViewInfo == null) { 400 return; 401 } 402 Rectangle r = canvasViewInfo.getSelectionRect(); 403 if (canvasRectangle.intersects(r)) { 404 405 // try to find a matching child first 406 for (CanvasViewInfo child : canvasViewInfo.getChildren()) { 407 addWithin(child, canvasRectangle, infos); 408 } 409 410 if (canvasViewInfo != mLastValidViewInfoRoot) { 411 infos.add(canvasViewInfo); 412 } 413 } 414 } 415 416 /** 417 * Locates and returns the {@link CanvasViewInfo} corresponding to the given 418 * node, or null if it cannot be found. 419 * 420 * @param node The node we want to find a corresponding 421 * {@link CanvasViewInfo} for. 422 * @return The {@link CanvasViewInfo} corresponding to the given node, or 423 * null if no match was found. 424 */ 425 public CanvasViewInfo findViewInfoFor(Node node) { 426 if (mLastValidViewInfoRoot != null) { 427 return findViewInfoForNode(node, mLastValidViewInfoRoot); 428 } 429 return null; 430 } 431 432 /** 433 * Tries to find a child with the same view XML node in the view info sub-tree. 434 * Returns null if not found. 435 */ 436 private CanvasViewInfo findViewInfoForNode(Node xmlNode, CanvasViewInfo canvasViewInfo) { 437 if (canvasViewInfo == null) { 438 return null; 439 } 440 if (canvasViewInfo.getXmlNode() == xmlNode) { 441 return canvasViewInfo; 442 } 443 444 // Try to find a matching child 445 for (CanvasViewInfo child : canvasViewInfo.getChildren()) { 446 CanvasViewInfo v = findViewInfoForNode(xmlNode, child); 447 if (v != null) { 448 return v; 449 } 450 } 451 452 return null; 453 } 454 455 456 /** 457 * Tries to find the inner most child matching the given x,y coordinates in 458 * the view info sub-tree, starting at the last know view info root. This 459 * uses the potentially-expanded selection bounds. 460 * <p/> 461 * Returns null if not found or if there's no view info root. 462 * 463 * @param p The point at which to look for the deepest match in the view 464 * hierarchy 465 * @return A {@link CanvasViewInfo} that intersects the given point, or null 466 * if nothing was found. 467 */ 468 public CanvasViewInfo findViewInfoAt(LayoutPoint p) { 469 if (mLastValidViewInfoRoot == null) { 470 return null; 471 } 472 473 return findViewInfoAt_Recursive(p, mLastValidViewInfoRoot); 474 } 475 476 /** 477 * Recursive internal version of {@link #findViewInfoAt(int, int)}. Please don't use directly. 478 * <p/> 479 * Tries to find the inner most child matching the given x,y coordinates in the view 480 * info sub-tree. This uses the potentially-expanded selection bounds. 481 * 482 * Returns null if not found. 483 */ 484 private CanvasViewInfo findViewInfoAt_Recursive(LayoutPoint p, CanvasViewInfo canvasViewInfo) { 485 if (canvasViewInfo == null) { 486 return null; 487 } 488 Rectangle r = canvasViewInfo.getSelectionRect(); 489 if (r.contains(p.x, p.y)) { 490 491 // try to find a matching child first 492 // Iterate in REVERSE z order such that siblings on top 493 // are checked before earlier siblings (this matters in layouts like 494 // FrameLayout and in <merge> contexts where the views are sitting on top 495 // of each other and we want to select the same view as the one drawn 496 // on top of the others 497 List<CanvasViewInfo> children = canvasViewInfo.getChildren(); 498 assert children instanceof RandomAccess; 499 for (int i = children.size() - 1; i >= 0; i--) { 500 CanvasViewInfo child = children.get(i); 501 CanvasViewInfo v = findViewInfoAt_Recursive(p, child); 502 if (v != null) { 503 return v; 504 } 505 } 506 507 // if no children matched, this is the view that we're looking for 508 return canvasViewInfo; 509 } 510 511 return null; 512 } 513 514 /** 515 * Returns a list of all the possible alternatives for a given view at the given 516 * position. This is used to build and manage the "alternate" selection that cycles 517 * around the parents or children of the currently selected element. 518 */ 519 /* package */ List<CanvasViewInfo> findAltViewInfoAt(LayoutPoint p) { 520 if (mLastValidViewInfoRoot != null) { 521 return findAltViewInfoAt_Recursive(p, mLastValidViewInfoRoot, null); 522 } 523 524 return null; 525 } 526 527 /** 528 * Internal recursive version of {@link #findAltViewInfoAt(int, int, CanvasViewInfo)}. 529 * Please don't use directly. 530 */ 531 private List<CanvasViewInfo> findAltViewInfoAt_Recursive( 532 LayoutPoint p, CanvasViewInfo parent, List<CanvasViewInfo> outList) { 533 Rectangle r; 534 535 if (outList == null) { 536 outList = new ArrayList<CanvasViewInfo>(); 537 538 if (parent != null) { 539 // add the parent root only once 540 r = parent.getSelectionRect(); 541 if (r.contains(p.x, p.y)) { 542 outList.add(parent); 543 } 544 } 545 } 546 547 if (parent != null && !parent.getChildren().isEmpty()) { 548 // then add all children that match the position 549 for (CanvasViewInfo child : parent.getChildren()) { 550 r = child.getSelectionRect(); 551 if (r.contains(p.x, p.y)) { 552 outList.add(child); 553 } 554 } 555 556 // finally recurse in the children 557 for (CanvasViewInfo child : parent.getChildren()) { 558 r = child.getSelectionRect(); 559 if (r.contains(p.x, p.y)) { 560 findAltViewInfoAt_Recursive(p, child, outList); 561 } 562 } 563 } 564 565 return outList; 566 } 567 568 /** 569 * Locates and returns the {@link CanvasViewInfo} corresponding to the given 570 * node, or null if it cannot be found. 571 * 572 * @param node The node we want to find a corresponding 573 * {@link CanvasViewInfo} for. 574 * @return The {@link CanvasViewInfo} corresponding to the given node, or 575 * null if no match was found. 576 */ 577 public CanvasViewInfo findViewInfoFor(INode node) { 578 return findViewInfoFor((NodeProxy) node); 579 } 580 581 /** 582 * Tries to find a child with the same view key in the view info sub-tree. 583 * Returns null if not found. 584 * 585 * @param viewKey The view key that a matching {@link CanvasViewInfo} should 586 * have as its key. 587 * @return A {@link CanvasViewInfo} matching the given key, or null if not 588 * found. 589 */ 590 public CanvasViewInfo findViewInfoFor(UiElementNode viewKey) { 591 return mNodeToView.get(viewKey); 592 } 593 594 /** 595 * Tries to find a child with the given node proxy as the view key. 596 * Returns null if not found. 597 * 598 * @param proxy The view key that a matching {@link CanvasViewInfo} should 599 * have as its key. 600 * @return A {@link CanvasViewInfo} matching the given key, or null if not 601 * found. 602 */ 603 public CanvasViewInfo findViewInfoFor(NodeProxy proxy) { 604 return mNodeToView.get(proxy.getNode()); 605 } 606 607 /** 608 * Returns a list of ALL ViewInfos (possibly excluding the root, depending 609 * on the parameter for that). 610 * 611 * @param includeRoot If true, include the root in the list, otherwise 612 * exclude it (but include all its children) 613 * @return A list of canvas view infos. 614 */ 615 public List<CanvasViewInfo> findAllViewInfos(boolean includeRoot) { 616 List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>(); 617 if (mIsResultValid && mLastValidViewInfoRoot != null) { 618 findAllViewInfos(infos, mLastValidViewInfoRoot, includeRoot); 619 } 620 621 return infos; 622 } 623 624 private void findAllViewInfos(List<CanvasViewInfo> result, CanvasViewInfo canvasViewInfo, 625 boolean includeRoot) { 626 if (canvasViewInfo != null) { 627 if (includeRoot || !canvasViewInfo.isRoot()) { 628 result.add(canvasViewInfo); 629 } 630 for (CanvasViewInfo child : canvasViewInfo.getChildren()) { 631 findAllViewInfos(result, child, true); 632 } 633 } 634 } 635 636 /** 637 * Returns the root of the view hierarchy, if any (could be null, for example 638 * on rendering failure). 639 * 640 * @return The current view hierarchy, or null 641 */ 642 public CanvasViewInfo getRoot() { 643 return mLastValidViewInfoRoot; 644 } 645 646 /** 647 * Returns a collection of views that have zero bounds and that correspond to empty 648 * parents. Note that the views may not actually have zero bounds; in particular, if 649 * they are exploded ({@link CanvasViewInfo#isExploded()}, then they will have the 650 * bounds of a shown invisible node. Therefore, this method returns the views that 651 * would be invisible in a real rendering of the scene. 652 * 653 * @return A collection of empty parent views. 654 */ 655 public List<CanvasViewInfo> getInvisibleViews() { 656 return mInvisibleParentsReadOnly; 657 } 658 659 /** 660 * Returns the invisible nodes (the {@link UiElementNode} objects corresponding 661 * to the {@link CanvasViewInfo} objects returned from {@link #getInvisibleViews()}. 662 * We are pulling out the nodes since they preserve their identity across layout 663 * rendering, and in particular we return it as a set such that the layout renderer 664 * can perform quick identity checks when looking up attribute values during the 665 * rendering process. 666 * 667 * @return A set of the invisible nodes. 668 */ 669 public Set<UiElementNode> getInvisibleNodes() { 670 if (mInvisibleParents.size() == 0) { 671 return Collections.emptySet(); 672 } 673 674 Set<UiElementNode> nodes = new HashSet<UiElementNode>(mInvisibleParents.size()); 675 for (CanvasViewInfo info : mInvisibleParents) { 676 UiViewElementNode node = info.getUiViewNode(); 677 if (node != null) { 678 nodes.add(node); 679 } 680 } 681 682 return nodes; 683 } 684 685 /** 686 * Returns the list of bounds for included views in the current view hierarchy. Can be null 687 * when there are no included views. 688 * 689 * @return a list of included view bounds, or null 690 */ 691 public List<Rectangle> getIncludedBounds() { 692 return mIncludedBounds; 693 } 694 695 /** 696 * Dumps a {@link ViewInfo} hierarchy to stdout 697 * 698 * @param info the {@link ViewInfo} object to dump 699 * @param depth the depth to indent it to 700 */ 701 public static void dump(ViewInfo info, int depth) { 702 if (DUMP_INFO) { 703 StringBuilder sb = new StringBuilder(); 704 for (int i = 0; i < depth; i++) { 705 sb.append(" "); //$NON-NLS-1$ 706 } 707 sb.append(info.getClassName()); 708 sb.append(" ["); //$NON-NLS-1$ 709 sb.append(info.getLeft()); 710 sb.append(","); //$NON-NLS-1$ 711 sb.append(info.getTop()); 712 sb.append(","); //$NON-NLS-1$ 713 sb.append(info.getRight()); 714 sb.append(","); //$NON-NLS-1$ 715 sb.append(info.getBottom()); 716 sb.append("]"); //$NON-NLS-1$ 717 Object cookie = info.getCookie(); 718 if (cookie instanceof UiViewElementNode) { 719 sb.append(" "); //$NON-NLS-1$ 720 UiViewElementNode node = (UiViewElementNode) cookie; 721 sb.append("<"); //$NON-NLS-1$ 722 sb.append(node.getDescriptor().getXmlName()); 723 sb.append(">"); //$NON-NLS-1$ 724 } else if (cookie != null) { 725 sb.append(" " + cookie); //$NON-NLS-1$ 726 } 727 728 System.out.println(sb.toString()); 729 730 for (ViewInfo child : info.getChildren()) { 731 dump(child, depth + 1); 732 } 733 } 734 } 735 } 736