diff -r 866adc8c315b -r 0acf98cd089a eric6/Debugger/DebugViewer.py --- a/eric6/Debugger/DebugViewer.py Sun Jan 17 13:53:08 2021 +0100 +++ b/eric6/Debugger/DebugViewer.py Mon Feb 01 10:38:16 2021 +0100 @@ -8,24 +8,25 @@ The views avaliable are: <ul> - <li>variables viewer for global variables</li> - <li>variables viewer for local variables</li> + <li>selector showing all connected debugger backends with associated + threads</li> + <li>variables viewer for global variables for the selected debug client</li> + <li>variables viewer for local variables for the selected debug client</li> + <li>call stack viewer for the selected debug client</li> <li>call trace viewer</li> <li>viewer for breakpoints</li> <li>viewer for watch expressions</li> <li>viewer for exceptions</li> - <li>viewer for threads</li> - <li>a file browser (optional)</li> - <li>an interpreter shell (optional)</li> + <li>viewer for a code disassembly for an exception<li> </ul> """ import os -from PyQt5.QtCore import pyqtSignal +from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QCoreApplication from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QSizePolicy, QPushButton, - QComboBox, QLabel, QTreeWidget, QTreeWidgetItem, QHeaderView + QComboBox, QLabel, QTreeWidget, QTreeWidgetItem, QHeaderView, QSplitter ) import UI.PixmapCache @@ -49,12 +50,37 @@ sourceFile = pyqtSignal(str, int) preferencesChanged = pyqtSignal() + ThreadIdRole = Qt.UserRole + 1 + DebuggerStateRole = Qt.UserRole + 2 + + # Map debug state to icon name + StateIcon = { + "broken": "break", + "exception": "exceptions", + "running": "mediaPlaybackStart", + "syntax": "syntaxError22", + } + + # Map debug state to user message + StateMessage = { + "broken": QCoreApplication.translate( + "DebugViewer", "waiting at breakpoint"), + "exception": QCoreApplication.translate( + "DebugViewer", "waiting at exception"), + "running": QCoreApplication.translate( + "DebugViewer", "running"), + "syntax": QCoreApplication.translate( + "DebugViewer", "syntax error"), + } + def __init__(self, debugServer, parent=None): """ Constructor - @param debugServer reference to the debug server object (DebugServer) - @param parent parent widget (QWidget) + @param debugServer reference to the debug server object + @type DebugServer + @param parent parent widget + @type QWidget """ super(DebugViewer, self).__init__(parent) @@ -64,11 +90,34 @@ self.setWindowIcon(UI.PixmapCache.getIcon("eric")) self.__mainLayout = QVBoxLayout() - self.__mainLayout.setContentsMargins(0, 0, 0, 0) + self.__mainLayout.setContentsMargins(0, 3, 0, 0) self.setLayout(self.__mainLayout) + self.__mainSplitter = QSplitter(Qt.Vertical, self) + self.__mainLayout.addWidget(self.__mainSplitter) + + # add the viewer showing the connected debug backends + self.__debuggersWidget = QWidget() + self.__debuggersLayout = QVBoxLayout(self.__debuggersWidget) + self.__debuggersLayout.setContentsMargins(0, 0, 0, 0) + self.__debuggersLayout.addWidget( + QLabel(self.tr("Debuggers and Threads:"))) + self.__debuggersList = QTreeWidget() + self.__debuggersList.setHeaderLabels( + [self.tr("ID"), self.tr("State"), ""]) + self.__debuggersList.header().setStretchLastSection(True) + self.__debuggersList.setSortingEnabled(True) + self.__debuggersList.setRootIsDecorated(True) + self.__debuggersList.setAlternatingRowColors(True) + self.__debuggersLayout.addWidget(self.__debuggersList) + self.__mainSplitter.addWidget(self.__debuggersWidget) + + self.__debuggersList.currentItemChanged.connect( + self.__debuggerSelected) + + # add the tab widget containing various debug related views self.__tabWidget = E5TabWidget() - self.__mainLayout.addWidget(self.__tabWidget) + self.__mainSplitter.addWidget(self.__tabWidget) from .VariablesViewer import VariablesViewer # add the global variables viewer @@ -105,7 +154,9 @@ index = self.__tabWidget.addTab( self.glvWidget, UI.PixmapCache.getIcon("globalVariables"), '') - self.__tabWidget.setTabToolTip(index, self.globalsViewer.windowTitle()) + self.__tabWidget.setTabToolTip( + index, + self.tr("Shows the list of global variables and their values.")) self.setGlobalsFilterButton.clicked.connect( self.setGlobalsFilter) @@ -159,7 +210,9 @@ index = self.__tabWidget.addTab( self.lvWidget, UI.PixmapCache.getIcon("localVariables"), '') - self.__tabWidget.setTabToolTip(index, self.localsViewer.windowTitle()) + self.__tabWidget.setTabToolTip( + index, + self.tr("Shows the list of local variables and their values.")) self.sourceButton.clicked.connect(self.__showSource) self.stackComboBox.currentIndexChanged[int].connect( @@ -178,19 +231,21 @@ self.callStackViewer, UI.PixmapCache.getIcon("callStack"), "") self.__tabWidget.setTabToolTip( - index, self.callStackViewer.windowTitle()) + index, + self.tr("Shows the current call stack.")) self.callStackViewer.sourceFile.connect(self.sourceFile) self.callStackViewer.frameSelected.connect( self.__callStackFrameSelected) from .CallTraceViewer import CallTraceViewer # add the call trace viewer - self.callTraceViewer = CallTraceViewer(self.debugServer) + self.callTraceViewer = CallTraceViewer(self.debugServer, self) index = self.__tabWidget.addTab( self.callTraceViewer, UI.PixmapCache.getIcon("callTrace"), "") self.__tabWidget.setTabToolTip( - index, self.callTraceViewer.windowTitle()) + index, + self.tr("Shows a trace of the program flow.")) self.callTraceViewer.sourceFile.connect(self.sourceFile) from .BreakPointViewer import BreakPointViewer @@ -201,7 +256,8 @@ self.breakpointViewer, UI.PixmapCache.getIcon("breakpoints"), '') self.__tabWidget.setTabToolTip( - index, self.breakpointViewer.windowTitle()) + index, + self.tr("Shows a list of defined breakpoints.")) self.breakpointViewer.sourceFile.connect(self.sourceFile) from .WatchPointViewer import WatchPointViewer @@ -212,7 +268,8 @@ self.watchpointViewer, UI.PixmapCache.getIcon("watchpoints"), '') self.__tabWidget.setTabToolTip( - index, self.watchpointViewer.windowTitle()) + index, + self.tr("Shows a list of defined watchpoints.")) from .ExceptionLogger import ExceptionLogger # add the exception logger @@ -221,7 +278,8 @@ self.exceptionLogger, UI.PixmapCache.getIcon("exceptions"), '') self.__tabWidget.setTabToolTip( - index, self.exceptionLogger.windowTitle()) + index, + self.tr("Shows a list of raised exceptions.")) from UI.PythonDisViewer import PythonDisViewer, PythonDisViewerModes # add the Python disassembly viewer @@ -231,34 +289,49 @@ self.disassemblyViewer, UI.PixmapCache.getIcon("disassembly"), '') self.__tabWidget.setTabToolTip( - index, self.disassemblyViewer.windowTitle()) + index, + self.tr("Shows a code disassembly in case of an exception.")) self.__tabWidget.setCurrentWidget(self.glvWidget) - # add the threads viewer - self.__mainLayout.addWidget(QLabel(self.tr("Threads:"))) - self.__threadList = QTreeWidget() - self.__threadList.setHeaderLabels( - [self.tr("ID"), self.tr("Name"), - self.tr("State"), ""]) - self.__threadList.setSortingEnabled(True) - self.__mainLayout.addWidget(self.__threadList) + self.__doDebuggersListUpdate = True - self.__doThreadListUpdate = True - - self.__threadList.currentItemChanged.connect(self.__threadSelected) - - self.__mainLayout.setStretchFactor(self.__tabWidget, 5) - self.__mainLayout.setStretchFactor(self.__threadList, 1) + self.__mainSplitter.setSizes([100, 700]) self.currentStack = None self.framenr = 0 - self.debugServer.clientStack.connect(self.handleClientStack) - self.__autoViewSource = Preferences.getDebugger("AutoViewSourceCode") self.sourceButton.setVisible(not self.__autoViewSource) + # connect some debug server signals + self.debugServer.clientStack.connect( + self.handleClientStack) + self.debugServer.clientThreadList.connect( + self.__addThreadList) + self.debugServer.clientDebuggerId.connect( + self.__clientDebuggerId) + self.debugServer.passiveDebugStarted.connect( + self.handleDebuggingStarted) + self.debugServer.clientLine.connect( + self.__clientLine) + self.debugServer.clientSyntaxError.connect( + self.__clientSyntaxError) + self.debugServer.clientException.connect( + self.__clientException) + self.debugServer.clientExit.connect( + self.__clientExit) + self.debugServer.clientDisconnected.connect( + self.__removeDebugger) + + self.debugServer.clientException.connect( + self.exceptionLogger.addException) + self.debugServer.passiveDebugStarted.connect( + self.exceptionLogger.debuggingStarted) + + self.debugServer.clientLine.connect( + self.breakpointViewer.highlightBreakpoint) + def handlePreferencesChanged(self): """ Public slot to handle the preferencesChanged signal. @@ -270,15 +343,25 @@ """ Public method to set a reference to the Debug UI. - @param debugUI reference to the DebugUI object (DebugUI) + @param debugUI reference to the DebugUI object + @type DebugUI """ self.debugUI = debugUI - self.debugUI.clientStack.connect(self.handleClientStack) self.callStackViewer.setDebugger(debugUI) - def handleResetUI(self): + # connect some debugUI signals + self.debugUI.clientStack.connect(self.handleClientStack) + self.debugUI.debuggingStarted.connect( + self.exceptionLogger.debuggingStarted) + self.debugUI.debuggingStarted.connect( + self.handleDebuggingStarted) + + def handleResetUI(self, fullReset): """ - Public method to reset the SBVviewer. + Public method to reset the viewer. + + @param fullReset flag indicating a full reset is required + @type bool """ self.globalsViewer.handleResetUI() self.localsViewer.handleResetUI() @@ -287,16 +370,18 @@ self.sourceButton.setEnabled(False) self.currentStack = None self.stackComboBox.clear() - self.__threadList.clear() self.__tabWidget.setCurrentWidget(self.glvWidget) self.breakpointViewer.handleResetUI() + if fullReset: + self.__debuggersList.clear() self.disassemblyViewer.clear() def initCallStackViewer(self, projectMode): """ Public method to initialize the call stack viewer. - @param projectMode flag indicating to enable the project mode (boolean) + @param projectMode flag indicating to enable the project mode + @type bool """ self.callStackViewer.clear() self.callStackViewer.setProjectMode(projectMode) @@ -305,7 +390,8 @@ """ Public method to get the state of the call trace function. - @return flag indicating the state of the call trace function (boolean) + @return flag indicating the state of the call trace function + @rtype bool """ return self.callTraceViewer.isCallTraceEnabled() @@ -322,7 +408,8 @@ In project mode the call trace info is shown with project relative path names. - @param enabled flag indicating to enable the project mode (boolean) + @param enabled flag indicating to enable the project mode + @type bool """ self.callTraceViewer.setProjectMode(enabled) @@ -331,7 +418,9 @@ Public method to show the variables in the respective window. @param vlist list of variables to display + @type list @param showGlobals flag indicating global/local state + @type bool """ if showGlobals: self.globalsViewer.showVariables(vlist, self.framenr) @@ -343,7 +432,9 @@ Public method to show the variables in the respective window. @param vlist list of variables to display + @type list @param showGlobals flag indicating global/local state + @type bool """ if showGlobals: self.globalsViewer.showVariable(vlist) @@ -355,40 +446,133 @@ Public method to make a variables tab visible. @param showGlobals flag indicating global/local state + @type bool """ if showGlobals: self.__tabWidget.setCurrentWidget(self.glvWidget) else: self.__tabWidget.setCurrentWidget(self.lvWidget) - def handleClientStack(self, stack): + def handleClientStack(self, stack, debuggerId): """ Public slot to show the call stack of the program being debugged. @param stack list of tuples with call stack data (file name, line number, function name, formatted argument/values list) + @type list of tuples of (str, str, str, str) + @param debuggerId ID of the debugger backend + @type str """ - block = self.stackComboBox.blockSignals(True) - self.framenr = 0 - self.stackComboBox.clear() - self.currentStack = stack - self.sourceButton.setEnabled(len(stack) > 0) - for s in stack: - # just show base filename to make it readable - s = (os.path.basename(s[0]), s[1], s[2]) - self.stackComboBox.addItem('{0}:{1}:{2}'.format(*s)) - self.stackComboBox.blockSignals(block) + if debuggerId == self.getSelectedDebuggerId(): + block = self.stackComboBox.blockSignals(True) + self.framenr = 0 + self.stackComboBox.clear() + self.currentStack = stack + self.sourceButton.setEnabled(len(stack) > 0) + for s in stack: + # just show base filename to make it readable + s = (os.path.basename(s[0]), s[1], s[2]) + self.stackComboBox.addItem('{0}:{1}:{2}'.format(*s)) + self.stackComboBox.blockSignals(block) + + def __clientLine(self, fn, line, debuggerId, threadName): + """ + Private method to handle a change to the current line. + + @param fn filename + @type str + @param line linenumber + @type int + @param debuggerId ID of the debugger backend + @type str + @param threadName name of the thread signaling the event + @type str + """ + self.__setDebuggerIconAndState(debuggerId, "broken") + self.__setThreadIconAndState(debuggerId, threadName, "broken") + if debuggerId != self.getSelectedDebuggerId(): + self.__setCurrentDebugger(debuggerId) + + @pyqtSlot(str, int, str, bool, str) + def __clientExit(self, program, status, message, quiet, debuggerId): + """ + Private method to handle the debugged program terminating. + @param program name of the exited program + @type str + @param status exit code of the debugged program + @type int + @param message exit message of the debugged program + @type str + @param quiet flag indicating to suppress exit info display + @type bool + @param debuggerId ID of the debugger backend + @type str + """ + if not self.isOnlyDebugger(): + if debuggerId == self.getSelectedDebuggerId(): + # the current client has exited + self.globalsViewer.handleResetUI() + self.localsViewer.handleResetUI() + self.setGlobalsFilter() + self.setLocalsFilter() + self.sourceButton.setEnabled(False) + self.currentStack = None + self.stackComboBox.clear() + + self.__removeDebugger(debuggerId) + + def __clientSyntaxError(self, message, filename, lineNo, characterNo, + debuggerId, threadName): + """ + Private method to handle a syntax error in the debugged program. + + @param message message of the syntax error + @type str + @param filename translated filename of the syntax error position + @type str + @param lineNo line number of the syntax error position + @type int + @param characterNo character number of the syntax error position + @type int + @param debuggerId ID of the debugger backend + @type str + @param threadName name of the thread signaling the event + @type str + """ + self.__setDebuggerIconAndState(debuggerId, "syntax") + self.__setThreadIconAndState(debuggerId, threadName, "syntax") + + def __clientException(self, exceptionType, exceptionMessage, stackTrace, + debuggerId, threadName): + """ + Private method to handle an exception of the debugged program. + + @param exceptionType type of exception raised + @type str + @param exceptionMessage message given by the exception + @type (str + @param stackTrace list of stack entries + @type list of str + @param debuggerId ID of the debugger backend + @type str + @param threadName name of the thread signaling the event + @type str + """ + self.__setDebuggerIconAndState(debuggerId, "exception") + self.__setThreadIconAndState(debuggerId, threadName, "exception") + def setVariablesFilter(self, globalsFilter, localsFilter): """ Public slot to set the local variables filter. @param globalsFilter filter list for global variable types - (list of int) - @param localsFilter filter list for local variable types (list of int) + @type list of str + @param localsFilter filter list for local variable types + @type list of str """ - self.globalsFilter = globalsFilter - self.localsFilter = localsFilter + self.__globalsFilter = globalsFilter + self.__localsFilter = localsFilter def __showSource(self): """ @@ -404,14 +588,18 @@ """ Private slot to handle the selection of a new stack frame number. - @param frmnr frame number (0 is the current frame) (int) + @param frmnr frame number (0 is the current frame) + @type int """ - self.framenr = frmnr - if self.debugServer.isDebugging(): - self.debugServer.remoteClientVariables(0, self.localsFilter, frmnr) - - if self.__autoViewSource: - self.__showSource() + if frmnr >= 0: + self.framenr = frmnr + if self.debugServer.isDebugging(): + self.debugServer.remoteClientVariables( + self.getSelectedDebuggerId(), 0, self.__localsFilter, + frmnr) + + if self.__autoViewSource: + self.__showSource() def setGlobalsFilter(self): """ @@ -419,8 +607,10 @@ """ if self.debugServer.isDebugging(): filterStr = self.globalsFilterEdit.text() - self.debugServer.remoteClientSetFilter(1, filterStr) - self.debugServer.remoteClientVariables(2, self.globalsFilter) + self.debugServer.remoteClientSetFilter( + self.getSelectedDebuggerId(), 1, filterStr) + self.debugServer.remoteClientVariables( + self.getSelectedDebuggerId(), 2, self.__globalsFilter) def setLocalsFilter(self): """ @@ -428,10 +618,12 @@ """ if self.debugServer.isDebugging(): filterStr = self.localsFilterEdit.text() - self.debugServer.remoteClientSetFilter(0, filterStr) + self.debugServer.remoteClientSetFilter( + self.getSelectedDebuggerId(), 0, filterStr) if self.currentStack: self.debugServer.remoteClientVariables( - 0, self.localsFilter, self.framenr) + self.getSelectedDebuggerId(), 0, self.__localsFilter, + self.framenr) def handleDebuggingStarted(self): """ @@ -449,7 +641,8 @@ """ Public method to get a reference to the current widget. - @return reference to the current widget (QWidget) + @return reference to the current widget + @rtype QWidget """ return self.__tabWidget.currentWidget() @@ -457,57 +650,298 @@ """ Public slot to set the current page based on the given widget. - @param widget reference to the widget (QWidget) + @param widget reference to the widget + @type QWidget """ self.__tabWidget.setCurrentWidget(widget) - - def showThreadList(self, currentID, threadList): - """ - Public method to show the thread list. - - @param currentID id of the current thread (integer) - @param threadList list of dictionaries containing the thread data - """ - citm = None - - self.__threadList.clear() - for thread in threadList: - if thread['broken']: - state = self.tr("waiting at breakpoint") - else: - state = self.tr("running") - itm = QTreeWidgetItem(self.__threadList, - ["{0:d}".format(thread['id']), - thread['name'], state]) - if thread['id'] == currentID: - citm = itm - - self.__threadList.header().resizeSections(QHeaderView.ResizeToContents) - self.__threadList.header().setStretchLastSection(True) - - if citm: - self.__doThreadListUpdate = False - self.__threadList.setCurrentItem(citm) - self.__doThreadListUpdate = True - - def __threadSelected(self, current, previous): - """ - Private slot to handle the selection of a thread in the thread list. - - @param current reference to the new current item (QTreeWidgetItem) - @param previous reference to the previous current item - (QTreeWidgetItem) - """ - if current is not None and self.__doThreadListUpdate: - tid = int(current.text(0)) - self.debugServer.remoteSetThread(tid) def __callStackFrameSelected(self, frameNo): """ Private slot to handle the selection of a call stack entry of the call stack viewer. - @param frameNo frame number (index) of the selected entry (integer) + @param frameNo frame number (index) of the selected entry + @type int """ if frameNo >= 0: self.stackComboBox.setCurrentIndex(frameNo) + + def __debuggerSelected(self, current, previous): + """ + Private slot to handle the selection of a debugger backend in the + debuggers list. + + @param current reference to the new current item + @type QTreeWidgetItem + @param previous reference to the previous current item + @type QTreeWidgetItem + """ + if current is not None and self.__doDebuggersListUpdate: + if current.parent() is None: + # it is a debugger item + debuggerId = current.text(0) + self.globalsViewer.handleResetUI() + self.localsViewer.handleResetUI() + self.currentStack = None + self.stackComboBox.clear() + self.callStackViewer.clear() + + self.debugServer.remoteSetThread(debuggerId, -1) + self.__showSource() + else: + # it is a thread item + tid = current.data(0, self.ThreadIdRole) + self.debugServer.remoteSetThread( + self.getSelectedDebuggerId(), tid) + + def __clientDebuggerId(self, debuggerId): + """ + Private slot to receive the ID of a newly connected debugger backend. + + @param debuggerId ID of a newly connected debugger backend + @type str + """ + itm = QTreeWidgetItem(self.__debuggersList, [debuggerId]) + if self.__debuggersList.topLevelItemCount() > 1: + self.debugUI.showNotification( + self.tr("<p>Debugger with ID <b>{0}</b> has been connected." + "</p>") + .format(debuggerId)) + + self.__debuggersList.header().resizeSections( + QHeaderView.ResizeToContents) + + if self.__debuggersList.topLevelItemCount() == 1: + # it is the only item, select it as the current one + self.__debuggersList.setCurrentItem(itm) + + def __setCurrentDebugger(self, debuggerId): + """ + Private method to set the current debugger based on the given ID. + + @param debuggerId ID of the debugger to set as current debugger + @type str + """ + debuggerItems = self.__debuggersList.findItems( + debuggerId, Qt.MatchExactly) + if debuggerItems: + debuggerItem = debuggerItems[0] + currentItem = self.__debuggersList.currentItem() + if currentItem is debuggerItem: + # nothing to do + return + + if currentItem: + currentParent = currentItem.parent() + else: + currentParent = None + if currentParent is None: + # current is a debugger item + self.__debuggersList.setCurrentItem(debuggerItem) + elif currentParent is debuggerItem: + # nothing to do + return + else: + self.__debuggersList.setCurrentItem(debuggerItem) + + def isOnlyDebugger(self): + """ + Public method to test, if only one debugger is connected. + + @return flag indicating that only one debugger is connected + @rtype bool + """ + return self.__debuggersList.topLevelItemCount() == 1 + + def getSelectedDebuggerId(self): + """ + Public method to get the currently selected debugger ID. + + @return selected debugger ID + @rtype str + """ + itm = self.__debuggersList.currentItem() + if itm: + if itm.parent() is None: + # it is a debugger item + return itm.text(0) + else: + # it is a thread item + return itm.parent().text(0) + else: + return "" + + def getSelectedDebuggerState(self): + """ + Public method to get the currently selected debugger's state. + + @return selected debugger's state (broken, exception, running) + @rtype str + """ + itm = self.__debuggersList.currentItem() + if itm: + if itm.parent() is None: + # it is a debugger item + return itm.data(0, self.DebuggerStateRole) + else: + # it is a thread item + return itm.parent().data(0, self.DebuggerStateRole) + else: + return "" + + def __setDebuggerIconAndState(self, debuggerId, state): + """ + Private method to set the icon for a specific debugger ID. + + @param debuggerId ID of the debugger backend (empty ID means the + currently selected one) + @type str + @param state state of the debugger (broken, exception, running) + @type str + """ + debuggerItem = None + if debuggerId: + foundItems = self.__debuggersList.findItems( + debuggerId, Qt.MatchExactly) + if foundItems: + debuggerItem = foundItems[0] + if debuggerItem is None: + debuggerItem = self.__debuggersList.currentItem() + if debuggerItem is not None: + try: + iconName = DebugViewer.StateIcon[state] + except KeyError: + iconName = "question" + try: + stateText = DebugViewer.StateMessage[state] + except KeyError: + stateText = self.tr("unknown state ({0})").format(state) + debuggerItem.setIcon(0, UI.PixmapCache.getIcon(iconName)) + debuggerItem.setData(0, self.DebuggerStateRole, state) + debuggerItem.setText(1, stateText) + + self.__debuggersList.header().resizeSections( + QHeaderView.ResizeToContents) + + def __removeDebugger(self, debuggerId): + """ + Private method to remove a debugger given its ID. + + @param debuggerId ID of the debugger to be removed from the list + @type str + """ + foundItems = self.__debuggersList.findItems( + debuggerId, Qt.MatchExactly) + if foundItems: + index = self.__debuggersList.indexOfTopLevelItem(foundItems[0]) + itm = self.__debuggersList.takeTopLevelItem(index) + # __IGNORE_WARNING__ + del itm + + def __addThreadList(self, currentID, threadList, debuggerId): + """ + Private method to add the list of threads to a debugger entry. + + @param currentID id of the current thread + @type int + @param threadList list of dictionaries containing the thread data + @type list of dict + @param debuggerId ID of the debugger backend + @type str + """ + debugStatus = -1 # i.e. running + + debuggerItems = self.__debuggersList.findItems( + debuggerId, Qt.MatchExactly) + if debuggerItems: + debuggerItem = debuggerItems[0] + + currentItem = self.__debuggersList.currentItem() + if currentItem.parent() is debuggerItem: + currentChild = currentItem.text(0) + else: + currentChild = "" + self.__doDebuggersListUpdate = False + debuggerItem.takeChildren() + for thread in threadList: + if thread.get('except', False): + stateText = DebugViewer.StateMessage["exception"] + iconName = DebugViewer.StateIcon["exception"] + debugStatus = 1 + elif thread['broken']: + stateText = DebugViewer.StateMessage["broken"] + iconName = DebugViewer.StateIcon["broken"] + if debugStatus < 1: + debugStatus = 0 + else: + stateText = DebugViewer.StateMessage["running"] + iconName = DebugViewer.StateIcon["running"] + itm = QTreeWidgetItem(debuggerItem, + [thread['name'], stateText]) + itm.setData(0, self.ThreadIdRole, thread['id']) + itm.setIcon(0, UI.PixmapCache.getIcon(iconName)) + if currentChild == thread['name']: + self.__debuggersList.setCurrentItem(itm) + if thread['id'] == currentID: + font = debuggerItem.font(0) + font.setItalic(True) + itm.setFont(0, font) + + debuggerItem.setExpanded(debuggerItem.childCount() > 0) + + self.__debuggersList.header().resizeSections( + QHeaderView.ResizeToContents) + self.__debuggersList.header().setStretchLastSection(True) + self.__doDebuggersListUpdate = True + + if debugStatus == -1: + debuggerState = "running" + elif debugStatus == 0: + debuggerState = "broken" + else: + debuggerState = "exception" + self.__setDebuggerIconAndState(debuggerId, debuggerState) + + def __setThreadIconAndState(self, debuggerId, threadName, state): + """ + Private method to set the icon for a specific thread name and + debugger ID. + + @param debuggerId ID of the debugger backend (empty ID means the + currently selected one) + @type str + @param threadName name of the thread signaling the event + @type str + @param state state of the debugger (broken, exception, running) + @type str + """ + debuggerItem = None + if debuggerId: + foundItems = self.__debuggersList.findItems( + debuggerId, Qt.MatchExactly) + if foundItems: + debuggerItem = foundItems[0] + if debuggerItem is None: + debuggerItem = self.__debuggersList.currentItem() + if debuggerItem is not None: + for index in range(debuggerItem.childCount()): + childItem = debuggerItem.child(index) + if childItem.text(0) == threadName: + break + else: + childItem = None + + if childItem is not None: + try: + iconName = DebugViewer.StateIcon[state] + except KeyError: + iconName = "question" + try: + stateText = DebugViewer.StateMessage[state] + except KeyError: + stateText = self.tr("unknown state ({0})").format(state) + childItem.setIcon(0, UI.PixmapCache.getIcon(iconName)) + childItem.setText(1, stateText) + + self.__debuggersList.header().resizeSections( + QHeaderView.ResizeToContents)