diff options
author | Picca Frédéric-Emmanuel <picca@synchrotron-soleil.fr> | 2017-08-18 14:48:52 +0200 |
---|---|---|
committer | Picca Frédéric-Emmanuel <picca@synchrotron-soleil.fr> | 2017-08-18 14:48:52 +0200 |
commit | f7bdc2acff3c13a6d632c28c4569690ab106eed7 (patch) | |
tree | 9d67cdb7152ee4e711379e03fe0546c7c3b97303 /silx/gui/data/RecordTableView.py |
Import Upstream version 0.5.0+dfsg
Diffstat (limited to 'silx/gui/data/RecordTableView.py')
-rw-r--r-- | silx/gui/data/RecordTableView.py | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/silx/gui/data/RecordTableView.py b/silx/gui/data/RecordTableView.py new file mode 100644 index 0000000..ce6a178 --- /dev/null +++ b/silx/gui/data/RecordTableView.py @@ -0,0 +1,405 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2017 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +""" +This module define model and widget to display 1D slices from numpy +array using compound data types or hdf5 databases. +""" +from __future__ import division + +import itertools +import numpy +from silx.gui import qt +import silx.io +from .TextFormatter import TextFormatter +from silx.gui.widgets.TableWidget import CopySelectedCellsAction + +__authors__ = ["V. Valls"] +__license__ = "MIT" +__date__ = "27/01/2017" + + +class _MultiLineItem(qt.QItemDelegate): + """Draw a multiline text without hiding anything. + + The paint method display a cell without any wrap. And an editor is + available to scroll into the selected cell. + """ + + def __init__(self, parent=None): + """ + Constructor + + :param qt.QWidget parent: Parent of the widget + """ + qt.QItemDelegate.__init__(self, parent) + self.__textOptions = qt.QTextOption() + self.__textOptions.setFlags(qt.QTextOption.IncludeTrailingSpaces | + qt.QTextOption.ShowTabsAndSpaces) + self.__textOptions.setWrapMode(qt.QTextOption.NoWrap) + self.__textOptions.setAlignment(qt.Qt.AlignTop | qt.Qt.AlignLeft) + + def paint(self, painter, option, index): + """ + Write multiline text without using any wrap or any alignment according + to the cell size. + + :param qt.QPainter painter: Painter context used to displayed the cell + :param qt.QStyleOptionViewItem option: Control how the editor is shown + :param qt.QIndex index: Index of the data to display + """ + painter.save() + + # set colors + painter.setPen(qt.QPen(qt.Qt.NoPen)) + if option.state & qt.QStyle.State_Selected: + brush = option.palette.highlight() + painter.setBrush(brush) + else: + brush = index.data(qt.Qt.BackgroundRole) + if brush is None: + # default background color for a cell + brush = qt.Qt.white + painter.setBrush(brush) + painter.drawRect(option.rect) + + if index.isValid(): + if option.state & qt.QStyle.State_Selected: + brush = option.palette.highlightedText() + else: + brush = index.data(qt.Qt.ForegroundRole) + if brush is None: + brush = option.palette.text() + painter.setPen(qt.QPen(brush.color())) + text = index.data(qt.Qt.DisplayRole) + painter.drawText(qt.QRectF(option.rect), text, self.__textOptions) + + painter.restore() + + def createEditor(self, parent, option, index): + """ + Returns the widget used to edit the item specified by index for editing. + + We use it not to edit the content but to show the content with a + convenient scroll bar. + + :param qt.QWidget parent: Parent of the widget + :param qt.QStyleOptionViewItem option: Control how the editor is shown + :param qt.QIndex index: Index of the data to display + """ + if not index.isValid(): + return super(_MultiLineItem, self).createEditor(parent, option, index) + + editor = qt.QTextEdit(parent) + editor.setReadOnly(True) + return editor + + def setEditorData(self, editor, index): + """ + Read data from the model and feed the editor. + + :param qt.QWidget editor: Editor widget + :param qt.QIndex index: Index of the data to display + """ + text = index.model().data(index, qt.Qt.EditRole) + editor.setText(text) + + def updateEditorGeometry(self, editor, option, index): + """ + Update the geometry of the editor according to the changes of the view. + + :param qt.QWidget editor: Editor widget + :param qt.QStyleOptionViewItem option: Control how the editor is shown + :param qt.QIndex index: Index of the data to display + """ + editor.setGeometry(option.rect) + + +class RecordTableModel(qt.QAbstractTableModel): + """This data model provides access to 1D slices from numpy array using + compound data types or hdf5 databases. + + Each entries are displayed in a single row, and each columns contain a + specific field of the compound type. + + It also allows to display 1D arrays of simple data types. + array. + + :param qt.QObject parent: Parent object + :param numpy.ndarray data: A numpy array or a h5py dataset + """ + def __init__(self, parent=None, data=None): + qt.QAbstractTableModel.__init__(self, parent) + + self.__data = None + self.__is_array = False + self.__fields = None + self.__formatter = None + self.__editFormatter = None + self.setFormatter(TextFormatter(self)) + + # set _data + self.setArrayData(data) + + # Methods to be implemented to subclass QAbstractTableModel + def rowCount(self, parent_idx=None): + """Returns number of rows to be displayed in table""" + if self.__data is None: + return 0 + elif not self.__is_array: + return 1 + else: + return len(self.__data) + + def columnCount(self, parent_idx=None): + """Returns number of columns to be displayed in table""" + if self.__fields is None: + return 1 + else: + return len(self.__fields) + + def data(self, index, role=qt.Qt.DisplayRole): + """QAbstractTableModel method to access data values + in the format ready to be displayed""" + if not index.isValid(): + return None + + if self.__data is None: + return None + + if self.__is_array: + if index.row() >= len(self.__data): + return None + data = self.__data[index.row()] + else: + if index.row() > 0: + return None + data = self.__data + + if self.__fields is not None: + if index.column() >= len(self.__fields): + return None + key = self.__fields[index.column()][1] + data = data[key[0]] + if len(key) > 1: + data = data[key[1]] + + if role == qt.Qt.DisplayRole: + return self.__formatter.toString(data) + elif role == qt.Qt.EditRole: + return self.__editFormatter.toString(data) + return None + + def headerData(self, section, orientation, role=qt.Qt.DisplayRole): + """Returns the 0-based row or column index, for display in the + horizontal and vertical headers""" + if section == -1: + # PyQt4 send -1 when there is columns but no rows + return None + + if role == qt.Qt.DisplayRole: + if orientation == qt.Qt.Vertical: + if not self.__is_array: + return "Scalar" + else: + return str(section) + if orientation == qt.Qt.Horizontal: + if self.__fields is None: + if section == 0: + return "Data" + else: + return None + else: + if section < len(self.__fields): + return self.__fields[section][0] + else: + return None + return None + + def flags(self, index): + """QAbstractTableModel method to inform the view whether data + is editable or not. + """ + return qt.QAbstractTableModel.flags(self, index) + + def setArrayData(self, data): + """Set the data array and the viewing perspective. + + You can set ``copy=False`` if you need more performances, when dealing + with a large numpy array. In this case, a simple reference to the data + is used to access the data, rather than a copy of the array. + + .. warning:: + + Any change to the data model will affect your original data + array, when using a reference rather than a copy.. + + :param data: 1D numpy array, or any object that can be + converted to a numpy array using ``numpy.array(data)`` (e.g. + a nested sequence). + """ + if qt.qVersion() > "4.6": + self.beginResetModel() + + self.__data = data + if isinstance(data, numpy.ndarray): + self.__is_array = True + elif silx.io.is_dataset(data) and data.shape != tuple(): + self.__is_array = True + else: + self.__is_array = False + + + self.__fields = [] + if data is not None: + if data.dtype.fields is not None: + for name, (dtype, _index) in data.dtype.fields.items(): + if dtype.shape != tuple(): + keys = itertools.product(*[range(x) for x in dtype.shape]) + for key in keys: + label = "%s%s" % (name, list(key)) + array_key = (name, key) + self.__fields.append((label, array_key)) + else: + self.__fields.append((name, (name,))) + else: + self.__fields = None + + if qt.qVersion() > "4.6": + self.endResetModel() + else: + self.reset() + + def arrayData(self): + """Returns the internal data. + + :rtype: numpy.ndarray of h5py.Dataset + """ + return self.__data + + def setFormatter(self, formatter): + """Set the formatter object to be used to display data from the model + + :param TextFormatter formatter: Formatter to use + """ + if formatter is self.__formatter: + return + + if qt.qVersion() > "4.6": + self.beginResetModel() + + if self.__formatter is not None: + self.__formatter.formatChanged.disconnect(self.__formatChanged) + + self.__formatter = formatter + self.__editFormatter = TextFormatter(formatter) + self.__editFormatter.setUseQuoteForText(False) + + if self.__formatter is not None: + self.__formatter.formatChanged.connect(self.__formatChanged) + + if qt.qVersion() > "4.6": + self.endResetModel() + else: + self.reset() + + def getFormatter(self): + """Returns the text formatter used. + + :rtype: TextFormatter + """ + return self.__formatter + + def __formatChanged(self): + """Called when the format changed. + """ + self.__editFormatter = TextFormatter(self, self.getFormatter()) + self.__editFormatter.setUseQuoteForText(False) + self.reset() + + +class _ShowEditorProxyModel(qt.QIdentityProxyModel): + """ + Allow to custom the flag edit of the model + """ + + def __init__(self, parent=None): + """ + Constructor + + :param qt.QObject arent: parent object + """ + super(_ShowEditorProxyModel, self).__init__(parent) + self.__forceEditable = False + + def flags(self, index): + flag = qt.QIdentityProxyModel.flags(self, index) + if self.__forceEditable: + flag = flag | qt.Qt.ItemIsEditable + return flag + + def forceCellEditor(self, show): + """ + Enable the editable flag to allow to display cell editor. + """ + if self.__forceEditable == show: + return + self.beginResetModel() + self.__forceEditable = show + self.endResetModel() + + +class RecordTableView(qt.QTableView): + """TableView using DatabaseTableModel as default model. + """ + def __init__(self, parent=None): + """ + Constructor + + :param qt.QWidget parent: parent QWidget + """ + qt.QTableView.__init__(self, parent) + + model = _ShowEditorProxyModel(self) + model.setSourceModel(RecordTableModel()) + self.setModel(model) + self.__multilineView = _MultiLineItem(self) + self.setEditTriggers(qt.QAbstractItemView.AllEditTriggers) + self._copyAction = CopySelectedCellsAction(self) + self.addAction(self._copyAction) + + def copy(self): + self._copyAction.trigger() + + def setArrayData(self, data): + self.model().sourceModel().setArrayData(data) + if data is not None: + if issubclass(data.dtype.type, (numpy.string_, numpy.unicode_)): + # TODO it would be nice to also fix fields + # but using it only for string array is already very useful + self.setItemDelegateForColumn(0, self.__multilineView) + self.model().forceCellEditor(True) + else: + self.setItemDelegateForColumn(0, None) + self.model().forceCellEditor(False) |