Refactoring and integrating into Eclipse

Change-Id: I1fd3c3828fb2474f2f7394ee2831fcd7eb675878
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk b/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk
index 922e337..ecaabde 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk
@@ -20,9 +20,9 @@
 
 LOCAL_JAVA_LIBRARIES := ddmlib \
     ddmuilib \
-    hierarchyviewerlib \
     swt \
-    org.eclipse.jface_3.4.2.M20090107-0800
+    org.eclipse.jface_3.4.2.M20090107-0800 \
+    org.eclipse.core.commands_3.4.0.I20080509-2000
 
 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 f0a705c..1264d60 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java
@@ -50,6 +50,8 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.HashSet;
+import java.util.Timer;
+import java.util.TimerTask;
 
 /**
  * This is the class where most of the logic resides.
@@ -63,8 +65,21 @@
 
     private int pixelPerfectRefreshesInProgress = 0;
 
+    private Timer pixelPerfectRefreshTimer = new Timer();
+
+    private boolean autoRefresh = false;
+
+    public static final int DEFAULT_PIXEL_PERFECT_AUTOREFRESH_INTERVAL = 5;
+
+    private int pixelPerfectAutoRefreshInterval = DEFAULT_PIXEL_PERFECT_AUTOREFRESH_INTERVAL;
+
+    private PixelPerfectAutoRefreshTask currentAutoRefreshTask;
+
+    private String filterText = "";
+
     public void terminate() {
         WindowUpdater.terminate();
+        pixelPerfectRefreshTimer.cancel();
     }
 
     public abstract String getAdbLocation();
@@ -101,7 +116,9 @@
     public void deviceConnected(final IDevice device) {
         executeInBackground("Connecting device", new Runnable() {
             public void run() {
-                if (device.isOnline()) {
+                if (DeviceSelectionModel.getModel().containsDevice(device)) {
+                    windowsChanged(device);
+                } else if (device.isOnline()) {
                     DeviceBridge.setupDeviceForward(device);
                     if (!DeviceBridge.isViewServerRunning(device)) {
                         if (!DeviceBridge.startViewServer(device)) {
@@ -157,6 +174,7 @@
                 Window treeViewWindow = TreeViewModel.getModel().getWindow();
                 if (treeViewWindow != null && treeViewWindow.getDevice() == device) {
                     TreeViewModel.getModel().setData(null, null);
+                    filterText = "";
                 }
             }
         });
@@ -289,6 +307,8 @@
         executeInBackground("Loading view hierarchy", new Runnable() {
             public void run() {
 
+                filterText = "";
+
                 ViewNode viewNode = DeviceBridge.loadWindowData(window);
                 if (viewNode != null) {
                     DeviceBridge.loadProfileData(window, viewNode);
@@ -329,7 +349,7 @@
                 final Image image = loadCapture(viewNode);
                 if (image != null) {
 
-                    Display.getDefault().asyncExec(new Runnable() {
+                    Display.getDefault().syncExec(new Runnable() {
                         public void run() {
                             CaptureDisplay.show(shell, viewNode, image);
                         }
@@ -576,6 +596,7 @@
     }
 
     public void filterNodes(String filterText) {
+        this.filterText = filterText;
         DrawableViewNode tree = TreeViewModel.getModel().getTree();
         if (tree != null) {
             tree.viewNode.filter(filterText);
@@ -584,4 +605,56 @@
         }
     }
 
+    public String getFilterText() {
+        return filterText;
+    }
+
+    private static class PixelPerfectAutoRefreshTask extends TimerTask {
+        @Override
+        public void run() {
+            HierarchyViewerDirector.getDirector().refreshPixelPerfect();
+        }
+    };
+
+    public void setPixelPerfectAutoRefresh(boolean value) {
+        synchronized (pixelPerfectRefreshTimer) {
+            if (value == autoRefresh) {
+                return;
+            }
+            autoRefresh = value;
+            if (autoRefresh) {
+                currentAutoRefreshTask = new PixelPerfectAutoRefreshTask();
+                pixelPerfectRefreshTimer.schedule(currentAutoRefreshTask,
+                        pixelPerfectAutoRefreshInterval * 1000,
+                        pixelPerfectAutoRefreshInterval * 1000);
+            } else {
+                currentAutoRefreshTask.cancel();
+                currentAutoRefreshTask = null;
+            }
+        }
+    }
+    
+    public void setPixelPerfectAutoRefreshInterval(int value) {
+        synchronized (pixelPerfectRefreshTimer) {
+            if (pixelPerfectAutoRefreshInterval == value) {
+                return;
+            }
+            pixelPerfectAutoRefreshInterval = value;
+            if (autoRefresh) {
+                currentAutoRefreshTask.cancel();
+                long timeLeft =
+                        Math.max(0, pixelPerfectAutoRefreshInterval
+                                * 1000
+                                - (System.currentTimeMillis() - currentAutoRefreshTask
+                                        .scheduledExecutionTime()));
+                currentAutoRefreshTask = new PixelPerfectAutoRefreshTask();
+                pixelPerfectRefreshTimer.schedule(currentAutoRefreshTask, timeLeft,
+                        pixelPerfectAutoRefreshInterval * 1000);
+            }
+        }
+    }
+
+    public int getPixelPerfectAutoRefreshInverval() {
+        return pixelPerfectAutoRefreshInterval;
+    }
 }
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/CapturePSDAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/CapturePSDAction.java
new file mode 100644
index 0000000..240ced1
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/CapturePSDAction.java
@@ -0,0 +1,61 @@
+/*
+ * 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.actions;
+
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class CapturePSDAction extends TreeViewEnabledAction implements ImageAction {
+
+    private static CapturePSDAction action;
+
+    private Image image;
+
+    private Shell shell;
+
+    private CapturePSDAction(Shell shell) {
+        super("&Capture Layers");
+        this.shell = shell;
+        setAccelerator(SWT.MOD1 + 'C');
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("capture-psd.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Capture the window layers as a photoshop document");
+    }
+
+    public static CapturePSDAction getAction(Shell shell) {
+        if (action == null) {
+            action = new CapturePSDAction(shell);
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().capturePSD(shell);
+    }
+
+    public Image getImage() {
+        return image;
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/DisplayViewAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/DisplayViewAction.java
new file mode 100644
index 0000000..4fc8024
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/DisplayViewAction.java
@@ -0,0 +1,61 @@
+/*
+ * 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.actions;
+
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class DisplayViewAction extends SelectedNodeEnabledAction implements ImageAction {
+
+    private static DisplayViewAction action;
+
+    private Image image;
+
+    private Shell shell;
+
+    private DisplayViewAction(Shell shell) {
+        super("&Display View");
+        this.shell = shell;
+        setAccelerator(SWT.MOD1 + 'D');
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("display.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Display the selected view image in a separate window");
+    }
+
+    public static DisplayViewAction getAction(Shell shell) {
+        if (action == null) {
+            action = new DisplayViewAction(shell);
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().showCapture(shell);
+    }
+
+    public Image getImage() {
+        return image;
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/ImageAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/ImageAction.java
new file mode 100644
index 0000000..08320fd
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/ImageAction.java
@@ -0,0 +1,27 @@
+/*
+ * 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.actions;
+
+import org.eclipse.swt.graphics.Image;
+
+public interface ImageAction {
+    public Image getImage();
+
+    public String getText();
+
+    public String getToolTipText();
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/InspectScreenshotAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/InspectScreenshotAction.java
new file mode 100644
index 0000000..7ef7109
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/InspectScreenshotAction.java
@@ -0,0 +1,89 @@
+/*
+ * 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.actions;
+
+import com.android.ddmlib.IDevice;
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+import com.android.hierarchyviewerlib.device.Window;
+import com.android.hierarchyviewerlib.models.DeviceSelectionModel;
+import com.android.hierarchyviewerlib.models.DeviceSelectionModel.WindowChangeListener;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+public class InspectScreenshotAction extends Action implements ImageAction, WindowChangeListener {
+
+    private static InspectScreenshotAction action;
+
+    private Image image;
+
+    private InspectScreenshotAction() {
+        super("Inspect &Screenshot");
+        setAccelerator(SWT.MOD1 + 'S');
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("inspect-screenshot.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Inspect a screenshot in the pixel perfect view");
+        setEnabled(
+                DeviceSelectionModel.getModel().getSelectedDevice() != null);
+        DeviceSelectionModel.getModel().addWindowChangeListener(this);
+    }
+
+    public static InspectScreenshotAction getAction() {
+        if (action == null) {
+            action = new InspectScreenshotAction();
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().inspectScreenshot();
+    }
+
+    public Image getImage() {
+        return image;
+    }
+
+    public void deviceChanged(IDevice device) {
+        // pass
+    }
+
+    public void deviceConnected(IDevice device) {
+        // pass
+    }
+
+    public void deviceDisconnected(IDevice device) {
+        // pass
+    }
+
+    public void focusChanged(IDevice device) {
+        // pass
+    }
+
+    public void selectionChanged(final IDevice device, final Window window) {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                InspectScreenshotAction.getAction().setEnabled(device != null);
+            }
+        });
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/InvalidateAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/InvalidateAction.java
new file mode 100644
index 0000000..aaf0ff0
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/InvalidateAction.java
@@ -0,0 +1,57 @@
+/*
+ * 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.actions;
+
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+public class InvalidateAction extends SelectedNodeEnabledAction implements ImageAction {
+
+    private static InvalidateAction action;
+
+    private Image image;
+
+    private InvalidateAction() {
+        super("&Invalidate Layout");
+        setAccelerator(SWT.MOD1 + 'I');
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("invalidate.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Invalidate the layout for the current window");
+    }
+
+    public static InvalidateAction getAction() {
+        if (action == null) {
+            action = new InvalidateAction();
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().invalidateCurrentNode();
+    }
+
+    public Image getImage() {
+        return image;
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/LoadOverlayAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/LoadOverlayAction.java
new file mode 100644
index 0000000..c948914
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/LoadOverlayAction.java
@@ -0,0 +1,61 @@
+/*
+ * 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.actions;
+
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class LoadOverlayAction extends PixelPerfectEnabledAction implements ImageAction {
+
+    private static LoadOverlayAction action;
+
+    private Image image;
+
+    private Shell shell;
+
+    private LoadOverlayAction(Shell shell) {
+        super("Load &Overlay");
+        this.shell = shell;
+        setAccelerator(SWT.MOD1 + 'O');
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("load-overlay.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Load an image to overlay the screenshot");
+    }
+
+    public static LoadOverlayAction getAction(Shell shell) {
+        if (action == null) {
+            action = new LoadOverlayAction(shell);
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().loadOverlay(shell);
+    }
+
+    public Image getImage() {
+        return image;
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/LoadViewHierarchyAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/LoadViewHierarchyAction.java
new file mode 100644
index 0000000..d26b2ef
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/LoadViewHierarchyAction.java
@@ -0,0 +1,89 @@
+/*
+ * 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.actions;
+
+import com.android.ddmlib.IDevice;
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+import com.android.hierarchyviewerlib.device.Window;
+import com.android.hierarchyviewerlib.models.DeviceSelectionModel;
+import com.android.hierarchyviewerlib.models.DeviceSelectionModel.WindowChangeListener;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+public class LoadViewHierarchyAction extends Action implements ImageAction, WindowChangeListener {
+
+    private static LoadViewHierarchyAction action;
+
+    private Image image;
+
+    private LoadViewHierarchyAction() {
+        super("Load View &Hierarchy");
+        setAccelerator(SWT.MOD1 + 'H');
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("load-view-hierarchy.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Load the view hierarchy into the tree view");
+        setEnabled(
+                DeviceSelectionModel.getModel().getSelectedWindow() != null);
+        DeviceSelectionModel.getModel().addWindowChangeListener(this);
+    }
+
+    public static LoadViewHierarchyAction getAction() {
+        if (action == null) {
+            action = new LoadViewHierarchyAction();
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().loadViewHierarchy();
+    }
+
+    public Image getImage() {
+        return image;
+    }
+
+    public void deviceChanged(IDevice device) {
+        // pass
+    }
+
+    public void deviceConnected(IDevice device) {
+        // pass
+    }
+
+    public void deviceDisconnected(IDevice device) {
+        // pass
+    }
+
+    public void focusChanged(IDevice device) {
+        // pass
+    }
+
+    public void selectionChanged(final IDevice device, final Window window) {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                LoadViewHierarchyAction.getAction().setEnabled(window != null);
+            }
+        });
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/PixelPerfectAutoRefreshAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/PixelPerfectAutoRefreshAction.java
new file mode 100644
index 0000000..5e31829
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/PixelPerfectAutoRefreshAction.java
@@ -0,0 +1,58 @@
+/*
+ * 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.actions;
+
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+public class PixelPerfectAutoRefreshAction extends PixelPerfectEnabledAction implements ImageAction {
+
+    private static PixelPerfectAutoRefreshAction action;
+
+    private Image image;
+
+    private PixelPerfectAutoRefreshAction() {
+        super("Auto &Refresh", Action.AS_CHECK_BOX);
+        setAccelerator(SWT.MOD1 + 'R');
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("auto-refresh.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Automatically refresh the screenshot");
+    }
+
+    public static PixelPerfectAutoRefreshAction getAction() {
+        if (action == null) {
+            action = new PixelPerfectAutoRefreshAction();
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().setPixelPerfectAutoRefresh(action.isChecked());
+    }
+
+    public Image getImage() {
+        return image;
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/PixelPerfectEnabledAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/PixelPerfectEnabledAction.java
new file mode 100644
index 0000000..9423d10
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/PixelPerfectEnabledAction.java
@@ -0,0 +1,73 @@
+/*
+ * 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.actions;
+
+import com.android.hierarchyviewerlib.models.PixelPerfectModel;
+import com.android.hierarchyviewerlib.models.PixelPerfectModel.ImageChangeListener;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.swt.widgets.Display;
+
+public class PixelPerfectEnabledAction extends Action implements ImageChangeListener {
+    public PixelPerfectEnabledAction(String name) {
+        super(name);
+        setEnabled(PixelPerfectModel.getModel().getImage() != null);
+        PixelPerfectModel.getModel().addImageChangeListener(this);
+    }
+
+    public PixelPerfectEnabledAction(String name, int type) {
+        super(name, type);
+        setEnabled(PixelPerfectModel.getModel().getImage() != null);
+        PixelPerfectModel.getModel().addImageChangeListener(this);
+    }
+
+    public void crosshairMoved() {
+        // pass
+    }
+
+    public void imageChanged() {
+        // 
+    }
+
+    public void imageLoaded() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                setEnabled(PixelPerfectModel.getModel().getImage() != null);
+            }
+        });
+    }
+
+    public void overlayChanged() {
+        // pass
+    }
+
+    public void overlayTransparencyChanged() {
+        // pass
+    }
+
+    public void selectionChanged() {
+        // pass
+    }
+
+    public void treeChanged() {
+        // pass
+    }
+
+    public void zoomChanged() {
+        // pass
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RefreshPixelPerfectAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RefreshPixelPerfectAction.java
new file mode 100644
index 0000000..a5d7514
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RefreshPixelPerfectAction.java
@@ -0,0 +1,57 @@
+/*
+ * 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.actions;
+
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+public class RefreshPixelPerfectAction extends PixelPerfectEnabledAction implements ImageAction {
+
+    private static RefreshPixelPerfectAction action;
+
+    private Image image;
+
+    private RefreshPixelPerfectAction() {
+        super("&Refresh Screenshot");
+        setAccelerator(SWT.F5);
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("refresh-windows.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Refresh the screenshot");
+    }
+
+    public static RefreshPixelPerfectAction getAction() {
+        if (action == null) {
+            action = new RefreshPixelPerfectAction();
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().refreshPixelPerfect();
+    }
+
+    public Image getImage() {
+        return image;
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RefreshPixelPerfectTreeAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RefreshPixelPerfectTreeAction.java
new file mode 100644
index 0000000..41214df
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RefreshPixelPerfectTreeAction.java
@@ -0,0 +1,57 @@
+/*
+ * 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.actions;
+
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+public class RefreshPixelPerfectTreeAction extends PixelPerfectEnabledAction implements ImageAction {
+
+    private static RefreshPixelPerfectTreeAction action;
+
+    private Image image;
+
+    private RefreshPixelPerfectTreeAction() {
+        super("Refresh &Tree");
+        setAccelerator(SWT.MOD1 + 'T');
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("load-view-hierarchy.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Refresh the tree");
+    }
+
+    public static RefreshPixelPerfectTreeAction getAction() {
+        if (action == null) {
+            action = new RefreshPixelPerfectTreeAction();
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().refreshPixelPerfectTree();
+    }
+
+    public Image getImage() {
+        return image;
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RefreshViewAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RefreshViewAction.java
new file mode 100644
index 0000000..06c48ee
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RefreshViewAction.java
@@ -0,0 +1,57 @@
+/*
+ * 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.actions;
+
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+public class RefreshViewAction extends TreeViewEnabledAction implements ImageAction {
+
+    private static RefreshViewAction action;
+
+    private Image image;
+
+    private RefreshViewAction() {
+        super("Load View &Hierarchy");
+        setAccelerator(SWT.MOD1 + 'H');
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("load-view-hierarchy.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Reload the view hierarchy");
+    }
+
+    public static RefreshViewAction getAction() {
+        if (action == null) {
+            action = new RefreshViewAction();
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().reloadViewHierarchy();
+    }
+
+    public Image getImage() {
+        return image;
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RefreshWindowsAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RefreshWindowsAction.java
new file mode 100644
index 0000000..47d692a
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RefreshWindowsAction.java
@@ -0,0 +1,58 @@
+/*
+ * 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.actions;
+
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+public class RefreshWindowsAction extends Action implements ImageAction {
+
+    private static RefreshWindowsAction action;
+
+    private Image image;
+
+    private RefreshWindowsAction() {
+        super("&Refresh");
+        setAccelerator(SWT.F5);
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("refresh-windows.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Refresh the list of devices");
+    }
+
+    public static RefreshWindowsAction getAction() {
+        if (action == null) {
+            action = new RefreshWindowsAction();
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().refreshWindows();
+    }
+
+    public Image getImage() {
+        return image;
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RequestLayoutAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RequestLayoutAction.java
new file mode 100644
index 0000000..e3d6f8f
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/RequestLayoutAction.java
@@ -0,0 +1,57 @@
+/*
+ * 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.actions;
+
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+public class RequestLayoutAction extends SelectedNodeEnabledAction implements ImageAction {
+
+    private static RequestLayoutAction action;
+
+    private Image image;
+
+    private RequestLayoutAction() {
+        super("Request &Layout");
+        setAccelerator(SWT.MOD1 + 'L');
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("request-layout.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Request the view to lay out");
+    }
+
+    public static RequestLayoutAction getAction() {
+        if (action == null) {
+            action = new RequestLayoutAction();
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().relayoutCurrentNode();
+    }
+
+    public Image getImage() {
+        return image;
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/SavePixelPerfectAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/SavePixelPerfectAction.java
new file mode 100644
index 0000000..9781d42
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/SavePixelPerfectAction.java
@@ -0,0 +1,61 @@
+/*
+ * 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.actions;
+
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class SavePixelPerfectAction extends PixelPerfectEnabledAction implements ImageAction {
+
+    private static SavePixelPerfectAction action;
+
+    private Image image;
+
+    private Shell shell;
+
+    private SavePixelPerfectAction(Shell shell) {
+        super("&Save as PNG");
+        this.shell = shell;
+        setAccelerator(SWT.MOD1 + 'S');
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("save.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Save the screenshot as a PNG image");
+    }
+
+    public static SavePixelPerfectAction getAction(Shell shell) {
+        if (action == null) {
+            action = new SavePixelPerfectAction(shell);
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().savePixelPerfect(shell);
+    }
+
+    public Image getImage() {
+        return image;
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/SaveTreeViewAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/SaveTreeViewAction.java
new file mode 100644
index 0000000..094b101
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/SaveTreeViewAction.java
@@ -0,0 +1,61 @@
+/*
+ * 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.actions;
+
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class SaveTreeViewAction extends TreeViewEnabledAction implements ImageAction {
+
+    private static SaveTreeViewAction action;
+
+    private Image image;
+
+    private Shell shell;
+
+    private SaveTreeViewAction(Shell shell) {
+        super("&Save as PNG");
+        this.shell = shell;
+        setAccelerator(SWT.MOD1 + 'S');
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        image = imageLoader.loadImage("save.png", Display.getDefault());
+        setImageDescriptor(ImageDescriptor.createFromImage(image));
+        setToolTipText("Save the tree view as a PNG image");
+    }
+
+    public static SaveTreeViewAction getAction(Shell shell) {
+        if (action == null) {
+            action = new SaveTreeViewAction(shell);
+        }
+        return action;
+    }
+
+    @Override
+    public void run() {
+        HierarchyViewerDirector.getDirector().saveTreeView(shell);
+    }
+
+    public Image getImage() {
+        return image;
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/SelectedNodeEnabledAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/SelectedNodeEnabledAction.java
new file mode 100644
index 0000000..86f75a4
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/SelectedNodeEnabledAction.java
@@ -0,0 +1,56 @@
+/*
+ * 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.actions;
+
+import com.android.hierarchyviewerlib.models.TreeViewModel;
+import com.android.hierarchyviewerlib.models.TreeViewModel.TreeChangeListener;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.swt.widgets.Display;
+
+public class SelectedNodeEnabledAction extends Action implements TreeChangeListener {
+    public SelectedNodeEnabledAction(String name) {
+        super(name);
+        setEnabled(TreeViewModel.getModel().getTree() != null
+                && TreeViewModel.getModel().getSelection() != null);
+        TreeViewModel.getModel().addTreeChangeListener(this);
+    }
+
+    public void selectionChanged() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                setEnabled(TreeViewModel.getModel().getTree() != null
+                        && TreeViewModel.getModel().getSelection() != null);
+            }
+        });
+    }
+
+    public void treeChanged() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                setEnabled(TreeViewModel.getModel().getTree() != null
+                        && TreeViewModel.getModel().getSelection() != null);
+            }
+        });
+    }
+
+    public void viewportChanged() {
+    }
+
+    public void zoomChanged() {
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/TreeViewEnabledAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/TreeViewEnabledAction.java
new file mode 100644
index 0000000..9ac7fb1
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/TreeViewEnabledAction.java
@@ -0,0 +1,49 @@
+/*
+ * 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.actions;
+
+import com.android.hierarchyviewerlib.models.TreeViewModel;
+import com.android.hierarchyviewerlib.models.TreeViewModel.TreeChangeListener;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.swt.widgets.Display;
+
+public class TreeViewEnabledAction extends Action implements TreeChangeListener {
+    public TreeViewEnabledAction(String name) {
+        super(name);
+        setEnabled(TreeViewModel.getModel().getTree() != null);
+        TreeViewModel.getModel().addTreeChangeListener(this);
+    }
+
+    public void selectionChanged() {
+        // pass
+    }
+
+    public void treeChanged() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                setEnabled(TreeViewModel.getModel().getTree() != null);
+            }
+        });
+    }
+
+    public void viewportChanged() {
+    }
+
+    public void zoomChanged() {
+    }
+}
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 9e91375..399e470 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java
@@ -94,6 +94,9 @@
     }
 
     public static IDevice[] getDevices() {
+        if (bridge == null) {
+            return new IDevice[0];
+        }
         return bridge.getDevices();
     }
 
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/DeviceSelectionModel.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/DeviceSelectionModel.java
index 8888642..09dfe76 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/DeviceSelectionModel.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/DeviceSelectionModel.java
@@ -51,6 +51,12 @@
         return model;
     }
 
+    public boolean containsDevice(IDevice device) {
+        synchronized (deviceMap) {
+            return deviceMap.containsKey(device);
+        }
+    }
+
     public void addDevice(IDevice device, Window[] windows) {
         synchronized (deviceMap) {
             deviceMap.put(device, windows);
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 7702f49..e52db14 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/PixelPerfectModel.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/PixelPerfectModel.java
@@ -33,6 +33,8 @@
 
     public static final int DEFAULT_ZOOM = 8;
 
+    public static final int DEFAULT_OVERLAY_TRANSPARENCY_PERCENTAGE = 50;
+
     private IDevice device;
 
     private Image image;
@@ -50,7 +52,7 @@
 
     private Image overlayImage;
 
-    private double overlayTransparency = 0.5;
+    private double overlayTransparency = DEFAULT_OVERLAY_TRANSPARENCY_PERCENTAGE / 100.0;
 
     private static PixelPerfectModel model;
 
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/CaptureDisplay.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/CaptureDisplay.java
index 3c2c356..ecee8d0 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/CaptureDisplay.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/CaptureDisplay.java
@@ -16,6 +16,8 @@
 
 package com.android.hierarchyviewerlib.ui;
 
+import com.android.ddmuilib.ImageLoader;
+import com.android.hierarchyviewerlib.HierarchyViewerDirector;
 import com.android.hierarchyviewerlib.device.ViewNode;
 
 import org.eclipse.swt.SWT;
@@ -118,6 +120,10 @@
         canvas.addPaintListener(paintListener);
 
         shell.addShellListener(shellListener);
+
+        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
+        Image image = imageLoader.loadImage("display.png", Display.getDefault());
+        shell.setImage(image);
     }
 
     private static PaintListener paintListener = new PaintListener() {
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/DeviceSelector.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/DeviceSelector.java
index 1aded6b..4e0748f 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/DeviceSelector.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/DeviceSelector.java
@@ -62,9 +62,13 @@
 
     private final static int ICON_WIDTH = 16;
 
+    private boolean doTreeViewStuff;
+
+    private boolean doPixelPerfectStuff;
+
     private class ContentProvider implements ITreeContentProvider, ILabelProvider, IFontProvider {
         public Object[] getChildren(Object parentElement) {
-            if (parentElement instanceof IDevice) {
+            if (parentElement instanceof IDevice && doTreeViewStuff) {
                 Window[] list = model.getWindows((IDevice) parentElement);
                 if (list != null) {
                     return list;
@@ -81,7 +85,7 @@
         }
 
         public boolean hasChildren(Object element) {
-            if (element instanceof IDevice) {
+            if (element instanceof IDevice && doTreeViewStuff) {
                 Window[] list = model.getWindows((IDevice) element);
                 if (list != null) {
                     return list.length != 0;
@@ -148,8 +152,10 @@
         }
     }
 
-    public DeviceSelector(Composite parent) {
+    public DeviceSelector(Composite parent, boolean doTreeViewStuff, boolean doPixelPerfectStuff) {
         super(parent, SWT.NONE);
+        this.doTreeViewStuff = doTreeViewStuff;
+        this.doPixelPerfectStuff = doPixelPerfectStuff;
         setLayout(new FillLayout());
         treeViewer = new TreeViewer(this, SWT.SINGLE);
         treeViewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS);
@@ -225,8 +231,25 @@
         return tree.setFocus();
     }
 
+    public void setMode(boolean doTreeViewStuff, boolean doPixelPerfectStuff) {
+        if (this.doTreeViewStuff != doTreeViewStuff
+                || this.doPixelPerfectStuff != doPixelPerfectStuff) {
+            final boolean expandAll = !this.doTreeViewStuff && doTreeViewStuff;
+            this.doTreeViewStuff = doTreeViewStuff;
+            this.doPixelPerfectStuff = doPixelPerfectStuff;
+            Display.getDefault().syncExec(new Runnable() {
+                public void run() {
+                    treeViewer.refresh();
+                    if (expandAll) {
+                        treeViewer.expandAll();
+                    }
+                }
+            });
+        }
+    }
+
     public void deviceConnected(final IDevice device) {
-        Display.getDefault().asyncExec(new Runnable() {
+        Display.getDefault().syncExec(new Runnable() {
             public void run() {
                 treeViewer.refresh();
                 treeViewer.setExpandedState(device, true);
@@ -235,7 +258,7 @@
     }
 
     public void deviceChanged(final IDevice device) {
-        Display.getDefault().asyncExec(new Runnable() {
+        Display.getDefault().syncExec(new Runnable() {
             public void run() {
                 TreeSelection selection = (TreeSelection) treeViewer.getSelection();
                 treeViewer.refresh(device);
@@ -248,7 +271,7 @@
     }
 
     public void deviceDisconnected(final IDevice device) {
-        Display.getDefault().asyncExec(new Runnable() {
+        Display.getDefault().syncExec(new Runnable() {
             public void run() {
                 treeViewer.refresh();
             }
@@ -256,7 +279,7 @@
     }
 
     public void focusChanged(final IDevice device) {
-        Display.getDefault().asyncExec(new Runnable() {
+        Display.getDefault().syncExec(new Runnable() {
             public void run() {
                 TreeSelection selection = (TreeSelection) treeViewer.getSelection();
                 treeViewer.refresh(device);
@@ -274,9 +297,9 @@
 
     public void widgetDefaultSelected(SelectionEvent e) {
         Object selection = ((TreeItem) e.item).getData();
-        if (selection instanceof IDevice) {
+        if (selection instanceof IDevice && doPixelPerfectStuff) {
             HierarchyViewerDirector.getDirector().loadPixelPerfectData((IDevice) selection);
-        } else if (selection instanceof Window) {
+        } else if (selection instanceof Window && doTreeViewStuff) {
             HierarchyViewerDirector.getDirector().loadViewTreeData((Window) selection);
         }
     }
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/LayoutViewer.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/LayoutViewer.java
index 6ae086d..1a13e48 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/LayoutViewer.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/LayoutViewer.java
@@ -70,6 +70,8 @@
 
         transform = new Transform(Display.getDefault());
         inverse = new Transform(Display.getDefault());
+
+        treeChanged();
     }
 
     public void setShowExtras(boolean show) {
@@ -88,8 +90,12 @@
 
     private DisposeListener disposeListener = new DisposeListener() {
         public void widgetDisposed(DisposeEvent e) {
+            model.removeTreeChangeListener(LayoutViewer.this);
             transform.dispose();
             inverse.dispose();
+            if (selectedNode != null) {
+                selectedNode.viewNode.dereferenceImage();
+            }
         }
     };
 
@@ -279,7 +285,7 @@
     }
 
     private void doRedraw() {
-        Display.getDefault().asyncExec(new Runnable() {
+        Display.getDefault().syncExec(new Runnable() {
             public void run() {
                 redraw();
             }
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfect.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfect.java
index 242270b..42bcc59 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfect.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfect.java
@@ -86,6 +86,8 @@
         borderColor = new Color(Display.getDefault(), new RGB(255, 0, 0));
         marginColor = new Color(Display.getDefault(), new RGB(0, 255, 0));
         paddingColor = new Color(Display.getDefault(), new RGB(0, 0, 255));
+
+        imageLoaded();
     }
 
     private DisposeListener disposeListener = new DisposeListener() {
@@ -278,7 +280,7 @@
     };
 
     private void doRedraw() {
-        Display.getDefault().asyncExec(new Runnable() {
+        Display.getDefault().syncExec(new Runnable() {
             public void run() {
                 canvas.redraw();
             }
@@ -305,6 +307,7 @@
                     crosshairLocation = model.getCrosshairLocation();
                     selectedNode = model.getSelected();
                     overlayImage = model.getOverlayImage();
+                    overlayTransparency = model.getOverlayTransparency();
                 }
             }
         });
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectControls.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectControls.java
new file mode 100644
index 0000000..5a593f6
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectControls.java
@@ -0,0 +1,277 @@
+/*
+ * 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.HierarchyViewerDirector;
+import com.android.hierarchyviewerlib.models.PixelPerfectModel;
+import com.android.hierarchyviewerlib.models.PixelPerfectModel.ImageChangeListener;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Slider;
+
+public class PixelPerfectControls extends Composite implements ImageChangeListener {
+
+    private Slider overlaySlider;
+
+    private Slider zoomSlider;
+
+    private Slider autoRefreshSlider;
+
+    public PixelPerfectControls(Composite parent) {
+        super(parent, SWT.NONE);
+        setLayout(new FormLayout());
+
+        Label overlayTransparencyRight = new Label(this, SWT.NONE);
+        overlayTransparencyRight.setText("100%");
+        FormData overlayTransparencyRightData = new FormData();
+        overlayTransparencyRightData.right = new FormAttachment(100, -2);
+        overlayTransparencyRightData.top = new FormAttachment(0, 2);
+        overlayTransparencyRight.setLayoutData(overlayTransparencyRightData);
+
+        Label refreshRight = new Label(this, SWT.NONE);
+        refreshRight.setText("40s");
+        FormData refreshRightData = new FormData();
+        refreshRightData.right = new FormAttachment(100, -2);
+        refreshRightData.top = new FormAttachment(overlayTransparencyRight, 2);
+        refreshRightData.left = new FormAttachment(overlayTransparencyRight, 0, SWT.LEFT);
+        refreshRight.setLayoutData(refreshRightData);
+
+        Label zoomRight = new Label(this, SWT.NONE);
+        zoomRight.setText("24x");
+        FormData zoomRightData = new FormData();
+        zoomRightData.right = new FormAttachment(100, -2);
+        zoomRightData.top = new FormAttachment(refreshRight, 2);
+        zoomRightData.left = new FormAttachment(overlayTransparencyRight, 0, SWT.LEFT);
+        zoomRight.setLayoutData(zoomRightData);
+
+        Label overlayTransparency = new Label(this, SWT.NONE);
+        Label refresh = new Label(this, SWT.NONE);
+
+        overlayTransparency.setText("Overlay:");
+        FormData overlayTransparencyData = new FormData();
+        overlayTransparencyData.left = new FormAttachment(0, 2);
+        overlayTransparencyData.top = new FormAttachment(0, 2);
+        overlayTransparencyData.right = new FormAttachment(refresh, 0, SWT.RIGHT);
+        overlayTransparency.setLayoutData(overlayTransparencyData);
+
+        refresh.setText("Refresh Rate:");
+        FormData refreshData = new FormData();
+        refreshData.top = new FormAttachment(overlayTransparency, 2);
+        refreshData.left = new FormAttachment(0, 2);
+        refresh.setLayoutData(refreshData);
+
+        Label zoom = new Label(this, SWT.NONE);
+        zoom.setText("Zoom:");
+        FormData zoomData = new FormData();
+        zoomData.right = new FormAttachment(refresh, 0, SWT.RIGHT);
+        zoomData.top = new FormAttachment(refresh, 2);
+        zoomData.left = new FormAttachment(0, 2);
+        zoom.setLayoutData(zoomData);
+
+        Label overlayTransparencyLeft = new Label(this, SWT.RIGHT);
+        overlayTransparencyLeft.setText("0%");
+        FormData overlayTransparencyLeftData = new FormData();
+        overlayTransparencyLeftData.top = new FormAttachment(0, 2);
+        overlayTransparencyLeftData.left = new FormAttachment(overlayTransparency, 2);
+        overlayTransparencyLeft.setLayoutData(overlayTransparencyLeftData);
+
+        Label refreshLeft = new Label(this, SWT.RIGHT);
+        refreshLeft.setText("1s");
+        FormData refreshLeftData = new FormData();
+        refreshLeftData.top = new FormAttachment(overlayTransparencyLeft, 2);
+        refreshLeftData.left = new FormAttachment(refresh, 2);
+        refreshLeft.setLayoutData(refreshLeftData);
+
+        Label zoomLeft = new Label(this, SWT.RIGHT);
+        zoomLeft.setText("2x");
+        FormData zoomLeftData = new FormData();
+        zoomLeftData.top = new FormAttachment(refreshLeft, 2);
+        zoomLeftData.left = new FormAttachment(zoom, 2);
+        zoomLeft.setLayoutData(zoomLeftData);
+
+        overlaySlider = new Slider(this, SWT.HORIZONTAL);
+        overlaySlider.setMinimum(0);
+        overlaySlider.setMaximum(101);
+        overlaySlider.setThumb(1);
+        overlaySlider.setSelection((int) Math.round(PixelPerfectModel.getModel()
+                .getOverlayTransparency() * 100));
+
+        Image overlayImage = PixelPerfectModel.getModel().getOverlayImage();
+        overlaySlider.setEnabled(overlayImage != null);
+        FormData overlaySliderData = new FormData();
+        overlaySliderData.right = new FormAttachment(overlayTransparencyRight, -4);
+        overlaySliderData.top = new FormAttachment(0, 2);
+        overlaySliderData.left = new FormAttachment(overlayTransparencyLeft, 4);
+        overlaySlider.setLayoutData(overlaySliderData);
+
+        overlaySlider.addSelectionListener(overlaySliderSelectionListener);
+
+        autoRefreshSlider = new Slider(this, SWT.HORIZONTAL);
+        autoRefreshSlider.setMinimum(1);
+        autoRefreshSlider.setMaximum(41);
+        autoRefreshSlider.setThumb(1);
+        autoRefreshSlider.setSelection(HierarchyViewerDirector.getDirector()
+                .getPixelPerfectAutoRefreshInverval());
+        FormData refreshSliderData = new FormData();
+        refreshSliderData.right = new FormAttachment(overlayTransparencyRight, -4);
+        refreshSliderData.top = new FormAttachment(overlayTransparencyRight, 2);
+        refreshSliderData.left = new FormAttachment(overlaySlider, 0, SWT.LEFT);
+        autoRefreshSlider.setLayoutData(refreshSliderData);
+
+        autoRefreshSlider.addSelectionListener(refreshSliderSelectionListener);
+
+        zoomSlider = new Slider(this, SWT.HORIZONTAL);
+        zoomSlider.setMinimum(2);
+        zoomSlider.setMaximum(25);
+        zoomSlider.setThumb(1);
+        zoomSlider.setSelection(PixelPerfectModel.getModel().getZoom());
+        FormData zoomSliderData = new FormData();
+        zoomSliderData.right = new FormAttachment(overlayTransparencyRight, -4);
+        zoomSliderData.top = new FormAttachment(refreshRight, 2);
+        zoomSliderData.left = new FormAttachment(overlaySlider, 0, SWT.LEFT);
+        zoomSlider.setLayoutData(zoomSliderData);
+
+        zoomSlider.addSelectionListener(zoomSliderSelectionListener);
+
+        addDisposeListener(disposeListener);
+
+        PixelPerfectModel.getModel().addImageChangeListener(this);
+    }
+
+    private DisposeListener disposeListener = new DisposeListener() {
+        public void widgetDisposed(DisposeEvent e) {
+            PixelPerfectModel.getModel().removeImageChangeListener(PixelPerfectControls.this);
+        }
+    };
+
+    private SelectionListener overlaySliderSelectionListener = new SelectionListener() {
+        private int oldValue;
+
+        public void widgetDefaultSelected(SelectionEvent e) {
+            // pass
+        }
+
+        public void widgetSelected(SelectionEvent e) {
+            int newValue = overlaySlider.getSelection();
+            if (oldValue != newValue) {
+                PixelPerfectModel.getModel().removeImageChangeListener(PixelPerfectControls.this);
+                PixelPerfectModel.getModel().setOverlayTransparency(newValue / 100.0);
+                PixelPerfectModel.getModel().addImageChangeListener(PixelPerfectControls.this);
+                oldValue = newValue;
+            }
+        }
+    };
+
+    private SelectionListener refreshSliderSelectionListener = new SelectionListener() {
+        private int oldValue;
+
+        public void widgetDefaultSelected(SelectionEvent e) {
+            // pass
+        }
+
+        public void widgetSelected(SelectionEvent e) {
+            int newValue = autoRefreshSlider.getSelection();
+            if (oldValue != newValue) {
+                HierarchyViewerDirector.getDirector().setPixelPerfectAutoRefreshInterval(newValue);
+            }
+        }
+    };
+
+    private SelectionListener zoomSliderSelectionListener = new SelectionListener() {
+        private int oldValue;
+
+        public void widgetDefaultSelected(SelectionEvent e) {
+            // pass
+        }
+
+        public void widgetSelected(SelectionEvent e) {
+            int newValue = zoomSlider.getSelection();
+            if (oldValue != newValue) {
+                PixelPerfectModel.getModel().removeImageChangeListener(PixelPerfectControls.this);
+                PixelPerfectModel.getModel().setZoom(newValue);
+                PixelPerfectModel.getModel().addImageChangeListener(PixelPerfectControls.this);
+                oldValue = newValue;
+            }
+        }
+    };
+
+    public void crosshairMoved() {
+        // pass
+    }
+
+    public void treeChanged() {
+        // pass
+    }
+
+    public void imageChanged() {
+        // pass
+    }
+
+    public void imageLoaded() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                Image overlayImage = PixelPerfectModel.getModel().getOverlayImage();
+                overlaySlider.setEnabled(overlayImage != null);
+                if (PixelPerfectModel.getModel().getImage() == null) {
+                } else {
+                    zoomSlider.setSelection(PixelPerfectModel.getModel().getZoom());
+                }
+            }
+        });
+    }
+
+    public void overlayChanged() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                Image overlayImage = PixelPerfectModel.getModel().getOverlayImage();
+                overlaySlider.setEnabled(overlayImage != null);
+            }
+        });
+    }
+
+    public void overlayTransparencyChanged() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                overlaySlider.setSelection((int) (PixelPerfectModel.getModel()
+                        .getOverlayTransparency() * 100));
+            }
+        });
+    }
+
+    public void selectionChanged() {
+        // pass
+    }
+
+    public void zoomChanged() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                zoomSlider.setSelection(PixelPerfectModel.getModel().getZoom());
+            }
+        });
+    }
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectLoupe.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectLoupe.java
index b5ad13a..53afc9e 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectLoupe.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectLoupe.java
@@ -85,6 +85,8 @@
         crosshairColor = new Color(Display.getDefault(), new RGB(255, 94, 254));
 
         transform = new Transform(Display.getDefault());
+
+        imageLoaded();
     }
 
     public void setShowOverlay(boolean value) {
@@ -275,7 +277,7 @@
     };
 
     private void doRedraw() {
-        Display.getDefault().asyncExec(new Runnable() {
+        Display.getDefault().syncExec(new Runnable() {
             public void run() {
                 redraw();
             }
@@ -302,6 +304,7 @@
                     crosshairLocation = model.getCrosshairLocation();
                     zoom = model.getZoom();
                     overlayImage = model.getOverlayImage();
+                    overlayTransparency = model.getOverlayTransparency();
                 }
             }
         });
@@ -344,8 +347,8 @@
                         // grid.
                         grid.dispose();
                         grid = null;
-                        zoom = model.getZoom();
                     }
+                    zoom = model.getZoom();
                 }
             }
         });
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectPixelPanel.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectPixelPanel.java
index 8fce603..afe3dc8 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectPixelPanel.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectPixelPanel.java
@@ -52,6 +52,8 @@
 
         addPaintListener(paintListener);
         addDisposeListener(disposeListener);
+
+        imageLoaded();
     }
 
     @Override
@@ -135,7 +137,7 @@
     };
 
     private void doRedraw() {
-        Display.getDefault().asyncExec(new Runnable() {
+        Display.getDefault().syncExec(new Runnable() {
             public void run() {
                 redraw();
             }
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectTree.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectTree.java
index 80e4091..d34dcf2 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectTree.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PixelPerfectTree.java
@@ -152,15 +152,12 @@
     private void loadResources() {
         ImageLoader loader = ImageLoader.getDdmUiLibLoader();
         fileImage = loader.loadImage("file.png", Display.getDefault());
-
         folderImage = loader.loadImage("folder.png", Display.getDefault());
     }
 
     private DisposeListener disposeListener = new DisposeListener() {
         public void widgetDisposed(DisposeEvent e) {
             model.removeImageChangeListener(PixelPerfectTree.this);
-            fileImage.dispose();
-            folderImage.dispose();
         }
     };
 
@@ -170,7 +167,7 @@
     }
 
     public void imageLoaded() {
-        Display.getDefault().asyncExec(new Runnable() {
+        Display.getDefault().syncExec(new Runnable() {
             public void run() {
                 treeViewer.refresh();
                 treeViewer.expandAll();
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PropertyViewer.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PropertyViewer.java
index d7c5b1a..14068a3 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PropertyViewer.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PropertyViewer.java
@@ -210,6 +210,8 @@
         new TreeColumnResizer(this, propertyColumn, valueColumn);
 
         addControlListener(controlListener);
+
+        treeChanged();
     }
 
     public void loadResources() {
@@ -280,7 +282,7 @@
     }
 
     private void doRefresh() {
-        Display.getDefault().asyncExec(new Runnable() {
+        Display.getDefault().syncExec(new Runnable() {
             public void run() {
                 treeViewer.refresh();
             }
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeView.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeView.java
index e2a0a6d..ea53ee4 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeView.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeView.java
@@ -16,6 +16,7 @@
 
 package com.android.hierarchyviewerlib.ui;
 
+import com.android.ddmlib.Log;
 import com.android.ddmuilib.ImageLoader;
 import com.android.hierarchyviewerlib.HierarchyViewerDirector;
 import com.android.hierarchyviewerlib.device.ViewNode.ProfileRating;
@@ -164,6 +165,7 @@
         transform = new Transform(Display.getDefault());
         inverse = new Transform(Display.getDefault());
 
+        loadAllData();
     }
 
     private void loadResources() {
@@ -190,6 +192,9 @@
             inverse.dispose();
             boxColor.dispose();
             textBackgroundColor.dispose();
+            if (tree != null) {
+                model.setViewport(null);
+            }
         }
     };
 
@@ -959,12 +964,37 @@
     }
 
     private void doRedraw() {
-        Display.getDefault().asyncExec(new Runnable() {
+        Display.getDefault().syncExec(new Runnable() {
             public void run() {
                 redraw();
             }
         });
     }
+    
+    public void loadAllData() {
+        boolean newViewport = viewport == null;
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (this) {
+                    tree = model.getTree();
+                    selectedNode = model.getSelection();
+                    viewport = model.getViewport();
+                    zoom = model.getZoom();
+                    if (tree != null && viewport == null) {
+                        viewport =
+                                new Rectangle(0, tree.top + DrawableViewNode.NODE_HEIGHT / 2
+                                        - getBounds().height / 2, getBounds().width,
+                                        getBounds().height);
+                    } else {
+                        setTransform();
+                    }
+                }
+            }
+        });
+        if (newViewport) {
+            model.setViewport(viewport);
+        }
+    }
 
     // Fickle behaviour... When a new tree is loaded, the model doesn't know
     // about the viewport until it passes through here.
@@ -987,6 +1017,8 @@
         });
         if (viewport != null) {
             model.setViewport(viewport);
+        } else {
+            doRedraw();
         }
     }
 
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeViewControls.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeViewControls.java
new file mode 100644
index 0000000..08117b5
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeViewControls.java
@@ -0,0 +1,143 @@
+/*
+ * 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.HierarchyViewerDirector;
+import com.android.hierarchyviewerlib.models.TreeViewModel;
+import com.android.hierarchyviewerlib.models.TreeViewModel.TreeChangeListener;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Slider;
+import org.eclipse.swt.widgets.Text;
+
+public class TreeViewControls extends Composite implements TreeChangeListener {
+
+    private Text filterText;
+
+    private Slider zoomSlider;
+
+    public TreeViewControls(Composite parent) {
+        super(parent, SWT.NONE);
+        GridLayout layout = new GridLayout(5, false);
+        layout.marginWidth = layout.marginHeight = 2;
+        layout.verticalSpacing = layout.horizontalSpacing = 4;
+        setLayout(layout);
+
+        Label filterLabel = new Label(this, SWT.NONE);
+        filterLabel.setText("Filter by class or id:");
+        filterLabel.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, true));
+
+        filterText = new Text(this, SWT.LEFT | SWT.SINGLE);
+        filterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+        filterText.addModifyListener(filterTextModifyListener);
+        filterText.setText(HierarchyViewerDirector.getDirector().getFilterText());
+
+        Label smallZoomLabel = new Label(this, SWT.NONE);
+        smallZoomLabel.setText(" 20%");
+        smallZoomLabel
+                .setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, true));
+
+        zoomSlider = new Slider(this, SWT.HORIZONTAL);
+        GridData zoomSliderGridData = new GridData(GridData.CENTER, GridData.CENTER, false, false);
+        zoomSliderGridData.widthHint = 190;
+        zoomSlider.setLayoutData(zoomSliderGridData);
+        zoomSlider.setMinimum((int) (TreeViewModel.MIN_ZOOM * 10));
+        zoomSlider.setMaximum((int) (TreeViewModel.MAX_ZOOM * 10 + 1));
+        zoomSlider.setThumb(1);
+        zoomSlider.setSelection((int) Math.round(TreeViewModel.getModel().getZoom() * 10));
+
+        zoomSlider.addSelectionListener(zoomSliderSelectionListener);
+
+        Label largeZoomLabel = new Label(this, SWT.NONE);
+        largeZoomLabel
+                .setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, true));
+        largeZoomLabel.setText("200%");
+
+        addDisposeListener(disposeListener);
+
+        TreeViewModel.getModel().addTreeChangeListener(this);
+    }
+
+    private DisposeListener disposeListener = new DisposeListener() {
+        public void widgetDisposed(DisposeEvent e) {
+            TreeViewModel.getModel().removeTreeChangeListener(TreeViewControls.this);
+        }
+    };
+
+    private SelectionListener zoomSliderSelectionListener = new SelectionListener() {
+        private int oldValue;
+
+        public void widgetDefaultSelected(SelectionEvent e) {
+            // pass
+        }
+
+        public void widgetSelected(SelectionEvent e) {
+            int newValue = zoomSlider.getSelection();
+            if (oldValue != newValue) {
+                TreeViewModel.getModel().removeTreeChangeListener(TreeViewControls.this);
+                TreeViewModel.getModel().setZoom(newValue / 10.0);
+                TreeViewModel.getModel().addTreeChangeListener(TreeViewControls.this);
+                oldValue = newValue;
+            }
+        }
+    };
+
+    private ModifyListener filterTextModifyListener = new ModifyListener() {
+        public void modifyText(ModifyEvent e) {
+            HierarchyViewerDirector.getDirector().filterNodes(filterText.getText());
+        }
+    };
+
+    public void selectionChanged() {
+        // pass
+    }
+
+    public void treeChanged() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                if (TreeViewModel.getModel().getTree() != null) {
+                    zoomSlider.setSelection((int) Math
+                            .round(TreeViewModel.getModel().getZoom() * 10));
+                }
+                filterText.setText("");
+            }
+        });
+    }
+
+    public void viewportChanged() {
+        // pass
+    }
+
+    public void zoomChanged() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                zoomSlider.setSelection((int) Math.round(TreeViewModel.getModel().getZoom() * 10));
+            }
+        });
+    };
+}
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeViewOverview.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeViewOverview.java
index ee84ccb..fb01b86 100644
--- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeViewOverview.java
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/TreeViewOverview.java
@@ -85,6 +85,8 @@
 
         transform = new Transform(Display.getDefault());
         inverse = new Transform(Display.getDefault());
+
+        loadAllData();
     }
 
     private void loadResources() {
@@ -207,7 +209,7 @@
     private PaintListener paintListener = new PaintListener() {
         public void paintControl(PaintEvent e) {
             synchronized (TreeViewOverview.this) {
-                if (tree != null && viewport != null) {
+                if (tree != null) {
                     e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                     e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
                     e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height);
@@ -218,16 +220,20 @@
                     e.gc.drawPath(connectionPath);
                     connectionPath.dispose();
 
-                    e.gc.setAlpha(50);
-                    e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
-                    e.gc.fillRectangle((int) viewport.x, (int) viewport.y, (int) Math
-                            .ceil(viewport.width), (int) Math.ceil(viewport.height));
+                    if (viewport != null) {
+                        e.gc.setAlpha(50);
+                        e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
+                        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_DARK_GRAY));
-                    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_DARK_GRAY));
+                        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));
+                    }
                 }
             }
         }
@@ -267,13 +273,27 @@
     }
 
     private void doRedraw() {
-        Display.getDefault().asyncExec(new Runnable() {
+        Display.getDefault().syncExec(new Runnable() {
             public void run() {
                 redraw();
             }
         });
     }
 
+    public void loadAllData() {
+        Display.getDefault().syncExec(new Runnable() {
+            public void run() {
+                synchronized (this) {
+                    tree = model.getTree();
+                    selectedNode = model.getSelection();
+                    viewport = model.getViewport();
+                    setBounds();
+                    setTransform();
+                }
+            }
+        });
+    }
+
     // Note the syncExec and then synchronized... It avoids deadlock
     public void treeChanged() {
         Display.getDefault().syncExec(new Runnable() {
@@ -281,6 +301,7 @@
                 synchronized (this) {
                     tree = model.getTree();
                     selectedNode = model.getSelection();
+                    viewport = model.getViewport();
                     setBounds();
                     setTransform();
                 }
@@ -299,11 +320,16 @@
             bounds.height =
                     Math.max(viewport.y + viewport.height, tree.bounds.y + tree.bounds.height)
                             - bounds.y;
+        } else if (tree != null) {
+            bounds.x = tree.bounds.x;
+            bounds.y = tree.bounds.y;
+            bounds.width = tree.bounds.x + tree.bounds.width - bounds.x;
+            bounds.height = tree.bounds.y + tree.bounds.height - bounds.y;
         }
     }
 
     private void setTransform() {
-        if (viewport != null && tree != null) {
+        if (tree != null) {
 
             transform.identity();
             inverse.identity();
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/show-extras.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/show-extras.png
new file mode 100644
index 0000000..ba9c305
--- /dev/null
+++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/show-extras.png
Binary files differ