diff options
Diffstat (limited to 'hierarchyviewer2/libs')
13 files changed, 1257 insertions, 73 deletions
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java index 528c35c..a50478e 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ComponentRegistry.java @@ -18,6 +18,7 @@ package com.android.hierarchyviewerlib; import com.android.hierarchyviewerlib.models.DeviceSelectionModel; import com.android.hierarchyviewerlib.models.PixelPerfectModel; +import com.android.hierarchyviewerlib.models.TreeViewModel; /** * This is the central point for getting access to the various parts of the @@ -32,6 +33,8 @@ public class ComponentRegistry { private static PixelPerfectModel pixelPerfectModel; + private static TreeViewModel treeViewModel; + public static HierarchyViewerDirector getDirector() { return director; } @@ -55,4 +58,12 @@ public class ComponentRegistry { public static PixelPerfectModel getPixelPerfectModel() { return pixelPerfectModel; } + + public static void setTreeViewModel(TreeViewModel treeViewModel) { + ComponentRegistry.treeViewModel = treeViewModel; + } + + public static TreeViewModel getTreeViewModel() { + return treeViewModel; + } } diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java index c21da49..b63fba8 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java @@ -109,6 +109,9 @@ public abstract class HierarchyViewerDirector implements IDeviceChangeListener, DeviceBridge.removeDeviceForward(device); DeviceBridge.removeViewServerInfo(device); ComponentRegistry.getDeviceSelectionModel().removeDevice(device); + if (ComponentRegistry.getPixelPerfectModel().getDevice() == device) { + ComponentRegistry.getPixelPerfectModel().setData(null, null, null); + } } public void deviceChanged(IDevice device, int changeMask) { @@ -192,4 +195,17 @@ public abstract class HierarchyViewerDirector implements IDeviceChangeListener, } }); } + + public void loadViewTreeData(final Window window) { + executeInBackground(new Runnable() { + public void run() { + + ViewNode viewNode = DeviceBridge.loadWindowData(window); + if (viewNode != null) { + DeviceBridge.loadProfileData(window, viewNode); + ComponentRegistry.getTreeViewModel().setData(window, viewNode); + } + } + }); + } } diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java index 001f98b..34e7c03 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java @@ -430,4 +430,41 @@ public class DeviceBridge { } return null; } + + public static boolean loadProfileData(Window window, ViewNode viewNode) { + DeviceConnection connection = null; + try { + connection = new DeviceConnection(window.getDevice()); + connection.sendCommand("PROFILE " + window.encode() + " " + viewNode.toString()); + BufferedReader in = connection.getInputStream(); + return loadProfileDataRecursive(viewNode, in); + } catch (IOException e) { + Log.e(TAG, "Unable to load profiling data for window " + window.getTitle() + + " on device " + window.getDevice()); + } finally { + if (connection != null) { + connection.close(); + } + } + return false; + } + + private static boolean loadProfileDataRecursive(ViewNode node, BufferedReader in) + throws IOException { + String line; + if ((line = in.readLine()) == null || line.equalsIgnoreCase("-1 -1 -1") + || line.equalsIgnoreCase("DONE.")) { + return false; + } + String[] data = line.split(" "); + node.measureTime = (Long.parseLong(data[0]) / 1000.0) / 1000.0; + node.layoutTime = (Long.parseLong(data[1]) / 1000.0) / 1000.0; + node.drawTime = (Long.parseLong(data[2]) / 1000.0) / 1000.0; + for (int i = 0; i < node.children.size(); i++) { + if (!loadProfileDataRecursive(node.children.get(i), in)) { + return false; + } + } + return true; + } } diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewNode.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewNode.java index 00453ee..2e22b56 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewNode.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewNode.java @@ -76,6 +76,12 @@ public class ViewNode { public int index; + public double measureTime; + + public double layoutTime; + + public double drawTime; + public ViewNode(ViewNode parent, String data) { this.parent = parent; index = this.parent == null ? 0 : this.parent.children.size(); @@ -88,6 +94,10 @@ public class ViewNode { delimIndex = data.indexOf(' '); hashCode = data.substring(0, delimIndex); loadProperties(data.substring(delimIndex + 1).trim()); + + measureTime = -1; + layoutTime = -1; + drawTime = -1; } private void loadProperties(String data) { diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/PixelPerfectModel.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/PixelPerfectModel.java index 6963b25..4f19368 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/PixelPerfectModel.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/PixelPerfectModel.java @@ -24,6 +24,12 @@ import java.util.ArrayList; public class PixelPerfectModel { + public static final int MIN_ZOOM = 2; + + public static final int MAX_ZOOM = 24; + + private static final int DEFAULT_ZOOM = 8; + public static class Point { public int x; @@ -45,6 +51,8 @@ public class PixelPerfectModel { private ViewNode selected; + private int zoom; + private final ArrayList<ImageChangeListener> imageChangeListeners = new ArrayList<ImageChangeListener>(); @@ -53,8 +61,13 @@ public class PixelPerfectModel { this.device = device; this.image = image; this.viewNode = viewNode; - this.crosshairLocation = new Point(image.width / 2, image.height / 2); + if (image != null) { + this.crosshairLocation = new Point(image.width / 2, image.height / 2); + } else { + this.crosshairLocation = null; + } this.selected = null; + zoom = DEFAULT_ZOOM; } notifyImageLoaded(); } @@ -82,6 +95,19 @@ public class PixelPerfectModel { notifyFocusChanged(); } + public void setZoom(int newZoom) { + synchronized (this) { + if (newZoom < MIN_ZOOM) { + newZoom = MIN_ZOOM; + } + if (newZoom > MAX_ZOOM) { + newZoom = MAX_ZOOM; + } + zoom = newZoom; + } + notifyZoomChanged(); + } + public ViewNode getViewNode() { synchronized (this) { return viewNode; @@ -112,6 +138,12 @@ public class PixelPerfectModel { } } + public int getZoom() { + synchronized (this) { + return zoom; + } + } + public static interface ImageChangeListener { public void imageLoaded(); @@ -122,6 +154,8 @@ public class PixelPerfectModel { public void selectionChanged(); public void focusChanged(); + + public void zoomChanged(); } private ImageChangeListener[] getImageChangeListenerList() { @@ -182,6 +216,15 @@ public class PixelPerfectModel { } } + public void notifyZoomChanged() { + ImageChangeListener[] listeners = getImageChangeListenerList(); + if (listeners != null) { + for (int i = 0; i < listeners.length; i++) { + listeners[i].zoomChanged(); + } + } + } + public void addImageChangeListener(ImageChangeListener listener) { synchronized (imageChangeListeners) { imageChangeListeners.add(listener); diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/TreeViewModel.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/TreeViewModel.java new file mode 100644 index 0000000..890b88c --- /dev/null +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/TreeViewModel.java @@ -0,0 +1,174 @@ +/* + * 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.models; + +import com.android.hierarchyviewerlib.device.ViewNode; +import com.android.hierarchyviewerlib.device.Window; +import com.android.hierarchyviewerlib.scene.DrawableViewNode; +import com.android.hierarchyviewerlib.scene.DrawableViewNode.Point; +import com.android.hierarchyviewerlib.scene.DrawableViewNode.Rectangle; + +import java.util.ArrayList; + +public class TreeViewModel { + public static final double MAX_ZOOM = 2; + + public static final double MIN_ZOOM = 0.2; + + private Window window; + + private DrawableViewNode tree; + + private Rectangle viewport; + + private double zoom; + + private final ArrayList<TreeChangeListener> treeChangeListeners = + new ArrayList<TreeChangeListener>(); + + public void setData(Window window, ViewNode viewNode) { + synchronized (this) { + this.window = window; + tree = new DrawableViewNode(viewNode); + tree.setLeft(); + tree.placeRoot(); + viewport = null; + zoom = 1; + } + notifyTreeChanged(); + } + + public void setViewport(Rectangle viewport) { + synchronized (this) { + this.viewport = viewport; + } + notifyViewportChanged(); + } + + public void setZoom(double newZoom) { + Point zoomPoint = null; + synchronized (this) { + if (tree != null && viewport != null) { + zoomPoint = + new Point(viewport.x + viewport.width / 2, viewport.y + viewport.height / 2); + } + } + zoomOnPoint(newZoom, zoomPoint); + } + + public void zoomOnPoint(double newZoom, Point zoomPoint) { + synchronized (this) { + if (tree != null && this.viewport != null) { + if (newZoom < MIN_ZOOM) { + newZoom = MIN_ZOOM; + } + if (newZoom > MAX_ZOOM) { + newZoom = MAX_ZOOM; + } + viewport.x = zoomPoint.x - (zoomPoint.x - viewport.x) * zoom / newZoom; + viewport.y = zoomPoint.y - (zoomPoint.y - viewport.y) * zoom / newZoom; + viewport.width = viewport.width * zoom / newZoom; + viewport.height = viewport.height * zoom / newZoom; + zoom = newZoom; + } + } + notifyZoomChanged(); + } + + public DrawableViewNode getTree() { + synchronized (this) { + return tree; + } + } + + public Window getWindow() { + synchronized (this) { + return window; + } + } + + public Rectangle getViewport() { + synchronized (this) { + return viewport; + } + } + + public double getZoom() { + synchronized (this) { + return zoom; + } + } + + public static interface TreeChangeListener { + public void treeChanged(); + + public void viewportChanged(); + + public void zoomChanged(); + } + + private TreeChangeListener[] getTreeChangeListenerList() { + TreeChangeListener[] listeners = null; + synchronized (treeChangeListeners) { + if (treeChangeListeners.size() == 0) { + return null; + } + listeners = + treeChangeListeners.toArray(new TreeChangeListener[treeChangeListeners.size()]); + } + return listeners; + } + + public void notifyTreeChanged() { + TreeChangeListener[] listeners = getTreeChangeListenerList(); + if (listeners != null) { + for (int i = 0; i < listeners.length; i++) { + listeners[i].treeChanged(); + } + } + } + + public void notifyViewportChanged() { + TreeChangeListener[] listeners = getTreeChangeListenerList(); + if (listeners != null) { + for (int i = 0; i < listeners.length; i++) { + listeners[i].viewportChanged(); + } + } + } + + public void notifyZoomChanged() { + TreeChangeListener[] listeners = getTreeChangeListenerList(); + if (listeners != null) { + for (int i = 0; i < listeners.length; i++) { + listeners[i].zoomChanged(); + } + } + } + + public void addTreeChangeListener(TreeChangeListener listener) { + synchronized (treeChangeListeners) { + treeChangeListeners.add(listener); + } + } + + public void removeTreeChangeListener(TreeChangeListener listener) { + synchronized (treeChangeListeners) { + treeChangeListeners.remove(listener); + } + } +} diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/scene/DrawableViewNode.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/scene/DrawableViewNode.java new file mode 100644 index 0000000..aff3d6d --- /dev/null +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/scene/DrawableViewNode.java @@ -0,0 +1,249 @@ +/* + * 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.scene; + +import com.android.hierarchyviewerlib.device.ViewNode; + +import java.util.ArrayList; + +public class DrawableViewNode { + public ViewNode viewNode; + + public final ArrayList<DrawableViewNode> children = new ArrayList<DrawableViewNode>(); + + public final static int NODE_HEIGHT = 70; + + public final static int NODE_WIDTH = 100; + + public final static int LEAF_NODE_SPACING = 5; + + public final static int NON_LEAF_NODE_SPACING = 10; + + public final static int PARENT_CHILD_SPACING = 40; + + public final static int PADDING = 30; + + public int treeHeight; + + public int treeWidth; + + public boolean leaf; + + public DrawableViewNode parent; + + public int left; + + public double top; + + public int topSpacing; + + public int bottomSpacing; + + public static class Rectangle { + public double x, y, width, height; + + public Rectangle() { + + } + + public Rectangle(Rectangle other) { + this.x = other.x; + this.y = other.y; + this.width = other.width; + this.height = other.height; + } + + public Rectangle(double x, double y, double width, double height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + @Override + public String toString() { + return "{" + x + ", " + y + ", " + width + ", " + height + "}"; + } + + } + + public static class Point { + public double x, y; + + public Point() { + } + + public Point(double x, double y) { + this.x = x; + this.y = y; + } + } + + public Rectangle bounds = new Rectangle(); + + public DrawableViewNode(ViewNode viewNode) { + this.viewNode = viewNode; + if (viewNode.children.size() == 0) { + treeHeight = NODE_HEIGHT; + treeWidth = NODE_WIDTH; + leaf = true; + } else { + leaf = false; + int N = viewNode.children.size(); + treeHeight = 0; + treeWidth = 0; + for (int i = 0; i < N; i++) { + DrawableViewNode child = new DrawableViewNode(viewNode.children.get(i)); + children.add(child); + child.parent = this; + treeHeight += child.treeHeight; + treeWidth = Math.max(treeWidth, child.treeWidth); + if (i != 0) { + DrawableViewNode prevChild = children.get(i - 1); + if (prevChild.leaf && child.leaf) { + treeHeight += LEAF_NODE_SPACING; + prevChild.bottomSpacing = LEAF_NODE_SPACING; + child.topSpacing = LEAF_NODE_SPACING; + } else { + treeHeight += NON_LEAF_NODE_SPACING; + prevChild.bottomSpacing = NON_LEAF_NODE_SPACING; + child.topSpacing = NON_LEAF_NODE_SPACING; + } + } + } + treeWidth += NODE_WIDTH + PARENT_CHILD_SPACING; + } + } + + public void setLeft() { + if (parent == null) { + left = PADDING; + bounds.x = 0; + bounds.width = treeWidth + 2 * PADDING; + } else { + left = parent.left + NODE_WIDTH + PARENT_CHILD_SPACING; + } + int N = children.size(); + for (int i = 0; i < N; i++) { + children.get(i).setLeft(); + } + } + + public void placeRoot() { + top = PADDING + (treeHeight - NODE_HEIGHT) / 2.0; + double currentTop = PADDING; + int N = children.size(); + for (int i = 0; i < N; i++) { + DrawableViewNode child = children.get(i); + child.place(currentTop, top - currentTop); + currentTop += child.treeHeight + child.bottomSpacing; + } + bounds.y = 0; + bounds.height = treeHeight + 2 * PADDING; + } + + private void place(double treeTop, double rootDistance) { + if (treeHeight <= rootDistance) { + top = treeTop + treeHeight - NODE_HEIGHT; + } else if (rootDistance <= -NODE_HEIGHT) { + top = treeTop; + } else { + if (children.size() == 0) { + top = treeTop; + } else { + top = + rootDistance + treeTop - NODE_HEIGHT + (2.0 * NODE_HEIGHT) + / (treeHeight + NODE_HEIGHT) * (treeHeight - rootDistance); + } + } + int N = children.size(); + double currentTop = treeTop; + for (int i = 0; i < N; i++) { + DrawableViewNode child = children.get(i); + child.place(currentTop, rootDistance); + currentTop += child.treeHeight + child.bottomSpacing; + rootDistance -= child.treeHeight + child.bottomSpacing; + } + } + + public DrawableViewNode getSelected(double x, double y) { + if (x >= left && x < left + NODE_WIDTH && y >= top && y <= top + NODE_HEIGHT) { + return this; + } + int N = children.size(); + for (int i = 0; i < N; i++) { + DrawableViewNode selected = children.get(i).getSelected(x, y); + if (selected != null) { + return selected; + } + } + return null; + } + + /* + * Moves the node the specified distance up. + */ + public void move(double distance) { + top -= distance; + + // Get the root + DrawableViewNode root = this; + while (root.parent != null) { + root = root.parent; + } + + // Figure out the new tree top. + double treeTop; + if (top + NODE_HEIGHT <= root.top) { + treeTop = top + NODE_HEIGHT - treeHeight; + } else if (top >= root.top + NODE_HEIGHT) { + treeTop = top; + } else { + if (leaf) { + treeTop = top; + } else { + double distanceRatio = 1 - (root.top + NODE_HEIGHT - top) / (2.0 * NODE_HEIGHT); + treeTop = root.top - treeHeight + distanceRatio * (treeHeight + NODE_HEIGHT); + } + } + // Go up the tree and figure out the tree top. + DrawableViewNode node = this; + while (node.parent != null) { + int index = node.viewNode.index; + for (int i = 0; i < index; i++) { + DrawableViewNode sibling = node.parent.children.get(i); + treeTop -= sibling.treeHeight + sibling.bottomSpacing; + } + node = node.parent; + } + + // Update the bounds. + root.bounds.y = Math.min(root.top - PADDING, treeTop - PADDING); + root.bounds.height = + Math.max(treeTop + root.treeHeight + PADDING, root.top + NODE_HEIGHT + PADDING) + - root.bounds.y; + // Place all the children of the root + double currentTop = treeTop; + int N = root.children.size(); + for (int i = 0; i < N; i++) { + DrawableViewNode child = root.children.get(i); + child.place(currentTop, root.top - currentTop); + currentTop += child.treeHeight + child.bottomSpacing; + } + + } +} diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/DeviceSelector.java b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/DeviceSelector.java index 60d8e6d..187a3fa 100644 --- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/DeviceSelector.java +++ b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/DeviceSelector.java @@ -36,13 +36,14 @@ import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; -public class DeviceSelector implements WindowChangeListener, SelectionListener { +public class DeviceSelector extends Composite implements WindowChangeListener, SelectionListener { private TreeViewer treeViewer; private Tree tree; @@ -144,7 +145,9 @@ public class DeviceSelector implements WindowChangeListener, SelectionListener { } public DeviceSelector(Composite parent) { - treeViewer = new TreeViewer(parent, SWT.SINGLE); + super(parent, SWT.NONE); + setLayout(new FillLayout()); + treeViewer = new TreeViewer(this, SWT.SINGLE); treeViewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS); tree = treeViewer.getTree(); @@ -189,13 +192,16 @@ public class DeviceSelector implements WindowChangeListener, SelectionListener { .getSystemColor(SWT.COLOR_BLUE)); } - public void terminate() { + @Override + public void dispose() { + super.dispose(); model.removeWindowChangeListener(this); boldFont.dispose(); } - public void setFocus() { - tree.setFocus(); + @Override + public boolean setFocus() { + return tree.setFocus(); } public void deviceConnected(final IDevice device) { @@ -246,6 +252,8 @@ public class DeviceSelector implements WindowChangeListener, SelectionListener { Object selection = ((TreeItem) e.item).getData(); if (selection instanceof IDevice) { ComponentRegistry.getDirector().loadPixelPerfectData((IDevice) selection); + } else if (selection instanceof Window) { + ComponentRegistry.getDirector().loadViewTreeData((Window) selection); } } diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfect.java b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfect.java index e4345e1..3972830 100644 --- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfect.java +++ b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfect.java @@ -39,13 +39,11 @@ import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; -public class PixelPerfect implements ImageChangeListener { +public class PixelPerfect extends ScrolledComposite implements ImageChangeListener { private Canvas canvas; private PixelPerfectModel model; - private ScrolledComposite scrolledComposite; - private Image image; private Color crosshairColor; @@ -65,11 +63,11 @@ public class PixelPerfect implements ImageChangeListener { private ViewNode selectedNode; public PixelPerfect(Composite parent) { - scrolledComposite = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL); - canvas = new Canvas(scrolledComposite, SWT.NONE); - scrolledComposite.setContent(canvas); - scrolledComposite.setExpandHorizontal(true); - scrolledComposite.setExpandVertical(true); + super(parent, SWT.H_SCROLL | SWT.V_SCROLL); + canvas = new Canvas(this, SWT.NONE); + setContent(canvas); + setExpandHorizontal(true); + setExpandVertical(true); model = ComponentRegistry.getPixelPerfectModel(); model.addImageChangeListener(this); @@ -83,7 +81,9 @@ public class PixelPerfect implements ImageChangeListener { paddingColor = new Color(Display.getDefault(), new RGB(0, 0, 255)); } - public void terminate() { + @Override + public void dispose() { + super.dispose(); if (image != null) { image.dispose(); } @@ -92,8 +92,9 @@ public class PixelPerfect implements ImageChangeListener { paddingColor.dispose(); } - public void setFocus() { - canvas.setFocus(); + @Override + public boolean setFocus() { + return canvas.setFocus(); } private MouseListener mouseListener = new MouseListener() { @@ -220,7 +221,7 @@ public class PixelPerfect implements ImageChangeListener { } }; - private void redraw() { + private void doRedraw() { Display.getDefault().asyncExec(new Runnable() { public void run() { canvas.redraw(); @@ -252,7 +253,7 @@ public class PixelPerfect implements ImageChangeListener { } Display.getDefault().asyncExec(new Runnable() { public void run() { - scrolledComposite.setMinSize(width, height); + setMinSize(width, height); } }); } @@ -263,28 +264,28 @@ public class PixelPerfect implements ImageChangeListener { crosshairLocation = model.getCrosshairLocation(); selectedNode = model.getSelected(); } - redraw(); + doRedraw(); } public void imageChanged() { synchronized (this) { loadImage(); } - redraw(); + doRedraw(); } public void crosshairMoved() { synchronized (this) { crosshairLocation = model.getCrosshairLocation(); } - redraw(); + doRedraw(); } public void selectionChanged() { synchronized (this) { selectedNode = model.getSelected(); } - redraw(); + doRedraw(); } public void focusChanged() { @@ -292,6 +293,10 @@ public class PixelPerfect implements ImageChangeListener { loadImage(); selectedNode = model.getSelected(); } - redraw(); + doRedraw(); + } + + public void zoomChanged() { + // pass } } diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectLoupe.java b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectLoupe.java index eb1df88..62bb8c7 100644 --- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectLoupe.java +++ b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectLoupe.java @@ -25,6 +25,7 @@ import com.android.hierarchyviewerlib.models.PixelPerfectModel.Point; import org.eclipse.swt.SWT; 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; @@ -39,9 +40,7 @@ import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; -public class PixelPerfectLoupe implements ImageChangeListener { - private Canvas canvas; - +public class PixelPerfectLoupe extends Canvas implements ImageChangeListener { private PixelPerfectModel model; private Image image; @@ -65,20 +64,22 @@ public class PixelPerfectLoupe implements ImageChangeListener { private int canvasHeight; public PixelPerfectLoupe(Composite parent) { - canvas = new Canvas(parent, SWT.NONE); + super(parent, SWT.NONE); model = ComponentRegistry.getPixelPerfectModel(); model.addImageChangeListener(this); - canvas.addPaintListener(paintListener); - canvas.addMouseListener(mouseListener); + addPaintListener(paintListener); + addMouseListener(mouseListener); + addMouseWheelListener(mouseWheelListener); crosshairColor = new Color(Display.getDefault(), new RGB(255, 94, 254)); transform = new Transform(Display.getDefault()); - zoom = 8; } - public void terminate() { + @Override + public void dispose() { + super.dispose(); if (image != null) { image.dispose(); } @@ -89,23 +90,6 @@ public class PixelPerfectLoupe implements ImageChangeListener { } } - public void setFocus() { - canvas.setFocus(); - } - - public void setZoom(int value) { - synchronized (this) { - if (grid != null) { - // To notify that the zoom level has changed, we get rid of the - // grid. - grid.dispose(); - grid = null; - zoom = value; - } - } - redraw(); - } - private MouseListener mouseListener = new MouseListener() { public void mouseDoubleClick(MouseEvent e) { @@ -113,15 +97,33 @@ public class PixelPerfectLoupe implements ImageChangeListener { } public void mouseDown(MouseEvent e) { - // pass + handleMouseEvent(e); } public void mouseUp(MouseEvent e) { - handleMouseEvent(e); + // } }; + private MouseWheelListener mouseWheelListener = new MouseWheelListener() { + public void mouseScrolled(MouseEvent e) { + int newZoom = -1; + synchronized (this) { + if (image != null && crosshairLocation != null) { + if (e.count > 0) { + newZoom = zoom + 1; + } else { + newZoom = zoom - 1; + } + } + } + if (newZoom != -1) { + model.setZoom(newZoom); + } + } + }; + private void handleMouseEvent(MouseEvent e) { int newX = -1; int newY = -1; @@ -129,8 +131,8 @@ public class PixelPerfectLoupe implements ImageChangeListener { if (image == null) { return; } - int zoomedX = -crosshairLocation.x * zoom - zoom / 2 + canvas.getBounds().width / 2; - int zoomedY = -crosshairLocation.y * zoom - zoom / 2 + canvas.getBounds().height / 2; + int zoomedX = -crosshairLocation.x * zoom - zoom / 2 + getBounds().width / 2; + int zoomedY = -crosshairLocation.y * zoom - zoom / 2 + getBounds().height / 2; int x = (e.x - zoomedX) / zoom; int y = (e.y - zoomedY) / zoom; if (x >= 0 && x < width && y >= 0 && y < height) { @@ -147,12 +149,10 @@ public class PixelPerfectLoupe implements ImageChangeListener { public void paintControl(PaintEvent e) { synchronized (this) { e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); - e.gc.fillRectangle(0, 0, canvas.getSize().x, canvas.getSize().y); + e.gc.fillRectangle(0, 0, getSize().x, getSize().y); if (image != null && crosshairLocation != null) { - int zoomedX = - -crosshairLocation.x * zoom - zoom / 2 + canvas.getBounds().width / 2; - int zoomedY = - -crosshairLocation.y * zoom - zoom / 2 + canvas.getBounds().height / 2; + int zoomedX = -crosshairLocation.x * zoom - zoom / 2 + getBounds().width / 2; + int zoomedY = -crosshairLocation.y * zoom - zoom / 2 + getBounds().height / 2; transform.translate(zoomedX, zoomedY); transform.scale(zoom, zoom); e.gc.setInterpolation(SWT.NONE); @@ -164,13 +164,12 @@ public class PixelPerfectLoupe implements ImageChangeListener { // If the size of the canvas has changed, we need to make // another grid. if (grid != null - && (canvasWidth != canvas.getBounds().width || canvasHeight != canvas - .getBounds().height)) { + && (canvasWidth != getBounds().width || canvasHeight != getBounds().height)) { grid.dispose(); grid = null; } - canvasWidth = canvas.getBounds().width; - canvasHeight = canvas.getBounds().height; + canvasWidth = getBounds().width; + canvasHeight = getBounds().height; if (grid == null) { // Make a transparent image; ImageData imageData = @@ -208,10 +207,10 @@ public class PixelPerfectLoupe implements ImageChangeListener { } }; - private void redraw() { + private void doRedraw() { Display.getDefault().asyncExec(new Runnable() { public void run() { - canvas.redraw(); + redraw(); } }); } @@ -243,22 +242,23 @@ public class PixelPerfectLoupe implements ImageChangeListener { synchronized (this) { loadImage(); crosshairLocation = model.getCrosshairLocation(); + zoom = model.getZoom(); } - redraw(); + doRedraw(); } public void imageChanged() { synchronized (this) { loadImage(); } - redraw(); + doRedraw(); } public void crosshairMoved() { synchronized (this) { crosshairLocation = model.getCrosshairLocation(); } - redraw(); + doRedraw(); } public void selectionChanged() { @@ -268,4 +268,17 @@ public class PixelPerfectLoupe implements ImageChangeListener { public void focusChanged() { imageChanged(); } + + public void zoomChanged() { + synchronized (this) { + if (grid != null) { + // To notify that the zoom level has changed, we get rid of the + // grid. + grid.dispose(); + grid = null; + zoom = model.getZoom(); + } + } + doRedraw(); + } } diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectTree.java b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectTree.java index a85c8eb..9f25a33 100644 --- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectTree.java +++ b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectTree.java @@ -25,12 +25,14 @@ import com.android.hierarchyviewerlib.models.PixelPerfectModel.ImageChangeListen import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Tree; @@ -38,7 +40,7 @@ import org.eclipse.swt.widgets.TreeColumn; import java.util.List; -public class PixelPerfectTree implements ImageChangeListener, SelectionListener { +public class PixelPerfectTree extends Composite implements ImageChangeListener, SelectionListener { private TreeViewer treeViewer; @@ -126,7 +128,9 @@ public class PixelPerfectTree implements ImageChangeListener, SelectionListener } public PixelPerfectTree(Composite parent) { - treeViewer = new TreeViewer(parent, SWT.SINGLE); + super(parent, SWT.NONE); + setLayout(new FillLayout()); + treeViewer = new TreeViewer(this, SWT.SINGLE); treeViewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS); tree = treeViewer.getTree(); @@ -154,13 +158,16 @@ public class PixelPerfectTree implements ImageChangeListener, SelectionListener folderImage = loader.loadImage("folder.png", Display.getDefault()); } - public void terminate() { + @Override + public void dispose() { + super.dispose(); fileImage.dispose(); folderImage.dispose(); } - public void setFocus() { - tree.setFocus(); + @Override + public boolean setFocus() { + return tree.setFocus(); } public void imageLoaded() { @@ -193,7 +200,16 @@ public class PixelPerfectTree implements ImageChangeListener, SelectionListener } public void widgetSelected(SelectionEvent e) { - model.setSelected((ViewNode) e.item.getData()); + // To combat phantom selection... + if (((TreeSelection) treeViewer.getSelection()).isEmpty()) { + model.setSelected(null); + } else { + model.setSelected((ViewNode) e.item.getData()); + } + } + + public void zoomChanged() { + // pass } } diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/TreeView.java b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/TreeView.java new file mode 100644 index 0000000..a29bdb3 --- /dev/null +++ b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/TreeView.java @@ -0,0 +1,333 @@ +/* + * 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.hierarchyvieweruilib; + +import com.android.hierarchyviewerlib.ComponentRegistry; +import com.android.hierarchyviewerlib.models.TreeViewModel; +import com.android.hierarchyviewerlib.models.TreeViewModel.TreeChangeListener; +import com.android.hierarchyviewerlib.scene.DrawableViewNode; +import com.android.hierarchyviewerlib.scene.DrawableViewNode.Point; +import com.android.hierarchyviewerlib.scene.DrawableViewNode.Rectangle; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.MouseWheelListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.GC; +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 TreeView extends Canvas implements TreeChangeListener { + + private TreeViewModel model; + + private DrawableViewNode tree; + + private Rectangle viewport; + + private Transform transform; + + private Transform inverse; + + private double zoom; + + private Point lastPoint; + + private DrawableViewNode draggedNode; + + public TreeView(Composite parent) { + super(parent, SWT.NONE); + + model = ComponentRegistry.getTreeViewModel(); + model.addTreeChangeListener(this); + + addPaintListener(paintListener); + addMouseListener(mouseListener); + addMouseMoveListener(mouseMoveListener); + addMouseWheelListener(mouseWheelListener); + addListener(SWT.Resize, resizeListener); + + transform = new Transform(Display.getDefault()); + inverse = new Transform(Display.getDefault()); + } + + @Override + public void dispose() { + super.dispose(); + transform.dispose(); + inverse.dispose(); + } + + private Listener resizeListener = new Listener() { + public void handleEvent(Event e) { + synchronized (this) { + if (tree != null && viewport != null) { + + // I don't know what the best behaviour is... This seems + // like a good idea. + Point viewCenter = + new Point(viewport.x + viewport.width / 2, viewport.y + viewport.height + / 2); + viewport.width = getBounds().width / zoom; + viewport.height = getBounds().height / zoom; + viewport.x = viewCenter.x - viewport.width / 2; + viewport.y = viewCenter.y - viewport.height / 2; + } + } + if (viewport != null) { + model.setViewport(viewport); + } + } + }; + + private MouseListener mouseListener = new MouseListener() { + + public void mouseDoubleClick(MouseEvent e) { + // pass + } + + public void mouseDown(MouseEvent e) { + synchronized (this) { + if (tree != null && viewport != null) { + Point pt = transformPoint(e.x, e.y); + draggedNode = tree.getSelected(pt.x, pt.y); + if (draggedNode == tree) { + draggedNode = null; + } + if (draggedNode != null) { + lastPoint = pt; + } else { + lastPoint = new Point(e.x, e.y); + } + } + } + } + + public void mouseUp(MouseEvent e) { + boolean redraw = false; + boolean viewportChanged = false; + synchronized (this) { + if (tree != null && viewport != null && lastPoint != null) { + if (draggedNode == null) { + handleMouseDrag(new Point(e.x, e.y)); + viewportChanged = true; + } else { + handleMouseDrag(transformPoint(e.x, e.y)); + } + lastPoint = null; + draggedNode = null; + redraw = true; + } + } + if (viewportChanged) { + model.setViewport(viewport); + } else if (redraw) { + model.removeTreeChangeListener(TreeView.this); + model.notifyTreeChanged(); + model.addTreeChangeListener(TreeView.this); + doRedraw(); + } + } + + }; + + private MouseMoveListener mouseMoveListener = new MouseMoveListener() { + public void mouseMove(MouseEvent e) { + boolean redraw = false; + boolean viewportChanged = false; + synchronized (this) { + if (tree != null && viewport != null && lastPoint != null) { + if (draggedNode == null) { + handleMouseDrag(new Point(e.x, e.y)); + viewportChanged = true; + } else { + handleMouseDrag(transformPoint(e.x, e.y)); + } + redraw = true; + } + } + if (viewportChanged) { + model.setViewport(viewport); + } else if (redraw) { + model.removeTreeChangeListener(TreeView.this); + model.notifyTreeChanged(); + model.addTreeChangeListener(TreeView.this); + doRedraw(); + } + } + }; + + private void handleMouseDrag(Point pt) { + if (draggedNode != null) { + draggedNode.move(lastPoint.y - pt.y); + lastPoint = pt; + return; + } + double xDif = (lastPoint.x - pt.x) / zoom; + double yDif = (lastPoint.y - pt.y) / zoom; + + if (viewport.width > tree.bounds.width) { + if (xDif < 0 && viewport.x + viewport.width > tree.bounds.x + tree.bounds.width) { + viewport.x = + Math.max(viewport.x + xDif, tree.bounds.x + tree.bounds.width + - viewport.width); + } else if (xDif > 0 && viewport.x < tree.bounds.x) { + viewport.x = Math.min(viewport.x + xDif, tree.bounds.x); + } + } else { + if (xDif < 0 && viewport.x > tree.bounds.x) { + viewport.x = Math.max(viewport.x + xDif, tree.bounds.x); + } else if (xDif > 0 && viewport.x + viewport.width < tree.bounds.x + tree.bounds.width) { + viewport.x = + Math.min(viewport.x + xDif, tree.bounds.x + tree.bounds.width + - viewport.width); + } + } + if (viewport.height > tree.bounds.height) { + if (yDif < 0 && viewport.y + viewport.height > tree.bounds.y + tree.bounds.height) { + viewport.y = + Math.max(viewport.y + yDif, tree.bounds.y + tree.bounds.height + - viewport.height); + } else if (yDif > 0 && viewport.y < tree.bounds.y) { + viewport.y = Math.min(viewport.y + yDif, tree.bounds.y); + } + } else { + if (yDif < 0 && viewport.y > tree.bounds.y) { + viewport.y = Math.max(viewport.y + yDif, tree.bounds.y); + } else if (yDif > 0 + && viewport.y + viewport.height < tree.bounds.y + tree.bounds.height) { + viewport.y = + Math.min(viewport.y + yDif, tree.bounds.y + tree.bounds.height + - viewport.height); + } + } + lastPoint = pt; + } + + private Point transformPoint(double x, double y) { + float[] pt = { + (float) x, (float) y + }; + inverse.transform(pt); + return new Point(pt[0], pt[1]); + } + + private MouseWheelListener mouseWheelListener = new MouseWheelListener() { + + public void mouseScrolled(MouseEvent e) { + Point zoomPoint = null; + synchronized (this) { + if (tree != null && viewport != null) { + zoom += Math.ceil(e.count / 3.0) * 0.1; + zoomPoint = transformPoint(e.x, e.y); + } + } + if (zoomPoint != null) { + model.zoomOnPoint(zoom, zoomPoint); + } + } + }; + + private PaintListener paintListener = new PaintListener() { + public void paintControl(PaintEvent e) { + synchronized (this) { + e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); + e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height); + if (tree != null && viewport != null) { + e.gc.setTransform(transform); + paintRecursive(e.gc, tree); + } + } + } + }; + + static void paintRecursive(GC gc, DrawableViewNode node) { + gc.drawRectangle(node.left, (int) Math.round(node.top), DrawableViewNode.NODE_WIDTH, + DrawableViewNode.NODE_HEIGHT); + int N = node.children.size(); + for (int i = 0; i < N; i++) { + DrawableViewNode child = node.children.get(i); + paintRecursive(gc, child); + gc.drawLine(node.left + DrawableViewNode.NODE_WIDTH, (int) Math.round(node.top) + + DrawableViewNode.NODE_HEIGHT / 2, child.left, (int) Math.round(child.top) + + DrawableViewNode.NODE_HEIGHT / 2); + } + } + + private void doRedraw() { + Display.getDefault().syncExec(new Runnable() { + public void run() { + redraw(); + } + }); + } + + public void treeChanged() { + synchronized (this) { + tree = model.getTree(); + if (tree == null) { + viewport = null; + } else { + Display.getDefault().syncExec(new Runnable() { + public void run() { + viewport = + new Rectangle((tree.bounds.width - getBounds().width) / 2, + (tree.bounds.height - getBounds().height) / 2, + getBounds().width, getBounds().height); + } + }); + } + } + if (viewport != null) { + model.setViewport(viewport); + } + } + + private void setTransform() { + if (viewport != null && tree != null) { + // Set the transform. + transform.identity(); + inverse.identity(); + + transform.scale((float) zoom, (float) zoom); + inverse.scale((float) zoom, (float) zoom); + transform.translate((float) -viewport.x, (float) -viewport.y); + inverse.translate((float) -viewport.x, (float) -viewport.y); + inverse.invert(); + } + } + + public void viewportChanged() { + synchronized (this) { + viewport = model.getViewport(); + zoom = model.getZoom(); + setTransform(); + } + doRedraw(); + } + + public void zoomChanged() { + viewportChanged(); + } +} diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/TreeViewOverview.java b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/TreeViewOverview.java new file mode 100644 index 0000000..cfa9070 --- /dev/null +++ b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/TreeViewOverview.java @@ -0,0 +1,269 @@ +/* + * 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.hierarchyvieweruilib; + +import com.android.hierarchyviewerlib.ComponentRegistry; +import com.android.hierarchyviewerlib.models.TreeViewModel; +import com.android.hierarchyviewerlib.models.TreeViewModel.TreeChangeListener; +import com.android.hierarchyviewerlib.scene.DrawableViewNode; +import com.android.hierarchyviewerlib.scene.DrawableViewNode.Point; +import com.android.hierarchyviewerlib.scene.DrawableViewNode.Rectangle; + +import org.eclipse.swt.SWT; +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.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 TreeChangeListener { + + private TreeViewModel model; + + private DrawableViewNode tree; + + private Rectangle viewport; + + private Transform transform; + + private Transform inverse; + + private Rectangle bounds = new Rectangle(); + + private double scale; + + private boolean dragging = false; + + public TreeViewOverview(Composite parent) { + super(parent, SWT.NONE); + + model = ComponentRegistry.getTreeViewModel(); + model.addTreeChangeListener(this); + + addPaintListener(paintListener); + addMouseListener(mouseListener); + addMouseMoveListener(mouseMoveListener); + addListener(SWT.Resize, resizeListener); + + transform = new Transform(Display.getDefault()); + inverse = new Transform(Display.getDefault()); + } + + @Override + public void dispose() { + super.dispose(); + transform.dispose(); + inverse.dispose(); + } + + private MouseListener mouseListener = new MouseListener() { + + public void mouseDoubleClick(MouseEvent e) { + // pass + } + + public void mouseDown(MouseEvent e) { + boolean redraw = false; + synchronized (this) { + if (tree != null && viewport != null) { + dragging = true; + redraw = true; + handleMouseEvent(transformPoint(e.x, e.y)); + } + } + if (redraw) { + model.removeTreeChangeListener(TreeViewOverview.this); + model.setViewport(viewport); + model.addTreeChangeListener(TreeViewOverview.this); + doRedraw(); + } + } + + public void mouseUp(MouseEvent e) { + boolean redraw = false; + synchronized (this) { + if (tree != null && viewport != null) { + dragging = false; + redraw = true; + handleMouseEvent(transformPoint(e.x, e.y)); + setBounds(); + setTransform(); + } + } + if (redraw) { + model.removeTreeChangeListener(TreeViewOverview.this); + model.setViewport(viewport); + model.addTreeChangeListener(TreeViewOverview.this); + doRedraw(); + } + } + + }; + + private MouseMoveListener mouseMoveListener = new MouseMoveListener() { + public void mouseMove(MouseEvent e) { + boolean moved = false; + synchronized (this) { + if (dragging) { + moved = true; + handleMouseEvent(transformPoint(e.x, e.y)); + } + } + if (moved) { + model.removeTreeChangeListener(TreeViewOverview.this); + model.setViewport(viewport); + model.addTreeChangeListener(TreeViewOverview.this); + doRedraw(); + } + } + }; + + private void handleMouseEvent(Point pt) { + viewport.x = pt.x - viewport.width / 2; + viewport.y = pt.y - viewport.height / 2; + if (viewport.x < bounds.x) { + viewport.x = bounds.x; + } + if (viewport.y < bounds.y) { + viewport.y = bounds.y; + } + if (viewport.x + viewport.width > bounds.x + bounds.width) { + viewport.x = bounds.x + bounds.width - viewport.width; + } + if (viewport.y + viewport.height > bounds.y + bounds.height) { + viewport.y = bounds.y + bounds.height - viewport.height; + } + } + + private Point transformPoint(double x, double y) { + float[] pt = { + (float) x, (float) y + }; + inverse.transform(pt); + return new Point(pt[0], pt[1]); + } + + private Listener resizeListener = new Listener() { + public void handleEvent(Event arg0) { + synchronized (this) { + setTransform(); + } + doRedraw(); + } + }; + + private PaintListener paintListener = new PaintListener() { + public void paintControl(PaintEvent e) { + if (tree != null && viewport != null) { + e.gc.setTransform(transform); + e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); + e.gc.fillRectangle((int) bounds.x, (int) bounds.y, (int) Math.ceil(bounds.width), + (int) Math.ceil(bounds.height)); + TreeView.paintRecursive(e.gc, tree); + + e.gc.setAlpha(100); + e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY)); + e.gc.fillRectangle((int) viewport.x, (int) viewport.y, (int) Math + .ceil(viewport.width), (int) Math.ceil(viewport.height)); + + e.gc.setAlpha(255); + e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); + e.gc.setLineWidth((int) Math.ceil(2 / scale)); + e.gc.drawRectangle((int) viewport.x, (int) viewport.y, (int) Math + .ceil(viewport.width), (int) Math.ceil(viewport.height)); + } + } + }; + + private void doRedraw() { + Display.getDefault().syncExec(new Runnable() { + public void run() { + redraw(); + } + }); + } + + public void treeChanged() { + synchronized (this) { + tree = model.getTree(); + setBounds(); + setTransform(); + } + doRedraw(); + } + + private void setBounds() { + if (viewport != null && tree != null) { + bounds.x = Math.min(viewport.x, tree.bounds.x); + bounds.y = Math.min(viewport.y, tree.bounds.y); + bounds.width = + Math.max(viewport.x + viewport.width, tree.bounds.x + tree.bounds.width) + - bounds.x; + bounds.height = + Math.max(viewport.y + viewport.height, tree.bounds.y + tree.bounds.height) + - bounds.y; + } + } + + private void setTransform() { + if (viewport != null && tree != null) { + + transform.identity(); + inverse.identity(); + final Point size = new Point(); + Display.getDefault().syncExec(new Runnable() { + public void run() { + size.x = getBounds().width; + size.y = getBounds().height; + } + }); + scale = Math.min(size.x / bounds.width, size.y / bounds.height); + transform.scale((float) scale, (float) scale); + inverse.scale((float) scale, (float) scale); + transform.translate((float) -bounds.x, (float) -bounds.y); + inverse.translate((float) -bounds.x, (float) -bounds.y); + if (size.x / bounds.width < size.y / bounds.height) { + transform.translate(0, (float) (size.y / scale - bounds.height) / 2); + inverse.translate(0, (float) (size.y / scale - bounds.height) / 2); + } else { + transform.translate((float) (size.x / scale - bounds.width) / 2, 0); + inverse.translate((float) (size.x / scale - bounds.width) / 2, 0); + } + inverse.invert(); + } + } + + public void viewportChanged() { + synchronized (this) { + viewport = model.getViewport(); + setBounds(); + setTransform(); + } + doRedraw(); + } + + public void zoomChanged() { + viewportChanged(); + } + +} |