aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside-tools/qml.py
blob: 5d029f93de72677b0a57c174aba35762435dfdee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
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())