|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2002 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a widget containing various debug related views. |
|
8 |
|
9 The views avaliable are: |
|
10 <ul> |
|
11 <li>variables viewer for global variables</li> |
|
12 <li>variables viewer for local variables</li> |
|
13 <li>viewer for breakpoints</li> |
|
14 <li>viewer for watch expressions</li> |
|
15 <li>viewer for exceptions</li> |
|
16 <li>viewer for threads</li> |
|
17 <li>a file browser (optional)</li> |
|
18 <li>an interpreter shell (optional)</li> |
|
19 </ul> |
|
20 """ |
|
21 |
|
22 import os |
|
23 |
|
24 from PyQt4.QtCore import * |
|
25 from PyQt4.QtGui import * |
|
26 |
|
27 from QScintilla.Shell import Shell |
|
28 from VariablesViewer import VariablesViewer |
|
29 from ExceptionLogger import ExceptionLogger |
|
30 from BreakPointViewer import BreakPointViewer |
|
31 from WatchPointViewer import WatchPointViewer |
|
32 |
|
33 import Utilities |
|
34 import UI.PixmapCache |
|
35 |
|
36 from E4Gui.E4TabWidget import E4TabWidget |
|
37 |
|
38 class DebugViewer(QWidget): |
|
39 """ |
|
40 Class implementing a widget conatining various debug related views. |
|
41 |
|
42 The individual tabs contain the interpreter shell (optional), |
|
43 the filesystem browser (optional), the two variables viewers (global and local), |
|
44 a breakpoint viewer, a watch expression viewer and the exception logger. Additionally |
|
45 a list of all threads is shown. |
|
46 |
|
47 @signal sourceFile(string, int) emitted to open a source file at a line |
|
48 """ |
|
49 def __init__(self, debugServer, docked, vm, parent = None, |
|
50 embeddedShell = True, embeddedBrowser = True): |
|
51 """ |
|
52 Constructor |
|
53 |
|
54 @param debugServer reference to the debug server object |
|
55 @param docked flag indicating a dock window |
|
56 @param vm reference to the viewmanager object |
|
57 @param parent parent widget (QWidget) |
|
58 @param embeddedShell flag indicating whether the shell should be included. |
|
59 This flag is set to False by those layouts, that have the interpreter |
|
60 shell in a separate window. |
|
61 @param embeddedBrowser flag indicating whether the file browser should |
|
62 be included. This flag is set to False by those layouts, that |
|
63 have the file browser in a separate window or embedded |
|
64 in the project browser instead. |
|
65 """ |
|
66 QWidget.__init__(self, parent) |
|
67 |
|
68 self.debugServer = debugServer |
|
69 self.debugUI = None |
|
70 |
|
71 self.setWindowIcon(UI.PixmapCache.getIcon("eric.png")) |
|
72 |
|
73 self.__mainLayout = QVBoxLayout() |
|
74 self.__mainLayout.setMargin(0) |
|
75 self.setLayout(self.__mainLayout) |
|
76 |
|
77 self.__tabWidget = E4TabWidget() |
|
78 self.__mainLayout.addWidget(self.__tabWidget) |
|
79 |
|
80 self.embeddedShell = embeddedShell |
|
81 if embeddedShell: |
|
82 # add the interpreter shell |
|
83 self.shell = Shell(debugServer, vm) |
|
84 index = self.__tabWidget.addTab(self.shell, |
|
85 UI.PixmapCache.getIcon("shell.png"), '') |
|
86 self.__tabWidget.setTabToolTip(index, self.shell.windowTitle()) |
|
87 |
|
88 self.embeddedBrowser = embeddedBrowser |
|
89 if embeddedBrowser: |
|
90 from UI.Browser import Browser |
|
91 # add the browser |
|
92 self.browser = Browser() |
|
93 index = self.__tabWidget.addTab(self.browser, |
|
94 UI.PixmapCache.getIcon("browser.png"), '') |
|
95 self.__tabWidget.setTabToolTip(index, self.browser.windowTitle()) |
|
96 |
|
97 # add the global variables viewer |
|
98 self.glvWidget = QWidget() |
|
99 self.glvWidgetVLayout = QVBoxLayout(self.glvWidget) |
|
100 self.glvWidgetVLayout.setMargin(0) |
|
101 self.glvWidgetVLayout.setSpacing(3) |
|
102 self.glvWidget.setLayout(self.glvWidgetVLayout) |
|
103 |
|
104 self.globalsViewer = VariablesViewer(self.glvWidget, True) |
|
105 self.glvWidgetVLayout.addWidget(self.globalsViewer) |
|
106 |
|
107 self.glvWidgetHLayout = QHBoxLayout() |
|
108 self.glvWidgetHLayout.setMargin(3) |
|
109 |
|
110 self.globalsFilterEdit = QLineEdit(self.glvWidget) |
|
111 self.globalsFilterEdit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) |
|
112 self.glvWidgetHLayout.addWidget(self.globalsFilterEdit) |
|
113 self.globalsFilterEdit.setToolTip(\ |
|
114 self.trUtf8("Enter regular expression patterns separated by ';' to define " |
|
115 "variable filters. ")) |
|
116 self.globalsFilterEdit.setWhatsThis(\ |
|
117 self.trUtf8("Enter regular expression patterns separated by ';' to define " |
|
118 "variable filters. All variables and class attributes matched by one of " |
|
119 "the expressions are not shown in the list above.")) |
|
120 |
|
121 self.setGlobalsFilterButton = QPushButton(self.trUtf8('Set'), self.glvWidget) |
|
122 self.glvWidgetHLayout.addWidget(self.setGlobalsFilterButton) |
|
123 self.glvWidgetVLayout.addLayout(self.glvWidgetHLayout) |
|
124 |
|
125 index = self.__tabWidget.addTab(self.glvWidget, |
|
126 UI.PixmapCache.getIcon("globalVariables.png"), '') |
|
127 self.__tabWidget.setTabToolTip(index, self.globalsViewer.windowTitle()) |
|
128 |
|
129 self.connect(self.setGlobalsFilterButton, SIGNAL('clicked()'), |
|
130 self.__setGlobalsFilter) |
|
131 self.connect(self.globalsFilterEdit, SIGNAL('returnPressed()'), |
|
132 self.__setGlobalsFilter) |
|
133 |
|
134 # add the local variables viewer |
|
135 self.lvWidget = QWidget() |
|
136 self.lvWidgetVLayout = QVBoxLayout(self.lvWidget) |
|
137 self.lvWidgetVLayout.setMargin(0) |
|
138 self.lvWidgetVLayout.setSpacing(3) |
|
139 self.lvWidget.setLayout(self.lvWidgetVLayout) |
|
140 |
|
141 self.lvWidgetHLayout1 = QHBoxLayout() |
|
142 self.lvWidgetHLayout1.setMargin(3) |
|
143 |
|
144 self.stackComboBox = QComboBox(self.lvWidget) |
|
145 self.stackComboBox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) |
|
146 self.lvWidgetHLayout1.addWidget(self.stackComboBox) |
|
147 |
|
148 self.sourceButton = QPushButton(self.trUtf8('Source'), self.lvWidget) |
|
149 self.lvWidgetHLayout1.addWidget(self.sourceButton) |
|
150 self.sourceButton.setEnabled(False) |
|
151 self.lvWidgetVLayout.addLayout(self.lvWidgetHLayout1) |
|
152 |
|
153 self.localsViewer = VariablesViewer(self.lvWidget, False) |
|
154 self.lvWidgetVLayout.addWidget(self.localsViewer) |
|
155 |
|
156 self.lvWidgetHLayout2 = QHBoxLayout() |
|
157 self.lvWidgetHLayout2.setMargin(3) |
|
158 |
|
159 self.localsFilterEdit = QLineEdit(self.lvWidget) |
|
160 self.localsFilterEdit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) |
|
161 self.lvWidgetHLayout2.addWidget(self.localsFilterEdit) |
|
162 self.localsFilterEdit.setToolTip(\ |
|
163 self.trUtf8("Enter regular expression patterns separated by ';' to define " |
|
164 "variable filters. ")) |
|
165 self.localsFilterEdit.setWhatsThis(\ |
|
166 self.trUtf8("Enter regular expression patterns separated by ';' to define " |
|
167 "variable filters. All variables and class attributes matched by one of " |
|
168 "the expressions are not shown in the list above.")) |
|
169 |
|
170 self.setLocalsFilterButton = QPushButton(self.trUtf8('Set'), self.lvWidget) |
|
171 self.lvWidgetHLayout2.addWidget(self.setLocalsFilterButton) |
|
172 self.lvWidgetVLayout.addLayout(self.lvWidgetHLayout2) |
|
173 |
|
174 index = self.__tabWidget.addTab(self.lvWidget, |
|
175 UI.PixmapCache.getIcon("localVariables.png"), '') |
|
176 self.__tabWidget.setTabToolTip(index, self.localsViewer.windowTitle()) |
|
177 |
|
178 self.connect(self.sourceButton, SIGNAL('clicked()'), |
|
179 self.__showSource) |
|
180 self.connect(self.stackComboBox, SIGNAL('activated(int)'), |
|
181 self.__frameSelected) |
|
182 self.connect(self.setLocalsFilterButton, SIGNAL('clicked()'), |
|
183 self.__setLocalsFilter) |
|
184 self.connect(self.localsFilterEdit, SIGNAL('returnPressed()'), |
|
185 self.__setLocalsFilter) |
|
186 |
|
187 # add the breakpoint viewer |
|
188 self.breakpointViewer = BreakPointViewer() |
|
189 self.breakpointViewer.setModel(self.debugServer.getBreakPointModel()) |
|
190 index = self.__tabWidget.addTab(self.breakpointViewer, |
|
191 UI.PixmapCache.getIcon("breakpoints.png"), '') |
|
192 self.__tabWidget.setTabToolTip(index, self.breakpointViewer.windowTitle()) |
|
193 self.connect(self.breakpointViewer, SIGNAL("sourceFile"), |
|
194 self, SIGNAL("sourceFile")) |
|
195 |
|
196 # add the watch expression viewer |
|
197 self.watchpointViewer = WatchPointViewer() |
|
198 self.watchpointViewer.setModel(self.debugServer.getWatchPointModel()) |
|
199 index = self.__tabWidget.addTab(self.watchpointViewer, |
|
200 UI.PixmapCache.getIcon("watchpoints.png"), '') |
|
201 self.__tabWidget.setTabToolTip(index, self.watchpointViewer.windowTitle()) |
|
202 |
|
203 # add the exception logger |
|
204 self.exceptionLogger = ExceptionLogger() |
|
205 index = self.__tabWidget.addTab(self.exceptionLogger, |
|
206 UI.PixmapCache.getIcon("exceptions.png"), '') |
|
207 self.__tabWidget.setTabToolTip(index, self.exceptionLogger.windowTitle()) |
|
208 |
|
209 if self.embeddedShell: |
|
210 self.__tabWidget.setCurrentWidget(self.shell) |
|
211 else: |
|
212 if self.embeddedBrowser: |
|
213 self.__tabWidget.setCurrentWidget(self.browser) |
|
214 else: |
|
215 self.__tabWidget.setCurrentWidget(self.lvWidget) |
|
216 |
|
217 # add the threads viewer |
|
218 self.__mainLayout.addWidget(QLabel(self.trUtf8("Threads:"))) |
|
219 self.__threadList = QTreeWidget() |
|
220 self.__threadList.setHeaderLabels([self.trUtf8("ID"), self.trUtf8("Name"), |
|
221 self.trUtf8("State"), ""]) |
|
222 self.__threadList.setSortingEnabled(True) |
|
223 self.__mainLayout.addWidget(self.__threadList) |
|
224 |
|
225 self.__doThreadListUpdate = True |
|
226 |
|
227 self.connect(self.__threadList, |
|
228 SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), |
|
229 self.__threadSelected) |
|
230 |
|
231 self.__mainLayout.setStretchFactor(self.__tabWidget, 5) |
|
232 self.__mainLayout.setStretchFactor(self.__threadList, 1) |
|
233 |
|
234 self.currPage = None |
|
235 self.currentStack = None |
|
236 self.framenr = 0 |
|
237 |
|
238 self.connect(self.debugServer, SIGNAL('clientStack'), self.handleClientStack) |
|
239 |
|
240 def setDebugger(self, debugUI): |
|
241 """ |
|
242 Public method to set a reference to the Debug UI. |
|
243 |
|
244 @param debugUI reference to the DebugUI objectTrees |
|
245 """ |
|
246 self.debugUI = debugUI |
|
247 self.connect(self.debugUI, SIGNAL('clientStack'), self.handleClientStack) |
|
248 |
|
249 def handleResetUI(self): |
|
250 """ |
|
251 Public method to reset the SBVviewer. |
|
252 """ |
|
253 self.globalsViewer.handleResetUI() |
|
254 self.localsViewer.handleResetUI() |
|
255 self.stackComboBox.clear() |
|
256 self.sourceButton.setEnabled(False) |
|
257 self.currentStack = None |
|
258 self.__threadList.clear() |
|
259 if self.embeddedShell: |
|
260 self.__tabWidget.setCurrentWidget(self.shell) |
|
261 else: |
|
262 if self.embeddedBrowser: |
|
263 self.__tabWidget.setCurrentWidget(self.browser) |
|
264 else: |
|
265 self.__tabWidget.setCurrentWidget(self.lvWidget) |
|
266 self.breakpointViewer.handleResetUI() |
|
267 |
|
268 def handleRawInput(self): |
|
269 """ |
|
270 Pulic slot to handle the switch to the shell in raw input mode. |
|
271 """ |
|
272 if self.embeddedShell: |
|
273 self.saveCurrentPage() |
|
274 self.__tabWidget.setCurrentWidget(self.shell) |
|
275 |
|
276 def showVariables(self, vlist, globals): |
|
277 """ |
|
278 Public method to show the variables in the respective window. |
|
279 |
|
280 @param vlist list of variables to display |
|
281 @param globals flag indicating global/local state |
|
282 """ |
|
283 if globals: |
|
284 self.globalsViewer.showVariables(vlist, self.framenr) |
|
285 else: |
|
286 self.localsViewer.showVariables(vlist, self.framenr) |
|
287 |
|
288 def showVariable(self, vlist, globals): |
|
289 """ |
|
290 Public method to show the variables in the respective window. |
|
291 |
|
292 @param vlist list of variables to display |
|
293 @param globals flag indicating global/local state |
|
294 """ |
|
295 if globals: |
|
296 self.globalsViewer.showVariable(vlist) |
|
297 else: |
|
298 self.localsViewer.showVariable(vlist) |
|
299 |
|
300 def showVariablesTab(self, globals): |
|
301 """ |
|
302 Public method to make a variables tab visible. |
|
303 |
|
304 @param globals flag indicating global/local state |
|
305 """ |
|
306 if globals: |
|
307 self.__tabWidget.setCurrentWidget(self.glvWidget) |
|
308 else: |
|
309 self.__tabWidget.setCurrentWidget(self.lvWidget) |
|
310 |
|
311 def saveCurrentPage(self): |
|
312 """ |
|
313 Public slot to save the current page. |
|
314 """ |
|
315 self.currPage = self.__tabWidget.currentWidget() |
|
316 |
|
317 def restoreCurrentPage(self): |
|
318 """ |
|
319 Public slot to restore the previously saved page. |
|
320 """ |
|
321 if self.currPage is not None: |
|
322 self.__tabWidget.setCurrentWidget(self.currPage) |
|
323 |
|
324 def handleClientStack(self, stack): |
|
325 """ |
|
326 Public slot to show the call stack of the program being debugged. |
|
327 """ |
|
328 self.framenr = 0 |
|
329 self.stackComboBox.clear() |
|
330 self.currentStack = stack |
|
331 self.sourceButton.setEnabled(len(stack) > 0) |
|
332 for s in stack: |
|
333 # just show base filename to make it readable |
|
334 s = (os.path.basename(s[0]), s[1], s[2]) |
|
335 self.stackComboBox.addItem('%s:%s:%s' % s) |
|
336 |
|
337 def setVariablesFilter(self, globalsFilter, localsFilter): |
|
338 """ |
|
339 Public slot to set the local variables filter. |
|
340 |
|
341 @param globalsFilter filter list for global variable types (list of int) |
|
342 @param localsFilter filter list for local variable types (list of int) |
|
343 """ |
|
344 self.globalsFilter = globalsFilter |
|
345 self.localsFilter = localsFilter |
|
346 |
|
347 def __showSource(self): |
|
348 """ |
|
349 Private slot to handle the source button press to show the selected file. |
|
350 """ |
|
351 s = self.currentStack[self.stackComboBox.currentIndex()] |
|
352 self.emit(SIGNAL('sourceFile'), s[0], int(s[1])) |
|
353 |
|
354 def __frameSelected(self, frmnr): |
|
355 """ |
|
356 Private slot to handle the selection of a new stack frame number. |
|
357 |
|
358 @param frmnr frame number (0 is the current frame) (int) |
|
359 """ |
|
360 self.framenr = frmnr |
|
361 self.debugServer.remoteClientVariables(0, self.localsFilter, frmnr) |
|
362 |
|
363 def __setGlobalsFilter(self): |
|
364 """ |
|
365 Private slot to set the global variable filter |
|
366 """ |
|
367 filter = self.globalsFilterEdit.text() |
|
368 self.debugServer.remoteClientSetFilter(1, filter) |
|
369 if self.currentStack: |
|
370 self.debugServer.remoteClientVariables(2, self.globalsFilter) |
|
371 |
|
372 def __setLocalsFilter(self): |
|
373 """ |
|
374 Private slot to set the local variable filter |
|
375 """ |
|
376 filter = self.localsFilterEdit.text() |
|
377 self.debugServer.remoteClientSetFilter(0, filter) |
|
378 if self.currentStack: |
|
379 self.debugServer.remoteClientVariables(0, self.localsFilter, self.framenr) |
|
380 |
|
381 def handleDebuggingStarted(self): |
|
382 """ |
|
383 Public slot to handle the start of a debugging session. |
|
384 |
|
385 This slot sets the variables filter expressions. |
|
386 """ |
|
387 self.__setGlobalsFilter() |
|
388 self.__setLocalsFilter() |
|
389 self.showVariablesTab(False) |
|
390 |
|
391 def currentWidget(self): |
|
392 """ |
|
393 Public method to get a reference to the current widget. |
|
394 |
|
395 @return reference to the current widget (QWidget) |
|
396 """ |
|
397 return self.__tabWidget.currentWidget() |
|
398 |
|
399 def showThreadList(self, currentID, threadList): |
|
400 """ |
|
401 Public method to show the thread list. |
|
402 |
|
403 @param currentID id of the current thread (integer) |
|
404 @param threadList list of dictionaries containing the thread data |
|
405 """ |
|
406 citm = None |
|
407 |
|
408 self.__threadList.clear() |
|
409 for thread in threadList: |
|
410 if thread['broken']: |
|
411 state = self.trUtf8("waiting at breakpoint") |
|
412 else: |
|
413 state = self.trUtf8("running") |
|
414 itm = QTreeWidgetItem(self.__threadList, |
|
415 ["%d" % thread['id'], thread['name'], state]) |
|
416 if thread['id'] == currentID: |
|
417 citm = itm |
|
418 |
|
419 self.__threadList.header().resizeSections(QHeaderView.ResizeToContents) |
|
420 self.__threadList.header().setStretchLastSection(True) |
|
421 |
|
422 if citm: |
|
423 self.__doThreadListUpdate = False |
|
424 self.__threadList.setCurrentItem(citm) |
|
425 self.__doThreadListUpdate = True |
|
426 |
|
427 def __threadSelected(self, current, previous): |
|
428 """ |
|
429 Private slot to handle the selection of a thread in the thread list. |
|
430 |
|
431 @param current reference to the new current item (QTreeWidgetItem) |
|
432 @param previous reference to the previous current item (QTreeWidgetItem) |
|
433 """ |
|
434 if current is not None and self.__doThreadListUpdate: |
|
435 tid, ok = current.text(0).toLong() |
|
436 if ok: |
|
437 self.debugServer.remoteSetThread(tid) |