/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.hierarchyviewer;

import com.android.ddmlib.Log;
import com.android.ddmuilib.ImageLoader;
import com.android.hierarchyviewer.actions.AboutAction;
import com.android.hierarchyviewer.actions.LoadAllViewsAction;
import com.android.hierarchyviewer.actions.QuitAction;
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.DumpThemeAction;
import com.android.hierarchyviewerlib.actions.EvaluateContrastAction;
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.ProfileNodesAction;
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.IHvDevice;
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.InvokeMethodPrompt;
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;
import com.android.menubar.IMenuBarEnhancer;
import com.android.menubar.IMenuBarEnhancer.MenuBarMode;
import com.android.menubar.MenuBarEnhancer;

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;

public class HierarchyViewerApplication extends ApplicationWindow {

    private static final String APP_NAME = "Hierarchy Viewer";
    private static final int INITIAL_WIDTH = 1280;
    private static final int INITIAL_HEIGHT = 800;

    private static HierarchyViewerApplication sMainWindow;

    // Images for moving between the 3 main windows.
    private Image mDeviceViewImage;
    private Image mPixelPerfectImage;
    private Image mTreeViewImage;
    private Image mDeviceViewSelectedImage;
    private Image mPixelPerfectSelectedImage;
    private Image mTreeViewSelectedImage;

    // And their buttons
    private Button mTreeViewButton;
    private Button mPixelPerfectButton;
    private Button mDeviceViewButton;

    private Label mProgressLabel;
    private ProgressBar mProgressBar;
    private String mProgressString;

    private Composite mDeviceSelectorPanel;
    private Composite mTreeViewPanel;
    private Composite mPixelPerfectPanel;
    private StackLayout mMainWindowStackLayout;
    private DeviceSelector mDeviceSelector;
    private Composite mStatusBar;
    private TreeView mTreeView;
    private Composite mMainWindow;
    private Image mOnBlackImage;
    private Image mOnWhiteImage;
    private Button mOnBlackWhiteButton;
    private Button mShowExtras;
    private LayoutViewer mLayoutViewer;
    private PixelPerfectLoupe mPixelPerfectLoupe;
    private Composite mTreeViewControls;
    private InvokeMethodPrompt mInvokeMethodPrompt;

    private ActionButton dumpDisplayList;

    private HierarchyViewerDirector mDirector;

