1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 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.hierarchyviewerlib.ui; 18 19 import com.android.hierarchyviewerlib.HierarchyViewerDirector; 20 import com.android.hierarchyviewerlib.models.TreeViewModel; 21 import com.android.hierarchyviewerlib.models.TreeViewModel.ITreeChangeListener; 22 import com.android.hierarchyviewerlib.ui.util.DrawableViewNode; 23 import com.android.hierarchyviewerlib.ui.util.DrawableViewNode.Point; 24 25 import org.eclipse.swt.SWT; 26 import org.eclipse.swt.events.DisposeEvent; 27 import org.eclipse.swt.events.DisposeListener; 28 import org.eclipse.swt.events.MouseEvent; 29 import org.eclipse.swt.events.MouseListener; 30 import org.eclipse.swt.events.PaintEvent; 31 import org.eclipse.swt.events.PaintListener; 32 import org.eclipse.swt.graphics.GC; 33 import org.eclipse.swt.graphics.Rectangle; 34 import org.eclipse.swt.graphics.Transform; 35 import org.eclipse.swt.widgets.Canvas; 36 import org.eclipse.swt.widgets.Composite; 37 import org.eclipse.swt.widgets.Display; 38 import org.eclipse.swt.widgets.Event; 39 import org.eclipse.swt.widgets.Listener; 40 41 import java.util.ArrayList; 42 43 public class LayoutViewer extends Canvas implements ITreeChangeListener { 44 45 private TreeViewModel mModel; 46 47 private DrawableViewNode mTree; 48 49 private DrawableViewNode mSelectedNode; 50 51 private Transform mTransform; 52 53 private Transform mInverse; 54 55 private double mScale; 56 57 private boolean mShowExtras = false; 58 59 private boolean mOnBlack = true; 60 61 public LayoutViewer(Composite parent) { 62 super(parent, SWT.NONE); 63 mModel = TreeViewModel.getModel(); 64 mModel.addTreeChangeListener(this); 65 66 addDisposeListener(mDisposeListener); 67 addPaintListener(mPaintListener); 68 addListener(SWT.Resize, mResizeListener); 69 addMouseListener(mMouseListener); 70 71 mTransform = new Transform(Display.getDefault()); 72 mInverse = new Transform(Display.getDefault()); 73 74 treeChanged(); 75 } 76 77 public void setShowExtras(boolean show) { 78 mShowExtras = show; 79 doRedraw(); 80 } 81 82 public void setOnBlack(boolean value) { 83 mOnBlack = value; 84 doRedraw(); 85 } 86 87 public boolean getOnBlack() { 88 return mOnBlack; 89 } 90 91 private DisposeListener mDisposeListener = new DisposeListener() { 92 @Override 93 public void widgetDisposed(DisposeEvent e) { 94 mModel.removeTreeChangeListener(LayoutViewer.this); 95 mTransform.dispose(); 96 mInverse.dispose(); 97 if (mSelectedNode != null) { 98 mSelectedNode.viewNode.dereferenceImage(); 99 } 100 } 101 }; 102 103 private Listener mResizeListener = new Listener() { 104 @Override 105 public void handleEvent(Event e) { 106 synchronized (this) { 107 setTransform(); 108 } 109 } 110 }; 111 112 private MouseListener mMouseListener = new MouseListener() { 113 114 @Override 115 public void mouseDoubleClick(MouseEvent e) { 116 if (mSelectedNode != null) { 117 HierarchyViewerDirector.getDirector() 118 .showCapture(getShell(), mSelectedNode.viewNode); 119 } 120 } 121 122 @Override 123 public void mouseDown(MouseEvent e) { 124 boolean selectionChanged = false; 125 DrawableViewNode newSelection = null; 126 synchronized (LayoutViewer.this) { 127 if (mTree != null) { 128 float[] pt = { 129 e.x, e.y 130 }; 131 mInverse.transform(pt); 132 newSelection = 133 updateSelection(mTree, pt[0], pt[1], 0, 0, 0, 0, mTree.viewNode.width, 134 mTree.viewNode.height); 135 if (mSelectedNode != newSelection) { 136 selectionChanged = true; 137 } 138 } 139 } 140 if (selectionChanged) { 141 mModel.setSelection(newSelection); 142 } 143 } 144 145 @Override 146 public void mouseUp(MouseEvent e) { 147 // pass 148 } 149 }; 150 151 private DrawableViewNode updateSelection(DrawableViewNode node, float x, float y, int left, 152 int top, int clipX, int clipY, int clipWidth, int clipHeight) { 153 if (!node.treeDrawn) { 154 return null; 155 } 156 // Update the clip 157 int x1 = Math.max(left, clipX); 158 int x2 = Math.min(left + node.viewNode.width, clipX + clipWidth); 159 int y1 = Math.max(top, clipY); 160 int y2 = Math.min(top + node.viewNode.height, clipY + clipHeight); 161 clipX = x1; 162 clipY = y1; 163 clipWidth = x2 - x1; 164 clipHeight = y2 - y1; 165 if (x < clipX || x > clipX + clipWidth || y < clipY || y > clipY + clipHeight) { 166 return null; 167 } 168 final int N = node.children.size(); 169 for (int i = N - 1; i >= 0; i--) { 170 DrawableViewNode child = node.children.get(i); 171 DrawableViewNode ret = 172 updateSelection(child, x, y, 173 left + child.viewNode.left - node.viewNode.scrollX, top 174 + child.viewNode.top - node.viewNode.scrollY, clipX, clipY, 175 clipWidth, clipHeight); 176 if (ret != null) { 177 return ret; 178 } 179 } 180 return node; 181 } 182 183 private PaintListener mPaintListener = new PaintListener() { 184 @Override 185 public void paintControl(PaintEvent e) { 186 synchronized (LayoutViewer.this) { 187 if (mOnBlack) { 188 e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); 189 } else { 190 e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); 191 } 192 e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height); 193 if (mTree != null) { 194 e.gc.setLineWidth((int) Math.ceil(0.3 / mScale)); 195 e.gc.setTransform(mTransform); 196 if (mOnBlack) { 197 e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); 198 } else { 199 e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); 200 } 201 Rectangle parentClipping = e.gc.getClipping(); 202 e.gc.setClipping(0, 0, mTree.viewNode.width + (int) Math.ceil(0.3 / mScale), 203 mTree.viewNode.height + (int) Math.ceil(0.3 / mScale)); 204 paintRecursive(e.gc, mTree, 0, 0, true); 205 206 if (mSelectedNode != null) { 207 e.gc.setClipping(parentClipping); 208 209 // w00t, let's be nice and display the whole path in 210 // light red and the selected node in dark red. 211 ArrayList<Point> rightLeftDistances = new ArrayList<Point>(); 212 int left = 0; 213 int top = 0; 214 DrawableViewNode currentNode = mSelectedNode; 215 while (currentNode != mTree) { 216 left += currentNode.viewNode.left; 217 top += currentNode.viewNode.top; 218 currentNode = currentNode.parent; 219 left -= currentNode.viewNode.scrollX; 220 top -= currentNode.viewNode.scrollY; 221 rightLeftDistances.add(new Point(left, top)); 222 } 223 e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_RED)); 224 currentNode = mSelectedNode.parent; 225 final int N = rightLeftDistances.size(); 226 for (int i = 0; i < N; i++) { 227 e.gc.drawRectangle((int) (left - rightLeftDistances.get(i).x), 228 (int) (top - rightLeftDistances.get(i).y), 229 currentNode.viewNode.width, currentNode.viewNode.height); 230 currentNode = currentNode.parent; 231 } 232 233 if (mShowExtras && mSelectedNode.viewNode.image != null) { 234 e.gc.drawImage(mSelectedNode.viewNode.image, left, top); 235 if (mOnBlack) { 236 e.gc.setForeground(Display.getDefault().getSystemColor( 237 SWT.COLOR_WHITE)); 238 } else { 239 e.gc.setForeground(Display.getDefault().getSystemColor( 240 SWT.COLOR_BLACK)); 241 } 242 paintRecursive(e.gc, mSelectedNode, left, top, true); 243 244 } 245 246 e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED)); 247 e.gc.setLineWidth((int) Math.ceil(2 / mScale)); 248 e.gc.drawRectangle(left, top, mSelectedNode.viewNode.width, 249 mSelectedNode.viewNode.height); 250 } 251 } 252 } 253 } 254 }; 255 256 private void paintRecursive(GC gc, DrawableViewNode node, int left, int top, boolean root) { 257 if (!node.treeDrawn) { 258 return; 259 } 260 // Don't shift the root 261 if (!root) { 262 left += node.viewNode.left; 263 top += node.viewNode.top; 264 } 265 Rectangle parentClipping = gc.getClipping(); 266 int x1 = Math.max(parentClipping.x, left); 267 int x2 = 268 Math.min(parentClipping.x + parentClipping.width, left + node.viewNode.width 269 + (int) Math.ceil(0.3 / mScale)); 270 int y1 = Math.max(parentClipping.y, top); 271 int y2 = 272 Math.min(parentClipping.y + parentClipping.height, top + node.viewNode.height 273 + (int) Math.ceil(0.3 / mScale)); 274 275 // Clipping is weird... You set it to -5 and it comes out 17 or 276 // something. 277 if (x2 <= x1 || y2 <= y1) { 278 return; 279 } 280 gc.setClipping(x1, y1, x2 - x1, y2 - y1); 281 final int N = node.children.size(); 282 for (int i = 0; i < N; i++) { 283 paintRecursive(gc, node.children.get(i), left - node.viewNode.scrollX, top 284 - node.viewNode.scrollY, false); 285 } 286 gc.setClipping(parentClipping); 287 if (!node.viewNode.willNotDraw) { 288 gc.drawRectangle(left, top, node.viewNode.width, node.viewNode.height); 289 } 290 291 } 292 293 private void doRedraw() { 294 Display.getDefault().syncExec(new Runnable() { 295 @Override 296 public void run() { 297 redraw(); 298 } 299 }); 300 } 301 302 private void setTransform() { 303 if (mTree != null) { 304 Rectangle bounds = getBounds(); 305 int leftRightPadding = bounds.width <= 30 ? 0 : 5; 306 int topBottomPadding = bounds.height <= 30 ? 0 : 5; 307 mScale = 308 Math.min(1.0 * (bounds.width - leftRightPadding * 2) / mTree.viewNode.width, 1.0 309 * (bounds.height - topBottomPadding * 2) / mTree.viewNode.height); 310 int scaledWidth = (int) Math.ceil(mTree.viewNode.width * mScale); 311 int scaledHeight = (int) Math.ceil(mTree.viewNode.height * mScale); 312 313 mTransform.identity(); 314 mInverse.identity(); 315 mTransform.translate((bounds.width - scaledWidth) / 2.0f, 316 (bounds.height - scaledHeight) / 2.0f); 317 mInverse.translate((bounds.width - scaledWidth) / 2.0f, 318 (bounds.height - scaledHeight) / 2.0f); 319 mTransform.scale((float) mScale, (float) mScale); 320 mInverse.scale((float) mScale, (float) mScale); 321 if (bounds.width != 0 && bounds.height != 0) { 322 mInverse.invert(); 323 } 324 } 325 } 326 327 @Override 328 public void selectionChanged() { 329 synchronized (this) { 330 if (mSelectedNode != null) { 331 mSelectedNode.viewNode.dereferenceImage(); 332 } 333 mSelectedNode = mModel.getSelection(); 334 if (mSelectedNode != null) { 335 mSelectedNode.viewNode.referenceImage(); 336 } 337 } 338 doRedraw(); 339 } 340 341 // Note the syncExec and then synchronized... It avoids deadlock 342 @Override 343 public void treeChanged() { 344 Display.getDefault().syncExec(new Runnable() { 345 @Override 346 public void run() { 347 synchronized (this) { 348 if (mSelectedNode != null) { 349 mSelectedNode.viewNode.dereferenceImage(); 350 } 351 mTree = mModel.getTree(); 352 mSelectedNode = mModel.getSelection(); 353 if (mSelectedNode != null) { 354 mSelectedNode.viewNode.referenceImage(); 355 } 356 setTransform(); 357 } 358 } 359 }); 360 doRedraw(); 361 } 362 363 @Override 364 public void viewportChanged() { 365 // pass 366 } 367 368 @Override 369 public void zoomChanged() { 370 // pass 371 } 372 } 373