src/eric7/Debugger/CallTraceViewer.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
23 23
24 24
25 class CallTraceViewer(QWidget, Ui_CallTraceViewer): 25 class CallTraceViewer(QWidget, Ui_CallTraceViewer):
26 """ 26 """
27 Class implementing the Call Trace viewer widget. 27 Class implementing the Call Trace viewer widget.
28 28
29 @signal sourceFile(str, int) emitted to show the source of a call/return 29 @signal sourceFile(str, int) emitted to show the source of a call/return
30 point 30 point
31 """ 31 """
32
32 sourceFile = pyqtSignal(str, int) 33 sourceFile = pyqtSignal(str, int)
33 34
34 def __init__(self, debugServer, debugViewer, parent=None): 35 def __init__(self, debugServer, debugViewer, parent=None):
35 """ 36 """
36 Constructor 37 Constructor
37 38
38 @param debugServer reference to the debug server object 39 @param debugServer reference to the debug server object
39 @type DebugServer 40 @type DebugServer
40 @param debugViewer reference to the debug viewer object 41 @param debugViewer reference to the debug viewer object
41 @type DebugViewer 42 @type DebugViewer
42 @param parent reference to the parent widget 43 @param parent reference to the parent widget
43 @type QWidget 44 @type QWidget
44 """ 45 """
45 super().__init__(parent) 46 super().__init__(parent)
46 self.setupUi(self) 47 self.setupUi(self)
47 48
48 self.__dbs = debugServer 49 self.__dbs = debugServer
49 self.__debugViewer = debugViewer 50 self.__debugViewer = debugViewer
50 51
51 self.startTraceButton.setIcon( 52 self.startTraceButton.setIcon(UI.PixmapCache.getIcon("callTraceStart"))
52 UI.PixmapCache.getIcon("callTraceStart")) 53 self.stopTraceButton.setIcon(UI.PixmapCache.getIcon("callTraceStop"))
53 self.stopTraceButton.setIcon(
54 UI.PixmapCache.getIcon("callTraceStop"))
55 self.resizeButton.setIcon(UI.PixmapCache.getIcon("resizeColumns")) 54 self.resizeButton.setIcon(UI.PixmapCache.getIcon("resizeColumns"))
56 self.clearButton.setIcon(UI.PixmapCache.getIcon("editDelete")) 55 self.clearButton.setIcon(UI.PixmapCache.getIcon("editDelete"))
57 self.saveButton.setIcon(UI.PixmapCache.getIcon("fileSave")) 56 self.saveButton.setIcon(UI.PixmapCache.getIcon("fileSave"))
58 57
59 self.__headerItem = QTreeWidgetItem( 58 self.__headerItem = QTreeWidgetItem(["", self.tr("From"), self.tr("To")])
60 ["", self.tr("From"), self.tr("To")])
61 self.__headerItem.setIcon(0, UI.PixmapCache.getIcon("callReturn")) 59 self.__headerItem.setIcon(0, UI.PixmapCache.getIcon("callReturn"))
62 self.callTrace.setHeaderItem(self.__headerItem) 60 self.callTrace.setHeaderItem(self.__headerItem)
63 61
64 self.__callStack = [] 62 self.__callStack = []
65 63
66 self.__entryFormat = "{0}:{1} ({2})" 64 self.__entryFormat = "{0}:{1} ({2})"
67 self.__entryRe = re.compile(r"""(.+):(\d+)\s\((.*)\)""") 65 self.__entryRe = re.compile(r"""(.+):(\d+)\s\((.*)\)""")
68 66
69 self.__projectMode = False 67 self.__projectMode = False
70 self.__project = None 68 self.__project = None
71 self.__tracedDebuggerId = "" 69 self.__tracedDebuggerId = ""
72 70
73 stopOnExit = Preferences.toBool( 71 stopOnExit = Preferences.toBool(
74 Preferences.getSettings().value("CallTrace/StopOnExit", True)) 72 Preferences.getSettings().value("CallTrace/StopOnExit", True)
73 )
75 self.stopCheckBox.setChecked(stopOnExit) 74 self.stopCheckBox.setChecked(stopOnExit)
76 75
77 self.__callTraceEnabled = (Preferences.toBool( 76 self.__callTraceEnabled = (
78 Preferences.getSettings().value("CallTrace/Enabled", False)) and 77 Preferences.toBool(
79 not stopOnExit) 78 Preferences.getSettings().value("CallTrace/Enabled", False)
80 79 )
80 and not stopOnExit
81 )
82
81 if self.__callTraceEnabled: 83 if self.__callTraceEnabled:
82 self.startTraceButton.setEnabled(False) 84 self.startTraceButton.setEnabled(False)
83 else: 85 else:
84 self.stopTraceButton.setEnabled(False) 86 self.stopTraceButton.setEnabled(False)
85 87
86 self.__dbs.callTraceInfo.connect(self.__addCallTraceInfo) 88 self.__dbs.callTraceInfo.connect(self.__addCallTraceInfo)
87 self.__dbs.clientExit.connect(self.__clientExit) 89 self.__dbs.clientExit.connect(self.__clientExit)
88 90
89 def __setCallTraceEnabled(self, enabled): 91 def __setCallTraceEnabled(self, enabled):
90 """ 92 """
91 Private slot to set the call trace enabled status. 93 Private slot to set the call trace enabled status.
92 94
93 @param enabled flag indicating the new state 95 @param enabled flag indicating the new state
94 @type bool 96 @type bool
95 """ 97 """
96 if enabled: 98 if enabled:
97 self.__tracedDebuggerId = ( 99 self.__tracedDebuggerId = self.__debugViewer.getSelectedDebuggerId()
98 self.__debugViewer.getSelectedDebuggerId()
99 )
100 self.__dbs.setCallTraceEnabled(self.__tracedDebuggerId, enabled) 100 self.__dbs.setCallTraceEnabled(self.__tracedDebuggerId, enabled)
101 self.stopTraceButton.setEnabled(enabled) 101 self.stopTraceButton.setEnabled(enabled)
102 self.startTraceButton.setEnabled(not enabled) 102 self.startTraceButton.setEnabled(not enabled)
103 self.__callTraceEnabled = enabled 103 self.__callTraceEnabled = enabled
104 Preferences.getSettings().setValue("CallTrace/Enabled", enabled) 104 Preferences.getSettings().setValue("CallTrace/Enabled", enabled)
105 105
106 if not enabled: 106 if not enabled:
107 for column in range(self.callTrace.columnCount()): 107 for column in range(self.callTrace.columnCount()):
108 self.callTrace.resizeColumnToContents(column) 108 self.callTrace.resizeColumnToContents(column)
109 109
110 @pyqtSlot(bool) 110 @pyqtSlot(bool)
111 def on_stopCheckBox_clicked(self, checked): 111 def on_stopCheckBox_clicked(self, checked):
112 """ 112 """
113 Private slot to handle a click on the stop check box. 113 Private slot to handle a click on the stop check box.
114 114
115 @param checked state of the check box 115 @param checked state of the check box
116 @type bool 116 @type bool
117 """ 117 """
118 Preferences.getSettings().setValue("CallTrace/StopOnExit", checked) 118 Preferences.getSettings().setValue("CallTrace/StopOnExit", checked)
119 119
120 @pyqtSlot() 120 @pyqtSlot()
121 def on_startTraceButton_clicked(self): 121 def on_startTraceButton_clicked(self):
122 """ 122 """
123 Private slot to start call tracing. 123 Private slot to start call tracing.
124 """ 124 """
125 self.__setCallTraceEnabled(True) 125 self.__setCallTraceEnabled(True)
126 126
127 @pyqtSlot() 127 @pyqtSlot()
128 def on_stopTraceButton_clicked(self): 128 def on_stopTraceButton_clicked(self):
129 """ 129 """
130 Private slot to start call tracing. 130 Private slot to start call tracing.
131 """ 131 """
132 self.__setCallTraceEnabled(False) 132 self.__setCallTraceEnabled(False)
133 133
134 @pyqtSlot() 134 @pyqtSlot()
135 def on_resizeButton_clicked(self): 135 def on_resizeButton_clicked(self):
136 """ 136 """
137 Private slot to resize the columns of the call trace to their contents. 137 Private slot to resize the columns of the call trace to their contents.
138 """ 138 """
139 for column in range(self.callTrace.columnCount()): 139 for column in range(self.callTrace.columnCount()):
140 self.callTrace.resizeColumnToContents(column) 140 self.callTrace.resizeColumnToContents(column)
141 141
142 @pyqtSlot() 142 @pyqtSlot()
143 def on_clearButton_clicked(self): 143 def on_clearButton_clicked(self):
144 """ 144 """
145 Private slot to clear the call trace. 145 Private slot to clear the call trace.
146 """ 146 """
147 self.clear() 147 self.clear()
148 148
149 @pyqtSlot() 149 @pyqtSlot()
150 def on_saveButton_clicked(self): 150 def on_saveButton_clicked(self):
151 """ 151 """
152 Private slot to save the call trace info to a file. 152 Private slot to save the call trace info to a file.
153 """ 153 """
156 self, 156 self,
157 self.tr("Save Call Trace Info"), 157 self.tr("Save Call Trace Info"),
158 "", 158 "",
159 self.tr("Text Files (*.txt);;All Files (*)"), 159 self.tr("Text Files (*.txt);;All Files (*)"),
160 None, 160 None,
161 EricFileDialog.DontConfirmOverwrite) 161 EricFileDialog.DontConfirmOverwrite,
162 )
162 if fname: 163 if fname:
163 fpath = pathlib.Path(fname) 164 fpath = pathlib.Path(fname)
164 if not fpath.suffix: 165 if not fpath.suffix:
165 ex = selectedFilter.split("(*")[1].split(")")[0] 166 ex = selectedFilter.split("(*")[1].split(")")[0]
166 if ex: 167 if ex:
167 fpath = fpath.with_suffix(ex) 168 fpath = fpath.with_suffix(ex)
168 if fpath.exists(): 169 if fpath.exists():
169 res = EricMessageBox.yesNo( 170 res = EricMessageBox.yesNo(
170 self, 171 self,
171 self.tr("Save Call Trace Info"), 172 self.tr("Save Call Trace Info"),
172 self.tr("<p>The file <b>{0}</b> already exists." 173 self.tr(
173 " Overwrite it?</p>").format(fpath), 174 "<p>The file <b>{0}</b> already exists."
174 icon=EricMessageBox.Warning) 175 " Overwrite it?</p>"
176 ).format(fpath),
177 icon=EricMessageBox.Warning,
178 )
175 if not res: 179 if not res:
176 return 180 return
177 181
178 try: 182 try:
179 title = self.tr("Call Trace Info of '{0}'").format( 183 title = self.tr("Call Trace Info of '{0}'").format(
180 self.__tracedDebuggerId) 184 self.__tracedDebuggerId
185 )
181 with fpath.open("w", encoding="utf-8") as f: 186 with fpath.open("w", encoding="utf-8") as f:
182 f.write("{0}\n".format(title)) 187 f.write("{0}\n".format(title))
183 f.write("{0}\n\n".format(len(title) * "=")) 188 f.write("{0}\n\n".format(len(title) * "="))
184 itm = self.callTrace.topLevelItem(0) 189 itm = self.callTrace.topLevelItem(0)
185 while itm is not None: 190 while itm is not None:
186 isCall = itm.data(0, Qt.ItemDataRole.UserRole) 191 isCall = itm.data(0, Qt.ItemDataRole.UserRole)
187 call = "->" if isCall else "<-" 192 call = "->" if isCall else "<-"
188 f.write("{0} {1} || {2}\n".format( 193 f.write(
189 call, 194 "{0} {1} || {2}\n".format(
190 itm.text(1), itm.text(2))) 195 call, itm.text(1), itm.text(2)
196 )
197 )
191 itm = self.callTrace.itemBelow(itm) 198 itm = self.callTrace.itemBelow(itm)
192 except OSError as err: 199 except OSError as err:
193 EricMessageBox.critical( 200 EricMessageBox.critical(
194 self, 201 self,
195 self.tr("Error saving Call Trace Info"), 202 self.tr("Error saving Call Trace Info"),
196 self.tr("""<p>The call trace info could not""" 203 self.tr(
197 """ be written to <b>{0}</b></p>""" 204 """<p>The call trace info could not"""
198 """<p>Reason: {1}</p>""") 205 """ be written to <b>{0}</b></p>"""
199 .format(fpath, str(err))) 206 """<p>Reason: {1}</p>"""
200 207 ).format(fpath, str(err)),
208 )
209
201 @pyqtSlot(QTreeWidgetItem, int) 210 @pyqtSlot(QTreeWidgetItem, int)
202 def on_callTrace_itemDoubleClicked(self, item, column): 211 def on_callTrace_itemDoubleClicked(self, item, column):
203 """ 212 """
204 Private slot to open the double clicked file in an editor. 213 Private slot to open the double clicked file in an editor.
205 214
206 @param item reference to the double clicked item 215 @param item reference to the double clicked item
207 @type QTreeWidgetItem 216 @type QTreeWidgetItem
208 @param column column that was double clicked 217 @param column column that was double clicked
209 @type int 218 @type int
210 """ 219 """
219 # do nothing, if the line info is not an integer 228 # do nothing, if the line info is not an integer
220 return 229 return
221 if self.__projectMode: 230 if self.__projectMode:
222 filename = self.__project.getAbsolutePath(filename) 231 filename = self.__project.getAbsolutePath(filename)
223 self.sourceFile.emit(filename, lineno) 232 self.sourceFile.emit(filename, lineno)
224 233
225 def clear(self): 234 def clear(self):
226 """ 235 """
227 Public slot to clear the call trace info. 236 Public slot to clear the call trace info.
228 """ 237 """
229 self.callTrace.clear() 238 self.callTrace.clear()
230 self.__callStack = [] 239 self.__callStack = []
231 240
232 def setProjectMode(self, enabled): 241 def setProjectMode(self, enabled):
233 """ 242 """
234 Public slot to set the call trace viewer to project mode. 243 Public slot to set the call trace viewer to project mode.
235 244
236 In project mode the call trace info is shown with project relative 245 In project mode the call trace info is shown with project relative
237 path names. 246 path names.
238 247
239 @param enabled flag indicating to enable the project mode 248 @param enabled flag indicating to enable the project mode
240 @type bool 249 @type bool
241 """ 250 """
242 self.__projectMode = enabled 251 self.__projectMode = enabled
243 if enabled and self.__project is None: 252 if enabled and self.__project is None:
244 self.__project = ericApp().getObject("Project") 253 self.__project = ericApp().getObject("Project")
245 254
246 def __addCallTraceInfo(self, isCall, fromFile, fromLine, fromFunction, 255 def __addCallTraceInfo(
247 toFile, toLine, toFunction, debuggerId): 256 self,
257 isCall,
258 fromFile,
259 fromLine,
260 fromFunction,
261 toFile,
262 toLine,
263 toFunction,
264 debuggerId,
265 ):
248 """ 266 """
249 Private method to add an entry to the call trace viewer. 267 Private method to add an entry to the call trace viewer.
250 268
251 @param isCall flag indicating a 'call' 269 @param isCall flag indicating a 'call'
252 @type bool 270 @type bool
253 @param fromFile name of the originating file 271 @param fromFile name of the originating file
254 @type str 272 @type str
255 @param fromLine line number in the originating file 273 @param fromLine line number in the originating file
268 if debuggerId == self.__tracedDebuggerId: 286 if debuggerId == self.__tracedDebuggerId:
269 if isCall: 287 if isCall:
270 icon = UI.PixmapCache.getIcon("forward") 288 icon = UI.PixmapCache.getIcon("forward")
271 else: 289 else:
272 icon = UI.PixmapCache.getIcon("back") 290 icon = UI.PixmapCache.getIcon("back")
273 parentItem = ( 291 parentItem = self.__callStack[-1] if self.__callStack else self.callTrace
274 self.__callStack[-1] if self.__callStack else self.callTrace) 292
275
276 if self.__projectMode: 293 if self.__projectMode:
277 fromFile = self.__project.getRelativePath(fromFile) 294 fromFile = self.__project.getRelativePath(fromFile)
278 toFile = self.__project.getRelativePath(toFile) 295 toFile = self.__project.getRelativePath(toFile)
279 296
280 itm = QTreeWidgetItem( 297 itm = QTreeWidgetItem(
281 parentItem, 298 parentItem,
282 ["", 299 [
283 self.__entryFormat.format(fromFile, fromLine, fromFunction), 300 "",
284 self.__entryFormat.format(toFile, toLine, toFunction)]) 301 self.__entryFormat.format(fromFile, fromLine, fromFunction),
302 self.__entryFormat.format(toFile, toLine, toFunction),
303 ],
304 )
285 itm.setIcon(0, icon) 305 itm.setIcon(0, icon)
286 itm.setData(0, Qt.ItemDataRole.UserRole, isCall) 306 itm.setData(0, Qt.ItemDataRole.UserRole, isCall)
287 itm.setExpanded(True) 307 itm.setExpanded(True)
288 308
289 if isCall: 309 if isCall:
290 self.__callStack.append(itm) 310 self.__callStack.append(itm)
291 else: 311 else:
292 if self.__callStack: 312 if self.__callStack:
293 self.__callStack.pop(-1) 313 self.__callStack.pop(-1)
294 314
295 def isCallTraceEnabled(self): 315 def isCallTraceEnabled(self):
296 """ 316 """
297 Public method to get the state of the call trace function. 317 Public method to get the state of the call trace function.
298 318
299 @return flag indicating the state of the call trace function 319 @return flag indicating the state of the call trace function
300 @rtype bool 320 @rtype bool
301 """ 321 """
302 return self.__callTraceEnabled 322 return self.__callTraceEnabled
303 323
304 @pyqtSlot(str, int, str, bool, str) 324 @pyqtSlot(str, int, str, bool, str)
305 def __clientExit(self, program, status, message, quiet, debuggerId): 325 def __clientExit(self, program, status, message, quiet, debuggerId):
306 """ 326 """
307 Private slot to handle a debug client terminating. 327 Private slot to handle a debug client terminating.
308 328
309 @param program name of the exited program 329 @param program name of the exited program
310 @type str 330 @type str
311 @param status exit code of the debugged program 331 @param status exit code of the debugged program
312 @type int 332 @type int
313 @param message exit message of the debugged program 333 @param message exit message of the debugged program

eric ide

mercurial