eric7/Debugger/CallStackViewer.py

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

eric ide

mercurial