sysperfana/perfinvestigator/com.nokia.carbide.cpp.pi.address/src/com/nokia/carbide/cpp/pi/address/AddrThreadTable.java
author Toni Pulkkinen <ext-toni.p.pulkkinen@nokia.com>
Wed, 23 Jun 2010 15:05:09 +0300
changeset 12 ae255c9aa552
parent 5 844b047e260d
permissions -rw-r--r--
Performance Investigator Carbide extension 2.4.0

/*
 * 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.address.GppModelAdapter;
import com.nokia.carbide.cpp.internal.pi.analyser.NpiInstanceRepository;
import com.nokia.carbide.cpp.internal.pi.interfaces.ISaveSamples;
import com.nokia.carbide.cpp.internal.pi.model.ICPUScale;
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, GppModelAdapter adapter)
	{
		super(myGraph, parent, adapter);
	}

	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(Integer.valueOf(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(Integer.valueOf(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(Integer.valueOf(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(Integer.valueOf(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();
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object, int)
		 */
		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();

						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
					Object object = profiledItem.getAdapter(ICPUScale.class);
					if(object != null && profiledItem.isScaledCpu()){
						String avgLoad = profiledItem.getAverageLoadValueString(graphIndex);
						if (avgLoad != null && avgLoad.length() > 0) {
							if(avgLoad.indexOf(',') != -1){ //$NON-NLS-1$
								avgLoad = avgLoad.replace(',', '.');
							}
							float value = Float.valueOf(avgLoad);
							if (value > 0.0) {
								ICPUScale cpuScale = (ICPUScale) object;
								value = value / 100;
								int startTime = (int) (PIPageEditor
										.currentPageEditor().getStartTime() * 1000);
								int endTime = (int) (PIPageEditor
										.currentPageEditor().getEndTime() * 1000);
								value = value
										* cpuScale.calculateScale(startTime,
												endTime) * 100;
								DecimalFormat decimalFormat = new DecimalFormat(
										"#0.00"); //$NON-NLS-1$
								return decimalFormat.format(value);
							}
						}									
					}
					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(Integer.valueOf(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(IGppTraceGraph.ACTION_CHANGE_THRESHOLD_THREAD))
		{
			ProfiledThreshold threshold = this.myGraph.getThresholdThread();
			boolean enabled = threshold.isEnabled(graphIndex);

			this.tableItemData.clear();
			this.profiledThreads.clear();
			this.myGraph.getSortedThreads().clear();
			if (threshold.getItems() != null)
				threshold.getItems().clear();
			adapter.init(threshold, graphIndex);

			// 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 (adapter.getTotalSampleCount(nextElement) < threadThreshold) {
					nextElement.setEnabled(graphIndex, enabled);
					adapter.addItem(threshold, graphIndex, nextElement, 0);
				} else {
					tableItemData.add(nextElement);
					profiledThreads.add(nextElement);
					myGraph.getSortedThreads().add(nextElement);
				}
			}

			if (threshold.getItemCount() != 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;
   					for (ProfiledGeneric p : pThreshold.getItems())
   						p.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.gppSelectedThreadNames = 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;
   					for (ProfiledGeneric p : pThreshold.getItems())
   						p.setEnabled(graphIndex, addIt);
   				}
			}
		}

        // this table's set of checkbox-selected rows has changed,
		// so propagate that information
		PIVisualSharedData shared = myGraph.getSharedDataInstance();
		shared.gppSelectedThreadNames = 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
						// CH: refactor! This could be done via an observer pattern. This class should not have knowledge of all other graphs  
						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);
						if (gppTrace.getCPUCount() > 1){ //SMP
							for (int cpu = 0; cpu < gppTrace.getCPUCount(); cpu++) {
								gppTrace.getGppGraph(3 + cpu, 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++) {
			IGppTraceGraph 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);

		// 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 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();
			gppTrace.setThreadBinary(graphIndex, adapter, this.myGraph.getProfiledBinaries());

			// 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) {

			// 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) { //CH: refactor!  Runtime exceptions should not be caught!
			}

			// move the left sash to the middle
			try {
				FormData formData = (FormData) this.myGraph.getLeftSash().getLayoutData();
				formData.left = new FormAttachment(50); // middle
			} catch (ClassCastException e1) { //CH: refactor!  Runtime exceptions should not be caught!
			}

			// 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 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();
		gppTrace.setThreadBinaryFunction(graphIndex, adapter, this.myGraph.getProfiledFunctions());

		// 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 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();
			gppTrace.setThreadFunction(graphIndex, adapter, this.myGraph.getProfiledFunctions());

			// 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) {
			// 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 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();
		gppTrace.setThreadFunctionBinary(graphIndex, adapter, this.myGraph.getProfiledBinaries());

		// 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 (adapter.getTotalSampleCount(pGeneric.elementAt(i)) < 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());
			PIEvent be3 = new PIEvent(be.getValueObject(), PIEvent.SELECTION_AREA_CHANGED3);

			switch (drawMode) {
				case Defines.THREADS_BINARIES:
				{
					trace.setThreadBinary(graphIndex, adapter, this.myGraph.getProfiledBinaries());

					// update the table items and redraw the table
					this.myGraph.getBinaryTable().piEventReceived(be3);
					break;
				}
				case Defines.THREADS_BINARIES_FUNCTIONS:
				{
					// previous binaries are not necessarily graphed this time - reset
					trace.setThreadBinary(graphIndex, adapter, this.myGraph.getProfiledBinaries());
					// previous functions are not necessarily graphed this time - reset
					trace.setThreadBinaryFunction(graphIndex, adapter, this.myGraph.getProfiledFunctions());

					// update the table items and redraw the table
					this.myGraph.getBinaryTable().piEventReceived(be3);
					this.myGraph.getFunctionTable().piEventReceived(be3);
					break;
				}
				case Defines.THREADS_FUNCTIONS:
				{
					// previous functions are not necessarily graphed this time
					trace.setThreadFunction(graphIndex, adapter, this.myGraph.getProfiledFunctions());

					// update the table items and redraw the table
					this.myGraph.getFunctionTable().piEventReceived(be3);
					break;
				}
				case Defines.THREADS_FUNCTIONS_BINARIES:
				{
					// previous functions and binaries are not necessarily graphed this time
					trace.setThreadFunction(graphIndex, adapter, this.myGraph.getProfiledFunctions());
					trace.setThreadFunctionBinary(graphIndex, adapter, this.myGraph.getProfiledBinaries());

					// 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
			PIEvent be3 = new PIEvent(be.getValueObject(), PIEvent.SELECTION_AREA_CHANGED3);
			GppTrace trace = (GppTrace)(this.myGraph.getTrace());
			int graphIndex = this.myGraph.getGraphIndex();
			trace.setThreadBinaryFunction(graphIndex, adapter, this.myGraph.getProfiledFunctions());

			// 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
			PIEvent be3 = new PIEvent(be.getValueObject(), PIEvent.SELECTION_AREA_CHANGED3);
			GppTrace trace = (GppTrace)(this.myGraph.getTrace());
			int graphIndex = this.myGraph.getGraphIndex();
			trace.setThreadFunctionBinary(graphIndex, adapter, this.myGraph.getProfiledBinaries());

			// update the table items and redraw the table
			this.myGraph.getBinaryTable().piEventReceived(be3);
		}
	}

	/**
	 * this table's set of checkbox-selected rows has changed, so propagate that information
	 */
	private 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.gppSelectedThreadNames = 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
	{
		/* (non-Javadoc)
		 * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
		 */
		@Override
		public void widgetSelected(SelectionEvent e)
        {
        	// wait for the previous sort to finish
        	if (sorting || !(e.widget instanceof TableColumn))
        		return;

        	sortOnColumnSelection((TableColumn) e.widget);
        }
	}

	/**
	 * Adds ProfiledGenerics and threshold items to the table data
	 * (this.tableItemData and this.profiledThreads). Sorts the ProfiledGenerics
	 * according to load; and sets the graph's sorted list accordingly. Refreshes
	 * the TableViewer if parameter setInput is true.
	 * 
	 * @param setInput
	 *            if true, reset the TableViewer
	 */
	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() != 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 (adapter.getTotalSampleCount((ProfiledGeneric)arg0)) -
							adapter.getTotalSampleCount(((ProfiledGeneric)arg1));
				return 0;
			}
		});

		// now create the sorted list used to draw the graph
		myGraph.getSortedThreads().clear();

		if (myGraph.getThresholdThread().getItemCount() != 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;

		//if the last element is a threshold element - remove it
		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() { 
				@Override
				public void widgetSelected(SelectionEvent e) {
					action("savePrioritySamples"); //$NON-NLS-1$
				}
			});
		}

		return saveSamplesItem;
	}

	@Override
	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(Integer.valueOf(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 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 Integer.parseInt(value);
		}
	}

    /** class to pass sample data to the save wizard */
    public class SavePrioritySampleString implements ISaveSamples {
    	int graphIndex;
    	int drawMode;
    	boolean done = false;
    	
    	/**
    	 * Constructor
    	 * @param graphIndex
    	 * @param drawMode
    	 */
    	public SavePrioritySampleString(int graphIndex, int drawMode) {
    		this.graphIndex = graphIndex;
    		this.drawMode   = drawMode;
		}

    	/* (non-Javadoc)
    	 * @see com.nokia.carbide.cpp.internal.pi.interfaces.ISaveSamples#getData()
    	 */
    	public String getData() {
    		if (done)
    			return null;
    		
			String returnString = getPrioritySampleString(graphIndex, drawMode);
			done = true;
			return returnString;
		}

		public String getData(int size) {
 			return getData();
		}

		/* (non-Javadoc)
		 * @see com.nokia.carbide.cpp.internal.pi.interfaces.ISaveSamples#getIndex()
		 */
		public int getIndex() {
			return done ? 1 : 0;
		}

		/* (non-Javadoc)
		 * @see com.nokia.carbide.cpp.internal.pi.interfaces.ISaveSamples#clear()
		 */
		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(Integer.valueOf(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().size(); j++) {
					pt = (ProfiledThread) pth.getItems().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;
	}
	
}