Skip to main content
`Ctrl`+`K`

# Source code for silx.gui.hdf5.NexusSortFilterProxyModel

    # /*##########################################################################
    #
    # 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