summaryrefslogtreecommitdiff
path: root/silx/gui/utils
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/utils')
-rwxr-xr-xsilx/gui/utils/__init__.py17
-rw-r--r--silx/gui/utils/glutils.py199
-rwxr-xr-xsilx/gui/utils/qtutils.py26
-rwxr-xr-xsilx/gui/utils/test/__init__.py4
-rw-r--r--silx/gui/utils/test/test_glutils.py66
-rw-r--r--silx/gui/utils/testutils.py16
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.