summaryrefslogtreecommitdiff
path: root/src/silx/gui/data/HexaTableView.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/silx/gui/data/HexaTableView.py')
-rw-r--r--src/silx/gui/data/HexaTableView.py272
1 files changed, 272 insertions, 0 deletions
diff --git a/src/silx/gui/data/HexaTableView.py b/src/silx/gui/data/HexaTableView.py
new file mode 100644
index 0000000..9e00a7b
--- /dev/null
+++ b/src/silx/gui/data/HexaTableView.py
@@ -0,0 +1,272 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2017-2021 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 defines model and widget to display raw data using an
+hexadecimal viewer.
+"""
+from __future__ import division
+
+import collections
+
+import numpy
+
+from silx.gui import qt
+import silx.io.utils
+from silx.gui.widgets.TableWidget import CopySelectedCellsAction
+
+__authors__ = ["V. Valls"]
+__license__ = "MIT"
+__date__ = "23/05/2018"
+
+
+class _VoidConnector(object):
+ """Byte connector to a numpy.void data.
+
+ It uses a cache of 32 x 1KB and a direct read access API from HDF5.
+ """
+
+ def __init__(self, data):
+ self.__cache = collections.OrderedDict()
+ self.__len = data.itemsize
+ self.__data = data
+
+ def __getBuffer(self, bufferId):
+ if bufferId not in self.__cache:
+ pos = bufferId << 10
+ data = self.__data
+ if hasattr(data, "tobytes"):
+ data = data.tobytes()[pos:pos + 1024]
+ else:
+ # Old fashion
+ data = data.data[pos:pos + 1024]
+
+ self.__cache[bufferId] = data
+ if len(self.__cache) > 32:
+ self.__cache.popitem()
+ else:
+ data = self.__cache[bufferId]
+ return data
+
+ def __getitem__(self, pos):
+ """Returns the value of the byte at the given position.
+
+ :param uint pos: Position of the byte
+ :rtype: int
+ """
+ bufferId = pos >> 10
+ bufferPos = pos & 0b1111111111
+ data = self.__getBuffer(bufferId)
+ return data[bufferPos]
+
+ def __len__(self):
+ """
+ Returns the number of available bytes.
+
+ :rtype: uint
+ """
+ return self.__len
+
+
+class HexaTableModel(qt.QAbstractTableModel):
+ """This data model provides access to a numpy void data.
+
+ Bytes are displayed one by one as a hexadecimal viewer.
+
+ The 16th first columns display bytes as hexadecimal, the last column
+ displays the same data as ASCII.
+
+ :param qt.QObject parent: Parent object
+ :param data: A numpy array or a h5py dataset
+ """
+ def __init__(self, parent=None, data=None):
+ qt.QAbstractTableModel.__init__(self, parent)
+
+ self.__data = None
+ self.__connector = None
+ self.setArrayData(data)
+
+ if hasattr(qt.QFontDatabase, "systemFont"): # Qt >= 5.2
+ self.__font = qt.QFontDatabase.systemFont(qt.QFontDatabase.FixedFont)
+ else: # Qt < 5.2
+ self.__font = qt.QFont("Monospace")
+ self.__font.setStyleHint(qt.QFont.TypeWriter)
+ self.__palette = qt.QPalette()
+
+ def rowCount(self, parent_idx=None):
+ """Returns number of rows to be displayed in table"""
+ if self.__connector is None:
+ return 0
+ return ((len(self.__connector) - 1) >> 4) + 1
+
+ def columnCount(self, parent_idx=None):
+ """Returns number of columns to be displayed in table"""
+ return 0x10 + 1
+
+ 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.__connector is None:
+ return None
+
+ row = index.row()
+ column = index.column()
+
+ if role == qt.Qt.DisplayRole:
+ if column == 0x10:
+ start = (row << 4)
+ text = ""
+ for i in range(0x10):
+ pos = start + i
+ if pos >= len(self.__connector):
+ break
+ value = self.__connector[pos]
+ if value > 0x20 and value < 0x7F:
+ text += chr(value)
+ else:
+ text += "."
+ return text
+ else:
+ pos = (row << 4) + column
+ if pos < len(self.__connector):
+ value = self.__connector[pos]
+ return "%02X" % value
+ else:
+ return ""
+ elif role == qt.Qt.FontRole:
+ return self.__font
+
+ elif role == qt.Qt.BackgroundRole:
+ pos = (row << 4) + column
+ if column != 0x10 and pos >= len(self.__connector):
+ return self.__palette.color(qt.QPalette.Disabled, qt.QPalette.Window)
+ else:
+ return None
+
+ 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:
+ return "%02X" % (section << 4)
+ if orientation == qt.Qt.Horizontal:
+ if section == 0x10:
+ return "ASCII"
+ else:
+ return "%02X" % section
+ elif role == qt.Qt.FontRole:
+ return self.__font
+ elif role == qt.Qt.TextAlignmentRole:
+ if orientation == qt.Qt.Vertical:
+ return qt.Qt.AlignRight
+ if orientation == qt.Qt.Horizontal:
+ if section == 0x10:
+ return qt.Qt.AlignLeft
+ else:
+ return qt.Qt.AlignCenter
+ return None
+
+ def flags(self, index):
+ """QAbstractTableModel method to inform the view whether data
+ is editable or not.
+ """
+ row = index.row()
+ column = index.column()
+ pos = (row << 4) + column
+ if column != 0x10 and pos >= len(self.__connector):
+ return qt.Qt.NoItemFlags
+ return qt.QAbstractTableModel.flags(self, index)
+
+ def setArrayData(self, data):
+ """Set the data array.
+
+ :param data: A numpy object or a dataset.
+ """
+ self.beginResetModel()
+
+ self.__connector = None
+ self.__data = data
+ if self.__data is not None:
+ if silx.io.utils.is_dataset(self.__data):
+ data = data[()]
+ elif isinstance(self.__data, numpy.ndarray):
+ data = data[()]
+ self.__connector = _VoidConnector(data)
+
+ self.endResetModel()
+
+ def arrayData(self):
+ """Returns the internal data.
+
+ :rtype: numpy.ndarray of h5py.Dataset
+ """
+ return self.__data
+
+
+class HexaTableView(qt.QTableView):
+ """TableView using HexaTableModel as default model.
+
+ It customs the column size to provide a better layout.
+ """
+ def __init__(self, parent=None):
+ """
+ Constructor
+
+ :param qt.QWidget parent: parent QWidget
+ """
+ qt.QTableView.__init__(self, parent)
+
+ model = HexaTableModel(self)
+ self.setModel(model)
+ self._copyAction = CopySelectedCellsAction(self)
+ self.addAction(self._copyAction)
+
+ def copy(self):
+ self._copyAction.trigger()
+
+ def setArrayData(self, data):
+ """Set the data array.
+
+ :param data: A numpy object or a dataset.
+ """
+ self.model().setArrayData(data)
+ self.__fixHeader()
+
+ def __fixHeader(self):
+ """Update the view according to the state of the auto-resize"""
+ header = self.horizontalHeader()
+ header.setDefaultSectionSize(30)
+ header.setStretchLastSection(True)
+ for i in range(0x10):
+ header.setSectionResizeMode(i, qt.QHeaderView.Fixed)
+ header.setSectionResizeMode(0x10, qt.QHeaderView.Stretch)