com.nokia.carbide.cpp.pi.call.CallVisualiser.java Source code

Java tutorial

Introduction

Here is the source code for com.nokia.carbide.cpp.pi.call.CallVisualiser.java

Source

/*
 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of the License "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 *
 * Initial Contributors:
 * Nokia Corporation - initial contribution.
 *
 * Contributors:
 *
 * Description: 
 *
 */

/**
 * 
 */
package com.nokia.carbide.cpp.pi.call;

//import java.awt.Color;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.MouseEvent;
import java.text.DecimalFormat;
import java.util.Vector;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionManager;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.SubMenuManager;
import org.eclipse.jface.dialogs.IPageChangedListener;
import org.eclipse.jface.dialogs.PageChangedEvent;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.ide.IIDEActionConstants;

import com.nokia.carbide.cpp.internal.pi.interfaces.ISaveSamples;
import com.nokia.carbide.cpp.internal.pi.interfaces.ISaveTable;
import com.nokia.carbide.cpp.internal.pi.save.SaveTableWizard;
import com.nokia.carbide.cpp.internal.pi.visual.GenericTable;
import com.nokia.carbide.cpp.pi.editors.PIPageEditor;
import com.nokia.carbide.cpp.pi.util.SourceLookup;

public class CallVisualiser extends GenericTable {
    private static final long serialVersionUID = 1L;

    // table column IDs in hopes it's quicker than comparing characters
    protected static final int COLUMN_ID_IS_CALLED = 100;
    protected static final int COLUMN_ID_IS_CALLER = 101;
    protected static final int COLUMN_ID_RECURSIVE_CALL = 102;
    protected static final int COLUMN_ID_CALLER_PERCENT = 103;
    protected static final int COLUMN_ID_CALLEE_PERCENT = 104;
    protected static final int COLUMN_ID_IS_CALLED_COUNT = 105;
    protected static final int COLUMN_ID_IS_CALLER_COUNT = 106;

    // table column headings
    protected static final String COLUMN_HEAD_IS_CALLED = Messages.getString("CallVisualiser.isCalled"); //$NON-NLS-1$
    protected static final String COLUMN_HEAD_IS_CALLER = Messages.getString("CallVisualiser.isCaller"); //$NON-NLS-1$
    protected static final String COLUMN_HEAD_RECURSIVE_CALL = Messages.getString("CallVisualiser.recursiveCaller"); //$NON-NLS-1$
    protected static final String COLUMN_HEAD_CALLER_PERCENT = Messages.getString("CallVisualiser.percentOfCalls"); //$NON-NLS-1$
    protected static final String COLUMN_HEAD_CALLEE_PERCENT = Messages.getString("CallVisualiser.percentOfCalls"); //$NON-NLS-1$
    protected static final String COLUMN_HEAD_IS_CALLED_COUNT = Messages.getString("CallVisualiser.calledSamples"); //$NON-NLS-1$
    protected static final String COLUMN_HEAD_IS_CALLER_COUNT = Messages.getString("CallVisualiser.callerSamples"); //$NON-NLS-1$
    protected static final String COLUMN_HEAD_IS_CALLED_COUNT2 = Messages.getString("CallVisualiser.calls"); //$NON-NLS-1$
    protected static final String COLUMN_HEAD_IS_CALLER_COUNT2 = Messages.getString("CallVisualiser.calls"); //$NON-NLS-1$

    // table column widths
    protected static final int COLUMN_WIDTH_IS_CALLED = 70;
    protected static final int COLUMN_WIDTH_IS_CALLER = 70;
    protected static final int COLUMN_WIDTH_RECURSIVE_CALL = 105;
    protected static final int COLUMN_WIDTH_CALLER_PERCENT = 98;
    protected static final int COLUMN_WIDTH_CALLEE_PERCENT = 98;
    protected static final int COLUMN_WIDTH_IS_CALLED_COUNT = 113;
    protected static final int COLUMN_WIDTH_IS_CALLER_COUNT = 85;
    protected static final int COLUMN_WIDTH_IS_CALLED_COUNT2 = 65;
    protected static final int COLUMN_WIDTH_IS_CALLER_COUNT2 = 65;

    // SashForm to hold the tables and their titles
    private SashForm sashForm;

    // Table viewers and their tables
    private TableViewer callerTableViewer;
    private Table callerTable;

    private TableViewer currentFunctionTableViewer;
    private Table currentFunctionTable;
    private TableColumn currentFunctionDefaultColumn;

    private TableViewer calleeTableViewer;
    private Table calleeTable;

    // context menus
    private Menu callerMenu;
    private Menu currentFunctionMenu;
    private Menu calleeMenu;

    private Table currentMenuTable;

    private SashForm parent;

    // this display's editor and page
    private PIPageEditor pageEditor;
    private int pageIndex;

    // trace associated with this display
    private GfcTrace myTrace;

    /** the editor page this is sitting on */
    private Composite curPage;
    protected boolean isPageActive;
    protected boolean needsRefresh;

    //start and end of the timeframe at the last selection event
    private int curStart;
    private int curEnd;

    // lists of functions, function callers, and function callees
    private GfcFunctionItem[] functionArray;
    private CallerCalleeItem[] callerList;
    private CallerCalleeItem[] calleeList;

    // menu items
    protected Action copyAction;
    protected Action copyTableAction;
    protected Action functionCopyFunctionAction;
    protected Action saveTableAction;
    protected Action functionSaveFunctionAction;

    protected final static int SAMPLES_AT_ONE_TIME = 1000;
    private Job setTimeframeJob = null;

    // class to pass sample data to the save wizard
    public class SaveSampleString implements ISaveSamples {
        int startIndex = 0;

        public SaveSampleString() {
        }

        public String getData() {
            return getData(SAMPLES_AT_ONE_TIME);
        }

        public String getData(int size) {
            String returnString = getSampleString(this.startIndex, this.startIndex + size);
            if (returnString == null)
                this.startIndex = 0;
            else
                this.startIndex += size;
            return returnString;
        }

        public int getIndex() {
            return this.startIndex;
        }

        public void clear() {
            this.startIndex = 0;
        }
    }

    /*
     * return the call samples selected in the interval 
     */
    protected String getSampleString(int startIndex, int endIndex) {
        int startTime = (int) (PIPageEditor.currentPageEditor().getStartTime() * 1000.0 + 0.0005);
        int endTime = (int) (PIPageEditor.currentPageEditor().getEndTime() * 1000.0 + 0.0005);

        // check if we have returned everything
        if (startIndex > (endTime - startTime))
            return null;

        GfcTrace trace = this.myTrace;
        Vector samples = trace.samples;

        String returnString = ""; //$NON-NLS-1$

        if (startIndex == 0)
            returnString = Messages.getString("CallVisualiser.callHeading"); //$NON-NLS-1$

        for (int i = startTime + startIndex; i < endTime && i < startTime + endIndex; i++) {
            GfcSample sample = (GfcSample) samples.get(i);

            returnString += sample.sampleSynchTime + ",0x" //$NON-NLS-1$
                    + Long.toHexString(sample.linkRegister) + ",\"" //$NON-NLS-1$
                    + (sample.getCallerFunctionItt() != null ? sample.getCallerFunctionItt().getFunctionName()
                            : sample.getCallerFunctionSym().getFunctionName())
                    + "\"," //$NON-NLS-1$
                    + (sample.getCallerFunctionItt() != null
                            ? sample.getCallerFunctionItt().getFunctionBinary().getBinaryName()
                            : sample.getCallerFunctionSym().getFunctionBinary().getBinaryName())
                    + ",0x" //$NON-NLS-1$
                    + Long.toHexString(sample.programCounter) + ",\"" //$NON-NLS-1$
                    + (sample.getCurrentFunctionItt() != null ? sample.getCurrentFunctionItt().getFunctionName()
                            : sample.getCurrentFunctionSym().getFunctionName())
                    + "\"," //$NON-NLS-1$
                    + (sample.getCurrentFunctionItt() != null
                            ? sample.getCurrentFunctionItt().getFunctionBinary().getBinaryName()
                            : sample.getCurrentFunctionSym().getFunctionBinary().getBinaryName())
                    + "\n"; //$NON-NLS-1$
        }

        return returnString;
    }

