diff options
Diffstat (limited to 'src/silx/gui/hdf5/NexusSortFilterProxyModel.py')
-rw-r--r-- | src/silx/gui/hdf5/NexusSortFilterProxyModel.py | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/src/silx/gui/hdf5/NexusSortFilterProxyModel.py b/src/silx/gui/hdf5/NexusSortFilterProxyModel.py new file mode 100644 index 0000000..9c3533f --- /dev/null +++ b/src/silx/gui/hdf5/NexusSortFilterProxyModel.py @@ -0,0 +1,224 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2018 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. +# +# ###########################################################################*/ + +__authors__ = ["V. Valls"] +__license__ = "MIT" +__date__ = "29/11/2018" + + +import logging +import re +import numpy +from .. import qt +from .Hdf5TreeModel import Hdf5TreeModel +import silx.io.utils +from silx.gui import icons + + +_logger = logging.getLogger(__name__) + + +class NexusSortFilterProxyModel(qt.QSortFilterProxyModel): + """Try to sort items according to Nexus structure. Else sort by name.""" + + def __init__(self, parent=None): + qt.QSortFilterProxyModel.__init__(self, parent) + self.__split = re.compile("(\\d+|\\D+)") + self.__iconCache = {} + + def hasChildren(self, parent): + """Returns true if parent has any children; otherwise returns false. + + :param qt.QModelIndex parent: Index of the item to check + :rtype: bool + """ + parent = self.mapToSource(parent) + return self.sourceModel().hasChildren(parent) + + def rowCount(self, parent): + """Returns the number of rows under the given parent. + + :param qt.QModelIndex parent: Index of the item to check + :rtype: int + """ + parent = self.mapToSource(parent) + return self.sourceModel().rowCount(parent) + + def lessThan(self, sourceLeft, sourceRight): + """Returns True if the value of the item referred to by the given + index `sourceLeft` is less than the value of the item referred to by + the given index `sourceRight`, otherwise returns false. + + :param qt.QModelIndex sourceLeft: + :param qt.QModelIndex sourceRight: + :rtype: bool + """ + if sourceLeft.column() != Hdf5TreeModel.NAME_COLUMN: + return super(NexusSortFilterProxyModel, self).lessThan( + sourceLeft, sourceRight) + + # Do not sort child of root (files) + if sourceLeft.parent() == qt.QModelIndex(): + return sourceLeft.row() < sourceRight.row() + + left = self.sourceModel().data(sourceLeft, Hdf5TreeModel.H5PY_ITEM_ROLE) + right = self.sourceModel().data(sourceRight, Hdf5TreeModel.H5PY_ITEM_ROLE) + + if self.__isNXentry(left) and self.__isNXentry(right): + less = self.childDatasetLessThan(left, right, "start_time") + if less is not None: + return less + less = self.childDatasetLessThan(left, right, "end_time") + if less is not None: + return less + + left = self.sourceModel().data(sourceLeft, qt.Qt.DisplayRole) + right = self.sourceModel().data(sourceRight, qt.Qt.DisplayRole) + return self.nameLessThan(left, right) + + def __isNXentry(self, node): + """Returns true if the node is an NXentry""" + class_ = node.h5Class + if class_ is None or class_ != silx.io.utils.H5Type.GROUP: + return False + nxClass = node.obj.attrs.get("NX_class", None) + return nxClass == "NXentry" + + def __isNXnode(self, node): + """Returns true if the node is an NX concept""" + if not hasattr(node, "h5Class"): + return False + class_ = node.h5Class + if class_ is None or class_ != silx.io.utils.H5Type.GROUP: + return False + nxClass = node.obj.attrs.get("NX_class", None) + return nxClass is not None + + def getWordsAndNumbers(self, name): + """ + Returns a list of words and integers composing the name. + + An input `"aaa10bbb50.30"` will return + `["aaa", 10, "bbb", 50, ".", 30]`. + + :param str name: A name + :rtype: List + """ + nonSensitive = self.sortCaseSensitivity() == qt.Qt.CaseInsensitive + words = self.__split.findall(name) + result = [] + for i in words: + if i[0].isdigit(): + i = int(i) + elif nonSensitive: + i = i.lower() + result.append(i) + return result + + def nameLessThan(self, left, right): + """Returns True if the left string is less than the right string. + + Number composing the names are compared as integers, as result "name2" + is smaller than "name10". + + :param str left: A string + :param str right: A string + :rtype: bool + """ + leftList = self.getWordsAndNumbers(left) + rightList = self.getWordsAndNumbers(right) + try: + return leftList < rightList + except TypeError: + # Back to string comparison if list are not type consistent + return left < right + + def childDatasetLessThan(self, left, right, childName): + """ + Reach the same children name of two items and compare their values. + + Returns True if the left one is smaller than the right one. + + :param Hdf5Item left: An item + :param Hdf5Item right: An item + :param str childName: Name of the children to search. Returns None if + the children is not found. + :rtype: bool + """ + try: + left_time = left.obj[childName][()] + right_time = right.obj[childName][()] + if isinstance(left_time, numpy.ndarray): + return left_time[0] < right_time[0] + return left_time < right_time + except KeyboardInterrupt: + raise + except Exception: + _logger.debug("Exception occurred", exc_info=True) + return None + + def __createCompoundIcon(self, backgroundIcon, foregroundIcon): + icon = qt.QIcon() + + sizes = backgroundIcon.availableSizes() + sizes = sorted(sizes, key=lambda s: s.height()) + sizes = filter(lambda s: s.height() < 100, sizes) + sizes = list(sizes) + if len(sizes) > 0: + baseSize = sizes[-1] + else: + baseSize = qt.QSize(32, 32) + + modes = [qt.QIcon.Normal, qt.QIcon.Disabled] + for mode in modes: + pixmap = qt.QPixmap(baseSize) + pixmap.fill(qt.Qt.transparent) + painter = qt.QPainter(pixmap) + painter.drawPixmap(0, 0, backgroundIcon.pixmap(baseSize, mode=mode)) + painter.drawPixmap(0, 0, foregroundIcon.pixmap(baseSize, mode=mode)) + painter.end() + icon.addPixmap(pixmap, mode=mode) + + return icon + + def __getNxIcon(self, baseIcon): + iconHash = baseIcon.cacheKey() + icon = self.__iconCache.get(iconHash, None) + if icon is None: + nxIcon = icons.getQIcon("layer-nx") + icon = self.__createCompoundIcon(baseIcon, nxIcon) + self.__iconCache[iconHash] = icon + return icon + + def data(self, index, role=qt.Qt.DisplayRole): + result = super(NexusSortFilterProxyModel, self).data(index, role) + + if index.column() == Hdf5TreeModel.NAME_COLUMN: + if role == qt.Qt.DecorationRole: + sourceIndex = self.mapToSource(index) + item = self.sourceModel().data(sourceIndex, Hdf5TreeModel.H5PY_ITEM_ROLE) + if self.__isNXnode(item): + result = self.__getNxIcon(result) + return result |