# 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__ = "24/07/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""" 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