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 LayoutViewer(Composite parent)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 setShowExtras(boolean show)77 public void setShowExtras(boolean show) { 78 mShowExtras = show; 79 doRedraw(); 80 } 81 setOnBlack(boolean value)82 public void setOnBlack(boolean value) { 83 mOnBlack = value; 84 doRedraw(); 85 } 86 getOnBlack()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 updateSelection(DrawableViewNode node, float x, float y, int left, int top, int clipX, int clipY, int clipWidth, int clipHeight)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 paintRecursive(GC gc, DrawableViewNode node, int left, int top, boolean root)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 doRedraw()293 private void doRedraw() { 294 Display.getDefault().syncExec(new Runnable() { 295 @Override 296 public void run() { 297 redraw(); 298 } 299 }); 300 } 301 setTransform()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 selectionChanged()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 treeChanged()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 viewportChanged()364 public void viewportChanged() { 365 // pass 366 } 367 368 @Override zoomChanged()369 public void zoomChanged() { 370 // pass 371 } 372 } 373