/*
* 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.ddmuilib.ImageLoader;
import com.android.hierarchyviewerlib.models.TreeViewModel;
import com.android.hierarchyviewerlib.models.TreeViewModel.ITreeChangeListener;
import com.android.hierarchyviewerlib.ui.util.DrawableViewNode;
import com.android.hierarchyviewerlib.ui.util.DrawableViewNode.Point;
import com.android.hierarchyviewerlib.ui.util.DrawableViewNode.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
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.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.graphics.Transform;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
public class TreeViewOverview extends Canvas implements ITreeChangeListener {
private TreeViewModel mModel;
private DrawableViewNode mTree;
private Rectangle mViewport;
private Transform mTransform;
private Transform mInverse;
private Rectangle mBounds = new Rectangle();
private double mScale;
private boolean mDragging = false;
private DrawableViewNode mSelectedNode;
private static Image sNotSelectedImage;
private static Image sSelectedImage;
private static Image sFilteredImage;
private static Image sFilteredSelectedImage;
public TreeViewOverview(Composite parent) {
super(parent, SWT.NONE);
mModel = TreeViewModel.getModel();
mModel.addTreeChangeListener(this);
loadResources();
addPaintListener(mPaintListener);
addMouseListener(mMouseListener);
addMouseMoveListener(mMouseMoveListener);
addListener(SWT.Resize, mResizeListener);
addDisposeListener(mDisposeListener);
mTransform = new Transform(Display.getDefault());
mInverse = new Transform(Display.getDefault());
loadAllData();
}
private void loadResources() {
ImageLoader loader = ImageLoader.getLoader(this.getClass());
sNotSelectedImage = loader.loadImage("not-selected.png", Display.getDefault()); //$NON-NLS-1$
sSelectedImage = loader.loadImage("selected-small.png", Display.getDefault()); //$NON-NLS-1$
sFilteredImage = loader.loadImage("filtered.png", Display.getDefault()); //$NON-NLS-1$
sFilteredSelectedImage =
loader.loadImage("selected-filtered-small.png", Display.getDefault()); //$NON-NLS-1$
}
private DisposeListener mDisposeListener = new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
mModel.removeTreeChangeListener(TreeViewOverview.this);
mTransform.dispose();
mInverse.dispose();
}
};
private MouseListener mMouseListener = new MouseListener() {
public void mouseDoubleClick(MouseEvent e) {
// pass
}
public void mouseDown(MouseEvent e) {
boolean redraw = false;
synchronized (TreeViewOverview.this) {
if (mTree != null && mViewport != null) {
mDragging = true;
redraw = true;
handleMouseEvent(transformPoint(e.x, e.y));
}
}
if (redraw) {
mModel.removeTreeChangeListener(TreeViewOverview.this);
mModel.setViewport(mViewport);
mModel.addTreeChangeListener(TreeViewOverview.this);
doRedraw();
}
}
public void mouseUp(MouseEvent e) {
boolean redraw = false;
synchronized (TreeViewOverview.this) {
if (mTree != null && mViewport != null) {
mDragging = false;
redraw = true;
handleMouseEvent(transformPoint(e.x, e.y));
// Update bounds and transform only on mouse up. That way,
// you don't get confusing behaviour during mouse drag and
// it snaps neatly at the end
setBounds();
setTransform();
}
}
if (redraw) {
mModel.removeTreeChangeListener(TreeViewOverview.this);
mModel.setViewport(mViewport);
mModel.addTreeChangeListener(TreeViewOverview.this);
doRedraw();
}
}
};
private MouseMoveListener mMouseMoveListener = new MouseMoveListener() {
public void mouseMove(MouseEvent e) {
boolean moved = false;
synchronized (TreeViewOverview.this) {
if (mDragging) {
moved = true;
handleMouseEvent(transformPoint(e.x, e.y));
}
}
if (moved) {
mModel.removeTreeChangeListener(TreeViewOverview.this);
mModel.setViewport(mViewport);
mModel.addTreeChangeListener(TreeViewOverview.this);
doRedraw();
}
}
};
private void handleMouseEvent(Point pt) {
mViewport.x = pt.x - mViewport.width / 2;
mViewport.y = pt.y - mViewport.height / 2;
if (mViewport.x < mBounds.x) {
mViewport.x = mBounds.x;
}
if (mViewport.y < mBounds.y) {
mViewport.y = mBounds.y;
}
if (mViewport.x + mViewport.width > mBounds.x + mBounds.width) {
mViewport.x = mBounds.x + mBounds.width - mViewport.width;
}
if (mViewport.y + mViewport.height > mBounds.y + mBounds.height) {
mViewport.y = mBounds.y + mBounds.height - mViewport.height;
}
}
private Point transformPoint(double x, double y) {
float[] pt = {
(float) x, (float) y
};
mInverse.transform(pt);
return new Point(pt[0], pt[1]);
}
private Listener mResizeListener = new Listener() {
public void handleEvent(Event arg0) {
synchronized (TreeViewOverview.this) {
setTransform();
}
doRedraw();
}
};
private PaintListener mPaintListener = new PaintListener() {
public void paintControl(PaintEvent e) {
synchronized (TreeViewOverview.this) {
if (mTree != null) {
e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height);
e.gc.setTransform(mTransform);
e.gc.setLineWidth((int) Math.ceil(0.7 / mScale));
Path connectionPath = new Path(Display.getDefault());
paintRecursive(e.gc, mTree, connectionPath);
e.gc.drawPath(connectionPath);
connectionPath.dispose();
if (mViewport != null) {
e.gc.setAlpha(50);
e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
e.gc.fillRectangle((int) mViewport.x, (int) mViewport.y, (int) Math
.ceil(mViewport.width), (int) Math.ceil(mViewport.height));
e.gc.setAlpha(255);
e.gc
.setForeground(Display.getDefault().getSystemColor(
SWT.COLOR_DARK_GRAY));
e.gc.setLineWidth((int) Math.ceil(2 / mScale));
e.gc.drawRectangle((int) mViewport.x, (int) mViewport.y, (int) Math
.ceil(mViewport.width), (int) Math.ceil(mViewport.height));
}
}
}
}
};
private void paintRecursive(GC gc, DrawableViewNode node, Path connectionPath) {
if (mSelectedNode == node && node.viewNode.filtered) {
gc.drawImage(sFilteredSelectedImage, node.left, (int) Math.round(node.top));
} else if (mSelectedNode == node) {
gc.drawImage(sSelectedImage, node.left, (int) Math.round(node.top));
} else if (node.viewNode.filtered) {
gc.drawImage(sFilteredImage, node.left, (int) Math.round(node.top));
} else {
gc.drawImage(sNotSelectedImage, node.left, (int) Math.round(node.top));
}
int N = node.children.size();
if (N == 0) {
return;
}
float childSpacing =
(1.0f * (DrawableViewNode.NODE_HEIGHT - 2 * TreeView.LINE_PADDING)) / N;
for (int i = 0; i < N; i++) {
DrawableViewNode child = node.children.get(i);
paintRecursive(gc, child, connectionPath);
float x1 = node.left + DrawableViewNode.NODE_WIDTH;
float y1 =
(float) node.top + TreeView.LINE_PADDING + childSpacing * i + childSpacing / 2;
float x2 = child.left;
float y2 = (float) child.top + DrawableViewNode.NODE_HEIGHT / 2.0f;
float cx1 = x1 + TreeView.BEZIER_FRACTION * DrawableViewNode.PARENT_CHILD_SPACING;
float cy1 = y1;
float cx2 = x2 - TreeView.BEZIER_FRACTION * DrawableViewNode.PARENT_CHILD_SPACING;
float cy2 = y2;
connectionPath.moveTo(x1, y1);
connectionPath.cubicTo(cx1, cy1, cx2, cy2, x2, y2);
}
}
private void doRedraw() {
Display.getDefault().syncExec(new Runnable() {
public void run() {
redraw();
}
});
}
public void loadAllData() {
Display.getDefault().syncExec(new Runnable() {
public void run() {
synchronized (this) {
mTree = mModel.getTree();
mSelectedNode = mModel.getSelection();
mViewport = mModel.getViewport();
setBounds();
setTransform();
}
}
});
}
// Note the syncExec and then synchronized... It avoids deadlock
public void treeChanged() {
Display.getDefault().syncExec(new Runnable() {
public void run() {
synchronized (this) {
mTree = mModel.getTree();
mSelectedNode = mModel.getSelection();
mViewport = mModel.getViewport();
setBounds();
setTransform();
}
}
});
doRedraw();
}
private void setBounds() {
if (mViewport != null && mTree != null) {
mBounds.x = Math.min(mViewport.x, mTree.bounds.x);
mBounds.y = Math.min(mViewport.y, mTree.bounds.y);
mBounds.width =
Math.max(mViewport.x + mViewport.width, mTree.bounds.x + mTree.bounds.width)
- mBounds.x;
mBounds.height =
Math.max(mViewport.y + mViewport.height, mTree.bounds.y + mTree.bounds.height)
- mBounds.y;
} else if (mTree != null) {
mBounds.x = mTree.bounds.x;
mBounds.y = mTree.bounds.y;
mBounds.width = mTree.bounds.x + mTree.bounds.width - mBounds.x;
mBounds.height = mTree.bounds.y + mTree.bounds.height - mBounds.y;
}
}
private void setTransform() {
if (mTree != null) {
mTransform.identity();
mInverse.identity();
final Point size = new Point();
size.x = getBounds().width;
size.y = getBounds().height;
if (mBounds.width == 0 || mBounds.height == 0 || size.x == 0 || size.y == 0) {
mScale = 1;
} else {
mScale = Math.min(size.x / mBounds.width, size.y / mBounds.height);
}
mTransform.scale((float) mScale, (float) mScale);
mInverse.scale((float) mScale, (float) mScale);
mTransform.translate((float) -mBounds.x, (float) -mBounds.y);
mInverse.translate((float) -mBounds.x, (float) -mBounds.y);
if (size.x / mBounds.width < size.y / mBounds.height) {
mTransform.translate(0, (float) (size.y / mScale - mBounds.height) / 2);
mInverse.translate(0, (float) (size.y / mScale - mBounds.height) / 2);
} else {
mTransform.translate((float) (size.x / mScale - mBounds.width) / 2, 0);
mInverse.translate((float) (size.x / mScale - mBounds.width) / 2, 0);
}
mInverse.invert();
}
}
public void viewportChanged() {
Display.getDefault().syncExec(new Runnable() {
public void run() {
synchronized (this) {
mViewport = mModel.getViewport();
setBounds();
setTransform();
}
}
});
doRedraw();
}
public void zoomChanged() {
viewportChanged();
}
public void selectionChanged() {
synchronized (this) {
mSelectedNode = mModel.getSelection();
}
doRedraw();
}
}