• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         @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 
handleMouseEvent(MouseEvent e)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 
doRedraw()286     private void doRedraw() {
287         Display.getDefault().syncExec(new Runnable() {
288             @Override
289             public void run() {
290                 redraw();
291             }
292         });
293     }
294 
loadImage()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
imageLoaded()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
imageChanged()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
crosshairMoved()338     public void crosshairMoved() {
339         synchronized (this) {
340             mCrosshairLocation = mModel.getCrosshairLocation();
341         }
342         doRedraw();
343     }
344 
345     @Override
selectionChanged()346     public void selectionChanged() {
347         // pass
348     }
349 
350     @Override
treeChanged()351     public void treeChanged() {
352         // pass
353     }
354 
355     @Override
zoomChanged()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
overlayChanged()376     public void overlayChanged() {
377         synchronized (this) {
378             mOverlayImage = mModel.getOverlayImage();
379             mOverlayTransparency = mModel.getOverlayTransparency();
380         }
381         doRedraw();
382     }
383 
384     @Override
overlayTransparencyChanged()385     public void overlayTransparencyChanged() {
386         synchronized (this) {
387             mOverlayTransparency = mModel.getOverlayTransparency();
388         }
389         doRedraw();
390     }
391 }
392