eric6/Debugger/CallStackViewer.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7229
53054eb5b15a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2013 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the Call Stack viewer widget.
8 """
9
10 from __future__ import unicode_literals
11
12 from PyQt5.QtCore import pyqtSignal, Qt, QFileInfo
13 from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QMenu
14
15 from E5Gui.E5Application import e5App
16 from E5Gui import E5FileDialog, E5MessageBox
17
18 import Utilities
19
20
21 class CallStackViewer(QTreeWidget):
22 """
23 Class implementing the Call Stack viewer widget.
24
25 @signal sourceFile(str, int) emitted to show the source of a stack entry
26 @signal frameSelected(int) emitted to signal the selection of a frame entry
27 """
28 sourceFile = pyqtSignal(str, int)
29 frameSelected = pyqtSignal(int)
30
31 FilenameRole = Qt.UserRole + 1
32 LinenoRole = Qt.UserRole + 2
33
34 def __init__(self, debugServer, parent=None):
35 """
36 Constructor
37
38 @param debugServer reference to the debug server object (DebugServer)
39 @param parent reference to the parent widget (QWidget)
40 """
41 super(CallStackViewer, self).__init__(parent)
42
43 self.setHeaderHidden(True)
44 self.setAlternatingRowColors(True)
45 self.setItemsExpandable(False)
46 self.setRootIsDecorated(False)
47 self.setWindowTitle(self.tr("Call Stack"))
48
49 self.__menu = QMenu(self)
50 self.__sourceAct = self.__menu.addAction(
51 self.tr("Show source"), self.__openSource)
52 self.__menu.addAction(self.tr("Clear"), self.clear)
53 self.__menu.addSeparator()
54 self.__menu.addAction(self.tr("Save"), self.__saveStackTrace)
55 self.setContextMenuPolicy(Qt.CustomContextMenu)
56 self.customContextMenuRequested.connect(self.__showContextMenu)
57
58 self.__dbs = debugServer
59
60 # file name, line number, function name, arguments
61 self.__entryFormat = self.tr("File: {0}\nLine: {1}\n{2}{3}")
62 # file name, line number
63 self.__entryFormatShort = self.tr("File: {0}\nLine: {1}")
64
65 self.__projectMode = False
66 self.__project = None
67
68 self.__dbs.clientStack.connect(self.__showCallStack)
69 self.itemDoubleClicked.connect(self.__itemDoubleClicked)
70
71 def setDebugger(self, debugUI):
72 """
73 Public method to set a reference to the Debug UI.
74
75 @param debugUI reference to the DebugUI object (DebugUI)
76 """
77 debugUI.clientStack.connect(self.__showCallStack)
78
79 def setProjectMode(self, enabled):
80 """
81 Public slot to set the call trace viewer to project mode.
82
83 In project mode the call trace info is shown with project relative
84 path names.
85
86 @param enabled flag indicating to enable the project mode (boolean)
87 """
88 self.__projectMode = enabled
89 if enabled and self.__project is None:
90 self.__project = e5App().getObject("Project")
91
92 def __showContextMenu(self, coord):
93 """
94 Private slot to show the context menu.
95
96 @param coord the position of the mouse pointer (QPoint)
97 """
98 if self.topLevelItemCount() > 0:
99 itm = self.currentItem()
100 self.__sourceAct.setEnabled(itm is not None)
101 self.__menu.popup(self.mapToGlobal(coord))
102
103 def __showCallStack(self, stack):
104 """
105 Private slot to show the call stack of the program being debugged.
106
107 @param stack list of tuples with call stack data (file name,
108 line number, function name, formatted argument/values list)
109 """
110 self.clear()
111 for fname, fline, ffunc, fargs in stack:
112 if self.__projectMode:
113 dfname = self.__project.getRelativePath(fname)
114 else:
115 dfname = fname
116 if ffunc and not ffunc.startswith("<"):
117 # use normal format
118 itm = QTreeWidgetItem(
119 self,
120 [self.__entryFormat.format(dfname, fline, ffunc, fargs)])
121 else:
122 # use short format
123 itm = QTreeWidgetItem(
124 self, [self.__entryFormatShort.format(dfname, fline)])
125 itm.setData(0, self.FilenameRole, fname)
126 itm.setData(0, self.LinenoRole, fline)
127
128 self.resizeColumnToContents(0)
129
130 def __itemDoubleClicked(self, itm):
131 """
132 Private slot to handle a double click of a stack entry.
133
134 @param itm reference to the double clicked item (QTreeWidgetItem)
135 """
136 fname = itm.data(0, self.FilenameRole)
137 fline = itm.data(0, self.LinenoRole)
138 if self.__projectMode:
139 fname = self.__project.getAbsolutePath(fname)
140 self.sourceFile.emit(fname, fline)
141
142 index = self.indexOfTopLevelItem(itm)
143 self.frameSelected.emit(index)
144
145 def __openSource(self):
146 """
147 Private slot to show the source for the selected stack entry.
148 """
149 itm = self.currentItem()
150 if itm:
151 self.__itemDoubleClicked(itm)
152
153 def __saveStackTrace(self):
154 """
155 Private slot to save the stack trace info to a file.
156 """
157 if self.topLevelItemCount() > 0:
158 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
159 self,
160 self.tr("Save Call Stack Info"),
161 "",
162 self.tr("Text Files (*.txt);;All Files (*)"),
163 None,
164 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
165 if fname:
166 ext = QFileInfo(fname).suffix()
167 if not ext:
168 ex = selectedFilter.split("(*")[1].split(")")[0]
169 if ex:
170 fname += ex
171 if QFileInfo(fname).exists():
172 res = E5MessageBox.yesNo(
173 self,
174 self.tr("Save Call Stack Info"),
175 self.tr("<p>The file <b>{0}</b> already exists."
176 " Overwrite it?</p>").format(fname),
177 icon=E5MessageBox.Warning)
178 if not res:
179 return
180 fname = Utilities.toNativeSeparators(fname)
181
182 try:
183 f = open(fname, "w", encoding="utf-8")
184 itm = self.topLevelItem(0)
185 while itm is not None:
186 f.write("{0}\n".format(itm.text(0)))
187 f.write(78 * "=" + "\n")
188 itm = self.itemBelow(itm)
189 f.close()
190 except IOError as err:
191 E5MessageBox.critical(
192 self,
193 self.tr("Error saving Call Stack Info"),
194 self.tr("""<p>The call stack info could not be"""
195 """ written to <b>{0}</b></p>"""
196 """<p>Reason: {1}</p>""")
197 .format(fname, str(err)))

eric ide

mercurial