Debugger/CallStackViewer.py

changeset 2622
08cc2f31c983
child 2677
3d4277929fb3
child 2732
c14fe50c113e
diff -r ab5918079c38 -r 08cc2f31c983 Debugger/CallStackViewer.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Debugger/CallStackViewer.py	Fri Apr 26 19:37:21 2013 +0200
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2013 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the Call Stack viewer widget.
+"""
+
+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().__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)))

eric ide

mercurial