sysperfana/perfinvestigator/com.nokia.carbide.cpp.pi.call/src/com/nokia/carbide/cpp/pi/call/CallVisualiser.java
changeset 2 b9ab3b238396
child 5 844b047e260d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sysperfana/perfinvestigator/com.nokia.carbide.cpp.pi.call/src/com/nokia/carbide/cpp/pi/call/CallVisualiser.java	Thu Feb 11 15:32:31 2010 +0200
@@ -0,0 +1,2020 @@
+/*
+ * 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.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.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.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.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.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;
+	
+	// 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 static int SAMPLES_AT_ONE_TIME = 1000;
+	
+	// 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.callerFunctionItt != null
+								? sample.callerFunctionItt.functionName
+								: sample.callerFunctionSym.functionName)
+							+ "\"," //$NON-NLS-1$
+							+ (sample.callerFunctionItt != null
+								? sample.callerFunctionItt.functionBinary.binaryName
+								: sample.callerFunctionSym.functionBinary.binaryName)
+							+ ",0x" //$NON-NLS-1$
+							+ Long.toHexString(sample.programCounter)
+							+ ",\"" //$NON-NLS-1$
+							+ (sample.currentFunctionItt != null
+								? sample.currentFunctionItt.functionName
+								: sample.currentFunctionSym.functionName)
+							+ "\"," //$NON-NLS-1$
+							+ (sample.currentFunctionItt != null
+								? sample.currentFunctionItt.functionBinary.binaryName
+								: sample.currentFunctionSym.functionBinary.binaryName)
+							+ "\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)
+	{
+		this.pageEditor = pageEditor;
+		this.pageIndex  = pageIndex;
+		this.parent     = parent;
+		this.myTrace    = trace;
+		
+		// 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);
+	}
+
+	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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(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(new Integer(COLUMN_ID_START_ADDR3));
+		column.setData(new Integer(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(new Integer(COLUMN_ID_IN_BINARY3));
+		column.setData(new Integer(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(new Integer(COLUMN_ID_IN_BINARY_PATH3));
+		column.setData(new Integer(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(new Integer(COLUMN_ID_IS_CALLED3));
+		column.setData(new Integer(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"))).format(myTrace.getAbsoluteTraditionalPercentageFor(item)); //$NON-NLS-1$
+				}
+				case COLUMN_ID_IS_CALLER:
+				{
+					// Percent load string
+					return (new DecimalFormat(Messages.getString("CallVisualiser.decimalFormat"))).format(myTrace.getAbsoluteCallerPercentageFor(item)); //$NON-NLS-1$
+				}
+				case COLUMN_ID_RECURSIVE_CALL:
+				{
+					// Percent load string
+					return (new DecimalFormat(Messages.getString("CallVisualiser.decimalFormat"))).format(myTrace.getRecursiveCallerPrecentageFor(item)); //$NON-NLS-1$
+				}
+				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.myTrace.parseEntries(start, end);
+	    this.functionArray = myTrace.getEntriesSorted(GfcTrace.SORT_BY_TOTAL_LOAD);
+	    this.currentFunctionTableViewer.setInput(this.functionArray);
+		
+	    updateCallerCalleeTables(null);
+	    
+	    Table table = this.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(this.currentFunctionTableViewer, table.getSortColumn());
+	}
+
+	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;
+	}
+	
+    // 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;
+	}
+}