Debugger/CallStackViewer.py

Tue, 30 Dec 2014 12:05:08 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 30 Dec 2014 12:05:08 +0100
branch
5_4_x
changeset 4007
123aca30f8f0
parent 3160
209a07d7e401
child 3178
f25fc1364c88
child 3190
a9a94491c4fd
permissions
-rw-r--r--

Branch 5_4_x is no longer maintained.

# -*- coding: utf-8 -*-

# Copyright (c) 2013 - 2014 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.setWindowTitle(self.trUtf8("Call Stack"))
        
        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