# 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.
#
# ###########################################################################*/
"""
This module contains an :class:`AbstractDataFileDialog`.
"""
__authors__ = ["V. Valls"]
__license__ = "MIT"
__date__ = "05/03/2019"
import sys
import os
import logging
import functools
from distutils.version import LooseVersion
import numpy
import six
import silx.io.url
from silx.gui import qt
from silx.gui.hdf5.Hdf5TreeModel import Hdf5TreeModel
from . import utils
from .FileTypeComboBox import FileTypeComboBox
import fabio
_logger = logging.getLogger(__name__)
DEFAULT_SIDEBAR_URL = True
"""Set it to false to disable initilializing of the sidebar urls with the
default Qt list. This could allow to disable a behaviour known to segfault on
some version of PyQt."""
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 = []
version = LooseVersion(qt.qVersion())
feed_sidebar = True
if not DEFAULT_SIDEBAR_URL:
_logger.debug("Skip default sidebar URLs (from setted variable)")
feed_sidebar = False
elif version.version[0] == 4 and sys.platform in ["win32"]:
# Avoid locking the GUI 5min in case of use of network driver
_logger.debug("Skip default sidebar URLs (avoid lock when using network drivers)")
feed_sidebar = False
elif version < LooseVersion("5.11.2") and qt.BINDING == "PyQt5" and sys.platform in ["linux", "linux2"]:
# Avoid segfault on PyQt5 + gtk
_logger.debug("Skip default sidebar URLs (avoid PyQt5 segfault)")
feed_sidebar = False
if feed_sidebar:
# 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
if self.__fabioFile.nframes == 1:
return [slice(None), slice(None)]
return [self.__fabioFile.nframes, slice(None), slice(None)]
def __getitem__(self, selector):
if self.__fabioFile.nframes == 1 and selector == tuple():
return self.__fabioFile.data
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
[docs]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.__openedFiles = []
"""Store the list of files opened by the model itself."""
# FIXME: It should be managed one by one by Hdf5Item itself
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)
# 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[:] = []
[docs] 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
[docs] 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()
[docs] def viewMode(self):
"""Returns the current view mode.
:rtype: qt.QFileDialog.ViewMode
"""
return self.__browser.viewMode()
[docs] 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
if self.__fileModel is None:
return
index = self.__fileModel.setRootPath(path)
if not index.isValid():
# There is a problem with this path
# No asynchronous process will be waked up
self.__processing -= 1
self.__browser.setRootIndex(index, model=self.__fileModel)
self.__clearData()
self.__updatePath()
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
if self.__fileModel is None:
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:
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 self._isFabioFilesSupported():
openners.append(self.__openFabioFile)
else:
if 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
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.setData(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 isinstance(data, _FabioData):
data = data[()]
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.isUsed():
slicing = self.__selectorWidget.slicing()
if slicing == tuple():
slicing = None
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.selectSlicing(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
[docs] def setDirectory(self, path):
"""Sets the data dialog's current directory."""
self.__fileModel_setRootPath(path)
[docs] def selectedFile(self):
"""Returns the file path containing the selected data.
:rtype: str
"""
return self.__selectedFile
[docs] 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
[docs] 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()
[docs] 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()
[docs] 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)
[docs] 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
[docs] def selectedNameFilter(self):
"""Returns the filter that the user selected in the file dialog."""
return self.__fileTypeCombo.currentText()
# History
[docs] 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
# State
__serialVersion = 1
"""Store the current version of the serialized data"""
@classmethod
def qualifiedName(cls):
return "%s.%s" % (cls.__module__, cls.__name__)
[docs] 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
[docs] 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