summaryrefslogtreecommitdiff
path: root/silx/gui/dialog
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/dialog')
-rw-r--r--silx/gui/dialog/AbstractDataFileDialog.py1720
-rw-r--r--silx/gui/dialog/ColormapDialog.py987
-rw-r--r--silx/gui/dialog/DataFileDialog.py342
-rw-r--r--silx/gui/dialog/DatasetDialog.py122
-rw-r--r--silx/gui/dialog/FileTypeComboBox.py213
-rw-r--r--silx/gui/dialog/GroupDialog.py230
-rw-r--r--silx/gui/dialog/ImageFileDialog.py338
-rw-r--r--silx/gui/dialog/SafeFileIconProvider.py154
-rw-r--r--silx/gui/dialog/SafeFileSystemModel.py802
-rw-r--r--silx/gui/dialog/__init__.py29
-rw-r--r--silx/gui/dialog/setup.py40
-rw-r--r--silx/gui/dialog/test/__init__.py49
-rw-r--r--silx/gui/dialog/test/test_colormapdialog.py398
-rw-r--r--silx/gui/dialog/test/test_datafiledialog.py995
-rw-r--r--silx/gui/dialog/test/test_imagefiledialog.py815
-rw-r--r--silx/gui/dialog/utils.py104
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)