src/eric7/Debugger/DebugViewer.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2022 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>selector showing all connected debugger backends with associated
12 threads</li>
13 <li>variables viewer for global variables for the selected debug client</li>
14 <li>variables viewer for local variables for the selected debug client</li>
15 <li>call stack viewer for the selected debug client</li>
16 <li>call trace viewer</li>
17 <li>viewer for breakpoints</li>
18 <li>viewer for watch expressions</li>
19 <li>viewer for exceptions</li>
20 <li>viewer for a code disassembly for an exception<li>
21 </ul>
22 """
23
24 import os
25
26 from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QCoreApplication
27 from PyQt6.QtWidgets import (
28 QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QSizePolicy, QPushButton,
29 QComboBox, QLabel, QTreeWidget, QTreeWidgetItem, QHeaderView, QSplitter
30 )
31
32 import UI.PixmapCache
33 import Preferences
34
35 from EricWidgets.EricTabWidget import EricTabWidget
36
37
38 class DebugViewer(QWidget):
39 """
40 Class implementing a widget containing various debug related views.
41
42 The individual tabs contain the interpreter shell (optional),
43 the filesystem browser (optional), the two variables viewers
44 (global and local), a breakpoint viewer, a watch expression viewer and
45 the exception logger. Additionally a list of all threads is shown.
46
47 @signal sourceFile(string, int) emitted to open a source file at a line
48 @signal preferencesChanged() emitted to react on changed preferences
49 """
50 sourceFile = pyqtSignal(str, int)
51 preferencesChanged = pyqtSignal()
52
53 ThreadIdRole = Qt.ItemDataRole.UserRole + 1
54 DebuggerStateRole = Qt.ItemDataRole.UserRole + 2
55
56 # Map debug state to icon name
57 StateIcon = {
58 "broken": "break",
59 "exception": "exceptions",
60 "running": "mediaPlaybackStart",
61 "syntax": "syntaxError22",
62 }
63
64 # Map debug state to user message
65 StateMessage = {
66 "broken": QCoreApplication.translate(
67 "DebugViewer", "waiting at breakpoint"),
68 "exception": QCoreApplication.translate(
69 "DebugViewer", "waiting at exception"),
70 "running": QCoreApplication.translate(
71 "DebugViewer", "running"),
72 "syntax": QCoreApplication.translate(
73 "DebugViewer", "syntax error"),
74 }
75
76 def __init__(self, debugServer, parent=None):
77 """
78 Constructor
79
80 @param debugServer reference to the debug server object
81 @type DebugServer
82 @param parent parent widget
83 @type QWidget
84 """
85 super().__init__(parent)
86
87 self.debugServer = debugServer
88 self.debugUI = None
89
90 self.setWindowIcon(UI.PixmapCache.getIcon("eric"))
91
92 self.__mainLayout = QVBoxLayout()
93 self.__mainLayout.setContentsMargins(0, 3, 0, 0)
94 self.setLayout(self.__mainLayout)
95
96 self.__mainSplitter = QSplitter(Qt.Orientation.Vertical, self)
97 self.__mainLayout.addWidget(self.__mainSplitter)
98
99 # add the viewer showing the connected debug backends
100 self.__debuggersWidget = QWidget()
101 self.__debuggersLayout = QVBoxLayout(self.__debuggersWidget)
102 self.__debuggersLayout.setContentsMargins(0, 0, 0, 0)
103 self.__debuggersLayout.addWidget(
104 QLabel(self.tr("Debuggers and Threads:")))
105 self.__debuggersList = QTreeWidget()
106 self.__debuggersList.setHeaderLabels(
107 [self.tr("ID"), self.tr("State"), ""])
108 self.__debuggersList.header().setStretchLastSection(True)
109 self.__debuggersList.setSortingEnabled(True)
110 self.__debuggersList.setRootIsDecorated(True)
111 self.__debuggersList.setAlternatingRowColors(True)
112 self.__debuggersLayout.addWidget(self.__debuggersList)
113 self.__mainSplitter.addWidget(self.__debuggersWidget)
114
115 self.__debuggersList.currentItemChanged.connect(
116 self.__debuggerSelected)
117
118 # add the tab widget containing various debug related views
119 self.__tabWidget = EricTabWidget()
120 self.__mainSplitter.addWidget(self.__tabWidget)
121
122 from .VariablesViewer import VariablesViewer
123 # add the global variables viewer
124 self.glvWidget = QWidget()
125 self.glvWidgetVLayout = QVBoxLayout(self.glvWidget)
126 self.glvWidgetVLayout.setContentsMargins(0, 0, 0, 0)
127 self.glvWidgetVLayout.setSpacing(3)
128 self.glvWidget.setLayout(self.glvWidgetVLayout)
129
130 self.globalsViewer = VariablesViewer(self, True, self.glvWidget)
131 self.glvWidgetVLayout.addWidget(self.globalsViewer)
132
133 self.glvWidgetHLayout = QHBoxLayout()
134 self.glvWidgetHLayout.setContentsMargins(3, 3, 3, 3)
135
136 self.globalsFilterEdit = QLineEdit(self.glvWidget)
137 self.globalsFilterEdit.setSizePolicy(
138 QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
139 self.glvWidgetHLayout.addWidget(self.globalsFilterEdit)
140 self.globalsFilterEdit.setToolTip(
141 self.tr("Enter regular expression patterns separated by ';'"
142 " to define variable filters. "))
143 self.globalsFilterEdit.setWhatsThis(
144 self.tr("Enter regular expression patterns separated by ';'"
145 " to define variable filters. All variables and"
146 " class attributes matched by one of the expressions"
147 " are not shown in the list above."))
148
149 self.setGlobalsFilterButton = QPushButton(
150 self.tr('Set'), self.glvWidget)
151 self.glvWidgetHLayout.addWidget(self.setGlobalsFilterButton)
152 self.glvWidgetVLayout.addLayout(self.glvWidgetHLayout)
153
154 index = self.__tabWidget.addTab(
155 self.glvWidget,
156 UI.PixmapCache.getIcon("globalVariables"), '')
157 self.__tabWidget.setTabToolTip(
158 index,
159 self.tr("Shows the list of global variables and their values."))
160
161 self.setGlobalsFilterButton.clicked.connect(
162 self.setGlobalsFilter)
163 self.globalsFilterEdit.returnPressed.connect(self.setGlobalsFilter)
164
165 # add the local variables viewer
166 self.lvWidget = QWidget()
167 self.lvWidgetVLayout = QVBoxLayout(self.lvWidget)
168 self.lvWidgetVLayout.setContentsMargins(0, 0, 0, 0)
169 self.lvWidgetVLayout.setSpacing(3)
170 self.lvWidget.setLayout(self.lvWidgetVLayout)
171
172 self.lvWidgetHLayout1 = QHBoxLayout()
173 self.lvWidgetHLayout1.setContentsMargins(3, 3, 3, 3)
174
175 self.stackComboBox = QComboBox(self.lvWidget)
176 self.stackComboBox.setSizePolicy(
177 QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
178 self.lvWidgetHLayout1.addWidget(self.stackComboBox)
179
180 self.sourceButton = QPushButton(self.tr('Source'), self.lvWidget)
181 self.lvWidgetHLayout1.addWidget(self.sourceButton)
182 self.sourceButton.setEnabled(False)
183 self.lvWidgetVLayout.addLayout(self.lvWidgetHLayout1)
184
185 self.localsViewer = VariablesViewer(self, False, self.lvWidget)
186 self.lvWidgetVLayout.addWidget(self.localsViewer)
187
188 self.lvWidgetHLayout2 = QHBoxLayout()
189 self.lvWidgetHLayout2.setContentsMargins(3, 3, 3, 3)
190
191 self.localsFilterEdit = QLineEdit(self.lvWidget)
192 self.localsFilterEdit.setSizePolicy(
193 QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
194 self.lvWidgetHLayout2.addWidget(self.localsFilterEdit)
195 self.localsFilterEdit.setToolTip(
196 self.tr(
197 "Enter regular expression patterns separated by ';' to define "
198 "variable filters. "))
199 self.localsFilterEdit.setWhatsThis(
200 self.tr(
201 "Enter regular expression patterns separated by ';' to define "
202 "variable filters. All variables and class attributes matched"
203 " by one of the expressions are not shown in the list above."))
204
205 self.setLocalsFilterButton = QPushButton(
206 self.tr('Set'), self.lvWidget)
207 self.lvWidgetHLayout2.addWidget(self.setLocalsFilterButton)
208 self.lvWidgetVLayout.addLayout(self.lvWidgetHLayout2)
209
210 index = self.__tabWidget.addTab(
211 self.lvWidget,
212 UI.PixmapCache.getIcon("localVariables"), '')
213 self.__tabWidget.setTabToolTip(
214 index,
215 self.tr("Shows the list of local variables and their values."))
216
217 self.sourceButton.clicked.connect(self.__showSource)
218 self.stackComboBox.currentIndexChanged[int].connect(
219 self.__frameSelected)
220 self.setLocalsFilterButton.clicked.connect(self.setLocalsFilter)
221 self.localsFilterEdit.returnPressed.connect(self.setLocalsFilter)
222
223 self.preferencesChanged.connect(self.handlePreferencesChanged)
224 self.preferencesChanged.connect(self.globalsViewer.preferencesChanged)
225 self.preferencesChanged.connect(self.localsViewer.preferencesChanged)
226
227 from .CallStackViewer import CallStackViewer
228 # add the call stack viewer
229 self.callStackViewer = CallStackViewer(self.debugServer)
230 index = self.__tabWidget.addTab(
231 self.callStackViewer,
232 UI.PixmapCache.getIcon("callStack"), "")
233 self.__tabWidget.setTabToolTip(
234 index,
235 self.tr("Shows the current call stack."))
236 self.callStackViewer.sourceFile.connect(self.sourceFile)
237 self.callStackViewer.frameSelected.connect(
238 self.__callStackFrameSelected)
239
240 from .CallTraceViewer import CallTraceViewer
241 # add the call trace viewer
242 self.callTraceViewer = CallTraceViewer(self.debugServer, self)
243 index = self.__tabWidget.addTab(
244 self.callTraceViewer,
245 UI.PixmapCache.getIcon("callTrace"), "")
246 self.__tabWidget.setTabToolTip(
247 index,
248 self.tr("Shows a trace of the program flow."))
249 self.callTraceViewer.sourceFile.connect(self.sourceFile)
250
251 from .BreakPointViewer import BreakPointViewer
252 # add the breakpoint viewer
253 self.breakpointViewer = BreakPointViewer()
254 self.breakpointViewer.setModel(self.debugServer.getBreakPointModel())
255 index = self.__tabWidget.addTab(
256 self.breakpointViewer,
257 UI.PixmapCache.getIcon("breakpoints"), '')
258 self.__tabWidget.setTabToolTip(
259 index,
260 self.tr("Shows a list of defined breakpoints."))
261 self.breakpointViewer.sourceFile.connect(self.sourceFile)
262
263 from .WatchPointViewer import WatchPointViewer
264 # add the watch expression viewer
265 self.watchpointViewer = WatchPointViewer()
266 self.watchpointViewer.setModel(self.debugServer.getWatchPointModel())
267 index = self.__tabWidget.addTab(
268 self.watchpointViewer,
269 UI.PixmapCache.getIcon("watchpoints"), '')
270 self.__tabWidget.setTabToolTip(
271 index,
272 self.tr("Shows a list of defined watchpoints."))
273
274 from .ExceptionLogger import ExceptionLogger
275 # add the exception logger
276 self.exceptionLogger = ExceptionLogger()
277 index = self.__tabWidget.addTab(
278 self.exceptionLogger,
279 UI.PixmapCache.getIcon("exceptions"), '')
280 self.__tabWidget.setTabToolTip(
281 index,
282 self.tr("Shows a list of raised exceptions."))
283
284 from UI.PythonDisViewer import PythonDisViewer, PythonDisViewerModes
285 # add the Python disassembly viewer
286 self.disassemblyViewer = PythonDisViewer(
287 None, mode=PythonDisViewerModes.TRACEBACK)
288 index = self.__tabWidget.addTab(
289 self.disassemblyViewer,
290 UI.PixmapCache.getIcon("disassembly"), '')
291 self.__tabWidget.setTabToolTip(
292 index,
293 self.tr("Shows a code disassembly in case of an exception."))
294
295 self.__tabWidget.setCurrentWidget(self.glvWidget)
296
297 self.__doDebuggersListUpdate = True
298
299 self.__mainSplitter.setSizes([100, 700])
300
301 self.currentStack = None
302 self.framenr = 0
303
304 self.__autoViewSource = Preferences.getDebugger("AutoViewSourceCode")
305 self.sourceButton.setVisible(not self.__autoViewSource)
306
307 # connect some debug server signals
308 self.debugServer.clientStack.connect(
309 self.handleClientStack)
310 self.debugServer.clientThreadList.connect(
311 self.__addThreadList)
312 self.debugServer.clientDebuggerId.connect(
313 self.__clientDebuggerId)
314 self.debugServer.passiveDebugStarted.connect(
315 self.handleDebuggingStarted)
316 self.debugServer.clientLine.connect(
317 self.__clientLine)
318 self.debugServer.clientSyntaxError.connect(
319 self.__clientSyntaxError)
320 self.debugServer.clientException.connect(
321 self.__clientException)
322 self.debugServer.clientExit.connect(
323 self.__clientExit)
324 self.debugServer.clientDisconnected.connect(
325 self.__removeDebugger)
326
327 self.debugServer.clientException.connect(
328 self.exceptionLogger.addException)
329 self.debugServer.passiveDebugStarted.connect(
330 self.exceptionLogger.debuggingStarted)
331
332 self.debugServer.clientLine.connect(
333 self.breakpointViewer.highlightBreakpoint)
334
335 def handlePreferencesChanged(self):
336 """
337 Public slot to handle the preferencesChanged signal.
338 """
339 self.__autoViewSource = Preferences.getDebugger("AutoViewSourceCode")
340 self.sourceButton.setVisible(not self.__autoViewSource)
341
342 def setDebugger(self, debugUI):
343 """
344 Public method to set a reference to the Debug UI.
345
346 @param debugUI reference to the DebugUI object
347 @type DebugUI
348 """
349 self.debugUI = debugUI
350 self.callStackViewer.setDebugger(debugUI)
351
352 # connect some debugUI signals
353 self.debugUI.clientStack.connect(self.handleClientStack)
354 self.debugUI.debuggingStarted.connect(
355 self.exceptionLogger.debuggingStarted)
356 self.debugUI.debuggingStarted.connect(
357 self.handleDebuggingStarted)
358
359 def handleResetUI(self, fullReset):
360 """
361 Public method to reset the viewer.
362
363 @param fullReset flag indicating a full reset is required
364 @type bool
365 """
366 self.globalsViewer.handleResetUI()
367 self.localsViewer.handleResetUI()
368 self.setGlobalsFilter()
369 self.setLocalsFilter()
370 self.sourceButton.setEnabled(False)
371 self.currentStack = None
372 self.stackComboBox.clear()
373 self.__tabWidget.setCurrentWidget(self.glvWidget)
374 self.breakpointViewer.handleResetUI()
375 if fullReset:
376 self.__debuggersList.clear()
377 self.disassemblyViewer.clear()
378
379 def initCallStackViewer(self, projectMode):
380 """
381 Public method to initialize the call stack viewer.
382
383 @param projectMode flag indicating to enable the project mode
384 @type bool
385 """
386 self.callStackViewer.clear()
387 self.callStackViewer.setProjectMode(projectMode)
388
389 def isCallTraceEnabled(self):
390 """
391 Public method to get the state of the call trace function.
392
393 @return flag indicating the state of the call trace function
394 @rtype bool
395 """
396 return self.callTraceViewer.isCallTraceEnabled()
397
398 def clearCallTrace(self):
399 """
400 Public method to clear the recorded call trace.
401 """
402 self.callTraceViewer.clear()
403
404 def setCallTraceToProjectMode(self, enabled):
405 """
406 Public slot to set the call trace viewer to project mode.
407
408 In project mode the call trace info is shown with project relative
409 path names.
410
411 @param enabled flag indicating to enable the project mode
412 @type bool
413 """
414 self.callTraceViewer.setProjectMode(enabled)
415
416 def showVariables(self, vlist, showGlobals):
417 """
418 Public method to show the variables in the respective window.
419
420 @param vlist list of variables to display
421 @type list
422 @param showGlobals flag indicating global/local state
423 @type bool
424 """
425 if showGlobals:
426 self.globalsViewer.showVariables(vlist, self.framenr)
427 else:
428 self.localsViewer.showVariables(vlist, self.framenr)
429
430 def showVariable(self, vlist, showGlobals):
431 """
432 Public method to show the variables in the respective window.
433
434 @param vlist list of variables to display
435 @type list
436 @param showGlobals flag indicating global/local state
437 @type bool
438 """
439 if showGlobals:
440 self.globalsViewer.showVariable(vlist)
441 else:
442 self.localsViewer.showVariable(vlist)
443
444 def showVariablesTab(self, showGlobals):
445 """
446 Public method to make a variables tab visible.
447
448 @param showGlobals flag indicating global/local state
449 @type bool
450 """
451 if showGlobals:
452 self.__tabWidget.setCurrentWidget(self.glvWidget)
453 else:
454 self.__tabWidget.setCurrentWidget(self.lvWidget)
455
456 def handleClientStack(self, stack, debuggerId):
457 """
458 Public slot to show the call stack of the program being debugged.
459
460 @param stack list of tuples with call stack data (file name,
461 line number, function name, formatted argument/values list)
462 @type list of tuples of (str, str, str, str)
463 @param debuggerId ID of the debugger backend
464 @type str
465 """
466 if debuggerId == self.getSelectedDebuggerId():
467 block = self.stackComboBox.blockSignals(True)
468 self.framenr = 0
469 self.stackComboBox.clear()
470 self.currentStack = stack
471 self.sourceButton.setEnabled(len(stack) > 0)
472 for s in stack:
473 # just show base filename to make it readable
474 s = (os.path.basename(s[0]), s[1], s[2])
475 self.stackComboBox.addItem('{0}:{1}:{2}'.format(*s))
476 self.stackComboBox.blockSignals(block)
477
478 def __clientLine(self, fn, line, debuggerId, threadName):
479 """
480 Private method to handle a change to the current line.
481
482 @param fn filename
483 @type str
484 @param line linenumber
485 @type int
486 @param debuggerId ID of the debugger backend
487 @type str
488 @param threadName name of the thread signaling the event
489 @type str
490 """
491 self.__setDebuggerIconAndState(debuggerId, "broken")
492 self.__setThreadIconAndState(debuggerId, threadName, "broken")
493 if debuggerId != self.getSelectedDebuggerId():
494 self.__setCurrentDebugger(debuggerId)
495
496 @pyqtSlot(str, int, str, bool, str)
497 def __clientExit(self, program, status, message, quiet, debuggerId):
498 """
499 Private method to handle the debugged program terminating.
500
501 @param program name of the exited program
502 @type str
503 @param status exit code of the debugged program
504 @type int
505 @param message exit message of the debugged program
506 @type str
507 @param quiet flag indicating to suppress exit info display
508 @type bool
509 @param debuggerId ID of the debugger backend
510 @type str
511 """
512 if not self.isOnlyDebugger():
513 if debuggerId == self.getSelectedDebuggerId():
514 # the current client has exited
515 self.globalsViewer.handleResetUI()
516 self.localsViewer.handleResetUI()
517 self.setGlobalsFilter()
518 self.setLocalsFilter()
519 self.sourceButton.setEnabled(False)
520 self.currentStack = None
521 self.stackComboBox.clear()
522
523 self.__removeDebugger(debuggerId)
524
525 def __clientSyntaxError(self, message, filename, lineNo, characterNo,
526 debuggerId, threadName):
527 """
528 Private method to handle a syntax error in the debugged program.
529
530 @param message message of the syntax error
531 @type str
532 @param filename translated filename of the syntax error position
533 @type str
534 @param lineNo line number of the syntax error position
535 @type int
536 @param characterNo character number of the syntax error position
537 @type int
538 @param debuggerId ID of the debugger backend
539 @type str
540 @param threadName name of the thread signaling the event
541 @type str
542 """
543 self.__setDebuggerIconAndState(debuggerId, "syntax")
544 self.__setThreadIconAndState(debuggerId, threadName, "syntax")
545
546 def __clientException(self, exceptionType, exceptionMessage, stackTrace,
547 debuggerId, threadName):
548 """
549 Private method to handle an exception of the debugged program.
550
551 @param exceptionType type of exception raised
552 @type str
553 @param exceptionMessage message given by the exception
554 @type (str
555 @param stackTrace list of stack entries
556 @type list of str
557 @param debuggerId ID of the debugger backend
558 @type str
559 @param threadName name of the thread signaling the event
560 @type str
561 """
562 self.__setDebuggerIconAndState(debuggerId, "exception")
563 self.__setThreadIconAndState(debuggerId, threadName, "exception")
564
565 def setVariablesFilter(self, globalsFilter, localsFilter):
566 """
567 Public slot to set the local variables filter.
568
569 @param globalsFilter filter list for global variable types
570 @type list of str
571 @param localsFilter filter list for local variable types
572 @type list of str
573 """
574 self.__globalsFilter = globalsFilter
575 self.__localsFilter = localsFilter
576
577 def __showSource(self):
578 """
579 Private slot to handle the source button press to show the selected
580 file.
581 """
582 index = self.stackComboBox.currentIndex()
583 if index > -1 and self.currentStack:
584 s = self.currentStack[index]
585 self.sourceFile.emit(s[0], int(s[1]))
586
587 def __frameSelected(self, frmnr):
588 """
589 Private slot to handle the selection of a new stack frame number.
590
591 @param frmnr frame number (0 is the current frame)
592 @type int
593 """
594 if frmnr >= 0:
595 self.framenr = frmnr
596 if self.debugServer.isDebugging():
597 self.debugServer.remoteClientVariables(
598 self.getSelectedDebuggerId(), 0, self.__localsFilter,
599 frmnr)
600
601 if self.__autoViewSource:
602 self.__showSource()
603
604 def setGlobalsFilter(self):
605 """
606 Public slot to set the global variable filter.
607 """
608 if self.debugServer.isDebugging():
609 filterStr = self.globalsFilterEdit.text()
610 self.debugServer.remoteClientSetFilter(
611 self.getSelectedDebuggerId(), 1, filterStr)
612 self.debugServer.remoteClientVariables(
613 self.getSelectedDebuggerId(), 2, self.__globalsFilter)
614
615 def setLocalsFilter(self):
616 """
617 Public slot to set the local variable filter.
618 """
619 if self.debugServer.isDebugging():
620 filterStr = self.localsFilterEdit.text()
621 self.debugServer.remoteClientSetFilter(
622 self.getSelectedDebuggerId(), 0, filterStr)
623 if self.currentStack:
624 self.debugServer.remoteClientVariables(
625 self.getSelectedDebuggerId(), 0, self.__localsFilter,
626 self.framenr)
627
628 def handleDebuggingStarted(self):
629 """
630 Public slot to handle the start of a debugging session.
631
632 This slot sets the variables filter expressions.
633 """
634 self.setGlobalsFilter()
635 self.setLocalsFilter()
636 self.showVariablesTab(False)
637
638 self.disassemblyViewer.clear()
639
640 def currentWidget(self):
641 """
642 Public method to get a reference to the current widget.
643
644 @return reference to the current widget
645 @rtype QWidget
646 """
647 return self.__tabWidget.currentWidget()
648
649 def setCurrentWidget(self, widget):
650 """
651 Public slot to set the current page based on the given widget.
652
653 @param widget reference to the widget
654 @type QWidget
655 """
656 self.__tabWidget.setCurrentWidget(widget)
657
658 def __callStackFrameSelected(self, frameNo):
659 """
660 Private slot to handle the selection of a call stack entry of the
661 call stack viewer.
662
663 @param frameNo frame number (index) of the selected entry
664 @type int
665 """
666 if frameNo >= 0:
667 self.stackComboBox.setCurrentIndex(frameNo)
668
669 def __debuggerSelected(self, current, previous):
670 """
671 Private slot to handle the selection of a debugger backend in the
672 debuggers list.
673
674 @param current reference to the new current item
675 @type QTreeWidgetItem
676 @param previous reference to the previous current item
677 @type QTreeWidgetItem
678 """
679 if current is not None and self.__doDebuggersListUpdate:
680 if current.parent() is None:
681 # it is a debugger item
682 debuggerId = current.text(0)
683 self.globalsViewer.handleResetUI()
684 self.localsViewer.handleResetUI()
685 self.currentStack = None
686 self.stackComboBox.clear()
687 self.callStackViewer.clear()
688
689 self.debugServer.remoteSetThread(debuggerId, -1)
690 self.__showSource()
691 else:
692 # it is a thread item
693 tid = current.data(0, self.ThreadIdRole)
694 self.debugServer.remoteSetThread(
695 self.getSelectedDebuggerId(), tid)
696
697 def __clientDebuggerId(self, debuggerId):
698 """
699 Private slot to receive the ID of a newly connected debugger backend.
700
701 @param debuggerId ID of a newly connected debugger backend
702 @type str
703 """
704 itm = QTreeWidgetItem(self.__debuggersList, [debuggerId])
705 if self.__debuggersList.topLevelItemCount() > 1:
706 self.debugUI.showNotification(
707 self.tr("<p>Debugger with ID <b>{0}</b> has been connected."
708 "</p>")
709 .format(debuggerId))
710
711 self.__debuggersList.header().resizeSections(
712 QHeaderView.ResizeMode.ResizeToContents)
713
714 if self.__debuggersList.topLevelItemCount() == 1:
715 # it is the only item, select it as the current one
716 self.__debuggersList.setCurrentItem(itm)
717
718 def __setCurrentDebugger(self, debuggerId):
719 """
720 Private method to set the current debugger based on the given ID.
721
722 @param debuggerId ID of the debugger to set as current debugger
723 @type str
724 """
725 debuggerItems = self.__debuggersList.findItems(
726 debuggerId, Qt.MatchFlag.MatchExactly)
727 if debuggerItems:
728 debuggerItem = debuggerItems[0]
729 currentItem = self.__debuggersList.currentItem()
730 if currentItem is debuggerItem:
731 # nothing to do
732 return
733
734 if currentItem:
735 currentParent = currentItem.parent()
736 else:
737 currentParent = None
738 if currentParent is None:
739 # current is a debugger item
740 self.__debuggersList.setCurrentItem(debuggerItem)
741 elif currentParent is debuggerItem:
742 # nothing to do
743 return
744 else:
745 self.__debuggersList.setCurrentItem(debuggerItem)
746
747 def isOnlyDebugger(self):
748 """
749 Public method to test, if only one debugger is connected.
750
751 @return flag indicating that only one debugger is connected
752 @rtype bool
753 """
754 return self.__debuggersList.topLevelItemCount() == 1
755
756 def getSelectedDebuggerId(self):
757 """
758 Public method to get the currently selected debugger ID.
759
760 @return selected debugger ID
761 @rtype str
762 """
763 itm = self.__debuggersList.currentItem()
764 if itm:
765 if itm.parent() is None:
766 # it is a debugger item
767 return itm.text(0)
768 else:
769 # it is a thread item
770 return itm.parent().text(0)
771 else:
772 return ""
773
774 def getSelectedDebuggerState(self):
775 """
776 Public method to get the currently selected debugger's state.
777
778 @return selected debugger's state (broken, exception, running)
779 @rtype str
780 """
781 itm = self.__debuggersList.currentItem()
782 if itm:
783 if itm.parent() is None:
784 # it is a debugger item
785 return itm.data(0, self.DebuggerStateRole)
786 else:
787 # it is a thread item
788 return itm.parent().data(0, self.DebuggerStateRole)
789 else:
790 return ""
791
792 def __setDebuggerIconAndState(self, debuggerId, state):
793 """
794 Private method to set the icon for a specific debugger ID.
795
796 @param debuggerId ID of the debugger backend (empty ID means the
797 currently selected one)
798 @type str
799 @param state state of the debugger (broken, exception, running)
800 @type str
801 """
802 debuggerItem = None
803 if debuggerId:
804 foundItems = self.__debuggersList.findItems(
805 debuggerId, Qt.MatchFlag.MatchExactly)
806 if foundItems:
807 debuggerItem = foundItems[0]
808 if debuggerItem is None:
809 debuggerItem = self.__debuggersList.currentItem()
810 if debuggerItem is not None:
811 try:
812 iconName = DebugViewer.StateIcon[state]
813 except KeyError:
814 iconName = "question"
815 try:
816 stateText = DebugViewer.StateMessage[state]
817 except KeyError:
818 stateText = self.tr("unknown state ({0})").format(state)
819 debuggerItem.setIcon(0, UI.PixmapCache.getIcon(iconName))
820 debuggerItem.setData(0, self.DebuggerStateRole, state)
821 debuggerItem.setText(1, stateText)
822
823 self.__debuggersList.header().resizeSections(
824 QHeaderView.ResizeMode.ResizeToContents)
825
826 def __removeDebugger(self, debuggerId):
827 """
828 Private method to remove a debugger given its ID.
829
830 @param debuggerId ID of the debugger to be removed from the list
831 @type str
832 """
833 foundItems = self.__debuggersList.findItems(
834 debuggerId, Qt.MatchFlag.MatchExactly)
835 if foundItems:
836 index = self.__debuggersList.indexOfTopLevelItem(foundItems[0])
837 itm = self.__debuggersList.takeTopLevelItem(index)
838 # __IGNORE_WARNING__
839 del itm
840
841 def __addThreadList(self, currentID, threadList, debuggerId):
842 """
843 Private method to add the list of threads to a debugger entry.
844
845 @param currentID id of the current thread
846 @type int
847 @param threadList list of dictionaries containing the thread data
848 @type list of dict
849 @param debuggerId ID of the debugger backend
850 @type str
851 """
852 debugStatus = -1 # i.e. running
853
854 debuggerItems = self.__debuggersList.findItems(
855 debuggerId, Qt.MatchFlag.MatchExactly)
856 if debuggerItems:
857 debuggerItem = debuggerItems[0]
858
859 currentItem = self.__debuggersList.currentItem()
860 if currentItem.parent() is debuggerItem:
861 currentChild = currentItem.text(0)
862 else:
863 currentChild = ""
864 self.__doDebuggersListUpdate = False
865 debuggerItem.takeChildren()
866 for thread in threadList:
867 if thread.get('except', False):
868 stateText = DebugViewer.StateMessage["exception"]
869 iconName = DebugViewer.StateIcon["exception"]
870 debugStatus = 1
871 elif thread['broken']:
872 stateText = DebugViewer.StateMessage["broken"]
873 iconName = DebugViewer.StateIcon["broken"]
874 if debugStatus < 1:
875 debugStatus = 0
876 else:
877 stateText = DebugViewer.StateMessage["running"]
878 iconName = DebugViewer.StateIcon["running"]
879 itm = QTreeWidgetItem(debuggerItem,
880 [thread['name'], stateText])
881 itm.setData(0, self.ThreadIdRole, thread['id'])
882 itm.setIcon(0, UI.PixmapCache.getIcon(iconName))
883 if currentChild == thread['name']:
884 self.__debuggersList.setCurrentItem(itm)
885 if thread['id'] == currentID:
886 font = debuggerItem.font(0)
887 font.setItalic(True)
888 itm.setFont(0, font)
889
890 debuggerItem.setExpanded(debuggerItem.childCount() > 0)
891
892 self.__debuggersList.header().resizeSections(
893 QHeaderView.ResizeMode.ResizeToContents)
894 self.__debuggersList.header().setStretchLastSection(True)
895 self.__doDebuggersListUpdate = True
896
897 if debugStatus == -1:
898 debuggerState = "running"
899 elif debugStatus == 0:
900 debuggerState = "broken"
901 else:
902 debuggerState = "exception"
903 self.__setDebuggerIconAndState(debuggerId, debuggerState)
904
905 def __setThreadIconAndState(self, debuggerId, threadName, state):
906 """
907 Private method to set the icon for a specific thread name and
908 debugger ID.
909
910 @param debuggerId ID of the debugger backend (empty ID means the
911 currently selected one)
912 @type str
913 @param threadName name of the thread signaling the event
914 @type str
915 @param state state of the debugger (broken, exception, running)
916 @type str
917 """
918 debuggerItem = None
919 if debuggerId:
920 foundItems = self.__debuggersList.findItems(
921 debuggerId, Qt.MatchFlag.MatchExactly)
922 if foundItems:
923 debuggerItem = foundItems[0]
924 if debuggerItem is None:
925 debuggerItem = self.__debuggersList.currentItem()
926 if debuggerItem is not None:
927 for index in range(debuggerItem.childCount()):
928 childItem = debuggerItem.child(index)
929 if childItem.text(0) == threadName:
930 break
931 else:
932 childItem = None
933
934 if childItem is not None:
935 try:
936 iconName = DebugViewer.StateIcon[state]
937 except KeyError:
938 iconName = "question"
939 try:
940 stateText = DebugViewer.StateMessage[state]
941 except KeyError:
942 stateText = self.tr("unknown state ({0})").format(state)
943 childItem.setIcon(0, UI.PixmapCache.getIcon(iconName))
944 childItem.setText(1, stateText)
945
946 self.__debuggersList.header().resizeSections(
947 QHeaderView.ResizeMode.ResizeToContents)

eric ide

mercurial