/*
* 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.device.ViewNode;
import com.android.hierarchyviewerlib.models.PixelPerfectModel;
import com.android.hierarchyviewerlib.models.PixelPerfectModel.IImageChangeListener;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
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.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
public class PixelPerfect extends ScrolledComposite implements IImageChangeListener {
private Canvas mCanvas;
private PixelPerfectModel mModel;
private Image mImage;
private Color mCrosshairColor;
private Color mMarginColor;
private Color mBorderColor;
private Color mPaddingColor;
private int mWidth;
private int mHeight;
private Point mCrosshairLocation;
private ViewNode mSelectedNode;
private Image mOverlayImage;
private double mOverlayTransparency;
public PixelPerfect(Composite parent) {
super(parent, SWT.H_SCROLL | SWT.V_SCROLL);
mCanvas = new Canvas(this, SWT.NONE);
setContent(mCanvas);
setExpandHorizontal(true);
setExpandVertical(true);
mModel = PixelPerfectModel.getModel();
mModel.addImageChangeListener(this);
mCanvas.addPaintListener(mPaintListener);
mCanvas.addMouseListener(mMouseListener);
mCanvas.addMouseMoveListener(mMouseMoveListener);
mCanvas.addKeyListener(mKeyListener);
addDisposeListener(mDisposeListener);
mCrosshairColor = new Color(Display.getDefault(), new RGB(0, 255, 255));
mBorderColor = new Color(Display.getDefault(), new RGB(255, 0, 0));
mMarginColor = new Color(Display.getDefault(), new RGB(0, 255, 0));
mPaddingColor = new Color(Display.getDefault(), new RGB(0, 0, 255));
imageLoaded();
}
private DisposeListener mDisposeListener = new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
mModel.removeImageChangeListener(PixelPerfect.this);
mCrosshairColor.dispose();
mBorderColor.dispose();
mPaddingColor.dispose();
}
};
@Override
public boolean setFocus() {
return mCanvas.setFocus();
}
private MouseListener mMouseListener = new MouseListener() {
public void mouseDoubleClick(MouseEvent e) {
// pass
}
public void mouseDown(MouseEvent e) {
handleMouseEvent(e);
}
public void mouseUp(MouseEvent e) {
handleMouseEvent(e);
}
};
private MouseMoveListener mMouseMoveListener = new MouseMoveListener() {
public void mouseMove(MouseEvent e) {
if (e.stateMask != 0) {
handleMouseEvent(e);
}
}
};
private void handleMouseEvent(MouseEvent e) {
synchronized (PixelPerfect.this) {
if (mImage == null) {
return;
}
int leftOffset = mCanvas.getSize().x / 2 - mWidth / 2;
int topOffset = mCanvas.getSize().y / 2 - mHeight / 2;
e.x -= leftOffset;
e.y -= topOffset;
e.x = Math.max(e.x, 0);
e.x = Math.min(e.x, mWidth - 1);
e.y = Math.max(e.y, 0);
e.y = Math.min(e.y, mHeight - 1);
}
mModel.setCrosshairLocation(e.x, e.y);
}
private KeyListener mKeyListener = new KeyListener() {
public void keyPressed(KeyEvent e) {
boolean crosshairMoved = false;
synchronized (PixelPerfect.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 (PixelPerfect.this) {
e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
e.gc.fillRectangle(0, 0, mCanvas.getSize().x, mCanvas.getSize().y);
if (mImage != null) {
// Let's be cool and put it in the center...
int leftOffset = mCanvas.getSize().x / 2 - mWidth / 2;
int topOffset = mCanvas.getSize().y / 2 - mHeight / 2;
e.gc.drawImage(mImage, leftOffset, topOffset);
if (mOverlayImage != null) {
e.gc.setAlpha((int) (mOverlayTransparency * 255));
int overlayTopOffset =
mCanvas.getSize().y / 2 + mHeight / 2
- mOverlayImage.getBounds().height;
e.gc.drawImage(mOverlayImage, leftOffset, overlayTopOffset);
e.gc.setAlpha(255);
}
if (mSelectedNode != null) {
// If the screen is in landscape mode, the
// coordinates are backwards.
int leftShift = 0;
int topShift = 0;
int nodeLeft = mSelectedNode.left;
int nodeTop = mSelectedNode.top;
int nodeWidth = mSelectedNode.width;
int nodeHeight = mSelectedNode.height;
int nodeMarginLeft = mSelectedNode.marginLeft;
int nodeMarginTop = mSelectedNode.marginTop;
int nodeMarginRight = mSelectedNode.marginRight;
int nodeMarginBottom = mSelectedNode.marginBottom;
int nodePadLeft = mSelectedNode.paddingLeft;
int nodePadTop = mSelectedNode.paddingTop;
int nodePadRight = mSelectedNode.paddingRight;
int nodePadBottom = mSelectedNode.paddingBottom;
ViewNode cur = mSelectedNode;
while (cur.parent != null) {
leftShift += cur.parent.left - cur.parent.scrollX;
topShift += cur.parent.top - cur.parent.scrollY;
cur = cur.parent;
}
// Everything is sideways.
if (cur.width > cur.height) {
e.gc.setForeground(mPaddingColor);
e.gc.drawRectangle(leftOffset + mWidth - nodeTop - topShift - nodeHeight
+ nodePadBottom,
topOffset + leftShift + nodeLeft + nodePadLeft, nodeHeight
- nodePadBottom - nodePadTop, nodeWidth - nodePadRight
- nodePadLeft);
e.gc.setForeground(mMarginColor);
e.gc.drawRectangle(leftOffset + mWidth - nodeTop - topShift - nodeHeight
- nodeMarginBottom, topOffset + leftShift + nodeLeft
- nodeMarginLeft,
nodeHeight + nodeMarginBottom + nodeMarginTop, nodeWidth
+ nodeMarginRight + nodeMarginLeft);
e.gc.setForeground(mBorderColor);
e.gc.drawRectangle(
leftOffset + mWidth - nodeTop - topShift - nodeHeight, topOffset
+ leftShift + nodeLeft, nodeHeight, nodeWidth);
} else {
e.gc.setForeground(mPaddingColor);
e.gc.drawRectangle(leftOffset + leftShift + nodeLeft + nodePadLeft,
topOffset + topShift + nodeTop + nodePadTop, nodeWidth
- nodePadRight - nodePadLeft, nodeHeight
- nodePadBottom - nodePadTop);
e.gc.setForeground(mMarginColor);
e.gc.drawRectangle(leftOffset + leftShift + nodeLeft - nodeMarginLeft,
topOffset + topShift + nodeTop - nodeMarginTop, nodeWidth
+ nodeMarginRight + nodeMarginLeft, nodeHeight
+ nodeMarginBottom + nodeMarginTop);
e.gc.setForeground(mBorderColor);
e.gc.drawRectangle(leftOffset + leftShift + nodeLeft, topOffset
+ topShift + nodeTop, nodeWidth, nodeHeight);
}
}
if (mCrosshairLocation != null) {
e.gc.setForeground(mCrosshairColor);
e.gc.drawLine(leftOffset, topOffset + mCrosshairLocation.y, leftOffset
+ mWidth - 1, topOffset + mCrosshairLocation.y);
e.gc.drawLine(leftOffset + mCrosshairLocation.x, topOffset, leftOffset
+ mCrosshairLocation.x, topOffset + mHeight - 1);
}
}
}
}
};
private void doRedraw() {
Display.getDefault().syncExec(new Runnable() {
public void run() {
mCanvas.redraw();
}
});
}
private void loadImage() {
mImage = mModel.getImage();
if (mImage != null) {
mWidth = mImage.getBounds().width;
mHeight = mImage.getBounds().height;
} else {
mWidth = 0;
mHeight = 0;
}
setMinSize(mWidth, mHeight);
}
public void imageLoaded() {
Display.getDefault().syncExec(new Runnable() {
public void run() {
synchronized (this) {
loadImage();
mCrosshairLocation = mModel.getCrosshairLocation();
mSelectedNode = mModel.getSelected();
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() {
synchronized (this) {
mSelectedNode = mModel.getSelected();
}
doRedraw();
}
// Note the syncExec and then synchronized... It avoids deadlock
public void treeChanged() {
Display.getDefault().syncExec(new Runnable() {
public void run() {
synchronized (this) {
mSelectedNode = mModel.getSelected();
}
}
});
doRedraw();
}
public void zoomChanged() {
// pass
}
public void overlayChanged() {
synchronized (this) {
mOverlayImage = mModel.getOverlayImage();
mOverlayTransparency = mModel.getOverlayTransparency();
}
doRedraw();
}
public void overlayTransparencyChanged() {
synchronized (this) {
mOverlayTransparency = mModel.getOverlayTransparency();
}
doRedraw();
}
}