summaryrefslogtreecommitdiff
path: root/silx/app
diff options
context:
space:
mode:
Diffstat (limited to 'silx/app')
-rw-r--r--silx/app/convert.py14
-rw-r--r--silx/app/test_.py7
-rw-r--r--silx/app/view/About.py44
-rw-r--r--silx/app/view/CustomNxdataWidget.py8
-rw-r--r--silx/app/view/DataPanel.py35
-rw-r--r--silx/app/view/Viewer.py149
-rw-r--r--silx/app/view/test/test_view.py2
7 files changed, 226 insertions, 33 deletions
diff --git a/silx/app/convert.py b/silx/app/convert.py
index cd48deb..a8c2783 100644
--- a/silx/app/convert.py
+++ b/silx/app/convert.py
@@ -57,22 +57,22 @@ def c_format_string_to_re(pattern_string):
:param pattern_string: C style format string with integer patterns
(e.g. "%d", "%04d").
Not supported: fixed length padded with whitespaces (e.g "%4d", "%-4d")
- :return: Equivalent regular expression (e.g. "\d+", "\d{4}")
+ :return: Equivalent regular expression (e.g. "\\d+", "\\d{4}")
"""
# escape dots and backslashes
pattern_string = pattern_string.replace("\\", "\\\\")
- pattern_string = pattern_string.replace(".", "\.")
+ pattern_string = pattern_string.replace(".", r"\.")
# %d
- pattern_string = pattern_string.replace("%d", "([-+]?\d+)")
+ pattern_string = pattern_string.replace("%d", r"([-+]?\d+)")
# %0nd
- for sub_pattern in re.findall("%0\d+d", pattern_string):
- n = int(re.search("%0(\d+)d", sub_pattern).group(1))
+ for sub_pattern in re.findall(r"%0\d+d", pattern_string):
+ n = int(re.search(r"%0(\d+)d", sub_pattern).group(1))
if n == 1:
- re_sub_pattern = "([+-]?\d)"
+ re_sub_pattern = r"([+-]?\d)"
else:
- re_sub_pattern = "([\d+-]\d{%d})" % (n - 1)
+ re_sub_pattern = r"([\d+-]\d{%d})" % (n - 1)
pattern_string = pattern_string.replace(sub_pattern, re_sub_pattern, 1)
return pattern_string
diff --git a/silx/app/test_.py b/silx/app/test_.py
index 2623c04..a8e58bf 100644
--- a/silx/app/test_.py
+++ b/silx/app/test_.py
@@ -1,6 +1,6 @@
# coding: utf-8
# /*##########################################################################
-# Copyright (C) 2016 European Synchrotron Radiation Facility
+# 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
@@ -99,7 +99,7 @@ def main(argv):
"INFO messages. Use -vv for full verbosity, " +
"including debug messages and test help strings.")
parser.add_argument("--qt-binding", dest="qt_binding", default=None,
- help="Force using a Qt binding, from 'PyQt4', 'PyQt5', or 'PySide'")
+ help="Force using a Qt binding: 'PyQt5' or 'PySide2'")
utils.test_options.add_parser_argument(parser)
options = parser.parse_args(argv[1:])
@@ -128,6 +128,9 @@ def main(argv):
elif binding == "pyside":
_logger.info("Force using PySide")
import PySide.QtCore # noqa
+ elif binding == "pyside2":
+ _logger.info("Force using PySide2")
+ import PySide2.QtCore # noqa
else:
raise ValueError("Qt binding '%s' is unknown" % options.qt_binding)
diff --git a/silx/app/view/About.py b/silx/app/view/About.py
index 07306ef..4b804f2 100644
--- a/silx/app/view/About.py
+++ b/silx/app/view/About.py
@@ -25,7 +25,7 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "05/06/2018"
+__date__ = "05/07/2018"
import sys
@@ -147,6 +147,15 @@ class About(qt.QDialog):
template = '<b>%s</b> is <font color="red">not loaded</font>'
return template % name
+ @staticmethod
+ def __formatOptionalFilters(name, isAvailable):
+ """Utils to format availability of features"""
+ if isAvailable:
+ template = '<b>%s</b> is <font color="green">available</font>'
+ else:
+ template = '<b>%s</b> is <font color="red">not available</font>'
+ return template % name
+
def __updateText(self):
"""Update the content of the dialog according to the settings."""
import silx._version
@@ -174,14 +183,29 @@ class About(qt.QDialog):
</p>
"""
- hdf5pluginLoaded = "hdf5plugin" in sys.modules
- fabioLoaded = "fabio" in sys.modules
- h5pyLoaded = "h5py" in sys.modules
-
- optional_lib = []
- optional_lib.append(self.__formatOptionalLibraries("FabIO", fabioLoaded))
- optional_lib.append(self.__formatOptionalLibraries("H5py", h5pyLoaded))
- optional_lib.append(self.__formatOptionalLibraries("hdf5plugin", hdf5pluginLoaded))
+ optionals = []
+ optionals.append(self.__formatOptionalLibraries("H5py", "h5py" in sys.modules))
+ optionals.append(self.__formatOptionalLibraries("FabIO", "fabio" in sys.modules))
+
+ try:
+ import h5py.version
+ if h5py.version.hdf5_version_tuple >= (1, 10, 2):
+ # Previous versions only return True if the filter was first used
+ # to decode a dataset
+ import h5py.h5z
+ FILTER_LZ4 = 32004
+ FILTER_BITSHUFFLE = 32008
+ filters = [
+ ("HDF5 LZ4 filter", FILTER_LZ4),
+ ("HDF5 Bitshuffle filter", FILTER_BITSHUFFLE),
+ ]
+ for name, filterId in filters:
+ isAvailable = h5py.h5z.filter_avail(filterId)
+ optionals.append(self.__formatOptionalFilters(name, isAvailable))
+ else:
+ optionals.append(self.__formatOptionalLibraries("hdf5plugin", "hdf5plugin" in sys.modules))
+ except ImportError:
+ pass
# Access to the logo in SVG or PNG
logo = icons.getQFile("../logo/silx")
@@ -194,7 +218,7 @@ class About(qt.QDialog):
qt_binding=qt.BINDING,
qt_version=qt.qVersion(),
python_version=sys.version.replace("\n", "<br />"),
- optional_lib="<br />".join(optional_lib),
+ optional_lib="<br />".join(optionals),
silx_image_path=logo.fileName()
)
diff --git a/silx/app/view/CustomNxdataWidget.py b/silx/app/view/CustomNxdataWidget.py
index 02ae6c0..72c9940 100644
--- a/silx/app/view/CustomNxdataWidget.py
+++ b/silx/app/view/CustomNxdataWidget.py
@@ -944,10 +944,10 @@ class CustomNxdataWidget(qt.QTreeView):
if edited:
item.setAxesDatasets(datasets)
- dataset = item.getSignalDataset()
- newDataset = self.__replaceDatasetRoot(dataset, removedRoot, loadedRoot)
- if dataset is not newDataset:
- item.setSignalDataset(newDataset)
+ dataset = item.getSignalDataset()
+ newDataset = self.__replaceDatasetRoot(dataset, removedRoot, loadedRoot)
+ if dataset is not newDataset:
+ item.setSignalDataset(newDataset)
def __replaceDatasetRoot(self, dataset, fromRoot, toRoot):
"""
diff --git a/silx/app/view/DataPanel.py b/silx/app/view/DataPanel.py
index 0653f74..5d87381 100644
--- a/silx/app/view/DataPanel.py
+++ b/silx/app/view/DataPanel.py
@@ -25,9 +25,10 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "06/06/2018"
+__date__ = "12/10/2018"
import logging
+import os.path
from silx.gui import qt
from silx.gui.data.DataViewerFrame import DataViewerFrame
@@ -45,6 +46,26 @@ class _HeaderLabel(qt.QLabel):
def sizeHint(self):
return qt.QSize(10, 30)
+ def minimumSizeHint(self):
+ return qt.QSize(10, 30)
+
+ def setData(self, filename, path):
+ if filename == "" and path == "":
+ text = ""
+ elif filename == "":
+ text = path
+ else:
+ text = "%s::%s" % (filename, path)
+ self.setText(text)
+ tooltip = ""
+ template = "<li><b>%s</b>: %s</li>"
+ tooltip += template % ("Directory", os.path.dirname(filename))
+ tooltip += template % ("File name", os.path.basename(filename))
+ tooltip += template % ("Data path", path)
+ tooltip = "<ul>%s</ul>" % tooltip
+ tooltip = "<html>%s</html>" % tooltip
+ self.setToolTip(tooltip)
+
def paintEvent(self, event):
painter = qt.QPainter(self)
@@ -101,14 +122,14 @@ class DataPanel(qt.QWidget):
self.__dataTitle.setVisible(True)
if hasattr(data, "name"):
if hasattr(data, "file"):
- label = str(data.file.filename)
- label += "::"
+ filename = str(data.file.filename)
else:
- label = ""
- label += data.name
+ filename = ""
+ path = data.name
else:
- label = ""
- self.__dataTitle.setText(label)
+ filename = ""
+ path = ""
+ self.__dataTitle.setData(filename, path)
def setCustomDataItem(self, item):
self.__customNxdataItem = item
diff --git a/silx/app/view/Viewer.py b/silx/app/view/Viewer.py
index 8f5db60..88ff989 100644
--- a/silx/app/view/Viewer.py
+++ b/silx/app/view/Viewer.py
@@ -25,7 +25,7 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "25/06/2018"
+__date__ = "08/10/2018"
import os
@@ -146,6 +146,18 @@ class Viewer(qt.QMainWindow):
toolbar.setStyleSheet("QToolBar { border: 0px }")
action = qt.QAction(toolbar)
+ action.setIcon(icons.getQIcon("view-refresh"))
+ action.setText("Refresh")
+ action.setToolTip("Refresh all selected items")
+ action.triggered.connect(self.__refreshSelected)
+ action.setShortcut(qt.QKeySequence(qt.Qt.ControlModifier + qt.Qt.Key_Plus))
+ toolbar.addAction(action)
+ treeView.addAction(action)
+ self.__refreshAction = action
+
+ toolbar.addSeparator()
+
+ action = qt.QAction(toolbar)
action.setIcon(icons.getQIcon("tree-expand-all"))
action.setText("Expand all")
action.setToolTip("Expand all selected items")
@@ -173,6 +185,135 @@ class Viewer(qt.QMainWindow):
layout.addWidget(treeView)
return widget
+ def __refreshSelected(self):
+ """Refresh all selected items
+ """
+ qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor)
+
+ selection = self.__treeview.selectionModel()
+ indexes = selection.selectedIndexes()
+ selectedItems = []
+ model = self.__treeview.model()
+ h5files = set([])
+ while len(indexes) > 0:
+ index = indexes.pop(0)
+ if index.column() != 0:
+ continue
+ h5 = model.data(index, role=silx.gui.hdf5.Hdf5TreeModel.H5PY_OBJECT_ROLE)
+ rootIndex = index
+ # Reach the root of the tree
+ while rootIndex.parent().isValid():
+ rootIndex = rootIndex.parent()
+ rootRow = rootIndex.row()
+ relativePath = self.__getRelativePath(model, rootIndex, index)
+ selectedItems.append((rootRow, relativePath))
+ h5files.add(h5.file)
+
+ if len(h5files) == 0:
+ qt.QApplication.restoreOverrideCursor()
+ return
+
+ model = self.__treeview.findHdf5TreeModel()
+ for h5 in h5files:
+ self.__synchronizeH5pyObject(h5)
+
+ model = self.__treeview.model()
+ itemSelection = qt.QItemSelection()
+ for rootRow, relativePath in selectedItems:
+ rootIndex = model.index(rootRow, 0, qt.QModelIndex())
+ index = self.__indexFromPath(model, rootIndex, relativePath)
+ if index is None:
+ continue
+ indexEnd = model.index(index.row(), model.columnCount() - 1, index.parent())
+ itemSelection.select(index, indexEnd)
+ selection.select(itemSelection, qt.QItemSelectionModel.ClearAndSelect)
+
+ qt.QApplication.restoreOverrideCursor()
+
+ def __synchronizeH5pyObject(self, h5):
+ model = self.__treeview.findHdf5TreeModel()
+ # This is buggy right now while h5py do not allow to close a file
+ # while references are still used.
+ # FIXME: The architecture have to be reworked to support this feature.
+ # model.synchronizeH5pyObject(h5)
+
+ filename = h5.filename
+ row = model.h5pyObjectRow(h5)
+ index = self.__treeview.model().index(row, 0, qt.QModelIndex())
+ paths = self.__getPathFromExpandedNodes(self.__treeview, index)
+ model.removeH5pyObject(h5)
+ model.insertFile(filename, row)
+ index = self.__treeview.model().index(row, 0, qt.QModelIndex())
+ self.__expandNodesFromPaths(self.__treeview, index, paths)
+
+ def __getRelativePath(self, model, rootIndex, index):
+ """Returns a relative path from an index to his rootIndex.
+
+ If the path is empty the index is also the rootIndex.
+ """
+ path = ""
+ while index.isValid():
+ if index == rootIndex:
+ return path
+ name = model.data(index)
+ if path == "":
+ path = name
+ else:
+ path = name + "/" + path
+ index = index.parent()
+
+ # index is not a children of rootIndex
+ raise ValueError("index is not a children of the rootIndex")
+
+ def __getPathFromExpandedNodes(self, view, rootIndex):
+ """Return relative path from the root index of the extended nodes"""
+ model = view.model()
+ rootPath = None
+ paths = []
+ indexes = [rootIndex]
+ while len(indexes):
+ index = indexes.pop(0)
+ if not view.isExpanded(index):
+ continue
+
+ node = model.data(index, role=silx.gui.hdf5.Hdf5TreeModel.H5PY_ITEM_ROLE)
+ path = node._getCanonicalName()
+ if rootPath is None:
+ rootPath = path
+ path = path[len(rootPath):]
+ paths.append(path)
+
+ for child in range(model.rowCount(index)):
+ childIndex = model.index(child, 0, index)
+ indexes.append(childIndex)
+ return paths
+
+ def __indexFromPath(self, model, rootIndex, path):
+ elements = path.split("/")
+ if elements[0] == "":
+ elements.pop(0)
+ index = rootIndex
+ while len(elements) != 0:
+ element = elements.pop(0)
+ found = False
+ for child in range(model.rowCount(index)):
+ childIndex = model.index(child, 0, index)
+ name = model.data(childIndex)
+ if element == name:
+ index = childIndex
+ found = True
+ break
+ if not found:
+ return None
+ return index
+
+ def __expandNodesFromPaths(self, view, rootIndex, paths):
+ model = view.model()
+ for path in paths:
+ index = self.__indexFromPath(model, rootIndex, path)
+ if index is not None:
+ view.setExpanded(index, True)
+
def __expandAllSelected(self):
"""Expand all selected items of the tree.
@@ -185,6 +326,8 @@ class Viewer(qt.QMainWindow):
model = self.__treeview.model()
while len(indexes) > 0:
index = indexes.pop(0)
+ if index.column() != 0:
+ continue
if isinstance(index, tuple):
index, depth = index
else:
@@ -211,6 +354,8 @@ class Viewer(qt.QMainWindow):
model = self.__treeview.model()
while len(indexes) > 0:
index = indexes.pop(0)
+ if index.column() != 0:
+ continue
if isinstance(index, tuple):
index, depth = index
else:
@@ -682,5 +827,5 @@ class Viewer(qt.QMainWindow):
action.triggered.connect(lambda: self.__treeview.findHdf5TreeModel().removeH5pyObject(h5))
menu.addAction(action)
action = qt.QAction("Synchronize %s" % obj.local_filename, event.source())
- action.triggered.connect(lambda: self.__treeview.findHdf5TreeModel().synchronizeH5pyObject(h5))
+ action.triggered.connect(lambda: self.__synchronizeH5pyObject(h5))
menu.addAction(action)
diff --git a/silx/app/view/test/test_view.py b/silx/app/view/test/test_view.py
index 010cda5..ebcd405 100644
--- a/silx/app/view/test/test_view.py
+++ b/silx/app/view/test/test_view.py
@@ -46,7 +46,7 @@ from silx.app.view.About import About
from silx.app.view.DataPanel import DataPanel
from silx.app.view.CustomNxdataWidget import CustomNxdataWidget
from silx.gui.hdf5._utils import Hdf5DatasetMimeData
-from silx.gui.test.utils import TestCaseQt
+from silx.gui.utils.testutils import TestCaseQt
from silx.io import commonh5
_tmpDirectory = None