eric6/Debugger/CallTraceViewer.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) 2012 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the Call Trace viewer widget.
8 """
9
10 from __future__ import unicode_literals
11
12 from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QRegExp, QFileInfo
13 from PyQt5.QtWidgets import QWidget, QTreeWidgetItem
14
15 from E5Gui.E5Application import e5App
16 from E5Gui import E5FileDialog, E5MessageBox
17
18 from .Ui_CallTraceViewer import Ui_CallTraceViewer
19
20 import UI.PixmapCache
21 import Preferences
22 import Utilities
23
24
25 class CallTraceViewer(QWidget, Ui_CallTraceViewer):
26 """
27 Class implementing the Call Trace viewer widget.
28
29 @signal sourceFile(str, int) emitted to show the source of a call/return
30 point
31 """
32 sourceFile = pyqtSignal(str, int)
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(CallTraceViewer, self).__init__(parent)
42 self.setupUi(self)
43
44 self.__dbs = debugServer
45
46 self.startTraceButton.setIcon(
47 UI.PixmapCache.getIcon("callTraceStart.png"))
48 self.stopTraceButton.setIcon(
49 UI.PixmapCache.getIcon("callTraceStop.png"))
50 self.resizeButton.setIcon(UI.PixmapCache.getIcon("resizeColumns.png"))
51 self.clearButton.setIcon(UI.PixmapCache.getIcon("editDelete.png"))
52 self.saveButton.setIcon(UI.PixmapCache.getIcon("fileSave.png"))
53
54 self.__headerItem = QTreeWidgetItem(
55 ["", self.tr("From"), self.tr("To")])
56 self.__headerItem.setIcon(0, UI.PixmapCache.getIcon("callReturn.png"))
57 self.callTrace.setHeaderItem(self.__headerItem)
58
59 self.__callStack = []
60
61 self.__entryFormat = "{0}:{1} ({2})"
62 self.__entryRe = QRegExp(r"""(.+):(\d+)\s\((.*)\)""")
63
64 self.__projectMode = False
65 self.__project = None
66
67 stopOnExit = Preferences.toBool(
68 Preferences.Prefs.settings.value("CallTrace/StopOnExit", True))
69 self.stopCheckBox.setChecked(stopOnExit)
70
71 self.__callTraceEnabled = (Preferences.toBool(
72 Preferences.Prefs.settings.value("CallTrace/Enabled", False)) and
73 not stopOnExit)
74
75 if self.__callTraceEnabled:
76 self.startTraceButton.setEnabled(False)
77 else:
78 self.stopTraceButton.setEnabled(False)
79
80 self.__dbs.callTraceInfo.connect(self.__addCallTraceInfo)
81 self.__dbs.clientExit.connect(self.__clientExit)
82
83 def __setCallTraceEnabled(self, enabled):
84 """
85 Private slot to set the call trace enabled status.
86
87 @param enabled flag indicating the new state (boolean)
88 """
89 self.__dbs.setCallTraceEnabled(enabled)
90 self.stopTraceButton.setEnabled(enabled)
91 self.startTraceButton.setEnabled(not enabled)
92 self.__callTraceEnabled = enabled
93 Preferences.Prefs.settings.setValue("CallTrace/Enabled", enabled)
94
95 if not enabled:
96 for column in range(self.callTrace.columnCount()):
97 self.callTrace.resizeColumnToContents(column)
98
99 @pyqtSlot(bool)
100 def on_stopCheckBox_clicked(self, checked):
101 """
102 Private slot to handle a click on the stop check box.
103
104 @param checked state of the check box
105 @type bool
106 """
107 Preferences.Prefs.settings.setValue("CallTrace/StopOnExit", checked)
108
109 @pyqtSlot()
110 def on_startTraceButton_clicked(self):
111 """
112 Private slot to start call tracing.
113 """
114 self.__setCallTraceEnabled(True)
115
116 @pyqtSlot()
117 def on_stopTraceButton_clicked(self):
118 """
119 Private slot to start call tracing.
120 """
121 self.__setCallTraceEnabled(False)
122
123 @pyqtSlot()
124 def on_resizeButton_clicked(self):
125 """
126 Private slot to resize the columns of the call trace to their contents.
127 """
128 for column in range(self.callTrace.columnCount()):
129 self.callTrace.resizeColumnToContents(column)
130
131 @pyqtSlot()
132 def on_clearButton_clicked(self):
133 """
134 Private slot to clear the call trace.
135 """
136 self.clear()
137
138 @pyqtSlot()
139 def on_saveButton_clicked(self):
140 """
141 Private slot to save the call trace info to a file.
142 """
143 if self.callTrace.topLevelItemCount() > 0:
144 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
145 self,
146 self.tr("Save Call Trace Info"),
147 "",
148 self.tr("Text Files (*.txt);;All Files (*)"),
149 None,
150 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
151 if fname:
152 ext = QFileInfo(fname).suffix()
153 if not ext:
154 ex = selectedFilter.split("(*")[1].split(")")[0]
155 if ex:
156 fname += ex
157 if QFileInfo(fname).exists():
158 res = E5MessageBox.yesNo(
159 self,
160 self.tr("Save Call Trace Info"),
161 self.tr("<p>The file <b>{0}</b> already exists."
162 " Overwrite it?</p>").format(fname),
163 icon=E5MessageBox.Warning)
164 if not res:
165 return
166 fname = Utilities.toNativeSeparators(fname)
167
168 try:
169 f = open(fname, "w", encoding="utf-8")
170 itm = self.callTrace.topLevelItem(0)
171 while itm is not None:
172 isCall = itm.data(0, Qt.UserRole)
173 if isCall:
174 call = "->"
175 else:
176 call = "<-"
177 f.write("{0} {1} || {2}\n".format(
178 call,
179 itm.text(1), itm.text(2)))
180 itm = self.callTrace.itemBelow(itm)
181 f.close()
182 except IOError as err:
183 E5MessageBox.critical(
184 self,
185 self.tr("Error saving Call Trace Info"),
186 self.tr("""<p>The call trace info could not"""
187 """ be written to <b>{0}</b></p>"""
188 """<p>Reason: {1}</p>""")
189 .format(fname, str(err)))
190
191 @pyqtSlot(QTreeWidgetItem, int)
192 def on_callTrace_itemDoubleClicked(self, item, column):
193 """
194 Private slot to open the double clicked file in an editor.
195
196 @param item reference to the double clicked item (QTreeWidgetItem)
197 @param column column that was double clicked (integer)
198 """
199 if item is not None and column > 0:
200 columnStr = item.text(column)
201 if self.__entryRe.exactMatch(columnStr.strip()):
202 filename, lineno, func = self.__entryRe.capturedTexts()[1:]
203 try:
204 lineno = int(lineno)
205 except ValueError:
206 # do nothing, if the line info is not an integer
207 return
208 if self.__projectMode:
209 filename = self.__project.getAbsolutePath(filename)
210 self.sourceFile.emit(filename, lineno)
211
212 def clear(self):
213 """
214 Public slot to clear the call trace info.
215 """
216 self.callTrace.clear()
217 self.__callStack = []
218
219 def setProjectMode(self, enabled):
220 """
221 Public slot to set the call trace viewer to project mode.
222
223 In project mode the call trace info is shown with project relative
224 path names.
225
226 @param enabled flag indicating to enable the project mode (boolean)
227 """
228 self.__projectMode = enabled
229 if enabled and self.__project is None:
230 self.__project = e5App().getObject("Project")
231
232 def __addCallTraceInfo(self, isCall, fromFile, fromLine, fromFunction,
233 toFile, toLine, toFunction):
234 """
235 Private method to add an entry to the call trace viewer.
236
237 @param isCall flag indicating a 'call' (boolean)
238 @param fromFile name of the originating file (string)
239 @param fromLine line number in the originating file (string)
240 @param fromFunction name of the originating function (string)
241 @param toFile name of the target file (string)
242 @param toLine line number in the target file (string)
243 @param toFunction name of the target function (string)
244 """
245 if isCall:
246 icon = UI.PixmapCache.getIcon("forward.png")
247 else:
248 icon = UI.PixmapCache.getIcon("back.png")
249 parentItem = \
250 self.__callStack[-1] if self.__callStack else self.callTrace
251
252 if self.__projectMode:
253 fromFile = self.__project.getRelativePath(fromFile)
254 toFile = self.__project.getRelativePath(toFile)
255
256 itm = QTreeWidgetItem(
257 parentItem,
258 ["",
259 self.__entryFormat.format(fromFile, fromLine, fromFunction),
260 self.__entryFormat.format(toFile, toLine, toFunction)])
261 itm.setIcon(0, icon)
262 itm.setData(0, Qt.UserRole, isCall)
263 itm.setExpanded(True)
264
265 if isCall:
266 self.__callStack.append(itm)
267 else:
268 if self.__callStack:
269 self.__callStack.pop(-1)
270
271 def isCallTraceEnabled(self):
272 """
273 Public method to get the state of the call trace function.
274
275 @return flag indicating the state of the call trace function (boolean)
276 """
277 return self.__callTraceEnabled
278
279 @pyqtSlot()
280 def __clientExit(self):
281 """
282 Private slot handling a client exiting.
283 """
284 if self.stopCheckBox.isChecked():
285 self.__setCallTraceEnabled(False)

eric ide

mercurial