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.models.PixelPerfectModel; 20 import com.android.hierarchyviewerlib.models.PixelPerfectModel.IImageChangeListener; 21 22 import org.eclipse.swt.SWT; 23 import org.eclipse.swt.events.DisposeEvent; 24 import org.eclipse.swt.events.DisposeListener; 25 import org.eclipse.swt.events.KeyEvent; 26 import org.eclipse.swt.events.KeyListener; 27 import org.eclipse.swt.events.MouseEvent; 28 import org.eclipse.swt.events.MouseListener; 29 import org.eclipse.swt.events.MouseWheelListener; 30 import org.eclipse.swt.events.PaintEvent; 31 import org.eclipse.swt.events.PaintListener; 32 import org.eclipse.swt.graphics.Color; 33 import org.eclipse.swt.graphics.GC; 34 import org.eclipse.swt.graphics.Image; 35 import org.eclipse.swt.graphics.ImageData; 36 import org.eclipse.swt.graphics.PaletteData; 37 import org.eclipse.swt.graphics.Point; 38 import org.eclipse.swt.graphics.RGB; 39 import org.eclipse.swt.graphics.Rectangle; 40 import org.eclipse.swt.graphics.Transform; 41 import org.eclipse.swt.widgets.Canvas; 42 import org.eclipse.swt.widgets.Composite; 43 import org.eclipse.swt.widgets.Display; 44 45 public class PixelPerfectLoupe extends Canvas implements IImageChangeListener { 46 private PixelPerfectModel mModel; 47 48 private Image mImage; 49 50 private Image mGrid; 51 52 private Color mCrosshairColor; 53 54 private int mWidth; 55 56 private int mHeight; 57 58 private Point mCrosshairLocation; 59 60 private int mZoom; 61 62 private Transform mTransform; 63 64 private int mCanvasWidth; 65 66 private int mCanvasHeight; 67 68 private Image mOverlayImage; 69 70 private double mOverlayTransparency; 71 72 private boolean mShowOverlay = false; 73 74 public PixelPerfectLoupe(Composite parent) { 75 super(parent, SWT.NONE); 76 mModel = PixelPerfectModel.getModel(); 77 mModel.addImageChangeListener(this); 78 79 addPaintListener(mPaintListener); 80 addMouseListener(mMouseListener); 81 addMouseWheelListener(mMouseWheelListener); 82 addDisposeListener(mDisposeListener); 83 addKeyListener(mKeyListener); 84 85 mCrosshairColor = new Color(Display.getDefault(), new RGB(255, 94, 254)); 86 87 mTransform = new Transform(Display.getDefault()); 88 89 imageLoaded(); 90 } 91 92 public void setShowOverlay(boolean value) { 93 synchronized (this) { 94 mShowOverlay = value; 95 } 96 doRedraw(); 97 } 98 99 private DisposeListener mDisposeListener = new DisposeListener() { 100 @Override 101 public void widgetDisposed(DisposeEvent e) { 102 mModel.removeImageChangeListener(PixelPerfectLoupe.this); 103 mCrosshairColor.dispose(); 104 mTransform.dispose(); 105 if (mGrid != null) { 106 mGrid.dispose(); 107 } 108 } 109 }; 110 111 private MouseListener mMouseListener = new MouseListener() { 112 113 @Override 114 public void mouseDoubleClick(MouseEvent e) { 115 // pass 116 } 117 118 @Override 119 public void mouseDown(MouseEvent e) { 120 handleMouseEvent(e); 121 } 122 123 @Override 124 public void mouseUp(MouseEvent e) { 125 // 126 } 127 128 }; 129 130 private MouseWheelListener mMouseWheelListener = new MouseWheelListener() { 131 @Override 132 public void mouseScrolled(MouseEvent e) { 133 int newZoom = -1; 134 synchronized (PixelPerfectLoupe.this) { 135 if (mImage != null && mCrosshairLocation != null) { 136 if (e.count > 0) { 137 newZoom = mZoom + 1; 138 } else { 139 newZoom = mZoom - 1; 140 } 141 } 142 } 143 if (newZoom != -1) { 144 mModel.setZoom(newZoom); 145 } 146 } 147 }; 148 149 private void handleMouseEvent(MouseEvent e) { 150 int newX = -1; 151 int newY = -1; 152 synchronized (PixelPerfectLoupe.this) { 153 if (mImage == null) { 154 return; 155 } 156 int zoomedX = -mCrosshairLocation.x * mZoom - mZoom / 2 + getBounds().width / 2; 157 int zoomedY = -mCrosshairLocation.y * mZoom - mZoom / 2 + getBounds().height / 2; 158 int x = (e.x - zoomedX) / mZoom; 159 int y = (e.y - zoomedY) / mZoom; 160 if (x >= 0 && x < mWidth && y >= 0 && y < mHeight) { 161 newX = x; 162 newY = y; 163 } 164 } 165 if (newX != -1) { 166 mModel.setCrosshairLocation(newX, newY); 167 } 168 } 169 170 private KeyListener mKeyListener = new KeyListener() { 171 172 @Override 173 public void keyPressed(KeyEvent e) { 174 boolean crosshairMoved = false; 175 synchronized (PixelPerfectLoupe.this) { 176 if (mImage != null) { 177 switch (e.keyCode) { 178 case SWT.ARROW_UP: 179 if (mCrosshairLocation.y != 0) { 180 mCrosshairLocation.y--; 181 crosshairMoved = true; 182 } 183 break; 184 case SWT.ARROW_DOWN: 185 if (mCrosshairLocation.y != mHeight - 1) { 186 mCrosshairLocation.y++; 187 crosshairMoved = true; 188 } 189 break; 190 case SWT.ARROW_LEFT: 191 if (mCrosshairLocation.x != 0) { 192 mCrosshairLocation.x--; 193 crosshairMoved = true; 194 } 195 break; 196 case SWT.ARROW_RIGHT: 197 if (mCrosshairLocation.x != mWidth - 1) { 198 mCrosshairLocation.x++; 199 crosshairMoved = true; 200 } 201 break; 202 } 203 } 204 } 205 if (crosshairMoved) { 206 mModel.setCrosshairLocation(mCrosshairLocation.x, mCrosshairLocation.y); 207 } 208 } 209 210 @Override 211 public void keyReleased(KeyEvent e) { 212 // pass 213 } 214 215 }; 216 217 private PaintListener mPaintListener = new PaintListener() { 218 @Override 219 public void paintControl(PaintEvent e) { 220 synchronized (PixelPerfectLoupe.this) { 221 e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); 222 e.gc.fillRectangle(0, 0, getSize().x, getSize().y); 223 if (mImage != null && mCrosshairLocation != null) { 224 int zoomedX = -mCrosshairLocation.x * mZoom - mZoom / 2 + getBounds().width / 2; 225 int zoomedY = -mCrosshairLocation.y * mZoom - mZoom / 2 + getBounds().height / 2; 226 mTransform.translate(zoomedX, zoomedY); 227 mTransform.scale(mZoom, mZoom); 228 e.gc.setInterpolation(SWT.NONE); 229 e.gc.setTransform(mTransform); 230 e.gc.drawImage(mImage, 0, 0); 231 if (mShowOverlay && mOverlayImage != null) { 232 e.gc.setAlpha((int) (mOverlayTransparency * 255)); 233 e.gc.drawImage(mOverlayImage, 0, mHeight - mOverlayImage.getBounds().height); 234 e.gc.setAlpha(255); 235 } 236 237 mTransform.identity(); 238 e.gc.setTransform(mTransform); 239 240 // If the size of the canvas has changed, we need to make 241 // another grid. 242 if (mGrid != null 243 && (mCanvasWidth != getBounds().width || mCanvasHeight != getBounds().height)) { 244 mGrid.dispose(); 245 mGrid = null; 246 } 247 mCanvasWidth = getBounds().width; 248 mCanvasHeight = getBounds().height; 249 if (mGrid == null) { 250 // Make a transparent image; 251 ImageData imageData = 252 new ImageData(mCanvasWidth + mZoom + 1, mCanvasHeight + mZoom + 1, 1, 253 new PaletteData(new RGB[] { 254 new RGB(0, 0, 0) 255 })); 256 imageData.transparentPixel = 0; 257 258 // Draw the grid. 259 mGrid = new Image(Display.getDefault(), imageData); 260 GC gc = new GC(mGrid); 261 gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); 262 for (int x = 0; x <= mCanvasWidth + mZoom; x += mZoom) { 263 gc.drawLine(x, 0, x, mCanvasHeight + mZoom); 264 } 265 for (int y = 0; y <= mCanvasHeight + mZoom; y += mZoom) { 266 gc.drawLine(0, y, mCanvasWidth + mZoom, y); 267 } 268 gc.dispose(); 269 } 270 271 e.gc.setClipping(new Rectangle(zoomedX, zoomedY, mWidth * mZoom + 1, mHeight 272 * mZoom + 1)); 273 e.gc.setAlpha(76); 274 e.gc.drawImage(mGrid, (mCanvasWidth / 2 - mZoom / 2) % mZoom - mZoom, 275 (mCanvasHeight / 2 - mZoom / 2) % mZoom - mZoom); 276 e.gc.setAlpha(255); 277 278 e.gc.setForeground(mCrosshairColor); 279 e.gc.drawLine(0, mCanvasHeight / 2, mCanvasWidth - 1, mCanvasHeight / 2); 280 e.gc.drawLine(mCanvasWidth / 2, 0, mCanvasWidth / 2, mCanvasHeight - 1); 281 } 282 } 283 } 284 }; 285 286 private void doRedraw() { 287 Display.getDefault().syncExec(new Runnable() { 288 @Override 289 public void run() { 290 redraw(); 291 } 292 }); 293 } 294 295 private void loadImage() { 296 mImage = mModel.getImage(); 297 if (mImage != null) { 298 mWidth = mImage.getBounds().width; 299 mHeight = mImage.getBounds().height; 300 } else { 301 mWidth = 0; 302 mHeight = 0; 303 } 304 } 305 306 // Note the syncExec and then synchronized... It avoids deadlock 307 @Override 308 public void imageLoaded() { 309 Display.getDefault().syncExec(new Runnable() { 310 @Override 311 public void run() { 312 synchronized (this) { 313 loadImage(); 314 mCrosshairLocation = mModel.getCrosshairLocation(); 315 mZoom = mModel.getZoom(); 316 mOverlayImage = mModel.getOverlayImage(); 317 mOverlayTransparency = mModel.getOverlayTransparency(); 318 } 319 } 320 }); 321 doRedraw(); 322 } 323 324 @Override 325 public void imageChanged() { 326 Display.getDefault().syncExec(new Runnable() { 327 @Override 328 public void run() { 329 synchronized (this) { 330 loadImage(); 331 } 332 } 333 }); 334 doRedraw(); 335 } 336 337 @Override 338 public void crosshairMoved() { 339 synchronized (this) { 340 mCrosshairLocation = mModel.getCrosshairLocation(); 341 } 342 doRedraw(); 343 } 344 345 @Override 346 public void selectionChanged() { 347 // pass 348 } 349 350 @Override 351 public void treeChanged() { 352 // pass 353 } 354 355 @Override 356 public void zoomChanged() { 357 Display.getDefault().syncExec(new Runnable() { 358 @Override 359 public void run() { 360 synchronized (this) { 361 if (mGrid != null) { 362 // To notify that the zoom level has changed, we get rid 363 // of the 364 // grid. 365 mGrid.dispose(); 366 mGrid = null; 367 } 368 mZoom = mModel.getZoom(); 369 } 370 } 371 }); 372 doRedraw(); 373 } 374 375 @Override 376 public void overlayChanged() { 377 synchronized (this) { 378 mOverlayImage = mModel.getOverlayImage(); 379 mOverlayTransparency = mModel.getOverlayTransparency(); 380 } 381 doRedraw(); 382 } 383 384 @Override 385 public void overlayTransparencyChanged() { 386 synchronized (this) { 387 mOverlayTransparency = mModel.getOverlayTransparency(); 388 } 389 doRedraw(); 390 } 391 } 392