diff options
author | Picca Frédéric-Emmanuel <picca@synchrotron-soleil.fr> | 2018-07-31 16:22:25 +0200 |
---|---|---|
committer | Picca Frédéric-Emmanuel <picca@synchrotron-soleil.fr> | 2018-07-31 16:22:25 +0200 |
commit | 159ef14fb9e198bb0066ea14e6b980f065de63dd (patch) | |
tree | bc37c7d4ba09ee59deb708897fa0571709aec293 /silx/gui/hdf5 | |
parent | 270d5ddc31c26b62379e3caa9044dd75ccc71847 (diff) |
New upstream version 0.8.0+dfsg
Diffstat (limited to 'silx/gui/hdf5')
-rw-r--r-- | silx/gui/hdf5/Hdf5Formatter.py | 18 | ||||
-rw-r--r-- | silx/gui/hdf5/Hdf5TreeModel.py | 90 | ||||
-rw-r--r-- | silx/gui/hdf5/Hdf5TreeView.py | 17 | ||||
-rw-r--r-- | silx/gui/hdf5/NexusSortFilterProxyModel.py | 59 | ||||
-rw-r--r-- | silx/gui/hdf5/_utils.py | 22 | ||||
-rw-r--r-- | silx/gui/hdf5/test/test_hdf5.py | 256 |
6 files changed, 351 insertions, 111 deletions
diff --git a/silx/gui/hdf5/Hdf5Formatter.py b/silx/gui/hdf5/Hdf5Formatter.py index 0e3697f..6802142 100644 --- a/silx/gui/hdf5/Hdf5Formatter.py +++ b/silx/gui/hdf5/Hdf5Formatter.py @@ -27,7 +27,7 @@ text.""" __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "23/01/2018" +__date__ = "06/06/2018" import numpy from silx.third_party import six @@ -119,7 +119,11 @@ class Hdf5Formatter(qt.QObject): return text def humanReadableType(self, dataset, full=False): - dtype = dataset.dtype + if hasattr(dataset, "dtype"): + dtype = dataset.dtype + else: + # Fallback... + dtype = type(dataset) return self.humanReadableDType(dtype, full) def humanReadableDType(self, dtype, full=False): @@ -164,6 +168,16 @@ class Hdf5Formatter(qt.QObject): 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 diff --git a/silx/gui/hdf5/Hdf5TreeModel.py b/silx/gui/hdf5/Hdf5TreeModel.py index 2d62429..835708a 100644 --- a/silx/gui/hdf5/Hdf5TreeModel.py +++ b/silx/gui/hdf5/Hdf5TreeModel.py @@ -25,7 +25,7 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "29/11/2017" +__date__ = "11/06/2018" import os @@ -205,7 +205,23 @@ class Hdf5TreeModel(qt.QAbstractItemModel): ] """List of logical columns available""" - def __init__(self, parent=None): + 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) @@ -221,6 +237,7 @@ class Hdf5TreeModel(qt.QAbstractItemModel): self.__root = Hdf5Node() self.__fileDropEnabled = True self.__fileMoveEnabled = True + self.__datasetDragEnabled = False self.__animatedIcon = icons.getWaitIcon() self.__animatedIcon.iconChanged.connect(self.__updateLoadingItems) @@ -235,6 +252,7 @@ class Hdf5TreeModel(qt.QAbstractItemModel): 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 @@ -285,16 +303,25 @@ class Hdf5TreeModel(qt.QAbstractItemModel): 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() - self.__openedFiles.append(newItem.obj) + 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): @@ -306,6 +333,15 @@ class Hdf5TreeModel(qt.QAbstractItemModel): 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 @@ -323,10 +359,12 @@ class Hdf5TreeModel(qt.QAbstractItemModel): return 0 def mimeTypes(self): + types = [] if self.__fileMoveEnabled: - return [_utils.Hdf5NodeMimeData.MIME_TYPE] - else: - return [] + types.append(_utils.Hdf5NodeMimeData.MIME_TYPE) + if self.__datasetDragEnabled: + types.append(_utils.Hdf5DatasetMimeData.MIME_TYPE) + return types def mimeData(self, indexes): """ @@ -336,7 +374,7 @@ class Hdf5TreeModel(qt.QAbstractItemModel): :param List[qt.QModelIndex] indexes: List of indexes :rtype: qt.QMimeData """ - if not self.__fileMoveEnabled or len(indexes) == 0: + if len(indexes) == 0: return None indexes = [i for i in indexes if i.column() == 0] @@ -346,7 +384,13 @@ class Hdf5TreeModel(qt.QAbstractItemModel): raise NotImplementedError("Drag of cell is not implemented") node = self.nodeFromIndex(indexes[0]) - mimeData = _utils.Hdf5NodeMimeData(node) + + 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): @@ -357,6 +401,8 @@ class Hdf5TreeModel(qt.QAbstractItemModel): 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 @@ -543,8 +589,7 @@ class Hdf5TreeModel(qt.QAbstractItemModel): return filename = node.obj.filename - self.removeIndex(index) - self.insertFileAsync(filename, index.row()) + self.insertFileAsync(filename, index.row(), synchronizingNode=node) def synchronizeH5pyObject(self, h5pyObject): """ @@ -560,8 +605,7 @@ class Hdf5TreeModel(qt.QAbstractItemModel): if item.obj is h5pyObject: qindex = self.index(index, 0, qt.QModelIndex()) self.synchronizeIndex(qindex) - else: - index += 1 + index += 1 def removeIndex(self, index): """ @@ -576,6 +620,7 @@ class Hdf5TreeModel(qt.QAbstractItemModel): self.beginRemoveRows(qt.QModelIndex(), index.row(), index.row()) self.__root.removeChildAtIndex(index.row()) self.endRemoveRows() + self.sigH5pyObjectRemoved.emit(node.obj) def removeH5pyObject(self, h5pyObject): """ @@ -608,14 +653,17 @@ class Hdf5TreeModel(qt.QAbstractItemModel): def hasPendingOperations(self): return len(self.__runnerSet) > 0 - def insertFileAsync(self, filename, row=-1): + 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 - text = os.path.basename(filename) - item = Hdf5LoadingItem(text=text, parent=self.__root, animatedIcon=self.__animatedIcon) - self.insertNode(row, 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) @@ -634,12 +682,20 @@ class Hdf5TreeModel(qt.QAbstractItemModel): """ try: h5file = silx_io.open(filename) - self.__openedFiles.append(h5file) + 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) diff --git a/silx/gui/hdf5/Hdf5TreeView.py b/silx/gui/hdf5/Hdf5TreeView.py index 78b5c19..a86140a 100644 --- a/silx/gui/hdf5/Hdf5TreeView.py +++ b/silx/gui/hdf5/Hdf5TreeView.py @@ -25,7 +25,7 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "20/02/2018" +__date__ = "30/04/2018" import logging @@ -66,10 +66,8 @@ class Hdf5TreeView(qt.QTreeView): """ qt.QTreeView.__init__(self, parent) - model = Hdf5TreeModel(self) - proxy_model = NexusSortFilterProxyModel(self) - proxy_model.setSourceModel(model) - self.setModel(proxy_model) + model = self.createDefaultModel() + self.setModel(model) self.setHeader(Hdf5HeaderView(qt.Qt.Horizontal, self)) self.setSelectionBehavior(qt.QAbstractItemView.SelectRows) @@ -87,6 +85,15 @@ class Hdf5TreeView(qt.QTreeView): 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) diff --git a/silx/gui/hdf5/NexusSortFilterProxyModel.py b/silx/gui/hdf5/NexusSortFilterProxyModel.py index 9a27968..3f2cf8d 100644 --- a/silx/gui/hdf5/NexusSortFilterProxyModel.py +++ b/silx/gui/hdf5/NexusSortFilterProxyModel.py @@ -25,7 +25,7 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "10/10/2017" +__date__ = "25/06/2018" import logging @@ -34,6 +34,7 @@ import numpy from .. import qt from .Hdf5TreeModel import Hdf5TreeModel import silx.io.utils +from silx.gui import icons _logger = logging.getLogger(__name__) @@ -45,6 +46,7 @@ class NexusSortFilterProxyModel(qt.QSortFilterProxyModel): def __init__(self, parent=None): qt.QSortFilterProxyModel.__init__(self, parent) self.__split = re.compile("(\\d+|\\D+)") + self.__iconCache = {} def lessThan(self, sourceLeft, sourceRight): """Returns True if the value of the item referred to by the given @@ -86,6 +88,14 @@ class NexusSortFilterProxyModel(qt.QSortFilterProxyModel): 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. @@ -96,11 +106,14 @@ class NexusSortFilterProxyModel(qt.QSortFilterProxyModel): :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 @@ -145,3 +158,47 @@ class NexusSortFilterProxyModel(qt.QSortFilterProxyModel): 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/_utils.py b/silx/gui/hdf5/_utils.py index ddf4db5..8385129 100644 --- a/silx/gui/hdf5/_utils.py +++ b/silx/gui/hdf5/_utils.py @@ -28,7 +28,7 @@ package `silx.gui.hdf5` package. __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "20/12/2017" +__date__ = "04/05/2018" import logging @@ -102,6 +102,26 @@ def htmlFromDict(dictionary, title=None): 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.""" diff --git a/silx/gui/hdf5/test/test_hdf5.py b/silx/gui/hdf5/test/test_hdf5.py index 44c4456..fc27f6b 100644 --- a/silx/gui/hdf5/test/test_hdf5.py +++ b/silx/gui/hdf5/test/test_hdf5.py @@ -26,7 +26,7 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "20/02/2018" +__date__ = "03/05/2018" import time @@ -39,6 +39,7 @@ from contextlib import contextmanager from silx.gui import qt from silx.gui.test.utils import TestCaseQt from silx.gui import hdf5 +from silx.gui.test.utils import SignalListener from silx.io import commonh5 import weakref @@ -48,6 +49,29 @@ 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 @@ -71,7 +95,7 @@ class TestHdf5TreeModel(TestCaseQt): self.skipTest("h5py is not available") def waitForPendingOperations(self, model): - for i in range(10): + for _ in range(10): if not model.hasPendingOperations(): break self.qWait(10) @@ -97,53 +121,53 @@ class TestHdf5TreeModel(TestCaseQt): self.assertIsNotNone(model) def testAppendFilename(self): - with self.h5TempFile() as filename: + filename = _tmpDirectory + "/data.h5" + model = hdf5.Hdf5TreeModel() + self.assertEquals(model.rowCount(qt.QModelIndex()), 0) + model.appendFile(filename) + self.assertEquals(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.assertEquals(model.rowCount(qt.QModelIndex()), 0) - model.appendFile(filename) + model.insertFile(filename) self.assertEquals(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 testAppendBadFilename(self): - model = hdf5.Hdf5TreeModel() - self.assertRaises(IOError, model.appendFile, "#%$") - - def testInsertFilename(self): - with self.h5TempFile() as filename: - try: - model = hdf5.Hdf5TreeModel() - self.assertEquals(model.rowCount(qt.QModelIndex()), 0) - model.insertFile(filename) - self.assertEquals(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): - with self.h5TempFile() as filename: - try: - model = hdf5.Hdf5TreeModel() - self.assertEquals(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) + filename = _tmpDirectory + "/data.h5" + try: + model = hdf5.Hdf5TreeModel() + self.assertEquals(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") @@ -162,36 +186,37 @@ class TestHdf5TreeModel(TestCaseQt): self.assertEquals(model.rowCount(qt.QModelIndex()), 0) def testSynchronizeObject(self): - with self.h5TempFile() as filename: - h5 = h5py.File(filename) - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(h5) - self.assertEquals(model.rowCount(qt.QModelIndex()), 1) - index = model.index(0, 0, qt.QModelIndex()) - node1 = model.nodeFromIndex(index) - model.synchronizeH5pyObject(h5) - # Now h5 was loaded from it's filename - # Another ref is owned by the model - h5.close() + filename = _tmpDirectory + "/data.h5" + h5 = h5py.File(filename) + model = hdf5.Hdf5TreeModel() + model.insertH5pyObject(h5) + self.assertEquals(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) + 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() @@ -222,24 +247,24 @@ class TestHdf5TreeModel(TestCaseQt): self.assertNotEquals(model.supportedDropActions(), 0) def testDropExternalFile(self): - with self.h5TempFile() as filename: - model = hdf5.Hdf5TreeModel() - mimeData = qt.QMimeData() - mimeData.setUrls([qt.QUrl.fromLocalFile(filename)]) - model.dropMimeData(mimeData, qt.Qt.CopyAction, 0, 0, qt.QModelIndex()) - self.assertEquals(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) + 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.assertEquals(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 = {} @@ -337,6 +362,66 @@ class TestHdf5TreeModel(TestCaseQt): self.assertEquals(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.assertEquals(self.listener.callCount(), 0) + + def testLoaded(self): + filename = _tmpDirectory + "/data.h5" + self.model.insertFile(filename) + self.assertEquals(self.listener.callCount(), 1) + self.assertEquals(self.listener.karguments(argumentName="signal")[0], "loaded") + self.assertIsNot(self.listener.arguments(callIndex=0)[0], self.h5) + self.assertEquals(self.listener.arguments(callIndex=0)[0].filename, filename) + + def testRemoved(self): + self.model.removeH5pyObject(self.h5) + self.assertEquals(self.listener.callCount(), 1) + self.assertEquals(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.assertEquals(self.listener.callCount(), 1) + self.assertEquals(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): @@ -873,6 +958,7 @@ 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)) |