    protected MenuItem getSaveSamplesItem(Menu menu, boolean enabled) {
        MenuItem saveSamplesItem = new MenuItem(menu, SWT.PUSH);

        saveSamplesItem.setText(Messages.getString("CallVisualiser.saveAllSamplesForInterval")); //$NON-NLS-1$
        saveSamplesItem.setEnabled(enabled);

        if (enabled) {
            saveSamplesItem.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent e) {
                    action("saveSamples"); //$NON-NLS-1$
                }
            });
        }

        return saveSamplesItem;
    }

    public CallVisualiser(PIPageEditor pageEditor, int pageIndex, SashForm parent, GfcTrace trace,
            Composite curPage) {
        this.pageEditor = pageEditor;
        this.pageIndex = pageIndex;
        this.parent = parent;
        this.myTrace = trace;
        this.curPage = curPage;
        isPageActive = true;
        needsRefresh = false;

        // let the trace know about the CallVisualiser so that unit tests can find it
        trace.setCallVisualiser(this);

        // create the 3 table viewers: caller functions, selected function, callee functions
        createTableViewers(parent);

        createSetTimeframeJob();
        createPageListeners();
    }

    public void createTableViewers(SashForm parent) {
        if (parent == null)
            return;

        /*
         * Create a SashForm with three labeled tables:
         * 
         * 1. Functions called by
         * 2. Functions (one checkbox selectable at a time)
         * 3. Functions called
         */

        // the 3 tables will be in a sashForm
        if (parent.getOrientation() != SWT.VERTICAL) {
            // put a SashForm in the SashForm
            this.sashForm = new SashForm(parent, SWT.VERTICAL);
            this.sashForm.SASH_WIDTH = 5; // 5 pixel wide sash
        } else
            this.sashForm = parent;

        createCallerTableViewer(sashForm);
        createCurrentFunctionTableViewer(sashForm);
        createCalleeTableViewer(sashForm);

        createDefaultActions(true);
    }

    private void createCurrentFunctionTableViewer(SashForm sashForm) {
        /*
         * Functions (one checkbox selectable at a time)
         *
         *       selected checkbox
         *      percent load
         *     function name
         *      function start address
         *      binary containing function
         *      sample count
         */
        Label label;
        Composite holder;
        Table table;
        TableColumn column;

        // middle functionTable and title
        holder = new Composite(sashForm, SWT.NONE);
        GridLayout gridLayout = new GridLayout(1, true);
        gridLayout.marginWidth = 0;
        gridLayout.marginHeight = 0;
        holder.setLayout(gridLayout);

        label = new Label(holder, SWT.CENTER | SWT.BORDER);
        label.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_YELLOW));
        label.setFont(PIPageEditor.helvetica_10);
        label.setText(Messages.getString("CallVisualiser.selectFunction")); //$NON-NLS-1$
        label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        this.currentFunctionTableViewer = new TableViewer(holder,
                SWT.BORDER | SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
        this.currentFunctionTable = currentFunctionTableViewer.getTable();

        // add the label provider and content provider
        this.currentFunctionTableViewer.setLabelProvider(new SharedLabelProvider(this.currentFunctionTable));
        this.currentFunctionTableViewer.setContentProvider(new ArrayContentProvider());
        this.currentFunctionTableViewer.setSorter(new SharedSorter());

        table = this.currentFunctionTable;
        table.setRedraw(false);

        // give the table a heading for use in copying and exported
        table.setData(Messages.getString("CallVisualiser.selectedFunction")); //$NON-NLS-1$

        // create the other table entries when a row in this table is selected
        table.addSelectionListener(new SelectionListener() {

            public void widgetSelected(SelectionEvent event) {

                if (!(event.item instanceof TableItem))
                    return;

                // set the other tables based on this selection
                if (!(event.item.getData() instanceof GfcFunctionItem))
                    return;

                updateCallerCalleeTables((GfcFunctionItem) event.item.getData());
            }

            public void widgetDefaultSelected(SelectionEvent e) {
                widgetSelected(e);
            }
        });

        // data associated with the TableViewer will note which columns contain hex values
        // Keep this in the order in which columns have been created
        boolean[] isHex = { false, false, false, true, false, false, false };
        this.currentFunctionTable.setData("isHex", isHex); //$NON-NLS-1$

        // is called percent column
        column = new TableColumn(table, SWT.RIGHT);
        column.setText(COLUMN_HEAD_IS_CALLED);
        column.setWidth(COLUMN_WIDTH_IS_CALLED);
        column.setData(Integer.valueOf(COLUMN_ID_IS_CALLED));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CheckboxColumnSelectionHandler());

        // is caller percent column
        column = new TableColumn(table, SWT.RIGHT);
        column.setText(COLUMN_HEAD_IS_CALLER);
        column.setWidth(COLUMN_WIDTH_IS_CALLER);
        column.setData(Integer.valueOf(COLUMN_ID_IS_CALLER));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CheckboxColumnSelectionHandler());

        // function name column
        column = new TableColumn(table, SWT.LEFT);
        column.setText(COLUMN_HEAD_FUNCTION);
        column.setWidth(COLUMN_WIDTH_FUNCTION_NAME);
        column.setData(Integer.valueOf(COLUMN_ID_FUNCTION));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CheckboxColumnSelectionHandler());

        // function start address column
        column = new TableColumn(table, SWT.CENTER);
        column.setText(COLUMN_HEAD_START_ADDR);
        column.setWidth(COLUMN_WIDTH_START_ADDRESS);
        column.setData(Integer.valueOf(COLUMN_ID_START_ADDR));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CheckboxColumnSelectionHandler());

        // binary containing function column
        column = new TableColumn(table, SWT.LEFT);
        column.setText(COLUMN_HEAD_IN_BINARY);
        column.setWidth(COLUMN_WIDTH_IN_BINARY);
        column.setData(Integer.valueOf(COLUMN_ID_IN_BINARY));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CheckboxColumnSelectionHandler());

        // path to binary containing function column
        column = new TableColumn(table, SWT.LEFT);
        column.setText(COLUMN_HEAD_IN_BINARY_PATH);
        column.setWidth(COLUMN_WIDTH_IN_BINARY_PATH);
        column.setData(Integer.valueOf(COLUMN_ID_IN_BINARY_PATH));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CheckboxColumnSelectionHandler());

        // sample count column
        column = new TableColumn(table, SWT.CENTER);
        column.setText(COLUMN_HEAD_IS_CALLED_COUNT);
        column.setWidth(COLUMN_WIDTH_IS_CALLED_COUNT);
        column.setData(Integer.valueOf(COLUMN_ID_IS_CALLED_COUNT));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CheckboxColumnSelectionHandler());

        // set the default sort column to COLUMN_ID_IS_CALLED_COUNT
        currentFunctionDefaultColumn = column;

        // sample count column
        column = new TableColumn(table, SWT.CENTER);
        column.setText(COLUMN_HEAD_IS_CALLER_COUNT);
        column.setWidth(COLUMN_WIDTH_IS_CALLER_COUNT);
        column.setData(Integer.valueOf(COLUMN_ID_IS_CALLER_COUNT));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CheckboxColumnSelectionHandler());

        table.setLayoutData(new GridData(GridData.FILL_BOTH));
        table.setHeaderVisible(true);
        table.setLinesVisible(true);
        table.setRedraw(true);

        // listen for mouse clicks: to select a row, pop up a menu, etc.
        table.addMouseListener(new CurrentFunctionMouseListener());

        table.addFocusListener(new FocusListener() {
            IAction oldCopyAction = null;

            public void focusGained(org.eclipse.swt.events.FocusEvent arg0) {
                IActionBars bars = PIPageEditor.getActionBars();

                // modify what is executed when Copy is called from the Edit menu
                oldCopyAction = bars.getGlobalActionHandler(ActionFactory.COPY.getId());

                bars.setGlobalActionHandler(ActionFactory.COPY.getId(), copyAction);

                copyAction.setEnabled(currentFunctionTable.getSelectionCount() > 0);
                bars.updateActionBars();

                // add to the Edit menu
                IMenuManager editMenuManager = bars.getMenuManager().findMenuUsingPath(IIDEActionConstants.M_EDIT);

                if (editMenuManager instanceof SubMenuManager) {
                    IContributionManager editManager = ((SubMenuManager) editMenuManager).getParent();
                    ActionContributionItem item;

                    editMenuManager.remove("PICopyFunction"); //$NON-NLS-1$
                    functionCopyFunctionAction.setEnabled(currentFunctionTable.getSelectionCount() > 0);
                    item = new ActionContributionItem(functionCopyFunctionAction);
                    item.setVisible(true);
                    editManager.prependToGroup(IIDEActionConstants.CUT_EXT, item);

                    editMenuManager.remove("PICopyTable"); //$NON-NLS-1$
                    copyTableAction.setEnabled(currentFunctionTable.getItemCount() > 0);
                    item = new ActionContributionItem(copyTableAction);
                    item.setVisible(true);
                    editManager.prependToGroup(IIDEActionConstants.CUT_EXT, item);
                }

                // add to the File menu
                IMenuManager fileMenuManager = bars.getMenuManager().findMenuUsingPath(IIDEActionConstants.M_FILE);

                if (fileMenuManager instanceof SubMenuManager) {
                    IContributionManager fileManager = ((SubMenuManager) fileMenuManager).getParent();
                    ActionContributionItem item;

                    fileMenuManager.remove("PISaveTable"); //$NON-NLS-1$
                    saveTableAction.setEnabled(currentFunctionTable.getItemCount() > 0);
                    item = new ActionContributionItem(saveTableAction);
                    item.setVisible(true);
                    fileManager.insertAfter("saveAll", item); //$NON-NLS-1$

                    fileMenuManager.remove("PISaveFunction"); //$NON-NLS-1$
                    functionSaveFunctionAction.setEnabled(currentFunctionTable.getSelectionCount() > 0);
                    item = new ActionContributionItem(functionSaveFunctionAction);
                    item.setVisible(true);
                    fileManager.insertAfter("PISaveTable", item); //$NON-NLS-1$
                }
            }

            public void focusLost(org.eclipse.swt.events.FocusEvent arg0) {
                IActionBars bars = PIPageEditor.getActionBars();
                bars.setGlobalActionHandler(ActionFactory.COPY.getId(), oldCopyAction);
                bars.updateActionBars();

                SubMenuManager editMenuManager = (SubMenuManager) PIPageEditor.getMenuManager()
                        .find(IIDEActionConstants.M_EDIT);
                editMenuManager.remove("PICopyTable"); //$NON-NLS-1$
                editMenuManager.remove("PICopyFunction"); //$NON-NLS-1$

                SubMenuManager fileMenuManager = (SubMenuManager) PIPageEditor.getMenuManager()
                        .find(IIDEActionConstants.M_FILE);
                fileMenuManager.remove("PISaveTable"); //$NON-NLS-1$
                fileMenuManager.remove("PISaveFunction"); //$NON-NLS-1$
            }
        });
    }

    private void createCallerTableViewer(SashForm sashForm) {
        /*
         * Functions called by
         * 
         *      percent of calls
         *      total percent
         *      caller percent
         *     function name
         *      function start address
         *      binary containing function
         *      sample count
         */
        Label label;
        Composite holder;
        Table table;
        TableColumn column;

        // top functionTable and title
        holder = new Composite(sashForm, SWT.NONE);
        //      holder.setLayout(new FillLayout(SWT.VERTICAL));
        GridLayout gridLayout = new GridLayout(1, true);
        gridLayout.marginWidth = 0;
        gridLayout.marginHeight = 0;
        holder.setLayout(gridLayout);

        label = new Label(holder, SWT.CENTER | SWT.BORDER);
        label.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_CYAN));
        label.setFont(PIPageEditor.helvetica_10);
        label.setText(Messages.getString("CallVisualiser.callingSelectedFunction")); //$NON-NLS-1$
        label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        this.callerTableViewer = new TableViewer(holder,
                SWT.BORDER | SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
        this.callerTable = callerTableViewer.getTable();

        // add the label provider and content provider
        this.callerTableViewer.setLabelProvider(new SharedLabelProvider(this.callerTable));
        this.callerTableViewer.setContentProvider(new ArrayContentProvider());
        this.callerTableViewer.setSorter(new SharedSorter());

        table = this.callerTable;
        table.setRedraw(false);

        // give the table a heading for possible use in copying and exported
        table.setData(Messages.getString("CallVisualiser.callerFunctions")); //$NON-NLS-1$

        // data associated with the TableViewer will note which columns contain hex values
        // Keep this in the order in which columns have been created
        boolean[] isHex = { false, false, false, true, false, false, false };
        this.callerTable.setData("isHex", isHex); //$NON-NLS-1$

        // percent of calls column
        column = new TableColumn(table, SWT.RIGHT);
        column.setText(COLUMN_HEAD_CALLER_PERCENT);
        column.setWidth(COLUMN_WIDTH_CALLER_PERCENT);
        column.setData(Integer.valueOf(COLUMN_ID_CALLER_PERCENT));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledByColumnSelectionHandler());

        // sample count column
        column = new TableColumn(table, SWT.CENTER);
        column.setText(COLUMN_HEAD_IS_CALLER_COUNT2);
        column.setWidth(COLUMN_WIDTH_IS_CALLER_COUNT2);
        column.setData(Integer.valueOf(COLUMN_ID_IS_CALLER_COUNT));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledByColumnSelectionHandler());

        // function name column
        column = new TableColumn(table, SWT.LEFT);
        column.setText(COLUMN_HEAD_FUNCTION);
        column.setWidth(COLUMN_WIDTH_FUNCTION_NAME);
        column.setData(Integer.valueOf(COLUMN_ID_FUNCTION));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledByColumnSelectionHandler());

        // function start address column
        column = new TableColumn(table, SWT.CENTER);
        column.setText(COLUMN_HEAD_START_ADDR);
        column.setWidth(COLUMN_WIDTH_START_ADDRESS);
        column.setData(Integer.valueOf(COLUMN_ID_START_ADDR));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledByColumnSelectionHandler());

        // binary containing function column
        column = new TableColumn(table, SWT.LEFT);
        column.setText(COLUMN_HEAD_IN_BINARY);
        column.setWidth(COLUMN_WIDTH_IN_BINARY);
        column.setData(Integer.valueOf(COLUMN_ID_IN_BINARY));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledByColumnSelectionHandler());

        // path to binary containing function column
        column = new TableColumn(table, SWT.LEFT);
        column.setText(COLUMN_HEAD_IN_BINARY_PATH);
        column.setWidth(COLUMN_WIDTH_IN_BINARY_PATH);
        column.setData(Integer.valueOf(COLUMN_ID_IN_BINARY_PATH));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledByColumnSelectionHandler());

        // is caller percent column
        column = new TableColumn(table, SWT.RIGHT);
        column.setText(COLUMN_HEAD_IS_CALLER);
        column.setWidth(COLUMN_WIDTH_IS_CALLER);
        column.setData(Integer.valueOf(COLUMN_ID_IS_CALLER));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledByColumnSelectionHandler());

        table.setLayoutData(new GridData(GridData.FILL_BOTH));
        //      table.setLayout(new FillLayout());
        table.setHeaderVisible(true);
        table.setLinesVisible(true);
        table.setRedraw(true);

        // when a row is selected, allow the user to double-click or right click and make that function the selected function
        table.addSelectionListener(new SelectionAdapter() {

            public void widgetDefaultSelected(SelectionEvent se) {
                TableItem item = (TableItem) se.item;
                Table table = (Table) se.widget;
                CallerCalleeItem callerCalleeItem = (CallerCalleeItem) item.getData();

                // deselect this line
                table.deselectAll();

                // choose this row's function
                chooseNewFunction(callerCalleeItem);
            }
        });

        table.addMouseListener(new CallerMouseListener());

        table.addFocusListener(new FocusListener() {
            IAction oldCopyAction = null;

            public void focusGained(org.eclipse.swt.events.FocusEvent arg0) {
                IActionBars bars = PIPageEditor.getActionBars();

                // modify what is executed when Copy is called from the Edit menu
                oldCopyAction = bars.getGlobalActionHandler(ActionFactory.COPY.getId());

                bars.setGlobalActionHandler(ActionFactory.COPY.getId(), copyAction);

                copyAction.setEnabled(callerTable.getSelectionCount() > 0);
                bars.updateActionBars();

                // add to the Edit menu
                IMenuManager editMenuManager = bars.getMenuManager().findMenuUsingPath(IIDEActionConstants.M_EDIT);

                if (editMenuManager instanceof SubMenuManager) {
                    IContributionManager editManager = ((SubMenuManager) editMenuManager).getParent();
                    ActionContributionItem item;

                    editMenuManager.remove("PICopyTable"); //$NON-NLS-1$
                    copyTableAction.setEnabled(callerTable.getItemCount() > 0);
                    item = new ActionContributionItem(copyTableAction);
                    item.setVisible(true);
                    editManager.prependToGroup(IIDEActionConstants.CUT_EXT, item);
                }

                // add to the File menu
                IMenuManager fileMenuManager = bars.getMenuManager().findMenuUsingPath(IIDEActionConstants.M_FILE);

                if (fileMenuManager instanceof SubMenuManager) {
                    IContributionManager fileManager = ((SubMenuManager) fileMenuManager).getParent();
                    ActionContributionItem item;

                    fileMenuManager.remove("PISaveTable"); //$NON-NLS-1$
                    saveTableAction.setEnabled(callerTable.getItemCount() > 0);
                    item = new ActionContributionItem(saveTableAction);
                    item.setVisible(true);
                    fileManager.insertAfter("saveAll", item); //$NON-NLS-1$
                }
            }

            public void focusLost(org.eclipse.swt.events.FocusEvent arg0) {
                IActionBars bars = PIPageEditor.getActionBars();
                bars.setGlobalActionHandler(ActionFactory.COPY.getId(), oldCopyAction);
                bars.updateActionBars();

                SubMenuManager editMenuManager = (SubMenuManager) PIPageEditor.getMenuManager()
                        .find(IIDEActionConstants.M_EDIT);
                editMenuManager.remove("PICopyTable"); //$NON-NLS-1$

                SubMenuManager fileMenuManager = (SubMenuManager) PIPageEditor.getMenuManager()
                        .find(IIDEActionConstants.M_FILE);
                fileMenuManager.remove("PISaveTable"); //$NON-NLS-1$
            }
        });
    }

    private void createCalleeTableViewer(SashForm sashForm) {
        /*
         * Functions called
         * 
         *      percent of calls
         *      total percent
         *      caller percent
         *     function name
         *      function start address
         *      binary containing function
         *      sample count
         */
        Label label;
        Composite holder;
        Table table;
        TableColumn column;

        // bottom functionTable and title
        holder = new Composite(sashForm, SWT.NONE);
        //      holder.setLayout(new FillLayout(SWT.VERTICAL));
        GridLayout gridLayout = new GridLayout(1, true);
        gridLayout.marginWidth = 0;
        gridLayout.marginHeight = 0;
        holder.setLayout(gridLayout);

        label = new Label(holder, SWT.CENTER | SWT.BORDER);
        label.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_CYAN));
        label.setFont(PIPageEditor.helvetica_10);
        label.setText(Messages.getString("CallVisualiser.calledBySelectedFunction")); //$NON-NLS-1$
        label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        this.calleeTableViewer = new TableViewer(holder,
                SWT.BORDER | SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
        this.calleeTable = calleeTableViewer.getTable();

        // add the label provider and content provider
        this.calleeTableViewer.setLabelProvider(new SharedLabelProvider(this.calleeTable));
        this.calleeTableViewer.setContentProvider(new ArrayContentProvider());
        this.calleeTableViewer.setSorter(new SharedSorter());

        table = this.calleeTable;
        table.setRedraw(false);

        // give the table a heading for possible use in copying and exported
        table.setData(Messages.getString("CallVisualiser.calleeFunctions")); //$NON-NLS-1$

        // data associated with the TableViewer will note which columns contain hex values
        // Keep this in the order in which columns have been created
        boolean[] isHex = { false, false, false, true, false, false, false };
        this.calleeTable.setData("isHex", isHex); //$NON-NLS-1$

        // percent of calls column
        column = new TableColumn(table, SWT.RIGHT);
        column.setText(COLUMN_HEAD_CALLEE_PERCENT);
        column.setWidth(COLUMN_WIDTH_CALLEE_PERCENT);
        column.setData(Integer.valueOf(COLUMN_ID_CALLEE_PERCENT));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledColumnSelectionHandler());

        // sample count column
        column = new TableColumn(table, SWT.CENTER);
        column.setText(COLUMN_HEAD_IS_CALLED_COUNT2);
        column.setWidth(COLUMN_WIDTH_IS_CALLED_COUNT2);
        column.setData(Integer.valueOf(COLUMN_ID_IS_CALLED_COUNT));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledColumnSelectionHandler());

        // function name column
        column = new TableColumn(table, SWT.LEFT);
        column.setText(COLUMN_HEAD_FUNCTION);
        column.setWidth(COLUMN_WIDTH_FUNCTION_NAME);
        column.setData(Integer.valueOf(COLUMN_ID_FUNCTION));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledColumnSelectionHandler());

        // function start address column
        column = new TableColumn(table, SWT.CENTER);
        column.setText(COLUMN_HEAD_START_ADDR);
        column.setWidth(COLUMN_WIDTH_START_ADDRESS);
        //      column.setData(Integer.valueOf(COLUMN_ID_START_ADDR3));
        column.setData(Integer.valueOf(COLUMN_ID_START_ADDR));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledColumnSelectionHandler());

        // binary containing function column
        column = new TableColumn(table, SWT.LEFT);
        column.setText(COLUMN_HEAD_IN_BINARY);
        column.setWidth(COLUMN_WIDTH_IN_BINARY);
        //      column.setData(Integer.valueOf(COLUMN_ID_IN_BINARY3));
        column.setData(Integer.valueOf(COLUMN_ID_IN_BINARY));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledColumnSelectionHandler());

        // path to binary containing function column
        column = new TableColumn(table, SWT.LEFT);
        column.setText(COLUMN_HEAD_IN_BINARY_PATH);
        column.setWidth(COLUMN_WIDTH_IN_BINARY_PATH);
        //      column.setData(Integer.valueOf(COLUMN_ID_IN_BINARY_PATH3));
        column.setData(Integer.valueOf(COLUMN_ID_IN_BINARY_PATH));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledColumnSelectionHandler());

        // percent of calls column
        column = new TableColumn(table, SWT.RIGHT);
        column.setText(COLUMN_HEAD_IS_CALLED);
        column.setWidth(COLUMN_WIDTH_IS_CALLED);
        //      column.setData(Integer.valueOf(COLUMN_ID_IS_CALLED3));
        column.setData(Integer.valueOf(COLUMN_ID_IS_CALLED));
        column.setMoveable(true);
        column.setResizable(true);
        column.addSelectionListener(new CalledColumnSelectionHandler());

        table.setLayoutData(new GridData(GridData.FILL_BOTH));
        //      table.setLayout(new FillLayout());
        table.setHeaderVisible(true);
        table.setLinesVisible(true);
        table.setRedraw(true);

        // when a row is selected, allow the user to double-click or right click and make that function the selected function
        table.addSelectionListener(new SelectionAdapter() {

            public void widgetDefaultSelected(SelectionEvent se) {
                TableItem item = (TableItem) se.item;
                Table table = (Table) se.widget;
                CallerCalleeItem callerCallee = (CallerCalleeItem) item.getData();

                // deselect this row
                table.deselectAll();

                // choose this row's function
                chooseNewFunction(callerCallee);
            }
        });

        table.addMouseListener(new CalleeMouseListener());

        table.addFocusListener(new FocusListener() {
            IAction oldCopyAction = null;

            public void focusGained(org.eclipse.swt.events.FocusEvent arg0) {
                IActionBars bars = PIPageEditor.getActionBars();

                // modify what is executed when Copy is called from the Edit menu
                oldCopyAction = bars.getGlobalActionHandler(ActionFactory.COPY.getId());

                bars.setGlobalActionHandler(ActionFactory.COPY.getId(), copyAction);

                copyAction.setEnabled(calleeTable.getSelectionCount() > 0);
                bars.updateActionBars();

                // add to the Edit menu
                IMenuManager editMenuManager = bars.getMenuManager().findMenuUsingPath(IIDEActionConstants.M_EDIT);

                if (editMenuManager instanceof SubMenuManager) {
                    IContributionManager editManager = ((SubMenuManager) editMenuManager).getParent();
                    ActionContributionItem item;

                    editMenuManager.remove("PICopyTable"); //$NON-NLS-1$
                    copyTableAction.setEnabled(calleeTable.getItemCount() > 0);
                    item = new ActionContributionItem(copyTableAction);
                    item.setVisible(true);
                    editManager.prependToGroup(IIDEActionConstants.CUT_EXT, item);
                }

                // add to the File menu
                IMenuManager fileMenuManager = bars.getMenuManager().findMenuUsingPath(IIDEActionConstants.M_FILE);

                if (fileMenuManager instanceof SubMenuManager) {
                    IContributionManager fileManager = ((SubMenuManager) fileMenuManager).getParent();
                    ActionContributionItem item;

                    fileMenuManager.remove("PISaveTable"); //$NON-NLS-1$
                    saveTableAction.setEnabled(calleeTable.getItemCount() > 0);
                    item = new ActionContributionItem(saveTableAction);
                    item.setVisible(true);
                    fileManager.insertAfter("saveAll", item); //$NON-NLS-1$
                }
            }

            public void focusLost(org.eclipse.swt.events.FocusEvent arg0) {
                IActionBars bars = PIPageEditor.getActionBars();
                bars.setGlobalActionHandler(ActionFactory.COPY.getId(), oldCopyAction);
                bars.updateActionBars();

                SubMenuManager editMenuManager = (SubMenuManager) PIPageEditor.getMenuManager()
                        .find(IIDEActionConstants.M_EDIT);
                editMenuManager.remove("PICopyTable"); //$NON-NLS-1$

                SubMenuManager fileMenuManager = (SubMenuManager) PIPageEditor.getMenuManager()
                        .find(IIDEActionConstants.M_FILE);
                fileMenuManager.remove("PISaveTable"); //$NON-NLS-1$
            }
        });
    }

    private void chooseNewFunction(CallerCalleeItem callerCalleeItem) {
        // find the correct line in the function table and select it
        int count = currentFunctionTable.getItemCount();

        for (int i = 0; i < count; i++) {
            GfcFunctionItem functionItem = (GfcFunctionItem) (currentFunctionTable.getItem(i).getData());
            if (functionItem.name.equals(callerCalleeItem.item.name)
                    && functionItem.dllName.equals(callerCalleeItem.item.dllName)) {
                currentFunctionTable.select(i);
                currentFunctionTable.showSelection();
                updateCallerCalleeTables(functionItem);
                return;
            }
        }
    }

    public void updateCallerCalleeTables(GfcFunctionItem item) {
        Table table;
        TableColumn sortByColumn;

        setCallerListToFunctionsThatCallThisFunction(item);
        setCalleeListToFunctionsThisFunctionCalls(item);

        callerTableViewer.setInput(callerList);

        if ((callerList != null) && (callerList.length > 0)) {
            SharedSorter sorter = ((SharedSorter) callerTableViewer.getSorter());
            int columnID = sorter.getColumnID();

            if (columnID == -1) {
                columnID = COLUMN_ID_CALLER_PERCENT;
            } else {
                sorter.setSortAscending(!sorter.getSortAscending());
            }
            sorter.doSort(columnID);

            sortByColumn = null;
            table = callerTableViewer.getTable();
            for (int i = 0; i < table.getColumnCount(); i++) {
                if (table.getColumn(i).getData() instanceof Integer) {
                    if (((Integer) table.getColumn(i).getData()) == columnID) {
                        sortByColumn = table.getColumn(i);
                        break;
                    }
                }
            }

            if (sortByColumn != null) {
                table.setSortColumn(sortByColumn);
                table.setSortDirection(sorter.getSortAscending() ? SWT.UP : SWT.DOWN);
            }
            callerTableViewer.refresh();
        } else {
            callerTableViewer.getTable().setSortColumn(null);
        }

        calleeTableViewer.setInput(calleeList);

        if ((calleeList != null) && (calleeList.length > 0)) {
            SharedSorter sorter = ((SharedSorter) calleeTableViewer.getSorter());
            int columnID = sorter.getColumnID();

            if (columnID == -1) {
                columnID = COLUMN_ID_CALLEE_PERCENT;
            } else {
                sorter.setSortAscending(!sorter.getSortAscending());
            }
            sorter.doSort(columnID);

            sortByColumn = null;
            table = calleeTableViewer.getTable();
            for (int i = 0; i < table.getColumnCount(); i++) {
                if (table.getColumn(i).getData() instanceof Integer) {
                    if (((Integer) table.getColumn(i).getData()) == columnID) {
                        sortByColumn = table.getColumn(i);
                        break;
                    }
                }
            }

            if (sortByColumn != null) {
                table.setSortColumn(sortByColumn);
                table.setSortDirection(sorter.getSortAscending() ? SWT.UP : SWT.DOWN);
            }
            calleeTableViewer.refresh();
        } else {
            calleeTableViewer.getTable().setSortColumn(null);
        }
    }

    private class CallerMouseListener implements MouseListener {
        public void mouseDoubleClick(org.eclipse.swt.events.MouseEvent me) {
        }

        public void mouseDown(org.eclipse.swt.events.MouseEvent me) {
        }

        public void mouseUp(org.eclipse.swt.events.MouseEvent me) {
            copyAction.setEnabled(callerTable.getSelectionCount() > 0);
            copyTableAction.setEnabled(callerTable.getItemCount() > 0);

            // only look for button 3 (right click)
            if (me.button != MouseEvent.BUTTON3)
                return;

            // make the caller table the menu table
            currentMenuTable = callerTable;

            if (callerMenu != null) {
                callerMenu.dispose();
            }

            callerMenu = new Menu(callerTable.getShell(), SWT.POP_UP);

            MenuItem showCallInfoItem = new MenuItem(callerMenu, SWT.PUSH);
            showCallInfoItem.setText(Messages.getString("CallVisualiser.showCallInfo")); //$NON-NLS-1$
            showCallInfoItem.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent se) {
                    // based on the table's selected item, update the tables
                    TableItem[] selections = callerTable.getSelection();
                    callerTable.deselectAll();

                    if (selections.length != 0)
                        chooseNewFunction((CallerCalleeItem) selections[0].getData());
                }
            });

            new MenuItem(callerMenu, SWT.SEPARATOR);

            MenuItem sourceLookupItem = new MenuItem(callerMenu, SWT.PUSH);
            sourceLookupItem.setText(Messages.getString("CallVisualiser.sourcelookup")); //$NON-NLS-1$
            sourceLookupItem.addSelectionListener(new SelectionListener() {

                public void widgetDefaultSelected(SelectionEvent arg0) {
                }

                public void widgetSelected(SelectionEvent arg0) {
                    doSourceLookup(callerTable);
                }

            });

            if (callerTable.getSelectionCount() == 0) {
                showCallInfoItem.setEnabled(false);
                sourceLookupItem.setEnabled(false);
            }

            // add copy, copy all, and save all
            new MenuItem(callerMenu, SWT.SEPARATOR);
            getCopyItem(callerMenu, callerTable.getSelectionCount() > 0);
            getCopyTableItem(callerMenu, callerTable.getItemCount() > 0);
            copyAction.setEnabled(callerTable.getSelectionCount() > 0);
            copyTableAction.setEnabled(callerTable.getItemCount() > 0);

            new MenuItem(callerMenu, SWT.SEPARATOR);
            getSaveTableItem(callerMenu, callerTable.getItemCount() > 0);
            saveTableAction.setEnabled(callerTable.getItemCount() > 0);

            // save samples
            int startTime = (int) (PIPageEditor.currentPageEditor().getStartTime() * 1000.0f);
            int endTime = (int) (PIPageEditor.currentPageEditor().getEndTime() * 1000.0f);

            if ((startTime == -1) || (endTime == -1) || (startTime == endTime))
                getSaveSamplesItem(callerMenu, false); //$NON-NLS-1$
            else
                getSaveSamplesItem(callerMenu, true); //$NON-NLS-1$

            callerMenu.setLocation(callerTable.getParent().toDisplay(me.x, me.y));
            callerMenu.setVisible(true);
            callerTable.setMenu(callerMenu);
        }
    }

    private class CurrentFunctionMouseListener implements MouseListener {
        public void mouseDoubleClick(org.eclipse.swt.events.MouseEvent me) {
        }

        public void mouseDown(org.eclipse.swt.events.MouseEvent me) {
        }

        public void mouseUp(org.eclipse.swt.events.MouseEvent me) {
            copyAction.setEnabled(currentFunctionTable.getSelectionCount() > 0);
            copyTableAction.setEnabled(currentFunctionTable.getItemCount() > 0);
            functionCopyFunctionAction.setEnabled(currentFunctionTable.getSelectionCount() > 0);

            // only look for button 3 (right click)
            if (me.button != MouseEvent.BUTTON3)
                return;

            // make the current function table the menu table
            currentMenuTable = currentFunctionTable;

            if (currentFunctionMenu != null) {
                currentFunctionMenu.dispose();
            }

            currentFunctionMenu = new Menu(currentFunctionTable.getShell(), SWT.POP_UP);

            //         new MenuItem(callerMenu, SWT.SEPARATOR);

            MenuItem sourceLookupItem = new MenuItem(currentFunctionMenu, SWT.PUSH);
            sourceLookupItem.setText(Messages.getString("CallVisualiser.sourcelookup")); //$NON-NLS-1$
            sourceLookupItem.addSelectionListener(new SelectionListener() {

                public void widgetDefaultSelected(SelectionEvent arg0) {
                }

                public void widgetSelected(SelectionEvent arg0) {
                    doSourceLookup(currentFunctionTable);
                }

            });

            if (currentFunctionTable.getSelectionCount() == 0) {
                sourceLookupItem.setEnabled(false);
            }

            // add copy, copy all, copy caller/callee info, save all, save caller/callee info
            new MenuItem(currentFunctionMenu, SWT.SEPARATOR);
            getCopyItem(currentFunctionMenu, currentFunctionTable.getSelectionCount() > 0);
            getCopyTableItem(currentFunctionMenu, currentFunctionTable.getItemCount() > 0);
            getCopyFunctionItem(currentFunctionMenu, currentFunctionTable.getSelectionCount() > 0);
            copyAction.setEnabled(currentFunctionTable.getSelectionCount() > 0);
            copyTableAction.setEnabled(currentFunctionTable.getItemCount() > 0);
            functionCopyFunctionAction.setEnabled(currentFunctionTable.getSelectionCount() > 0);

            new MenuItem(currentFunctionMenu, SWT.SEPARATOR);
            getSaveTableItem(currentFunctionMenu, currentFunctionTable.getItemCount() > 0);
            getSaveFunctionItem(currentFunctionMenu, currentFunctionTable.getSelectionCount() > 0);
            saveTableAction.setEnabled(currentFunctionTable.getItemCount() > 0);
            functionSaveFunctionAction.setEnabled(currentFunctionTable.getSelectionCount() > 0);

            // save samples
            int startTime = (int) (PIPageEditor.currentPageEditor().getStartTime() * 1000.0f);
            int endTime = (int) (PIPageEditor.currentPageEditor().getEndTime() * 1000.0f);

            if ((startTime == -1) || (endTime == -1) || (startTime == endTime))
                getSaveSamplesItem(currentFunctionMenu, false); //$NON-NLS-1$
            else
                getSaveSamplesItem(currentFunctionMenu, true); //$NON-NLS-1$

            currentFunctionMenu.setLocation(currentFunctionTable.getParent().toDisplay(me.x, me.y));
            currentFunctionMenu.setVisible(true);
            currentFunctionTable.setMenu(currentFunctionMenu);
        }
    }

    private class CalleeMouseListener implements MouseListener {
        public void mouseDoubleClick(org.eclipse.swt.events.MouseEvent me) {
        }

        public void mouseDown(org.eclipse.swt.events.MouseEvent me) {
        }

        public void mouseUp(org.eclipse.swt.events.MouseEvent me) {
            copyAction.setEnabled(calleeTable.getSelectionCount() > 0);
            copyTableAction.setEnabled(calleeTable.getItemCount() > 0);

            // only look for button 3 (right click)
            if (me.button != MouseEvent.BUTTON3)
                return;

            // make the callee table the menu table
            currentMenuTable = calleeTable;

            if (calleeMenu != null) {
                calleeMenu.dispose();
            }

            calleeMenu = new Menu(calleeTable.getShell(), SWT.POP_UP);

            MenuItem showCallInfoItem = new MenuItem(calleeMenu, SWT.PUSH);
            showCallInfoItem.setText(Messages.getString("CallVisualiser.showCallInfo")); //$NON-NLS-1$
            showCallInfoItem.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent se) {
                    // based on the table's selected item, update the tables
                    TableItem[] selections = calleeTable.getSelection();
                    calleeTable.deselectAll();

                    if (selections.length != 0)
                        chooseNewFunction((CallerCalleeItem) selections[0].getData());
                }
            });

            new MenuItem(calleeMenu, SWT.SEPARATOR);

            MenuItem sourceLookupItem = new MenuItem(calleeMenu, SWT.PUSH);
            sourceLookupItem.setText(Messages.getString("CallVisualiser.sourcelookup")); //$NON-NLS-1$
            sourceLookupItem.addSelectionListener(new SelectionListener() {

                public void widgetDefaultSelected(SelectionEvent arg0) {
                }

                public void widgetSelected(SelectionEvent arg0) {
                    doSourceLookup(calleeTable);
                }

            });

            if (calleeTable.getSelectionCount() == 0) {
                showCallInfoItem.setEnabled(false);
                sourceLookupItem.setEnabled(false);
            }

            // add copy, copy all, and save all
            new MenuItem(calleeMenu, SWT.SEPARATOR);
            getCopyItem(calleeMenu, calleeTable.getSelectionCount() > 0);
            getCopyTableItem(calleeMenu, calleeTable.getItemCount() > 0);
            copyAction.setEnabled(calleeTable.getSelectionCount() > 0);
            copyTableAction.setEnabled(calleeTable.getItemCount() > 0);

            new MenuItem(calleeMenu, SWT.SEPARATOR);
            getSaveTableItem(calleeMenu, calleeTable.getItemCount() > 0);
            saveTableAction.setEnabled(calleeTable.getItemCount() > 0);

            // save samples
            int startTime = (int) (PIPageEditor.currentPageEditor().getStartTime() * 1000.0f);
            int endTime = (int) (PIPageEditor.currentPageEditor().getEndTime() * 1000.0f);

            if ((startTime == -1) || (endTime == -1) || (startTime == endTime))
                getSaveSamplesItem(calleeMenu, false); //$NON-NLS-1$
            else
                getSaveSamplesItem(calleeMenu, true); //$NON-NLS-1$

            calleeMenu.setLocation(calleeTable.getParent().toDisplay(me.x, me.y));
            calleeMenu.setVisible(true);
            calleeTable.setMenu(calleeMenu);
        }
    }

    protected void createDefaultActions(boolean copyFunction) {
        copyAction = new Action(Messages.getString("CallVisualiser.0")) { //$NON-NLS-1$
            public void run() {
                action("copy"); //$NON-NLS-1$
            }
        };
        copyAction.setEnabled(false);

        copyTableAction = new Action(Messages.getString("CallVisualiser.1")) { //$NON-NLS-1$
            public void run() {
                action("copyTable"); //$NON-NLS-1$
            }
        };
        copyTableAction.setEnabled(true);
        copyTableAction.setId("PICopyTable"); //$NON-NLS-1$
        copyTableAction.setText(Messages.getString("CallVisualiser.CopyTable")); //$NON-NLS-1$

        saveTableAction = new Action(Messages.getString("CallVisualiser.2")) { //$NON-NLS-1$
            public void run() {
                action("saveTable"); //$NON-NLS-1$
            }
        };
        saveTableAction.setEnabled(true);
        saveTableAction.setId("PISaveTable"); //$NON-NLS-1$
        saveTableAction.setText(Messages.getString("CallVisualiser.SaveTable")); //$NON-NLS-1$

        functionCopyFunctionAction = new Action(Messages.getString("CallVisualiser.3")) { //$NON-NLS-1$
            public void run() {
                action("copyFunction"); //$NON-NLS-1$
            }
        };
        functionCopyFunctionAction.setEnabled(true);
        functionCopyFunctionAction.setId("PICopyFunction"); //$NON-NLS-1$
        functionCopyFunctionAction.setText(Messages.getString("CallVisualiser.CopyDataForFunction")); //$NON-NLS-1$

        functionSaveFunctionAction = new Action(Messages.getString("CallVisualiser.4")) { //$NON-NLS-1$
            public void run() {
                action("saveFunction"); //$NON-NLS-1$
            }
        };
        functionSaveFunctionAction.setEnabled(true);
        functionSaveFunctionAction.setId("PISaveFunction"); //$NON-NLS-1$
        functionSaveFunctionAction.setText(Messages.getString("CallVisualiser.SaveDataForFunction")); //$NON-NLS-1$
    }

    private class SharedLabelProvider extends LabelProvider implements ITableLabelProvider {

        Table table;

        public SharedLabelProvider(Table table) {
            super();
            this.table = table;
        }

        public String getColumnText(Object element, int columnIndex) {
            if (!(element instanceof GfcFunctionItem) && !(element instanceof CallerCalleeItem))
                return ""; //$NON-NLS-1$

            int columnId = ((Integer) this.table.getColumn(columnIndex).getData()).intValue();

            GfcFunctionItem item = null;

            if (element instanceof GfcFunctionItem)
                item = (GfcFunctionItem) element;
            else
                item = ((CallerCalleeItem) element).item;

            switch (columnId) {
            case COLUMN_ID_CALLEE_PERCENT:
            case COLUMN_ID_CALLER_PERCENT: {
                double percent;
                if (element instanceof CallerCalleeItem)
                    percent = ((CallerCalleeItem) element).percent;
                else
                    percent = 0.0;

                // Percent load string
                return (new DecimalFormat(Messages.getString("CallVisualiser.shortDecimalFormat"))).format(percent); //$NON-NLS-1$
            }
            case COLUMN_ID_FUNCTION: {
                // Function
                return item.name;
            }
            case COLUMN_ID_START_ADDR: {
                // Function start
                return Long.toHexString(item.address);
            }
            case COLUMN_ID_IN_BINARY: {
                // Binary
                String binary = item.dllName;
                int index = binary.lastIndexOf('\\');
                if (index == -1)
                    return binary;
                else
                    return binary.substring(index + 1);
            }
            case COLUMN_ID_IN_BINARY_PATH: {
                // Path
                String binary = item.dllName;
                int index = binary.lastIndexOf('\\');
                if (index == -1)
                    return ""; //$NON-NLS-1$
                else
                    return binary.substring(0, index);
            }
            case COLUMN_ID_IS_CALLED: {
                // Percent load string
                return (new DecimalFormat(Messages.getString("CallVisualiser.decimalFormat"))) //$NON-NLS-1$
                        .format(myTrace.getAbsoluteTraditionalPercentageFor(item));
            }
            case COLUMN_ID_IS_CALLER: {
                // Percent load string
                return (new DecimalFormat(Messages.getString("CallVisualiser.decimalFormat"))) //$NON-NLS-1$
                        .format(myTrace.getAbsoluteCallerPercentageFor(item));
            }
            case COLUMN_ID_RECURSIVE_CALL: {
                // Percent load string
                return (new DecimalFormat(Messages.getString("CallVisualiser.decimalFormat"))) //$NON-NLS-1$
                        .format(myTrace.getRecursiveCallerPrecentageFor(item));
            }
            case COLUMN_ID_IS_CALLED_COUNT: {
                // Sample count
                int samples;
                if (element instanceof CallerCalleeItem)
                    samples = ((CallerCalleeItem) element).samples;
                else
                    samples = item.isCalledCount();
                return String.valueOf(samples);
            }
            case COLUMN_ID_IS_CALLER_COUNT: {
                // Sample count
                int samples;
                if (element instanceof CallerCalleeItem)
                    samples = ((CallerCalleeItem) element).samples;
                else
                    samples = item.isCallerCount();
                return String.valueOf(samples);
            }
            default: {
                break;
            }
            }
            // should never get here
            return ""; //$NON-NLS-1$
        }

        public Image getColumnImage(Object element, int columnIndex) {
            return null;
        }
    }

    public void selectFunction(String functionName) {
    }

    public void setStartAndEnd(int start, int end) {
        if (this.myTrace == null)
            return;

        this.curStart = start;
        this.curEnd = end;
        needsRefresh = true;

        if (isPageActive) {
            setTimeframeJob.cancel();
            setTimeframeJob.schedule();
        }
    }

    private static class CallerCalleeItem {
        GfcFunctionItem item;
        double percent;
        int samples;
    }

    public void setCallerListToFunctionsThatCallThisFunction(GfcFunctionItem function) {
        if (function == null) {
            this.callerList = null;
            return;
        }

        GfcFunctionItem[] list = function.getCallerList();
        Double[] perc = function.getCallerPercentages();

        this.callerList = new CallerCalleeItem[list.length];

        for (int i = 0; i < list.length; i++) {
            this.callerList[i] = new CallerCalleeItem();
            this.callerList[i].item = list[i];
            this.callerList[i].percent = perc[i];
            this.callerList[i].samples = (int) (perc[i] * function.isCalledCount() + 0.5) / 100;
        }
    }

    public void setCalleeListToFunctionsThisFunctionCalls(GfcFunctionItem function) {
        if (function == null) {
            this.calleeList = null;
            return;
        }

        GfcFunctionItem[] list = function.getCalleeList();
        Double[] perc = function.getCalleePercentages();

        this.calleeList = new CallerCalleeItem[list.length];

        for (int i = 0; i < list.length; i++) {
            this.calleeList[i] = new CallerCalleeItem();
            this.calleeList[i].item = list[i];
            this.calleeList[i].percent = perc[i];
            this.calleeList[i].samples = (int) (perc[i] * function.isCallerCount() + 0.5) / 100;
        }
    }

    public void action(String actionString) {
        if (actionString.equals("copy")) //$NON-NLS-1$
        {
            actionCopyOrSave(true, currentMenuTable, CHECKBOX_NONE, false, "\t", "\n"); //$NON-NLS-1$ //$NON-NLS-2$
            return;
        } else if (actionString.equals("copyTable")) //$NON-NLS-1$
        {
            actionCopyOrSave(true, currentMenuTable, CHECKBOX_NONE, true, "\t", "\n"); //$NON-NLS-1$ //$NON-NLS-2$
            return;
        } else if (actionString.equals("copyFunction")) //$NON-NLS-1$
        {
            actionCopyOrSaveFunction(true, "\t"); //$NON-NLS-1$
            return;
        } else if (actionString.equals("saveTable")) //$NON-NLS-1$
        {
            actionCopyOrSave(false, currentMenuTable, CHECKBOX_NONE, true, ",", "\n"); //$NON-NLS-1$ //$NON-NLS-2$
            return;
        } else if (actionString.equals("saveFunction")) //$NON-NLS-1$
        {
            actionCopyOrSaveFunction(false, ","); //$NON-NLS-1$
            return;
        } else if (actionString.equals("saveSamples")) //$NON-NLS-1$
        {
            SaveSampleString saveSampleString = new SaveSampleString();
            actionSaveSamples(saveSampleString); //$NON-NLS-1$
            return;
        } else if (actionString.equals("saveTableTest")) //$NON-NLS-1$
        {
            // copy save file contents to the clipboard for easy viewing
            Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
            SaveTableString getString = new SaveTableString(currentMenuTable, CHECKBOX_NONE, ",", "\n"); //$NON-NLS-1$ //$NON-NLS-2$
            String copyString = getString.getData();
            StringSelection contents = new StringSelection(copyString);
            cb.setContents(contents, contents);
            return;
        } else if (actionString.equals("saveFunctionTest")) //$NON-NLS-1$
        {
            // copy save file contents to the clipboard for easy viewing
            Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
            SaveFunctionString getString = new SaveFunctionString(","); //$NON-NLS-1$
            String copyString = getString.getData();
            StringSelection contents = new StringSelection(copyString);
            cb.setContents(contents, contents);
            return;
        }
    }

    /*
     * TableViewer sorter for the called-by and called function tableviewers
     */
    private class SharedSorter extends ViewerSorter {
        // sort direction
        private boolean sortAscending;

        // last column sorted
        private int column = -1;

        /* 
         * decide on which column to sort by, and the sort ordering
         */
        public void doSort(int column) {
            // ignore the column passed in and use the id set by the column selection handler
            if (column == this.column) {
                // sort in other order
                sortAscending = !sortAscending;
            } else {
                switch (column) {
                case COLUMN_ID_FUNCTION:
                case COLUMN_ID_START_ADDR:
                case COLUMN_ID_IN_BINARY:
                case COLUMN_ID_IN_BINARY_PATH: {
                    // sort in ascending order
                    sortAscending = true;
                    break;
                }
                case COLUMN_ID_IS_CALLED:
                case COLUMN_ID_IS_CALLER:
                case COLUMN_ID_RECURSIVE_CALL:
                case COLUMN_ID_CALLER_PERCENT:
                case COLUMN_ID_CALLEE_PERCENT:
                case COLUMN_ID_IS_CALLED_COUNT:
                case COLUMN_ID_IS_CALLER_COUNT: {
                    // sort in descending order
                    sortAscending = false;
                    break;
                }
                default: {
                    // ignore the column
                    return;
                }
                }
                this.column = column;
            }
        }

        /*
         * compare two items from a table column
         */
        public int compare(Viewer viewer, Object e1, Object e2) {
            int comparison = 0;

            GfcFunctionItem item1 = null;
            GfcFunctionItem item2 = null;
            CallerCalleeItem ccItem1 = null;
            CallerCalleeItem ccItem2 = null;

            if (e1 instanceof GfcFunctionItem) {
                item1 = (GfcFunctionItem) e1;
                item2 = (GfcFunctionItem) e2;
            } else {
                ccItem1 = (CallerCalleeItem) e1;
                ccItem2 = (CallerCalleeItem) e2;
                item1 = ccItem1.item;
                item2 = ccItem2.item;
            }

            switch (column) {
            case COLUMN_ID_CALLER_PERCENT:
            case COLUMN_ID_CALLEE_PERCENT: {
                double percent1;
                double percent2;
                if (e1 instanceof GfcFunctionItem) {
                    percent1 = 0.0;
                    percent2 = 0.0;
                } else {
                    percent1 = ccItem1.percent;
                    percent2 = ccItem2.percent;
                }
                comparison = percent1 > percent2 ? 1 : -1;
                break;
            }
            case COLUMN_ID_FUNCTION: {
                comparison = this.getComparator().compare(item1.name, item2.name);
                break;
            }
            case COLUMN_ID_START_ADDR: {
                comparison = (item1.address > item2.address) ? 1 : -1;
                break;
            }
            case COLUMN_ID_IN_BINARY: {
                int index;
                String name1 = item1.dllName;
                index = name1.lastIndexOf('\\');
                if (index != -1)
                    name1 = name1.substring(index);

                String name2 = item2.dllName;
                index = name2.lastIndexOf('\\');
                if (index != -1)
                    name2 = name2.substring(index);

                comparison = this.getComparator().compare(name1, name2);
                break;
            }
            case COLUMN_ID_IN_BINARY_PATH: {
                int index;
                String name1 = item1.dllName;
                index = name1.lastIndexOf('\\');
                if (index == -1)
                    name1 = ""; //$NON-NLS-1$
                else
                    name1 = name1.substring(0, index);

                String name2 = item2.dllName;
                index = name2.lastIndexOf('\\');
                if (index == -1)
                    name2 = ""; //$NON-NLS-1$
                else
                    name2 = name2.substring(0, index);

                comparison = this.getComparator().compare(name1, name2);
                break;
            }
            case COLUMN_ID_IS_CALLED: {
                // actual sample count used as a proxy for the percentage
                comparison = item1.isCalledCount() - item2.isCalledCount();
                break;
            }
            case COLUMN_ID_IS_CALLER: {
                // actual sample count used as a proxy for the percentage
                comparison = item1.isCallerCount() - item2.isCallerCount();
                break;
            }
            case COLUMN_ID_RECURSIVE_CALL: {
                comparison = myTrace.getRecursiveCallerPrecentageFor(item1) > myTrace
                        .getRecursiveCallerPrecentageFor(item2) ? 1 : -1;
                break;
            }
            case COLUMN_ID_IS_CALLED_COUNT: {
                if (e1 instanceof GfcFunctionItem) {
                    comparison = item1.isCalledCount() - item2.isCalledCount();
                } else {
                    comparison = ccItem1.samples - ccItem2.samples;
                }
                break;
            }
            case COLUMN_ID_IS_CALLER_COUNT: {
                if (e1 instanceof GfcFunctionItem) {
                    comparison = item1.isCallerCount() - item2.isCallerCount();
                } else {
                    comparison = ccItem1.samples - ccItem2.samples;
                }
                break;
            }
            default: {
                break;
            }
            }

            // for descending order, reverse the sense of the compare
            if (!sortAscending)
                comparison = -comparison;
            return comparison;
        }

        public boolean getSortAscending() {
            return sortAscending;
        }

        public void setSortAscending(boolean sortAscending) {
            this.sortAscending = sortAscending;
        }

        public int getColumnID() {
            return column;
        }
    }

    public void sortAndRefresh(TableViewer tableViewer, TableColumn tableColumn) {
        Table table = tableViewer.getTable();
        int columnID = ((Integer) tableColumn.getData()).intValue();

        if (table.getItemCount() == 0)
            return;

        // sort by selected columnID
        ((SharedSorter) tableViewer.getSorter()).doSort(columnID);
        table.setSortColumn(tableColumn);
        table.setSortDirection(((SharedSorter) tableViewer.getSorter()).getSortAscending() ? SWT.UP : SWT.DOWN);
        tableViewer.refresh();
    }

    private class CalledByColumnSelectionHandler extends SelectionAdapter {
        public void widgetSelected(SelectionEvent e) {
            if (!(e.widget instanceof TableColumn))
                return;

            sortAndRefresh(callerTableViewer, (TableColumn) e.widget);
        }
    }

    private class CalledColumnSelectionHandler extends SelectionAdapter {
        public void widgetSelected(SelectionEvent e) {
            if (!(e.widget instanceof TableColumn))
                return;

            sortAndRefresh(calleeTableViewer, (TableColumn) e.widget);
        }
    }

    private class CheckboxColumnSelectionHandler extends SelectionAdapter {
        public void widgetSelected(SelectionEvent e) {
            if (!(e.widget instanceof TableColumn))
                return;

            sortAndRefresh(currentFunctionTableViewer, (TableColumn) e.widget);
        }
    }

    public PIPageEditor getPageEditor() {
        return this.pageEditor;
    }

    public int getPageIndex() {
        return this.pageIndex;
    }

    private MenuItem getCopyFunctionItem(Menu menu, boolean enabled) {
        MenuItem copyFunctionItem = new MenuItem(menu, SWT.PUSH);

        copyFunctionItem.setText(Messages.getString("CallVisualiser.CopyDataForFunction")); //$NON-NLS-1$
        copyFunctionItem.setEnabled(enabled);

        if (enabled) {
            copyFunctionItem.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent e) {
                    action("copyFunction"); //$NON-NLS-1$
                }
            });
        }

        return copyFunctionItem;
    }

    private MenuItem getSaveFunctionItem(Menu menu, boolean enabled) {
        MenuItem saveFunctionItem = new MenuItem(menu, SWT.PUSH);

        saveFunctionItem.setText(Messages.getString("CallVisualiser.SaveDataForFunction")); //$NON-NLS-1$
        saveFunctionItem.setEnabled(enabled);

        if (enabled) {
            saveFunctionItem.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent e) {
                    action("saveFunction"); //$NON-NLS-1$
                }
            });
        }

        return saveFunctionItem;
    }

    private void doSourceLookup(Table mytable) {
        // source look up for selected items
        if ((mytable.getItemCount() == 0) || (mytable.getSelectionCount() == 0))
            return;

        TableItem selectedItem = mytable.getSelection()[0];
        Object selectedData = selectedItem.getData();
        String functionName = null;
        String binaryName = null;
        if (selectedData instanceof GfcFunctionItem) {
            GfcFunctionItem functionItem = (GfcFunctionItem) selectedData;
            functionName = functionItem.name;
            binaryName = functionItem.dllName;
        } else if (selectedData instanceof CallerCalleeItem) {
            CallerCalleeItem callerCalleeItem = (CallerCalleeItem) selectedData;
            functionName = callerCalleeItem.item.name;
            binaryName = callerCalleeItem.item.dllName;
        }
        if (functionName != null && binaryName != null) {
            SourceLookup.getInstance().lookupAndopenEditorWithHighlight(functionName, binaryName);
        }

    }

    // class to pass the function caller/callee data to the save wizard
    private class SaveFunctionString implements ISaveTable {
        private String separator;

        public SaveFunctionString(String separator) {
            this.separator = separator;
        }

        public String getData() {
            return copyFunction(separator);
        }
    }

    private void actionCopyOrSaveFunction(boolean doCopy, String separator) {
        // one function must be selected
        if (currentFunctionTable.getSelectionCount() != 1)
            return;

        // copy one function's caller and callee data to the clipboard or save to a file
        if (doCopy) {
            // change the clipboard contents
            Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
            String copyString = copyFunction(separator);
            StringSelection contents = new StringSelection(copyString);
            cb.setContents(contents, contents);
        } else {
            SaveFunctionString getString = new SaveFunctionString(separator);
            WizardDialog dialog;
            Wizard w = new SaveTableWizard(getString);
            dialog = new WizardDialog(PlatformUI.getWorkbench().getDisplay().getActiveShell(), w);
            dialog.open();
        }
    }

    private String copyFunction(String separator) {
        // one function must be selected
        if (currentFunctionTable.getSelectionCount() != 1)
            return ""; //$NON-NLS-1$

        String copyString = ""; //$NON-NLS-1$

        // create the multiple table heading line (e.g., "Selected Function     Callers")
        // space them out based on how many columns are in their table
        if (currentFunctionTable.getData() instanceof String) {
            copyString += (String) (currentFunctionTable.getData());
        }
        for (int j = 0; j < currentFunctionTable.getColumnCount(); j++) {
            copyString += separator;
        }

        if (callerTable.getData() instanceof String) {
            copyString += (String) (callerTable.getData());
        }
        for (int j = 0; j < callerTable.getColumnCount(); j++) {
            copyString += separator;
        }

        if (calleeTable.getData() instanceof String) {
            copyString += (String) (calleeTable.getData());
        }
        for (int j = 0; j < calleeTable.getColumnCount(); j++) {
            copyString += separator;
        }

        copyString += "\n"; //$NON-NLS-1$

        // create the multiple table column headings
        copyString += copyHeading(currentFunctionTable, CHECKBOX_NONE, separator, separator); //$NON-NLS-1$
        copyString += copyHeading(callerTable, CHECKBOX_NONE, separator, separator); //$NON-NLS-1$
        copyString += copyHeading(calleeTable, CHECKBOX_NONE, separator, "\n"); //$NON-NLS-1$

        // determine the row, column count, and column ordering in each table
        // NOTE: the first table in the copy will contain the function, and it only its
        // one selected line will be shown
        int rowCount0 = 1;
        int columnCount0 = currentFunctionTable.getColumnCount();
        int[] columnOrder0 = currentFunctionTable.getColumnOrder();
        boolean[] isHex0 = (boolean[]) currentFunctionTable.getData("isHex"); //$NON-NLS-1$
        String emptyRow0 = ""; //$NON-NLS-1$

        int rowCount1 = callerTable.getItemCount();
        int columnCount1 = callerTable.getColumnCount();
        int[] columnOrder1 = callerTable.getColumnOrder();
        boolean[] isHex1 = (boolean[]) callerTable.getData("isHex"); //$NON-NLS-1$
        String emptyRow1 = ""; //$NON-NLS-1$

        int rowCount2 = calleeTable.getItemCount();
        int columnCount2 = calleeTable.getColumnCount();
        int[] columnOrder2 = calleeTable.getColumnOrder();
        boolean[] isHex2 = (boolean[]) calleeTable.getData("isHex"); //$NON-NLS-1$
        String emptyRow2 = ""; //$NON-NLS-1$

        // determine the number of multiple table rows (max of any table's rows) 
        int rowCount = rowCount0 >= rowCount1 ? rowCount0 : rowCount1;
        rowCount = rowCount2 > rowCount ? rowCount2 : rowCount;

        // generate empty row strings, to speed things up
        if (rowCount0 < rowCount) {
            for (int j = 0; j < columnCount0 - 1; j++) {
                emptyRow0 += separator;
            }
        }

        if (rowCount1 < rowCount) {
            for (int j = 0; j < columnCount1; j++) {
                emptyRow1 += separator;
            }
        }

        if (rowCount2 < rowCount) {
            for (int j = 0; j < columnCount2; j++) {
                emptyRow2 += separator;
            }
        }

        // generate the rows
        for (int i = 0; i < rowCount; i++) {
            if (i < 1) {
                copyString += copyRow(isHex0,
                        currentFunctionTable.getItem(currentFunctionTable.getSelectionIndex()), CHECKBOX_NONE,
                        columnCount0, columnOrder0, separator);
            } else {
                copyString += emptyRow0;
            }

            if (i < rowCount1) {
                copyString += separator + copyRow(isHex1, callerTable.getItem(i), CHECKBOX_NONE, columnCount1,
                        columnOrder1, separator);
            } else {
                // NOTE: if this is the last table, or the 3rd table has nothing but empty
                // rows left, we may not need to fill in these fields
                copyString += emptyRow1;
            }

            if (i < rowCount2) {
                copyString += separator + copyRow(isHex2, calleeTable.getItem(i), CHECKBOX_NONE, columnCount2,
                        columnOrder2, separator);
            } else {
                // NOTE: we may not need to fill in the empty fields of the last table
                copyString += emptyRow2;
            }

            copyString += "\n"; //$NON-NLS-1$
        }

        return copyString;
    }

    private void createSetTimeframeJob() {
        //this functionality used to be in the setStartEnd() method
        //but now the long-running calculations are in a background job
        //and the short-running refresh of table viewers is done in the UI thread

        setTimeframeJob = new Job("Updating function hierarchy...") {
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                //run the time consuming calculations in the background
                if (myTrace == null) {
                    return Status.CANCEL_STATUS;
                }

                needsRefresh = false;
                myTrace.parseEntries(curStart, curEnd);
                functionArray = myTrace.getEntriesSorted(GfcTrace.SORT_BY_TOTAL_LOAD);

                return Status.OK_STATUS;
            }
        };

        setTimeframeJob.addJobChangeListener(new JobChangeAdapter() {
            public void done(IJobChangeEvent event) {
                if (event.getResult().isOK()) {
                    Display.getDefault().syncExec(new Runnable() {
                        public void run() {

                            if (!currentFunctionTableViewer.getControl().isDisposed()) {

                                //updating the table viewers has to be done in the UI thread (this operation doesn't take long)
                                currentFunctionTableViewer.setInput(functionArray);

                                updateCallerCalleeTables(null);

                                Table table = currentFunctionTableViewer.getTable();

                                if (table.getItemCount() == 0)
                                    return;

                                if (table.getSortColumn() == null) {
                                    table.setSortColumn(currentFunctionDefaultColumn);
                                    table.setSortDirection(SWT.UP);
                                } else {
                                    // use the user's preferred sort column, if any
                                    boolean sortAscending = !((SharedSorter) currentFunctionTableViewer.getSorter())
                                            .getSortAscending();
                                    ((SharedSorter) currentFunctionTableViewer.getSorter())
                                            .setSortAscending(sortAscending);
                                }

                                sortAndRefresh(currentFunctionTableViewer, table.getSortColumn());

                            }
                        }
                    });

                } else {
                    //unsuccessful operation: we still need to refresh
                    needsRefresh = true;
                }
            }
        });

        setTimeframeJob.setUser(true); //show a progress dialog to the user

        currentFunctionTableViewer.getControl().addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                setTimeframeJob.cancel();
            }
        });
    }

    private void createPageListeners() {
        final IPageChangedListener pageChangeListener = new IPageChangedListener() {

            /* (non-Javadoc)
             * @see org.eclipse.jface.dialogs.IPageChangedListener#pageChanged(org.eclipse.jface.dialogs.PageChangedEvent)
             */
            public void pageChanged(PageChangedEvent event) {
                isPageActive = (event.getSelectedPage() == CallVisualiser.this.curPage);//compare on reference
                if (isPageActive && needsRefresh) {
                    // if this ProfileVisualiser is the page being activated, 
                    // check if the time frame selection needs updating
                    setTimeframeJob.cancel();
                    setTimeframeJob.schedule();
                }

            }

        };
        final IPartListener partListener = new IPartListener() {

            public void partActivated(IWorkbenchPart part) {
                if (part instanceof PIPageEditor) {
                    PIPageEditor editor = (PIPageEditor) part;
                    isPageActive = (editor.getActivePage() == pageIndex);
                    if (isPageActive && needsRefresh) {
                        setTimeframeJob.cancel();
                        setTimeframeJob.schedule();
                    }
                }
            }

            public void partBroughtToTop(IWorkbenchPart part) {
                // nothing to do
            }

            public void partClosed(IWorkbenchPart part) {
                if (part instanceof PIPageEditor) {
                    //remove listeners
                    PIPageEditor editor = (PIPageEditor) part;
                    editor.removePageChangedListener(pageChangeListener);
                    editor.getSite().getPage().removePartListener(this);
                }
            }

            public void partDeactivated(IWorkbenchPart part) {
                // nothing to do
            }

            public void partOpened(IWorkbenchPart part) {
                // nothing to do
            }

        };

        PIPageEditor.currentPageEditor().addPageChangedListener(pageChangeListener);
        PIPageEditor.currentPageEditor().getSite().getPage().addPartListener(partListener);

    }

    // added to give JUnit tests access
    public TableViewer getCallerViewer() {
        return this.callerTableViewer;
    }

    // added to give JUnit tests access
    public TableViewer getCurrentFunctionViewer() {
        return this.currentFunctionTableViewer;
    }

    // added to give JUnit tests access
    public TableViewer getCalleeViewer() {
        return this.calleeTableViewer;
    }

    // added to give JUnit tests access
    public void setCurrentMenuTable(Table currentMenuTable) {
        this.currentMenuTable = currentMenuTable;
    }

}