5 |
5 |
6 """ |
6 """ |
7 Module implementing the Call Trace viewer widget. |
7 Module implementing the Call Trace viewer widget. |
8 """ |
8 """ |
9 |
9 |
10 from PyQt4.QtCore import pyqtSlot, pyqtSignal |
10 from PyQt4.QtCore import pyqtSlot, pyqtSignal, Qt, QRegExp, QFileInfo |
11 from PyQt4.QtGui import QWidget, QTreeWidgetItem |
11 from PyQt4.QtGui import QWidget, QTreeWidgetItem |
|
12 |
|
13 from E5Gui.E5Application import e5App |
|
14 from E5Gui import E5FileDialog, E5MessageBox |
12 |
15 |
13 from .Ui_CallTraceViewer import Ui_CallTraceViewer |
16 from .Ui_CallTraceViewer import Ui_CallTraceViewer |
14 |
17 |
15 import UI.PixmapCache |
18 import UI.PixmapCache |
16 import Preferences |
19 import Preferences |
|
20 import Utilities |
17 |
21 |
18 |
22 |
19 class CallTraceViewer(QWidget, Ui_CallTraceViewer): |
23 class CallTraceViewer(QWidget, Ui_CallTraceViewer): |
20 """ |
24 """ |
21 Class implementing the Call Trace viewer widget. |
25 Class implementing the Call Trace viewer widget. |
46 self.callTrace.setHeaderItem(self.__headerItem) |
50 self.callTrace.setHeaderItem(self.__headerItem) |
47 |
51 |
48 self.__callStack = [] |
52 self.__callStack = [] |
49 |
53 |
50 self.__entryFormat = "{0}:{1} ({2})" |
54 self.__entryFormat = "{0}:{1} ({2})" |
|
55 self.__entryRe = QRegExp(r"""(.+):(\d+)\s\((.*)\)""") |
|
56 |
|
57 self.__projectMode = False |
|
58 self.__project = None |
51 |
59 |
52 self.__callTraceEnabled = Preferences.toBool( |
60 self.__callTraceEnabled = Preferences.toBool( |
53 Preferences.Prefs.settings.value("CallTrace/Enabled", False)) |
61 Preferences.Prefs.settings.value("CallTrace/Enabled", False)) |
54 |
|
55 if self.__callTraceEnabled: |
62 if self.__callTraceEnabled: |
|
63 self.startTraceButton.setEnabled(False) |
|
64 else: |
56 self.stopTraceButton.setEnabled(False) |
65 self.stopTraceButton.setEnabled(False) |
57 else: |
|
58 self.startTraceButton.setEnabled(False) |
|
59 |
66 |
60 self.__dbs.callTraceInfo.connect(self.__addCallTraceInfo) |
67 self.__dbs.callTraceInfo.connect(self.__addCallTraceInfo) |
61 |
68 |
|
69 def __setCallTraceEnabled(self, enabled): |
|
70 """ |
|
71 Private slot to set the call trace enabled status. |
|
72 |
|
73 @param enabled flag indicating the new state (boolean) |
|
74 """ |
|
75 self.__dbs.setCallTraceEnabled(enabled) |
|
76 self.stopTraceButton.setEnabled(enabled) |
|
77 self.startTraceButton.setEnabled(not enabled) |
|
78 self.__callTraceEnabled = enabled |
|
79 Preferences.Prefs.settings.setValue("CallTrace/Enabled", enabled) |
|
80 |
62 @pyqtSlot() |
81 @pyqtSlot() |
63 def on_startTraceButton_clicked(self): |
82 def on_startTraceButton_clicked(self): |
64 """ |
83 """ |
65 Private slot to start call tracing. |
84 Private slot to start call tracing. |
66 """ |
85 """ |
67 self.__dbs.setCallTraceEnabled(True) |
86 self.__setCallTraceEnabled(True) |
68 self.stopTraceButton.setEnabled(True) |
|
69 self.startTraceButton.setEnabled(False) |
|
70 Preferences.Prefs.settings.setValue("CallTrace/Enabled", True) |
|
71 |
87 |
72 @pyqtSlot() |
88 @pyqtSlot() |
73 def on_stopTraceButton_clicked(self): |
89 def on_stopTraceButton_clicked(self): |
74 """ |
90 """ |
75 Private slot to start call tracing. |
91 Private slot to start call tracing. |
76 """ |
92 """ |
77 self.__dbs.setCallTraceEnabled(False) |
93 self.__setCallTraceEnabled(False) |
78 self.stopTraceButton.setEnabled(False) |
|
79 self.startTraceButton.setEnabled(True) |
|
80 Preferences.Prefs.settings.setValue("CallTrace/Enabled", False) |
|
81 |
94 |
82 @pyqtSlot() |
95 @pyqtSlot() |
83 def on_resizeButton_clicked(self): |
96 def on_resizeButton_clicked(self): |
84 """ |
97 """ |
85 Private slot to resize the columns of the call trace to their contents. |
98 Private slot to resize the columns of the call trace to their contents. |
95 self.clear() |
108 self.clear() |
96 |
109 |
97 @pyqtSlot() |
110 @pyqtSlot() |
98 def on_saveButton_clicked(self): |
111 def on_saveButton_clicked(self): |
99 """ |
112 """ |
100 Slot documentation goes here. |
113 Private slot to save the call trace info to a file. |
101 """ |
114 """ |
102 # TODO: not implemented yet |
115 if self.callTrace.topLevelItemCount() > 0: |
103 raise NotImplementedError |
116 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( |
|
117 self, |
|
118 self.trUtf8("Save Call Trace Info"), |
|
119 "", |
|
120 self.trUtf8("Text Files (*.txt);;All Files (*)"), |
|
121 None, |
|
122 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) |
|
123 if fname: |
|
124 ext = QFileInfo(fname).suffix() |
|
125 if not ext: |
|
126 ex = selectedFilter.split("(*")[1].split(")")[0] |
|
127 if ex: |
|
128 fname += ex |
|
129 if QFileInfo(fname).exists(): |
|
130 res = E5MessageBox.yesNo(self, |
|
131 self.trUtf8("Save Call Trace Info"), |
|
132 self.trUtf8("<p>The file <b>{0}</b> already exists." |
|
133 " Overwrite it?</p>").format(fname), |
|
134 icon=E5MessageBox.Warning) |
|
135 if not res: |
|
136 return |
|
137 fname = Utilities.toNativeSeparators(fname) |
|
138 |
|
139 try: |
|
140 f = open(fname, "w", encoding="utf-8") |
|
141 itm = self.callTrace.topLevelItem(0) |
|
142 while itm is not None: |
|
143 isCall = itm.data(0, Qt.UserRole) |
|
144 if isCall: |
|
145 call = "->" |
|
146 else: |
|
147 call = "<-" |
|
148 f.write("{0} {1} || {2}\n".format(call, |
|
149 itm.text(1), itm.text(2))) |
|
150 itm = self.callTrace.itemBelow(itm) |
|
151 f.close() |
|
152 except IOError as err: |
|
153 E5MessageBox.critical(self, |
|
154 self.trUtf8("Error saving Call Trace Info"), |
|
155 self.trUtf8("""<p>The call trace info could not be written""" |
|
156 """ to <b>{0}</b></p><p>Reason: {1}</p>""")\ |
|
157 .format(fname, str(err))) |
104 |
158 |
105 @pyqtSlot(QTreeWidgetItem, int) |
159 @pyqtSlot(QTreeWidgetItem, int) |
106 def on_callTrace_itemDoubleClicked(self, item, column): |
160 def on_callTrace_itemDoubleClicked(self, item, column): |
107 """ |
161 """ |
108 Slot documentation goes here. |
162 Private slot to open the double clicked file in an editor. |
109 """ |
163 |
110 # TODO: not implemented yet |
164 @param item reference to the double clicked item (QTreeWidgetItem) |
111 raise NotImplementedError |
165 @param column column that was double clicked (integer) |
|
166 """ |
|
167 if item is not None and column > 0: |
|
168 columnStr = item.text(column) |
|
169 if self.__entryRe.exactMatch(columnStr.strip()): |
|
170 filename, lineno, func = self.__entryRe.capturedTexts()[1:] |
|
171 try: |
|
172 lineno = int(lineno) |
|
173 except ValueError: |
|
174 # do nothing, if the line info is not an integer |
|
175 return |
|
176 if self.__projectMode: |
|
177 filename = self.__project.getAbsolutePath(filename) |
|
178 self.sourceFile.emit(filename, lineno) |
112 |
179 |
113 def clear(self): |
180 def clear(self): |
114 """ |
181 """ |
115 Public slot to clear the call trace info. |
182 Public slot to clear the call trace info. |
116 """ |
183 """ |
117 self.callTrace.clear() |
184 self.callTrace.clear() |
118 self.__callStack = [] |
185 self.__callStack = [] |
|
186 |
|
187 def setProjectMode(self, enabled): |
|
188 """ |
|
189 Public slot to set the call trace viewer to project mode. |
|
190 |
|
191 In project mode the call trace info is shown with project relative |
|
192 path names. |
|
193 |
|
194 @param enabled flag indicating to enable the project mode (boolean) |
|
195 """ |
|
196 self.__projectMode = enabled |
|
197 if enabled and self.__project is None: |
|
198 self.__project = e5App().getObject("Project") |
119 |
199 |
120 def __addCallTraceInfo(self, isCall, fromFile, fromLine, fromFunction, |
200 def __addCallTraceInfo(self, isCall, fromFile, fromLine, fromFunction, |
121 toFile, toLine, toFunction): |
201 toFile, toLine, toFunction): |
122 """ |
202 """ |
123 Private method to add an entry to the call trace viewer. |
203 Private method to add an entry to the call trace viewer. |
134 icon = UI.PixmapCache.getIcon("forward.png") |
214 icon = UI.PixmapCache.getIcon("forward.png") |
135 else: |
215 else: |
136 icon = UI.PixmapCache.getIcon("back.png") |
216 icon = UI.PixmapCache.getIcon("back.png") |
137 parentItem = self.__callStack[-1] if self.__callStack else self.callTrace |
217 parentItem = self.__callStack[-1] if self.__callStack else self.callTrace |
138 |
218 |
|
219 if self.__projectMode: |
|
220 fromFile = self.__project.getRelativePath(fromFile) |
|
221 toFile = self.__project.getRelativePath(toFile) |
|
222 |
139 itm = QTreeWidgetItem(parentItem, ["", |
223 itm = QTreeWidgetItem(parentItem, ["", |
140 self.__entryFormat.format(fromFile, fromLine, fromFunction), |
224 self.__entryFormat.format(fromFile, fromLine, fromFunction), |
141 self.__entryFormat.format(toFile, toLine, toFunction)]) |
225 self.__entryFormat.format(toFile, toLine, toFunction)]) |
142 itm.setIcon(0, icon) |
226 itm.setIcon(0, icon) |
|
227 itm.setData(0, Qt.UserRole, isCall) |
143 itm.setExpanded(True) |
228 itm.setExpanded(True) |
144 |
229 |
145 if isCall: |
230 if isCall: |
146 self.__callStack.append(itm) |
231 self.__callStack.append(itm) |
147 else: |
232 else: |
148 self.__callStack.pop(-1) |
233 if self.__callStack: |
|
234 self.__callStack.pop(-1) |
149 |
235 |
150 def isCallTraceEnabled(self): |
236 def isCallTraceEnabled(self): |
151 """ |
237 """ |
152 Public method to get the state of the call trace function. |
238 Public method to get the state of the call trace function. |
153 |
239 |