Tweaking and moving.

Change-Id: I9906268a91c53c7b9e938b9c969cedeae2a4303a
diff --git a/hierarchyviewer2/libs/Android.mk b/hierarchyviewer2/libs/Android.mk
index 7d5c631..f4b34cd 100644
--- a/hierarchyviewer2/libs/Android.mk
+++ b/hierarchyviewer2/libs/Android.mk
@@ -14,4 +14,3 @@
 
 HIERARCHYVIEWERLIBS_LOCAL_DIR := $(call my-dir)
 include $(HIERARCHYVIEWERLIBS_LOCAL_DIR)/hierarchyviewerlib/Android.mk
-include $(HIERARCHYVIEWERLIBS_LOCAL_DIR)/hierarchyvieweruilib/Android.mk
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/.classpath b/hierarchyviewer2/libs/hierarchyviewerlib/.classpath
index b0326c8..ef57724 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/.classpath
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/.classpath
@@ -3,5 +3,7 @@
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/ddmuilib"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk b/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk
index 1be1a29..922e337 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk
@@ -16,8 +16,13 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_JAVA_RESOURCE_DIRS := resources
 
-LOCAL_JAVA_LIBRARIES := ddmlib
+LOCAL_JAVA_LIBRARIES := ddmlib \
+    ddmuilib \
+    hierarchyviewerlib \
+    swt \
+    org.eclipse.jface_3.4.2.M20090107-0800
 
 LOCAL_MODULE := hierarchyviewerlib
 
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java
index 65cb24f..48a45dd 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java
@@ -28,6 +28,16 @@
 import com.android.hierarchyviewerlib.device.WindowUpdater;
 import com.android.hierarchyviewerlib.device.DeviceBridge.ViewServerInfo;
 import com.android.hierarchyviewerlib.device.WindowUpdater.IWindowChangeListener;
+import com.android.hierarchyviewerlib.ui.CaptureDisplay;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.PaletteData;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
 
 import java.io.IOException;
 
@@ -175,19 +185,12 @@
                 public void run() {
                     if (ComponentRegistry.getDeviceSelectionModel().getFocusedWindow(device) != -1
                             && device == ComponentRegistry.getPixelPerfectModel().getDevice()) {
-                        try {
-                            ViewNode viewNode =
-                                    DeviceBridge.loadWindowData(Window.getFocusedWindow(device));
-                            RawImage screenshot = device.getScreenshot();
-                            ComponentRegistry.getPixelPerfectModel().setFocusData(screenshot,
+                        ViewNode viewNode =
+                                DeviceBridge.loadWindowData(Window.getFocusedWindow(device));
+                        Image screenshotImage = getScreenshotImage(device);
+                        if (screenshotImage != null) {
+                            ComponentRegistry.getPixelPerfectModel().setFocusData(screenshotImage,
                                     viewNode);
-                        } catch (IOException e) {
-                            Log.e(TAG, "Unable to load screenshot from device " + device);
-                        } catch (TimeoutException e) {
-                            Log.e(TAG, "Timeout loading screenshot from device " + device);
-                        } catch (AdbCommandRejectedException e) {
-                            Log.e(TAG, "Adb rejected command to load screenshot from device "
-                                    + device);
                         }
                     }
                     synchronized (HierarchyViewerDirector.this) {
@@ -202,22 +205,48 @@
     public void loadPixelPerfectData(final IDevice device) {
         executeInBackground(new Runnable() {
             public void run() {
-                try {
-                    RawImage screenshot = device.getScreenshot();
+                Image screenshotImage = getScreenshotImage(device);
+                if (screenshotImage != null) {
                     ViewNode viewNode =
                             DeviceBridge.loadWindowData(Window.getFocusedWindow(device));
-                    ComponentRegistry.getPixelPerfectModel().setData(device, screenshot, viewNode);
-                } catch (IOException e) {
-                    Log.e(TAG, "Unable to load screenshot from device " + device);
-                } catch (TimeoutException e) {
-                    Log.e(TAG, "Timeout loading screenshot from device " + device);
-                } catch (AdbCommandRejectedException e) {
-                    Log.e(TAG, "Adb rejected command to load screenshot from device " + device);
+                    ComponentRegistry.getPixelPerfectModel().setData(device, screenshotImage,
+                            viewNode);
                 }
             }
         });
     }
 
+    private Image getScreenshotImage(IDevice device) {
+        try {
+            final RawImage screenshot = device.getScreenshot();
+            if (screenshot == null) {
+                return null;
+            }
+            class ImageContainer {
+                public Image image;
+            }
+            final ImageContainer imageContainer = new ImageContainer();
+            Display.getDefault().syncExec(new Runnable() {
+                public void run() {
+                    ImageData imageData =
+                            new ImageData(screenshot.width, screenshot.height, screenshot.bpp,
+                                    new PaletteData(screenshot.getRedMask(), screenshot
+                                            .getGreenMask(), screenshot.getBlueMask()), 1,
+                                    screenshot.data);
+                    imageContainer.image = new Image(Display.getDefault(), imageData);
+                }
+            });
+            return imageContainer.image;
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to load screenshot from device " + device);
+        } catch (TimeoutException e) {
+            Log.e(TAG, "Timeout loading screenshot from device " + device);
+        } catch (AdbCommandRejectedException e) {
+            Log.e(TAG, "Adb rejected command to load screenshot from device " + device);
+        }
+        return null;
+    }
+
     public void loadViewTreeData(final Window window) {
         executeInBackground(new Runnable() {
             public void run() {
@@ -230,4 +259,47 @@
             }
         });
     }
+
+    public void loadOverlay(final Shell shell) {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                FileDialog fileDialog = new FileDialog(shell, SWT.OPEN);
+                fileDialog.setFilterExtensions(new String[] {
+                    "*.jpg;*.jpeg;*.png;*.gif;*.bmp"
+                });
+                fileDialog.setFilterNames(new String[] {
+                    "Image (*.jpg, *.jpeg, *.png, *.gif, *.bmp)"
+                });
+                String fileName = fileDialog.open();
+                if (fileName != null) {
+                    try {
+                        Image image = new Image(Display.getDefault(), fileName);
+                        ComponentRegistry.getPixelPerfectModel().setOverlayImage(image);
+                    } catch (SWTException e) {
+                        Log.e(TAG, "Unable to load image from " + fileName);
+                    }
+                }
+            }
+        });
+    }
+
+    public void showCapture(final Shell shell, final ViewNode viewNode) {
+        executeInBackground(new Runnable() {
+            public void run() {
+                final Image image = DeviceBridge.loadCapture(viewNode.window, viewNode);
+                if (image != null) {
+                    viewNode.image = image;
+
+                    // Force the layout viewer to redraw.
+                    ComponentRegistry.getTreeViewModel().notifySelectionChanged();
+
+                    Display.getDefault().asyncExec(new Runnable() {
+                        public void run() {
+                            CaptureDisplay.show(shell, viewNode, image);
+                        }
+                    });
+                }
+            }
+        });
+    }
 }
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 af3f9f1..23c6f07 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java
@@ -24,6 +24,9 @@
 import com.android.ddmlib.ShellCommandUnresponsiveException;
 import com.android.ddmlib.TimeoutException;
 
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -413,7 +416,7 @@
                     currentNode = currentNode.parent;
                     currentDepth--;
                 }
-                currentNode = new ViewNode(currentNode, line.substring(depth));
+                currentNode = new ViewNode(window, currentNode, line.substring(depth));
                 currentDepth = depth;
             }
             if (currentNode == null) {
@@ -447,7 +450,11 @@
             if (protocol < 3) {
                 return loadProfileData(viewNode, in);
             } else {
-                return loadProfileDataRecursive(viewNode, in);
+                boolean ret = loadProfileDataRecursive(viewNode, in);
+                if (ret) {
+                    viewNode.setProfileRatings();
+                }
+                return ret;
             }
         } catch (IOException e) {
             Log.e(TAG, "Unable to load profiling data for window " + window.getTitle()
@@ -485,4 +492,22 @@
         }
         return true;
     }
