diff options
Diffstat (limited to 'sources/pyside-tools/qml.py')
-rw-r--r-- | sources/pyside-tools/qml.py | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/sources/pyside-tools/qml.py b/sources/pyside-tools/qml.py new file mode 100644 index 000000000..5d029f93d --- /dev/null +++ b/sources/pyside-tools/qml.py @@ -0,0 +1,246 @@ +# Copyright (C) 2018 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +"""pyside6-qml tool implementation. This tool mimics the capabilities of qml runtime utility +for python and enables quick protyping with python modules""" + +import argparse +import importlib.util +import logging +import sys +import os +from pathlib import Path +from pprint import pprint +from typing import List, Set + +from PySide6.QtCore import QCoreApplication, Qt, QLibraryInfo, QUrl, SignalInstance +from PySide6.QtGui import QGuiApplication, QSurfaceFormat +from PySide6.QtQml import QQmlApplicationEngine, QQmlComponent +from PySide6.QtQuick import QQuickView, QQuickItem +from PySide6.QtWidgets import QApplication + + +def import_qml_modules(qml_parent_path: Path, module_paths: List[Path] = []): + ''' + Import all the python modules in the qml_parent_path. This way all the classes + containing the @QmlElement/@QmlNamedElement are also imported + + Parameters: + qml_parent_path (Path): Parent directory of the qml file + module_paths (int): user give import paths obtained through cli + ''' + + search_dir_paths = [] + search_file_paths = [] + + if not module_paths: + search_dir_paths.append(qml_parent_path) + else: + for module_path in module_paths: + if module_path.is_dir(): + search_dir_paths.append(module_path) + elif module_path.exists() and module_path.suffix == ".py": + search_file_paths.append(module_path) + + def import_module(import_module_paths: Set[Path]): + """Import the modules in 'import_module_paths'""" + for module_path in import_module_paths: + module_name = module_path.name[:-3] + _spec = importlib.util.spec_from_file_location(f"{module_name}", module_path) + _module = importlib.util.module_from_spec(_spec) + _spec.loader.exec_module(module=_module) + + modules_to_import = set() + for search_path in search_dir_paths: + possible_modules = list(search_path.glob("**/*.py")) + for possible_module in possible_modules: + if possible_module.is_file() and possible_module.name != "__init__.py": + module_parent = str(possible_module.parent) + if module_parent not in sys.path: + sys.path.append(module_parent) + modules_to_import.add(possible_module) + + for search_path in search_file_paths: + sys.path.append(str(search_path.parent)) + modules_to_import.add(search_path) + + import_module(import_module_paths=modules_to_import) + + +def print_configurations(): + return "Built-in configurations \n\t default \n\t resizeToItem" + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="This tools mimics the capabilities of qml runtime utility by directly" + " invoking QQmlEngine/QQuickView. It enables quick prototyping with qml files.", + formatter_class=argparse.RawTextHelpFormatter + ) + parser.add_argument( + "file", + type=lambda p: Path(p).absolute(), + help="Path to qml file to display", + ) + parser.add_argument( + "--module-paths", "-I", + type=lambda p: Path(p).absolute(), + nargs="+", + help="Specify space separated folder/file paths where the Qml classes are defined. By" + " default,the parent directory of the qml_path is searched recursively for all .py" + " files and they are imported. Otherwise only the paths give in module paths are" + " searched", + ) + parser.add_argument( + "--list-conf", + action="version", + help="List the built-in configurations.", + version=print_configurations() + ) + parser.add_argument( + "--apptype", "-a", + choices=["core", "gui", "widget"], + default="gui", + help="Select which application class to use. Default is gui", + ) + parser.add_argument( + "--config", "-c", + choices=["default", "resizeToItem"], + default="default", + help="Select the built-in configurations.", + ) + parser.add_argument( + "--rhi", "-r", + choices=["vulkan", "metal", "d3dll", "gl"], + help="Set the backend for the Qt graphics abstraction (RHI).", + ) + parser.add_argument( + "--core-profile", + action="store_true", + help="Force use of OpenGL Core Profile.", + ) + parser.add_argument( + '-v', '--verbose', + help="Print information about what qml is doing, like specific file URLs being loaded.", + action="store_const", dest="loglevel", const=logging.INFO, + ) + + gl_group = parser.add_mutually_exclusive_group(required=False) + gl_group.add_argument( + "--gles", + action="store_true", + help="Force use of GLES (AA_UseOpenGLES)", + ) + gl_group.add_argument( + "--desktop", + action="store_true", + help="Force use of desktop OpenGL (AA_UseDesktopOpenGL)", + ) + gl_group.add_argument( + "--software", + action="store_true", + help="Force use of software rendering(AA_UseSoftwareOpenGL)", + ) + gl_group.add_argument( + "--disable-context-sharing", + action="store_true", + help=" Disable the use of a shared GL context for QtQuick Windows", + ) + + args = parser.parse_args() + apptype = args.apptype + + qquick_present = False + + with open(args.file) as myfile: + if 'import QtQuick' in myfile.read(): + qquick_present = True + + # no import QtQuick => QQCoreApplication + if not qquick_present: + apptype = "core" + + import_qml_modules(args.file.parent, args.module_paths) + + logging.basicConfig(level=args.loglevel) + logging.info(f"qml: {QLibraryInfo.build()}") + logging.info(f"qml: Using built-in configuration: {args.config}") + + if args.rhi: + os.environ['QSG_RHI_BACKEND'] = args.rhi + + logging.info(f"qml: loading {args.file}") + qml_file = QUrl.fromLocalFile(str(args.file)) + + if apptype == "gui": + if args.gles: + logging.info("qml: Using attribute AA_UseOpenGLES") + QCoreApplication.setAttribute(Qt.AA_UseOpenGLES) + elif args.desktop: + logging.info("qml: Using attribute AA_UseDesktopOpenGL") + QCoreApplication.setAttribute(Qt.AA_UseDesktopOpenGL) + elif args.software: + logging.info("qml: Using attribute AA_UseSoftwareOpenGL") + QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL) + + # context-sharing is enabled by default + if not args.disable_context_sharing: + logging.info("qml: Using attribute AA_ShareOpenGLContexts") + QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts) + + if apptype == "core": + logging.info("qml: Core application") + app = QCoreApplication(sys.argv) + elif apptype == "widgets": + logging.info("qml: Widget application") + app = QApplication(sys.argv) + else: + logging.info("qml: Gui application") + app = QGuiApplication(sys.argv) + + engine = QQmlApplicationEngine() + + # set OpenGLContextProfile + if apptype == "gui" and args.core_profile: + logging.info("qml: Set profile for QSurfaceFormat as CoreProfile") + surfaceFormat = QSurfaceFormat() + surfaceFormat.setStencilBufferSize(8) + surfaceFormat.setDepthBufferSize(24) + surfaceFormat.setVersion(4, 1) + surfaceFormat.setProfile(QSurfaceFormat.CoreProfile) + QSurfaceFormat.setDefaultFormat(surfaceFormat) + + # in the case of QCoreApplication we print the attributes of the object created via + # QQmlComponent and exit + if apptype == "core": + component = QQmlComponent(engine, qml_file) + obj = component.create() + filtered_attributes = {k: v for k, v in vars(obj).items() if type(v) is not SignalInstance} + logging.info("qml: component object attributes are") + pprint(filtered_attributes) + del engine + sys.exit(0) + + engine.load(qml_file) + rootObjects = engine.rootObjects() + if not rootObjects: + sys.exit(-1) + + qquick_view = False + if isinstance(rootObjects[0], QQuickItem) and qquick_present: + logging.info("qml: loading with QQuickView") + viewer = QQuickView() + viewer.setSource(qml_file) + if args.config != "resizeToItem": + viewer.setResizeMode(QQuickView.SizeRootObjectToView) + else: + viewer.setResizeMode(QQuickView.SizeViewToRootObject) + viewer.show() + qquick_view = True + + if not qquick_view: + logging.info("qml: loading with QQmlApplicationEngine") + if args.config == "resizeToItem": + logging.info("qml: Not a QQuickview item. resizeToItem is done by default") + + sys.exit(app.exec()) |