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 public void widgetDisposed(DisposeEvent e) { 95 mModel.removeImageChangeListener(PixelPerfect.this); 96 mCrosshairColor.dispose(); 97 mBorderColor.dispose(); 98 mPaddingColor.dispose(); 99 } 100 }; 101 102 @Override 103 public boolean setFocus() { 104 return mCanvas.setFocus(); 105 } 106 107 private MouseListener mMouseListener = new MouseListener() { 108 109 public void mouseDoubleClick(MouseEvent e) { 110 // pass 111 } 112 113 public void mouseDown(MouseEvent e) { 114 handleMouseEvent(e); 115 } 116 117 public void mouseUp(MouseEvent e) { 118 handleMouseEvent(e); 119 } 120 121 }; 122 123 private MouseMoveListener mMouseMoveListener = new MouseMoveListener() { 124 public void mouseMove(MouseEvent e) { 125 if (e.stateMask != 0) { 126 handleMouseEvent(e); 127 } 128 } 129 }; 130 131 private void handleMouseEvent(MouseEvent e) { 132 synchronized (PixelPerfect.this) { 133 if (mImage == null) { 134 return; 135 } 136 int leftOffset = mCanvas.getSize().x / 2 - mWidth / 2; 137 int topOffset = mCanvas.getSize().y / 2 - mHeight / 2; 138 e.x -= leftOffset; 139 e.y -= topOffset; 140 e.x = Math.max(e.x, 0); 141 e.x = Math.min(e.x, mWidth - 1); 142 e.y = Math.max(e.y, 0); 143 e.y = Math.min(e.y, mHeight - 1); 144 } 145 mModel.setCrosshairLocation(e.x, e.y); 146 } 147 148 private KeyListener mKeyListener = new KeyListener() { 149 150 public void keyPressed(KeyEvent e) { 151 boolean crosshairMoved = false; 152 synchronized (PixelPerfect.this) { 153 if (mImage != null) { 154 switch (e.keyCode) { 155 case SWT.ARROW_UP: 156 if (mCrosshairLocation.y != 0) { 157 mCrosshairLocation.y--; 158 crosshairMoved = true; 159 } 160 break; 161 case SWT.ARROW_DOWN: 162 if (mCrosshairLocation.y != mHeight - 1) { 163 mCrosshairLocation.y++; 164 crosshairMoved = true; 165 } 166 break; 167 case SWT.ARROW_LEFT: 168 if (mCrosshairLocation.x != 0) { 169 mCrosshairLocation.x--; 170 crosshairMoved = true; 171 } 172 break; 173 case SWT.ARROW_RIGHT: 174 if (mCrosshairLocation.x != mWidth - 1) { 175 mCrosshairLocation.x++; 176 crosshairMoved = true; 177 } 178 break; 179 } 180 } 181 } 182 if (crosshairMoved) { 183 mModel.setCrosshairLocation(mCrosshairLocation.x, mCrosshairLocation.y); 184 } 185 } 186 187 public void keyReleased(KeyEvent e) { 188 // pass 189 } 190 191 }; 192 193 private PaintListener mPaintListener = new PaintListener() { 194 public void paintControl(PaintEvent e) { 195 synchronized (PixelPerfect.this) { 196 e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); 197 e.gc.fillRectangle(0, 0, mCanvas.getSize().x, mCanvas.getSize().y); 198 if (mImage != null) { 199 // Let's be cool and put it in the center... 200 int leftOffset = mCanvas.getSize().x / 2 - mWidth / 2; 201 int topOffset = mCanvas.getSize().y / 2 - mHeight / 2; 202 e.gc.drawImage(mImage, leftOffset, topOffset); 203 if (mOverlayImage != null) { 204 e.gc.setAlpha((int) (mOverlayTransparency * 255)); 205 int overlayTopOffset = 206 mCanvas.getSize().y / 2 + mHeight / 2 207 - mOverlayImage.getBounds().height; 208 e.gc.drawImage(mOverlayImage, leftOffset, overlayTopOffset); 209 e.gc.setAlpha(255); 210 } 211 212 if (mSelectedNode != null) { 213 // If the screen is in landscape mode, the 214 // coordinates are backwards. 215 int leftShift = 0; 216 int topShift = 0; 217 int nodeLeft = mSelectedNode.left; 218 int nodeTop = mSelectedNode.top; 219 int nodeWidth = mSelectedNode.width; 220 int nodeHeight = mSelectedNode.height; 221 int nodeMarginLeft = mSelectedNode.marginLeft; 222 int nodeMarginTop = mSelectedNode.marginTop; 223 int nodeMarginRight = mSelectedNode.marginRight; 224 int nodeMarginBottom = mSelectedNode.marginBottom; 225 int nodePadLeft = mSelectedNode.paddingLeft; 226 int nodePadTop = mSelectedNode.paddingTop; 227 int nodePadRight = mSelectedNode.paddingRight; 228 int nodePadBottom = mSelectedNode.paddingBottom; 229 ViewNode cur = mSelectedNode; 230 while (cur.parent != null) { 231 leftShift += cur.parent.left - cur.parent.scrollX; 232 topShift += cur.parent.top - cur.parent.scrollY; 233 cur = cur.parent; 234 } 235 236 // Everything is sideways. 237 if (cur.width > cur.height) { 238 e.gc.setForeground(mPaddingColor); 239 e.gc.drawRectangle(leftOffset + mWidth - nodeTop - topShift - nodeHeight 240 + nodePadBottom, 241 topOffset + leftShift + nodeLeft + nodePadLeft, nodeHeight 242 - nodePadBottom - nodePadTop, nodeWidth - nodePadRight 243 - nodePadLeft); 244 e.gc.setForeground(mMarginColor); 245 e.gc.drawRectangle(leftOffset + mWidth - nodeTop - topShift - nodeHeight 246 - nodeMarginBottom, topOffset + leftShift + nodeLeft 247 - nodeMarginLeft, 248 nodeHeight + nodeMarginBottom + nodeMarginTop, nodeWidth 249 + nodeMarginRight + nodeMarginLeft); 250 e.gc.setForeground(mBorderColor); 251 e.gc.drawRectangle( 252 leftOffset + mWidth - nodeTop - topShift - nodeHeight, topOffset 253 + leftShift + nodeLeft, nodeHeight, nodeWidth); 254 } else { 255 e.gc.setForeground(mPaddingColor); 256 e.gc.drawRectangle(leftOffset + leftShift + nodeLeft + nodePadLeft, 257 topOffset + topShift + nodeTop + nodePadTop, nodeWidth 258 - nodePadRight - nodePadLeft, nodeHeight 259 - nodePadBottom - nodePadTop); 260 e.gc.setForeground(mMarginColor); 261 e.gc.drawRectangle(leftOffset + leftShift + nodeLeft - nodeMarginLeft, 262 topOffset + topShift + nodeTop - nodeMarginTop, nodeWidth 263 + nodeMarginRight + nodeMarginLeft, nodeHeight 264 + nodeMarginBottom + nodeMarginTop); 265 e.gc.setForeground(mBorderColor); 266 e.gc.drawRectangle(leftOffset + leftShift + nodeLeft, topOffset 267 + topShift + nodeTop, nodeWidth, nodeHeight); 268 } 269 } 270 if (mCrosshairLocation != null) { 271 e.gc.setForeground(mCrosshairColor); 272 e.gc.drawLine(leftOffset, topOffset + mCrosshairLocation.y, leftOffset 273 + mWidth - 1, topOffset + mCrosshairLocation.y); 274 e.gc.drawLine(leftOffset + mCrosshairLocation.x, topOffset, leftOffset 275 + mCrosshairLocation.x, topOffset + mHeight - 1); 276 } 277 } 278 } 279 } 280 }; 281 282 private void doRedraw() { 283 Display.getDefault().syncExec(new Runnable() { 284 public void run() { 285 mCanvas.redraw(); 286 } 287 }); 288 } 289 290 private void loadImage() { 291 mImage = mModel.getImage(); 292 if (mImage != null) { 293 mWidth = mImage.getBounds().width; 294 mHeight = mImage.getBounds().height; 295 } else { 296 mWidth = 0; 297 mHeight = 0; 298 } 299 setMinSize(mWidth, mHeight); 300 } 301 302 public void imageLoaded() { 303 Display.getDefault().syncExec(new Runnable() { 304 public void run() { 305 synchronized (this) { 306 loadImage(); 307 mCrosshairLocation = mModel.getCrosshairLocation(); 308 mSelectedNode = mModel.getSelected(); 309 mOverlayImage = mModel.getOverlayImage(); 310 mOverlayTransparency = mModel.getOverlayTransparency(); 311 } 312 } 313 }); 314 doRedraw(); 315 } 316 317 public void imageChanged() { 318 Display.getDefault().syncExec(new Runnable() { 319 public void run() { 320 synchronized (this) { 321 loadImage(); 322 } 323 } 324 }); 325 doRedraw(); 326 } 327 328 public void crosshairMoved() { 329 synchronized (this) { 330 mCrosshairLocation = mModel.getCrosshairLocation(); 331 } 332 doRedraw(); 333 } 334 335 public void selectionChanged() { 336 synchronized (this) { 337 mSelectedNode = mModel.getSelected(); 338 } 339 doRedraw(); 340 } 341 342 // Note the syncExec and then synchronized... It avoids deadlock 343 public void treeChanged() { 344 Display.getDefault().syncExec(new Runnable() { 345 public void run() { 346 synchronized (this) { 347 mSelectedNode = mModel.getSelected(); 348 } 349 } 350 }); 351 doRedraw(); 352 } 353 354 public void zoomChanged() { 355 // pass 356 } 357 358 public void overlayChanged() { 359 synchronized (this) { 360 mOverlayImage = mModel.getOverlayImage(); 361 mOverlayTransparency = mModel.getOverlayTransparency(); 362 } 363 doRedraw(); 364 } 365 366 public void overlayTransparencyChanged() { 367 synchronized (this) { 368 mOverlayTransparency = mModel.getOverlayTransparency(); 369 } 370 doRedraw(); 371 } 372 } 373