# 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 `_ 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())