summaryrefslogtreecommitdiff
path: root/src/silx/gui/hdf5/NexusSortFilterProxyModel.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/silx/gui/hdf5/NexusSortFilterProxyModel.py')
-rw-r--r--src/silx/gui/hdf5/NexusSortFilterProxyModel.py224
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