• 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         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