# -*- coding: utf-8 -*- # Copyright (c) 2002 - 2021 Detlev Offenbach # """ Module implementing the variables viewer view based on QTreeView. """ import ast import re import contextlib from PyQt5.QtCore import ( Qt, QAbstractItemModel, QModelIndex, QCoreApplication, QSortFilterProxyModel, pyqtSignal ) from PyQt5.QtGui import QBrush, QFontMetrics from PyQt5.QtWidgets import QTreeView, QAbstractItemView, QToolTip, QMenu from E5Gui.E5Application import e5App from .Config import ConfigVarTypeDispStrings from DebugClients.Python.DebugConfig import ConfigQtNames, ConfigKnownQtTypes import Preferences import Utilities SORT_ROLE = Qt.ItemDataRole.UserRole class VariableItem: """ Class implementing the data structure for all variable items. """ Type2Indicators = { # Python types 'list': '[]', 'tuple': '()', 'dict': '{:}', # __IGNORE_WARNING_M613__ 'set': '{}', # __IGNORE_WARNING_M613__ 'frozenset': '{}', # __IGNORE_WARNING_M613__ 'numpy.ndarray': '[ndarray]', # __IGNORE_WARNING_M613__ } # Initialize regular expression for unprintable strings rx_nonprintable = re.compile(r"""(\\x\d\d)+""") noOfItemsStr = QCoreApplication.translate("VariablesViewer", "{0} items") unsized = QCoreApplication.translate("VariablesViewer", "unsized") arrayTypes = { 'list', 'tuple', 'dict', 'set', 'frozenset', 'numpy.ndarray', 'django.MultiValueDict', 'array.array', 'collections.defaultdict', "class 'dict_items'", "class 'dict_keys'", "class 'dict_values'", } nonExpandableTypes = ( 'method_descriptor', 'wrapper_descriptor', '', 'getset_descriptor', 'method-wrapper', 'member_descriptor', ) def __init__(self, parent, dvar, dtype, dvalue): """ Constructor @param parent reference to the parent item @type VariableItem @param dvar variable name @type str @param dtype type string @type str @param dvalue value string @type str """ self.parent = parent # Take the additional methods into account for childCount self.methodCount = 0 self.childCount = 0 self.currentCount = -1 # -1 indicates to (re)load children # Indicator that there are children self.hasChildren = False self.populated = False # Indicator that item was at least once fully populated self.wasPopulated = False self.children = [] # Flag to prevent endless reloading of current item while waiting on # a response from debugger self.pendigFetch = False # Set of child items, which are displayed the first time or changed self.newItems = set() self.changedItems = set() # Name including its ID if it's a dict, set, etc. self.nameWithId = dvar self.name = '' self.sort = '' self.type = '' self.indicator = '' self.value = None self.valueShort = None self.tooltip = '' self.__getName(dvar) self.__getType(dtype) self.__getValue(dtype, dvalue) def __getName(self, dvar): """ Private method to extract the variable name. @param dvar name of variable maybe with ID @type str """ try: idx = dvar.index(" (ID:") dvar = dvar[:idx] except AttributeError: idx = dvar dvar = str(dvar) except ValueError: pass self.name = dvar try: # Convert numbers to strings with preceding zeros sort = int(dvar) sort = "{0:06}".format(sort) except ValueError: sort = dvar.lower() self.sort = sort def __getType(self, dtype): """ Private method to process the type of the variable. If type is known to have children, the corresponding flag is set. @param dtype type string @type str """ # Python class? if dtype.startswith('class '): dtype = dtype[7:-1] # Qt related stuff? elif ( (dtype.startswith(ConfigQtNames) and dtype.endswith(ConfigKnownQtTypes)) or dtype in ('instance', 'class') ): self.hasChildren = True # Special Qt types should not be expanded infinite elif ".{0}".format(dtype) in ConfigKnownQtTypes: self.type = dtype # It's a Qt type, so skipping translation is ok return vtype = ConfigVarTypeDispStrings.get(dtype, dtype) # Unkown types should be expandable by default if vtype is dtype and dtype not in self.nonExpandableTypes: self.hasChildren = True self.type = QCoreApplication.translate("VariablesViewer", vtype) def __getValue(self, dtype, dvalue): """ Private method to process the variables value. Define and limit value, set tooltip text. If type is known to have children, the corresponding flag is set. @param dtype type string @type str @param dvalue value of variable encoded as utf-8 @type str """ if dtype == 'collections.defaultdict': dvalue, default_factory = dvalue.split('|') self.indicator = '{{:<{0}>}}'.format(default_factory) elif dtype == 'array.array': dvalue, typecode = dvalue.split('|') self.indicator = '[<{0}>]'.format(typecode) else: self.indicator = VariableItem.Type2Indicators.get(dtype, '') if dtype == 'numpy.ndarray': if dvalue: self.childCount = int(dvalue.split('x')[0]) dvalue = VariableItem.noOfItemsStr.format(dvalue) else: dvalue = VariableItem.unsized self.hasChildren = True elif dtype in VariableItem.arrayTypes: self.childCount = int(dvalue) dvalue = VariableItem.noOfItemsStr.format(dvalue) self.hasChildren = True elif dtype == "Shiboken.EnumType": self.hasChildren = True elif dtype == 'str': if VariableItem.rx_nonprintable.search(dvalue) is None: with contextlib.suppress(Exception): dvalue = ast.literal_eval(dvalue) dvalue = str(dvalue) elif ( dvalue.startswith(("{", "(", "[")) and dvalue.endswith(("}", ")", "]")) ): # it is most probably a dict, tuple or list derived class with contextlib.suppress(Exception): value = ast.literal_eval(dvalue) valueTypeStr = str(type(value))[8:-2] if valueTypeStr in VariableItem.arrayTypes: self.childCount = len(value) self.hasChildren = True elif ( (dvalue.endswith("})") and "({" in dvalue) or (dvalue.endswith("])") and "([" in dvalue) ): # that is probably a set derived class with contextlib.suppress(Exception): value = ast.literal_eval(dvalue.split("(", 1)[1][:-1]) valueTypeStr = str(type(value))[8:-2] if valueTypeStr in VariableItem.arrayTypes: self.childCount = len(value) self.hasChildren = True self.value = dvalue if len(dvalue) > 2048: # 2 kB self.tooltip = dvalue[:2048] dvalue = QCoreApplication.translate( "VariableItem", "") else: self.tooltip = dvalue lines = dvalue[:2048].splitlines() if len(lines) > 1: # only show the first non-empty line; # indicate skipped lines by <...> at the # beginning and/or end index = 0 while index < len(lines) - 1 and lines[index].strip(' \t') == "": index += 1 dvalue = "" if index > 0: dvalue += "<...>" dvalue += lines[index] if index < len(lines) - 1 or len(dvalue) > 2048: dvalue += "<...>" self.valueShort = dvalue @property def absolutCount(self): """ Public property to get the total number of children. @return total number of children @rtype int """ return self.childCount + self.methodCount class VariablesModel(QAbstractItemModel): """ Class implementing the data model for QTreeView. @signal expand trigger QTreeView to expand given index """ expand = pyqtSignal(QModelIndex) def __init__(self, treeView, globalScope): """ Constructor @param treeView QTreeView showing the data @type VariablesViewer @param globalScope flag indicating global (True) or local (False) variables @type bool """ super().__init__() self.treeView = treeView self.proxyModel = treeView.proxyModel self.framenr = -1 self.openItems = [] self.closedItems = [] visibility = self.tr("Globals") if globalScope else self.tr("Locals") self.rootNode = VariableItem(None, visibility, self.tr("Type"), self.tr("Value")) self.__globalScope = globalScope def clear(self, reset=False): """ Public method to clear the complete data model. @param reset flag to clear the expanded keys also @type bool """ self.beginResetModel() self.rootNode.children = [] self.rootNode.newItems.clear() self.rootNode.changedItems.clear() self.rootNode.wasPopulated = False if reset: self.openItems = [] self.closedItems = [] self.endResetModel() def __findVariable(self, pathlist): """ Private method to get to the given variable. @param pathlist full path to the variable @type list of str @return the found variable or None if it doesn't exist @rtype VariableItem or None """ node = self.rootNode for childName in pathlist or []: for item in node.children: if item.nameWithId == childName: node = item break else: return None return node # __IGNORE_WARNING_M834__ def showVariables(self, vlist, frmnr, pathlist=None): """ Public method to update the data model of variable in pathlist. @param vlist the list of variables to be displayed. Each list entry is a tuple of three values. @type list of str @param frmnr frame number (0 is the current frame) @type int @param pathlist full path to the variable @type list of str """ if pathlist: itemStartIndex = pathlist.pop(0) else: itemStartIndex = -1 if self.framenr != frmnr: self.clear() self.framenr = frmnr parent = self.__findVariable(pathlist) if parent is None: return parent.pendigFetch = False if parent == self.rootNode: parentIdx = QModelIndex() parent.methodCount = len(vlist) else: row = parent.parent.children.index(parent) parentIdx = self.createIndex(row, 0, parent) if itemStartIndex == -3: # Item doesn't exist any more parentIdx = self.parent(parentIdx) self.beginRemoveRows(parentIdx, row, row) del parent.parent.children[row] self.endRemoveRows() parent.parent.childCount -= 1 return elif itemStartIndex == -2: parent.wasPopulated = True parent.currentCount = parent.absolutCount parent.populated = True # Remove items which are left over at the end of child list self.__cleanupParentList(parent, parentIdx) return elif itemStartIndex == -1: parent.methodCount = len(vlist) idx = max(parent.currentCount, 0) parent.currentCount = idx + len(vlist) parent.populated = True else: idx = itemStartIndex parent.currentCount = idx + len(vlist) # Sort items for Python versions where dict doesn't retain order vlist.sort(key=lambda x: x[0]) # Now update the table endIndex = idx + len(vlist) newChild = None knownChildrenCount = len(parent.children) while idx < endIndex: # Fetch next old item from last cycle try: child = parent.children[idx] except IndexError: child = None # Fetch possible new item if not newChild and vlist: newChild = vlist.pop(0) # Process parameters of new item newItem = VariableItem(parent, *newChild) sort = newItem.sort # Append or insert before already existing item if child is None or newChild and sort < child.sort: self.beginInsertRows(parentIdx, idx, idx) parent.children.insert(idx, newItem) if knownChildrenCount <= idx and not parent.wasPopulated: parent.newItems.add(newItem) knownChildrenCount += 1 else: parent.changedItems.add(newItem) self.endInsertRows() idx += 1 newChild = None continue # Check if same name, type and afterwards value elif sort == child.sort and child.type == newItem.type: # Check if value has changed if child.value != newItem.value: child.value = newItem.value child.valueShort = newItem.valueShort child.tooltip = newItem.tooltip child.nameWithId = newItem.nameWithId child.currentCount = -1 child.populated = False child.childCount = newItem.childCount # Highlight item because it has changed parent.changedItems.add(child) changedIndexStart = self.index(idx, 0, parentIdx) changedIndexEnd = self.index(idx, 2, parentIdx) self.dataChanged.emit(changedIndexStart, changedIndexEnd) newChild = None idx += 1 continue # Remove obsolete item self.beginRemoveRows(parentIdx, idx, idx) parent.children.remove(child) self.endRemoveRows() # idx stay unchanged knownChildrenCount -= 1 # Remove items which are left over at the end of child list if itemStartIndex == -1: parent.wasPopulated = True self.__cleanupParentList(parent, parentIdx) # Request data for any expanded node self.getMore() def __cleanupParentList(self, parent, parentIdx): """ Private method to remove items which are left over at the end of the child list. @param parent to clean up @type VariableItem @param parentIdx the parent index as QModelIndex @type QModelIndex """ end = len(parent.children) if end > parent.absolutCount: self.beginRemoveRows(parentIdx, parent.absolutCount, end) del parent.children[parent.absolutCount:] self.endRemoveRows() def resetModifiedMarker(self, parentIdx=QModelIndex(), pathlist=()): """ Public method to remove the modified marker from changed items. @param parentIdx item to reset marker @type QModelIndex @param pathlist full path to the variable @type list of str """ parent = (parentIdx.internalPointer() if parentIdx.isValid() else self.rootNode) parent.newItems.clear() parent.changedItems.clear() pll = len(pathlist) posPaths = {x for x in self.openItems if len(x) > pll} posPaths |= {x for x in self.closedItems if len(x) > pll} posPaths = {x[pll] for x in posPaths if x[:pll] == pathlist} if posPaths: for child in parent.children: if ( child.hasChildren and child.nameWithId in posPaths and child.currentCount >= 0 ): # Discard loaded elements and refresh if still expanded child.currentCount = -1 child.populated = False row = parent.children.index(child) newParentIdx = self.index(row, 0, parentIdx) self.resetModifiedMarker( newParentIdx, pathlist + (child.nameWithId,)) self.closedItems = [] # Little quirk: Refresh all visible items to clear the changed marker if parentIdx == QModelIndex(): self.rootNode.currentCount = -1 self.rootNode.populated = False idxStart = self.index(0, 0, QModelIndex()) idxEnd = self.index(0, 2, QModelIndex()) self.dataChanged.emit(idxStart, idxEnd) def columnCount(self, parent=QModelIndex()): """ Public method to get the column count. @param parent the model parent @type QModelIndex @return number of columns @rtype int """ return 3 def rowCount(self, parent=QModelIndex()): """ Public method to get the row count. @param parent the model parent @type QModelIndex @return number of rows @rtype int """ node = parent.internalPointer() if parent.isValid() else self.rootNode return len(node.children) def flags(self, index): """ Public method to get the item flags. @param index of item @type QModelIndex @return item flags @rtype QtCore.Qt.ItemFlag """ if not index.isValid(): return Qt.ItemFlag.NoItemFlags return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable def hasChildren(self, parent=QModelIndex()): """ Public method to get a flag if parent has children. @param parent the model parent @type QModelIndex @return flag indicating parent has children @rtype bool """ if not parent.isValid(): return self.rootNode.children != [] return parent.internalPointer().hasChildren def index(self, row, column, parent=QModelIndex()): """ Public method to get the index of item at row:column of parent. @param row number of rows @type int @param column number of columns @type int @param parent the model parent @type QModelIndex @return new model index for child @rtype QModelIndex """ if not self.hasIndex(row, column, parent): return QModelIndex() node = parent.internalPointer() if parent.isValid() else self.rootNode return self.createIndex(row, column, node.children[row]) def parent(self, child): """ Public method to get the parent of the given child. @param child the model child node @type QModelIndex @return new model index for parent @rtype QModelIndex """ if not child.isValid(): return QModelIndex() childNode = child.internalPointer() if childNode == self.rootNode: return QModelIndex() parentNode = childNode.parent if parentNode == self.rootNode: return QModelIndex() row = parentNode.parent.children.index(parentNode) return self.createIndex(row, 0, parentNode) def data(self, index, role=Qt.ItemDataRole.DisplayRole): """ Public method get the role data of item. @param index the model index @type QModelIndex @param role the requested data role @type QtCore.Qt.ItemDataRole @return role data of item @rtype Any """ if not index.isValid() or index.row() < 0: return None node = index.internalPointer() column = index.column() if role in ( Qt.ItemDataRole.DisplayRole, SORT_ROLE, Qt.ItemDataRole.EditRole ): try: if column == 0: # Sort first column with values from third column if role == SORT_ROLE: return node.sort return node.name + node.indicator else: return { 1: node.valueShort, 2: node.type, 3: node.sort }.get(column) except AttributeError: return ['None', '', '', ''][column] elif role == Qt.ItemDataRole.BackgroundRole: if node in node.parent.changedItems: return self.__bgColorChanged elif node in node.parent.newItems: return self.__bgColorNew elif role == Qt.ItemDataRole.ToolTipRole: if column == 0: tooltip = node.name + node.indicator elif column == 1: tooltip = node.tooltip elif column == 2: tooltip = node.type elif column == 3: tooltip = node.sort else: return None if Qt.mightBeRichText(tooltip): tooltip = Utilities.html_encode(tooltip) if column == 0: indentation = self.treeView.indentation() indentCount = 0 currentNode = node while currentNode.parent: indentCount += 1 currentNode = currentNode.parent indentation *= indentCount else: indentation = 0 # Check if text is longer than available space fontMetrics = QFontMetrics(self.treeView.font()) try: textSize = fontMetrics.horizontalAdvance(tooltip) except AttributeError: textSize = fontMetrics.width(tooltip) textSize += indentation + 5 # How to determine border size? header = self.treeView.header() if textSize >= header.sectionSize(column): return tooltip else: QToolTip.hideText() return None def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole): """ Public method get the header names. @param section the header section (row/coulumn) @type int @param orientation the header's orientation @type QtCore.Qt.Orientation @param role the requested data role @type QtCore.Qt.ItemDataRole @return header name @rtype str or None """ if ( role != Qt.ItemDataRole.DisplayRole or orientation != Qt.Orientation.Horizontal ): return None return { 0: self.rootNode.name, 1: self.rootNode.value, 2: self.rootNode.type, 3: self.rootNode.sort }.get(section) def __findPendingItem(self, parent=None, pathlist=()): """ Private method to find the next item to request data from debugger. @param parent the model parent @type VariableItem @param pathlist full path to the variable @type list of str @return next item index to request data from debugger @rtype QModelIndex """ if parent is None: parent = self.rootNode for child in parent.children: if not child.hasChildren: continue if pathlist + (child.nameWithId,) in self.openItems: if child.populated: index = None else: idx = parent.children.index(child) index = self.createIndex(idx, 0, child) self.expand.emit(index) if child.currentCount < 0: return index possibleIndex = self.__findPendingItem( child, pathlist + (child.nameWithId,)) if (possibleIndex or index) is None: continue return possibleIndex or index return None def getMore(self): """ Public method to fetch the next variable from debugger. """ # step 1: find expanded but not populated items item = self.__findPendingItem() if not item or not item.isValid(): return # step 2: check if data has to be retrieved node = item.internalPointer() lastVisibleItem = self.index(node.currentCount - 1, 0, item) lastVisibleItem = self.proxyModel.mapFromSource(lastVisibleItem) rect = self.treeView.visualRect(lastVisibleItem) if rect.y() > self.treeView.height() or node.pendigFetch: return node.pendigFetch = True # step 3: get a pathlist up to the requested variable pathlist = self.__buildTreePath(node) # step 4: request the variable from the debugger variablesFilter = e5App().getObject("DebugUI").variablesFilter( self.__globalScope) e5App().getObject("DebugServer").remoteClientVariable( e5App().getObject("DebugUI").getSelectedDebuggerId(), self.__globalScope, variablesFilter, pathlist, self.framenr) def setExpanded(self, index, state): """ Public method to set the expanded state of item. @param index item to change expanded state @type QModelIndex @param state state of the item @type bool """ node = index.internalPointer() pathlist = self.__buildTreePath(node) if state: if pathlist not in self.openItems: self.openItems.append(pathlist) if pathlist in self.closedItems: self.closedItems.remove(pathlist) self.getMore() else: if pathlist in self.openItems: self.openItems.remove(pathlist) self.closedItems.append(pathlist) def __buildTreePath(self, parent): """ Private method to build up a path from the root to parent. @param parent item to build the path for @type VariableItem @return list of names denoting the path from the root @rtype tuple of str """ pathlist = [] # build up a path from the top to the item while parent.parent: pathlist.append(parent.nameWithId) parent = parent.parent pathlist.reverse() return tuple(pathlist) def handlePreferencesChanged(self): """ Public slot to handle the preferencesChanged signal. """ self.__bgColorNew = QBrush(Preferences.getDebugger("BgColorNew")) self.__bgColorChanged = QBrush( Preferences.getDebugger("BgColorChanged")) idxStart = self.index(0, 0, QModelIndex()) idxEnd = self.index(0, 2, QModelIndex()) self.dataChanged.emit(idxStart, idxEnd) class VariablesProxyModel(QSortFilterProxyModel): """ Class for handling the sort operations. """ def __init__(self, parent=None): """ Constructor @param parent the parent model index @type QModelIndex """ super().__init__(parent) self.setSortRole(SORT_ROLE) def hasChildren(self, parent): """ Public method to get a flag if parent has children. The given model index has to be transformed to the underlying source model to get the correct result. @param parent the model parent @type QModelIndex @return flag if parent has children @rtype bool """ return self.sourceModel().hasChildren(self.mapToSource(parent)) def setExpanded(self, index, state): """ Public slot to get a flag if parent has children. The given model index has to be transformed to the underlying source model to get the correct result. @param index item to change expanded state @type QModelIndex @param state state of the item @type bool """ self.sourceModel().setExpanded(self.mapToSource(index), state) class VariablesViewer(QTreeView): """ Class implementing the variables viewer view. This view is used to display the variables of the program being debugged in a tree. Compound types will be shown with their main entry first. Once the subtree has been expanded, the individual entries will be shown. Double clicking an entry will expand or collapse the item, if it has children and the double click was performed on the first column of the tree, otherwise it'll popup a dialog showing the variables parameters in a more readable form. This is especially useful for lengthy strings. This view has two modes for displaying the global and the local variables. @signal preferencesChanged() to inform model about new background colours """ preferencesChanged = pyqtSignal() def __init__(self, viewer, globalScope, parent=None): """ Constructor @param viewer reference to the debug viewer object @type DebugViewer @param globalScope flag indicating global (True) or local (False) variables @type bool @param parent the parent @type QWidget """ super().__init__(parent) self.__debugViewer = viewer self.__globalScope = globalScope self.framenr = 0 # Massive performance gain self.setUniformRowHeights(True) # Implements sorting and filtering self.proxyModel = VariablesProxyModel() # Variable model implements the underlying data model self.varModel = VariablesModel(self, globalScope) self.proxyModel.setSourceModel(self.varModel) self.setModel(self.proxyModel) self.preferencesChanged.connect(self.varModel.handlePreferencesChanged) self.preferencesChanged.emit() # Force initialization of colors self.expanded.connect( lambda idx: self.proxyModel.setExpanded(idx, True)) self.collapsed.connect( lambda idx: self.proxyModel.setExpanded(idx, False)) self.setExpandsOnDoubleClick(False) self.doubleClicked.connect(self.__itemDoubleClicked) self.varModel.expand.connect(self.__mdlRequestExpand) self.setSortingEnabled(True) self.setAlternatingRowColors(True) self.setSelectionBehavior( QAbstractItemView.SelectionBehavior.SelectRows) if self.__globalScope: self.setWindowTitle(self.tr("Global Variables")) self.setWhatsThis(self.tr( """The Global Variables Viewer Window""" """

