diff options
Diffstat (limited to 'silx/gui/dialog')
-rw-r--r-- | silx/gui/dialog/AbstractDataFileDialog.py | 1720 | ||||
-rw-r--r-- | silx/gui/dialog/ColormapDialog.py | 987 | ||||
-rw-r--r-- | silx/gui/dialog/DataFileDialog.py | 342 | ||||
-rw-r--r-- | silx/gui/dialog/DatasetDialog.py | 122 | ||||
-rw-r--r-- | silx/gui/dialog/FileTypeComboBox.py | 213 | ||||
-rw-r--r-- | silx/gui/dialog/GroupDialog.py | 230 | ||||
-rw-r--r-- | silx/gui/dialog/ImageFileDialog.py | 338 | ||||
-rw-r--r-- | silx/gui/dialog/SafeFileIconProvider.py | 154 | ||||
-rw-r--r-- | silx/gui/dialog/SafeFileSystemModel.py | 802 | ||||
-rw-r--r-- | silx/gui/dialog/__init__.py | 29 | ||||
-rw-r--r-- | silx/gui/dialog/setup.py | 40 | ||||
-rw-r--r-- | silx/gui/dialog/test/__init__.py | 49 | ||||
-rw-r--r-- | silx/gui/dialog/test/test_colormapdialog.py | 398 | ||||
-rw-r--r-- | silx/gui/dialog/test/test_datafiledialog.py | 995 | ||||
-rw-r--r-- | silx/gui/dialog/test/test_imagefiledialog.py | 815 | ||||
-rw-r--r-- | silx/gui/dialog/utils.py | 104 |
16 files changed, 0 insertions, 7338 deletions
diff --git a/silx/gui/dialog/AbstractDataFileDialog.py b/silx/gui/dialog/AbstractDataFileDialog.py deleted file mode 100644 index 40045fe..0000000 --- a/silx/gui/dialog/AbstractDataFileDialog.py +++ /dev/null @@ -1,1720 +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. -# -# ###########################################################################*/ -""" -This module contains an :class:`AbstractDataFileDialog`. -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "05/03/2018" - - -import sys -import os -import logging -import numpy -import functools -import silx.io.url -from silx.gui import qt -from silx.gui.hdf5.Hdf5TreeModel import Hdf5TreeModel -from . import utils -from silx.third_party import six -from .FileTypeComboBox import FileTypeComboBox -try: - import fabio -except ImportError: - fabio = None - - -_logger = logging.getLogger(__name__) - - -class _IconProvider(object): - - FileDialogToParentDir = qt.QStyle.SP_CustomBase + 1 - - FileDialogToParentFile = qt.QStyle.SP_CustomBase + 2 - - def __init__(self): - self.__iconFileDialogToParentDir = None - self.__iconFileDialogToParentFile = None - - def _createIconToParent(self, standardPixmap): - """ - - FIXME: It have to be tested for some OS (arrow icon do not have always - the same direction) - """ - style = qt.QApplication.style() - baseIcon = style.standardIcon(qt.QStyle.SP_FileDialogToParent) - backgroundIcon = style.standardIcon(standardPixmap) - icon = qt.QIcon() - - sizes = baseIcon.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 = baseIcon.availableSizes()[0] - size = qt.QSize(baseSize.width(), baseSize.height() * 3 // 2) - - modes = [qt.QIcon.Normal, qt.QIcon.Disabled] - for mode in modes: - pixmap = qt.QPixmap(size) - pixmap.fill(qt.Qt.transparent) - painter = qt.QPainter(pixmap) - painter.drawPixmap(0, 0, backgroundIcon.pixmap(baseSize, mode=mode)) - painter.drawPixmap(0, size.height() // 3, baseIcon.pixmap(baseSize, mode=mode)) - painter.end() - icon.addPixmap(pixmap, mode=mode) - - return icon - - def getFileDialogToParentDir(self): - if self.__iconFileDialogToParentDir is None: - self.__iconFileDialogToParentDir = self._createIconToParent(qt.QStyle.SP_DirIcon) - return self.__iconFileDialogToParentDir - - def getFileDialogToParentFile(self): - if self.__iconFileDialogToParentFile is None: - self.__iconFileDialogToParentFile = self._createIconToParent(qt.QStyle.SP_FileIcon) - return self.__iconFileDialogToParentFile - - def icon(self, kind): - if kind == self.FileDialogToParentDir: - return self.getFileDialogToParentDir() - elif kind == self.FileDialogToParentFile: - return self.getFileDialogToParentFile() - else: - style = qt.QApplication.style() - icon = style.standardIcon(kind) - return icon - - -class _SideBar(qt.QListView): - """Sidebar containing shortcuts for common directories""" - - def __init__(self, parent=None): - super(_SideBar, self).__init__(parent) - self.__iconProvider = qt.QFileIconProvider() - self.setUniformItemSizes(True) - model = qt.QStandardItemModel(self) - self.setModel(model) - self._initModel() - self.setEditTriggers(qt.QAbstractItemView.NoEditTriggers) - - def iconProvider(self): - return self.__iconProvider - - def _initModel(self): - urls = self._getDefaultUrls() - self.setUrls(urls) - - def _getDefaultUrls(self): - """Returns the default shortcuts. - - It uses the default QFileDialog shortcuts if it is possible, else - provides a link to the computer's root and the user's home. - - :rtype: List[str] - """ - urls = [] - if qt.qVersion().startswith("5.") and sys.platform in ["linux", "linux2"]: - # Avoid segfault on PyQt5 + gtk - _logger.debug("Skip default sidebar URLs (avoid PyQt5 segfault)") - pass - elif qt.qVersion().startswith("4.") and sys.platform in ["win32"]: - # Avoid 5min of locked GUI relative to network driver - _logger.debug("Skip default sidebar URLs (avoid lock when using network drivers)") - else: - # Get default shortcut - # There is no other way - d = qt.QFileDialog(self) - # Needed to be able to reach the sidebar urls - d.setOption(qt.QFileDialog.DontUseNativeDialog, True) - urls = d.sidebarUrls() - d.deleteLater() - d = None - - if len(urls) == 0: - urls.append(qt.QUrl("file://")) - urls.append(qt.QUrl.fromLocalFile(qt.QDir.homePath())) - - return urls - - def setSelectedPath(self, path): - selected = None - model = self.model() - for i in range(model.rowCount()): - index = model.index(i, 0) - url = model.data(index, qt.Qt.UserRole) - urlPath = url.toLocalFile() - if path == urlPath: - selected = index - - selectionModel = self.selectionModel() - if selected is not None: - selectionModel.setCurrentIndex(selected, qt.QItemSelectionModel.ClearAndSelect) - else: - selectionModel.clear() - - def setUrls(self, urls): - model = self.model() - model.clear() - - names = {} - names[qt.QDir.rootPath()] = "Computer" - names[qt.QDir.homePath()] = "Home" - - style = qt.QApplication.style() - iconProvider = self.iconProvider() - for url in urls: - path = url.toLocalFile() - if path == "": - if sys.platform != "win32": - url = qt.QUrl(qt.QDir.rootPath()) - name = "Computer" - icon = style.standardIcon(qt.QStyle.SP_ComputerIcon) - else: - fileInfo = qt.QFileInfo(path) - name = names.get(path, fileInfo.fileName()) - icon = iconProvider.icon(fileInfo) - - if icon.isNull(): - icon = style.standardIcon(qt.QStyle.SP_MessageBoxCritical) - - item = qt.QStandardItem() - item.setText(name) - item.setIcon(icon) - item.setData(url, role=qt.Qt.UserRole) - model.appendRow(item) - - def urls(self): - result = [] - model = self.model() - for i in range(model.rowCount()): - index = model.index(i, 0) - url = model.data(index, qt.Qt.UserRole) - result.append(url) - return result - - def sizeHint(self): - index = self.model().index(0, 0) - return self.sizeHintForIndex(index) + qt.QSize(2 * self.frameWidth(), 2 * self.frameWidth()) - - -class _Browser(qt.QStackedWidget): - - activated = qt.Signal(qt.QModelIndex) - selected = qt.Signal(qt.QModelIndex) - rootIndexChanged = qt.Signal(qt.QModelIndex) - - def __init__(self, parent, listView, detailView): - qt.QStackedWidget.__init__(self, parent) - self.__listView = listView - self.__detailView = detailView - self.insertWidget(0, self.__listView) - self.insertWidget(1, self.__detailView) - - self.__listView.activated.connect(self.__emitActivated) - self.__detailView.activated.connect(self.__emitActivated) - - def __emitActivated(self, index): - self.activated.emit(index) - - def __emitSelected(self, selected, deselected): - index = self.selectedIndex() - if index is not None: - self.selected.emit(index) - - def selectedIndex(self): - if self.currentIndex() == 0: - selectionModel = self.__listView.selectionModel() - else: - selectionModel = self.__detailView.selectionModel() - - if selectionModel is None: - return None - - indexes = selectionModel.selectedIndexes() - # Filter non-main columns - indexes = [i for i in indexes if i.column() == 0] - if len(indexes) == 1: - index = indexes[0] - return index - return None - - def model(self): - """Returns the current model.""" - if self.currentIndex() == 0: - return self.__listView.model() - else: - return self.__detailView.model() - - def selectIndex(self, index): - if self.currentIndex() == 0: - selectionModel = self.__listView.selectionModel() - else: - selectionModel = self.__detailView.selectionModel() - if selectionModel is None: - return - selectionModel.setCurrentIndex(index, qt.QItemSelectionModel.ClearAndSelect) - - def viewMode(self): - """Returns the current view mode. - - :rtype: qt.QFileDialog.ViewMode - """ - if self.currentIndex() == 0: - return qt.QFileDialog.List - elif self.currentIndex() == 1: - return qt.QFileDialog.Detail - else: - assert(False) - - def setViewMode(self, mode): - """Set the current view mode. - - :param qt.QFileDialog.ViewMode mode: The new view mode - """ - if mode == qt.QFileDialog.Detail: - self.showDetails() - elif mode == qt.QFileDialog.List: - self.showList() - else: - assert(False) - - def showList(self): - self.__listView.show() - self.__detailView.hide() - self.setCurrentIndex(0) - - def showDetails(self): - self.__listView.hide() - self.__detailView.show() - self.setCurrentIndex(1) - self.__detailView.updateGeometry() - - def clear(self): - self.__listView.setRootIndex(qt.QModelIndex()) - self.__detailView.setRootIndex(qt.QModelIndex()) - selectionModel = self.__listView.selectionModel() - if selectionModel is not None: - selectionModel.selectionChanged.disconnect() - selectionModel.clear() - selectionModel = self.__detailView.selectionModel() - if selectionModel is not None: - selectionModel.selectionChanged.disconnect() - selectionModel.clear() - self.__listView.setModel(None) - self.__detailView.setModel(None) - - def setRootIndex(self, index, model=None): - """Sets the root item to the item at the given index. - """ - rootIndex = self.__listView.rootIndex() - newModel = model or index.model() - assert(newModel is not None) - - if rootIndex is None or rootIndex.model() is not newModel: - # update the model - selectionModel = self.__listView.selectionModel() - if selectionModel is not None: - selectionModel.selectionChanged.disconnect() - selectionModel.clear() - selectionModel = self.__detailView.selectionModel() - if selectionModel is not None: - selectionModel.selectionChanged.disconnect() - selectionModel.clear() - pIndex = qt.QPersistentModelIndex(index) - self.__listView.setModel(newModel) - # changing the model of the tree view change the index mapping - # that is why we are using a persistance model index - self.__detailView.setModel(newModel) - index = newModel.index(pIndex.row(), pIndex.column(), pIndex.parent()) - selectionModel = self.__listView.selectionModel() - selectionModel.selectionChanged.connect(self.__emitSelected) - selectionModel = self.__detailView.selectionModel() - selectionModel.selectionChanged.connect(self.__emitSelected) - - self.__listView.setRootIndex(index) - self.__detailView.setRootIndex(index) - self.rootIndexChanged.emit(index) - - def rootIndex(self): - """Returns the model index of the model's root item. The root item is - the parent item to the view's toplevel items. The root can be invalid. - """ - return self.__listView.rootIndex() - - __serialVersion = 1 - """Store the current version of the serialized data""" - - def visualRect(self, index): - """Returns the rectangle on the viewport occupied by the item at index. - - :param qt.QModelIndex index: An index - :rtype: QRect - """ - if self.currentIndex() == 0: - return self.__listView.visualRect(index) - else: - return self.__detailView.visualRect(index) - - def viewport(self): - """Returns the viewport widget. - - :param qt.QModelIndex index: An index - :rtype: QRect - """ - if self.currentIndex() == 0: - return self.__listView.viewport() - else: - return self.__detailView.viewport() - - def restoreState(self, state): - """Restores the dialogs's layout, history and current directory to the - state specified. - - :param qt.QByeArray state: Stream containing the new state - :rtype: bool - """ - stream = qt.QDataStream(state, qt.QIODevice.ReadOnly) - - nameId = stream.readQString() - if nameId != "Browser": - _logger.warning("Stored state contains an invalid name id. Browser restoration cancelled.") - return False - - version = stream.readInt32() - if version != self.__serialVersion: - _logger.warning("Stored state contains an invalid version. Browser restoration cancelled.") - return False - - headerData = stream.readQVariant() - self.__detailView.header().restoreState(headerData) - - viewMode = stream.readInt32() - self.setViewMode(viewMode) - return True - - def saveState(self): - """Saves the state of the dialog's layout. - - :rtype: qt.QByteArray - """ - data = qt.QByteArray() - stream = qt.QDataStream(data, qt.QIODevice.WriteOnly) - - nameId = u"Browser" - stream.writeQString(nameId) - stream.writeInt32(self.__serialVersion) - stream.writeQVariant(self.__detailView.header().saveState()) - stream.writeInt32(self.viewMode()) - - return data - - -class _FabioData(object): - - def __init__(self, fabioFile): - self.__fabioFile = fabioFile - - @property - def dtype(self): - # Let say it is a valid type - return numpy.dtype("float") - - @property - def shape(self): - if self.__fabioFile.nframes == 0: - return None - return [self.__fabioFile.nframes, slice(None), slice(None)] - - def __getitem__(self, selector): - if isinstance(selector, tuple) and len(selector) == 1: - selector = selector[0] - - if isinstance(selector, six.integer_types): - if 0 <= selector < self.__fabioFile.nframes: - if self.__fabioFile.nframes == 1: - return self.__fabioFile.data - else: - frame = self.__fabioFile.getframe(selector) - return frame.data - else: - raise ValueError("Invalid selector %s" % selector) - else: - raise TypeError("Unsupported selector type %s" % type(selector)) - - -class _PathEdit(qt.QLineEdit): - pass - - -class _CatchResizeEvent(qt.QObject): - - resized = qt.Signal(qt.QResizeEvent) - - def __init__(self, parent, target): - super(_CatchResizeEvent, self).__init__(parent) - self.__target = target - self.__target_oldResizeEvent = self.__target.resizeEvent - self.__target.resizeEvent = self.__resizeEvent - - def __resizeEvent(self, event): - result = self.__target_oldResizeEvent(event) - self.resized.emit(event) - return result - - -class AbstractDataFileDialog(qt.QDialog): - """The `AbstractFileDialog` provides a generic GUI to create a custom dialog - allowing to access to file resources like HDF5 files or HDF5 datasets. - - .. image:: img/abstractdatafiledialog.png - - The dialog contains: - - - Shortcuts: It provides few links to have a fast access of browsing - locations. - - Browser: It provides a display to browse throw the file system and inside - HDF5 files or fabio files. A file format selector is provided. - - URL: Display the URL available to reach the data using - :meth:`silx.io.get_data`, :meth:`silx.io.open`. - - Data selector: A widget to apply a sub selection of the browsed dataset. - This widget can be provided, else nothing will be used. - - Data preview: A widget to preview the selected data, which is the result - of the filter from the data selector. - This widget can be provided, else nothing will be used. - - Preview's toolbar: Provides tools used to custom data preview or data - selector. - This widget can be provided, else nothing will be used. - - Buttons to validate the dialog - """ - - _defaultIconProvider = None - """Lazy loaded default icon provider""" - - def __init__(self, parent=None): - super(AbstractDataFileDialog, self).__init__(parent) - self._init() - - def _init(self): - self.setWindowTitle("Open") - - self.__directory = None - self.__directoryLoadedFilter = None - self.__errorWhileLoadingFile = None - self.__selectedFile = None - self.__selectedData = None - self.__currentHistory = [] - """Store history of URLs, last index one is the latest one""" - self.__currentHistoryLocation = -1 - """Store the location in the history. Bigger is older""" - - self.__processing = 0 - """Number of asynchronous processing tasks""" - self.__h5 = None - self.__fabio = None - - if qt.qVersion() < "5.0": - # On Qt4 it is needed to provide a safe file system model - _logger.debug("Uses SafeFileSystemModel") - from .SafeFileSystemModel import SafeFileSystemModel - self.__fileModel = SafeFileSystemModel(self) - else: - # On Qt5 a safe icon provider is still needed to avoid freeze - _logger.debug("Uses default QFileSystemModel with a SafeFileIconProvider") - self.__fileModel = qt.QFileSystemModel(self) - from .SafeFileIconProvider import SafeFileIconProvider - iconProvider = SafeFileIconProvider() - self.__fileModel.setIconProvider(iconProvider) - - # The common file dialog filter only on Mac OS X - self.__fileModel.setNameFilterDisables(sys.platform == "darwin") - self.__fileModel.setReadOnly(True) - self.__fileModel.directoryLoaded.connect(self.__directoryLoaded) - - self.__dataModel = Hdf5TreeModel(self) - - self.__createWidgets() - self.__initLayout() - self.__showAsListView() - - path = os.getcwd() - self.__fileModel_setRootPath(path) - - self.__clearData() - self.__updatePath() - - # Update the file model filter - self.__fileTypeCombo.setCurrentIndex(0) - self.__filterSelected(0) - - 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 AbstractDataFileDialog") - for obj in fileList: - _logger.debug("Close file %s", obj.filename) - obj.close() - fileList[:] = [] - - def done(self, result): - self._clear() - super(AbstractDataFileDialog, self).done(result) - - def _clear(self): - """Explicit method to clear data stored in the dialog. - After this call it is not anymore possible to use the widget. - - This method is triggered by the destruction of the object and the - QDialog :meth:`done`. Then it can be triggered more than once. - """ - _logger.debug("Clear dialog") - self.__errorWhileLoadingFile = None - self.__clearData() - if self.__fileModel is not None: - # Cache the directory before cleaning the model - self.__directory = self.directory() - self.__browser.clear() - self.__closeFile() - self.__fileModel = None - self.__dataModel = None - - def hasPendingEvents(self): - """Returns true if the dialog have asynchronous tasks working on the - background.""" - return self.__processing > 0 - - # User interface - - def __createWidgets(self): - self.__sidebar = self._createSideBar() - if self.__sidebar is not None: - sideBarModel = self.__sidebar.selectionModel() - sideBarModel.selectionChanged.connect(self.__shortcutSelected) - self.__sidebar.setSelectionMode(qt.QAbstractItemView.SingleSelection) - - listView = qt.QListView(self) - listView.setSelectionBehavior(qt.QAbstractItemView.SelectRows) - listView.setSelectionMode(qt.QAbstractItemView.SingleSelection) - listView.setResizeMode(qt.QListView.Adjust) - listView.setWrapping(True) - listView.setEditTriggers(qt.QAbstractItemView.NoEditTriggers) - listView.setContextMenuPolicy(qt.Qt.CustomContextMenu) - utils.patchToConsumeReturnKey(listView) - - treeView = qt.QTreeView(self) - treeView.setSelectionBehavior(qt.QAbstractItemView.SelectRows) - treeView.setSelectionMode(qt.QAbstractItemView.SingleSelection) - treeView.setRootIsDecorated(False) - treeView.setItemsExpandable(False) - treeView.setSortingEnabled(True) - treeView.header().setSortIndicator(0, qt.Qt.AscendingOrder) - treeView.header().setStretchLastSection(False) - treeView.setTextElideMode(qt.Qt.ElideMiddle) - treeView.setEditTriggers(qt.QAbstractItemView.NoEditTriggers) - treeView.setContextMenuPolicy(qt.Qt.CustomContextMenu) - treeView.setDragDropMode(qt.QAbstractItemView.InternalMove) - utils.patchToConsumeReturnKey(treeView) - - self.__browser = _Browser(self, listView, treeView) - self.__browser.activated.connect(self.__browsedItemActivated) - self.__browser.selected.connect(self.__browsedItemSelected) - self.__browser.rootIndexChanged.connect(self.__rootIndexChanged) - self.__browser.setObjectName("browser") - - self.__previewWidget = self._createPreviewWidget(self) - - self.__fileTypeCombo = FileTypeComboBox(self) - self.__fileTypeCombo.setObjectName("fileTypeCombo") - self.__fileTypeCombo.setDuplicatesEnabled(False) - self.__fileTypeCombo.setSizeAdjustPolicy(qt.QComboBox.AdjustToMinimumContentsLength) - self.__fileTypeCombo.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Fixed) - self.__fileTypeCombo.activated[int].connect(self.__filterSelected) - self.__fileTypeCombo.setFabioUrlSupproted(self._isFabioFilesSupported()) - - self.__pathEdit = _PathEdit(self) - self.__pathEdit.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Fixed) - self.__pathEdit.textChanged.connect(self.__textChanged) - self.__pathEdit.setObjectName("url") - utils.patchToConsumeReturnKey(self.__pathEdit) - - self.__buttons = qt.QDialogButtonBox(self) - self.__buttons.setSizePolicy(qt.QSizePolicy.Fixed, qt.QSizePolicy.Fixed) - types = qt.QDialogButtonBox.Open | qt.QDialogButtonBox.Cancel - self.__buttons.setStandardButtons(types) - self.__buttons.button(qt.QDialogButtonBox.Cancel).setObjectName("cancel") - self.__buttons.button(qt.QDialogButtonBox.Open).setObjectName("open") - - self.__buttons.accepted.connect(self.accept) - self.__buttons.rejected.connect(self.reject) - - self.__browseToolBar = self._createBrowseToolBar() - self.__backwardAction.setEnabled(False) - self.__forwardAction.setEnabled(False) - self.__fileDirectoryAction.setEnabled(False) - self.__parentFileDirectoryAction.setEnabled(False) - - self.__selectorWidget = self._createSelectorWidget(self) - if self.__selectorWidget is not None: - self.__selectorWidget.selectionChanged.connect(self.__selectorWidgetChanged) - - self.__previewToolBar = self._createPreviewToolbar(self, self.__previewWidget, self.__selectorWidget) - - self.__dataIcon = qt.QLabel(self) - self.__dataIcon.setSizePolicy(qt.QSizePolicy.Fixed, qt.QSizePolicy.Fixed) - self.__dataIcon.setScaledContents(True) - self.__dataIcon.setMargin(2) - self.__dataIcon.setAlignment(qt.Qt.AlignCenter) - - self.__dataInfo = qt.QLabel(self) - self.__dataInfo.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Fixed) - - def _createSideBar(self): - sidebar = _SideBar(self) - sidebar.setObjectName("sidebar") - return sidebar - - def iconProvider(self): - iconProvider = self.__class__._defaultIconProvider - if iconProvider is None: - iconProvider = _IconProvider() - self.__class__._defaultIconProvider = iconProvider - return iconProvider - - def _createBrowseToolBar(self): - toolbar = qt.QToolBar(self) - toolbar.setIconSize(qt.QSize(16, 16)) - iconProvider = self.iconProvider() - - backward = qt.QAction(toolbar) - backward.setText("Back") - backward.setObjectName("backwardAction") - backward.setIcon(iconProvider.icon(qt.QStyle.SP_ArrowBack)) - backward.triggered.connect(self.__navigateBackward) - self.__backwardAction = backward - - forward = qt.QAction(toolbar) - forward.setText("Forward") - forward.setObjectName("forwardAction") - forward.setIcon(iconProvider.icon(qt.QStyle.SP_ArrowForward)) - forward.triggered.connect(self.__navigateForward) - self.__forwardAction = forward - - parentDirectory = qt.QAction(toolbar) - parentDirectory.setText("Go to parent") - parentDirectory.setObjectName("toParentAction") - parentDirectory.setIcon(iconProvider.icon(qt.QStyle.SP_FileDialogToParent)) - parentDirectory.triggered.connect(self.__navigateToParent) - self.__toParentAction = parentDirectory - - fileDirectory = qt.QAction(toolbar) - fileDirectory.setText("Root of the file") - fileDirectory.setObjectName("toRootFileAction") - fileDirectory.setIcon(iconProvider.icon(iconProvider.FileDialogToParentFile)) - fileDirectory.triggered.connect(self.__navigateToParentFile) - self.__fileDirectoryAction = fileDirectory - - parentFileDirectory = qt.QAction(toolbar) - parentFileDirectory.setText("Parent directory of the file") - parentFileDirectory.setObjectName("toDirectoryAction") - parentFileDirectory.setIcon(iconProvider.icon(iconProvider.FileDialogToParentDir)) - parentFileDirectory.triggered.connect(self.__navigateToParentDir) - self.__parentFileDirectoryAction = parentFileDirectory - - listView = qt.QAction(toolbar) - listView.setText("List view") - listView.setObjectName("listModeAction") - listView.setIcon(iconProvider.icon(qt.QStyle.SP_FileDialogListView)) - listView.triggered.connect(self.__showAsListView) - listView.setCheckable(True) - - detailView = qt.QAction(toolbar) - detailView.setText("Detail view") - detailView.setObjectName("detailModeAction") - detailView.setIcon(iconProvider.icon(qt.QStyle.SP_FileDialogDetailedView)) - detailView.triggered.connect(self.__showAsDetailedView) - detailView.setCheckable(True) - - self.__listViewAction = listView - self.__detailViewAction = detailView - - toolbar.addAction(backward) - toolbar.addAction(forward) - toolbar.addSeparator() - toolbar.addAction(parentDirectory) - toolbar.addAction(fileDirectory) - toolbar.addAction(parentFileDirectory) - toolbar.addSeparator() - toolbar.addAction(listView) - toolbar.addAction(detailView) - - toolbar.setStyleSheet("QToolBar { border: 0px }") - - return toolbar - - def __initLayout(self): - sideBarLayout = qt.QVBoxLayout() - sideBarLayout.setContentsMargins(0, 0, 0, 0) - dummyToolBar = qt.QWidget(self) - dummyToolBar.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Fixed) - dummyCombo = qt.QWidget(self) - dummyCombo.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Fixed) - sideBarLayout.addWidget(dummyToolBar) - if self.__sidebar is not None: - sideBarLayout.addWidget(self.__sidebar) - sideBarLayout.addWidget(dummyCombo) - sideBarWidget = qt.QWidget(self) - sideBarWidget.setLayout(sideBarLayout) - - dummyCombo.setFixedHeight(self.__fileTypeCombo.height()) - self.__resizeCombo = _CatchResizeEvent(self, self.__fileTypeCombo) - self.__resizeCombo.resized.connect(lambda e: dummyCombo.setFixedHeight(e.size().height())) - - dummyToolBar.setFixedHeight(self.__browseToolBar.height()) - self.__resizeToolbar = _CatchResizeEvent(self, self.__browseToolBar) - self.__resizeToolbar.resized.connect(lambda e: dummyToolBar.setFixedHeight(e.size().height())) - - datasetSelection = qt.QWidget(self) - layoutLeft = qt.QVBoxLayout() - layoutLeft.setContentsMargins(0, 0, 0, 0) - layoutLeft.addWidget(self.__browseToolBar) - layoutLeft.addWidget(self.__browser) - layoutLeft.addWidget(self.__fileTypeCombo) - datasetSelection.setLayout(layoutLeft) - datasetSelection.setSizePolicy(qt.QSizePolicy.MinimumExpanding, qt.QSizePolicy.Expanding) - - infoLayout = qt.QHBoxLayout() - infoLayout.setContentsMargins(0, 0, 0, 0) - infoLayout.addWidget(self.__dataIcon) - infoLayout.addWidget(self.__dataInfo) - - dataFrame = qt.QFrame(self) - dataFrame.setFrameShape(qt.QFrame.StyledPanel) - layout = qt.QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - layout.addWidget(self.__previewWidget) - layout.addLayout(infoLayout) - dataFrame.setLayout(layout) - - dataSelection = qt.QWidget(self) - dataLayout = qt.QVBoxLayout() - dataLayout.setContentsMargins(0, 0, 0, 0) - if self.__previewToolBar is not None: - dataLayout.addWidget(self.__previewToolBar) - else: - # Add dummy space - dummyToolbar2 = qt.QWidget(self) - dummyToolbar2.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Fixed) - dummyToolbar2.setFixedHeight(self.__browseToolBar.height()) - self.__resizeToolbar = _CatchResizeEvent(self, self.__browseToolBar) - self.__resizeToolbar.resized.connect(lambda e: dummyToolbar2.setFixedHeight(e.size().height())) - dataLayout.addWidget(dummyToolbar2) - - dataLayout.addWidget(dataFrame) - if self.__selectorWidget is not None: - dataLayout.addWidget(self.__selectorWidget) - else: - # Add dummy space - dummyCombo2 = qt.QWidget(self) - dummyCombo2.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Fixed) - dummyCombo2.setFixedHeight(self.__fileTypeCombo.height()) - self.__resizeToolbar = _CatchResizeEvent(self, self.__fileTypeCombo) - self.__resizeToolbar.resized.connect(lambda e: dummyCombo2.setFixedHeight(e.size().height())) - dataLayout.addWidget(dummyCombo2) - dataSelection.setLayout(dataLayout) - - self.__splitter = qt.QSplitter(self) - self.__splitter.setContentsMargins(0, 0, 0, 0) - self.__splitter.addWidget(sideBarWidget) - self.__splitter.addWidget(datasetSelection) - self.__splitter.addWidget(dataSelection) - self.__splitter.setStretchFactor(1, 10) - - bottomLayout = qt.QHBoxLayout() - bottomLayout.setContentsMargins(0, 0, 0, 0) - bottomLayout.addWidget(self.__pathEdit) - bottomLayout.addWidget(self.__buttons) - - layout = qt.QVBoxLayout(self) - layout.addWidget(self.__splitter) - layout.addLayout(bottomLayout) - - self.setLayout(layout) - self.updateGeometry() - - # Logic - - def __navigateBackward(self): - """Navigate through the history one step backward.""" - if len(self.__currentHistory) > 0 and self.__currentHistoryLocation > 0: - self.__currentHistoryLocation -= 1 - url = self.__currentHistory[self.__currentHistoryLocation] - self.selectUrl(url) - - def __navigateForward(self): - """Navigate through the history one step forward.""" - if len(self.__currentHistory) > 0 and self.__currentHistoryLocation < len(self.__currentHistory) - 1: - self.__currentHistoryLocation += 1 - url = self.__currentHistory[self.__currentHistoryLocation] - self.selectUrl(url) - - def __navigateToParent(self): - index = self.__browser.rootIndex() - if index.model() is self.__fileModel: - # browse throw the file system - index = index.parent() - path = self.__fileModel.filePath(index) - self.__fileModel_setRootPath(path) - self.__browser.selectIndex(qt.QModelIndex()) - self.__updatePath() - elif index.model() is self.__dataModel: - index = index.parent() - if index.isValid(): - # browse throw the hdf5 - self.__browser.setRootIndex(index) - self.__browser.selectIndex(qt.QModelIndex()) - self.__updatePath() - else: - # go back to the file system - self.__navigateToParentDir() - else: - # Root of the file system (my computer) - pass - - def __navigateToParentFile(self): - index = self.__browser.rootIndex() - if index.model() is self.__dataModel: - index = self.__dataModel.indexFromH5Object(self.__h5) - self.__browser.setRootIndex(index) - self.__browser.selectIndex(qt.QModelIndex()) - self.__updatePath() - - def __navigateToParentDir(self): - index = self.__browser.rootIndex() - if index.model() is self.__dataModel: - path = os.path.dirname(self.__h5.file.filename) - index = self.__fileModel.index(path) - self.__browser.setRootIndex(index) - self.__browser.selectIndex(qt.QModelIndex()) - self.__closeFile() - self.__updatePath() - - def viewMode(self): - """Returns the current view mode. - - :rtype: qt.QFileDialog.ViewMode - """ - return self.__browser.viewMode() - - def setViewMode(self, mode): - """Set the current view mode. - - :param qt.QFileDialog.ViewMode mode: The new view mode - """ - if mode == qt.QFileDialog.Detail: - self.__browser.showDetails() - self.__listViewAction.setChecked(False) - self.__detailViewAction.setChecked(True) - elif mode == qt.QFileDialog.List: - self.__browser.showList() - self.__listViewAction.setChecked(True) - self.__detailViewAction.setChecked(False) - else: - assert(False) - - def __showAsListView(self): - self.setViewMode(qt.QFileDialog.List) - - def __showAsDetailedView(self): - self.setViewMode(qt.QFileDialog.Detail) - - def __shortcutSelected(self): - self.__browser.selectIndex(qt.QModelIndex()) - self.__clearData() - self.__updatePath() - selectionModel = self.__sidebar.selectionModel() - indexes = selectionModel.selectedIndexes() - if len(indexes) == 1: - index = indexes[0] - url = self.__sidebar.model().data(index, role=qt.Qt.UserRole) - path = url.toLocalFile() - self.__fileModel_setRootPath(path) - - def __browsedItemActivated(self, index): - if not index.isValid(): - return - if index.model() is self.__fileModel: - path = self.__fileModel.filePath(index) - if self.__fileModel.isDir(index): - self.__fileModel_setRootPath(path) - if os.path.isfile(path): - self.__fileActivated(index) - elif index.model() is self.__dataModel: - obj = self.__dataModel.data(index, role=Hdf5TreeModel.H5PY_OBJECT_ROLE) - if silx.io.is_group(obj): - self.__browser.setRootIndex(index) - else: - assert(False) - - def __browsedItemSelected(self, index): - self.__dataSelected(index) - self.__updatePath() - - def __fileModel_setRootPath(self, path): - """Set the root path of the fileModel with a filter on the - directoryLoaded event. - - Without this filter an extra event is received (at least with PyQt4) - when we use for the first time the sidebar. - - :param str path: Path to load - """ - assert(path is not None) - if path != "" and not os.path.exists(path): - return - if self.hasPendingEvents(): - # Make sure the asynchronous fileModel setRootPath is finished - qt.QApplication.instance().processEvents() - - if self.__directoryLoadedFilter is not None: - if utils.samefile(self.__directoryLoadedFilter, path): - return - self.__directoryLoadedFilter = path - self.__processing += 1 - index = self.__fileModel.setRootPath(path) - if not index.isValid(): - self.__processing -= 1 - self.__browser.setRootIndex(index, model=self.__fileModel) - self.__clearData() - self.__updatePath() - else: - # asynchronous process - pass - - def __directoryLoaded(self, path): - if self.__directoryLoadedFilter is not None: - if not utils.samefile(self.__directoryLoadedFilter, path): - # Filter event which should not arrive in PyQt4 - # The first click on the sidebar sent 2 events - self.__processing -= 1 - return - index = self.__fileModel.index(path) - self.__browser.setRootIndex(index, model=self.__fileModel) - self.__updatePath() - self.__processing -= 1 - - def __closeFile(self): - self.__openedFiles[:] = [] - self.__fileDirectoryAction.setEnabled(False) - self.__parentFileDirectoryAction.setEnabled(False) - if self.__h5 is not None: - self.__dataModel.removeH5pyObject(self.__h5) - self.__h5.close() - self.__h5 = None - if self.__fabio is not None: - if hasattr(self.__fabio, "close"): - self.__fabio.close() - self.__fabio = None - - def __openFabioFile(self, filename): - self.__closeFile() - try: - if fabio is None: - raise ImportError("Fabio module is not available") - self.__fabio = fabio.open(filename) - self.__openedFiles.append(self.__fabio) - self.__selectedFile = filename - except Exception as e: - _logger.error("Error while loading file %s: %s", filename, e.args[0]) - _logger.debug("Backtrace", exc_info=True) - self.__errorWhileLoadingFile = filename, e.args[0] - return False - else: - return True - - def __openSilxFile(self, filename): - self.__closeFile() - try: - self.__h5 = silx.io.open(filename) - self.__openedFiles.append(self.__h5) - self.__selectedFile = filename - except IOError as e: - _logger.error("Error while loading file %s: %s", filename, e.args[0]) - _logger.debug("Backtrace", exc_info=True) - self.__errorWhileLoadingFile = filename, e.args[0] - return False - else: - self.__fileDirectoryAction.setEnabled(True) - self.__parentFileDirectoryAction.setEnabled(True) - self.__dataModel.insertH5pyObject(self.__h5) - return True - - def __isSilxHavePriority(self, filename): - """Silx have priority when there is a specific decoder - """ - _, ext = os.path.splitext(filename) - ext = "*%s" % ext - formats = silx.io.supported_extensions(flat_formats=False) - for extensions in formats.values(): - if ext in extensions: - return True - return False - - def __openFile(self, filename): - codec = self.__fileTypeCombo.currentCodec() - openners = [] - if codec.is_autodetect(): - if self.__isSilxHavePriority(filename): - openners.append(self.__openSilxFile) - if fabio is not None and self._isFabioFilesSupported(): - openners.append(self.__openFabioFile) - else: - if fabio is not None and self._isFabioFilesSupported(): - openners.append(self.__openFabioFile) - openners.append(self.__openSilxFile) - elif codec.is_silx_codec(): - openners.append(self.__openSilxFile) - elif self._isFabioFilesSupported() and codec.is_fabio_codec(): - # It is requested to use fabio, anyway fabio is here or not - openners.append(self.__openFabioFile) - - for openner in openners: - ref = openner(filename) - if ref is not None: - return True - return False - - def __fileActivated(self, index): - self.__selectedFile = None - path = self.__fileModel.filePath(index) - if os.path.isfile(path): - loaded = self.__openFile(path) - if loaded: - if self.__h5 is not None: - index = self.__dataModel.indexFromH5Object(self.__h5) - self.__browser.setRootIndex(index) - elif self.__fabio is not None: - data = _FabioData(self.__fabio) - self.__setData(data) - self.__updatePath() - else: - self.__clearData() - - def __dataSelected(self, index): - selectedData = None - if index is not None: - if index.model() is self.__dataModel: - obj = self.__dataModel.data(index, self.__dataModel.H5PY_OBJECT_ROLE) - if self._isDataSupportable(obj): - selectedData = obj - elif index.model() is self.__fileModel: - self.__closeFile() - if self._isFabioFilesSupported(): - path = self.__fileModel.filePath(index) - if os.path.isfile(path): - codec = self.__fileTypeCombo.currentCodec() - is_fabio_decoder = codec.is_fabio_codec() - is_fabio_have_priority = not codec.is_silx_codec() and not self.__isSilxHavePriority(path) - if is_fabio_decoder or is_fabio_have_priority: - # Then it's flat frame container - if fabio is not None: - self.__openFabioFile(path) - if self.__fabio is not None: - selectedData = _FabioData(self.__fabio) - else: - assert(False) - - self.__setData(selectedData) - - def __filterSelected(self, index): - filters = self.__fileTypeCombo.itemExtensions(index) - self.__fileModel.setNameFilters(list(filters)) - - def __setData(self, data): - self.__data = data - - if data is not None and self._isDataSupportable(data): - if self.__selectorWidget is not None: - self.__selectorWidget.setData(data) - if not self.__selectorWidget.isUsed(): - # Needed to fake the fact we have to reset the zoom in preview - self.__selectedData = None - self.__setSelectedData(data) - self.__selectorWidget.hide() - else: - self.__selectorWidget.setVisible(self.__selectorWidget.hasVisibleSelectors()) - # Needed to fake the fact we have to reset the zoom in preview - self.__selectedData = None - self.__selectorWidget.selectionChanged.emit() - else: - # Needed to fake the fact we have to reset the zoom in preview - self.__selectedData = None - self.__setSelectedData(data) - else: - self.__clearData() - self.__updatePath() - - def _isDataSupported(self, data): - """Check if the data can be returned by the dialog. - - If true, this data can be returned by the dialog and the open button - while be enabled. If false the button will be disabled. - - :rtype: bool - """ - raise NotImplementedError() - - def _isDataSupportable(self, data): - """Check if the selected data can be supported at one point. - - If true, the data selector will be checked and it will update the data - preview. Else the selecting is disabled. - - :rtype: bool - """ - raise NotImplementedError() - - def __clearData(self): - """Clear the data part of the GUI""" - if self.__previewWidget is not None: - self.__previewWidget.setData(None) - if self.__selectorWidget is not None: - self.__selectorWidget.hide() - self.__selectedData = None - self.__data = None - self.__updateDataInfo() - button = self.__buttons.button(qt.QDialogButtonBox.Open) - button.setEnabled(False) - - def __selectorWidgetChanged(self): - data = self.__selectorWidget.getSelectedData(self.__data) - self.__setSelectedData(data) - - def __setSelectedData(self, data): - """Set the data selected by the dialog. - - If :meth:`_isDataSupported` returns false, this function will be - inhibited and no data will be selected. - """ - if self.__previewWidget is not None: - fromDataSelector = self.__selectedData is not None - self.__previewWidget.setData(data, fromDataSelector=fromDataSelector) - if self._isDataSupported(data): - self.__selectedData = data - else: - self.__clearData() - return - self.__updateDataInfo() - self.__updatePath() - button = self.__buttons.button(qt.QDialogButtonBox.Open) - button.setEnabled(True) - - def __updateDataInfo(self): - if self.__errorWhileLoadingFile is not None: - filename, message = self.__errorWhileLoadingFile - message = "<b>Error while loading file '%s'</b><hr/>%s" % (filename, message) - size = self.__dataInfo.height() - icon = self.style().standardIcon(qt.QStyle.SP_MessageBoxCritical) - pixmap = icon.pixmap(size, size) - - self.__dataInfo.setText("Error while loading file") - self.__dataInfo.setToolTip(message) - self.__dataIcon.setToolTip(message) - self.__dataIcon.setVisible(True) - self.__dataIcon.setPixmap(pixmap) - - self.__errorWhileLoadingFile = None - return - - self.__dataIcon.setVisible(False) - self.__dataInfo.setToolTip("") - if self.__selectedData is None: - self.__dataInfo.setText("No data selected") - else: - text = self._displayedDataInfo(self.__data, self.__selectedData) - self.__dataInfo.setVisible(text is not None) - if text is not None: - self.__dataInfo.setText(text) - - def _displayedDataInfo(self, dataBeforeSelection, dataAfterSelection): - """Returns the text displayed under the data preview. - - This zone is used to display error in case or problem of data selection - or problems with IO. - - :param numpy.ndarray dataAfterSelection: Data as it is after the - selection widget (basically the data from the preview widget) - :param numpy.ndarray dataAfterSelection: Data as it is before the - selection widget (basically the data from the browsing widget) - :rtype: bool - """ - return None - - def __createUrlFromIndex(self, index, useSelectorWidget=True): - if index.model() is self.__fileModel: - filename = self.__fileModel.filePath(index) - dataPath = None - elif index.model() is self.__dataModel: - obj = self.__dataModel.data(index, role=Hdf5TreeModel.H5PY_OBJECT_ROLE) - filename = obj.file.filename - dataPath = obj.name - else: - # root of the computer - filename = "" - dataPath = None - - if useSelectorWidget and self.__selectorWidget is not None and self.__selectorWidget.isVisible(): - slicing = self.__selectorWidget.slicing() - else: - slicing = None - - if self.__fabio is not None: - scheme = "fabio" - elif self.__h5 is not None: - scheme = "silx" - else: - if os.path.isfile(filename): - codec = self.__fileTypeCombo.currentCodec() - if codec.is_fabio_codec(): - scheme = "fabio" - elif codec.is_silx_codec(): - scheme = "silx" - else: - scheme = None - else: - scheme = None - - url = silx.io.url.DataUrl(file_path=filename, data_path=dataPath, data_slice=slicing, scheme=scheme) - return url - - def __updatePath(self): - index = self.__browser.selectedIndex() - if index is None: - index = self.__browser.rootIndex() - url = self.__createUrlFromIndex(index) - if url.path() != self.__pathEdit.text(): - old = self.__pathEdit.blockSignals(True) - self.__pathEdit.setText(url.path()) - self.__pathEdit.blockSignals(old) - - def __rootIndexChanged(self, index): - url = self.__createUrlFromIndex(index, useSelectorWidget=False) - - currentUrl = None - if 0 <= self.__currentHistoryLocation < len(self.__currentHistory): - currentUrl = self.__currentHistory[self.__currentHistoryLocation] - - if currentUrl is None or currentUrl != url.path(): - # clean up the forward history - self.__currentHistory = self.__currentHistory[0:self.__currentHistoryLocation + 1] - self.__currentHistory.append(url.path()) - self.__currentHistoryLocation += 1 - - if index.model() != self.__dataModel: - if sys.platform == "win32": - # path == "" - isRoot = not index.isValid() - else: - # path in ["", "/"] - isRoot = not index.isValid() or not index.parent().isValid() - else: - isRoot = False - - if index.isValid(): - self.__dataSelected(index) - self.__toParentAction.setEnabled(not isRoot) - self.__updateActionHistory() - self.__updateSidebar() - - def __updateSidebar(self): - """Called when the current directory location change""" - if self.__sidebar is None: - return - selectionModel = self.__sidebar.selectionModel() - selectionModel.selectionChanged.disconnect(self.__shortcutSelected) - index = self.__browser.rootIndex() - if index.model() == self.__fileModel: - path = self.__fileModel.filePath(index) - self.__sidebar.setSelectedPath(path) - elif index.model() is None: - path = "" - self.__sidebar.setSelectedPath(path) - else: - selectionModel.clear() - selectionModel.selectionChanged.connect(self.__shortcutSelected) - - def __updateActionHistory(self): - self.__forwardAction.setEnabled(len(self.__currentHistory) - 1 > self.__currentHistoryLocation) - self.__backwardAction.setEnabled(self.__currentHistoryLocation > 0) - - def __textChanged(self, text): - self.__pathChanged() - - def _isFabioFilesSupported(self): - """Returns true fabio files can be loaded. - """ - return True - - def _isLoadableUrl(self, url): - """Returns true if the URL is loadable by this dialog. - - :param DataUrl url: The requested URL - """ - return True - - def __pathChanged(self): - url = silx.io.url.DataUrl(path=self.__pathEdit.text()) - if url.is_valid() or url.path() == "": - if url.path() in ["", "/"] or url.file_path() in ["", "/"]: - self.__fileModel_setRootPath(qt.QDir.rootPath()) - elif os.path.exists(url.file_path()): - rootIndex = None - if os.path.isdir(url.file_path()): - self.__fileModel_setRootPath(url.file_path()) - index = self.__fileModel.index(url.file_path()) - elif os.path.isfile(url.file_path()): - if self._isLoadableUrl(url): - if url.scheme() == "silx": - loaded = self.__openSilxFile(url.file_path()) - elif url.scheme() == "fabio" and self._isFabioFilesSupported(): - loaded = self.__openFabioFile(url.file_path()) - else: - loaded = self.__openFile(url.file_path()) - else: - loaded = False - if loaded: - if self.__h5 is not None: - rootIndex = self.__dataModel.indexFromH5Object(self.__h5) - elif self.__fabio is not None: - index = self.__fileModel.index(url.file_path()) - rootIndex = index - if rootIndex is None: - index = self.__fileModel.index(url.file_path()) - index = index.parent() - - if rootIndex is not None: - if rootIndex.model() == self.__dataModel: - if url.data_path() is not None: - dataPath = url.data_path() - if dataPath in self.__h5: - obj = self.__h5[dataPath] - else: - path = utils.findClosestSubPath(self.__h5, dataPath) - if path is None: - path = "/" - obj = self.__h5[path] - - if silx.io.is_file(obj): - self.__browser.setRootIndex(rootIndex) - elif silx.io.is_group(obj): - index = self.__dataModel.indexFromH5Object(obj) - self.__browser.setRootIndex(index) - else: - index = self.__dataModel.indexFromH5Object(obj) - self.__browser.setRootIndex(index.parent()) - self.__browser.selectIndex(index) - else: - self.__browser.setRootIndex(rootIndex) - self.__clearData() - elif rootIndex.model() == self.__fileModel: - # that's a fabio file - self.__browser.setRootIndex(rootIndex.parent()) - self.__browser.selectIndex(rootIndex) - # data = _FabioData(self.__fabio) - # self.__setData(data) - else: - assert(False) - else: - self.__browser.setRootIndex(index, model=self.__fileModel) - self.__clearData() - - if self.__selectorWidget is not None: - self.__selectorWidget.setVisible(url.data_slice() is not None) - if url.data_slice() is not None: - self.__selectorWidget.setSlicing(url.data_slice()) - else: - self.__errorWhileLoadingFile = (url.file_path(), "File not found") - self.__clearData() - else: - self.__errorWhileLoadingFile = (url.file_path(), "Path invalid") - self.__clearData() - - def previewToolbar(self): - return self.__previewToolbar - - def previewWidget(self): - return self.__previewWidget - - def selectorWidget(self): - return self.__selectorWidget - - def _createPreviewToolbar(self, parent, dataPreviewWidget, dataSelectorWidget): - return None - - def _createPreviewWidget(self, parent): - return None - - def _createSelectorWidget(self, parent): - return None - - # Selected file - - def setDirectory(self, path): - """Sets the data dialog's current directory.""" - self.__fileModel_setRootPath(path) - - def selectedFile(self): - """Returns the file path containing the selected data. - - :rtype: str - """ - return self.__selectedFile - - def selectFile(self, filename): - """Sets the data dialog's current file.""" - self.__directoryLoadedFilter = "" - old = self.__pathEdit.blockSignals(True) - try: - self.__pathEdit.setText(filename) - finally: - self.__pathEdit.blockSignals(old) - self.__pathChanged() - - # Selected data - - def selectUrl(self, url): - """Sets the data dialog's current data url. - - :param Union[str,DataUrl] url: URL identifying a data (it can be a - `DataUrl` object) - """ - if isinstance(url, silx.io.url.DataUrl): - url = url.path() - self.__directoryLoadedFilter = "" - old = self.__pathEdit.blockSignals(True) - try: - self.__pathEdit.setText(url) - finally: - self.__pathEdit.blockSignals(old) - self.__pathChanged() - - def selectedUrl(self): - """Returns the URL from the file system to the data. - - If the dialog is not validated, the path can be an intermediat - selected path, or an invalid path. - - :rtype: str - """ - return self.__pathEdit.text() - - def selectedDataUrl(self): - """Returns the URL as a :class:`DataUrl` from the file system to the - data. - - If the dialog is not validated, the path can be an intermediat - selected path, or an invalid path. - - :rtype: DataUrl - """ - url = self.selectedUrl() - return silx.io.url.DataUrl(url) - - def directory(self): - """Returns the path from the current browsed directory. - - :rtype: str - """ - if self.__directory is not None: - # At post execution, returns the cache - return self.__directory - - index = self.__browser.rootIndex() - if index.model() is self.__fileModel: - path = self.__fileModel.filePath(index) - return path - elif index.model() is self.__dataModel: - path = os.path.dirname(self.__h5.file.filename) - return path - else: - return "" - - def _selectedData(self): - """Returns the internal selected data - - :rtype: numpy.ndarray - """ - return self.__selectedData - - # Filters - - def selectedNameFilter(self): - """Returns the filter that the user selected in the file dialog.""" - return self.__fileTypeCombo.currentText() - - # History - - def history(self): - """Returns the browsing history of the filedialog as a list of paths. - - :rtype: List<str> - """ - if len(self.__currentHistory) <= 1: - return [] - history = self.__currentHistory[0:self.__currentHistoryLocation] - return list(history) - - def setHistory(self, history): - self.__currentHistory = [] - self.__currentHistory.extend(history) - self.__currentHistoryLocation = len(self.__currentHistory) - 1 - self.__updateActionHistory() - - # Colormap - - def colormap(self): - if self.__previewWidget is None: - return None - return self.__previewWidget.colormap() - - def setColormap(self, colormap): - if self.__previewWidget is None: - raise RuntimeError("No preview widget defined") - self.__previewWidget.setColormap(colormap) - - # Sidebar - - def setSidebarUrls(self, urls): - """Sets the urls that are located in the sidebar.""" - if self.__sidebar is None: - return - self.__sidebar.setUrls(urls) - - def sidebarUrls(self): - """Returns a list of urls that are currently in the sidebar.""" - if self.__sidebar is None: - return [] - return self.__sidebar.urls() - - # State - - __serialVersion = 1 - """Store the current version of the serialized data""" - - @classmethod - def qualifiedName(cls): - return "%s.%s" % (cls.__module__, cls.__name__) - - def restoreState(self, state): - """Restores the dialogs's layout, history and current directory to the - state specified. - - :param qt.QByteArray state: Stream containing the new state - :rtype: bool - """ - stream = qt.QDataStream(state, qt.QIODevice.ReadOnly) - - qualifiedName = stream.readQString() - if qualifiedName != self.qualifiedName(): - _logger.warning("Stored state contains an invalid qualified name. %s restoration cancelled.", self.__class__.__name__) - return False - - version = stream.readInt32() - if version != self.__serialVersion: - _logger.warning("Stored state contains an invalid version. %s restoration cancelled.", self.__class__.__name__) - return False - - result = True - - splitterData = stream.readQVariant() - sidebarUrls = stream.readQStringList() - history = stream.readQStringList() - workingDirectory = stream.readQString() - browserData = stream.readQVariant() - viewMode = stream.readInt32() - colormapData = stream.readQVariant() - - result &= self.__splitter.restoreState(splitterData) - sidebarUrls = [qt.QUrl(s) for s in sidebarUrls] - self.setSidebarUrls(list(sidebarUrls)) - history = [s for s in history] - self.setHistory(list(history)) - if workingDirectory is not None: - self.setDirectory(workingDirectory) - result &= self.__browser.restoreState(browserData) - self.setViewMode(viewMode) - colormap = self.colormap() - if colormap is not None: - result &= self.colormap().restoreState(colormapData) - - return result - - def saveState(self): - """Saves the state of the dialog's layout, history and current - directory. - - :rtype: qt.QByteArray - """ - data = qt.QByteArray() - stream = qt.QDataStream(data, qt.QIODevice.WriteOnly) - - s = self.qualifiedName() - stream.writeQString(u"%s" % s) - stream.writeInt32(self.__serialVersion) - stream.writeQVariant(self.__splitter.saveState()) - strings = [u"%s" % s.toString() for s in self.sidebarUrls()] - stream.writeQStringList(strings) - strings = [u"%s" % s for s in self.history()] - stream.writeQStringList(strings) - stream.writeQString(u"%s" % self.directory()) - stream.writeQVariant(self.__browser.saveState()) - stream.writeInt32(self.viewMode()) - colormap = self.colormap() - if colormap is not None: - stream.writeQVariant(self.colormap().saveState()) - else: - stream.writeQVariant(None) - - return data diff --git a/silx/gui/dialog/ColormapDialog.py b/silx/gui/dialog/ColormapDialog.py deleted file mode 100644 index cbbfa5a..0000000 --- a/silx/gui/dialog/ColormapDialog.py +++ /dev/null @@ -1,987 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2004-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. -# -# ###########################################################################*/ -"""A QDialog widget to set-up the colormap. - -It uses a description of colormaps as dict compatible with :class:`Plot`. - -To run the following sample code, a QApplication must be initialized. - -Create the colormap dialog and set the colormap description and data range: - ->>> from silx.gui.dialog.ColormapDialog import ColormapDialog ->>> from silx.gui.colors import Colormap - ->>> dialog = ColormapDialog() ->>> colormap = Colormap(name='red', normalization='log', -... vmin=1., vmax=2.) - ->>> dialog.setColormap(colormap) ->>> colormap.setVRange(1., 100.) # This scale the width of the plot area ->>> dialog.show() - -Get the colormap description (compatible with :class:`Plot`) from the dialog: - ->>> cmap = dialog.getColormap() ->>> cmap.getName() -'red' - -It is also possible to display an histogram of the image in the dialog. -This updates the data range with the range of the bins. - ->>> import numpy ->>> image = numpy.random.normal(size=512 * 512).reshape(512, -1) ->>> hist, bin_edges = numpy.histogram(image, bins=10) ->>> dialog.setHistogram(hist, bin_edges) - -The updates of the colormap description are also available through the signal: -:attr:`ColormapDialog.sigColormapChanged`. -""" # noqa - -from __future__ import division - -__authors__ = ["V.A. Sole", "T. Vincent", "H. Payno"] -__license__ = "MIT" -__date__ = "23/05/2018" - - -import logging - -import numpy - -from .. import qt -from ..colors import Colormap, preferredColormaps -from ..plot import PlotWidget -from silx.gui.widgets.FloatEdit import FloatEdit -import weakref -from silx.math.combo import min_max -from silx.third_party import enum -from silx.gui import icons -from silx.math.histogram import Histogramnd - -_logger = logging.getLogger(__name__) - - -_colormapIconPreview = {} - - -class _BoundaryWidget(qt.QWidget): - """Widget to edit a boundary of the colormap (vmin, vmax)""" - sigValueChanged = qt.Signal(object) - """Signal emitted when value is changed""" - - def __init__(self, parent=None, value=0.0): - qt.QWidget.__init__(self, parent=None) - self.setLayout(qt.QHBoxLayout()) - self.layout().setContentsMargins(0, 0, 0, 0) - self._numVal = FloatEdit(parent=self, value=value) - self.layout().addWidget(self._numVal) - self._autoCB = qt.QCheckBox('auto', parent=self) - self.layout().addWidget(self._autoCB) - self._autoCB.setChecked(False) - - self._autoCB.toggled.connect(self._autoToggled) - self.sigValueChanged = self._autoCB.toggled - self.textEdited = self._numVal.textEdited - self.editingFinished = self._numVal.editingFinished - self._dataValue = None - - def isAutoChecked(self): - return self._autoCB.isChecked() - - def getValue(self): - return None if self._autoCB.isChecked() else self._numVal.value() - - def getFiniteValue(self): - if not self._autoCB.isChecked(): - return self._numVal.value() - elif self._dataValue is None: - return self._numVal.value() - else: - return self._dataValue - - def _autoToggled(self, enabled): - self._numVal.setEnabled(not enabled) - self._updateDisplayedText() - - def _updateDisplayedText(self): - # if dataValue is finite - if self._autoCB.isChecked() and self._dataValue is not None: - old = self._numVal.blockSignals(True) - self._numVal.setValue(self._dataValue) - self._numVal.blockSignals(old) - - def setDataValue(self, dataValue): - self._dataValue = dataValue - self._updateDisplayedText() - - def setFiniteValue(self, value): - assert(value is not None) - old = self._numVal.blockSignals(True) - self._numVal.setValue(value) - self._numVal.blockSignals(old) - - def setValue(self, value, isAuto=False): - self._autoCB.setChecked(isAuto or value is None) - if value is not None: - self._numVal.setValue(value) - self._updateDisplayedText() - - -class _ColormapNameCombox(qt.QComboBox): - def __init__(self, parent=None): - qt.QComboBox.__init__(self, parent) - self.__initItems() - - ORIGINAL_NAME = qt.Qt.UserRole + 1 - - def __initItems(self): - for colormapName in preferredColormaps(): - index = self.count() - self.addItem(str.title(colormapName)) - self.setItemIcon(index, self.getIconPreview(colormapName)) - self.setItemData(index, colormapName, role=self.ORIGINAL_NAME) - - def getIconPreview(self, colormapName): - """Return an icon preview from a LUT name. - - This icons are cached into a global structure. - - :param str colormapName: str - :rtype: qt.QIcon - """ - if colormapName not in _colormapIconPreview: - icon = self.createIconPreview(colormapName) - _colormapIconPreview[colormapName] = icon - return _colormapIconPreview[colormapName] - - def createIconPreview(self, colormapName): - """Create and return an icon preview from a LUT name. - - This icons are cached into a global structure. - - :param str colormapName: Name of the LUT - :rtype: qt.QIcon - """ - colormap = Colormap(colormapName) - size = 32 - lut = colormap.getNColors(size) - if lut is None or len(lut) == 0: - return qt.QIcon() - - pixmap = qt.QPixmap(size, size) - painter = qt.QPainter(pixmap) - for i in range(size): - rgb = lut[i] - r, g, b = rgb[0], rgb[1], rgb[2] - painter.setPen(qt.QColor(r, g, b)) - painter.drawPoint(qt.QPoint(i, 0)) - - painter.drawPixmap(0, 1, size, size - 1, pixmap, 0, 0, size, 1) - painter.end() - - return qt.QIcon(pixmap) - - def getCurrentName(self): - return self.itemData(self.currentIndex(), self.ORIGINAL_NAME) - - def findColormap(self, name): - return self.findData(name, role=self.ORIGINAL_NAME) - - def setCurrentName(self, name): - index = self.findColormap(name) - if index < 0: - index = self.count() - self.addItem(str.title(name)) - self.setItemIcon(index, self.getIconPreview(name)) - self.setItemData(index, name, role=self.ORIGINAL_NAME) - self.setCurrentIndex(index) - - -@enum.unique -class _DataInPlotMode(enum.Enum): - """Enum for each mode of display of the data in the plot.""" - NONE = 'none' - RANGE = 'range' - HISTOGRAM = 'histogram' - - -class ColormapDialog(qt.QDialog): - """A QDialog widget to set the colormap. - - :param parent: See :class:`QDialog` - :param str title: The QDialog title - """ - - visibleChanged = qt.Signal(bool) - """This event is sent when the dialog visibility change""" - - def __init__(self, parent=None, title="Colormap Dialog"): - qt.QDialog.__init__(self, parent) - self.setWindowTitle(title) - - self._colormap = None - self._data = None - self._dataInPlotMode = _DataInPlotMode.RANGE - - self._ignoreColormapChange = False - """Used as a semaphore to avoid editing the colormap object when we are - only attempt to display it. - Used instead of n connect and disconnect of the sigChanged. The - disconnection to sigChanged was also limiting when this colormapdialog - is used in the colormapaction and associated to the activeImageChanged. - (because the activeImageChanged is send when the colormap changed and - the self.setcolormap is a callback) - """ - - self._histogramData = None - self._minMaxWasEdited = False - self._initialRange = None - - self._dataRange = None - """If defined 3-tuple containing information from a data: - minimum, positive minimum, maximum""" - - self._colormapStoredState = None - - # Make the GUI - vLayout = qt.QVBoxLayout(self) - - formWidget = qt.QWidget(parent=self) - vLayout.addWidget(formWidget) - formLayout = qt.QFormLayout(formWidget) - formLayout.setContentsMargins(10, 10, 10, 10) - formLayout.setSpacing(0) - - # Colormap row - self._comboBoxColormap = _ColormapNameCombox(parent=formWidget) - self._comboBoxColormap.currentIndexChanged[int].connect(self._updateName) - formLayout.addRow('Colormap:', self._comboBoxColormap) - - # Normalization row - self._normButtonLinear = qt.QRadioButton('Linear') - self._normButtonLinear.setChecked(True) - self._normButtonLog = qt.QRadioButton('Log') - self._normButtonLog.toggled.connect(self._activeLogNorm) - - normButtonGroup = qt.QButtonGroup(self) - normButtonGroup.setExclusive(True) - normButtonGroup.addButton(self._normButtonLinear) - normButtonGroup.addButton(self._normButtonLog) - self._normButtonLinear.toggled[bool].connect(self._updateLinearNorm) - - normLayout = qt.QHBoxLayout() - normLayout.setContentsMargins(0, 0, 0, 0) - normLayout.setSpacing(10) - normLayout.addWidget(self._normButtonLinear) - normLayout.addWidget(self._normButtonLog) - - formLayout.addRow('Normalization:', normLayout) - - # Min row - self._minValue = _BoundaryWidget(parent=self, value=1.0) - self._minValue.textEdited.connect(self._minMaxTextEdited) - self._minValue.editingFinished.connect(self._minEditingFinished) - self._minValue.sigValueChanged.connect(self._updateMinMax) - formLayout.addRow('\tMin:', self._minValue) - - # Max row - self._maxValue = _BoundaryWidget(parent=self, value=10.0) - self._maxValue.textEdited.connect(self._minMaxTextEdited) - self._maxValue.sigValueChanged.connect(self._updateMinMax) - self._maxValue.editingFinished.connect(self._maxEditingFinished) - formLayout.addRow('\tMax:', self._maxValue) - - # Add plot for histogram - self._plotToolbar = qt.QToolBar(self) - self._plotToolbar.setFloatable(False) - self._plotToolbar.setMovable(False) - self._plotToolbar.setIconSize(qt.QSize(8, 8)) - self._plotToolbar.setStyleSheet("QToolBar { border: 0px }") - self._plotToolbar.setOrientation(qt.Qt.Vertical) - - group = qt.QActionGroup(self._plotToolbar) - group.setExclusive(True) - - action = qt.QAction("Nothing", self) - action.setToolTip("No range nor histogram are displayed. No extra computation have to be done.") - action.setIcon(icons.getQIcon('colormap-none')) - action.setCheckable(True) - action.setData(_DataInPlotMode.NONE) - action.setChecked(action.data() == self._dataInPlotMode) - self._plotToolbar.addAction(action) - group.addAction(action) - action = qt.QAction("Data range", self) - action.setToolTip("Display the data range within the colormap range. A fast data processing have to be done.") - action.setIcon(icons.getQIcon('colormap-range')) - action.setCheckable(True) - action.setData(_DataInPlotMode.RANGE) - action.setChecked(action.data() == self._dataInPlotMode) - self._plotToolbar.addAction(action) - group.addAction(action) - action = qt.QAction("Histogram", self) - action.setToolTip("Display the data histogram within the colormap range. A slow data processing have to be done. ") - action.setIcon(icons.getQIcon('colormap-histogram')) - action.setCheckable(True) - action.setData(_DataInPlotMode.HISTOGRAM) - action.setChecked(action.data() == self._dataInPlotMode) - self._plotToolbar.addAction(action) - group.addAction(action) - group.triggered.connect(self._displayDataInPlotModeChanged) - - self._plotBox = qt.QWidget(self) - self._plotInit() - - plotBoxLayout = qt.QHBoxLayout() - plotBoxLayout.setContentsMargins(0, 0, 0, 0) - plotBoxLayout.setSpacing(2) - plotBoxLayout.addWidget(self._plotToolbar) - plotBoxLayout.addWidget(self._plot) - plotBoxLayout.setSizeConstraint(qt.QLayout.SetMinimumSize) - self._plotBox.setLayout(plotBoxLayout) - vLayout.addWidget(self._plotBox) - - # define modal buttons - types = qt.QDialogButtonBox.Ok | qt.QDialogButtonBox.Cancel - self._buttonsModal = qt.QDialogButtonBox(parent=self) - self._buttonsModal.setStandardButtons(types) - self.layout().addWidget(self._buttonsModal) - self._buttonsModal.accepted.connect(self.accept) - self._buttonsModal.rejected.connect(self.reject) - - # define non modal buttons - types = qt.QDialogButtonBox.Close | qt.QDialogButtonBox.Reset - self._buttonsNonModal = qt.QDialogButtonBox(parent=self) - self._buttonsNonModal.setStandardButtons(types) - self.layout().addWidget(self._buttonsNonModal) - self._buttonsNonModal.button(qt.QDialogButtonBox.Close).clicked.connect(self.accept) - self._buttonsNonModal.button(qt.QDialogButtonBox.Reset).clicked.connect(self.resetColormap) - - # Set the colormap to default values - self.setColormap(Colormap(name='gray', normalization='linear', - vmin=None, vmax=None)) - - self.setModal(self.isModal()) - - vLayout.setSizeConstraint(qt.QLayout.SetMinimumSize) - self.setFixedSize(self.sizeHint()) - self._applyColormap() - - def showEvent(self, event): - self.visibleChanged.emit(True) - super(ColormapDialog, self).showEvent(event) - - def closeEvent(self, event): - if not self.isModal(): - self.accept() - super(ColormapDialog, self).closeEvent(event) - - def hideEvent(self, event): - self.visibleChanged.emit(False) - super(ColormapDialog, self).hideEvent(event) - - def close(self): - self.accept() - qt.QDialog.close(self) - - def setModal(self, modal): - assert type(modal) is bool - self._buttonsNonModal.setVisible(not modal) - self._buttonsModal.setVisible(modal) - qt.QDialog.setModal(self, modal) - - def exec_(self): - wasModal = self.isModal() - self.setModal(True) - result = super(ColormapDialog, self).exec_() - self.setModal(wasModal) - return result - - def _plotInit(self): - """Init the plot to display the range and the values""" - self._plot = PlotWidget() - self._plot.setDataMargins(yMinMargin=0.125, yMaxMargin=0.125) - self._plot.getXAxis().setLabel("Data Values") - self._plot.getYAxis().setLabel("") - self._plot.setInteractiveMode('select', zoomOnWheel=False) - self._plot.setActiveCurveHandling(False) - self._plot.setMinimumSize(qt.QSize(250, 200)) - self._plot.sigPlotSignal.connect(self._plotSlot) - - self._plotUpdate() - - def sizeHint(self): - return self.layout().minimumSize() - - def _plotUpdate(self, updateMarkers=True): - """Update the plot content - - :param bool updateMarkers: True to update markers, False otherwith - """ - colormap = self.getColormap() - if colormap is None: - if self._plotBox.isVisibleTo(self): - self._plotBox.setVisible(False) - self.setFixedSize(self.sizeHint()) - return - - if not self._plotBox.isVisibleTo(self): - self._plotBox.setVisible(True) - self.setFixedSize(self.sizeHint()) - - minData, maxData = self._minValue.getFiniteValue(), self._maxValue.getFiniteValue() - if minData > maxData: - # avoid a full collapse - minData, maxData = maxData, minData - minimum = minData - maximum = maxData - - if self._dataRange is not None: - minRange = self._dataRange[0] - maxRange = self._dataRange[2] - minimum = min(minimum, minRange) - maximum = max(maximum, maxRange) - - if self._histogramData is not None: - minHisto = self._histogramData[1][0] - maxHisto = self._histogramData[1][-1] - minimum = min(minimum, minHisto) - maximum = max(maximum, maxHisto) - - marge = abs(maximum - minimum) / 6.0 - if marge < 0.0001: - # Smaller that the QLineEdit precision - marge = 0.0001 - - minView, maxView = minimum - marge, maximum + marge - - if updateMarkers: - # Save the state in we are not moving the markers - self._initialRange = minView, maxView - elif self._initialRange is not None: - minView = min(minView, self._initialRange[0]) - maxView = max(maxView, self._initialRange[1]) - - x = [minView, minData, maxData, maxView] - y = [0, 0, 1, 1] - - self._plot.addCurve(x, y, - legend="ConstrainedCurve", - color='black', - symbol='o', - linestyle='-', - resetzoom=False) - - if updateMarkers: - minDraggable = (self._colormap().isEditable() and - not self._minValue.isAutoChecked()) - self._plot.addXMarker( - self._minValue.getFiniteValue(), - legend='Min', - text='Min', - draggable=minDraggable, - color='blue', - constraint=self._plotMinMarkerConstraint) - - maxDraggable = (self._colormap().isEditable() and - not self._maxValue.isAutoChecked()) - self._plot.addXMarker( - self._maxValue.getFiniteValue(), - legend='Max', - text='Max', - draggable=maxDraggable, - color='blue', - constraint=self._plotMaxMarkerConstraint) - - self._plot.resetZoom() - - def _plotMinMarkerConstraint(self, x, y): - """Constraint of the min marker""" - return min(x, self._maxValue.getFiniteValue()), y - - def _plotMaxMarkerConstraint(self, x, y): - """Constraint of the max marker""" - return max(x, self._minValue.getFiniteValue()), y - - def _plotSlot(self, event): - """Handle events from the plot""" - if event['event'] in ('markerMoving', 'markerMoved'): - value = float(str(event['xdata'])) - if event['label'] == 'Min': - self._minValue.setValue(value) - elif event['label'] == 'Max': - self._maxValue.setValue(value) - - # This will recreate the markers while interacting... - # It might break if marker interaction is changed - if event['event'] == 'markerMoved': - self._initialRange = None - self._updateMinMax() - else: - self._plotUpdate(updateMarkers=False) - - @staticmethod - def computeDataRange(data): - """Compute the data range as used by :meth:`setDataRange`. - - :param data: The data to process - :rtype: Tuple(float, float, float) - """ - if data is None or len(data) == 0: - return None, None, None - - dataRange = min_max(data, min_positive=True, finite=True) - if dataRange.minimum is None: - # Only non-finite data - dataRange = None - - if dataRange is not None: - min_positive = dataRange.min_positive - if min_positive is None: - min_positive = float('nan') - dataRange = dataRange.minimum, min_positive, dataRange.maximum - - if dataRange is None or len(dataRange) != 3: - qt.QMessageBox.warning( - None, "No Data", - "Image data does not contain any real value") - dataRange = 1., 1., 10. - - return dataRange - - @staticmethod - def computeHistogram(data): - """Compute the data histogram as used by :meth:`setHistogram`. - - :param data: The data to process - :rtype: Tuple(List(float),List(float) - """ - _data = data - if _data.ndim == 3: # RGB(A) images - _logger.info('Converting current image from RGB(A) to grayscale\ - in order to compute the intensity distribution') - _data = (_data[:, :, 0] * 0.299 + - _data[:, :, 1] * 0.587 + - _data[:, :, 2] * 0.114) - - if len(_data) == 0: - return None, None - - xmin, xmax = min_max(_data, min_positive=False, finite=True) - nbins = min(256, int(numpy.sqrt(_data.size))) - data_range = xmin, xmax - - # bad hack: get 256 bins in the case we have a B&W - if numpy.issubdtype(_data.dtype, numpy.integer): - if nbins > xmax - xmin: - nbins = xmax - xmin - - nbins = max(2, nbins) - _data = _data.ravel().astype(numpy.float32) - - histogram = Histogramnd(_data, n_bins=nbins, histo_range=data_range) - return histogram.histo, histogram.edges[0] - - def _getData(self): - if self._data is None: - return None - return self._data() - - def setData(self, data): - """Store the data as a weakref. - - According to the state of the dialog, the data will be used to display - the data range or the histogram of the data using :meth:`setDataRange` - and :meth:`setHistogram` - """ - oldData = self._getData() - if oldData is data: - return - - if data is None: - self._data = None - else: - self._data = weakref.ref(data, self._dataAboutToFinalize) - - self._updateDataInPlot() - - def _setDataInPlotMode(self, mode): - if self._dataInPlotMode == mode: - return - self._dataInPlotMode = mode - self._updateDataInPlot() - - def _displayDataInPlotModeChanged(self, action): - mode = action.data() - self._setDataInPlotMode(mode) - - def _updateDataInPlot(self): - data = self._getData() - if data is None: - self.setDataRange() - self.setHistogram() - return - - if data.size == 0: - # One or more dimensions are equal to 0 - self.setHistogram() - self.setDataRange() - return - - mode = self._dataInPlotMode - - if mode == _DataInPlotMode.NONE: - self.setHistogram() - self.setDataRange() - elif mode == _DataInPlotMode.RANGE: - result = self.computeDataRange(data) - self.setHistogram() - self.setDataRange(*result) - elif mode == _DataInPlotMode.HISTOGRAM: - # The histogram should be done in a worker thread - result = self.computeHistogram(data) - self.setHistogram(*result) - self.setDataRange() - - def _colormapAboutToFinalize(self, weakrefColormap): - """Callback when the data weakref is about to be finalized.""" - if self._colormap is weakrefColormap: - self.setColormap(None) - - def _dataAboutToFinalize(self, weakrefData): - """Callback when the data weakref is about to be finalized.""" - if self._data is weakrefData: - self.setData(None) - - def getHistogram(self): - """Returns the counts and bin edges of the displayed histogram. - - :return: (hist, bin_edges) - :rtype: 2-tuple of numpy arrays""" - if self._histogramData is None: - return None - else: - bins, counts = self._histogramData - return numpy.array(bins, copy=True), numpy.array(counts, copy=True) - - def setHistogram(self, hist=None, bin_edges=None): - """Set the histogram to display. - - This update the data range with the bounds of the bins. - - :param hist: array-like of counts or None to hide histogram - :param bin_edges: array-like of bins edges or None to hide histogram - """ - if hist is None or bin_edges is None: - self._histogramData = None - self._plot.remove(legend='Histogram', kind='histogram') - else: - hist = numpy.array(hist, copy=True) - bin_edges = numpy.array(bin_edges, copy=True) - self._histogramData = hist, bin_edges - norm_hist = hist / max(hist) - self._plot.addHistogram(norm_hist, - bin_edges, - legend="Histogram", - color='gray', - align='center', - fill=True) - self._updateMinMaxData() - - def getColormap(self): - """Return the colormap description. - - :rtype: ~silx.gui.colors.Colormap - """ - if self._colormap is None: - return None - return self._colormap() - - def resetColormap(self): - """ - Reset the colormap state before modification. - - ..note :: the colormap reference state is the state when set or the - state when validated - """ - colormap = self.getColormap() - if colormap is not None and self._colormapStoredState is not None: - if self._colormap()._toDict() != self._colormapStoredState: - self._ignoreColormapChange = True - colormap._setFromDict(self._colormapStoredState) - self._ignoreColormapChange = False - self._applyColormap() - - def setDataRange(self, minimum=None, positiveMin=None, maximum=None): - """Set the range of data to use for the range of the histogram area. - - :param float minimum: The minimum of the data - :param float positiveMin: The positive minimum of the data - :param float maximum: The maximum of the data - """ - if minimum is None or positiveMin is None or maximum is None: - self._dataRange = None - self._plot.remove(legend='Range', kind='histogram') - else: - hist = numpy.array([1]) - bin_edges = numpy.array([minimum, maximum]) - self._plot.addHistogram(hist, - bin_edges, - legend="Range", - color='gray', - align='center', - fill=True) - self._dataRange = minimum, positiveMin, maximum - self._updateMinMaxData() - - def _updateMinMaxData(self): - """Update the min and max of the data according to the data range and - the histogram preset.""" - colormap = self.getColormap() - - minimum = float("+inf") - maximum = float("-inf") - - if colormap is not None and colormap.getNormalization() == colormap.LOGARITHM: - # find a range in the positive part of the data - if self._dataRange is not None: - minimum = min(minimum, self._dataRange[1]) - maximum = max(maximum, self._dataRange[2]) - if self._histogramData is not None: - positives = list(filter(lambda x: x > 0, self._histogramData[1])) - if len(positives) > 0: - minimum = min(minimum, positives[0]) - maximum = max(maximum, positives[-1]) - else: - if self._dataRange is not None: - minimum = min(minimum, self._dataRange[0]) - maximum = max(maximum, self._dataRange[2]) - if self._histogramData is not None: - minimum = min(minimum, self._histogramData[1][0]) - maximum = max(maximum, self._histogramData[1][-1]) - - if not numpy.isfinite(minimum): - minimum = None - if not numpy.isfinite(maximum): - maximum = None - - self._minValue.setDataValue(minimum) - self._maxValue.setDataValue(maximum) - self._plotUpdate() - - def accept(self): - self.storeCurrentState() - qt.QDialog.accept(self) - - def storeCurrentState(self): - """ - save the current value sof the colormap if the user want to undo is - modifications - """ - colormap = self.getColormap() - if colormap is not None: - self._colormapStoredState = colormap._toDict() - else: - self._colormapStoredState = None - - def reject(self): - self.resetColormap() - qt.QDialog.reject(self) - - def setColormap(self, colormap): - """Set the colormap description - - :param ~silx.gui.colors.Colormap colormap: the colormap to edit - """ - assert colormap is None or isinstance(colormap, Colormap) - if self._ignoreColormapChange is True: - return - - oldColormap = self.getColormap() - if oldColormap is colormap: - return - if oldColormap is not None: - oldColormap.sigChanged.disconnect(self._applyColormap) - - if colormap is not None: - colormap.sigChanged.connect(self._applyColormap) - colormap = weakref.ref(colormap, self._colormapAboutToFinalize) - - self._colormap = colormap - self.storeCurrentState() - self._updateResetButton() - self._applyColormap() - - def _updateResetButton(self): - resetButton = self._buttonsNonModal.button(qt.QDialogButtonBox.Reset) - rStateEnabled = False - colormap = self.getColormap() - if colormap is not None and colormap.isEditable(): - # can reset only in the case the colormap changed - rStateEnabled = colormap._toDict() != self._colormapStoredState - resetButton.setEnabled(rStateEnabled) - - def _applyColormap(self): - self._updateResetButton() - if self._ignoreColormapChange is True: - return - - colormap = self.getColormap() - if colormap is None: - self._comboBoxColormap.setEnabled(False) - self._normButtonLinear.setEnabled(False) - self._normButtonLog.setEnabled(False) - self._minValue.setEnabled(False) - self._maxValue.setEnabled(False) - else: - self._ignoreColormapChange = True - - if colormap.getName() is not None: - name = colormap.getName() - self._comboBoxColormap.setCurrentName(name) - self._comboBoxColormap.setEnabled(self._colormap().isEditable()) - - assert colormap.getNormalization() in Colormap.NORMALIZATIONS - self._normButtonLinear.setChecked( - colormap.getNormalization() == Colormap.LINEAR) - self._normButtonLog.setChecked( - colormap.getNormalization() == Colormap.LOGARITHM) - vmin = colormap.getVMin() - vmax = colormap.getVMax() - dataRange = colormap.getColormapRange() - self._normButtonLinear.setEnabled(self._colormap().isEditable()) - self._normButtonLog.setEnabled(self._colormap().isEditable()) - self._minValue.setValue(vmin or dataRange[0], isAuto=vmin is None) - self._maxValue.setValue(vmax or dataRange[1], isAuto=vmax is None) - self._minValue.setEnabled(self._colormap().isEditable()) - self._maxValue.setEnabled(self._colormap().isEditable()) - self._ignoreColormapChange = False - - self._plotUpdate() - - def _updateMinMax(self): - if self._ignoreColormapChange is True: - return - - vmin = self._minValue.getFiniteValue() - vmax = self._maxValue.getFiniteValue() - if vmax is not None and vmin is not None and vmax < vmin: - # If only one autoscale is checked constraints are too strong - # We have to edit a user value anyway it is not requested - # TODO: It would be better IMO to disable the auto checkbox before - # this case occur (valls) - cmin = self._minValue.isAutoChecked() - cmax = self._maxValue.isAutoChecked() - if cmin is False: - self._minValue.setFiniteValue(vmax) - if cmax is False: - self._maxValue.setFiniteValue(vmin) - - vmin = self._minValue.getValue() - vmax = self._maxValue.getValue() - self._ignoreColormapChange = True - colormap = self._colormap() - if colormap is not None: - colormap.setVRange(vmin, vmax) - self._ignoreColormapChange = False - self._plotUpdate() - self._updateResetButton() - - def _updateName(self): - if self._ignoreColormapChange is True: - return - - if self._colormap(): - self._ignoreColormapChange = True - self._colormap().setName( - self._comboBoxColormap.getCurrentName()) - self._ignoreColormapChange = False - - def _updateLinearNorm(self, isNormLinear): - if self._ignoreColormapChange is True: - return - - if self._colormap(): - self._ignoreColormapChange = True - norm = Colormap.LINEAR if isNormLinear else Colormap.LOGARITHM - self._colormap().setNormalization(norm) - self._ignoreColormapChange = False - - def _minMaxTextEdited(self, text): - """Handle _minValue and _maxValue textEdited signal""" - self._minMaxWasEdited = True - - def _minEditingFinished(self): - """Handle _minValue editingFinished signal - - Together with :meth:`_minMaxTextEdited`, this avoids to notify - colormap change when the min and max value where not edited. - """ - if self._minMaxWasEdited: - self._minMaxWasEdited = False - - # Fix start value - if (self._maxValue.getValue() is not None and - self._minValue.getValue() > self._maxValue.getValue()): - self._minValue.setValue(self._maxValue.getValue()) - self._updateMinMax() - - def _maxEditingFinished(self): - """Handle _maxValue editingFinished signal - - Together with :meth:`_minMaxTextEdited`, this avoids to notify - colormap change when the min and max value where not edited. - """ - if self._minMaxWasEdited: - self._minMaxWasEdited = False - - # Fix end value - if (self._minValue.getValue() is not None and - self._minValue.getValue() > self._maxValue.getValue()): - self._maxValue.setValue(self._minValue.getValue()) - self._updateMinMax() - - def keyPressEvent(self, event): - """Override key handling. - - It disables leaving the dialog when editing a text field. - """ - if event.key() == qt.Qt.Key_Enter and (self._minValue.hasFocus() or - self._maxValue.hasFocus()): - # Bypass QDialog keyPressEvent - # To avoid leaving the dialog when pressing enter on a text field - super(qt.QDialog, self).keyPressEvent(event) - else: - # Use QDialog keyPressEvent - super(ColormapDialog, self).keyPressEvent(event) - - def _activeLogNorm(self, isLog): - if self._ignoreColormapChange is True: - return - if self._colormap(): - self._ignoreColormapChange = True - norm = Colormap.LOGARITHM if isLog is True else Colormap.LINEAR - self._colormap().setNormalization(norm) - self._ignoreColormapChange = False - self._updateMinMaxData() diff --git a/silx/gui/dialog/DataFileDialog.py b/silx/gui/dialog/DataFileDialog.py deleted file mode 100644 index 7ff1258..0000000 --- a/silx/gui/dialog/DataFileDialog.py +++ /dev/null @@ -1,342 +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. -# -# ###########################################################################*/ -""" -This module contains an :class:`DataFileDialog`. -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "14/02/2018" - -import logging -from silx.gui import qt -from silx.gui.hdf5.Hdf5Formatter import Hdf5Formatter -import silx.io -from .AbstractDataFileDialog import AbstractDataFileDialog -from silx.third_party import enum -try: - import fabio -except ImportError: - fabio = None - - -_logger = logging.getLogger(__name__) - - -class _DataPreview(qt.QWidget): - """Provide a preview of the selected image""" - - def __init__(self, parent=None): - super(_DataPreview, self).__init__(parent) - - self.__formatter = Hdf5Formatter(self) - self.__data = None - self.__info = qt.QTableView(self) - self.__model = qt.QStandardItemModel(self) - self.__info.setModel(self.__model) - self.__info.horizontalHeader().hide() - self.__info.horizontalHeader().setStretchLastSection(True) - layout = qt.QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.__info) - self.setLayout(layout) - - def colormap(self): - return None - - def setColormap(self, colormap): - # Ignored - pass - - def sizeHint(self): - return qt.QSize(200, 200) - - def setData(self, data, fromDataSelector=False): - self.__info.setEnabled(data is not None) - if data is None: - self.__model.clear() - else: - self.__model.clear() - - if silx.io.is_dataset(data): - kind = "Dataset" - elif silx.io.is_group(data): - kind = "Group" - elif silx.io.is_file(data): - kind = "File" - else: - kind = "Unknown" - - headers = [] - - basename = data.name.split("/")[-1] - if basename == "": - basename = "/" - headers.append("Basename") - self.__model.appendRow([qt.QStandardItem(basename)]) - headers.append("Kind") - self.__model.appendRow([qt.QStandardItem(kind)]) - if hasattr(data, "dtype"): - headers.append("Type") - text = self.__formatter.humanReadableType(data) - self.__model.appendRow([qt.QStandardItem(text)]) - if hasattr(data, "shape"): - headers.append("Shape") - text = self.__formatter.humanReadableShape(data) - self.__model.appendRow([qt.QStandardItem(text)]) - if hasattr(data, "attrs") and "NX_class" in data.attrs: - headers.append("NX_class") - value = data.attrs["NX_class"] - formatter = self.__formatter.textFormatter() - old = formatter.useQuoteForText() - formatter.setUseQuoteForText(False) - text = self.__formatter.textFormatter().toString(value) - formatter.setUseQuoteForText(old) - self.__model.appendRow([qt.QStandardItem(text)]) - self.__model.setVerticalHeaderLabels(headers) - self.__data = data - - def __imageItem(self): - image = self.__plot.getImage("data") - return image - - def data(self): - if self.__data is not None: - if hasattr(self.__data, "name"): - # in case of HDF5 - if self.__data.name is None: - # The dataset was closed - self.__data = None - return self.__data - - def clear(self): - self.__data = None - self.__info.setText("") - - -class DataFileDialog(AbstractDataFileDialog): - """The `DataFileDialog` class provides a dialog that allow users to select - any datasets or groups from an HDF5-like file. - - The `DataFileDialog` class enables a user to traverse the file system in - order to select an HDF5-like file. Then to traverse the file to select an - HDF5 node. - - .. image:: img/datafiledialog.png - - The selected data is any kind of group or dataset. It can be restricted - to only existing datasets or only existing groups using - :meth:`setFilterMode`. A callback can be defining using - :meth:`setFilterCallback` to filter even more data which can be returned. - - Filtering data which can be returned by a `DataFileDialog` can be done like - that: - - .. code-block:: python - - # Force to return only a dataset - dialog = DataFileDialog() - dialog.setFilterMode(DataFileDialog.FilterMode.ExistingDataset) - - .. code-block:: python - - def customFilter(obj): - if "NX_class" in obj.attrs: - return obj.attrs["NX_class"] in [b"NXentry", u"NXentry"] - return False - - # Force to return an NX entry - dialog = DataFileDialog() - # 1st, filter out everything which is not a group - dialog.setFilterMode(DataFileDialog.FilterMode.ExistingGroup) - # 2nd, check what NX_class is an NXentry - dialog.setFilterCallback(customFilter) - - Executing a `DataFileDialog` can be done like that: - - .. code-block:: python - - dialog = DataFileDialog() - result = dialog.exec_() - if result: - print("Selection:") - print(dialog.selectedFile()) - print(dialog.selectedUrl()) - else: - print("Nothing selected") - - If the selection is a dataset you can access to the data using - :meth:`selectedData`. - - If the selection is a group or if you want to read the selected object on - your own you can use the `silx.io` API. - - .. code-block:: python - - url = dialog.selectedUrl() - with silx.io.open(url) as data: - pass - - Or by loading the file first - - .. code-block:: python - - url = dialog.selectedDataUrl() - with silx.io.open(url.file_path()) as h5: - data = h5[url.data_path()] - - Or by using `h5py` library - - .. code-block:: python - - url = dialog.selectedDataUrl() - with h5py.File(url.file_path()) as h5: - data = h5[url.data_path()] - """ - - class FilterMode(enum.Enum): - """This enum is used to indicate what the user may select in the - dialog; i.e. what the dialog will return if the user clicks OK.""" - - AnyNode = 0 - """Any existing node from an HDF5-like file.""" - ExistingDataset = 1 - """An existing HDF5-like dataset.""" - ExistingGroup = 2 - """An existing HDF5-like group. A file root is a group.""" - - def __init__(self, parent=None): - AbstractDataFileDialog.__init__(self, parent=parent) - self.__filter = DataFileDialog.FilterMode.AnyNode - self.__filterCallback = None - - def selectedData(self): - """Returns the selected data by using the :meth:`silx.io.get_data` - API with the selected URL provided by the dialog. - - If the URL identify a group of a file it will raise an exception. For - group or file you have to use on your own the API :meth:`silx.io.open`. - - :rtype: numpy.ndarray - :raise ValueError: If the URL do not link to a dataset - """ - url = self.selectedUrl() - return silx.io.get_data(url) - - def _createPreviewWidget(self, parent): - previewWidget = _DataPreview(parent) - previewWidget.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding) - return previewWidget - - def _createSelectorWidget(self, parent): - # There is no selector - return None - - def _createPreviewToolbar(self, parent, dataPreviewWidget, dataSelectorWidget): - # There is no toolbar - return None - - def _isDataSupportable(self, data): - """Check if the selected data can be supported at one point. - - If true, the data selector will be checked and it will update the data - preview. Else the selecting is disabled. - - :rtype: bool - """ - # Everything is supported - return True - - def _isFabioFilesSupported(self): - # Everything is supported - return False - - def _isDataSupported(self, data): - """Check if the data can be returned by the dialog. - - If true, this data can be returned by the dialog and the open button - will be enabled. If false the button will be disabled. - - :rtype: bool - """ - if self.__filter == DataFileDialog.FilterMode.AnyNode: - accepted = True - elif self.__filter == DataFileDialog.FilterMode.ExistingDataset: - accepted = silx.io.is_dataset(data) - elif self.__filter == DataFileDialog.FilterMode.ExistingGroup: - accepted = silx.io.is_group(data) - else: - raise ValueError("Filter %s is not supported" % self.__filter) - if not accepted: - return False - if self.__filterCallback is not None: - try: - return self.__filterCallback(data) - except Exception: - _logger.error("Error while executing custom callback", exc_info=True) - return False - return True - - def setFilterCallback(self, callback): - """Set the filter callback. This filter is applied only if the filter - mode (:meth:`filterMode`) first accepts the selected data. - - It is not supposed to be set while the dialog is being used. - - :param callable callback: Define a custom function returning a boolean - and taking as argument an h5-like node. If the function returns true - the dialog can return the associated URL. - """ - self.__filterCallback = callback - - def setFilterMode(self, mode): - """Set the filter mode. - - It is not supposed to be set while the dialog is being used. - - :param DataFileDialog.FilterMode mode: The new filter. - """ - self.__filter = mode - - def fileMode(self): - """Returns the filter mode. - - :rtype: DataFileDialog.FilterMode - """ - return self.__filter - - def _displayedDataInfo(self, dataBeforeSelection, dataAfterSelection): - """Returns the text displayed under the data preview. - - This zone is used to display error in case or problem of data selection - or problems with IO. - - :param numpy.ndarray dataAfterSelection: Data as it is after the - selection widget (basically the data from the preview widget) - :param numpy.ndarray dataAfterSelection: Data as it is before the - selection widget (basically the data from the browsing widget) - :rtype: bool - """ - return u"" diff --git a/silx/gui/dialog/DatasetDialog.py b/silx/gui/dialog/DatasetDialog.py deleted file mode 100644 index 87fc89d..0000000 --- a/silx/gui/dialog/DatasetDialog.py +++ /dev/null @@ -1,122 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 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. -# -# ###########################################################################*/ -"""This module provides a dialog widget to select a HDF5 dataset in a -tree. - -.. autoclass:: DatasetDialog - :members: addFile, addGroup, getSelectedDataUrl, setMode - -""" -from .GroupDialog import _Hdf5ItemSelectionDialog -import silx.io -from silx.io.url import DataUrl - - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "05/09/2018" - - -class DatasetDialog(_Hdf5ItemSelectionDialog): - """This :class:`QDialog` uses a :class:`silx.gui.hdf5.Hdf5TreeView` to - provide a HDF5 dataset selection dialog. - - The information identifying the selected node is provided as a - :class:`silx.io.url.DataUrl`. - - Example: - - .. code-block:: python - - dialog = DatasetDialog() - dialog.addFile(filepath1) - dialog.addFile(filepath2) - - if dialog.exec_(): - print("File path: %s" % dialog.getSelectedDataUrl().file_path()) - print("HDF5 dataset path : %s " % dialog.getSelectedDataUrl().data_path()) - else: - print("Operation cancelled :(") - - """ - def __init__(self, parent=None): - _Hdf5ItemSelectionDialog.__init__(self, parent) - - # customization for groups - self.setWindowTitle("HDF5 dataset selection") - - self._header.setSections([self._model.NAME_COLUMN, - self._model.NODE_COLUMN, - self._model.LINK_COLUMN, - self._model.TYPE_COLUMN, - self._model.SHAPE_COLUMN]) - self._selectDatasetStatusText = "Select a dataset or type a new dataset name" - - def setMode(self, mode): - """Set dialog mode DatasetDialog.SaveMode or DatasetDialog.LoadMode - - :param mode: DatasetDialog.SaveMode or DatasetDialog.LoadMode - """ - _Hdf5ItemSelectionDialog.setMode(self, mode) - if mode == DatasetDialog.SaveMode: - self._selectDatasetStatusText = "Select a dataset or type a new dataset name" - elif mode == DatasetDialog.LoadMode: - self._selectDatasetStatusText = "Select a dataset" - - def _onActivation(self, idx): - # double-click or enter press: filter for datasets - nodes = list(self._tree.selectedH5Nodes()) - node = nodes[0] - if silx.io.is_dataset(node.h5py_object): - self.accept() - - def _updateUrl(self): - # overloaded to filter for datasets - nodes = list(self._tree.selectedH5Nodes()) - newDatasetName = self._lineEditNewItem.text() - isDatasetSelected = False - if nodes: - node = nodes[0] - if silx.io.is_dataset(node.h5py_object): - data_path = node.local_name - isDatasetSelected = True - elif silx.io.is_group(node.h5py_object): - data_path = node.local_name - if newDatasetName.lstrip("/"): - if not data_path.endswith("/"): - data_path += "/" - data_path += newDatasetName.lstrip("/") - isDatasetSelected = True - - if isDatasetSelected: - self._selectedUrl = DataUrl(file_path=node.local_filename, - data_path=data_path) - self._okButton.setEnabled(True) - self._labelSelection.setText( - self._selectedUrl.path()) - else: - self._selectedUrl = None - self._okButton.setEnabled(False) - self._labelSelection.setText(self._selectDatasetStatusText) diff --git a/silx/gui/dialog/FileTypeComboBox.py b/silx/gui/dialog/FileTypeComboBox.py deleted file mode 100644 index 07b11cf..0000000 --- a/silx/gui/dialog/FileTypeComboBox.py +++ /dev/null @@ -1,213 +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. -# -# ###########################################################################*/ -""" -This module contains utilitaries used by other dialog modules. -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "06/02/2018" - -try: - import fabio -except ImportError: - fabio = None -import silx.io -from silx.gui import qt - - -class Codec(object): - - def __init__(self, any_fabio=False, any_silx=False, fabio_codec=None, auto=False): - self.__any_fabio = any_fabio - self.__any_silx = any_silx - self.fabio_codec = fabio_codec - self.__auto = auto - - def is_autodetect(self): - return self.__auto - - def is_fabio_codec(self): - return self.__any_fabio or self.fabio_codec is not None - - def is_silx_codec(self): - return self.__any_silx - - -class FileTypeComboBox(qt.QComboBox): - """ - A combobox providing all image file formats supported by fabio and silx. - - It provides access for each fabio codecs individually. - """ - - EXTENSIONS_ROLE = qt.Qt.UserRole + 1 - - CODEC_ROLE = qt.Qt.UserRole + 2 - - INDENTATION = u"\u2022 " - - def __init__(self, parent=None): - qt.QComboBox.__init__(self, parent) - self.__fabioUrlSupported = True - self.__initItems() - - def setFabioUrlSupproted(self, isSupported): - if self.__fabioUrlSupported == isSupported: - return - self.__fabioUrlSupported = isSupported - self.__initItems() - - def __initItems(self): - self.clear() - if fabio is not None and self.__fabioUrlSupported: - self.__insertFabioFormats() - self.__insertSilxFormats() - self.__insertAllSupported() - self.__insertAnyFiles() - - def __insertAnyFiles(self): - index = self.count() - self.addItem("All files (*)") - self.setItemData(index, ["*"], role=self.EXTENSIONS_ROLE) - self.setItemData(index, Codec(auto=True), role=self.CODEC_ROLE) - - def __insertAllSupported(self): - allExtensions = set([]) - for index in range(self.count()): - ext = self.itemExtensions(index) - allExtensions.update(ext) - allExtensions = allExtensions - set("*") - list(sorted(list(allExtensions))) - index = 0 - self.insertItem(index, "All supported files") - self.setItemData(index, allExtensions, role=self.EXTENSIONS_ROLE) - self.setItemData(index, Codec(auto=True), role=self.CODEC_ROLE) - - def __insertSilxFormats(self): - formats = silx.io.supported_extensions() - - extensions = [] - allExtensions = set([]) - - for description, ext in formats.items(): - allExtensions.update(ext) - if ext == []: - ext = ["*"] - extensions.append((description, ext, "silx")) - extensions = list(sorted(extensions)) - - allExtensions = list(sorted(list(allExtensions))) - index = self.count() - self.addItem("All supported files, using Silx") - self.setItemData(index, allExtensions, role=self.EXTENSIONS_ROLE) - self.setItemData(index, Codec(any_silx=True), role=self.CODEC_ROLE) - - for e in extensions: - index = self.count() - if len(e[1]) < 10: - self.addItem("%s%s (%s)" % (self.INDENTATION, e[0], " ".join(e[1]))) - else: - self.addItem("%s%s" % (self.INDENTATION, e[0])) - codec = Codec(any_silx=True) - self.setItemData(index, e[1], role=self.EXTENSIONS_ROLE) - self.setItemData(index, codec, role=self.CODEC_ROLE) - - def __insertFabioFormats(self): - formats = fabio.fabioformats.get_classes(reader=True) - - extensions = [] - allExtensions = set([]) - - for reader in formats: - if not hasattr(reader, "DESCRIPTION"): - continue - if not hasattr(reader, "DEFAULT_EXTENSIONS"): - continue - - ext = reader.DEFAULT_EXTENSIONS - ext = ["*.%s" % e for e in ext] - allExtensions.update(ext) - if ext == []: - ext = ["*"] - extensions.append((reader.DESCRIPTION, ext, reader.codec_name())) - extensions = list(sorted(extensions)) - - allExtensions = list(sorted(list(allExtensions))) - index = self.count() - self.addItem("All supported files, using Fabio") - self.setItemData(index, allExtensions, role=self.EXTENSIONS_ROLE) - self.setItemData(index, Codec(any_fabio=True), role=self.CODEC_ROLE) - - for e in extensions: - index = self.count() - if len(e[1]) < 10: - self.addItem("%s%s (%s)" % (self.INDENTATION, e[0], " ".join(e[1]))) - else: - self.addItem(e[0]) - codec = Codec(fabio_codec=e[2]) - self.setItemData(index, e[1], role=self.EXTENSIONS_ROLE) - self.setItemData(index, codec, role=self.CODEC_ROLE) - - def itemExtensions(self, index): - """Returns the extensions associated to an index.""" - result = self.itemData(index, self.EXTENSIONS_ROLE) - if result is None: - result = None - return result - - def currentExtensions(self): - """Returns the current selected extensions.""" - index = self.currentIndex() - return self.itemExtensions(index) - - def indexFromCodec(self, codecName): - for i in range(self.count()): - codec = self.itemCodec(i) - if codecName == "auto": - if codec.is_autodetect(): - return i - elif codecName == "silx": - if codec.is_silx_codec(): - return i - elif codecName == "fabio": - if codec.is_fabio_codec() and codec.fabio_codec is None: - return i - elif codecName == codec.fabio_codec: - return i - return -1 - - def itemCodec(self, index): - """Returns the codec associated to an index.""" - result = self.itemData(index, self.CODEC_ROLE) - if result is None: - result = None - return result - - def currentCodec(self): - """Returns the current selected codec. None if nothing selected - or if the item is not a codec""" - index = self.currentIndex() - return self.itemCodec(index) diff --git a/silx/gui/dialog/GroupDialog.py b/silx/gui/dialog/GroupDialog.py deleted file mode 100644 index 217a03c..0000000 --- a/silx/gui/dialog/GroupDialog.py +++ /dev/null @@ -1,230 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 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. -# -# ###########################################################################*/ -"""This module provides a dialog widget to select a HDF5 group in a -tree. - -.. autoclass:: GroupDialog - :members: addFile, addGroup, getSelectedDataUrl, setMode - -""" -from silx.gui import qt -from silx.gui.hdf5.Hdf5TreeView import Hdf5TreeView -import silx.io -from silx.io.url import DataUrl - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "22/03/2018" - - -class _Hdf5ItemSelectionDialog(qt.QDialog): - SaveMode = 1 - """Mode used to set the HDF5 item selection dialog to *save* mode. - This adds a text field to type in a new item name.""" - - LoadMode = 2 - """Mode used to set the HDF5 item selection dialog to *load* mode. - Only existing items of the HDF5 file can be selected in this mode.""" - - def __init__(self, parent=None): - qt.QDialog.__init__(self, parent) - self.setWindowTitle("HDF5 item selection") - - self._tree = Hdf5TreeView(self) - self._tree.setSelectionMode(qt.QAbstractItemView.SingleSelection) - self._tree.activated.connect(self._onActivation) - self._tree.selectionModel().selectionChanged.connect( - self._onSelectionChange) - - self._model = self._tree.findHdf5TreeModel() - - self._header = self._tree.header() - - self._newItemWidget = qt.QWidget(self) - newItemLayout = qt.QVBoxLayout(self._newItemWidget) - self._labelNewItem = qt.QLabel(self._newItemWidget) - self._labelNewItem.setText("Create new item in selected group (optional):") - self._lineEditNewItem = qt.QLineEdit(self._newItemWidget) - self._lineEditNewItem.setToolTip( - "Specify the name of a new item " - "to be created in the selected group.") - self._lineEditNewItem.textChanged.connect( - self._onNewItemNameChange) - newItemLayout.addWidget(self._labelNewItem) - newItemLayout.addWidget(self._lineEditNewItem) - - _labelSelectionTitle = qt.QLabel(self) - _labelSelectionTitle.setText("Current selection") - self._labelSelection = qt.QLabel(self) - self._labelSelection.setStyleSheet("color: gray") - self._labelSelection.setWordWrap(True) - self._labelSelection.setText("Select an item") - - buttonBox = qt.QDialogButtonBox() - self._okButton = buttonBox.addButton(qt.QDialogButtonBox.Ok) - self._okButton.setEnabled(False) - buttonBox.addButton(qt.QDialogButtonBox.Cancel) - - buttonBox.accepted.connect(self.accept) - buttonBox.rejected.connect(self.reject) - - vlayout = qt.QVBoxLayout(self) - vlayout.addWidget(self._tree) - vlayout.addWidget(self._newItemWidget) - vlayout.addWidget(_labelSelectionTitle) - vlayout.addWidget(self._labelSelection) - vlayout.addWidget(buttonBox) - self.setLayout(vlayout) - - self.setMinimumWidth(400) - - self._selectedUrl = None - - def _onSelectionChange(self, old, new): - self._updateUrl() - - def _onNewItemNameChange(self, text): - self._updateUrl() - - def _onActivation(self, idx): - # double-click or enter press - self.accept() - - def setMode(self, mode): - """Set dialog mode DatasetDialog.SaveMode or DatasetDialog.LoadMode - - :param mode: DatasetDialog.SaveMode or DatasetDialog.LoadMode - """ - if mode == self.LoadMode: - # hide "Create new item" field - self._lineEditNewItem.clear() - self._newItemWidget.hide() - elif mode == self.SaveMode: - self._newItemWidget.show() - else: - raise ValueError("Invalid DatasetDialog mode %s" % mode) - - def addFile(self, path): - """Add a HDF5 file to the tree. - All groups it contains will be selectable in the dialog. - - :param str path: File path - """ - self._model.insertFile(path) - - def addGroup(self, group): - """Add a HDF5 group to the tree. This group and all its subgroups - will be selectable in the dialog. - - :param h5py.Group group: HDF5 group - """ - self._model.insertH5pyObject(group) - - def _updateUrl(self): - nodes = list(self._tree.selectedH5Nodes()) - subgroupName = self._lineEditNewItem.text() - if nodes: - node = nodes[0] - data_path = node.local_name - if subgroupName.lstrip("/"): - if not data_path.endswith("/"): - data_path += "/" - data_path += subgroupName.lstrip("/") - self._selectedUrl = DataUrl(file_path=node.local_filename, - data_path=data_path) - self._okButton.setEnabled(True) - self._labelSelection.setText( - self._selectedUrl.path()) - - def getSelectedDataUrl(self): - """Return a :class:`DataUrl` with a file path and a data path. - Return None if the dialog was cancelled. - - :return: :class:`silx.io.url.DataUrl` object pointing to the - selected HDF5 item. - """ - return self._selectedUrl - - -class GroupDialog(_Hdf5ItemSelectionDialog): - """This :class:`QDialog` uses a :class:`silx.gui.hdf5.Hdf5TreeView` to - provide a HDF5 group selection dialog. - - The information identifying the selected node is provided as a - :class:`silx.io.url.DataUrl`. - - Example: - - .. code-block:: python - - dialog = GroupDialog() - dialog.addFile(filepath1) - dialog.addFile(filepath2) - - if dialog.exec_(): - print("File path: %s" % dialog.getSelectedDataUrl().file_path()) - print("HDF5 group path : %s " % dialog.getSelectedDataUrl().data_path()) - else: - print("Operation cancelled :(") - - """ - def __init__(self, parent=None): - _Hdf5ItemSelectionDialog.__init__(self, parent) - - # customization for groups - self.setWindowTitle("HDF5 group selection") - - self._header.setSections([self._model.NAME_COLUMN, - self._model.NODE_COLUMN, - self._model.LINK_COLUMN]) - - def _onActivation(self, idx): - # double-click or enter press: filter for groups - nodes = list(self._tree.selectedH5Nodes()) - node = nodes[0] - if silx.io.is_group(node.h5py_object): - self.accept() - - def _updateUrl(self): - # overloaded to filter for groups - nodes = list(self._tree.selectedH5Nodes()) - subgroupName = self._lineEditNewItem.text() - if nodes: - node = nodes[0] - if silx.io.is_group(node.h5py_object): - data_path = node.local_name - if subgroupName.lstrip("/"): - if not data_path.endswith("/"): - data_path += "/" - data_path += subgroupName.lstrip("/") - self._selectedUrl = DataUrl(file_path=node.local_filename, - data_path=data_path) - self._okButton.setEnabled(True) - self._labelSelection.setText( - self._selectedUrl.path()) - else: - self._selectedUrl = None - self._okButton.setEnabled(False) - self._labelSelection.setText("Select a group") diff --git a/silx/gui/dialog/ImageFileDialog.py b/silx/gui/dialog/ImageFileDialog.py deleted file mode 100644 index c324071..0000000 --- a/silx/gui/dialog/ImageFileDialog.py +++ /dev/null @@ -1,338 +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. -# -# ###########################################################################*/ -""" -This module contains an :class:`ImageFileDialog`. -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "12/02/2018" - -import logging -from silx.gui.plot import actions -from silx.gui import qt -from silx.gui.plot.PlotWidget import PlotWidget -from .AbstractDataFileDialog import AbstractDataFileDialog -import silx.io -try: - import fabio -except ImportError: - fabio = None - - -_logger = logging.getLogger(__name__) - - -class _ImageSelection(qt.QWidget): - """Provide a widget allowing to select an image from an hypercube by - selecting a slice.""" - - selectionChanged = qt.Signal() - """Emitted when the selection change.""" - - def __init__(self, parent=None): - qt.QWidget.__init__(self, parent) - self.__shape = None - self.__axis = [] - layout = qt.QVBoxLayout() - self.setLayout(layout) - - def hasVisibleSelectors(self): - return self.__visibleSliders > 0 - - def isUsed(self): - if self.__shape is None: - return None - return len(self.__shape) > 2 - - def getSelectedData(self, data): - slicing = self.slicing() - image = data[slicing] - return image - - def setData(self, data): - shape = data.shape - if self.__shape is not None: - # clean up - for widget in self.__axis: - self.layout().removeWidget(widget) - widget.deleteLater() - self.__axis = [] - - self.__shape = shape - self.__visibleSliders = 0 - - if shape is not None: - # create expected axes - for index in range(len(shape) - 2): - axis = qt.QSlider(self) - axis.setMinimum(0) - axis.setMaximum(shape[index] - 1) - axis.setOrientation(qt.Qt.Horizontal) - if shape[index] == 1: - axis.setVisible(False) - else: - self.__visibleSliders += 1 - - axis.valueChanged.connect(self.__axisValueChanged) - self.layout().addWidget(axis) - self.__axis.append(axis) - - self.selectionChanged.emit() - - def __axisValueChanged(self): - self.selectionChanged.emit() - - def slicing(self): - slicing = [] - for axes in self.__axis: - slicing.append(axes.value()) - return tuple(slicing) - - def setSlicing(self, slicing): - for i, value in enumerate(slicing): - if i > len(self.__axis): - break - self.__axis[i].setValue(value) - - -class _ImagePreview(qt.QWidget): - """Provide a preview of the selected image""" - - def __init__(self, parent=None): - super(_ImagePreview, self).__init__(parent) - - self.__data = None - self.__plot = PlotWidget(self) - self.__plot.setAxesDisplayed(False) - self.__plot.setKeepDataAspectRatio(True) - layout = qt.QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.__plot) - self.setLayout(layout) - - def resizeEvent(self, event): - self.__updateConstraints() - return qt.QWidget.resizeEvent(self, event) - - def sizeHint(self): - return qt.QSize(200, 200) - - def plot(self): - return self.__plot - - def setData(self, data, fromDataSelector=False): - if data is None: - self.clear() - return - - resetzoom = not fromDataSelector - previousImage = self.data() - if previousImage is not None and data.shape != previousImage.shape: - resetzoom = True - - self.__plot.addImage(legend="data", data=data, resetzoom=resetzoom) - self.__data = data - self.__updateConstraints() - - def __updateConstraints(self): - """ - Update the constraints depending on the size of the widget - """ - image = self.data() - if image is None: - return - size = self.size() - if size.width() == 0 or size.height() == 0: - return - - heightData, widthData = image.shape - - widthContraint = heightData * size.width() / size.height() - if widthContraint > widthData: - heightContraint = heightData - else: - heightContraint = heightData * size.height() / size.width() - widthContraint = widthData - - midWidth, midHeight = widthData * 0.5, heightData * 0.5 - heightContraint, widthContraint = heightContraint * 0.5, widthContraint * 0.5 - - axis = self.__plot.getXAxis() - axis.setLimitsConstraints(midWidth - widthContraint, midWidth + widthContraint) - axis = self.__plot.getYAxis() - axis.setLimitsConstraints(midHeight - heightContraint, midHeight + heightContraint) - - def __imageItem(self): - image = self.__plot.getImage("data") - return image - - def data(self): - if self.__data is not None: - if hasattr(self.__data, "name"): - # in case of HDF5 - if self.__data.name is None: - # The dataset was closed - self.__data = None - return self.__data - - def colormap(self): - image = self.__imageItem() - if image is not None: - return image.getColormap() - return self.__plot.getDefaultColormap() - - def setColormap(self, colormap): - self.__plot.setDefaultColormap(colormap) - - def clear(self): - self.__data = None - image = self.__imageItem() - if image is not None: - self.__plot.removeImage(legend="data") - - -class ImageFileDialog(AbstractDataFileDialog): - """The `ImageFileDialog` class provides a dialog that allow users to select - an image from a file. - - The `ImageFileDialog` class enables a user to traverse the file system in - order to select one file. Then to traverse the file to select a frame or - a slice of a dataset. - - .. image:: img/imagefiledialog_h5.png - - It supports fast access to image files using `FabIO`. Which is not the case - of the default silx API. Image files still also can be available using the - NeXus layout, by editing the file type combo box. - - .. image:: img/imagefiledialog_edf.png - - The selected data is an numpy array with 2 dimension. - - Using an `ImageFileDialog` can be done like that. - - .. code-block:: python - - dialog = ImageFileDialog() - result = dialog.exec_() - if result: - print("Selection:") - print(dialog.selectedFile()) - print(dialog.selectedUrl()) - print(dialog.selectedImage()) - else: - print("Nothing selected") - """ - - def selectedImage(self): - """Returns the selected image data as numpy - - :rtype: numpy.ndarray - """ - url = self.selectedUrl() - return silx.io.get_data(url) - - def _createPreviewWidget(self, parent): - previewWidget = _ImagePreview(parent) - previewWidget.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding) - return previewWidget - - def _createSelectorWidget(self, parent): - return _ImageSelection(parent) - - def _createPreviewToolbar(self, parent, dataPreviewWidget, dataSelectorWidget): - plot = dataPreviewWidget.plot() - toolbar = qt.QToolBar(parent) - toolbar.setIconSize(qt.QSize(16, 16)) - toolbar.setStyleSheet("QToolBar { border: 0px }") - toolbar.addAction(actions.mode.ZoomModeAction(plot, parent)) - toolbar.addAction(actions.mode.PanModeAction(plot, parent)) - toolbar.addSeparator() - toolbar.addAction(actions.control.ResetZoomAction(plot, parent)) - toolbar.addSeparator() - toolbar.addAction(actions.control.ColormapAction(plot, parent)) - return toolbar - - def _isDataSupportable(self, data): - """Check if the selected data can be supported at one point. - - If true, the data selector will be checked and it will update the data - preview. Else the selecting is disabled. - - :rtype: bool - """ - if not hasattr(data, "dtype"): - # It is not an HDF5 dataset nor a fabio image wrapper - return False - - if data is None or data.shape is None: - return False - - if data.dtype.kind not in set(["f", "u", "i", "b"]): - return False - - dim = len(data.shape) - return dim >= 2 - - def _isFabioFilesSupported(self): - return True - - def _isDataSupported(self, data): - """Check if the data can be returned by the dialog. - - If true, this data can be returned by the dialog and the open button - while be enabled. If false the button will be disabled. - - :rtype: bool - """ - dim = len(data.shape) - return dim == 2 - - def _displayedDataInfo(self, dataBeforeSelection, dataAfterSelection): - """Returns the text displayed under the data preview. - - This zone is used to display error in case or problem of data selection - or problems with IO. - - :param numpy.ndarray dataAfterSelection: Data as it is after the - selection widget (basically the data from the preview widget) - :param numpy.ndarray dataAfterSelection: Data as it is before the - selection widget (basically the data from the browsing widget) - :rtype: bool - """ - destination = self.__formatShape(dataAfterSelection.shape) - source = self.__formatShape(dataBeforeSelection.shape) - return u"%s \u2192 %s" % (source, destination) - - def __formatShape(self, shape): - result = [] - for s in shape: - if isinstance(s, slice): - v = u"\u2026" - else: - v = str(s) - result.append(v) - return u" \u00D7 ".join(result) diff --git a/silx/gui/dialog/SafeFileIconProvider.py b/silx/gui/dialog/SafeFileIconProvider.py deleted file mode 100644 index 1e06b64..0000000 --- a/silx/gui/dialog/SafeFileIconProvider.py +++ /dev/null @@ -1,154 +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. -# -# ###########################################################################*/ -""" -This module contains :class:`SafeIconProvider`. -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "31/10/2017" - -import sys -import logging -from silx.gui import qt - - -_logger = logging.getLogger(__name__) - - -class SafeFileIconProvider(qt.QFileIconProvider): - """ - This class reimplement :class:`qt.QFileIconProvider` to avoid blocking - access to the file system. - - It avoid to use `qt.QFileInfo.absoluteFilePath` or - `qt.QFileInfo.canonicalPath` to reach drive icons which are known to - freeze the file system using network drives. - - Computer root, and drive root paths are filtered. Other paths are not - filtered while it is anyway needed to synchronoze a drive to accesss to it. - """ - - WIN32_DRIVE_UNKNOWN = 0 - """The drive type cannot be determined.""" - WIN32_DRIVE_NO_ROOT_DIR = 1 - """The root path is invalid; for example, there is no volume mounted at the - specified path.""" - WIN32_DRIVE_REMOVABLE = 2 - """The drive has removable media; for example, a floppy drive, thumb drive, - or flash card reader.""" - WIN32_DRIVE_FIXED = 3 - """The drive has fixed media; for example, a hard disk drive or flash - drive.""" - WIN32_DRIVE_REMOTE = 4 - """The drive is a remote (network) drive.""" - WIN32_DRIVE_CDROM = 5 - """The drive is a CD-ROM drive.""" - WIN32_DRIVE_RAMDISK = 6 - """The drive is a RAM disk.""" - - def __init__(self): - qt.QFileIconProvider.__init__(self) - self.__filterDirAndFiles = False - if sys.platform == "win32": - self._windowsTypes = {} - item = "Drive", qt.QStyle.SP_DriveHDIcon - self._windowsTypes[self.WIN32_DRIVE_UNKNOWN] = item - item = "Invalid root", qt.QStyle.SP_DriveHDIcon - self._windowsTypes[self.WIN32_DRIVE_NO_ROOT_DIR] = item - item = "Removable", qt.QStyle.SP_DriveNetIcon - self._windowsTypes[self.WIN32_DRIVE_REMOVABLE] = item - item = "Drive", qt.QStyle.SP_DriveHDIcon - self._windowsTypes[self.WIN32_DRIVE_FIXED] = item - item = "Remote", qt.QStyle.SP_DriveNetIcon - self._windowsTypes[self.WIN32_DRIVE_REMOTE] = item - item = "CD-ROM", qt.QStyle.SP_DriveCDIcon - self._windowsTypes[self.WIN32_DRIVE_CDROM] = item - item = "RAM disk", qt.QStyle.SP_DriveHDIcon - self._windowsTypes[self.WIN32_DRIVE_RAMDISK] = item - - def __windowsDriveTypeId(self, info): - try: - import ctypes - path = info.filePath() - dtype = ctypes.cdll.kernel32.GetDriveTypeW(path) - except Exception: - _logger.warning("Impossible to identify drive %s" % path) - _logger.debug("Backtrace", exc_info=True) - return self.WIN32_DRIVE_UNKNOWN - return dtype - - def __windowsDriveIcon(self, info): - dtype = self.__windowsDriveTypeId(info) - default = self._windowsTypes[self.WIN32_DRIVE_UNKNOWN] - driveInfo = self._windowsTypes.get(dtype, default) - style = qt.QApplication.instance().style() - icon = style.standardIcon(driveInfo[1]) - return icon - - def __windowsDriveType(self, info): - dtype = self.__windowsDriveTypeId(info) - default = self._windowsTypes[self.WIN32_DRIVE_UNKNOWN] - driveInfo = self._windowsTypes.get(dtype, default) - return driveInfo[0] - - def icon(self, info): - if isinstance(info, qt.QFileIconProvider.IconType): - # It's another C++ method signature: - # QIcon QFileIconProvider::icon(QFileIconProvider::IconType type) - return super(SafeFileIconProvider, self).icon(info) - style = qt.QApplication.instance().style() - path = info.filePath() - if path in ["", "/"]: - # That's the computer root on Windows or Linux - result = style.standardIcon(qt.QStyle.SP_ComputerIcon) - elif sys.platform == "win32" and path[-2] == ":": - # That's a drive on Windows - result = self.__windowsDriveIcon(info) - elif self.__filterDirAndFiles: - if info.isDir(): - result = style.standardIcon(qt.QStyle.SP_DirIcon) - else: - result = style.standardIcon(qt.QStyle.SP_FileIcon) - else: - result = qt.QFileIconProvider.icon(self, info) - return result - - def type(self, info): - path = info.filePath() - if path in ["", "/"]: - # That's the computer root on Windows or Linux - result = "Computer" - elif sys.platform == "win32" and path[-2] == ":": - # That's a drive on Windows - result = self.__windowsDriveType(info) - elif self.__filterDirAndFiles: - if info.isDir(): - result = "Directory" - else: - result = info.suffix() - else: - result = qt.QFileIconProvider.type(self, info) - return result diff --git a/silx/gui/dialog/SafeFileSystemModel.py b/silx/gui/dialog/SafeFileSystemModel.py deleted file mode 100644 index 198e089..0000000 --- a/silx/gui/dialog/SafeFileSystemModel.py +++ /dev/null @@ -1,802 +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. -# -# ###########################################################################*/ -""" -This module contains an :class:`SafeFileSystemModel`. -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "22/11/2017" - -import sys -import os.path -import logging -import weakref -from silx.gui import qt -from silx.third_party import six -from .SafeFileIconProvider import SafeFileIconProvider - -_logger = logging.getLogger(__name__) - - -class _Item(object): - - def __init__(self, fileInfo): - self.__fileInfo = fileInfo - self.__parent = None - self.__children = None - self.__absolutePath = None - - def isDrive(self): - if sys.platform == "win32": - return self.parent().parent() is None - else: - return False - - def isRoot(self): - return self.parent() is None - - def isFile(self): - """ - Returns true if the path is a file. - - It avoid to access to the `Qt.QFileInfo` in case the file is a drive. - """ - if self.isDrive(): - return False - return self.__fileInfo.isFile() - - def isDir(self): - """ - Returns true if the path is a directory. - - The default `qt.QFileInfo.isDir` can freeze the file system with - network drives. This function avoid the freeze in case of browsing - the root. - """ - if self.isDrive(): - # A drive is a directory, we don't have to synchronize the - # drive to know that - return True - return self.__fileInfo.isDir() - - def absoluteFilePath(self): - """ - Returns an absolute path including the file name. - - This function uses in most cases the default - `qt.QFileInfo.absoluteFilePath`. But it is known to freeze the file - system with network drives. - - This function uses `qt.QFileInfo.filePath` in case of root drives, to - avoid this kind of issues. In case of drive, the result is the same, - while the file path is already absolute. - - :rtype: str - """ - if self.__absolutePath is None: - if self.isRoot(): - path = "" - elif self.isDrive(): - path = self.__fileInfo.filePath() - else: - path = os.path.join(self.parent().absoluteFilePath(), self.__fileInfo.fileName()) - if path == "": - return "/" - self.__absolutePath = path - return self.__absolutePath - - def child(self): - self.populate() - return self.__children - - def childAt(self, position): - self.populate() - return self.__children[position] - - def childCount(self): - self.populate() - return len(self.__children) - - def indexOf(self, item): - self.populate() - return self.__children.index(item) - - def parent(self): - parent = self.__parent - if parent is None: - return None - return parent() - - def filePath(self): - return self.__fileInfo.filePath() - - def fileName(self): - if self.isDrive(): - name = self.absoluteFilePath() - if name[-1] == "/": - name = name[:-1] - return name - return os.path.basename(self.absoluteFilePath()) - - def fileInfo(self): - """ - Returns the Qt file info. - - :rtype: Qt.QFileInfo - """ - return self.__fileInfo - - def _setParent(self, parent): - self.__parent = weakref.ref(parent) - - def findChildrenByPath(self, path): - if path == "": - return self - path = path.replace("\\", "/") - if path[-1] == "/": - path = path[:-1] - names = path.split("/") - caseSensitive = qt.QFSFileEngine(path).caseSensitive() - count = len(names) - cursor = self - for name in names: - for item in cursor.child(): - if caseSensitive: - same = item.fileName() == name - else: - same = item.fileName().lower() == name.lower() - if same: - cursor = item - count -= 1 - break - else: - return None - if count == 0: - break - else: - return None - return cursor - - def populate(self): - if self.__children is not None: - return - self.__children = [] - if self.isRoot(): - items = qt.QDir.drives() - else: - directory = qt.QDir(self.absoluteFilePath()) - filters = qt.QDir.AllEntries | qt.QDir.Hidden | qt.QDir.System - items = directory.entryInfoList(filters) - for fileInfo in items: - i = _Item(fileInfo) - self.__children.append(i) - i._setParent(self) - - -class _RawFileSystemModel(qt.QAbstractItemModel): - """ - This class implement a file system model and try to avoid freeze. On Qt4, - :class:`qt.QFileSystemModel` is known to freeze the file system when - network drives are available. - - To avoid this behaviour, this class does not use - `qt.QFileInfo.absoluteFilePath` nor `qt.QFileInfo.canonicalPath` to reach - information on drives. - - This model do not take care of sorting and filtering. This features are - managed by another model, by composition. - - And because it is the end of life of Qt4, we do not implement asynchronous - loading of files as it is done by :class:`qt.QFileSystemModel`, nor some - useful features. - """ - - __directoryLoadedSync = qt.Signal(str) - """This signal is connected asynchronously to a slot. It allows to - emit directoryLoaded as an asynchronous signal.""" - - directoryLoaded = qt.Signal(str) - """This signal is emitted when the gatherer thread has finished to load the - path.""" - - rootPathChanged = qt.Signal(str) - """This signal is emitted whenever the root path has been changed to a - newPath.""" - - NAME_COLUMN = 0 - SIZE_COLUMN = 1 - TYPE_COLUMN = 2 - LAST_MODIFIED_COLUMN = 3 - - def __init__(self, parent=None): - qt.QAbstractItemModel.__init__(self, parent) - self.__computer = _Item(qt.QFileInfo()) - self.__header = "Name", "Size", "Type", "Last modification" - self.__currentPath = "" - self.__iconProvider = SafeFileIconProvider() - self.__directoryLoadedSync.connect(self.__emitDirectoryLoaded, qt.Qt.QueuedConnection) - - def headerData(self, section, orientation, role=qt.Qt.DisplayRole): - if orientation == qt.Qt.Horizontal: - if role == qt.Qt.DisplayRole: - return self.__header[section] - if role == qt.Qt.TextAlignmentRole: - return qt.Qt.AlignRight if section == 1 else qt.Qt.AlignLeft - return None - - def flags(self, index): - if not index.isValid(): - return 0 - return qt.Qt.ItemIsEnabled | qt.Qt.ItemIsSelectable - - def columnCount(self, parent=qt.QModelIndex()): - return len(self.__header) - - def rowCount(self, parent=qt.QModelIndex()): - item = self.__getItem(parent) - return item.childCount() - - def data(self, index, role=qt.Qt.DisplayRole): - if not index.isValid(): - return None - - column = index.column() - if role in [qt.Qt.DisplayRole, qt.Qt.EditRole]: - if column == self.NAME_COLUMN: - return self.__displayName(index) - elif column == self.SIZE_COLUMN: - return self.size(index) - elif column == self.TYPE_COLUMN: - return self.type(index) - elif column == self.LAST_MODIFIED_COLUMN: - return self.lastModified(index) - else: - _logger.warning("data: invalid display value column %d", index.column()) - elif role == qt.QFileSystemModel.FilePathRole: - return self.filePath(index) - elif role == qt.QFileSystemModel.FileNameRole: - return self.fileName(index) - elif role == qt.Qt.DecorationRole: - if column == self.NAME_COLUMN: - icon = self.fileIcon(index) - if icon is None or icon.isNull(): - if self.isDir(index): - self.__iconProvider.icon(qt.QFileIconProvider.Folder) - else: - self.__iconProvider.icon(qt.QFileIconProvider.File) - return icon - elif role == qt.Qt.TextAlignmentRole: - if column == self.SIZE_COLUMN: - return qt.Qt.AlignRight - elif role == qt.QFileSystemModel.FilePermissions: - return self.permissions(index) - - return None - - def index(self, *args, **kwargs): - path_api = False - path_api |= len(args) >= 1 and isinstance(args[0], six.string_types) - path_api |= "path" in kwargs - - if path_api: - return self.__indexFromPath(*args, **kwargs) - else: - return self.__index(*args, **kwargs) - - def __index(self, row, column, parent=qt.QModelIndex()): - if parent.isValid() and parent.column() != 0: - return None - - parentItem = self.__getItem(parent) - item = parentItem.childAt(row) - return self.createIndex(row, column, item) - - def __indexFromPath(self, path, column=0): - """ - Uses the index(str) C++ API - - :rtype: qt.QModelIndex - """ - if path == "": - return qt.QModelIndex() - - item = self.__computer.findChildrenByPath(path) - if item is None: - return qt.QModelIndex() - - return self.createIndex(item.parent().indexOf(item), column, item) - - def parent(self, index): - if not index.isValid(): - return qt.QModelIndex() - - item = self.__getItem(index) - if index is None: - return qt.QModelIndex() - - parent = item.parent() - if parent is None or parent is self.__computer: - return qt.QModelIndex() - - return self.createIndex(parent.parent().indexOf(parent), 0, parent) - - def __emitDirectoryLoaded(self, path): - self.directoryLoaded.emit(path) - - def __emitRootPathChanged(self, path): - self.rootPathChanged.emit(path) - - def __getItem(self, index): - if not index.isValid(): - return self.__computer - item = index.internalPointer() - return item - - def fileIcon(self, index): - item = self.__getItem(index) - if self.__iconProvider is not None: - fileInfo = item.fileInfo() - result = self.__iconProvider.icon(fileInfo) - else: - style = qt.QApplication.instance().style() - if item.isRoot(): - result = style.standardIcon(qt.QStyle.SP_ComputerIcon) - elif item.isDrive(): - result = style.standardIcon(qt.QStyle.SP_DriveHDIcon) - elif item.isDir(): - result = style.standardIcon(qt.QStyle.SP_DirIcon) - else: - result = style.standardIcon(qt.QStyle.SP_FileIcon) - return result - - def _item(self, index): - item = self.__getItem(index) - return item - - def fileInfo(self, index): - item = self.__getItem(index) - result = item.fileInfo() - return result - - def __fileIcon(self, index): - item = self.__getItem(index) - result = item.fileName() - return result - - def __displayName(self, index): - item = self.__getItem(index) - result = item.fileName() - return result - - def fileName(self, index): - item = self.__getItem(index) - result = item.fileName() - return result - - def filePath(self, index): - item = self.__getItem(index) - result = item.fileInfo().filePath() - return result - - def isDir(self, index): - item = self.__getItem(index) - result = item.isDir() - return result - - def lastModified(self, index): - item = self.__getItem(index) - result = item.fileInfo().lastModified() - return result - - def permissions(self, index): - item = self.__getItem(index) - result = item.fileInfo().permissions() - return result - - def size(self, index): - item = self.__getItem(index) - result = item.fileInfo().size() - return result - - def type(self, index): - item = self.__getItem(index) - if self.__iconProvider is not None: - fileInfo = item.fileInfo() - result = self.__iconProvider.type(fileInfo) - else: - if item.isRoot(): - result = "Computer" - elif item.isDrive(): - result = "Drive" - elif item.isDir(): - result = "Directory" - else: - fileInfo = item.fileInfo() - result = fileInfo.suffix() - return result - - # File manipulation - - # bool remove(const QModelIndex & index) const - # bool rmdir(const QModelIndex & index) const - # QModelIndex mkdir(const QModelIndex & parent, const QString & name) - - # Configuration - - def rootDirectory(self): - return qt.QDir(self.rootPath()) - - def rootPath(self): - return self.__currentPath - - def setRootPath(self, path): - if self.__currentPath == path: - return - self.__currentPath = path - item = self.__computer.findChildrenByPath(path) - self.__emitRootPathChanged(path) - if item is None or item.parent() is None: - return qt.QModelIndex() - index = self.createIndex(item.parent().indexOf(item), 0, item) - self.__directoryLoadedSync.emit(path) - return index - - def iconProvider(self): - # FIXME: invalidate the model - return self.__iconProvider - - def setIconProvider(self, provider): - # FIXME: invalidate the model - self.__iconProvider = provider - - # bool resolveSymlinks() const - # void setResolveSymlinks(bool enable) - - def setNameFilterDisables(self, enable): - return None - - def nameFilterDisables(self): - return None - - def myComputer(self, role=qt.Qt.DisplayRole): - return None - - def setNameFilters(self, filters): - return - - def nameFilters(self): - return None - - def filter(self): - return self.__filters - - def setFilter(self, filters): - return - - def setReadOnly(self, enable): - assert(enable is True) - - def isReadOnly(self): - return False - - -class SafeFileSystemModel(qt.QSortFilterProxyModel): - """ - This class implement a file system model and try to avoid freeze. On Qt4, - :class:`qt.QFileSystemModel` is known to freeze the file system when - network drives are available. - - To avoid this behaviour, this class does not use - `qt.QFileInfo.absoluteFilePath` nor `qt.QFileInfo.canonicalPath` to reach - information on drives. - - And because it is the end of life of Qt4, we do not implement asynchronous - loading of files as it is done by :class:`qt.QFileSystemModel`, nor some - useful features. - """ - - def __init__(self, parent=None): - qt.QSortFilterProxyModel.__init__(self, parent=parent) - self.__nameFilterDisables = sys.platform == "darwin" - self.__nameFilters = [] - self.__filters = qt.QDir.AllEntries | qt.QDir.NoDotAndDotDot | qt.QDir.AllDirs - sourceModel = _RawFileSystemModel(self) - self.setSourceModel(sourceModel) - - @property - def directoryLoaded(self): - return self.sourceModel().directoryLoaded - - @property - def rootPathChanged(self): - return self.sourceModel().rootPathChanged - - def index(self, *args, **kwargs): - path_api = False - path_api |= len(args) >= 1 and isinstance(args[0], six.string_types) - path_api |= "path" in kwargs - - if path_api: - return self.__indexFromPath(*args, **kwargs) - else: - return self.__index(*args, **kwargs) - - def __index(self, row, column, parent=qt.QModelIndex()): - return qt.QSortFilterProxyModel.index(self, row, column, parent) - - def __indexFromPath(self, path, column=0): - """ - Uses the index(str) C++ API - - :rtype: qt.QModelIndex - """ - if path == "": - return qt.QModelIndex() - - index = self.sourceModel().index(path, column) - index = self.mapFromSource(index) - return index - - def lessThan(self, leftSourceIndex, rightSourceIndex): - sourceModel = self.sourceModel() - sortColumn = self.sortColumn() - if sortColumn == _RawFileSystemModel.NAME_COLUMN: - leftItem = sourceModel._item(leftSourceIndex) - rightItem = sourceModel._item(rightSourceIndex) - if sys.platform != "darwin": - # Sort directories before files - leftIsDir = leftItem.isDir() - rightIsDir = rightItem.isDir() - if leftIsDir ^ rightIsDir: - return leftIsDir - return leftItem.fileName().lower() < rightItem.fileName().lower() - elif sortColumn == _RawFileSystemModel.SIZE_COLUMN: - left = sourceModel.fileInfo(leftSourceIndex) - right = sourceModel.fileInfo(rightSourceIndex) - return left.size() < right.size() - elif sortColumn == _RawFileSystemModel.TYPE_COLUMN: - left = sourceModel.type(leftSourceIndex) - right = sourceModel.type(rightSourceIndex) - return left < right - elif sortColumn == _RawFileSystemModel.LAST_MODIFIED_COLUMN: - left = sourceModel.fileInfo(leftSourceIndex) - right = sourceModel.fileInfo(rightSourceIndex) - return left.lastModified() < right.lastModified() - else: - _logger.warning("Unsupported sorted column %d", sortColumn) - - return False - - def __filtersAccepted(self, item, filters): - """ - Check individual flag filters. - """ - if not (filters & (qt.QDir.Dirs | qt.QDir.AllDirs)): - # Hide dirs - if item.isDir(): - return False - if not (filters & qt.QDir.Files): - # Hide files - if item.isFile(): - return False - if not (filters & qt.QDir.Drives): - # Hide drives - if item.isDrive(): - return False - - fileInfo = item.fileInfo() - if fileInfo is None: - return False - - filterPermissions = (filters & qt.QDir.PermissionMask) != 0 - if filterPermissions and (filters & (qt.QDir.Dirs | qt.QDir.Files)): - if (filters & qt.QDir.Readable): - # Hide unreadable - if not fileInfo.isReadable(): - return False - if (filters & qt.QDir.Writable): - # Hide unwritable - if not fileInfo.isWritable(): - return False - if (filters & qt.QDir.Executable): - # Hide unexecutable - if not fileInfo.isExecutable(): - return False - - if (filters & qt.QDir.NoSymLinks): - # Hide sym links - if fileInfo.isSymLink(): - return False - - if not (filters & qt.QDir.System): - # Hide system - if not item.isDir() and not item.isFile(): - return False - - fileName = item.fileName() - isDot = fileName == "." - isDotDot = fileName == ".." - - if not (filters & qt.QDir.Hidden): - # Hide hidden - if not (isDot or isDotDot) and fileInfo.isHidden(): - return False - - if filters & (qt.QDir.NoDot | qt.QDir.NoDotDot | qt.QDir.NoDotAndDotDot): - # Hide parent/self references - if filters & qt.QDir.NoDot: - if isDot: - return False - if filters & qt.QDir.NoDotDot: - if isDotDot: - return False - if filters & qt.QDir.NoDotAndDotDot: - if isDot or isDotDot: - return False - - return True - - def filterAcceptsRow(self, sourceRow, sourceParent): - if not sourceParent.isValid(): - return True - - sourceModel = self.sourceModel() - index = sourceModel.index(sourceRow, 0, sourceParent) - if not index.isValid(): - return True - item = sourceModel._item(index) - - filters = self.__filters - - if item.isDrive(): - # Let say a user always have access to a drive - # It avoid to access to fileInfo then avoid to freeze the file - # system - return True - - if not self.__filtersAccepted(item, filters): - return False - - if self.__nameFilterDisables: - return True - - if item.isDir() and (filters & qt.QDir.AllDirs): - # dont apply the filters to directory names - return True - - return self.__nameFiltersAccepted(item) - - def __nameFiltersAccepted(self, item): - if len(self.__nameFilters) == 0: - return True - - fileName = item.fileName() - for reg in self.__nameFilters: - if reg.exactMatch(fileName): - return True - return False - - def setNameFilterDisables(self, enable): - self.__nameFilterDisables = enable - self.invalidate() - - def nameFilterDisables(self): - return self.__nameFilterDisables - - def myComputer(self, role=qt.Qt.DisplayRole): - return self.sourceModel().myComputer(role) - - def setNameFilters(self, filters): - self.__nameFilters = [] - isCaseSensitive = self.__filters & qt.QDir.CaseSensitive - caseSensitive = qt.Qt.CaseSensitive if isCaseSensitive else qt.Qt.CaseInsensitive - for f in filters: - reg = qt.QRegExp(f, caseSensitive, qt.QRegExp.Wildcard) - self.__nameFilters.append(reg) - self.invalidate() - - def nameFilters(self): - return [f.pattern() for f in self.__nameFilters] - - def filter(self): - return self.__filters - - def setFilter(self, filters): - self.__filters = filters - # In case of change of case sensitivity - self.setNameFilters(self.nameFilters()) - self.invalidate() - - def setReadOnly(self, enable): - assert(enable is True) - - def isReadOnly(self): - return False - - def rootPath(self): - return self.sourceModel().rootPath() - - def setRootPath(self, path): - index = self.sourceModel().setRootPath(path) - index = self.mapFromSource(index) - return index - - def flags(self, index): - sourceModel = self.sourceModel() - index = self.mapToSource(index) - filters = sourceModel.flags(index) - - if self.__nameFilterDisables and not sourceModel.isDir(index): - item = sourceModel._item(index) - if not self.__nameFiltersAccepted(item): - filters &= ~qt.Qt.ItemIsEnabled - - return filters - - def fileIcon(self, index): - sourceModel = self.sourceModel() - index = self.mapToSource(index) - return sourceModel.fileIcon(index) - - def fileInfo(self, index): - sourceModel = self.sourceModel() - index = self.mapToSource(index) - return sourceModel.fileInfo(index) - - def fileName(self, index): - sourceModel = self.sourceModel() - index = self.mapToSource(index) - return sourceModel.fileName(index) - - def filePath(self, index): - sourceModel = self.sourceModel() - index = self.mapToSource(index) - return sourceModel.filePath(index) - - def isDir(self, index): - sourceModel = self.sourceModel() - index = self.mapToSource(index) - return sourceModel.isDir(index) - - def lastModified(self, index): - sourceModel = self.sourceModel() - index = self.mapToSource(index) - return sourceModel.lastModified(index) - - def permissions(self, index): - sourceModel = self.sourceModel() - index = self.mapToSource(index) - return sourceModel.permissions(index) - - def size(self, index): - sourceModel = self.sourceModel() - index = self.mapToSource(index) - return sourceModel.size(index) - - def type(self, index): - sourceModel = self.sourceModel() - index = self.mapToSource(index) - return sourceModel.type(index) diff --git a/silx/gui/dialog/__init__.py b/silx/gui/dialog/__init__.py deleted file mode 100644 index 77c5949..0000000 --- a/silx/gui/dialog/__init__.py +++ /dev/null @@ -1,29 +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. -# -# ###########################################################################*/ -"""Qt dialogs""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "11/10/2017" diff --git a/silx/gui/dialog/setup.py b/silx/gui/dialog/setup.py deleted file mode 100644 index 48ab8d8..0000000 --- a/silx/gui/dialog/setup.py +++ /dev/null @@ -1,40 +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__ = "23/10/2017" - -from numpy.distutils.misc_util import Configuration - - -def configuration(parent_package='', top_path=None): - config = Configuration('dialog', 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/dialog/test/__init__.py b/silx/gui/dialog/test/__init__.py deleted file mode 100644 index f43a37a..0000000 --- a/silx/gui/dialog/test/__init__.py +++ /dev/null @@ -1,49 +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. -# -# ###########################################################################*/ -"""Tests for Qt dialogs""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "24/04/2018" - - -import logging -import os -import sys -import unittest - - -_logger = logging.getLogger(__name__) - - -def suite(): - test_suite = unittest.TestSuite() - from . import test_imagefiledialog - from . import test_datafiledialog - from . import test_colormapdialog - test_suite.addTest(test_imagefiledialog.suite()) - test_suite.addTest(test_datafiledialog.suite()) - test_suite.addTest(test_colormapdialog.suite()) - return test_suite diff --git a/silx/gui/dialog/test/test_colormapdialog.py b/silx/gui/dialog/test/test_colormapdialog.py deleted file mode 100644 index 6e50193..0000000 --- a/silx/gui/dialog/test/test_colormapdialog.py +++ /dev/null @@ -1,398 +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. -# -# ###########################################################################*/ -"""Basic tests for ColormapDialog""" - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "23/05/2018" - - -import doctest -import unittest - -from silx.gui.utils.testutils import qWaitForWindowExposedAndActivate -from silx.gui import qt -from silx.gui.dialog import ColormapDialog -from silx.gui.utils.testutils import TestCaseQt -from silx.gui.colors import Colormap, preferredColormaps -from silx.utils.testutils import ParametricTestCase -from silx.gui.plot.PlotWindow import PlotWindow - -import numpy.random - - -# Makes sure a QApplication exists -_qapp = qt.QApplication.instance() or qt.QApplication([]) - - -def _tearDownQt(docTest): - """Tear down to use for test from docstring. - - Checks that dialog widget is displayed - """ - dialogWidget = docTest.globs['dialog'] - qWaitForWindowExposedAndActivate(dialogWidget) - dialogWidget.setAttribute(qt.Qt.WA_DeleteOnClose) - dialogWidget.close() - del dialogWidget - _qapp.processEvents() - - -cmapDocTestSuite = doctest.DocTestSuite(ColormapDialog, tearDown=_tearDownQt) -"""Test suite of tests from the module's docstrings.""" - - -class TestColormapDialog(TestCaseQt, ParametricTestCase): - """Test the ColormapDialog.""" - def setUp(self): - TestCaseQt.setUp(self) - ParametricTestCase.setUp(self) - self.colormap = Colormap(name='gray', vmin=10.0, vmax=20.0, - normalization='linear') - - self.colormapDiag = ColormapDialog.ColormapDialog() - self.colormapDiag.setAttribute(qt.Qt.WA_DeleteOnClose) - - def tearDown(self): - del self.colormapDiag - ParametricTestCase.tearDown(self) - TestCaseQt.tearDown(self) - - def testGUIEdition(self): - """Make sure the colormap is correctly edited and also that the - modification are correctly updated if an other colormapdialog is - editing the same colormap""" - colormapDiag2 = ColormapDialog.ColormapDialog() - colormapDiag2.setColormap(self.colormap) - self.colormapDiag.setColormap(self.colormap) - - self.colormapDiag._comboBoxColormap.setCurrentName('red') - self.colormapDiag._normButtonLog.setChecked(True) - self.assertTrue(self.colormap.getName() == 'red') - self.assertTrue(self.colormapDiag.getColormap().getName() == 'red') - self.assertTrue(self.colormap.getNormalization() == 'log') - self.assertTrue(self.colormap.getVMin() == 10) - self.assertTrue(self.colormap.getVMax() == 20) - # checked second colormap dialog - self.assertTrue(colormapDiag2._comboBoxColormap.getCurrentName() == 'red') - self.assertTrue(colormapDiag2._normButtonLog.isChecked()) - self.assertTrue(int(colormapDiag2._minValue.getValue()) == 10) - self.assertTrue(int(colormapDiag2._maxValue.getValue()) == 20) - colormapDiag2.close() - - def testGUIModalOk(self): - """Make sure the colormap is modified if gone through accept""" - assert self.colormap.isAutoscale() is False - self.colormapDiag.setModal(True) - self.colormapDiag.show() - self.colormapDiag.setColormap(self.colormap) - self.assertTrue(self.colormap.getVMin() is not None) - self.colormapDiag._minValue.setValue(None) - self.assertTrue(self.colormap.getVMin() is None) - self.colormapDiag._maxValue.setValue(None) - self.mouseClick( - widget=self.colormapDiag._buttonsModal.button(qt.QDialogButtonBox.Ok), - button=qt.Qt.LeftButton - ) - self.assertTrue(self.colormap.getVMin() is None) - self.assertTrue(self.colormap.getVMax() is None) - self.assertTrue(self.colormap.isAutoscale() is True) - - def testGUIModalCancel(self): - """Make sure the colormap is not modified if gone through reject""" - assert self.colormap.isAutoscale() is False - self.colormapDiag.setModal(True) - self.colormapDiag.show() - self.colormapDiag.setColormap(self.colormap) - self.assertTrue(self.colormap.getVMin() is not None) - self.colormapDiag._minValue.setValue(None) - self.assertTrue(self.colormap.getVMin() is None) - self.mouseClick( - widget=self.colormapDiag._buttonsModal.button(qt.QDialogButtonBox.Cancel), - button=qt.Qt.LeftButton - ) - self.assertTrue(self.colormap.getVMin() is not None) - - def testGUIModalClose(self): - assert self.colormap.isAutoscale() is False - self.colormapDiag.setModal(False) - self.colormapDiag.show() - self.colormapDiag.setColormap(self.colormap) - self.assertTrue(self.colormap.getVMin() is not None) - self.colormapDiag._minValue.setValue(None) - self.assertTrue(self.colormap.getVMin() is None) - self.mouseClick( - widget=self.colormapDiag._buttonsNonModal.button(qt.QDialogButtonBox.Close), - button=qt.Qt.LeftButton - ) - self.assertTrue(self.colormap.getVMin() is None) - - def testGUIModalReset(self): - assert self.colormap.isAutoscale() is False - self.colormapDiag.setModal(False) - self.colormapDiag.show() - self.colormapDiag.setColormap(self.colormap) - self.assertTrue(self.colormap.getVMin() is not None) - self.colormapDiag._minValue.setValue(None) - self.assertTrue(self.colormap.getVMin() is None) - self.mouseClick( - widget=self.colormapDiag._buttonsNonModal.button(qt.QDialogButtonBox.Reset), - button=qt.Qt.LeftButton - ) - self.assertTrue(self.colormap.getVMin() is not None) - self.colormapDiag.close() - - def testGUIClose(self): - """Make sure the colormap is modify if go through reject""" - assert self.colormap.isAutoscale() is False - self.colormapDiag.show() - self.colormapDiag.setColormap(self.colormap) - self.assertTrue(self.colormap.getVMin() is not None) - self.colormapDiag._minValue.setValue(None) - self.assertTrue(self.colormap.getVMin() is None) - self.colormapDiag.close() - self.assertTrue(self.colormap.getVMin() is None) - - def testSetColormapIsCorrect(self): - """Make sure the interface fir the colormap when set a new colormap""" - self.colormap.setName('red') - for norm in (Colormap.NORMALIZATIONS): - for autoscale in (True, False): - if autoscale is True: - self.colormap.setVRange(None, None) - else: - self.colormap.setVRange(11, 101) - self.colormap.setNormalization(norm) - with self.subTest(colormap=self.colormap): - self.colormapDiag.setColormap(self.colormap) - self.assertTrue( - self.colormapDiag._normButtonLinear.isChecked() == (norm is Colormap.LINEAR)) - self.assertTrue( - self.colormapDiag._comboBoxColormap.getCurrentName() == 'red') - self.assertTrue( - self.colormapDiag._minValue.isAutoChecked() == autoscale) - self.assertTrue( - self.colormapDiag._maxValue.isAutoChecked() == autoscale) - if autoscale is False: - self.assertTrue(self.colormapDiag._minValue.getValue() == 11) - self.assertTrue(self.colormapDiag._maxValue.getValue() == 101) - self.assertTrue(self.colormapDiag._minValue.isEnabled()) - self.assertTrue(self.colormapDiag._maxValue.isEnabled()) - else: - self.assertFalse(self.colormapDiag._minValue._numVal.isEnabled()) - self.assertFalse(self.colormapDiag._maxValue._numVal.isEnabled()) - - def testColormapDel(self): - """Check behavior if the colormap has been deleted outside. For now - we make sure the colormap is still running and nothing more""" - self.colormapDiag.setColormap(self.colormap) - self.colormapDiag.show() - del self.colormap - self.assertTrue(self.colormapDiag.getColormap() is None) - self.colormapDiag._comboBoxColormap.setCurrentName('blue') - - def testColormapEditedOutside(self): - """Make sure the GUI is still up to date if the colormap is modified - outside""" - self.colormapDiag.setColormap(self.colormap) - self.colormapDiag.show() - - self.colormap.setName('red') - self.assertTrue( - self.colormapDiag._comboBoxColormap.getCurrentName() == 'red') - self.colormap.setNormalization(Colormap.LOGARITHM) - self.assertFalse(self.colormapDiag._normButtonLinear.isChecked()) - self.colormap.setVRange(11, 201) - self.assertTrue(self.colormapDiag._minValue.getValue() == 11) - self.assertTrue(self.colormapDiag._maxValue.getValue() == 201) - self.assertTrue(self.colormapDiag._minValue._numVal.isEnabled()) - self.assertTrue(self.colormapDiag._maxValue._numVal.isEnabled()) - self.assertFalse(self.colormapDiag._minValue.isAutoChecked()) - self.assertFalse(self.colormapDiag._maxValue.isAutoChecked()) - self.colormap.setVRange(None, None) - self.assertFalse(self.colormapDiag._minValue._numVal.isEnabled()) - self.assertFalse(self.colormapDiag._maxValue._numVal.isEnabled()) - self.assertTrue(self.colormapDiag._minValue.isAutoChecked()) - self.assertTrue(self.colormapDiag._maxValue.isAutoChecked()) - - def testSetColormapScenario(self): - """Test of a simple scenario of a colormap dialog editing several - colormap""" - colormap1 = Colormap(name='gray', vmin=10.0, vmax=20.0, - normalization='linear') - colormap2 = Colormap(name='red', vmin=10.0, vmax=20.0, - normalization='log') - colormap3 = Colormap(name='blue', vmin=None, vmax=None, - normalization='linear') - self.colormapDiag.setColormap(self.colormap) - self.colormapDiag.setColormap(colormap1) - del colormap1 - self.colormapDiag.setColormap(colormap2) - del colormap2 - self.colormapDiag.setColormap(colormap3) - del colormap3 - - def testNotPreferredColormap(self): - """Test that the colormapEditor is able to edit a colormap which is not - part of the 'prefered colormap' - """ - def getFirstNotPreferredColormap(): - cms = Colormap.getSupportedColormaps() - preferred = preferredColormaps() - for cm in cms: - if cm not in preferred: - return cm - return None - - colormapName = getFirstNotPreferredColormap() - assert colormapName is not None - colormap = Colormap(name=colormapName) - self.colormapDiag.setColormap(colormap) - self.colormapDiag.show() - cb = self.colormapDiag._comboBoxColormap - self.assertTrue(cb.getCurrentName() == colormapName) - cb.setCurrentIndex(0) - index = cb.findColormap(colormapName) - assert index is not 0 # if 0 then the rest of the test has no sense - cb.setCurrentIndex(index) - self.assertTrue(cb.getCurrentName() == colormapName) - - def testColormapEditableMode(self): - """Test that the colormapDialog is correctly updated when changing the - colormap editable status""" - colormap = Colormap(normalization='linear', vmin=1.0, vmax=10.0) - self.colormapDiag.setColormap(colormap) - for editable in (True, False): - with self.subTest(editable=editable): - colormap.setEditable(editable) - self.assertTrue( - self.colormapDiag._comboBoxColormap.isEnabled() is editable) - self.assertTrue( - self.colormapDiag._minValue.isEnabled() is editable) - self.assertTrue( - self.colormapDiag._maxValue.isEnabled() is editable) - self.assertTrue( - self.colormapDiag._normButtonLinear.isEnabled() is editable) - self.assertTrue( - self.colormapDiag._normButtonLog.isEnabled() is editable) - - # Make sure the reset button is also set to enable when edition mode is - # False - self.colormapDiag.setModal(False) - colormap.setEditable(True) - self.colormapDiag._normButtonLog.setChecked(True) - resetButton = self.colormapDiag._buttonsNonModal.button(qt.QDialogButtonBox.Reset) - self.assertTrue(resetButton.isEnabled()) - colormap.setEditable(False) - self.assertFalse(resetButton.isEnabled()) - - def testImageData(self): - data = numpy.random.rand(5, 5) - self.colormapDiag.setData(data) - - def testEmptyData(self): - data = numpy.empty((10, 0)) - self.colormapDiag.setData(data) - - def testNoneData(self): - data = numpy.random.rand(5, 5) - self.colormapDiag.setData(data) - self.colormapDiag.setData(None) - - -class TestColormapAction(TestCaseQt): - def setUp(self): - TestCaseQt.setUp(self) - self.plot = PlotWindow() - self.plot.setAttribute(qt.Qt.WA_DeleteOnClose) - - self.colormap1 = Colormap(name='blue', vmin=0.0, vmax=1.0, - normalization='linear') - self.colormap2 = Colormap(name='red', vmin=10.0, vmax=100.0, - normalization='log') - self.defaultColormap = self.plot.getDefaultColormap() - - self.plot.getColormapAction()._actionTriggered(checked=True) - self.colormapDialog = self.plot.getColormapAction()._dialog - self.colormapDialog.setAttribute(qt.Qt.WA_DeleteOnClose) - - def tearDown(self): - self.colormapDialog.close() - self.plot.close() - del self.colormapDialog - del self.plot - TestCaseQt.tearDown(self) - - def testActiveColormap(self): - self.assertTrue(self.colormapDialog.getColormap() is self.defaultColormap) - - self.plot.addImage(data=numpy.random.rand(10, 10), legend='img1', - origin=(0, 0), - colormap=self.colormap1) - self.plot.setActiveImage('img1') - self.assertTrue(self.colormapDialog.getColormap() is self.colormap1) - - self.plot.addImage(data=numpy.random.rand(10, 10), legend='img2', - origin=(0, 0), - colormap=self.colormap2) - self.plot.addImage(data=numpy.random.rand(10, 10), legend='img3', - origin=(0, 0)) - - self.plot.setActiveImage('img3') - self.assertTrue(self.colormapDialog.getColormap() is self.defaultColormap) - self.plot.getActiveImage().setColormap(self.colormap2) - self.assertTrue(self.colormapDialog.getColormap() is self.colormap2) - - self.plot.remove('img2') - self.plot.remove('img3') - self.plot.remove('img1') - self.assertTrue(self.colormapDialog.getColormap() is self.defaultColormap) - - def testShowHideColormapDialog(self): - self.plot.getColormapAction()._actionTriggered(checked=False) - self.assertFalse(self.plot.getColormapAction().isChecked()) - self.plot.getColormapAction()._actionTriggered(checked=True) - self.assertTrue(self.plot.getColormapAction().isChecked()) - self.plot.addImage(data=numpy.random.rand(10, 10), legend='img1', - origin=(0, 0), - colormap=self.colormap1) - self.colormap1.setName('red') - self.plot.getColormapAction()._actionTriggered() - self.colormap1.setName('blue') - self.colormapDialog.close() - self.assertFalse(self.plot.getColormapAction().isChecked()) - - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTest(cmapDocTestSuite) - for testClass in (TestColormapDialog, TestColormapAction): - test_suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase( - testClass)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/gui/dialog/test/test_datafiledialog.py b/silx/gui/dialog/test/test_datafiledialog.py deleted file mode 100644 index aff6bc4..0000000 --- a/silx/gui/dialog/test/test_datafiledialog.py +++ /dev/null @@ -1,995 +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__ = "05/10/2018" - - -import unittest -import tempfile -import numpy -import shutil -import os -import io -import weakref - -try: - import fabio -except ImportError: - fabio = None -try: - import h5py -except ImportError: - h5py = None - -import silx.io.url -from silx.gui import qt -from silx.gui.utils import testutils -from ..DataFileDialog import DataFileDialog -from silx.gui.hdf5 import Hdf5TreeModel - -_tmpDirectory = None - - -def setUpModule(): - global _tmpDirectory - _tmpDirectory = tempfile.mkdtemp(prefix=__name__) - - data = numpy.arange(100 * 100) - data.shape = 100, 100 - - if fabio is not None: - filename = _tmpDirectory + "/singleimage.edf" - image = fabio.edfimage.EdfImage(data=data) - image.write(filename) - - if h5py is not None: - filename = _tmpDirectory + "/data.h5" - f = h5py.File(filename, "w") - f["scalar"] = 10 - f["image"] = data - f["cube"] = [data, data + 1, data + 2] - f["complex_image"] = data * 1j - f["group/image"] = data - f["nxdata/foo"] = 10 - f["nxdata"].attrs["NX_class"] = u"NXdata" - f.close() - - if h5py is not None: - directory = os.path.join(_tmpDirectory, "data") - os.mkdir(directory) - filename = os.path.join(directory, "data.h5") - f = h5py.File(filename, "w") - f["scalar"] = 10 - f["image"] = data - f["cube"] = [data, data + 1, data + 2] - f["complex_image"] = data * 1j - f["group/image"] = data - f["nxdata/foo"] = 10 - f["nxdata"].attrs["NX_class"] = u"NXdata" - f.close() - - filename = _tmpDirectory + "/badformat.h5" - with io.open(filename, "wb") as f: - f.write(b"{\nHello Nurse!") - - -def tearDownModule(): - global _tmpDirectory - shutil.rmtree(_tmpDirectory) - _tmpDirectory = None - - -class _UtilsMixin(object): - - def createDialog(self): - self._deleteDialog() - self._dialog = self._createDialog() - return self._dialog - - def _createDialog(self): - return DataFileDialog() - - def _deleteDialog(self): - if not hasattr(self, "_dialog"): - return - if self._dialog is not None: - ref = weakref.ref(self._dialog) - self._dialog = None - self.qWaitForDestroy(ref) - - def qWaitForPendingActions(self, dialog): - for _ in range(20): - if not dialog.hasPendingEvents(): - return - self.qWait(10) - raise RuntimeError("Still have pending actions") - - def assertSamePath(self, path1, path2): - path1_ = os.path.normcase(path1) - path2_ = os.path.normcase(path2) - if path1_ != path2_: - # Use the unittest API to log and display error - self.assertEqual(path1, path2) - - def assertNotSamePath(self, path1, path2): - path1_ = os.path.normcase(path1) - path2_ = os.path.normcase(path2) - if path1_ == path2_: - # Use the unittest API to log and display error - self.assertNotEquals(path1, path2) - - -class TestDataFileDialogInteraction(testutils.TestCaseQt, _UtilsMixin): - - def tearDown(self): - self._deleteDialog() - testutils.TestCaseQt.tearDown(self) - - def testDisplayAndKeyEscape(self): - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - - self.keyClick(dialog, qt.Qt.Key_Escape) - self.assertFalse(dialog.isVisible()) - self.assertEqual(dialog.result(), qt.QDialog.Rejected) - - def testDisplayAndClickCancel(self): - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - - button = testutils.findChildren(dialog, qt.QPushButton, name="cancel")[0] - self.mouseClick(button, qt.Qt.LeftButton) - self.assertFalse(dialog.isVisible()) - self.assertFalse(dialog.isVisible()) - self.assertEqual(dialog.result(), qt.QDialog.Rejected) - - def testDisplayAndClickLockedOpen(self): - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - - button = testutils.findChildren(dialog, qt.QPushButton, name="open")[0] - self.mouseClick(button, qt.Qt.LeftButton) - # open button locked, dialog is not closed - self.assertTrue(dialog.isVisible()) - self.assertEqual(dialog.result(), qt.QDialog.Rejected) - - def testSelectRoot_Activate(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - filename = _tmpDirectory + "/data.h5" - dialog.selectFile(os.path.dirname(filename)) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().index(filename) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - button = testutils.findChildren(dialog, qt.QPushButton, name="open")[0] - self.assertTrue(button.isEnabled()) - self.mouseClick(button, qt.Qt.LeftButton) - url = silx.io.url.DataUrl(dialog.selectedUrl()) - self.assertTrue(url.data_path() is not None) - self.assertFalse(dialog.isVisible()) - self.assertEqual(dialog.result(), qt.QDialog.Accepted) - - def testSelectGroup_Activate(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - filename = _tmpDirectory + "/data.h5" - dialog.selectFile(os.path.dirname(filename)) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().index(filename) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().indexFromH5Object(dialog._AbstractDataFileDialog__h5["/group"]) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - button = testutils.findChildren(dialog, qt.QPushButton, name="open")[0] - self.assertTrue(button.isEnabled()) - self.mouseClick(button, qt.Qt.LeftButton) - url = silx.io.url.DataUrl(dialog.selectedUrl()) - self.assertEqual(url.data_path(), "/group") - self.assertFalse(dialog.isVisible()) - self.assertEqual(dialog.result(), qt.QDialog.Accepted) - - def testSelectDataset_Activate(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - filename = _tmpDirectory + "/data.h5" - dialog.selectFile(os.path.dirname(filename)) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().index(filename) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().indexFromH5Object(dialog._AbstractDataFileDialog__h5["/scalar"]) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - button = testutils.findChildren(dialog, qt.QPushButton, name="open")[0] - self.assertTrue(button.isEnabled()) - self.mouseClick(button, qt.Qt.LeftButton) - url = silx.io.url.DataUrl(dialog.selectedUrl()) - self.assertEqual(url.data_path(), "/scalar") - self.assertFalse(dialog.isVisible()) - self.assertEqual(dialog.result(), qt.QDialog.Accepted) - - def testClickOnBackToParentTool(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - url = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] - action = testutils.findChildren(dialog, qt.QAction, name="toParentAction")[0] - toParentButton = testutils.getQToolButtonFromAction(action) - filename = _tmpDirectory + "/data/data.h5" - - # init state - path = silx.io.url.DataUrl(file_path=filename, data_path="/group/image").path() - dialog.selectUrl(path) - self.qWaitForPendingActions(dialog) - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/group/image").path() - self.assertSamePath(url.text(), path) - # test - self.mouseClick(toParentButton, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/").path() - self.assertSamePath(url.text(), path) - - self.mouseClick(toParentButton, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - self.assertSamePath(url.text(), _tmpDirectory + "/data") - - self.mouseClick(toParentButton, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - self.assertSamePath(url.text(), _tmpDirectory) - - def testClickOnBackToRootTool(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - url = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] - action = testutils.findChildren(dialog, qt.QAction, name="toRootFileAction")[0] - button = testutils.getQToolButtonFromAction(action) - filename = _tmpDirectory + "/data.h5" - - # init state - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/group/image").path() - dialog.selectUrl(path) - self.qWaitForPendingActions(dialog) - self.assertSamePath(url.text(), path) - self.assertTrue(button.isEnabled()) - # test - self.mouseClick(button, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/").path() - self.assertSamePath(url.text(), path) - # self.assertFalse(button.isEnabled()) - - def testClickOnBackToDirectoryTool(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - url = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] - action = testutils.findChildren(dialog, qt.QAction, name="toDirectoryAction")[0] - button = testutils.getQToolButtonFromAction(action) - filename = _tmpDirectory + "/data.h5" - - # init state - path = silx.io.url.DataUrl(file_path=filename, data_path="/group/image").path() - dialog.selectUrl(path) - self.qWaitForPendingActions(dialog) - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/group/image").path() - self.assertSamePath(url.text(), path) - self.assertTrue(button.isEnabled()) - # test - self.mouseClick(button, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - self.assertSamePath(url.text(), _tmpDirectory) - self.assertFalse(button.isEnabled()) - - # FIXME: There is an unreleased qt.QWidget without nameObject - # No idea where it come from. - self.allowedLeakingWidgets = 1 - - def testClickOnHistoryTools(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - url = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] - forwardAction = testutils.findChildren(dialog, qt.QAction, name="forwardAction")[0] - backwardAction = testutils.findChildren(dialog, qt.QAction, name="backwardAction")[0] - filename = _tmpDirectory + "/data.h5" - - dialog.setDirectory(_tmpDirectory) - self.qWaitForPendingActions(dialog) - # No way to use QTest.mouseDClick with QListView, QListWidget - # Then we feed the history using selectPath - dialog.selectUrl(filename) - self.qWaitForPendingActions(dialog) - path2 = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/").path() - dialog.selectUrl(path2) - self.qWaitForPendingActions(dialog) - path3 = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/group").path() - dialog.selectUrl(path3) - self.qWaitForPendingActions(dialog) - self.assertFalse(forwardAction.isEnabled()) - self.assertTrue(backwardAction.isEnabled()) - - button = testutils.getQToolButtonFromAction(backwardAction) - self.mouseClick(button, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - self.assertTrue(forwardAction.isEnabled()) - self.assertTrue(backwardAction.isEnabled()) - self.assertSamePath(url.text(), path2) - - button = testutils.getQToolButtonFromAction(forwardAction) - self.mouseClick(button, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - self.assertFalse(forwardAction.isEnabled()) - self.assertTrue(backwardAction.isEnabled()) - self.assertSamePath(url.text(), path3) - - def testSelectImageFromEdf(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - filename = _tmpDirectory + "/singleimage.edf" - url = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/scan_0/instrument/detector_0/data") - dialog.selectUrl(url.path()) - self.assertTrue(dialog._selectedData().shape, (100, 100)) - self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), url.path()) - - def testSelectImage(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - filename = _tmpDirectory + "/data.h5" - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/image").path() - dialog.selectUrl(path) - # test - self.assertTrue(dialog._selectedData().shape, (100, 100)) - self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) - - def testSelectScalar(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - filename = _tmpDirectory + "/data.h5" - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/scalar").path() - dialog.selectUrl(path) - # test - self.assertEqual(dialog._selectedData()[()], 10) - self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) - - def testSelectGroup(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - filename = _tmpDirectory + "/data.h5" - uri = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/group") - dialog.selectUrl(uri.path()) - self.qWaitForPendingActions(dialog) - # test - self.assertTrue(silx.io.is_group(dialog._selectedData())) - self.assertSamePath(dialog.selectedFile(), filename) - uri = silx.io.url.DataUrl(dialog.selectedUrl()) - self.assertSamePath(uri.data_path(), "/group") - - def testSelectRoot(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - filename = _tmpDirectory + "/data.h5" - uri = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/") - dialog.selectUrl(uri.path()) - self.qWaitForPendingActions(dialog) - # test - self.assertTrue(silx.io.is_file(dialog._selectedData())) - self.assertSamePath(dialog.selectedFile(), filename) - uri = silx.io.url.DataUrl(dialog.selectedUrl()) - self.assertSamePath(uri.data_path(), "/") - - def testSelectH5_Activate(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - dialog.selectUrl(_tmpDirectory) - self.qWaitForPendingActions(dialog) - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - filename = _tmpDirectory + "/data.h5" - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/").path() - index = browser.rootIndex().model().index(filename) - # click - browser.selectIndex(index) - # double click - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - # test - self.assertSamePath(dialog.selectedUrl(), path) - - def testSelectBadFileFormat_Activate(self): - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - dialog.selectUrl(_tmpDirectory) - self.qWaitForPendingActions(dialog) - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - filename = _tmpDirectory + "/badformat.h5" - index = browser.rootIndex().model().index(filename) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - # test - self.assertTrue(dialog.selectedUrl(), filename) - - def _countSelectableItems(self, model, rootIndex): - selectable = 0 - for i in range(model.rowCount(rootIndex)): - index = model.index(i, 0, rootIndex) - flags = model.flags(index) - isEnabled = (int(flags) & qt.Qt.ItemIsEnabled) != 0 - if isEnabled: - selectable += 1 - return selectable - - def testFilterExtensions(self): - if h5py is None: - self.skipTest("h5py is missing") - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - dialog.show() - self.qWaitForWindowExposed(dialog) - dialog.selectUrl(_tmpDirectory) - self.qWaitForPendingActions(dialog) - self.assertEqual(self._countSelectableItems(browser.model(), browser.rootIndex()), 4) - - -class TestDataFileDialog_FilterDataset(testutils.TestCaseQt, _UtilsMixin): - - def tearDown(self): - self._deleteDialog() - testutils.TestCaseQt.tearDown(self) - - def _createDialog(self): - dialog = DataFileDialog() - dialog.setFilterMode(DataFileDialog.FilterMode.ExistingDataset) - return dialog - - def testSelectGroup_Activate(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - filename = _tmpDirectory + "/data.h5" - dialog.selectFile(os.path.dirname(filename)) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().index(filename) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().indexFromH5Object(dialog._AbstractDataFileDialog__h5["/group"]) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - button = testutils.findChildren(dialog, qt.QPushButton, name="open")[0] - self.assertFalse(button.isEnabled()) - - def testSelectDataset_Activate(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - filename = _tmpDirectory + "/data.h5" - dialog.selectFile(os.path.dirname(filename)) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().index(filename) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().indexFromH5Object(dialog._AbstractDataFileDialog__h5["/scalar"]) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - button = testutils.findChildren(dialog, qt.QPushButton, name="open")[0] - self.assertTrue(button.isEnabled()) - self.mouseClick(button, qt.Qt.LeftButton) - url = silx.io.url.DataUrl(dialog.selectedUrl()) - self.assertEqual(url.data_path(), "/scalar") - self.assertFalse(dialog.isVisible()) - self.assertEqual(dialog.result(), qt.QDialog.Accepted) - - data = dialog.selectedData() - self.assertEqual(data, 10) - - -class TestDataFileDialog_FilterGroup(testutils.TestCaseQt, _UtilsMixin): - - def tearDown(self): - self._deleteDialog() - testutils.TestCaseQt.tearDown(self) - - def _createDialog(self): - dialog = DataFileDialog() - dialog.setFilterMode(DataFileDialog.FilterMode.ExistingGroup) - return dialog - - def testSelectGroup_Activate(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - filename = _tmpDirectory + "/data.h5" - dialog.selectFile(os.path.dirname(filename)) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().index(filename) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().indexFromH5Object(dialog._AbstractDataFileDialog__h5["/group"]) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - button = testutils.findChildren(dialog, qt.QPushButton, name="open")[0] - self.assertTrue(button.isEnabled()) - self.mouseClick(button, qt.Qt.LeftButton) - url = silx.io.url.DataUrl(dialog.selectedUrl()) - self.assertEqual(url.data_path(), "/group") - self.assertFalse(dialog.isVisible()) - self.assertEqual(dialog.result(), qt.QDialog.Accepted) - - self.assertRaises(Exception, dialog.selectedData) - - def testSelectDataset_Activate(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - filename = _tmpDirectory + "/data.h5" - dialog.selectFile(os.path.dirname(filename)) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().index(filename) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().indexFromH5Object(dialog._AbstractDataFileDialog__h5["/scalar"]) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - button = testutils.findChildren(dialog, qt.QPushButton, name="open")[0] - self.assertFalse(button.isEnabled()) - - -class TestDataFileDialog_FilterNXdata(testutils.TestCaseQt, _UtilsMixin): - - def tearDown(self): - self._deleteDialog() - testutils.TestCaseQt.tearDown(self) - - def _createDialog(self): - def customFilter(obj): - if "NX_class" in obj.attrs: - return obj.attrs["NX_class"] == u"NXdata" - return False - - dialog = DataFileDialog() - dialog.setFilterMode(DataFileDialog.FilterMode.ExistingGroup) - dialog.setFilterCallback(customFilter) - return dialog - - def testSelectGroupRefused_Activate(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - filename = _tmpDirectory + "/data.h5" - dialog.selectFile(os.path.dirname(filename)) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().index(filename) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().indexFromH5Object(dialog._AbstractDataFileDialog__h5["/group"]) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - button = testutils.findChildren(dialog, qt.QPushButton, name="open")[0] - self.assertFalse(button.isEnabled()) - - self.assertRaises(Exception, dialog.selectedData) - - def testSelectNXdataAccepted_Activate(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - filename = _tmpDirectory + "/data.h5" - dialog.selectFile(os.path.dirname(filename)) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().index(filename) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - # select, then double click on the file - index = browser.rootIndex().model().indexFromH5Object(dialog._AbstractDataFileDialog__h5["/nxdata"]) - browser.selectIndex(index) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - - button = testutils.findChildren(dialog, qt.QPushButton, name="open")[0] - self.assertTrue(button.isEnabled()) - self.mouseClick(button, qt.Qt.LeftButton) - url = silx.io.url.DataUrl(dialog.selectedUrl()) - self.assertEqual(url.data_path(), "/nxdata") - self.assertFalse(dialog.isVisible()) - self.assertEqual(dialog.result(), qt.QDialog.Accepted) - - -class TestDataFileDialogApi(testutils.TestCaseQt, _UtilsMixin): - - def tearDown(self): - self._deleteDialog() - testutils.TestCaseQt.tearDown(self) - - def _createDialog(self): - dialog = DataFileDialog() - return dialog - - def testSaveRestoreState(self): - dialog = self.createDialog() - dialog.setDirectory(_tmpDirectory) - self.qWaitForPendingActions(dialog) - state = dialog.saveState() - dialog = None - - dialog2 = self.createDialog() - result = dialog2.restoreState(state) - self.assertTrue(result) - dialog2 = None - - def printState(self): - """ - Print state of the ImageFileDialog. - - Can be used to add or regenerate `STATE_VERSION1_QT4` or - `STATE_VERSION1_QT5`. - - >>> ./run_tests.py -v silx.gui.dialog.test.test_datafiledialog.TestDataFileDialogApi.printState - """ - dialog = self.createDialog() - dialog.setDirectory("") - dialog.setHistory([]) - dialog.setSidebarUrls([]) - state = dialog.saveState() - string = "" - strings = [] - for i in range(state.size()): - d = state.data()[i] - if not isinstance(d, int): - d = ord(d) - if d > 0x20 and d < 0x7F: - string += chr(d) - else: - string += "\\x%02X" % d - if len(string) > 60: - strings.append(string) - string = "" - strings.append(string) - strings = ["b'%s'" % s for s in strings] - print() - print("\\\n".join(strings)) - - STATE_VERSION1_QT4 = b''\ - b'\x00\x00\x00Z\x00s\x00i\x00l\x00x\x00.\x00g\x00u\x00i\x00.\x00'\ - b'd\x00i\x00a\x00l\x00o\x00g\x00.\x00D\x00a\x00t\x00a\x00F\x00i'\ - b'\x00l\x00e\x00D\x00i\x00a\x00l\x00o\x00g\x00.\x00D\x00a\x00t\x00'\ - b'a\x00F\x00i\x00l\x00e\x00D\x00i\x00a\x00l\x00o\x00g\x00\x00\x00'\ - b'\x01\x00\x00\x00\x0C\x00\x00\x00\x00"\x00\x00\x00\xFF\x00\x00'\ - b'\x00\x00\x00\x00\x00\x03\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'\ - b'\xFF\xFF\x01\x00\x00\x00\x06\x01\x00\x00\x00\x01\x00\x00\x00\x00'\ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x00\x00\x00'\ - b'}\x00\x00\x00\x0E\x00B\x00r\x00o\x00w\x00s\x00e\x00r\x00\x00\x00'\ - b'\x01\x00\x00\x00\x0C\x00\x00\x00\x00Z\x00\x00\x00\xFF\x00\x00'\ - b'\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00'\ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ - b'\x00\x01\x90\x00\x00\x00\x04\x01\x01\x00\x00\x00\x00\x00\x00\x00'\ - b'\x00\x00\x00\x00\x00\x00\x00d\xFF\xFF\xFF\xFF\x00\x00\x00\x81'\ - b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x01\x90\x00\x00\x00\x04'\ - b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00'\ - b'\x01\xFF\xFF\xFF\xFF' - """Serialized state on Qt4. Generated using :meth:`printState`""" - - STATE_VERSION1_QT5 = b''\ - b'\x00\x00\x00Z\x00s\x00i\x00l\x00x\x00.\x00g\x00u\x00i\x00.\x00'\ - b'd\x00i\x00a\x00l\x00o\x00g\x00.\x00D\x00a\x00t\x00a\x00F\x00i'\ - b'\x00l\x00e\x00D\x00i\x00a\x00l\x00o\x00g\x00.\x00D\x00a\x00t\x00'\ - b'a\x00F\x00i\x00l\x00e\x00D\x00i\x00a\x00l\x00o\x00g\x00\x00\x00'\ - b'\x01\x00\x00\x00\x0C\x00\x00\x00\x00#\x00\x00\x00\xFF\x00\x00'\ - b'\x00\x01\x00\x00\x00\x03\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'\ - b'\xFF\xFF\x01\xFF\xFF\xFF\xFF\x01\x00\x00\x00\x01\x00\x00\x00\x00'\ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x00\x00'\ - b'\x00\xAA\x00\x00\x00\x0E\x00B\x00r\x00o\x00w\x00s\x00e\x00r\x00'\ - b'\x00\x00\x01\x00\x00\x00\x0C\x00\x00\x00\x00\x87\x00\x00\x00\xFF'\ - b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00'\ - b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ - b'\x00\x00\x00\x01\x90\x00\x00\x00\x04\x01\x01\x00\x00\x00\x00\x00'\ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00d\xFF\xFF\xFF\xFF\x00\x00'\ - b'\x00\x81\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00d\x00\x00'\ - b'\x00\x01\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x01\x00\x00'\ - b'\x00\x00\x00\x00\x00d\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00'\ - b'\x00d\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03\xE8\x00\xFF'\ - b'\xFF\xFF\xFF\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x01' - """Serialized state on Qt5. Generated using :meth:`printState`""" - - def testAvoidRestoreRegression_Version1(self): - version = qt.qVersion().split(".")[0] - if version == "4": - state = self.STATE_VERSION1_QT4 - elif version == "5": - state = self.STATE_VERSION1_QT5 - else: - self.skipTest("Resource not available") - - state = qt.QByteArray(state) - dialog = self.createDialog() - result = dialog.restoreState(state) - self.assertTrue(result) - - def testRestoreRobusness(self): - """What's happen if you try to open a config file with a different - binding.""" - state = qt.QByteArray(self.STATE_VERSION1_QT4) - dialog = self.createDialog() - dialog.restoreState(state) - state = qt.QByteArray(self.STATE_VERSION1_QT5) - dialog = None - dialog = self.createDialog() - dialog.restoreState(state) - - def testRestoreNonExistingDirectory(self): - directory = os.path.join(_tmpDirectory, "dir") - os.mkdir(directory) - dialog = self.createDialog() - dialog.setDirectory(directory) - self.qWaitForPendingActions(dialog) - state = dialog.saveState() - os.rmdir(directory) - dialog = None - - dialog2 = self.createDialog() - result = dialog2.restoreState(state) - self.assertTrue(result) - self.assertNotEquals(dialog2.directory(), directory) - - def testHistory(self): - dialog = self.createDialog() - history = dialog.history() - dialog.setHistory([]) - self.assertEqual(dialog.history(), []) - dialog.setHistory(history) - self.assertEqual(dialog.history(), history) - - def testSidebarUrls(self): - dialog = self.createDialog() - urls = dialog.sidebarUrls() - dialog.setSidebarUrls([]) - self.assertEqual(dialog.sidebarUrls(), []) - dialog.setSidebarUrls(urls) - self.assertEqual(dialog.sidebarUrls(), urls) - - def testDirectory(self): - dialog = self.createDialog() - self.qWaitForPendingActions(dialog) - dialog.selectUrl(_tmpDirectory) - self.qWaitForPendingActions(dialog) - self.assertSamePath(dialog.directory(), _tmpDirectory) - - def testBadFileFormat(self): - dialog = self.createDialog() - dialog.selectUrl(_tmpDirectory + "/badformat.h5") - self.qWaitForPendingActions(dialog) - self.assertIsNone(dialog._selectedData()) - - def testBadPath(self): - dialog = self.createDialog() - dialog.selectUrl("#$%/#$%") - self.qWaitForPendingActions(dialog) - self.assertIsNone(dialog._selectedData()) - - def testBadSubpath(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - self.qWaitForPendingActions(dialog) - - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - - filename = _tmpDirectory + "/data.h5" - url = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/group/foobar") - dialog.selectUrl(url.path()) - self.qWaitForPendingActions(dialog) - self.assertIsNotNone(dialog._selectedData()) - - # an existing node is browsed, but the wrong path is selected - index = browser.rootIndex() - obj = index.model().data(index, role=Hdf5TreeModel.H5PY_OBJECT_ROLE) - self.assertEqual(obj.name, "/group") - url = silx.io.url.DataUrl(dialog.selectedUrl()) - self.assertEqual(url.data_path(), "/group") - - def testUnsupportedSlicingPath(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - self.qWaitForPendingActions(dialog) - dialog.selectUrl(_tmpDirectory + "/data.h5?path=/cube&slice=0") - self.qWaitForPendingActions(dialog) - data = dialog._selectedData() - if data is None: - # Maybe nothing is selected - self.assertTrue(True) - else: - # Maybe the cube is selected but not sliced - self.assertEqual(len(data.shape), 3) - - -def suite(): - test_suite = unittest.TestSuite() - loadTests = unittest.defaultTestLoader.loadTestsFromTestCase - test_suite.addTest(loadTests(TestDataFileDialogInteraction)) - test_suite.addTest(loadTests(TestDataFileDialogApi)) - test_suite.addTest(loadTests(TestDataFileDialog_FilterDataset)) - test_suite.addTest(loadTests(TestDataFileDialog_FilterGroup)) - test_suite.addTest(loadTests(TestDataFileDialog_FilterNXdata)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/gui/dialog/test/test_imagefiledialog.py b/silx/gui/dialog/test/test_imagefiledialog.py deleted file mode 100644 index 66469f3..0000000 --- a/silx/gui/dialog/test/test_imagefiledialog.py +++ /dev/null @@ -1,815 +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__ = "05/10/2018" - - -import unittest -import tempfile -import numpy -import shutil -import os -import io -import weakref - -try: - import fabio -except ImportError: - fabio = None -try: - import h5py -except ImportError: - h5py = None - -import silx.io.url -from silx.gui import qt -from silx.gui.utils import testutils -from ..ImageFileDialog import ImageFileDialog -from silx.gui.colors import Colormap -from silx.gui.hdf5 import Hdf5TreeModel - -_tmpDirectory = None - - -def setUpModule(): - global _tmpDirectory - _tmpDirectory = tempfile.mkdtemp(prefix=__name__) - - data = numpy.arange(100 * 100) - data.shape = 100, 100 - - if fabio is not None: - filename = _tmpDirectory + "/singleimage.edf" - image = fabio.edfimage.EdfImage(data=data) - image.write(filename) - - filename = _tmpDirectory + "/multiframe.edf" - image = fabio.edfimage.EdfImage(data=data) - image.appendFrame(data=data + 1) - image.appendFrame(data=data + 2) - image.write(filename) - - filename = _tmpDirectory + "/singleimage.msk" - image = fabio.fit2dmaskimage.Fit2dMaskImage(data=data % 2 == 1) - image.write(filename) - - if h5py is not None: - filename = _tmpDirectory + "/data.h5" - f = h5py.File(filename, "w") - f["scalar"] = 10 - f["image"] = data - f["cube"] = [data, data + 1, data + 2] - f["complex_image"] = data * 1j - f["group/image"] = data - f.close() - - if h5py is not None: - directory = os.path.join(_tmpDirectory, "data") - os.mkdir(directory) - filename = os.path.join(directory, "data.h5") - f = h5py.File(filename, "w") - f["scalar"] = 10 - f["image"] = data - f["cube"] = [data, data + 1, data + 2] - f["complex_image"] = data * 1j - f["group/image"] = data - f.close() - - filename = _tmpDirectory + "/badformat.edf" - with io.open(filename, "wb") as f: - f.write(b"{\nHello Nurse!") - - -def tearDownModule(): - global _tmpDirectory - shutil.rmtree(_tmpDirectory) - _tmpDirectory = None - - -class _UtilsMixin(object): - - def createDialog(self): - self._deleteDialog() - self._dialog = self._createDialog() - return self._dialog - - def _createDialog(self): - return ImageFileDialog() - - def _deleteDialog(self): - if not hasattr(self, "_dialog"): - return - if self._dialog is not None: - ref = weakref.ref(self._dialog) - self._dialog = None - self.qWaitForDestroy(ref) - - def qWaitForPendingActions(self, dialog): - for _ in range(20): - if not dialog.hasPendingEvents(): - return - self.qWait(10) - raise RuntimeError("Still have pending actions") - - def assertSamePath(self, path1, path2): - path1_ = os.path.normcase(path1) - path2_ = os.path.normcase(path2) - if path1_ != path2_: - # Use the unittest API to log and display error - self.assertEqual(path1, path2) - - def assertNotSamePath(self, path1, path2): - path1_ = os.path.normcase(path1) - path2_ = os.path.normcase(path2) - if path1_ == path2_: - # Use the unittest API to log and display error - self.assertNotEquals(path1, path2) - - -class TestImageFileDialogInteraction(testutils.TestCaseQt, _UtilsMixin): - - def tearDown(self): - self._deleteDialog() - testutils.TestCaseQt.tearDown(self) - - def testDisplayAndKeyEscape(self): - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - - self.keyClick(dialog, qt.Qt.Key_Escape) - self.assertFalse(dialog.isVisible()) - self.assertEqual(dialog.result(), qt.QDialog.Rejected) - - def testDisplayAndClickCancel(self): - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - - button = testutils.findChildren(dialog, qt.QPushButton, name="cancel")[0] - self.mouseClick(button, qt.Qt.LeftButton) - self.assertFalse(dialog.isVisible()) - self.assertFalse(dialog.isVisible()) - self.assertEqual(dialog.result(), qt.QDialog.Rejected) - - def testDisplayAndClickLockedOpen(self): - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - - button = testutils.findChildren(dialog, qt.QPushButton, name="open")[0] - self.mouseClick(button, qt.Qt.LeftButton) - # open button locked, dialog is not closed - self.assertTrue(dialog.isVisible()) - self.assertEqual(dialog.result(), qt.QDialog.Rejected) - - def testDisplayAndClickOpen(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - self.assertTrue(dialog.isVisible()) - filename = _tmpDirectory + "/singleimage.edf" - dialog.selectFile(filename) - self.qWaitForPendingActions(dialog) - - button = testutils.findChildren(dialog, qt.QPushButton, name="open")[0] - self.assertTrue(button.isEnabled()) - self.mouseClick(button, qt.Qt.LeftButton) - self.assertFalse(dialog.isVisible()) - self.assertEqual(dialog.result(), qt.QDialog.Accepted) - - def testClickOnShortcut(self): - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - sidebar = testutils.findChildren(dialog, qt.QListView, name="sidebar")[0] - url = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - dialog.setDirectory(_tmpDirectory) - self.qWaitForPendingActions(dialog) - - self.assertSamePath(url.text(), _tmpDirectory) - - urls = sidebar.urls() - if len(urls) == 0: - self.skipTest("No sidebar path") - path = urls[0].path() - if path != "" and not os.path.exists(path): - self.skipTest("Sidebar path do not exists") - - index = sidebar.model().index(0, 0) - # rect = sidebar.visualRect(index) - # self.mouseClick(sidebar, qt.Qt.LeftButton, pos=rect.center()) - # Using mouse click is not working, let's use the selection API - sidebar.selectionModel().select(index, qt.QItemSelectionModel.ClearAndSelect) - self.qWaitForPendingActions(dialog) - - index = browser.rootIndex() - if not index.isValid(): - path = "" - else: - path = index.model().filePath(index) - self.assertNotSamePath(_tmpDirectory, path) - self.assertNotSamePath(url.text(), _tmpDirectory) - - def testClickOnDetailView(self): - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - action = testutils.findChildren(dialog, qt.QAction, name="detailModeAction")[0] - detailModeButton = testutils.getQToolButtonFromAction(action) - self.mouseClick(detailModeButton, qt.Qt.LeftButton) - self.assertEqual(dialog.viewMode(), qt.QFileDialog.Detail) - - action = testutils.findChildren(dialog, qt.QAction, name="listModeAction")[0] - listModeButton = testutils.getQToolButtonFromAction(action) - self.mouseClick(listModeButton, qt.Qt.LeftButton) - self.assertEqual(dialog.viewMode(), qt.QFileDialog.List) - - def testClickOnBackToParentTool(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - url = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] - action = testutils.findChildren(dialog, qt.QAction, name="toParentAction")[0] - toParentButton = testutils.getQToolButtonFromAction(action) - filename = _tmpDirectory + "/data/data.h5" - - # init state - path = silx.io.url.DataUrl(file_path=filename, data_path="/group/image").path() - dialog.selectUrl(path) - self.qWaitForPendingActions(dialog) - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/group/image").path() - self.assertSamePath(url.text(), path) - # test - self.mouseClick(toParentButton, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/").path() - self.assertSamePath(url.text(), path) - - self.mouseClick(toParentButton, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - self.assertSamePath(url.text(), _tmpDirectory + "/data") - - self.mouseClick(toParentButton, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - self.assertSamePath(url.text(), _tmpDirectory) - - def testClickOnBackToRootTool(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - url = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] - action = testutils.findChildren(dialog, qt.QAction, name="toRootFileAction")[0] - button = testutils.getQToolButtonFromAction(action) - filename = _tmpDirectory + "/data.h5" - - # init state - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/group/image").path() - dialog.selectUrl(path) - self.qWaitForPendingActions(dialog) - self.assertSamePath(url.text(), path) - self.assertTrue(button.isEnabled()) - # test - self.mouseClick(button, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/").path() - self.assertSamePath(url.text(), path) - # self.assertFalse(button.isEnabled()) - - def testClickOnBackToDirectoryTool(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - url = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] - action = testutils.findChildren(dialog, qt.QAction, name="toDirectoryAction")[0] - button = testutils.getQToolButtonFromAction(action) - filename = _tmpDirectory + "/data.h5" - - # init state - path = silx.io.url.DataUrl(file_path=filename, data_path="/group/image").path() - dialog.selectUrl(path) - self.qWaitForPendingActions(dialog) - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/group/image").path() - self.assertSamePath(url.text(), path) - self.assertTrue(button.isEnabled()) - # test - self.mouseClick(button, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - self.assertSamePath(url.text(), _tmpDirectory) - self.assertFalse(button.isEnabled()) - - # FIXME: There is an unreleased qt.QWidget without nameObject - # No idea where it come from. - self.allowedLeakingWidgets = 1 - - def testClickOnHistoryTools(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - url = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] - forwardAction = testutils.findChildren(dialog, qt.QAction, name="forwardAction")[0] - backwardAction = testutils.findChildren(dialog, qt.QAction, name="backwardAction")[0] - filename = _tmpDirectory + "/data.h5" - - dialog.setDirectory(_tmpDirectory) - self.qWaitForPendingActions(dialog) - # No way to use QTest.mouseDClick with QListView, QListWidget - # Then we feed the history using selectPath - dialog.selectUrl(filename) - self.qWaitForPendingActions(dialog) - path2 = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/").path() - dialog.selectUrl(path2) - self.qWaitForPendingActions(dialog) - path3 = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/group").path() - dialog.selectUrl(path3) - self.qWaitForPendingActions(dialog) - self.assertFalse(forwardAction.isEnabled()) - self.assertTrue(backwardAction.isEnabled()) - - button = testutils.getQToolButtonFromAction(backwardAction) - self.mouseClick(button, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - self.assertTrue(forwardAction.isEnabled()) - self.assertTrue(backwardAction.isEnabled()) - self.assertSamePath(url.text(), path2) - - button = testutils.getQToolButtonFromAction(forwardAction) - self.mouseClick(button, qt.Qt.LeftButton) - self.qWaitForPendingActions(dialog) - self.assertFalse(forwardAction.isEnabled()) - self.assertTrue(backwardAction.isEnabled()) - self.assertSamePath(url.text(), path3) - - def testSelectImageFromEdf(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - filename = _tmpDirectory + "/singleimage.edf" - path = filename - dialog.selectUrl(path) - self.assertTrue(dialog.selectedImage().shape, (100, 100)) - self.assertSamePath(dialog.selectedFile(), filename) - path = silx.io.url.DataUrl(scheme="fabio", file_path=filename).path() - self.assertSamePath(dialog.selectedUrl(), path) - - def testSelectImageFromEdf_Activate(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - dialog.selectUrl(_tmpDirectory) - self.qWaitForPendingActions(dialog) - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - filename = _tmpDirectory + "/singleimage.edf" - path = silx.io.url.DataUrl(scheme="fabio", file_path=filename).path() - index = browser.rootIndex().model().index(filename) - # click - browser.selectIndex(index) - # double click - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - # test - self.assertTrue(dialog.selectedImage().shape, (100, 100)) - self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) - - def testSelectFrameFromEdf(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - filename = _tmpDirectory + "/multiframe.edf" - path = silx.io.url.DataUrl(scheme="fabio", file_path=filename, data_slice=(1,)).path() - dialog.selectUrl(path) - # test - image = dialog.selectedImage() - self.assertTrue(image.shape, (100, 100)) - self.assertTrue(image[0, 0], 1) - self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) - - def testSelectImageFromMsk(self): - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - filename = _tmpDirectory + "/singleimage.msk" - path = silx.io.url.DataUrl(scheme="fabio", file_path=filename).path() - dialog.selectUrl(path) - # test - self.assertTrue(dialog.selectedImage().shape, (100, 100)) - self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) - - def testSelectImageFromH5(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - filename = _tmpDirectory + "/data.h5" - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/image").path() - dialog.selectUrl(path) - # test - self.assertTrue(dialog.selectedImage().shape, (100, 100)) - self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) - - def testSelectH5_Activate(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - dialog.selectUrl(_tmpDirectory) - self.qWaitForPendingActions(dialog) - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - filename = _tmpDirectory + "/data.h5" - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/").path() - index = browser.rootIndex().model().index(filename) - # click - browser.selectIndex(index) - # double click - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - # test - self.assertSamePath(dialog.selectedUrl(), path) - - def testSelectFrameFromH5(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - filename = _tmpDirectory + "/data.h5" - path = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/cube", data_slice=(1, )).path() - dialog.selectUrl(path) - # test - self.assertTrue(dialog.selectedImage().shape, (100, 100)) - self.assertTrue(dialog.selectedImage()[0, 0], 1) - self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) - - def testSelectBadFileFormat_Activate(self): - dialog = self.createDialog() - dialog.show() - self.qWaitForWindowExposed(dialog) - - # init state - dialog.selectUrl(_tmpDirectory) - self.qWaitForPendingActions(dialog) - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - filename = _tmpDirectory + "/badformat.edf" - index = browser.rootIndex().model().index(filename) - browser.activated.emit(index) - self.qWaitForPendingActions(dialog) - # test - self.assertTrue(dialog.selectedUrl(), filename) - - def _countSelectableItems(self, model, rootIndex): - selectable = 0 - for i in range(model.rowCount(rootIndex)): - index = model.index(i, 0, rootIndex) - flags = model.flags(index) - isEnabled = (int(flags) & qt.Qt.ItemIsEnabled) != 0 - if isEnabled: - selectable += 1 - return selectable - - def testFilterExtensions(self): - if h5py is None: - self.skipTest("h5py is missing") - if fabio is None: - self.skipTest("fabio is missing") - dialog = self.createDialog() - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - filters = testutils.findChildren(dialog, qt.QWidget, name="fileTypeCombo")[0] - dialog.show() - self.qWaitForWindowExposed(dialog) - dialog.selectUrl(_tmpDirectory) - self.qWaitForPendingActions(dialog) - self.assertEqual(self._countSelectableItems(browser.model(), browser.rootIndex()), 6) - - codecName = fabio.edfimage.EdfImage.codec_name() - index = filters.indexFromCodec(codecName) - filters.setCurrentIndex(index) - filters.activated[int].emit(index) - self.qWait(50) - self.assertEqual(self._countSelectableItems(browser.model(), browser.rootIndex()), 4) - - codecName = fabio.fit2dmaskimage.Fit2dMaskImage.codec_name() - index = filters.indexFromCodec(codecName) - filters.setCurrentIndex(index) - filters.activated[int].emit(index) - self.qWait(50) - self.assertEqual(self._countSelectableItems(browser.model(), browser.rootIndex()), 2) - - -class TestImageFileDialogApi(testutils.TestCaseQt, _UtilsMixin): - - def tearDown(self): - self._deleteDialog() - testutils.TestCaseQt.tearDown(self) - - def testSaveRestoreState(self): - dialog = self.createDialog() - dialog.setDirectory(_tmpDirectory) - colormap = Colormap(normalization=Colormap.LOGARITHM) - dialog.setColormap(colormap) - self.qWaitForPendingActions(dialog) - state = dialog.saveState() - dialog = None - - dialog2 = self.createDialog() - result = dialog2.restoreState(state) - self.qWaitForPendingActions(dialog2) - self.assertTrue(result) - self.assertTrue(dialog2.colormap().getNormalization(), "log") - - def printState(self): - """ - Print state of the ImageFileDialog. - - Can be used to add or regenerate `STATE_VERSION1_QT4` or - `STATE_VERSION1_QT5`. - - >>> ./run_tests.py -v silx.gui.dialog.test.test_imagefiledialog.TestImageFileDialogApi.printState - """ - dialog = self.createDialog() - colormap = Colormap(normalization=Colormap.LOGARITHM) - dialog.setDirectory("") - dialog.setHistory([]) - dialog.setColormap(colormap) - dialog.setSidebarUrls([]) - state = dialog.saveState() - string = "" - strings = [] - for i in range(state.size()): - d = state.data()[i] - if not isinstance(d, int): - d = ord(d) - if d > 0x20 and d < 0x7F: - string += chr(d) - else: - string += "\\x%02X" % d - if len(string) > 60: - strings.append(string) - string = "" - strings.append(string) - strings = ["b'%s'" % s for s in strings] - print() - print("\\\n".join(strings)) - - STATE_VERSION1_QT4 = b''\ - b'\x00\x00\x00^\x00s\x00i\x00l\x00x\x00.\x00g\x00u\x00i\x00.\x00'\ - b'd\x00i\x00a\x00l\x00o\x00g\x00.\x00I\x00m\x00a\x00g\x00e\x00F'\ - b'\x00i\x00l\x00e\x00D\x00i\x00a\x00l\x00o\x00g\x00.\x00I\x00m\x00'\ - b'a\x00g\x00e\x00F\x00i\x00l\x00e\x00D\x00i\x00a\x00l\x00o\x00g'\ - b'\x00\x00\x00\x01\x00\x00\x00\x0C\x00\x00\x00\x00"\x00\x00\x00'\ - b'\xFF\x00\x00\x00\x00\x00\x00\x00\x03\xFF\xFF\xFF\xFF\xFF\xFF\xFF'\ - b'\xFF\xFF\xFF\xFF\xFF\x01\x00\x00\x00\x06\x01\x00\x00\x00\x01\x00'\ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00'\ - b'\x00\x00\x00}\x00\x00\x00\x0E\x00B\x00r\x00o\x00w\x00s\x00e\x00'\ - b'r\x00\x00\x00\x01\x00\x00\x00\x0C\x00\x00\x00\x00Z\x00\x00\x00'\ - b'\xFF\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00'\ - b'\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ - b'\x00\x00\x00\x00\x01\x90\x00\x00\x00\x04\x01\x01\x00\x00\x00\x00'\ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\xFF\xFF\xFF\xFF\x00'\ - b'\x00\x00\x81\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x01\x90\x00'\ - b'\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00'\ - b'\x00\x00\x0C\x00\x00\x00\x000\x00\x00\x00\x10\x00C\x00o\x00l\x00'\ - b'o\x00r\x00m\x00a\x00p\x00\x00\x00\x01\x00\x00\x00\x08\x00g\x00'\ - b'r\x00a\x00y\x01\x01\x00\x00\x00\x06\x00l\x00o\x00g' - """Serialized state on Qt4. Generated using :meth:`printState`""" - - STATE_VERSION1_QT5 = b''\ - b'\x00\x00\x00^\x00s\x00i\x00l\x00x\x00.\x00g\x00u\x00i\x00.\x00'\ - b'd\x00i\x00a\x00l\x00o\x00g\x00.\x00I\x00m\x00a\x00g\x00e\x00F'\ - b'\x00i\x00l\x00e\x00D\x00i\x00a\x00l\x00o\x00g\x00.\x00I\x00m\x00'\ - b'a\x00g\x00e\x00F\x00i\x00l\x00e\x00D\x00i\x00a\x00l\x00o\x00g'\ - b'\x00\x00\x00\x01\x00\x00\x00\x0C\x00\x00\x00\x00#\x00\x00\x00'\ - b'\xFF\x00\x00\x00\x01\x00\x00\x00\x03\xFF\xFF\xFF\xFF\xFF\xFF\xFF'\ - b'\xFF\xFF\xFF\xFF\xFF\x01\xFF\xFF\xFF\xFF\x01\x00\x00\x00\x01\x00'\ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C'\ - b'\x00\x00\x00\x00\xAA\x00\x00\x00\x0E\x00B\x00r\x00o\x00w\x00s'\ - b'\x00e\x00r\x00\x00\x00\x01\x00\x00\x00\x0C\x00\x00\x00\x00\x87'\ - b'\x00\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00'\ - b'\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ - b'\x00\x00\x00\x00\x00\x00\x00\x01\x90\x00\x00\x00\x04\x01\x01\x00'\ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\xFF\xFF'\ - b'\xFF\xFF\x00\x00\x00\x81\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00'\ - b'\x00d\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00'\ - b'\x01\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x01\x00\x00\x00'\ - b'\x00\x00\x00\x00d\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03'\ - b'\xE8\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00'\ - b'\x00\x0C\x00\x00\x00\x000\x00\x00\x00\x10\x00C\x00o\x00l\x00o'\ - b'\x00r\x00m\x00a\x00p\x00\x00\x00\x01\x00\x00\x00\x08\x00g\x00'\ - b'r\x00a\x00y\x01\x01\x00\x00\x00\x06\x00l\x00o\x00g' - """Serialized state on Qt5. Generated using :meth:`printState`""" - - def testAvoidRestoreRegression_Version1(self): - version = qt.qVersion().split(".")[0] - if version == "4": - state = self.STATE_VERSION1_QT4 - elif version == "5": - state = self.STATE_VERSION1_QT5 - else: - self.skipTest("Resource not available") - - state = qt.QByteArray(state) - dialog = self.createDialog() - result = dialog.restoreState(state) - self.assertTrue(result) - colormap = dialog.colormap() - self.assertTrue(colormap.getNormalization(), "log") - - def testRestoreRobusness(self): - """What's happen if you try to open a config file with a different - binding.""" - state = qt.QByteArray(self.STATE_VERSION1_QT4) - dialog = self.createDialog() - dialog.restoreState(state) - state = qt.QByteArray(self.STATE_VERSION1_QT5) - dialog = None - dialog = self.createDialog() - dialog.restoreState(state) - - def testRestoreNonExistingDirectory(self): - directory = os.path.join(_tmpDirectory, "dir") - os.mkdir(directory) - dialog = self.createDialog() - dialog.setDirectory(directory) - self.qWaitForPendingActions(dialog) - state = dialog.saveState() - os.rmdir(directory) - dialog = None - - dialog2 = self.createDialog() - result = dialog2.restoreState(state) - self.assertTrue(result) - self.assertNotEquals(dialog2.directory(), directory) - - def testHistory(self): - dialog = self.createDialog() - history = dialog.history() - dialog.setHistory([]) - self.assertEqual(dialog.history(), []) - dialog.setHistory(history) - self.assertEqual(dialog.history(), history) - - def testSidebarUrls(self): - dialog = self.createDialog() - urls = dialog.sidebarUrls() - dialog.setSidebarUrls([]) - self.assertEqual(dialog.sidebarUrls(), []) - dialog.setSidebarUrls(urls) - self.assertEqual(dialog.sidebarUrls(), urls) - - def testColomap(self): - dialog = self.createDialog() - colormap = dialog.colormap() - self.assertEqual(colormap.getNormalization(), "linear") - colormap = Colormap(normalization=Colormap.LOGARITHM) - dialog.setColormap(colormap) - self.assertEqual(colormap.getNormalization(), "log") - - def testDirectory(self): - dialog = self.createDialog() - self.qWaitForPendingActions(dialog) - dialog.selectUrl(_tmpDirectory) - self.qWaitForPendingActions(dialog) - self.assertSamePath(dialog.directory(), _tmpDirectory) - - def testBadDataType(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.selectUrl(_tmpDirectory + "/data.h5::/complex_image") - self.qWaitForPendingActions(dialog) - self.assertIsNone(dialog._selectedData()) - - def testBadDataShape(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - dialog.selectUrl(_tmpDirectory + "/data.h5::/unknown") - self.qWaitForPendingActions(dialog) - self.assertIsNone(dialog._selectedData()) - - def testBadDataFormat(self): - dialog = self.createDialog() - dialog.selectUrl(_tmpDirectory + "/badformat.edf") - self.qWaitForPendingActions(dialog) - self.assertIsNone(dialog._selectedData()) - - def testBadPath(self): - dialog = self.createDialog() - dialog.selectUrl("#$%/#$%") - self.qWaitForPendingActions(dialog) - self.assertIsNone(dialog._selectedData()) - - def testBadSubpath(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - self.qWaitForPendingActions(dialog) - - browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] - - filename = _tmpDirectory + "/data.h5" - url = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/group/foobar") - dialog.selectUrl(url.path()) - self.qWaitForPendingActions(dialog) - self.assertIsNone(dialog._selectedData()) - - # an existing node is browsed, but the wrong path is selected - index = browser.rootIndex() - obj = index.model().data(index, role=Hdf5TreeModel.H5PY_OBJECT_ROLE) - self.assertEqual(obj.name, "/group") - url = silx.io.url.DataUrl(dialog.selectedUrl()) - self.assertEqual(url.data_path(), "/group") - - def testBadSlicingPath(self): - if h5py is None: - self.skipTest("h5py is missing") - dialog = self.createDialog() - self.qWaitForPendingActions(dialog) - dialog.selectUrl(_tmpDirectory + "/data.h5::/cube[a;45,-90]") - self.qWaitForPendingActions(dialog) - self.assertIsNone(dialog._selectedData()) - - -def suite(): - test_suite = unittest.TestSuite() - loadTests = unittest.defaultTestLoader.loadTestsFromTestCase - test_suite.addTest(loadTests(TestImageFileDialogInteraction)) - test_suite.addTest(loadTests(TestImageFileDialogApi)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/gui/dialog/utils.py b/silx/gui/dialog/utils.py deleted file mode 100644 index 1c16b44..0000000 --- a/silx/gui/dialog/utils.py +++ /dev/null @@ -1,104 +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. -# -# ###########################################################################*/ -""" -This module contains utilitaries used by other dialog modules. -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "25/10/2017" - -import os -import sys -import types -from silx.gui import qt -from silx.third_party import six - - -def samefile(path1, path2): - """Portable :func:`os.path.samepath` function. - - :param str path1: A path to a file - :param str path2: Another path to a file - :rtype: bool - """ - if six.PY2 and sys.platform == "win32": - path1 = os.path.normcase(path1) - path2 = os.path.normcase(path2) - return path1 == path2 - if path1 == path2: - return True - if path1 == "": - return False - if path2 == "": - return False - return os.path.samefile(path1, path2) - - -def findClosestSubPath(hdf5Object, path): - """Find the closest existing path from the hdf5Object using a subset of the - provided path. - - Returns None if no path found. It is possible if the path is a relative - path. - - :param h5py.Node hdf5Object: An HDF5 node - :param str path: A path - :rtype: str - """ - if path in ["", "/"]: - return "/" - names = path.split("/") - if path[0] == "/": - names.pop(0) - for i in range(len(names)): - n = len(names) - i - path2 = "/".join(names[0:n]) - if path2 == "": - return "" - if path2 in hdf5Object: - return path2 - - if path[0] == "/": - return "/" - return None - - -def patchToConsumeReturnKey(widget): - """ - Monkey-patch a widget to consume the return key instead of propagating it - to the dialog. - """ - assert(not hasattr(widget, "_oldKeyPressEvent")) - - def keyPressEvent(self, event): - k = event.key() - result = self._oldKeyPressEvent(event) - if k in [qt.Qt.Key_Return, qt.Qt.Key_Enter]: - event.accept() - return result - - widget._oldKeyPressEvent = widget.keyPressEvent - widget.keyPressEvent = types.MethodType(keyPressEvent, widget) |