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