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 public void widgetDisposed(DisposeEvent e) { 93 mModel.removeTreeChangeListener(LayoutViewer.this); 94 mTransform.dispose(); 95 mInverse.dispose(); 96 if (mSelectedNode != null) { 97 mSelectedNode.viewNode.dereferenceImage(); 98 } 99 } 100 }; 101 102 private Listener mResizeListener = new Listener() { 103 public void handleEvent(Event e) { 104 synchronized (this) { 105 setTransform(); 106 } 107 } 108 }; 109 110 private MouseListener mMouseListener = new MouseListener() { 111 112 public void mouseDoubleClick(MouseEvent e) { 113 if (mSelectedNode != null) { 114 HierarchyViewerDirector.getDirector() 115 .showCapture(getShell(), mSelectedNode.viewNode); 116 } 117 } 118 119 public void mouseDown(MouseEvent e) { 120 boolean selectionChanged = false; 121 DrawableViewNode newSelection = null; 122 synchronized (LayoutViewer.this) { 123 if (mTree != null) { 124 float[] pt = { 125 e.x, e.y 126 }; 127 mInverse.transform(pt); 128 newSelection = 129 updateSelection(mTree, pt[0], pt[1], 0, 0, 0, 0, mTree.viewNode.width, 130 mTree.viewNode.height); 131 if (mSelectedNode != newSelection) { 132 selectionChanged = true; 133 } 134 } 135 } 136 if (selectionChanged) { 137 mModel.setSelection(newSelection); 138 } 139 } 140 141 public void mouseUp(MouseEvent e) { 142 // pass 143 } 144 }; 145 updateSelection(DrawableViewNode node, float x, float y, int left, int top, int clipX, int clipY, int clipWidth, int clipHeight)146 private DrawableViewNode updateSelection(DrawableViewNode node, float x, float y, int left, 147 int top, int clipX, int clipY, int clipWidth, int clipHeight) { 148 if (!node.treeDrawn) { 149 return null; 150 } 151 // Update the clip 152 int x1 = Math.max(left, clipX); 153 int x2 = Math.min(left + node.viewNode.width, clipX + clipWidth); 154 int y1 = Math.max(top, clipY); 155 int y2 = Math.min(top + node.viewNode.height, clipY + clipHeight); 156 clipX = x1; 157 clipY = y1; 158 clipWidth = x2 - x1; 159 clipHeight = y2 - y1; 160 if (x < clipX || x > clipX + clipWidth || y < clipY || y > clipY + clipHeight) { 161 return null; 162 } 163 final int N = node.children.size(); 164 for (int i = N - 1; i >= 0; i--) { 165 DrawableViewNode child = node.children.get(i); 166 DrawableViewNode ret = 167 updateSelection(child, x, y, 168 left + child.viewNode.left - node.viewNode.scrollX, top 169 + child.viewNode.top - node.viewNode.scrollY, clipX, clipY, 170 clipWidth, clipHeight); 171 if (ret != null) { 172 return ret; 173 } 174 } 175 return node; 176 } 177 178 private PaintListener mPaintListener = new PaintListener() { 179 public void paintControl(PaintEvent e) { 180 synchronized (LayoutViewer.this) { 181 if (mOnBlack) { 182 e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); 183 } else { 184 e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); 185 } 186 e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height); 187 if (mTree != null) { 188 e.gc.setLineWidth((int) Math.ceil(0.3 / mScale)); 189 e.gc.setTransform(mTransform); 190 if (mOnBlack) { 191 e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); 192 } else { 193 e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); 194 } 195 Rectangle parentClipping = e.gc.getClipping(); 196 e.gc.setClipping(0, 0, mTree.viewNode.width + (int) Math.ceil(0.3 / mScale), 197 mTree.viewNode.height + (int) Math.ceil(0.3 / mScale)); 198 paintRecursive(e.gc, mTree, 0, 0, true); 199 200 if (mSelectedNode != null) { 201 e.gc.setClipping(parentClipping); 202 203 // w00t, let's be nice and display the whole path in 204 // light red and the selected node in dark red. 205 ArrayList<Point> rightLeftDistances = new ArrayList<Point>(); 206 int left = 0; 207 int top = 0; 208 DrawableViewNode currentNode = mSelectedNode; 209 while (currentNode != mTree) { 210 left += currentNode.viewNode.left; 211 top += currentNode.viewNode.top; 212 currentNode = currentNode.parent; 213 left -= currentNode.viewNode.scrollX; 214 top -= currentNode.viewNode.scrollY; 215 rightLeftDistances.add(new Point(left, top)); 216 } 217 e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_RED)); 218 currentNode = mSelectedNode.parent; 219 final int N = rightLeftDistances.size(); 220 for (int i = 0; i < N; i++) { 221 e.gc.drawRectangle((int) (left - rightLeftDistances.get(i).x), 222 (int) (top - rightLeftDistances.get(i).y), 223 currentNode.viewNode.width, currentNode.viewNode.height); 224 currentNode = currentNode.parent; 225 } 226 227 if (mShowExtras && mSelectedNode.viewNode.image != null) { 228 e.gc.drawImage(mSelectedNode.viewNode.image, left, top); 229 if (mOnBlack) { 230 e.gc.setForeground(Display.getDefault().getSystemColor( 231 SWT.COLOR_WHITE)); 232 } else { 233 e.gc.setForeground(Display.getDefault().getSystemColor( 234 SWT.COLOR_BLACK)); 235 } 236 paintRecursive(e.gc, mSelectedNode, left, top, true); 237 238 } 239 240 e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED)); 241 e.gc.setLineWidth((int) Math.ceil(2 / mScale)); 242 e.gc.drawRectangle(left, top, mSelectedNode.viewNode.width, 243 mSelectedNode.viewNode.height); 244 } 245 } 246 } 247 } 248 }; 249 paintRecursive(GC gc, DrawableViewNode node, int left, int top, boolean root)250 private void paintRecursive(GC gc, DrawableViewNode node, int left, int top, boolean root) { 251 if (!node.treeDrawn) { 252 return; 253 } 254 // Don't shift the root 255 if (!root) { 256 left += node.viewNode.left; 257 top += node.viewNode.top; 258 } 259 Rectangle parentClipping = gc.getClipping(); 260 int x1 = Math.max(parentClipping.x, left); 261 int x2 = 262 Math.min(parentClipping.x + parentClipping.width, left + node.viewNode.width 263 + (int) Math.ceil(0.3 / mScale)); 264 int y1 = Math.max(parentClipping.y, top); 265 int y2 = 266 Math.min(parentClipping.y + parentClipping.height, top + node.viewNode.height 267 + (int) Math.ceil(0.3 / mScale)); 268 269 // Clipping is weird... You set it to -5 and it comes out 17 or 270 // something. 271 if (x2 <= x1 || y2 <= y1) { 272 return; 273 } 274 gc.setClipping(x1, y1, x2 - x1, y2 - y1); 275 final int N = node.children.size(); 276 for (int i = 0; i < N; i++) { 277 paintRecursive(gc, node.children.get(i), left - node.viewNode.scrollX, top 278 - node.viewNode.scrollY, false); 279 } 280 gc.setClipping(parentClipping); 281 if (!node.viewNode.willNotDraw) { 282 gc.drawRectangle(left, top, node.viewNode.width, node.viewNode.height); 283 } 284 285 } 286 doRedraw()287 private void doRedraw() { 288 Display.getDefault().syncExec(new Runnable() { 289 public void run() { 290 redraw(); 291 } 292 }); 293 } 294 setTransform()295 private void setTransform() { 296 if (mTree != null) { 297 Rectangle bounds = getBounds(); 298 int leftRightPadding = bounds.width <= 30 ? 0 : 5; 299 int topBottomPadding = bounds.height <= 30 ? 0 : 5; 300 mScale = 301 Math.min(1.0 * (bounds.width - leftRightPadding * 2) / mTree.viewNode.width, 1.0 302 * (bounds.height - topBottomPadding * 2) / mTree.viewNode.height); 303 int scaledWidth = (int) Math.ceil(mTree.viewNode.width * mScale); 304 int scaledHeight = (int) Math.ceil(mTree.viewNode.height * mScale); 305 306 mTransform.identity(); 307 mInverse.identity(); 308 mTransform.translate((bounds.width - scaledWidth) / 2.0f, 309 (bounds.height - scaledHeight) / 2.0f); 310 mInverse.translate((bounds.width - scaledWidth) / 2.0f, 311 (bounds.height - scaledHeight) / 2.0f); 312 mTransform.scale((float) mScale, (float) mScale); 313 mInverse.scale((float) mScale, (float) mScale); 314 if (bounds.width != 0 && bounds.height != 0) { 315 mInverse.invert(); 316 } 317 } 318 } 319 selectionChanged()320 public void selectionChanged() { 321 synchronized (this) { 322 if (mSelectedNode != null) { 323 mSelectedNode.viewNode.dereferenceImage(); 324 } 325 mSelectedNode = mModel.getSelection(); 326 if (mSelectedNode != null) { 327 mSelectedNode.viewNode.referenceImage(); 328 } 329 } 330 doRedraw(); 331 } 332 333 // Note the syncExec and then synchronized... It avoids deadlock treeChanged()334 public void treeChanged() { 335 Display.getDefault().syncExec(new Runnable() { 336 public void run() { 337 synchronized (this) { 338 if (mSelectedNode != null) { 339 mSelectedNode.viewNode.dereferenceImage(); 340 } 341 mTree = mModel.getTree(); 342 mSelectedNode = mModel.getSelection(); 343 if (mSelectedNode != null) { 344 mSelectedNode.viewNode.referenceImage(); 345 } 346 setTransform(); 347 } 348 } 349 }); 350 doRedraw(); 351 } 352 viewportChanged()353 public void viewportChanged() { 354 // pass 355 } 356 zoomChanged()357 public void zoomChanged() { 358 // pass 359 } 360 } 361