Sat, 05 Dec 2020 18:02:17 +0100
Merged with the default branch.
# -*- coding: utf-8 -*- # Copyright (c) 2013 - 2020 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the Call Stack viewer widget. """ from PyQt5.QtCore import pyqtSignal, Qt, QFileInfo from PyQt5.QtWidgets 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.setWindowTitle(self.tr("Call Stack")) self.__menu = QMenu(self) self.__sourceAct = self.__menu.addAction( self.tr("Show source"), self.__openSource) self.__menu.addAction(self.tr("Clear"), self.clear) self.__menu.addSeparator() self.__menu.addAction(self.tr("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.tr("File: {0}\nLine: {1}\n{2}{3}") # file name, line number self.__entryFormatShort = self.tr("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): """ Private 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) @type list of tuples of (str, str, str, str) """ 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.tr("Save Call Stack Info"), "", self.tr("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.tr("Save Call Stack Info"), self.tr("<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: with open(fname, "w", encoding="utf-8") as f: 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) except OSError as err: E5MessageBox.critical( self, self.tr("Error saving Call Stack Info"), self.tr("""<p>The call stack info could not be""" """ written to <b>{0}</b></p>""" """<p>Reason: {1}</p>""") .format(fname, str(err)))