Skip to main content
`Ctrl`+`K`

# Source code for silx.gui.dialog.AbstractDataFileDialog

    # /*##########################################################################
    #
    # Copyright (c) 2016-2022 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

    import numpy

    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 = tuple(map(int, qt.qVersion().split(".")[:3]))
            feed_sidebar = True

            if not DEFAULT_SIDEBAR_URL:
                _logger.debug("Skip default sidebar URLs (from setted variable)")
                feed_sidebar = False
            elif (
                version < (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(qt.QFileDialog.ViewMode(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 = "Browser"
            stream.writeQString(nameId)
            stream.writeInt32(self.__serialVersion)
            stream.writeQVariant(self.__detailView.header().saveState())
            viewMode = self.viewMode()
            if qt.BINDING in ("PyQt6", "PySide6"):  # No auto conversion to int
                viewMode = viewMode.value
            stream.writeInt32(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, int):
                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.__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

            # 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[:] = []

        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.AdjustToMinimumContentsLengthWithIcon
            )
            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
            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

        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 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(qt.QFileDialog.ViewMode(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("%s" % s)
            stream.writeInt32(self.__serialVersion)
            stream.writeQVariant(self.__splitter.saveState())
            strings = ["%s" % s.toString() for s in self.sidebarUrls()]
            stream.writeQStringList(strings)
            strings = ["%s" % s for s in self.history()]
            stream.writeQStringList(strings)
            stream.writeQString("%s" % self.directory())
            stream.writeQVariant(self.__browser.saveState())
            viewMode = self.viewMode()
            if qt.BINDING in ("PyQt6", "PySide6"):  # No auto conversion to int
                viewMode = viewMode.value
            stream.writeInt32(viewMode)
            colormap = self.colormap()
            if colormap is not None:
                stream.writeQVariant(self.colormap().saveState())
            else:
                stream.writeQVariant(None)

            return data