public class LoadAllViewsAction extends TreeViewEnabledAction implements ImageAction {

    private static LoadAllViewsAction sAction;

    private Image mImage;

    private LoadAllViewsAction() {
        super("Load All &Views");
        setAccelerator(SWT.MOD1 + 'V');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("load-all-views.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Load all view images");
    }

    public static LoadAllViewsAction getAction() {
        if (sAction == null) {
            sAction = new LoadAllViewsAction();
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().loadAllViews();
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public class ShowOverlayAction extends Action implements ImageAction, IImageChangeListener {

    private static ShowOverlayAction sAction;

    private Image mImage;

    private ShowOverlayAction() {
        super("Show In &Loupe", Action.AS_CHECK_BOX);
        setAccelerator(SWT.MOD1 + 'L');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("show-overlay.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Show the overlay in the loupe view");
        setEnabled(PixelPerfectModel.getModel().getOverlayImage() != null);
        PixelPerfectModel.getModel().addImageChangeListener(this);
    }

    public static ShowOverlayAction getAction() {
        if (sAction == null) {
            sAction = new ShowOverlayAction();
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerApplication.getMainWindow().showOverlayInLoupe(sAction.isChecked());
    }

    @Override
    public Image getImage() {
        return mImage;
    }

    @Override
    public void crosshairMoved() {
        // pass
    }

    @Override
    public void treeChanged() {
        // pass
    }

    @Override
    public void imageChanged() {
        // pass
    }

    @Override
    public void imageLoaded() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                Image overlayImage = PixelPerfectModel.getModel().getOverlayImage();
                setEnabled(overlayImage != null);
            }
        });
    }

