/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.hierarchyviewerlib.ui; import com.android.hierarchyviewerlib.models.PixelPerfectModel; import com.android.hierarchyviewerlib.models.PixelPerfectModel.IImageChangeListener; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.MouseWheelListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.Transform; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; public class PixelPerfectLoupe extends Canvas implements IImageChangeListener { private PixelPerfectModel mModel; private Image mImage; private Image mGrid; private Color mCrosshairColor; private int mWidth; private int mHeight; private Point mCrosshairLocation; private int mZoom; private Transform mTransform; private int mCanvasWidth; private int mCanvasHeight; private Image mOverlayImage; private double mOverlayTransparency; private boolean mShowOverlay = false; public PixelPerfectLoupe(Composite parent) { super(parent, SWT.NONE); mModel = PixelPerfectModel.getModel(); mModel.addImageChangeListener(this); addPaintListener(mPaintListener); addMouseListener(mMouseListener); addMouseWheelListener(mMouseWheelListener); addDisposeListener(mDisposeListener); addKeyListener(mKeyListener); mCrosshairColor = new Color(Display.getDefault(), new RGB(255, 94, 254)); mTransform = new Transform(Display.getDefault()); imageLoaded(); } public void setShowOverlay(boolean value) { synchronized (this) { mShowOverlay = value; } doRedraw(); } private DisposeListener mDisposeListener = new DisposeListener() { public void widgetDisposed(DisposeEvent e) { mModel.removeImageChangeListener(PixelPerfectLoupe.this); mCrosshairColor.dispose(); mTransform.dispose(); if (mGrid != null) { mGrid.dispose(); } } }; private MouseListener mMouseListener = new MouseListener() { public void mouseDoubleClick(MouseEvent e) { // pass } public void mouseDown(MouseEvent e) { handleMouseEvent(e); } public void mouseUp(MouseEvent e) { // } }; private MouseWheelListener mMouseWheelListener = new MouseWheelListener() { public void mouseScrolled(MouseEvent e) { int newZoom = -1; synchronized (PixelPerfectLoupe.this) { if (mImage != null && mCrosshairLocation != null) { if (e.count > 0) { newZoom = mZoom + 1; } else { newZoom = mZoom - 1; } } } if (newZoom != -1) { mModel.setZoom(newZoom); } } }; private void handleMouseEvent(MouseEvent e) { int newX = -1; int newY = -1; synchronized (PixelPerfectLoupe.this) { if (mImage == null) { return; } int zoomedX = -mCrosshairLocation.x * mZoom - mZoom / 2 + getBounds().width / 2; int zoomedY = -mCrosshairLocation.y * mZoom - mZoom / 2 + getBounds().height / 2; int x = (e.x - zoomedX) / mZoom; int y = (e.y - zoomedY) / mZoom; if (x >= 0 && x < mWidth && y >= 0 && y < mHeight) { newX = x; newY = y; } } if (newX != -1) { mModel.setCrosshairLocation(newX, newY); } } private KeyListener mKeyListener = new KeyListener() { public void keyPressed(KeyEvent e) { boolean crosshairMoved = false; synchronized (PixelPerfectLoupe.this) { if (mImage != null) { switch (e.keyCode) { case SWT.ARROW_UP: if (mCrosshairLocation.y != 0) { mCrosshairLocation.y--; crosshairMoved = true; } break; case SWT.ARROW_DOWN: if (mCrosshairLocation.y != mHeight - 1) { mCrosshairLocation.y++; crosshairMoved = true; } break; case SWT.ARROW_LEFT: if (mCrosshairLocation.x != 0) { mCrosshairLocation.x--; crosshairMoved = true; } break; case SWT.ARROW_RIGHT: if (mCrosshairLocation.x != mWidth - 1) { mCrosshairLocation.x++; crosshairMoved = true; } break; } } } if (crosshairMoved) { mModel.setCrosshairLocation(mCrosshairLocation.x, mCrosshairLocation.y); } } public void keyReleased(KeyEvent e) { // pass } }; private PaintListener mPaintListener = new PaintListener() { public void paintControl(PaintEvent e) { synchronized (PixelPerfectLoupe.this) { e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); e.gc.fillRectangle(0, 0, getSize().x, getSize().y); if (mImage != null && mCrosshairLocation != null) { int zoomedX = -mCrosshairLocation.x * mZoom - mZoom / 2 + getBounds().width / 2; int zoomedY = -mCrosshairLocation.y * mZoom - mZoom / 2 + getBounds().height / 2; mTransform.translate(zoomedX, zoomedY); mTransform.scale(mZoom, mZoom); e.gc.setInterpolation(SWT.NONE); e.gc.setTransform(mTransform); e.gc.drawImage(mImage, 0, 0); if (mShowOverlay && mOverlayImage != null) { e.gc.setAlpha((int) (mOverlayTransparency * 255)); e.gc.drawImage(mOverlayImage, 0, mHeight - mOverlayImage.getBounds().height); e.gc.setAlpha(255); } mTransform.identity(); e.gc.setTransform(mTransform); // If the size of the canvas has changed, we need to make // another grid. if (mGrid != null && (mCanvasWidth != getBounds().width || mCanvasHeight != getBounds().height)) { mGrid.dispose(); mGrid = null; } mCanvasWidth = getBounds().width; mCanvasHeight = getBounds().height; if (mGrid == null) { // Make a transparent image; ImageData imageData = new ImageData(mCanvasWidth + mZoom + 1, mCanvasHeight + mZoom + 1, 1, new PaletteData(new RGB[] { new RGB(0, 0, 0) })); imageData.transparentPixel = 0; // Draw the grid. mGrid = new Image(Display.getDefault(), imageData); GC gc = new GC(mGrid); gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); for (int x = 0; x <= mCanvasWidth + mZoom; x += mZoom) { gc.drawLine(x, 0, x, mCanvasHeight + mZoom); } for (int y = 0; y <= mCanvasHeight + mZoom; y += mZoom) { gc.drawLine(0, y, mCanvasWidth + mZoom, y); } gc.dispose(); } e.gc.setClipping(new Rectangle(zoomedX, zoomedY, mWidth * mZoom + 1, mHeight * mZoom + 1)); e.gc.setAlpha(76); e.gc.drawImage(mGrid, (mCanvasWidth / 2 - mZoom / 2) % mZoom - mZoom, (mCanvasHeight / 2 - mZoom / 2) % mZoom - mZoom); e.gc.setAlpha(255); e.gc.setForeground(mCrosshairColor); e.gc.drawLine(0, mCanvasHeight / 2, mCanvasWidth - 1, mCanvasHeight / 2); e.gc.drawLine(mCanvasWidth / 2, 0, mCanvasWidth / 2, mCanvasHeight - 1); } } } }; private void doRedraw() { Display.getDefault().syncExec(new Runnable() { public void run() { redraw(); } }); } private void loadImage() { mImage = mModel.getImage(); if (mImage != null) { mWidth = mImage.getBounds().width; mHeight = mImage.getBounds().height; } else { mWidth = 0; mHeight = 0; } } // Note the syncExec and then synchronized... It avoids deadlock public void imageLoaded() { Display.getDefault().syncExec(new Runnable() { public void run() { synchronized (this) { loadImage(); mCrosshairLocation = mModel.getCrosshairLocation(); mZoom = mModel.getZoom(); mOverlayImage = mModel.getOverlayImage(); mOverlayTransparency = mModel.getOverlayTransparency(); } } }); doRedraw(); } public void imageChanged() { Display.getDefault().syncExec(new Runnable() { public void run() { synchronized (this) { loadImage(); } } }); doRedraw(); } public void crosshairMoved() { synchronized (this) { mCrosshairLocation = mModel.getCrosshairLocation(); } doRedraw(); } public void selectionChanged() { // pass } public void treeChanged() { // pass } public void zoomChanged() { Display.getDefault().syncExec(new Runnable() { public void run() { synchronized (this) { if (mGrid != null) { // To notify that the zoom level has changed, we get rid // of the // grid. mGrid.dispose(); mGrid = null; } mZoom = mModel.getZoom(); } } }); doRedraw(); } public void overlayChanged() { synchronized (this) { mOverlayImage = mModel.getOverlayImage(); mOverlayTransparency = mModel.getOverlayTransparency(); } doRedraw(); } public void overlayTransparencyChanged() { synchronized (this) { mOverlayTransparency = mModel.getOverlayTransparency(); } doRedraw(); } }