sysperfana/perfinvestigator/com.nokia.carbide.cpp.pi.address/src/com/nokia/carbide/cpp/pi/address/AddrThreadTable.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.address/src/com/nokia/carbide/cpp/pi/address/AddrThreadTable.java	Thu Feb 11 15:32:31 2010 +0200
@@ -0,0 +1,2541 @@
+/*
+ * 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.address;
+
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.FocusEvent;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Decorations;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.swt.widgets.Sash;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+
+import com.nokia.carbide.cpp.internal.pi.analyser.NpiInstanceRepository;
+import com.nokia.carbide.cpp.internal.pi.analyser.ProfileVisualiser;
+import com.nokia.carbide.cpp.internal.pi.interfaces.ISaveSamples;
+import com.nokia.carbide.cpp.internal.pi.model.ProfiledBinary;
+import com.nokia.carbide.cpp.internal.pi.model.ProfiledFunction;
+import com.nokia.carbide.cpp.internal.pi.model.ProfiledGeneric;
+import com.nokia.carbide.cpp.internal.pi.model.ProfiledThread;
+import com.nokia.carbide.cpp.internal.pi.model.ProfiledThreshold;
+import com.nokia.carbide.cpp.internal.pi.visual.Defines;
+import com.nokia.carbide.cpp.internal.pi.visual.PIEvent;
+import com.nokia.carbide.cpp.internal.pi.visual.PIVisualSharedData;
+import com.nokia.carbide.cpp.pi.editors.PIPageEditor;
+import com.nokia.carbide.cpp.pi.util.TableColorPalette;
+
+
+public class AddrThreadTable extends GenericAddrTable
+{
+	// local copy of profiled threads, which we can sort
+	// without affecting the original
+	Vector<ProfiledGeneric> profiledThreads = new Vector<ProfiledGeneric>();
+
+	// changes made to support priority plugin
+	private boolean priorityAdded = false;
+	private Hashtable<Integer,String> priorityTable;
+	private Hashtable<Integer,Integer> priorityValues;
+
+	public AddrThreadTable(GppTraceGraph myGraph, Composite parent)
+	{
+		this.myGraph = myGraph;
+		this.parent  = parent;
+	}
+
+	public void createTableViewer(int drawMode)
+	{
+		if (this.parent == null)
+			return;
+
+		// Thread table:
+		//		checkbox + colored or white background
+		//  	percent load
+		//  	thread name
+		//		sample count
+		//		priority (optional)
+
+		// Check the drawMode, and use it to decide whether or not to show a color column
+		// or the number of samples
+		switch (drawMode)
+		{
+			case Defines.THREADS:
+			case Defines.BINARIES_THREADS:
+			case Defines.BINARIES_FUNCTIONS_THREADS:
+			case Defines.FUNCTIONS_THREADS:
+			case Defines.FUNCTIONS_BINARIES_THREADS:
+			case Defines.THREADS_FUNCTIONS:
+			case Defines.THREADS_FUNCTIONS_BINARIES:
+			case Defines.THREADS_BINARIES:
+			case Defines.THREADS_BINARIES_FUNCTIONS:
+			case Defines.BINARIES_THREADS_FUNCTIONS:
+			case Defines.FUNCTIONS_THREADS_BINARIES:
+			{
+				break;
+			}
+			default:
+				// no thread table in this draw mode
+				return;
+		}
+
+		// create the table viewer
+		this.tableViewer = CheckboxTableViewer.newCheckList(this.parent,
+  				SWT.BORDER | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
+
+		// add the check state handler, label provider and content provider
+		tableViewer.addCheckStateListener(new SharedCheckHandler());
+		tableViewer.setLabelProvider(new shownThreadsLabelProvider());
+		tableViewer.setContentProvider(new shownThreadsContentProvider());
+		tableViewer.addSelectionChangedListener(new ISelectionChangedListener() {
+			public void selectionChanged(SelectionChangedEvent arg0) {
+				if (copyAction == null)
+					return;
+
+				// when selection changes, the ability to copy may change
+				copyAction.setEnabled(table.getSelectionCount() > 0);
+				PIPageEditor.getActionBars().updateActionBars();
+			}
+		});
+
+		createDefaultActions();
+		
+		// make sure the table viewer has a sorter
+		if (this.sorter == null)
+			this.sorter = new GppTableSorter();
+
+		this.table = tableViewer.getTable();
+		this.table.setRedraw(false);
+
+		// give the table a heading for use in copying and exported
+		this.table.setData(Messages.getString("AddrThreadTable.threads")); //$NON-NLS-1$
+		
+		// create the columns
+		TableColumn column;
+
+		// data associated with the TableViewer will note which columns contain hex values
+		// Keep this in the order in which columns have been created
+		// Includes the priority column
+		boolean[] isHex = {false, false, false, false, false};
+		this.table.setData("isHex", isHex); //$NON-NLS-1$
+
+		// select/deselect column
+		column = new TableColumn(table, SWT.CENTER);
+		column.setText(COLUMN_HEAD_SHOW);
+		column.setWidth(COLUMN_WIDTH_SHOW);
+		column.setData(new Integer(COLUMN_ID_SHOW));
+		column.setMoveable(true);
+		column.setResizable(true);
+		column.addSelectionListener(new ColumnSelectionHandler());
+
+		// percent load column
+		column = new TableColumn(table, SWT.RIGHT);
+		column.setText(COLUMN_HEAD_PERCENT_LOAD);
+		column.setWidth(COLUMN_WIDTH_PERCENT_LOAD);
+		column.setData(new Integer(COLUMN_ID_PERCENT_LOAD));
+		column.setMoveable(true);
+		column.setResizable(true);
+		column.addSelectionListener(new ColumnSelectionHandler());
+
+		// thread name column
+		column = new TableColumn(table, SWT.LEFT);
+		column.setText(COLUMN_HEAD_THREAD);
+		column.setWidth(COLUMN_WIDTH_THREAD_NAME);
+		column.setData(new Integer(COLUMN_ID_THREAD));
+		column.setMoveable(true);
+		column.setResizable(true);
+		column.addSelectionListener(new ColumnSelectionHandler());
+
+		// sample count column
+		column = new TableColumn(table, SWT.CENTER);
+		column.setText(COLUMN_HEAD_SAMPLE_COUNT);
+		column.setWidth(COLUMN_WIDTH_SAMPLE_COUNT);
+		column.setData(new Integer(COLUMN_ID_SAMPLE_COUNT));
+		column.setMoveable(true);
+		column.setResizable(true);
+		column.addSelectionListener(new ColumnSelectionHandler());
+
+		// priority column
+		if (priorityAdded)
+			this.addPriorityColumn();
+
+		// listen for mouse clicks: to select a row, pop up a menu, etc.
+		table.addMouseListener(new TableMouseListener());
+
+		// listen for key sequences such as Ctrl-A and Ctrl-C
+		table.addKeyListener(new TableKeyListener());
+		
+		table.addFocusListener(new AddrTableFocusListener());
+		
+		// add form data in case later we add a sash to the right
+		FormData viewerData = new FormData();
+		viewerData.top    = new FormAttachment(0);
+		viewerData.bottom = new FormAttachment(100);
+		viewerData.left   = new FormAttachment(0);
+		viewerData.right  = new FormAttachment(100);
+		table.setLayoutData(viewerData);
+		table.setLayout(new FormLayout());
+
+		table.setHeaderVisible(true);
+		table.setLinesVisible(true);
+		table.setRedraw(true);
+	}
+
+	public void setTableViewer(CheckboxTableViewer tableViewer)
+	{
+		this.tableViewer = tableViewer;
+
+		if (tableViewer == null)
+			this.table = null;
+	}
+
+	public void setTableViewer(int drawMode)
+	{
+		if (this.parent == null)
+			return;
+
+		createTableViewer(drawMode);
+
+		// sort by percent load
+		this.sortColumn = COLUMN_ID_SAMPLE_COUNT;
+		this.sortAscending = false;
+
+		// profiledThreads and tableItemData contain one entry per table row
+		updateProfiledAndItemData(false);
+		quickSort(sortColumn, profiledThreads);
+
+		// initially, all rows are checked
+		this.tableViewer.setAllChecked(true);
+	}
+
+	public void refreshTableViewer()
+	{
+		if (this.tableViewer == null)
+			return;
+
+		this.tableViewer.setInput(tableItemData);
+		
+		addColor(this.myGraph.getDrawMode());
+	}
+	
+	public void addColor(int drawMode)
+	{
+		if (this.tableViewer == null)
+			return;
+
+		// make sure that this table's colors are being shown
+		if (   (drawMode != Defines.THREADS)
+			&& (drawMode != Defines.BINARIES_THREADS)
+			&& (drawMode != Defines.BINARIES_FUNCTIONS_THREADS)
+			&& (drawMode != Defines.FUNCTIONS_THREADS)
+			&& (drawMode != Defines.FUNCTIONS_BINARIES_THREADS))
+			return;
+
+		ProfiledGeneric pGeneric;
+
+		TableItem[] items = this.table.getItems();
+
+		for (int i = 0; i < items.length; i++) {
+			pGeneric = (ProfiledGeneric) items[i].getData();
+//			Color color = ((GppTrace)this.myGraph.getTrace()).getThreadColorPalette().getColor(pGeneric.getNameString());
+			items[i].setBackground(COLOR_COLUMN_INDEX, pGeneric.getColor());
+		}
+
+		table.redraw();
+	}
+	
+	public void removeColor(int drawMode)
+	{
+		if (this.tableViewer == null)
+			return;
+
+		// make sure that this table's colors should not be shown
+		if (   (drawMode == Defines.THREADS)
+			|| (drawMode == Defines.BINARIES_THREADS)
+			|| (drawMode == Defines.BINARIES_FUNCTIONS_THREADS)
+			|| (drawMode == Defines.FUNCTIONS_THREADS)
+			|| (drawMode == Defines.FUNCTIONS_BINARIES_THREADS))
+			return;
+
+		TableItem[] items = this.table.getItems();
+
+		for (int i = 0; i < items.length; i++) {
+			items[i].setBackground(COLOR_COLUMN_INDEX, this.parent.getDisplay().getSystemColor(SWT.COLOR_WHITE));
+		}
+
+		table.redraw();
+	}
+
+	private static class shownThreadsContentProvider implements IStructuredContentProvider {
+
+		public shownThreadsContentProvider() {
+			super();
+		}
+
+		public Object[] getElements(Object inputElement) {
+			return ((Vector) inputElement).toArray();
+		}
+
+		public void dispose() {
+		}
+
+		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+		}
+	}
+
+	private class shownThreadsLabelProvider extends LabelProvider implements ITableLabelProvider {
+
+        public shownThreadsLabelProvider() {
+			super();
+		}
+
+		public String getColumnText(Object element, int columnIndex) {
+
+			int graphIndex = myGraph.getGraphIndex();
+	        int columnId = ((Integer) table.getColumn(columnIndex).getData()).intValue();
+
+			if (element instanceof ProfiledThreshold) {
+				ProfiledThreshold pThreshold = (ProfiledThreshold) element;
+
+				switch (columnId)
+				{
+					case COLUMN_ID_SHOW:
+					{
+						return SHOW_ITEM_VALUE;
+					}
+					case COLUMN_ID_PERCENT_LOAD:
+					{
+						// Percent load string
+						double startTime = PIPageEditor.currentPageEditor().getStartTime();
+						double endTime   = PIPageEditor.currentPageEditor().getEndTime();
+						if (   (startTime == -1)
+							|| (endTime   == -1)
+							|| (startTime == endTime)) {
+							pThreshold.setAverageLoadValueString(graphIndex, ""); //$NON-NLS-1$
+						} else {
+							float load = (float) (pThreshold.getSampleCount(graphIndex)/(endTime - startTime)/10.0);
+							
+							if (load < 0.005)
+								pThreshold.setAverageLoadValueString(graphIndex, Messages.getString("AddrThreadTable.zeroFormat")); //$NON-NLS-1$
+							else
+								pThreshold.setAverageLoadValueString(graphIndex, load);
+						}
+						return pThreshold.getAverageLoadValueString(graphIndex);
+					}
+					case COLUMN_ID_THREAD:
+					{
+						DecimalFormat timeFormat = new DecimalFormat(Messages.getString("AddrThreadTable.decimalFormat")); //$NON-NLS-1$
+
+						int count = pThreshold.getItemCount(graphIndex);
+
+						return count + (count > 1 ? Messages.getString("AddrThreadTable.thresholdMsg1") : Messages.getString("AddrThreadTable.thresholdMsg2"))   //$NON-NLS-1$ //$NON-NLS-2$
+								+ timeFormat.format((Double)NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.thresholdLoadThread") * 100.0) + Messages.getString("AddrThreadTable.thresholdMsg3") //$NON-NLS-1$ //$NON-NLS-2$ 
+								+ (Integer)NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.thresholdCountThread") + Messages.getString("AddrThreadTable.thresholdMsg4"); //$NON-NLS-1$ //$NON-NLS-2$ 
+					}
+					case COLUMN_ID_SAMPLE_COUNT:
+					{
+						// Sample count
+						return String.valueOf(pThreshold.getSampleCount(graphIndex));
+					}
+					default:
+					{
+						return ""; //$NON-NLS-1$
+					}
+				}
+			}
+
+			if (!(element instanceof ProfiledThread))
+				return ""; //$NON-NLS-1$
+
+			ProfiledThread profiledItem = (ProfiledThread) element;
+
+			switch (columnId)
+			{
+				case COLUMN_ID_SHOW:
+				{
+					return SHOW_ITEM_VALUE;
+				}
+				case COLUMN_ID_PERCENT_LOAD:
+				{
+					// Percent load string
+					return profiledItem.getAverageLoadValueString(graphIndex);
+				}
+				case COLUMN_ID_THREAD:
+				{
+					// Thread
+					if (profiledItem.getNameString().startsWith("*Native*")) //$NON-NLS-1$
+						return Messages.getString("AddrThreadTable.NOSthreads"); //$NON-NLS-1$
+					else
+						return profiledItem.getNameString();
+				}
+				case COLUMN_ID_SAMPLE_COUNT:
+				{
+					// Sample count
+					return String.valueOf(profiledItem.getSampleCount(graphIndex));
+				}
+				case COLUMN_ID_PRIORITY:
+			    {
+					if (priorityAdded) {
+				        String priority = priorityTable.get(new Integer(profiledItem.getThreadId()));
+				        if (priority == null)
+				        	return Messages.getString("AddrThreadTable.unknownPriority"); //$NON-NLS-1$
+				        else
+				        	return priority;
+					}
+					return ""; //$NON-NLS-1$
+			    }
+				default:
+				{
+					break;
+				}
+			}
+			// should never get here
+			return ""; //$NON-NLS-1$
+		}
+
+		public Image getColumnImage(Object element, int columnIndex) {
+			return null;
+		}
+	}
+
+    public void action(String actionString)
+	{
+		int graphIndex = this.myGraph.getGraphIndex();
+
+		if (   actionString.equals("add") //$NON-NLS-1$
+			|| actionString.equals("remove")) //$NON-NLS-1$
+	    {
+			actionAddRemove(actionString, graphIndex);
+			return;
+	    }
+		else if (   actionString.equals("addall") //$NON-NLS-1$
+				 || actionString.equals("removeall")) //$NON-NLS-1$
+	    {
+			actionAddRemoveAll(actionString, graphIndex);
+			return;
+	    }
+		else if (actionString.equals("recolor")) //$NON-NLS-1$
+		{
+			actionRecolor();
+			return;
+		}
+	    else if (actionString.equals("copy")) //$NON-NLS-1$
+	    {
+	    	actionCopyOrSave(true, this.table, CHECKBOX_NO_TEXT, false, "\t", "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+	        return;
+	    }
+	    else if (actionString.equals("copyTable")) //$NON-NLS-1$
+	    {
+	    	actionCopyOrSave(true, this.table, CHECKBOX_NO_TEXT, true, "\t", "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+	        return;
+	    }
+	    else if (actionString.equals("copyDrilldown")) //$NON-NLS-1$
+	    {
+	    	actionCopyOrSaveDrilldown(true, "\t"); //$NON-NLS-1$
+	        return;
+	    }
+	    else if (actionString.equals("saveTable")) //$NON-NLS-1$
+	    {
+	    	actionCopyOrSave(false, this.table, CHECKBOX_NO_TEXT, true, ",", "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+	        return;
+	    }
+	    else if (actionString.equals("saveDrilldown")) //$NON-NLS-1$
+	    {
+	    	actionCopyOrSaveDrilldown(false, ","); //$NON-NLS-1$
+	        return;
+	    }
+	    else if (actionString.equals("saveSamples")) //$NON-NLS-1$
+	    {
+	    	SaveSampleString saveSampleString = new SaveSampleString(graphIndex, myGraph.getDrawMode());
+	    	actionSaveSamples(saveSampleString); //$NON-NLS-1$
+	        return;
+	    }
+	    else if (actionString.equals("savePrioritySamples")) //$NON-NLS-1$
+	    {
+	    	SavePrioritySampleString saveSampleString = new SavePrioritySampleString(graphIndex, myGraph.getDrawMode());
+	    	actionSaveSamples(saveSampleString); //$NON-NLS-1$
+	        return;
+	    }
+		else if (actionString.equals("selectAll")) //$NON-NLS-1$
+	    {
+	    	actionSelectAll();
+	        return;
+	    }
+		else if (actionString.equals("doubleClick")) //$NON-NLS-1$
+	    {
+	    	copyAction.setEnabled(false);
+			PIPageEditor.getActionBars().updateActionBars();
+	        return;
+	    }
+		else if (actionString.equals("thread-only")) //$NON-NLS-1$
+		{
+			actionThread();
+			return;
+		}
+		else if (actionString.equals("thread-binary")) //$NON-NLS-1$
+		{
+			actionThreadBinary();
+			return;
+		}
+		else if (actionString.equals("thread-binary-function")) //$NON-NLS-1$
+		{
+			actionThreadBinaryFunction();
+			return;
+		}
+		else if (actionString.equals("thread-function")) //$NON-NLS-1$
+		{
+			actionThreadFunction();
+			return;
+		}
+		else if (actionString.equals("thread-function-binary")) //$NON-NLS-1$
+		{
+			actionThreadFunctionBinary();
+			return;
+		}
+		else if (   (actionString.equals("binary-only")) //$NON-NLS-1$
+				 || (actionString.equals("binary-thread")) //$NON-NLS-1$
+				 || (actionString.equals("binary-thread-function")) //$NON-NLS-1$
+				 || (actionString.equals("binary-function")) //$NON-NLS-1$
+				 || (actionString.equals("binary-function-thread"))) //$NON-NLS-1$
+		{
+			// let the binary page action handler handle it
+			this.myGraph.getBinaryTable().action(actionString);
+			return;
+		}
+		else if (   (actionString.equals("function-only")) //$NON-NLS-1$
+				 || (actionString.equals("function-thread")) //$NON-NLS-1$
+				 || (actionString.equals("function-thread-binary")) //$NON-NLS-1$
+				 || (actionString.equals("function-binary")) //$NON-NLS-1$
+				 || (actionString.equals("function-binary-thread"))) //$NON-NLS-1$
+		{
+			// let the function page action handler handle it
+			this.myGraph.getFunctionTable().action(actionString);
+			return;
+		}
+		else if (actionString.equals("changeThresholdThread")) //$NON-NLS-1$
+		{
+			ProfiledThreshold threshold = this.myGraph.getThresholdThread();
+			boolean enabled = threshold.isEnabled(graphIndex);
+
+			this.tableItemData.clear();
+			this.profiledThreads.clear();
+			this.myGraph.getSortedThreads().clear();
+			if (threshold.getItems(graphIndex) != null)
+				threshold.getItems(graphIndex).clear();
+			threshold.initAll();
+
+			// if this appears, it needs to be the first item, so that it is drawn at the bottom
+			myGraph.getSortedThreads().add(threshold);
+
+			int threadThreshold = (Integer) NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.thresholdCountThread"); //$NON-NLS-1$
+			for (int i = 0; i < this.myGraph.getGppTrace().getSortedThreads().size(); i++) {
+				ProfiledGeneric nextElement = (ProfiledGeneric)this.myGraph.getGppTrace().getSortedThreads().get(i);
+				if (nextElement.getTotalSampleCount() < threadThreshold) {
+					nextElement.setEnabled(graphIndex, enabled);
+					threshold.addItem(graphIndex, nextElement, 0);
+				} else {
+					tableItemData.add(nextElement);
+					profiledThreads.add(nextElement);
+					myGraph.getSortedThreads().add(nextElement);
+				}
+			}
+
+			if (threshold.getItemCount(graphIndex) != 0) {
+				tableItemData.add(threshold);
+				profiledThreads.add(threshold);
+			} else {
+				// remove the threshold item
+				myGraph.getSortedThreads().remove(0);
+			}
+			
+			refreshTableViewer();
+			threshold.setEnabled(graphIndex, enabled);
+			
+			// make sure that checkboxes shown reflect the actual enabled values
+			TableItem[] tableItems = this.table.getItems();
+			for (int i = 0; i < tableItems.length; i++) {
+				if (tableItems[i].getData() instanceof ProfiledGeneric) {
+					ProfiledGeneric pGeneric = (ProfiledGeneric) tableItems[i].getData();
+					if (tableItems[i].getChecked() != pGeneric.isEnabled(graphIndex))
+						tableItems[i].setChecked(pGeneric.isEnabled(graphIndex));
+				}
+			}
+
+			this.myGraph.genericRefreshCumulativeThreadTable();
+		}
+	    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(this.table, CHECKBOX_NO_TEXT, ",", "\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("saveDrilldownTest")) //$NON-NLS-1$
+	    {
+			// copy save file contents to the clipboard for easy viewing
+	        Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
+	        Table[] tables = getDrillDownTables();
+	        
+	        int tableCount = 0;
+	        while (tableCount < tables.length && tables[tableCount] != null)
+	        	tableCount++;
+
+	        SaveDrillDownString getString = new SaveDrillDownString(tableCount, tables, ","); //$NON-NLS-1$
+	        String copyString = getString.getData();
+			StringSelection contents = new StringSelection(copyString);
+	        cb.setContents(contents, contents);
+	        return;
+	    }
+	}
+
+	private void actionAddRemove(String actionString, int graphIndex)
+	{
+		ProfiledGeneric pGeneric;
+		int totalSamples = 0;
+		
+		// true for "add", false for "remove"
+		boolean addIt = actionString.equals("add"); //$NON-NLS-1$
+
+		TableItem[] selectedItems = this.table.getSelection();
+		for (int i = 0; i < selectedItems.length; i++)
+		{
+			selectedItems[i].setChecked(addIt);
+			Object item = ((TableItem)selectedItems[i]).getData();
+			if (item instanceof ProfiledGeneric)
+			{
+				pGeneric = (ProfiledGeneric)item;
+				pGeneric.setEnabled(graphIndex, addIt);
+				totalSamples += pGeneric.getSampleCount(graphIndex);
+   				
+   				if (item instanceof ProfiledThreshold)
+   				{
+   					ProfiledThreshold pThreshold = (ProfiledThreshold)item;
+   					ArrayList<ProfiledGeneric> items = pThreshold.getItems(graphIndex);
+   					for (int j = 0; j < items.size(); j++)
+   						items.get(j).setEnabled(graphIndex, addIt);
+   				}
+			}
+		}
+
+        // this table's set of checkbox-selected rows has changed,
+		// so propagate that information
+		Object[] selectedValues = this.tableViewer.getCheckedElements();
+		String[] nameList = new String[selectedValues.length];
+
+		for (int i = 0; i < selectedValues.length; i++)
+		{
+   			if (selectedValues[i] instanceof ProfiledGeneric)
+   			{
+   				pGeneric = (ProfiledGeneric)selectedValues[i];
+   				nameList[i] = pGeneric.getNameString();
+   			}
+		}
+
+		PIVisualSharedData shared = myGraph.getSharedDataInstance();
+		shared.GPP_SelectedThreadNames = nameList;
+
+  		if (   (totalSamples != 0)
+      		|| (myGraph.getDrawMode() == Defines.THREADS))
+      			selectionChangeNotify();
+
+  		this.table.deselectAll();
+	}
+	
+	private void actionAddRemoveAll(String actionString, int graphIndex)
+	{
+		ProfiledGeneric pGeneric;
+
+		// true for "add", false for "remove"
+		boolean addIt = actionString.equals("addall"); //$NON-NLS-1$
+
+		TableItem[] selectedItems = this.table.getItems();
+		String[] nameList = new String[selectedItems.length];
+		for (int i = 0; i < selectedItems.length; i++)
+		{
+			selectedItems[i].setChecked(addIt);
+			Object item = ((TableItem)selectedItems[i]).getData();
+			if (item instanceof ProfiledGeneric)
+			{
+				pGeneric = (ProfiledGeneric)item;
+				pGeneric.setEnabled(graphIndex, addIt);
+   				nameList[i] = pGeneric.getNameString();
+   				
+   				if (item instanceof ProfiledThreshold)
+   				{
+   					ProfiledThreshold pThreshold = (ProfiledThreshold)item;
+   					ArrayList<ProfiledGeneric> items = pThreshold.getItems(graphIndex);
+   					for (int j = 0; j < items.size(); j++)
+   						items.get(j).setEnabled(graphIndex, addIt);
+   				}
+			}
+		}
+
+        // this table's set of checkbox-selected rows has changed,
+		// so propagate that information
+		PIVisualSharedData shared = myGraph.getSharedDataInstance();
+		shared.GPP_SelectedThreadNames = nameList;
+
+			selectionChangeNotify();
+  		this.table.deselectAll();
+	}
+	
+	private void actionRecolor()
+	{
+		int uid 			= this.myGraph.getUid();
+		GppTrace gppTrace = this.myGraph.getGppTrace();
+
+		// recolor selected items
+		boolean didRecolor = false;
+
+		TableItem[] selectedItems = table.getSelection();
+		for (int i = 0; i < selectedItems.length;i++)
+		{
+			if (selectedItems[i].getData() instanceof ProfiledGeneric)
+			{
+				ProfiledGeneric pGeneric = (ProfiledGeneric)selectedItems[i].getData();
+				TableColorPalette palette = ((GppTrace)this.myGraph.getTrace()).getThreadColorPalette();
+				String nameKey = pGeneric.getNameString();
+				if (palette.recolorEntryDialog(table.getShell(), nameKey))
+				{
+					Color color = palette.getColor(nameKey);
+					Color oldColor = pGeneric.getColor();
+					
+					if (color.equals(oldColor))
+						continue;
+					
+					didRecolor = true;
+					
+					if (!(pGeneric instanceof ProfiledThreshold)) {
+						PIPageEditor.currentPageEditor().setDirty();
+						pGeneric.setColor(color);
+					} else {
+						// for the threshold item, we must change every graph's thread threshold item
+						gppTrace.getGppGraph(PIPageEditor.THREADS_PAGE,   uid).getThresholdThread().setColor(color);
+						gppTrace.getGppGraph(PIPageEditor.BINARIES_PAGE,  uid).getThresholdThread().setColor(color);
+						gppTrace.getGppGraph(PIPageEditor.FUNCTIONS_PAGE, uid).getThresholdThread().setColor(color);
+					}
+				}
+
+				// recoloring should only be done in a draw mode that displays this table's colors
+				selectedItems[i].setBackground(COLOR_COLUMN_INDEX, palette.getColor(nameKey));
+			}
+		}
+		
+		if (!didRecolor)
+			return;
+
+		table.redraw();
+		this.myGraph.repaint();
+		this.myGraph.setGraphImageChanged(true);	// any selection change to drill down will change graph
+		
+		// if any other tabs are displaying this type of graph, they need to be scheduled for redrawing
+		for (int i = 0; i < 3; i++) {
+			GppTraceGraph graph = gppTrace.getGppGraph(i, uid);
+
+			if (graph == this.myGraph)
+				continue;
+
+			int drawMode = graph.getDrawMode();
+			
+			if (   (drawMode == Defines.THREADS)
+				|| (drawMode == Defines.BINARIES_THREADS)
+				|| (drawMode == Defines.BINARIES_FUNCTIONS_THREADS)
+				|| (drawMode == Defines.FUNCTIONS_THREADS)
+				|| (drawMode == Defines.FUNCTIONS_BINARIES_THREADS)) {
+				graph.getThreadTable().addColor(drawMode);
+				graph.setGraphImageChanged(true);	// any selection change to drill down will change graph
+				graph.repaint();
+			}
+		}
+	}
+
+	private void actionThread()
+	{
+		// current drawMode should be THREADS, THREADS_BINARIES, THREADS_BINARIES_FUNCTIONS,
+		// THREADS_FUNCTIONS, or THREADS_FUNCTIONS_BINARIES
+		int drawMode = this.myGraph.getDrawMode();
+
+		if (   (drawMode != Defines.THREADS_BINARIES)
+			&& (drawMode != Defines.THREADS_BINARIES_FUNCTIONS)
+			&& (drawMode != Defines.THREADS_FUNCTIONS)
+			&& (drawMode != Defines.THREADS_FUNCTIONS_BINARIES))
+		{
+			// this case should be drawMode == Defines.THREADS
+			return;
+		}
+		
+		setIsDrilldown(false);
+
+		// set the page's graph title
+		ProfileVisualiser pV =  NpiInstanceRepository.getInstance().getProfilePage(this.myGraph.getUid(), this.myGraph.getGraphIndex());
+		pV.getTitle().setText(Messages.getString("AddrThreadTable.threadLoad")); //$NON-NLS-1$
+		pV.getTitle2().setText(""); //$NON-NLS-1$
+
+		// get rid of any existing tables and sashes
+		if (this.myGraph.getLeftSash() != null) {
+			this.myGraph.getLeftSash().dispose();
+			this.myGraph.setLeftSash(null);
+
+			// detach the table from the sash
+			try {
+				FormData formData = (FormData) this.table.getLayoutData();
+				formData.right = new FormAttachment(100);
+			} catch (ClassCastException e1) {
+			}
+		}
+		if (this.myGraph.getRightSash() != null) {
+			this.myGraph.getRightSash().dispose();
+			this.myGraph.setRightSash(null);
+		}
+		if (   (this.myGraph.getBinaryTable() != null)
+			&& (this.myGraph.getBinaryTable().getTable() != null)) {
+			this.myGraph.getBinaryTable().getTableViewer().getTable().dispose();
+			this.myGraph.getBinaryTable().setTableViewer(null);
+		}
+		if (   (this.myGraph.getFunctionTable() != null)
+			&& (this.myGraph.getFunctionTable().getTable() != null)) {
+			this.myGraph.getFunctionTable().getTableViewer().getTable().dispose();
+			this.myGraph.getFunctionTable().setTableViewer(null);
+		}
+
+		// set the draw mode
+		this.myGraph.setDrawMode(Defines.THREADS);
+
+		// add colors to the rightmost table
+		addColor(Defines.THREADS);
+
+		this.parent.layout();
+
+		this.myGraph.repaint();
+	}
+
+	private void actionThreadBinary()
+	{
+		// current drawMode should be THREADS, THREADS_BINARIES, or THREADS_BINARIES_FUNCTIONS
+		int drawMode = this.myGraph.getDrawMode();
+		int graphIndex = this.myGraph.getGraphIndex();
+
+		if (   drawMode != Defines.THREADS
+			&& drawMode != Defines.THREADS_BINARIES_FUNCTIONS) {
+			return;
+		}
+		
+		setIsDrilldown(true);
+
+		if (drawMode == Defines.THREADS) {
+			// set the page's graph title
+			ProfileVisualiser pV =  NpiInstanceRepository.getInstance().getProfilePage(this.myGraph.getUid(), this.myGraph.getGraphIndex());
+			pV.getTitle().setText(Messages.getString("AddrThreadTable.binaryLoad")); //$NON-NLS-1$
+			pV.getTitle2().setText(Messages.getString("AddrThreadTable.threadTo")); //$NON-NLS-1$
+
+			// set the draw mode before populating table viewers, since the
+			// color column depends on the draw mode
+			this.myGraph.setDrawMode(Defines.THREADS_BINARIES);
+
+			// create the binary graph table viewer
+			AddrBinaryTable binaryTable = this.myGraph.getBinaryTable();
+			binaryTable.createTableViewer(Defines.THREADS_BINARIES);
+			binaryTable.setIsDrilldown(true);
+
+			// create a reduced set of binary entries based on enabled thread entries
+			GppTrace gppTrace = (GppTrace) this.myGraph.getTrace();
+			Vector<ProfiledGeneric> binaries = gppTrace.setThreadBinary(graphIndex);
+			this.myGraph.setProfiledBinaries(binaries);
+
+			// put check marks on all rows, and sort by sample count
+			binaryTable.quickSort(COLUMN_ID_SAMPLE_COUNT, this.myGraph.getProfiledBinaries());
+			binaryTable.updateProfiledAndItemData(true);
+			binaryTable.getTableViewer().setAllChecked(true);
+			binaryTable.getTableViewer().refresh();
+
+			// put check marks on all enabled thread rows
+			for (int i = 0; i < this.table.getItemCount(); i++) {
+				ProfiledGeneric pThread = (ProfiledGeneric) this.table.getItem(i).getData();
+				if (pThread.isEnabled(graphIndex))
+					this.table.getItem(i).setChecked(true);
+			}
+			
+			// remove colors where appropriate
+			removeColor(Defines.THREADS_BINARIES);
+
+			// connect the tables with a sash
+			Sash leftSash   = new Sash(this.parent, SWT.VERTICAL);
+
+			final FormData leftSashData = new FormData();
+			leftSashData.top    = new FormAttachment(0);
+			leftSashData.bottom = new FormAttachment(100);
+			leftSashData.left   = new FormAttachment(50); // middle
+			leftSash.setLayoutData(leftSashData);
+
+			final Composite sashParent = this.parent;
+			leftSash.addListener(SWT.Selection, new Listener() {
+				public void handleEvent(Event event) {
+					if (event.detail != SWT.DRAG) {
+						leftSashData.left = new FormAttachment(0, event.x);
+						sashParent.layout();
+					}
+				}
+			});
+
+			myGraph.setLeftSash(leftSash);
+
+			// attach the binary table to the sash
+			final FormData viewerData = new FormData();
+			viewerData.top    = new FormAttachment(0);
+			viewerData.bottom = new FormAttachment(100);
+			viewerData.left   = new FormAttachment(leftSash);
+			viewerData.right  = new FormAttachment(100);
+			binaryTable.getTable().setLayoutData(viewerData);
+
+			// attach the thread table to the sash
+			try {
+				FormData formData = (FormData) this.table.getLayoutData();
+				formData.right = new FormAttachment(leftSash);
+			} catch (ClassCastException e1) {
+			}
+
+			this.parent.layout();
+
+			this.myGraph.repaint();
+
+		} else if (drawMode == Defines.THREADS_BINARIES_FUNCTIONS) {
+			// set the page's graph title
+			ProfileVisualiser pV =  NpiInstanceRepository.getInstance().getProfilePage(this.myGraph.getUid(), this.myGraph.getGraphIndex());
+			pV.getTitle().setText(Messages.getString("AddrThreadTable.binaryLoad")); //$NON-NLS-1$
+			pV.getTitle2().setText(Messages.getString("AddrThreadTable.threadTo")); //$NON-NLS-1$
+
+			// get rid of the function table and its sash
+			if (this.myGraph.getRightSash() != null) {
+				this.myGraph.getRightSash().dispose();
+				this.myGraph.setRightSash(null);
+			}
+			if (   (this.myGraph.getFunctionTable() != null)
+				&& (this.myGraph.getFunctionTable().getTable() != null)) {
+				this.myGraph.getFunctionTable().getTableViewer().getTable().dispose();
+				this.myGraph.getFunctionTable().setTableViewer(null);
+			}
+
+			// get rid of the middle table's connection to the sash
+			try {
+				FormData formData = (FormData) this.myGraph.getBinaryTable().getTable().getLayoutData();
+				formData.right = new FormAttachment(100);
+			} catch (ClassCastException e1) {
+			}
+
+			// move the left sash to the middle
+			try {
+				FormData formData = (FormData) this.myGraph.getLeftSash().getLayoutData();
+				formData.left = new FormAttachment(50); // middle
+			} catch (ClassCastException e1) {
+			}
+
+			// set the draw mode
+			this.myGraph.setDrawMode(Defines.THREADS_BINARIES);
+
+			// show colors in the rightmost table
+			this.myGraph.getBinaryTable().addColor(Defines.THREADS_BINARIES);
+
+			this.parent.layout();
+
+			this.myGraph.repaint();
+		}
+
+		// this case should be drawMode == Defines.THREADS_BINARIES
+		return;
+	}
+
+	private void actionThreadBinaryFunction()
+	{
+		// current drawMode should be THREADS_BINARIES or THREADS_BINARIES_FUNCTIONS
+		int drawMode = this.myGraph.getDrawMode();
+		int graphIndex = this.myGraph.getGraphIndex();
+
+		if (drawMode != Defines.THREADS_BINARIES) {
+			// this case should be drawMode == Defines.THREADS_BINARIES_FUNCTIONS
+			return;
+		}
+
+		setIsDrilldown(true);
+
+		// set the page's graph title
+		ProfileVisualiser pV =  NpiInstanceRepository.getInstance().getProfilePage(this.myGraph.getUid(), this.myGraph.getGraphIndex());
+		pV.getTitle().setText(Messages.getString("AddrThreadTable.functionLoad")); //$NON-NLS-1$
+		pV.getTitle2().setText(Messages.getString("AddrThreadTable.threadToBinaryTo")); //$NON-NLS-1$
+
+		// set the draw mode before populating table viewers, since the
+		// color column depends on the draw mode
+		this.myGraph.setDrawMode(Defines.THREADS_BINARIES_FUNCTIONS);
+
+		// create the function graph table viewer
+		AddrFunctionTable functionTable = this.myGraph.getFunctionTable();
+		functionTable.createTableViewer(Defines.THREADS_BINARIES_FUNCTIONS);
+		functionTable.setIsDrilldown(true);
+
+		// create a reduced set of function entries based on enabled thread and binary entries
+		GppTrace gppTrace = (GppTrace) this.myGraph.getTrace();
+		Vector<ProfiledGeneric> functions = gppTrace.setThreadBinaryFunction(graphIndex);
+		this.myGraph.setProfiledFunctions(functions);
+
+		// put check marks on all rows, and sort by sample count
+		functionTable.quickSort(COLUMN_ID_SAMPLE_COUNT, this.myGraph.getProfiledFunctions());
+		functionTable.updateProfiledAndItemData(true);
+		functionTable.getTableViewer().setAllChecked(true);
+		functionTable.getTableViewer().refresh();
+
+		// remove colors where appropriate
+		this.myGraph.getBinaryTable().removeColor(Defines.THREADS_BINARIES_FUNCTIONS);
+
+		// connect the 2nd and 3rd tables with a sash
+		Sash rightSash   = new Sash(this.parent, SWT.VERTICAL);
+
+		final FormData rightSashData = new FormData();
+		rightSashData.top    = new FormAttachment(0);
+		rightSashData.bottom = new FormAttachment(100);
+		rightSashData.left   = new FormAttachment(67); // two thirds
+		rightSash.setLayoutData(rightSashData);
+
+		final Composite sashParent = this.parent;
+		rightSash.addListener(SWT.Selection, new Listener() {
+			public void handleEvent(Event event) {
+				if (event.detail != SWT.DRAG) {
+					rightSashData.left = new FormAttachment(0, event.x);
+					sashParent.layout();
+				}
+			}
+		});
+
+		myGraph.setRightSash(rightSash);
+
+		// attach the function table to the sash
+		final FormData viewerData = new FormData();
+		viewerData.top    = new FormAttachment(0);
+		viewerData.bottom = new FormAttachment(100);
+		viewerData.left   = new FormAttachment(rightSash);
+		viewerData.right  = new FormAttachment(100);
+		functionTable.getTable().setLayoutData(viewerData);
+
+		// attach the binary table to the sash
+		try {
+			FormData formData = (FormData) this.myGraph.getBinaryTable().getTable().getLayoutData();
+			formData.right = new FormAttachment(rightSash);
+		} catch (ClassCastException e1) {
+		}
+
+		// move the left sash to 1/3 from the left
+		try {
+			FormData formData = (FormData) this.myGraph.getLeftSash().getLayoutData();
+			formData.left = new FormAttachment(33); // one third
+		} catch (ClassCastException e1) {
+		}
+
+		this.parent.layout();
+
+		this.myGraph.repaint();
+	}
+
+	private void actionThreadFunction()
+	{
+		// current drawMode should be THREADS, THREADS_FUNCTIONS, or THREADS_FUNCTIONS_BINARIES
+		int drawMode = this.myGraph.getDrawMode();
+		int graphIndex = this.myGraph.getGraphIndex();
+
+		if (   drawMode != Defines.THREADS
+			&& drawMode != Defines.THREADS_FUNCTIONS_BINARIES) {
+			return;
+		}
+
+		setIsDrilldown(true);
+
+		if (drawMode == Defines.THREADS) {
+			// set the page's graph title
+			ProfileVisualiser pV =  NpiInstanceRepository.getInstance().getProfilePage(this.myGraph.getUid(), this.myGraph.getGraphIndex());
+			pV.getTitle().setText(Messages.getString("AddrThreadTable.functionLoad")); //$NON-NLS-1$
+			pV.getTitle2().setText(Messages.getString("AddrThreadTable.threadTo")); //$NON-NLS-1$
+
+			// set the draw mode
+			this.myGraph.setDrawMode(Defines.THREADS_FUNCTIONS);
+
+			// create the function graph table viewer
+			AddrFunctionTable functionTable = this.myGraph.getFunctionTable();
+			functionTable.createTableViewer(Defines.THREADS_FUNCTIONS);
+			functionTable.setIsDrilldown(true);
+
+			// create a reduced set of function entries based on enabled thread and binary entries
+			GppTrace gppTrace = (GppTrace) this.myGraph.getTrace();
+			Vector<ProfiledGeneric> functions = gppTrace.setThreadFunction(graphIndex);
+			this.myGraph.setProfiledFunctions(functions);
+
+			// put check marks on all rows, and sort by sample count
+			functionTable.quickSort(COLUMN_ID_SAMPLE_COUNT, this.myGraph.getProfiledFunctions());
+			functionTable.updateProfiledAndItemData(true);
+			functionTable.getTableViewer().setAllChecked(true);
+			functionTable.getTableViewer().refresh();
+
+			// remove colors where appropriate
+			removeColor(Defines.THREADS_FUNCTIONS);
+
+			// connect the tables with a sash
+			Sash leftSash   = new Sash(this.parent, SWT.VERTICAL);
+
+			final FormData leftSashData = new FormData();
+			leftSashData.top    = new FormAttachment(0);
+			leftSashData.bottom = new FormAttachment(100);
+			leftSashData.left   = new FormAttachment(50); // middle
+			leftSash.setLayoutData(leftSashData);
+
+			final Composite sashParent = this.parent;
+			leftSash.addListener(SWT.Selection, new Listener() {
+				public void handleEvent(Event event) {
+					if (event.detail != SWT.DRAG) {
+						leftSashData.left = new FormAttachment(0, event.x);
+						sashParent.layout();
+					}
+				}
+			});
+
+			myGraph.setLeftSash(leftSash);
+
+			// attach the function table to the sash
+			final FormData viewerData = new FormData();
+			viewerData.top    = new FormAttachment(0);
+			viewerData.bottom = new FormAttachment(100);
+			viewerData.left   = new FormAttachment(leftSash);
+			viewerData.right  = new FormAttachment(100);
+			functionTable.getTable().setLayoutData(viewerData);
+
+			// attach the thread table to the sash
+			try {
+				FormData formData = (FormData) this.table.getLayoutData();
+				formData.right = new FormAttachment(leftSash);
+			} catch (ClassCastException e1) {
+			}
+
+			this.parent.layout();
+
+			this.myGraph.repaint();
+
+		} else if (drawMode == Defines.THREADS_FUNCTIONS_BINARIES) {
+			// set the page's graph title
+			ProfileVisualiser pV =  NpiInstanceRepository.getInstance().getProfilePage(this.myGraph.getUid(), this.myGraph.getGraphIndex());
+			pV.getTitle().setText(Messages.getString("AddrThreadTable.functionLoad")); //$NON-NLS-1$
+			pV.getTitle2().setText(Messages.getString("AddrThreadTable.threadTo")); //$NON-NLS-1$
+
+			// get rid of the binary table and its sash
+			if (this.myGraph.getRightSash() != null) {
+				this.myGraph.getRightSash().dispose();
+				this.myGraph.setRightSash(null);
+			}
+			if (   (this.myGraph.getBinaryTable() != null)
+				&& (this.myGraph.getBinaryTable().getTable() != null)) {
+				this.myGraph.getBinaryTable().getTableViewer().getTable().dispose();
+				this.myGraph.getBinaryTable().setTableViewer(null);
+			}
+
+			// get rid of the middle table's connection to the sash
+			try {
+				FormData formData = (FormData) this.myGraph.getFunctionTable().getTable().getLayoutData();
+				formData.right = new FormAttachment(100);
+			} catch (ClassCastException e1) {
+			}
+
+			// move the left sash to the middle
+			try {
+				FormData formData = (FormData) this.myGraph.getLeftSash().getLayoutData();
+				formData.left = new FormAttachment(50); // middle
+			} catch (ClassCastException e1) {
+			}
+
+			// set the draw mode
+			this.myGraph.setDrawMode(Defines.THREADS_FUNCTIONS);
+
+			// show colors in the rightmost table
+			this.myGraph.getFunctionTable().addColor(Defines.THREADS_FUNCTIONS);
+
+			this.parent.layout();
+
+			this.myGraph.repaint();
+		}
+
+		// this case should be drawMode == Defines.THREADS_BINARIES
+		return;
+	}
+
+	private void actionThreadFunctionBinary()
+	{
+		// current drawMode is THREADS_FUNCTIONS, or THREADS_FUNCTIONS_BINARIES
+		int drawMode   = this.myGraph.getDrawMode();
+		int graphIndex = this.myGraph.getGraphIndex();
+
+		if (drawMode != Defines.THREADS_FUNCTIONS) {
+			// this case should be drawMode == Defines.THREADS_FUNCTIONS_BINARIES
+			return;
+		}
+
+		setIsDrilldown(true);
+
+		// set the page's graph title
+		ProfileVisualiser pV =  NpiInstanceRepository.getInstance().getProfilePage(this.myGraph.getUid(), this.myGraph.getGraphIndex());
+		pV.getTitle().setText(Messages.getString("AddrThreadTable.binaryLoad")); //$NON-NLS-1$
+		pV.getTitle2().setText(Messages.getString("AddrThreadTable.threadToFunctionTo")); //$NON-NLS-1$
+
+		// set the draw mode
+		this.myGraph.setDrawMode(Defines.THREADS_FUNCTIONS_BINARIES);
+
+		// create the binary graph table viewer
+		AddrBinaryTable binaryTable = this.myGraph.getBinaryTable();
+		binaryTable.createTableViewer(Defines.THREADS_FUNCTIONS_BINARIES);
+		binaryTable.setIsDrilldown(true);
+
+		// create a reduced set of binary entries based on enabled thread and function entries
+		GppTrace gppTrace = (GppTrace) this.myGraph.getTrace();
+		Vector<ProfiledGeneric> binaries = gppTrace.setThreadFunctionBinary(graphIndex);
+		this.myGraph.setProfiledBinaries(binaries);
+
+		// put check marks on all rows, and sort by sample count
+		binaryTable.quickSort(COLUMN_ID_SAMPLE_COUNT, this.myGraph.getProfiledBinaries());
+		binaryTable.updateProfiledAndItemData(true);
+		binaryTable.getTableViewer().setAllChecked(true);
+		binaryTable.getTableViewer().refresh();
+
+		// remove colors where appropriate
+		this.myGraph.getFunctionTable().removeColor(Defines.THREADS_FUNCTIONS_BINARIES);
+
+		// connect the 2nd and 3rd tables with a sash
+		Sash rightSash   = new Sash(this.parent, SWT.VERTICAL);
+
+		final FormData rightSashData = new FormData();
+		rightSashData.top    = new FormAttachment(0);
+		rightSashData.bottom = new FormAttachment(100);
+		rightSashData.left   = new FormAttachment(67); // two thirds
+		rightSash.setLayoutData(rightSashData);
+
+		final Composite sashParent = this.parent;
+		rightSash.addListener(SWT.Selection, new Listener() {
+			public void handleEvent(Event event) {
+				if (event.detail != SWT.DRAG) {
+					rightSashData.left = new FormAttachment(0, event.x);
+					sashParent.layout();
+				}
+			}
+		});
+
+		myGraph.setRightSash(rightSash);
+
+		// attach the binary table to the sash
+		final FormData viewerData = new FormData();
+		viewerData.top    = new FormAttachment(0);
+		viewerData.bottom = new FormAttachment(100);
+		viewerData.left   = new FormAttachment(rightSash);
+		viewerData.right  = new FormAttachment(100);
+		binaryTable.getTable().setLayoutData(viewerData);
+
+		// attach the function table to the sash
+		try {
+			FormData formData = (FormData) this.myGraph.getFunctionTable().getTable().getLayoutData();
+			formData.right = new FormAttachment(rightSash);
+		} catch (ClassCastException e1) {
+		}
+
+		// move the left sash to 1/3 from the left
+		try {
+			FormData formData = (FormData) this.myGraph.getLeftSash().getLayoutData();
+			formData.left = new FormAttachment(33); // one third
+		} catch (ClassCastException e1) {
+		}
+
+		this.parent.layout();
+
+		this.myGraph.repaint();
+	}
+
+	public void focusGained(FocusEvent e) {}
+
+	public void focusLost(FocusEvent e) {}
+
+	public void piEventReceived(PIEvent be)
+	{
+		if (be.getType() == PIEvent.SELECTION_AREA_CHANGED3)
+		{
+			// % loads, % load strings, and/or sample counts have been updated
+			// due to a change in the graph area selected, and all table items
+			// need to be checked
+			updateProfiledAndItemData(true);
+
+			Display.getDefault().syncExec(new Runnable() {
+				public void run() {
+					quickSort(sortColumn, profiledThreads);
+
+					// initially, all rows are selected
+					tableViewer.setAllChecked(true);
+
+					table.redraw();
+				}
+			});
+		}
+		else if (   (be.getType() == PIEvent.SELECTION_AREA_CHANGED2)
+				 || (be.getType() == PIEvent.CHANGED_THREAD_TABLE))
+		{
+			int graphIndex = this.myGraph.getGraphIndex();
+
+			// This routine does not change which threads are enabled, but it enables
+			// all entries in the 2nd and 3rd tables.
+			// It assumes that GppTrace.setSelectedArea(), action("add") or action("remove") has set
+			// the % load and sample counts for threads, except for the threshold list.
+			if (be.getType() == PIEvent.SELECTION_AREA_CHANGED2) {
+				int thresholdCount = (Integer)NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.thresholdCountThread"); //$NON-NLS-1$
+				if (thresholdCount > 0) {
+					Vector<ProfiledGeneric> pGeneric = this.myGraph.getProfiledThreads();
+					int sampleCount = 0;
+					for (int i = 0; i < pGeneric.size(); i++)
+						if (pGeneric.elementAt(i).getTotalSampleCount() < thresholdCount)
+							sampleCount += pGeneric.elementAt(i).getSampleCount(graphIndex);
+					this.myGraph.getThresholdThread().setSampleCount(graphIndex, sampleCount);
+				}
+			}
+
+			// redraw this table
+			Display.getDefault().syncExec(new Runnable() {
+				public void run() {
+					if (   (sortColumn == COLUMN_ID_PERCENT_LOAD)
+						|| (sortColumn == COLUMN_ID_SAMPLE_COUNT))
+					{
+						quickSort(sortColumn, profiledThreads);
+					}
+					else
+						refreshTableViewer();
+
+					table.redraw();
+				}
+			});
+
+			int drawMode = this.myGraph.getDrawMode();
+			if (drawMode == Defines.THREADS)
+				return;
+
+			GppTrace trace = (GppTrace)(this.myGraph.getTrace());
+			Vector<ProfiledGeneric> traceThreads = trace.getIndexedThreads();
+
+			int startSampleIndex = trace.getStartSampleIndex();
+			int endSampleIndex   = trace.getEndSampleIndex();
+			double percentPerSample;
+			if (startSampleIndex == endSampleIndex)
+				percentPerSample = 0.0;
+			else
+				percentPerSample = 100.0 / ((double)(endSampleIndex - startSampleIndex));
+
+			PIEvent be3 = new PIEvent(be.getValueObject(), PIEvent.SELECTION_AREA_CHANGED3);
+
+			switch (drawMode) {
+				case Defines.THREADS_BINARIES:
+				{
+					// get new binary counts and loads
+					Vector<ProfiledGeneric> traceBinaries = trace.getIndexedBinaries();
+					Vector<ProfiledGeneric> graphBinaries  = this.myGraph.getProfiledBinaries();
+					Hashtable<String,String> foundBinaries  = new Hashtable<String,String>();
+
+					// previous binaries are not necessarily graphed this time
+					Enumeration<ProfiledGeneric> enu = graphBinaries.elements();
+					while (enu.hasMoreElements()) {
+						ProfiledBinary pBinary = (ProfiledBinary) enu.nextElement();
+						pBinary.setEnabled(graphIndex, false);
+					}
+					graphBinaries.clear();
+
+					ProfiledThreshold thresholdBinary = this.myGraph.getThresholdBinary();
+
+					if (thresholdBinary.isEnabled(graphIndex)) {
+						ArrayList<ProfiledGeneric> items = thresholdBinary.getItems(graphIndex);
+						// disable all items below the threshold
+						for (int i = 0; i < items.size(); i++) {
+							items.get(i).setEnabled(graphIndex, false);
+						}
+					}
+
+					GppSample[] sortedSamples = trace.getSortedGppSamples();
+
+					// set up in case we find binaries below the threshold
+					boolean lowBinary;
+					thresholdBinary.init(graphIndex);
+					
+					int binaryThreshold = (Integer) NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.thresholdCountBinary"); //$NON-NLS-1$
+					for (int i = startSampleIndex; i < endSampleIndex; i++) {
+						GppSample sample = sortedSamples[i];
+
+						ProfiledThread pThread = (ProfiledThread) traceThreads.elementAt(sample.threadIndex);
+						if (pThread.isEnabled(graphIndex)) {
+							// binary list is based on threads
+							ProfiledBinary pBinary = (ProfiledBinary) traceBinaries.elementAt(sample.binaryIndex);
+							String binaryName = pBinary.getNameString();
+
+							lowBinary = pBinary.getTotalSampleCount() < binaryThreshold;
+
+							if (!foundBinaries.containsKey(binaryName)) {
+								foundBinaries.put(binaryName, binaryName);
+								if (lowBinary) {
+									thresholdBinary.addItem(graphIndex, pBinary, 1);
+								} else {
+									pBinary.setEnabled(graphIndex, true);
+									pBinary.setSampleCount(graphIndex, 1);
+									graphBinaries.add(pBinary);
+								}
+							} else {
+								if (lowBinary)
+									thresholdBinary.incSampleCount(graphIndex);
+								else
+									pBinary.incSampleCount(graphIndex);
+							}
+						}
+					}
+
+					// set the % load
+					for (int i = 0; i < graphBinaries.size(); i++) {
+						ProfiledBinary pBinary = (ProfiledBinary) graphBinaries.elementAt(i);
+						pBinary.setLoadAndString(graphIndex, (float)(pBinary.getSampleCount(graphIndex)*percentPerSample));
+					}
+					thresholdBinary.setLoadAndString(graphIndex,
+							(float)(thresholdBinary.getSampleCount(graphIndex)*percentPerSample));
+
+					// update the table items and redraw the table
+					this.myGraph.getBinaryTable().piEventReceived(be3);
+					break;
+				}
+				case Defines.THREADS_BINARIES_FUNCTIONS:
+				{
+					// get new binary and function counts and loads
+					Vector<ProfiledGeneric> traceBinaries   = trace.getIndexedBinaries();
+					Vector<ProfiledGeneric> traceFunctions  = trace.getIndexedFunctions();
+					Vector<ProfiledGeneric> graphBinaries   = this.myGraph.getProfiledBinaries();
+					Vector<ProfiledGeneric> graphFunctions  = this.myGraph.getProfiledFunctions();
+					Hashtable<String,String> foundBinaries  = new Hashtable<String,String>();
+					Hashtable<String,String> foundFunctions = new Hashtable<String,String>();
+
+					// previous functions are not necessarily graphed this time
+					Enumeration<ProfiledGeneric> enuFunctions = graphFunctions.elements();
+					while (enuFunctions.hasMoreElements()) {
+						ProfiledFunction pFunction = (ProfiledFunction) enuFunctions.nextElement();
+						pFunction.setEnabled(graphIndex, false);
+					}
+					graphFunctions.clear();
+
+					ProfiledThreshold thresholdFunction = this.myGraph.getThresholdFunction();
+
+					if (thresholdFunction.isEnabled(graphIndex)) {
+						ArrayList<ProfiledGeneric> items = thresholdFunction.getItems(graphIndex);
+						// disable all items below the threshold
+						for (int i = 0; i < items.size(); i++) {
+							items.get(i).setEnabled(graphIndex, false);
+						}
+					}
+
+					// previous binaries are not necessarily graphed this time
+					Enumeration<ProfiledGeneric> enuBinary = graphBinaries.elements();
+					while (enuBinary.hasMoreElements()) {
+						ProfiledBinary pBinary = (ProfiledBinary) enuBinary.nextElement();
+						pBinary.setEnabled(graphIndex, false);
+					}
+					graphBinaries.clear();
+
+					ProfiledThreshold thresholdBinary = this.myGraph.getThresholdBinary();
+
+					if (thresholdBinary.isEnabled(graphIndex)) {
+						ArrayList<ProfiledGeneric> items = thresholdBinary.getItems(graphIndex);
+						// disable all items below the threshold
+						for (int i = 0; i < items.size(); i++) {
+							items.get(i).setEnabled(graphIndex, false);
+						}
+					}
+
+					GppSample[] sortedSamples = trace.getSortedGppSamples();
+
+					// set up in case we find functions below the threshold
+					boolean lowFunction;
+					thresholdFunction.init(graphIndex);
+
+					// set up in case we find binaries below the threshold
+					boolean lowBinary;
+					thresholdBinary.init(graphIndex);
+
+					int binaryThreshold = (Integer) NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.thresholdCountBinary"); //$NON-NLS-1$
+					for (int i = startSampleIndex; i < endSampleIndex; i++) {
+						GppSample sample = sortedSamples[i];
+
+						ProfiledThread pThread = (ProfiledThread) traceThreads.elementAt(sample.threadIndex);
+						if (pThread.isEnabled(graphIndex)) {
+							// binary list is based on threads
+							ProfiledBinary pBinary = (ProfiledBinary) traceBinaries.elementAt(sample.binaryIndex);
+							String binaryName = pBinary.getNameString();
+
+							lowBinary = pBinary.getTotalSampleCount() < binaryThreshold;
+
+							if (!foundBinaries.containsKey(binaryName)) {
+								foundBinaries.put(binaryName, binaryName);
+								if (lowBinary) {
+									thresholdBinary.addItem(graphIndex, pBinary, 1);
+								} else {
+									pBinary.setEnabled(graphIndex, true);
+									pBinary.setSampleCount(graphIndex, 1);
+									graphBinaries.add(pBinary);
+								}
+							} else {
+								if (lowBinary)
+									thresholdBinary.incSampleCount(graphIndex);
+								else
+									pBinary.incSampleCount(graphIndex);
+							}
+
+							// function list is based on threads and binaries
+							ProfiledFunction pFunction = (ProfiledFunction) traceFunctions.elementAt(sample.functionIndex);
+							String functionName = pFunction.getNameString();
+
+							lowFunction = pFunction.getTotalSampleCount() < (Integer)NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.thresholdCountFunction"); //$NON-NLS-1$
+
+							if (!foundFunctions.containsKey(functionName)) {
+								foundFunctions.put(functionName, functionName);
+								if (lowFunction) {
+									thresholdFunction.addItem(graphIndex, pFunction, 1);
+								} else {
+									pFunction.setEnabled(graphIndex, true);
+									pFunction.setSampleCount(graphIndex, 1);
+									graphFunctions.add(pFunction);
+								}
+							} else {
+								if (lowFunction)
+									thresholdFunction.incSampleCount(graphIndex);
+								else
+									pFunction.incSampleCount(graphIndex);
+							}
+						}
+					}
+
+					// set the % load
+					for (int i = 0; i < graphBinaries.size(); i++) {
+						ProfiledBinary pBinary = (ProfiledBinary) graphBinaries.elementAt(i);
+						pBinary.setLoadAndString(graphIndex, (float)(pBinary.getSampleCount(graphIndex)*percentPerSample));
+					}
+					thresholdBinary.setLoadAndString(graphIndex,
+							(float)(thresholdBinary.getSampleCount(graphIndex)*percentPerSample));
+
+					for (int i = 0; i < graphFunctions.size(); i++) {
+						ProfiledFunction pFunction = (ProfiledFunction) graphFunctions.elementAt(i);
+						pFunction.setLoadAndString(graphIndex, (float)(pFunction.getSampleCount(graphIndex)*percentPerSample));
+					}
+					thresholdFunction.setLoadAndString(graphIndex,
+							(float)(thresholdFunction.getSampleCount(graphIndex)*percentPerSample));
+
+					// update the table items and redraw the table
+					this.myGraph.getBinaryTable().piEventReceived(be3);
+					this.myGraph.getFunctionTable().piEventReceived(be3);
+					break;
+				}
+				case Defines.THREADS_FUNCTIONS:
+				{
+					// get new function counts and loads
+					Vector<ProfiledGeneric> traceFunctions  = trace.getIndexedFunctions();
+					Vector<ProfiledGeneric> graphFunctions  = this.myGraph.getProfiledFunctions();
+					Hashtable<String,String> foundFunctions = new Hashtable<String,String>();
+
+					// previous functions are not necessarily graphed this time
+					Enumeration<ProfiledGeneric> enu = graphFunctions.elements();
+					while (enu.hasMoreElements()) {
+						ProfiledFunction pFunction = (ProfiledFunction) enu.nextElement();
+						pFunction.setEnabled(graphIndex, false);
+					}
+					graphFunctions.clear();
+
+					ProfiledThreshold thresholdFunction = this.myGraph.getThresholdFunction();
+
+					if (thresholdFunction.isEnabled(graphIndex)) {
+						ArrayList<ProfiledGeneric> items = thresholdFunction.getItems(graphIndex);
+						// disable all items below the threshold
+						for (int i = 0; i < items.size(); i++) {
+							items.get(i).setEnabled(graphIndex, false);
+						}
+					}
+
+					GppSample[] sortedSamples = trace.getSortedGppSamples();
+
+					// set up in case we find functions below the threshold
+					boolean lowFunction;
+					thresholdFunction.init(graphIndex);
+
+					int functionThreshold = (Integer) NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.thresholdCountFunction"); //$NON-NLS-1$
+					for (int i = startSampleIndex; i < endSampleIndex; i++) {
+						GppSample sample = sortedSamples[i];
+
+						ProfiledThread pThread = (ProfiledThread) traceThreads.elementAt(sample.threadIndex);
+						if (pThread.isEnabled(graphIndex)) {
+							// function list is based on threads
+							ProfiledFunction pFunction = (ProfiledFunction) traceFunctions.elementAt(sample.functionIndex);
+							String functionName = pFunction.getNameString();
+
+							lowFunction = pFunction.getTotalSampleCount() < functionThreshold;
+
+							if (!foundFunctions.containsKey(functionName)) {
+								foundFunctions.put(functionName, functionName);
+								if (lowFunction) {
+									thresholdFunction.addItem(graphIndex, pFunction, 1);
+								} else {
+									pFunction.setEnabled(graphIndex, true);
+									pFunction.setSampleCount(graphIndex, 1);
+									graphFunctions.add(pFunction);
+								}
+							} else {
+								if (lowFunction)
+									thresholdFunction.incSampleCount(graphIndex);
+								else
+									pFunction.incSampleCount(graphIndex);
+							}
+						}
+					}
+
+					// set the % load
+					for (int i = 0; i < graphFunctions.size(); i++) {
+						ProfiledFunction pFunction = (ProfiledFunction) graphFunctions.elementAt(i);
+						pFunction.setLoadAndString(graphIndex, (float)(pFunction.getSampleCount(graphIndex)*percentPerSample));
+					}
+					thresholdFunction.setLoadAndString(graphIndex,
+							(float)(thresholdFunction.getSampleCount(graphIndex)*percentPerSample));
+
+					// update the table items and redraw the table
+					this.myGraph.getFunctionTable().piEventReceived(be3);
+					break;
+				}
+				case Defines.THREADS_FUNCTIONS_BINARIES:
+				{
+					// get new function and binary counts and loads
+					Vector<ProfiledGeneric> traceBinaries   = trace.getIndexedBinaries();
+					Vector<ProfiledGeneric> traceFunctions  = trace.getIndexedFunctions();
+					Vector<ProfiledGeneric> graphBinaries   = this.myGraph.getProfiledBinaries();
+					Vector<ProfiledGeneric> graphFunctions  = this.myGraph.getProfiledFunctions();
+					Hashtable<String,String> foundBinaries  = new Hashtable<String,String>();
+					Hashtable<String,String> foundFunctions = new Hashtable<String,String>();
+
+					// previous binaries are not necessarily graphed this time
+					Enumeration<ProfiledGeneric> enuBinaries = graphBinaries.elements();
+					while (enuBinaries.hasMoreElements()) {
+						ProfiledBinary pBinary = (ProfiledBinary) enuBinaries.nextElement();
+						pBinary.setEnabled(graphIndex, false);
+					}
+					graphBinaries.clear();
+
+					ProfiledThreshold thresholdBinary = this.myGraph.getThresholdBinary();
+
+					if (thresholdBinary.isEnabled(graphIndex)) {
+						ArrayList<ProfiledGeneric> items = thresholdBinary.getItems(graphIndex);
+						// disable all items below the threshold
+						for (int i = 0; i < items.size(); i++) {
+							items.get(i).setEnabled(graphIndex, false);
+						}
+					}
+
+					// previous functions are not necessarily graphed this time
+					Enumeration<ProfiledGeneric> enuFunctions = graphFunctions.elements();
+					while (enuFunctions.hasMoreElements()) {
+						ProfiledFunction pFunction = (ProfiledFunction) enuFunctions.nextElement();
+						pFunction.setEnabled(graphIndex, false);
+					}
+					graphFunctions.clear();
+
+					ProfiledThreshold thresholdFunction = this.myGraph.getThresholdFunction();
+
+					if (thresholdFunction.isEnabled(graphIndex)) {
+						ArrayList<ProfiledGeneric> items = thresholdFunction.getItems(graphIndex);
+						// disable all items below the threshold
+						for (int i = 0; i < items.size(); i++) {
+							items.get(i).setEnabled(graphIndex, false);
+						}
+					}
+
+					GppSample[] sortedSamples = trace.getSortedGppSamples();
+
+					// set up in case we find binaries below the threshold
+					boolean lowBinary;
+					thresholdBinary.init(graphIndex);
+
+					// set up in case we find functions below the threshold
+					boolean lowFunction;
+					thresholdFunction.init(graphIndex);
+
+					int functionThreshold = (Integer) NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.thresholdCountFunction"); //$NON-NLS-1$
+					for (int i = startSampleIndex; i < endSampleIndex; i++) {
+						GppSample sample = sortedSamples[i];
+
+						ProfiledThread pThread = (ProfiledThread) traceThreads.elementAt(sample.threadIndex);
+						if (pThread.isEnabled(graphIndex)) {
+							// function list is based on threads
+							ProfiledFunction pFunction = (ProfiledFunction) traceFunctions.elementAt(sample.functionIndex);
+							String functionName = pFunction.getNameString();
+
+							lowFunction = pFunction.getTotalSampleCount() < functionThreshold;
+
+							if (!foundFunctions.containsKey(functionName)) {
+								foundFunctions.put(functionName, functionName);
+								if (lowFunction) {
+									thresholdFunction.addItem(graphIndex, pFunction, 1);
+								} else {
+									pFunction.setEnabled(graphIndex, true);
+									pFunction.setSampleCount(graphIndex, 1);
+									graphFunctions.add(pFunction);
+								}
+							} else {
+								if (lowFunction)
+									thresholdFunction.incSampleCount(graphIndex);
+								else
+									pFunction.incSampleCount(graphIndex);
+							}
+
+							// binary list is based on threads and functions
+							ProfiledBinary pBinary = (ProfiledBinary) traceBinaries.elementAt(sample.binaryIndex);
+							String binaryName = pBinary.getNameString();
+
+							lowBinary = pBinary.getTotalSampleCount() < (Integer)NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.thresholdCountBinary"); //$NON-NLS-1$
+
+							if (!foundBinaries.containsKey(binaryName)) {
+								foundBinaries.put(binaryName, binaryName);
+								if (lowBinary) {
+									thresholdBinary.addItem(graphIndex, pBinary, 1);
+								} else {
+									pBinary.setEnabled(graphIndex, true);
+									pBinary.setSampleCount(graphIndex, 1);
+									graphBinaries.add(pBinary);
+								}
+							} else {
+								if (lowBinary)
+									thresholdBinary.incSampleCount(graphIndex);
+								else
+									pBinary.incSampleCount(graphIndex);
+							}
+						}
+					}
+
+					// set the % load
+					for (int i = 0; i < graphBinaries.size(); i++) {
+						ProfiledBinary pBinary = (ProfiledBinary) graphBinaries.elementAt(i);
+						pBinary.setLoadAndString(graphIndex, (float)(pBinary.getSampleCount(graphIndex)*percentPerSample));
+					}
+					thresholdBinary.setLoadAndString(graphIndex,
+							(float)(thresholdBinary.getSampleCount(graphIndex)*percentPerSample));
+
+					for (int i = 0; i < graphFunctions.size(); i++) {
+						ProfiledFunction pFunction = (ProfiledFunction) graphFunctions.elementAt(i);
+						pFunction.setLoadAndString(graphIndex, (float)(pFunction.getSampleCount(graphIndex)*percentPerSample));
+					}
+					thresholdFunction.setLoadAndString(graphIndex,
+							(float)(thresholdFunction.getSampleCount(graphIndex)*percentPerSample));
+
+					// update the table items and redraw the table
+					this.myGraph.getBinaryTable().piEventReceived(be3);
+					this.myGraph.getFunctionTable().piEventReceived(be3);
+					break;
+				}
+				default:
+				{
+					break;
+				}
+			}
+		}
+		else if (be.getType() == PIEvent.CHANGED_BINARY_TABLE)
+		{
+			// This routine enables all entries in the next table
+
+			int drawMode = this.myGraph.getDrawMode();
+			if (drawMode != Defines.THREADS_BINARIES_FUNCTIONS)
+				return;
+
+			// we don't need to redraw the thread table, since it has not changed
+
+			GppTrace trace = (GppTrace)(this.myGraph.getTrace());
+			Vector<ProfiledGeneric> traceBinaries  = trace.getIndexedBinaries();
+
+			int graphIndex = this.myGraph.getGraphIndex();
+			int startSampleIndex = trace.getStartSampleIndex();
+			int endSampleIndex   = trace.getEndSampleIndex();
+			double percentPerSample;
+			if (startSampleIndex == endSampleIndex)
+				percentPerSample = 0.0;
+			else
+				percentPerSample = 100.0 / ((double)(endSampleIndex - startSampleIndex));
+
+			PIEvent be3 = new PIEvent(be.getValueObject(), PIEvent.SELECTION_AREA_CHANGED3);
+
+			// get new function counts and loads
+			Vector<ProfiledGeneric> traceFunctions = trace.getIndexedFunctions();
+			Vector<ProfiledGeneric> graphFunctions = this.myGraph.getProfiledFunctions();
+			Hashtable<String,String> foundFunctions = new Hashtable<String,String>();
+
+			// previous functions are not necessarily graphed this time
+			Enumeration<ProfiledGeneric> enu = graphFunctions.elements();
+			while (enu.hasMoreElements()) {
+				ProfiledFunction pFunction = (ProfiledFunction) enu.nextElement();
+				pFunction.setEnabled(graphIndex, false);
+			}
+			graphFunctions.clear();
+
+			ProfiledThreshold thresholdFunction = this.myGraph.getThresholdFunction();
+
+			if (thresholdFunction.isEnabled(graphIndex)) {
+				ArrayList<ProfiledGeneric> items = thresholdFunction.getItems(graphIndex);
+				// disable all items below the threshold
+				for (int i = 0; i < items.size(); i++) {
+					items.get(i).setEnabled(graphIndex, false);
+				}
+			}
+
+			GppSample[] sortedSamples = trace.getSortedGppSamples();
+
+			// set up in case we find functions below the threshold
+			boolean lowFunction;
+			thresholdFunction.init(graphIndex);
+
+			for (int i = startSampleIndex; i < endSampleIndex; i++) {
+				GppSample sample = sortedSamples[i];
+
+				ProfiledBinary pBinary = (ProfiledBinary) traceBinaries.elementAt(sample.binaryIndex);
+				if (pBinary.isEnabled(graphIndex)) {
+					// function list is based on binaries
+					ProfiledFunction pFunction = (ProfiledFunction) traceFunctions.elementAt(sample.functionIndex);
+					String functionName = pFunction.getNameString();
+
+					lowFunction = pFunction.getTotalSampleCount() < (Integer)NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.thresholdCountFunction"); //$NON-NLS-1$
+
+					if (!foundFunctions.containsKey(functionName)) {
+						foundFunctions.put(functionName, functionName);
+						if (lowFunction) {
+							thresholdFunction.addItem(graphIndex, pFunction, 1);
+						} else {
+							pFunction.setEnabled(graphIndex, true);
+							pFunction.setSampleCount(graphIndex, 1);
+							graphFunctions.add(pFunction);
+						}
+					} else {
+						if (lowFunction)
+							thresholdFunction.incSampleCount(graphIndex);
+						else
+							pFunction.incSampleCount(graphIndex);
+					}
+				}
+			}
+
+			// set the % load
+			for (int i = 0; i < graphFunctions.size(); i++) {
+				ProfiledFunction pFunction = (ProfiledFunction) graphFunctions.elementAt(i);
+				pFunction.setLoadAndString(graphIndex, (float)(pFunction.getSampleCount(graphIndex)*percentPerSample));
+			}
+			thresholdFunction.setLoadAndString(graphIndex,
+					(float)(thresholdFunction.getSampleCount(graphIndex)*percentPerSample));
+
+			// update the table items and redraw the table
+			this.myGraph.getFunctionTable().piEventReceived(be3);
+		}
+		else if (be.getType() == PIEvent.CHANGED_FUNCTION_TABLE)
+		{
+			// This routine enables all entries in the next table
+
+			int drawMode = this.myGraph.getDrawMode();
+			if (drawMode != Defines.THREADS_FUNCTIONS_BINARIES)
+				return;
+
+			// we don't need to redraw the thread table, since it has not changed
+
+			GppTrace trace = (GppTrace)(this.myGraph.getTrace());
+			Vector<ProfiledGeneric> traceFunctions = trace.getIndexedFunctions();
+
+			int graphIndex = this.myGraph.getGraphIndex();
+			int startSampleIndex = trace.getStartSampleIndex();
+			int endSampleIndex   = trace.getEndSampleIndex();
+			double percentPerSample;
+			if (startSampleIndex == endSampleIndex)
+				percentPerSample = 0.0;
+			else
+				percentPerSample = 100.0 / ((double)(endSampleIndex - startSampleIndex));
+
+			PIEvent be3 = new PIEvent(be.getValueObject(), PIEvent.SELECTION_AREA_CHANGED3);
+
+			// get new counts and loads
+			Vector<ProfiledGeneric> traceBinaries   = trace.getIndexedBinaries();
+			Vector<ProfiledGeneric> graphBinaries   = this.myGraph.getProfiledBinaries();
+			Hashtable<String,String> foundBinaries  = new Hashtable<String,String>();
+
+			// previous binaries are not necessarily graphed this time
+			Enumeration<ProfiledGeneric> enu = graphBinaries.elements();
+			while (enu.hasMoreElements()) {
+				ProfiledBinary pBinary = (ProfiledBinary) enu.nextElement();
+				pBinary.setEnabled(graphIndex, false);
+			}
+			graphBinaries.clear();
+
+			ProfiledThreshold thresholdBinary = this.myGraph.getThresholdBinary();
+
+			if (thresholdBinary.isEnabled(graphIndex)) {
+				ArrayList<ProfiledGeneric> items = thresholdBinary.getItems(graphIndex);
+				// disable all items below the threshold
+				for (int i = 0; i < items.size(); i++) {
+					items.get(i).setEnabled(graphIndex, false);
+				}
+			}
+
+			GppSample[] sortedSamples = trace.getSortedGppSamples();
+
+			// set up in case we find binaries below the threshold
+			boolean lowBinary;
+			thresholdBinary.init(graphIndex);
+
+			int binaryThreshold = (Integer) NpiInstanceRepository.getInstance().activeUidGetPersistState("com.nokia.carbide.cpp.pi.address.thresholdCountBinary"); //$NON-NLS-1$
+			for (int i = startSampleIndex; i < endSampleIndex; i++) {
+				GppSample sample = sortedSamples[i];
+
+				ProfiledFunction pFunction = (ProfiledFunction) traceFunctions.elementAt(sample.functionIndex);
+				if (pFunction.isEnabled(graphIndex)) {
+					// binary list is based on functions
+					ProfiledBinary pBinary = (ProfiledBinary) traceBinaries.elementAt(sample.binaryIndex);
+					String binaryName = pBinary.getNameString();
+
+					lowBinary = pBinary.getTotalSampleCount() < binaryThreshold;
+
+					if (!foundBinaries.containsKey(binaryName)) {
+						foundBinaries.put(binaryName, binaryName);
+						if (lowBinary) {
+							thresholdBinary.addItem(graphIndex, pBinary, 1);
+						} else {
+							pBinary.setEnabled(graphIndex, true);
+							pBinary.setSampleCount(graphIndex, 1);
+							graphBinaries.add(pBinary);
+						}
+					} else {
+						if (lowBinary)
+							thresholdBinary.incSampleCount(graphIndex);
+						else
+							pBinary.incSampleCount(graphIndex);
+					}
+				}
+			}
+
+			// set the % load
+			for (int i = 0; i < graphBinaries.size(); i++) {
+				ProfiledBinary pBinary = (ProfiledBinary) graphBinaries.elementAt(i);
+				pBinary.setLoadAndString(graphIndex, (float)(pBinary.getSampleCount(graphIndex)*percentPerSample));
+			}
+			thresholdBinary.setLoadAndString(graphIndex,
+					(float)(thresholdBinary.getSampleCount(graphIndex)*percentPerSample));
+
+			// update the table items and redraw the table
+			this.myGraph.getBinaryTable().piEventReceived(be3);
+		}
+	}
+
+	public void setSelectedNames()
+	{
+		Object[] selectedValues = this.tableViewer.getCheckedElements();
+        String[] threadNames = new String[selectedValues.length];
+
+        for (int i = 0; i < selectedValues.length; i++)
+		{
+			if (selectedValues[i] instanceof ProfiledThread)
+			{
+				ProfiledThread pt = (ProfiledThread)selectedValues[i];
+				threadNames[i] = pt.getNameString();
+			}
+		}
+
+        PIVisualSharedData shared = myGraph.getSharedDataInstance();
+		shared.GPP_SelectedThreadNames = threadNames;
+	}
+
+	private class SharedCheckHandler implements ICheckStateListener
+	{
+		public void checkStateChanged(CheckStateChangedEvent event) {
+
+       		if (!(event.getElement() instanceof ProfiledGeneric))
+       			return;
+       		
+       		// set the stored value to the checkbox value
+       		ProfiledGeneric pg = (ProfiledGeneric)event.getElement();
+	        pg.setEnabled(myGraph.getGraphIndex(), event.getChecked());
+ 
+	        // this table's set of checkbox-selected rows has changed, so propagate that information
+       		setSelectedNames();
+
+       		if (   (pg.getSampleCount(myGraph.getGraphIndex()) != 0)
+       			|| (myGraph.getDrawMode() == Defines.THREADS))
+       			selectionChangeNotify();
+
+       		table.deselectAll();
+		}
+	}
+
+	protected void selectionChangeNotify() {
+		PIEvent be = new PIEvent(null, PIEvent.CHANGED_THREAD_TABLE);
+		myGraph.piEventReceived(be);
+    }
+	
+	public void sortOnColumnSelection(TableColumn tableColumn) {
+			int columnId = ((Integer) tableColumn.getData()).intValue();
+            if (sortColumn == columnId) {
+            	// sort in other order
+            	sortAscending = !sortAscending;
+            } else {
+            	// sort in the default order
+				switch (columnId)
+				{
+					case COLUMN_ID_SHOW:
+					case COLUMN_ID_PERCENT_LOAD:
+					case COLUMN_ID_SAMPLE_COUNT:
+					case COLUMN_ID_PRIORITY:
+					{
+		            	// sort in descending order (for checked boxes column, this means selected boxes first)
+		            	sortAscending = false;
+		                break;
+					}
+					case COLUMN_ID_THREAD:
+					{
+		            	// sort in ascending order
+		            	sortAscending = true;
+		                break;
+					}
+					default:
+					{
+						return;
+					}
+				}
+            }
+
+			sortColumn = columnId;
+			quickSort(sortColumn, profiledThreads);
+	}
+
+	private class ColumnSelectionHandler extends SelectionAdapter
+	{
+		public void widgetSelected(SelectionEvent e)
+        {
+        	// wait for the previous sort to finish
+        	if (sorting || !(e.widget instanceof TableColumn))
+        		return;
+
+        	sortOnColumnSelection((TableColumn) e.widget);
+        }
+	}
+
+	public void updateProfiledAndItemData(boolean setInput)
+	{
+		tableItemData.clear();
+		profiledThreads.clear();
+
+		// profiledThreads and tableItemData contain one entry per table row
+		Enumeration<ProfiledGeneric> enu = myGraph.getProfiledThreads().elements();
+		while (enu.hasMoreElements())
+		{
+			ProfiledThread nextElement = (ProfiledThread)enu.nextElement();
+			tableItemData.add(nextElement);
+			profiledThreads.add(nextElement);
+		}
+
+		if (myGraph.getThresholdThread().getItemCount(myGraph.getGraphIndex()) != 0) {
+			tableItemData.add(myGraph.getThresholdThread());
+			profiledThreads.add(myGraph.getThresholdThread());
+		}
+		
+		// now sort the items in increasing total sample count, so that they graph correctly
+		Object[] sorted = myGraph.getProfiledThreads().toArray();
+		Arrays.sort(sorted, new Comparator<Object>() {
+			
+			public int compare(Object arg0, Object arg1)
+			{
+				if (arg0 instanceof ProfiledGeneric && arg1 instanceof ProfiledGeneric)
+					return ((ProfiledGeneric)arg0).getTotalSampleCount() -
+							((ProfiledGeneric)arg1).getTotalSampleCount();
+				else
+					return 0;
+			}
+		});
+
+		// now create the sorted list used to draw the graph
+		myGraph.getSortedThreads().clear();
+
+		if (myGraph.getThresholdThread().getItemCount(myGraph.getGraphIndex()) != 0)
+			myGraph.getSortedThreads().add(myGraph.getThresholdThread());
+
+		for (int i = 0; i < sorted.length; i++)
+			myGraph.getSortedThreads().add((ProfiledGeneric) sorted[i]);
+
+		// refresh the table, if needed
+		if (setInput)
+			refreshTableViewer();
+	}
+
+	public void quickSort(int sortBy, Vector<ProfiledGeneric> profiled)
+	{
+		if (profiled.size() == 0)
+			return;
+
+		this.sorting = true;
+
+		ProfiledGeneric pGeneric = profiled.elementAt(profiled.size() - 1);
+
+		if (pGeneric instanceof ProfiledThreshold) {
+			profiled.removeElementAt(profiled.size() - 1);
+		}
+
+		this.sorter.setupSort(sortBy, this.myGraph.getGraphIndex(), sortAscending);
+
+		switch (sortBy) {
+			case COLUMN_ID_SHOW:
+			{
+			    this.sorter.quickSortByShow(profiled);
+	            break;
+			}
+			case COLUMN_ID_PERCENT_LOAD:
+			{
+			    this.sorter.quickSortByAverageLoad(profiled);
+	            break;
+			}
+			case COLUMN_ID_THREAD:
+			{
+			    this.sorter.quickSortByThread(profiled);
+	            break;
+			}
+			case COLUMN_ID_BINARY:
+			{
+			    this.sorter.quickSortByBinary(profiled);
+	            break;
+			}
+			case COLUMN_ID_FUNCTION:
+			{
+			    this.sorter.quickSortByFunction(profiled);
+	            break;
+			}
+			case COLUMN_ID_SAMPLE_COUNT:
+			{
+			    this.sorter.quickSortBySampleCount(profiled);
+	            break;
+			}
+			case COLUMN_ID_PRIORITY:
+			{
+			    this.sorter.quickSortByPriority(profiled, priorityValues);
+	            break;
+			}
+			default:
+			{
+				break;
+			}
+		}
+ 		
+		Enumeration<ProfiledGeneric> e = this.sorter.getSortedList().elements();
+		tableItemData = setTableItemData(e);
+		if (pGeneric instanceof ProfiledThreshold) {
+			tableItemData.add(pGeneric);
+			profiled.add(pGeneric);
+		}
+		
+		// find the column corresponding to sortBy, and give it a column direction
+		// NOTE: treat sort by binary path then by binary name as sort by path
+		if (sortBy == COLUMN_ID_FULL_PATH)
+			sortBy = COLUMN_ID_PATH;
+		else if (sortBy == COLUMN_ID_FULL_IN_PATH)
+			sortBy = COLUMN_ID_IN_BINARY_PATH;
+
+		TableColumn sortByColumn = null;
+		for (int i = 0; i < this.table.getColumnCount(); i++) {
+			if (this.table.getColumn(i).getData() instanceof Integer) {
+				if (((Integer)this.table.getColumn(i).getData()) == sortBy) {
+					sortByColumn = this.table.getColumn(i);
+					break;
+				}
+			}
+		}
+
+		if (sortByColumn != null) {
+			this.table.setSortColumn(sortByColumn);
+			this.table.setSortDirection(sortAscending ? SWT.UP : SWT.DOWN);
+		}
+
+		refreshTableViewer();
+		this.sorting = false;
+	}
+
+	protected MenuItem getSavePrioritySamplesItem(Menu menu, boolean enabled) {
+	    MenuItem saveSamplesItem = new MenuItem(menu, SWT.PUSH);
+
+		saveSamplesItem.setText(Messages.getString("AddrThreadTable.savePrioritySamples")); //$NON-NLS-1$
+		saveSamplesItem.setEnabled(enabled);
+		
+		if (enabled) {
+			saveSamplesItem.addSelectionListener(new SelectionAdapter() { 
+				public void widgetSelected(SelectionEvent e) {
+					action("savePrioritySamples"); //$NON-NLS-1$
+				}
+			});
+		}
+
+		return saveSamplesItem;
+	}
+
+	protected Menu getTableMenu(Decorations parent, int graphIndex, int drawMode) {
+
+		// get rid of last Menu created so we don't have double menu
+		// in on click
+		if (contextMenu != null) {
+			contextMenu.dispose();
+		}
+		
+		// recreate each time, in case the drawMode has changed since creation
+		contextMenu = new Menu(parent, SWT.POP_UP);
+
+		// Use drawMode to determine the drill down items and
+		// whether to show a color column
+		addDrillDownItems(contextMenu, drawMode);
+		
+		// check and uncheck boxes
+		new MenuItem(contextMenu, SWT.SEPARATOR);
+		getCheckRows(contextMenu, this.table.getSelectionCount() > 0);
+		
+		// select all, copy, and copy all
+		new MenuItem(contextMenu, SWT.SEPARATOR);
+		getSelectAllItem(contextMenu, this.table.getItemCount() > 0);
+		getCopyItem(contextMenu, this.table.getSelectionCount() > 0);
+		getCopyTableItem(contextMenu, this.table.getItemCount() > 0);
+		
+		// copy drilldown tables, if in drilldown mode
+		switch (drawMode)
+		{
+			case Defines.THREADS:
+			case Defines.BINARIES:
+			case Defines.FUNCTIONS:
+				getCopyDrilldownItem(contextMenu, false);
+				break;
+			default:
+				getCopyDrilldownItem(contextMenu, true);
+				break;
+		}
+		
+		// save all and save drilldown tables, if in drilldown mode
+		new MenuItem(contextMenu, SWT.SEPARATOR);
+		getSaveTableItem(contextMenu, this.table.getItemCount() > 0);
+		
+		switch (drawMode)
+		{
+			case Defines.THREADS:
+			case Defines.BINARIES:
+			case Defines.FUNCTIONS:
+				getSaveDrilldownItem(contextMenu, false);
+				break;
+			default:
+				getSaveDrilldownItem(contextMenu, true);
+				break;
+		}
+		
+		double startTime = PIPageEditor.currentPageEditor().getStartTime();
+		double endTime   = PIPageEditor.currentPageEditor().getEndTime();
+
+		// save raw samples
+		boolean haveSamples = false;
+		for (int i = 0; !haveSamples && i < profiledThreads.size(); i++) {
+			ProfiledGeneric pg = profiledThreads.get(i);
+			haveSamples = pg.isEnabled(graphIndex) && pg.getSampleCount(graphIndex) > 0;
+		}
+		
+		if (!haveSamples || (startTime == -1) || (endTime   == -1) || (startTime == endTime))
+			getSaveSamplesItem(contextMenu, Messages.getString("AddrThreadTable.threads"), false); //$NON-NLS-1$
+		else
+			getSaveSamplesItem(contextMenu, Messages.getString("AddrThreadTable.threads"), true); //$NON-NLS-1$
+		
+		// save priority samples
+		if (priorityAdded) {
+			if (!haveSamples || (startTime == -1) || (endTime   == -1) || (startTime == endTime))
+				getSavePrioritySamplesItem(contextMenu, false);
+			else
+				getSavePrioritySamplesItem(contextMenu, true);
+		}
+
+		// recolor selected threads
+		switch (drawMode)
+		{
+			case Defines.THREADS:
+			case Defines.BINARIES_THREADS:
+			case Defines.BINARIES_FUNCTIONS_THREADS:
+			case Defines.FUNCTIONS_THREADS:
+			case Defines.FUNCTIONS_BINARIES_THREADS:
+			{
+				// recolor selected items
+				new MenuItem(contextMenu, SWT.SEPARATOR);
+				getRecolorItem(contextMenu, Messages.getString("AddressPlugin.threads"),  //$NON-NLS-1$
+								this.table.getSelectionCount() > 0);
+				break;
+			}
+			case Defines.THREADS_FUNCTIONS:
+			case Defines.THREADS_FUNCTIONS_BINARIES:
+			case Defines.THREADS_BINARIES:
+			case Defines.THREADS_BINARIES_FUNCTIONS:
+			case Defines.BINARIES_THREADS_FUNCTIONS:
+			case Defines.FUNCTIONS_THREADS_BINARIES:
+			{
+				break;
+			}
+			default:
+				break;
+		}
+
+		new MenuItem(contextMenu, SWT.SEPARATOR);
+		getChangeThresholds(contextMenu);
+
+		contextMenu.setVisible(true);
+		
+		return contextMenu;
+	}
+
+	protected void addPriorityColumn(Hashtable<Integer,String> priorities)
+	{
+		if (priorityAdded)
+			return;
+		
+		// store the priority information
+		this.priorityTable = priorities;
+	    priorityValues = new Hashtable<Integer,Integer>();
+		for (Enumeration<Integer> e = priorityTable.keys(); e.hasMoreElements();)
+		{
+			Integer key = e.nextElement();
+			priorityValues.put(key, parsePriorityValue(this.priorityTable.get(key)));
+		}
+
+		// if the tableViewer already exists, add a column
+		if (   (tableViewer != null)
+			&& (tableViewer.getTable() != null))
+	    {
+			Display.getDefault().syncExec( new Runnable() {
+				public void run() {
+					addPriorityColumn();
+				}
+			});	
+	    }
+
+		priorityAdded = true;
+	}
+	
+	private void addPriorityColumn()
+	{
+		TableColumn column;
+		column = new TableColumn(tableViewer.getTable(), SWT.LEFT);
+		column.setText(COLUMN_HEAD_PRIORITY);
+		column.setWidth(COLUMN_WIDTH_PRIORITY);
+		column.setData(new Integer(COLUMN_ID_PRIORITY));
+		column.setMoveable(true);
+		column.setResizable(true);
+		column.addSelectionListener(new ColumnSelectionHandler());
+	}
+
+	private Integer parsePriorityValue(String priorityString)
+	{
+		if (priorityString == null || priorityString.equals(Messages.getString("AddrThreadTable.unsolvedPriority"))) //$NON-NLS-1$
+			return new Integer(Integer.MIN_VALUE);
+		else
+		{
+			int endIndex = priorityString.indexOf(Messages.getString("AddrThreadTable.priorityStringEnding")); //$NON-NLS-1$
+			int beginIndex = priorityString.indexOf(Messages.getString("AddrThreadTable.priorityStringStart")); //$NON-NLS-1$
+			String value = priorityString.substring(beginIndex+2, endIndex);
+			return new Integer(Integer.parseInt(value));
+		}
+	}
+
+    // class to pass sample data to the save wizard
+    public class SavePrioritySampleString implements ISaveSamples {
+    	int graphIndex;
+    	int drawMode;
+    	boolean done = false;
+    	
+    	public SavePrioritySampleString(int graphIndex, int drawMode) {
+    		this.graphIndex = graphIndex;
+    		this.drawMode   = drawMode;
+		}
+
+    	public String getData() {
+    		if (done)
+    			return null;
+    		
+			String returnString = getPrioritySampleString(graphIndex, drawMode);
+			done = true;
+			return returnString;
+		}
+
+		public String getData(int size) {
+ 			return getData();
+		}
+
+		public int getIndex() {
+			return done ? 1 : 0;
+		}
+
+		public void clear() {
+			done = false;
+		}
+    }
+    
+    protected class PrioritySample {
+    	int time;
+    	ProfiledThread pt;
+    	String priorityString;
+    	
+    	PrioritySample(int time, ProfiledThread pt, String priorityString) {
+    		this.time = time;
+    		this.pt   = pt;
+    		this.priorityString = priorityString;
+    	}
+    }
+
+    protected void createPrioritySample(ArrayList<PrioritySample> prioritySamples, int graphIndex, ProfiledThread pt, int startTime, int endTime) {
+    	PrioritySample localSample = null;
+    	boolean addedLocalSample = false;
+    	String priority = priorityTable.get(new Integer(pt.getThreadId()));
+		
+		if (priority == null) {
+			prioritySamples.add(new PrioritySample(0, pt, Messages.getString("AddrThreadTable.unknownPriority"))); //$NON-NLS-1$
+			return;
+		}
+		
+		localSample = new PrioritySample(0, pt, Messages.getString("AddrThreadTable.unrecordedPriority")); //$NON-NLS-1$
+		addedLocalSample = false;
+		
+		while (priority.indexOf('(') != -1) {
+			while (priority.charAt(0) == ' ')		// remove leading spaces
+				priority = priority.substring(1);
+			
+			int nextMatch = priority.indexOf('(');
+			String priorityStr = priority.substring(0, nextMatch);	// priority is before the open paren
+			priority = priority.substring(nextMatch + 1);			// consume the paren, too 
+			
+			nextMatch = priority.indexOf(')');
+			String timeStr = priority.substring(0, nextMatch - 1);
+			
+			double d = Double.parseDouble(timeStr) * 1000.0; 
+			int time = (int) d;
+			
+			if (time > endTime) {
+				// we're past the end
+				if (!addedLocalSample) {
+					// previous priority was before the start time, but include it anyway
+					prioritySamples.add(localSample);
+					return;
+				}
+			} else if (time < startTime) {
+				// we're before the start
+				// only keep the priority closest to the start time
+				localSample = new PrioritySample(time, pt, priorityStr);
+			} else {
+				// we're in the interval, so add the previous sample, if needed
+				if (!addedLocalSample && time != startTime)
+					prioritySamples.add(localSample);
+
+				localSample = new PrioritySample(time, pt, priorityStr);
+				prioritySamples.add(localSample);
+				addedLocalSample = true;
+			}
+			priority = priority.substring(nextMatch + 1);
+		}
+		
+		if (localSample != null && !addedLocalSample)
+			prioritySamples.add(localSample);
+    }
+    
+    protected class PrioritySampleCompare implements Comparator<PrioritySample> {
+    	public int compare(PrioritySample ps1, PrioritySample ps2) {
+    		try {
+    			if (ps1.time == ps2.time)
+    				return ps1.pt.getNameString().compareToIgnoreCase(ps2.pt.getNameString());
+    			else
+    				return ps1.time - ps2.time;
+    		} catch (Exception e) {
+    			return 0;
+    		}
+    	}
+    }
+    
+	protected String getPrioritySampleString(int graphIndex, int drawMode)
+	{
+		GppTraceGraph graph = (GppTraceGraph)(this.myGraph);
+		GppTrace trace = (GppTrace)(graph.getTrace());
+		
+		int startIndex = trace.getStartSampleIndex();
+		int endIndex   = trace.getEndSampleIndex();
+
+		ArrayList<PrioritySample> prioritySamples = new ArrayList<PrioritySample>();
+		ProfiledThread pt;
+		
+		for (int i = 0; i < profiledThreads.size(); i++) {
+			if (!profiledThreads.get(i).isEnabled(graphIndex) || profiledThreads.get(i).getSampleCount(graphIndex) <= 0)
+				continue;
+
+			if (profiledThreads.get(i) instanceof ProfiledThread) {
+				pt = (ProfiledThread) profiledThreads.get(i);
+
+				createPrioritySample(prioritySamples, graphIndex, pt, startIndex, endIndex); 
+			} else if (profiledThreads.get(i) instanceof ProfiledThreshold) {
+				ProfiledThreshold pth = (ProfiledThreshold) profiledThreads.get(i);
+				for (int j = 0; j < pth.getItems(graphIndex).size(); j++) {
+					pt = (ProfiledThread) pth.getItems(graphIndex).get(j);
+					
+					if (pt.isEnabled(graphIndex) && pt.getSampleCount(graphIndex) > 0)
+						createPrioritySample(prioritySamples, graphIndex, pt, startIndex, endIndex); 
+				}
+			}
+		}
+		
+		Collections.sort(prioritySamples, new PrioritySampleCompare());
+		
+		String returnString = "Time (ms),Thread,Priority\n";  //$NON-NLS-1$
+			
+			for (PrioritySample sample : prioritySamples) {
+				returnString +=   sample.time + "," + sample.pt.getNameString() + "," //$NON-NLS-1$ //$NON-NLS-2$
+								+ sample.priorityString + "\n";  //$NON-NLS-1$ //$NON-NLS-2$
+			}
+		
+		return returnString;
+	}
+}