1# -*- coding: utf-8 -*- 2 3# Copyright (c) 2002 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> 4# 5 6""" 7Module implementing a widget containing various debug related views. 8 9The 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 24import os 25 26from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QCoreApplication 27from PyQt5.QtWidgets import ( 28 QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QSizePolicy, QPushButton, 29 QComboBox, QLabel, QTreeWidget, QTreeWidgetItem, QHeaderView, QSplitter 30) 31 32import UI.PixmapCache 33import Preferences 34 35from E5Gui.E5TabWidget import E5TabWidget 36 37 38class 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 = E5TabWidget() 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) 948