diff options
Diffstat (limited to 'doc/source/ext/snapshotqt_directive.py')
-rw-r--r-- | doc/source/ext/snapshotqt_directive.py | 245 |
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()) |