diff options
author | Alexandre Marie <alexandre.marie@synchrotron-soleil.fr> | 2020-07-21 14:45:14 +0200 |
---|---|---|
committer | Alexandre Marie <alexandre.marie@synchrotron-soleil.fr> | 2020-07-21 14:45:14 +0200 |
commit | 328032e2317e3ac4859196bbf12bdb71795302fe (patch) | |
tree | 8cd13462beab109e3cb53410c42335b6d1e00ee6 /silx/gui/utils | |
parent | 33ed2a64c92b0311ae35456c016eb284e426afc2 (diff) |
New upstream version 0.13.0+dfsg
Diffstat (limited to 'silx/gui/utils')
-rwxr-xr-x | silx/gui/utils/__init__.py | 17 | ||||
-rw-r--r-- | silx/gui/utils/glutils.py | 199 | ||||
-rwxr-xr-x | silx/gui/utils/qtutils.py | 26 | ||||
-rwxr-xr-x | silx/gui/utils/test/__init__.py | 4 | ||||
-rw-r--r-- | silx/gui/utils/test/test_glutils.py | 66 | ||||
-rw-r--r-- | silx/gui/utils/testutils.py | 16 |
6 files changed, 319 insertions, 9 deletions
diff --git a/silx/gui/utils/__init__.py b/silx/gui/utils/__init__.py index a4e442f..726ad74 100755 --- a/silx/gui/utils/__init__.py +++ b/silx/gui/utils/__init__.py @@ -48,6 +48,23 @@ def blockSignals(*objs): obj.blockSignals(previous) +class LockReentrant(): + """Context manager to lock a code block and check the state. + """ + def __init__(self): + self.__locked = False + + def __enter__(self): + self.__locked = True + + def __exit__(self, exc_type, exc_val, exc_tb): + self.__locked = False + + def locked(self): + """Returns True if the code block is locked""" + return self.__locked + + def getQEventName(eventType): """ Returns the name of a QEvent. diff --git a/silx/gui/utils/glutils.py b/silx/gui/utils/glutils.py new file mode 100644 index 0000000..fca9a32 --- /dev/null +++ b/silx/gui/utils/glutils.py @@ -0,0 +1,199 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2020 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""This module provides the :func:`isOpenGLAvailable` utility function. +""" + +import os +import sys +import subprocess +from silx.gui import qt + + +class _isOpenGLAvailableResult: + """Store result of checking OpenGL availability. + + It provides a `status` boolean attribute storing the result of the check and + an `error` string attribute storting the possible error message. + """ + + def __init__(self, status=True, error=''): + self.__status = bool(status) + self.__error = str(error) + + status = property(lambda self: self.__status, doc="True if OpenGL is working") + error = property(lambda self: self.__error, doc="Error message") + + def __bool__(self): + return self.status + + def __repr__(self): + return '<_isOpenGLAvailableResult: %s, "%s">' % (self.status, self.error) + + +def _runtimeOpenGLCheck(version): + """Run OpenGL check in a subprocess. + + This is done by starting a subprocess that displays a Qt OpenGL widget. + + :param List[int] version: + The minimal required OpenGL version as a 2-tuple (major, minor). + Default: (2, 1) + :return: An error string that is empty if no error occured + :rtype: str + """ + major, minor = str(version[0]), str(version[1]) + env = os.environ.copy() + env['PYTHONPATH'] = os.pathsep.join( + [os.path.abspath(p) for p in sys.path]) + + try: + error = subprocess.check_output( + [sys.executable, __file__, major, minor], + env=env, + timeout=2) + except subprocess.TimeoutExpired: + status = False + error = "Qt OpenGL widget hang" + if sys.platform.startswith('linux'): + error += ':\nIf connected remotely, GLX forwarding might be disabled.' + except subprocess.CalledProcessError as e: + status = False + error = "Qt OpenGL widget error: retcode=%d, error=%s" % (e.returncode, e.output) + else: + status = True + error = error.decode() + return _isOpenGLAvailableResult(status, error) + + +_runtimeCheckCache = {} # Cache runtime check results: {version: result} + + +def isOpenGLAvailable(version=(2, 1), runtimeCheck=True): + """Check if OpenGL is available through Qt and actually working. + + After some basic tests, this is done by starting a subprocess that + displays a Qt OpenGL widget. + + :param List[int] version: + The minimal required OpenGL version as a 2-tuple (major, minor). + Default: (2, 1) + :param bool runtimeCheck: + True (default) to run the test creating a Qt OpenGL widgt in a subprocess, + False to avoid this check. + :return: A result object that evaluates to True if successful and + which has a `status` boolean attribute (True if successful) and + an `error` string attribute that is not empty if `status` is False. + """ + error = '' + + if sys.platform.startswith('linux') and not os.environ.get('DISPLAY', ''): + # On Linux and no DISPLAY available (e.g., ssh without -X) + error = 'DISPLAY environment variable not set' + + else: + # Check pyopengl availability + try: + import silx.gui._glutils.gl # noqa + except ImportError: + error = "Cannot import OpenGL wrapper: pyopengl is not installed" + else: + # Pre checks for Qt < 5.4 + if not hasattr(qt, 'QOpenGLWidget'): + if not qt.HAS_OPENGL: + error = '%s.QtOpenGL not available' % qt.BINDING + + elif qt.QApplication.instance() and not qt.QGLFormat.hasOpenGL(): + # qt.QGLFormat.hasOpenGL MUST be called with a QApplication created + # so this is only checked if the QApplication is already created + error = 'Qt reports OpenGL not available' + + result = _isOpenGLAvailableResult(error == '', error) + + if result: # No error so far, runtime check + if version in _runtimeCheckCache: # Use cache + result = _runtimeCheckCache[version] + elif runtimeCheck: # Run test in subprocess + result = _runtimeOpenGLCheck(version) + _runtimeCheckCache[version] = result + + return result + + +if __name__ == "__main__": + from silx.gui._glutils import OpenGLWidget + from silx.gui._glutils import gl + import argparse + + class _TestOpenGLWidget(OpenGLWidget): + """Widget checking that OpenGL is indeed available + + :param List[int] version: (major, minor) minimum OpenGL version + """ + + def __init__(self, version): + super(_TestOpenGLWidget, self).__init__( + alphaBufferSize=0, + depthBufferSize=0, + stencilBufferSize=0, + version=version) + + def paintEvent(self, event): + super(_TestOpenGLWidget, self).paintEvent(event) + + # Check once paint has been done + app = qt.QApplication.instance() + if not self.isValid(): + print("OpenGL widget is not valid") + app.exit(1) + else: + qt.QTimer.singleShot(100, app.quit) + + def paintGL(self): + gl.glClearColor(1., 0., 0., 0.) + gl.glClear(gl.GL_COLOR_BUFFER_BIT) + + + parser = argparse.ArgumentParser() + parser.add_argument('major') + parser.add_argument('minor') + + args = parser.parse_args(args=sys.argv[1:]) + + app = qt.QApplication([]) + window = qt.QMainWindow(flags= + qt.Qt.Window | + qt.Qt.FramelessWindowHint | + qt.Qt.NoDropShadowWindowHint | + qt.Qt.WindowStaysOnTopHint) + window.setAttribute(qt.Qt.WA_ShowWithoutActivating) + window.move(0, 0) + window.resize(3, 3) + widget = _TestOpenGLWidget(version=(args.major, args.minor)) + window.setCentralWidget(widget) + window.setWindowOpacity(0.04) + window.show() + + qt.QTimer.singleShot(1000, app.quit) + sys.exit(app.exec_()) diff --git a/silx/gui/utils/qtutils.py b/silx/gui/utils/qtutils.py index eb823a8..9682913 100755 --- a/silx/gui/utils/qtutils.py +++ b/silx/gui/utils/qtutils.py @@ -1,3 +1,29 @@ +# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2020 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""This module provides the :func:`getQEventName` utility function."""
+
from silx.gui import qt
diff --git a/silx/gui/utils/test/__init__.py b/silx/gui/utils/test/__init__.py index d500c05..41e0d6a 100755 --- a/silx/gui/utils/test/__init__.py +++ b/silx/gui/utils/test/__init__.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2018-2019 European Synchrotron Radiation Facility +# Copyright (c) 2018-2020 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 @@ -33,6 +33,7 @@ __date__ = "24/04/2018" import unittest from . import test_async +from . import test_glutils from . import test_image from . import test_qtutils from . import test_testutils @@ -44,6 +45,7 @@ def suite(): test_suite = unittest.TestSuite() test_suite.addTest(test.suite()) test_suite.addTest(test_async.suite()) + test_suite.addTest(test_glutils.suite()) test_suite.addTest(test_image.suite()) test_suite.addTest(test_qtutils.suite()) test_suite.addTest(test_testutils.suite()) diff --git a/silx/gui/utils/test/test_glutils.py b/silx/gui/utils/test/test_glutils.py new file mode 100644 index 0000000..66df8cf --- /dev/null +++ b/silx/gui/utils/test/test_glutils.py @@ -0,0 +1,66 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2020 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Tests for the silx.gui.utils.glutils module.""" + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "15/01/2020" + + +import logging +import unittest +from silx.gui.utils.glutils import isOpenGLAvailable + + +_logger = logging.getLogger(__name__) + + +class TestIsOpenGLAvailable(unittest.TestCase): + """Test isOpenGLAvailable""" + + def test(self): + for version in ((2, 1), (2, 1), (1000, 1)): + with self.subTest(version=version): + result = isOpenGLAvailable(version=version) + _logger.info("isOpenGLAvailable returned: %s", str(result)) + if version[0] == 1000: + self.assertFalse(result) + if not result: + self.assertFalse(result.status) + self.assertTrue(len(result.error) > 0) + else: + self.assertTrue(result.status) + self.assertTrue(len(result.error) == 0) + + +def suite(): + test_suite = unittest.TestSuite() + test_suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase( + TestIsOpenGLAvailable)) + return test_suite + + +if __name__ == '__main__': + unittest.main(defaultTest='suite') diff --git a/silx/gui/utils/testutils.py b/silx/gui/utils/testutils.py index 14dcc3f..c086657 100644 --- a/silx/gui/utils/testutils.py +++ b/silx/gui/utils/testutils.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2016-2019 European Synchrotron Radiation Facility +# Copyright (c) 2016-2020 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 @@ -265,7 +265,7 @@ class TestCaseQt(unittest.TestCase): """ if modifier is None: modifier = qt.Qt.KeyboardModifiers() - pos = qt.QPoint(pos[0], pos[1]) if pos is not None else qt.QPoint() + pos = qt.QPoint(int(pos[0]), int(pos[1])) if pos is not None else qt.QPoint() QTest.mouseClick(widget, button, modifier, pos, delay) self.qWait(20) @@ -276,7 +276,7 @@ class TestCaseQt(unittest.TestCase): """ if modifier is None: modifier = qt.Qt.KeyboardModifiers() - pos = qt.QPoint(pos[0], pos[1]) if pos is not None else qt.QPoint() + pos = qt.QPoint(int(pos[0]), int(pos[1])) if pos is not None else qt.QPoint() QTest.mouseDClick(widget, button, modifier, pos, delay) self.qWait(20) @@ -285,7 +285,7 @@ class TestCaseQt(unittest.TestCase): See QTest.mouseMove for details. """ - pos = qt.QPoint(pos[0], pos[1]) if pos is not None else qt.QPoint() + pos = qt.QPoint(int(pos[0]), int(pos[1])) if pos is not None else qt.QPoint() QTest.mouseMove(widget, pos, delay) self.qWait(20) @@ -296,7 +296,7 @@ class TestCaseQt(unittest.TestCase): """ if modifier is None: modifier = qt.Qt.KeyboardModifiers() - pos = qt.QPoint(pos[0], pos[1]) if pos is not None else qt.QPoint() + pos = qt.QPoint(int(pos[0]), int(pos[1])) if pos is not None else qt.QPoint() QTest.mousePress(widget, button, modifier, pos, delay) self.qWait(20) @@ -307,7 +307,7 @@ class TestCaseQt(unittest.TestCase): """ if modifier is None: modifier = qt.Qt.KeyboardModifiers() - pos = qt.QPoint(pos[0], pos[1]) if pos is not None else qt.QPoint() + pos = qt.QPoint(int(pos[0]), int(pos[1])) if pos is not None else qt.QPoint() QTest.mouseRelease(widget, button, modifier, pos, delay) self.qWait(20) @@ -316,7 +316,7 @@ class TestCaseQt(unittest.TestCase): See QTest.qSleep for details. """ - QTest.qSleep(ms + self.TIMEOUT_WAIT) + QTest.qSleep(int(ms) + self.TIMEOUT_WAIT) @classmethod def qWait(cls, ms=None): @@ -337,7 +337,7 @@ class TestCaseQt(unittest.TestCase): maxtime=timeout) timeout = endTimeMS - int(time.time() * 1000) else: - QTest.qWait(ms + cls.TIMEOUT_WAIT) + QTest.qWait(int(ms) + cls.TIMEOUT_WAIT) def qWaitForWindowExposed(self, window, timeout=None): """Waits until the window is shown in the screen. |