crashanalysis/crashanalyser/com.nokia.s60tools.crashanalyser/src/com/nokia/s60tools/crashanalyser/ui/viewers/CallStackTableViewer.java
changeset 0 5ad7ad99af01
child 4 615035072f7e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/crashanalysis/crashanalyser/com.nokia.s60tools.crashanalyser/src/com/nokia/s60tools/crashanalyser/ui/viewers/CallStackTableViewer.java	Thu Feb 11 15:06:45 2010 +0200
@@ -0,0 +1,537 @@
+/*
+* Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). 
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "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.s60tools.crashanalyser.ui.viewers;
+
+import org.eclipse.jface.action.*;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.swt.widgets.*;
+import org.eclipse.swt.SWT;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.ui.*;
+import org.eclipse.ui.editors.text.*;
+import org.eclipse.ui.ide.IDE;
+import org.eclipse.ui.menus.*;
+import sun.awt.windows.*;
+import com.nokia.s60tools.crashanalyser.model.*;
+import com.nokia.s60tools.crashanalyser.resources.*;
+import com.nokia.s60tools.crashanalyser.containers.*;
+import com.nokia.s60tools.crashanalyser.ui.console.CrashAnalyserEditorConsole;
+import com.nokia.s60tools.util.sourcecode.*;
+import java.util.*;
+import java.net.URI;
+import java.io.*;
+import java.awt.datatransfer.*;
+import java.awt.Toolkit;
+
+/**
+ * Table viewer for call stack table. Call stack table is used in 
+ * SummaryPage.java (Crash Data page in Crash Visualiser editor) 
+ *
+ */
+public class CallStackTableViewer extends CrashAnalyserTableViewer {
+	Action actionCopySelectionToClipboardAsRichText;
+	Action actionCopySelectionToClipboardAsPlainText;
+	Action actionCopyStackToClipboardAsRichText;
+	Action actionCopyStackToClipboardAsPlainText;
+	Action actionOpenSourceFile;
+	Action actionDoubleClick;
+	Table tableControl;
+	
+	public final static int COLUMN_ADDRESS = 0;
+	public final static int COLUMN_SYMBOL = 1;
+	public final static int COLUMN_VALUE = 2;
+	public final static int COLUMN_OFFSET = 3;
+	public final static int COLUMN_OBJECT = 4;
+	public final static int COLUMN_TEXT = 5;
+	
+	MenuManager subMenuSdk;
+
+	/**
+	 * Constructor
+	 * @param table table where this viewer is used
+	 */
+	public CallStackTableViewer(Table table) {
+		super(table);
+		
+		tableControl = table;
+
+		doCreateActions();
+		doCreateContextMenu();
+		
+		TableViewerColumn column = new TableViewerColumn(this, SWT.NONE);
+		column.setLabelProvider(labelProvider);
+		column.getColumn().setText("");
+
+		column = new TableViewerColumn(this, SWT.NONE);
+		column.setLabelProvider(labelProvider);
+		column.getColumn().setText("Symbol");
+		
+		column = new TableViewerColumn(this, SWT.NONE);
+		column.setLabelProvider(labelProvider);
+		column.getColumn().setText("Address");
+
+		column = new TableViewerColumn(this, SWT.NONE);
+		column.setLabelProvider(labelProvider);
+		column.getColumn().setText("Offset");
+
+		column = new TableViewerColumn(this, SWT.NONE);
+		column.setLabelProvider(labelProvider);
+		column.getColumn().setText("Object");
+
+		column = new TableViewerColumn(this, SWT.NONE);
+		column.setLabelProvider(labelProvider);
+		column.getColumn().setText("Data");
+		
+		addDoubleClickListener(new IDoubleClickListener() {
+			public void doubleClick(DoubleClickEvent event) {
+				actionDoubleClick.run();
+			}
+		});			
+	}
+	
+	/**
+	 * Creates pop-up menu actions
+	 */
+	private void doCreateActions() {
+		actionCopySelectionToClipboardAsRichText = new Action("as Rich Text") {
+			public void run() {
+				copyToClipboard(tableControl, true, true);
+				}
+			};
+
+		actionCopyStackToClipboardAsRichText = new Action("as Rich Text") {
+			public void run() {
+				copyToClipboard(tableControl, false, true);
+				}
+			};
+
+		actionCopySelectionToClipboardAsPlainText = new Action("as Plain Text") {
+			public void run() {
+				copyToClipboard(tableControl, true, false);
+				}
+			};
+
+		actionCopyStackToClipboardAsPlainText = new Action("as Plain Text") {
+			public void run() {
+				copyToClipboard(tableControl, false, false);
+				}
+			};
+
+		actionOpenSourceFile = new Action("Open Source File") {
+			public void run() {
+				openSourceFile(tableControl);
+			}
+		};
+		
+		actionDoubleClick = new Action() {
+			public void run() {
+				openSourceFile(tableControl);
+			}
+		};
+	}
+	
+	/**
+	 * Setting the focus in opened file there where the method name occurs,
+	 * must call after file is opened and only if opening was successful
+	 * @param location
+	 * @throws CoreException
+	 */
+	private static void setFocusToLineWhereMethodIs(
+			final SourceFileLocation location) throws CoreException {
+		//Runnable to open new file
+		final IWorkspaceRunnable runSetFocus = new IWorkspaceRunnable() {
+			public void run(IProgressMonitor monitor) throws CoreException {
+				// do the actual work in here
+
+				try {
+					//Setting focus to correct line
+					IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+					IEditorPart activeEditor = activePage.getActiveEditor();
+
+					if(activeEditor != null && activeEditor instanceof TextEditor){
+						
+						// This is actually an instance of 'org.eclipse.cdt.internal.ui.editor.CEditor' 
+						// that extends org.eclipse.ui.editors.text.TextEditor
+						TextEditor editor = (TextEditor) activeEditor;
+						IDocument doc =  getDocument(editor);
+						if(doc != null){
+
+							String text = doc.get();
+							int methodOffset = location.getMethodOffset();
+							if(methodOffset == SourceFileLocation.OFFSET_NOT_FOUND){
+								// Removing parameters and getting new offset.
+								String methodNameWithOutParams = location.getMethodName();
+								methodNameWithOutParams = methodNameWithOutParams.substring(0, methodNameWithOutParams.indexOf("(")); //$NON-NLS-1$
+								methodOffset = text.indexOf(methodNameWithOutParams);
+
+								if(methodOffset == SourceFileLocation.OFFSET_NOT_FOUND){
+									// Removing possible namespace and getting new offset.
+									String separator = "::"; //$NON-NLS-1$
+									int separatorLocation = methodNameWithOutParams.lastIndexOf(separator);
+									if(separatorLocation > 0){
+										methodNameWithOutParams = methodNameWithOutParams.substring(separatorLocation + separator.length());
+										methodOffset = text.indexOf(methodNameWithOutParams);
+									}
+								}
+							}
+							
+							editor.setHighlightRange(methodOffset, 0, true);
+						}							
+					}					
+
+				} catch (Exception e) {
+					e.printStackTrace();
+					Status status = new Status(IStatus.ERROR,
+							"com.nokia.s60tools.crashanalyser", 0, e //$NON-NLS-1$
+									.getMessage(), e);
+
+					throw new CoreException(status);
+				} 
+
+			}
+		};
+		
+		
+		ResourcesPlugin.getWorkspace().run(runSetFocus, null, IWorkspace.AVOID_UPDATE, null);
+	}
+
+	/**
+	 * Returns the document interface for the currently active document 
+	 * in the given editor.
+	 * @param editor Editor to ask currently active document from. 
+	 * @return Document interface if found, otherwise <code>null</code>.
+	 */
+	private static IDocument getDocument(TextEditor editor) {
+		
+		TextFileDocumentProvider  documentProvider = (TextFileDocumentProvider) editor.getDocumentProvider();
+		if(documentProvider != null){
+			return  documentProvider.getDocument(editor.getEditorInput());
+			}								
+		return null;
+	}		
+	
+	/**
+	 * Shows a message box with given message
+	 * @param message
+	 */
+	private static void showMessage(String message, Shell shell) {
+		MessageDialog.openInformation(
+			shell,
+			"Crash Analyser",
+			message);
+	}
+	
+	/**
+	 * Tries to open source file for the selected line in the call stack table
+	 * @param tableControl
+	 */
+	static void openSourceFile(Table tableControl) {
+		try {
+			if (tableControl.getSelection() != null &&
+				tableControl.getSelection().length == 1) {
+				StackEntry stackEntry = 
+					(StackEntry)tableControl.getSelection()[0].getData();
+				
+				// we need code segment in order to be able to open source code,
+				// if we don't find code segment, notify user
+				if (stackEntry == null ||
+					"".equals(stackEntry.getCodeSegmentName())) {
+					showMessage("CODE SEGMENT NOT FOUND\n\nThe selected row does not contain information from which Code Segment (e.g. dll, exe) its address can be found. Source file cannot be opened.", tableControl.getShell());
+					return;
+				}
+				
+				String currentSdkName = SourceSdkManager.getCurrentSkdName();
+				// user must first choose an active SDK. If it has not been chosen, notify user
+				if (currentSdkName == null || "".equals(currentSdkName)) {
+					showMessage("ACTIVE SDK NOT SELECTED.\n\n Please select Active SDK first by right-clicking in the Call Stack table.", tableControl.getShell());
+					return;
+				}
+				
+				String epocroot = SourceSdkManager.getEpocroot(currentSdkName);
+				// user might have e.g. selected long ago an sdk which is not available anymore
+				if (epocroot == null) {
+					showMessage("INVALID ACTIVE SDK.\n\n Please select a valid Active SDK by right-clicking in the Call Stack table.", tableControl.getShell());
+					return;
+				}
+				File epoc = new File(epocroot);
+				
+				// epocroot for the selected SDK if not valid, notify user
+				if (!epoc.isDirectory() || !epoc.exists()) {
+					String message = "INVALID EPOCROOT.\n\nThe EPOCROOT (" +
+					 epocroot +
+					 ") of selected Active SDK ("+
+					 currentSdkName +
+					 ") is not valid. Please check your SDK settings from Window > Preferences > Carbide.c++ > SDK Preferences, or select a different Active SDK by right-clicking in the Call Stack table.";
+					 showMessage(message, tableControl.getShell());
+					return;
+				}
+				// currentSdkName is e.g. 'SDK_Name armv5 urel', we need to get out, build and variant
+				int lastSpace = currentSdkName.lastIndexOf(" ");
+				int secondToLastSpace = currentSdkName.lastIndexOf(" ", lastSpace-1);
+				
+				String build = currentSdkName.substring(lastSpace+1, currentSdkName.length()); //e.g. urel
+				String variant = currentSdkName.substring(secondToLastSpace+1, lastSpace);  // e.g. armv5
+				
+				String methodNameAsItsInMapFile = tableControl.getSelection()[0].getText(COLUMN_SYMBOL); 
+				String dllName = stackEntry.getCodeSegmentName(); 
+
+				ISourceFinder finder = SourceFinderFactory.createMapSourceFinder(CrashAnalyserEditorConsole.getInstance());
+
+				SourceFileLocation location = 
+					finder.findSourceFileByMethodName(methodNameAsItsInMapFile, dllName, variant, build, epocroot);
+
+				File file = new File(location.getSourceFileLocation());
+				// we could not find the source file, notify user
+				if(file == null || !file.exists()){
+					showMessage("SOURCE FILE CANNOT BE FOUND\n\n" +
+								file.getName() +
+								" was not found from " +
+								FileOperations.getFolder(location.getSourceFileLocation()) +
+								". Please make sure you have selected a correct Active SDK by right-clicking in the Call Stack table. It is also possible that you don't have all source files for your Active SDK present. Source file cannot be opened.", 
+								tableControl.getShell());
+					return;
+				}
+				
+				//Create URI to open file
+				String uriStr = location.getSourceFileLocation().replace("\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$
+				uriStr = "file://" + uriStr; //$NON-NLS-1$
+				final URI srcURI = new URI(uriStr);
+				
+				//Find default editor for that file
+				IEditorRegistry reg = PlatformUI.getWorkbench().getEditorRegistry();
+				
+				IEditorDescriptor editor = reg.getDefaultEditor(file.getName());
+				//We open editor by it's ID
+				final String editorId = editor.getId();
+				
+				IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+				try {
+					IEditorPart part = IDE.openEditor(page, srcURI, editorId, true);
+					if(part != null){
+						//Set focus to correct line
+						setFocusToLineWhereMethodIs(location);
+					}
+				} catch (PartInitException e) {
+					e.printStackTrace();
+				}
+			} 
+		}catch (CannotFoundFileException ex) {
+			String newLine = System.getProperty("line.separator");
+			showMessage(ex.getMessage() + newLine + newLine + "Please check that you have selected correct Active SDK, by right-clicking in the Call Stack table.", tableControl.getShell());
+		} catch (Exception e) {
+			e.printStackTrace();
+			showMessage("Unable to find source file. Please check your SDK properties.", tableControl.getShell());
+		}
+	}
+
+	/**
+	 * Copies data from table into clipboard 
+	 * @param table table from which data is copied from
+	 * @param selection if true, only the selected rows are copied, if false, all rows are copied
+	 * @param richText if true, data is copied in rich-text format, if false, data is copied in plain text format
+	 */
+	static void copyToClipboard(Table table, boolean selection, boolean richText) {
+		TableItem[] items = null;
+		// copy only selected rows
+		if (selection)
+			items = table.getSelection();
+		// copy all rows
+		else
+			items = table.getItems();
+		
+		if (items != null && items.length > 0) {
+			// rich-text format
+			if (richText) {
+				try {
+					String data = HtmlFormatter.formatStackForClipboard(items, table.getColumnCount());
+					byte[] bytes = convertToHTMLFormat(data);
+					PEBClip clip = new PEBClip();
+					clip.setData( WDataTransferer.CF_HTML, bytes);
+				} catch (Exception e) {
+				}
+			// plain text format
+			} else {
+				String separator = System.getProperty("line.separator");
+				String data = "";
+				for (int i = 0; i < items.length; i++) {
+					TableItem item = items[i];
+					data += String.format("%-10s  %-10s  %-6s  %-6s  %s    %s", 
+											item.getText(COLUMN_ADDRESS),
+											item.getText(COLUMN_VALUE),
+											item.getText(COLUMN_TEXT),
+											item.getText(COLUMN_OFFSET),
+											item.getText(COLUMN_OBJECT),
+											item.getText(COLUMN_SYMBOL)) +
+											separator;
+				}
+				StringSelection stringSelection = new StringSelection(data);
+				Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+				clipboard.setContents(stringSelection, null);
+			}
+		}
+	}
+	
+	/**
+	 * surrounds the html with this envelope, ready for
+	 * windows clipboard (jdk support can be made better)
+	 * <pre>
+	 * Version:1.0
+	 * StartHTML:00000000000
+	 * EndHTML:00000000000
+	 * StartFragment:00000000000
+	 * EndFragment:00000000000
+	 * &lt;!--StartFragment--&gt;
+	 * ...
+	 * &lt;!-- EndFragment-- &gt;
+	 * </pre>
+	 * We have to return a byte array 'cause in Windows the html needs to be utf-8
+	 * encoded. And because we have to calculate char-offsets, we encode it here.
+	 * @param htmlText
+	 * @return byte[]
+	 */
+	static byte[] convertToHTMLFormat(String htmlText) {
+		try {
+			String sep = "\r\n";
+			String header = "Version:1.0"+ sep +
+										 "StartHTML:00000000000"+ sep +
+										 "EndHTML:00000000000"+ sep +
+										 "StartFragment:00000000000"+ sep +
+										 "EndFragment:00000000000" + sep;
+			
+			String html = "<!--StartFragment-->\r\n" + htmlText + "<!--EndFragment-->\r\n";
+	
+			byte[] bHtml = html.getBytes("UTF-8");// encode first 'cause it may grow
+	
+			int headerLen = header.length();
+			int htmlLen = bHtml.length;
+	
+			StringBuffer buf = new  StringBuffer(header);
+			setValue( buf, "StartHTML", headerLen-1);
+			setValue( buf, "EndHTML", headerLen + htmlLen-1);
+			setValue( buf, "StartFragment", headerLen-1);
+			setValue( buf, "EndFragment", headerLen + htmlLen-1);
+			byte[] bHeader = buf.toString().getBytes("UTF-8");// should stay the same (no nonASCII chars in header)
+	
+			byte result[] = new byte[headerLen + htmlLen ];
+			System.arraycopy(bHeader, 0, result, 0, bHeader.length);
+			System.arraycopy(bHtml, 0, result, bHeader.length, bHtml.length);
+	
+			return result;
+		} catch (Exception e) {
+			return null;
+		}
+	}
+
+	/**
+	 * Replaces name+":00000000000" with name+":xxxxxxxxxxx" where xxx... is the '0' padded value.
+	 * Value can't be to long, since maxint can be displayed with 11 digits. If value is below zero
+	 * there is enough place (10 for the digits 1 for sign).<br>
+	 * If the search is not found nothing is done.
+	 * @param src
+	 * @param name
+	 * @param value
+	 */
+	private static void setValue( StringBuffer src, String name, int value){
+		int val = value;
+		String search = name+":00000000000";
+		int pos = src.indexOf(search);
+		if (pos ==-1) return;// not found, do nothing
+
+		boolean belowZero = val<0;
+		if (belowZero) val = -val;
+
+		src.replace(pos+search.length()-(val+"").length(), pos+search.length(), val+"");
+		if (belowZero) src.setCharAt(pos+name.length()+1,'-'); // +1 'cause of ':' in "SearchMe:"
+	}
+	
+	/**
+	 * creates context menu accordingly to what is selected in the table.
+	 */
+	private void doCreateContextMenu() {
+		
+		// Active SDK menu
+		subMenuSdk = new MenuManager("Active SDK");	
+		subMenuSdk.setRemoveAllWhenShown(true);
+		subMenuSdk.addMenuListener(new IMenuListener() {
+			public void menuAboutToShow(IMenuManager manager) {
+				Map<String, String> sdks = SourceSdkManager.getAllSdks();
+				String currentSdk = SourceSdkManager.getCurrentSkdName();
+				// if there are sdks
+				if (sdks != null && !sdks.isEmpty()) {
+					String[] sdkNames = sdks.keySet().toArray(new String[sdks.size()]);
+					java.util.Arrays.sort(sdkNames);
+					// go through all found sdks and and them to pop-up menu
+					for (int i = 0; i < sdkNames.length; i++) {
+						String sdk = sdkNames[i];
+						CommandContributionItemParameter p = 
+							new CommandContributionItemParameter(PlatformUI.getWorkbench().getActiveWorkbenchWindow(),
+																 null,
+																 "com.nokia.s60tools.crashanalyser.commands.SdkSelection",
+																 CommandContributionItem.STYLE_PUSH);
+						p.label = sdk;
+						// if this sdk is selected as Active sdk, draw an circular image to 
+						// this menu item to indicate that this is the active sdk
+						if (sdk.equalsIgnoreCase(currentSdk)) {
+							p.icon = ImageResourceManager.getImageDescriptor((ImageKeys.SELECTED_SDK));
+						}
+						CommandContributionItem item = new CommandContributionItem(p);	    
+				   	    subMenuSdk.add(item);
+					}
+				}
+			}
+		});
+
+   	    final MenuManager subMenuSelection = new MenuManager("Copy Selection to Clipboard");
+		subMenuSelection.add(actionCopySelectionToClipboardAsPlainText);
+		subMenuSelection.add(actionCopySelectionToClipboardAsRichText);
+
+		final MenuManager subMenuFull = new MenuManager("Copy Whole Stack to Clipboard");
+		subMenuFull.add(actionCopyStackToClipboardAsPlainText);
+		subMenuFull.add(actionCopyStackToClipboardAsRichText);
+
+		MenuManager manager = new MenuManager("#PopupMenu");
+		Menu menu = manager.createContextMenu(getControl());
+		getControl().setMenu(menu);
+		
+		manager.setRemoveAllWhenShown(true);
+		manager.addMenuListener(new IMenuListener() {
+			public void menuAboutToShow(IMenuManager manager) {
+				Table table = (Table)getControl();
+				if (table.getSelection() != null) {
+					// add 'Open source file' and 'Active SDK' menu items to pop-up menu
+					// only if exactly one row is selected in table and if that
+					// row's symbol column is not empty
+					if (table.getSelection().length == 1 && 
+						!"".equals(table.getSelection()[0].getText(COLUMN_SYMBOL))) {
+						manager.add(actionOpenSourceFile);
+						manager.add(subMenuSdk);
+						manager.add(new Separator());
+					}
+					manager.add(subMenuSelection);
+					manager.add(subMenuFull);
+				}
+			}
+		});
+	}
+}
+