    /*
     * If a thread bails with an uncaught exception, bring the whole
     * thing down.
     */
    private static class UncaughtHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            Log.e("HierarchyViewer", "shutting down due to uncaught exception");
            Log.e("HierarchyViewer", e);
            System.exit(1);
        }
    }

    public static final HierarchyViewerApplication getMainWindow() {
        return sMainWindow;
    }

    public HierarchyViewerApplication() {
        super(null /*shell*/);

        sMainWindow = this;

        addMenuBar();
    }

    @Override
    protected void configureShell(Shell shell) {
        super.configureShell(shell);
        shell.setText(APP_NAME);
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        Image image = imageLoader.loadImage("sdk-hierarchyviewer-128.png", Display.getDefault()); //$NON-NLS-1$
        shell.setImage(image);
    }

    @Override
    public MenuManager createMenuManager() {
        return new MenuManager();
    }

    public void run() {
        setBlockOnOpen(true);

        try {
            open();
        } catch (SWTException e) {
         // Ignore "widget disposed" errors after we closed.
            if (!getShell().isDisposed()) {
                throw e;
            }
        }

        TreeViewModel.getModel().removeTreeChangeListener(mTreeChangeListener);
        PixelPerfectModel.getModel().removeImageChangeListener(mImageChangeListener);

        ImageLoader.dispose();
        mDirector.stopListenForDevices();
        mDirector.stopDebugBridge();
        mDirector.terminate();
    }

    @Override
    protected void initializeBounds() {
        Rectangle monitorArea = Display.getDefault().getPrimaryMonitor().getBounds();
        getShell().setSize(Math.min(monitorArea.width, INITIAL_WIDTH),
                Math.min(monitorArea.height, INITIAL_HEIGHT));
        getShell().setLocation(monitorArea.x + (monitorArea.width - INITIAL_WIDTH) / 2,
                monitorArea.y + (monitorArea.height - INITIAL_HEIGHT) / 2);
    }

    private void loadResources() {
        ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class);
        mTreeViewImage = imageLoader.loadImage("tree-view.png", Display.getDefault()); //$NON-NLS-1$
        mTreeViewSelectedImage =
                imageLoader.loadImage("tree-view-selected.png", Display.getDefault()); //$NON-NLS-1$
        mPixelPerfectImage = imageLoader.loadImage("pixel-perfect-view.png", Display.getDefault()); //$NON-NLS-1$
        mPixelPerfectSelectedImage =
                imageLoader.loadImage("pixel-perfect-view-selected.png", Display.getDefault()); //$NON-NLS-1$
        mDeviceViewImage = imageLoader.loadImage("device-view.png", Display.getDefault()); //$NON-NLS-1$
        mDeviceViewSelectedImage =
                imageLoader.loadImage("device-view-selected.png", Display.getDefault()); //$NON-NLS-1$
        mOnBlackImage = imageLoader.loadImage("on-black.png", Display.getDefault()); //$NON-NLS-1$
        mOnWhiteImage = imageLoader.loadImage("on-white.png", Display.getDefault()); //$NON-NLS-1$
    }

    @Override
    protected Control createContents(Composite parent) {
        // create this only once the window is opened to please SWT on Mac
        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());
        refreshWindows.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton loadViewHierarchyButton =
                new ActionButton(innerButtonPanel, LoadViewHierarchyAction.getAction());
        loadViewHierarchyButton.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton inspectScreenshotButton =
                new ActionButton(innerButtonPanel, InspectScreenshotAction.getAction());
        inspectScreenshotButton.setLayoutData(new GridData(GridData.FILL_BOTH));

        Composite deviceSelectorContainer = new Composite(mDeviceSelectorPanel, SWT.BORDER);
        deviceSelectorContainer.setLayoutData(new GridData(GridData.FILL_BOTH));
        deviceSelectorContainer.setLayout(new FillLayout());
        mDeviceSelector = new DeviceSelector(deviceSelectorContainer, true, true);
    }

    public void buildTreeViewPanel(Composite parent) {
        mTreeViewPanel = new Composite(parent, SWT.NONE);
        GridLayout gridLayout = new GridLayout();
        gridLayout.marginWidth = gridLayout.marginHeight = 0;
        gridLayout.horizontalSpacing = gridLayout.verticalSpacing = 0;
        mTreeViewPanel.setLayout(gridLayout);

        Composite buttonPanel = new Composite(mTreeViewPanel, 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(5, true);
        innerButtonPanelLayout.marginWidth = innerButtonPanelLayout.marginHeight = 2;
        innerButtonPanelLayout.horizontalSpacing = innerButtonPanelLayout.verticalSpacing = 2;
        innerButtonPanel.setLayout(innerButtonPanelLayout);

        ActionButton saveTreeView =
                new ActionButton(innerButtonPanel, SaveTreeViewAction.getAction(getShell()));
        saveTreeView.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton capturePSD =
                new ActionButton(innerButtonPanel, CapturePSDAction.getAction(getShell()));
        capturePSD.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton refreshViewAction =
                new ActionButton(innerButtonPanel, RefreshViewAction.getAction());
        refreshViewAction.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton evaluateContrast =
                new ActionButton(innerButtonPanel, EvaluateContrastAction.getAction(getShell()));
        evaluateContrast.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton displayView =
                new ActionButton(innerButtonPanel, DisplayViewAction.getAction(getShell()));
        displayView.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton invalidate = new ActionButton(innerButtonPanel, InvalidateAction.getAction());
        invalidate.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton requestLayout =
                new ActionButton(innerButtonPanel, RequestLayoutAction.getAction());
        requestLayout.setLayoutData(new GridData(GridData.FILL_BOTH));

        dumpDisplayList =
                new ActionButton(innerButtonPanel, DumpDisplayListAction.getAction());
        dumpDisplayList.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton dumpTheme =
                new ActionButton(innerButtonPanel, DumpThemeAction.getAction(getShell()));
        dumpTheme.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton profileNodes =
                new ActionButton(innerButtonPanel, ProfileNodesAction.getAction());
        profileNodes.setLayoutData(new GridData(GridData.FILL_BOTH));

        SashForm mainSash = new SashForm(mTreeViewPanel, SWT.HORIZONTAL | SWT.SMOOTH);
        mainSash.setLayoutData(new GridData(GridData.FILL_BOTH));
        Composite treeViewContainer = new Composite(mainSash, SWT.BORDER);
        treeViewContainer.setLayout(new FillLayout());
        mTreeView = new TreeView(treeViewContainer);

        SashForm sideSash = new SashForm(mainSash, SWT.VERTICAL | SWT.SMOOTH);

        mainSash.SASH_WIDTH = 4;
        mainSash.setWeights(new int[] {
                7, 3
        });

        Composite treeViewOverviewContainer = new Composite(sideSash, SWT.BORDER);
        treeViewOverviewContainer.setLayout(new FillLayout());
        new TreeViewOverview(treeViewOverviewContainer);

        Composite propertyViewerContainer = new Composite(sideSash, SWT.BORDER);
        propertyViewerContainer.setLayout(new GridLayout());

        PropertyViewer pv = new PropertyViewer(propertyViewerContainer);
        pv.setLayoutData(new GridData(GridData.FILL_BOTH));

        mInvokeMethodPrompt = new InvokeMethodPrompt(propertyViewerContainer);
        mInvokeMethodPrompt.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        Composite layoutViewerContainer = new Composite(sideSash, SWT.NONE);
        GridLayout layoutViewerLayout = new GridLayout();
        layoutViewerLayout.marginWidth = layoutViewerLayout.marginHeight = 0;
        layoutViewerLayout.horizontalSpacing = layoutViewerLayout.verticalSpacing = 1;
        layoutViewerContainer.setLayout(layoutViewerLayout);

        Composite fullButtonBar = new Composite(layoutViewerContainer, SWT.NONE);
        fullButtonBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        GridLayout fullButtonBarLayout = new GridLayout(2, false);
        fullButtonBarLayout.marginWidth = fullButtonBarLayout.marginHeight = 0;
        fullButtonBarLayout.marginRight = 2;
        fullButtonBarLayout.horizontalSpacing = fullButtonBarLayout.verticalSpacing = 0;
        fullButtonBar.setLayout(fullButtonBarLayout);

        Composite buttonBar = new Composite(fullButtonBar, SWT.NONE);
        buttonBar.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        RowLayout rowLayout = new RowLayout(SWT.HORIZONTAL);
        rowLayout.marginLeft =
                rowLayout.marginRight = rowLayout.marginTop = rowLayout.marginBottom = 0;
        rowLayout.pack = true;
        rowLayout.center = true;
        buttonBar.setLayout(rowLayout);

        mOnBlackWhiteButton = new Button(buttonBar, SWT.PUSH);
        mOnBlackWhiteButton.setImage(mOnWhiteImage);
        mOnBlackWhiteButton.addSelectionListener(onBlackWhiteSelectionListener);
        mOnBlackWhiteButton.setToolTipText("Change layout viewer background color");

        mShowExtras = new Button(buttonBar, SWT.CHECK);
        mShowExtras.setText("Show Extras");
        mShowExtras.addSelectionListener(showExtrasSelectionListener);
        mShowExtras.setToolTipText("Show images");

        ActionButton loadAllViewsButton =
                new ActionButton(fullButtonBar, LoadAllViewsAction.getAction());
        loadAllViewsButton.setLayoutData(new GridData(GridData.END, GridData.CENTER, true, true));
        loadAllViewsButton.addSelectionListener(loadAllViewsSelectionListener);

        Composite layoutViewerMainContainer = new Composite(layoutViewerContainer, SWT.BORDER);
        layoutViewerMainContainer.setLayoutData(new GridData(GridData.FILL_BOTH));
        layoutViewerMainContainer.setLayout(new FillLayout());
        mLayoutViewer = new LayoutViewer(layoutViewerMainContainer);

        sideSash.SASH_WIDTH = 4;
        sideSash.setWeights(new int[] {
                238, 332, 416
        });

    }

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

        Composite buttonPanel = new Composite(mPixelPerfectPanel, 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(6, true);
        innerButtonPanelLayout.marginWidth = innerButtonPanelLayout.marginHeight = 2;
        innerButtonPanelLayout.horizontalSpacing = innerButtonPanelLayout.verticalSpacing = 2;
        innerButtonPanel.setLayout(innerButtonPanelLayout);

        ActionButton saveTreeView =
                new ActionButton(innerButtonPanel, SavePixelPerfectAction.getAction(getShell()));
        saveTreeView.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton refreshPixelPerfect =
                new ActionButton(innerButtonPanel, RefreshPixelPerfectAction.getAction());
        refreshPixelPerfect.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton refreshPixelPerfectTree =
                new ActionButton(innerButtonPanel, RefreshPixelPerfectTreeAction.getAction());
        refreshPixelPerfectTree.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton loadOverlay =
                new ActionButton(innerButtonPanel, LoadOverlayAction.getAction(getShell()));
        loadOverlay.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton showInLoupe =
                new ActionButton(innerButtonPanel, ShowOverlayAction.getAction());
        showInLoupe.setLayoutData(new GridData(GridData.FILL_BOTH));

        ActionButton autoRefresh =
                new ActionButton(innerButtonPanel, PixelPerfectAutoRefreshAction.getAction());
        autoRefresh.setLayoutData(new GridData(GridData.FILL_BOTH));

        SashForm mainSash = new SashForm(mPixelPerfectPanel, SWT.HORIZONTAL | SWT.SMOOTH);
        mainSash.setLayoutData(new GridData(GridData.FILL_BOTH));
        mainSash.SASH_WIDTH = 4;

        Composite pixelPerfectTreeContainer = new Composite(mainSash, SWT.BORDER);
        pixelPerfectTreeContainer.setLayout(new FillLayout());
        new PixelPerfectTree(pixelPerfectTreeContainer);

        Composite pixelPerfectLoupeContainer = new Composite(mainSash, SWT.NONE);
        GridLayout loupeLayout = new GridLayout();
        loupeLayout.marginWidth = loupeLayout.marginHeight = 0;
        loupeLayout.horizontalSpacing = loupeLayout.verticalSpacing = 0;
        pixelPerfectLoupeContainer.setLayout(loupeLayout);

        Composite pixelPerfectLoupeBorder = new Composite(pixelPerfectLoupeContainer, SWT.BORDER);
        pixelPerfectLoupeBorder.setLayoutData(new GridData(GridData.FILL_BOTH));
        GridLayout pixelPerfectLoupeBorderGridLayout = new GridLayout();
        pixelPerfectLoupeBorderGridLayout.marginWidth =
                pixelPerfectLoupeBorderGridLayout.marginHeight = 0;
        pixelPerfectLoupeBorderGridLayout.horizontalSpacing =
                pixelPerfectLoupeBorderGridLayout.verticalSpacing = 0;
        pixelPerfectLoupeBorder.setLayout(pixelPerfectLoupeBorderGridLayout);

        mPixelPerfectLoupe = new PixelPerfectLoupe(pixelPerfectLoupeBorder);
        mPixelPerfectLoupe.setLayoutData(new GridData(GridData.FILL_BOTH));

        PixelPerfectPixelPanel pixelPerfectPixelPanel =
                new PixelPerfectPixelPanel(pixelPerfectLoupeBorder);
        pixelPerfectPixelPanel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        PixelPerfectControls pixelPerfectControls =
                new PixelPerfectControls(pixelPerfectLoupeContainer);
        pixelPerfectControls.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));


        Composite pixelPerfectContainer = new Composite(mainSash, SWT.BORDER);
        pixelPerfectContainer.setLayout(new FillLayout());
        new PixelPerfect(pixelPerfectContainer);

        mainSash.setWeights(new int[] {
                272, 376, 346
        });

    }

    public void showOverlayInLoupe(boolean value) {
        mPixelPerfectLoupe.setShowOverlay(value);
    }

    // Shows the progress notification...
    public void startTask(final String taskName) {
        mProgressString = taskName;
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                if (mProgressLabel != null && mProgressBar != null) {
                    mProgressLabel.setText(taskName);
                    mProgressLabel.setVisible(true);
                    mProgressBar.setVisible(true);
                    mStatusBar.layout();
                }
            }
        });
    }

    // And hides it!
    public void endTask() {
        mProgressString = null;
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                if (mProgressLabel != null && mProgressBar != null) {
                    mProgressLabel.setVisible(false);
                    mProgressBar.setVisible(false);
                }
            }
        });
    }

    public void showDeviceSelector() {
        // Show the menus.
        MenuManager mm = getMenuBarManager();
        mm.removeAll();

        MenuManager file = new MenuManager("&File");
        IMenuBarEnhancer enhancer = MenuBarEnhancer.setupMenuManager(
                APP_NAME,
                getShell().getDisplay(),
                file,
                AboutAction.getAction(getShell()),
                null /*preferencesAction*/,
                QuitAction.getAction());
        if (enhancer.getMenuBarMode() == MenuBarMode.GENERIC) {
            mm.add(file);
        }

        MenuManager device = new MenuManager("&Devices");
        mm.add(device);

        device.add(RefreshWindowsAction.getAction());
        device.add(LoadViewHierarchyAction.getAction());
        device.add(InspectScreenshotAction.getAction());

        mm.updateAll(true);

        mDeviceViewButton.setSelection(true);
        mDeviceViewButton.setImage(mDeviceViewSelectedImage);

        mTreeViewButton.setSelection(false);
        mTreeViewButton.setImage(mTreeViewImage);

        mPixelPerfectButton.setSelection(false);
        mPixelPerfectButton.setImage(mPixelPerfectImage);

        mMainWindowStackLayout.topControl = mDeviceSelectorPanel;

        mMainWindow.layout();

        mDeviceSelector.setFocus();

        mTreeViewControls.setVisible(false);
    }

    public void showTreeView() {
        // Show the menus.
        MenuManager mm = getMenuBarManager();
        mm.removeAll();

        MenuManager file = new MenuManager("&File");
        IMenuBarEnhancer enhancer = MenuBarEnhancer.setupMenuManager(
                APP_NAME,
                getShell().getDisplay(),
                file,
                AboutAction.getAction(getShell()),
                null /*preferencesAction*/,
                QuitAction.getAction());
        if (enhancer.getMenuBarMode() == MenuBarMode.GENERIC) {
            mm.add(file);
        }

        MenuManager treeViewMenu = new MenuManager("&Tree View");
        mm.add(treeViewMenu);

        treeViewMenu.add(SaveTreeViewAction.getAction(getShell()));
        treeViewMenu.add(CapturePSDAction.getAction(getShell()));
        treeViewMenu.add(new Separator());
        treeViewMenu.add(RefreshViewAction.getAction());
        treeViewMenu.add(DisplayViewAction.getAction(getShell()));

        IHvDevice hvDevice = DeviceSelectionModel.getModel().getSelectedDevice();
        if (hvDevice.supportsDisplayListDump()) {
            treeViewMenu.add(DumpDisplayListAction.getAction());
            dumpDisplayList.setVisible(true);
        } else {
            dumpDisplayList.setVisible(false);
        }
        treeViewMenu.add(new Separator());
        treeViewMenu.add(InvalidateAction.getAction());
        treeViewMenu.add(RequestLayoutAction.getAction());

        mm.updateAll(true);

        mDeviceViewButton.setSelection(false);
        mDeviceViewButton.setImage(mDeviceViewImage);

        mTreeViewButton.setSelection(true);
        mTreeViewButton.setImage(mTreeViewSelectedImage);

        mInvokeMethodPrompt.setEnabled(hvDevice.isViewUpdateEnabled());

        mPixelPerfectButton.setSelection(false);
        mPixelPerfectButton.setImage(mPixelPerfectImage);

        mMainWindowStackLayout.topControl = mTreeViewPanel;

        mMainWindow.layout();

        mTreeView.setFocus();

        mTreeViewControls.setVisible(true);
    }

    public void showPixelPerfect() {
        // Show the menus.
        MenuManager mm = getMenuBarManager();
        mm.removeAll();

        MenuManager file = new MenuManager("&File");
        IMenuBarEnhancer enhancer = MenuBarEnhancer.setupMenuManager(
                APP_NAME,
                getShell().getDisplay(),
                file,
                AboutAction.getAction(getShell()),
                null /*preferencesAction*/,
                QuitAction.getAction());
        if (enhancer.getMenuBarMode() == MenuBarMode.GENERIC) {
            mm.add(file);
        }

        MenuManager pixelPerfect = new MenuManager("&Pixel Perfect");
        pixelPerfect.add(SavePixelPerfectAction.getAction(getShell()));
        pixelPerfect.add(RefreshPixelPerfectAction.getAction());
        pixelPerfect.add(RefreshPixelPerfectTreeAction.getAction());
        pixelPerfect.add(PixelPerfectAutoRefreshAction.getAction());
        pixelPerfect.add(new Separator());
        pixelPerfect.add(LoadOverlayAction.getAction(getShell()));
        pixelPerfect.add(ShowOverlayAction.getAction());

        mm.add(pixelPerfect);

        mm.updateAll(true);

        mDeviceViewButton.setSelection(false);
        mDeviceViewButton.setImage(mDeviceViewImage);

        mTreeViewButton.setSelection(false);
        mTreeViewButton.setImage(mTreeViewImage);

        mPixelPerfectButton.setSelection(true);
        mPixelPerfectButton.setImage(mPixelPerfectSelectedImage);

        mMainWindowStackLayout.topControl = mPixelPerfectPanel;

        mMainWindow.layout();

        mPixelPerfectLoupe.setFocus();

        mTreeViewControls.setVisible(false);
    }

    private SelectionListener deviceViewButtonSelectionListener = new SelectionListener() {
        @Override
        public void widgetDefaultSelected(SelectionEvent e) {
            // pass
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            mDeviceViewButton.setSelection(true);
            showDeviceSelector();
        }
    };

    private SelectionListener treeViewButtonSelectionListener = new SelectionListener() {
        @Override
        public void widgetDefaultSelected(SelectionEvent e) {
            // pass
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            mTreeViewButton.setSelection(true);
            showTreeView();
        }
    };

    private SelectionListener pixelPerfectButtonSelectionListener = new SelectionListener() {
        @Override
        public void widgetDefaultSelected(SelectionEvent e) {
            // pass
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            mPixelPerfectButton.setSelection(true);
            showPixelPerfect();
        }
    };

    private SelectionListener onBlackWhiteSelectionListener = new SelectionListener() {
        @Override
        public void widgetDefaultSelected(SelectionEvent e) {
            // pass
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            if (mLayoutViewer.getOnBlack()) {
                mLayoutViewer.setOnBlack(false);
                mOnBlackWhiteButton.setImage(mOnBlackImage);
            } else {
                mLayoutViewer.setOnBlack(true);
                mOnBlackWhiteButton.setImage(mOnWhiteImage);
            }
        }
    };

    private SelectionListener showExtrasSelectionListener = new SelectionListener() {
        @Override
        public void widgetDefaultSelected(SelectionEvent e) {
            // pass
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            mLayoutViewer.setShowExtras(mShowExtras.getSelection());
        }
    };

    private SelectionListener loadAllViewsSelectionListener = new SelectionListener() {
        @Override
        public void widgetDefaultSelected(SelectionEvent e) {
            // pass
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            mShowExtras.setSelection(true);
            showExtrasSelectionListener.widgetSelected(null);
        }
    };

    private ITreeChangeListener mTreeChangeListener = new ITreeChangeListener() {
        @Override
        public void selectionChanged() {
            // pass
        }

        @Override
        public void treeChanged() {
            Display.getDefault().syncExec(new Runnable() {
                @Override
                public void run() {
                    if (TreeViewModel.getModel().getTree() == null) {
                        showDeviceSelector();
                        mTreeViewButton.setEnabled(false);
                    } else {
                        showTreeView();
                        mTreeViewButton.setEnabled(true);
                    }
                }
            });
        }

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

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

    private IImageChangeListener mImageChangeListener = new IImageChangeListener() {

        @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() {
                    if (PixelPerfectModel.getModel().getImage() == null) {
                        mPixelPerfectButton.setEnabled(false);
                        showDeviceSelector();
                    } else {
                        mPixelPerfectButton.setEnabled(true);
                        showPixelPerfect();
                    }
                }
            });
        }

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

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

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

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

    };

    public static void main(String[] args) {
        Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());

        Display.setAppName("HierarchyViewer");
        new HierarchyViewerApplication().run();
    }
}
