summaryrefslogtreecommitdiff
path: root/silx/gui/hdf5
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/hdf5')
-rw-r--r--silx/gui/hdf5/Hdf5Formatter.py244
-rw-r--r--silx/gui/hdf5/Hdf5HeaderView.py195
-rw-r--r--silx/gui/hdf5/Hdf5Item.py488
-rw-r--r--silx/gui/hdf5/Hdf5LoadingItem.py77
-rw-r--r--silx/gui/hdf5/Hdf5Node.py238
-rw-r--r--silx/gui/hdf5/Hdf5TreeModel.py779
-rw-r--r--silx/gui/hdf5/Hdf5TreeView.py271
-rw-r--r--silx/gui/hdf5/NexusSortFilterProxyModel.py222
-rw-r--r--silx/gui/hdf5/__init__.py44
-rw-r--r--silx/gui/hdf5/_utils.py421
-rw-r--r--silx/gui/hdf5/setup.py41
-rw-r--r--silx/gui/hdf5/test/__init__.py39
-rw-r--r--silx/gui/hdf5/test/test_hdf5.py981
13 files changed, 0 insertions, 4040 deletions
diff --git a/silx/gui/hdf5/Hdf5Formatter.py b/silx/gui/hdf5/Hdf5Formatter.py
deleted file mode 100644
index 6802142..0000000
--- a/silx/gui/hdf5/Hdf5Formatter.py
+++ /dev/null
@@ -1,244 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2017 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 package provides a class sharred by widgets to format HDF5 data as
-text."""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "06/06/2018"
-
-import numpy
-from silx.third_party import six
-from silx.gui import qt
-from silx.gui.data.TextFormatter import TextFormatter
-
-try:
- import h5py
-except ImportError:
- h5py = None
-
-
-class Hdf5Formatter(qt.QObject):
- """Formatter to convert HDF5 data to string.
- """
-
- formatChanged = qt.Signal()
- """Emitted when properties of the formatter change."""
-
- def __init__(self, parent=None, textFormatter=None):
- """
- Constructor
-
- :param qt.QObject parent: Owner of the object
- :param TextFormatter formatter: Text formatter
- """
- qt.QObject.__init__(self, parent)
- if textFormatter is not None:
- self.__formatter = textFormatter
- else:
- self.__formatter = TextFormatter(self)
- self.__formatter.formatChanged.connect(self.__formatChanged)
-
- def textFormatter(self):
- """Returns the used text formatter
-
- :rtype: TextFormatter
- """
- return self.__formatter
-
- def setTextFormatter(self, textFormatter):
- """Set the text formatter to be used
-
- :param TextFormatter textFormatter: The text formatter to use
- """
- if textFormatter is None:
- raise ValueError("Formatter expected but None found")
- if self.__formatter is textFormatter:
- return
- self.__formatter.formatChanged.disconnect(self.__formatChanged)
- self.__formatter = textFormatter
- self.__formatter.formatChanged.connect(self.__formatChanged)
- self.__formatChanged()
-
- def __formatChanged(self):
- self.formatChanged.emit()
-
- def humanReadableShape(self, dataset):
- if dataset.shape is None:
- return "none"
- if dataset.shape == tuple():
- return "scalar"
- shape = [str(i) for i in dataset.shape]
- text = u" \u00D7 ".join(shape)
- return text
-
- def humanReadableValue(self, dataset):
- if dataset.shape is None:
- return "No data"
-
- dtype = dataset.dtype
- if dataset.dtype.type == numpy.void:
- if dtype.fields is None:
- return "Raw data"
-
- if dataset.shape == tuple():
- numpy_object = dataset[()]
- text = self.__formatter.toString(numpy_object, dtype=dataset.dtype)
- else:
- if dataset.size < 5 and dataset.compression is None:
- numpy_object = dataset[0:5]
- text = self.__formatter.toString(numpy_object, dtype=dataset.dtype)
- else:
- dimension = len(dataset.shape)
- if dataset.compression is not None:
- text = "Compressed %dD data" % dimension
- else:
- text = "%dD data" % dimension
- return text
-
- def humanReadableType(self, dataset, full=False):
- if hasattr(dataset, "dtype"):
- dtype = dataset.dtype
- else:
- # Fallback...
- dtype = type(dataset)
- return self.humanReadableDType(dtype, full)
-
- def humanReadableDType(self, dtype, full=False):
- if dtype == six.binary_type or numpy.issubdtype(dtype, numpy.string_):
- text = "string"
- if full:
- text = "ASCII " + text
- return text
- elif dtype == six.text_type or numpy.issubdtype(dtype, numpy.unicode_):
- text = "string"
- if full:
- text = "UTF-8 " + text
- return text
- elif dtype.type == numpy.object_:
- ref = h5py.check_dtype(ref=dtype)
- if ref is not None:
- return "reference"
- vlen = h5py.check_dtype(vlen=dtype)
- if vlen is not None:
- text = self.humanReadableDType(vlen, full=full)
- if full:
- text = "variable-length " + text
- return text
- return "object"
- elif dtype.type == numpy.bool_:
- return "bool"
- elif dtype.type == numpy.void:
- if dtype.fields is None:
- return "opaque"
- else:
- if not full:
- return "compound"
- else:
- fields = sorted(dtype.fields.items(), key=lambda e: e[1][1])
- compound = [d[1][0] for d in fields]
- compound = [self.humanReadableDType(d) for d in compound]
- return "compound(%s)" % ", ".join(compound)
- elif numpy.issubdtype(dtype, numpy.integer):
- if h5py is not None:
- enumType = h5py.check_dtype(enum=dtype)
- if enumType is not None:
- return "enum"
-
- text = str(dtype.newbyteorder('N'))
- if numpy.issubdtype(dtype, numpy.floating):
- if hasattr(numpy, "float128") and dtype == numpy.float128:
- text = "float80"
- if full:
- text += " (padding 128bits)"
- elif hasattr(numpy, "float96") and dtype == numpy.float96:
- text = "float80"
- if full:
- text += " (padding 96bits)"
-
- if full:
- if dtype.byteorder == "<":
- text = "Little-endian " + text
- elif dtype.byteorder == ">":
- text = "Big-endian " + text
- elif dtype.byteorder == "=":
- text = "Native " + text
-
- dtype = dtype.newbyteorder('N')
- return text
-
- def humanReadableHdf5Type(self, dataset):
- """Format the internal HDF5 type as a string"""
- t = dataset.id.get_type()
- class_ = t.get_class()
- if class_ == h5py.h5t.NO_CLASS:
- return "NO_CLASS"
- elif class_ == h5py.h5t.INTEGER:
- return "INTEGER"
- elif class_ == h5py.h5t.FLOAT:
- return "FLOAT"
- elif class_ == h5py.h5t.TIME:
- return "TIME"
- elif class_ == h5py.h5t.STRING:
- charset = t.get_cset()
- strpad = t.get_strpad()
- text = ""
-
- if strpad == h5py.h5t.STR_NULLTERM:
- text += "NULLTERM"
- elif strpad == h5py.h5t.STR_NULLPAD:
- text += "NULLPAD"
- elif strpad == h5py.h5t.STR_SPACEPAD:
- text += "SPACEPAD"
- else:
- text += "UNKNOWN_STRPAD"
-
- if t.is_variable_str():
- text += " VARIABLE"
-
- if charset == h5py.h5t.CSET_ASCII:
- text += " ASCII"
- elif charset == h5py.h5t.CSET_UTF8:
- text += " UTF8"
- else:
- text += " UNKNOWN_CSET"
-
- return text + " STRING"
- elif class_ == h5py.h5t.BITFIELD:
- return "BITFIELD"
- elif class_ == h5py.h5t.OPAQUE:
- return "OPAQUE"
- elif class_ == h5py.h5t.COMPOUND:
- return "COMPOUND"
- elif class_ == h5py.h5t.REFERENCE:
- return "REFERENCE"
- elif class_ == h5py.h5t.ENUM:
- return "ENUM"
- elif class_ == h5py.h5t.VLEN:
- return "VLEN"
- elif class_ == h5py.h5t.ARRAY:
- return "ARRAY"
- else:
- return "UNKNOWN_CLASS"
diff --git a/silx/gui/hdf5/Hdf5HeaderView.py b/silx/gui/hdf5/Hdf5HeaderView.py
deleted file mode 100644
index 7baa6e0..0000000
--- a/silx/gui/hdf5/Hdf5HeaderView.py
+++ /dev/null
@@ -1,195 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016 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__ = "16/06/2017"
-
-
-from .. import qt
-from .Hdf5TreeModel import Hdf5TreeModel
-
-QTVERSION = qt.qVersion()
-
-
-class Hdf5HeaderView(qt.QHeaderView):
- """
- Default HDF5 header
-
- Manage auto-resize and context menu to display/hide columns
- """
-
- def __init__(self, orientation, parent=None):
- """
- Constructor
-
- :param orientation qt.Qt.Orientation: Orientation of the header
- :param parent qt.QWidget: Parent of the widget
- """
- super(Hdf5HeaderView, self).__init__(orientation, parent)
- self.setContextMenuPolicy(qt.Qt.CustomContextMenu)
- self.customContextMenuRequested.connect(self.__createContextMenu)
-
- # default initialization done by QTreeView for it's own header
- if QTVERSION < "5.0":
- self.setClickable(True)
- self.setMovable(True)
- else:
- self.setSectionsClickable(True)
- self.setSectionsMovable(True)
- self.setDefaultAlignment(qt.Qt.AlignLeft | qt.Qt.AlignVCenter)
- self.setStretchLastSection(True)
-
- self.__auto_resize = True
- self.__hide_columns_popup = True
-
- def setModel(self, model):
- """Override model to configure view when a model is expected
-
- `qt.QHeaderView.setResizeMode` expect already existing columns
- to work.
-
- :param model qt.QAbstractItemModel: A model
- """
- super(Hdf5HeaderView, self).setModel(model)
- self.__updateAutoResize()
-
- def __updateAutoResize(self):
- """Update the view according to the state of the auto-resize"""
- if QTVERSION < "5.0":
- setResizeMode = self.setResizeMode
- else:
- setResizeMode = self.setSectionResizeMode
-
- if self.__auto_resize:
- setResizeMode(Hdf5TreeModel.NAME_COLUMN, qt.QHeaderView.ResizeToContents)
- setResizeMode(Hdf5TreeModel.TYPE_COLUMN, qt.QHeaderView.ResizeToContents)
- setResizeMode(Hdf5TreeModel.SHAPE_COLUMN, qt.QHeaderView.ResizeToContents)
- setResizeMode(Hdf5TreeModel.VALUE_COLUMN, qt.QHeaderView.Interactive)
- setResizeMode(Hdf5TreeModel.DESCRIPTION_COLUMN, qt.QHeaderView.Interactive)
- setResizeMode(Hdf5TreeModel.NODE_COLUMN, qt.QHeaderView.ResizeToContents)
- setResizeMode(Hdf5TreeModel.LINK_COLUMN, qt.QHeaderView.ResizeToContents)
- else:
- setResizeMode(Hdf5TreeModel.NAME_COLUMN, qt.QHeaderView.Interactive)
- setResizeMode(Hdf5TreeModel.TYPE_COLUMN, qt.QHeaderView.Interactive)
- setResizeMode(Hdf5TreeModel.SHAPE_COLUMN, qt.QHeaderView.Interactive)
- setResizeMode(Hdf5TreeModel.VALUE_COLUMN, qt.QHeaderView.Interactive)
- setResizeMode(Hdf5TreeModel.DESCRIPTION_COLUMN, qt.QHeaderView.Interactive)
- setResizeMode(Hdf5TreeModel.NODE_COLUMN, qt.QHeaderView.Interactive)
- setResizeMode(Hdf5TreeModel.LINK_COLUMN, qt.QHeaderView.Interactive)
-
- def setAutoResizeColumns(self, autoResize):
- """Enable/disable auto-resize. When auto-resized, the header take care
- of the content of the column to set fixed size of some of them, or to
- auto fix the size according to the content.
-
- :param autoResize bool: Enable/disable auto-resize
- """
- if self.__auto_resize == autoResize:
- return
- self.__auto_resize = autoResize
- self.__updateAutoResize()
-
- def hasAutoResizeColumns(self):
- """Is auto-resize enabled.
-
- :rtype: bool
- """
- return self.__auto_resize
-
- autoResizeColumns = qt.Property(bool, hasAutoResizeColumns, setAutoResizeColumns)
- """Property to enable/disable auto-resize."""
-
- def setEnableHideColumnsPopup(self, enablePopup):
- """Enable/disable a popup to allow to hide/show each column of the
- model.
-
- :param bool enablePopup: Enable/disable popup to hide/show columns
- """
- self.__hide_columns_popup = enablePopup
-
- def hasHideColumnsPopup(self):
- """Is popup to hide/show columns is enabled.
-
- :rtype: bool
- """
- return self.__hide_columns_popup
-
- enableHideColumnsPopup = qt.Property(bool, hasHideColumnsPopup, setAutoResizeColumns)
- """Property to enable/disable popup allowing to hide/show columns."""
-
- def __genHideSectionEvent(self, column):
- """Generate a callback which change the column visibility according to
- the event parameter
-
- :param int column: logical id of the column
- :rtype: callable
- """
- return lambda checked: self.setSectionHidden(column, not checked)
-
- def __createContextMenu(self, pos):
- """Callback to create and display a context menu
-
- :param pos qt.QPoint: Requested position for the context menu
- """
- if not self.__hide_columns_popup:
- return
-
- model = self.model()
- if model.columnCount() > 1:
- menu = qt.QMenu(self)
- menu.setTitle("Display/hide columns")
-
- action = qt.QAction("Display/hide column", self)
- action.setEnabled(False)
- menu.addAction(action)
-
- for column in range(model.columnCount()):
- if column == 0:
- # skip the main column
- continue
- text = model.headerData(column, qt.Qt.Horizontal, qt.Qt.DisplayRole)
- action = qt.QAction("%s displayed" % text, self)
- action.setCheckable(True)
- action.setChecked(not self.isSectionHidden(column))
- action.toggled.connect(self.__genHideSectionEvent(column))
- menu.addAction(action)
-
- menu.popup(self.viewport().mapToGlobal(pos))
-
- def setSections(self, logicalIndexes):
- """
- Defines order of visible sections by logical indexes.
-
- Use `Hdf5TreeModel.NAME_COLUMN` to set the list.
-
- :param list logicalIndexes: List of logical indexes to display
- """
- for pos, column_id in enumerate(logicalIndexes):
- current_pos = self.visualIndex(column_id)
- self.moveSection(current_pos, pos)
- self.setSectionHidden(column_id, False)
- for column_id in set(range(self.model().columnCount())) - set(logicalIndexes):
- self.setSectionHidden(column_id, True)
diff --git a/silx/gui/hdf5/Hdf5Item.py b/silx/gui/hdf5/Hdf5Item.py
deleted file mode 100644
index b3c313e..0000000
--- a/silx/gui/hdf5/Hdf5Item.py
+++ /dev/null
@@ -1,488 +0,0 @@
-# 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__ = "03/09/2018"
-
-
-import logging
-import collections
-from .. import qt
-from .. import icons
-from . import _utils
-from .Hdf5Node import Hdf5Node
-import silx.io.utils
-from silx.gui.data.TextFormatter import TextFormatter
-from ..hdf5.Hdf5Formatter import Hdf5Formatter
-from ...third_party import six
-_logger = logging.getLogger(__name__)
-_formatter = TextFormatter()
-_hdf5Formatter = Hdf5Formatter(textFormatter=_formatter)
-# FIXME: The formatter should be an attribute of the Hdf5Model
-
-
-class Hdf5Item(Hdf5Node):
- """Subclass of :class:`qt.QStandardItem` to represent an HDF5-like
- item (dataset, file, group or link) as an element of a HDF5-like
- tree structure.
- """
-
- def __init__(self, text, obj, parent, key=None, h5Class=None, linkClass=None, populateAll=False):
- """
- :param str text: text displayed
- :param object obj: Pointer to a h5py-link object. See the `obj` attribute.
- """
- self.__obj = obj
- self.__key = key
- self.__h5Class = h5Class
- self.__isBroken = obj is None and h5Class is None
- self.__error = None
- self.__text = text
- self.__linkClass = linkClass
- self.__nx_class = None
- Hdf5Node.__init__(self, parent, populateAll=populateAll)
-
- def _getCanonicalName(self):
- parent = self.parent
- if parent is None:
- return self.__text
- else:
- return "%s/%s" % (parent._getCanonicalName(), self.__text)
-
- @property
- def obj(self):
- if self.__key:
- self.__initH5Object()
- return self.__obj
-
- @property
- def basename(self):
- return self.__text
-
- @property
- def h5Class(self):
- """Returns the class of the stored object.
-
- When the object is in lazy loading, this method should be able to
- return the type of the futrue loaded object. It allows to delay the
- real load of the object.
-
- :rtype: silx.io.utils.H5Type
- """
- if self.__h5Class is None and self.obj is not None:
- self.__h5Class = silx.io.utils.get_h5_class(self.obj)
- return self.__h5Class
-
- @property
- def h5pyClass(self):
- """Returns the class of the stored object.
-
- When the object is in lazy loading, this method should be able to
- return the type of the futrue loaded object. It allows to delay the
- real load of the object.
-
- :rtype: h5py.File or h5py.Dataset or h5py.Group
- """
- type_ = self.h5Class
- return silx.io.utils.h5type_to_h5py_class(type_)
-
- @property
- def linkClass(self):
- """Returns the link class object of this node
-
- :rtype: H5Type
- """
- return self.__linkClass
-
- def isGroupObj(self):
- """Returns true if the stored HDF5 object is a group (contains sub
- groups or datasets).
-
- :rtype: bool
- """
- if self.h5Class is None:
- return False
- return self.h5Class in [silx.io.utils.H5Type.GROUP, silx.io.utils.H5Type.FILE]
-
- def isBrokenObj(self):
- """Returns true if the stored HDF5 object is broken.
-
- The stored object is then an h5py-like link (external or not) which
- point to nowhere (tbhe external file is not here, the expected
- dataset is still not on the file...)
-
- :rtype: bool
- """
- return self.__isBroken
-
- def _getFormatter(self):
- """
- Returns an Hdf5Formatter
-
- :rtype: Hdf5Formatter
- """
- return _hdf5Formatter
-
- def _expectedChildCount(self):
- if self.isGroupObj():
- return len(self.obj)
- return 0
-
- def __initH5Object(self):
- """Lazy load of the HDF5 node. It is reached from the parent node
- with the key of the node."""
- parent_obj = self.parent.obj
-
- try:
- obj = parent_obj.get(self.__key)
- except Exception as e:
- _logger.error("Internal error while reaching HDF5 object: %s", str(e))
- _logger.debug("Backtrace", exc_info=True)
- try:
- self.__obj = parent_obj.get(self.__key, getlink=True)
- except Exception:
- self.__obj = None
- self.__error = e.args[0]
- self.__isBroken = True
- else:
- if obj is None:
- # that's a broken link
- self.__obj = parent_obj.get(self.__key, getlink=True)
-
- # TODO monkey-patch file (ask that in h5py for consistency)
- if not hasattr(self.__obj, "name"):
- parent_name = parent_obj.name
- if parent_name == "/":
- self.__obj.name = "/" + self.__key
- else:
- self.__obj.name = parent_name + "/" + self.__key
- # TODO monkey-patch file (ask that in h5py for consistency)
- if not hasattr(self.__obj, "file"):
- self.__obj.file = parent_obj.file
-
- class_ = silx.io.utils.get_h5_class(self.__obj)
-
- if class_ == silx.io.utils.H5Type.EXTERNAL_LINK:
- message = "External link broken. Path %s::%s does not exist" % (self.__obj.filename, self.__obj.path)
- elif class_ == silx.io.utils.H5Type.SOFT_LINK:
- message = "Soft link broken. Path %s does not exist" % (self.__obj.path)
- else:
- name = self.__obj.__class__.__name__.split(".")[-1].capitalize()
- message = "%s broken" % (name)
- self.__error = message
- self.__isBroken = True
- else:
- self.__obj = obj
- if not self.isGroupObj():
- try:
- # pre-fetch of the data
- if obj.shape is None:
- pass
- elif obj.shape == tuple():
- obj[()]
- else:
- if obj.compression is None and obj.size > 0:
- key = tuple([0] * len(obj.shape))
- obj[key]
- except Exception as e:
- _logger.debug(e, exc_info=True)
- message = "%s broken. %s" % (self.__obj.name, e.args[0])
- self.__error = message
- self.__isBroken = True
-
- self.__key = None
-
- def _populateChild(self, populateAll=False):
- if self.isGroupObj():
- for name in self.obj:
- try:
- class_ = self.obj.get(name, getclass=True)
- link = self.obj.get(name, getclass=True, getlink=True)
- link = silx.io.utils.get_h5_class(class_=link)
- except Exception:
- lib_name = self.obj.__class__.__module__.split(".")[0]
- _logger.warning("Internal %s error", lib_name, exc_info=True)
- _logger.debug("Backtrace", exc_info=True)
- class_ = None
- try:
- link = self.obj.get(name, getclass=True, getlink=True)
- link = silx.io.utils.get_h5_class(class_=link)
- except Exception:
- _logger.debug("Backtrace", exc_info=True)
- link = silx.io.utils.H5Type.HARD_LINK
-
- h5class = None
- if class_ is not None:
- h5class = silx.io.utils.get_h5_class(class_=class_)
- if h5class is None:
- _logger.error("Class %s unsupported", class_)
- item = Hdf5Item(text=name, obj=None, parent=self, key=name, h5Class=h5class, linkClass=link)
- self.appendChild(item)
-
- def hasChildren(self):
- """Retuens true of this node have chrild.
-
- :rtype: bool
- """
- if not self.isGroupObj():
- return False
- return Hdf5Node.hasChildren(self)
-
- def _getDefaultIcon(self):
- """Returns the icon displayed by the main column.
-
- :rtype: qt.QIcon
- """
- # Pre-fetch the object, in case it is broken
- obj = self.obj
- style = qt.QApplication.style()
- if self.__isBroken:
- icon = style.standardIcon(qt.QStyle.SP_MessageBoxCritical)
- return icon
- class_ = self.h5Class
- if class_ == silx.io.utils.H5Type.FILE:
- return style.standardIcon(qt.QStyle.SP_FileIcon)
- elif class_ == silx.io.utils.H5Type.GROUP:
- return style.standardIcon(qt.QStyle.SP_DirIcon)
- elif class_ == silx.io.utils.H5Type.SOFT_LINK:
- return style.standardIcon(qt.QStyle.SP_DirLinkIcon)
- elif class_ == silx.io.utils.H5Type.EXTERNAL_LINK:
- return style.standardIcon(qt.QStyle.SP_FileLinkIcon)
- elif class_ == silx.io.utils.H5Type.DATASET:
- if obj.shape is None:
- name = "item-none"
- elif len(obj.shape) < 4:
- name = "item-%ddim" % len(obj.shape)
- else:
- name = "item-ndim"
- icon = icons.getQIcon(name)
- return icon
- return None
-
- def _createTooltipAttributes(self):
- """
- Add key/value attributes that will be displayed in the item tooltip
-
- :param Dict[str,str] attributeDict: Key/value attributes
- """
- attributeDict = collections.OrderedDict()
-
- if self.h5Class == silx.io.utils.H5Type.DATASET:
- attributeDict["#Title"] = "HDF5 Dataset"
- attributeDict["Name"] = self.basename
- attributeDict["Path"] = self.obj.name
- attributeDict["Shape"] = self._getFormatter().humanReadableShape(self.obj)
- attributeDict["Value"] = self._getFormatter().humanReadableValue(self.obj)
- attributeDict["Data type"] = self._getFormatter().humanReadableType(self.obj, full=True)
- elif self.h5Class == silx.io.utils.H5Type.GROUP:
- attributeDict["#Title"] = "HDF5 Group"
- if self.nexusClassName:
- attributeDict["NX_class"] = self.nexusClassName
- attributeDict["Name"] = self.basename
- attributeDict["Path"] = self.obj.name
- elif self.h5Class == silx.io.utils.H5Type.FILE:
- attributeDict["#Title"] = "HDF5 File"
- attributeDict["Name"] = self.basename
- attributeDict["Path"] = "/"
- elif self.h5Class == silx.io.utils.H5Type.EXTERNAL_LINK:
- attributeDict["#Title"] = "HDF5 External Link"
- attributeDict["Name"] = self.basename
- attributeDict["Path"] = self.obj.name
- attributeDict["Linked path"] = self.obj.path
- attributeDict["Linked file"] = self.obj.filename
- elif self.h5Class == silx.io.utils.H5Type.SOFT_LINK:
- attributeDict["#Title"] = "HDF5 Soft Link"
- attributeDict["Name"] = self.basename
- attributeDict["Path"] = self.obj.name
- attributeDict["Linked path"] = self.obj.path
- else:
- pass
- return attributeDict
-
- def _getDefaultTooltip(self):
- """Returns the default tooltip
-
- :rtype: str
- """
- if self.__error is not None:
- self.obj # lazy loading of the object
- return self.__error
-
- attrs = self._createTooltipAttributes()
- title = attrs.pop("#Title", None)
- if len(attrs) > 0:
- tooltip = _utils.htmlFromDict(attrs, title=title)
- else:
- tooltip = ""
-
- return tooltip
-
- @property
- def nexusClassName(self):
- """Returns the Nexus class name"""
- if self.__nx_class is None:
- self.__nx_class = self.obj.attrs.get("NX_class", None)
- if self.__nx_class is None:
- self.__nx_class = ""
- else:
- if six.PY2:
- self.__nx_class = self.__nx_class.decode()
- elif not isinstance(self.__nx_class, str):
- self.__nx_class = str(self.__nx_class, "UTF-8")
- return self.__nx_class
-
- def dataName(self, role):
- """Data for the name column"""
- if role == qt.Qt.TextAlignmentRole:
- return qt.Qt.AlignTop | qt.Qt.AlignLeft
- if role == qt.Qt.DisplayRole:
- return self.__text
- if role == qt.Qt.DecorationRole:
- return self._getDefaultIcon()
- if role == qt.Qt.ToolTipRole:
- return self._getDefaultTooltip()
- return None
-
- def dataType(self, role):
- """Data for the type column"""
- if role == qt.Qt.DecorationRole:
- return None
- if role == qt.Qt.TextAlignmentRole:
- return qt.Qt.AlignTop | qt.Qt.AlignLeft
- if role == qt.Qt.DisplayRole:
- if self.__error is not None:
- return ""
- class_ = self.h5Class
- if self.isGroupObj():
- text = self.nexusClassName
- elif class_ == silx.io.utils.H5Type.DATASET:
- text = self._getFormatter().humanReadableType(self.obj)
- else:
- text = ""
- return text
- return None
-
- def dataShape(self, role):
- """Data for the shape column"""
- if role == qt.Qt.DecorationRole:
- return None
- if role == qt.Qt.TextAlignmentRole:
- return qt.Qt.AlignTop | qt.Qt.AlignLeft
- if role == qt.Qt.DisplayRole:
- if self.__error is not None:
- return ""
- class_ = self.h5Class
- if class_ != silx.io.utils.H5Type.DATASET:
- return ""
- return self._getFormatter().humanReadableShape(self.obj)
- return None
-
- def dataValue(self, role):
- """Data for the value column"""
- if role == qt.Qt.DecorationRole:
- return None
- if role == qt.Qt.TextAlignmentRole:
- return qt.Qt.AlignTop | qt.Qt.AlignLeft
- if role == qt.Qt.DisplayRole:
- if self.__error is not None:
- return ""
- if self.h5Class != silx.io.utils.H5Type.DATASET:
- return ""
- return self._getFormatter().humanReadableValue(self.obj)
- return None
-
- def dataDescription(self, role):
- """Data for the description column"""
- if role == qt.Qt.DecorationRole:
- return None
- if role == qt.Qt.TextAlignmentRole:
- return qt.Qt.AlignTop | qt.Qt.AlignLeft
- if role == qt.Qt.DisplayRole:
- if self.__isBroken:
- self.obj # lazy loading of the object
- return self.__error
- if "desc" in self.obj.attrs:
- text = self.obj.attrs["desc"]
- else:
- return ""
- return text
- if role == qt.Qt.ToolTipRole:
- if self.__error is not None:
- self.obj # lazy loading of the object
- self.__initH5Object()
- return self.__error
- if "desc" in self.obj.attrs:
- text = self.obj.attrs["desc"]
- else:
- return ""
- return "Description: %s" % text
- return None
-
- def dataNode(self, role):
- """Data for the node column"""
- if role == qt.Qt.DecorationRole:
- return None
- if role == qt.Qt.TextAlignmentRole:
- return qt.Qt.AlignTop | qt.Qt.AlignLeft
- if role == qt.Qt.DisplayRole:
- if self.isBrokenObj():
- return ""
- class_ = self.obj.__class__
- text = class_.__name__.split(".")[-1]
- return text
- if role == qt.Qt.ToolTipRole:
- class_ = self.obj.__class__
- if class_ is None:
- return ""
- return "Class name: %s" % self.__class__
- return None
-
- def dataLink(self, role):
- """Data for the link column
-
- Overwrite it to implement the content of the 'link' column.
-
- :rtype: qt.QVariant
- """
- if role == qt.Qt.DecorationRole:
- return None
- if role == qt.Qt.TextAlignmentRole:
- return qt.Qt.AlignTop | qt.Qt.AlignLeft
- if role == qt.Qt.DisplayRole:
- link = self.linkClass
- if link is None:
- return ""
- elif link == silx.io.utils.H5Type.EXTERNAL_LINK:
- return "External"
- elif link == silx.io.utils.H5Type.SOFT_LINK:
- return "Soft"
- elif link == silx.io.utils.H5Type.HARD_LINK:
- return ""
- else:
- return link.__name__
- if role == qt.Qt.ToolTipRole:
- return None
- return None
diff --git a/silx/gui/hdf5/Hdf5LoadingItem.py b/silx/gui/hdf5/Hdf5LoadingItem.py
deleted file mode 100644
index f11d252..0000000
--- a/silx/gui/hdf5/Hdf5LoadingItem.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016 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__ = "06/07/2018"
-
-
-from .. import qt
-from .Hdf5Node import Hdf5Node
-import silx.io.utils
-
-
-class Hdf5LoadingItem(Hdf5Node):
- """Item displayed when an Hdf5Node is loading.
-
- At the end of the loading this item is replaced by the loaded one.
- """
-
- def __init__(self, text, parent, animatedIcon):
- """Constructor"""
- Hdf5Node.__init__(self, parent)
- self.__text = text
- self.__animatedIcon = animatedIcon
- self.__animatedIcon.register(self)
-
- @property
- def obj(self):
- return None
-
- @property
- def h5Class(self):
- """Returns the class of the stored object.
-
- :rtype: silx.io.utils.H5Type
- """
- return silx.io.utils.H5Type.FILE
-
- def dataName(self, role):
- if role == qt.Qt.DecorationRole:
- return self.__animatedIcon.currentIcon()
- if role == qt.Qt.TextAlignmentRole:
- return qt.Qt.AlignTop | qt.Qt.AlignLeft
- if role == qt.Qt.DisplayRole:
- return self.__text
- return None
-
- def dataDescription(self, role):
- if role == qt.Qt.DecorationRole:
- return None
- if role == qt.Qt.TextAlignmentRole:
- return qt.Qt.AlignTop | qt.Qt.AlignLeft
- if role == qt.Qt.DisplayRole:
- return "Loading..."
- return None
diff --git a/silx/gui/hdf5/Hdf5Node.py b/silx/gui/hdf5/Hdf5Node.py
deleted file mode 100644
index be16535..0000000
--- a/silx/gui/hdf5/Hdf5Node.py
+++ /dev/null
@@ -1,238 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016 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 weakref
-
-
-class Hdf5Node(object):
- """Abstract tree node
-
- It provides link to the childs and to the parents, and a link to an
- external object.
- """
- def __init__(self, parent=None, populateAll=False):
- """
- Constructor
-
- :param Hdf5Node parent: Parent of the node, if exists, else None
- :param bool populateAll: If true, populate all the tree node. Else
- everything is lazy loaded.
- """
- self.__child = None
- self.__parent = None
- if parent is not None:
- self.__parent = weakref.ref(parent)
- if populateAll:
- self.__child = []
- self._populateChild(populateAll=True)
-
- def _getCanonicalName(self):
- parent = self.parent
- if parent is None:
- return "root"
- else:
- return "%s/?" % (parent._getCanonicalName())
-
- @property
- def parent(self):
- """Parent of the node, or None if the node is a root
-
- :rtype: Hdf5Node
- """
- if self.__parent is None:
- return None
- parent = self.__parent()
- if parent is None:
- self.__parent = parent
- return parent
-
- def setParent(self, parent):
- """Redefine the parent of the node.
-
- It does not set the node as the children of the new parent.
-
- :param Hdf5Node parent: The new parent
- """
- if parent is None:
- self.__parent = None
- else:
- self.__parent = weakref.ref(parent)
-
- def appendChild(self, child):
- """Append a child to the node.
-
- It does not update the parent of the child.
-
- :param Hdf5Node child: Child to append to the node.
- """
- self.__initChild()
- self.__child.append(child)
-
- def removeChildAtIndex(self, index):
- """Remove a child at an index of the children list.
-
- The child is removed and returned.
-
- :param int index: Index in the child list.
- :rtype: Hdf5Node
- :raises: IndexError if list is empty or index is out of range.
- """
- self.__initChild()
- return self.__child.pop(index)
-
- def insertChild(self, index, child):
- """
- Insert a child at a specific index of the child list.
-
- It does not update the parent of the child.
-
- :param int index: Index in the child list.
- :param Hdf5Node child: Child to insert in the child list.
- """
- self.__initChild()
- self.__child.insert(index, child)
-
- def indexOfChild(self, child):
- """
- Returns the index of the child in the child list of this node.
-
- :param Hdf5Node child: Child to find
- :raises: ValueError if the value is not present.
- """
- self.__initChild()
- return self.__child.index(child)
-
- def hasChildren(self):
- """Returns true if the node contains children.
-
- :rtype: bool
- """
- return self.childCount() > 0
-
- def childCount(self):
- """Returns the number of child in this node.
-
- :rtype: int
- """
- if self.__child is not None:
- return len(self.__child)
- return self._expectedChildCount()
-
- def child(self, index):
- """Return the child at an expected index.
-
- :param int index: Index of the child in the child list of the node
- :rtype: Hdf5Node
- """
- self.__initChild()
- return self.__child[index]
-
- def __initChild(self):
- """Init the child of the node in case the list was lazy loaded."""
- if self.__child is None:
- self.__child = []
- self._populateChild()
-
- def _expectedChildCount(self):
- """Returns the expected count of children
-
- :rtype: int
- """
- return 0
-
- def _populateChild(self, populateAll=False):
- """Recurse through an HDF5 structure to append groups an datasets
- into the tree model.
-
- Overwrite it to implement the initialisation of child of the node.
- """
- pass
-
- def dataName(self, role):
- """Data for the name column
-
- Overwrite it to implement the content of the 'name' column.
-
- :rtype: qt.QVariant
- """
- return None
-
- def dataType(self, role):
- """Data for the type column
-
- Overwrite it to implement the content of the 'type' column.
-
- :rtype: qt.QVariant
- """
- return None
-
- def dataShape(self, role):
- """Data for the shape column
-
- Overwrite it to implement the content of the 'shape' column.
-
- :rtype: qt.QVariant
- """
- return None
-
- def dataValue(self, role):
- """Data for the value column
-
- Overwrite it to implement the content of the 'value' column.
-
- :rtype: qt.QVariant
- """
- return None
-
- def dataDescription(self, role):
- """Data for the description column
-
- Overwrite it to implement the content of the 'description' column.
-
- :rtype: qt.QVariant
- """
- return None
-
- def dataNode(self, role):
- """Data for the node column
-
- Overwrite it to implement the content of the 'node' column.
-
- :rtype: qt.QVariant
- """
- return None
-
- def dataLink(self, role):
- """Data for the link column
-
- Overwrite it to implement the content of the 'link' column.
-
- :rtype: qt.QVariant
- """
- return None
diff --git a/silx/gui/hdf5/Hdf5TreeModel.py b/silx/gui/hdf5/Hdf5TreeModel.py
deleted file mode 100644
index 438200b..0000000
--- a/silx/gui/hdf5/Hdf5TreeModel.py
+++ /dev/null
@@ -1,779 +0,0 @@
-# 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__ = "08/10/2018"
-
-
-import os
-import logging
-import functools
-from .. import qt
-from .. import icons
-from .Hdf5Node import Hdf5Node
-from .Hdf5Item import Hdf5Item
-from .Hdf5LoadingItem import Hdf5LoadingItem
-from . import _utils
-from ... import io as silx_io
-
-_logger = logging.getLogger(__name__)
-
-"""Helpers to take care of None objects as signal parameters.
-PySide crash if a signal with a None parameter is emitted between threads.
-"""
-if qt.BINDING == 'PySide':
- class _NoneWraper(object):
- pass
- _NoneWraperInstance = _NoneWraper()
-
- def _wrapNone(x):
- """Wrap x if it is a None value, else returns x"""
- if x is None:
- return _NoneWraperInstance
- else:
- return x
-
- def _unwrapNone(x):
- """Unwrap x as a None if a None was stored by `wrapNone`, else returns
- x"""
- if x is _NoneWraperInstance:
- return None
- else:
- return x
-else:
- # Allow to fix None event params to avoid PySide crashes
- def _wrapNone(x):
- return x
-
- def _unwrapNone(x):
- return x
-
-
-def _createRootLabel(h5obj):
- """
- Create label for the very first npde of the tree.
-
- :param h5obj: The h5py object to display in the GUI
- :type h5obj: h5py-like object
- :rtpye: str
- """
- if silx_io.is_file(h5obj):
- label = os.path.basename(h5obj.filename)
- else:
- filename = os.path.basename(h5obj.file.filename)
- path = h5obj.name
- if path.startswith("/"):
- path = path[1:]
- label = "%s::%s" % (filename, path)
- return label
-
-
-class LoadingItemRunnable(qt.QRunnable):
- """Runner to process item loading from a file"""
-
- class __Signals(qt.QObject):
- """Signal holder"""
- itemReady = qt.Signal(object, object, object)
- runnerFinished = qt.Signal(object)
-
- def __init__(self, filename, item):
- """Constructor
-
- :param LoadingItemWorker worker: Object holding data and signals
- """
- super(LoadingItemRunnable, self).__init__()
- self.filename = filename
- self.oldItem = item
- self.signals = self.__Signals()
-
- def setFile(self, filename, item):
- self.filenames.append((filename, item))
-
- @property
- def itemReady(self):
- return self.signals.itemReady
-
- @property
- def runnerFinished(self):
- return self.signals.runnerFinished
-
- def __loadItemTree(self, oldItem, h5obj):
- """Create an item tree used by the GUI from an h5py object.
-
- :param Hdf5Node oldItem: The current item displayed the GUI
- :param h5py.File h5obj: The h5py object to display in the GUI
- :rtpye: Hdf5Node
- """
- text = _createRootLabel(h5obj)
- item = Hdf5Item(text=text, obj=h5obj, parent=oldItem.parent, populateAll=True)
- return item
-
- def run(self):
- """Process the file loading. The worker is used as holder
- of the data and the signal. The result is sent as a signal.
- """
- h5file = None
- try:
- h5file = silx_io.open(self.filename)
- newItem = self.__loadItemTree(self.oldItem, h5file)
- error = None
- except IOError as e:
- # Should be logged
- error = e
- newItem = None
- if h5file is not None:
- h5file.close()
-
- # Take care of None value in case of PySide
- newItem = _wrapNone(newItem)
- error = _wrapNone(error)
- self.itemReady.emit(self.oldItem, newItem, error)
- self.runnerFinished.emit(self)
-
- def autoDelete(self):
- return True
-
-
-class Hdf5TreeModel(qt.QAbstractItemModel):
- """Tree model storing a list of :class:`h5py.File` like objects.
-
- The main column display the :class:`h5py.File` list and there hierarchy.
- Other columns display information on node hierarchy.
- """
-
- H5PY_ITEM_ROLE = qt.Qt.UserRole
- """Role to reach h5py item from an item index"""
-
- H5PY_OBJECT_ROLE = qt.Qt.UserRole + 1
- """Role to reach h5py object from an item index"""
-
- USER_ROLE = qt.Qt.UserRole + 2
- """Start of range of available user role for derivative models"""
-
- NAME_COLUMN = 0
- """Column id containing HDF5 node names"""
-
- TYPE_COLUMN = 1
- """Column id containing HDF5 dataset types"""
-
- SHAPE_COLUMN = 2
- """Column id containing HDF5 dataset shapes"""
-
- VALUE_COLUMN = 3
- """Column id containing HDF5 dataset values"""
-
- DESCRIPTION_COLUMN = 4
- """Column id containing HDF5 node description/title/message"""
-
- NODE_COLUMN = 5
- """Column id containing HDF5 node type"""
-
- LINK_COLUMN = 6
- """Column id containing HDF5 link type"""
-
- COLUMN_IDS = [
- NAME_COLUMN,
- TYPE_COLUMN,
- SHAPE_COLUMN,
- VALUE_COLUMN,
- DESCRIPTION_COLUMN,
- NODE_COLUMN,
- LINK_COLUMN,
- ]
- """List of logical columns available"""
-
- sigH5pyObjectLoaded = qt.Signal(object)
- """Emitted when a new root item was loaded and inserted to the model."""
-
- sigH5pyObjectRemoved = qt.Signal(object)
- """Emitted when a root item is removed from the model."""
-
- sigH5pyObjectSynchronized = qt.Signal(object, object)
- """Emitted when an item was synchronized."""
-
- def __init__(self, parent=None, ownFiles=True):
- """
- Constructor
-
- :param qt.QWidget parent: Parent widget
- :param bool ownFiles: If true (default) the model will manage the files
- life cycle when they was added using path (like DnD).
- """
- super(Hdf5TreeModel, self).__init__(parent)
-
- self.header_labels = [None] * len(self.COLUMN_IDS)
- self.header_labels[self.NAME_COLUMN] = 'Name'
- self.header_labels[self.TYPE_COLUMN] = 'Type'
- self.header_labels[self.SHAPE_COLUMN] = 'Shape'
- self.header_labels[self.VALUE_COLUMN] = 'Value'
- self.header_labels[self.DESCRIPTION_COLUMN] = 'Description'
- self.header_labels[self.NODE_COLUMN] = 'Node'
- self.header_labels[self.LINK_COLUMN] = 'Link'
-
- # Create items
- self.__root = Hdf5Node()
- self.__fileDropEnabled = True
- self.__fileMoveEnabled = True
- self.__datasetDragEnabled = False
-
- self.__animatedIcon = icons.getWaitIcon()
- self.__animatedIcon.iconChanged.connect(self.__updateLoadingItems)
- self.__runnerSet = set([])
-
- # store used icons to avoid the cache to release it
- self.__icons = []
- self.__icons.append(icons.getQIcon("item-none"))
- self.__icons.append(icons.getQIcon("item-0dim"))
- self.__icons.append(icons.getQIcon("item-1dim"))
- self.__icons.append(icons.getQIcon("item-2dim"))
- self.__icons.append(icons.getQIcon("item-3dim"))
- self.__icons.append(icons.getQIcon("item-ndim"))
-
- self.__ownFiles = ownFiles
- self.__openedFiles = []
- """Store the list of files opened by the model itself."""
- # FIXME: It should be managed one by one by Hdf5Item itself
-
- # It is not possible to override the QObject destructor nor
- # to access to the content of the Python object with the `destroyed`
- # signal cause the Python method was already removed with the QWidget,
- # while the QObject still exists.
- # We use a static method plus explicit references to objects to
- # release. The callback do not use any ref to self.
- onDestroy = functools.partial(self._closeFileList, self.__openedFiles)
- self.destroyed.connect(onDestroy)
-
- @staticmethod
- def _closeFileList(fileList):
- """Static method to close explicit references to internal objects."""
- _logger.debug("Clear Hdf5TreeModel")
- for obj in fileList:
- _logger.debug("Close file %s", obj.filename)
- obj.close()
- fileList[:] = []
-
- def _closeOpened(self):
- """Close files which was opened by this model.
-
- File are opened by the model when it was inserted using
- `insertFileAsync`, `insertFile`, `appendFile`."""
- self._closeFileList(self.__openedFiles)
-
- def __updateLoadingItems(self, icon):
- for i in range(self.__root.childCount()):
- item = self.__root.child(i)
- if isinstance(item, Hdf5LoadingItem):
- index1 = self.index(i, 0, qt.QModelIndex())
- index2 = self.index(i, self.columnCount() - 1, qt.QModelIndex())
- self.dataChanged.emit(index1, index2)
-
- def __itemReady(self, oldItem, newItem, error):
- """Called at the end of a concurent file loading, when the loading
- item is ready. AN error is defined if an exception occured when
- loading the newItem .
-
- :param Hdf5Node oldItem: current displayed item
- :param Hdf5Node newItem: item loaded, or None if error is defined
- :param Exception error: An exception, or None if newItem is defined
- """
- # Take care of None value in case of PySide
- newItem = _unwrapNone(newItem)
- error = _unwrapNone(error)
- row = self.__root.indexOfChild(oldItem)
-
- rootIndex = qt.QModelIndex()
- self.beginRemoveRows(rootIndex, row, row)
- self.__root.removeChildAtIndex(row)
- self.endRemoveRows()
-
- if newItem is not None:
- rootIndex = qt.QModelIndex()
- if self.__ownFiles:
- self.__openedFiles.append(newItem.obj)
- self.beginInsertRows(rootIndex, row, row)
- self.__root.insertChild(row, newItem)
- self.endInsertRows()
-
- if isinstance(oldItem, Hdf5LoadingItem):
- self.sigH5pyObjectLoaded.emit(newItem.obj)
- else:
- self.sigH5pyObjectSynchronized.emit(oldItem.obj, newItem.obj)
-
- # FIXME the error must be displayed
-
- def isFileDropEnabled(self):
- return self.__fileDropEnabled
-
- def setFileDropEnabled(self, enabled):
- self.__fileDropEnabled = enabled
-
- fileDropEnabled = qt.Property(bool, isFileDropEnabled, setFileDropEnabled)
- """Property to enable/disable file dropping in the model."""
-
- def isDatasetDragEnabled(self):
- return self.__datasetDragEnabled
-
- def setDatasetDragEnabled(self, enabled):
- self.__datasetDragEnabled = enabled
-
- datasetDragEnabled = qt.Property(bool, isDatasetDragEnabled, setDatasetDragEnabled)
- """Property to enable/disable drag of datasets."""
-
- def isFileMoveEnabled(self):
- return self.__fileMoveEnabled
-
- def setFileMoveEnabled(self, enabled):
- self.__fileMoveEnabled = enabled
-
- fileMoveEnabled = qt.Property(bool, isFileMoveEnabled, setFileMoveEnabled)
- """Property to enable/disable drag-and-drop of files to
- change the ordering in the model."""
-
- def supportedDropActions(self):
- if self.__fileMoveEnabled or self.__fileDropEnabled:
- return qt.Qt.CopyAction | qt.Qt.MoveAction
- else:
- return 0
-
- def mimeTypes(self):
- types = []
- if self.__fileMoveEnabled:
- types.append(_utils.Hdf5NodeMimeData.MIME_TYPE)
- if self.__datasetDragEnabled:
- types.append(_utils.Hdf5DatasetMimeData.MIME_TYPE)
- return types
-
- def mimeData(self, indexes):
- """
- Returns an object that contains serialized items of data corresponding
- to the list of indexes specified.
-
- :param List[qt.QModelIndex] indexes: List of indexes
- :rtype: qt.QMimeData
- """
- if len(indexes) == 0:
- return None
-
- indexes = [i for i in indexes if i.column() == 0]
- if len(indexes) > 1:
- raise NotImplementedError("Drag of multi rows is not implemented")
- if len(indexes) == 0:
- raise NotImplementedError("Drag of cell is not implemented")
-
- node = self.nodeFromIndex(indexes[0])
-
- if self.__fileMoveEnabled and node.parent is self.__root:
- mimeData = _utils.Hdf5NodeMimeData(node=node)
- elif self.__datasetDragEnabled:
- mimeData = _utils.Hdf5DatasetMimeData(node=node)
- else:
- mimeData = None
- return mimeData
-
- def flags(self, index):
- defaultFlags = qt.QAbstractItemModel.flags(self, index)
-
- if index.isValid():
- node = self.nodeFromIndex(index)
- if self.__fileMoveEnabled and node.parent is self.__root:
- # that's a root
- return qt.Qt.ItemIsDragEnabled | defaultFlags
- elif self.__datasetDragEnabled:
- return qt.Qt.ItemIsDragEnabled | defaultFlags
- return defaultFlags
- elif self.__fileDropEnabled or self.__fileMoveEnabled:
- return qt.Qt.ItemIsDropEnabled | defaultFlags
- else:
- return defaultFlags
-
- def dropMimeData(self, mimedata, action, row, column, parentIndex):
- if action == qt.Qt.IgnoreAction:
- return True
-
- if self.__fileMoveEnabled and mimedata.hasFormat(_utils.Hdf5NodeMimeData.MIME_TYPE):
- dragNode = mimedata.node()
- parentNode = self.nodeFromIndex(parentIndex)
- if parentNode is not dragNode.parent:
- return False
-
- if row == -1:
- # append to the parent
- row = parentNode.childCount()
- else:
- # insert at row
- pass
-
- dragNodeParent = dragNode.parent
- sourceRow = dragNodeParent.indexOfChild(dragNode)
- self.moveRow(parentIndex, sourceRow, parentIndex, row)
- return True
-
- if self.__fileDropEnabled and mimedata.hasFormat("text/uri-list"):
-
- parentNode = self.nodeFromIndex(parentIndex)
- if parentNode is not self.__root:
- while(parentNode is not self.__root):
- node = parentNode
- parentNode = node.parent
- row = parentNode.indexOfChild(node)
- else:
- if row == -1:
- row = self.__root.childCount()
-
- messages = []
- for url in mimedata.urls():
- try:
- self.insertFileAsync(url.toLocalFile(), row)
- row += 1
- except IOError as e:
- messages.append(e.args[0])
- if len(messages) > 0:
- title = "Error occurred when loading files"
- message = "<html>%s:<ul><li>%s</li><ul></html>" % (title, "</li><li>".join(messages))
- qt.QMessageBox.critical(None, title, message)
- return True
-
- return False
-
- def headerData(self, section, orientation, role=qt.Qt.DisplayRole):
- if orientation == qt.Qt.Horizontal:
- if role in [qt.Qt.DisplayRole, qt.Qt.EditRole]:
- return self.header_labels[section]
- return None
-
- def insertNode(self, row, node):
- if row == -1:
- row = self.__root.childCount()
- self.beginInsertRows(qt.QModelIndex(), row, row)
- self.__root.insertChild(row, node)
- self.endInsertRows()
-
- def moveRow(self, sourceParentIndex, sourceRow, destinationParentIndex, destinationRow):
- if sourceRow == destinationRow or sourceRow == destinationRow - 1:
- # abort move, same place
- return
- return self.moveRows(sourceParentIndex, sourceRow, 1, destinationParentIndex, destinationRow)
-
- def moveRows(self, sourceParentIndex, sourceRow, count, destinationParentIndex, destinationRow):
- self.beginMoveRows(sourceParentIndex, sourceRow, sourceRow, destinationParentIndex, destinationRow)
- sourceNode = self.nodeFromIndex(sourceParentIndex)
- destinationNode = self.nodeFromIndex(destinationParentIndex)
-
- if sourceNode is destinationNode and sourceRow < destinationRow:
- item = sourceNode.child(sourceRow)
- destinationNode.insertChild(destinationRow, item)
- sourceNode.removeChildAtIndex(sourceRow)
- else:
- item = sourceNode.removeChildAtIndex(sourceRow)
- destinationNode.insertChild(destinationRow, item)
-
- self.endMoveRows()
- return True
-
- def index(self, row, column, parent=qt.QModelIndex()):
- try:
- node = self.nodeFromIndex(parent)
- return self.createIndex(row, column, node.child(row))
- except IndexError:
- return qt.QModelIndex()
-
- def data(self, index, role=qt.Qt.DisplayRole):
- node = self.nodeFromIndex(index)
-
- if role == self.H5PY_ITEM_ROLE:
- return node
-
- if role == self.H5PY_OBJECT_ROLE:
- return node.obj
-
- if index.column() == self.NAME_COLUMN:
- return node.dataName(role)
- elif index.column() == self.TYPE_COLUMN:
- return node.dataType(role)
- elif index.column() == self.SHAPE_COLUMN:
- return node.dataShape(role)
- elif index.column() == self.VALUE_COLUMN:
- return node.dataValue(role)
- elif index.column() == self.DESCRIPTION_COLUMN:
- return node.dataDescription(role)
- elif index.column() == self.NODE_COLUMN:
- return node.dataNode(role)
- elif index.column() == self.LINK_COLUMN:
- return node.dataLink(role)
- else:
- return None
-
- def columnCount(self, parent=qt.QModelIndex()):
- return len(self.COLUMN_IDS)
-
- def hasChildren(self, parent=qt.QModelIndex()):
- node = self.nodeFromIndex(parent)
- if node is None:
- return 0
- return node.hasChildren()
-
- def rowCount(self, parent=qt.QModelIndex()):
- node = self.nodeFromIndex(parent)
- if node is None:
- return 0
- return node.childCount()
-
- def parent(self, child):
- if not child.isValid():
- return qt.QModelIndex()
-
- node = self.nodeFromIndex(child)
-
- if node is None:
- return qt.QModelIndex()
-
- parent = node.parent
-
- if parent is None:
- return qt.QModelIndex()
-
- grandparent = parent.parent
- if grandparent is None:
- return qt.QModelIndex()
- row = grandparent.indexOfChild(parent)
-
- assert row != - 1
- return self.createIndex(row, 0, parent)
-
- def nodeFromIndex(self, index):
- return index.internalPointer() if index.isValid() else self.__root
-
- def _closeFileIfOwned(self, node):
- """"Close the file if it was loaded from a filename or a
- drag-and-drop"""
- obj = node.obj
- for f in self.__openedFiles:
- if f in obj:
- _logger.debug("Close file %s", obj.filename)
- obj.close()
- self.__openedFiles.remove(obj)
-
- def synchronizeIndex(self, index):
- """
- Synchronize a file a given its index.
-
- Basically close it and load it again.
-
- :param qt.QModelIndex index: Index of the item to update
- """
- node = self.nodeFromIndex(index)
- if node.parent is not self.__root:
- return
-
- filename = node.obj.filename
- self.insertFileAsync(filename, index.row(), synchronizingNode=node)
-
- def h5pyObjectRow(self, h5pyObject):
- for row in range(self.__root.childCount()):
- item = self.__root.child(row)
- if item.obj == h5pyObject:
- return row
- return -1
-
- def synchronizeH5pyObject(self, h5pyObject):
- """
- Synchronize a h5py object in all the tree.
-
- Basically close it and load it again.
-
- :param h5py.File h5pyObject: A :class:`h5py.File` object.
- """
- index = 0
- while index < self.__root.childCount():
- item = self.__root.child(index)
- if item.obj == h5pyObject:
- qindex = self.index(index, 0, qt.QModelIndex())
- self.synchronizeIndex(qindex)
- index += 1
-
- def removeIndex(self, index):
- """
- Remove an item from the model using its index.
-
- :param qt.QModelIndex index: Index of the item to remove
- """
- node = self.nodeFromIndex(index)
- if node.parent != self.__root:
- return
- self._closeFileIfOwned(node)
- self.beginRemoveRows(qt.QModelIndex(), index.row(), index.row())
- self.__root.removeChildAtIndex(index.row())
- self.endRemoveRows()
- self.sigH5pyObjectRemoved.emit(node.obj)
-
- def removeH5pyObject(self, h5pyObject):
- """
- Remove an item from the model using the holding h5py object.
- It can remove more than one item.
-
- :param h5py.File h5pyObject: A :class:`h5py.File` object.
- """
- index = 0
- while index < self.__root.childCount():
- item = self.__root.child(index)
- if item.obj == h5pyObject:
- qindex = self.index(index, 0, qt.QModelIndex())
- self.removeIndex(qindex)
- else:
- index += 1
-
- def insertH5pyObject(self, h5pyObject, text=None, row=-1):
- """Append an HDF5 object from h5py to the tree.
-
- :param h5pyObject: File handle/descriptor for a :class:`h5py.File`
- or any other class of h5py file structure.
- """
- if text is None:
- text = _createRootLabel(h5pyObject)
- if row == -1:
- row = self.__root.childCount()
- self.insertNode(row, Hdf5Item(text=text, obj=h5pyObject, parent=self.__root))
-
- def hasPendingOperations(self):
- return len(self.__runnerSet) > 0
-
- def insertFileAsync(self, filename, row=-1, synchronizingNode=None):
- if not os.path.isfile(filename):
- raise IOError("Filename '%s' must be a file path" % filename)
-
- # create temporary item
- if synchronizingNode is None:
- text = os.path.basename(filename)
- item = Hdf5LoadingItem(text=text, parent=self.__root, animatedIcon=self.__animatedIcon)
- self.insertNode(row, item)
- else:
- item = synchronizingNode
-
- # start loading the real one
- runnable = LoadingItemRunnable(filename, item)
- runnable.itemReady.connect(self.__itemReady)
- runnable.runnerFinished.connect(self.__releaseRunner)
- self.__runnerSet.add(runnable)
- qt.silxGlobalThreadPool().start(runnable)
-
- def __releaseRunner(self, runner):
- self.__runnerSet.remove(runner)
-
- def insertFile(self, filename, row=-1):
- """Load a HDF5 file into the data model.
-
- :param filename: file path.
- """
- try:
- h5file = silx_io.open(filename)
- if self.__ownFiles:
- self.__openedFiles.append(h5file)
- self.sigH5pyObjectLoaded.emit(h5file)
- self.insertH5pyObject(h5file, row=row)
- except IOError:
- _logger.debug("File '%s' can't be read.", filename, exc_info=True)
- raise
-
- def clear(self):
- """Remove all the content of the model"""
- for _ in range(self.rowCount()):
- qindex = self.index(0, 0, qt.QModelIndex())
- self.removeIndex(qindex)
-
- def appendFile(self, filename):
- self.insertFile(filename, -1)
-
- def indexFromH5Object(self, h5Object):
- """Returns a model index from an h5py-like object.
-
- :param object h5Object: An h5py-like object
- :rtype: qt.QModelIndex
- """
- if h5Object is None:
- return qt.QModelIndex()
-
- filename = h5Object.file.filename
-
- # Seach for the right roots
- rootIndices = []
- for index in range(self.rowCount(qt.QModelIndex())):
- index = self.index(index, 0, qt.QModelIndex())
- obj = self.data(index, Hdf5TreeModel.H5PY_OBJECT_ROLE)
- if obj.file.filename == filename:
- # We can have many roots with different subtree of the same
- # root
- rootIndices.append(index)
-
- if len(rootIndices) == 0:
- # No root found
- return qt.QModelIndex()
-
- path = h5Object.name + "/"
- path = path.replace("//", "/")
-
- # Search for the right node
- found = False
- foundIndices = []
- for _ in range(1000 * len(rootIndices)):
- # Avoid too much iterations, in case of recurssive links
- if len(foundIndices) == 0:
- if len(rootIndices) == 0:
- # Nothing found
- break
- # Start fron a new root
- foundIndices.append(rootIndices.pop(0))
-
- obj = self.data(index, Hdf5TreeModel.H5PY_OBJECT_ROLE)
- p = obj.name + "/"
- p = p.replace("//", "/")
- if path == p:
- found = True
- break
-
- parentIndex = foundIndices[-1]
- for index in range(self.rowCount(parentIndex)):
- index = self.index(index, 0, parentIndex)
- obj = self.data(index, Hdf5TreeModel.H5PY_OBJECT_ROLE)
-
- p = obj.name + "/"
- p = p.replace("//", "/")
- if path == p:
- foundIndices.append(index)
- found = True
- break
- elif path.startswith(p):
- foundIndices.append(index)
- break
- else:
- # Nothing found, start again with another root
- foundIndices = []
-
- if found:
- break
-
- if found:
- return foundIndices[-1]
- return qt.QModelIndex()
diff --git a/silx/gui/hdf5/Hdf5TreeView.py b/silx/gui/hdf5/Hdf5TreeView.py
deleted file mode 100644
index a86140a..0000000
--- a/silx/gui/hdf5/Hdf5TreeView.py
+++ /dev/null
@@ -1,271 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2017 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__ = "30/04/2018"
-
-
-import logging
-from .. import qt
-from ...utils import weakref as silxweakref
-from .Hdf5TreeModel import Hdf5TreeModel
-from .Hdf5HeaderView import Hdf5HeaderView
-from .NexusSortFilterProxyModel import NexusSortFilterProxyModel
-from .Hdf5Item import Hdf5Item
-from . import _utils
-
-_logger = logging.getLogger(__name__)
-
-
-class Hdf5TreeView(qt.QTreeView):
- """TreeView which allow to browse HDF5 file structure.
-
- .. image:: img/Hdf5TreeView.png
-
- It provides columns width auto-resizing and additional
- signals.
-
- The default model is a :class:`NexusSortFilterProxyModel` sourcing
- a :class:`Hdf5TreeModel`. The :class:`Hdf5TreeModel` is reachable using
- :meth:`findHdf5TreeModel`. The default header is :class:`Hdf5HeaderView`.
-
- Context menu is managed by the :meth:`setContextMenuPolicy` with the value
- Qt.CustomContextMenu. This policy must not be changed, otherwise context
- menus will not work anymore. You can use :meth:`addContextMenuCallback` and
- :meth:`removeContextMenuCallback` to add your custum actions according
- to the selected objects.
- """
- def __init__(self, parent=None):
- """
- Constructor
-
- :param parent qt.QWidget: The parent widget
- """
- qt.QTreeView.__init__(self, parent)
-
- model = self.createDefaultModel()
- self.setModel(model)
-
- self.setHeader(Hdf5HeaderView(qt.Qt.Horizontal, self))
- self.setSelectionBehavior(qt.QAbstractItemView.SelectRows)
- self.sortByColumn(0, qt.Qt.AscendingOrder)
- # optimise the rendering
- self.setUniformRowHeights(True)
-
- self.setIconSize(qt.QSize(16, 16))
- self.setAcceptDrops(True)
- self.setDragEnabled(True)
- self.setDragDropMode(qt.QAbstractItemView.DragDrop)
- self.showDropIndicator()
-
- self.__context_menu_callbacks = silxweakref.WeakList()
- self.setContextMenuPolicy(qt.Qt.CustomContextMenu)
- self.customContextMenuRequested.connect(self._createContextMenu)
-
- def createDefaultModel(self):
- """Creates and returns the default model.
-
- Inherite to custom the default model"""
- model = Hdf5TreeModel(self)
- proxy_model = NexusSortFilterProxyModel(self)
- proxy_model.setSourceModel(model)
- return proxy_model
-
- def __removeContextMenuProxies(self, ref):
- """Callback to remove dead proxy from the list"""
- self.__context_menu_callbacks.remove(ref)
-
- def _createContextMenu(self, pos):
- """
- Create context menu.
-
- :param pos qt.QPoint: Position of the context menu
- """
- actions = []
-
- menu = qt.QMenu(self)
-
- hovered_index = self.indexAt(pos)
- hovered_node = self.model().data(hovered_index, Hdf5TreeModel.H5PY_ITEM_ROLE)
- if hovered_node is None or not isinstance(hovered_node, Hdf5Item):
- return
-
- hovered_object = _utils.H5Node(hovered_node)
- event = _utils.Hdf5ContextMenuEvent(self, menu, hovered_object)
-
- for callback in self.__context_menu_callbacks:
- try:
- callback(event)
- except KeyboardInterrupt:
- raise
- except Exception:
- # make sure no user callback crash the application
- _logger.error("Error while calling callback", exc_info=True)
- pass
-
- if not menu.isEmpty():
- for action in actions:
- menu.addAction(action)
- menu.popup(self.viewport().mapToGlobal(pos))
-
- def addContextMenuCallback(self, callback):
- """Register a context menu callback.
-
- The callback will be called when a context menu is requested with the
- treeview and the list of selected h5py objects in parameters. The
- callback must return a list of :class:`qt.QAction` object.
-
- Callbacks are stored as saferef. The object must store a reference by
- itself.
- """
- self.__context_menu_callbacks.append(callback)
-
- def removeContextMenuCallback(self, callback):
- """Unregister a context menu callback"""
- self.__context_menu_callbacks.remove(callback)
-
- def findHdf5TreeModel(self):
- """Find the Hdf5TreeModel from the stack of model filters.
-
- :returns: A Hdf5TreeModel, else None
- :rtype: Hdf5TreeModel
- """
- model = self.model()
- while model is not None:
- if isinstance(model, qt.QAbstractProxyModel):
- model = model.sourceModel()
- else:
- break
- if model is None:
- return None
- if isinstance(model, Hdf5TreeModel):
- return model
- else:
- return None
-
- def dragEnterEvent(self, event):
- model = self.findHdf5TreeModel()
- if model is not None and model.isFileDropEnabled() and event.mimeData().hasFormat("text/uri-list"):
- self.setState(qt.QAbstractItemView.DraggingState)
- event.accept()
- else:
- qt.QTreeView.dragEnterEvent(self, event)
-
- def dragMoveEvent(self, event):
- model = self.findHdf5TreeModel()
- if model is not None and model.isFileDropEnabled() and event.mimeData().hasFormat("text/uri-list"):
- event.setDropAction(qt.Qt.CopyAction)
- event.accept()
- else:
- qt.QTreeView.dragMoveEvent(self, event)
-
- def selectedH5Nodes(self, ignoreBrokenLinks=True):
- """Returns selected h5py objects like :class:`h5py.File`,
- :class:`h5py.Group`, :class:`h5py.Dataset` or mimicked objects.
-
- :param ignoreBrokenLinks bool: Returns objects which are not not
- broken links.
- :rtype: iterator(:class:`_utils.H5Node`)
- """
- for index in self.selectedIndexes():
- if index.column() != 0:
- continue
- item = self.model().data(index, Hdf5TreeModel.H5PY_ITEM_ROLE)
- if item is None:
- continue
- if isinstance(item, Hdf5Item):
- if ignoreBrokenLinks and item.isBrokenObj():
- continue
- yield _utils.H5Node(item)
-
- def __intermediateModels(self, index):
- """Returns intermediate models from the view model to the
- model of the index."""
- models = []
- targetModel = index.model()
- model = self.model()
- while model is not None:
- if model is targetModel:
- # found
- return models
- models.append(model)
- if isinstance(model, qt.QAbstractProxyModel):
- model = model.sourceModel()
- else:
- break
- raise RuntimeError("Model from the requested index is not reachable from this view")
-
- def mapToModel(self, index):
- """Map an index from any model reachable by the view to an index from
- the very first model connected to the view.
-
- :param qt.QModelIndex index: Index from the Hdf5Tree model
- :rtype: qt.QModelIndex
- :return: Index from the model connected to the view
- """
- if not index.isValid():
- return index
- models = self.__intermediateModels(index)
- for model in reversed(models):
- index = model.mapFromSource(index)
- return index
-
- def setSelectedH5Node(self, h5Object):
- """
- Select the specified node of the tree using an h5py node.
-
- - If the item is found, parent items are expended, and then the item
- is selected.
- - If the item is not found, the selection do not change.
- - A none argument allow to deselect everything
-
- :param h5py.Node h5Object: The node to select
- """
- if h5Object is None:
- self.setCurrentIndex(qt.QModelIndex())
- return
-
- model = self.findHdf5TreeModel()
- index = model.indexFromH5Object(h5Object)
- index = self.mapToModel(index)
- if index.isValid():
- # Update the GUI
- i = index
- while i.isValid():
- self.expand(i)
- i = i.parent()
- self.setCurrentIndex(index)
-
- def mousePressEvent(self, event):
- """Override mousePressEvent to provide a consistante compatible API
- between Qt4 and Qt5
- """
- super(Hdf5TreeView, self).mousePressEvent(event)
- if event.button() != qt.Qt.LeftButton:
- # Qt5 only sends itemClicked on left button mouse click
- if qt.qVersion() > "5":
- qindex = self.indexAt(event.pos())
- self.clicked.emit(qindex)
diff --git a/silx/gui/hdf5/NexusSortFilterProxyModel.py b/silx/gui/hdf5/NexusSortFilterProxyModel.py
deleted file mode 100644
index 216e992..0000000
--- a/silx/gui/hdf5/NexusSortFilterProxyModel.py
+++ /dev/null
@@ -1,222 +0,0 @@
-# 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
diff --git a/silx/gui/hdf5/__init__.py b/silx/gui/hdf5/__init__.py
deleted file mode 100644
index 1b5a602..0000000
--- a/silx/gui/hdf5/__init__.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2017 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 package provides a set of Qt widgets for displaying content relative to
-HDF5 format.
-
-.. note::
-
- This package depends on *h5py*.
-"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "23/09/2016"
-
-
-from .Hdf5TreeView import Hdf5TreeView # noqa
-from ._utils import H5Node
-from ._utils import Hdf5ContextMenuEvent # noqa
-from .NexusSortFilterProxyModel import NexusSortFilterProxyModel # noqa
-from .Hdf5TreeModel import Hdf5TreeModel # noqa
-
-__all__ = ['Hdf5TreeView', 'H5Node', 'Hdf5ContextMenuEvent', 'NexusSortFilterProxyModel', 'Hdf5TreeModel']
diff --git a/silx/gui/hdf5/_utils.py b/silx/gui/hdf5/_utils.py
deleted file mode 100644
index 6a34933..0000000
--- a/silx/gui/hdf5/_utils.py
+++ /dev/null
@@ -1,421 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2017 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 package provides a set of helper class and function used by the
-package `silx.gui.hdf5` package.
-"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "04/05/2018"
-
-
-import logging
-from .. import qt
-import silx.io.utils
-from silx.utils.html import escape
-
-_logger = logging.getLogger(__name__)
-
-
-class Hdf5ContextMenuEvent(object):
- """Hold information provided to context menu callbacks."""
-
- def __init__(self, source, menu, hoveredObject):
- """
- Constructor
-
- :param QWidget source: Widget source
- :param QMenu menu: Context menu which will be displayed
- :param H5Node hoveredObject: Hovered H5 node
- """
- self.__source = source
- self.__menu = menu
- self.__hoveredObject = hoveredObject
-
- def source(self):
- """Source of the event
-
- :rtype: Hdf5TreeView
- """
- return self.__source
-
- def menu(self):
- """Menu which will be displayed
-
- :rtype: qt.QMenu
- """
- return self.__menu
-
- def hoveredObject(self):
- """Item content hovered by the mouse when the context menu was
- requested
-
- :rtype: H5Node
- """
- return self.__hoveredObject
-
-
-def htmlFromDict(dictionary, title=None):
- """Generate a readable HTML from a dictionary
-
- :param dict dictionary: A Dictionary
- :rtype: str
- """
- result = """<html>
- <head>
- <style type="text/css">
- ul { -qt-list-indent: 0; list-style: none; }
- li > b {display: inline-block; min-width: 4em; font-weight: bold; }
- </style>
- </head>
- <body>
- """
- if title is not None:
- result += "<b>%s</b>" % escape(title)
- result += "<ul>"
- for key, value in dictionary.items():
- result += "<li><b>%s</b>: %s</li>" % (escape(key), escape(value))
- result += "</ul>"
- result += "</body></html>"
- return result
-
-
-class Hdf5DatasetMimeData(qt.QMimeData):
- """Mimedata class to identify an internal drag and drop of a Hdf5Node."""
-
- MIME_TYPE = "application/x-internal-h5py-dataset"
-
- def __init__(self, node=None, dataset=None):
- qt.QMimeData.__init__(self)
- self.__dataset = dataset
- self.__node = node
- self.setData(self.MIME_TYPE, "".encode(encoding='utf-8'))
-
- def node(self):
- return self.__node
-
- def dataset(self):
- if self.__node is not None:
- return self.__node.obj
- return self.__dataset
-
-
-class Hdf5NodeMimeData(qt.QMimeData):
- """Mimedata class to identify an internal drag and drop of a Hdf5Node."""
-
- MIME_TYPE = "application/x-internal-h5py-node"
-
- def __init__(self, node=None):
- qt.QMimeData.__init__(self)
- self.__node = node
- self.setData(self.MIME_TYPE, "".encode(encoding='utf-8'))
-
- def node(self):
- return self.__node
-
-
-class H5Node(object):
- """Adapter over an h5py object to provide missing informations from h5py
- nodes, like internal node path and filename (which are not provided by
- :mod:`h5py` for soft and external links).
-
- It also provides an abstraction to reach node type for mimicked h5py
- objects.
- """
-
- def __init__(self, h5py_item=None):
- """Constructor
-
- :param Hdf5Item h5py_item: An Hdf5Item
- """
- self.__h5py_object = h5py_item.obj
- self.__h5py_target = None
- self.__h5py_item = h5py_item
-
- def __getattr__(self, name):
- if hasattr(self.__h5py_object, name):
- attr = getattr(self.__h5py_object, name)
- return attr
- raise AttributeError("H5Node has no attribute %s" % name)
-
- def __get_target(self, obj):
- """
- Return the actual physical target of the provided object.
-
- Objects can contains links in the middle of the path, this function
- check each groups and remove this prefix in case of the link by the
- link of the path.
-
- :param obj: A valid h5py object (File, group or dataset)
- :type obj: h5py.Dataset or h5py.Group or h5py.File
- :rtype: h5py.Dataset or h5py.Group or h5py.File
- """
- elements = obj.name.split("/")
- if obj.name == "/":
- return obj
- elif obj.name.startswith("/"):
- elements.pop(0)
- path = ""
- subpath = ""
- while len(elements) > 0:
- e = elements.pop(0)
- subpath = path + "/" + e
- link = obj.parent.get(subpath, getlink=True)
- classlink = silx.io.utils.get_h5_class(link)
-
- if classlink == silx.io.utils.H5Type.EXTERNAL_LINK:
- subpath = "/".join(elements)
- external_obj = obj.parent.get(self.basename + "/" + subpath)
- return self.__get_target(external_obj)
- elif classlink == silx.io.utils.H5Type.SOFT_LINK:
- # Restart from this stat
- root_elements = link.path.split("/")
- if link.path == "/":
- path = ""
- root_elements = []
- elif link.path.startswith("/"):
- path = ""
- root_elements.pop(0)
-
- for name in reversed(root_elements):
- elements.insert(0, name)
- else:
- path = subpath
-
- return obj.file[path]
-
- @property
- def h5py_target(self):
- if self.__h5py_target is not None:
- return self.__h5py_target
- self.__h5py_target = self.__get_target(self.__h5py_object)
- return self.__h5py_target
-
- @property
- def h5py_object(self):
- """Returns the internal h5py node.
-
- :rtype: h5py.File or h5py.Group or h5py.Dataset
- """
- return self.__h5py_object
-
- @property
- def h5type(self):
- """Returns the node type, as an H5Type.
-
- :rtype: H5Node
- """
- return silx.io.utils.get_h5_class(self.__h5py_object)
-
- @property
- def ntype(self):
- """Returns the node type, as an h5py class.
-
- :rtype:
- :class:`h5py.File`, :class:`h5py.Group` or :class:`h5py.Dataset`
- """
- type_ = self.h5type
- return silx.io.utils.h5type_to_h5py_class(type_)
-
- @property
- def basename(self):
- """Returns the basename of this h5py node. It is the last identifier of
- the path.
-
- :rtype: str
- """
- return self.__h5py_object.name.split("/")[-1]
-
- @property
- def is_broken(self):
- """Returns true if the node is a broken link.
-
- :rtype: bool
- """
- if self.__h5py_item is None:
- raise RuntimeError("h5py_item is not defined")
- return self.__h5py_item.isBrokenObj()
-
- @property
- def local_name(self):
- """Returns the path from the master file root to this node.
-
- For links, this path is not equal to the h5py one.
-
- :rtype: str
- """
- if self.__h5py_item is None:
- raise RuntimeError("h5py_item is not defined")
-
- result = []
- item = self.__h5py_item
- while item is not None:
- # stop before the root item (item without parent)
- if item.parent.parent is None:
- name = item.obj.name
- if name != "/":
- result.append(item.obj.name)
- break
- else:
- result.append(item.basename)
- item = item.parent
- if item is None:
- raise RuntimeError("The item does not have parent holding h5py.File")
- if result == []:
- return "/"
- if not result[-1].startswith("/"):
- result.append("")
- result.reverse()
- name = "/".join(result)
- return name
-
- def __get_local_file(self):
- """Returns the file of the root of this tree
-
- :rtype: h5py.File
- """
- item = self.__h5py_item
- while item.parent.parent is not None:
- class_ = silx.io.utils.get_h5_class(class_=item.h5pyClass)
- if class_ == silx.io.utils.H5Type.FILE:
- break
- item = item.parent
-
- class_ = silx.io.utils.get_h5_class(class_=item.h5pyClass)
- if class_ == silx.io.utils.H5Type.FILE:
- return item.obj
- else:
- return item.obj.file
-
- @property
- def local_file(self):
- """Returns the master file in which is this node.
-
- For path containing external links, this file is not equal to the h5py
- one.
-
- :rtype: h5py.File
- :raises RuntimeException: If no file are found
- """
- return self.__get_local_file()
-
- @property
- def local_filename(self):
- """Returns the filename from the master file of this node.
-
- For path containing external links, this path is not equal to the
- filename provided by h5py.
-
- :rtype: str
- :raises RuntimeException: If no file are found
- """
- return self.local_file.filename
-
- @property
- def local_basename(self):
- """Returns the basename from the master file root to this node.
-
- For path containing links, this basename can be different than the
- basename provided by h5py.
-
- :rtype: str
- """
- class_ = self.__h5py_item.h5Class
- if class_ is not None and class_ == silx.io.utils.H5Type.FILE:
- return ""
- return self.__h5py_item.basename
-
- @property
- def physical_file(self):
- """Returns the physical file in which is this node.
-
- .. versionadded:: 0.6
-
- :rtype: h5py.File
- :raises RuntimeError: If no file are found
- """
- class_ = silx.io.utils.get_h5_class(self.__h5py_object)
- if class_ == silx.io.utils.H5Type.EXTERNAL_LINK:
- # It means the link is broken
- raise RuntimeError("No file node found")
- if class_ == silx.io.utils.H5Type.SOFT_LINK:
- # It means the link is broken
- return self.local_file
-
- physical_obj = self.h5py_target
- return physical_obj.file
-
- @property
- def physical_name(self):
- """Returns the path from the location this h5py node is physically
- stored.
-
- For broken links, this filename can be different from the
- filename provided by h5py.
-
- :rtype: str
- """
- class_ = silx.io.utils.get_h5_class(self.__h5py_object)
- if class_ == silx.io.utils.H5Type.EXTERNAL_LINK:
- # It means the link is broken
- return self.__h5py_object.path
- if class_ == silx.io.utils.H5Type.SOFT_LINK:
- # It means the link is broken
- return self.__h5py_object.path
-
- physical_obj = self.h5py_target
- return physical_obj.name
-
- @property
- def physical_filename(self):
- """Returns the filename from the location this h5py node is physically
- stored.
-
- For broken links, this filename can be different from the
- filename provided by h5py.
-
- :rtype: str
- """
- class_ = silx.io.utils.get_h5_class(self.__h5py_object)
- if class_ == silx.io.utils.H5Type.EXTERNAL_LINK:
- # It means the link is broken
- return self.__h5py_object.filename
- if class_ == silx.io.utils.H5Type.SOFT_LINK:
- # It means the link is broken
- return self.local_file.filename
-
- return self.physical_file.filename
-
- @property
- def physical_basename(self):
- """Returns the basename from the location this h5py node is physically
- stored.
-
- For broken links, this basename can be different from the
- basename provided by h5py.
-
- :rtype: str
- """
- return self.physical_name.split("/")[-1]
diff --git a/silx/gui/hdf5/setup.py b/silx/gui/hdf5/setup.py
deleted file mode 100644
index 786a851..0000000
--- a/silx/gui/hdf5/setup.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016 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__ = "28/09/2016"
-
-
-from numpy.distutils.misc_util import Configuration
-
-
-def configuration(parent_package='', top_path=None):
- config = Configuration('hdf5', parent_package, top_path)
- config.add_subpackage('test')
- return config
-
-
-if __name__ == "__main__":
- from numpy.distutils.core import setup
- setup(configuration=configuration)
diff --git a/silx/gui/hdf5/test/__init__.py b/silx/gui/hdf5/test/__init__.py
deleted file mode 100644
index 3000d96..0000000
--- a/silx/gui/hdf5/test/__init__.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016 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.
-#
-# ###########################################################################*/
-import unittest
-
-from . import test_hdf5
-
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "28/09/2016"
-
-
-def suite():
- test_suite = unittest.TestSuite()
- test_suite.addTests(
- [test_hdf5.suite()])
- return test_suite
diff --git a/silx/gui/hdf5/test/test_hdf5.py b/silx/gui/hdf5/test/test_hdf5.py
deleted file mode 100644
index 1751a21..0000000
--- a/silx/gui/hdf5/test/test_hdf5.py
+++ /dev/null
@@ -1,981 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016 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.
-#
-# ###########################################################################*/
-"""Test for silx.gui.hdf5 module"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "03/05/2018"
-
-
-import time
-import os
-import unittest
-import tempfile
-import numpy
-import shutil
-from contextlib import contextmanager
-from silx.gui import qt
-from silx.gui.utils.testutils import TestCaseQt
-from silx.gui import hdf5
-from silx.gui.utils.testutils import SignalListener
-from silx.io import commonh5
-import weakref
-
-try:
- import h5py
-except ImportError:
- h5py = None
-
-
-_tmpDirectory = None
-
-
-def setUpModule():
- global _tmpDirectory
- _tmpDirectory = tempfile.mkdtemp(prefix=__name__)
-
- if h5py is not None:
- filename = _tmpDirectory + "/data.h5"
-
- # create h5 data
- f = h5py.File(filename, "w")
- g = f.create_group("arrays")
- g.create_dataset("scalar", data=10)
- f.close()
-
-
-def tearDownModule():
- global _tmpDirectory
- shutil.rmtree(_tmpDirectory)
- _tmpDirectory = None
-
-
-_called = 0
-
-
-class _Holder(object):
- def callback(self, *args, **kvargs):
- _called += 1
-
-
-def create_NXentry(group, name):
- attrs = {"NX_class": "NXentry"}
- node = commonh5.Group(name, parent=group, attrs=attrs)
- group.add_node(node)
- return node
-
-
-class TestHdf5TreeModel(TestCaseQt):
-
- def setUp(self):
- super(TestHdf5TreeModel, self).setUp()
- if h5py is None:
- self.skipTest("h5py is not available")
-
- def waitForPendingOperations(self, model):
- for _ in range(10):
- if not model.hasPendingOperations():
- break
- self.qWait(10)
- else:
- raise RuntimeError("Still waiting for a pending operation")
-
- @contextmanager
- def h5TempFile(self):
- # create tmp file
- fd, tmp_name = tempfile.mkstemp(suffix=".h5")
- os.close(fd)
- # create h5 data
- h5file = h5py.File(tmp_name, "w")
- g = h5file.create_group("arrays")
- g.create_dataset("scalar", data=10)
- h5file.close()
- yield tmp_name
- # clean up
- os.unlink(tmp_name)
-
- def testCreate(self):
- model = hdf5.Hdf5TreeModel()
- self.assertIsNotNone(model)
-
- def testAppendFilename(self):
- filename = _tmpDirectory + "/data.h5"
- model = hdf5.Hdf5TreeModel()
- self.assertEqual(model.rowCount(qt.QModelIndex()), 0)
- model.appendFile(filename)
- self.assertEqual(model.rowCount(qt.QModelIndex()), 1)
- # clean up
- index = model.index(0, 0, qt.QModelIndex())
- h5File = model.data(index, hdf5.Hdf5TreeModel.H5PY_OBJECT_ROLE)
- ref = weakref.ref(model)
- model = None
- self.qWaitForDestroy(ref)
-
- def testAppendBadFilename(self):
- model = hdf5.Hdf5TreeModel()
- self.assertRaises(IOError, model.appendFile, "#%$")
-
- def testInsertFilename(self):
- filename = _tmpDirectory + "/data.h5"
- try:
- model = hdf5.Hdf5TreeModel()
- self.assertEqual(model.rowCount(qt.QModelIndex()), 0)
- model.insertFile(filename)
- self.assertEqual(model.rowCount(qt.QModelIndex()), 1)
- # clean up
- index = model.index(0, 0, qt.QModelIndex())
- h5File = model.data(index, hdf5.Hdf5TreeModel.H5PY_OBJECT_ROLE)
- self.assertIsNotNone(h5File)
- finally:
- ref = weakref.ref(model)
- model = None
- self.qWaitForDestroy(ref)
-
- def testInsertFilenameAsync(self):
- filename = _tmpDirectory + "/data.h5"
- try:
- model = hdf5.Hdf5TreeModel()
- self.assertEqual(model.rowCount(qt.QModelIndex()), 0)
- model.insertFileAsync(filename)
- index = model.index(0, 0, qt.QModelIndex())
- self.assertIsInstance(model.nodeFromIndex(index), hdf5.Hdf5LoadingItem.Hdf5LoadingItem)
- self.waitForPendingOperations(model)
- index = model.index(0, 0, qt.QModelIndex())
- self.assertIsInstance(model.nodeFromIndex(index), hdf5.Hdf5Item.Hdf5Item)
- finally:
- ref = weakref.ref(model)
- model = None
- self.qWaitForDestroy(ref)
-
- def testInsertObject(self):
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- model = hdf5.Hdf5TreeModel()
- self.assertEqual(model.rowCount(qt.QModelIndex()), 0)
- model.insertH5pyObject(h5)
- self.assertEqual(model.rowCount(qt.QModelIndex()), 1)
-
- def testRemoveObject(self):
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- model = hdf5.Hdf5TreeModel()
- self.assertEqual(model.rowCount(qt.QModelIndex()), 0)
- model.insertH5pyObject(h5)
- self.assertEqual(model.rowCount(qt.QModelIndex()), 1)
- model.removeH5pyObject(h5)
- self.assertEqual(model.rowCount(qt.QModelIndex()), 0)
-
- def testSynchronizeObject(self):
- filename = _tmpDirectory + "/data.h5"
- h5 = h5py.File(filename)
- model = hdf5.Hdf5TreeModel()
- model.insertH5pyObject(h5)
- self.assertEqual(model.rowCount(qt.QModelIndex()), 1)
- index = model.index(0, 0, qt.QModelIndex())
- node1 = model.nodeFromIndex(index)
- model.synchronizeH5pyObject(h5)
- self.waitForPendingOperations(model)
- # Now h5 was loaded from it's filename
- # Another ref is owned by the model
- h5.close()
-
- index = model.index(0, 0, qt.QModelIndex())
- node2 = model.nodeFromIndex(index)
- self.assertIsNot(node1, node2)
- # after sync
- time.sleep(0.1)
- self.qapp.processEvents()
- time.sleep(0.1)
- index = model.index(0, 0, qt.QModelIndex())
- self.assertIsInstance(model.nodeFromIndex(index), hdf5.Hdf5Item.Hdf5Item)
- # clean up
- index = model.index(0, 0, qt.QModelIndex())
- h5File = model.data(index, hdf5.Hdf5TreeModel.H5PY_OBJECT_ROLE)
- self.assertIsNotNone(h5File)
- h5File = None
- # delete the model
- ref = weakref.ref(model)
- model = None
- self.qWaitForDestroy(ref)
-
- def testFileMoveState(self):
- model = hdf5.Hdf5TreeModel()
- self.assertEqual(model.isFileMoveEnabled(), True)
- model.setFileMoveEnabled(False)
- self.assertEqual(model.isFileMoveEnabled(), False)
-
- def testFileDropState(self):
- model = hdf5.Hdf5TreeModel()
- self.assertEqual(model.isFileDropEnabled(), True)
- model.setFileDropEnabled(False)
- self.assertEqual(model.isFileDropEnabled(), False)
-
- def testSupportedDrop(self):
- model = hdf5.Hdf5TreeModel()
- self.assertNotEquals(model.supportedDropActions(), 0)
-
- model.setFileMoveEnabled(False)
- model.setFileDropEnabled(False)
- self.assertEqual(model.supportedDropActions(), 0)
-
- model.setFileMoveEnabled(False)
- model.setFileDropEnabled(True)
- self.assertNotEquals(model.supportedDropActions(), 0)
-
- model.setFileMoveEnabled(True)
- model.setFileDropEnabled(False)
- self.assertNotEquals(model.supportedDropActions(), 0)
-
- def testDropExternalFile(self):
- filename = _tmpDirectory + "/data.h5"
- model = hdf5.Hdf5TreeModel()
- mimeData = qt.QMimeData()
- mimeData.setUrls([qt.QUrl.fromLocalFile(filename)])
- model.dropMimeData(mimeData, qt.Qt.CopyAction, 0, 0, qt.QModelIndex())
- self.assertEqual(model.rowCount(qt.QModelIndex()), 1)
- # after sync
- self.waitForPendingOperations(model)
- index = model.index(0, 0, qt.QModelIndex())
- self.assertIsInstance(model.nodeFromIndex(index), hdf5.Hdf5Item.Hdf5Item)
- # clean up
- index = model.index(0, 0, qt.QModelIndex())
- h5File = model.data(index, role=hdf5.Hdf5TreeModel.H5PY_OBJECT_ROLE)
- self.assertIsNotNone(h5File)
- h5File = None
- ref = weakref.ref(model)
- model = None
- self.qWaitForDestroy(ref)
-
- def getRowDataAsDict(self, model, row):
- displayed = {}
- roles = [qt.Qt.DisplayRole, qt.Qt.DecorationRole, qt.Qt.ToolTipRole, qt.Qt.TextAlignmentRole]
- for column in range(0, model.columnCount(qt.QModelIndex())):
- index = model.index(0, column, qt.QModelIndex())
- for role in roles:
- datum = model.data(index, role)
- displayed[column, role] = datum
- return displayed
-
- def getItemName(self, model, row):
- index = model.index(row, hdf5.Hdf5TreeModel.NAME_COLUMN, qt.QModelIndex())
- return model.data(index, qt.Qt.DisplayRole)
-
- def testFileData(self):
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- model = hdf5.Hdf5TreeModel()
- model.insertH5pyObject(h5)
- displayed = self.getRowDataAsDict(model, row=0)
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.NAME_COLUMN, qt.Qt.DisplayRole], "1.mock")
- self.assertIsInstance(displayed[hdf5.Hdf5TreeModel.NAME_COLUMN, qt.Qt.DecorationRole], qt.QIcon)
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.TYPE_COLUMN, qt.Qt.DisplayRole], "")
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.SHAPE_COLUMN, qt.Qt.DisplayRole], "")
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.VALUE_COLUMN, qt.Qt.DisplayRole], "")
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.DESCRIPTION_COLUMN, qt.Qt.DisplayRole], "")
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.NODE_COLUMN, qt.Qt.DisplayRole], "File")
-
- def testGroupData(self):
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- d = h5.create_group("foo")
- d.attrs["desc"] = "fooo"
-
- model = hdf5.Hdf5TreeModel()
- model.insertH5pyObject(d)
- displayed = self.getRowDataAsDict(model, row=0)
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.NAME_COLUMN, qt.Qt.DisplayRole], "1.mock::foo")
- self.assertIsInstance(displayed[hdf5.Hdf5TreeModel.NAME_COLUMN, qt.Qt.DecorationRole], qt.QIcon)
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.TYPE_COLUMN, qt.Qt.DisplayRole], "")
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.SHAPE_COLUMN, qt.Qt.DisplayRole], "")
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.VALUE_COLUMN, qt.Qt.DisplayRole], "")
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.DESCRIPTION_COLUMN, qt.Qt.DisplayRole], "fooo")
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.NODE_COLUMN, qt.Qt.DisplayRole], "Group")
-
- def testDatasetData(self):
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- value = numpy.array([1, 2, 3])
- d = h5.create_dataset("foo", data=value)
-
- model = hdf5.Hdf5TreeModel()
- model.insertH5pyObject(d)
- displayed = self.getRowDataAsDict(model, row=0)
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.NAME_COLUMN, qt.Qt.DisplayRole], "1.mock::foo")
- self.assertIsInstance(displayed[hdf5.Hdf5TreeModel.NAME_COLUMN, qt.Qt.DecorationRole], qt.QIcon)
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.TYPE_COLUMN, qt.Qt.DisplayRole], value.dtype.name)
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.SHAPE_COLUMN, qt.Qt.DisplayRole], "3")
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.VALUE_COLUMN, qt.Qt.DisplayRole], "[1 2 3]")
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.DESCRIPTION_COLUMN, qt.Qt.DisplayRole], "")
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.NODE_COLUMN, qt.Qt.DisplayRole], "Dataset")
-
- def testDropLastAsFirst(self):
- model = hdf5.Hdf5TreeModel()
- h5_1 = commonh5.File("/foo/bar/1.mock", "w")
- h5_2 = commonh5.File("/foo/bar/2.mock", "w")
- model.insertH5pyObject(h5_1)
- model.insertH5pyObject(h5_2)
- self.assertEqual(self.getItemName(model, 0), "1.mock")
- self.assertEqual(self.getItemName(model, 1), "2.mock")
- index = model.index(1, 0, qt.QModelIndex())
- mimeData = model.mimeData([index])
- model.dropMimeData(mimeData, qt.Qt.MoveAction, 0, 0, qt.QModelIndex())
- self.assertEqual(self.getItemName(model, 0), "2.mock")
- self.assertEqual(self.getItemName(model, 1), "1.mock")
-
- def testDropFirstAsLast(self):
- model = hdf5.Hdf5TreeModel()
- h5_1 = commonh5.File("/foo/bar/1.mock", "w")
- h5_2 = commonh5.File("/foo/bar/2.mock", "w")
- model.insertH5pyObject(h5_1)
- model.insertH5pyObject(h5_2)
- self.assertEqual(self.getItemName(model, 0), "1.mock")
- self.assertEqual(self.getItemName(model, 1), "2.mock")
- index = model.index(0, 0, qt.QModelIndex())
- mimeData = model.mimeData([index])
- model.dropMimeData(mimeData, qt.Qt.MoveAction, 2, 0, qt.QModelIndex())
- self.assertEqual(self.getItemName(model, 0), "2.mock")
- self.assertEqual(self.getItemName(model, 1), "1.mock")
-
- def testRootParent(self):
- model = hdf5.Hdf5TreeModel()
- h5_1 = commonh5.File("/foo/bar/1.mock", "w")
- model.insertH5pyObject(h5_1)
- index = model.index(0, 0, qt.QModelIndex())
- index = model.parent(index)
- self.assertEqual(index, qt.QModelIndex())
-
-
-class TestHdf5TreeModelSignals(TestCaseQt):
-
- def setUp(self):
- TestCaseQt.setUp(self)
- self.model = hdf5.Hdf5TreeModel()
- filename = _tmpDirectory + "/data.h5"
- self.h5 = h5py.File(filename)
- self.model.insertH5pyObject(self.h5)
-
- self.listener = SignalListener()
- self.model.sigH5pyObjectLoaded.connect(self.listener.partial(signal="loaded"))
- self.model.sigH5pyObjectRemoved.connect(self.listener.partial(signal="removed"))
- self.model.sigH5pyObjectSynchronized.connect(self.listener.partial(signal="synchronized"))
-
- def tearDown(self):
- self.signals = None
- ref = weakref.ref(self.model)
- self.model = None
- self.qWaitForDestroy(ref)
- self.h5.close()
- self.h5 = None
- TestCaseQt.tearDown(self)
-
- def waitForPendingOperations(self, model):
- for _ in range(10):
- if not model.hasPendingOperations():
- break
- self.qWait(10)
- else:
- raise RuntimeError("Still waiting for a pending operation")
-
- def testInsert(self):
- filename = _tmpDirectory + "/data.h5"
- h5 = h5py.File(filename)
- self.model.insertH5pyObject(h5)
- self.assertEqual(self.listener.callCount(), 0)
-
- def testLoaded(self):
- filename = _tmpDirectory + "/data.h5"
- self.model.insertFile(filename)
- self.assertEqual(self.listener.callCount(), 1)
- self.assertEqual(self.listener.karguments(argumentName="signal")[0], "loaded")
- self.assertIsNot(self.listener.arguments(callIndex=0)[0], self.h5)
- self.assertEqual(self.listener.arguments(callIndex=0)[0].filename, filename)
-
- def testRemoved(self):
- self.model.removeH5pyObject(self.h5)
- self.assertEqual(self.listener.callCount(), 1)
- self.assertEqual(self.listener.karguments(argumentName="signal")[0], "removed")
- self.assertIs(self.listener.arguments(callIndex=0)[0], self.h5)
-
- def testSynchonized(self):
- self.model.synchronizeH5pyObject(self.h5)
- self.waitForPendingOperations(self.model)
- self.assertEqual(self.listener.callCount(), 1)
- self.assertEqual(self.listener.karguments(argumentName="signal")[0], "synchronized")
- self.assertIs(self.listener.arguments(callIndex=0)[0], self.h5)
- self.assertIsNot(self.listener.arguments(callIndex=0)[1], self.h5)
-
-
-class TestNexusSortFilterProxyModel(TestCaseQt):
-
- def getChildNames(self, model, index):
- count = model.rowCount(index)
- result = []
- for row in range(0, count):
- itemIndex = model.index(row, hdf5.Hdf5TreeModel.NAME_COLUMN, index)
- name = model.data(itemIndex, qt.Qt.DisplayRole)
- result.append(name)
- return result
-
- def testNXentryStartTime(self):
- """Test NXentry with start_time"""
- model = hdf5.Hdf5TreeModel()
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- create_NXentry(h5, "a").create_dataset("start_time", data=numpy.string_("2015"))
- create_NXentry(h5, "b").create_dataset("start_time", data=numpy.string_("2013"))
- create_NXentry(h5, "c").create_dataset("start_time", data=numpy.string_("2014"))
- model.insertH5pyObject(h5)
-
- proxy = hdf5.NexusSortFilterProxyModel()
- proxy.setSourceModel(model)
- proxy.sort(0, qt.Qt.DescendingOrder)
- names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex()))
- self.assertListEqual(names, ["a", "c", "b"])
-
- def testNXentryStartTimeInArray(self):
- """Test NXentry with start_time"""
- model = hdf5.Hdf5TreeModel()
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- create_NXentry(h5, "a").create_dataset("start_time", data=numpy.array([numpy.string_("2015")]))
- create_NXentry(h5, "b").create_dataset("start_time", data=numpy.array([numpy.string_("2013")]))
- create_NXentry(h5, "c").create_dataset("start_time", data=numpy.array([numpy.string_("2014")]))
- model.insertH5pyObject(h5)
-
- proxy = hdf5.NexusSortFilterProxyModel()
- proxy.setSourceModel(model)
- proxy.sort(0, qt.Qt.DescendingOrder)
- names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex()))
- self.assertListEqual(names, ["a", "c", "b"])
-
- def testNXentryEndTimeInArray(self):
- """Test NXentry with end_time"""
- model = hdf5.Hdf5TreeModel()
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- create_NXentry(h5, "a").create_dataset("end_time", data=numpy.array([numpy.string_("2015")]))
- create_NXentry(h5, "b").create_dataset("end_time", data=numpy.array([numpy.string_("2013")]))
- create_NXentry(h5, "c").create_dataset("end_time", data=numpy.array([numpy.string_("2014")]))
- model.insertH5pyObject(h5)
-
- proxy = hdf5.NexusSortFilterProxyModel()
- proxy.setSourceModel(model)
- proxy.sort(0, qt.Qt.DescendingOrder)
- names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex()))
- self.assertListEqual(names, ["a", "c", "b"])
-
- def testNXentryName(self):
- """Test NXentry without start_time or end_time"""
- model = hdf5.Hdf5TreeModel()
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- create_NXentry(h5, "a")
- create_NXentry(h5, "c")
- create_NXentry(h5, "b")
- model.insertH5pyObject(h5)
-
- proxy = hdf5.NexusSortFilterProxyModel()
- proxy.setSourceModel(model)
- proxy.sort(0, qt.Qt.AscendingOrder)
- names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex()))
- self.assertListEqual(names, ["a", "b", "c"])
-
- def testStartTime(self):
- """If it is not NXentry, start_time is not used"""
- model = hdf5.Hdf5TreeModel()
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- h5.create_group("a").create_dataset("start_time", data=numpy.string_("2015"))
- h5.create_group("b").create_dataset("start_time", data=numpy.string_("2013"))
- h5.create_group("c").create_dataset("start_time", data=numpy.string_("2014"))
- model.insertH5pyObject(h5)
-
- proxy = hdf5.NexusSortFilterProxyModel()
- proxy.setSourceModel(model)
- proxy.sort(0, qt.Qt.AscendingOrder)
- names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex()))
- self.assertListEqual(names, ["a", "b", "c"])
-
- def testName(self):
- model = hdf5.Hdf5TreeModel()
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- h5.create_group("a")
- h5.create_group("c")
- h5.create_group("b")
- model.insertH5pyObject(h5)
-
- proxy = hdf5.NexusSortFilterProxyModel()
- proxy.setSourceModel(model)
- proxy.sort(0, qt.Qt.AscendingOrder)
- names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex()))
- self.assertListEqual(names, ["a", "b", "c"])
-
- def testNumber(self):
- model = hdf5.Hdf5TreeModel()
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- h5.create_group("a1")
- h5.create_group("a20")
- h5.create_group("a3")
- model.insertH5pyObject(h5)
-
- proxy = hdf5.NexusSortFilterProxyModel()
- proxy.setSourceModel(model)
- proxy.sort(0, qt.Qt.AscendingOrder)
- names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex()))
- self.assertListEqual(names, ["a1", "a3", "a20"])
-
- def testMultiNumber(self):
- model = hdf5.Hdf5TreeModel()
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- h5.create_group("a1-1")
- h5.create_group("a20-1")
- h5.create_group("a3-1")
- h5.create_group("a3-20")
- h5.create_group("a3-3")
- model.insertH5pyObject(h5)
-
- proxy = hdf5.NexusSortFilterProxyModel()
- proxy.setSourceModel(model)
- proxy.sort(0, qt.Qt.AscendingOrder)
- names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex()))
- self.assertListEqual(names, ["a1-1", "a3-1", "a3-3", "a3-20", "a20-1"])
-
- def testUnconsistantTypes(self):
- model = hdf5.Hdf5TreeModel()
- h5 = commonh5.File("/foo/bar/1.mock", "w")
- h5.create_group("aaa100")
- h5.create_group("100aaa")
- model.insertH5pyObject(h5)
-
- proxy = hdf5.NexusSortFilterProxyModel()
- proxy.setSourceModel(model)
- proxy.sort(0, qt.Qt.AscendingOrder)
- names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex()))
- self.assertListEqual(names, ["100aaa", "aaa100"])
-
-
-class TestH5Node(TestCaseQt):
-
- @classmethod
- def setUpClass(cls):
- super(TestH5Node, cls).setUpClass()
- if h5py is None:
- raise unittest.SkipTest("h5py is not available")
-
- cls.tmpDirectory = tempfile.mkdtemp()
- cls.h5Filename = cls.createResource(cls.tmpDirectory)
- cls.h5File = h5py.File(cls.h5Filename, mode="r")
- cls.model = cls.createModel(cls.h5File)
-
- @classmethod
- def createResource(cls, directory):
- filename = os.path.join(directory, "base.h5")
- externalFilename = os.path.join(directory, "base__external.h5")
-
- externalh5 = h5py.File(externalFilename, mode="w")
- externalh5["target/dataset"] = 50
- externalh5["target/link"] = h5py.SoftLink("/target/dataset")
- externalh5.close()
-
- h5 = h5py.File(filename, mode="w")
- h5["group/dataset"] = 50
- h5["link/soft_link"] = h5py.SoftLink("/group/dataset")
- h5["link/soft_link_to_group"] = h5py.SoftLink("/group")
- h5["link/soft_link_to_link"] = h5py.SoftLink("/link/soft_link")
- h5["link/soft_link_to_file"] = h5py.SoftLink("/")
- h5["group/soft_link_relative"] = h5py.SoftLink("dataset")
- h5["link/external_link"] = h5py.ExternalLink(externalFilename, "/target/dataset")
- h5["link/external_link_to_link"] = h5py.ExternalLink(externalFilename, "/target/link")
- h5["broken_link/external_broken_file"] = h5py.ExternalLink(externalFilename + "_not_exists", "/target/link")
- h5["broken_link/external_broken_link"] = h5py.ExternalLink(externalFilename, "/target/not_exists")
- h5["broken_link/soft_broken_link"] = h5py.SoftLink("/group/not_exists")
- h5["broken_link/soft_link_to_broken_link"] = h5py.SoftLink("/group/not_exists")
- h5.close()
-
- return filename
-
- @classmethod
- def createModel(cls, h5pyFile):
- model = hdf5.Hdf5TreeModel()
- model.insertH5pyObject(h5pyFile)
- return model
-
- @classmethod
- def tearDownClass(cls):
- ref = weakref.ref(cls.model)
- cls.model = None
- cls.qWaitForDestroy(ref)
- cls.h5File.close()
- shutil.rmtree(cls.tmpDirectory)
- super(TestH5Node, cls).tearDownClass()
-
- def getIndexFromPath(self, model, path):
- """
- :param qt.QAbstractItemModel: model
- """
- index = qt.QModelIndex()
- for name in path:
- for row in range(model.rowCount(index)):
- i = model.index(row, 0, index)
- label = model.data(i)
- if label == name:
- index = i
- break
- else:
- raise RuntimeError("Path not found")
- return index
-
- def getH5NodeFromPath(self, model, path):
- index = self.getIndexFromPath(model, path)
- item = model.data(index, hdf5.Hdf5TreeModel.H5PY_ITEM_ROLE)
- h5node = hdf5.H5Node(item)
- return h5node
-
- def testFile(self):
- path = ["base.h5"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.physical_filename)
- self.assertEqual(h5node.physical_basename, "")
- self.assertEqual(h5node.physical_name, "/")
- self.assertEqual(h5node.local_basename, "")
- self.assertEqual(h5node.local_name, "/")
-
- def testGroup(self):
- path = ["base.h5", "group"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.physical_filename)
- self.assertEqual(h5node.physical_basename, "group")
- self.assertEqual(h5node.physical_name, "/group")
- self.assertEqual(h5node.local_basename, "group")
- self.assertEqual(h5node.local_name, "/group")
-
- def testDataset(self):
- path = ["base.h5", "group", "dataset"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.physical_filename)
- self.assertEqual(h5node.physical_basename, "dataset")
- self.assertEqual(h5node.physical_name, "/group/dataset")
- self.assertEqual(h5node.local_basename, "dataset")
- self.assertEqual(h5node.local_name, "/group/dataset")
-
- def testSoftLink(self):
- path = ["base.h5", "link", "soft_link"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.physical_filename)
- self.assertEqual(h5node.physical_basename, "dataset")
- self.assertEqual(h5node.physical_name, "/group/dataset")
- self.assertEqual(h5node.local_basename, "soft_link")
- self.assertEqual(h5node.local_name, "/link/soft_link")
-
- def testSoftLinkToLink(self):
- path = ["base.h5", "link", "soft_link_to_link"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.physical_filename)
- self.assertEqual(h5node.physical_basename, "dataset")
- self.assertEqual(h5node.physical_name, "/group/dataset")
- self.assertEqual(h5node.local_basename, "soft_link_to_link")
- self.assertEqual(h5node.local_name, "/link/soft_link_to_link")
-
- def testSoftLinkRelative(self):
- path = ["base.h5", "group", "soft_link_relative"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.physical_filename)
- self.assertEqual(h5node.physical_basename, "dataset")
- self.assertEqual(h5node.physical_name, "/group/dataset")
- self.assertEqual(h5node.local_basename, "soft_link_relative")
- self.assertEqual(h5node.local_name, "/group/soft_link_relative")
-
- def testExternalLink(self):
- path = ["base.h5", "link", "external_link"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertNotEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.local_filename)
- self.assertIn("base__external.h5", h5node.physical_filename)
- self.assertEqual(h5node.physical_basename, "dataset")
- self.assertEqual(h5node.physical_name, "/target/dataset")
- self.assertEqual(h5node.local_basename, "external_link")
- self.assertEqual(h5node.local_name, "/link/external_link")
-
- def testExternalLinkToLink(self):
- path = ["base.h5", "link", "external_link_to_link"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertNotEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.local_filename)
- self.assertIn("base__external.h5", h5node.physical_filename)
-
- self.assertNotEqual(h5node.physical_filename, h5node.local_filename)
- self.assertEqual(h5node.physical_basename, "dataset")
- self.assertEqual(h5node.physical_name, "/target/dataset")
- self.assertEqual(h5node.local_basename, "external_link_to_link")
- self.assertEqual(h5node.local_name, "/link/external_link_to_link")
-
- def testExternalBrokenFile(self):
- path = ["base.h5", "broken_link", "external_broken_file"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertNotEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.local_filename)
- self.assertIn("not_exists", h5node.physical_filename)
- self.assertEqual(h5node.physical_basename, "link")
- self.assertEqual(h5node.physical_name, "/target/link")
- self.assertEqual(h5node.local_basename, "external_broken_file")
- self.assertEqual(h5node.local_name, "/broken_link/external_broken_file")
-
- def testExternalBrokenLink(self):
- path = ["base.h5", "broken_link", "external_broken_link"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertNotEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.local_filename)
- self.assertIn("__external", h5node.physical_filename)
- self.assertEqual(h5node.physical_basename, "not_exists")
- self.assertEqual(h5node.physical_name, "/target/not_exists")
- self.assertEqual(h5node.local_basename, "external_broken_link")
- self.assertEqual(h5node.local_name, "/broken_link/external_broken_link")
-
- def testSoftBrokenLink(self):
- path = ["base.h5", "broken_link", "soft_broken_link"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.physical_filename)
- self.assertEqual(h5node.physical_basename, "not_exists")
- self.assertEqual(h5node.physical_name, "/group/not_exists")
- self.assertEqual(h5node.local_basename, "soft_broken_link")
- self.assertEqual(h5node.local_name, "/broken_link/soft_broken_link")
-
- def testSoftLinkToBrokenLink(self):
- path = ["base.h5", "broken_link", "soft_link_to_broken_link"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.physical_filename)
- self.assertEqual(h5node.physical_basename, "not_exists")
- self.assertEqual(h5node.physical_name, "/group/not_exists")
- self.assertEqual(h5node.local_basename, "soft_link_to_broken_link")
- self.assertEqual(h5node.local_name, "/broken_link/soft_link_to_broken_link")
-
- def testDatasetFromSoftLinkToGroup(self):
- path = ["base.h5", "link", "soft_link_to_group", "dataset"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.physical_filename)
- self.assertEqual(h5node.physical_basename, "dataset")
- self.assertEqual(h5node.physical_name, "/group/dataset")
- self.assertEqual(h5node.local_basename, "dataset")
- self.assertEqual(h5node.local_name, "/link/soft_link_to_group/dataset")
-
- def testDatasetFromSoftLinkToFile(self):
- path = ["base.h5", "link", "soft_link_to_file", "link", "soft_link_to_group", "dataset"]
- h5node = self.getH5NodeFromPath(self.model, path)
-
- self.assertEqual(h5node.physical_filename, h5node.local_filename)
- self.assertIn("base.h5", h5node.physical_filename)
- self.assertEqual(h5node.physical_basename, "dataset")
- self.assertEqual(h5node.physical_name, "/group/dataset")
- self.assertEqual(h5node.local_basename, "dataset")
- self.assertEqual(h5node.local_name, "/link/soft_link_to_file/link/soft_link_to_group/dataset")
-
-
-class TestHdf5TreeView(TestCaseQt):
- """Test to check that icons module."""
-
- def setUp(self):
- super(TestHdf5TreeView, self).setUp()
- if h5py is None:
- self.skipTest("h5py is not available")
-
- def testCreate(self):
- view = hdf5.Hdf5TreeView()
- self.assertIsNotNone(view)
-
- def testContextMenu(self):
- view = hdf5.Hdf5TreeView()
- view._createContextMenu(qt.QPoint(0, 0))
-
- def testSelection_OriginalModel(self):
- tree = commonh5.File("/foo/bar/1.mock", "w")
- item = tree.create_group("a/b/c/d")
- item.create_group("e").create_group("f")
-
- view = hdf5.Hdf5TreeView()
- view.findHdf5TreeModel().insertH5pyObject(tree)
- view.setSelectedH5Node(item)
-
- selected = list(view.selectedH5Nodes())[0]
- self.assertIs(item, selected.h5py_object)
-
- def testSelection_Simple(self):
- tree = commonh5.File("/foo/bar/1.mock", "w")
- item = tree.create_group("a/b/c/d")
- item.create_group("e").create_group("f")
-
- model = hdf5.Hdf5TreeModel()
- model.insertH5pyObject(tree)
- view = hdf5.Hdf5TreeView()
- view.setModel(model)
- view.setSelectedH5Node(item)
-
- selected = list(view.selectedH5Nodes())[0]
- self.assertIs(item, selected.h5py_object)
-
- def testSelection_NotFound(self):
- tree2 = commonh5.File("/foo/bar/2.mock", "w")
- tree = commonh5.File("/foo/bar/1.mock", "w")
- item = tree.create_group("a/b/c/d")
- item.create_group("e").create_group("f")
-
- model = hdf5.Hdf5TreeModel()
- model.insertH5pyObject(tree)
- view = hdf5.Hdf5TreeView()
- view.setModel(model)
- view.setSelectedH5Node(tree2)
-
- selection = list(view.selectedH5Nodes())
- self.assertEqual(len(selection), 0)
-
- def testSelection_ManyGroupFromSameFile(self):
- tree = commonh5.File("/foo/bar/1.mock", "w")
- group1 = tree.create_group("a1")
- group2 = tree.create_group("a2")
- group3 = tree.create_group("a3")
- group1.create_group("b/c/d")
- item = group2.create_group("b/c/d")
- group3.create_group("b/c/d")
-
- model = hdf5.Hdf5TreeModel()
- model.insertH5pyObject(group1)
- model.insertH5pyObject(group2)
- model.insertH5pyObject(group3)
- view = hdf5.Hdf5TreeView()
- view.setModel(model)
- view.setSelectedH5Node(item)
-
- selected = list(view.selectedH5Nodes())[0]
- self.assertIs(item, selected.h5py_object)
-
- def testSelection_RootFromSubTree(self):
- tree = commonh5.File("/foo/bar/1.mock", "w")
- group = tree.create_group("a1")
- group.create_group("b/c/d")
-
- model = hdf5.Hdf5TreeModel()
- model.insertH5pyObject(group)
- view = hdf5.Hdf5TreeView()
- view.setModel(model)
- view.setSelectedH5Node(group)
-
- selected = list(view.selectedH5Nodes())[0]
- self.assertIs(group, selected.h5py_object)
-
- def testSelection_FileFromSubTree(self):
- tree = commonh5.File("/foo/bar/1.mock", "w")
- group = tree.create_group("a1")
- group.create_group("b").create_group("b").create_group("d")
-
- model = hdf5.Hdf5TreeModel()
- model.insertH5pyObject(group)
- view = hdf5.Hdf5TreeView()
- view.setModel(model)
- view.setSelectedH5Node(tree)
-
- selection = list(view.selectedH5Nodes())
- self.assertEqual(len(selection), 0)
-
- def testSelection_Tree(self):
- tree1 = commonh5.File("/foo/bar/1.mock", "w")
- tree2 = commonh5.File("/foo/bar/2.mock", "w")
- tree3 = commonh5.File("/foo/bar/3.mock", "w")
- tree1.create_group("a/b/c")
- tree2.create_group("a/b/c")
- tree3.create_group("a/b/c")
- item = tree2
-
- model = hdf5.Hdf5TreeModel()
- model.insertH5pyObject(tree1)
- model.insertH5pyObject(tree2)
- model.insertH5pyObject(tree3)
- view = hdf5.Hdf5TreeView()
- view.setModel(model)
- view.setSelectedH5Node(item)
-
- selected = list(view.selectedH5Nodes())[0]
- self.assertIs(item, selected.h5py_object)
-
- def testSelection_RecurssiveLink(self):
- """
- Recurssive link selection
-
- This example is not really working as expected cause commonh5 do not
- support recurssive links.
- But item.name == "/a/b" and the result is found.
- """
- tree = commonh5.File("/foo/bar/1.mock", "w")
- group = tree.create_group("a")
- group.add_node(commonh5.SoftLink("b", "/"))
-
- item = tree["/a/b/a/b/a/b/a/b/a/b/a/b/a/b/a/b"]
-
- model = hdf5.Hdf5TreeModel()
- model.insertH5pyObject(tree)
- view = hdf5.Hdf5TreeView()
- view.setModel(model)
- view.setSelectedH5Node(item)
-
- selected = list(view.selectedH5Nodes())[0]
- self.assertEqual(item.name, selected.h5py_object.name)
-
- def testSelection_SelectNone(self):
- tree = commonh5.File("/foo/bar/1.mock", "w")
-
- model = hdf5.Hdf5TreeModel()
- model.insertH5pyObject(tree)
- view = hdf5.Hdf5TreeView()
- view.setModel(model)
- view.setSelectedH5Node(tree)
- view.setSelectedH5Node(None)
-
- selection = list(view.selectedH5Nodes())
- self.assertEqual(len(selection), 0)
-
-
-def suite():
- test_suite = unittest.TestSuite()
- loadTests = unittest.defaultTestLoader.loadTestsFromTestCase
- test_suite.addTest(loadTests(TestHdf5TreeModel))
- test_suite.addTest(loadTests(TestHdf5TreeModelSignals))
- test_suite.addTest(loadTests(TestNexusSortFilterProxyModel))
- test_suite.addTest(loadTests(TestHdf5TreeView))
- test_suite.addTest(loadTests(TestH5Node))
- return test_suite
-
-
-if __name__ == '__main__':
- unittest.main(defaultTest='suite')