# 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. # # ############################################################################*/ """Widget to custom NXdata groups""" __authors__ = ["V. Valls"] __license__ = "MIT" __date__ = "15/06/2018" import logging import numpy import weakref from silx.gui import qt from silx.io import commonh5 import silx.io.nxdata from silx.gui.hdf5._utils import Hdf5DatasetMimeData from silx.gui.data.TextFormatter import TextFormatter from silx.gui.hdf5.Hdf5Formatter import Hdf5Formatter from silx.gui import icons _logger = logging.getLogger(__name__) _formatter = TextFormatter() _hdf5Formatter = Hdf5Formatter(textFormatter=_formatter) class _RowItems(qt.QStandardItem): """Define the list of items used for a specific row.""" def type(self): return qt.QStandardItem.UserType + 1 def getRowItems(self): """Returns the list of items used for a specific row. The first item should be this class. :rtype: List[qt.QStandardItem] """ raise NotImplementedError() class _DatasetItemRow(_RowItems): """Define a row which can contain a dataset.""" def __init__(self, label="", dataset=None): """Constructor""" super(_DatasetItemRow, self).__init__(label) self.setEditable(False) self.setDropEnabled(False) self.setDragEnabled(False) self.__name = qt.QStandardItem() self.__name.setEditable(False) self.__name.setDropEnabled(True) self.__type = qt.QStandardItem() self.__type.setEditable(False) self.__type.setDropEnabled(False) self.__type.setDragEnabled(False) self.__shape = qt.QStandardItem() self.__shape.setEditable(False) self.__shape.setDropEnabled(False) self.__shape.setDragEnabled(False) self.setDataset(dataset) def getDefaultFormatter(self): """Get the formatter used to display dataset informations. :rtype: Hdf5Formatter """ return _hdf5Formatter def setDataset(self, dataset): """Set the dataset stored in this item. :param Union[numpy.ndarray,h5py.Dataset,silx.io.commonh5.Dataset] dataset: The dataset to store. """ self.__dataset = dataset if self.__dataset is not None: name = self.__dataset.name if silx.io.is_dataset(dataset): type_ = self.getDefaultFormatter().humanReadableType(dataset) shape = self.getDefaultFormatter().humanReadableShape(dataset) if dataset.shape is None: icon_name = "item-none" elif len(dataset.shape) < 4: icon_name = "item-%ddim" % len(dataset.shape) else: icon_name = "item-ndim" icon = icons.getQIcon(icon_name) else: type_ = "" shape = "" icon = qt.QIcon() else: name = "" type_ = "" shape = "" icon = qt.QIcon() self.__icon = icon self.__name.setText(name) self.__name.setDragEnabled(self.__dataset is not None) self.__name.setIcon(self.__icon) self.__type.setText(type_) self.__shape.setText(shape) parent = self.parent() if parent is not None: self.parent()._datasetUpdated() def getDataset(self): """Returns the dataset stored within the item.""" return self.__dataset def getRowItems(self): """Returns the list of items used for a specific row. The first item should be this class. :rtype: List[qt.QStandardItem] """ return [self, self.__name, self.__type, self.__shape] class _DatasetAxisItemRow(_DatasetItemRow): """Define a row describing an axis.""" def __init__(self): """Constructor""" super(_DatasetAxisItemRow, self).__init__() def setAxisId(self, axisId): """Set the id of the axis (the first axis is 0) :param int axisId: Identifier of this axis. """ self.__axisId = axisId label = "Axis %d" % (axisId + 1) self.setText(label) def getAxisId(self): """Returns the identifier of this axis. :rtype: int """ return self.__axisId class _NxDataItem(qt.QStandardItem): """ Define a custom NXdata. """ def __init__(self): """Constructor""" qt.QStandardItem.__init__(self) self.__error = None self.__title = None self.__axes = [] self.__virtual = None item = _DatasetItemRow("Signal", None) self.appendRow(item.getRowItems()) self.__signal = item self.setEditable(False) self.setDragEnabled(False) self.setDropEnabled(False) self.__setError(None) def getRowItems(self): """Returns the list of items used for a specific row. The first item should be this class. :rtype: List[qt.QStandardItem] """ row = [self] for _ in range(3): item = qt.QStandardItem("") item.setEditable(False) item.setDragEnabled(False) item.setDropEnabled(False) row.append(item) return row def _datasetUpdated(self): """Called when the NXdata contained of the item have changed. It invalidates the NXdata stored and send an event `sigNxdataUpdated`. """ self.__virtual = None self.__setError(None) model = self.model() if model is not None: model.sigNxdataUpdated.emit(self.index()) def createVirtualGroup(self): """Returns a new virtual Group using a NeXus NXdata structure to store data :rtype: silx.io.commonh5.Group """ name = "" if self.__title is not None: name = self.__title virtual = commonh5.Group(name) virtual.attrs["NX_class"] = "NXdata" if self.__title is not None: virtual.attrs["title"] = self.__title if self.__signal is not None: signal = self.__signal.getDataset() if signal is not None: # Could be done using a link instead of a copy node = commonh5.DatasetProxy("signal", target=signal) virtual.attrs["signal"] = "signal" virtual.add_node(node) axesAttr = [] for i, axis in enumerate(self.__axes): if axis is None: name = "." else: axis = axis.getDataset() if axis is None: name = "." else: name = "axis%d" % i node = commonh5.DatasetProxy(name, target=axis) virtual.add_node(node) axesAttr.append(name) if axesAttr != []: virtual.attrs["axes"] = numpy.array(axesAttr) validator = silx.io.nxdata.NXdata(virtual) if not validator.is_valid: message = "" message += "This NXdata is not consistant" message += "" message += "" self.__setError(message) else: self.__setError(None) return virtual def isValid(self): """Returns true if the stored NXdata is valid :rtype: bool """ return self.__error is None def getVirtualGroup(self): """Returns a cached virtual Group using a NeXus NXdata structure to store data. If the stored NXdata was invalidated, :meth:`createVirtualGroup` is internally called to update the cache. :rtype: silx.io.commonh5.Group """ if self.__virtual is None: self.__virtual = self.createVirtualGroup() return self.__virtual def getTitle(self): """Returns the title of the NXdata :rtype: str """ return self.text() def setTitle(self, title): """Set the title of the NXdata :param str title: The title of this NXdata """ self.setText(title) def __setError(self, error): """Set the error message in case of the current state of the stored NXdata is not valid. :param str error: Message to display """ self.__error = error style = qt.QApplication.style() if error is None: message = "" icon = style.standardIcon(qt.QStyle.SP_DirLinkIcon) else: message = error icon = style.standardIcon(qt.QStyle.SP_MessageBoxCritical) self.setIcon(icon) self.setToolTip(message) def getError(self): """Returns the error message in case the NXdata is not valid. :rtype: str""" return self.__error def setSignalDataset(self, dataset): """Set the dataset to use as signal with this NXdata. :param Union[numpy.ndarray,h5py.Dataset,silx.io.commonh5.Dataset] dataset: The dataset to use as signal. """ self.__signal.setDataset(dataset) self._datasetUpdated() def getSignalDataset(self): """Returns the dataset used as signal. :rtype: Union[numpy.ndarray,h5py.Dataset,silx.io.commonh5.Dataset] """ return self.__signal.getDataset() def setAxesDatasets(self, datasets): """Set all the available dataset used as axes. Axes will be created or removed from the GUI in order to provide the same amount of requested axes. A `None` element is an axes with no dataset. :param List[Union[numpy.ndarray,h5py.Dataset,silx.io.commonh5.Dataset,None]] datasets: List of dataset to use as axes. """ for i, dataset in enumerate(datasets): if i < len(self.__axes): mustAppend = False item = self.__axes[i] else: mustAppend = True item = _DatasetAxisItemRow() item.setAxisId(i) item.setDataset(dataset) if mustAppend: self.__axes.append(item) self.appendRow(item.getRowItems()) # Clean up extra axis for i in range(len(datasets), len(self.__axes)): item = self.__axes.pop(len(datasets)) self.removeRow(item.row()) self._datasetUpdated() def getAxesDatasets(self): """Returns available axes as dataset. A `None` element is an axes with no dataset. :rtype: List[Union[numpy.ndarray,h5py.Dataset,silx.io.commonh5.Dataset,None]] """ datasets = [] for axis in self.__axes: datasets.append(axis.getDataset()) return datasets class _Model(qt.QStandardItemModel): """Model storing a list of custom NXdata items. Supports drag and drop of datasets. """ sigNxdataUpdated = qt.Signal(qt.QModelIndex) """Emitted when stored NXdata was edited""" def __init__(self, parent=None): """Constructor""" qt.QStandardItemModel.__init__(self, parent) root = self.invisibleRootItem() root.setDropEnabled(True) root.setDragEnabled(False) def supportedDropActions(self): """Inherited method to redefine supported drop actions.""" return qt.Qt.CopyAction | qt.Qt.MoveAction def mimeTypes(self): """Inherited method to redefine draggable mime types.""" return [Hdf5DatasetMimeData.MIME_TYPE] 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) > 1: return None if len(indexes) == 0: return None qindex = indexes[0] qindex = self.index(qindex.row(), 0, parent=qindex.parent()) item = self.itemFromIndex(qindex) if isinstance(item, _DatasetItemRow): dataset = item.getDataset() if dataset is None: return None else: mimeData = Hdf5DatasetMimeData(dataset=item.getDataset()) else: mimeData = None return mimeData def dropMimeData(self, mimedata, action, row, column, parentIndex): """Inherited method to handle a drop operation to this model.""" if action == qt.Qt.IgnoreAction: return True if mimedata.hasFormat(Hdf5DatasetMimeData.MIME_TYPE): if row != -1 or column != -1: # It is not a drop on a specific item return False item = self.itemFromIndex(parentIndex) if item is None or item is self.invisibleRootItem(): # Drop at the end dataset = mimedata.dataset() if silx.io.is_dataset(dataset): self.createFromSignal(dataset) elif silx.io.is_group(dataset): nxdata = dataset try: self.createFromNxdata(nxdata) except ValueError: _logger.error("Error while dropping a group as an NXdata") _logger.debug("Backtrace", exc_info=True) return False else: _logger.error("Dropping a wrong object") return False else: item = item.parent().child(item.row(), 0) if not isinstance(item, _DatasetItemRow): # Dropped at a bad place return False dataset = mimedata.dataset() if silx.io.is_dataset(dataset): item.setDataset(dataset) else: _logger.error("Dropping a wrong object") return False return True return False def __getNxdataByTitle(self, title): """Returns an NXdata item by its title, else None. :rtype: Union[_NxDataItem,None] """ for row in range(self.rowCount()): qindex = self.index(row, 0) item = self.itemFromIndex(qindex) if item.getTitle() == title: return item return None def findFreeNxdataTitle(self): """Returns an NXdata title which is not yet used. :rtype: str """ for i in range(self.rowCount() + 1): name = "NXData #%d" % (i + 1) group = self.__getNxdataByTitle(name) if group is None: break return name def createNewNxdata(self, name=None): """Create a new NXdata item. :param Union[str,None] name: A title for the new NXdata """ item = _NxDataItem() if name is None: name = self.findFreeNxdataTitle() item.setTitle(name) self.appendRow(item.getRowItems()) def createFromSignal(self, dataset): """Create a new NXdata item from a signal dataset. This signal will also define an amount of axes according to its number of dimensions. :param Union[numpy.ndarray,h5py.Dataset,silx.io.commonh5.Dataset] dataset: A dataset uses as signal. """ item = _NxDataItem() name = self.findFreeNxdataTitle() item.setTitle(name) item.setSignalDataset(dataset) item.setAxesDatasets([None] * len(dataset.shape)) self.appendRow(item.getRowItems()) def createFromNxdata(self, nxdata): """Create a new custom NXdata item from an existing NXdata group. If the NXdata is not valid, nothing is created, and an exception is returned. :param Union[h5py.Group,silx.io.commonh5.Group] nxdata: An h5py group following the NXData specification. :raise ValueError:If `nxdata` is not valid. """ validator = silx.io.nxdata.NXdata(nxdata) if validator.is_valid: item = _NxDataItem() title = validator.title if title in [None or ""]: title = self.findFreeNxdataTitle() item.setTitle(title) item.setSignalDataset(validator.signal) item.setAxesDatasets(validator.axes) self.appendRow(item.getRowItems()) else: raise ValueError("Not a valid NXdata") def removeNxdataItem(self, item): """Remove an NXdata item from this model. :param _NxDataItem item: An item """ if isinstance(item, _NxDataItem): parent = item.parent() assert(parent is None) model = item.model() model.removeRow(item.row()) else: _logger.error("Unexpected item") def appendAxisToNxdataItem(self, item): """Append a new axes to this item (or the NXdata item own by this item). :param Union[_NxDataItem,qt.QStandardItem] item: An item """ if item is not None and not isinstance(item, _NxDataItem): item = item.parent() nxdataItem = item if isinstance(item, _NxDataItem): datasets = nxdataItem.getAxesDatasets() datasets.append(None) nxdataItem.setAxesDatasets(datasets) else: _logger.error("Unexpected item") def removeAxisItem(self, item): """Remove an axis item from this model. :param _DatasetAxisItemRow item: An axis item """ if isinstance(item, _DatasetAxisItemRow): axisId = item.getAxisId() nxdataItem = item.parent() datasets = nxdataItem.getAxesDatasets() del datasets[axisId] nxdataItem.setAxesDatasets(datasets) else: _logger.error("Unexpected item") class CustomNxDataToolBar(qt.QToolBar): """A specialised toolbar to manage custom NXdata model and items.""" def __init__(self, parent=None): """Constructor""" super(CustomNxDataToolBar, self).__init__(parent=parent) self.__nxdataWidget = None self.__initContent() # Initialize action state self.__currentSelectionChanged(qt.QModelIndex(), qt.QModelIndex()) def __initContent(self): """Create all expected actions and set the content of this toolbar.""" action = qt.QAction("Create a new custom NXdata", self) action.setIcon(icons.getQIcon("nxdata-create")) action.triggered.connect(self.__createNewNxdata) self.addAction(action) self.__addNxDataAction = action action = qt.QAction("Remove the selected NXdata", self) action.setIcon(icons.getQIcon("nxdata-remove")) action.triggered.connect(self.__removeSelectedNxdata) self.addAction(action) self.__removeNxDataAction = action self.addSeparator() action = qt.QAction("Create a new axis to the selected NXdata", self) action.setIcon(icons.getQIcon("nxdata-axis-add")) action.triggered.connect(self.__appendNewAxisToSelectedNxdata) self.addAction(action) self.__addNxDataAxisAction = action action = qt.QAction("Remove the selected NXdata axis", self) action.setIcon(icons.getQIcon("nxdata-axis-remove")) action.triggered.connect(self.__removeSelectedAxis) self.addAction(action) self.__removeNxDataAxisAction = action def __getSelectedItem(self): """Get the selected item from the linked CustomNxdataWidget. :rtype: qt.QStandardItem """ selectionModel = self.__nxdataWidget.selectionModel() index = selectionModel.currentIndex() if not index.isValid(): return model = self.__nxdataWidget.model() index = model.index(index.row(), 0, index.parent()) item = model.itemFromIndex(index) return item def __createNewNxdata(self): """Create a new NXdata item to the linked CustomNxdataWidget.""" if self.__nxdataWidget is None: return model = self.__nxdataWidget.model() model.createNewNxdata() def __removeSelectedNxdata(self): """Remove the NXdata item currently selected in the linked CustomNxdataWidget.""" if self.__nxdataWidget is None: return model = self.__nxdataWidget.model() item = self.__getSelectedItem() model.removeNxdataItem(item) def __appendNewAxisToSelectedNxdata(self): """Append a new axis to the NXdata item currently selected in the linked CustomNxdataWidget.""" if self.__nxdataWidget is None: return model = self.__nxdataWidget.model() item = self.__getSelectedItem() model.appendAxisToNxdataItem(item) def __removeSelectedAxis(self): """Remove the axis item currently selected in the linked CustomNxdataWidget.""" if self.__nxdataWidget is None: return model = self.__nxdataWidget.model() item = self.__getSelectedItem() model.removeAxisItem(item) def setCustomNxDataWidget(self, widget): """Set the linked CustomNxdataWidget to this toolbar.""" assert(isinstance(widget, CustomNxdataWidget)) if self.__nxdataWidget is not None: selectionModel = self.__nxdataWidget.selectionModel() selectionModel.currentChanged.disconnect(self.__currentSelectionChanged) self.__nxdataWidget = widget if self.__nxdataWidget is not None: selectionModel = self.__nxdataWidget.selectionModel() selectionModel.currentChanged.connect(self.__currentSelectionChanged) def __currentSelectionChanged(self, current, previous): """Update the actions according to the linked CustomNxdataWidget item selection""" if not current.isValid(): item = None else: model = self.__nxdataWidget.model() index = model.index(current.row(), 0, current.parent()) item = model.itemFromIndex(index) self.__removeNxDataAction.setEnabled(isinstance(item, _NxDataItem)) self.__removeNxDataAxisAction.setEnabled(isinstance(item, _DatasetAxisItemRow)) self.__addNxDataAxisAction.setEnabled(isinstance(item, _NxDataItem) or isinstance(item, _DatasetItemRow)) class _HashDropZones(qt.QStyledItemDelegate): """Delegate item displaying a drop zone when the item do not contains dataset.""" def __init__(self, parent=None): """Constructor""" super(_HashDropZones, self).__init__(parent) pen = qt.QPen() pen.setColor(qt.QColor("#D0D0D0")) pen.setStyle(qt.Qt.DotLine) pen.setWidth(2) self.__dropPen = pen def paint(self, painter, option, index): """ Paint the item :param qt.QPainter painter: A painter :param qt.QStyleOptionViewItem option: Options of the item to paint :param qt.QModelIndex index: Index of the item to paint """ displayDropZone = False if index.isValid(): model = index.model() rowIndex = model.index(index.row(), 0, index.parent()) rowItem = model.itemFromIndex(rowIndex) if isinstance(rowItem, _DatasetItemRow): displayDropZone = rowItem.getDataset() is None if displayDropZone: painter.save() # Draw background if selected if option.state & qt.QStyle.State_Selected: colorGroup = qt.QPalette.Inactive if option.state & qt.QStyle.State_Active: colorGroup = qt.QPalette.Active if not option.state & qt.QStyle.State_Enabled: colorGroup = qt.QPalette.Disabled brush = option.palette.brush(colorGroup, qt.QPalette.Highlight) painter.fillRect(option.rect, brush) painter.setPen(self.__dropPen) painter.drawRect(option.rect.adjusted(3, 3, -3, -3)) painter.restore() else: qt.QStyledItemDelegate.paint(self, painter, option, index) class CustomNxdataWidget(qt.QTreeView): """Widget providing a table displaying and allowing to custom virtual NXdata.""" sigNxdataItemUpdated = qt.Signal(qt.QStandardItem) """Emitted when the NXdata from an NXdata item was edited""" sigNxdataItemRemoved = qt.Signal(qt.QStandardItem) """Emitted when an NXdata item was removed""" def __init__(self, parent=None): """Constructor""" qt.QTreeView.__init__(self, parent=None) self.__model = _Model(self) self.__model.setColumnCount(4) self.__model.setHorizontalHeaderLabels(["Name", "Dataset", "Type", "Shape"]) self.setModel(self.__model) self.setItemDelegateForColumn(1, _HashDropZones(self)) self.__model.sigNxdataUpdated.connect(self.__nxdataUpdate) self.__model.rowsAboutToBeRemoved.connect(self.__rowsAboutToBeRemoved) self.__model.rowsAboutToBeInserted.connect(self.__rowsAboutToBeInserted) header = self.header() if qt.qVersion() < "5.0": setResizeMode = header.setResizeMode else: setResizeMode = header.setSectionResizeMode setResizeMode(0, qt.QHeaderView.ResizeToContents) setResizeMode(1, qt.QHeaderView.Stretch) setResizeMode(2, qt.QHeaderView.ResizeToContents) setResizeMode(3, qt.QHeaderView.ResizeToContents) self.setSelectionMode(qt.QAbstractItemView.SingleSelection) self.setDropIndicatorShown(True) self.setDragDropOverwriteMode(True) self.setDragEnabled(True) self.viewport().setAcceptDrops(True) self.setContextMenuPolicy(qt.Qt.CustomContextMenu) self.customContextMenuRequested[qt.QPoint].connect(self.__executeContextMenu) def __rowsAboutToBeInserted(self, parentIndex, start, end): if qt.qVersion()[0:2] == "5.": # FIXME: workaround for https://github.com/silx-kit/silx/issues/1919 # Uses of ResizeToContents looks to break nice update of cells with Qt5 # This patch make the view blinking self.repaint() def __rowsAboutToBeRemoved(self, parentIndex, start, end): """Called when an item was removed from the model.""" items = [] model = self.model() for index in range(start, end): qindex = model.index(index, 0, parent=parentIndex) item = self.__model.itemFromIndex(qindex) if isinstance(item, _NxDataItem): items.append(item) for item in items: self.sigNxdataItemRemoved.emit(item) if qt.qVersion()[0:2] == "5.": # FIXME: workaround for https://github.com/silx-kit/silx/issues/1919 # Uses of ResizeToContents looks to break nice update of cells with Qt5 # This patch make the view blinking self.repaint() def __nxdataUpdate(self, index): """Called when a virtual NXdata was updated from the model.""" model = self.model() item = model.itemFromIndex(index) self.sigNxdataItemUpdated.emit(item) def createDefaultContextMenu(self, index): """Create a default context menu at this position. :param qt.QModelIndex index: Index of the item """ index = self.__model.index(index.row(), 0, parent=index.parent()) item = self.__model.itemFromIndex(index) menu = qt.QMenu() weakself = weakref.proxy(self) if isinstance(item, _NxDataItem): action = qt.QAction("Add a new axis", menu) action.triggered.connect(lambda: weakself.model().appendAxisToNxdataItem(item)) action.setIcon(icons.getQIcon("nxdata-axis-add")) action.setIconVisibleInMenu(True) menu.addAction(action) menu.addSeparator() action = qt.QAction("Remove this NXdata", menu) action.triggered.connect(lambda: weakself.model().removeNxdataItem(item)) action.setIcon(icons.getQIcon("remove")) action.setIconVisibleInMenu(True) menu.addAction(action) else: if isinstance(item, _DatasetItemRow): if item.getDataset() is not None: action = qt.QAction("Remove this dataset", menu) action.triggered.connect(lambda: item.setDataset(None)) menu.addAction(action) if isinstance(item, _DatasetAxisItemRow): menu.addSeparator() action = qt.QAction("Remove this axis", menu) action.triggered.connect(lambda: weakself.model().removeAxisItem(item)) action.setIcon(icons.getQIcon("remove")) action.setIconVisibleInMenu(True) menu.addAction(action) return menu def __executeContextMenu(self, point): """Execute the context menu at this position.""" index = self.indexAt(point) menu = self.createDefaultContextMenu(index) if menu is None or menu.isEmpty(): return menu.exec_(qt.QCursor.pos()) def removeDatasetsFrom(self, root): """ Remove all datasets provided by this root :param root: The root file of datasets to remove """ for row in range(self.__model.rowCount()): qindex = self.__model.index(row, 0) item = self.model().itemFromIndex(qindex) edited = False datasets = item.getAxesDatasets() for i, dataset in enumerate(datasets): if dataset is not None: # That's an approximation, IS can't be used as h5py generates # To objects for each requests to a node if dataset.file.filename == root.file.filename: datasets[i] = None edited = True if edited: item.setAxesDatasets(datasets) dataset = item.getSignalDataset() if dataset is not None: # That's an approximation, IS can't be used as h5py generates # To objects for each requests to a node if dataset.file.filename == root.file.filename: item.setSignalDataset(None) def replaceDatasetsFrom(self, removedRoot, loadedRoot): """ Replace any dataset from any NXdata items using the same dataset name from another root. Usually used when a file was synchronized. :param removedRoot: The h5py root file which is replaced (which have to be removed) :param loadedRoot: The new h5py root file which have to be used instread. """ for row in range(self.__model.rowCount()): qindex = self.__model.index(row, 0) item = self.model().itemFromIndex(qindex) edited = False datasets = item.getAxesDatasets() for i, dataset in enumerate(datasets): newDataset = self.__replaceDatasetRoot(dataset, removedRoot, loadedRoot) if dataset is not newDataset: datasets[i] = newDataset edited = True if edited: item.setAxesDatasets(datasets) dataset = item.getSignalDataset() newDataset = self.__replaceDatasetRoot(dataset, removedRoot, loadedRoot) if dataset is not newDataset: item.setSignalDataset(newDataset) def __replaceDatasetRoot(self, dataset, fromRoot, toRoot): """ Replace the dataset by the same dataset name from another root. """ if dataset is None: return None if dataset.file is None: # Not from the expected root return dataset # That's an approximation, IS can't be used as h5py generates # To objects for each requests to a node if dataset.file.filename == fromRoot.file.filename: # Try to find the same dataset name try: return toRoot[dataset.name] except Exception: _logger.debug("Backtrace", exc_info=True) return None else: # Not from the expected root return dataset def selectedItems(self): """Returns the list of selected items containing NXdata :rtype: List[qt.QStandardItem] """ result = [] for qindex in self.selectedIndexes(): if qindex.column() != 0: continue if not qindex.isValid(): continue item = self.__model.itemFromIndex(qindex) if not isinstance(item, _NxDataItem): continue result.append(item) return result def selectedNxdata(self): """Returns the list of selected NXdata :rtype: List[silx.io.commonh5.Group] """ result = [] for qindex in self.selectedIndexes(): if qindex.column() != 0: continue if not qindex.isValid(): continue item = self.__model.itemFromIndex(qindex) if not isinstance(item, _NxDataItem): continue result.append(item.getVirtualGroup()) return result