1bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev/*
2bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev * Copyright (C) 2010 The Android Open Source Project
3bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev *
4bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev * Licensed under the Apache License, Version 2.0 (the "License");
5bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev * you may not use this file except in compliance with the License.
6bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev * You may obtain a copy of the License at
7bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev *
8bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev *      http://www.apache.org/licenses/LICENSE-2.0
9bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev *
10bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev * Unless required by applicable law or agreed to in writing, software
11bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev * distributed under the License is distributed on an "AS IS" BASIS,
12bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev * See the License for the specific language governing permissions and
14bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev * limitations under the License.
15bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev */
16bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
1770a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrevpackage com.android.hierarchyviewerlib.ui;
18bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
19bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport com.android.hierarchyviewerlib.models.PixelPerfectModel;
20901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrevimport com.android.hierarchyviewerlib.models.PixelPerfectModel.IImageChangeListener;
21bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
22bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.SWT;
23e361a961cbe1ead74659b4a761453b312d026e55Konstantin Lopyrevimport org.eclipse.swt.events.DisposeEvent;
24e361a961cbe1ead74659b4a761453b312d026e55Konstantin Lopyrevimport org.eclipse.swt.events.DisposeListener;
2597b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrevimport org.eclipse.swt.events.KeyEvent;
2697b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrevimport org.eclipse.swt.events.KeyListener;
27bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.events.MouseEvent;
28bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.events.MouseListener;
29c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrevimport org.eclipse.swt.events.MouseWheelListener;
30bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.events.PaintEvent;
31bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.events.PaintListener;
32bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.graphics.Color;
33bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.graphics.GC;
34bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.graphics.Image;
35bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.graphics.ImageData;
36bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.graphics.PaletteData;
3770a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrevimport org.eclipse.swt.graphics.Point;
38bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.graphics.RGB;
39bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.graphics.Rectangle;
40bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.graphics.Transform;
41bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.widgets.Canvas;
42bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.widgets.Composite;
43bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrevimport org.eclipse.swt.widgets.Display;
44bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
45901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrevpublic class PixelPerfectLoupe extends Canvas implements IImageChangeListener {
46901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private PixelPerfectModel mModel;
47bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
48901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private Image mImage;
49bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
50901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private Image mGrid;
51bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
52901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private Color mCrosshairColor;
53bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
54901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private int mWidth;
55bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
56901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private int mHeight;
57bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
58901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private Point mCrosshairLocation;
59bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
60901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private int mZoom;
61bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
62901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private Transform mTransform;
63bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
64901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private int mCanvasWidth;
65bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
66901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private int mCanvasHeight;
67bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
68901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private Image mOverlayImage;
6970a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev
70901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private double mOverlayTransparency;
7170a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev
72901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private boolean mShowOverlay = false;
7370a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev
74bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    public PixelPerfectLoupe(Composite parent) {
75c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev        super(parent, SWT.NONE);
76901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev        mModel = PixelPerfectModel.getModel();
77901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev        mModel.addImageChangeListener(this);
78bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
79901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev        addPaintListener(mPaintListener);
80901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev        addMouseListener(mMouseListener);
81901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev        addMouseWheelListener(mMouseWheelListener);
82901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev        addDisposeListener(mDisposeListener);
83901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev        addKeyListener(mKeyListener);
84bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
85901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev        mCrosshairColor = new Color(Display.getDefault(), new RGB(255, 94, 254));
86bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
87901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev        mTransform = new Transform(Display.getDefault());
8899bd6912e7b5b97fc6d4bb787e76b2d9dfffd7aeKonstantin Lopyrev
8999bd6912e7b5b97fc6d4bb787e76b2d9dfffd7aeKonstantin Lopyrev        imageLoaded();
90bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    }
91bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
9270a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev    public void setShowOverlay(boolean value) {
9370a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        synchronized (this) {
94901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            mShowOverlay = value;
9570a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        }
9697b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev        doRedraw();
9770a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev    }
9870a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev
99901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private DisposeListener mDisposeListener = new DisposeListener() {
100f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet        @Override
101e361a961cbe1ead74659b4a761453b312d026e55Konstantin Lopyrev        public void widgetDisposed(DisposeEvent e) {
102901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            mModel.removeImageChangeListener(PixelPerfectLoupe.this);
103901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            mCrosshairColor.dispose();
104901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            mTransform.dispose();
105901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            if (mGrid != null) {
106901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                mGrid.dispose();
107e361a961cbe1ead74659b4a761453b312d026e55Konstantin Lopyrev            }
108bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        }
109e361a961cbe1ead74659b4a761453b312d026e55Konstantin Lopyrev    };
110bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
111901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private MouseListener mMouseListener = new MouseListener() {
112bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
113f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet        @Override
114bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        public void mouseDoubleClick(MouseEvent e) {
115bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev            // pass
116bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        }
117bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
118f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet        @Override
119bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        public void mouseDown(MouseEvent e) {
120c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev            handleMouseEvent(e);
121bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        }
122bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
123f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet        @Override
124bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        public void mouseUp(MouseEvent e) {
125c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev            //
126bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        }
127bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
128bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    };
129bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
130901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private MouseWheelListener mMouseWheelListener = new MouseWheelListener() {
131f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet        @Override
132c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev        public void mouseScrolled(MouseEvent e) {
133c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev            int newZoom = -1;
13470a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev            synchronized (PixelPerfectLoupe.this) {
135901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                if (mImage != null && mCrosshairLocation != null) {
136c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev                    if (e.count > 0) {
137901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                        newZoom = mZoom + 1;
138c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev                    } else {
139901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                        newZoom = mZoom - 1;
140c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev                    }
141c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev                }
142c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev            }
143c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev            if (newZoom != -1) {
144901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                mModel.setZoom(newZoom);
145c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev            }
146c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev        }
147c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev    };
148c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev
149bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    private void handleMouseEvent(MouseEvent e) {
150bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        int newX = -1;
151bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        int newY = -1;
15270a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        synchronized (PixelPerfectLoupe.this) {
153901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            if (mImage == null) {
154bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                return;
155bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev            }
156901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            int zoomedX = -mCrosshairLocation.x * mZoom - mZoom / 2 + getBounds().width / 2;
157901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            int zoomedY = -mCrosshairLocation.y * mZoom - mZoom / 2 + getBounds().height / 2;
158901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            int x = (e.x - zoomedX) / mZoom;
159901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            int y = (e.y - zoomedY) / mZoom;
160901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            if (x >= 0 && x < mWidth && y >= 0 && y < mHeight) {
161bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                newX = x;
162bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                newY = y;
163bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev            }
164bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        }
165bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        if (newX != -1) {
166901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            mModel.setCrosshairLocation(newX, newY);
167bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        }
168bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    }
169bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
170901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private KeyListener mKeyListener = new KeyListener() {
17197b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev
172f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet        @Override
17397b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev        public void keyPressed(KeyEvent e) {
17497b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev            boolean crosshairMoved = false;
17597b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev            synchronized (PixelPerfectLoupe.this) {
176901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                if (mImage != null) {
17797b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                    switch (e.keyCode) {
17897b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                        case SWT.ARROW_UP:
179901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                            if (mCrosshairLocation.y != 0) {
180901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                                mCrosshairLocation.y--;
18197b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                                crosshairMoved = true;
18297b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                            }
18397b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                            break;
18497b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                        case SWT.ARROW_DOWN:
185901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                            if (mCrosshairLocation.y != mHeight - 1) {
186901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                                mCrosshairLocation.y++;
18797b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                                crosshairMoved = true;
18897b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                            }
18997b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                            break;
19097b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                        case SWT.ARROW_LEFT:
191901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                            if (mCrosshairLocation.x != 0) {
192901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                                mCrosshairLocation.x--;
19397b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                                crosshairMoved = true;
19497b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                            }
19597b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                            break;
19697b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                        case SWT.ARROW_RIGHT:
197901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                            if (mCrosshairLocation.x != mWidth - 1) {
198901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                                mCrosshairLocation.x++;
19997b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                                crosshairMoved = true;
20097b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                            }
20197b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                            break;
20297b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                    }
20397b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev                }
20497b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev            }
20597b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev            if (crosshairMoved) {
206901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                mModel.setCrosshairLocation(mCrosshairLocation.x, mCrosshairLocation.y);
20797b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev            }
20897b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev        }
20997b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev
210f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet        @Override
21197b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev        public void keyReleased(KeyEvent e) {
21297b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev            // pass
21397b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev        }
21497b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev
21597b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev    };
21697b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev
217901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev    private PaintListener mPaintListener = new PaintListener() {
218f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet        @Override
219bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        public void paintControl(PaintEvent e) {
22070a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev            synchronized (PixelPerfectLoupe.this) {
221bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
222c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev                e.gc.fillRectangle(0, 0, getSize().x, getSize().y);
223901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                if (mImage != null && mCrosshairLocation != null) {
224901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    int zoomedX = -mCrosshairLocation.x * mZoom - mZoom / 2 + getBounds().width / 2;
225901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    int zoomedY = -mCrosshairLocation.y * mZoom - mZoom / 2 + getBounds().height / 2;
226901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    mTransform.translate(zoomedX, zoomedY);
227901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    mTransform.scale(mZoom, mZoom);
228bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                    e.gc.setInterpolation(SWT.NONE);
229901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    e.gc.setTransform(mTransform);
230901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    e.gc.drawImage(mImage, 0, 0);
231901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    if (mShowOverlay && mOverlayImage != null) {
232901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                        e.gc.setAlpha((int) (mOverlayTransparency * 255));
233901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                        e.gc.drawImage(mOverlayImage, 0, mHeight - mOverlayImage.getBounds().height);
23470a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                        e.gc.setAlpha(255);
23570a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                    }
23670a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev
237901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    mTransform.identity();
238901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    e.gc.setTransform(mTransform);
239bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
240bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                    // If the size of the canvas has changed, we need to make
241bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                    // another grid.
242901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    if (mGrid != null
243901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                            && (mCanvasWidth != getBounds().width || mCanvasHeight != getBounds().height)) {
244901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                        mGrid.dispose();
245901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                        mGrid = null;
246bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                    }
247901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    mCanvasWidth = getBounds().width;
248901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    mCanvasHeight = getBounds().height;
249901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    if (mGrid == null) {
250bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                        // Make a transparent image;
251bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                        ImageData imageData =
252901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                                new ImageData(mCanvasWidth + mZoom + 1, mCanvasHeight + mZoom + 1, 1,
253bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                                        new PaletteData(new RGB[] {
254bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                                            new RGB(0, 0, 0)
255bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                                        }));
256bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                        imageData.transparentPixel = 0;
257bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
258bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                        // Draw the grid.
259901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                        mGrid = new Image(Display.getDefault(), imageData);
260901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                        GC gc = new GC(mGrid);
261bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                        gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
262901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                        for (int x = 0; x <= mCanvasWidth + mZoom; x += mZoom) {
263901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                            gc.drawLine(x, 0, x, mCanvasHeight + mZoom);
264bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                        }
265901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                        for (int y = 0; y <= mCanvasHeight + mZoom; y += mZoom) {
266901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                            gc.drawLine(0, y, mCanvasWidth + mZoom, y);
267bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                        }
268bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                        gc.dispose();
269bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                    }
270bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
271901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    e.gc.setClipping(new Rectangle(zoomedX, zoomedY, mWidth * mZoom + 1, mHeight
272901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                            * mZoom + 1));
273bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                    e.gc.setAlpha(76);
274901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    e.gc.drawImage(mGrid, (mCanvasWidth / 2 - mZoom / 2) % mZoom - mZoom,
275901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                            (mCanvasHeight / 2 - mZoom / 2) % mZoom - mZoom);
276bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                    e.gc.setAlpha(255);
277bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
278901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    e.gc.setForeground(mCrosshairColor);
279901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    e.gc.drawLine(0, mCanvasHeight / 2, mCanvasWidth - 1, mCanvasHeight / 2);
280901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    e.gc.drawLine(mCanvasWidth / 2, 0, mCanvasWidth / 2, mCanvasHeight - 1);
281bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev                }
282bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev            }
283bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        }
284bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    };
285bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
286c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev    private void doRedraw() {
28799bd6912e7b5b97fc6d4bb787e76b2d9dfffd7aeKonstantin Lopyrev        Display.getDefault().syncExec(new Runnable() {
288f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet            @Override
289bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev            public void run() {
290c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev                redraw();
291bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev            }
292bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        });
293bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    }
294bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
295bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    private void loadImage() {
296901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev        mImage = mModel.getImage();
297901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev        if (mImage != null) {
298901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            mWidth = mImage.getBounds().width;
299901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            mHeight = mImage.getBounds().height;
300bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        } else {
301901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            mWidth = 0;
302901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            mHeight = 0;
303bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        }
304bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    }
305bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
306285f39916682c0f70a8dc9ccd930affa37365b97Konstantin Lopyrev    // Note the syncExec and then synchronized... It avoids deadlock
307f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet    @Override
308bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    public void imageLoaded() {
30970a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        Display.getDefault().syncExec(new Runnable() {
310f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet            @Override
31170a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev            public void run() {
31270a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                synchronized (this) {
31370a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                    loadImage();
314901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    mCrosshairLocation = mModel.getCrosshairLocation();
315901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    mZoom = mModel.getZoom();
316901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    mOverlayImage = mModel.getOverlayImage();
317901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    mOverlayTransparency = mModel.getOverlayTransparency();
31870a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                }
31970a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev            }
32070a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        });
321c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev        doRedraw();
322bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    }
323bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
324f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet    @Override
325bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    public void imageChanged() {
32670a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        Display.getDefault().syncExec(new Runnable() {
327f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet            @Override
32870a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev            public void run() {
32970a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                synchronized (this) {
33070a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                    loadImage();
33170a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                }
33270a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev            }
33370a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        });
334c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev        doRedraw();
335bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    }
336bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
337f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet    @Override
338bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    public void crosshairMoved() {
339bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        synchronized (this) {
340901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            mCrosshairLocation = mModel.getCrosshairLocation();
341bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        }
342c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev        doRedraw();
343bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    }
344bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
345f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet    @Override
346bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    public void selectionChanged() {
347bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev        // pass
348bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    }
349bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev
350f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet    @Override
35197b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev    public void treeChanged() {
35297b0639645d9c387cdd9884272e053c467a240daKonstantin Lopyrev        // pass
353bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev    }
354c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev
355f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet    @Override
356c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev    public void zoomChanged() {
35770a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        Display.getDefault().syncExec(new Runnable() {
358f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet            @Override
35970a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev            public void run() {
36070a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                synchronized (this) {
361901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    if (mGrid != null) {
36270a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                        // To notify that the zoom level has changed, we get rid
36370a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                        // of the
36470a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                        // grid.
365901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                        mGrid.dispose();
366901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                        mGrid = null;
36770a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                    }
368901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev                    mZoom = mModel.getZoom();
36970a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev                }
370c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev            }
37170a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        });
37270a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        doRedraw();
37370a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev    }
37470a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev
375f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet    @Override
37670a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev    public void overlayChanged() {
37770a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        synchronized (this) {
378901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            mOverlayImage = mModel.getOverlayImage();
379901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            mOverlayTransparency = mModel.getOverlayTransparency();
38070a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        }
38170a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        doRedraw();
38270a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev    }
38370a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev
384f526dc3bb658b07c5a96d0372b17ffa2fbbe6079Xavier Ducrohet    @Override
38570a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev    public void overlayTransparencyChanged() {
38670a830d8ab396328ab984c75f923400af6a52e55Konstantin Lopyrev        synchronized (this) {
387901803a3b2aaeafb4074974ec5bf9b08cc39d1c8Konstantin Lopyrev            mOverlayTransparency = mModel.getOverlayTransparency();
388c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev        }
389c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev        doRedraw();
390c1db1ab270b953da579e657de11d4486a49e1c1eKonstantin Lopyrev    }
391bc494ce06410728bef8a6fa25f3da361cb66df79Konstantin Lopyrev}
392