diff options
Diffstat (limited to 'doc/source/ext/snapshotqt_directive.py')
-rw-r--r-- | doc/source/ext/snapshotqt_directive.py | 269 |
1 files changed, 269 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..53508df --- /dev/null +++ b/doc/source/ext/snapshotqt_directive.py @@ -0,0 +1,269 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2004-2018 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: '..'). +""" +from __future__ import absolute_import + +__authors__ = ["H. Payno", "T. Vincent"] +__license__ = "MIT" +__date__ = "07/12/2018" + +import os +import logging +import subprocess +import sys +import distutils +import shutil +from docutils.parsers.rst.directives.images import Image +from docutils.parsers.rst import directives +from docutils.parsers.rst import Directive +from docutils.nodes import fully_normalize_name +from docutils import nodes + +# from docutils.par +# note: conf.py is patching the PATH so this will be the 'current' qt version + + +def _distutils_dir_name(dname="lib"): + """ + Returns the name of a distutils build directory + """ + platform = distutils.util.get_platform() + architecture = "%s.%s-%i.%i" % (dname, platform, + sys.version_info[0], sys.version_info[1]) + return architecture + +home = os.path.abspath(os.path.join(__file__, "..", "..", "..", '..')) +home = os.path.abspath(home) +LIBPATH = os.path.join(home, 'build', _distutils_dir_name('lib')) + +if not os.path.exists(LIBPATH): + raise RuntimeError("%s is not on the path. Fix your PYTHONPATH and restart sphinx." % project) + +sys.path.append(LIBPATH) +env = os.environ.copy() +env.update( + {"PYTHONPATH": LIBPATH + os.pathsep + os.environ.get("PYTHONPATH", ""), + "PATH": os.environ.get("PATH", "")}) + + +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) is 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)) + + # Probe Qt binding + if qt.BINDING == 'PyQt4': + def grabWindow(winID): + return qt.QPixmap.grabWindow(winID) + elif qt.BINDING in ('PyQt5', 'PySide2'): + def grabWindow(winID): + screen = qt.QApplication.primaryScreen() + return screen.grabWindow(winID) + + global _count + _count = 15 + global _TIMEOUT + _TIMEOUT = 1000. # 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) + if sys.version_info < (3, ): + execfile(script_or_module) + else: + with open(script_or_module) as f: + code = compile(f.read(), script_or_module, 'exec') + exec(code, globals(), locals()) |