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.device.ViewNode; 20 import com.android.hierarchyviewerlib.models.PixelPerfectModel; 21 import com.android.hierarchyviewerlib.models.PixelPerfectModel.IImageChangeListener; 22 23 import org.eclipse.swt.SWT; 24 import org.eclipse.swt.custom.ScrolledComposite; 25 import org.eclipse.swt.events.DisposeEvent; 26 import org.eclipse.swt.events.DisposeListener; 27 import org.eclipse.swt.events.KeyEvent; 28 import org.eclipse.swt.events.KeyListener; 29 import org.eclipse.swt.events.MouseEvent; 30 import org.eclipse.swt.events.MouseListener; 31 import org.eclipse.swt.events.MouseMoveListener; 32 import org.eclipse.swt.events.PaintEvent; 33 import org.eclipse.swt.events.PaintListener; 34 import org.eclipse.swt.graphics.Color; 35 import org.eclipse.swt.graphics.Image; 36 import org.eclipse.swt.graphics.Point; 37 import org.eclipse.swt.graphics.RGB; 38 import org.eclipse.swt.widgets.Canvas; 39 import org.eclipse.swt.widgets.Composite; 40 import org.eclipse.swt.widgets.Display; 41 42 public class PixelPerfect extends ScrolledComposite implements IImageChangeListener { 43 private Canvas mCanvas; 44 45 private PixelPerfectModel mModel; 46 47 private Image mImage; 48 49 private Color mCrosshairColor; 50 51 private Color mMarginColor; 52 53 private Color mBorderColor; 54 55 private Color mPaddingColor; 56 57 private int mWidth; 58 59 private int mHeight; 60 61 private Point mCrosshairLocation; 62 63 private ViewNode mSelectedNode; 64 65 private Image mOverlayImage; 66 67 private double mOverlayTransparency; 68 69 public PixelPerfect(Composite parent) { 70 super(parent, SWT.H_SCROLL | SWT.V_SCROLL); 71 mCanvas = new Canvas(this, SWT.NONE); 72 setContent(mCanvas); 73 setExpandHorizontal(true); 74 setExpandVertical(true); 75 mModel = PixelPerfectModel.getModel(); 76 mModel.addImageChangeListener(this); 77 78 mCanvas.addPaintListener(mPaintListener); 79 mCanvas.addMouseListener(mMouseListener); 80 mCanvas.addMouseMoveListener(mMouseMoveListener); 81 mCanvas.addKeyListener(mKeyListener); 82 83 addDisposeListener(mDisposeListener); 84 85 mCrosshairColor = new Color(Display.getDefault(), new RGB(0, 255, 255)); 86 mBorderColor = new Color(Display.getDefault(), new RGB(255, 0, 0)); 87 mMarginColor = new Color(Display.getDefault(), new RGB(0, 255, 0)); 88 mPaddingColor = new Color(Display.getDefault(), new RGB(0, 0, 255)); 89 90 imageLoaded(); 91 } 92 93 private DisposeListener mDisposeListener = new DisposeListener() { 94 @Override 95 public void widgetDisposed(DisposeEvent e) { 96 mModel.removeImageChangeListener(PixelPerfect.this); 97 mCrosshairColor.dispose(); 98 mBorderColor.dispose(); 99 mPaddingColor.dispose(); 100 } 101 }; 102 103 @Override 104 public boolean setFocus() { 105 return mCanvas.setFocus(); 106 } 107 108 private MouseListener mMouseListener = new MouseListener() { 109 110 @Override 111 public void mouseDoubleClick(MouseEvent e) { 112 // pass 113 } 114 115 @Override 116 public void mouseDown(MouseEvent e) { 117 handleMouseEvent(e); 118 } 119 120 @Override 121 public void mouseUp(MouseEvent e) { 122 handleMouseEvent(e); 123 } 124 125 }; 126 127 private MouseMoveListener mMouseMoveListener = new MouseMoveListener() { 128 @Override 129 public void mouseMove(MouseEvent e) { 130 if (e.stateMask != 0) { 131 handleMouseEvent(e); 132 } 133 } 134 }; 135 136 private void handleMouseEvent(MouseEvent e) { 137 synchronized (PixelPerfect.this) { 138 if (mImage == null) { 139 return; 140 } 141 int leftOffset = mCanvas.getSize().x / 2 - mWidth / 2; 142 int topOffset = mCanvas.getSize().y / 2 - mHeight / 2; 143 e.x -= leftOffset; 144 e.y -= topOffset; 145 e.x = Math.max(e.x, 0); 146 e.x = Math.min(e.x, mWidth - 1); 147 e.y = Math.max(e.y, 0); 148 e.y = Math.min(e.y, mHeight - 1); 149 } 150 mModel.setCrosshairLocation(e.x, e.y); 151 } 152 153 private KeyListener mKeyListener = new KeyListener() { 154 155 @Override 156 public void keyPressed(KeyEvent e) { 157 boolean crosshairMoved = false; 158 synchronized (PixelPerfect.this) { 159 if (mImage != null) { 160 switch (e.keyCode) { 161 case SWT.ARROW_UP: 162 if (mCrosshairLocation.y != 0) { 163 mCrosshairLocation.y--; 164 crosshairMoved = true; 165 } 166 break; 167 case SWT.ARROW_DOWN: 168 if (mCrosshairLocation.y != mHeight - 1) { 169 mCrosshairLocation.y++; 170 crosshairMoved = true; 171 } 172 break; 173 case SWT.ARROW_LEFT: 174 if (mCrosshairLocation.x != 0) { 175 mCrosshairLocation.x--; 176 crosshairMoved = true; 177 } 178 break; 179 case SWT.ARROW_RIGHT: 180 if (mCrosshairLocation.x != mWidth - 1) { 181 mCrosshairLocation.x++; 182 crosshairMoved = true; 183 } 184 break; 185 } 186 } 187 } 188 if (crosshairMoved) { 189 mModel.setCrosshairLocation(mCrosshairLocation.x, mCrosshairLocation.y); 190 } 191 } 192 193 @Override 194 public void keyReleased(KeyEvent e) { 195 // pass 196 } 197 198 }; 199 200 private PaintListener mPaintListener = new PaintListener() { 201 @Override 202 public void paintControl(PaintEvent e) { 203 synchronized (PixelPerfect.this) { 204 e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); 205 e.gc.fillRectangle(0, 0, mCanvas.getSize().x, mCanvas.getSize().y); 206 if (mImage != null) { 207 // Let's be cool and put it in the center... 208 int leftOffset = mCanvas.getSize().x / 2 - mWidth / 2; 209 int topOffset = mCanvas.getSize().y / 2 - mHeight / 2; 210 e.gc.drawImage(mImage, leftOffset, topOffset); 211 if (mOverlayImage != null) { 212 e.gc.setAlpha((int) (mOverlayTransparency * 255)); 213 int overlayTopOffset = 214 mCanvas.getSize().y / 2 + mHeight / 2 215 - mOverlayImage.getBounds().height; 216 e.gc.drawImage(mOverlayImage, leftOffset, overlayTopOffset); 217 e.gc.setAlpha(255); 218 } 219 220 if (mSelectedNode != null) { 221 // If the screen is in landscape mode, the 222 // coordinates are backwards. 223 int leftShift = 0; 224 int topShift = 0; 225 int nodeLeft = mSelectedNode.left; 226 int nodeTop = mSelectedNode.top; 227 int nodeWidth = mSelectedNode.width; 228 int nodeHeight = mSelectedNode.height; 229 int nodeMarginLeft = mSelectedNode.marginLeft; 230 int nodeMarginTop = mSelectedNode.marginTop; 231 int nodeMarginRight = mSelectedNode.marginRight; 232 int nodeMarginBottom = mSelectedNode.marginBottom; 233 int nodePadLeft = mSelectedNode.paddingLeft; 234 int nodePadTop = mSelectedNode.paddingTop; 235 int nodePadRight = mSelectedNode.paddingRight; 236 int nodePadBottom = mSelectedNode.paddingBottom; 237 ViewNode cur = mSelectedNode; 238 while (cur.parent != null) { 239 leftShift += cur.parent.left - cur.parent.scrollX; 240 topShift += cur.parent.top - cur.parent.scrollY; 241 cur = cur.parent; 242 } 243 244 // Everything is sideways. 245 if (cur.width > cur.height) { 246 e.gc.setForeground(mPaddingColor); 247 e.gc.drawRectangle(leftOffset + mWidth - nodeTop - topShift - nodeHeight 248 + nodePadBottom, 249 topOffset + leftShift + nodeLeft + nodePadLeft, nodeHeight 250 - nodePadBottom - nodePadTop, nodeWidth - nodePadRight 251 - nodePadLeft); 252 e.gc.setForeground(mMarginColor); 253 e.gc.drawRectangle(leftOffset + mWidth - nodeTop - topShift - nodeHeight 254 - nodeMarginBottom, topOffset + leftShift + nodeLeft 255 - nodeMarginLeft, 256 nodeHeight + nodeMarginBottom + nodeMarginTop, nodeWidth 257 + nodeMarginRight + nodeMarginLeft); 258 e.gc.setForeground(mBorderColor); 259 e.gc.drawRectangle( 260 leftOffset + mWidth - nodeTop - topShift - nodeHeight, topOffset 261 + leftShift + nodeLeft, nodeHeight, nodeWidth); 262 } else { 263 e.gc.setForeground(mPaddingColor); 264 e.gc.drawRectangle(leftOffset + leftShift + nodeLeft + nodePadLeft, 265 topOffset + topShift + nodeTop + nodePadTop, nodeWidth 266 - nodePadRight - nodePadLeft, nodeHeight 267 - nodePadBottom - nodePadTop); 268 e.gc.setForeground(mMarginColor); 269 e.gc.drawRectangle(leftOffset + leftShift + nodeLeft - nodeMarginLeft, 270 topOffset + topShift + nodeTop - nodeMarginTop, nodeWidth 271 + nodeMarginRight + nodeMarginLeft, nodeHeight 272 + nodeMarginBottom + nodeMarginTop); 273 e.gc.setForeground(mBorderColor); 274 e.gc.drawRectangle(leftOffset + leftShift + nodeLeft, topOffset 275 + topShift + nodeTop, nodeWidth, nodeHeight); 276 } 277 } 278 if (mCrosshairLocation != null) { 279 e.gc.setForeground(mCrosshairColor); 280 e.gc.drawLine(leftOffset, topOffset + mCrosshairLocation.y, leftOffset 281 + mWidth - 1, topOffset + mCrosshairLocation.y); 282 e.gc.drawLine(leftOffset + mCrosshairLocation.x, topOffset, leftOffset 283 + mCrosshairLocation.x, topOffset + mHeight - 1); 284 } 285 } 286 } 287 } 288 }; 289 290 private void doRedraw() { 291 Display.getDefault().syncExec(new Runnable() { 292 @Override 293 public void run() { 294 mCanvas.redraw(); 295 } 296 }); 297 } 298 299 private void loadImage() { 300 mImage = mModel.getImage(); 301 if (mImage != null) { 302 mWidth = mImage.getBounds().width; 303 mHeight = mImage.getBounds().height; 304 } else { 305 mWidth = 0; 306 mHeight = 0; 307 } 308 setMinSize(mWidth, mHeight); 309 } 310 311 @Override 312 public void imageLoaded() { 313 Display.getDefault().syncExec(new Runnable() { 314 @Override 315 public void run() { 316 synchronized (this) { 317 loadImage(); 318 mCrosshairLocation = mModel.getCrosshairLocation(); 319 mSelectedNode = mModel.getSelected(); 320 mOverlayImage = mModel.getOverlayImage(); 321 mOverlayTransparency = mModel.getOverlayTransparency(); 322 } 323 } 324 }); 325 doRedraw(); 326 } 327 328 @Override 329 public void imageChanged() { 330 Display.getDefault().syncExec(new Runnable() { 331 @Override 332 public void run() { 333 synchronized (this) { 334 loadImage(); 335 } 336 } 337 }); 338 doRedraw(); 339 } 340 341 @Override 342 public void crosshairMoved() { 343 synchronized (this) { 344 mCrosshairLocation = mModel.getCrosshairLocation(); 345 } 346 doRedraw(); 347 } 348 349 @Override 350 public void selectionChanged() { 351 synchronized (this) { 352 mSelectedNode = mModel.getSelected(); 353 } 354 doRedraw(); 355 } 356 357 // Note the syncExec and then synchronized... It avoids deadlock 358 @Override 359 public void treeChanged() { 360 Display.getDefault().syncExec(new Runnable() { 361 @Override 362 public void run() { 363 synchronized (this) { 364 mSelectedNode = mModel.getSelected(); 365 } 366 } 367 }); 368 doRedraw(); 369 } 370 371 @Override 372 public void zoomChanged() { 373 // pass 374 } 375 376 @Override 377 public void overlayChanged() { 378 synchronized (this) { 379 mOverlayImage = mModel.getOverlayImage(); 380 mOverlayTransparency = mModel.getOverlayTransparency(); 381 } 382 doRedraw(); 383 } 384 385 @Override 386 public void overlayTransparencyChanged() { 387 synchronized (this) { 388 mOverlayTransparency = mModel.getOverlayTransparency(); 389 } 390 doRedraw(); 391 } 392 } 393