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 PixelPerfectLoupe(Composite parent)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 setShowOverlay(boolean value)92 public void setShowOverlay(boolean value) { 93 synchronized (this) { 94 mShowOverlay = value; 95 } 96 doRedraw(); 97 } 98 99 private DisposeListener mDisposeListener = new DisposeListener() { 100 public void widgetDisposed(DisposeEvent e) { 101 mModel.removeImageChangeListener(PixelPerfectLoupe.this); 102 mCrosshairColor.dispose(); 103 mTransform.dispose(); 104 if (mGrid != null) { 105 mGrid.dispose(); 106 } 107 } 108 }; 109 110 private MouseListener mMouseListener = new MouseListener() { 111 112 public void mouseDoubleClick(MouseEvent e) { 113 // pass 114 } 115 116 public void mouseDown(MouseEvent e) { 117 handleMouseEvent(e); 118 } 119 120 public void mouseUp(MouseEvent e) { 121 // 122 } 123 124 }; 125 126 private MouseWheelListener mMouseWheelListener = new MouseWheelListener() { 127 public void mouseScrolled(MouseEvent e) { 128 int newZoom = -1; 129 synchronized (PixelPerfectLoupe.this) { 130 if (mImage != null && mCrosshairLocation != null) { 131 if (e.count > 0) { 132 newZoom = mZoom + 1; 133 } else { 134 newZoom = mZoom - 1; 135 } 136 } 137 } 138 if (newZoom != -1) { 139 mModel.setZoom(newZoom); 140 } 141 } 142 }; 143 handleMouseEvent(MouseEvent e)144 private void handleMouseEvent(MouseEvent e) { 145 int newX = -1; 146 int newY = -1; 147 synchronized (PixelPerfectLoupe.this) { 148 if (mImage == null) { 149 return; 150 } 151 int zoomedX = -mCrosshairLocation.x * mZoom - mZoom / 2 + getBounds().width / 2; 152 int zoomedY = -mCrosshairLocation.y * mZoom - mZoom / 2 + getBounds().height / 2; 153 int x = (e.x - zoomedX) / mZoom; 154 int y = (e.y - zoomedY) / mZoom; 155 if (x >= 0 && x < mWidth && y >= 0 && y < mHeight) { 156 newX = x; 157 newY = y; 158 } 159 } 160 if (newX != -1) { 161 mModel.setCrosshairLocation(newX, newY); 162 } 163 } 164 165 private KeyListener mKeyListener = new KeyListener() { 166 167 public void keyPressed(KeyEvent e) { 168 boolean crosshairMoved = false; 169 synchronized (PixelPerfectLoupe.this) { 170 if (mImage != null) { 171 switch (e.keyCode) { 172 case SWT.ARROW_UP: 173 if (mCrosshairLocation.y != 0) { 174 mCrosshairLocation.y--; 175 crosshairMoved = true; 176 } 177 break; 178 case SWT.ARROW_DOWN: 179 if (mCrosshairLocation.y != mHeight - 1) { 180 mCrosshairLocation.y++; 181 crosshairMoved = true; 182 } 183 break; 184 case SWT.ARROW_LEFT: 185 if (mCrosshairLocation.x != 0) { 186 mCrosshairLocation.x--; 187 crosshairMoved = true; 188 } 189 break; 190 case SWT.ARROW_RIGHT: 191 if (mCrosshairLocation.x != mWidth - 1) { 192 mCrosshairLocation.x++; 193 crosshairMoved = true; 194 } 195 break; 196 } 197 } 198 } 199 if (crosshairMoved) { 200 mModel.setCrosshairLocation(mCrosshairLocation.x, mCrosshairLocation.y); 201 } 202 } 203 204 public void keyReleased(KeyEvent e) { 205 // pass 206 } 207 208 }; 209 210 private PaintListener mPaintListener = new PaintListener() { 211 public void paintControl(PaintEvent e) { 212 synchronized (PixelPerfectLoupe.this) { 213 e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); 214 e.gc.fillRectangle(0, 0, getSize().x, getSize().y); 215 if (mImage != null && mCrosshairLocation != null) { 216 int zoomedX = -mCrosshairLocation.x * mZoom - mZoom / 2 + getBounds().width / 2; 217 int zoomedY = -mCrosshairLocation.y * mZoom - mZoom / 2 + getBounds().height / 2; 218 mTransform.translate(zoomedX, zoomedY); 219 mTransform.scale(mZoom, mZoom); 220 e.gc.setInterpolation(SWT.NONE); 221 e.gc.setTransform(mTransform); 222 e.gc.drawImage(mImage, 0, 0); 223 if (mShowOverlay && mOverlayImage != null) { 224 e.gc.setAlpha((int) (mOverlayTransparency * 255)); 225 e.gc.drawImage(mOverlayImage, 0, mHeight - mOverlayImage.getBounds().height); 226 e.gc.setAlpha(255); 227 } 228 229 mTransform.identity(); 230 e.gc.setTransform(mTransform); 231 232 // If the size of the canvas has changed, we need to make 233 // another grid. 234 if (mGrid != null 235 && (mCanvasWidth != getBounds().width || mCanvasHeight != getBounds().height)) { 236 mGrid.dispose(); 237 mGrid = null; 238 } 239 mCanvasWidth = getBounds().width; 240 mCanvasHeight = getBounds().height; 241 if (mGrid == null) { 242 // Make a transparent image; 243 ImageData imageData = 244 new ImageData(mCanvasWidth + mZoom + 1, mCanvasHeight + mZoom + 1, 1, 245 new PaletteData(new RGB[] { 246 new RGB(0, 0, 0) 247 })); 248 imageData.transparentPixel = 0; 249 250 // Draw the grid. 251 mGrid = new Image(Display.getDefault(), imageData); 252 GC gc = new GC(mGrid); 253 gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); 254 for (int x = 0; x <= mCanvasWidth + mZoom; x += mZoom) { 255 gc.drawLine(x, 0, x, mCanvasHeight + mZoom); 256 } 257 for (int y = 0; y <= mCanvasHeight + mZoom; y += mZoom) { 258 gc.drawLine(0, y, mCanvasWidth + mZoom, y); 259 } 260 gc.dispose(); 261 } 262 263 e.gc.setClipping(new Rectangle(zoomedX, zoomedY, mWidth * mZoom + 1, mHeight 264 * mZoom + 1)); 265 e.gc.setAlpha(76); 266 e.gc.drawImage(mGrid, (mCanvasWidth / 2 - mZoom / 2) % mZoom - mZoom, 267 (mCanvasHeight / 2 - mZoom / 2) % mZoom - mZoom); 268 e.gc.setAlpha(255); 269 270 e.gc.setForeground(mCrosshairColor); 271 e.gc.drawLine(0, mCanvasHeight / 2, mCanvasWidth - 1, mCanvasHeight / 2); 272 e.gc.drawLine(mCanvasWidth / 2, 0, mCanvasWidth / 2, mCanvasHeight - 1); 273 } 274 } 275 } 276 }; 277 doRedraw()278 private void doRedraw() { 279 Display.getDefault().syncExec(new Runnable() { 280 public void run() { 281 redraw(); 282 } 283 }); 284 } 285 loadImage()286 private void loadImage() { 287 mImage = mModel.getImage(); 288 if (mImage != null) { 289 mWidth = mImage.getBounds().width; 290 mHeight = mImage.getBounds().height; 291 } else { 292 mWidth = 0; 293 mHeight = 0; 294 } 295 } 296 297 // Note the syncExec and then synchronized... It avoids deadlock imageLoaded()298 public void imageLoaded() { 299 Display.getDefault().syncExec(new Runnable() { 300 public void run() { 301 synchronized (this) { 302 loadImage(); 303 mCrosshairLocation = mModel.getCrosshairLocation(); 304 mZoom = mModel.getZoom(); 305 mOverlayImage = mModel.getOverlayImage(); 306 mOverlayTransparency = mModel.getOverlayTransparency(); 307 } 308 } 309 }); 310 doRedraw(); 311 } 312 imageChanged()313 public void imageChanged() { 314 Display.getDefault().syncExec(new Runnable() { 315 public void run() { 316 synchronized (this) { 317 loadImage(); 318 } 319 } 320 }); 321 doRedraw(); 322 } 323 crosshairMoved()324 public void crosshairMoved() { 325 synchronized (this) { 326 mCrosshairLocation = mModel.getCrosshairLocation(); 327 } 328 doRedraw(); 329 } 330 selectionChanged()331 public void selectionChanged() { 332 // pass 333 } 334 treeChanged()335 public void treeChanged() { 336 // pass 337 } 338 zoomChanged()339 public void zoomChanged() { 340 Display.getDefault().syncExec(new Runnable() { 341 public void run() { 342 synchronized (this) { 343 if (mGrid != null) { 344 // To notify that the zoom level has changed, we get rid 345 // of the 346 // grid. 347 mGrid.dispose(); 348 mGrid = null; 349 } 350 mZoom = mModel.getZoom(); 351 } 352 } 353 }); 354 doRedraw(); 355 } 356 overlayChanged()357 public void overlayChanged() { 358 synchronized (this) { 359 mOverlayImage = mModel.getOverlayImage(); 360 mOverlayTransparency = mModel.getOverlayTransparency(); 361 } 362 doRedraw(); 363 } 364 overlayTransparencyChanged()365 public void overlayTransparencyChanged() { 366 synchronized (this) { 367 mOverlayTransparency = mModel.getOverlayTransparency(); 368 } 369 doRedraw(); 370 } 371 } 372