This window displays the global variables""" """ of the debugged program.

""" )) else: self.setWindowTitle(self.tr("Local Variables")) self.setWhatsThis(self.tr( """The Local Variables Viewer Window""" """

This window displays the local variables""" """ of the debugged program.

""" )) header = self.header() header.setSortIndicator(0, Qt.SortOrder.AscendingOrder) header.setSortIndicatorShown(True) try: header.setSectionsClickable(True) except Exception: header.setClickable(True) header.resizeSection(0, 130) # variable column header.resizeSection(1, 180) # value column header.resizeSection(2, 50) # type column header.sortIndicatorChanged.connect(lambda *x: self.varModel.getMore()) self.__createPopupMenus() self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self.__showContextMenu) self.resortEnabled = True def showVariables(self, vlist, frmnr): """ Public method to show variables in a list. @param vlist the list of variables to be displayed. Each list entry is a tuple of three values. @type list @param frmnr frame number (0 is the current frame) @type int """ self.varModel.resetModifiedMarker() self.varModel.showVariables(vlist, frmnr) def showVariable(self, vlist): """ Public method to show variables in a list. @param vlist the list of subitems to be displayed. The first element gives the path of the parent variable. Each other list entry is a tuple of three values. @type list """ self.varModel.showVariables(vlist[1:], 0, vlist[0]) def handleResetUI(self): """ Public method to reset the VariablesViewer. """ self.varModel.clear(True) def verticalScrollbarValueChanged(self, value): """ Public slot informing about the scrollbar change. @param value current value of the vertical scrollbar @type int """ self.varModel.getMore() super().verticalScrollbarValueChanged(value) def resizeEvent(self, event): """ Protected slot informing about the widget size change. @param event information @type QResizeEvent """ self.varModel.getMore() super().resizeEvent(event) def __itemDoubleClicked(self, index): """ Private method called if an item was double clicked. @param index the double clicked item @type QModelIndex """ node = self.proxyModel.mapToSource(index).internalPointer() if node.hasChildren and index.column() == 0: state = self.isExpanded(index) self.setExpanded(index, not state) else: self.__showVariableDetails(index) def __mdlRequestExpand(self, modelIndex): """ Private method to inform the view about items to be expand. @param modelIndex the model index @type QModelIndex """ index = self.proxyModel.mapFromSource(modelIndex) self.expand(index) def __createPopupMenus(self): """ Private method to generate the popup menus. """ self.menu = QMenu() self.menu.addAction(self.tr("Show Details..."), self.__showDetails) self.menu.addSeparator() self.menu.addAction(self.tr("Expand"), self.__expandChildren) self.menu.addAction(self.tr("Collapse"), self.__collapseChildren) self.menu.addAction(self.tr("Collapse All"), self.collapseAll) self.menu.addSeparator() self.menu.addAction(self.tr("Refresh"), self.__refreshView) self.menu.addSeparator() self.menu.addAction(self.tr("Configure..."), self.__configure) self.menu.addAction(self.tr("Variables Type Filter..."), self.__configureFilter) self.backMenu = QMenu() self.backMenu.addAction(self.tr("Refresh"), self.__refreshView) self.backMenu.addSeparator() self.backMenu.addAction(self.tr("Configure..."), self.__configure) self.backMenu.addAction(self.tr("Variables Type Filter..."), self.__configureFilter) def __showContextMenu(self, coord): """ Private slot to show the context menu. @param coord the position of the mouse pointer @type QPoint """ gcoord = self.mapToGlobal(coord) if self.indexAt(coord).isValid(): self.menu.popup(gcoord) else: self.backMenu.popup(gcoord) def __expandChildren(self): """ Private slot to expand all child items of current parent. """ index = self.currentIndex() node = self.proxyModel.mapToSource(index).internalPointer() for child in node.children: if child.hasChildren: row = node.children.index(child) idx = self.varModel.createIndex(row, 0, child) idx = self.proxyModel.mapFromSource(idx) self.expand(idx) def __collapseChildren(self): """ Private slot to collapse all child items of current parent. """ index = self.currentIndex() node = self.proxyModel.mapToSource(index).internalPointer() for child in node.children: row = node.children.index(child) idx = self.varModel.createIndex(row, 0, child) idx = self.proxyModel.mapFromSource(idx) if self.isExpanded(idx): self.collapse(idx) def __refreshView(self): """ Private slot to refresh the view. """ if self.__globalScope: self.__debugViewer.setGlobalsFilter() else: self.__debugViewer.setLocalsFilter() def __showDetails(self): """ Private slot to show details about the selected variable. """ idx = self.currentIndex() self.__showVariableDetails(idx) def __showVariableDetails(self, index): """ Private method to show details about a variable. @param index reference to the variable item @type QModelIndex """ node = self.proxyModel.mapToSource(index).internalPointer() val = node.value vtype = node.type name = node.name par = node.parent nlist = [name] # build up the fully qualified name while par.parent is not None: pname = par.name if par.indicator: if nlist[0].endswith("."): nlist[0] = '[{0}].'.format(nlist[0][:-1]) else: nlist[0] = '[{0}]'.format(nlist[0]) nlist.insert(0, pname) else: if par.type == "django.MultiValueDict": nlist[0] = 'getlist({0})'.format(nlist[0]) elif par.type == "numpy.ndarray": if nlist and nlist[0][0].isalpha(): if nlist[0] in ["min", "max", "mean"]: nlist[0] = ".{0}()".format(nlist[0]) else: nlist[0] = ".{0}".format(nlist[0]) nlist.insert(0, pname) else: nlist.insert(0, '{0}.'.format(pname)) par = par.parent name = ''.join(nlist) # now show the dialog from .VariableDetailDialog import VariableDetailDialog dlg = VariableDetailDialog(name, vtype, val) dlg.exec() def __configure(self): """ Private method to open the configuration dialog. """ e5App().getObject("UserInterface").showPreferences( "debuggerGeneralPage") def __configureFilter(self): """ Private method to open the variables filter dialog. """ e5App().getObject("DebugUI").dbgFilterAct.triggered.emit() # # eflag: noqa = M822