    @Override
    public void overlayChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                setEnabled(PixelPerfectModel.getModel().getOverlayImage() != null);
            }
        });
    }

    @Override
    public void overlayTransparencyChanged() {
        // pass
    }

    @Override
    public void selectionChanged() {
        // pass
    }

    @Override
    public void zoomChanged() {
        // pass
    }
} public class ActionButton implements IPropertyChangeListener, SelectionListener {
    private Button mButton;

    private Action mAction;

    public ActionButton(Composite parent, ImageAction action) {
        this.mAction = (Action) action;
        if (this.mAction.getStyle() == Action.AS_CHECK_BOX) {
            mButton = new Button(parent, SWT.CHECK);
        } else {
            mButton = new Button(parent, SWT.PUSH);
        }
        mButton.setText(action.getText());
        mButton.setImage(action.getImage());
        this.mAction.addPropertyChangeListener(this);
        mButton.addSelectionListener(this);
        mButton.setToolTipText(action.getToolTipText());
        mButton.setEnabled(this.mAction.isEnabled());
    }

    @Override
    public void propertyChange(PropertyChangeEvent e) {
        if (e.getProperty().toUpperCase().equals("ENABLED")) { //$NON-NLS-1$
            mButton.setEnabled((Boolean) e.getNewValue());
        } else if (e.getProperty().toUpperCase().equals("CHECKED")) { //$NON-NLS-1$
            mButton.setSelection(mAction.isChecked());
        }
    }

    public void setLayoutData(Object data) {
        mButton.setLayoutData(data);
    }

    @Override
    public void widgetDefaultSelected(SelectionEvent e) {
        // pass
    }

    @Override
    public void widgetSelected(SelectionEvent e) {
        if (mAction.getStyle() == Action.AS_CHECK_BOX) {
            mAction.setChecked(mButton.getSelection());
        }
        mAction.run();
    }

    public void addSelectionListener(SelectionListener listener) {
        mButton.addSelectionListener(listener);
    }

    public void setVisible(boolean visible) {
        mButton.setVisible(visible);
    }
} public abstract class HierarchyViewerDirector implements IDeviceChangeListener,
        IWindowChangeListener {
	
	static Logger logger = Logger.getLogger(HierarchyViewerDirector.class);
	static boolean logDebug = logger.isDebugEnabled();
	
    protected static HierarchyViewerDirector sDirector;

    public static final String TAG = "hierarchyviewer";

    private int mPixelPerfectRefreshesInProgress = 0;

    private Timer mPixelPerfectRefreshTimer = new Timer();

    private boolean mAutoRefresh = false;

    public static final int DEFAULT_PIXEL_PERFECT_AUTOREFRESH_INTERVAL = 5;

    private int mPixelPerfectAutoRefreshInterval = DEFAULT_PIXEL_PERFECT_AUTOREFRESH_INTERVAL;

    private PixelPerfectAutoRefreshTask mCurrentAutoRefreshTask;

    private String mFilterText = ""; //$NON-NLS-1$

    public void terminate() {
        WindowUpdater.terminate();
        mPixelPerfectRefreshTimer.cancel();
    }

    public abstract String getAdbLocation();

    public static HierarchyViewerDirector getDirector() {
        return sDirector;
    }

    /**
     * Init the DeviceBridge with an existing {@link AndroidDebugBridge}.
     * @param bridge the bridge object to use
     */
    public void acquireBridge(AndroidDebugBridge bridge) {
        DeviceBridge.acquireBridge(bridge);
    }

    /**
     * Creates an {@link AndroidDebugBridge} connected to adb at the given location.
     *
     * If a bridge is already running, this disconnects it and creates a new one.
     *
     * @param adbLocation the location to adb.
     */
    public void initDebugBridge() {
        DeviceBridge.initDebugBridge(getAdbLocation());
    }

    public void stopDebugBridge() {
        DeviceBridge.terminate();
    }

    public void populateDeviceSelectionModel() {
        IDevice[] devices = DeviceBridge.getDevices();
        for (IDevice device : devices) {
            deviceConnected(device);
        }
    }

    public void startListenForDevices() {
        DeviceBridge.startListenForDevices(this);
    }

    public void stopListenForDevices() {
        DeviceBridge.stopListenForDevices(this);
    }

    public abstract void executeInBackground(String taskName, Runnable task);

    @Override
    public void deviceConnected(final IDevice device) {
        executeInBackground("Connecting device", new Runnable() {
            @Override
            public void run() {
                if (DeviceSelectionModel.getModel().containsDevice(device)) {
                    windowsChanged(device);
                } else if (device.isOnline()) {
                    DeviceBridge.setupDeviceForward(device);
                    if (!DeviceBridge.isViewServerRunning(device)) {
                        if (!DeviceBridge.startViewServer(device)) {
                            // Let's do something interesting here... Try again
                            // in 2 seconds.
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException e) {
                            }
                            if (!DeviceBridge.startViewServer(device)) {
                                Log.e(TAG, "Unable to debug device " + device);
                                DeviceBridge.removeDeviceForward(device);
                            } else {
                                loadViewServerInfoAndWindows(device);
                            }
                            return;
                        }
                    }
                    loadViewServerInfoAndWindows(device);
                }
            }
        });
    }

    private void loadViewServerInfoAndWindows(final IDevice device) {

        ViewServerInfo viewServerInfo = DeviceBridge.loadViewServerInfo(device);
        if (viewServerInfo == null) {
            return;
        }
        Window[] windows = DeviceBridge.loadWindows(device);
        DeviceSelectionModel.getModel().addDevice(device, windows, viewServerInfo);
        if (viewServerInfo.protocolVersion >= 3) {
            WindowUpdater.startListenForWindowChanges(HierarchyViewerDirector.this, device);
            focusChanged(device);
        }

    }

    @Override
    public void deviceDisconnected(final IDevice device) {
        executeInBackground("Disconnecting device", new Runnable() {
            @Override
            public void run() {
                ViewServerInfo viewServerInfo = DeviceBridge.getViewServerInfo(device);
                if (viewServerInfo != null && viewServerInfo.protocolVersion >= 3) {
                    WindowUpdater.stopListenForWindowChanges(HierarchyViewerDirector.this, device);
                }
                DeviceBridge.removeDeviceForward(device);
                DeviceBridge.removeViewServerInfo(device);
                DeviceSelectionModel.getModel().removeDevice(device);
                if (PixelPerfectModel.getModel().getDevice() == device) {
                    PixelPerfectModel.getModel().setData(null, null, null);
                }
                Window treeViewWindow = TreeViewModel.getModel().getWindow();
                if (treeViewWindow != null && treeViewWindow.getDevice() == device) {
                    TreeViewModel.getModel().setData(null, null);
                    mFilterText = ""; //$NON-NLS-1$
                }
            }
        });
    }

    @Override
    public void deviceChanged(IDevice device, int changeMask) {
        if ((changeMask & IDevice.CHANGE_STATE) != 0 && device.isOnline()) {
            deviceConnected(device);
        }
    }

    @Override
    public void windowsChanged(final IDevice device) {
        executeInBackground("Refreshing windows", new Runnable() {
            @Override
            public void run() {
                if (!DeviceBridge.isViewServerRunning(device)) {
                    if (!DeviceBridge.startViewServer(device)) {
                        Log.e(TAG, "Unable to debug device " + device);
                        return;
                    }
                }
                Window[] windows = DeviceBridge.loadWindows(device);
                DeviceSelectionModel.getModel().updateDevice(device, windows);
            }
        });
    }

    @Override
    public void focusChanged(final IDevice device) {
        executeInBackground("Updating focus", new Runnable() {
            @Override
            public void run() {
                int focusedWindow = DeviceBridge.getFocusedWindow(device);
                DeviceSelectionModel.getModel().updateFocusedWindow(device, focusedWindow);
            }
        });
    }

    public void refreshPixelPerfect() {
        final IDevice device = PixelPerfectModel.getModel().getDevice();
        if (device != null) {
            // Some interesting logic here. We don't want to refresh the pixel
            // perfect view 1000 times in a row if the focus keeps changing. We
            // just
            // want it to refresh following the last focus change.
            boolean proceed = false;
            synchronized (this) {
                if (mPixelPerfectRefreshesInProgress <= 1) {
                    proceed = true;
                    mPixelPerfectRefreshesInProgress++;
                }
            }
            if (proceed) {
                executeInBackground("Refreshing pixel perfect screenshot", new Runnable() {
                    @Override
                    public void run() {
                        Image screenshotImage = getScreenshotImage(device);
                        if (screenshotImage != null) {
                            PixelPerfectModel.getModel().setImage(screenshotImage);
                        }
                        synchronized (HierarchyViewerDirector.this) {
                            mPixelPerfectRefreshesInProgress--;
                        }
                    }

                });
            }
        }
    }

    public void refreshPixelPerfectTree() {
        final IDevice device = PixelPerfectModel.getModel().getDevice();
        if (device != null) {
            executeInBackground("Refreshing pixel perfect tree", new Runnable() {
                @Override
                public void run() {
                    ViewNode viewNode =
                            DeviceBridge.loadWindowData(Window.getFocusedWindow(device));
                    if (viewNode != null) {
                        PixelPerfectModel.getModel().setTree(viewNode);
                    }
                }

            });
        }
    }

    public void loadPixelPerfectData(final IDevice device) {
        executeInBackground("Loading pixel perfect data", new Runnable() {
            @Override
            public void run() {
                Image screenshotImage = getScreenshotImage(device);
                if (screenshotImage != null) {
                    ViewNode viewNode =
                            DeviceBridge.loadWindowData(Window.getFocusedWindow(device));
                    if (viewNode != null) {
                        PixelPerfectModel.getModel().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() {
                @Override
                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("Loading view hierarchy", new Runnable() {
            @Override
            public void run() {

                mFilterText = ""; //$NON-NLS-1$

                ViewNode viewNode = DeviceBridge.loadWindowData(window);
                if (viewNode != null) {
                    DeviceBridge.loadProfileData(window, viewNode);
                    viewNode.setViewCount();
                    TreeViewModel.getModel().setData(window, viewNode);
                }
            }
        });
    }

    public void loadOverlay(final Shell shell) {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                FileDialog fileDialog = new FileDialog(shell, SWT.OPEN);
                fileDialog.setFilterExtensions(new String[] {
                    "*.jpg;*.jpeg;*.png;*.gif;*.bmp" //$NON-NLS-1$
                });
                fileDialog.setFilterNames(new String[] {
                    "Image (*.jpg, *.jpeg, *.png, *.gif, *.bmp)"
                });
                fileDialog.setText("Choose an overlay image");
                String fileName = fileDialog.open();
                if (fileName != null) {
                    try {
                        Image image = new Image(Display.getDefault(), fileName);
                        PixelPerfectModel.getModel().setOverlayImage(image);
                    } catch (SWTException e) {
                        Log.e(TAG, "Unable to load image from " + fileName);
                    }
                }
            }
        });
    }

    public void showCapture(final Shell shell, final ViewNode viewNode) {
        executeInBackground("Capturing node", new Runnable() {
            @Override
            public void run() {
                final Image image = loadCapture(viewNode);
                if (image != null) {

                    Display.getDefault().syncExec(new Runnable() {
                        @Override
                        public void run() {
                            CaptureDisplay.show(shell, viewNode, image);
                        }
                    });
                }
            }
        });
    }

    public Image loadCapture(ViewNode viewNode) {
        final Image image = DeviceBridge.loadCapture(viewNode.window, viewNode);
        if (image != null) {
            viewNode.image = image;

            // Force the layout viewer to redraw.
            TreeViewModel.getModel().notifySelectionChanged();
        }
        return image;
    }

    public void loadCaptureInBackground(final ViewNode viewNode) {
        executeInBackground("Capturing node", new Runnable() {
            @Override
            public void run() {
                loadCapture(viewNode);
            }
        });
    }

    public void showCapture(Shell shell) {
        DrawableViewNode viewNode = TreeViewModel.getModel().getSelection();
        if (viewNode != null) {
            showCapture(shell, viewNode.viewNode);
        }
    }

    public void refreshWindows() {
        executeInBackground("Refreshing windows", new Runnable() {
            @Override
            public void run() {
                IDevice[] devicesA = DeviceSelectionModel.getModel().getDevices();
                IDevice[] devicesB = DeviceBridge.getDevices();
                HashSet<IDevice> deviceSet = new HashSet<IDevice>();
                for (int i = 0; i < devicesB.length; i++) {
                    deviceSet.add(devicesB[i]);
                }
                for (int i = 0; i < devicesA.length; i++) {
                    if (deviceSet.contains(devicesA[i])) {
                        windowsChanged(devicesA[i]);
                        deviceSet.remove(devicesA[i]);
                    } else {
                        deviceDisconnected(devicesA[i]);
                    }
                }
                for (IDevice device : deviceSet) {
                    deviceConnected(device);
                }
            }
        });
    }

    public void loadViewHierarchy() {
        Window window = DeviceSelectionModel.getModel().getSelectedWindow();
        if(logDebug) logger.debug(window.getTitle());
        if (window != null) {
            loadViewTreeData(window);
        }
    }

    public void inspectScreenshot() {
        IDevice device = DeviceSelectionModel.getModel().getSelectedDevice();
        if (device != null) {
            loadPixelPerfectData(device);
        }
    }

    public void saveTreeView(final Shell shell) {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                final DrawableViewNode viewNode = TreeViewModel.getModel().getTree();
                if (viewNode != null) {
                    FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
                    fileDialog.setFilterExtensions(new String[] {
                        "*.png" //$NON-NLS-1$
                    });
                    fileDialog.setFilterNames(new String[] {
                        "Portable Network Graphics File (*.png)"
                    });
                    fileDialog.setText("Choose where to save the tree image");
                    final String fileName = fileDialog.open();
                    if (fileName != null) {
                        executeInBackground("Saving tree view", new Runnable() {
                            @Override
                            public void run() {
                                Image image = TreeView.paintToImage(viewNode);
                                ImageLoader imageLoader = new ImageLoader();
                                imageLoader.data = new ImageData[] {
                                    image.getImageData()
                                };
                                String extensionedFileName = fileName;
                                if (!extensionedFileName.toLowerCase().endsWith(".png")) { //$NON-NLS-1$
                                    extensionedFileName += ".png"; //$NON-NLS-1$
                                }
                                try {
                                    imageLoader.save(extensionedFileName, SWT.IMAGE_PNG);
                                } catch (SWTException e) {
                                    Log.e(TAG, "Unable to save tree view as a PNG image at "
                                            + fileName);
                                }
                                image.dispose();
                            }
                        });
                    }
                }
            }
        });
    }

    public void savePixelPerfect(final Shell shell) {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                Image untouchableImage = PixelPerfectModel.getModel().getImage();
                if (untouchableImage != null) {
                    final ImageData imageData = untouchableImage.getImageData();
                    FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
                    fileDialog.setFilterExtensions(new String[] {
                        "*.png" //$NON-NLS-1$
                    });
                    fileDialog.setFilterNames(new String[] {
                        "Portable Network Graphics File (*.png)"
                    });
                    fileDialog.setText("Choose where to save the screenshot");
                    final String fileName = fileDialog.open();
                    if (fileName != null) {
                        executeInBackground("Saving pixel perfect", new Runnable() {
                            @Override
                            public void run() {
                                ImageLoader imageLoader = new ImageLoader();
                                imageLoader.data = new ImageData[] {
                                    imageData
                                };
                                String extensionedFileName = fileName;
                                if (!extensionedFileName.toLowerCase().endsWith(".png")) { //$NON-NLS-1$
                                    extensionedFileName += ".png"; //$NON-NLS-1$
                                }
                                try {
                                    imageLoader.save(extensionedFileName, SWT.IMAGE_PNG);
                                } catch (SWTException e) {
                                    Log.e(TAG, "Unable to save tree view as a PNG image at "
                                            + fileName);
                                }
                            }
                        });
                    }
                }
            }
        });
    }

    public void capturePSD(final Shell shell) {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                final Window window = TreeViewModel.getModel().getWindow();
                if (window != null) {
                    FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
                    fileDialog.setFilterExtensions(new String[] {
                        "*.psd" //$NON-NLS-1$
                    });
                    fileDialog.setFilterNames(new String[] {
                        "Photoshop Document (*.psd)"
                    });
                    fileDialog.setText("Choose where to save the window layers");
                    final String fileName = fileDialog.open();
                    if (fileName != null) {
                        executeInBackground("Saving window layers", new Runnable() {
                            @Override
                            public void run() {
                                PsdFile psdFile = DeviceBridge.captureLayers(window);
                                if (psdFile != null) {
                                    String extensionedFileName = fileName;
                                    if (!extensionedFileName.toLowerCase().endsWith(".psd")) { //$NON-NLS-1$
                                        extensionedFileName += ".psd"; //$NON-NLS-1$
                                    }
                                    try {
                                        psdFile.write(new FileOutputStream(extensionedFileName));
                                    } catch (FileNotFoundException e) {
                                        Log.e(TAG, "Unable to write to file " + fileName);
                                    }
                                }
                            }
                        });
                    }
                }
            }
        });
    }

    public void reloadViewHierarchy() {
        Window window = TreeViewModel.getModel().getWindow();
        if (window != null) {
            loadViewTreeData(window);
        }
    }

    public void invalidateCurrentNode() {
        final DrawableViewNode selectedNode = TreeViewModel.getModel().getSelection();
        if (selectedNode != null) {
            executeInBackground("Invalidating view", new Runnable() {
                @Override
                public void run() {
                    DeviceBridge.invalidateView(selectedNode.viewNode);
                }
            });
        }
    }

    public void relayoutCurrentNode() {
        final DrawableViewNode selectedNode = TreeViewModel.getModel().getSelection();
        if (selectedNode != null) {
            executeInBackground("Request layout", new Runnable() {
                @Override
                public void run() {
                    DeviceBridge.requestLayout(selectedNode.viewNode);
                }
            });
        }
    }

    public void dumpDisplayListForCurrentNode() {
        final DrawableViewNode selectedNode = TreeViewModel.getModel().getSelection();
        if (selectedNode != null) {
            executeInBackground("Dump displaylist", new Runnable() {
                @Override
                public void run() {
                    DeviceBridge.outputDisplayList(selectedNode.viewNode);
                }
            });
        }
    }

    public void loadAllViews() {
        executeInBackground("Loading all views", new Runnable() {
            @Override
            public void run() {
                DrawableViewNode tree = TreeViewModel.getModel().getTree();
                if (tree != null) {
                    loadViewRecursive(tree.viewNode);
                    // Force the layout viewer to redraw.
                    TreeViewModel.getModel().notifySelectionChanged();
                }
            }
        });
    }

    private void loadViewRecursive(ViewNode viewNode) {
        Image image = DeviceBridge.loadCapture(viewNode.window, viewNode);
        if (image == null) {
            return;
        }
        viewNode.image = image;
        final int N = viewNode.children.size();
        for (int i = 0; i < N; i++) {
            loadViewRecursive(viewNode.children.get(i));
        }
    }

    public void filterNodes(String filterText) {
        this.mFilterText = filterText;
        DrawableViewNode tree = TreeViewModel.getModel().getTree();
        if (tree != null) {
            tree.viewNode.filter(filterText);
            // Force redraw
            TreeViewModel.getModel().notifySelectionChanged();
        }
    }

    public String getFilterText() {
        return mFilterText;
    }

    private static class PixelPerfectAutoRefreshTask extends TimerTask {
        @Override
        public void run() {
            HierarchyViewerDirector.getDirector().refreshPixelPerfect();
        }
    };

    public void setPixelPerfectAutoRefresh(boolean value) {
        synchronized (mPixelPerfectRefreshTimer) {
            if (value == mAutoRefresh) {
                return;
            }
            mAutoRefresh = value;
            if (mAutoRefresh) {
                mCurrentAutoRefreshTask = new PixelPerfectAutoRefreshTask();
                mPixelPerfectRefreshTimer.schedule(mCurrentAutoRefreshTask,
                        mPixelPerfectAutoRefreshInterval * 1000,
                        mPixelPerfectAutoRefreshInterval * 1000);
            } else {
                mCurrentAutoRefreshTask.cancel();
                mCurrentAutoRefreshTask = null;
            }
        }
    }

    public void setPixelPerfectAutoRefreshInterval(int value) {
        synchronized (mPixelPerfectRefreshTimer) {
            if (mPixelPerfectAutoRefreshInterval == value) {
                return;
            }
            mPixelPerfectAutoRefreshInterval = value;
            if (mAutoRefresh) {
                mCurrentAutoRefreshTask.cancel();
                long timeLeft =
                        Math.max(0, mPixelPerfectAutoRefreshInterval
                                * 1000
                                - (System.currentTimeMillis() - mCurrentAutoRefreshTask
                                        .scheduledExecutionTime()));
                mCurrentAutoRefreshTask = new PixelPerfectAutoRefreshTask();
                mPixelPerfectRefreshTimer.schedule(mCurrentAutoRefreshTask, timeLeft,
                        mPixelPerfectAutoRefreshInterval * 1000);
            }
        }
    }

    public int getPixelPerfectAutoRefreshInverval() {
        return mPixelPerfectAutoRefreshInterval;
    }
} public class CapturePSDAction extends TreeViewEnabledAction implements ImageAction {

    private static CapturePSDAction sAction;

    private Image mImage;

    private Shell mShell;

    private CapturePSDAction(Shell shell) {
        super("&Capture Layers");
        this.mShell = shell;
        setAccelerator(SWT.MOD1 + 'C');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("capture-psd.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Capture the window layers as a photoshop document");
    }

    public static CapturePSDAction getAction(Shell shell) {
        if (sAction == null) {
            sAction = new CapturePSDAction(shell);
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().capturePSD(mShell);
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public class DisplayViewAction extends SelectedNodeEnabledAction implements ImageAction {

    private static DisplayViewAction sAction;

    private Image mImage;

    private Shell mShell;

    private DisplayViewAction(Shell shell) {
        super("&Display View");
        this.mShell = shell;
        setAccelerator(SWT.MOD1 + 'D');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("display.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Display the selected view image in a separate window");
    }

    public static DisplayViewAction getAction(Shell shell) {
        if (sAction == null) {
            sAction = new DisplayViewAction(shell);
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().showCapture(mShell);
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public class DumpDisplayListAction extends SelectedNodeEnabledAction implements ImageAction {

    private static DumpDisplayListAction sAction;

    private Image mImage;

    private DumpDisplayListAction() {
        super("Dump DisplayList");
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("load-view-hierarchy.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Request the view to output its displaylist to logcat");
    }

    public static DumpDisplayListAction getAction() {
        if (sAction == null) {
            sAction = new DumpDisplayListAction();
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().dumpDisplayListForCurrentNode();
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public class InspectScreenshotAction extends Action implements ImageAction, IWindowChangeListener {

    private static InspectScreenshotAction sAction;

    private Image mImage;

    private InspectScreenshotAction() {
        super("Inspect &Screenshot");
        setAccelerator(SWT.MOD1 + 'S');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("inspect-screenshot.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Inspect a screenshot in the pixel perfect view");
        setEnabled(
                DeviceSelectionModel.getModel().getSelectedDevice() != null);
        DeviceSelectionModel.getModel().addWindowChangeListener(this);
    }

    public static InspectScreenshotAction getAction() {
        if (sAction == null) {
            sAction = new InspectScreenshotAction();
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().inspectScreenshot();
    }

    @Override
    public Image getImage() {
        return mImage;
    }

    @Override
    public void deviceChanged(IDevice device) {
        // pass
    }

    @Override
    public void deviceConnected(IDevice device) {
        // pass
    }

    @Override
    public void deviceDisconnected(IDevice device) {
        // pass
    }

    @Override
    public void focusChanged(IDevice device) {
        // pass
    }

    @Override
    public void selectionChanged(final IDevice device, final Window window) {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                InspectScreenshotAction.getAction().setEnabled(device != null);
            }
        });
    }
} public class InvalidateAction extends SelectedNodeEnabledAction implements ImageAction {

    private static InvalidateAction sAction;

    private Image mImage;

    private InvalidateAction() {
        super("&Invalidate Layout");
        setAccelerator(SWT.MOD1 + 'I');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("invalidate.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Invalidate the layout for the current window");
    }

    public static InvalidateAction getAction() {
        if (sAction == null) {
            sAction = new InvalidateAction();
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().invalidateCurrentNode();
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public class LoadOverlayAction extends PixelPerfectEnabledAction implements ImageAction {

    private static LoadOverlayAction sAction;

    private Image mImage;

    private Shell mShell;

    private LoadOverlayAction(Shell shell) {
        super("Load &Overlay");
        this.mShell = shell;
        setAccelerator(SWT.MOD1 + 'O');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("load-overlay.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Load an image to overlay the screenshot");
    }

    public static LoadOverlayAction getAction(Shell shell) {
        if (sAction == null) {
            sAction = new LoadOverlayAction(shell);
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().loadOverlay(mShell);
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public class LoadViewHierarchyAction extends Action implements ImageAction, IWindowChangeListener {

    private static LoadViewHierarchyAction sAction;

    private Image mImage;

    private LoadViewHierarchyAction() {
        super("Load View &Hierarchy");
        setAccelerator(SWT.MOD1 + 'H');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("load-view-hierarchy.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Load the view hierarchy into the tree view");
        setEnabled(
                DeviceSelectionModel.getModel().getSelectedWindow() != null);
        DeviceSelectionModel.getModel().addWindowChangeListener(this);
    }

    public static LoadViewHierarchyAction getAction() {
        if (sAction == null) {
            sAction = new LoadViewHierarchyAction();
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().loadViewHierarchy();
    }

    @Override
    public Image getImage() {
        return mImage;
    }

    @Override
    public void deviceChanged(IDevice device) {
        // pass
    }

    @Override
    public void deviceConnected(IDevice device) {
        // pass
    }

    @Override
    public void deviceDisconnected(IDevice device) {
        // pass
    }

    @Override
    public void focusChanged(IDevice device) {
        // pass
    }

    @Override
    public void selectionChanged(final IDevice device, final Window window) {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                LoadViewHierarchyAction.getAction().setEnabled(window != null);
            }
        });
    }
} public class PixelPerfectAutoRefreshAction extends PixelPerfectEnabledAction implements ImageAction {

    private static PixelPerfectAutoRefreshAction sAction;

    private Image mImage;

    private PixelPerfectAutoRefreshAction() {
        super("Auto &Refresh", Action.AS_CHECK_BOX);
        setAccelerator(SWT.MOD1 + 'R');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("auto-refresh.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Automatically refresh the screenshot");
    }

    public static PixelPerfectAutoRefreshAction getAction() {
        if (sAction == null) {
            sAction = new PixelPerfectAutoRefreshAction();
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().setPixelPerfectAutoRefresh(sAction.isChecked());
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public class RefreshPixelPerfectAction extends PixelPerfectEnabledAction implements ImageAction {

    private static RefreshPixelPerfectAction sAction;

    private Image mImage;

    private RefreshPixelPerfectAction() {
        super("&Refresh Screenshot");
        setAccelerator(SWT.F5);
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("refresh-windows.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Refresh the screenshot");
    }

    public static RefreshPixelPerfectAction getAction() {
        if (sAction == null) {
            sAction = new RefreshPixelPerfectAction();
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().refreshPixelPerfect();
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public class RefreshPixelPerfectTreeAction extends PixelPerfectEnabledAction implements ImageAction {

    private static RefreshPixelPerfectTreeAction sAction;

    private Image mImage;

    private RefreshPixelPerfectTreeAction() {
        super("Refresh &Tree");
        setAccelerator(SWT.MOD1 + 'T');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("load-view-hierarchy.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Refresh the tree");
    }

    public static RefreshPixelPerfectTreeAction getAction() {
        if (sAction == null) {
            sAction = new RefreshPixelPerfectTreeAction();
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().refreshPixelPerfectTree();
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public class RefreshViewAction extends TreeViewEnabledAction implements ImageAction {

    private static RefreshViewAction sAction;

    private Image mImage;

    private RefreshViewAction() {
        super("Load View &Hierarchy");
        setAccelerator(SWT.MOD1 + 'H');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("load-view-hierarchy.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Reload the view hierarchy");
    }

    public static RefreshViewAction getAction() {
        if (sAction == null) {
            sAction = new RefreshViewAction();
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().reloadViewHierarchy();
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public class RefreshWindowsAction extends Action implements ImageAction {

    private static RefreshWindowsAction sAction;

    private Image mImage;

    private RefreshWindowsAction() {
        super("&Refresh");
        setAccelerator(SWT.F5);
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("refresh-windows.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Refresh the list of devices");
    }

    public static RefreshWindowsAction getAction() {
        if (sAction == null) {
            sAction = new RefreshWindowsAction();
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().refreshWindows();
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public class RequestLayoutAction extends SelectedNodeEnabledAction implements ImageAction {

    private static RequestLayoutAction sAction;

    private Image mImage;

    private RequestLayoutAction() {
        super("Request &Layout");
        setAccelerator(SWT.MOD1 + 'L');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("request-layout.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Request the view to lay out");
    }

    public static RequestLayoutAction getAction() {
        if (sAction == null) {
            sAction = new RequestLayoutAction();
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().relayoutCurrentNode();
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public class SavePixelPerfectAction extends PixelPerfectEnabledAction implements ImageAction {

    private static SavePixelPerfectAction sAction;

    private Image mImage;

    private Shell mShell;

    private SavePixelPerfectAction(Shell shell) {
        super("&Save as PNG");
        this.mShell = shell;
        setAccelerator(SWT.MOD1 + 'S');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("save.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Save the screenshot as a PNG image");
    }

    public static SavePixelPerfectAction getAction(Shell shell) {
        if (sAction == null) {
            sAction = new SavePixelPerfectAction(shell);
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().savePixelPerfect(mShell);
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public class SaveTreeViewAction extends TreeViewEnabledAction implements ImageAction {

    private static SaveTreeViewAction sAction;

    private Image mImage;

    private Shell mShell;

    private SaveTreeViewAction(Shell shell) {
        super("&Save as PNG");
        this.mShell = shell;
        setAccelerator(SWT.MOD1 + 'S');
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mImage = imageLoader.loadImage("save.png", Display.getDefault()); //$NON-NLS-1$
        setImageDescriptor(ImageDescriptor.createFromImage(mImage));
        setToolTipText("Save the tree view as a PNG image");
    }

    public static SaveTreeViewAction getAction(Shell shell) {
        if (sAction == null) {
            sAction = new SaveTreeViewAction(shell);
        }
        return sAction;
    }

    @Override
    public void run() {
        HierarchyViewerDirector.getDirector().saveTreeView(mShell);
    }

    @Override
    public Image getImage() {
        return mImage;
    }
} public static class ViewServerInfo {
    public final int protocolVersion;

    public final int serverVersion;

    ViewServerInfo(int serverVersion, int protocolVersion) {
        this.protocolVersion = protocolVersion;
        this.serverVersion = serverVersion;
    }
} public class DeviceSelectionModel {

    private final HashMap<IDevice, DeviceInfo> mDeviceMap = new HashMap<IDevice, DeviceInfo>();

    private final HashMap<IDevice, Integer> mFocusedWindowHashes = new HashMap<IDevice, Integer>();

    private final ArrayList<IDevice> mDeviceList = new ArrayList<IDevice>();

    private final ArrayList<IWindowChangeListener> mWindowChangeListeners =
            new ArrayList<IWindowChangeListener>();

    private IDevice mSelectedDevice;

    private Window mSelectedWindow;

    private static DeviceSelectionModel sModel;

    private static class DeviceInfo {
        Window[] windows;
        ViewServerInfo viewServerInfo;

        private DeviceInfo(Window[] windows, ViewServerInfo viewServerInfo) {
            this.windows = windows;
            this.viewServerInfo = viewServerInfo;
        }
    }
    public static DeviceSelectionModel getModel() {
        if (sModel == null) {
            sModel = new DeviceSelectionModel();
        }
        return sModel;
    }

    public boolean containsDevice(IDevice device) {
        synchronized (mDeviceMap) {
            return mDeviceMap.containsKey(device);
        }
    }

    public void addDevice(IDevice device, Window[] windows, ViewServerInfo info) {
        synchronized (mDeviceMap) {
            mDeviceMap.put(device, new DeviceInfo(windows, info));
            mDeviceList.add(device);
        }
        notifyDeviceConnected(device);
    }

    public void removeDevice(IDevice device) {
        boolean selectionChanged = false;
        synchronized (mDeviceMap) {
            mDeviceList.remove(device);
            if (!mDeviceList.contains(device)) {
                mDeviceMap.remove(device);
                mFocusedWindowHashes.remove(device);
                if (mSelectedDevice == device) {
                    mSelectedDevice = null;
                    mSelectedWindow = null;
                    selectionChanged = true;
                }
            }
        }
        notifyDeviceDisconnected(device);
        if (selectionChanged) {
            notifySelectionChanged(mSelectedDevice, mSelectedWindow);
        }
    }

    public void updateDevice(IDevice device, Window[] windows) {
        boolean selectionChanged = false;
        synchronized (mDeviceMap) {
            DeviceInfo oldDeviceInfo = mDeviceMap.get(device);
            ViewServerInfo oldViewServerInfo = null;
            if (oldDeviceInfo != null) {
                oldViewServerInfo = oldDeviceInfo.viewServerInfo;
            }
            mDeviceMap.put(device, new DeviceInfo(windows, oldViewServerInfo));
            // If the selected window no longer exists, we clear the selection.
            if (mSelectedDevice == device && mSelectedWindow != null) {
                boolean windowStillExists = false;
                for (int i = 0; i < windows.length && !windowStillExists; i++) {
                    if (windows[i].equals(mSelectedWindow)) {
                        windowStillExists = true;
                    }
                }
                if (!windowStillExists) {
                    mSelectedDevice = null;
                    mSelectedWindow = null;
                    selectionChanged = true;
                }
            }
        }
        notifyDeviceChanged(device);
        if (selectionChanged) {
            notifySelectionChanged(mSelectedDevice, mSelectedWindow);
        }
    }

    /*
     * Change which window has focus and notify the listeners.
     */
    public void updateFocusedWindow(IDevice device, int focusedWindow) {
        Integer oldValue = null;
        synchronized (mDeviceMap) {
            oldValue = mFocusedWindowHashes.put(device, new Integer(focusedWindow));
        }
        // Only notify if the values are different. It would be cool if Java
        // containers accepted basic types like int.
        if (oldValue == null || (oldValue != null && oldValue.intValue() != focusedWindow)) {
            notifyFocusChanged(device);
        }
    }

    public static interface IWindowChangeListener {
        public void deviceConnected(IDevice device);

        public void deviceChanged(IDevice device);

        public void deviceDisconnected(IDevice device);

        public void focusChanged(IDevice device);

        public void selectionChanged(IDevice device, Window window);
    }

    private IWindowChangeListener[] getWindowChangeListenerList() {
        IWindowChangeListener[] listeners = null;
        synchronized (mWindowChangeListeners) {
            if (mWindowChangeListeners.size() == 0) {
                return null;
            }
            listeners =
                    mWindowChangeListeners.toArray(new IWindowChangeListener[mWindowChangeListeners
                            .size()]);
        }
        return listeners;
    }

    private void notifyDeviceConnected(IDevice device) {
        IWindowChangeListener[] listeners = getWindowChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].deviceConnected(device);
            }
        }
    }

    private void notifyDeviceChanged(IDevice device) {
        IWindowChangeListener[] listeners = getWindowChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].deviceChanged(device);
            }
        }
    }

    private void notifyDeviceDisconnected(IDevice device) {
        IWindowChangeListener[] listeners = getWindowChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].deviceDisconnected(device);
            }
        }
    }

    private void notifyFocusChanged(IDevice device) {
        IWindowChangeListener[] listeners = getWindowChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].focusChanged(device);
            }
        }
    }

    private void notifySelectionChanged(IDevice device, Window window) {
        IWindowChangeListener[] listeners = getWindowChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].selectionChanged(device, window);
            }
        }
    }

    public void addWindowChangeListener(IWindowChangeListener listener) {
        synchronized (mWindowChangeListeners) {
            mWindowChangeListeners.add(listener);
        }
    }

    public void removeWindowChangeListener(IWindowChangeListener listener) {
        synchronized (mWindowChangeListeners) {
            mWindowChangeListeners.remove(listener);
        }
    }

    public IDevice[] getDevices() {
        synchronized (mDeviceMap) {
            return mDeviceList.toArray(new IDevice[mDeviceList.size()]);
        }
    }

    public Window[] getWindows(IDevice device) {
        Window[] windows = null;
        synchronized (mDeviceMap) {
            DeviceInfo info = mDeviceMap.get(device);
            if (info != null) {
                windows = mDeviceMap.get(device).windows;
            }
        }
        return windows;
    }

    // Returns the window that currently has focus or -1. Note that this means
    // that a window with hashcode -1 gets highlighted. If you remember, this is
    // the infamous <Focused Window>
    public int getFocusedWindow(IDevice device) {
        synchronized (mDeviceMap) {
            Integer focusedWindow = mFocusedWindowHashes.get(device);
            if (focusedWindow == null) {
                return -1;
            }
            return focusedWindow.intValue();
        }
    }

    public void setSelection(IDevice device, Window window) {
        synchronized (mDeviceMap) {
            mSelectedDevice = device;
            mSelectedWindow = window;
        }
        notifySelectionChanged(device, window);
    }

    public IDevice getSelectedDevice() {
        synchronized (mDeviceMap) {
            return mSelectedDevice;
        }
    }

    public Window getSelectedWindow() {
        synchronized (mDeviceMap) {
            return mSelectedWindow;
        }
    }

    public ViewServerInfo getSelectedDeviceInfo() {
        synchronized (mDeviceMap) {
            ViewServerInfo viewServerInfo = null;
            if (mSelectedDevice != null) {
                return mDeviceMap.get(mSelectedDevice).viewServerInfo;
            }
            return null;
        }
    }

} public class PixelPerfectModel {

    public static final int MIN_ZOOM = 2;

    public static final int MAX_ZOOM = 24;

    public static final int DEFAULT_ZOOM = 8;

    public static final int DEFAULT_OVERLAY_TRANSPARENCY_PERCENTAGE = 50;

    private IDevice mDevice;

    private Image mImage;

    private Point mCrosshairLocation;

    private ViewNode mViewNode;

    private ViewNode mSelectedNode;

    private int mZoom;

    private final ArrayList<IImageChangeListener> mImageChangeListeners =
            new ArrayList<IImageChangeListener>();

    private Image mOverlayImage;

    private double mOverlayTransparency = DEFAULT_OVERLAY_TRANSPARENCY_PERCENTAGE / 100.0;

    private static PixelPerfectModel sModel;

    public static PixelPerfectModel getModel() {
        if (sModel == null) {
            sModel = new PixelPerfectModel();
        }
        return sModel;
    }

    public void setData(final IDevice device, final Image image, final ViewNode viewNode) {
        final Image toDispose = this.mImage;
        final Image toDispose2 = this.mOverlayImage;
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (PixelPerfectModel.this) {
                    PixelPerfectModel.this.mDevice = device;
                    PixelPerfectModel.this.mImage = image;
                    PixelPerfectModel.this.mViewNode = viewNode;
                    if (image != null) {
                        PixelPerfectModel.this.mCrosshairLocation =
                                new Point(image.getBounds().width / 2, image.getBounds().height / 2);
                    } else {
                        PixelPerfectModel.this.mCrosshairLocation = null;
                    }
                    mOverlayImage = null;
                    PixelPerfectModel.this.mSelectedNode = null;
                    mZoom = DEFAULT_ZOOM;
                }
            }
        });
        notifyImageLoaded();
        if (toDispose != null) {
            Display.getDefault().syncExec(new Runnable() {
                @Override
                public void run() {
                    toDispose.dispose();
                }
            });
        }
        if (toDispose2 != null) {
            Display.getDefault().syncExec(new Runnable() {
                @Override
                public void run() {
                    toDispose2.dispose();
                }
            });
        }

    }

    public void setCrosshairLocation(int x, int y) {
        synchronized (this) {
            mCrosshairLocation = new Point(x, y);
        }
        notifyCrosshairMoved();
    }

    public void setSelected(ViewNode selected) {
        synchronized (this) {
            this.mSelectedNode = selected;
        }
        notifySelectionChanged();
    }

    public void setTree(final ViewNode viewNode) {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (PixelPerfectModel.this) {
                    PixelPerfectModel.this.mViewNode = viewNode;
                    PixelPerfectModel.this.mSelectedNode = null;
                }
            }
        });
        notifyTreeChanged();
    }

    public void setImage(final Image image) {
        final Image toDispose = this.mImage;
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (PixelPerfectModel.this) {
                    PixelPerfectModel.this.mImage = image;
                }
            }
        });
        notifyImageChanged();
        if (toDispose != null) {
            Display.getDefault().syncExec(new Runnable() {
                @Override
                public void run() {
                    toDispose.dispose();
                }
            });
        }
    }

    public void setZoom(int newZoom) {
        synchronized (this) {
            if (newZoom < MIN_ZOOM) {
                newZoom = MIN_ZOOM;
            }
            if (newZoom > MAX_ZOOM) {
                newZoom = MAX_ZOOM;
            }
            mZoom = newZoom;
        }
        notifyZoomChanged();
    }

    public void setOverlayImage(final Image overlayImage) {
        final Image toDispose = this.mOverlayImage;
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (PixelPerfectModel.this) {
                    PixelPerfectModel.this.mOverlayImage = overlayImage;
                }
            }
        });
        notifyOverlayChanged();
        if (toDispose != null) {
            Display.getDefault().syncExec(new Runnable() {
                @Override
                public void run() {
                    toDispose.dispose();
                }
            });
        }
    }

    public void setOverlayTransparency(double value) {
        synchronized (this) {
            value = Math.max(value, 0);
            value = Math.min(value, 1);
            mOverlayTransparency = value;
        }
        notifyOverlayTransparencyChanged();
    }

    public ViewNode getViewNode() {
        synchronized (this) {
            return mViewNode;
        }
    }

    public Point getCrosshairLocation() {
        synchronized (this) {
            return mCrosshairLocation;
        }
    }

    public Image getImage() {
        synchronized (this) {
            return mImage;
        }
    }

    public ViewNode getSelected() {
        synchronized (this) {
            return mSelectedNode;
        }
    }

    public IDevice getDevice() {
        synchronized (this) {
            return mDevice;
        }
    }

    public int getZoom() {
        synchronized (this) {
            return mZoom;
        }
    }

    public Image getOverlayImage() {
        synchronized (this) {
            return mOverlayImage;
        }
    }

    public double getOverlayTransparency() {
        synchronized (this) {
            return mOverlayTransparency;
        }
    }

    public static interface IImageChangeListener {
        public void imageLoaded();

        public void imageChanged();

        public void crosshairMoved();

        public void selectionChanged();

        public void treeChanged();

        public void zoomChanged();

        public void overlayChanged();

        public void overlayTransparencyChanged();
    }

    private IImageChangeListener[] getImageChangeListenerList() {
        IImageChangeListener[] listeners = null;
        synchronized (mImageChangeListeners) {
            if (mImageChangeListeners.size() == 0) {
                return null;
            }
            listeners =
                    mImageChangeListeners.toArray(new IImageChangeListener[mImageChangeListeners
                            .size()]);
        }
        return listeners;
    }

    public void notifyImageLoaded() {
        IImageChangeListener[] listeners = getImageChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].imageLoaded();
            }
        }
    }

    public void notifyImageChanged() {
        IImageChangeListener[] listeners = getImageChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].imageChanged();
            }
        }
    }

    public void notifyCrosshairMoved() {
        IImageChangeListener[] listeners = getImageChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].crosshairMoved();
            }
        }
    }

    public void notifySelectionChanged() {
        IImageChangeListener[] listeners = getImageChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].selectionChanged();
            }
        }
    }

    public void notifyTreeChanged() {
        IImageChangeListener[] listeners = getImageChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].treeChanged();
            }
        }
    }

    public void notifyZoomChanged() {
        IImageChangeListener[] listeners = getImageChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].zoomChanged();
            }
        }
    }

    public void notifyOverlayChanged() {
        IImageChangeListener[] listeners = getImageChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].overlayChanged();
            }
        }
    }

    public void notifyOverlayTransparencyChanged() {
        IImageChangeListener[] listeners = getImageChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].overlayTransparencyChanged();
            }
        }
    }

    public void addImageChangeListener(IImageChangeListener listener) {
        synchronized (mImageChangeListeners) {
            mImageChangeListeners.add(listener);
        }
    }

    public void removeImageChangeListener(IImageChangeListener listener) {
        synchronized (mImageChangeListeners) {
            mImageChangeListeners.remove(listener);
        }
    }
} public static interface IImageChangeListener {
    public void imageLoaded();

    public void imageChanged();

    public void crosshairMoved();

    public void selectionChanged();

    public void treeChanged();

    public void zoomChanged();

    public void overlayChanged();

    public void overlayTransparencyChanged();
} public class TreeViewModel {
    public static final double MAX_ZOOM = 2;

    public static final double MIN_ZOOM = 0.2;

    private Window mWindow;

    private DrawableViewNode mTree;

    private DrawableViewNode mSelectedNode;

    private Rectangle mViewport;

    private double mZoom;

    private final ArrayList<ITreeChangeListener> mTreeChangeListeners =
            new ArrayList<ITreeChangeListener>();

    private static TreeViewModel sModel;

    public static TreeViewModel getModel() {
        if (sModel == null) {
            sModel = new TreeViewModel();
        }
        return sModel;
    }

    public void setData(Window window, ViewNode viewNode) {
        synchronized (this) {
            if (mTree != null) {
                mTree.viewNode.dispose();
            }
            this.mWindow = window;
            if (viewNode == null) {
                mTree = null;
            } else {
                mTree = new DrawableViewNode(viewNode);
                mTree.setLeft();
                mTree.placeRoot();
            }
            mViewport = null;
            mZoom = 1;
            mSelectedNode = null;
        }
        notifyTreeChanged();
    }

    public void setSelection(DrawableViewNode selectedNode) {
        synchronized (this) {
            this.mSelectedNode = selectedNode;
        }
        notifySelectionChanged();
    }

    public void setViewport(Rectangle viewport) {
        synchronized (this) {
            this.mViewport = viewport;
        }
        notifyViewportChanged();
    }

    public void setZoom(double newZoom) {
        Point zoomPoint = null;
        synchronized (this) {
            if (mTree != null && mViewport != null) {
                zoomPoint =
                        new Point(mViewport.x + mViewport.width / 2, mViewport.y + mViewport.height / 2);
            }
        }
        zoomOnPoint(newZoom, zoomPoint);
    }

    public void zoomOnPoint(double newZoom, Point zoomPoint) {
        synchronized (this) {
            if (mTree != null && this.mViewport != null) {
                if (newZoom < MIN_ZOOM) {
                    newZoom = MIN_ZOOM;
                }
                if (newZoom > MAX_ZOOM) {
                    newZoom = MAX_ZOOM;
                }
                mViewport.x = zoomPoint.x - (zoomPoint.x - mViewport.x) * mZoom / newZoom;
                mViewport.y = zoomPoint.y - (zoomPoint.y - mViewport.y) * mZoom / newZoom;
                mViewport.width = mViewport.width * mZoom / newZoom;
                mViewport.height = mViewport.height * mZoom / newZoom;
                mZoom = newZoom;
            }
        }
        notifyZoomChanged();
    }

    public DrawableViewNode getTree() {
        synchronized (this) {
            return mTree;
        }
    }

    public Window getWindow() {
        synchronized (this) {
            return mWindow;
        }
    }

    public Rectangle getViewport() {
        synchronized (this) {
            return mViewport;
        }
    }

    public double getZoom() {
        synchronized (this) {
            return mZoom;
        }
    }

    public DrawableViewNode getSelection() {
        synchronized (this) {
            return mSelectedNode;
        }
    }

    public static interface ITreeChangeListener {
        public void treeChanged();

        public void selectionChanged();

        public void viewportChanged();

        public void zoomChanged();
    }

    private ITreeChangeListener[] getTreeChangeListenerList() {
        ITreeChangeListener[] listeners = null;
        synchronized (mTreeChangeListeners) {
            if (mTreeChangeListeners.size() == 0) {
                return null;
            }
            listeners =
                    mTreeChangeListeners.toArray(new ITreeChangeListener[mTreeChangeListeners.size()]);
        }
        return listeners;
    }

    public void notifyTreeChanged() {
        ITreeChangeListener[] listeners = getTreeChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].treeChanged();
            }
        }
    }

    public void notifySelectionChanged() {
        ITreeChangeListener[] listeners = getTreeChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].selectionChanged();
            }
        }
    }

    public void notifyViewportChanged() {
        ITreeChangeListener[] listeners = getTreeChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].viewportChanged();
            }
        }
    }

    public void notifyZoomChanged() {
        ITreeChangeListener[] listeners = getTreeChangeListenerList();
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].zoomChanged();
            }
        }
    }

    public void addTreeChangeListener(ITreeChangeListener listener) {
        synchronized (mTreeChangeListeners) {
            mTreeChangeListeners.add(listener);
        }
    }

    public void removeTreeChangeListener(ITreeChangeListener listener) {
        synchronized (mTreeChangeListeners) {
            mTreeChangeListeners.remove(listener);
        }
    }
} public static interface ITreeChangeListener {
    public void treeChanged();

    public void selectionChanged();

    public void viewportChanged();

    public void zoomChanged();
} public class DeviceSelector extends Composite implements IWindowChangeListener, SelectionListener {
    private TreeViewer mTreeViewer;

    private Tree mTree;

    private DeviceSelectionModel mModel;

    private Font mBoldFont;

    private Image mDeviceImage;

    private Image mEmulatorImage;

    private final static int ICON_WIDTH = 16;

    private boolean mDoTreeViewStuff;

    private boolean mDoPixelPerfectStuff;

    private class ContentProvider implements ITreeContentProvider, ILabelProvider, IFontProvider {
        @Override
        public Object[] getChildren(Object parentElement) {
            if (parentElement instanceof IDevice && mDoTreeViewStuff) {
                Window[] list = mModel.getWindows((IDevice) parentElement);
                if (list != null) {
                    return list;
                }
            }
            return new Object[0];
        }

        @Override
        public Object getParent(Object element) {
            if (element instanceof Window) {
                return ((Window) element).getDevice();
            }
            return null;
        }

        @Override
        public boolean hasChildren(Object element) {
            if (element instanceof IDevice && mDoTreeViewStuff) {
                Window[] list = mModel.getWindows((IDevice) element);
                if (list != null) {
                    return list.length != 0;
                }
            }
            return false;
        }

        @Override
        public Object[] getElements(Object inputElement) {
            if (inputElement instanceof DeviceSelectionModel) {
                return mModel.getDevices();
            }
            return new Object[0];
        }

        @Override
        public void dispose() {
            // pass
        }

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            // pass
        }

        @Override
        public Image getImage(Object element) {
            if (element instanceof IDevice) {
                if (((IDevice) element).isEmulator()) {
                    return mEmulatorImage;
                }
                return mDeviceImage;
            }
            return null;
        }

        @Override
        public String getText(Object element) {
            if (element instanceof IDevice) {
                return ((IDevice) element).toString();
            } else if (element instanceof Window) {
                return ((Window) element).getTitle();
            }
            return null;
        }

        @Override
        public Font getFont(Object element) {
            if (element instanceof Window) {
                int focusedWindow = mModel.getFocusedWindow(((Window) element).getDevice());
                if (focusedWindow == ((Window) element).getHashCode()) {
                    return mBoldFont;
                }
            }
            return null;
        }

        @Override
        public void addListener(ILabelProviderListener listener) {
            // pass
        }

        @Override
        public boolean isLabelProperty(Object element, String property) {
            // pass
            return false;
        }

        @Override
        public void removeListener(ILabelProviderListener listener) {
            // pass
        }
    }

    public DeviceSelector(Composite parent, boolean doTreeViewStuff, boolean doPixelPerfectStuff) {
        super(parent, SWT.NONE);
        this.mDoTreeViewStuff = doTreeViewStuff;
        this.mDoPixelPerfectStuff = doPixelPerfectStuff;
        setLayout(new FillLayout());
        mTreeViewer = new TreeViewer(this, SWT.SINGLE);
        mTreeViewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS);

        mTree = mTreeViewer.getTree();
        mTree.setLinesVisible(true);
        mTree.addSelectionListener(this);

        addDisposeListener(mDisposeListener);

        loadResources();

        mModel = DeviceSelectionModel.getModel();
        ContentProvider contentProvider = new ContentProvider();
        mTreeViewer.setContentProvider(contentProvider);
        mTreeViewer.setLabelProvider(contentProvider);
        mModel.addWindowChangeListener(this);
        mTreeViewer.setInput(mModel);

        addControlListener(mControlListener);
    }

    public void loadResources() {
        Display display = Display.getDefault();
        Font systemFont = display.getSystemFont();
        FontData[] fontData = systemFont.getFontData();
        FontData[] newFontData = new FontData[fontData.length];
        for (int i = 0; i < fontData.length; i++) {
            newFontData[i] =
                    new FontData(fontData[i].getName(), fontData[i].getHeight(), fontData[i]
                            .getStyle()
                            | SWT.BOLD);
        }
        mBoldFont = new Font(Display.getDefault(), newFontData);

        ImageLoader loader = ImageLoader.getDdmUiLibLoader();
        mDeviceImage =
                loader.loadImage(display, "device.png", ICON_WIDTH, ICON_WIDTH, display //$NON-NLS-1$
                        .getSystemColor(SWT.COLOR_RED));

        mEmulatorImage =
                loader.loadImage(display, "emulator.png", ICON_WIDTH, ICON_WIDTH, display //$NON-NLS-1$
                        .getSystemColor(SWT.COLOR_BLUE));
    }

    private DisposeListener mDisposeListener = new DisposeListener() {
        @Override
        public void widgetDisposed(DisposeEvent e) {
            mModel.removeWindowChangeListener(DeviceSelector.this);
            mBoldFont.dispose();
        }
    };

    // If the window gets too small, hide the data, otherwise SWT throws an
    // ERROR.

    private ControlListener mControlListener = new ControlAdapter() {
        private boolean noInput = false;

        @Override
        public void controlResized(ControlEvent e) {
            if (getBounds().height <= 38) {
                mTreeViewer.setInput(null);
                noInput = true;
            } else if (noInput) {
                mTreeViewer.setInput(mModel);
                noInput = false;
            }
        }
    };

    @Override
    public boolean setFocus() {
        return mTree.setFocus();
    }

    public void setMode(boolean doTreeViewStuff, boolean doPixelPerfectStuff) {
        if (this.mDoTreeViewStuff != doTreeViewStuff
                || this.mDoPixelPerfectStuff != doPixelPerfectStuff) {
            final boolean expandAll = !this.mDoTreeViewStuff && doTreeViewStuff;
            this.mDoTreeViewStuff = doTreeViewStuff;
            this.mDoPixelPerfectStuff = doPixelPerfectStuff;
            Display.getDefault().syncExec(new Runnable() {
                @Override
                public void run() {
                    mTreeViewer.refresh();
                    if (expandAll) {
                        mTreeViewer.expandAll();
                    }
                }
            });
        }
    }

    @Override
    public void deviceConnected(final IDevice device) {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                mTreeViewer.refresh();
                mTreeViewer.setExpandedState(device, true);
            }
        });
    }

    @Override
    public void deviceChanged(final IDevice device) {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                TreeSelection selection = (TreeSelection) mTreeViewer.getSelection();
                mTreeViewer.refresh(device);
                if (selection.getFirstElement() instanceof Window
                        && ((Window) selection.getFirstElement()).getDevice() == device) {
                    mTreeViewer.setSelection(selection, true);
                }
            }
        });
    }

    @Override
    public void deviceDisconnected(final IDevice device) {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                mTreeViewer.refresh();
            }
        });
    }

    @Override
    public void focusChanged(final IDevice device) {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                TreeSelection selection = (TreeSelection) mTreeViewer.getSelection();
                mTreeViewer.refresh(device);
                if (selection.getFirstElement() instanceof Window
                        && ((Window) selection.getFirstElement()).getDevice() == device) {
                    mTreeViewer.setSelection(selection, true);
                }
            }
        });
    }

    @Override
    public void selectionChanged(IDevice device, Window window) {
        // pass
    }

    @Override
    public void widgetDefaultSelected(SelectionEvent e) {
        Object selection = ((TreeItem) e.item).getData();
        if (selection instanceof IDevice && mDoPixelPerfectStuff) {
            HierarchyViewerDirector.getDirector().loadPixelPerfectData((IDevice) selection);
        } else if (selection instanceof Window && mDoTreeViewStuff) {
            HierarchyViewerDirector.getDirector().loadViewTreeData((Window) selection);
        }
    }

    @Override
    public void widgetSelected(SelectionEvent e) {
        Object selection = ((TreeItem) e.item).getData();
        if (selection instanceof IDevice) {
            mModel.setSelection((IDevice) selection, null);
        } else if (selection instanceof Window) {
            mModel.setSelection(((Window) selection).getDevice(), (Window) selection);
        }
    }
} public class LayoutViewer extends Canvas implements ITreeChangeListener {

    private TreeViewModel mModel;

    private DrawableViewNode mTree;

    private DrawableViewNode mSelectedNode;

    private Transform mTransform;

    private Transform mInverse;

    private double mScale;

    private boolean mShowExtras = false;

    private boolean mOnBlack = true;

    public LayoutViewer(Composite parent) {
        super(parent, SWT.NONE);
        mModel = TreeViewModel.getModel();
        mModel.addTreeChangeListener(this);

        addDisposeListener(mDisposeListener);
        addPaintListener(mPaintListener);
        addListener(SWT.Resize, mResizeListener);
        addMouseListener(mMouseListener);

        mTransform = new Transform(Display.getDefault());
        mInverse = new Transform(Display.getDefault());

        treeChanged();
    }

    public void setShowExtras(boolean show) {
        mShowExtras = show;
        doRedraw();
    }

    public void setOnBlack(boolean value) {
        mOnBlack = value;
        doRedraw();
    }

    public boolean getOnBlack() {
        return mOnBlack;
    }

    private DisposeListener mDisposeListener = new DisposeListener() {
        @Override
        public void widgetDisposed(DisposeEvent e) {
            mModel.removeTreeChangeListener(LayoutViewer.this);
            mTransform.dispose();
            mInverse.dispose();
            if (mSelectedNode != null) {
                mSelectedNode.viewNode.dereferenceImage();
            }
        }
    };

    private Listener mResizeListener = new Listener() {
        @Override
        public void handleEvent(Event e) {
            synchronized (this) {
                setTransform();
            }
        }
    };

    private MouseListener mMouseListener = new MouseListener() {

        @Override
        public void mouseDoubleClick(MouseEvent e) {
            if (mSelectedNode != null) {
                HierarchyViewerDirector.getDirector()
                        .showCapture(getShell(), mSelectedNode.viewNode);
            }
        }

        @Override
        public void mouseDown(MouseEvent e) {
            boolean selectionChanged = false;
            DrawableViewNode newSelection = null;
            synchronized (LayoutViewer.this) {
                if (mTree != null) {
                    float[] pt = {
                            e.x, e.y
                    };
                    mInverse.transform(pt);
                    newSelection =
                            updateSelection(mTree, pt[0], pt[1], 0, 0, 0, 0, mTree.viewNode.width,
                                    mTree.viewNode.height);
                    if (mSelectedNode != newSelection) {
                        selectionChanged = true;
                    }
                }
            }
            if (selectionChanged) {
                mModel.setSelection(newSelection);
            }
        }

        @Override
        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 mPaintListener = new PaintListener() {
        @Override
        public void paintControl(PaintEvent e) {
            synchronized (LayoutViewer.this) {
                if (mOnBlack) {
                    e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                } else {
                    e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
                }
                e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height);
                if (mTree != null) {
                    e.gc.setLineWidth((int) Math.ceil(0.3 / mScale));
                    e.gc.setTransform(mTransform);
                    if (mOnBlack) {
                        e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
                    } else {
                        e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                    }
                    Rectangle parentClipping = e.gc.getClipping();
                    e.gc.setClipping(0, 0, mTree.viewNode.width + (int) Math.ceil(0.3 / mScale),
                            mTree.viewNode.height + (int) Math.ceil(0.3 / mScale));
                    paintRecursive(e.gc, mTree, 0, 0, true);

                    if (mSelectedNode != 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 = mSelectedNode;
                        while (currentNode != mTree) {
                            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 = mSelectedNode.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, currentNode.viewNode.height);
                            currentNode = currentNode.parent;
                        }

                        if (mShowExtras && mSelectedNode.viewNode.image != null) {
                            e.gc.drawImage(mSelectedNode.viewNode.image, left, top);
                            if (mOnBlack) {
                                e.gc.setForeground(Display.getDefault().getSystemColor(
                                        SWT.COLOR_WHITE));
                            } else {
                                e.gc.setForeground(Display.getDefault().getSystemColor(
                                        SWT.COLOR_BLACK));
                            }
                            paintRecursive(e.gc, mSelectedNode, left, top, true);

                        }

                        e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
                        e.gc.setLineWidth((int) Math.ceil(2 / mScale));
                        e.gc.drawRectangle(left, top, mSelectedNode.viewNode.width,
                                mSelectedNode.viewNode.height);
                    }
                }
            }
        }
    };

    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) Math.ceil(0.3 / mScale));
        int y1 = Math.max(parentClipping.y, top);
        int y2 =
                Math.min(parentClipping.y + parentClipping.height, top + node.viewNode.height
                        + (int) Math.ceil(0.3 / mScale));

        // Clipping is weird... You set it to -5 and it comes out 17 or
        // something.
        if (x2 <= x1 || y2 <= y1) {
            return;
        }
        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, node.viewNode.height);
        }

    }

    private void doRedraw() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                redraw();
            }
        });
    }

    private void setTransform() {
        if (mTree != null) {
            Rectangle bounds = getBounds();
            int leftRightPadding = bounds.width <= 30 ? 0 : 5;
            int topBottomPadding = bounds.height <= 30 ? 0 : 5;
            mScale =
                    Math.min(1.0 * (bounds.width - leftRightPadding * 2) / mTree.viewNode.width, 1.0
                            * (bounds.height - topBottomPadding * 2) / mTree.viewNode.height);
            int scaledWidth = (int) Math.ceil(mTree.viewNode.width * mScale);
            int scaledHeight = (int) Math.ceil(mTree.viewNode.height * mScale);

            mTransform.identity();
            mInverse.identity();
            mTransform.translate((bounds.width - scaledWidth) / 2.0f,
                    (bounds.height - scaledHeight) / 2.0f);
            mInverse.translate((bounds.width - scaledWidth) / 2.0f,
                    (bounds.height - scaledHeight) / 2.0f);
            mTransform.scale((float) mScale, (float) mScale);
            mInverse.scale((float) mScale, (float) mScale);
            if (bounds.width != 0 && bounds.height != 0) {
                mInverse.invert();
            }
        }
    }

    @Override
    public void selectionChanged() {
        synchronized (this) {
            if (mSelectedNode != null) {
                mSelectedNode.viewNode.dereferenceImage();
            }
            mSelectedNode = mModel.getSelection();
            if (mSelectedNode != null) {
                mSelectedNode.viewNode.referenceImage();
            }
        }
        doRedraw();
    }

    // Note the syncExec and then synchronized... It avoids deadlock
    @Override
    public void treeChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    if (mSelectedNode != null) {
                        mSelectedNode.viewNode.dereferenceImage();
                    }
                    mTree = mModel.getTree();
                    mSelectedNode = mModel.getSelection();
                    if (mSelectedNode != null) {
                        mSelectedNode.viewNode.referenceImage();
                    }
                    setTransform();
                }
            }
        });
        doRedraw();
    }

    @Override
    public void viewportChanged() {
        // pass
    }

    @Override
    public void zoomChanged() {
        // pass
    }
} public class PixelPerfect extends ScrolledComposite implements IImageChangeListener {
    private Canvas mCanvas;

    private PixelPerfectModel mModel;

    private Image mImage;

    private Color mCrosshairColor;

    private Color mMarginColor;

    private Color mBorderColor;

    private Color mPaddingColor;

    private int mWidth;

    private int mHeight;

    private Point mCrosshairLocation;

    private ViewNode mSelectedNode;

    private Image mOverlayImage;

    private double mOverlayTransparency;

    public PixelPerfect(Composite parent) {
        super(parent, SWT.H_SCROLL | SWT.V_SCROLL);
        mCanvas = new Canvas(this, SWT.NONE);
        setContent(mCanvas);
        setExpandHorizontal(true);
        setExpandVertical(true);
        mModel = PixelPerfectModel.getModel();
        mModel.addImageChangeListener(this);

        mCanvas.addPaintListener(mPaintListener);
        mCanvas.addMouseListener(mMouseListener);
        mCanvas.addMouseMoveListener(mMouseMoveListener);
        mCanvas.addKeyListener(mKeyListener);

        addDisposeListener(mDisposeListener);

        mCrosshairColor = new Color(Display.getDefault(), new RGB(0, 255, 255));
        mBorderColor = new Color(Display.getDefault(), new RGB(255, 0, 0));
        mMarginColor = new Color(Display.getDefault(), new RGB(0, 255, 0));
        mPaddingColor = new Color(Display.getDefault(), new RGB(0, 0, 255));

        imageLoaded();
    }

    private DisposeListener mDisposeListener = new DisposeListener() {
        @Override
        public void widgetDisposed(DisposeEvent e) {
            mModel.removeImageChangeListener(PixelPerfect.this);
            mCrosshairColor.dispose();
            mBorderColor.dispose();
            mPaddingColor.dispose();
        }
    };

    @Override
    public boolean setFocus() {
        return mCanvas.setFocus();
    }

    private MouseListener mMouseListener = new MouseListener() {

        @Override
        public void mouseDoubleClick(MouseEvent e) {
            // pass
        }

        @Override
        public void mouseDown(MouseEvent e) {
            handleMouseEvent(e);
        }

        @Override
        public void mouseUp(MouseEvent e) {
            handleMouseEvent(e);
        }

    };

    private MouseMoveListener mMouseMoveListener = new MouseMoveListener() {
        @Override
        public void mouseMove(MouseEvent e) {
            if (e.stateMask != 0) {
                handleMouseEvent(e);
            }
        }
    };

    private void handleMouseEvent(MouseEvent e) {
        synchronized (PixelPerfect.this) {
            if (mImage == null) {
                return;
            }
            int leftOffset = mCanvas.getSize().x / 2 - mWidth / 2;
            int topOffset = mCanvas.getSize().y / 2 - mHeight / 2;
            e.x -= leftOffset;
            e.y -= topOffset;
            e.x = Math.max(e.x, 0);
            e.x = Math.min(e.x, mWidth - 1);
            e.y = Math.max(e.y, 0);
            e.y = Math.min(e.y, mHeight - 1);
        }
        mModel.setCrosshairLocation(e.x, e.y);
    }

    private KeyListener mKeyListener = new KeyListener() {

        @Override
        public void keyPressed(KeyEvent e) {
            boolean crosshairMoved = false;
            synchronized (PixelPerfect.this) {
                if (mImage != null) {
                    switch (e.keyCode) {
                        case SWT.ARROW_UP:
                            if (mCrosshairLocation.y != 0) {
                                mCrosshairLocation.y--;
                                crosshairMoved = true;
                            }
                            break;
                        case SWT.ARROW_DOWN:
                            if (mCrosshairLocation.y != mHeight - 1) {
                                mCrosshairLocation.y++;
                                crosshairMoved = true;
                            }
                            break;
                        case SWT.ARROW_LEFT:
                            if (mCrosshairLocation.x != 0) {
                                mCrosshairLocation.x--;
                                crosshairMoved = true;
                            }
                            break;
                        case SWT.ARROW_RIGHT:
                            if (mCrosshairLocation.x != mWidth - 1) {
                                mCrosshairLocation.x++;
                                crosshairMoved = true;
                            }
                            break;
                    }
                }
            }
            if (crosshairMoved) {
                mModel.setCrosshairLocation(mCrosshairLocation.x, mCrosshairLocation.y);
            }
        }

        @Override
        public void keyReleased(KeyEvent e) {
            // pass
        }

    };

    private PaintListener mPaintListener = new PaintListener() {
        @Override
        public void paintControl(PaintEvent e) {
            synchronized (PixelPerfect.this) {
                e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                e.gc.fillRectangle(0, 0, mCanvas.getSize().x, mCanvas.getSize().y);
                if (mImage != null) {
                    // Let's be cool and put it in the center...
                    int leftOffset = mCanvas.getSize().x / 2 - mWidth / 2;
                    int topOffset = mCanvas.getSize().y / 2 - mHeight / 2;
                    e.gc.drawImage(mImage, leftOffset, topOffset);
                    if (mOverlayImage != null) {
                        e.gc.setAlpha((int) (mOverlayTransparency * 255));
                        int overlayTopOffset =
                                mCanvas.getSize().y / 2 + mHeight / 2
                                        - mOverlayImage.getBounds().height;
                        e.gc.drawImage(mOverlayImage, leftOffset, overlayTopOffset);
                        e.gc.setAlpha(255);
                    }

                    if (mSelectedNode != null) {
                        // If the screen is in landscape mode, the
                        // coordinates are backwards.
                        int leftShift = 0;
                        int topShift = 0;
                        int nodeLeft = mSelectedNode.left;
                        int nodeTop = mSelectedNode.top;
                        int nodeWidth = mSelectedNode.width;
                        int nodeHeight = mSelectedNode.height;
                        int nodeMarginLeft = mSelectedNode.marginLeft;
                        int nodeMarginTop = mSelectedNode.marginTop;
                        int nodeMarginRight = mSelectedNode.marginRight;
                        int nodeMarginBottom = mSelectedNode.marginBottom;
                        int nodePadLeft = mSelectedNode.paddingLeft;
                        int nodePadTop = mSelectedNode.paddingTop;
                        int nodePadRight = mSelectedNode.paddingRight;
                        int nodePadBottom = mSelectedNode.paddingBottom;
                        ViewNode cur = mSelectedNode;
                        while (cur.parent != null) {
                            leftShift += cur.parent.left - cur.parent.scrollX;
                            topShift += cur.parent.top - cur.parent.scrollY;
                            cur = cur.parent;
                        }

                        // Everything is sideways.
                        if (cur.width > cur.height) {
                            e.gc.setForeground(mPaddingColor);
                            e.gc.drawRectangle(leftOffset + mWidth - nodeTop - topShift - nodeHeight
                                    + nodePadBottom,
                                    topOffset + leftShift + nodeLeft + nodePadLeft, nodeHeight
                                            - nodePadBottom - nodePadTop, nodeWidth - nodePadRight
                                            - nodePadLeft);
                            e.gc.setForeground(mMarginColor);
                            e.gc.drawRectangle(leftOffset + mWidth - nodeTop - topShift - nodeHeight
                                    - nodeMarginBottom, topOffset + leftShift + nodeLeft
                                    - nodeMarginLeft,
                                    nodeHeight + nodeMarginBottom + nodeMarginTop, nodeWidth
                                            + nodeMarginRight + nodeMarginLeft);
                            e.gc.setForeground(mBorderColor);
                            e.gc.drawRectangle(
                                    leftOffset + mWidth - nodeTop - topShift - nodeHeight, topOffset
                                            + leftShift + nodeLeft, nodeHeight, nodeWidth);
                        } else {
                            e.gc.setForeground(mPaddingColor);
                            e.gc.drawRectangle(leftOffset + leftShift + nodeLeft + nodePadLeft,
                                    topOffset + topShift + nodeTop + nodePadTop, nodeWidth
                                            - nodePadRight - nodePadLeft, nodeHeight
                                            - nodePadBottom - nodePadTop);
                            e.gc.setForeground(mMarginColor);
                            e.gc.drawRectangle(leftOffset + leftShift + nodeLeft - nodeMarginLeft,
                                    topOffset + topShift + nodeTop - nodeMarginTop, nodeWidth
                                            + nodeMarginRight + nodeMarginLeft, nodeHeight
                                            + nodeMarginBottom + nodeMarginTop);
                            e.gc.setForeground(mBorderColor);
                            e.gc.drawRectangle(leftOffset + leftShift + nodeLeft, topOffset
                                    + topShift + nodeTop, nodeWidth, nodeHeight);
                        }
                    }
                    if (mCrosshairLocation != null) {
                        e.gc.setForeground(mCrosshairColor);
                        e.gc.drawLine(leftOffset, topOffset + mCrosshairLocation.y, leftOffset
                                + mWidth - 1, topOffset + mCrosshairLocation.y);
                        e.gc.drawLine(leftOffset + mCrosshairLocation.x, topOffset, leftOffset
                                + mCrosshairLocation.x, topOffset + mHeight - 1);
                    }
                }
            }
        }
    };

    private void doRedraw() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                mCanvas.redraw();
            }
        });
    }

    private void loadImage() {
        mImage = mModel.getImage();
        if (mImage != null) {
            mWidth = mImage.getBounds().width;
            mHeight = mImage.getBounds().height;
        } else {
            mWidth = 0;
            mHeight = 0;
        }
        setMinSize(mWidth, mHeight);
    }

    @Override
    public void imageLoaded() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    loadImage();
                    mCrosshairLocation = mModel.getCrosshairLocation();
                    mSelectedNode = mModel.getSelected();
                    mOverlayImage = mModel.getOverlayImage();
                    mOverlayTransparency = mModel.getOverlayTransparency();
                }
            }
        });
        doRedraw();
    }

    @Override
    public void imageChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    loadImage();
                }
            }
        });
        doRedraw();
    }

    @Override
    public void crosshairMoved() {
        synchronized (this) {
            mCrosshairLocation = mModel.getCrosshairLocation();
        }
        doRedraw();
    }

    @Override
    public void selectionChanged() {
        synchronized (this) {
            mSelectedNode = mModel.getSelected();
        }
        doRedraw();
    }

    // Note the syncExec and then synchronized... It avoids deadlock
    @Override
    public void treeChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    mSelectedNode = mModel.getSelected();
                }
            }
        });
        doRedraw();
    }

    @Override
    public void zoomChanged() {
        // pass
    }

    @Override
    public void overlayChanged() {
        synchronized (this) {
            mOverlayImage = mModel.getOverlayImage();
            mOverlayTransparency = mModel.getOverlayTransparency();
        }
        doRedraw();
    }

    @Override
    public void overlayTransparencyChanged() {
        synchronized (this) {
            mOverlayTransparency = mModel.getOverlayTransparency();
        }
        doRedraw();
    }
} public class PixelPerfectControls extends Composite implements IImageChangeListener {

    private Slider mOverlaySlider;

    private Slider mZoomSlider;

    private Slider mAutoRefreshSlider;

    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);

        mOverlaySlider = new Slider(this, SWT.HORIZONTAL);
        mOverlaySlider.setMinimum(0);
        mOverlaySlider.setMaximum(101);
        mOverlaySlider.setThumb(1);
        mOverlaySlider.setSelection((int) Math.round(PixelPerfectModel.getModel()
                .getOverlayTransparency() * 100));

        Image overlayImage = PixelPerfectModel.getModel().getOverlayImage();
        mOverlaySlider.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);
        mOverlaySlider.setLayoutData(overlaySliderData);

        mOverlaySlider.addSelectionListener(overlaySliderSelectionListener);

        mAutoRefreshSlider = new Slider(this, SWT.HORIZONTAL);
        mAutoRefreshSlider.setMinimum(1);
        mAutoRefreshSlider.setMaximum(41);
        mAutoRefreshSlider.setThumb(1);
        mAutoRefreshSlider.setSelection(HierarchyViewerDirector.getDirector()
                .getPixelPerfectAutoRefreshInverval());
        FormData refreshSliderData = new FormData();
        refreshSliderData.right = new FormAttachment(overlayTransparencyRight, -4);
        refreshSliderData.top = new FormAttachment(overlayTransparencyRight, 2);
        refreshSliderData.left = new FormAttachment(mOverlaySlider, 0, SWT.LEFT);
        mAutoRefreshSlider.setLayoutData(refreshSliderData);

        mAutoRefreshSlider.addSelectionListener(mRefreshSliderSelectionListener);

        mZoomSlider = new Slider(this, SWT.HORIZONTAL);
        mZoomSlider.setMinimum(2);
        mZoomSlider.setMaximum(25);
        mZoomSlider.setThumb(1);
        mZoomSlider.setSelection(PixelPerfectModel.getModel().getZoom());
        FormData zoomSliderData = new FormData();
        zoomSliderData.right = new FormAttachment(overlayTransparencyRight, -4);
        zoomSliderData.top = new FormAttachment(refreshRight, 2);
        zoomSliderData.left = new FormAttachment(mOverlaySlider, 0, SWT.LEFT);
        mZoomSlider.setLayoutData(zoomSliderData);

        mZoomSlider.addSelectionListener(mZoomSliderSelectionListener);

        addDisposeListener(mDisposeListener);

        PixelPerfectModel.getModel().addImageChangeListener(this);
    }

    private DisposeListener mDisposeListener = new DisposeListener() {
        @Override
        public void widgetDisposed(DisposeEvent e) {
            PixelPerfectModel.getModel().removeImageChangeListener(PixelPerfectControls.this);
        }
    };

    private SelectionListener overlaySliderSelectionListener = new SelectionListener() {
        private int oldValue;

        @Override
        public void widgetDefaultSelected(SelectionEvent e) {
            // pass
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            int newValue = mOverlaySlider.getSelection();
            if (oldValue != newValue) {
                PixelPerfectModel.getModel().removeImageChangeListener(PixelPerfectControls.this);
                PixelPerfectModel.getModel().setOverlayTransparency(newValue / 100.0);
                PixelPerfectModel.getModel().addImageChangeListener(PixelPerfectControls.this);
                oldValue = newValue;
            }
        }
    };

    private SelectionListener mRefreshSliderSelectionListener = new SelectionListener() {
        private int oldValue;

        @Override
        public void widgetDefaultSelected(final SelectionEvent e) {
            // pass
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            int newValue = mAutoRefreshSlider.getSelection();
            if (oldValue != newValue) {
                HierarchyViewerDirector.getDirector().setPixelPerfectAutoRefreshInterval(newValue);
            }
        }
    };

    private SelectionListener mZoomSliderSelectionListener = new SelectionListener() {
        private int oldValue;

        @Override
        public void widgetDefaultSelected(SelectionEvent e) {
            // pass
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            int newValue = mZoomSlider.getSelection();
            if (oldValue != newValue) {
                PixelPerfectModel.getModel().removeImageChangeListener(PixelPerfectControls.this);
                PixelPerfectModel.getModel().setZoom(newValue);
                PixelPerfectModel.getModel().addImageChangeListener(PixelPerfectControls.this);
                oldValue = newValue;
            }
        }
    };

    @Override
    public void crosshairMoved() {
        // pass
    }

    @Override
    public void treeChanged() {
        // pass
    }

    @Override
    public void imageChanged() {
        // pass
    }

    @Override
    public void imageLoaded() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                Image overlayImage = PixelPerfectModel.getModel().getOverlayImage();
                mOverlaySlider.setEnabled(overlayImage != null);
                if (PixelPerfectModel.getModel().getImage() == null) {
                } else {
                    mZoomSlider.setSelection(PixelPerfectModel.getModel().getZoom());
                }
            }
        });
    }

    @Override
    public void overlayChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                Image overlayImage = PixelPerfectModel.getModel().getOverlayImage();
                mOverlaySlider.setEnabled(overlayImage != null);
            }
        });
    }

    @Override
    public void overlayTransparencyChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                mOverlaySlider.setSelection((int) (PixelPerfectModel.getModel()
                        .getOverlayTransparency() * 100));
            }
        });
    }

    @Override
    public void selectionChanged() {
        // pass
    }

    @Override
    public void zoomChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                mZoomSlider.setSelection(PixelPerfectModel.getModel().getZoom());
            }
        });
    }
} public class PixelPerfectLoupe extends Canvas implements IImageChangeListener {
    private PixelPerfectModel mModel;

    private Image mImage;

    private Image mGrid;

    private Color mCrosshairColor;

    private int mWidth;

    private int mHeight;

    private Point mCrosshairLocation;

    private int mZoom;

    private Transform mTransform;

    private int mCanvasWidth;

    private int mCanvasHeight;

    private Image mOverlayImage;

    private double mOverlayTransparency;

    private boolean mShowOverlay = false;

    public PixelPerfectLoupe(Composite parent) {
        super(parent, SWT.NONE);
        mModel = PixelPerfectModel.getModel();
        mModel.addImageChangeListener(this);

        addPaintListener(mPaintListener);
        addMouseListener(mMouseListener);
        addMouseWheelListener(mMouseWheelListener);
        addDisposeListener(mDisposeListener);
        addKeyListener(mKeyListener);

        mCrosshairColor = new Color(Display.getDefault(), new RGB(255, 94, 254));

        mTransform = new Transform(Display.getDefault());

        imageLoaded();
    }

    public void setShowOverlay(boolean value) {
        synchronized (this) {
            mShowOverlay = value;
        }
        doRedraw();
    }

    private DisposeListener mDisposeListener = new DisposeListener() {
        @Override
        public void widgetDisposed(DisposeEvent e) {
            mModel.removeImageChangeListener(PixelPerfectLoupe.this);
            mCrosshairColor.dispose();
            mTransform.dispose();
            if (mGrid != null) {
                mGrid.dispose();
            }
        }
    };

    private MouseListener mMouseListener = new MouseListener() {

        @Override
        public void mouseDoubleClick(MouseEvent e) {
            // pass
        }

        @Override
        public void mouseDown(MouseEvent e) {
            handleMouseEvent(e);
        }

        @Override
        public void mouseUp(MouseEvent e) {
            //
        }

    };

    private MouseWheelListener mMouseWheelListener = new MouseWheelListener() {
        @Override
        public void mouseScrolled(MouseEvent e) {
            int newZoom = -1;
            synchronized (PixelPerfectLoupe.this) {
                if (mImage != null && mCrosshairLocation != null) {
                    if (e.count > 0) {
                        newZoom = mZoom + 1;
                    } else {
                        newZoom = mZoom - 1;
                    }
                }
            }
            if (newZoom != -1) {
                mModel.setZoom(newZoom);
            }
        }
    };

    private void handleMouseEvent(MouseEvent e) {
        int newX = -1;
        int newY = -1;
        synchronized (PixelPerfectLoupe.this) {
            if (mImage == null) {
                return;
            }
            int zoomedX = -mCrosshairLocation.x * mZoom - mZoom / 2 + getBounds().width / 2;
            int zoomedY = -mCrosshairLocation.y * mZoom - mZoom / 2 + getBounds().height / 2;
            int x = (e.x - zoomedX) / mZoom;
            int y = (e.y - zoomedY) / mZoom;
            if (x >= 0 && x < mWidth && y >= 0 && y < mHeight) {
                newX = x;
                newY = y;
            }
        }
        if (newX != -1) {
            mModel.setCrosshairLocation(newX, newY);
        }
    }

    private KeyListener mKeyListener = new KeyListener() {

        @Override
        public void keyPressed(KeyEvent e) {
            boolean crosshairMoved = false;
            synchronized (PixelPerfectLoupe.this) {
                if (mImage != null) {
                    switch (e.keyCode) {
                        case SWT.ARROW_UP:
                            if (mCrosshairLocation.y != 0) {
                                mCrosshairLocation.y--;
                                crosshairMoved = true;
                            }
                            break;
                        case SWT.ARROW_DOWN:
                            if (mCrosshairLocation.y != mHeight - 1) {
                                mCrosshairLocation.y++;
                                crosshairMoved = true;
                            }
                            break;
                        case SWT.ARROW_LEFT:
                            if (mCrosshairLocation.x != 0) {
                                mCrosshairLocation.x--;
                                crosshairMoved = true;
                            }
                            break;
                        case SWT.ARROW_RIGHT:
                            if (mCrosshairLocation.x != mWidth - 1) {
                                mCrosshairLocation.x++;
                                crosshairMoved = true;
                            }
                            break;
                    }
                }
            }
            if (crosshairMoved) {
                mModel.setCrosshairLocation(mCrosshairLocation.x, mCrosshairLocation.y);
            }
        }

        @Override
        public void keyReleased(KeyEvent e) {
            // pass
        }

    };

    private PaintListener mPaintListener = new PaintListener() {
        @Override
        public void paintControl(PaintEvent e) {
            synchronized (PixelPerfectLoupe.this) {
                e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                e.gc.fillRectangle(0, 0, getSize().x, getSize().y);
                if (mImage != null && mCrosshairLocation != null) {
                    int zoomedX = -mCrosshairLocation.x * mZoom - mZoom / 2 + getBounds().width / 2;
                    int zoomedY = -mCrosshairLocation.y * mZoom - mZoom / 2 + getBounds().height / 2;
                    mTransform.translate(zoomedX, zoomedY);
                    mTransform.scale(mZoom, mZoom);
                    e.gc.setInterpolation(SWT.NONE);
                    e.gc.setTransform(mTransform);
                    e.gc.drawImage(mImage, 0, 0);
                    if (mShowOverlay && mOverlayImage != null) {
                        e.gc.setAlpha((int) (mOverlayTransparency * 255));
                        e.gc.drawImage(mOverlayImage, 0, mHeight - mOverlayImage.getBounds().height);
                        e.gc.setAlpha(255);
                    }

                    mTransform.identity();
                    e.gc.setTransform(mTransform);

                    // If the size of the canvas has changed, we need to make
                    // another grid.
                    if (mGrid != null
                            && (mCanvasWidth != getBounds().width || mCanvasHeight != getBounds().height)) {
                        mGrid.dispose();
                        mGrid = null;
                    }
                    mCanvasWidth = getBounds().width;
                    mCanvasHeight = getBounds().height;
                    if (mGrid == null) {
                        // Make a transparent image;
                        ImageData imageData =
                                new ImageData(mCanvasWidth + mZoom + 1, mCanvasHeight + mZoom + 1, 1,
                                        new PaletteData(new RGB[] {
                                            new RGB(0, 0, 0)
                                        }));
                        imageData.transparentPixel = 0;

                        // Draw the grid.
                        mGrid = new Image(Display.getDefault(), imageData);
                        GC gc = new GC(mGrid);
                        gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
                        for (int x = 0; x <= mCanvasWidth + mZoom; x += mZoom) {
                            gc.drawLine(x, 0, x, mCanvasHeight + mZoom);
                        }
                        for (int y = 0; y <= mCanvasHeight + mZoom; y += mZoom) {
                            gc.drawLine(0, y, mCanvasWidth + mZoom, y);
                        }
                        gc.dispose();
                    }

                    e.gc.setClipping(new Rectangle(zoomedX, zoomedY, mWidth * mZoom + 1, mHeight
                            * mZoom + 1));
                    e.gc.setAlpha(76);
                    e.gc.drawImage(mGrid, (mCanvasWidth / 2 - mZoom / 2) % mZoom - mZoom,
                            (mCanvasHeight / 2 - mZoom / 2) % mZoom - mZoom);
                    e.gc.setAlpha(255);

                    e.gc.setForeground(mCrosshairColor);
                    e.gc.drawLine(0, mCanvasHeight / 2, mCanvasWidth - 1, mCanvasHeight / 2);
                    e.gc.drawLine(mCanvasWidth / 2, 0, mCanvasWidth / 2, mCanvasHeight - 1);
                }
            }
        }
    };

    private void doRedraw() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                redraw();
            }
        });
    }

    private void loadImage() {
        mImage = mModel.getImage();
        if (mImage != null) {
            mWidth = mImage.getBounds().width;
            mHeight = mImage.getBounds().height;
        } else {
            mWidth = 0;
            mHeight = 0;
        }
    }

    // Note the syncExec and then synchronized... It avoids deadlock
    @Override
    public void imageLoaded() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    loadImage();
                    mCrosshairLocation = mModel.getCrosshairLocation();
                    mZoom = mModel.getZoom();
                    mOverlayImage = mModel.getOverlayImage();
                    mOverlayTransparency = mModel.getOverlayTransparency();
                }
            }
        });
        doRedraw();
    }

    @Override
    public void imageChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    loadImage();
                }
            }
        });
        doRedraw();
    }

    @Override
    public void crosshairMoved() {
        synchronized (this) {
            mCrosshairLocation = mModel.getCrosshairLocation();
        }
        doRedraw();
    }

    @Override
    public void selectionChanged() {
        // pass
    }

    @Override
    public void treeChanged() {
        // pass
    }

    @Override
    public void zoomChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    if (mGrid != null) {
                        // To notify that the zoom level has changed, we get rid
                        // of the
                        // grid.
                        mGrid.dispose();
                        mGrid = null;
                    }
                    mZoom = mModel.getZoom();
                }
            }
        });
        doRedraw();
    }

    @Override
    public void overlayChanged() {
        synchronized (this) {
            mOverlayImage = mModel.getOverlayImage();
            mOverlayTransparency = mModel.getOverlayTransparency();
        }
        doRedraw();
    }

    @Override
    public void overlayTransparencyChanged() {
        synchronized (this) {
            mOverlayTransparency = mModel.getOverlayTransparency();
        }
        doRedraw();
    }
} public class PixelPerfectPixelPanel extends Canvas implements IImageChangeListener {
    private PixelPerfectModel mModel;

    private Image mImage;

    private Image mOverlayImage;

    private Point mCrosshairLocation;

    public static final int PREFERRED_WIDTH = 180;

    public static final int PREFERRED_HEIGHT = 52;

    public PixelPerfectPixelPanel(Composite parent) {
        super(parent, SWT.NONE);
        mModel = PixelPerfectModel.getModel();
        mModel.addImageChangeListener(this);

        addPaintListener(mPaintListener);
        addDisposeListener(mDisposeListener);

        imageLoaded();
    }

    @Override
    public Point computeSize(int wHint, int hHint, boolean changed) {
        int height = PREFERRED_HEIGHT;
        int width = (wHint == SWT.DEFAULT) ? PREFERRED_WIDTH : wHint;
        return new Point(width, height);
    }

    private DisposeListener mDisposeListener = new DisposeListener() {
        @Override
        public void widgetDisposed(DisposeEvent e) {
            mModel.removeImageChangeListener(PixelPerfectPixelPanel.this);
        }
    };

    private PaintListener mPaintListener = new PaintListener() {
        @Override
        public void paintControl(PaintEvent e) {
            synchronized (PixelPerfectPixelPanel.this) {
                e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height);
                if (mImage != null) {
                    RGB pixel =
                            mImage.getImageData().palette.getRGB(mImage.getImageData().getPixel(
                                    mCrosshairLocation.x, mCrosshairLocation.y));
                    Color rgbColor = new Color(Display.getDefault(), pixel);
                    e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
                    e.gc.setBackground(rgbColor);
                    e.gc.drawRectangle(4, 4, 60, 30);
                    e.gc.fillRectangle(5, 5, 59, 29);
                    rgbColor.dispose();
                    e.gc.drawText("#"
                            + Integer
                                    .toHexString(
                                            (1 << 24) + (pixel.red << 16) + (pixel.green << 8)
                                                    + pixel.blue).substring(1), 4, 35, true);
                    e.gc.drawText("R:", 80, 4, true);
                    e.gc.drawText("G:", 80, 20, true);
                    e.gc.drawText("B:", 80, 35, true);
                    e.gc.drawText(Integer.toString(pixel.red), 97, 4, true);
                    e.gc.drawText(Integer.toString(pixel.green), 97, 20, true);
                    e.gc.drawText(Integer.toString(pixel.blue), 97, 35, true);
                    e.gc.drawText("X:", 132, 4, true);
                    e.gc.drawText("Y:", 132, 20, true);
                    e.gc.drawText(Integer.toString(mCrosshairLocation.x) + " px", 149, 4, true);
                    e.gc.drawText(Integer.toString(mCrosshairLocation.y) + " px", 149, 20, true);

                    if (mOverlayImage != null) {
                        int xInOverlay = mCrosshairLocation.x;
                        int yInOverlay =
                                mCrosshairLocation.y
                                        - (mImage.getBounds().height - mOverlayImage.getBounds().height);
                        if (xInOverlay >= 0 && yInOverlay >= 0
                                && xInOverlay < mOverlayImage.getBounds().width
                                && yInOverlay < mOverlayImage.getBounds().height) {
                            pixel =
                                    mOverlayImage.getImageData().palette.getRGB(mOverlayImage
                                            .getImageData().getPixel(xInOverlay, yInOverlay));
                            rgbColor = new Color(Display.getDefault(), pixel);
                            e.gc
                                    .setForeground(Display.getDefault().getSystemColor(
                                            SWT.COLOR_WHITE));
                            e.gc.setBackground(rgbColor);
                            e.gc.drawRectangle(204, 4, 60, 30);
                            e.gc.fillRectangle(205, 5, 59, 29);
                            rgbColor.dispose();
                            e.gc.drawText("#"
                                    + Integer.toHexString(
                                            (1 << 24) + (pixel.red << 16) + (pixel.green << 8)
                                                    + pixel.blue).substring(1), 204, 35, true);
                            e.gc.drawText("R:", 280, 4, true);
                            e.gc.drawText("G:", 280, 20, true);
                            e.gc.drawText("B:", 280, 35, true);
                            e.gc.drawText(Integer.toString(pixel.red), 297, 4, true);
                            e.gc.drawText(Integer.toString(pixel.green), 297, 20, true);
                            e.gc.drawText(Integer.toString(pixel.blue), 297, 35, true);
                        }
                    }
                }
            }
        }
    };

    private void doRedraw() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                redraw();
            }
        });
    }

    @Override
    public void crosshairMoved() {
        synchronized (this) {
            mCrosshairLocation = mModel.getCrosshairLocation();
        }
        doRedraw();
    }

    @Override
    public void imageChanged() {
        synchronized (this) {
            mImage = mModel.getImage();
        }
        doRedraw();
    }

    @Override
    public void imageLoaded() {
        synchronized (this) {
            mImage = mModel.getImage();
            mCrosshairLocation = mModel.getCrosshairLocation();
            mOverlayImage = mModel.getOverlayImage();
        }
        doRedraw();
    }

    @Override
    public void overlayChanged() {
        synchronized (this) {
            mOverlayImage = mModel.getOverlayImage();
        }
        doRedraw();
    }

    @Override
    public void overlayTransparencyChanged() {
        // pass
    }

    @Override
    public void selectionChanged() {
        // pass
    }

    @Override
    public void treeChanged() {
        // pass
    }

    @Override
    public void zoomChanged() {
        // pass
    }
} public class PixelPerfectTree extends Composite implements IImageChangeListener, SelectionListener {

    private TreeViewer mTreeViewer;

    private Tree mTree;

    private PixelPerfectModel mModel;

    private Image mFolderImage;

    private Image mFileImage;

    private class ContentProvider implements ITreeContentProvider, ILabelProvider {
        @Override
        public Object[] getChildren(Object element) {
            if (element instanceof ViewNode) {
                List<ViewNode> children = ((ViewNode) element).children;
                return children.toArray(new ViewNode[children.size()]);
            }
            return null;
        }

        @Override
        public Object getParent(Object element) {
            if (element instanceof ViewNode) {
                return ((ViewNode) element).parent;
            }
            return null;
        }

        @Override
        public boolean hasChildren(Object element) {
            if (element instanceof ViewNode) {
                return ((ViewNode) element).children.size() != 0;
            }
            return false;
        }

        @Override
        public Object[] getElements(Object element) {
            if (element instanceof PixelPerfectModel) {
                ViewNode viewNode = ((PixelPerfectModel) element).getViewNode();
                if (viewNode == null) {
                    return new Object[0];
                }
                return new Object[] {
                    viewNode
                };
            }
            return new Object[0];
        }

        @Override
        public void dispose() {
            // pass
        }

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            // pass
        }

        @Override
        public Image getImage(Object element) {
            if (element instanceof ViewNode) {
                if (hasChildren(element)) {
                    return mFolderImage;
                }
                return mFileImage;
            }
            return null;
        }

        @Override
        public String getText(Object element) {
            if (element instanceof ViewNode) {
                return ((ViewNode) element).name;
            }
            return null;
        }

        @Override
        public void addListener(ILabelProviderListener listener) {
            // pass
        }

        @Override
        public boolean isLabelProperty(Object element, String property) {
            // pass
            return false;
        }

        @Override
        public void removeListener(ILabelProviderListener listener) {
            // pass
        }
    }

    public PixelPerfectTree(Composite parent) {
        super(parent, SWT.NONE);
        setLayout(new FillLayout());
        mTreeViewer = new TreeViewer(this, SWT.SINGLE);
        mTreeViewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS);

        mTree = mTreeViewer.getTree();
        mTree.addSelectionListener(this);

        loadResources();

        addDisposeListener(mDisposeListener);

        mModel = PixelPerfectModel.getModel();
        ContentProvider contentProvider = new ContentProvider();
        mTreeViewer.setContentProvider(contentProvider);
        mTreeViewer.setLabelProvider(contentProvider);
        mTreeViewer.setInput(mModel);
        mModel.addImageChangeListener(this);

    }

    private void loadResources() {
        ImageLoader loader = ImageLoader.getDdmUiLibLoader();
        mFileImage = loader.loadImage("file.png", Display.getDefault());
        mFolderImage = loader.loadImage("folder.png", Display.getDefault());
    }

    private DisposeListener mDisposeListener = new DisposeListener() {
        @Override
        public void widgetDisposed(DisposeEvent e) {
            mModel.removeImageChangeListener(PixelPerfectTree.this);
        }
    };

    @Override
    public boolean setFocus() {
        return mTree.setFocus();
    }

    @Override
    public void imageLoaded() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                mTreeViewer.refresh();
                mTreeViewer.expandAll();
            }
        });
    }

    @Override
    public void imageChanged() {
        // pass
    }

    @Override
    public void crosshairMoved() {
        // pass
    }

    @Override
    public void selectionChanged() {
        // pass
    }

    @Override
    public void treeChanged() {
        imageLoaded();
    }

    @Override
    public void widgetDefaultSelected(SelectionEvent e) {
        // pass
    }

    @Override
    public void widgetSelected(SelectionEvent e) {
        // To combat phantom selection...
        if (((TreeSelection) mTreeViewer.getSelection()).isEmpty()) {
            mModel.setSelected(null);
        } else {
            mModel.setSelected((ViewNode) e.item.getData());
        }
    }

    @Override
    public void zoomChanged() {
        // pass
    }

    @Override
    public void overlayChanged() {
        // pass
    }

    @Override
    public void overlayTransparencyChanged() {
        // pass
    }
} public class PropertyViewer extends Composite implements ITreeChangeListener {
    private TreeViewModel mModel;

    private TreeViewer mTreeViewer;

    private Tree mTree;

    private DrawableViewNode mSelectedNode;

    private class ContentProvider implements ITreeContentProvider, ITableLabelProvider {

        @Override
        public Object[] getChildren(Object parentElement) {
            synchronized (PropertyViewer.this) {
                if (mSelectedNode != null && parentElement instanceof String) {
                    String category = (String) parentElement;
                    ArrayList<Property> returnValue = new ArrayList<Property>();
                    for (Property property : mSelectedNode.viewNode.properties) {
                        if (category.equals(ViewNode.MISCELLANIOUS)) {
                            if (property.name.indexOf(':') == -1) {
                                returnValue.add(property);
                            }
                        } else {
                            if (property.name.startsWith(((String) parentElement) + ":")) {
                                returnValue.add(property);
                            }
                        }
                    }
                    return returnValue.toArray(new Property[returnValue.size()]);
                }
                return new Object[0];
            }
        }

        @Override
        public Object getParent(Object element) {
            synchronized (PropertyViewer.this) {
                if (mSelectedNode != null && element instanceof Property) {
                    if (mSelectedNode.viewNode.categories.size() == 0) {
                        return null;
                    }
                    String name = ((Property) element).name;
                    int index = name.indexOf(':');
                    if (index == -1) {
                        return ViewNode.MISCELLANIOUS;
                    }
                    return name.substring(0, index);
                }
                return null;
            }
        }

        @Override
        public boolean hasChildren(Object element) {
            synchronized (PropertyViewer.this) {
                if (mSelectedNode != null && element instanceof String) {
                    String category = (String) element;
                    for (String name : mSelectedNode.viewNode.namedProperties.keySet()) {
                        if (category.equals(ViewNode.MISCELLANIOUS)) {
                            if (name.indexOf(':') == -1) {
                                return true;
                            }
                        } else {
                            if (name.startsWith(((String) element) + ":")) {
                                return true;
                            }
                        }
                    }
                }
                return false;
            }
        }

        @Override
        public Object[] getElements(Object inputElement) {
            synchronized (PropertyViewer.this) {
                if (mSelectedNode != null && inputElement instanceof TreeViewModel) {
                    if (mSelectedNode.viewNode.categories.size() == 0) {
                        return mSelectedNode.viewNode.properties
                                .toArray(new Property[mSelectedNode.viewNode.properties.size()]);
                    } else {
                        return mSelectedNode.viewNode.categories
                                .toArray(new String[mSelectedNode.viewNode.categories.size()]);
                    }
                }
                return new Object[0];
            }
        }

        @Override
        public void dispose() {
            // pass
        }

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            // pass
        }

        @Override
        public Image getColumnImage(Object element, int column) {
            return null;
        }

        @Override
        public String getColumnText(Object element, int column) {
            synchronized (PropertyViewer.this) {
                if (mSelectedNode != null) {
                    if (element instanceof String && column == 0) {
                        String category = (String) element;
                        return Character.toUpperCase(category.charAt(0)) + category.substring(1);
                    } else if (element instanceof Property) {
                        if (column == 0) {
                            String returnValue = ((Property) element).name;
                            int index = returnValue.indexOf(':');
                            if (index != -1) {
                                return returnValue.substring(index + 1);
                            }
                            return returnValue;
                        } else if (column == 1) {
                            return ((Property) element).value;
                        }
                    }
                }
                return "";
            }
        }

        @Override
        public void addListener(ILabelProviderListener listener) {
            // pass
        }

        @Override
        public boolean isLabelProperty(Object element, String property) {
            // pass
            return false;
        }

        @Override
        public void removeListener(ILabelProviderListener listener) {
            // pass
        }
    }

    public PropertyViewer(Composite parent) {
        super(parent, SWT.NONE);
        setLayout(new FillLayout());
        mTreeViewer = new TreeViewer(this, SWT.NONE);

        mTree = mTreeViewer.getTree();
        mTree.setLinesVisible(true);
        mTree.setHeaderVisible(true);

        TreeColumn propertyColumn = new TreeColumn(mTree, SWT.NONE);
        propertyColumn.setText("Property");
        TreeColumn valueColumn = new TreeColumn(mTree, SWT.NONE);
        valueColumn.setText("Value");

        mModel = TreeViewModel.getModel();
        ContentProvider contentProvider = new ContentProvider();
        mTreeViewer.setContentProvider(contentProvider);
        mTreeViewer.setLabelProvider(contentProvider);
        mTreeViewer.setInput(mModel);
        mModel.addTreeChangeListener(this);

        addDisposeListener(mDisposeListener);

        new TreeColumnResizer(this, propertyColumn, valueColumn);

        addControlListener(mControlListener);

        treeChanged();
    }

    private DisposeListener mDisposeListener = new DisposeListener() {
        @Override
        public void widgetDisposed(DisposeEvent e) {
            mModel.removeTreeChangeListener(PropertyViewer.this);
        }
    };

    // If the window gets too small, hide the data, otherwise SWT throws an
    // ERROR.

    private ControlListener mControlListener = new ControlAdapter() {
        private boolean noInput = false;

        private boolean noHeader = false;

        @Override
        public void controlResized(ControlEvent e) {
            if (getBounds().height <= 20) {
                mTree.setHeaderVisible(false);
                noHeader = true;
            } else if (noHeader) {
                mTree.setHeaderVisible(true);
                noHeader = false;
            }
            if (getBounds().height <= 38) {
                mTreeViewer.setInput(null);
                noInput = true;
            } else if (noInput) {
                mTreeViewer.setInput(mModel);
                noInput = false;
            }
        }
    };

    @Override
    public void selectionChanged() {
        synchronized (this) {
            mSelectedNode = mModel.getSelection();
        }
        doRefresh();
    }

    @Override
    public void treeChanged() {
        synchronized (this) {
            mSelectedNode = mModel.getSelection();
        }
        doRefresh();
    }

    @Override
    public void viewportChanged() {
        // pass
    }

    @Override
    public void zoomChanged() {
        // pass
    }

    private void doRefresh() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                mTreeViewer.refresh();
            }
        });
    }
} public class TreeView extends Canvas implements ITreeChangeListener {

    private TreeViewModel mModel;

    private DrawableViewNode mTree;

    private DrawableViewNode mSelectedNode;

    private Rectangle mViewport;

    private Transform mTransform;

    private Transform mInverse;

    private double mZoom;

    private Point mLastPoint;

    private boolean mAlreadySelectedOnMouseDown;

    private boolean mDoubleClicked;

    private boolean mNodeMoved;

    private DrawableViewNode mDraggedNode;

    public static final int LINE_PADDING = 10;

    public static final float BEZIER_FRACTION = 0.35f;

    private static Image sRedImage;

    private static Image sYellowImage;

    private static Image sGreenImage;

    private static Image sNotSelectedImage;

    private static Image sSelectedImage;

    private static Image sFilteredImage;

    private static Image sFilteredSelectedImage;

    private static Font sSystemFont;

    private Color mBoxColor;

    private Color mTextBackgroundColor;

    private Rectangle mSelectedRectangleLocation;

    private Point mButtonCenter;

    private static final int BUTTON_SIZE = 13;

    private Image mScaledSelectedImage;

    private boolean mButtonClicked;

    private DrawableViewNode mLastDrawnSelectedViewNode;

    // The profile-image box needs to be moved to,
    // so add some dragging leeway.
    private static final int DRAG_LEEWAY = 220;

    // Profile-image box constants
    private static final int RECT_WIDTH = 190;

    private static final int RECT_HEIGHT = 224;

    private static final int BUTTON_RIGHT_OFFSET = 5;

    private static final int BUTTON_TOP_OFFSET = 5;

    private static final int IMAGE_WIDTH = 125;

    private static final int IMAGE_HEIGHT = 120;

    private static final int IMAGE_OFFSET = 6;

    private static final int IMAGE_ROUNDING = 8;

    private static final int RECTANGLE_SIZE = 5;

    private static final int TEXT_SIDE_OFFSET = 8;

    private static final int TEXT_TOP_OFFSET = 4;

    private static final int TEXT_SPACING = 2;

    private static final int TEXT_ROUNDING = 20;

    public TreeView(Composite parent) {
        super(parent, SWT.NONE);

        mModel = TreeViewModel.getModel();
        mModel.addTreeChangeListener(this);

        addPaintListener(mPaintListener);
        addMouseListener(mMouseListener);
        addMouseMoveListener(mMouseMoveListener);
        addMouseWheelListener(mMouseWheelListener);
        addListener(SWT.Resize, mResizeListener);
        addDisposeListener(mDisposeListener);
        addKeyListener(mKeyListener);

        loadResources();

        mTransform = new Transform(Display.getDefault());
        mInverse = new Transform(Display.getDefault());

        loadAllData();
    }

    private void loadResources() {
        ImageLoader loader = ImageLoader.getLoader(this.getClass());
        sRedImage = loader.loadImage("red.png", Display.getDefault()); //$NON-NLS-1$
        sYellowImage = loader.loadImage("yellow.png", Display.getDefault()); //$NON-NLS-1$
        sGreenImage = loader.loadImage("green.png", Display.getDefault()); //$NON-NLS-1$
        sNotSelectedImage = loader.loadImage("not-selected.png", Display.getDefault()); //$NON-NLS-1$
        sSelectedImage = loader.loadImage("selected.png", Display.getDefault()); //$NON-NLS-1$
        sFilteredImage = loader.loadImage("filtered.png", Display.getDefault()); //$NON-NLS-1$
        sFilteredSelectedImage = loader.loadImage("selected-filtered.png", Display.getDefault()); //$NON-NLS-1$
        mBoxColor = new Color(Display.getDefault(), new RGB(225, 225, 225));
        mTextBackgroundColor = new Color(Display.getDefault(), new RGB(82, 82, 82));
        if (mScaledSelectedImage != null) {
            mScaledSelectedImage.dispose();
        }
        sSystemFont = Display.getDefault().getSystemFont();
    }

    private DisposeListener mDisposeListener = new DisposeListener() {
        @Override
        public void widgetDisposed(DisposeEvent e) {
            mModel.removeTreeChangeListener(TreeView.this);
            mTransform.dispose();
            mInverse.dispose();
            mBoxColor.dispose();
            mTextBackgroundColor.dispose();
            if (mTree != null) {
                mModel.setViewport(null);
            }
        }
    };

    private Listener mResizeListener = new Listener() {
        @Override
        public void handleEvent(Event e) {
            synchronized (TreeView.this) {
                if (mTree != null && mViewport != null) {

                    // Keep the center in the same place.
                    Point viewCenter =
                            new Point(mViewport.x + mViewport.width / 2, mViewport.y + mViewport.height
                                    / 2);
                    mViewport.width = getBounds().width / mZoom;
                    mViewport.height = getBounds().height / mZoom;
                    mViewport.x = viewCenter.x - mViewport.width / 2;
                    mViewport.y = viewCenter.y - mViewport.height / 2;
                }
            }
            if (mViewport != null) {
                mModel.setViewport(mViewport);
            }
        }
    };

    private KeyListener mKeyListener = new KeyListener() {

        @Override
        public void keyPressed(KeyEvent e) {
            boolean selectionChanged = false;
            DrawableViewNode clickedNode = null;
            synchronized (TreeView.this) {
                if (mTree != null && mViewport != null && mSelectedNode != null) {
                    switch (e.keyCode) {
                        case SWT.ARROW_LEFT:
                            if (mSelectedNode.parent != null) {
                                mSelectedNode = mSelectedNode.parent;
                                selectionChanged = true;
                            }
                            break;
                        case SWT.ARROW_UP:

                            // On up and down, it is cool to go up and down only
                            // the leaf nodes.
                            // It goes well with the layout viewer
                            DrawableViewNode currentNode = mSelectedNode;
                            while (currentNode.parent != null && currentNode.viewNode.index == 0) {
                                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);
                                }
                            }
                            if (selectionChanged) {
                                mSelectedNode = currentNode;
                            }
                            break;
                        case SWT.ARROW_DOWN:
                            currentNode = mSelectedNode;
                            while (currentNode.parent != null
                                    && currentNode.viewNode.index + 1 == currentNode.parent.children
                                            .size()) {
                                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);
                                }
                            }
                            if (selectionChanged) {
                                mSelectedNode = currentNode;
                            }
                            break;
                        case SWT.ARROW_RIGHT:
                            DrawableViewNode rightNode = null;
                            double mostOverlap = 0;
                            final int N = mSelectedNode.children.size();

                            // We consider all the children and pick the one
                            // who's tree overlaps the most.
                            for (int i = 0; i < N; i++) {
                                DrawableViewNode child = mSelectedNode.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(
                                                mSelectedNode.top + DrawableViewNode.NODE_HEIGHT
                                                        - topMostChild.top, topMostChild.top
                                                        + child.treeHeight - mSelectedNode.top));
                                if (overlap > mostOverlap) {
                                    mostOverlap = overlap;
                                    rightNode = child;
                                }
                            }
                            if (rightNode != null) {
                                mSelectedNode = rightNode;
                                selectionChanged = true;
                            }
                            break;
                        case SWT.CR:
                            clickedNode = mSelectedNode;
                            break;
                    }
                }
            }
            if (selectionChanged) {
                mModel.setSelection(mSelectedNode);
            }
            if (clickedNode != null) {
                HierarchyViewerDirector.getDirector().showCapture(getShell(), clickedNode.viewNode);
            }
        }

        @Override
        public void keyReleased(KeyEvent e) {
        }
    };

    private MouseListener mMouseListener = new MouseListener() {

        @Override
        public void mouseDoubleClick(MouseEvent e) {
            DrawableViewNode clickedNode = null;
            synchronized (TreeView.this) {
                if (mTree != null && mViewport != null) {
                    Point pt = transformPoint(e.x, e.y);
                    clickedNode = mTree.getSelected(pt.x, pt.y);
                }
            }
            if (clickedNode != null) {
                HierarchyViewerDirector.getDirector().showCapture(getShell(), clickedNode.viewNode);
                mDoubleClicked = true;
            }
        }

        @Override
        public void mouseDown(MouseEvent e) {
            boolean selectionChanged = false;
            synchronized (TreeView.this) {
                if (mTree != null && mViewport != null) {
                    Point pt = transformPoint(e.x, e.y);

                    // Ignore profiling rectangle, except for...
                    if (mSelectedRectangleLocation != null
                            && pt.x >= mSelectedRectangleLocation.x
                            && pt.x < mSelectedRectangleLocation.x
                                    + mSelectedRectangleLocation.width
                            && pt.y >= mSelectedRectangleLocation.y
                            && pt.y < mSelectedRectangleLocation.y
                                    + mSelectedRectangleLocation.height) {

                        // the small button!
                        if ((pt.x - mButtonCenter.x) * (pt.x - mButtonCenter.x)
                                + (pt.y - mButtonCenter.y) * (pt.y - mButtonCenter.y) <= (BUTTON_SIZE * BUTTON_SIZE) / 4) {
                            mButtonClicked = true;
                            doRedraw();
                        }
                        return;
                    }
                    mDraggedNode = mTree.getSelected(pt.x, pt.y);

                    // Update the selection.
                    if (mDraggedNode != null && mDraggedNode != mSelectedNode) {
                        mSelectedNode = mDraggedNode;
                        selectionChanged = true;
                        mAlreadySelectedOnMouseDown = false;
                    } else if (mDraggedNode != null) {
                        mAlreadySelectedOnMouseDown = true;
                    }

                    // Can't drag the root.
                    if (mDraggedNode == mTree) {
                        mDraggedNode = null;
                    }

                    if (mDraggedNode != null) {
                        mLastPoint = pt;
                    } else {
                        mLastPoint = new Point(e.x, e.y);
                    }
                    mNodeMoved = false;
                    mDoubleClicked = false;
                }
            }
            if (selectionChanged) {
                mModel.setSelection(mSelectedNode);
            }
        }

        @Override
        public void mouseUp(MouseEvent e) {
            boolean redraw = false;
            boolean redrawButton = false;
            boolean viewportChanged = false;
            boolean selectionChanged = false;
            synchronized (TreeView.this) {
                if (mTree != null && mViewport != null && mLastPoint != null) {
                    if (mDraggedNode == null) {
                        // The viewport moves.
                        handleMouseDrag(new Point(e.x, e.y));
                        viewportChanged = true;
                    } else {
                        // The nodes move.
                        handleMouseDrag(transformPoint(e.x, e.y));
                    }

                    // Deselect on the second click...
                    // This is in the mouse up, because mouse up happens after a
                    // double click event.
                    // During a double click, we don't want to deselect.
                    Point pt = transformPoint(e.x, e.y);
                    DrawableViewNode mouseUpOn = mTree.getSelected(pt.x, pt.y);
                    if (mouseUpOn != null && mouseUpOn == mSelectedNode
                            && mAlreadySelectedOnMouseDown && !mNodeMoved && !mDoubleClicked) {
                        mSelectedNode = null;
                        selectionChanged = true;
                    }
                    mLastPoint = null;
                    mDraggedNode = null;
                    redraw = true;
                }

                // Just clicked the button here.
                if (mButtonClicked) {
                    HierarchyViewerDirector.getDirector().showCapture(getShell(),
                            mSelectedNode.viewNode);
                    mButtonClicked = false;
                    redrawButton = true;
                }
            }

            // Complicated.
            if (viewportChanged) {
                mModel.setViewport(mViewport);
            } else if (redraw) {
                mModel.removeTreeChangeListener(TreeView.this);
                mModel.notifyViewportChanged();
                if (selectionChanged) {
                    mModel.setSelection(mSelectedNode);
                }
                mModel.addTreeChangeListener(TreeView.this);
                doRedraw();
            } else if (redrawButton) {
                doRedraw();
            }
        }

    };

    private MouseMoveListener mMouseMoveListener = new MouseMoveListener() {
        @Override
        public void mouseMove(MouseEvent e) {
            boolean redraw = false;
            boolean viewportChanged = false;
            synchronized (TreeView.this) {
                if (mTree != null && mViewport != null && mLastPoint != null) {
                    if (mDraggedNode == null) {
                        handleMouseDrag(new Point(e.x, e.y));
                        viewportChanged = true;
                    } else {
                        handleMouseDrag(transformPoint(e.x, e.y));
                    }
                    redraw = true;
                }
            }
            if (viewportChanged) {
                mModel.setViewport(mViewport);
            } else if (redraw) {
                mModel.removeTreeChangeListener(TreeView.this);
                mModel.notifyViewportChanged();
                mModel.addTreeChangeListener(TreeView.this);
                doRedraw();
            }
        }
    };

    private void handleMouseDrag(Point pt) {

        // Case 1: a node is dragged. DrawableViewNode knows how to handle this.
        if (mDraggedNode != null) {
            if (mLastPoint.y - pt.y != 0) {
                mNodeMoved = true;
            }
            mDraggedNode.move(mLastPoint.y - pt.y);
            mLastPoint = pt;
            return;
        }

        // Case 2: the viewport is dragged. We have to make sure we respect the
        // bounds - don't let the user drag way out... + some leeway for the
        // profiling box.
        double xDif = (mLastPoint.x - pt.x) / mZoom;
        double yDif = (mLastPoint.y - pt.y) / mZoom;

        double treeX = mTree.bounds.x - DRAG_LEEWAY;
        double treeY = mTree.bounds.y - DRAG_LEEWAY;
        double treeWidth = mTree.bounds.width + 2 * DRAG_LEEWAY;
        double treeHeight = mTree.bounds.height + 2 * DRAG_LEEWAY;

        if (mViewport.width > treeWidth) {
            if (xDif < 0 && mViewport.x + mViewport.width > treeX + treeWidth) {
                mViewport.x = Math.max(mViewport.x + xDif, treeX + treeWidth - mViewport.width);
            } else if (xDif > 0 && mViewport.x < treeX) {
                mViewport.x = Math.min(mViewport.x + xDif, treeX);
            }
        } else {
            if (xDif < 0 && mViewport.x > treeX) {
                mViewport.x = Math.max(mViewport.x + xDif, treeX);
            } else if (xDif > 0 && mViewport.x + mViewport.width < treeX + treeWidth) {
                mViewport.x = Math.min(mViewport.x + xDif, treeX + treeWidth - mViewport.width);
            }
        }
        if (mViewport.height > treeHeight) {
            if (yDif < 0 && mViewport.y + mViewport.height > treeY + treeHeight) {
                mViewport.y = Math.max(mViewport.y + yDif, treeY + treeHeight - mViewport.height);
            } else if (yDif > 0 && mViewport.y < treeY) {
                mViewport.y = Math.min(mViewport.y + yDif, treeY);
            }
        } else {
            if (yDif < 0 && mViewport.y > treeY) {
                mViewport.y = Math.max(mViewport.y + yDif, treeY);
            } else if (yDif > 0 && mViewport.y + mViewport.height < treeY + treeHeight) {
                mViewport.y = Math.min(mViewport.y + yDif, treeY + treeHeight - mViewport.height);
            }
        }
        mLastPoint = pt;
    }

    private Point transformPoint(double x, double y) {
        float[] pt = {
                (float) x, (float) y
        };
        mInverse.transform(pt);
        return new Point(pt[0], pt[1]);
    }

    private MouseWheelListener mMouseWheelListener = new MouseWheelListener() {
        @Override
        public void mouseScrolled(MouseEvent e) {
            Point zoomPoint = null;
            synchronized (TreeView.this) {
                if (mTree != null && mViewport != null) {
                    mZoom += Math.ceil(e.count / 3.0) * 0.1;
                    zoomPoint = transformPoint(e.x, e.y);
                }
            }
            if (zoomPoint != null) {
                mModel.zoomOnPoint(mZoom, zoomPoint);
            }
        }
    };

    private PaintListener mPaintListener = new PaintListener() {
        @Override
        public void paintControl(PaintEvent e) {
            synchronized (TreeView.this) {
                e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height);
                if (mTree != null && mViewport != null) {

                    // Easy stuff!
                    e.gc.setTransform(mTransform);
                    e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
                    Path connectionPath = new Path(Display.getDefault());
                    paintRecursive(e.gc, mTransform, mTree, mSelectedNode, connectionPath);
                    e.gc.drawPath(connectionPath);
                    connectionPath.dispose();

                    // Draw the profiling box.
                    if (mSelectedNode != null) {

                        e.gc.setAlpha(200);

                        // Draw the little triangle
                        int x = mSelectedNode.left + DrawableViewNode.NODE_WIDTH / 2;
                        int y = (int) mSelectedNode.top + 4;
                        e.gc.setBackground(mBoxColor);
                        e.gc.fillPolygon(new int[] {
                                x, y, x - 11, y - 11, x + 11, y - 11
                        });

                        // Draw the rectangle and update the location.
                        y -= 10 + RECT_HEIGHT;
                        e.gc.fillRoundRectangle(x - RECT_WIDTH / 2, y, RECT_WIDTH, RECT_HEIGHT, 30,
                                30);
                        mSelectedRectangleLocation =
                                new Rectangle(x - RECT_WIDTH / 2, y, RECT_WIDTH, RECT_HEIGHT);

                        e.gc.setAlpha(255);

                        // Draw the button
                        mButtonCenter =
                                new Point(x - BUTTON_RIGHT_OFFSET + (RECT_WIDTH - BUTTON_SIZE) / 2,
                                        y + BUTTON_TOP_OFFSET + BUTTON_SIZE / 2);

                        if (mButtonClicked) {
                            e.gc
                                    .setBackground(Display.getDefault().getSystemColor(
                                            SWT.COLOR_BLACK));
                        } else {
                            e.gc.setBackground(mTextBackgroundColor);

                        }
                        e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));

                        e.gc.fillOval(x + RECT_WIDTH / 2 - BUTTON_RIGHT_OFFSET - BUTTON_SIZE, y
                                + BUTTON_TOP_OFFSET, BUTTON_SIZE, BUTTON_SIZE);

                        e.gc.drawRectangle(x - BUTTON_RIGHT_OFFSET
                                + (RECT_WIDTH - BUTTON_SIZE - RECTANGLE_SIZE) / 2 - 1, y
                                + BUTTON_TOP_OFFSET + (BUTTON_SIZE - RECTANGLE_SIZE) / 2,
                                RECTANGLE_SIZE + 1, RECTANGLE_SIZE);

                        y += 15;

                        // If there is an image, draw it.
                        if (mSelectedNode.viewNode.image != null
                                && mSelectedNode.viewNode.image.getBounds().height != 1
                                && mSelectedNode.viewNode.image.getBounds().width != 1) {

                            // Scaling the image to the right size takes lots of
                            // time, so we want to do it only once.

                            // If the selection changed, get rid of the old
                            // image.
                            if (mLastDrawnSelectedViewNode != mSelectedNode) {
                                if (mScaledSelectedImage != null) {
                                    mScaledSelectedImage.dispose();
                                    mScaledSelectedImage = null;
                                }
                                mLastDrawnSelectedViewNode = mSelectedNode;
                            }

                            if (mScaledSelectedImage == null) {
                                double ratio =
                                        1.0 * mSelectedNode.viewNode.image.getBounds().width
                                                / mSelectedNode.viewNode.image.getBounds().height;
                                int newWidth, newHeight;
                                if (ratio > 1.0 * IMAGE_WIDTH / IMAGE_HEIGHT) {
                                    newWidth =
                                            Math.min(IMAGE_WIDTH, mSelectedNode.viewNode.image
                                                    .getBounds().width);
                                    newHeight = (int) (newWidth / ratio);
                                } else {
                                    newHeight =
                                            Math.min(IMAGE_HEIGHT, mSelectedNode.viewNode.image
                                                    .getBounds().height);
                                    newWidth = (int) (newHeight * ratio);
                                }

                                // Interesting note... We make the image twice
                                // the needed size so that there is better
                                // resolution under zoom.
                                newWidth = Math.max(newWidth * 2, 1);
                                newHeight = Math.max(newHeight * 2, 1);
                                mScaledSelectedImage =
                                        new Image(Display.getDefault(), newWidth, newHeight);
                                GC gc = new GC(mScaledSelectedImage);
                                gc.setBackground(mTextBackgroundColor);
                                gc.fillRectangle(0, 0, newWidth, newHeight);
                                gc.drawImage(mSelectedNode.viewNode.image, 0, 0,
                                        mSelectedNode.viewNode.image.getBounds().width,
                                        mSelectedNode.viewNode.image.getBounds().height, 0, 0,
                                        newWidth, newHeight);
                                gc.dispose();
                            }

                            // Draw the background rectangle
                            e.gc.setBackground(mTextBackgroundColor);
                            e.gc.fillRoundRectangle(x - mScaledSelectedImage.getBounds().width / 4
                                    - IMAGE_OFFSET, y
                                    + (IMAGE_HEIGHT - mScaledSelectedImage.getBounds().height / 2)
                                    / 2 - IMAGE_OFFSET, mScaledSelectedImage.getBounds().width / 2
                                    + 2 * IMAGE_OFFSET, mScaledSelectedImage.getBounds().height / 2
                                    + 2 * IMAGE_OFFSET, IMAGE_ROUNDING, IMAGE_ROUNDING);

                            // Under max zoom, we want the image to be
                            // untransformed. So, get back to the identity
                            // transform.
                            int imageX = x - mScaledSelectedImage.getBounds().width / 4;
                            int imageY =
                                    y
                                            + (IMAGE_HEIGHT - mScaledSelectedImage.getBounds().height / 2)
                                            / 2;

                            Transform untransformedTransform = new Transform(Display.getDefault());
                            e.gc.setTransform(untransformedTransform);
                            float[] pt = new float[] {
                                    imageX, imageY
                            };
                            mTransform.transform(pt);
                            e.gc.drawImage(mScaledSelectedImage, 0, 0, mScaledSelectedImage
                                    .getBounds().width, mScaledSelectedImage.getBounds().height,
                                    (int) pt[0], (int) pt[1], (int) (mScaledSelectedImage
                                            .getBounds().width
                                            * mZoom / 2),
                                    (int) (mScaledSelectedImage.getBounds().height * mZoom / 2));
                            untransformedTransform.dispose();
                            e.gc.setTransform(mTransform);
                        }

                        // Text stuff

                        y += IMAGE_HEIGHT;
                        y += 10;
                        Font font = getFont(8, false);
                        e.gc.setFont(font);

                        String text =
                                mSelectedNode.viewNode.viewCount + " view"
                                        + (mSelectedNode.viewNode.viewCount != 1 ? "s" : "");
                        DecimalFormat formatter = new DecimalFormat("0.000");

                        String measureText =
                                "Measure: "
                                        + (mSelectedNode.viewNode.measureTime != -1 ? formatter
                                                .format(mSelectedNode.viewNode.measureTime)
                                                + " ms" : "n/a");
                        String layoutText =
                                "Layout: "
                                        + (mSelectedNode.viewNode.layoutTime != -1 ? formatter
                                                .format(mSelectedNode.viewNode.layoutTime)
                                                + " ms" : "n/a");
                        String drawText =
                                "Draw: "
                                        + (mSelectedNode.viewNode.drawTime != -1 ? formatter
                                                .format(mSelectedNode.viewNode.drawTime)
                                                + " ms" : "n/a");

                        org.eclipse.swt.graphics.Point titleExtent = e.gc.stringExtent(text);
                        org.eclipse.swt.graphics.Point measureExtent =
                                e.gc.stringExtent(measureText);
                        org.eclipse.swt.graphics.Point layoutExtent = e.gc.stringExtent(layoutText);
                        org.eclipse.swt.graphics.Point drawExtent = e.gc.stringExtent(drawText);
                        int boxWidth =
                                Math.max(titleExtent.x, Math.max(measureExtent.x, Math.max(
                                        layoutExtent.x, drawExtent.x)))
                                        + 2 * TEXT_SIDE_OFFSET;
                        int boxHeight =
                                titleExtent.y + TEXT_SPACING + measureExtent.y + TEXT_SPACING
                                        + layoutExtent.y + TEXT_SPACING + drawExtent.y + 2
                                        * TEXT_TOP_OFFSET;

                        e.gc.setBackground(mTextBackgroundColor);
                        e.gc.fillRoundRectangle(x - boxWidth / 2, y, boxWidth, boxHeight,
                                TEXT_ROUNDING, TEXT_ROUNDING);

                        e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));

                        y += TEXT_TOP_OFFSET;

                        e.gc.drawText(text, x - titleExtent.x / 2, y, true);

                        x -= boxWidth / 2;
                        x += TEXT_SIDE_OFFSET;

                        y += titleExtent.y + TEXT_SPACING;

                        e.gc.drawText(measureText, x, y, true);

                        y += measureExtent.y + TEXT_SPACING;

                        e.gc.drawText(layoutText, x, y, true);

                        y += layoutExtent.y + TEXT_SPACING;

                        e.gc.drawText(drawText, x, y, true);

                        font.dispose();
                    } else {
                        mSelectedRectangleLocation = null;
                        mButtonCenter = null;
                    }
                }
            }
        }
    };

    private static void paintRecursive(GC gc, Transform transform, DrawableViewNode node,
            DrawableViewNode selectedNode, Path connectionPath) {
        if (selectedNode == node && node.viewNode.filtered) {
            gc.drawImage(sFilteredSelectedImage, node.left, (int) Math.round(node.top));
        } else if (selectedNode == node) {
            gc.drawImage(sSelectedImage, node.left, (int) Math.round(node.top));
        } else if (node.viewNode.filtered) {
            gc.drawImage(sFilteredImage, node.left, (int) Math.round(node.top));
        } else {
            gc.drawImage(sNotSelectedImage, node.left, (int) Math.round(node.top));
        }

        int 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, transform, name, x, y, contentWidth, fontHeight, 10, true);

        y += fontHeight + DrawableViewNode.CONTENT_INTER_PADDING;

        drawTextInArea(gc, transform, "@" + node.viewNode.hashCode, x, y, contentWidth, fontHeight,
                8, false);

        y += fontHeight + DrawableViewNode.CONTENT_INTER_PADDING;
        if (!node.viewNode.id.equals("NO_ID")) {
            drawTextInArea(gc, transform, node.viewNode.id, x, y, contentWidth, fontHeight, 8,
                    false);
        }

        if (node.viewNode.measureRating != ProfileRating.NONE) {
            y =
                    node.top + DrawableViewNode.NODE_HEIGHT
                            - DrawableViewNode.CONTENT_TOP_BOTTOM_PADDING
                            - sRedImage.getBounds().height;
            x +=
                    (contentWidth - (sRedImage.getBounds().width * 3 + 2 * DrawableViewNode.CONTENT_INTER_PADDING)) / 2;
            switch (node.viewNode.measureRating) {
                case GREEN:
                    gc.drawImage(sGreenImage, (int) x, (int) y);
                    break;
                case YELLOW:
                    gc.drawImage(sYellowImage, (int) x, (int) y);
                    break;
                case RED:
                    gc.drawImage(sRedImage, (int) x, (int) y);
                    break;
            }

            x += sRedImage.getBounds().width + DrawableViewNode.CONTENT_INTER_PADDING;
            switch (node.viewNode.layoutRating) {
                case GREEN:
                    gc.drawImage(sGreenImage, (int) x, (int) y);
                    break;
                case YELLOW:
                    gc.drawImage(sYellowImage, (int) x, (int) y);
                    break;
                case RED:
                    gc.drawImage(sRedImage, (int) x, (int) y);
                    break;
            }

            x += sRedImage.getBounds().width + DrawableViewNode.CONTENT_INTER_PADDING;
            switch (node.viewNode.drawRating) {
                case GREEN:
                    gc.drawImage(sGreenImage, (int) x, (int) y);
                    break;
                case YELLOW:
                    gc.drawImage(sYellowImage, (int) x, (int) y);
                    break;
                case RED:
                    gc.drawImage(sRedImage, (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, transform, child, selectedNode, 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 static void drawTextInArea(GC gc, Transform transform, String text, double x, double y,
            double width, double height, int fontSize, boolean bold) {

        Font oldFont = gc.getFont();

        Font newFont = getFont(fontSize, bold);
        gc.setFont(newFont);

        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 + (width - extent.x) / 2),
                    (int) (y + (height - extent.y) / 2), SWT.DRAW_TRANSPARENT);
        }
        gc.setFont(oldFont);
        newFont.dispose();

    }

    public static Image paintToImage(DrawableViewNode tree) {
        Image image =
                new Image(Display.getDefault(), (int) Math.ceil(tree.bounds.width), (int) Math
                        .ceil(tree.bounds.height));

        Transform transform = new Transform(Display.getDefault());
        transform.identity();
        transform.translate((float) -tree.bounds.x, (float) -tree.bounds.y);
        Path connectionPath = new Path(Display.getDefault());
        GC gc = new GC(image);

        // Can't use Display.getDefault().getSystemColor in a non-UI thread.
        Color white = new Color(Display.getDefault(), 255, 255, 255);
        Color black = new Color(Display.getDefault(), 0, 0, 0);
        gc.setForeground(white);
        gc.setBackground(black);
        gc.fillRectangle(0, 0, image.getBounds().width, image.getBounds().height);
        gc.setTransform(transform);
        paintRecursive(gc, transform, tree, null, connectionPath);
        gc.drawPath(connectionPath);
        gc.dispose();
        connectionPath.dispose();
        white.dispose();
        black.dispose();
        return image;
    }

    private static Font getFont(int size, boolean bold) {
        FontData[] fontData = sSystemFont.getFontData();
        for (int i = 0; i < fontData.length; i++) {
            fontData[i].setHeight(size);
            if (bold) {
                fontData[i].setStyle(SWT.BOLD);
            }
        }
        return new Font(Display.getDefault(), fontData);
    }

    private void doRedraw() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                redraw();
            }
        });
    }

    public void loadAllData() {
        boolean newViewport = mViewport == null;
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    mTree = mModel.getTree();
                    mSelectedNode = mModel.getSelection();
                    mViewport = mModel.getViewport();
                    mZoom = mModel.getZoom();
                    if (mTree != null && mViewport == null) {
                        mViewport =
                                new Rectangle(0, mTree.top + DrawableViewNode.NODE_HEIGHT / 2
                                        - getBounds().height / 2, getBounds().width,
                                        getBounds().height);
                    } else {
                        setTransform();
                    }
                }
            }
        });
        if (newViewport) {
            mModel.setViewport(mViewport);
        }
    }

    // Fickle behaviour... When a new tree is loaded, the model doesn't know
    // about the viewport until it passes through here.
    @Override
    public void treeChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    mTree = mModel.getTree();
                    mSelectedNode = mModel.getSelection();
                    if (mTree == null) {
                        mViewport = null;
                    } else {
                        mViewport =
                                new Rectangle(0, mTree.top + DrawableViewNode.NODE_HEIGHT / 2
                                        - getBounds().height / 2, getBounds().width,
                                        getBounds().height);
                    }
                }
            }
        });
        if (mViewport != null) {
            mModel.setViewport(mViewport);
        } else {
            doRedraw();
        }
    }

    private void setTransform() {
        if (mViewport != null && mTree != null) {
            // Set the transform.
            mTransform.identity();
            mInverse.identity();

            mTransform.scale((float) mZoom, (float) mZoom);
            mInverse.scale((float) mZoom, (float) mZoom);
            mTransform.translate((float) -mViewport.x, (float) -mViewport.y);
            mInverse.translate((float) -mViewport.x, (float) -mViewport.y);
            mInverse.invert();
        }
    }

    // Note the syncExec and then synchronized... It avoids deadlock
    @Override
    public void viewportChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    mViewport = mModel.getViewport();
                    mZoom = mModel.getZoom();
                    setTransform();
                }
            }
        });
        doRedraw();
    }

    @Override
    public void zoomChanged() {
        viewportChanged();
    }

    @Override
    public void selectionChanged() {
        synchronized (this) {
            mSelectedNode = mModel.getSelection();
            if (mSelectedNode != null && mSelectedNode.viewNode.image == null) {
                HierarchyViewerDirector.getDirector()
                        .loadCaptureInBackground(mSelectedNode.viewNode);
            }
        }
        doRedraw();
    }
} public class TreeViewControls extends Composite implements ITreeChangeListener {

    private Text mFilterText;

    private Slider mZoomSlider;

    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));

        mFilterText = new Text(this, SWT.LEFT | SWT.SINGLE);
        mFilterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        mFilterText.addModifyListener(mFilterTextModifyListener);
        mFilterText.setText(HierarchyViewerDirector.getDirector().getFilterText());

        Label smallZoomLabel = new Label(this, SWT.NONE);
        smallZoomLabel.setText(" 20%");
        smallZoomLabel
                .setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, true));

        mZoomSlider = new Slider(this, SWT.HORIZONTAL);
        GridData zoomSliderGridData = new GridData(GridData.CENTER, GridData.CENTER, false, false);
        zoomSliderGridData.widthHint = 190;
        mZoomSlider.setLayoutData(zoomSliderGridData);
        mZoomSlider.setMinimum((int) (TreeViewModel.MIN_ZOOM * 10));
        mZoomSlider.setMaximum((int) (TreeViewModel.MAX_ZOOM * 10 + 1));
        mZoomSlider.setThumb(1);
        mZoomSlider.setSelection((int) Math.round(TreeViewModel.getModel().getZoom() * 10));

        mZoomSlider.addSelectionListener(mZoomSliderSelectionListener);

        Label largeZoomLabel = new Label(this, SWT.NONE);
        largeZoomLabel
                .setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, true));
        largeZoomLabel.setText("200%");

        addDisposeListener(mDisposeListener);

        TreeViewModel.getModel().addTreeChangeListener(this);
    }

    private DisposeListener mDisposeListener = new DisposeListener() {
        @Override
        public void widgetDisposed(DisposeEvent e) {
            TreeViewModel.getModel().removeTreeChangeListener(TreeViewControls.this);
        }
    };

    private SelectionListener mZoomSliderSelectionListener = new SelectionListener() {
        private int oldValue;

        @Override
        public void widgetDefaultSelected(SelectionEvent e) {
            // pass
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            int newValue = mZoomSlider.getSelection();
            if (oldValue != newValue) {
                TreeViewModel.getModel().removeTreeChangeListener(TreeViewControls.this);
                TreeViewModel.getModel().setZoom(newValue / 10.0);
                TreeViewModel.getModel().addTreeChangeListener(TreeViewControls.this);
                oldValue = newValue;
            }
        }
    };

    private ModifyListener mFilterTextModifyListener = new ModifyListener() {
        @Override
        public void modifyText(ModifyEvent e) {
            HierarchyViewerDirector.getDirector().filterNodes(mFilterText.getText());
        }
    };

    @Override
    public void selectionChanged() {
        // pass
    }

    @Override
    public void treeChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                if (TreeViewModel.getModel().getTree() != null) {
                    mZoomSlider.setSelection((int) Math
                            .round(TreeViewModel.getModel().getZoom() * 10));
                }
                mFilterText.setText(""); //$NON-NLS-1$
            }
        });
    }

    @Override
    public void viewportChanged() {
        // pass
    }

    @Override
    public void zoomChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                mZoomSlider.setSelection((int) Math.round(TreeViewModel.getModel().getZoom() * 10));
            }
        });
    };
} public class TreeViewOverview extends Canvas implements ITreeChangeListener {

    private TreeViewModel mModel;

    private DrawableViewNode mTree;

    private Rectangle mViewport;

    private Transform mTransform;

    private Transform mInverse;

    private Rectangle mBounds = new Rectangle();

    private double mScale;

    private boolean mDragging = false;

    private DrawableViewNode mSelectedNode;

    private static Image sNotSelectedImage;

    private static Image sSelectedImage;

    private static Image sFilteredImage;

    private static Image sFilteredSelectedImage;

    public TreeViewOverview(Composite parent) {
        super(parent, SWT.NONE);

        mModel = TreeViewModel.getModel();
        mModel.addTreeChangeListener(this);

        loadResources();

        addPaintListener(mPaintListener);
        addMouseListener(mMouseListener);
        addMouseMoveListener(mMouseMoveListener);
        addListener(SWT.Resize, mResizeListener);
        addDisposeListener(mDisposeListener);

        mTransform = new Transform(Display.getDefault());
        mInverse = new Transform(Display.getDefault());

        loadAllData();
    }

    private void loadResources() {
        ImageLoader loader = ImageLoader.getLoader(this.getClass());
        sNotSelectedImage = loader.loadImage("not-selected.png", Display.getDefault()); //$NON-NLS-1$
        sSelectedImage = loader.loadImage("selected-small.png", Display.getDefault()); //$NON-NLS-1$
        sFilteredImage = loader.loadImage("filtered.png", Display.getDefault()); //$NON-NLS-1$
        sFilteredSelectedImage =
                loader.loadImage("selected-filtered-small.png", Display.getDefault()); //$NON-NLS-1$
    }

    private DisposeListener mDisposeListener = new DisposeListener() {
        @Override
        public void widgetDisposed(DisposeEvent e) {
            mModel.removeTreeChangeListener(TreeViewOverview.this);
            mTransform.dispose();
            mInverse.dispose();
        }
    };

    private MouseListener mMouseListener = new MouseListener() {

        @Override
        public void mouseDoubleClick(MouseEvent e) {
            // pass
        }

        @Override
        public void mouseDown(MouseEvent e) {
            boolean redraw = false;
            synchronized (TreeViewOverview.this) {
                if (mTree != null && mViewport != null) {
                    mDragging = true;
                    redraw = true;
                    handleMouseEvent(transformPoint(e.x, e.y));
                }
            }
            if (redraw) {
                mModel.removeTreeChangeListener(TreeViewOverview.this);
                mModel.setViewport(mViewport);
                mModel.addTreeChangeListener(TreeViewOverview.this);
                doRedraw();
            }
        }

        @Override
        public void mouseUp(MouseEvent e) {
            boolean redraw = false;
            synchronized (TreeViewOverview.this) {
                if (mTree != null && mViewport != null) {
                    mDragging = false;
                    redraw = true;
                    handleMouseEvent(transformPoint(e.x, e.y));

                    // Update bounds and transform only on mouse up. That way,
                    // you don't get confusing behaviour during mouse drag and
                    // it snaps neatly at the end
                    setBounds();
                    setTransform();
                }
            }
            if (redraw) {
                mModel.removeTreeChangeListener(TreeViewOverview.this);
                mModel.setViewport(mViewport);
                mModel.addTreeChangeListener(TreeViewOverview.this);
                doRedraw();
            }
        }

    };

    private MouseMoveListener mMouseMoveListener = new MouseMoveListener() {
        @Override
        public void mouseMove(MouseEvent e) {
            boolean moved = false;
            synchronized (TreeViewOverview.this) {
                if (mDragging) {
                    moved = true;
                    handleMouseEvent(transformPoint(e.x, e.y));
                }
            }
            if (moved) {
                mModel.removeTreeChangeListener(TreeViewOverview.this);
                mModel.setViewport(mViewport);
                mModel.addTreeChangeListener(TreeViewOverview.this);
                doRedraw();
            }
        }
    };

    private void handleMouseEvent(Point pt) {
        mViewport.x = pt.x - mViewport.width / 2;
        mViewport.y = pt.y - mViewport.height / 2;
        if (mViewport.x < mBounds.x) {
            mViewport.x = mBounds.x;
        }
        if (mViewport.y < mBounds.y) {
            mViewport.y = mBounds.y;
        }
        if (mViewport.x + mViewport.width > mBounds.x + mBounds.width) {
            mViewport.x = mBounds.x + mBounds.width - mViewport.width;
        }
        if (mViewport.y + mViewport.height > mBounds.y + mBounds.height) {
            mViewport.y = mBounds.y + mBounds.height - mViewport.height;
        }
    }

    private Point transformPoint(double x, double y) {
        float[] pt = {
                (float) x, (float) y
        };
        mInverse.transform(pt);
        return new Point(pt[0], pt[1]);
    }

    private Listener mResizeListener = new Listener() {
        @Override
        public void handleEvent(Event arg0) {
            synchronized (TreeViewOverview.this) {
                setTransform();
            }
            doRedraw();
        }
    };

    private PaintListener mPaintListener = new PaintListener() {
        @Override
        public void paintControl(PaintEvent e) {
            synchronized (TreeViewOverview.this) {
                if (mTree != null) {
                    e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                    e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
                    e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height);
                    e.gc.setTransform(mTransform);
                    e.gc.setLineWidth((int) Math.ceil(0.7 / mScale));
                    Path connectionPath = new Path(Display.getDefault());
                    paintRecursive(e.gc, mTree, connectionPath);
                    e.gc.drawPath(connectionPath);
                    connectionPath.dispose();

                    if (mViewport != null) {
                        e.gc.setAlpha(50);
                        e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
                        e.gc.fillRectangle((int) mViewport.x, (int) mViewport.y, (int) Math
                                .ceil(mViewport.width), (int) Math.ceil(mViewport.height));

                        e.gc.setAlpha(255);
                        e.gc
                                .setForeground(Display.getDefault().getSystemColor(
                                        SWT.COLOR_DARK_GRAY));
                        e.gc.setLineWidth((int) Math.ceil(2 / mScale));
                        e.gc.drawRectangle((int) mViewport.x, (int) mViewport.y, (int) Math
                                .ceil(mViewport.width), (int) Math.ceil(mViewport.height));
                    }
                }
            }
        }
    };

    private void paintRecursive(GC gc, DrawableViewNode node, Path connectionPath) {
        if (mSelectedNode == node && node.viewNode.filtered) {
            gc.drawImage(sFilteredSelectedImage, node.left, (int) Math.round(node.top));
        } else if (mSelectedNode == node) {
            gc.drawImage(sSelectedImage, node.left, (int) Math.round(node.top));
        } else if (node.viewNode.filtered) {
            gc.drawImage(sFilteredImage, node.left, (int) Math.round(node.top));
        } else {
            gc.drawImage(sNotSelectedImage, node.left, (int) Math.round(node.top));
        }
        int N = node.children.size();
        if (N == 0) {
            return;
        }
        float childSpacing =
                (1.0f * (DrawableViewNode.NODE_HEIGHT - 2 * TreeView.LINE_PADDING)) / N;
        for (int i = 0; i < N; i++) {
            DrawableViewNode child = node.children.get(i);
            paintRecursive(gc, child, connectionPath);
            float x1 = node.left + DrawableViewNode.NODE_WIDTH;
            float y1 =
                    (float) node.top + TreeView.LINE_PADDING + childSpacing * i + childSpacing / 2;
            float x2 = child.left;
            float y2 = (float) child.top + DrawableViewNode.NODE_HEIGHT / 2.0f;
            float cx1 = x1 + TreeView.BEZIER_FRACTION * DrawableViewNode.PARENT_CHILD_SPACING;
            float cy1 = y1;
            float cx2 = x2 - TreeView.BEZIER_FRACTION * DrawableViewNode.PARENT_CHILD_SPACING;
            float cy2 = y2;
            connectionPath.moveTo(x1, y1);
            connectionPath.cubicTo(cx1, cy1, cx2, cy2, x2, y2);
        }
    }

    private void doRedraw() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                redraw();
            }
        });
    }

    public void loadAllData() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    mTree = mModel.getTree();
                    mSelectedNode = mModel.getSelection();
                    mViewport = mModel.getViewport();
                    setBounds();
                    setTransform();
                }
            }
        });
    }

    // Note the syncExec and then synchronized... It avoids deadlock
    @Override
    public void treeChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    mTree = mModel.getTree();
                    mSelectedNode = mModel.getSelection();
                    mViewport = mModel.getViewport();
                    setBounds();
                    setTransform();
                }
            }
        });
        doRedraw();
    }

    private void setBounds() {
        if (mViewport != null && mTree != null) {
            mBounds.x = Math.min(mViewport.x, mTree.bounds.x);
            mBounds.y = Math.min(mViewport.y, mTree.bounds.y);
            mBounds.width =
                    Math.max(mViewport.x + mViewport.width, mTree.bounds.x + mTree.bounds.width)
                            - mBounds.x;
            mBounds.height =
                    Math.max(mViewport.y + mViewport.height, mTree.bounds.y + mTree.bounds.height)
                            - mBounds.y;
        } else if (mTree != null) {
            mBounds.x = mTree.bounds.x;
            mBounds.y = mTree.bounds.y;
            mBounds.width = mTree.bounds.x + mTree.bounds.width - mBounds.x;
            mBounds.height = mTree.bounds.y + mTree.bounds.height - mBounds.y;
        }
    }

    private void setTransform() {
        if (mTree != null) {

            mTransform.identity();
            mInverse.identity();
            final Point size = new Point();
            size.x = getBounds().width;
            size.y = getBounds().height;
            if (mBounds.width == 0 || mBounds.height == 0 || size.x == 0 || size.y == 0) {
                mScale = 1;
            } else {
                mScale = Math.min(size.x / mBounds.width, size.y / mBounds.height);
            }
            mTransform.scale((float) mScale, (float) mScale);
            mInverse.scale((float) mScale, (float) mScale);
            mTransform.translate((float) -mBounds.x, (float) -mBounds.y);
            mInverse.translate((float) -mBounds.x, (float) -mBounds.y);
            if (size.x / mBounds.width < size.y / mBounds.height) {
                mTransform.translate(0, (float) (size.y / mScale - mBounds.height) / 2);
                mInverse.translate(0, (float) (size.y / mScale - mBounds.height) / 2);
            } else {
                mTransform.translate((float) (size.x / mScale - mBounds.width) / 2, 0);
                mInverse.translate((float) (size.x / mScale - mBounds.width) / 2, 0);
            }
            mInverse.invert();
        }
    }

    @Override
    public void viewportChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    mViewport = mModel.getViewport();
                    setBounds();
                    setTransform();
                }
            }
        });
        doRedraw();
    }

    @Override
    public void zoomChanged() {
        viewportChanged();
    }

    @Override
    public void selectionChanged() {
        synchronized (this) {
            mSelectedNode = mModel.getSelection();
        }
        doRedraw();
    }
}
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
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.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import com.android.ddmlib.Log;
import com.android.ddmuilib.ImageLoader;
import com.android.hierarchyviewer.actions.LoadAllViewsAction;
import com.android.hierarchyviewer.actions.ShowOverlayAction;
import com.android.hierarchyviewer.util.ActionButton;
import com.android.hierarchyviewerlib.HierarchyViewerDirector;
import com.android.hierarchyviewerlib.actions.CapturePSDAction;
import com.android.hierarchyviewerlib.actions.DisplayViewAction;
import com.android.hierarchyviewerlib.actions.DumpDisplayListAction;
import com.android.hierarchyviewerlib.actions.InspectScreenshotAction;
import com.android.hierarchyviewerlib.actions.InvalidateAction;
import com.android.hierarchyviewerlib.actions.LoadOverlayAction;
import com.android.hierarchyviewerlib.actions.LoadViewHierarchyAction;
import com.android.hierarchyviewerlib.actions.PixelPerfectAutoRefreshAction;
import com.android.hierarchyviewerlib.actions.RefreshPixelPerfectAction;
import com.android.hierarchyviewerlib.actions.RefreshPixelPerfectTreeAction;
import com.android.hierarchyviewerlib.actions.RefreshViewAction;
import com.android.hierarchyviewerlib.actions.RefreshWindowsAction;
import com.android.hierarchyviewerlib.actions.RequestLayoutAction;
import com.android.hierarchyviewerlib.actions.SavePixelPerfectAction;
import com.android.hierarchyviewerlib.actions.SaveTreeViewAction;
import com.android.hierarchyviewerlib.device.DeviceBridge.ViewServerInfo;
import com.android.hierarchyviewerlib.models.DeviceSelectionModel;
import com.android.hierarchyviewerlib.models.PixelPerfectModel;
import com.android.hierarchyviewerlib.models.PixelPerfectModel.IImageChangeListener;
import com.android.hierarchyviewerlib.models.TreeViewModel;
import com.android.hierarchyviewerlib.models.TreeViewModel.ITreeChangeListener;
import com.android.hierarchyviewerlib.ui.DeviceSelector;
import com.android.hierarchyviewerlib.ui.LayoutViewer;
import com.android.hierarchyviewerlib.ui.PixelPerfect;
import com.android.hierarchyviewerlib.ui.PixelPerfectControls;
import com.android.hierarchyviewerlib.ui.PixelPerfectLoupe;
import com.android.hierarchyviewerlib.ui.PixelPerfectPixelPanel;
import com.android.hierarchyviewerlib.ui.PixelPerfectTree;
import com.android.hierarchyviewerlib.ui.PropertyViewer;
import com.android.hierarchyviewerlib.ui.TreeView;
import com.android.hierarchyviewerlib.ui.TreeViewControls;
import com.android.hierarchyviewerlib.ui.TreeViewOverview;
        mDirector = HierarchyViewerApplicationDirector.createDirector();
        mDirector.initDebugBridge();
        mDirector.startListenForDevices();
        mDirector.populateDeviceSelectionModel();

        TreeViewModel.getModel().addTreeChangeListener(mTreeChangeListener);
        PixelPerfectModel.getModel().addImageChangeListener(mImageChangeListener);

        loadResources();

        Composite control = new Composite(parent, SWT.NONE);
        GridLayout mainLayout = new GridLayout();
        mainLayout.marginHeight = mainLayout.marginWidth = 0;
        mainLayout.verticalSpacing = mainLayout.horizontalSpacing = 0;
        control.setLayout(mainLayout);
        mMainWindow = new Composite(control, SWT.NONE);
        mMainWindow.setLayoutData(new GridData(GridData.FILL_BOTH));
        mMainWindowStackLayout = new StackLayout();
        mMainWindow.setLayout(mMainWindowStackLayout);

        buildDeviceSelectorPanel(mMainWindow);
        buildTreeViewPanel(mMainWindow);
        buildPixelPerfectPanel(mMainWindow);

        buildStatusBar(control);

        showDeviceSelector();

        return control;
    }


    private void buildStatusBar(Composite parent) {
        mStatusBar = new Composite(parent, SWT.NONE);
        mStatusBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        FormLayout statusBarLayout = new FormLayout();
        statusBarLayout.marginHeight = statusBarLayout.marginWidth = 2;

        mStatusBar.setLayout(statusBarLayout);

        mDeviceViewButton = new Button(mStatusBar, SWT.TOGGLE);
        mDeviceViewButton.setImage(mDeviceViewImage);
        mDeviceViewButton.setToolTipText("Switch to the window selection view");
        mDeviceViewButton.addSelectionListener(deviceViewButtonSelectionListener);
        FormData deviceViewButtonFormData = new FormData();
        deviceViewButtonFormData.left = new FormAttachment();
        mDeviceViewButton.setLayoutData(deviceViewButtonFormData);

        mTreeViewButton = new Button(mStatusBar, SWT.TOGGLE);
        mTreeViewButton.setImage(mTreeViewImage);
        mTreeViewButton.setEnabled(false);
        mTreeViewButton.setToolTipText("Switch to the tree view");
        mTreeViewButton.addSelectionListener(treeViewButtonSelectionListener);
        FormData treeViewButtonFormData = new FormData();
        treeViewButtonFormData.left = new FormAttachment(mDeviceViewButton, 2);
        mTreeViewButton.setLayoutData(treeViewButtonFormData);

        mPixelPerfectButton = new Button(mStatusBar, SWT.TOGGLE);
        mPixelPerfectButton.setImage(mPixelPerfectImage);
        mPixelPerfectButton.setEnabled(false);
        mPixelPerfectButton.setToolTipText("Switch to the pixel perfect view");
        mPixelPerfectButton.addSelectionListener(pixelPerfectButtonSelectionListener);
        FormData pixelPerfectButtonFormData = new FormData();
        pixelPerfectButtonFormData.left = new FormAttachment(mTreeViewButton, 2);
        mPixelPerfectButton.setLayoutData(pixelPerfectButtonFormData);

        // Tree View control panel...
        mTreeViewControls = new TreeViewControls(mStatusBar);
        FormData treeViewControlsFormData = new FormData();
        treeViewControlsFormData.left = new FormAttachment(mPixelPerfectButton, 2);
        treeViewControlsFormData.top = new FormAttachment(mTreeViewButton, 0, SWT.CENTER);
        treeViewControlsFormData.width = 552;
        mTreeViewControls.setLayoutData(treeViewControlsFormData);

        // Progress stuff
        mProgressLabel = new Label(mStatusBar, SWT.RIGHT);

        mProgressBar = new ProgressBar(mStatusBar, SWT.HORIZONTAL | SWT.INDETERMINATE | SWT.SMOOTH);
        FormData progressBarFormData = new FormData();
        progressBarFormData.right = new FormAttachment(100, 0);
        progressBarFormData.top = new FormAttachment(mTreeViewButton, 0, SWT.CENTER);
        mProgressBar.setLayoutData(progressBarFormData);

        FormData progressLabelFormData = new FormData();
        progressLabelFormData.right = new FormAttachment(mProgressBar, -2);
        progressLabelFormData.top = new FormAttachment(mTreeViewButton, 0, SWT.CENTER);
        mProgressLabel.setLayoutData(progressLabelFormData);

        if (mProgressString == null) {
            mProgressLabel.setVisible(false);
            mProgressBar.setVisible(false);
        } else {
            mProgressLabel.setText(mProgressString);
        }
    }

    private void buildDeviceSelectorPanel(Composite parent) {
        mDeviceSelectorPanel = new Composite(parent, SWT.NONE);
        GridLayout gridLayout = new GridLayout();
        gridLayout.marginWidth = gridLayout.marginHeight = 0;
        gridLayout.horizontalSpacing = gridLayout.verticalSpacing = 0;
        mDeviceSelectorPanel.setLayout(gridLayout);

        Composite buttonPanel = new Composite(mDeviceSelectorPanel, SWT.NONE);
        buttonPanel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        GridLayout buttonLayout = new GridLayout();
        buttonLayout.marginWidth = buttonLayout.marginHeight = 0;
        buttonLayout.horizontalSpacing = buttonLayout.verticalSpacing = 0;
        buttonPanel.setLayout(buttonLayout);

        Composite innerButtonPanel = new Composite(buttonPanel, SWT.NONE);
        innerButtonPanel.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        GridLayout innerButtonPanelLayout = new GridLayout(3, true);
        innerButtonPanelLayout.marginWidth = innerButtonPanelLayout.marginHeight = 2;
        innerButtonPanelLayout.horizontalSpacing = innerButtonPanelLayout.verticalSpacing = 2;
        innerButtonPanel.setLayout(innerButtonPanelLayout);

        ActionButton refreshWindows =
                new ActionButton(innerButtonPanel, RefreshWindowsAction.getAction());