Add support for formatting TableWidget items
Merge branch 'tablewidget_formatting' into develop
This commit is contained in:
commit
202edc4e30
@ -46,6 +46,7 @@ pyqtgraph-0.9.9 [unreleased]
|
||||
- Stdout filter that colors text by thread
|
||||
- PeriodicTrace used to report deadlocks
|
||||
- Added AxisItem.setStyle()
|
||||
- Added configurable formatting for TableWidget
|
||||
|
||||
Bugfixes:
|
||||
- PlotCurveItem now has correct clicking behavior--clicks within a few px
|
||||
|
@ -56,6 +56,8 @@ class TableWidget(QtGui.QTableWidget):
|
||||
|
||||
QtGui.QTableWidget.__init__(self, *args)
|
||||
|
||||
self.itemClass = TableWidgetItem
|
||||
|
||||
self.setVerticalScrollMode(self.ScrollPerPixel)
|
||||
self.setSelectionMode(QtGui.QAbstractItemView.ContiguousSelection)
|
||||
self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
|
||||
@ -69,7 +71,10 @@ class TableWidget(QtGui.QTableWidget):
|
||||
if len(kwds) > 0:
|
||||
raise TypeError("Invalid keyword arguments '%s'" % kwds.keys())
|
||||
|
||||
self._sorting = None
|
||||
self._sorting = None # used when temporarily disabling sorting
|
||||
|
||||
self._formats = {None: None} # stores per-column formats and entire table format
|
||||
self.sortModes = {} # stores per-column sort mode
|
||||
|
||||
self.itemChanged.connect(self.handleItemChanged)
|
||||
|
||||
@ -153,7 +158,49 @@ class TableWidget(QtGui.QTableWidget):
|
||||
self.editable = editable
|
||||
for item in self.items:
|
||||
item.setEditable(editable)
|
||||
|
||||
|
||||
def setFormat(self, format, column=None):
|
||||
"""
|
||||
Specify the default text formatting for the entire table, or for a
|
||||
single column if *column* is specified.
|
||||
|
||||
If a string is specified, it is used as a format string for converting
|
||||
float values (and all other types are converted using str). If a
|
||||
function is specified, it will be called with the item as its only
|
||||
argument and must return a string. Setting format = None causes the
|
||||
default formatter to be used instead.
|
||||
|
||||
Added in version 0.9.9.
|
||||
|
||||
"""
|
||||
if format is not None and not isinstance(format, basestring) and not callable(format):
|
||||
raise ValueError("Format argument must string, callable, or None. (got %s)" % format)
|
||||
|
||||
self._formats[column] = format
|
||||
|
||||
|
||||
if column is None:
|
||||
# update format of all items that do not have a column format
|
||||
# specified
|
||||
for c in range(self.columnCount()):
|
||||
if self._formats.get(c, None) is None:
|
||||
for r in range(self.rowCount()):
|
||||
item = self.item(r, c)
|
||||
if item is None:
|
||||
continue
|
||||
item.setFormat(format)
|
||||
else:
|
||||
# set all items in the column to use this format, or the default
|
||||
# table format if None was specified.
|
||||
if format is None:
|
||||
format = self._formats[None]
|
||||
for r in range(self.rowCount()):
|
||||
item = self.item(r, column)
|
||||
if item is None:
|
||||
continue
|
||||
item.setFormat(format)
|
||||
|
||||
|
||||
def iteratorFn(self, data):
|
||||
## Return 1) a function that will provide an iterator for data and 2) a list of header strings
|
||||
if isinstance(data, list) or isinstance(data, tuple):
|
||||
@ -203,13 +250,17 @@ class TableWidget(QtGui.QTableWidget):
|
||||
self.setRowCount(row + 1)
|
||||
for col in range(len(vals)):
|
||||
val = vals[col]
|
||||
item = TableWidgetItem(val, row)
|
||||
item = self.itemClass(val, row)
|
||||
item.setEditable(self.editable)
|
||||
sortMode = self.sortModes.get(col, None)
|
||||
if sortMode is not None:
|
||||
item.setSortMode(sortMode)
|
||||
format = self._formats.get(col, self._formats[None])
|
||||
item.setFormat(format)
|
||||
self.items.append(item)
|
||||
self.setItem(row, col, item)
|
||||
item.setValue(val) # Required--the text-change callback is invoked
|
||||
# when we call setItem.
|
||||
|
||||
def setSortMode(self, column, mode):
|
||||
"""
|
||||
@ -254,7 +305,6 @@ class TableWidget(QtGui.QTableWidget):
|
||||
rows = list(range(self.rowCount()))
|
||||
columns = list(range(self.columnCount()))
|
||||
|
||||
|
||||
data = []
|
||||
if self.horizontalHeadersSet:
|
||||
row = []
|
||||
@ -303,7 +353,6 @@ class TableWidget(QtGui.QTableWidget):
|
||||
if fileName == '':
|
||||
return
|
||||
open(fileName, 'w').write(data)
|
||||
|
||||
|
||||
def contextMenuEvent(self, ev):
|
||||
self.contextMenu.popup(ev.globalPos())
|
||||
@ -316,24 +365,21 @@ class TableWidget(QtGui.QTableWidget):
|
||||
ev.ignore()
|
||||
|
||||
def handleItemChanged(self, item):
|
||||
try:
|
||||
item.value = type(item.value)(item.text())
|
||||
except ValueError:
|
||||
item.value = str(item.text())
|
||||
item.textChanged()
|
||||
|
||||
|
||||
class TableWidgetItem(QtGui.QTableWidgetItem):
|
||||
def __init__(self, val, index):
|
||||
if isinstance(val, float) or isinstance(val, np.floating):
|
||||
s = "%0.3g" % val
|
||||
else:
|
||||
s = asUnicode(val)
|
||||
QtGui.QTableWidgetItem.__init__(self, s)
|
||||
def __init__(self, val, index, format=None):
|
||||
QtGui.QTableWidgetItem.__init__(self, '')
|
||||
self._blockValueChange = False
|
||||
self._format = None
|
||||
self._defaultFormat = '%0.3g'
|
||||
self.sortMode = 'value'
|
||||
self.value = val
|
||||
self.index = index
|
||||
flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
|
||||
self.setFlags(flags)
|
||||
self.setValue(val)
|
||||
self.setFormat(format)
|
||||
|
||||
def setEditable(self, editable):
|
||||
"""
|
||||
@ -360,6 +406,55 @@ class TableWidgetItem(QtGui.QTableWidgetItem):
|
||||
if mode not in modes:
|
||||
raise ValueError('Sort mode must be one of %s' % str(modes))
|
||||
self.sortMode = mode
|
||||
|
||||
def setFormat(self, fmt):
|
||||
"""Define the conversion from item value to displayed text.
|
||||
|
||||
If a string is specified, it is used as a format string for converting
|
||||
float values (and all other types are converted using str). If a
|
||||
function is specified, it will be called with the item as its only
|
||||
argument and must return a string.
|
||||
|
||||
Added in version 0.9.9.
|
||||
"""
|
||||
if fmt is not None and not isinstance(fmt, basestring) and not callable(fmt):
|
||||
raise ValueError("Format argument must string, callable, or None. (got %s)" % fmt)
|
||||
self._format = fmt
|
||||
self._updateText()
|
||||
|
||||
def _updateText(self):
|
||||
self._blockValueChange = True
|
||||
try:
|
||||
self.setText(self.format())
|
||||
finally:
|
||||
self._blockValueChange = False
|
||||
|
||||
def setValue(self, value):
|
||||
self.value = value
|
||||
self._updateText()
|
||||
|
||||
def textChanged(self):
|
||||
"""Called when this item's text has changed for any reason."""
|
||||
if self._blockValueChange:
|
||||
# text change was result of value or format change; do not
|
||||
# propagate.
|
||||
return
|
||||
|
||||
try:
|
||||
self.value = type(self.value)(self.text())
|
||||
except ValueError:
|
||||
self.value = str(self.text())
|
||||
|
||||
def format(self):
|
||||
if callable(self._format):
|
||||
return self._format(self)
|
||||
if isinstance(self.value, (float, np.floating)):
|
||||
if self._format is None:
|
||||
return self._defaultFormat % self.value
|
||||
else:
|
||||
return self._format % self.value
|
||||
else:
|
||||
return asUnicode(self.value)
|
||||
|
||||
def __lt__(self, other):
|
||||
if self.sortMode == 'index' and hasattr(other, 'index'):
|
||||
|
@ -5,7 +5,7 @@ from pyqtgraph.pgcollections import OrderedDict
|
||||
app = pg.mkQApp()
|
||||
|
||||
|
||||
listOfTuples = [('text_%d' % i, i, i/10.) for i in range(12)]
|
||||
listOfTuples = [('text_%d' % i, i, i/9.) for i in range(12)]
|
||||
listOfLists = [list(row) for row in listOfTuples]
|
||||
plainArray = np.array(listOfLists, dtype=object)
|
||||
recordArray = np.array(listOfTuples, dtype=[('string', object),
|
||||
@ -78,6 +78,47 @@ def test_TableWidget():
|
||||
w.sortByColumn(1, pg.QtCore.Qt.AscendingOrder)
|
||||
assertTableData(w, listOfTuples)
|
||||
|
||||
# Test formatting
|
||||
item = w.item(0, 2)
|
||||
assert item.text() == ('%0.3g' % item.value)
|
||||
|
||||
w.setFormat('%0.6f')
|
||||
assert item.text() == ('%0.6f' % item.value)
|
||||
|
||||
w.setFormat('X%0.7f', column=2)
|
||||
assert isinstance(item.value, float)
|
||||
assert item.text() == ('X%0.7f' % item.value)
|
||||
|
||||
# test setting items that do not exist yet
|
||||
w.setFormat('X%0.7f', column=3)
|
||||
|
||||
# test append uses correct formatting
|
||||
w.appendRow(('x', 10, 7.3))
|
||||
item = w.item(w.rowCount()-1, 2)
|
||||
assert isinstance(item.value, float)
|
||||
assert item.text() == ('X%0.7f' % item.value)
|
||||
|
||||
# test reset back to defaults
|
||||
w.setFormat(None, column=2)
|
||||
assert isinstance(item.value, float)
|
||||
assert item.text() == ('%0.6f' % item.value)
|
||||
|
||||
w.setFormat(None)
|
||||
assert isinstance(item.value, float)
|
||||
assert item.text() == ('%0.3g' % item.value)
|
||||
|
||||
# test function formatter
|
||||
def fmt(item):
|
||||
if isinstance(item.value, float):
|
||||
return "%d %f" % (item.index, item.value)
|
||||
else:
|
||||
return pg.asUnicode(item.value)
|
||||
w.setFormat(fmt)
|
||||
assert isinstance(item.value, float)
|
||||
assert isinstance(item.index, int)
|
||||
assert item.text() == ("%d %f" % (item.index, item.value))
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
w = pg.TableWidget(editable=True)
|
||||
|
Loading…
Reference in New Issue
Block a user