summaryrefslogtreecommitdiff
path: root/doc/source/ext/snapshotqt_directive.py
diff options
context:
space:
mode:
Diffstat (limited to 'doc/source/ext/snapshotqt_directive.py')
-rw-r--r--doc/source/ext/snapshotqt_directive.py245
1 files changed, 245 insertions, 0 deletions
diff --git a/doc/source/ext/snapshotqt_directive.py b/doc/source/ext/snapshotqt_directive.py
new file mode 100644
index 0000000..ab7ae1b
--- /dev/null
+++ b/doc/source/ext/snapshotqt_directive.py
@@ -0,0 +1,245 @@
+# /*##########################################################################
+#
+# Copyright (c) 2004-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
+# 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.
+#
+# ###########################################################################*/
+"""RST directive to include snapshot of a Qt application in Sphinx doc.
+
+Configuration variable in conf.py:
+
+- snapshotqt_image_type: image file extension (default 'png').
+- snapshotqt_script_dir: relative path of the root directory for scripts from
+ the documentation source directory (i.e., the directory of conf.py)
+ (default: '..').
+"""
+
+__authors__ = ["H. Payno", "T. Vincent"]
+__license__ = "MIT"
+__date__ = "07/12/2018"
+
+import os
+import logging
+import sys
+from docutils.parsers.rst.directives.images import Image
+from docutils.parsers.rst import directives
+
+# from docutils.par
+# note: conf.py is patching the PATH so this will be the 'current' qt version
+
+home = os.path.abspath(os.path.join(__file__, "..", "..", "..", ".."))
+
+
+if not os.environ.get("DIRECTIVE_SNAPSHOT_QT") == "True":
+ """
+ In case we don't wan't to regenerate screenshot, simply apply Figure
+ directive
+ """
+
+ class SnapshotQtDirective(Image):
+ option_spec = Image.option_spec.copy()
+ option_spec["script"] = directives.unchanged
+ has_content = True
+
+ def run(self):
+ self.options["figwidth"] = "image"
+ self.content = []
+
+ # Create an image filename from arguments
+ return Image.run(self)
+
+ def makescreenshot(*args, **kwargs):
+ raise RuntimeError(
+ "not defined without env variable SILX_GENERATE_SCREENSHOT set to True"
+ )
+
+ def setup(app):
+ app.add_config_value("snapshotqt_image_type", "png", "env")
+ app.add_config_value("snapshotqt_script_dir", "..", "env")
+ app.add_directive("snapshotqt", SnapshotQtDirective)
+ return {"version": "0.1"}
+
+else:
+ from silx.gui import qt
+
+ logging.basicConfig()
+ _logger = logging.getLogger(__name__)
+
+ # RST directive ###############################################################
+
+ class SnapshotQtDirective(Image):
+ """Image of a Qt application snapshot.
+
+ Directive Type: "snapshotqt"
+ Doctree Elements: As for figure
+ Directive Arguments: One or more, required (script URI + script arguments).
+ Directive Options: Possible.
+ Directive Content: Interpreted as the figure caption and optional legend.
+
+ A "snapshotqt" is a rst `figure
+ <http://docutils.sourceforge.net/docs/ref/rst/directives.html#figure>`_
+ that is generated from a Python script that uses Qt.
+
+ The path of the script to take a snapshot is relative to
+ the path given in conf.py 'snapshotqt_script_dir' value.
+
+ ::
+
+ .. snapshotqt: img/demo.py
+ :align: center
+ :height: 5cm
+
+ source code
+
+
+ you can also define a snapshot from a script, using the :script: option
+ .. note:: on this path are given from the project root level
+
+ ::
+ .. snapshotqt: img/demo.py
+ :align: center
+ :height: 5cm
+ :script: myscript.py
+ """
+
+ option_spec = Image.option_spec.copy()
+ option_spec["script"] = directives.unchanged
+ has_content = True
+
+ def run(self):
+ assert len(self.arguments) > 0
+ # Run script stored in arguments and replace by snapshot filename
+ script = self.options.pop("script", None)
+ env = self.state.document.settings.env
+
+ image_ext = env.config.snapshotqt_image_type.lower()
+ script_name = self.arguments[0].replace(image_ext, "py")
+ output_script = os.path.join(env.app.outdir, script_name)
+
+ image_file_source_path = env.relfn2path(self.arguments[0])[0]
+ image_file_source_path = os.path.join(
+ home, env.srcdir, image_file_source_path
+ )
+
+ def createNeededDirs(_dir):
+ parentDir = os.path.dirname(_dir)
+ if parentDir not in ("", os.sep):
+ createNeededDirs(parentDir)
+ if os.path.exists(_dir) is False:
+ os.mkdir(_dir)
+
+ createNeededDirs(os.path.dirname(output_script))
+
+ has_source_code = not (self.content is None or len(self.content) == 0)
+ if has_source_code:
+ with open(output_script, "w") as _file:
+ _file.write("# from silx.gui import qt\n")
+ _file.write("# app = qt.QApplication([])\n")
+ for _line in self.content:
+ _towrite = _line.lstrip(" ")
+ if not _towrite.startswith(":"):
+ _file.write(_towrite + "\n")
+ _file.write("app.exec()")
+ self.content = []
+ if script is not None:
+ _logger.warning(
+ "Cannot specify a script if source code (content) is given."
+ "Ignore script option"
+ )
+ makescreenshot(
+ script_or_module=output_script, filename=image_file_source_path
+ )
+ else:
+ # script
+ if script is None:
+ _logger.warning(
+ "no source code or script defined in the snapshot"
+ "directive, fail to generate a screenshot"
+ )
+ else:
+ script_path = os.path.join(home, script)
+ makescreenshot(
+ script_or_module=script_path, filename=image_file_source_path
+ )
+
+ #
+ # Use created image as in Figure
+ return super(SnapshotQtDirective, self).run()
+
+ def setup(app):
+ app.add_config_value("snapshotqt_image_type", "png", "env")
+ app.add_config_value("snapshotqt_script_dir", "..", "env")
+ app.add_directive("snapshotqt", SnapshotQtDirective)
+ return {"version": "0.1"}
+
+ # screensImageFileDialogH5.hot function ########################################################
+
+ def makescreenshot(script_or_module, filename):
+ _logger.info(
+ "generate screenshot for %s from %s, binding is %s"
+ "" % (filename, script_or_module, qt.BINDING)
+ )
+
+ def grabWindow(winID):
+ screen = qt.QApplication.primaryScreen()
+ return screen.grabWindow(winID)
+
+ global _count
+ _count = 15
+ global _TIMEOUT
+ _TIMEOUT = 1000.0 # in ms
+ app = qt.QApplication.instance() or qt.QApplication([])
+ _logger.debug("Using Qt bindings: %s", qt)
+
+ def _grabActiveWindowAndClose():
+ global _count
+ activeWindow = qt.QApplication.activeWindow()
+ if activeWindow is not None:
+ if activeWindow.isVisible():
+ # hot fix since issue with pySide2 API
+ if qt.BINDING == "PySide2":
+ pixmap = activeWindow.grab()
+ else:
+ pixmap = grabWindow(activeWindow.winId())
+ saveOK = pixmap.save(filename)
+ if not saveOK:
+ _logger.error("Cannot save snapshot to %s", filename)
+ else:
+ _logger.error("activeWindow is not visible.")
+ app.quit()
+ else:
+ _count -= 1
+ if _count > 0:
+ # Only restart a timer if everything is OK
+ qt.QTimer.singleShot(_TIMEOUT, _grabActiveWindowAndClose)
+ else:
+ app.quit()
+ raise TimeoutError(
+ "Aborted: It took too long to have an active window."
+ )
+
+ script_or_module = os.path.abspath(script_or_module)
+
+ sys.argv = [script_or_module]
+ sys.path.append(os.path.abspath(os.path.dirname(script_or_module)))
+ qt.QTimer.singleShot(_TIMEOUT, _grabActiveWindowAndClose)
+ with open(script_or_module) as f:
+ code = compile(f.read(), script_or_module, "exec")
+ exec(code, globals(), locals())