src/eric7/Debugger/VariablesViewer.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the variables viewer view based on QTreeView.
8 """
9
10 import ast
11 import re
12 import contextlib
13
14 from PyQt6.QtCore import (
15 Qt, QAbstractItemModel, QModelIndex, QCoreApplication,
16 QSortFilterProxyModel, pyqtSignal
17 )
18 from PyQt6.QtGui import QBrush, QFontMetrics
19 from PyQt6.QtWidgets import QTreeView, QAbstractItemView, QToolTip, QMenu
20
21 from EricWidgets.EricApplication import ericApp
22
23 from .Config import ConfigVarTypeDispStrings
24
25 import Preferences
26 import Utilities
27
28 SORT_ROLE = Qt.ItemDataRole.UserRole
29
30
31 class VariableItem:
32 """
33 Class implementing the data structure for all variable items.
34 """
35 # Initialize regular expression for unprintable strings
36 rx_nonprintable = re.compile(r"""(\\x\d\d)+""")
37
38 noOfItemsStr = QCoreApplication.translate("VariablesViewer", "{0} items")
39 unsized = QCoreApplication.translate("VariablesViewer", "unsized")
40
41 def __init__(self, parent, dvar, indicator, dtype, hasChildren, length,
42 dvalue):
43 """
44 Constructor
45
46 @param parent reference to the parent item
47 @type VariableItem
48 @param dvar variable name
49 @type str
50 @param indicator type indicator appended to the name
51 @type str
52 @param dtype type string
53 @type str
54 @param hasChildren indicator for children
55 @type bool
56 @param length length of the array or string (-1 if uninitialized
57 numpy.ndarray)
58 @type int
59 @param dvalue value string
60 @type str
61 """
62 self.parent = parent
63 # Take the additional methods into account for childCount
64 self.methodCount = 0
65 self.childCount = 0
66 self.currentCount = -1 # -1 indicates to (re)load children
67 # Indicator that there are children
68 self.hasChildren = hasChildren
69 self.populated = False
70 # Indicator that item was at least once fully populated
71 self.wasPopulated = False
72
73 self.children = []
74 # Flag to prevent endless reloading of current item while waiting on
75 # a response from debugger
76 self.pendigFetch = False
77
78 # Set of child items, which are displayed the first time or changed
79 self.newItems = set()
80 self.changedItems = set()
81 # Name including its ID if it's a dict, set, etc.
82 self.nameWithId = dvar
83
84 self.name = ''
85 self.sort = ''
86 vtype = ConfigVarTypeDispStrings.get(dtype, dtype)
87 self.type = QCoreApplication.translate("VariablesViewer", vtype)
88 self.indicator = indicator
89 self.value = dvalue
90 self.valueShort = None
91 self.tooltip = ''
92
93 self.__getName(dvar)
94 self.__getValue(dtype, dvalue, indicator, length)
95
96 def __getName(self, dvar):
97 """
98 Private method to extract the variable name.
99
100 @param dvar name of variable maybe with ID
101 @type str
102 """
103 try:
104 idx = dvar.index(" (ID:")
105 dvar = dvar[:idx]
106 except AttributeError:
107 idx = dvar
108 dvar = str(dvar)
109 except ValueError:
110 pass
111
112 self.name = dvar
113 try:
114 # Convert numbers to strings with preceding zeros
115 asInt = int(dvar)
116 self.sort = "{0:06}".format(asInt)
117 except ValueError:
118 self.sort = dvar.lower()
119
120 def __getValue(self, dtype, dvalue, indicator, length):
121 """
122 Private method to process the variables value.
123
124 Define and limit value, set tooltip text. If type is known to have
125 children, the corresponding flag is set.
126
127 @param dtype type string
128 @type str
129 @param dvalue value of variable encoded as utf-8
130 @type str
131 @param indicator type indicator appended to the name
132 @type str
133 @param length length of the array or string (-1 if uninitialized
134 numpy.ndarray)
135 @type int or str
136 """
137 length_code = length
138 if isinstance(length, str):
139 length = int(length.split('x')[0])
140
141 if indicator and length > -2:
142 self.childCount = max(0, length) # Update count if array
143 if dtype == 'numpy.ndarray' and length == -1:
144 self.value = VariableItem.unsized
145 else:
146 self.value = VariableItem.noOfItemsStr.format(length_code)
147
148 if dtype != 'str':
149 self.valueShort = self.value
150 self.tooltip = str(self.value)[:256]
151 return
152
153 if VariableItem.rx_nonprintable.search(dvalue) is None:
154 with contextlib.suppress(Exception):
155 dvalue = ast.literal_eval(dvalue)
156
157 dvalue = str(dvalue)
158 self.value = dvalue
159
160 if len(dvalue) > 2048: # 2 kB
161 self.tooltip = dvalue[:2048]
162 dvalue = QCoreApplication.translate(
163 "VariableItem", "<double click to show value>")
164 else:
165 self.tooltip = dvalue
166
167 lines = dvalue[:2048].splitlines()
168 if len(lines) > 1:
169 # only show the first non-empty line;
170 # indicate skipped lines by <...> at the
171 # beginning and/or end
172 index = 0
173 while index < len(lines) - 1 and lines[index].strip(' \t') == "":
174 index += 1
175
176 dvalue = ""
177 if index > 0:
178 dvalue += "<...>"
179 dvalue += lines[index]
180 if index < len(lines) - 1 or len(dvalue) > 2048:
181 dvalue += "<...>"
182
183 self.valueShort = dvalue
184
185 @property
186 def absolutCount(self):
187 """
188 Public property to get the total number of children.
189
190 @return total number of children
191 @rtype int
192 """
193 return self.childCount + self.methodCount
194
195
196 class VariablesModel(QAbstractItemModel):
197 """
198 Class implementing the data model for QTreeView.
199
200 @signal expand trigger QTreeView to expand given index
201 """
202 expand = pyqtSignal(QModelIndex)
203
204 def __init__(self, treeView, globalScope):
205 """
206 Constructor
207
208 @param treeView QTreeView showing the data
209 @type VariablesViewer
210 @param globalScope flag indicating global (True) or local (False)
211 variables
212 @type bool
213 """
214 super().__init__()
215 self.treeView = treeView
216 self.proxyModel = treeView.proxyModel
217
218 self.framenr = -1
219 self.openItems = []
220 self.closedItems = []
221
222 visibility = self.tr("Globals") if globalScope else self.tr("Locals")
223 self.rootNode = VariableItem(None, visibility, '', self.tr("Type"),
224 True, 0, self.tr("Value"))
225
226 self.__globalScope = globalScope
227
228 def clear(self, reset=False):
229 """
230 Public method to clear the complete data model.
231
232 @param reset flag to clear the expanded keys also
233 @type bool
234 """
235 self.beginResetModel()
236 self.rootNode.children = []
237 self.rootNode.newItems.clear()
238 self.rootNode.changedItems.clear()
239 self.rootNode.wasPopulated = False
240 if reset:
241 self.openItems = []
242 self.closedItems = []
243 self.endResetModel()
244
245 def __findVariable(self, pathlist):
246 """
247 Private method to get to the given variable.
248
249 @param pathlist full path to the variable
250 @type list of str
251 @return the found variable or None if it doesn't exist
252 @rtype VariableItem or None
253 """
254 node = self.rootNode
255
256 for childName in pathlist or []:
257 for item in node.children:
258 if item.nameWithId == childName:
259 node = item
260 break
261 else:
262 return None
263
264 return node # __IGNORE_WARNING_M834__
265
266 def showVariables(self, vlist, frmnr, pathlist=None):
267 """
268 Public method to update the data model of variable in pathlist.
269
270 @param vlist the list of variables to be displayed. Each
271 list entry is a tuple of three values.
272 <ul>
273 <li>the variable name (string)</li>
274 <li>the variables type (string)</li>
275 <li>the variables value (string)</li>
276 </ul>
277 @type list of str
278 @param frmnr frame number (0 is the current frame)
279 @type int
280 @param pathlist full path to the variable
281 @type list of str
282 """
283 if pathlist:
284 itemStartIndex = pathlist.pop(0)
285 else:
286 itemStartIndex = -1
287 if self.framenr != frmnr:
288 self.clear()
289 self.framenr = frmnr
290
291 parent = self.__findVariable(pathlist)
292 if parent is None:
293 return
294
295 parent.pendigFetch = False
296
297 if parent == self.rootNode:
298 parentIdx = QModelIndex()
299 parent.methodCount = len(vlist)
300 else:
301 row = parent.parent.children.index(parent)
302 parentIdx = self.createIndex(row, 0, parent)
303
304 if itemStartIndex == -3:
305 # Item doesn't exist any more
306 parentIdx = self.parent(parentIdx)
307 self.beginRemoveRows(parentIdx, row, row)
308 del parent.parent.children[row]
309 self.endRemoveRows()
310 parent.parent.childCount -= 1
311 return
312
313 elif itemStartIndex == -2:
314 parent.wasPopulated = True
315 parent.currentCount = parent.absolutCount
316 parent.populated = True
317 # Remove items which are left over at the end of child list
318 self.__cleanupParentList(parent, parentIdx)
319 return
320
321 elif itemStartIndex == -1:
322 parent.methodCount = len(vlist)
323 idx = max(parent.currentCount, 0)
324 parent.currentCount = idx + len(vlist)
325 parent.populated = True
326 else:
327 idx = itemStartIndex
328 parent.currentCount = idx + len(vlist)
329
330 # Now update the table
331 endIndex = idx + len(vlist)
332 newChild = None
333 knownChildrenCount = len(parent.children)
334 while idx < endIndex:
335 # Fetch next old item from last cycle
336 try:
337 child = parent.children[idx]
338 except IndexError:
339 child = None
340
341 # Fetch possible new item
342 if not newChild and vlist:
343 newChild = vlist.pop(0)
344
345 # Process parameters of new item
346 newItem = VariableItem(parent, *newChild)
347 sort = newItem.sort
348
349 # Append or insert before already existing item
350 if child is None or newChild and sort < child.sort:
351 self.beginInsertRows(parentIdx, idx, idx)
352 parent.children.insert(idx, newItem)
353 if knownChildrenCount <= idx and not parent.wasPopulated:
354 parent.newItems.add(newItem)
355 knownChildrenCount += 1
356 else:
357 parent.changedItems.add(newItem)
358 self.endInsertRows()
359
360 idx += 1
361 newChild = None
362 continue
363
364 # Check if same name, type and afterwards value
365 elif sort == child.sort and child.type == newItem.type:
366 # Check if value has changed
367 if child.value != newItem.value:
368 child.value = newItem.value
369 child.valueShort = newItem.valueShort
370 child.tooltip = newItem.tooltip
371 child.nameWithId = newItem.nameWithId
372
373 child.currentCount = -1
374 child.populated = False
375 child.childCount = newItem.childCount
376
377 # Highlight item because it has changed
378 parent.changedItems.add(child)
379
380 changedIndexStart = self.index(idx, 0, parentIdx)
381 changedIndexEnd = self.index(idx, 2, parentIdx)
382 self.dataChanged.emit(changedIndexStart, changedIndexEnd)
383
384 newChild = None
385 idx += 1
386 continue
387
388 # Remove obsolete item
389 self.beginRemoveRows(parentIdx, idx, idx)
390 parent.children.remove(child)
391 self.endRemoveRows()
392 # idx stay unchanged
393 knownChildrenCount -= 1
394
395 # Remove items which are left over at the end of child list
396 if itemStartIndex == -1:
397 parent.wasPopulated = True
398 self.__cleanupParentList(parent, parentIdx)
399
400 # Request data for any expanded node
401 self.getMore()
402
403 def __cleanupParentList(self, parent, parentIdx):
404 """
405 Private method to remove items which are left over at the end of the
406 child list.
407
408 @param parent to clean up
409 @type VariableItem
410 @param parentIdx the parent index as QModelIndex
411 @type QModelIndex
412 """
413 end = len(parent.children)
414 if end > parent.absolutCount:
415 self.beginRemoveRows(parentIdx, parent.absolutCount, end)
416 del parent.children[parent.absolutCount:]
417 self.endRemoveRows()
418
419 def resetModifiedMarker(self, parentIdx=QModelIndex(), pathlist=()):
420 """
421 Public method to remove the modified marker from changed items.
422
423 @param parentIdx item to reset marker
424 @type QModelIndex
425 @param pathlist full path to the variable
426 @type list of str
427 """
428 parent = (parentIdx.internalPointer() if parentIdx.isValid()
429 else self.rootNode)
430
431 parent.newItems.clear()
432 parent.changedItems.clear()
433
434 pll = len(pathlist)
435 posPaths = {x for x in self.openItems if len(x) > pll}
436 posPaths |= {x for x in self.closedItems if len(x) > pll}
437 posPaths = {x[pll] for x in posPaths if x[:pll] == pathlist}
438
439 if posPaths:
440 for child in parent.children:
441 if (
442 child.hasChildren and
443 child.nameWithId in posPaths and
444 child.currentCount >= 0
445 ):
446 # Discard loaded elements and refresh if still expanded
447 child.currentCount = -1
448 child.populated = False
449 row = parent.children.index(child)
450 newParentIdx = self.index(row, 0, parentIdx)
451 self.resetModifiedMarker(
452 newParentIdx, pathlist + (child.nameWithId,))
453
454 self.closedItems = []
455
456 # Little quirk: Refresh all visible items to clear the changed marker
457 if parentIdx == QModelIndex():
458 self.rootNode.currentCount = -1
459 self.rootNode.populated = False
460 idxStart = self.index(0, 0, QModelIndex())
461 idxEnd = self.index(0, 2, QModelIndex())
462 self.dataChanged.emit(idxStart, idxEnd)
463
464 def columnCount(self, parent=QModelIndex()):
465 """
466 Public method to get the column count.
467
468 @param parent the model parent
469 @type QModelIndex
470 @return number of columns
471 @rtype int
472 """
473 return 3
474
475 def rowCount(self, parent=QModelIndex()):
476 """
477 Public method to get the row count.
478
479 @param parent the model parent
480 @type QModelIndex
481 @return number of rows
482 @rtype int
483 """
484 node = parent.internalPointer() if parent.isValid() else self.rootNode
485
486 return len(node.children)
487
488 def flags(self, index):
489 """
490 Public method to get the item flags.
491
492 @param index of item
493 @type QModelIndex
494 @return item flags
495 @rtype QtCore.Qt.ItemFlag
496 """
497 if not index.isValid():
498 return Qt.ItemFlag.NoItemFlags
499
500 return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable
501
502 def hasChildren(self, parent=QModelIndex()):
503 """
504 Public method to get a flag if parent has children.
505
506 @param parent the model parent
507 @type QModelIndex
508 @return flag indicating parent has children
509 @rtype bool
510 """
511 if not parent.isValid():
512 return self.rootNode.children != []
513
514 return parent.internalPointer().hasChildren
515
516 def index(self, row, column, parent=QModelIndex()):
517 """
518 Public method to get the index of item at row:column of parent.
519
520 @param row number of rows
521 @type int
522 @param column number of columns
523 @type int
524 @param parent the model parent
525 @type QModelIndex
526 @return new model index for child
527 @rtype QModelIndex
528 """
529 if not self.hasIndex(row, column, parent):
530 return QModelIndex()
531
532 node = parent.internalPointer() if parent.isValid() else self.rootNode
533
534 return self.createIndex(row, column, node.children[row])
535
536 def parent(self, child):
537 """
538 Public method to get the parent of the given child.
539
540 @param child the model child node
541 @type QModelIndex
542 @return new model index for parent
543 @rtype QModelIndex
544 """
545 if not child.isValid():
546 return QModelIndex()
547
548 childNode = child.internalPointer()
549 if childNode == self.rootNode:
550 return QModelIndex()
551
552 parentNode = childNode.parent
553
554 if parentNode == self.rootNode:
555 return QModelIndex()
556
557 row = parentNode.parent.children.index(parentNode)
558 return self.createIndex(row, 0, parentNode)
559
560 def data(self, index, role=Qt.ItemDataRole.DisplayRole):
561 """
562 Public method get the role data of item.
563
564 @param index the model index
565 @type QModelIndex
566 @param role the requested data role
567 @type QtCore.Qt.ItemDataRole
568 @return role data of item
569 @rtype Any
570 """
571 if not index.isValid() or index.row() < 0:
572 return None
573
574 node = index.internalPointer()
575 column = index.column()
576
577 if role in (
578 Qt.ItemDataRole.DisplayRole, SORT_ROLE, Qt.ItemDataRole.EditRole
579 ):
580 try:
581 if column == 0:
582 # Sort first column with values from third column
583 if role == SORT_ROLE:
584 return node.sort
585 return node.name + node.indicator
586 else:
587 return {
588 1: node.valueShort,
589 2: node.type,
590 3: node.sort
591 }.get(column)
592 except AttributeError:
593 return ('None', '', '', '')[column]
594
595 elif role == Qt.ItemDataRole.BackgroundRole:
596 if node in node.parent.changedItems:
597 return self.__bgColorChanged
598 elif node in node.parent.newItems:
599 return self.__bgColorNew
600
601 elif role == Qt.ItemDataRole.ToolTipRole:
602 if column == 0:
603 tooltip = node.name + node.indicator
604 elif column == 1:
605 tooltip = node.tooltip
606 elif column == 2:
607 tooltip = node.type
608 elif column == 3:
609 tooltip = node.sort
610 else:
611 return None
612
613 if Qt.mightBeRichText(tooltip):
614 tooltip = Utilities.html_encode(tooltip)
615
616 if column == 0:
617 indentation = self.treeView.indentation()
618 indentCount = 0
619 currentNode = node
620 while currentNode.parent:
621 indentCount += 1
622 currentNode = currentNode.parent
623
624 indentation *= indentCount
625 else:
626 indentation = 0
627 # Check if text is longer than available space
628 fontMetrics = QFontMetrics(self.treeView.font())
629 try:
630 textSize = fontMetrics.horizontalAdvance(tooltip)
631 except AttributeError:
632 textSize = fontMetrics.width(tooltip)
633 textSize += indentation + 5 # How to determine border size?
634 header = self.treeView.header()
635 if textSize >= header.sectionSize(column):
636 return tooltip
637 else:
638 QToolTip.hideText()
639
640 return None
641
642 def headerData(self, section, orientation,
643 role=Qt.ItemDataRole.DisplayRole):
644 """
645 Public method get the header names.
646
647 @param section the header section (row/column)
648 @type int
649 @param orientation the header's orientation
650 @type QtCore.Qt.Orientation
651 @param role the requested data role
652 @type QtCore.Qt.ItemDataRole
653 @return header name
654 @rtype str or None
655 """
656 if (
657 role != Qt.ItemDataRole.DisplayRole or
658 orientation != Qt.Orientation.Horizontal
659 ):
660 return None
661
662 return {
663 0: self.rootNode.name,
664 1: self.rootNode.value,
665 2: self.rootNode.type,
666 3: self.rootNode.sort
667 }.get(section)
668
669 def __findPendingItem(self, parent=None, pathlist=()):
670 """
671 Private method to find the next item to request data from debugger.
672
673 @param parent the model parent
674 @type VariableItem
675 @param pathlist full path to the variable
676 @type list of str
677 @return next item index to request data from debugger
678 @rtype QModelIndex
679 """
680 if parent is None:
681 parent = self.rootNode
682
683 for child in parent.children:
684 if not child.hasChildren:
685 continue
686
687 if pathlist + (child.nameWithId,) in self.openItems:
688 if child.populated:
689 index = None
690 else:
691 idx = parent.children.index(child)
692 index = self.createIndex(idx, 0, child)
693 self.expand.emit(index)
694
695 if child.currentCount < 0:
696 return index
697
698 possibleIndex = self.__findPendingItem(
699 child, pathlist + (child.nameWithId,))
700
701 if (possibleIndex or index) is None:
702 continue
703
704 return possibleIndex or index
705
706 return None
707
708 def getMore(self):
709 """
710 Public method to fetch the next variable from debugger.
711 """
712 # step 1: find expanded but not populated items
713 item = self.__findPendingItem()
714 if not item or not item.isValid():
715 return
716
717 # step 2: check if data has to be retrieved
718 node = item.internalPointer()
719 lastVisibleItem = self.index(node.currentCount - 1, 0, item)
720 lastVisibleItem = self.proxyModel.mapFromSource(lastVisibleItem)
721 rect = self.treeView.visualRect(lastVisibleItem)
722 if rect.y() > self.treeView.height() or node.pendigFetch:
723 return
724
725 node.pendigFetch = True
726 # step 3: get a pathlist up to the requested variable
727 pathlist = self.__buildTreePath(node)
728 # step 4: request the variable from the debugger
729 variablesFilter = ericApp().getObject("DebugUI").variablesFilter(
730 self.__globalScope)
731 ericApp().getObject("DebugServer").remoteClientVariable(
732 ericApp().getObject("DebugUI").getSelectedDebuggerId(),
733 self.__globalScope, variablesFilter, pathlist, self.framenr)
734
735 def setExpanded(self, index, state):
736 """
737 Public method to set the expanded state of item.
738
739 @param index item to change expanded state
740 @type QModelIndex
741 @param state state of the item
742 @type bool
743 """
744 node = index.internalPointer()
745 pathlist = self.__buildTreePath(node)
746 if state:
747 if pathlist not in self.openItems:
748 self.openItems.append(pathlist)
749 if pathlist in self.closedItems:
750 self.closedItems.remove(pathlist)
751 self.getMore()
752 else:
753 if pathlist in self.openItems:
754 self.openItems.remove(pathlist)
755 self.closedItems.append(pathlist)
756
757 def __buildTreePath(self, parent):
758 """
759 Private method to build up a path from the root to parent.
760
761 @param parent item to build the path for
762 @type VariableItem
763 @return list of names denoting the path from the root
764 @rtype tuple of str
765 """
766 pathlist = []
767
768 # build up a path from the top to the item
769 while parent.parent:
770 pathlist.append(parent.nameWithId)
771 parent = parent.parent
772
773 pathlist.reverse()
774 return tuple(pathlist)
775
776 def handlePreferencesChanged(self):
777 """
778 Public slot to handle the preferencesChanged signal.
779 """
780 self.__bgColorNew = QBrush(Preferences.getDebugger("BgColorNew"))
781 self.__bgColorChanged = QBrush(
782 Preferences.getDebugger("BgColorChanged"))
783
784 idxStart = self.index(0, 0, QModelIndex())
785 idxEnd = self.index(0, 2, QModelIndex())
786 self.dataChanged.emit(idxStart, idxEnd)
787
788
789 class VariablesProxyModel(QSortFilterProxyModel):
790 """
791 Class for handling the sort operations.
792 """
793 def __init__(self, parent=None):
794 """
795 Constructor
796
797 @param parent the parent model index
798 @type QModelIndex
799 """
800 super().__init__(parent)
801 self.setSortRole(SORT_ROLE)
802
803 def hasChildren(self, parent):
804 """
805 Public method to get a flag if parent has children.
806
807 The given model index has to be transformed to the underlying source
808 model to get the correct result.
809
810 @param parent the model parent
811 @type QModelIndex
812 @return flag if parent has children
813 @rtype bool
814 """
815 return self.sourceModel().hasChildren(self.mapToSource(parent))
816
817 def setExpanded(self, index, state):
818 """
819 Public slot to get a flag if parent has children.
820
821 The given model index has to be transformed to the underlying source
822 model to get the correct result.
823 @param index item to change expanded state
824 @type QModelIndex
825 @param state state of the item
826 @type bool
827 """
828 self.sourceModel().setExpanded(self.mapToSource(index), state)
829
830
831 class VariablesViewer(QTreeView):
832 """
833 Class implementing the variables viewer view.
834
835 This view is used to display the variables of the program being
836 debugged in a tree. Compound types will be shown with
837 their main entry first. Once the subtree has been expanded, the
838 individual entries will be shown. Double clicking an entry will
839 expand or collapse the item, if it has children and the double click
840 was performed on the first column of the tree, otherwise it'll
841 popup a dialog showing the variables parameters in a more readable
842 form. This is especially useful for lengthy strings.
843
844 This view has two modes for displaying the global and the local
845 variables.
846
847 @signal preferencesChanged() to inform model about new background colours
848 """
849 preferencesChanged = pyqtSignal()
850
851 def __init__(self, viewer, globalScope, parent=None):
852 """
853 Constructor
854
855 @param viewer reference to the debug viewer object
856 @type DebugViewer
857 @param globalScope flag indicating global (True) or local (False)
858 variables
859 @type bool
860 @param parent the parent
861 @type QWidget
862 """
863 super().__init__(parent)
864
865 self.__debugViewer = viewer
866 self.__globalScope = globalScope
867 self.framenr = 0
868
869 # Massive performance gain
870 self.setUniformRowHeights(True)
871
872 # Implements sorting and filtering
873 self.proxyModel = VariablesProxyModel()
874 # Variable model implements the underlying data model
875 self.varModel = VariablesModel(self, globalScope)
876 self.proxyModel.setSourceModel(self.varModel)
877 self.setModel(self.proxyModel)
878 self.preferencesChanged.connect(self.varModel.handlePreferencesChanged)
879 self.preferencesChanged.emit() # Force initialization of colors
880
881 self.expanded.connect(
882 lambda idx: self.proxyModel.setExpanded(idx, True))
883 self.collapsed.connect(
884 lambda idx: self.proxyModel.setExpanded(idx, False))
885
886 self.setExpandsOnDoubleClick(False)
887 self.doubleClicked.connect(self.__itemDoubleClicked)
888
889 self.varModel.expand.connect(self.__mdlRequestExpand)
890
891 self.setSortingEnabled(True)
892 self.setAlternatingRowColors(True)
893 self.setSelectionBehavior(
894 QAbstractItemView.SelectionBehavior.SelectRows)
895
896 if self.__globalScope:
897 self.setWindowTitle(self.tr("Global Variables"))
898 self.setWhatsThis(self.tr(
899 """<b>The Global Variables Viewer Window</b>"""
900 """<p>This window displays the global variables"""
901 """ of the debugged program.</p>"""
902 ))
903 else:
904 self.setWindowTitle(self.tr("Local Variables"))
905 self.setWhatsThis(self.tr(
906 """<b>The Local Variables Viewer Window</b>"""
907 """<p>This window displays the local variables"""
908 """ of the debugged program.</p>"""
909 ))
910
911 header = self.header()
912 header.setSortIndicator(0, Qt.SortOrder.AscendingOrder)
913 header.setSortIndicatorShown(True)
914
915 try:
916 header.setSectionsClickable(True)
917 except Exception:
918 header.setClickable(True)
919
920 header.resizeSection(0, 130) # variable column
921 header.resizeSection(1, 180) # value column
922 header.resizeSection(2, 50) # type column
923
924 header.sortIndicatorChanged.connect(lambda *x: self.varModel.getMore())
925
926 self.__createPopupMenus()
927 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
928 self.customContextMenuRequested.connect(self.__showContextMenu)
929
930 self.resortEnabled = True
931
932 def showVariables(self, vlist, frmnr):
933 """
934 Public method to show variables in a list.
935
936 @param vlist the list of variables to be displayed. Each
937 list entry is a tuple of three values.
938 <ul>
939 <li>the variable name (string)</li>
940 <li>the variables type (string)</li>
941 <li>the variables value (string)</li>
942 </ul>
943 @type list
944 @param frmnr frame number (0 is the current frame)
945 @type int
946 """
947 self.varModel.resetModifiedMarker()
948 self.varModel.showVariables(vlist, frmnr)
949
950 def showVariable(self, vlist):
951 """
952 Public method to show variables in a list.
953
954 @param vlist the list of subitems to be displayed.
955 The first element gives the path of the
956 parent variable. Each other list entry is
957 a tuple of three values.
958 <ul>
959 <li>the variable name (string)</li>
960 <li>the variables type (string)</li>
961 <li>the variables value (string)</li>
962 </ul>
963 @type list
964 """
965 self.varModel.showVariables(vlist[1:], 0, vlist[0])
966
967 def handleResetUI(self):
968 """
969 Public method to reset the VariablesViewer.
970 """
971 self.varModel.clear(True)
972
973 def verticalScrollbarValueChanged(self, value):
974 """
975 Public slot informing about the scrollbar change.
976
977 @param value current value of the vertical scrollbar
978 @type int
979 """
980 self.varModel.getMore()
981 super().verticalScrollbarValueChanged(value)
982
983 def resizeEvent(self, event):
984 """
985 Protected slot informing about the widget size change.
986
987 @param event information
988 @type QResizeEvent
989 """
990 self.varModel.getMore()
991 super().resizeEvent(event)
992
993 def __itemDoubleClicked(self, index):
994 """
995 Private method called if an item was double clicked.
996
997 @param index the double clicked item
998 @type QModelIndex
999 """
1000 node = self.proxyModel.mapToSource(index).internalPointer()
1001 if node.hasChildren and index.column() == 0:
1002 state = self.isExpanded(index)
1003 self.setExpanded(index, not state)
1004 else:
1005 self.__showVariableDetails(index)
1006
1007 def __mdlRequestExpand(self, modelIndex):
1008 """
1009 Private method to inform the view about items to be expand.
1010
1011 @param modelIndex the model index
1012 @type QModelIndex
1013 """
1014 index = self.proxyModel.mapFromSource(modelIndex)
1015 self.expand(index)
1016
1017 def __createPopupMenus(self):
1018 """
1019 Private method to generate the popup menus.
1020 """
1021 self.menu = QMenu()
1022 self.menu.addAction(self.tr("Show Details..."), self.__showDetails)
1023 self.menu.addSeparator()
1024 self.menu.addAction(self.tr("Expand"), self.__expandChildren)
1025 self.menu.addAction(self.tr("Collapse"), self.__collapseChildren)
1026 self.menu.addAction(self.tr("Collapse All"), self.collapseAll)
1027 self.menu.addSeparator()
1028 self.menu.addAction(self.tr("Refresh"), self.__refreshView)
1029 self.menu.addSeparator()
1030 self.menu.addAction(self.tr("Configure..."), self.__configure)
1031 self.menu.addAction(self.tr("Variables Type Filter..."),
1032 self.__configureFilter)
1033
1034 self.backMenu = QMenu()
1035 self.backMenu.addAction(self.tr("Refresh"), self.__refreshView)
1036 self.backMenu.addSeparator()
1037 self.backMenu.addAction(self.tr("Configure..."), self.__configure)
1038 self.backMenu.addAction(self.tr("Variables Type Filter..."),
1039 self.__configureFilter)
1040
1041 def __showContextMenu(self, coord):
1042 """
1043 Private slot to show the context menu.
1044
1045 @param coord the position of the mouse pointer
1046 @type QPoint
1047 """
1048 gcoord = self.mapToGlobal(coord)
1049 if self.indexAt(coord).isValid():
1050 self.menu.popup(gcoord)
1051 else:
1052 self.backMenu.popup(gcoord)
1053
1054 def __expandChildren(self):
1055 """
1056 Private slot to expand all child items of current parent.
1057 """
1058 index = self.currentIndex()
1059 node = self.proxyModel.mapToSource(index).internalPointer()
1060 for child in node.children:
1061 if child.hasChildren:
1062 row = node.children.index(child)
1063 idx = self.varModel.createIndex(row, 0, child)
1064 idx = self.proxyModel.mapFromSource(idx)
1065 self.expand(idx)
1066
1067 def __collapseChildren(self):
1068 """
1069 Private slot to collapse all child items of current parent.
1070 """
1071 index = self.currentIndex()
1072 node = self.proxyModel.mapToSource(index).internalPointer()
1073 for child in node.children:
1074 row = node.children.index(child)
1075 idx = self.varModel.createIndex(row, 0, child)
1076 idx = self.proxyModel.mapFromSource(idx)
1077 if self.isExpanded(idx):
1078 self.collapse(idx)
1079
1080 def __refreshView(self):
1081 """
1082 Private slot to refresh the view.
1083 """
1084 if self.__globalScope:
1085 self.__debugViewer.setGlobalsFilter()
1086 else:
1087 self.__debugViewer.setLocalsFilter()
1088
1089 def __showDetails(self):
1090 """
1091 Private slot to show details about the selected variable.
1092 """
1093 idx = self.currentIndex()
1094 self.__showVariableDetails(idx)
1095
1096 def __showVariableDetails(self, index):
1097 """
1098 Private method to show details about a variable.
1099
1100 @param index reference to the variable item
1101 @type QModelIndex
1102 """
1103 node = self.proxyModel.mapToSource(index).internalPointer()
1104
1105 val = node.value
1106 vtype = node.type
1107 name = node.name
1108
1109 par = node.parent
1110 nlist = [name]
1111
1112 # build up the fully qualified name
1113 while par.parent is not None:
1114 pname = par.name
1115 if par.indicator:
1116 if nlist[0].endswith("."):
1117 nlist[0] = '[{0}].'.format(nlist[0][:-1])
1118 else:
1119 nlist[0] = '[{0}]'.format(nlist[0])
1120 nlist.insert(0, pname)
1121 else:
1122 if par.type == "django.MultiValueDict":
1123 nlist[0] = 'getlist({0})'.format(nlist[0])
1124 elif par.type == "numpy.ndarray":
1125 if nlist and nlist[0][0].isalpha():
1126 if nlist[0] in ["min", "max", "mean"]:
1127 nlist[0] = ".{0}()".format(nlist[0])
1128 else:
1129 nlist[0] = ".{0}".format(nlist[0])
1130 nlist.insert(0, pname)
1131 else:
1132 nlist.insert(0, '{0}.'.format(pname))
1133 par = par.parent
1134
1135 name = ''.join(nlist)
1136 # now show the dialog
1137 from .VariableDetailDialog import VariableDetailDialog
1138 dlg = VariableDetailDialog(name, vtype, val)
1139 dlg.exec()
1140
1141 def __configure(self):
1142 """
1143 Private method to open the configuration dialog.
1144 """
1145 ericApp().getObject("UserInterface").showPreferences(
1146 "debuggerGeneralPage")
1147
1148 def __configureFilter(self):
1149 """
1150 Private method to open the variables filter dialog.
1151 """
1152 ericApp().getObject("DebugUI").dbgFilterAct.triggered.emit()
1153
1154 #
1155 # eflag: noqa = M822

eric ide

mercurial