src/eric7/Debugger/CallStackViewer.py

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

eric ide

mercurial