From cabd9d6bf2ec61701bdc3ab927d1e9444f09e9df Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Thu, 25 Sep 2014 15:21:28 -0400 Subject: [PATCH] Cleanup, better support for tracebacks in DataTreeWidget --- examples/DataTreeWidget.py | 20 +++++- pyqtgraph/widgets/DataTreeWidget.py | 96 ++++++++++++++++++++--------- 2 files changed, 85 insertions(+), 31 deletions(-) diff --git a/examples/DataTreeWidget.py b/examples/DataTreeWidget.py index 2c2b00f9..70ac49bd 100644 --- a/examples/DataTreeWidget.py +++ b/examples/DataTreeWidget.py @@ -11,15 +11,29 @@ from pyqtgraph.Qt import QtCore, QtGui import numpy as np +# for generating a traceback object to display +def some_func1(): + return some_func2() +def some_func2(): + try: + raise Exception() + except: + import sys + return sys.exc_info()[2] + + app = QtGui.QApplication([]) d = { - 'list1': [1,2,3,4,5,6, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"], - 'dict1': { + 'a list': [1,2,3,4,5,6, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"], + 'a dict': { 'x': 1, 'y': 2, 'z': 'three' }, - 'array1 (40x10)': np.random.randint(10, size=(40,10)) + 'an array': np.random.randint(10, size=(40,10)), + 'a traceback': some_func1(), + 'a function': some_func1, + 'a class': pg.DataTreeWidget, } tree = pg.DataTreeWidget(data=d) diff --git a/pyqtgraph/widgets/DataTreeWidget.py b/pyqtgraph/widgets/DataTreeWidget.py index b8215b06..f960cab9 100644 --- a/pyqtgraph/widgets/DataTreeWidget.py +++ b/pyqtgraph/widgets/DataTreeWidget.py @@ -2,6 +2,7 @@ from ..Qt import QtGui, QtCore from ..pgcollections import OrderedDict from .TableWidget import TableWidget +from ..python2_3 import asUnicode import types, traceback import numpy as np @@ -18,19 +19,18 @@ class DataTreeWidget(QtGui.QTreeWidget): Widget for displaying hierarchical python data structures (eg, nested dicts, lists, and arrays) """ - - def __init__(self, parent=None, data=None): QtGui.QTreeWidget.__init__(self, parent) self.setVerticalScrollMode(self.ScrollPerPixel) self.setData(data) self.setColumnCount(3) self.setHeaderLabels(['key / index', 'type', 'value']) + self.setAlternatingRowColors(True) def setData(self, data, hideRoot=False): """data should be a dictionary.""" self.clear() - self.tables = [] + self.widgets = [] self.buildTree(data, self.invisibleRootItem(), hideRoot=hideRoot) self.expandToDepth(3) self.resizeColumnToContents(0) @@ -39,35 +39,75 @@ class DataTreeWidget(QtGui.QTreeWidget): if hideRoot: node = parent else: - typeStr = type(data).__name__ - if typeStr == 'instance': - typeStr += ": " + data.__class__.__name__ - node = QtGui.QTreeWidgetItem([name, typeStr, ""]) + node = QtGui.QTreeWidgetItem([name, "", ""]) parent.addChild(node) - - if isinstance(data, types.TracebackType): ## convert traceback to a list of strings - data = list(map(str.strip, traceback.format_list(traceback.extract_tb(data)))) - elif HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): - data = { - 'data': data.view(np.ndarray), - 'meta': data.infoCopy() - } - - if isinstance(data, dict): - for k in data.keys(): - self.buildTree(data[k], node, str(k)) - elif isinstance(data, list) or isinstance(data, tuple): - for i in range(len(data)): - self.buildTree(data[i], node, str(i)) - elif isinstance(data, np.ndarray): - desc = "<%s shape=%s dtype=%s>" % (data.__class__.__name__, data.shape, data.dtype) - node.setText(2, desc) + + typeStr, desc, childs, widget = self.parse(data) + node.setText(1, typeStr) + node.setText(2, desc) + if widget is not None: + self.widgets.append(widget) subnode = QtGui.QTreeWidgetItem(["", "", ""]) node.addChild(subnode) + self.setItemWidget(subnode, 0, widget) + self.setFirstItemColumnSpanned(subnode, True) + + for name, data in childs.items(): + self.buildTree(data, node, asUnicode(name)) + + def parse(self, data): + """ + Given any python object, return: + * type + * a short string representation + * a dict of sub-objects to be parsed + * optional widget to display as sub-node + """ + # defaults for all objects + typeStr = type(data).__name__ + if typeStr == 'instance': + typeStr += ": " + data.__class__.__name__ + widget = None + desc = "" + childs = {} + + # type-specific changes + if isinstance(data, dict): + desc = "length=%d" % len(data) + if isinstance(data, OrderedDict): + childs = data + else: + childs = OrderedDict(sorted(data.items())) + elif isinstance(data, (list, tuple)): + desc = "length=%d" % len(data) + childs = OrderedDict(enumerate(data)) + elif HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): + childs = OrderedDict([ + ('data', data.view(np.ndarray)), + ('meta', data.infoCopy()) + ]) + elif isinstance(data, np.ndarray): + desc = "shape=%s dtype=%s" % (data.shape, data.dtype) table = TableWidget() table.setData(data) table.setMaximumHeight(200) - self.setItemWidget(subnode, 2, table) - self.tables.append(table) + widget = table + elif isinstance(data, types.TracebackType): ## convert traceback to a list of strings + frames = list(map(str.strip, traceback.format_list(traceback.extract_tb(data)))) + #childs = OrderedDict([ + #(i, {'file': child[0], 'line': child[1], 'function': child[2], 'code': child[3]}) + #for i, child in enumerate(frames)]) + #childs = OrderedDict([(i, ch) for i,ch in enumerate(frames)]) + widget = QtGui.QPlainTextEdit(asUnicode('\n'.join(frames))) + widget.setMaximumHeight(200) + widget.setReadOnly(True) else: - node.setText(2, str(data)) + desc = asUnicode(data) + if len(desc) > 100: + desc = desc[:97] + '...' + widget = QtGui.QPlainTextEdit(asUnicode(data)) + widget.setMaximumHeight(200) + widget.setReadOnly(True) + + return typeStr, desc, childs, widget + \ No newline at end of file