Debugger/VariablesViewer.py

changeset 0
de9c2efb9d02
child 7
c679fb30c8f3
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the variables viewer widget.
8 """
9
10 import types
11 from math import log10
12 import sys
13
14 from PyQt4.QtCore import *
15 from PyQt4.QtGui import *
16
17 from E4Gui.E4Application import e4App
18
19 from Config import ConfigVarTypeDispStrings, ConfigVarTypeStrings
20 from VariableDetailDialog import VariableDetailDialog
21 from Utilities import toUnicode
22
23 import Preferences
24
25
26 class VariableItem(QTreeWidgetItem):
27 """
28 Class implementing the data structure for variable items.
29 """
30 def __init__(self, parent, dvar, dvalue, dtype):
31 """
32 Constructor.
33
34 @param parent reference to the parent item
35 @param dvar variable name (string)
36 @param dvalue value string (string)
37 @param dtype type string (string)
38 """
39 self.value = dvalue
40 if len(dvalue) > 2048: # 1024 * 2
41 dvalue = QApplication.translate("VariableItem",
42 "<double click to show value>")
43
44 QTreeWidgetItem.__init__(self, parent, [dvar, dvalue, dtype])
45
46 self.populated = True
47
48 def getValue(self):
49 """
50 Public method to return the value of the item.
51
52 @return value of the item (string)
53 """
54 return self.value
55
56 def data(self, column, role):
57 """
58 Public method to return the data for the requested role.
59
60 This implementation changes the original behavior in a way, that the display
61 data is returned as the tooltip for column 1.
62
63 @param column column number (integer)
64 @param role data role (Qt.ItemDataRole)
65 @return requested data (QVariant)
66 """
67 if column == 1 and role == Qt.ToolTipRole:
68 role = Qt.DisplayRole
69 return QTreeWidgetItem.data(self, column, role)
70
71 def attachDummy(self):
72 """
73 Public method to attach a dummy sub item to allow for lazy population.
74 """
75 QTreeWidgetItem(self, ["DUMMY"])
76
77 def deleteChildren(self):
78 """
79 Public method to delete all children (cleaning the subtree).
80 """
81 for itm in self.takeChildren():
82 del itm
83
84 def key(self, column):
85 """
86 Public method generating the key for this item.
87
88 @param column the column to sort on (integer)
89 @return text of the column (string)
90 """
91 return self.text(column)
92
93 def __lt__(self, other):
94 """
95 Public method to check, if the item is less than the other one.
96
97 @param other reference to item to compare against (QTreeWidgetItem)
98 @return true, if this item is less than other (boolean)
99 """
100 column = self.treeWidget().sortColumn()
101 return self.key(column) < other.key(column)
102
103 def expand(self):
104 """
105 Public method to expand the item.
106
107 Note: This is just a do nothing and should be overwritten.
108 """
109 return
110
111 def collapse(self):
112 """
113 Public method to collapse the item.
114
115 Note: This is just a do nothing and should be overwritten.
116 """
117 return
118
119 class SpecialVarItem(VariableItem):
120 """
121 Class implementing a VariableItem that represents a special variable node.
122
123 These special variable nodes are generated for classes, lists,
124 tuples and dictionaries.
125 """
126 def __init__(self, parent, dvar, dvalue, dtype, frmnr, scope):
127 """
128 Constructor
129
130 @param parent parent of this item
131 @param dvar variable name (string)
132 @param dvalue value string (string)
133 @param dtype type string (string)
134 @param frmnr frame number (0 is the current frame) (int)
135 @param scope flag indicating global (1) or local (0) variables
136 """
137 VariableItem.__init__(self, parent, dvar, dvalue, dtype)
138 self.attachDummy()
139 self.populated = False
140
141 self.framenr = frmnr
142 self.scope = scope
143
144 def expand(self):
145 """
146 Public method to expand the item.
147 """
148 self.deleteChildren()
149 self.populated = True
150
151 pathlist = [self.text(0)]
152 par = self.parent()
153
154 # step 1: get a pathlist up to the requested variable
155 while par is not None:
156 pathlist.insert(0, par.text(0))
157 par = par.parent()
158
159 # step 2: request the variable from the debugger
160 filter = e4App().getObject("DebugUI").variablesFilter(self.scope)
161 e4App().getObject("DebugServer").remoteClientVariable(\
162 self.scope, filter, pathlist, self.framenr)
163
164 class ArrayElementVarItem(VariableItem):
165 """
166 Class implementing a VariableItem that represents an array element.
167 """
168 def __init__(self, parent, dvar, dvalue, dtype):
169 """
170 Constructor
171
172 @param parent parent of this item
173 @param dvar variable name (string)
174 @param dvalue value string (string)
175 @param dtype type string (string)
176 """
177 VariableItem.__init__(self, parent, dvar, dvalue, dtype)
178
179 """
180 Array elements have numbers as names, but the key must be
181 right justified and zero filled to 6 decimal places. Then
182 element 2 will have a key of '000002' and appear before
183 element 10 with a key of '000010'
184 """
185 keyStr = self.text(0)
186 self.arrayElementKey = "%.6d" % int(keyStr)
187
188 def key(self, column):
189 """
190 Public method generating the key for this item.
191
192 @param column the column to sort on (integer)
193 """
194 if column == 0:
195 return self.arrayElementKey
196 else:
197 return VariableItem.key(self, column)
198
199 class SpecialArrayElementVarItem(SpecialVarItem):
200 """
201 Class implementing a QTreeWidgetItem that represents a special array variable node.
202 """
203 def __init__(self, parent, dvar, dvalue, dtype, frmnr, scope):
204 """
205 Constructor
206
207 @param parent parent of this item
208 @param dvar variable name (string)
209 @param dvalue value string (string)
210 @param dtype type string (string)
211 @param frmnr frame number (0 is the current frame) (int)
212 @param scope flag indicating global (1) or local (0) variables
213 """
214 SpecialVarItem.__init__(self, parent, dvar, dvalue, dtype, frmnr, scope)
215
216 """
217 Array elements have numbers as names, but the key must be
218 right justified and zero filled to 6 decimal places. Then
219 element 2 will have a key of '000002' and appear before
220 element 10 with a key of '000010'
221 """
222 keyStr = self.text(0)[:-2] # strip off [], () or {}
223 self.arrayElementKey = "%.6d" % int(keyStr)
224
225 def key(self, column):
226 """
227 Public method generating the key for this item.
228
229 @param column the column to sort on (integer)
230 """
231 if column == 0:
232 return self.arrayElementKey
233 else:
234 return SpecialVarItem.key(self, column)
235
236 class VariablesViewer(QTreeWidget):
237 """
238 Class implementing the variables viewer widget.
239
240 This widget is used to display the variables of the program being
241 debugged in a tree. Compound types will be shown with
242 their main entry first. Once the subtree has been expanded, the
243 individual entries will be shown. Double clicking an entry will
244 popup a dialog showing the variables parameters in a more readable
245 form. This is especially useful for lengthy strings.
246
247 This widget has two modes for displaying the global and the local
248 variables.
249 """
250 def __init__(self, parent=None, scope=1):
251 """
252 Constructor
253
254 @param parent the parent (QWidget)
255 @param scope flag indicating global (1) or local (0) variables
256 """
257 QTreeWidget.__init__(self, parent)
258
259 self.indicators = {'list' : '[]', 'tuple' : '()', 'dict' : '{}', # Python types
260 'Array' : '[]', 'Hash' : '{}'} # Ruby types
261
262 self.rx_class = QRegExp('<.*(instance|object) at 0x.*>')
263 self.rx_class2 = QRegExp('class .*')
264 self.rx_class3 = QRegExp('<class .* at 0x.*>')
265 self.dvar_rx_class1 = QRegExp(r'<.*(instance|object) at 0x.*>(\[\]|\{\}|\(\))')
266 self.dvar_rx_class2 = QRegExp(r'<class .* at 0x.*>(\[\]|\{\}|\(\))')
267 self.dvar_rx_array_element = QRegExp(r'^\d+$')
268 self.dvar_rx_special_array_element = QRegExp(r'^\d+(\[\]|\{\}|\(\))$')
269 self.rx_nonprintable = QRegExp(r"""(\\x\d\d)+""")
270
271 self.framenr = 0
272
273 self.loc = Preferences.getSystem("StringEncoding")
274
275 self.openItems = []
276
277 self.setRootIsDecorated(True)
278 self.setAlternatingRowColors(True)
279 self.setSelectionBehavior(QAbstractItemView.SelectRows)
280
281 self.scope = scope
282 if scope:
283 self.setWindowTitle(self.trUtf8("Global Variables"))
284 self.setHeaderLabels([
285 self.trUtf8("Globals"),
286 self.trUtf8("Value"),
287 self.trUtf8("Type")])
288 self.setWhatsThis(self.trUtf8(
289 """<b>The Global Variables Viewer Window</b>"""
290 """<p>This window displays the global variables"""
291 """ of the debugged program.</p>"""
292 ))
293 else:
294 self.setWindowTitle(self.trUtf8("Local Variables"))
295 self.setHeaderLabels([
296 self.trUtf8("Locals"),
297 self.trUtf8("Value"),
298 self.trUtf8("Type")])
299 self.setWhatsThis(self.trUtf8(
300 """<b>The Local Variables Viewer Window</b>"""
301 """<p>This window displays the local variables"""
302 """ of the debugged program.</p>"""
303 ))
304
305 header = self.header()
306 header.setSortIndicator(0, Qt.AscendingOrder)
307 header.setSortIndicatorShown(True)
308 header.setClickable(True)
309 header.resizeSection(0, 120) # variable column
310 header.resizeSection(1, 150) # value column
311
312 self.__createPopupMenus()
313 self.setContextMenuPolicy(Qt.CustomContextMenu)
314 self.connect(self,SIGNAL('customContextMenuRequested(const QPoint &)'),
315 self.__showContextMenu)
316
317 self.connect(self, SIGNAL("itemExpanded(QTreeWidgetItem *)"),
318 self.__expandItemSignal)
319 self.connect(self, SIGNAL("itemCollapsed(QTreeWidgetItem *)"),
320 self.collapseItem)
321
322 self.resortEnabled = True
323
324 def __createPopupMenus(self):
325 """
326 Private method to generate the popup menus.
327 """
328 self.menu = QMenu()
329 self.menu.addAction(self.trUtf8("Show Details..."), self.__showDetails)
330 self.menu.addSeparator()
331 self.menu.addAction(self.trUtf8("Configure..."), self.__configure)
332
333 self.backMenu = QMenu()
334 self.backMenu.addAction(self.trUtf8("Configure..."), self.__configure)
335
336 def __showContextMenu(self, coord):
337 """
338 Private slot to show the context menu.
339
340 @param coord the position of the mouse pointer (QPoint)
341 """
342 gcoord = self.mapToGlobal(coord)
343 if self.itemAt(coord) is not None:
344 self.menu.popup(gcoord)
345 else:
346 self.backMenu.popup(gcoord)
347
348 def __findItem(self, slist, column, node=None):
349 """
350 Private method to search for an item.
351
352 It is used to find a specific item in column,
353 that is a child of node. If node is None, a child of the
354 QTreeWidget is searched.
355
356 @param slist searchlist (list of strings)
357 @param column index of column to search in (int)
358 @param node start point of the search
359 @return the found item or None
360 """
361 if node is None:
362 count = self.topLevelItemCount()
363 else:
364 count = node.childCount()
365
366 for index in range(count):
367 if node is None:
368 itm = self.topLevelItem(index)
369 else:
370 itm = node.child(index)
371 if itm.text(column) == slist[0]:
372 if len(slist) > 1:
373 itm = self.__findItem(slist[1:], column, itm)
374 return itm
375
376 return None
377
378 def showVariables(self, vlist, frmnr):
379 """
380 Public method to show variables in a list.
381
382 @param vlist the list of variables to be displayed. Each
383 listentry is a tuple of three values.
384 <ul>
385 <li>the variable name (string)</li>
386 <li>the variables type (string)</li>
387 <li>the variables value (string)</li>
388 </ul>
389 @param frmnr frame number (0 is the current frame) (int)
390 """
391 self.current = self.currentItem()
392 if self.current:
393 self.curpathlist = self.__buildTreePath(self.current)
394 self.clear()
395 self.__scrollToItem = None
396 self.framenr = frmnr
397
398 if len(vlist):
399 self.resortEnabled = False
400 for (var, vtype, value) in vlist:
401 self.__addItem(None, vtype, var, value)
402
403 # reexpand tree
404 openItems = self.openItems[:]
405 openItems.sort()
406 self.openItems = []
407 for itemPath in openItems:
408 itm = self.__findItem(itemPath, 0)
409 if itm is not None:
410 self.expandItem(itm)
411 else:
412 self.openItems.append(itemPath)
413
414 if self.current:
415 citm = self.__findItem(self.curpathlist, 0)
416 if citm:
417 self.setCurrentItem(citm)
418 self.setItemSelected(citm, True)
419 self.scrollToItem(citm, QAbstractItemView.PositionAtTop)
420 self.current = None
421
422 self.resortEnabled = True
423 self.__resort()
424
425 def showVariable(self, vlist):
426 """
427 Public method to show variables in a list.
428
429 @param vlist the list of subitems to be displayed.
430 The first element gives the path of the
431 parent variable. Each other listentry is
432 a tuple of three values.
433 <ul>
434 <li>the variable name (string)</li>
435 <li>the variables type (string)</li>
436 <li>the variables value (string)</li>
437 </ul>
438 """
439 resortEnabled = self.resortEnabled
440 self.resortEnabled = False
441 if self.current is None:
442 self.current = self.currentItem()
443 if self.current:
444 self.curpathlist = self.__buildTreePath(self.current)
445
446 subelementsAdded = False
447 if vlist:
448 itm = self.__findItem(vlist[0], 0)
449 for var, vtype, value in vlist[1:]:
450 self.__addItem(itm, vtype, var, value)
451 subelementsAdded = True
452
453 # reexpand tree
454 openItems = self.openItems[:]
455 openItems.sort()
456 self.openItems = []
457 for itemPath in openItems:
458 itm = self.__findItem(itemPath, 0)
459 if itm is not None and not itm.isExpanded():
460 if itm.populated:
461 self.blockSignals(True)
462 itm.setExpanded(True)
463 self.blockSignals(False)
464 else:
465 self.expandItem(itm)
466 self.openItems = openItems[:]
467
468 if self.current:
469 citm = self.__findItem(self.curpathlist, 0)
470 if citm:
471 self.setCurrentItem(citm)
472 self.setItemSelected(citm, True)
473 if self.__scrollToItem:
474 self.scrollToItem(self.__scrollToItem,
475 QAbstractItemView.PositionAtTop)
476 else:
477 self.scrollToItem(citm, QAbstractItemView.PositionAtTop)
478 self.current = None
479 elif self.__scrollToItem:
480 self.scrollToItem(self.__scrollToItem, QAbstractItemView.PositionAtTop)
481
482 self.resortEnabled = resortEnabled
483 self.__resort()
484
485 def __generateItem(self, parent, dvar, dvalue, dtype, isSpecial = False):
486 """
487 Private method used to generate a VariableItem.
488
489 @param parent parent of the item to be generated
490 @param dvar variable name (string)
491 @param dvalue value string (string)
492 @param dtype type string (string)
493 @param isSpecial flag indicating that a special node should be generated (boolean)
494 @return The item that was generated (VariableItem).
495 """
496 if isSpecial and \
497 (self.dvar_rx_class1.exactMatch(dvar) or \
498 self.dvar_rx_class2.exactMatch(dvar)):
499 isSpecial = False
500
501 if self.rx_class2.exactMatch(dtype):
502 return SpecialVarItem(parent, dvar, dvalue, dtype[7:-1],
503 self.framenr, self.scope)
504 elif dtype != "void *" and \
505 (self.rx_class.exactMatch(dvalue) or \
506 self.rx_class3.exactMatch(dvalue) or \
507 isSpecial):
508 if self.dvar_rx_special_array_element.exactMatch(dvar):
509 return SpecialArrayElementVarItem(parent, dvar, dvalue, dtype,
510 self.framenr, self.scope)
511 else:
512 return SpecialVarItem(parent, dvar, dvalue, dtype,
513 self.framenr, self.scope)
514 else:
515 if self.dvar_rx_array_element.exactMatch(dvar):
516 return ArrayElementVarItem(parent, dvar, dvalue, dtype)
517 else:
518 return VariableItem(parent, dvar, dvalue, dtype)
519
520 def __unicode(self, s):
521 """
522 Private method to convert a string to unicode.
523
524 @param s the string to be converted (string)
525 @return unicode representation of s (unicode object)
526 """
527 if type(s) is type(u""):
528 return s
529 try:
530 u = unicode(s, self.loc)
531 except TypeError:
532 u = str(s)
533 except UnicodeError:
534 u = toUnicode(s)
535 return u
536
537 def __addItem(self, parent, vtype, var, value):
538 """
539 Private method used to add an item to the list.
540
541 If the item is of a type with subelements (i.e. list, dictionary,
542 tuple), these subelements are added by calling this method recursively.
543
544 @param parent the parent of the item to be added
545 (QTreeWidgetItem or None)
546 @param vtype the type of the item to be added
547 (string)
548 @param var the variable name (string)
549 @param value the value string (string)
550 @return The item that was added to the listview (QTreeWidgetItem).
551 """
552 if parent is None:
553 parent = self
554 try:
555 dvar = '%s%s' % (var, self.indicators[vtype])
556 except KeyError:
557 dvar = var
558 dvtype = self.__getDispType(vtype)
559
560 if vtype in ['list', 'Array', 'tuple', 'dict', 'Hash']:
561 itm = self.__generateItem(parent, dvar,
562 self.trUtf8("{0} items").format(value),
563 dvtype, True)
564 elif vtype in ['unicode', 'str']:
565 if self.rx_nonprintable.indexIn(value) != -1:
566 sval = value
567 else:
568 try:
569 sval = eval(value)
570 except:
571 sval = value
572 itm = self.__generateItem(parent, dvar, self.__unicode(sval), dvtype)
573
574 else:
575 itm = self.__generateItem(parent, dvar, value, dvtype)
576
577 return itm
578
579 def __getDispType(self, vtype):
580 """
581 Private method used to get the display string for type vtype.
582
583 @param vtype the type, the display string should be looked up for
584 (string)
585 @return displaystring (string)
586 """
587 try:
588 i = ConfigVarTypeStrings.index(vtype)
589 dvtype = self.trUtf8(ConfigVarTypeDispStrings[i])
590 except ValueError:
591 if vtype == 'classobj':
592 dvtype = self.trUtf8(\
593 ConfigVarTypeDispStrings[ConfigVarTypeStrings.index('instance')]\
594 )
595 else:
596 dvtype = vtype
597 return dvtype
598
599 def mouseDoubleClickEvent(self, mouseEvent):
600 """
601 Protected method of QAbstractItemView.
602
603 Reimplemented to disable expanding/collapsing
604 of items when double-clicking. Instead the double-clicked entry is opened.
605
606 @param mouseEvent the mouse event object (QMouseEvent)
607 """
608 itm = self.itemAt(mouseEvent.pos())
609 self.__showVariableDetails(itm)
610
611 def __showDetails(self):
612 """
613 Private slot to show details about the selected variable.
614 """
615 itm = self.currentItem()
616 self.__showVariableDetails(itm)
617
618 def __showVariableDetails(self, itm):
619 """
620 Private method to show details about a variable.
621
622 @param itm reference to the variable item
623 """
624 if itm is None:
625 return
626
627 val = itm.getValue()
628
629 if not val:
630 return # do not display anything, if the variable has no value
631
632 vtype = itm.text(2)
633 name = itm.text(0)
634 if name[-2:] in ['[]', '{}', '()']:
635 name = name[:-2]
636
637 par = itm.parent()
638 nlist = [name]
639 # build up the fully qualified name
640 while par is not None:
641 pname = par.text(0)
642 if pname[-2:] in ['[]', '{}', '()']:
643 if nlist[0].endswith("."):
644 nlist[0] = '[%s].' % nlist[0][:-1]
645 else:
646 nlist[0] = '[%s]' % nlist[0]
647 nlist.insert(0, pname[:-2])
648 else:
649 nlist.insert(0, '%s.' % pname)
650 par = par.parent()
651
652 name = ''.join(nlist)
653 # now show the dialog
654 dlg = VariableDetailDialog(name, vtype, val)
655 dlg.exec_()
656
657 def __buildTreePath(self, itm):
658 """
659 Private method to build up a path from the top to an item.
660
661 @param itm item to build the path for (QTreeWidgetItem)
662 @return list of names denoting the path from the top (list of strings)
663 """
664 name = itm.text(0)
665 pathlist = [name]
666
667 par = itm.parent()
668 # build up a path from the top to the item
669 while par is not None:
670 pname = par.text(0)
671 pathlist.insert(0, pname)
672 par = par.parent()
673
674 return pathlist[:]
675
676 def __expandItemSignal(self, parentItem):
677 """
678 Private slot to handle the expanded signal.
679
680 @param parentItem reference to the item being expanded (QTreeWidgetItem)
681 """
682 self.expandItem(parentItem)
683 self.__scrollToItem = parentItem
684
685 def expandItem(self, parentItem):
686 """
687 Public slot to handle the expanded signal.
688
689 @param parentItem reference to the item being expanded (QTreeWidgetItem)
690 """
691 pathlist = self.__buildTreePath(parentItem)
692 self.openItems.append(pathlist)
693 if parentItem.populated:
694 return
695
696 try:
697 parentItem.expand()
698 self.__resort()
699 except AttributeError:
700 QTreeWidget.expandItem(self, parentItem)
701
702 def collapseItem(self, parentItem):
703 """
704 Public slot to handle the collapsed signal.
705
706 @param parentItem reference to the item being collapsed (QTreeWidgetItem)
707 """
708 pathlist = self.__buildTreePath(parentItem)
709 self.openItems.remove(pathlist)
710
711 try:
712 parentItem.collapse()
713 except AttributeError:
714 QTreeWidget.collapseItem(self, parentItem)
715
716 def __resort(self):
717 """
718 Private method to resort the tree.
719 """
720 if self.resortEnabled:
721 self.sortItems(self.sortColumn(), self.header().sortIndicatorOrder())
722
723 def handleResetUI(self):
724 """
725 Public method to reset the VariablesViewer.
726 """
727 self.clear()
728 self.openItems = []
729
730 def __configure(self):
731 """
732 Private method to open the configuration dialog.
733 """
734 e4App().getObject("UserInterface").showPreferences("debuggerGeneralPage")

eric ide

mercurial