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