eric6/Debugger/DebugViewer.py

branch
maintenance
changeset 8043
0acf98cd089a
parent 7924
8a96736d465e
parent 7986
2971d5d19951
child 8176
31965986ecd1
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)

eric ide

mercurial