From a763e5d1b3921b3194f3d4e94ab9de3fbe08bbdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Picca=20Fr=C3=A9d=C3=A9ric-Emmanuel?= Date: Tue, 28 May 2019 08:16:16 +0200 Subject: New upstream version 0.10.1+dfsg --- silx/app/view/Viewer.py | 104 ++++++++++++++++++++++++++++++++-------- silx/app/view/main.py | 74 ++++++++++++---------------- silx/app/view/test/test_view.py | 38 ++++++--------- 3 files changed, 128 insertions(+), 88 deletions(-) (limited to 'silx/app/view') diff --git a/silx/app/view/Viewer.py b/silx/app/view/Viewer.py index 88ff989..d543352 100644 --- a/silx/app/view/Viewer.py +++ b/silx/app/view/Viewer.py @@ -1,6 +1,6 @@ # coding: utf-8 # /*########################################################################## -# Copyright (C) 2016-2018 European Synchrotron Radiation Facility +# Copyright (C) 2016-2019 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 @@ -25,7 +25,7 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "08/10/2018" +__date__ = "15/01/2019" import os @@ -41,6 +41,7 @@ from .ApplicationContext import ApplicationContext from .CustomNxdataWidget import CustomNxdataWidget from .CustomNxdataWidget import CustomNxDataToolBar from . import utils +from silx.gui.utils import projecturl from .DataPanel import DataPanel @@ -61,6 +62,9 @@ class Viewer(qt.QMainWindow): qt.QMainWindow.__init__(self, parent) self.setWindowTitle("Silx viewer") + silxIcon = icons.getQIcon("silx") + self.setWindowIcon(silxIcon) + self.__context = ApplicationContext(self, settings) self.__context.restoreLibrarySettings() @@ -74,6 +78,7 @@ class Viewer(qt.QMainWindow): rightPanel.setOrientation(qt.Qt.Vertical) self.__splitter2 = rightPanel + self.__displayIt = None self.__treeWindow = self.__createTreeWindow(self.__treeview) # Custom the model to be able to manage the life cycle of the files @@ -150,11 +155,26 @@ class Viewer(qt.QMainWindow): 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)) + action.setShortcut(qt.QKeySequence(qt.Qt.Key_F5)) toolbar.addAction(action) treeView.addAction(action) self.__refreshAction = action + # Another shortcut for refresh + action = qt.QAction(toolbar) + action.setShortcut(qt.QKeySequence(qt.Qt.ControlModifier + qt.Qt.Key_R)) + treeView.addAction(action) + action.triggered.connect(self.__refreshSelected) + + action = qt.QAction(toolbar) + # action.setIcon(icons.getQIcon("view-refresh")) + action.setText("Close") + action.setToolTip("Close selected item") + action.triggered.connect(self.__removeSelected) + action.setShortcut(qt.QKeySequence(qt.Qt.Key_Delete)) + treeView.addAction(action) + self.__closeAction = action + toolbar.addSeparator() action = qt.QAction(toolbar) @@ -185,6 +205,37 @@ class Viewer(qt.QMainWindow): layout.addWidget(treeView) return widget + def __removeSelected(self): + """Close 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: + model = self.__treeview.findHdf5TreeModel() + for h5 in h5files: + row = model.h5pyObjectRow(h5) + model.removeH5pyObject(h5) + + qt.QApplication.restoreOverrideCursor() + def __refreshSelected(self): """Refresh all selected items """ @@ -387,6 +438,9 @@ class Viewer(qt.QMainWindow): def __h5FileLoaded(self, loadedH5): self.__context.pushRecentFile(loadedH5.file.filename) + if loadedH5.file.filename == self.__displayIt: + self.__displayIt = None + self.displayData(loadedH5) def __h5FileRemoved(self, removedH5): self.__dataPanel.removeDatasetsFrom(removedH5) @@ -402,10 +456,11 @@ class Viewer(qt.QMainWindow): self.__context.saveSettings() # Clean up as much as possible Python objects - model = self.__customNxdata.model() - model.clear() - model = self.__treeview.findHdf5TreeModel() - model.clear() + self.displayData(None) + customModel = self.__customNxdata.model() + customModel.clear() + hdf5Model = self.__treeview.findHdf5TreeModel() + hdf5Model.clear() def saveSettings(self, settings): """Save the window settings to this settings object @@ -502,6 +557,11 @@ class Viewer(qt.QMainWindow): action.triggered.connect(self.about) self._aboutAction = action + action = qt.QAction("&Documentation", self) + action.setStatusTip("Show the Silx library's documentation") + action.triggered.connect(self.showDocumentation) + self._documentationAction = action + # Plot backend action = qt.QAction("Plot rendering backend", self) @@ -563,7 +623,7 @@ class Viewer(qt.QMainWindow): action = qt.QAction("Show custom NXdata selector", self) action.setStatusTip("Show a widget which allow to create plot by selecting data and axes") action.setCheckable(True) - action.setShortcut(qt.QKeySequence(qt.Qt.Key_F5)) + action.setShortcut(qt.QKeySequence(qt.Qt.Key_F6)) action.toggled.connect(self.__toggleCustomNxdataWindow) self._displayCustomNxdataWindow = action @@ -662,6 +722,7 @@ class Viewer(qt.QMainWindow): helpMenu = self.menuBar().addMenu("&Help") helpMenu.addAction(self._aboutAction) + helpMenu.addAction(self._documentationAction) def open(self): dialog = self.createFileDialog() @@ -691,18 +752,11 @@ class Viewer(qt.QMainWindow): for description, ext in silx.io.supported_extensions().items(): extensions[description] = " ".join(sorted(list(ext))) - try: - # NOTE: hdf5plugin have to be loaded before - import fabio - except Exception: - _logger.debug("Backtrace while loading fabio", exc_info=True) - fabio = None - - if fabio is not None: - extensions["NeXus layout from EDF files"] = "*.edf" - extensions["NeXus layout from TIFF image files"] = "*.tif *.tiff" - extensions["NeXus layout from CBF files"] = "*.cbf" - extensions["NeXus layout from MarCCD image files"] = "*.mccd" + # Add extensions supported by fabio + extensions["NeXus layout from EDF files"] = "*.edf" + extensions["NeXus layout from TIFF image files"] = "*.tif *.tiff" + extensions["NeXus layout from CBF files"] = "*.cbf" + extensions["NeXus layout from MarCCD image files"] = "*.mccd" all_supported_extensions = set() for name, exts in extensions.items(): @@ -724,6 +778,11 @@ class Viewer(qt.QMainWindow): from .About import About About.about(self, "Silx viewer") + def showDocumentation(self): + subpath = "index.html" + url = projecturl.getDocumentationUrl(subpath) + qt.QDesktopServices.openUrl(qt.QUrl(url)) + def __forcePlotImageDownward(self): silx.config.DEFAULT_PLOT_IMAGE_Y_AXIS_ORIENTATION = "downward" @@ -737,6 +796,9 @@ class Viewer(qt.QMainWindow): silx.config.DEFAULT_PLOT_BACKEND = "opengl" def appendFile(self, filename): + if self.__displayIt is None: + # Store the file to display it (loading could be async) + self.__displayIt = filename self.__treeview.findHdf5TreeModel().appendFile(filename) def displaySelectedData(self): @@ -823,7 +885,7 @@ class Viewer(qt.QMainWindow): menu.addAction(action) if silx.io.is_file(h5): - action = qt.QAction("Remove %s" % obj.local_filename, event.source()) + action = qt.QAction("Close %s" % obj.local_filename, event.source()) action.triggered.connect(lambda: self.__treeview.findHdf5TreeModel().removeH5pyObject(h5)) menu.addAction(action) action = qt.QAction("Synchronize %s" % obj.local_filename, event.source()) diff --git a/silx/app/view/main.py b/silx/app/view/main.py index fc89a22..90b8b17 100644 --- a/silx/app/view/main.py +++ b/silx/app/view/main.py @@ -25,7 +25,7 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "07/06/2018" +__date__ = "17/01/2019" import sys import argparse @@ -36,25 +36,6 @@ import signal _logger = logging.getLogger(__name__) """Module logger""" -if "silx.gui.qt" not in sys.modules: - # Try first PyQt5 and not the priority imposed by silx.gui.qt. - # To avoid problem with unittests we only do it if silx.gui.qt is not - # yet loaded. - # TODO: Can be removed for silx 0.8, as it should be the default binding - # of the silx library. - try: - import PyQt5.QtCore - except ImportError: - pass - -import silx -from silx.gui import qt - - -def sigintHandler(*args): - """Handler for the SIGINT signal.""" - qt.QApplication.quit() - def createParser(): parser = argparse.ArgumentParser(description=__doc__) @@ -83,16 +64,8 @@ def createParser(): return parser -def main(argv): - """ - Main function to launch the viewer as an application - - :param argv: Command line arguments - :returns: exit status - """ - parser = createParser() - options = parser.parse_args(argv[1:]) - +def mainQt(options): + """Part of the main depending on Qt""" if options.debug: logging.root.setLevel(logging.DEBUG) @@ -106,25 +79,22 @@ def main(argv): except ImportError: _logger.debug("Backtrace", exc_info=True) - try: - import h5py - except ImportError: - _logger.debug("Backtrace", exc_info=True) - h5py = None + import h5py - if h5py is None: - message = "Module 'h5py' is not installed but is mandatory."\ - + " You can install it using \"pip install h5py\"." - _logger.error(message) - return -1 - - # - # Run the application - # + import silx + import silx.utils.files + from silx.gui import qt + # Make sure matplotlib is configured + # Needed for Debian 8: compatibility between Qt4/Qt5 and old matplotlib + from silx.gui.plot import matplotlib app = qt.QApplication([]) qt.QLocale.setDefault(qt.QLocale.c()) + def sigintHandler(*args): + """Handler for the SIGINT signal.""" + qt.QApplication.quit() + signal.signal(signal.SIGINT, sigintHandler) sys.excepthook = qt.exceptionHandler @@ -150,7 +120,11 @@ def main(argv): # It have to be done after the settings (after the Viewer creation) silx.config.DEFAULT_PLOT_BACKEND = "opengl" + # NOTE: under Windows, cmd does not convert `*.tif` into existing files + options.files = silx.utils.files.expand_filenames(options.files) + for filename in options.files: + # TODO: Would be nice to add a process widget and a cancel button try: window.appendFile(filename) except IOError as e: @@ -164,5 +138,17 @@ def main(argv): return result +def main(argv): + """ + Main function to launch the viewer as an application + + :param argv: Command line arguments + :returns: exit status + """ + parser = createParser() + options = parser.parse_args(argv[1:]) + mainQt(options) + + if __name__ == '__main__': main(sys.argv) diff --git a/silx/app/view/test/test_view.py b/silx/app/view/test/test_view.py index ebcd405..6601dce 100644 --- a/silx/app/view/test/test_view.py +++ b/silx/app/view/test/test_view.py @@ -35,10 +35,7 @@ import numpy import tempfile import shutil import os.path -try: - import h5py -except ImportError: - h5py = None +import h5py from silx.gui import qt from silx.app.view.Viewer import Viewer @@ -56,22 +53,21 @@ def setUpModule(): global _tmpDirectory _tmpDirectory = tempfile.mkdtemp(prefix=__name__) - if h5py is not None: - # create h5 data - filename = _tmpDirectory + "/data.h5" - f = h5py.File(filename, "w") - g = f.create_group("arrays") - g.create_dataset("scalar", data=10) - g.create_dataset("integers", data=numpy.array([10, 20, 30])) - f.close() + # create h5 data + filename = _tmpDirectory + "/data.h5" + f = h5py.File(filename, "w") + g = f.create_group("arrays") + g.create_dataset("scalar", data=10) + g.create_dataset("integers", data=numpy.array([10, 20, 30])) + f.close() - # create h5 data - filename = _tmpDirectory + "/data2.h5" - f = h5py.File(filename, "w") - g = f.create_group("arrays") - g.create_dataset("scalar", data=20) - g.create_dataset("integers", data=numpy.array([10, 20, 30])) - f.close() + # create h5 data + filename = _tmpDirectory + "/data2.h5" + f = h5py.File(filename, "w") + g = f.create_group("arrays") + g.create_dataset("scalar", data=20) + g.create_dataset("integers", data=numpy.array([10, 20, 30])) + f.close() def tearDownModule(): @@ -167,7 +163,6 @@ class TestDataPanel(TestCaseQt): self.assertIs(widget.getData(), None) self.assertIs(widget.getCustomNxdataItem(), data) - @unittest.skipIf(h5py is None, "Could not import h5py") def testRemoveDatasetsFrom(self): f = h5py.File(os.path.join(_tmpDirectory, "data.h5")) try: @@ -179,7 +174,6 @@ class TestDataPanel(TestCaseQt): widget.setData(None) f.close() - @unittest.skipIf(h5py is None, "Could not import h5py") def testReplaceDatasetsFrom(self): f = h5py.File(os.path.join(_tmpDirectory, "data.h5")) f2 = h5py.File(os.path.join(_tmpDirectory, "data2.h5")) @@ -248,7 +242,6 @@ class TestCustomNxdataWidget(TestCaseQt): self.assertIsNotNone(nxdata) self.assertFalse(item.isValid()) - @unittest.skipIf(h5py is None, "Could not import h5py") def testRemoveDatasetsFrom(self): f = h5py.File(os.path.join(_tmpDirectory, "data.h5")) try: @@ -261,7 +254,6 @@ class TestCustomNxdataWidget(TestCaseQt): model.clear() f.close() - @unittest.skipIf(h5py is None, "Could not import h5py") def testReplaceDatasetsFrom(self): f = h5py.File(os.path.join(_tmpDirectory, "data.h5")) f2 = h5py.File(os.path.join(_tmpDirectory, "data2.h5")) -- cgit v1.2.3