--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Debugger/CallStackViewer.py Tue May 28 20:52:12 2013 +0200 @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2013 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the Call Stack viewer widget. +""" + +from __future__ import unicode_literals # __IGNORE_WARNING__ + +from PyQt4.QtCore import pyqtSignal, Qt, QFileInfo +from PyQt4.QtGui import QTreeWidget, QTreeWidgetItem, QMenu + +from E5Gui.E5Application import e5App +from E5Gui import E5FileDialog, E5MessageBox + +import Utilities + + +class CallStackViewer(QTreeWidget): + """ + Class implementing the Call Stack viewer widget. + + @signal sourceFile(str, int) emitted to show the source of a stack entry + @signal frameSelected(int) emitted to signal the selection of a frame entry + """ + sourceFile = pyqtSignal(str, int) + frameSelected = pyqtSignal(int) + + FilenameRole = Qt.UserRole + 1 + LinenoRole = Qt.UserRole + 2 + + def __init__(self, debugServer, parent=None): + """ + Constructor + + @param debugServer reference to the debug server object (DebugServer) + @param parent reference to the parent widget (QWidget) + """ + super(CallStackViewer, self).__init__(parent) + + self.setHeaderHidden(True) + self.setAlternatingRowColors(True) + self.setItemsExpandable(False) + self.setRootIsDecorated(False) + + self.__menu = QMenu(self) + self.__sourceAct = self.__menu.addAction( + self.trUtf8("Show source"), self.__openSource) + self.__menu.addAction(self.trUtf8("Clear"), self.clear) + self.__menu.addSeparator() + self.__menu.addAction(self.trUtf8("Save"), self.__saveStackTrace) + self.setContextMenuPolicy(Qt.CustomContextMenu) + self.customContextMenuRequested.connect(self.__showContextMenu) + + self.__dbs = debugServer + + # file name, line number, function name, arguments + self.__entryFormat = self.trUtf8("File: {0}\nLine: {1}\n{2}{3}") + # file name, line number + self.__entryFormatShort = self.trUtf8("File: {0}\nLine: {1}") + + self.__projectMode = False + self.__project = None + + self.__dbs.clientStack.connect(self.__showCallStack) + self.itemDoubleClicked.connect(self.__itemDoubleClicked) + + def setDebugger(self, debugUI): + """ + Public method to set a reference to the Debug UI. + + @param debugUI reference to the DebugUI object (DebugUI) + """ + debugUI.clientStack.connect(self.__showCallStack) + + def setProjectMode(self, enabled): + """ + Public slot to set the call trace viewer to project mode. + + In project mode the call trace info is shown with project relative + path names. + + @param enabled flag indicating to enable the project mode (boolean) + """ + self.__projectMode = enabled + if enabled and self.__project is None: + self.__project = e5App().getObject("Project") + + def __showContextMenu(self, coord): + """ + Private slot to show the context menu. + + @param coord the position of the mouse pointer (QPoint) + """ + if self.topLevelItemCount() > 0: + itm = self.currentItem() + self.__sourceAct.setEnabled(itm is not None) + self.__menu.popup(self.mapToGlobal(coord)) + + def __showCallStack(self, stack): + """ + Public slot to show the call stack of the program being debugged. + + @param stack list of tuples with call stack data (file name, line number, + function name, formatted argument/values list) + """ + self.clear() + for fname, fline, ffunc, fargs in stack: + if self.__projectMode: + dfname = self.__project.getRelativePath(fname) + else: + dfname = fname + if ffunc and not ffunc.startswith("<"): + # use normal format + itm = QTreeWidgetItem(self, + [self.__entryFormat.format(dfname, fline, ffunc, fargs)]) + else: + # use short format + itm = QTreeWidgetItem(self, + [self.__entryFormatShort.format(dfname, fline)]) + itm.setData(0, self.FilenameRole, fname) + itm.setData(0, self.LinenoRole, fline) + + self.resizeColumnToContents(0) + + def __itemDoubleClicked(self, itm): + """ + Private slot to handle a double click of a stack entry. + + @param itm reference to the double clicked item (QTreeWidgetItem) + """ + fname = itm.data(0, self.FilenameRole) + fline = itm.data(0, self.LinenoRole) + if self.__projectMode: + fname = self.__project.getAbsolutePath(fname) + self.sourceFile.emit(fname, fline) + + index = self.indexOfTopLevelItem(itm) + self.frameSelected.emit(index) + + def __openSource(self): + """ + Private slot to show the source for the selected stack entry. + """ + itm = self.currentItem() + if itm: + self.__itemDoubleClicked(itm) + + def __saveStackTrace(self): + """ + Private slot to save the stack trace info to a file. + """ + if self.topLevelItemCount() > 0: + fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( + self, + self.trUtf8("Save Call Stack Info"), + "", + self.trUtf8("Text Files (*.txt);;All Files (*)"), + None, + E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) + if fname: + ext = QFileInfo(fname).suffix() + if not ext: + ex = selectedFilter.split("(*")[1].split(")")[0] + if ex: + fname += ex + if QFileInfo(fname).exists(): + res = E5MessageBox.yesNo(self, + self.trUtf8("Save Call Stack Info"), + self.trUtf8("<p>The file <b>{0}</b> already exists." + " Overwrite it?</p>").format(fname), + icon=E5MessageBox.Warning) + if not res: + return + fname = Utilities.toNativeSeparators(fname) + + try: + f = open(fname, "w", encoding="utf-8") + itm = self.topLevelItem(0) + while itm is not None: + f.write("{0}\n".format(itm.text(0))) + f.write(78 * "=" + "\n") + itm = self.itemBelow(itm) + f.close() + except IOError as err: + E5MessageBox.critical(self, + self.trUtf8("Error saving Call Stack Info"), + self.trUtf8("""<p>The call stack info could not be written""" + """ to <b>{0}</b></p><p>Reason: {1}</p>""")\ + .format(fname, str(err)))