--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/Debugger/VariablesViewer.py Thu Jul 07 11:23:56 2022 +0200 @@ -0,0 +1,1155 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the variables viewer view based on QTreeView. +""" + +import ast +import re +import contextlib + +from PyQt6.QtCore import ( + Qt, QAbstractItemModel, QModelIndex, QCoreApplication, + QSortFilterProxyModel, pyqtSignal +) +from PyQt6.QtGui import QBrush, QFontMetrics +from PyQt6.QtWidgets import QTreeView, QAbstractItemView, QToolTip, QMenu + +from EricWidgets.EricApplication import ericApp + +from .Config import ConfigVarTypeDispStrings + +import Preferences +import Utilities + +SORT_ROLE = Qt.ItemDataRole.UserRole + + +class VariableItem: + """ + Class implementing the data structure for all variable items. + """ + # 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") + + def __init__(self, parent, dvar, indicator, dtype, hasChildren, length, + dvalue): + """ + Constructor + + @param parent reference to the parent item + @type VariableItem + @param dvar variable name + @type str + @param indicator type indicator appended to the name + @type str + @param dtype type string + @type str + @param hasChildren indicator for children + @type bool + @param length length of the array or string (-1 if uninitialized + numpy.ndarray) + @type int + @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 = hasChildren + 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 = '' + vtype = ConfigVarTypeDispStrings.get(dtype, dtype) + self.type = QCoreApplication.translate("VariablesViewer", vtype) + self.indicator = indicator + self.value = dvalue + self.valueShort = None + self.tooltip = '' + + self.__getName(dvar) + self.__getValue(dtype, dvalue, indicator, length) + + 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 + asInt = int(dvar) + self.sort = "{0:06}".format(asInt) + except ValueError: + self.sort = dvar.lower() + + def __getValue(self, dtype, dvalue, indicator, length): + """ + 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 + @param indicator type indicator appended to the name + @type str + @param length length of the array or string (-1 if uninitialized + numpy.ndarray) + @type int or str + """ + length_code = length + if isinstance(length, str): + length = int(length.split('x')[0]) + + if indicator and length > -2: + self.childCount = max(0, length) # Update count if array + if dtype == 'numpy.ndarray' and length == -1: + self.value = VariableItem.unsized + else: + self.value = VariableItem.noOfItemsStr.format(length_code) + + if dtype != 'str': + self.valueShort = self.value + self.tooltip = str(self.value)[:256] + return + + if VariableItem.rx_nonprintable.search(dvalue) is None: + with contextlib.suppress(Exception): + dvalue = ast.literal_eval(dvalue) + + dvalue = str(dvalue) + self.value = dvalue + + if len(dvalue) > 2048: # 2 kB + self.tooltip = dvalue[:2048] + dvalue = QCoreApplication.translate( + "VariableItem", "<double click to show value>") + 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"), + True, 0, 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. + <ul> + <li>the variable name (string)</li> + <li>the variables type (string)</li> + <li>the variables value (string)</li> + </ul> + @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) + + # 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/column) + @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 = ericApp().getObject("DebugUI").variablesFilter( + self.__globalScope) + ericApp().getObject("DebugServer").remoteClientVariable( + ericApp().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( + """<b>The Global Variables Viewer Window</b>""" + """<p>This window displays the global variables""" + """ of the debugged program.</p>""" + )) + else: + self.setWindowTitle(self.tr("Local Variables")) + self.setWhatsThis(self.tr( + """<b>The Local Variables Viewer Window</b>""" + """<p>This window displays the local variables""" + """ of the debugged program.</p>""" + )) + + 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. + <ul> + <li>the variable name (string)</li> + <li>the variables type (string)</li> + <li>the variables value (string)</li> + </ul> + @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. + <ul> + <li>the variable name (string)</li> + <li>the variables type (string)</li> + <li>the variables value (string)</li> + </ul> + @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. + """ + ericApp().getObject("UserInterface").showPreferences( + "debuggerGeneralPage") + + def __configureFilter(self): + """ + Private method to open the variables filter dialog. + """ + ericApp().getObject("DebugUI").dbgFilterAct.triggered.emit() + +# +# eflag: noqa = M822