+
+    public static Image loadCapture(Window window, ViewNode viewNode) {
+        DeviceConnection connection = null;
+        try {
+            connection = new DeviceConnection(window.getDevice());
+            connection.sendCommand("CAPTURE " + window.encode() + " " + viewNode.toString());
+            return new Image(Display.getDefault(), connection.getSocket().getInputStream());
+        } catch (Exception e) {
+            Log.e(TAG, "Unable to capture data for node " + viewNode + " in window "
+                    + window.getTitle() + " on device " + window.getDevice());
+        } finally {
+            if (connection != null) {
+                connection.close();
+            }
+        }
+        return null;
+    }
+
 }
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceConnection.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceConnection.java
index 581f76b..18b9619 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceConnection.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceConnection.java
@@ -24,6 +24,7 @@
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.net.InetSocketAddress;
+import java.net.Socket;
 import java.nio.channels.SocketChannel;
 
 /**
@@ -61,6 +62,10 @@
         return out;
     }
 
+    public Socket getSocket() {
+        return socketChannel.socket();
+    }
+
     public void sendCommand(String command) throws IOException {
         BufferedWriter out = getOutputStream();
         out.write(command);
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 2dd9b61..2872952 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewNode.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewNode.java
@@ -16,6 +16,8 @@
 
 package com.android.hierarchyviewerlib.device;
 
+import org.eclipse.swt.graphics.Image;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -26,6 +28,15 @@
 import java.util.TreeSet;
 
 public class ViewNode {
+
+    public static enum ProfileRating {
+        RED, YELLOW, GREEN, NONE
+    };
+
+    private static final double RED_THRESHOLD = 0.8;
+
+    private static final double YELLOW_THRESHOLD = 0.5;
+
     public static final String MISCELLANIOUS = "miscellaneous";
 
     public String id;
@@ -86,9 +97,22 @@
 
     public double drawTime;
 
+    public ProfileRating measureRating = ProfileRating.NONE;
+
+    public ProfileRating layoutRating = ProfileRating.NONE;
+
+    public ProfileRating drawRating = ProfileRating.NONE;
+
     public Set<String> categories = new TreeSet<String>();
 
-    public ViewNode(ViewNode parent, String data) {
+    public Window window;
+
+    public Image image;
+
+    public int imageReferences = 1;
+
+    public ViewNode(Window window, ViewNode parent, String data) {
+        this.window = window;
         this.parent = parent;
         index = this.parent == null ? 0 : this.parent.children.size();
         if (this.parent != null) {
@@ -106,6 +130,25 @@
         drawTime = -1;
     }
 
+    public void dispose() {
+        final int N = children.size();
+        for(int i = 0; i<N; i++) {
+            children.get(i).dispose();
+        }
+        dereferenceImage();
+    }
+
+    public void referenceImage() {
+        imageReferences++;
+    }
+
+    public void dereferenceImage() {
+        imageReferences--;
+        if (image != null && imageReferences == 0) {
+            image.dispose();
+        }
+    }
+
     private void loadProperties(String data) {
         int start = 0;
         boolean stop;
@@ -141,9 +184,9 @@
         top = namedProperties.containsKey("mTop") ?
                 getInt("mTop", 0) : getInt("layout:mTop", 0);
         width = namedProperties.containsKey("getWidth()") ?
-                getInt("getWidth()", 0) : getInt("measurement:getWidth()", 0);
+                getInt("getWidth()", 0) : getInt("layout:getWidth()", 0);
         height = namedProperties.containsKey("getHeight()") ?
-                getInt("getHeight()", 0) : getInt("measurement:getHeight()", 0);
+                getInt("getHeight()", 0) : getInt("layout:getHeight()", 0);
         scrollX = namedProperties.containsKey("mScrollX") ?
                 getInt("mScrollX", 0) : getInt("scrolling:mScrollX", 0);
         scrollY = namedProperties.containsKey("mScrollY") ?
@@ -158,19 +201,19 @@
                 getInt("mPaddingBottom", 0) : getInt("padding:mPaddingBottom", 0);
         marginLeft = namedProperties.containsKey("layout_leftMargin") ?
                 getInt("layout_leftMargin", Integer.MIN_VALUE) :
-                getInt("layout:leftMargin", Integer.MIN_VALUE);
+                getInt("layout:layout_leftMargin", Integer.MIN_VALUE);
         marginRight = namedProperties.containsKey("layout_rightMargin") ?
                 getInt("layout_rightMargin", Integer.MIN_VALUE) :
-                getInt("layout:rightMargin", Integer.MIN_VALUE);
+                getInt("layout:layout_rightMargin", Integer.MIN_VALUE);
         marginTop = namedProperties.containsKey("layout_topMargin") ?
                 getInt("layout_topMargin", Integer.MIN_VALUE) :
-                getInt("layout:topMargin", Integer.MIN_VALUE);
+                getInt("layout:layout_topMargin", Integer.MIN_VALUE);
         marginBottom = namedProperties.containsKey("layout_bottomMargin") ?
                 getInt("layout_bottomMargin", Integer.MIN_VALUE) :
-                getInt("layout:bottomMargin", Integer.MIN_VALUE);
+                getInt("layout:layout_bottomMargin", Integer.MIN_VALUE);
         baseline = namedProperties.containsKey("getBaseline()") ?
                 getInt("getBaseline()", 0) :
-                getInt("measurement:getBaseline()", 0);
+                getInt("layout:getBaseline()", 0);
         willNotDraw = namedProperties.containsKey("willNotDraw()") ?
                 getBoolean("willNotDraw()", false) :
                 getBoolean("drawing:willNotDraw()", false);
@@ -193,6 +236,48 @@
         }
     }
 
+    public void setProfileRatings() {
+        final int N = children.size();
+        if (N > 1) {
+            double totalMeasure = 0;
+            double totalLayout = 0;
+            double totalDraw = 0;
+            for (int i = 0; i < N; i++) {
+                ViewNode child = children.get(i);
+                totalMeasure += child.measureTime;
+                totalLayout += child.layoutTime;
+                totalDraw += child.drawTime;
+            }
+            for (int i = 0; i < N; i++) {
+                ViewNode child = children.get(i);
+                if (child.measureTime / totalMeasure >= RED_THRESHOLD) {
+                    child.measureRating = ProfileRating.RED;
+                } else if (child.measureTime / totalMeasure >= YELLOW_THRESHOLD) {
+                    child.measureRating = ProfileRating.YELLOW;
+                } else {
+                    child.measureRating = ProfileRating.GREEN;
+                }
+                if (child.layoutTime / totalLayout >= RED_THRESHOLD) {
+                    child.layoutRating = ProfileRating.RED;
+                } else if (child.layoutTime / totalLayout >= YELLOW_THRESHOLD) {
+                    child.layoutRating = ProfileRating.YELLOW;
+                } else {
+                    child.layoutRating = ProfileRating.GREEN;
+                }
+                if (child.drawTime / totalDraw >= RED_THRESHOLD) {
+                    child.drawRating = ProfileRating.RED;
+                } else if (child.drawTime / totalDraw >= YELLOW_THRESHOLD) {
+                    child.drawRating = ProfileRating.YELLOW;
+                } else {
+                    child.drawRating = ProfileRating.GREEN;
+                }
+            }
+        }
+        for (int i = 0; i < N; i++) {
+            children.get(i).setProfileRatings();
+        }
+    }
+
     private boolean getBoolean(String name, boolean defaultValue) {
         Property p = namedProperties.get(name);
         if (p != null) {
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 4f19368..e8f8240 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/PixelPerfectModel.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/PixelPerfectModel.java
@@ -20,6 +20,10 @@
 import com.android.ddmlib.RawImage;
 import com.android.hierarchyviewerlib.device.ViewNode;
 
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Display;
+
 import java.util.ArrayList;
 
 public class PixelPerfectModel {
@@ -30,20 +34,9 @@
 
     private static final int DEFAULT_ZOOM = 8;
 
-    public static class Point {
-        public int x;
-
-        public int y;
-
-        Point(int x, int y) {
-            this.x = x;
-            this.y = y;
-        }
-    }
-
     private IDevice device;
 
-    private RawImage image;
+    private Image image;
 
     private Point crosshairLocation;
 
@@ -56,19 +49,31 @@
     private final ArrayList<ImageChangeListener> imageChangeListeners =
             new ArrayList<ImageChangeListener>();
 
-    public void setData(IDevice device, RawImage image, ViewNode viewNode) {
-        synchronized (this) {
-            this.device = device;
-            this.image = image;
-            this.viewNode = viewNode;
-            if (image != null) {
-                this.crosshairLocation = new Point(image.width / 2, image.height / 2);
-            } else {
-                this.crosshairLocation = null;
+    private Image overlayImage;
+
+    private double overlayTransparency = 0.5;
+
+    public void setData(final IDevice device, final Image image, final ViewNode viewNode) {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (PixelPerfectModel.this) {
+                    PixelPerfectModel.this.device = device;
+                    if (PixelPerfectModel.this.image != null) {
+                        PixelPerfectModel.this.image.dispose();
+                    }
+                    PixelPerfectModel.this.image = image;
+                    PixelPerfectModel.this.viewNode = viewNode;
+                    if (image != null) {
+                        PixelPerfectModel.this.crosshairLocation =
+                                new Point(image.getBounds().width / 2, image.getBounds().height / 2);
+                    } else {
+                        PixelPerfectModel.this.crosshairLocation = null;
+                    }
+                    PixelPerfectModel.this.selected = null;
+                    zoom = DEFAULT_ZOOM;
+                }
             }
-            this.selected = null;
-            zoom = DEFAULT_ZOOM;
-        }
+        });
         notifyImageLoaded();
     }
 
@@ -86,12 +91,19 @@
         notifySelectionChanged();
     }
 
-    public void setFocusData(RawImage image, ViewNode viewNode) {
-        synchronized (this) {
-            this.image = image;
-            this.viewNode = viewNode;
-            this.selected = null;
-        }
+    public void setFocusData(final Image image, final ViewNode viewNode) {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (PixelPerfectModel.this) {
+                    if (PixelPerfectModel.this.image != null) {
+                        PixelPerfectModel.this.image.dispose();
+                    }
+                    PixelPerfectModel.this.image = image;
+                    PixelPerfectModel.this.viewNode = viewNode;
+                    PixelPerfectModel.this.selected = null;
+                }
+            }
+        });
         notifyFocusChanged();
     }
 
@@ -108,6 +120,29 @@
         notifyZoomChanged();
     }
 
+    public void setOverlayImage(final Image overlayImage) {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (PixelPerfectModel.this) {
+                    if (PixelPerfectModel.this.overlayImage != null) {
+                        PixelPerfectModel.this.overlayImage.dispose();
+                    }
+                    PixelPerfectModel.this.overlayImage = overlayImage;
+                }
+            }
+        });
+        notifyOverlayChanged();
+    }
+
+    public void setOverlayTransparency(double value) {
+        synchronized (this) {
+            value = Math.max(value, 0);
+            value = Math.min(value, 1);
+            overlayTransparency = value;
+        }
+        notifyOverlayTransparencyChanged();
+    }
+
     public ViewNode getViewNode() {
         synchronized (this) {
             return viewNode;
@@ -120,7 +155,7 @@
         }
     }
 
-    public RawImage getImage() {
+    public Image getImage() {
         synchronized (this) {
             return image;
         }
@@ -144,6 +179,18 @@
         }
     }
 
+    public Image getOverlayImage() {
+        synchronized (this) {
+            return overlayImage;
+        }
+    }
+
+    public double getOverlayTransparency() {
+        synchronized (this) {
+            return overlayTransparency;
+        }
+    }
+
     public static interface ImageChangeListener {
         public void imageLoaded();
 
@@ -156,6 +203,10 @@
         public void focusChanged();
 
         public void zoomChanged();
+
+        public void overlayChanged();
+
+        public void overlayTransparencyChanged();
     }
 
     private ImageChangeListener[] getImageChangeListenerList() {
@@ -225,6 +276,24 @@
         }
     }
 
+    public void notifyOverlayChanged() {
+        ImageChangeListener[] listeners = getImageChangeListenerList();
+        if (listeners != null) {
+            for (int i = 0; i < listeners.length; i++) {
+                listeners[i].overlayChanged();
+            }
+        }
+    }
+
+    public void notifyOverlayTransparencyChanged() {
+        ImageChangeListener[] listeners = getImageChangeListenerList();
+        if (listeners != null) {
+            for (int i = 0; i < listeners.length; i++) {
+                listeners[i].overlayTransparencyChanged();
+            }
+        }
+    }
+
     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
index f6279df..c49ce95 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/TreeViewModel.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/TreeViewModel.java
@@ -18,9 +18,9 @@
 
 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 com.android.hierarchyviewerlib.ui.util.DrawableViewNode;
+import com.android.hierarchyviewerlib.ui.util.DrawableViewNode.Point;
+import com.android.hierarchyviewerlib.ui.util.DrawableViewNode.Rectangle;
 
 import java.util.ArrayList;
 
@@ -44,6 +44,9 @@
 
     public void setData(Window window, ViewNode viewNode) {
         synchronized (this) {
+            if (tree != null) {
+                tree.viewNode.dispose();
+            }
             this.window = window;
             tree = new DrawableViewNode(viewNode);
             tree.setLeft();
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/CaptureDisplay.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/CaptureDisplay.java
new file mode 100644
index 0000000..54a94fd
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/CaptureDisplay.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2008 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 org.eclipse.swt.SWT;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+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;
+import org.eclipse.swt.widgets.Shell;
+
+public class CaptureDisplay {
+    private static Shell shell;
+
+    private static Canvas canvas;
+
+    private static Image image;
+
+    private static ViewNode viewNode;
+
+    private static Composite buttonBar;
+
+    private static Button onWhite;
+
+    private static Button onBlack;
+
+    private static Button showExtras;
+
+    public static void show(Shell parentShell, ViewNode viewNode, Image image) {
+        if (shell == null) {
+            createShell();
+        }
+        if (shell.isVisible() && CaptureDisplay.viewNode != null) {
+            CaptureDisplay.viewNode.dereferenceImage();
+        }
+        CaptureDisplay.image = image;
+        CaptureDisplay.viewNode = viewNode;
+        viewNode.referenceImage();
+        int dotIndex = viewNode.name.lastIndexOf('.');
+        if (dotIndex != -1) {
+            shell.setText(viewNode.name.substring(dotIndex + 1));
+        } else {
+            shell.setText(viewNode.name);
+        }
+
+        boolean shellVisible = shell.isVisible();
+        if (!shellVisible) {
+            shell.setSize(0, 0);
+        }
+        shell.open();
+        Rectangle bounds =
+                shell.computeTrim(0, 0, Math.max(buttonBar.getBounds().width,
+                        image.getBounds().width), buttonBar.getBounds().height
+                        + image.getBounds().height + 5);
+        System.out.println(bounds);
+        shell.setSize(bounds.width, bounds.height);
+        if (!shellVisible) {
+            shell.setLocation(parentShell.getBounds().x
+                    + (parentShell.getBounds().width - bounds.width) / 2, parentShell.getBounds().y
+                    + (parentShell.getBounds().height - bounds.height) / 2);
+        }
+        if (shellVisible) {
+            canvas.redraw();
+        }
+    }
+
+    private static void createShell() {
+        shell = new Shell(Display.getDefault(), SWT.CLOSE | SWT.TITLE);
+        GridLayout gridLayout = new GridLayout();
+        gridLayout.marginWidth = 0;
+        gridLayout.marginHeight = 0;
+        shell.setLayout(gridLayout);
+
+        buttonBar = new Composite(shell, SWT.NONE);
+        RowLayout rowLayout = new RowLayout(SWT.HORIZONTAL);
+        rowLayout.pack = true;
+        rowLayout.center = true;
+        buttonBar.setLayout(rowLayout);
+        Composite buttons = new Composite(buttonBar, SWT.NONE);
+        buttons.setLayout(new FillLayout());
+
+        onWhite = new Button(buttons, SWT.TOGGLE);
+        onWhite.setText("On White");
+        onBlack = new Button(buttons, SWT.TOGGLE);
+        onBlack.setText("On Black");
+        onBlack.setSelection(true);
+        onWhite.addSelectionListener(whiteSelectionListener);
+        onBlack.addSelectionListener(blackSelectionListener);
+
+        showExtras = new Button(buttonBar, SWT.CHECK);
+        showExtras.setText("Show Extras");
+        showExtras.addSelectionListener(extrasSelectionListener);
+
+        canvas = new Canvas(shell, SWT.NONE);
+        canvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+        canvas.addPaintListener(paintListener);
+
+        shell.addShellListener(shellListener);
+    }
+
+    private static PaintListener paintListener = new PaintListener() {
+
+        public void paintControl(PaintEvent e) {
+            if (onWhite.getSelection()) {
+                e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
+            } else {
+                e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
+            }
+            e.gc.fillRectangle(0, 0, canvas.getBounds().width, canvas.getBounds().height);
+            if (image != null) {
+                int width = image.getBounds().width;
+                int height = image.getBounds().height;
+                int x = (canvas.getBounds().width - width) / 2;
+                int y = (canvas.getBounds().height - height) / 2;
+                e.gc.drawImage(image, x, y);
+                if (showExtras.getSelection()) {
+                    if ((viewNode.paddingLeft | viewNode.paddingRight | viewNode.paddingTop | viewNode.paddingBottom) != 0) {
+                        e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLUE));
+                        e.gc.drawRectangle(x + viewNode.paddingLeft, y + viewNode.paddingTop, width
+                                - viewNode.paddingLeft - viewNode.paddingRight - 1, height
+                                - viewNode.paddingTop - viewNode.paddingBottom - 1);
+                    }
+                    if (viewNode.hasMargins) {
+                        e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_GREEN));
+                        e.gc.drawRectangle(x - viewNode.marginLeft, y - viewNode.marginTop, width
+                                + viewNode.marginLeft + viewNode.marginRight - 1, height
+                                + viewNode.marginTop + viewNode.marginBottom - 1);
+                    }
+                    if (viewNode.baseline != -1) {
+                        e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
+                        e.gc.drawLine(x, y + viewNode.baseline, x + width - 1, viewNode.baseline);
+                    }
+                }
+            }
+        }
+    };
+
+    private static ShellAdapter shellListener = new ShellAdapter() {
+        @Override
+        public void shellClosed(ShellEvent e) {
+            e.doit = false;
+            shell.setVisible(false);
+            if (viewNode != null) {
+                viewNode.dereferenceImage();
+            }
+        }
+
+    };
+
+    private static SelectionListener whiteSelectionListener = new SelectionListener() {
+        public void widgetDefaultSelected(SelectionEvent e) {
+            // pass
+        }
+
+        public void widgetSelected(SelectionEvent e) {
+            onWhite.setSelection(true);
+            onBlack.setSelection(false);
+            canvas.redraw();
+        }
+    };
+
+    private static SelectionListener blackSelectionListener = new SelectionListener() {
+        public void widgetDefaultSelected(SelectionEvent e) {
+            // pass
+        }
+
+        public void widgetSelected(SelectionEvent e) {
+            onBlack.setSelection(true);
+            onWhite.setSelection(false);
+            canvas.redraw();
+        }
+    };
+
+    private static SelectionListener extrasSelectionListener = new SelectionListener() {
+        public void widgetDefaultSelected(SelectionEvent e) {
+            // pass
+        }
+
+        public void widgetSelected(SelectionEvent e) {
+            canvas.redraw();
+        }
+    };
+}
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/DeviceSelector.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/DeviceSelector.java
similarity index 99%
rename from hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/DeviceSelector.java
rename to hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/DeviceSelector.java
index 49eb418..5e7c606 100644
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/DeviceSelector.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/DeviceSelector.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.hierarchyvieweruilib;
+package com.android.hierarchyviewerlib.ui;
 
 import com.android.ddmlib.IDevice;
 import com.android.ddmuilib.ImageLoader;
@@ -163,8 +163,8 @@
         ContentProvider contentProvider = new ContentProvider();
         treeViewer.setContentProvider(contentProvider);
         treeViewer.setLabelProvider(contentProvider);
-        treeViewer.setInput(model);
         model.addWindowChangeListener(this);
+        treeViewer.setInput(model);
     }
 
     public void loadResources() {
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/LayoutViewer.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/LayoutViewer.java
new file mode 100644
index 0000000..917e96e
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/LayoutViewer.java
@@ -0,0 +1,319 @@
+/*
+ * 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.ComponentRegistry;
+import com.android.hierarchyviewerlib.models.TreeViewModel;
+import com.android.hierarchyviewerlib.models.TreeViewModel.TreeChangeListener;
+import com.android.hierarchyviewerlib.ui.util.DrawableViewNode;
+import com.android.hierarchyviewerlib.ui.util.DrawableViewNode.Point;
+
+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.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.GC;
+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;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+
+import java.util.ArrayList;
+
+public class LayoutViewer extends Canvas implements TreeChangeListener {
+
+    private TreeViewModel model;
+
+    private DrawableViewNode tree;
+
+    private DrawableViewNode selectedNode;
+
+    private Transform transform;
+
+    private Transform inverse;
+
+    private double scale;
+
+    private boolean showExtras = true;
+
+    public LayoutViewer(Composite parent) {
+        super(parent, SWT.NONE);
+        model = ComponentRegistry.getTreeViewModel();
+        model.addTreeChangeListener(this);
+
+        addDisposeListener(disposeListener);
+        addPaintListener(paintListener);
+        addListener(SWT.Resize, resizeListener);
+        addMouseListener(mouseListener);
+
+        transform = new Transform(Display.getDefault());
+        inverse = new Transform(Display.getDefault());
+    }
+
+    public void setShowExtras(boolean show) {
+        showExtras = show;
+    }
+
+    private DisposeListener disposeListener = new DisposeListener() {
+        public void widgetDisposed(DisposeEvent e) {
+            transform.dispose();
+            inverse.dispose();
+        }
+    };
+
+    private Listener resizeListener = new Listener() {
+        public void handleEvent(Event e) {
+            synchronized (this) {
+                setTransform();
+            }
+        }
+    };
+
+    private MouseListener mouseListener = new MouseListener() {
+
+        public void mouseDoubleClick(MouseEvent e) {
+            if (selectedNode != null) {
+                ComponentRegistry.getDirector().showCapture(getShell(), selectedNode.viewNode);
+            }
+        }
+
+        public void mouseDown(MouseEvent e) {
+            System.out.println("CLICK");
+            boolean selectionChanged = false;
+            DrawableViewNode newSelection = null;
+            synchronized (LayoutViewer.this) {
+                if (tree != null) {
+                    float[] pt = {
+                            e.x, e.y
+                    };
+                    inverse.transform(pt);
+                    newSelection =
+                            updateSelection(tree, pt[0], pt[1], 0, 0, 0, 0, tree.viewNode.width,
+                                    tree.viewNode.height);
+                    if (selectedNode != newSelection) {
+                        selectionChanged = true;
+                    }
+                }
+            }
+            if (selectionChanged) {
+                model.setSelection(newSelection);
+            }
+        }
+
+        public void mouseUp(MouseEvent e) {
+            // pass
+        }
+    };
+
+    private DrawableViewNode updateSelection(DrawableViewNode node, float x, float y, int left, int top,
+            int clipX, int clipY, int clipWidth, int clipHeight) {
+        if (!node.treeDrawn) {
+            return null;
+        }
+        // Update the clip
+        int x1 = Math.max(left, clipX);
+        int x2 = Math.min(left + node.viewNode.width, clipX + clipWidth);
+        int y1 = Math.max(top, clipY);
+        int y2 = Math.min(top + node.viewNode.height, clipY + clipHeight);
+        clipX = x1;
+        clipY = y1;
+        clipWidth = x2 - x1;
+        clipHeight = y2 - y1;
+        if (x < clipX || x > clipX + clipWidth || y < clipY || y > clipY + clipHeight) {
+            return null;
+        }
+        final int N = node.children.size();
+        for (int i = N - 1; i >= 0; i--) {
+            DrawableViewNode child = node.children.get(i);
+            DrawableViewNode ret = updateSelection(child, x, y, left + child.viewNode.left - node.viewNode.scrollX,
+                    top + child.viewNode.top - node.viewNode.scrollY, clipX, clipY, clipWidth,
+                    clipHeight);
+            if(ret != null) {
+                return ret;
+            }
+        }
+        return node;
+    }
+
+    private PaintListener paintListener = new PaintListener() {
+        public void paintControl(PaintEvent e) {
+            synchronized (LayoutViewer.this) {
+                e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
+                e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height);
+                if (tree != null) {
+                    e.gc.setLineWidth((int) Math.ceil(0.2 / scale));
+                    e.gc.setTransform(transform);
+                    e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
+                    Rectangle parentClipping = e.gc.getClipping();
+                    e.gc.setClipping(0, 0, tree.viewNode.width, tree.viewNode.height);
+                    paintRecursive(e.gc, tree, 0, 0, true);
+
+                    if (selectedNode != null) {
+                        e.gc.setClipping(parentClipping);
+
+                        // w00t, let's be nice and display the whole path in
+                        // light red and the selected node in dark red.
+                        ArrayList<Point> rightLeftDistances = new ArrayList<Point>();
+                        int left = 0;
+                        int top = 0;
+                        DrawableViewNode currentNode = selectedNode;
+                        while (currentNode != tree) {
+                            left += currentNode.viewNode.left;
+                            top += currentNode.viewNode.top;
+                            currentNode = currentNode.parent;
+                            left -= currentNode.viewNode.scrollX;
+                            top -= currentNode.viewNode.scrollY;
+                            rightLeftDistances.add(new Point(left, top));
+                        }
+                        e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_RED));
+                        currentNode = selectedNode.parent;
+                        final int N = rightLeftDistances.size();
+                        for (int i = 0; i < N; i++) {
+                            e.gc.drawRectangle((int) (left - rightLeftDistances.get(i).x),
+                                    (int) (top - rightLeftDistances.get(i).y),
+                                    currentNode.viewNode.width - (int) Math.ceil(0.2 / scale),
+                                    currentNode.viewNode.height - (int) Math.ceil(0.2 / scale));
+                            currentNode = currentNode.parent;
+                        }
+
+                        if (showExtras && selectedNode.viewNode.image != null) {
+                            e.gc.drawImage(selectedNode.viewNode.image, left, top);
+                            e.gc
+                                    .setForeground(Display.getDefault().getSystemColor(
+                                            SWT.COLOR_WHITE));
+                            paintRecursive(e.gc, selectedNode, left, top, true);
+
+                        }
+
+                        e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
+                        e.gc.setLineWidth((int) Math.ceil(2 / scale));
+                        e.gc.drawRectangle(left, top, selectedNode.viewNode.width
+                                - (int) Math.ceil(2 / scale) + 1, selectedNode.viewNode.height
+                                - (int) Math.ceil(2 / scale) + 1);
+                    }
+                }
+            }
+        }
+    };
+
+    private void paintRecursive(GC gc, DrawableViewNode node, int left, int top, boolean root) {
+        if (!node.treeDrawn) {
+            return;
+        }
+        // Don't shift the root
+        if (!root) {
+            left += node.viewNode.left;
+            top += node.viewNode.top;
+        }
+        Rectangle parentClipping = gc.getClipping();
+        int x1 = Math.max(parentClipping.x, left);
+        int x2 = Math.min(parentClipping.x + parentClipping.width, left + node.viewNode.width);
+        int y1 = Math.max(parentClipping.y, top);
+        int y2 = Math.min(parentClipping.y + parentClipping.height, top + node.viewNode.height);
+        gc.setClipping(x1, y1, x2 - x1, y2 - y1);
+        final int N = node.children.size();
+        for (int i = 0; i < N; i++) {
+            paintRecursive(gc, node.children.get(i), left - node.viewNode.scrollX, top
+                    - node.viewNode.scrollY, false);
+        }
+        gc.setClipping(parentClipping);
+        if (!node.viewNode.willNotDraw) {
+            gc.drawRectangle(left, top, node.viewNode.width - (int) Math.ceil(0.2 / scale),
+                    node.viewNode.height - (int) Math.ceil(0.2 / scale));
+        }
+
+    }
+
+    private void doRedraw() {
+        Display.getDefault().asyncExec(new Runnable() {
+            public void run() {
+                redraw();
+            }
+        });
+    }
+
+    private void setTransform() {
+        if (tree != null) {
+            Rectangle bounds = getBounds();
+            int leftRightPadding = bounds.width <= 30 ? 0 : 5;
+            int topBottomPadding = bounds.height <= 30 ? 0 : 5;
+            scale =
+                    Math.min(1.0 * (bounds.width - leftRightPadding * 2) / tree.viewNode.width, 1.0
+                            * (bounds.height - topBottomPadding * 2) / tree.viewNode.height);
+            int scaledWidth = (int) Math.ceil(tree.viewNode.width * scale);
+            int scaledHeight = (int) Math.ceil(tree.viewNode.height * scale);
+
+            transform.identity();
+            inverse.identity();
+            transform.translate((bounds.width - scaledWidth) / 2.0f,
+                    (bounds.height - scaledHeight) / 2.0f);
+            inverse.translate((bounds.width - scaledWidth) / 2.0f,
+                    (bounds.height - scaledHeight) / 2.0f);
+            transform.scale((float) scale, (float) scale);
+            inverse.scale((float) scale, (float) scale);
+            if (bounds.width != 0 && bounds.height != 0) {
+                inverse.invert();
+            }
+        }
+    }
+
+    public void selectionChanged() {
+        synchronized (this) {
+            if (selectedNode != null) {
+                selectedNode.viewNode.dereferenceImage();
+            }
+            selectedNode = model.getSelection();
+            if (selectedNode != null) {
+                selectedNode.viewNode.referenceImage();
+            }
+        }
+        doRedraw();
+    }
+
+    public void treeChanged() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (this) {
+                    if (selectedNode != null) {
+                        selectedNode.viewNode.dereferenceImage();
+                    }
+                    tree = model.getTree();
+                    selectedNode = model.getSelection();
+                    if (selectedNode != null) {
+                        selectedNode.viewNode.referenceImage();
+                    }
+                    setTransform();
+                }
+            }
+        });
+        doRedraw();
+    }
+
+    public void viewportChanged() {
+        // pass
+    }
+
+    public void zoomChanged() {
+        // pass
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfect.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfect.java
similarity index 83%
rename from hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfect.java
rename to hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfect.java
index 8165c0b..1a2876b 100644
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfect.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfect.java
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.hierarchyvieweruilib;
+package com.android.hierarchyviewerlib.ui;
 
 import com.android.ddmlib.RawImage;
 import com.android.hierarchyviewerlib.ComponentRegistry;
 import com.android.hierarchyviewerlib.device.ViewNode;
 import com.android.hierarchyviewerlib.models.PixelPerfectModel;
 import com.android.hierarchyviewerlib.models.PixelPerfectModel.ImageChangeListener;
-import com.android.hierarchyviewerlib.models.PixelPerfectModel.Point;
 
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.ScrolledComposite;
@@ -36,6 +35,7 @@
 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.widgets.Canvas;
 import org.eclipse.swt.widgets.Composite;
@@ -64,6 +64,10 @@
 
     private ViewNode selectedNode;
 
+    private Image overlayImage;
+
+    private double overlayTransparency;
+
     public PixelPerfect(Composite parent) {
         super(parent, SWT.H_SCROLL | SWT.V_SCROLL);
         canvas = new Canvas(this, SWT.NONE);
@@ -127,7 +131,7 @@
     };
 
     private void handleMouseEvent(MouseEvent e) {
-        synchronized (this) {
+        synchronized (PixelPerfect.this) {
             if (image == null) {
                 return;
             }
@@ -145,7 +149,7 @@
 
     private PaintListener paintListener = new PaintListener() {
         public void paintControl(PaintEvent e) {
-            synchronized (this) {
+            synchronized (PixelPerfect.this) {
                 e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                 e.gc.fillRectangle(0, 0, canvas.getSize().x, canvas.getSize().y);
                 if (image != null) {
@@ -153,6 +157,15 @@
                     int leftOffset = canvas.getSize().x / 2 - width / 2;
                     int topOffset = canvas.getSize().y / 2 - height / 2;
                     e.gc.drawImage(image, leftOffset, topOffset);
+                    if (overlayImage != null) {
+                        e.gc.setAlpha((int) (overlayTransparency * 255));
+                        int overlayTopOffset =
+                                canvas.getSize().y / 2 + height / 2
+                                        - overlayImage.getBounds().height;
+                        e.gc.drawImage(overlayImage, leftOffset, overlayTopOffset);
+                        e.gc.setAlpha(255);
+                    }
+
                     if (selectedNode != null) {
                         // There are a few quirks here. First of all, margins
                         // are sometimes negative or positive numbers... Yet,
@@ -175,8 +188,8 @@
                         int nodePadBottom = selectedNode.paddingBottom;
                         ViewNode cur = selectedNode;
                         while (cur.parent != null) {
-                            leftShift += cur.parent.left;
-                            topShift += cur.parent.top;
+                            leftShift += cur.parent.left - cur.parent.scrollX;
+                            topShift += cur.parent.top - cur.parent.scrollY;
                             cur = cur.parent;
                         }
 
@@ -235,47 +248,38 @@
     }
 
     private void loadImage() {
-        final RawImage rawImage = model.getImage();
-        if (rawImage != null) {
-            ImageData imageData =
-                    new ImageData(rawImage.width, rawImage.height, rawImage.bpp,
-                            new PaletteData(rawImage.getRedMask(), rawImage.getGreenMask(),
-                                    rawImage.getBlueMask()), 1, rawImage.data);
-            if (image != null) {
-                image.dispose();
-            }
-            image = new Image(Display.getDefault(), imageData);
-            width = rawImage.width;
-            height = rawImage.height;
-
+        image = model.getImage();
+        if (image != null) {
+            width = image.getBounds().width;
+            height = image.getBounds().height;
         } else {
-            if (image != null) {
-                image.dispose();
-                image = null;
-            }
             width = 0;
             height = 0;
         }
-        Display.getDefault().asyncExec(new Runnable() {
-            public void run() {
-                setMinSize(width, height);
-            }
-        });
+        setMinSize(width, height);
     }
 
     public void imageLoaded() {
-        synchronized (this) {
-            loadImage();
-            crosshairLocation = model.getCrosshairLocation();
-            selectedNode = model.getSelected();
-        }
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (this) {
+                    loadImage();
+                    crosshairLocation = model.getCrosshairLocation();
+                    selectedNode = model.getSelected();
+                }
+            }
+        });
         doRedraw();
     }
 
     public void imageChanged() {
-        synchronized (this) {
-            loadImage();
-        }
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (this) {
+                    loadImage();
+                }
+            }
+        });
         doRedraw();
     }
 
@@ -294,14 +298,33 @@
     }
 
     public void focusChanged() {
-        synchronized (this) {
-            loadImage();
-            selectedNode = model.getSelected();
-        }
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (this) {
+                    loadImage();
+                    selectedNode = model.getSelected();
+                }
+            }
+        });
         doRedraw();
     }
 
     public void zoomChanged() {
         // pass
     }
+
+    public void overlayChanged() {
+        synchronized (this) {
+            overlayImage = model.getOverlayImage();
+            overlayTransparency = model.getOverlayTransparency();
+        }
+        doRedraw();
+    }
+
+    public void overlayTransparencyChanged() {
+        synchronized (this) {
+            overlayTransparency = model.getOverlayTransparency();
+        }
+        doRedraw();
+    }
 }
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectLoupe.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectLoupe.java
similarity index 79%
rename from hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectLoupe.java
rename to hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectLoupe.java
index f0402f3..84ce08f 100644
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectLoupe.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectLoupe.java
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.hierarchyvieweruilib;
+package com.android.hierarchyviewerlib.ui;
 
 import com.android.ddmlib.RawImage;
 import com.android.hierarchyviewerlib.ComponentRegistry;
 import com.android.hierarchyviewerlib.models.PixelPerfectModel;
 import com.android.hierarchyviewerlib.models.PixelPerfectModel.ImageChangeListener;
-import com.android.hierarchyviewerlib.models.PixelPerfectModel.Point;
 
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.DisposeEvent;
@@ -35,6 +34,7 @@
 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;
@@ -65,6 +65,12 @@
 
     private int canvasHeight;
 
+    private Image overlayImage;
+
+    private double overlayTransparency;
+
+    private boolean showOverlay = false;
+
     public PixelPerfectLoupe(Composite parent) {
         super(parent, SWT.NONE);
         model = ComponentRegistry.getPixelPerfectModel();
@@ -80,6 +86,12 @@
         transform = new Transform(Display.getDefault());
     }
 
+    public void setShowOverlay(boolean value) {
+        synchronized (this) {
+            showOverlay = value;
+        }
+    }
+
     private DisposeListener disposeListener = new DisposeListener() {
         public void widgetDisposed(DisposeEvent e) {
             model.removeImageChangeListener(PixelPerfectLoupe.this);
@@ -113,7 +125,7 @@
     private MouseWheelListener mouseWheelListener = new MouseWheelListener() {
         public void mouseScrolled(MouseEvent e) {
             int newZoom = -1;
-            synchronized (this) {
+            synchronized (PixelPerfectLoupe.this) {
                 if (image != null && crosshairLocation != null) {
                     if (e.count > 0) {
                         newZoom = zoom + 1;
@@ -131,7 +143,7 @@
     private void handleMouseEvent(MouseEvent e) {
         int newX = -1;
         int newY = -1;
-        synchronized (this) {
+        synchronized (PixelPerfectLoupe.this) {
             if (image == null) {
                 return;
             }
@@ -151,7 +163,7 @@
 
     private PaintListener paintListener = new PaintListener() {
         public void paintControl(PaintEvent e) {
-            synchronized (this) {
+            synchronized (PixelPerfectLoupe.this) {
                 e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                 e.gc.fillRectangle(0, 0, getSize().x, getSize().y);
                 if (image != null && crosshairLocation != null) {
@@ -162,6 +174,12 @@
                     e.gc.setInterpolation(SWT.NONE);
                     e.gc.setTransform(transform);
                     e.gc.drawImage(image, 0, 0);
+                    if (showOverlay && overlayImage != null) {
+                        e.gc.setAlpha((int) (overlayTransparency * 255));
+                        e.gc.drawImage(overlayImage, 0, height - overlayImage.getBounds().height);
+                        e.gc.setAlpha(255);
+                    }
+
                     transform.identity();
                     e.gc.setTransform(transform);
 
@@ -220,41 +238,37 @@
     }
 
     private void loadImage() {
-        final RawImage rawImage = model.getImage();
-        if (rawImage != null) {
-            ImageData imageData =
-                    new ImageData(rawImage.width, rawImage.height, rawImage.bpp,
-                            new PaletteData(rawImage.getRedMask(), rawImage.getGreenMask(),
-                                    rawImage.getBlueMask()), 1, rawImage.data);
-            if (image != null) {
-                image.dispose();
-            }
-            image = new Image(Display.getDefault(), imageData);
-            width = rawImage.width;
-            height = rawImage.height;
+        image = model.getImage();
+        if (image != null) {
+            width = image.getBounds().width;
+            height = image.getBounds().height;
         } else {
-            if (image != null) {
-                image.dispose();
-                image = null;
-            }
             width = 0;
             height = 0;
         }
     }
 
     public void imageLoaded() {
-        synchronized (this) {
-            loadImage();
-            crosshairLocation = model.getCrosshairLocation();
-            zoom = model.getZoom();
-        }
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (this) {
+                    loadImage();
+                    crosshairLocation = model.getCrosshairLocation();
+                    zoom = model.getZoom();
+                }
+            }
+        });
         doRedraw();
     }
 
     public void imageChanged() {
-        synchronized (this) {
-            loadImage();
-        }
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (this) {
+                    loadImage();
+                }
+            }
+        });
         doRedraw();
     }
 
@@ -274,14 +288,34 @@
     }
 
     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();
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                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();
+    }
+
+    public void overlayChanged() {
+        synchronized (this) {
+            overlayImage = model.getOverlayImage();
+            overlayTransparency = model.getOverlayTransparency();
+        }
+        doRedraw();
+    }
+
+    public void overlayTransparencyChanged() {
+        synchronized (this) {
+            overlayTransparency = model.getOverlayTransparency();
         }
         doRedraw();
     }
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectTree.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectTree.java
similarity index 97%
rename from hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectTree.java
rename to hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectTree.java
index 2c30857..7df4d9d 100644
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PixelPerfectTree.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectTree.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.hierarchyvieweruilib;
+package com.android.hierarchyviewerlib.ui;
 
 import com.android.ddmuilib.ImageLoader;
 import com.android.hierarchyviewerlib.ComponentRegistry;
@@ -38,7 +38,6 @@
 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 java.util.List;
 
@@ -213,4 +212,11 @@
         // pass
     }
 
+    public void overlayChanged() {
+        // pass
+    }
+
+    public void overlayTransparencyChanged() {
+        // pass
+    }
 }
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/ProfileViewer.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/ProfileViewer.java
similarity index 89%
rename from hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/ProfileViewer.java
rename to hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/ProfileViewer.java
index 400318c..f83ba3d 100644
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/ProfileViewer.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/ProfileViewer.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.hierarchyvieweruilib;
+package com.android.hierarchyviewerlib.ui;
 
 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.hierarchyvieweruilib.util.TreeColumnResizer;
+import com.android.hierarchyviewerlib.ui.util.DrawableViewNode;
+import com.android.hierarchyviewerlib.ui.util.TreeColumnResizer;
 
 import org.eclipse.jface.viewers.ILabelProviderListener;
 import org.eclipse.jface.viewers.ITableLabelProvider;
@@ -97,10 +97,19 @@
                     } else if (column == 1) {
                         DecimalFormat formatter = new DecimalFormat("0.000");
                         if(((String)element).equals("measure")) {
+                            if (selectedNode.viewNode.measureTime == -1) {
+                                return "unknown";
+                            }
                             return formatter.format(selectedNode.viewNode.measureTime);
                         } else if (((String) element).equals("layout")) {
+                            if (selectedNode.viewNode.layoutTime == -1) {
+                                return "unknown";
+                            }
                             return formatter.format(selectedNode.viewNode.layoutTime);
                         } else {
+                            if (selectedNode.viewNode.drawTime == -1) {
+                                return "unknown";
+                            }
                             return formatter.format(selectedNode.viewNode.drawTime);
                         }
                     }
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PropertyViewer.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PropertyViewer.java
similarity index 96%
rename from hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PropertyViewer.java
rename to hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PropertyViewer.java
index da4997b..d262d16 100644
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/PropertyViewer.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PropertyViewer.java
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.hierarchyvieweruilib;
+package com.android.hierarchyviewerlib.ui;
 
 import com.android.hierarchyviewerlib.ComponentRegistry;
 import com.android.hierarchyviewerlib.device.ViewNode;
 import com.android.hierarchyviewerlib.device.ViewNode.Property;
 import com.android.hierarchyviewerlib.models.TreeViewModel;
 import com.android.hierarchyviewerlib.models.TreeViewModel.TreeChangeListener;
-import com.android.hierarchyviewerlib.scene.DrawableViewNode;
-import com.android.hierarchyvieweruilib.util.TreeColumnResizer;
+import com.android.hierarchyviewerlib.ui.util.DrawableViewNode;
+import com.android.hierarchyviewerlib.ui.util.TreeColumnResizer;
 
 import org.eclipse.jface.viewers.ILabelProviderListener;
 import org.eclipse.jface.viewers.ITableLabelProvider;
@@ -34,8 +34,6 @@
 import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Event;
-import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.Tree;
 import org.eclipse.swt.widgets.TreeColumn;
 
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeView.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeView.java
new file mode 100644
index 0000000..abf5690
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeView.java
@@ -0,0 +1,617 @@
+/*
+ * 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.ComponentRegistry;
+import com.android.hierarchyviewerlib.device.ViewNode.ProfileRating;
+import com.android.hierarchyviewerlib.models.TreeViewModel;
+import com.android.hierarchyviewerlib.models.TreeViewModel.TreeChangeListener;
+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.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.MouseWheelListener;
+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 TreeView extends Canvas implements TreeChangeListener {
+
+    private TreeViewModel model;
+
+    private DrawableViewNode tree;
+
+    private DrawableViewNode selectedNode;
+
+    private Rectangle viewport;
+
+    private Transform transform;
+
+    private Transform inverse;
+
+    private double zoom;
+
+    private Point lastPoint;
+
+    private DrawableViewNode draggedNode;
+
+    public static final int LINE_PADDING = 10;
+
+    public static final float BEZIER_FRACTION = 0.35f;
+
+    private Image redImage;
+
+    private Image yellowImage;
+
+    private Image greenImage;
+
+    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);
+        addDisposeListener(disposeListener);
+        addKeyListener(keyListener);
+
+        transform = new Transform(Display.getDefault());
+        inverse = new Transform(Display.getDefault());
+
+        ImageLoader loader = ImageLoader.getLoader(this.getClass());
+        redImage = loader.loadImage("red.png", Display.getDefault());
+        yellowImage = loader.loadImage("yellow.png", Display.getDefault());
+        greenImage = loader.loadImage("green.png", Display.getDefault());
+    }
+
+    private DisposeListener disposeListener = new DisposeListener() {
+        public void widgetDisposed(DisposeEvent e) {
+            model.removeTreeChangeListener(TreeView.this);
+            transform.dispose();
+            inverse.dispose();
+        }
+    };
+
+    private Listener resizeListener = new Listener() {
+        public void handleEvent(Event e) {
+            synchronized (TreeView.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 KeyListener keyListener = new KeyListener() {
+
+        public void keyPressed(KeyEvent e) {
+            boolean selectionChanged = false;
+            DrawableViewNode clickedNode = null;
+            synchronized (TreeView.this) {
+                if (tree != null && viewport != null && selectedNode != null) {
+                    switch (e.keyCode) {
+                        case SWT.ARROW_LEFT:
+                            if(selectedNode.parent != null) {
+                                selectedNode = selectedNode.parent;
+                                selectionChanged = true;
+                            }
+                            break;
+                        case SWT.ARROW_UP:
+                            int levelsOut = 0;
+                            DrawableViewNode currentNode = selectedNode;
+                            while (currentNode.parent != null && currentNode.viewNode.index == 0) {
+                                levelsOut++;
+                                currentNode = currentNode.parent;
+                            }
+                            if (currentNode.parent != null) {
+                                selectionChanged = true;
+                                currentNode =
+                                        currentNode.parent.children
+                                                .get(currentNode.viewNode.index - 1);
+                                while (currentNode.children.size() != 0) {
+                                    currentNode =
+                                            currentNode.children
+                                                    .get(currentNode.children.size() - 1);
+                                    levelsOut--;
+                                }
+                            }
+                            if (selectionChanged) {
+                                selectedNode = currentNode;
+                            }
+                            break;
+                        case SWT.ARROW_DOWN:
+                            levelsOut = 0;
+                            currentNode = selectedNode;
+                            while (currentNode.parent != null
+                                    && currentNode.viewNode.index + 1 == currentNode.parent.children
+                                    .size()) {
+                                levelsOut++;
+                                currentNode = currentNode.parent;
+                            }
+                            if (currentNode.parent != null) {
+                                selectionChanged = true;
+                                currentNode =
+                                        currentNode.parent.children
+                                                .get(currentNode.viewNode.index + 1);
+                                while (currentNode.children.size() != 0) {
+                                    currentNode = currentNode.children.get(0);
+                                    levelsOut--;
+                                }
+                            }
+                            if (selectionChanged) {
+                                selectedNode = currentNode;
+                            }
+                            break;
+                        case SWT.ARROW_RIGHT:
+                            DrawableViewNode rightNode = null;
+                            double mostOverlap = 0;
+                            final int N = selectedNode.children.size();
+                            for(int i = 0; i<N; i++) {
+                                DrawableViewNode child = selectedNode.children.get(i);
+                                DrawableViewNode topMostChild = child;
+                                while (topMostChild.children.size() != 0) {
+                                    topMostChild = topMostChild.children.get(0);
+                                }
+                                double overlap =
+                                        Math.min(DrawableViewNode.NODE_HEIGHT, Math.min(
+                                                selectedNode.top + DrawableViewNode.NODE_HEIGHT
+                                                        - topMostChild.top, topMostChild.top
+                                                        + child.treeHeight - selectedNode.top));
+                                if (overlap > mostOverlap) {
+                                    mostOverlap = overlap;
+                                    rightNode = child;
+                                }
+                            }
+                            if (rightNode != null) {
+                                selectedNode = rightNode;
+                                selectionChanged = true;
+                            }
+                            break;
+                        case SWT.CR:
+                            clickedNode = selectedNode;
+                            break;
+                    }
+                }
+            }
+            if (selectionChanged) {
+                model.setSelection(selectedNode);
+            }
+            if (clickedNode != null) {
+                ComponentRegistry.getDirector().showCapture(getShell(), clickedNode.viewNode);
+            }
+        }
+
+        public void keyReleased(KeyEvent e) {
+        }
+    };
+
+    private MouseListener mouseListener = new MouseListener() {
+
+        public void mouseDoubleClick(MouseEvent e) {
+            DrawableViewNode clickedNode = null;
+            synchronized (TreeView.this) {
+                if (tree != null && viewport != null) {
+                    Point pt = transformPoint(e.x, e.y);
+                    clickedNode = tree.getSelected(pt.x, pt.y);
+                }
+            }
+            if (clickedNode != null) {
+                ComponentRegistry.getDirector().showCapture(getShell(), clickedNode.viewNode);
+            }
+        }
+
+        public void mouseDown(MouseEvent e) {
+            boolean selectionChanged = false;
+            synchronized (TreeView.this) {
+                if (tree != null && viewport != null) {
+                    Point pt = transformPoint(e.x, e.y);
+                    draggedNode = tree.getSelected(pt.x, pt.y);
+                    if (draggedNode != null && draggedNode != selectedNode) {
+                        selectedNode = draggedNode;
+                        selectionChanged = true;
+                    }
+                    if (draggedNode == tree) {
+                        draggedNode = null;
+                    }
+                    if (draggedNode != null) {
+                        lastPoint = pt;
+                    } else {
+                        lastPoint = new Point(e.x, e.y);
+                    }
+                }
+            }
+            if (selectionChanged) {
+                model.setSelection(selectedNode);
+            }
+        }
+
+        public void mouseUp(MouseEvent e) {
+            boolean redraw = false;
+            boolean viewportChanged = false;
+            synchronized (TreeView.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.notifyViewportChanged();
+                model.addTreeChangeListener(TreeView.this);
+                doRedraw();
+            }
+        }
+
+    };
+
+    private MouseMoveListener mouseMoveListener = new MouseMoveListener() {
+        public void mouseMove(MouseEvent e) {
+            boolean redraw = false;
+            boolean viewportChanged = false;
+            synchronized (TreeView.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.notifyViewportChanged();
+                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 (TreeView.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 (TreeView.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);
+                    e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_GRAY));
+                    Path connectionPath = new Path(Display.getDefault());
+                    paintRecursive(e.gc, tree, connectionPath);
+                    e.gc.drawPath(connectionPath);
+                    connectionPath.dispose();
+                }
+            }
+        }
+    };
+
+    private void paintRecursive(GC gc, DrawableViewNode node, Path connectionPath) {
+        if (selectedNode == node) {
+            gc.fillRectangle(node.left, (int) Math.round(node.top), DrawableViewNode.NODE_WIDTH,
+                    DrawableViewNode.NODE_HEIGHT);
+        } else {
+            gc.drawRectangle(node.left, (int) Math.round(node.top), DrawableViewNode.NODE_WIDTH,
+                    DrawableViewNode.NODE_HEIGHT);
+        }
+
+        int fontHeight = gc.getFontMetrics().getHeight();
+
+        // Draw the text...
+        int contentWidth =
+                DrawableViewNode.NODE_WIDTH - 2 * DrawableViewNode.CONTENT_LEFT_RIGHT_PADDING;
+        String name = node.viewNode.name;
+        int dotIndex = name.lastIndexOf('.');
+        if (dotIndex != -1) {
+            name = name.substring(dotIndex + 1);
+        }
+        double x = node.left + DrawableViewNode.CONTENT_LEFT_RIGHT_PADDING;
+        double y = node.top + DrawableViewNode.CONTENT_TOP_BOTTOM_PADDING;
+        drawTextInArea(gc, name, x, y, contentWidth, fontHeight);
+
+        y += fontHeight + DrawableViewNode.CONTENT_INTER_PADDING;
+
+        gc.drawText("@" + node.viewNode.hashCode, (int) x, (int) y, SWT.DRAW_TRANSPARENT);
+
+        y += fontHeight + DrawableViewNode.CONTENT_INTER_PADDING;
+        if (!node.viewNode.id.equals("NO_ID")) {
+            drawTextInArea(gc, node.viewNode.id, x, y, contentWidth, fontHeight);
+        }
+
+        if (node.viewNode.measureRating != ProfileRating.NONE) {
+            y =
+                    node.top + DrawableViewNode.NODE_HEIGHT
+                            - DrawableViewNode.CONTENT_TOP_BOTTOM_PADDING
+                            - redImage.getBounds().height;
+            x +=
+                    (contentWidth - (redImage.getBounds().width * 3 + 2 * DrawableViewNode.CONTENT_INTER_PADDING)) / 2;
+            switch (node.viewNode.measureRating) {
+                case GREEN:
+                    gc.drawImage(greenImage, (int) x, (int) y);
+                    break;
+                case YELLOW:
+                    gc.drawImage(yellowImage, (int) x, (int) y);
+                    break;
+                case RED:
+                    gc.drawImage(redImage, (int) x, (int) y);
+                    break;
+            }
+
+            x += redImage.getBounds().width + DrawableViewNode.CONTENT_INTER_PADDING;
+            switch (node.viewNode.layoutRating) {
+                case GREEN:
+                    gc.drawImage(greenImage, (int) x, (int) y);
+                    break;
+                case YELLOW:
+                    gc.drawImage(yellowImage, (int) x, (int) y);
+                    break;
+                case RED:
+                    gc.drawImage(redImage, (int) x, (int) y);
+                    break;
+            }
+
+            x += redImage.getBounds().width + DrawableViewNode.CONTENT_INTER_PADDING;
+            switch (node.viewNode.drawRating) {
+                case GREEN:
+                    gc.drawImage(greenImage, (int) x, (int) y);
+                    break;
+                case YELLOW:
+                    gc.drawImage(yellowImage, (int) x, (int) y);
+                    break;
+                case RED:
+                    gc.drawImage(redImage, (int) x, (int) y);
+                    break;
+            }
+        }
+
+
+        org.eclipse.swt.graphics.Point indexExtent =
+                gc.stringExtent(Integer.toString(node.viewNode.index));
+        x = node.left+DrawableViewNode.NODE_WIDTH-DrawableViewNode.INDEX_PADDING-indexExtent.x;
+        y = node.top+DrawableViewNode.NODE_HEIGHT-DrawableViewNode.INDEX_PADDING-indexExtent.y;
+        gc.drawText(Integer.toString(node.viewNode.index), (int) x, (int) y, SWT.DRAW_TRANSPARENT);
+
+
+
+        int N = node.children.size();
+        if (N == 0) {
+            return;
+        }
+        float childSpacing = (1.0f * (DrawableViewNode.NODE_HEIGHT - 2 * 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 + LINE_PADDING + childSpacing * i + childSpacing / 2;
+            float x2 = child.left;
+            float y2 = (float) child.top + DrawableViewNode.NODE_HEIGHT / 2.0f;
+            float cx1 = x1 + BEZIER_FRACTION * DrawableViewNode.PARENT_CHILD_SPACING;
+            float cy1 = y1;
+            float cx2 = x2 - BEZIER_FRACTION * DrawableViewNode.PARENT_CHILD_SPACING;
+            float cy2 = y2;
+            connectionPath.moveTo(x1, y1);
+            connectionPath.cubicTo(cx1, cy1, cx2, cy2, x2, y2);
+        }
+    }
+
+    private void drawTextInArea(GC gc, String text, double x, double y, double width, double height) {
+        org.eclipse.swt.graphics.Point extent = gc.stringExtent(text);
+
+        if (extent.x > width) {
+            // Oh no... we need to scale it.
+            double scale = width / extent.x;
+            float[] transformElements = new float[6];
+            transform.getElements(transformElements);
+            transform.scale((float) scale, (float) scale);
+            gc.setTransform(transform);
+
+            x/=scale;
+            y/=scale;
+            y += (extent.y / scale - extent.y) / 2;
+
+            gc.drawText(text, (int) x, (int) y, SWT.DRAW_TRANSPARENT);
+
+            transform.setElements(transformElements[0], transformElements[1], transformElements[2],
+                    transformElements[3], transformElements[4], transformElements[5]);
+            gc.setTransform(transform);
+        } else {
+            gc.drawText(text, (int) x, (int) y, SWT.DRAW_TRANSPARENT);
+        }
+
+    }
+
+    private void doRedraw() {
+        Display.getDefault().asyncExec(new Runnable() {
+            public void run() {
+                redraw();
+            }
+        });
+    }
+
+    public void treeChanged() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (this) {
+                    tree = model.getTree();
+                    selectedNode = model.getSelection();
+                    if (tree == null) {
+                        viewport = null;
+                    } else {
+                        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() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (this) {
+                    viewport = model.getViewport();
+                    zoom = model.getZoom();
+                    setTransform();
+                }
+            }
+        });
+        doRedraw();
+    }
+
+    public void zoomChanged() {
+        viewportChanged();
+    }
+
+    public void selectionChanged() {
+        synchronized (this) {
+            selectedNode = model.getSelection();
+        }
+        doRedraw();
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/TreeViewOverview.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeViewOverview.java
similarity index 68%
rename from hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/TreeViewOverview.java
rename to hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeViewOverview.java
index feed1af..83a2b0d 100644
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/TreeViewOverview.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeViewOverview.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.hierarchyvieweruilib;
+package com.android.hierarchyviewerlib.ui;
 
 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 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;
@@ -32,6 +32,7 @@
 import org.eclipse.swt.events.PaintEvent;
 import org.eclipse.swt.events.PaintListener;
 import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Path;
 import org.eclipse.swt.graphics.Transform;
 import org.eclipse.swt.widgets.Canvas;
 import org.eclipse.swt.widgets.Composite;
@@ -89,7 +90,7 @@
 
         public void mouseDown(MouseEvent e) {
             boolean redraw = false;
-            synchronized (this) {
+            synchronized (TreeViewOverview.this) {
                 if (tree != null && viewport != null) {
                     dragging = true;
                     redraw = true;
@@ -106,7 +107,7 @@
 
         public void mouseUp(MouseEvent e) {
             boolean redraw = false;
-            synchronized (this) {
+            synchronized (TreeViewOverview.this) {
                 if (tree != null && viewport != null) {
                     dragging = false;
                     redraw = true;
@@ -128,7 +129,7 @@
     private MouseMoveListener mouseMoveListener = new MouseMoveListener() {
         public void mouseMove(MouseEvent e) {
             boolean moved = false;
-            synchronized (this) {
+            synchronized (TreeViewOverview.this) {
                 if (dragging) {
                     moved = true;
                     handleMouseEvent(transformPoint(e.x, e.y));
@@ -170,7 +171,7 @@
 
     private Listener resizeListener = new Listener() {
         public void handleEvent(Event arg0) {
-            synchronized (this) {
+            synchronized (TreeViewOverview.this) {
                 setTransform();
             }
             doRedraw();
@@ -179,41 +180,59 @@
 
     private PaintListener paintListener = new PaintListener() {
         public void paintControl(PaintEvent e) {
-            if (tree != null && viewport != null) {
-                e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
-                e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height);
-                e.gc.setTransform(transform);
-                paintRecursive(e.gc, tree);
+            synchronized (TreeViewOverview.this) {
+                if (tree != null && viewport != null) {
+                    e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
+                    e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height);
+                    e.gc.setTransform(transform);
+                    Path connectionPath = new Path(Display.getDefault());
+                    paintRecursive(e.gc, tree, connectionPath);
+                    e.gc.drawPath(connectionPath);
+                    connectionPath.dispose();
 
-                e.gc.setAlpha(80);
-                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(80);
+                    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));
+                    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 paintRecursive(GC gc, DrawableViewNode node) {
+    private void paintRecursive(GC gc, DrawableViewNode node, Path connectionPath) {
         gc.drawRectangle(node.left, (int) Math.round(node.top), DrawableViewNode.NODE_WIDTH,
                 DrawableViewNode.NODE_HEIGHT);
         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);
-            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);
+            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() {
+        Display.getDefault().asyncExec(new Runnable() {
             public void run() {
                 redraw();
             }
@@ -221,11 +240,15 @@
     }
 
     public void treeChanged() {
-        synchronized (this) {
-            tree = model.getTree();
-            setBounds();
-            setTransform();
-        }
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (this) {
+                    tree = model.getTree();
+                    setBounds();
+                    setTransform();
+                }
+            }
+        });
         doRedraw();
     }
 
@@ -248,13 +271,13 @@
             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);
+            size.x = getBounds().width;
+            size.y = getBounds().height;
+            if (bounds.width == 0 || bounds.height == 0 || size.x == 0 || size.y == 0) {
+                scale = 1;
+            } else {
+                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);
@@ -271,11 +294,15 @@
     }
 
     public void viewportChanged() {
-        synchronized (this) {
-            viewport = model.getViewport();
-            setBounds();
-            setTransform();
-        }
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (this) {
+                    viewport = model.getViewport();
+                    setBounds();
+                    setTransform();
+                }
+            }
+        });
         doRedraw();
     }
 
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/scene/DrawableViewNode.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/util/DrawableViewNode.java
similarity index 90%
rename from hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/scene/DrawableViewNode.java
rename to hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/util/DrawableViewNode.java
index aff3d6d..fccc3ba 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/scene/DrawableViewNode.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/util/DrawableViewNode.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.hierarchyviewerlib.scene;
+package com.android.hierarchyviewerlib.ui.util;
 
 import com.android.hierarchyviewerlib.device.ViewNode;
 
@@ -25,15 +25,23 @@
 
     public final ArrayList<DrawableViewNode> children = new ArrayList<DrawableViewNode>();
 
-    public final static int NODE_HEIGHT = 70;
+    public final static int NODE_HEIGHT = 110;
 
-    public final static int NODE_WIDTH = 100;
+    public final static int NODE_WIDTH = 170;
 
-    public final static int LEAF_NODE_SPACING = 5;
+    public final static int CONTENT_LEFT_RIGHT_PADDING = 3;
 
-    public final static int NON_LEAF_NODE_SPACING = 10;
+    public final static int CONTENT_TOP_BOTTOM_PADDING = 7;
 
-    public final static int PARENT_CHILD_SPACING = 40;
+    public final static int CONTENT_INTER_PADDING = 3;
+
+    public final static int INDEX_PADDING = 5;
+
+    public final static int LEAF_NODE_SPACING = 9;
+
+    public final static int NON_LEAF_NODE_SPACING = 15;
+
+    public final static int PARENT_CHILD_SPACING = 50;
 
     public final static int PADDING = 30;
 
@@ -53,6 +61,8 @@
 
     public int bottomSpacing;
 
+    public boolean treeDrawn;
+
     public static class Rectangle {
         public double x, y, width, height;
 
@@ -91,12 +101,18 @@
             this.x = x;
             this.y = y;
         }
+
+        @Override
+        public String toString() {
+            return "(" + x + ", " + y + ")";
+        }
     }
 
     public Rectangle bounds = new Rectangle();
 
     public DrawableViewNode(ViewNode viewNode) {
         this.viewNode = viewNode;
+        treeDrawn = !viewNode.willNotDraw;
         if (viewNode.children.size() == 0) {
             treeHeight = NODE_HEIGHT;
             treeWidth = NODE_WIDTH;
@@ -124,6 +140,7 @@
                         child.topSpacing = NON_LEAF_NODE_SPACING;
                     }
                 }
+                treeDrawn |= child.treeDrawn;
             }
             treeWidth += NODE_WIDTH + PARENT_CHILD_SPACING;
         }
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/util/TreeColumnResizer.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/util/TreeColumnResizer.java
similarity index 81%
rename from hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/util/TreeColumnResizer.java
rename to hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/util/TreeColumnResizer.java
index 09851c3..ad18540 100644
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/util/TreeColumnResizer.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/util/TreeColumnResizer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.hierarchyvieweruilib.util;
+package com.android.hierarchyviewerlib.ui.util;
 
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.widgets.Composite;
@@ -30,6 +30,11 @@
     private Composite control;
     private int column1Width;
     private int column2Width;
+
+    private final static int MIN_COLUMN1_WIDTH = 18;
+
+    private final static int MIN_COLUMN2_WIDTH = 3;
+
     public TreeColumnResizer(Composite control, TreeColumn column1, TreeColumn column2) {
         this.control = control;
         this.column1 = column1;
@@ -79,9 +84,21 @@
             int widthDif = column1Width - column1.getWidth();
             column1Width -= widthDif;
             column2Width += widthDif;
-            if (column2Width < 0) {
-                column1Width += column2Width;
-                column2Width = 0;
+            boolean column1Changed = false;
+
+            // Strange, but these constants make the columns look the same.
+
+            if (column1Width < MIN_COLUMN1_WIDTH) {
+                column2Width -= MIN_COLUMN1_WIDTH - column1Width;
+                column1Width += MIN_COLUMN1_WIDTH - column1Width;
+                column1Changed = true;
+            }
+            if (column2Width < MIN_COLUMN2_WIDTH) {
+                column1Width += column2Width - MIN_COLUMN2_WIDTH;
+                column2Width = MIN_COLUMN2_WIDTH;
+                column1Changed = true;
+            }
+            if (column1Changed) {
                 column1.removeListener(SWT.Resize, this);
                 column1.setWidth(column1Width);
                 column1.addListener(SWT.Resize, this);
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/green.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/green.png
new file mode 100644
index 0000000..b52a342
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/green.png
Binary files differ
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/red.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/red.png
new file mode 100644
index 0000000..338c2d9
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/red.png
Binary files differ
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/yellow.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/yellow.png
new file mode 100644
index 0000000..b6fadac
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/yellow.png
Binary files differ
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/.classpath b/hierarchyviewer2/libs/hierarchyvieweruilib/.classpath
deleted file mode 100644
index 4bddd6c..0000000
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/hierarchyviewerlib"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/ddmuilib"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/.gitignore b/hierarchyviewer2/libs/hierarchyvieweruilib/.gitignore
deleted file mode 100644
index e660fd9..0000000
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-bin/
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/.project b/hierarchyviewer2/libs/hierarchyvieweruilib/.project
deleted file mode 100644
index e9b1298..0000000
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>hierarchyvieweruilib</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/Android.mk b/hierarchyviewer2/libs/hierarchyvieweruilib/Android.mk
deleted file mode 100644
index 341880b..0000000
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# 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.
-
-HIERARCHYVIEWERUILIB_LOCAL_DIR := $(call my-dir)
-include $(HIERARCHYVIEWERUILIB_LOCAL_DIR)/src/Android.mk
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/Android.mk b/hierarchyviewer2/libs/hierarchyvieweruilib/src/Android.mk
deleted file mode 100644
index 44d6b51..0000000
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/Android.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-# 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.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-LOCAL_JAVA_LIBRARIES := \
-    ddmlib \
-    ddmuilib \
-    hierarchyviewerlib \
-    swt \
-    org.eclipse.jface_3.4.2.M20090107-0800
-LOCAL_MODULE := hierarchyvieweruilib
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
diff --git a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/TreeView.java b/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/TreeView.java
deleted file mode 100644
index f5d7ae0..0000000
--- a/hierarchyviewer2/libs/hierarchyvieweruilib/src/com/android/hierarchyvieweruilib/TreeView.java
+++ /dev/null
@@ -1,361 +0,0 @@
-/*
- * 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.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.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 DrawableViewNode selectedNode;
-
-    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);
-        addDisposeListener(disposeListener);
-
-        transform = new Transform(Display.getDefault());
-        inverse = new Transform(Display.getDefault());
-    }
-
-    private DisposeListener disposeListener = new DisposeListener() {
-        public void widgetDisposed(DisposeEvent e) {
-            model.removeTreeChangeListener(TreeView.this);
-            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) {
-            boolean selectionChanged = false;
-            synchronized (this) {
-                if (tree != null && viewport != null) {
-                    Point pt = transformPoint(e.x, e.y);
-                    draggedNode = tree.getSelected(pt.x, pt.y);
-                    if (draggedNode != null && draggedNode != selectedNode) {
-                        selectedNode = draggedNode;
-                        selectionChanged = true;
-                    }
-                    if (draggedNode == tree) {
-                        draggedNode = null;
-                    }
-                    if (draggedNode != null) {
-                        lastPoint = pt;
-                    } else {
-                        lastPoint = new Point(e.x, e.y);
-                    }
-                }
-            }
-            if (selectionChanged) {
-                model.setSelection(selectedNode);
-            }
-        }
-
-        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);
-                    e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_GRAY));
-                    paintRecursive(e.gc, tree);
-                }
-            }
-        }
-    };
-
-    private void paintRecursive(GC gc, DrawableViewNode node) {
-        if (selectedNode == node) {
-            gc.fillRectangle(node.left, (int) Math.round(node.top), DrawableViewNode.NODE_WIDTH,
-                    DrawableViewNode.NODE_HEIGHT);
-        } else {
-            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();
-            selectedNode = model.getSelection();
-            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();
-    }
-
-    public void selectionChanged() {
-        synchronized (this) {
-            selectedNode = model.getSelection();
-        }
-        doRedraw();
-    }
-}