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 |