-Copyright (c) <year> <owner>.
+Copyright (c) 2026 The Qt Company Ltd.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
## Requirements
- * Python 3.9+ is supported (for Qt 6.7+)
+ * Python 3.9+ is supported (for Qt 6.10+)
* CMake: Specify the path to cmake with `--cmake` option or add cmake to the
system path.
* Qt 6.x is supported. Specify the path to qtpaths with `--qtpaths` option or
[official Qt for Python documentation](https://doc.qt.io/qtforpython/).
If you come across any issue, please file a bug report at our
-[JIRA tracker](https://bugreports.qt.io/projects/PYSIDE) following
+[JIRA tracker](https://qt-project.atlassian.net/browse/PYSIDE) following
our [guidelines](https://wiki.qt.io/Qt_for_Python/Reporting_Bugs).
### Community
[official Qt for Python documentation](https://doc.qt.io/qtforpython/).
If you come across any issue, please file a bug report at our
-[JIRA tracker](https://bugreports.qt.io/projects/PYSIDE) following
+[JIRA tracker](https://qt-project.atlassian.net/browse/PYSIDE) following
our [guidelines](https://wiki.qt.io/Qt_for_Python/Reporting_Bugs).
### Community
[official Qt for Python documentation](https://doc.qt.io/qtforpython/).
If you come across any issue, please file a bug report at our
-[JIRA tracker](https://bugreports.qt.io/projects/PYSIDE) following
+[JIRA tracker](https://qt-project.atlassian.net/browse/PYSIDE) following
our [guidelines](https://wiki.qt.io/Qt_for_Python/Reporting_Bugs).
### Community
[official Qt for Python documentation](https://doc.qt.io/qtforpython/).
If you come across any issue, please file a bug report at our
-[JIRA tracker](https://bugreports.qt.io/projects/PYSIDE) following
+[JIRA tracker](https://qt-project.atlassian.net/browse/PYSIDE) following
our [guidelines](https://wiki.qt.io/Qt_for_Python/Reporting_Bugs).
### Community
darwin ci
# PYSIDE-474
[QtWebEngineWidgets::pyside-474-qtwebengineview]
- py2
py3
[QtCore::qthread_test]
# PYSIDE-754
# Use Legacy OpenGL to avoid issues on systems like Ubuntu 20.04
# which require to manually install the libraries which
- # were previously linked to the QtGui module in 6.1
- # https://bugreports.qt.io/browse/QTBUG-89754
+ # were previously linked to the QtGui module in 6.1 (QTBUG-89754).
cmake_cmd.append("-DOpenGL_GL_PREFERENCE=LEGACY")
if OPTION['AVOID_PROTECTED_HACK']:
data.metatypes.extend(_metatypes)
json_data = get_module_json_data("Quick")
data.plugins = get_module_plugins(json_data)
+
+ # This includes the vectorimageformats plugin
+ json_data_vector = get_module_json_data("QuickVectorImageGeneratorPrivate")
+ vector_plugins = get_module_plugins(json_data_vector)
+ if vector_plugins:
+ data.plugins += vector_plugins
+
data.extra_files.append("svgtoqml*")
return data
product_dependency:
../../qt/qt5:
- ref: "abfb3788bcfd08d6192899eb39a8ff34d7546da8"
+ ref: "6417867c2da7aa2b1b94fb9ab084b514fb7009bb"
dependency_source: supermodule
dependencies: [
"../../qt/qt3d",
--- /dev/null
+Qt for Python 6.10.3 is a bug-fix release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+* PySide6 *
+****************************************************************************
+
+ - [PYSIDE-2862] QHttpServerConfiguration has been added.
+ - [PYSIDE-3079] type hints: The type hints of the Slot decorator have
+ been fixed.
+ - [PYSIDE-3263] Documentation on how to use image resources in ui files
+ has been improved.
+ - [PYSIDE-3265] pyside6-project: Additional command line arguments to "run"
+ are now suported.
+ - [PYSIDE-3266] A crash when connecting a slot with result to a signal
+ has been fixed.
+ - [PYSIDE-3267] Missing libraries and plugins required for pyside6-svgtoqml
+ have been added.
+ - [PYSIDE-3280] A bug related to QWebEngineExtensionManager.extensions()
+ has been fixed.
+
+****************************************************************************
+* Shiboken6 *
+****************************************************************************
+
+- [PYSIDE-3281] A bug affecting functions with "char" parameters with
+ default values, such as QLocale.toString(double,char,...)
+ has been fixed.
@staticmethod
def formatMimeTypeInfo(t: QMimeType):
out = f"<html><head/><body><h3><center>{t.name()}</center></h3><br><table>"
- aliases_str = ", ".join(t.aliases())
- if aliases_str:
+ if aliases_str := ", ".join(t.aliases()):
out += f"<tr><td>Aliases:</td><td> ({aliases_str})"
out += (
f"<tr><td>Generic icon name</td><td>{t.genericIconName()}</td></tr>"
)
- filter_str = t.filterString()
- if filter_str:
+ if filter_str := t.filterString():
out += f"<tr><td>Filter:</td><td>{filter_str}</td></tr>"
- patterns_str = ", ".join(t.globPatterns())
- if patterns_str:
+ if patterns_str := ", ".join(t.globPatterns()):
out += f"<tr><td>Glob patterns:</td><td>{patterns_str}</td></tr>"
- parentMimeTypes_str = ", ".join(t.parentMimeTypes())
- if parentMimeTypes_str:
+ if parentMimeTypes_str := ", ".join(t.parentMimeTypes()):
out += f"<tr><td>Parent types:</td><td>{parentMimeTypes_str}</td></tr>"
- suffixes = t.suffixes()
- if suffixes:
+ if suffixes := t.suffixes():
out += "<tr><td>Suffixes:</td><td>"
- preferredSuffix = t.preferredSuffix()
- if preferredSuffix:
+ if preferredSuffix := t.preferredSuffix():
suffixes.remove(preferredSuffix)
out += f"<b>{preferredSuffix}</b> "
suffixes_str = ", ".join(suffixes)
else:
# Try to convert to type unless a QByteArray is received
if isinstance(value, str):
- value_type = self._type_checker.type_from_text(value)
- if value_type:
+ if value_type := self._type_checker.type_from_text(value):
value = self.settings.value(key, type=value_type)
child.setText(1, value.__class__.__name__)
child.setText(2, VariantDelegate.display_text(value))
else:
editor = QLineEdit(parent)
editor.setFrame(False)
- validator = self._type_checker.create_validator(original_value, editor)
- if validator:
+ if validator := self._type_checker.create_validator(original_value, editor):
editor.setValidator(validator)
return editor
(json, error) = reply.readJson()
if json and json.isObject():
json_object = json.object()
- token = json_object.get(tokenField)
- if token:
+ if token := json_object.get(tokenField):
email = data[emailField]
token = json_object[tokenField]
id = data[idField]
-------------------------
The example provides the following basic functionalities:
+
* Select the server to communicate with
* List users and colors
* Login and logout users
with. The predefined options are:
* ``https://reqres.in``, a publicly available REST API test service
-* A Qt-based REST API server example in ``QtHttpServer``
+* A `Qt-based REST API server C++ example`_ in the `QtHttpServer Module`_
Once selected, the RESTful API client issues a test HTTP GET to the color API
to check if the service is accessible.
The actual communication is done with a rest access manager which implements
some convenience functionality to deal specifically with HTTP REST APIs and
-effectively deals with sending and receiving the ``QNetworkRequest`` and
-``QNetworkReply`` as needed.
+effectively deals with sending and receiving the
+:class:`~PySide6.QtNetwork.QNetworkRequest` and
+:class:`~PySide6.QtNetwork.QNetworkReply` as needed.
.. image:: colorpaletteclient.webp
- :width: 90%
:align: center
:alt: RESTful API client
+
+.. _`Qt-based REST API server C++ example`: https://doc.qt.io/qt-6/qthttpserver-colorpalette-example.html
+.. _`QtHttpServer Module`: https://doc.qt.io/qt-6/qthttpserver-index.html
+.. _`QML views`: https://doc.qt.io/qt-6/qml-qtquick-listview.html
self.m_manager.get(request, self, self.refreshCurrentPageReply)
def refreshCurrentPageReply(self, reply):
- if not reply.isSuccess():
- print("PaginatedResource: ", reply.errorString(), file=sys.stderr)
- (json, error) = reply.readJson()
- if json:
- self.refreshRequestFinished(json)
+ error = ""
+ if reply.isSuccess():
+ (json, jsonError) = reply.readJson()
+ if json:
+ self.refreshRequestFinished(json)
+ else:
+ error = jsonError.errorString()
else:
+ reply_error = reply.errorString()
+ error = reply_error if reply_error else "Network error"
+
+ if error:
+ url = reply.networkReply().url().toString()
+ print(f'PaginatedResource: request "{url}" failed: "{error}"', file=sys.stderr)
self.refreshRequestFailed()
def refreshRequestFinished(self, json):
json_object = json.object()
- self._populateModel(json_object["data"])
- self.m_pages = int(json_object[totalPagesField])
- self.m_currentPage = int(json_object[currentPageField])
+ data = json_object.get("data")
+ totalPages = json_object.get(totalPagesField)
+ currentPage = json_object.get(currentPageField)
+ self._populateModel(data if data else [])
+ self.m_pages = int(totalPages) if totalPages else 1
+ self.m_currentPage = int(currentPage) if currentPage else 1
self.pageUpdated.emit()
self.pagesUpdated.emit()
self.dataUpdated.emit()
@Slot("QVariantMap", int)
def update(self, data, id):
request = self.m_api.createRequest(f"{self.m_path}/{id}")
- self.m_manager.put(request, self, self.updateReply)
+ self.m_manager.put(request, data, self, self.updateReply)
def updateReply(self, reply):
if reply.isSuccess():
for format_bytes in all_formats:
format_str = bytes(format_bytes).decode("utf-8") # Convert QByteArray to str
- if format_str not in ["tif", "cur"]: # Exclude duplicate/non-existent formats
+ if format_str not in ["pdf", "tif", "cur"]: # Exclude duplicate/non-existent formats
result.append(f"image/{format_str}")
return result
menu = RecentFileMenu(self, self._recentFiles)
self.ui.actionRecent.setMenu(menu)
menu.fileOpened.connect(self.openFile)
- button = self.ui.mainToolBar.widgetForAction(self.ui.actionRecent)
- if button:
+ if button := self.ui.mainToolBar.widgetForAction(self.ui.actionRecent):
self.ui.actionRecent.triggered.connect(button.showMenu)
@Slot(int)
--- /dev/null
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtGraphs
+
+Rectangle {
+ id: graphContainer
+ width: 1280
+ height: 720
+ property alias theme: lines.theme
+
+ color: "white"
+
+ GraphsView {
+ id: lines
+ anchors.fill: parent
+ anchors.margins: 16
+ theme: GraphsTheme {
+ grid.mainColor: "darkgrey"
+ grid.subColor: "lightgrey"
+ labelTextColor: "black"
+ plotAreaBackgroundColor: "white"
+ backgroundColor: "white"
+ colorScheme: Qt.Light
+ }
+ axisX: ValueAxis {
+ max: 5
+ tickInterval: 1
+ subTickCount: 9
+ labelDecimals: 1
+ }
+ axisY: ValueAxis {
+ max: 10
+ tickInterval: 1
+ subTickCount: 4
+ labelDecimals: 1
+ }
+
+ component Marker : Rectangle {
+ width: 8
+ height: 8
+ color: "#ffffff"
+ radius: width * 0.5
+ border.width: 4
+ border.color: "#000000"
+ }
+
+ LineSeries {
+ id: lineSeries1
+ width: 4
+ pointDelegate: Marker { }
+ color: "black"
+ XYPoint { x: 0; y: 0 }
+ XYPoint { x: 1; y: 2.1 }
+ XYPoint { x: 2; y: 3.3 }
+ XYPoint { x: 3; y: 2.1 }
+ XYPoint { x: 4; y: 4.9 }
+ XYPoint { x: 5; y: 3.0 }
+ }
+
+ LineSeries {
+ id: lineSeries2
+ width: 4
+ pointDelegate: Marker { }
+ color: "black"
+ XYPoint { x: 0; y: 5.0 }
+ XYPoint { x: 1; y: 3.3 }
+ XYPoint { x: 2; y: 7.1 }
+ XYPoint { x: 3; y: 7.5 }
+ XYPoint { x: 4; y: 6.1 }
+ XYPoint { x: 5; y: 3.2 }
+ }
+ }
+}
--- /dev/null
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtGraphs
+
+Item {
+ id: graphContainer
+ width: 1280
+ height: 720
+ property alias theme: bars.theme
+
+ Bars3D {
+ id: bars
+ anchors.fill: parent
+ msaaSamples: 8
+ cameraPreset: Graphs3D.CameraPreset.IsometricLeftHigh
+
+ theme: GraphsTheme {
+ backgroundColor: "white"
+ plotAreaBackgroundVisible: false
+ grid.mainColor: "black"
+ labelFont.pointSize: 20
+ labelBackgroundVisible: false
+ colorScheme: Qt.Light
+ }
+
+ Bar3DSeries {
+ id: series
+ itemLabelFormat: "Expenses, @colLabel, @rowLabel: -@valueLabel"
+ baseGradient: gradient
+ colorStyle: GraphsTheme.ColorStyle.RangeGradient
+
+ ItemModelBarDataProxy {
+ id: barProxy
+ itemModel: ListModel {
+ ListElement{ coords: "0,0"; data: "4.75"; }
+ ListElement{ coords: "1,0"; data: "3.00"; }
+ ListElement{ coords: "0,1"; data: "3.55"; }
+ ListElement{ coords: "1,1"; data: "3.03"; }
+ ListElement{ coords: "0,2"; data: "3.37"; }
+ ListElement{ coords: "1,2"; data: "2.98"; }
+ ListElement{ coords: "0,3"; data: "5.34"; }
+ ListElement{ coords: "1,3"; data: "4.54"; }
+ ListElement{ coords: "0,4"; data: "6.01"; }
+ ListElement{ coords: "1,4"; data: "5.83"; }
+ }
+ rowRole: "coords"
+ columnRole: "coords"
+ valueRole: "data"
+ rowRolePattern: /(\d),(\d)/
+ columnRolePattern: /(\d),(\d)/
+ rowRoleReplace: "\\1"
+ columnRoleReplace: "\\2"
+ }
+
+ Gradient {
+ id: gradient
+ GradientStop { position: 1.0; color: "#5000FF" }
+ GradientStop { position: 0.0; color: "#2000FF" }
+ }
+ }
+ }
+}
--- /dev/null
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtCore
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls.Fusion
+import QtQuick.Dialogs
+
+Rectangle {
+ id: mainView
+ width: 1280
+ height: 720
+ color: Application.styleHints.colorScheme === Qt.Dark ? "darkgray" : "lightgray"
+
+ property var item: stackLayout.itemAt(stackLayout.currentIndex)
+ property var outputsize: Qt.size(linegraph.width * 4, linegraph.height * 4)
+
+ RowLayout {
+ id: rowLayout
+ anchors.fill: parent
+ anchors.leftMargin: 5
+ anchors.rightMargin: 5
+ anchors.topMargin: 5
+ anchors.bottomMargin: 5
+ spacing: 5
+
+ GroupBox {
+ id: groupBox
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ title: qsTr("Printing and exporting")
+
+ ColumnLayout {
+ id: buttonLayout
+ spacing: 0
+ uniformCellSizes: true
+ Button {
+ id: captureButton
+ text: qsTr("Save to PDF")
+ flat: true
+
+ icon.source: pressed ? "documents_fill.svg" : "documents.svg"
+ icon.height: 36
+ icon.width: 36
+
+ onPressed: dialog.open()
+ }
+
+ Button {
+ id: printButton
+ text: qsTr("Send to printer")
+ flat: true
+
+ icon.source: pressed ? "print_fill.svg" : "print.svg"
+ icon.height: 36
+ icon.width: 36
+
+ onPressed: printerDialog.open()
+ }
+ }
+ }
+
+ Item {
+ id: tabGroup
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ TabBar {
+ id: tabBar
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ TabButton {
+ text: "2D Graph"
+ implicitHeight: 48
+ icon.source: checked ? "flatten_square_fill.svg" : "flatten.svg"
+ icon.height: 36
+ icon.width: 36
+ }
+
+ TabButton {
+ text: "3D Graph"
+ implicitHeight: 48
+ icon.source: checked ? "box_left_fill.svg" : "box_left.svg"
+ icon.height: 36
+ icon.width: 36
+ }
+ }
+ Frame {
+ id: tabFrame
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: tabBar.bottom
+ anchors.bottom: parent.bottom
+
+ StackLayout {
+ id: stackLayout
+
+ anchors.fill: parent
+ currentIndex: tabBar.currentIndex
+
+ Graph2D {
+ id: linegraph
+ }
+
+ Graph3D {
+ id: bargraph
+ }
+ }
+ }
+ }
+ }
+
+ MessageDialog {
+ id: message
+ onButtonClicked: mainView.cleanAfterPrint()
+ }
+
+ FileDialog {
+ id: dialog
+ currentFolder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
+ nameFilters: ["PDF files (*.pdf)"]
+ defaultSuffix: "pdf"
+
+ fileMode: FileDialog.SaveFile
+ onAccepted: {
+ mainView.prepareForPrint()
+
+ mainView.item.grabToImage(function (result) {
+ message.title = "Save PDF"
+ message.text = "PDF saved to " + graphPrinter.generatePDF(
+ dialog.selectedFile, result.image)
+ message.open()
+ }, mainView.outputsize)
+ }
+ }
+
+ Dialog {
+ id: printerDialog
+ anchors.centerIn: parent
+ contentHeight: printerListView.height
+ contentWidth: printerListView.width
+
+ title: qsTr("Available Printers")
+ modal: true
+
+ onOpened: {
+ printerModel.clear()
+ var printers = graphPrinter.getPrinters()
+ printers.forEach((x, i) => printerModel.append({
+ "name": x
+ }))
+ }
+
+ onAccepted: {
+ var selectedPrinter = printerModel.get(printerListView.currentIndex)
+ mainView.prepareForPrint()
+ mainView.item.grabToImage(function (result) {
+ message.title = "Print"
+ message.text = graphPrinter.print(result.image,
+ selectedPrinter.name)
+ message.open()
+ }, mainView.outputsize)
+ }
+
+ onClosed: {
+ mainView.cleanAfterPrint()
+ }
+
+ Component {
+ id: printerDelegate
+ Rectangle {
+ width: 198
+ height: 25
+ color: "transparent"
+ border.color: mainView.item.theme.grid.mainColor
+ clip: true
+
+ Text {
+ padding: 5
+ text: qsTr("<b>%1</b>").arg(name)
+ color: mainView.item.theme.labelTextColor
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: printerListView.currentIndex = index
+ }
+ }
+ }
+
+ contentItem: Rectangle {
+ id: printerItem
+ height: printerListView.height
+ width: printerListView.width
+ color: mainView.item.theme.plotAreaBackgroundColor
+
+ ListView {
+ id: printerListView
+ height: 100
+ width: 200
+ clip: true
+
+ model: printerModel
+ delegate: printerDelegate
+ highlight: Rectangle {
+ color: mainView.item.theme.grid.subColor
+ }
+ }
+ }
+
+ footer: DialogButtonBox {
+ Button {
+ text: "Print"
+ DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
+ }
+ Button {
+ text: "Cancel"
+ DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
+ }
+ }
+ }
+
+ ListModel {
+ id: printerModel
+ }
+
+ function prepareForPrint() {
+ if (stackLayout.currentIndex === 1) {
+ var newsize = Qt.size(bargraph.width * 4, bargraph.height * 4)
+
+ // check that we do not exceed maximum texture size
+ if (newsize.width * Screen.devicePixelRatio > graphPrinter.maxTextureSize ) {
+ // scale to 25% under max texture size to be on the safe side; some GPUs seem
+ // to glitch when using the abosulute max
+ var ratio = (newsize.width * Screen.devicePixelRatio * 1.25)
+ / graphPrinter.maxTextureSize
+ newsize.width /= ratio
+ newsize.height /= ratio
+ }
+ outputsize.width = Math.round(newsize.width)
+ outputsize.height = Math.round(newsize.height)
+
+ // resize the bar graph to match the PDF output size
+ item.width = outputsize.width
+ item.height = outputsize.height
+ } else {
+ outputsize = Qt.size(linegraph.width * 4, linegraph.height * 4)
+ }
+ }
+
+ function cleanAfterPrint() {
+ if (stackLayout.currentIndex === 1) {
+ // resize the bar graph back to the actual visual size
+ item.width = stackLayout.width
+ item.height = stackLayout.height
+ }
+ }
+}
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M4 7.35407L4 16.6459L12 19.8459L20 16.6459V7.35407L12 4.15407L4 7.35407ZM2 16.6459V7.35407C2 6.53626 2.4979 5.80084 3.25722 5.49711L11.2572 2.29711C11.734 2.10639 12.266 2.10639 12.7428 2.29711L20.7428 5.49711C21.5021 5.80084 22 6.53626 22 7.35407V16.6459C22 17.4637 21.5021 18.1992 20.7428 18.5029L12.7428 21.7029C12.266 21.8936 11.734 21.8936 11.2572 21.7029L3.25722 18.5029C2.4979 18.1992 2 17.4637 2 16.6459Z" fill="#181818"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2903 10.2903L10.8761 11.7045L4 8.95407V11.3864V16.6459L11 19.4459L11 11.8284L10.8761 11.7045L12.2903 10.2903ZM2 16.6459V11.3864V8.95407C2 7.53914 3.42905 6.57162 4.74278 7.09711L11.6188 9.84754C11.8703 9.94813 12.0988 10.0988 12.2903 10.2903L12.4142 10.4142C12.7893 10.7893 13 11.298 13 11.8284V19.5434C13 19.8437 12.9301 20.1398 12.7958 20.4083C12.346 21.308 11.2817 21.7127 10.3478 21.3391L3.25722 18.5029C2.4979 18.1992 2 17.4637 2 16.6459Z" fill="#181818"/>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M4 7.35407L4 16.6459L12 19.8459L20 16.6459V7.35407L12 4.15407L4 7.35407ZM2 16.6459V7.35407C2 6.53626 2.4979 5.80084 3.25722 5.49711L11.2572 2.29711C11.734 2.10639 12.266 2.10639 12.7428 2.29711L20.7428 5.49711C21.5021 5.80084 22 6.53626 22 7.35407V16.6459C22 17.4637 21.5021 18.1992 20.7428 18.5029L12.7428 21.7029C12.266 21.8936 11.734 21.8936 11.2572 21.7029L3.25722 18.5029C2.4979 18.1992 2 17.4637 2 16.6459Z" fill="#181818"/>
+<path d="M2 11.3864V16.6459C2 17.4637 2.4979 18.1992 3.25722 18.5029L10.3478 21.3391C11.2817 21.7127 12.346 21.308 12.7958 20.4083C12.9301 20.1398 13 19.8437 13 19.5434V11.8284C13 11.298 12.7893 10.7893 12.4142 10.4142L12.2903 10.2903C12.0988 10.0988 11.8703 9.94813 11.6188 9.84754L4.74278 7.09711C3.42905 6.57162 2 7.53914 2 8.95407V11.3864Z" fill="#181818"/>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M6 4L6 20H18L18 8.40053L13.4737 4H6ZM4 4V20C4 21.1046 4.89543 22 6 22H18C19.1046 22 20 21.1046 20 20V8.40053C20 7.86033 19.7815 7.3431 19.3942 6.96654L14.8679 2.56601C14.4946 2.20306 13.9944 2 13.4737 2H6C4.89543 2 4 2.89543 4 4Z" fill="#181818"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13 3.70711V7C13 8.10457 13.8954 9 15 9H18.2929C18.6834 9 19 8.68342 19 8.29289C19 8.10536 18.9255 7.9255 18.7929 7.79289L14.2071 3.20711C14.0745 3.0745 13.8946 3 13.7071 3C13.3166 3 13 3.31658 13 3.70711ZM8 11C7.44772 11 7 11.4477 7 12C7 12.5523 7.44772 13 8 13H16C16.5523 13 17 12.5523 17 12C17 11.4477 16.5523 11 16 11H8ZM8 14C7.44772 14 7 14.4477 7 15C7 15.5523 7.44772 16 8 16H16C16.5523 16 17 15.5523 17 15C17 14.4477 16.5523 14 16 14H8ZM7 18C7 17.4477 7.44772 17 8 17H13C13.5523 17 14 17.4477 14 18C14 18.5523 13.5523 19 13 19H8C7.44772 19 7 18.5523 7 18Z" fill="#181818"/>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M4 4V20C4 21.1046 4.89543 22 6 22H18C19.1046 22 20 21.1046 20 20V8.40053C20 7.86033 19.7815 7.3431 19.3942 6.96654L14.8679 2.56601C14.4946 2.20306 13.9944 2 13.4737 2H6C4.89543 2 4 2.89543 4 4ZM8 11C7.44772 11 7 11.4477 7 12C7 12.5523 7.44772 13 8 13H16C16.5523 13 17 12.5523 17 12C17 11.4477 16.5523 11 16 11H8ZM7 15C7 14.4477 7.44772 14 8 14H16C16.5523 14 17 14.4477 17 15C17 15.5523 16.5523 16 16 16H8C7.44772 16 7 15.5523 7 15ZM8 17C7.44772 17 7 17.4477 7 18C7 18.5523 7.44772 19 8 19H13C13.5523 19 14 18.5523 14 18C14 17.4477 13.5523 17 13 17H8Z" fill="#181818"/>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M20 4H4V20H20V4ZM4 2C2.89543 2 2 2.89543 2 4V20C2 21.1046 2.89543 22 4 22H20C21.1046 22 22 21.1046 22 20V4C22 2.89543 21.1046 2 20 2H4Z" fill="#181818"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M21.9042 7.573C22.1401 8.0724 21.9264 8.66843 21.427 8.90426L3.427 17.4043C2.92759 17.6401 2.33157 17.4264 2.09574 16.927C1.85991 16.4276 2.07358 15.8316 2.57298 15.5958L20.573 7.09576C21.0724 6.85993 21.6684 7.0736 21.9042 7.573Z" fill="#181818"/>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M4 3H20C20.5523 3 21 3.44772 21 4V20C21 20.5523 20.5523 21 20 21H4C3.44772 21 3 20.5523 3 20V4C3 3.44772 3.44772 3 4 3ZM2 4C2 2.89543 2.89543 2 4 2H20C21.1046 2 22 2.89543 22 4V20C22 21.1046 21.1046 22 20 22H4C2.89543 22 2 21.1046 2 20V4ZM20 4V7.39559L19.5756 7.59454L4 14.8956V4H20ZM4.42443 16.9055L4 17.1044V20H20V9.60441L4.42443 16.9055Z" fill="#181818"/>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="File / print">
+<path id="Layer01" fill-rule="evenodd" clip-rule="evenodd" d="M7 2C6.44772 2 6 2.44772 6 3V6H5C3.34315 6 2 7.34315 2 9V17C2 17.5523 2.44772 18 3 18H6V16H4V9C4 8.44772 4.44772 8 5 8H7H17H19C19.5523 8 20 8.44772 20 9V16H18V18H21C21.5523 18 22 17.5523 22 17V9C22 7.34315 20.6569 6 19 6H18V3C18 2.44772 17.5523 2 17 2H7ZM16 6V4H8V6H16ZM19 10C19 10.5523 18.5523 11 18 11C17.4477 11 17 10.5523 17 10C17 9.44772 17.4477 9 18 9C18.5523 9 19 9.44772 19 10Z" fill="#181818"/>
+<path id="Layer02" fill-rule="evenodd" clip-rule="evenodd" d="M6 13C6 12.4477 6.44772 12 7 12H17C17.5523 12 18 12.4477 18 13V21C18 21.5523 17.5523 22 17 22H7C6.44772 22 6 21.5523 6 21V13ZM8 14V20H16V14H8ZM8.5 18C8.5 17.7239 8.72386 17.5 9 17.5H15C15.2761 17.5 15.5 17.7239 15.5 18C15.5 18.2761 15.2761 18.5 15 18.5H9C8.72386 18.5 8.5 18.2761 8.5 18ZM9 15.5C8.72386 15.5 8.5 15.7239 8.5 16C8.5 16.2761 8.72386 16.5 9 16.5H15C15.2761 16.5 15.5 16.2761 15.5 16C15.5 15.7239 15.2761 15.5 15 15.5H9Z" fill="#181818"/>
+</g>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8 6V4H16V6H8ZM6 6V3C6 2.44772 6.44772 2 7 2H17C17.5523 2 18 2.44772 18 3V6H19C20.6569 6 22 7.34315 22 9V17C22 17.5523 21.5523 18 21 18H19V21C19 22.1046 18.1046 23 17 23H7C5.89543 23 5 22.1046 5 21V18H3C2.44772 18 2 17.5523 2 17V9C2 7.34315 3.34315 6 5 6H6ZM6 18V20C6 21.1046 6.89543 22 8 22H16C17.1046 22 18 21.1046 18 20V18V14C18 12.8954 17.1046 12 16 12H8C6.89543 12 6 12.8954 6 14V18ZM18 11C18.5523 11 19 10.5523 19 10C19 9.44772 18.5523 9 18 9C17.4477 9 17 9.44772 17 10C17 10.5523 17.4477 11 18 11ZM17 14C17 13.4477 16.5523 13 16 13H8C7.44772 13 7 13.4477 7 14V20C7 20.5523 7.44772 21 8 21H16C16.5523 21 17 20.5523 17 20V14ZM9 15.5C8.72386 15.5 8.5 15.7239 8.5 16C8.5 16.2761 8.72386 16.5 9 16.5H15C15.2761 16.5 15.5 16.2761 15.5 16C15.5 15.7239 15.2761 15.5 15 15.5H9ZM9 17.5C8.72386 17.5 8.5 17.7239 8.5 18C8.5 18.2761 8.72386 18.5 9 18.5H15C15.2761 18.5 15.5 18.2761 15.5 18C15.5 17.7239 15.2761 17.5 15 17.5H9Z" fill="#181818"/>
+</svg>
--- /dev/null
+module GraphPrintingExample
+Main 1.0 Main.qml
+Graph2D 1.0 Graph2D.qml
+Graph3D 1.0 Graph3D.qml
--- /dev/null
+Graph Printing Example
+======================
+
+The Graph Printing example demonstrates how to print or export to PDF 2D and
+3D graphs.
+
+The printing functionality is implemented in the `GraphPrinter` class whose
+slots are invoked from QML, passing an image obtained from
+`QuickItem.grabToImage()`_ . The image is scaled and painted onto either
+a :class:`~PySide6.QtPrintSupport.QPrinter` or a
+:class:`~PySide6.QtGui.QPdfWriter`, which inherit
+:class:`~PySide6.QtGui.QPaintDevice`.
+
+
+.. image:: graphprinting.webp
+ :align: center
+ :alt: Graph Printing example
+
+.. _`QuickItem.grabToImage()`: https://doc.qt.io/qt-6/qquickitem.html#grabToImage
--- /dev/null
+# Copyright (C) 2026 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import QObject, QMarginsF, QUrl, Qt, Property, Slot
+from PySide6.QtGui import (QDesktopServices, QImage, QPageSize, QPainter, QPaintDevice,
+ QPdfWriter)
+from PySide6.QtQml import QmlElement
+from PySide6.QtPrintSupport import QPrinter, QPrinterInfo
+
+QML_IMPORT_NAME = "GraphPrintingExample"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+class GraphPrinter(QObject):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ @Property(int, constant=True)
+ def maxTextureSize(self):
+ return 4096 # Use 4096 as the minimum
+
+ def paintImage(self, image: QImage, device: QPaintDevice):
+ painter = QPainter(device)
+ viewportSize = painter.viewport().size()
+ imageSize = image.size()
+ print(f"Scaling {imageSize.width()}x{imageSize.height()} to "
+ f"{viewportSize.width()}x{viewportSize.height()}.")
+ finalImage = image.scaled(viewportSize, Qt.AspectRatioMode.KeepAspectRatio)
+ painter.setRenderHint(QPainter.RenderHint.LosslessImageRendering)
+ painter.drawImage(finalImage.rect(), finalImage)
+ painter.end()
+
+ def _generatePDF(self, fileName: str, image: QImage):
+ writer = QPdfWriter(fileName)
+ writer.setResolution(90)
+ writer.setTitle("Graph")
+ writer.setPageSize(QPageSize(image.size()))
+ writer.setPageMargins(QMarginsF(0, 0, 0, 0))
+ writer.newPage()
+ self.paintImage(image, writer)
+
+ @Slot(QUrl, QImage, result=str)
+ def generatePDF(self, path: QUrl, image: QImage):
+ fileName = path.toLocalFile()
+ self._generatePDF(fileName, image)
+
+ QDesktopServices.openUrl(path)
+
+ return fileName
+
+ @Slot(QImage, str, result=str)
+ def print(self, image: QImage, printerName: str):
+ printInfo = QPrinterInfo.printerInfo(printerName)
+ if printInfo.isNull():
+ return f"{printerName} is not a valid printer"
+
+ printer = QPrinter(printInfo, QPrinter.PrinterMode.HighResolution)
+ printer.setOutputFormat(QPrinter.OutputFormat.NativeFormat)
+ self.paintImage(image, printer)
+
+ return f"Printed to {printerName}"
+
+ @Slot(result="QStringList")
+ def getPrinters(self):
+ return QPrinterInfo.availablePrinterNames()
--- /dev/null
+{
+ "files": ["main.py", "graphprinter.py",
+ "GraphPrintingExample/qmldir",
+ "GraphPrintingExample/Main.qml",
+ "GraphPrintingExample/Graph2D.qml",
+ "GraphPrintingExample/Graph3D.qml",
+ "GraphPrintingExample/Main.qml",
+ "GraphPrintingExample/box_left.svg",
+ "GraphPrintingExample/box_left_fill.svg",
+ "GraphPrintingExample/documents.svg",
+ "GraphPrintingExample/documents_fill.svg",
+ "GraphPrintingExample/flatten.svg",
+ "GraphPrintingExample/flatten_square_fill.svg",
+ "GraphPrintingExample/folder.svg",
+ "GraphPrintingExample/folder_fill.svg",
+ "GraphPrintingExample/print.svg",
+ "GraphPrintingExample/print_fill.svg"]
+}
--- /dev/null
+# Copyright (C) 2026 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from pathlib import Path
+
+from PySide6.QtCore import QCoreApplication, QSize, Qt
+from PySide6.QtGui import QGuiApplication, QShortcut, QKeySequence
+from PySide6.QtQuick import QQuickView
+
+from graphprinter import GraphPrinter
+
+
+if __name__ == "__main__":
+ app = QGuiApplication(sys.argv)
+
+ viewer = QQuickView()
+ viewer.setTitle("Graph Printing")
+ graphPrinter = GraphPrinter()
+ viewer.rootContext().setContextProperty("graphPrinter", graphPrinter)
+ viewer.setMinimumSize(QSize(1280, 720))
+ viewer.engine().addImportPath(Path(__file__).parent)
+ viewer.loadFromModule("GraphPrintingExample", "Main")
+ window = viewer.rootObject()
+ if not window:
+ sys.exit(-1)
+ quitKey = QKeySequence(QKeySequence.StandardKey.Quit)
+ if not quitKey.isEmpty():
+ quitShortcut = QShortcut(quitKey, window)
+ quitShortcut.activated.connect(app.quit)
+ quitShortcut.setContext(Qt.ShortcutContext.ApplicationShortcut)
+ viewer.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView)
+ viewer.setColor(Qt.GlobalColor.white)
+ viewer.show()
+
+ ex = QCoreApplication.exec()
+ del viewer
+ sys.exit(ex)
def pull_timer_expired(self):
if self.m_audioSink is not None and self.m_audioSink.state() != QtAudio.State.StoppedState:
bytes_free = self.m_audioSink.bytesFree()
- data = self.m_generator.read(bytes_free)
- if data:
+ if data := self.m_generator.read(bytes_free):
self.m_output.write(data)
@Slot()
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-import os
import sys
-from pathlib import Path
from PySide6.QtMultimedia import (QAudioInput, QCamera, QCameraDevice,
QImageCapture, QMediaCaptureSession,
QMediaDevices, QMediaMetaData,
QMediaRecorder)
from PySide6.QtWidgets import QDialog, QMainWindow, QMessageBox
-from PySide6.QtGui import QAction, QActionGroup, QIcon, QImage, QPixmap
+from PySide6.QtGui import QAction, QActionGroup, QImage, QPixmap
from PySide6.QtCore import QDateTime, QDir, QTimer, Qt, Slot, qWarning
from metadatadialog import MetaDataDialog
self._ui = Ui_Camera()
self._ui.setupUi(self)
- image = Path(__file__).parent / "shutter.svg"
- self._ui.takeImageButton.setIcon(QIcon(os.fspath(image)))
if not is_android:
self._ui.actionAbout_Qt.triggered.connect(qApp.aboutQt) # noqa: F821
def saveMetaData(self):
data = QMediaMetaData()
for i in range(0, QMediaMetaData.NumMetaData):
- val = self.m_metaDataDialog.m_metaDataFields[i].text()
- if val:
+ if val := self.m_metaDataDialog.m_metaDataFields[i].text():
key = QMediaMetaData.Key(i)
if key == QMediaMetaData.Key.CoverArtImage:
cover_art = QImage(val)
"metadatadialog.py",
"videosettings.py",
"videosettings.ui",
- "videosettings_mobile.ui"]
+ "videosettings_mobile.ui",
+ "camera.qrc"]
}
--- /dev/null
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>images/shutter.svg</file>
+</qresource>
+</RCC>
<property name="text">
<string>Capture Photo</string>
</property>
+ <property name="icon">
+ <iconset resource="camera.qrc">
+ <normaloff>:/images/shutter.svg</normaloff>:/images/shutter.svg</iconset>
+ </property>
+ <property name="icon">
+ <iconset resource="camera.qrc">
+ <normaloff>:/images/shutter.svg</normaloff>:/images/shutter.svg</iconset>
+ </property>
</widget>
</item>
</layout>
<container>1</container>
</customwidget>
</customwidgets>
- <resources/>
+ <resources>
+ <include location="camera.qrc"/>
+ </resources>
<connections>
<connection>
<sender>recordButton</sender>
<container>1</container>
</customwidget>
</customwidgets>
- <resources/>
+ <resources>
+ <include location="camera.qrc"/>
+ </resources>
<connections>
<connection>
<sender>recordButton</sender>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 23.3 19.4" style="enable-background:new 0 0 23.3 19.4;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:none;}
+</style>
+<g>
+ <path class="st0" d="M6.2,4.8H2.4c-0.2,0-0.1-0.1-0.1,0.1V17c0,0.2-0.1,0.8,0.1,0.8h3.9V4.8z"/>
+ <circle class="st0" cx="14" cy="11" r="4.5"/>
+ <path class="st0" d="M20.9,4.8h-1.8c-0.3,0-0.6-0.4-0.8-0.6l-1.7-2.4h-5.3L9.7,4.2C9.5,4.4,9.2,4.8,8.9,4.8H7.2v13h13.7
+ c0.2,0,0.3-0.6,0.3-0.8V4.9C21.2,4.7,21.1,4.8,20.9,4.8z M14,16.4c-3,0-5.5-2.4-5.5-5.5c0-3,2.4-5.5,5.5-5.5c3,0,5.5,2.4,5.5,5.5
+ C19.5,14,17,16.4,14,16.4z"/>
+ <path d="M14,5.5C11,5.5,8.6,8,8.6,11c0,3,2.4,5.5,5.5,5.5c3,0,5.5-2.4,5.5-5.5C19.5,8,17,5.5,14,5.5z M14,15.4
+ c-2.5,0-4.5-2-4.5-4.5c0-2.5,2-4.5,4.5-4.5c2.5,0,4.5,2,4.5,4.5C18.5,13.4,16.5,15.4,14,15.4z"/>
+ <path d="M20.9,2.8h-1.3l-1.7-2.4c-0.2-0.2-0.5-0.6-0.8-0.6h-6.3c-0.3,0-0.6,0.4-0.8,0.6L8.4,2.8h-6c-1.3,0-2.1,0.8-2.1,2.1V17
+ c0,1.3,0.8,2.8,2.1,2.8h18.5c1.3,0,2.3-1.5,2.3-2.8V4.9C23.2,3.6,22.2,2.8,20.9,2.8z M2.2,17V4.9c0-0.2-0.1-0.1,0.1-0.1h3.9v13H2.4
+ C2.2,17.8,2.2,17.2,2.2,17z M21.2,17c0,0.2-0.1,0.8-0.3,0.8H7.2v-13h1.7c0.3,0,0.6-0.4,0.8-0.6l1.7-2.4h5.3l1.7,2.4
+ c0.2,0.2,0.5,0.6,0.8,0.6h1.8c0.2,0,0.3-0.1,0.3,0.1V17z"/>
+</g>
+</svg>
--- /dev/null
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 6.10.2
+# WARNING! All changes made in this file will be lost!
+
+from PySide6 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x05\xbe\
+<\
+?xml version=\x221.\
+0\x22 encoding=\x22utf\
+-8\x22?>\x0a<!-- Gener\
+ator: Adobe Illu\
+strator 20.1.0, \
+SVG Export Plug-\
+In . SVG Version\
+: 6.00 Build 0) \
+ -->\x0a<svg versio\
+n=\x221.1\x22 id=\x22Laye\
+r_1\x22 xmlns=\x22http\
+://www.w3.org/20\
+00/svg\x22 xmlns:xl\
+ink=\x22http://www.\
+w3.org/1999/xlin\
+k\x22 x=\x220px\x22 y=\x220p\
+x\x22\x0a\x09 viewBox=\x220 \
+0 23.3 19.4\x22 sty\
+le=\x22enable-backg\
+round:new 0 0 23\
+.3 19.4;\x22 xml:sp\
+ace=\x22preserve\x22>\x0a\
+<style type=\x22tex\
+t/css\x22>\x0a\x09.st0{fi\
+ll:none;}\x0a</styl\
+e>\x0a<g>\x0a\x09<path cl\
+ass=\x22st0\x22 d=\x22M6.\
+2,4.8H2.4c-0.2,0\
+-0.1-0.1-0.1,0.1\
+V17c0,0.2-0.1,0.\
+8,0.1,0.8h3.9V4.\
+8z\x22/>\x0a\x09<circle c\
+lass=\x22st0\x22 cx=\x221\
+4\x22 cy=\x2211\x22 r=\x224.\
+5\x22/>\x0a\x09<path clas\
+s=\x22st0\x22 d=\x22M20.9\
+,4.8h-1.8c-0.3,0\
+-0.6-0.4-0.8-0.6\
+l-1.7-2.4h-5.3L9\
+.7,4.2C9.5,4.4,9\
+.2,4.8,8.9,4.8H7\
+.2v13h13.7\x0a\x09\x09c0.\
+2,0,0.3-0.6,0.3-\
+0.8V4.9C21.2,4.7\
+,21.1,4.8,20.9,4\
+.8z M14,16.4c-3,\
+0-5.5-2.4-5.5-5.\
+5c0-3,2.4-5.5,5.\
+5-5.5c3,0,5.5,2.\
+4,5.5,5.5\x0a\x09\x09C19.\
+5,14,17,16.4,14,\
+16.4z\x22/>\x0a\x09<path \
+d=\x22M14,5.5C11,5.\
+5,8.6,8,8.6,11c0\
+,3,2.4,5.5,5.5,5\
+.5c3,0,5.5-2.4,5\
+.5-5.5C19.5,8,17\
+,5.5,14,5.5z M14\
+,15.4\x0a\x09\x09c-2.5,0-\
+4.5-2-4.5-4.5c0-\
+2.5,2-4.5,4.5-4.\
+5c2.5,0,4.5,2,4.\
+5,4.5C18.5,13.4,\
+16.5,15.4,14,15.\
+4z\x22/>\x0a\x09<path d=\x22\
+M20.9,2.8h-1.3l-\
+1.7-2.4c-0.2-0.2\
+-0.5-0.6-0.8-0.6\
+h-6.3c-0.3,0-0.6\
+,0.4-0.8,0.6L8.4\
+,2.8h-6c-1.3,0-2\
+.1,0.8-2.1,2.1V1\
+7\x0a\x09\x09c0,1.3,0.8,2\
+.8,2.1,2.8h18.5c\
+1.3,0,2.3-1.5,2.\
+3-2.8V4.9C23.2,3\
+.6,22.2,2.8,20.9\
+,2.8z M2.2,17V4.\
+9c0-0.2-0.1-0.1,\
+0.1-0.1h3.9v13H2\
+.4\x0a\x09\x09C2.2,17.8,2\
+.2,17.2,2.2,17z \
+M21.2,17c0,0.2-0\
+.1,0.8-0.3,0.8H7\
+.2v-13h1.7c0.3,0\
+,0.6-0.4,0.8-0.6\
+l1.7-2.4h5.3l1.7\
+,2.4\x0a\x09\x09c0.2,0.2,\
+0.5,0.6,0.8,0.6h\
+1.8c0.2,0,0.3-0.\
+1,0.3,0.1V17z\x22/>\
+\x0a</g>\x0a</svg>\x0a\
+"
+
+qt_resource_name = b"\
+\x00\x06\
+\x07\x03}\xc3\
+\x00i\
+\x00m\x00a\x00g\x00e\x00s\
+\x00\x0b\
+\x0a6\xa3\xa7\
+\x00s\
+\x00h\x00u\x00t\x00t\x00e\x00r\x00.\x00s\x00v\x00g\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01\x9b\xc6\x15\x833\
+"
+
+def qInitResources():
+ QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+def qCleanupResources():
+ QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+qInitResources()
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 23.3 19.4" style="enable-background:new 0 0 23.3 19.4;" xml:space="preserve">
-<style type="text/css">
- .st0{fill:none;}
-</style>
-<g>
- <path class="st0" d="M6.2,4.8H2.4c-0.2,0-0.1-0.1-0.1,0.1V17c0,0.2-0.1,0.8,0.1,0.8h3.9V4.8z"/>
- <circle class="st0" cx="14" cy="11" r="4.5"/>
- <path class="st0" d="M20.9,4.8h-1.8c-0.3,0-0.6-0.4-0.8-0.6l-1.7-2.4h-5.3L9.7,4.2C9.5,4.4,9.2,4.8,8.9,4.8H7.2v13h13.7
- c0.2,0,0.3-0.6,0.3-0.8V4.9C21.2,4.7,21.1,4.8,20.9,4.8z M14,16.4c-3,0-5.5-2.4-5.5-5.5c0-3,2.4-5.5,5.5-5.5c3,0,5.5,2.4,5.5,5.5
- C19.5,14,17,16.4,14,16.4z"/>
- <path d="M14,5.5C11,5.5,8.6,8,8.6,11c0,3,2.4,5.5,5.5,5.5c3,0,5.5-2.4,5.5-5.5C19.5,8,17,5.5,14,5.5z M14,15.4
- c-2.5,0-4.5-2-4.5-4.5c0-2.5,2-4.5,4.5-4.5c2.5,0,4.5,2,4.5,4.5C18.5,13.4,16.5,15.4,14,15.4z"/>
- <path d="M20.9,2.8h-1.3l-1.7-2.4c-0.2-0.2-0.5-0.6-0.8-0.6h-6.3c-0.3,0-0.6,0.4-0.8,0.6L8.4,2.8h-6c-1.3,0-2.1,0.8-2.1,2.1V17
- c0,1.3,0.8,2.8,2.1,2.8h18.5c1.3,0,2.3-1.5,2.3-2.8V4.9C23.2,3.6,22.2,2.8,20.9,2.8z M2.2,17V4.9c0-0.2-0.1-0.1,0.1-0.1h3.9v13H2.4
- C2.2,17.8,2.2,17.2,2.2,17z M21.2,17c0,0.2-0.1,0.8-0.3,0.8H7.2v-13h1.7c0.3,0,0.6-0.4,0.8-0.6l1.7-2.4h5.3l1.7,2.4
- c0.2,0.2,0.5,0.6,0.8,0.6h1.8c0.2,0,0.3-0.1,0.3,0.1V17z"/>
-</g>
-</svg>
################################################################################
## Form generated from reading UI file 'camera.ui'
##
-## Created by: Qt User Interface Compiler version 6.8.0
+## Created by: Qt User Interface Compiler version 6.10.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
QMainWindow, QMenu, QMenuBar, QPushButton,
QSizePolicy, QSpacerItem, QStackedWidget, QStatusBar,
QTabWidget, QWidget)
+import rc_camera
class Ui_Camera(object):
def setupUi(self, Camera):
self.takeImageButton = QPushButton(self.tab_2)
self.takeImageButton.setObjectName(u"takeImageButton")
self.takeImageButton.setEnabled(False)
+ icon = QIcon()
+ icon.addFile(u":/images/shutter.svg", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
+ self.takeImageButton.setIcon(icon)
+ self.takeImageButton.setIcon(icon)
self.gridLayout.addWidget(self.takeImageButton, 0, 0, 1, 1)
################################################################################
## Form generated from reading UI file 'camera_mobile.ui'
##
-## Created by: Qt User Interface Compiler version 6.8.0
+## Created by: Qt User Interface Compiler version 6.10.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
QPushButton, QSizePolicy, QSlider, QSpacerItem,
QStackedWidget, QStatusBar, QTabWidget, QVBoxLayout,
QWidget)
+import rc_camera
class Ui_Camera(object):
def setupUi(self, Camera):
################################################################################
## Form generated from reading UI file 'videosettings_mobile.ui'
##
-## Created by: Qt User Interface Compiler version 6.8.0
+## Created by: Qt User Interface Compiler version 6.10.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@Slot(QItemSelection)
def on_current_screen_selection_changed(self, selection):
self.clear_error_string()
- indexes = selection.indexes()
- if indexes:
+ if indexes := selection.indexes():
self._screen_capture.setScreen(self._screen_list_model.screen(indexes[0]))
self.update_active(SourceType.Screen, self.is_active())
self._window_list_view.clearSelection()
@Slot(QItemSelection)
def on_current_window_selection_changed(self, selection):
self.clear_error_string()
- indexes = selection.indexes()
- if indexes:
+ if indexes := selection.indexes():
window = self._window_list_model.window(indexes[0])
if not window.isValid():
m = "The window is no longer valid. Update the list of windows?"
self.timer.stop()
self.popup.hide()
self.editor.setFocus()
- item = self.popup.currentItem()
- if item:
+ if item := self.popup.currentItem():
self.editor.setText(item.text(0))
self.editor.returnPressed.emit()
--- /dev/null
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Dialogs
+import QtQuick.Layouts
+import QtQuick.Pdf
+
+ApplicationWindow {
+ id: root
+ width: 800
+ height: 1024
+ color: "lightgrey"
+ title: document.title
+ visible: true
+ required property url source // for main.py
+ property real scaleStep: Math.sqrt(2)
+
+ header: ToolBar {
+ RowLayout {
+ anchors.fill: parent
+ anchors.rightMargin: 6
+ ToolButton {
+ action: Action {
+ shortcut: StandardKey.Open
+ icon.source: "qrc:/pdfviewer/resources/document-open.svg"
+ onTriggered: fileDialog.open()
+ }
+ }
+ ToolButton {
+ action: Action {
+ shortcut: StandardKey.ZoomIn
+ enabled: view.sourceSize.width < 10000
+ icon.source: "qrc:/pdfviewer/resources/zoom-in.svg"
+ onTriggered: view.renderScale *= root.scaleStep
+ }
+ }
+ ToolButton {
+ action: Action {
+ shortcut: StandardKey.ZoomOut
+ enabled: view.sourceSize.width > 50
+ icon.source: "qrc:/pdfviewer/resources/zoom-out.svg"
+ onTriggered: view.renderScale /= root.scaleStep
+ }
+ }
+ ToolButton {
+ action: Action {
+ icon.source: "qrc:/pdfviewer/resources/zoom-fit-width.svg"
+ onTriggered: view.scaleToWidth(root.contentItem.width, root.contentItem.height)
+ }
+ }
+ ToolButton {
+ action: Action {
+ icon.source: "qrc:/pdfviewer/resources/zoom-fit-best.svg"
+ onTriggered: view.scaleToPage(root.contentItem.width, root.contentItem.height)
+ }
+ }
+ ToolButton {
+ action: Action {
+ shortcut: "Ctrl+0"
+ icon.source: "qrc:/pdfviewer/resources/zoom-original.svg"
+ onTriggered: view.resetScale()
+ }
+ }
+ ToolButton {
+ action: Action {
+ shortcut: "Ctrl+L"
+ icon.source: "qrc:/pdfviewer/resources/rotate-left.svg"
+ onTriggered: view.pageRotation -= 90
+ }
+ }
+ ToolButton {
+ action: Action {
+ shortcut: "Ctrl+R"
+ icon.source: "qrc:/pdfviewer/resources/rotate-right.svg"
+ onTriggered: view.pageRotation += 90
+ }
+ }
+ ToolButton {
+ action: Action {
+ icon.source: "qrc:/pdfviewer/resources/go-previous-view-page.svg"
+ enabled: view.backEnabled
+ onTriggered: view.back()
+ }
+ ToolTip.visible: enabled && hovered
+ ToolTip.delay: 2000
+ ToolTip.text: "go back"
+ }
+ SpinBox {
+ id: currentPageSB
+ from: 1
+ to: document.pageCount
+ editable: true
+ value: view.currentPage + 1
+ onValueModified: view.goToPage(value - 1)
+ Shortcut {
+ sequence: StandardKey.MoveToPreviousPage
+ onActivated: view.goToPage(currentPageSB.value - 2)
+ }
+ Shortcut {
+ sequence: StandardKey.MoveToNextPage
+ onActivated: view.goToPage(currentPageSB.value)
+ }
+ }
+ ToolButton {
+ action: Action {
+ icon.source: "qrc:/pdfviewer/resources/go-next-view-page.svg"
+ enabled: view.forwardEnabled
+ onTriggered: view.forward()
+ }
+ ToolTip.visible: enabled && hovered
+ ToolTip.delay: 2000
+ ToolTip.text: "go forward"
+ }
+ ToolButton {
+ action: Action {
+ shortcut: StandardKey.SelectAll
+ icon.source: "qrc:/pdfviewer/resources/edit-select-all.svg"
+ onTriggered: view.selectAll()
+ }
+ }
+ ToolButton {
+ action: Action {
+ shortcut: StandardKey.Copy
+ icon.source: "qrc:/pdfviewer/resources/edit-copy.svg"
+ enabled: view.selectedText !== ""
+ onTriggered: view.copySelectionToClipboard()
+ }
+ }
+ Shortcut {
+ sequences: [ StandardKey.Find ]
+ onActivated: searchField.forceActiveFocus()
+ }
+ Shortcut {
+ sequences: [ StandardKey.Quit ]
+ onActivated: Qt.quit()
+ }
+ }
+ }
+
+ FileDialog {
+ id: fileDialog
+ title: "Open a PDF file"
+ nameFilters: [ "PDF files (*.pdf)" ]
+ onAccepted: document.source = selectedFile
+ }
+
+ Dialog {
+ id: passwordDialog
+ title: "Password"
+ standardButtons: Dialog.Ok | Dialog.Cancel
+ modal: true
+ closePolicy: Popup.CloseOnEscape
+ anchors.centerIn: parent
+ width: 300
+
+ contentItem: TextField {
+ id: passwordField
+ placeholderText: qsTr("Please provide the password")
+ echoMode: TextInput.Password
+ width: parent.width
+ onAccepted: passwordDialog.accept()
+ }
+ onOpened: function() { passwordField.forceActiveFocus() }
+ onAccepted: document.password = passwordField.text
+ }
+
+ Dialog {
+ id: errorDialog
+ title: "Error loading " + document.source
+ standardButtons: Dialog.Close
+ modal: true
+ closePolicy: Popup.CloseOnEscape
+ anchors.centerIn: parent
+ width: 300
+ visible: document.status === PdfDocument.Error
+
+ contentItem: Label {
+ id: errorField
+ text: document.error
+ }
+ }
+
+ PdfScrollablePageView {
+ id: view
+ anchors.fill: parent
+ anchors.leftMargin: searchDrawer.position * searchDrawer.width
+ document: PdfDocument {
+ id: document
+ source: Qt.resolvedUrl(root.source)
+ onPasswordRequired: passwordDialog.open()
+ }
+ searchString: searchField.text
+ }
+
+ Drawer {
+ id: searchDrawer
+ edge: Qt.LeftEdge
+// modal: false
+// dim: false // commented out as workaround for QTBUG-83859
+ width: 300
+ y: root.header.height
+ height: view.height
+ clip: true
+ ListView {
+ id: searchResultsList
+ anchors.fill: parent
+ anchors.margins: 2
+ model: view.searchModel
+ currentIndex: view.searchModel.currentResult
+ ScrollBar.vertical: ScrollBar { }
+ delegate: ItemDelegate {
+ id: resultDelegate
+ required property int index
+ required property int page
+ required property string contextBefore
+ required property string contextAfter
+ width: parent ? parent.width : 0
+ RowLayout {
+ anchors.fill: parent
+ spacing: 0
+ Label {
+ text: "Page " + (resultDelegate.page + 1) + ": "
+ }
+ Label {
+ text: resultDelegate.contextBefore
+ elide: Text.ElideLeft
+ horizontalAlignment: Text.AlignRight
+ Layout.fillWidth: true
+ Layout.preferredWidth: parent.width / 2
+ }
+ Label {
+ font.bold: true
+ text: view.searchString
+ width: implicitWidth
+ }
+ Label {
+ text: resultDelegate.contextAfter
+ elide: Text.ElideRight
+ Layout.fillWidth: true
+ Layout.preferredWidth: parent.width / 2
+ }
+ }
+ highlighted: ListView.isCurrentItem
+ onClicked: view.searchModel.currentResult = resultDelegate.index
+ }
+ }
+ }
+
+ footer: ToolBar {
+ height: footerRow.implicitHeight
+ RowLayout {
+ id: footerRow
+ anchors.fill: parent
+ ToolButton {
+ action: Action {
+ icon.source: "qrc:/pdfviewer/resources/go-up-search.svg"
+ shortcut: StandardKey.FindPrevious
+ onTriggered: view.searchBack()
+ }
+ ToolTip.visible: enabled && hovered
+ ToolTip.delay: 2000
+ ToolTip.text: "find previous"
+ }
+ TextField {
+ id: searchField
+ placeholderText: "search"
+ Layout.minimumWidth: 150
+ Layout.maximumWidth: 300
+ Layout.fillWidth: true
+ onAccepted: searchDrawer.open()
+ Image {
+ visible: searchField.text !== ""
+ source: "qrc:/pdfviewer/resources/edit-clear.svg"
+ anchors {
+ right: parent.right
+ top: parent.top
+ bottom: parent.bottom
+ margins: 3
+ rightMargin: 5
+ }
+ TapHandler {
+ onTapped: searchField.clear()
+ }
+ }
+ }
+ ToolButton {
+ action: Action {
+ icon.source: "qrc:/pdfviewer/resources/go-down-search.svg"
+ shortcut: StandardKey.FindNext
+ onTriggered: view.searchForward()
+ }
+ ToolTip.visible: enabled && hovered
+ ToolTip.delay: 2000
+ ToolTip.text: "find next"
+ }
+ Label {
+ Layout.fillWidth: true
+ property size implicitPointSize: document.pagePointSize(view.currentPage)
+ text: "page " + (view.currentPage + 1) + " of " + document.pageCount +
+ " scale " + view.renderScale.toFixed(2) +
+ " original " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + "pts"
+ visible: document.status === PdfDocument.Ready
+ }
+ }
+ }
+}
--- /dev/null
+module SinglePageModule
+Viewer 254.0 Viewer.qml
engine = QQmlApplicationEngine()
engine.setInitialProperties({"source": url})
-
- engine.load(QUrl.fromLocalFile(os.fspath(dir / "viewer.qml")))
+ engine.addImportPath(Path(__file__).parent)
+ engine.loadFromModule("SinglePageModule", "Viewer")
if not engine.rootObjects():
sys.exit(-1)
{
- "files": ["main.py", "viewer.qml", "viewer.qrc", "resources/test.pdf"]
+ "files": ["main.py", "SinglePageModule/Viewer.qml", "SinglePageModule/qmldir",
+ "viewer.qrc", "resources/test.pdf"]
}
+++ /dev/null
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Dialogs
-import QtQuick.Layouts
-import QtQuick.Pdf
-
-ApplicationWindow {
- id: root
- width: 800
- height: 1024
- color: "lightgrey"
- title: document.title
- visible: true
- required property url source // for main.py
- property real scaleStep: Math.sqrt(2)
-
- header: ToolBar {
- RowLayout {
- anchors.fill: parent
- anchors.rightMargin: 6
- ToolButton {
- action: Action {
- shortcut: StandardKey.Open
- icon.source: "qrc:/pdfviewer/resources/document-open.svg"
- onTriggered: fileDialog.open()
- }
- }
- ToolButton {
- action: Action {
- shortcut: StandardKey.ZoomIn
- enabled: view.sourceSize.width < 10000
- icon.source: "qrc:/pdfviewer/resources/zoom-in.svg"
- onTriggered: view.renderScale *= root.scaleStep
- }
- }
- ToolButton {
- action: Action {
- shortcut: StandardKey.ZoomOut
- enabled: view.sourceSize.width > 50
- icon.source: "qrc:/pdfviewer/resources/zoom-out.svg"
- onTriggered: view.renderScale /= root.scaleStep
- }
- }
- ToolButton {
- action: Action {
- icon.source: "qrc:/pdfviewer/resources/zoom-fit-width.svg"
- onTriggered: view.scaleToWidth(root.contentItem.width, root.contentItem.height)
- }
- }
- ToolButton {
- action: Action {
- icon.source: "qrc:/pdfviewer/resources/zoom-fit-best.svg"
- onTriggered: view.scaleToPage(root.contentItem.width, root.contentItem.height)
- }
- }
- ToolButton {
- action: Action {
- shortcut: "Ctrl+0"
- icon.source: "qrc:/pdfviewer/resources/zoom-original.svg"
- onTriggered: view.resetScale()
- }
- }
- ToolButton {
- action: Action {
- shortcut: "Ctrl+L"
- icon.source: "qrc:/pdfviewer/resources/rotate-left.svg"
- onTriggered: view.pageRotation -= 90
- }
- }
- ToolButton {
- action: Action {
- shortcut: "Ctrl+R"
- icon.source: "qrc:/pdfviewer/resources/rotate-right.svg"
- onTriggered: view.pageRotation += 90
- }
- }
- ToolButton {
- action: Action {
- icon.source: "qrc:/pdfviewer/resources/go-previous-view-page.svg"
- enabled: view.backEnabled
- onTriggered: view.back()
- }
- ToolTip.visible: enabled && hovered
- ToolTip.delay: 2000
- ToolTip.text: "go back"
- }
- SpinBox {
- id: currentPageSB
- from: 1
- to: document.pageCount
- editable: true
- value: view.currentPage + 1
- onValueModified: view.goToPage(value - 1)
- Shortcut {
- sequence: StandardKey.MoveToPreviousPage
- onActivated: view.goToPage(currentPageSB.value - 2)
- }
- Shortcut {
- sequence: StandardKey.MoveToNextPage
- onActivated: view.goToPage(currentPageSB.value)
- }
- }
- ToolButton {
- action: Action {
- icon.source: "qrc:/pdfviewer/resources/go-next-view-page.svg"
- enabled: view.forwardEnabled
- onTriggered: view.forward()
- }
- ToolTip.visible: enabled && hovered
- ToolTip.delay: 2000
- ToolTip.text: "go forward"
- }
- ToolButton {
- action: Action {
- shortcut: StandardKey.SelectAll
- icon.source: "qrc:/pdfviewer/resources/edit-select-all.svg"
- onTriggered: view.selectAll()
- }
- }
- ToolButton {
- action: Action {
- shortcut: StandardKey.Copy
- icon.source: "qrc:/pdfviewer/resources/edit-copy.svg"
- enabled: view.selectedText !== ""
- onTriggered: view.copySelectionToClipboard()
- }
- }
- Shortcut {
- sequence: StandardKey.Find
- onActivated: searchField.forceActiveFocus()
- }
- Shortcut {
- sequence: StandardKey.Quit
- onActivated: Qt.quit()
- }
- }
- }
-
- FileDialog {
- id: fileDialog
- title: "Open a PDF file"
- nameFilters: [ "PDF files (*.pdf)" ]
- onAccepted: document.source = selectedFile
- }
-
- Dialog {
- id: passwordDialog
- title: "Password"
- standardButtons: Dialog.Ok | Dialog.Cancel
- modal: true
- closePolicy: Popup.CloseOnEscape
- anchors.centerIn: parent
- width: 300
-
- contentItem: TextField {
- id: passwordField
- placeholderText: qsTr("Please provide the password")
- echoMode: TextInput.Password
- width: parent.width
- onAccepted: passwordDialog.accept()
- }
- onOpened: function() { passwordField.forceActiveFocus() }
- onAccepted: document.password = passwordField.text
- }
-
- Dialog {
- id: errorDialog
- title: "Error loading " + document.source
- standardButtons: Dialog.Close
- modal: true
- closePolicy: Popup.CloseOnEscape
- anchors.centerIn: parent
- width: 300
- visible: document.status === PdfDocument.Error
-
- contentItem: Label {
- id: errorField
- text: document.error
- }
- }
-
- PdfScrollablePageView {
- id: view
- anchors.fill: parent
- anchors.leftMargin: searchDrawer.position * searchDrawer.width
- document: PdfDocument {
- id: document
- source: Qt.resolvedUrl(root.source)
- onPasswordRequired: passwordDialog.open()
- }
- searchString: searchField.text
- }
-
- Drawer {
- id: searchDrawer
- edge: Qt.LeftEdge
-// modal: false
-// dim: false // commented out as workaround for QTBUG-83859
- width: 300
- y: root.header.height
- height: view.height
- clip: true
- ListView {
- id: searchResultsList
- anchors.fill: parent
- anchors.margins: 2
- model: view.searchModel
- currentIndex: view.searchModel.currentResult
- ScrollBar.vertical: ScrollBar { }
- delegate: ItemDelegate {
- id: resultDelegate
- required property int index
- required property int page
- required property string contextBefore
- required property string contextAfter
- width: parent ? parent.width : 0
- RowLayout {
- anchors.fill: parent
- spacing: 0
- Label {
- text: "Page " + (resultDelegate.page + 1) + ": "
- }
- Label {
- text: resultDelegate.contextBefore
- elide: Text.ElideLeft
- horizontalAlignment: Text.AlignRight
- Layout.fillWidth: true
- Layout.preferredWidth: parent.width / 2
- }
- Label {
- font.bold: true
- text: view.searchString
- width: implicitWidth
- }
- Label {
- text: resultDelegate.contextAfter
- elide: Text.ElideRight
- Layout.fillWidth: true
- Layout.preferredWidth: parent.width / 2
- }
- }
- highlighted: ListView.isCurrentItem
- onClicked: view.searchModel.currentResult = resultDelegate.index
- }
- }
- }
-
- footer: ToolBar {
- height: footerRow.implicitHeight
- RowLayout {
- id: footerRow
- anchors.fill: parent
- ToolButton {
- action: Action {
- icon.source: "qrc:/pdfviewer/resources/go-up-search.svg"
- shortcut: StandardKey.FindPrevious
- onTriggered: view.searchBack()
- }
- ToolTip.visible: enabled && hovered
- ToolTip.delay: 2000
- ToolTip.text: "find previous"
- }
- TextField {
- id: searchField
- placeholderText: "search"
- Layout.minimumWidth: 150
- Layout.maximumWidth: 300
- Layout.fillWidth: true
- onAccepted: searchDrawer.open()
- Image {
- visible: searchField.text !== ""
- source: "qrc:/pdfviewer/resources/edit-clear.svg"
- anchors {
- right: parent.right
- top: parent.top
- bottom: parent.bottom
- margins: 3
- rightMargin: 5
- }
- TapHandler {
- onTapped: searchField.clear()
- }
- }
- }
- ToolButton {
- action: Action {
- icon.source: "qrc:/pdfviewer/resources/go-down-search.svg"
- shortcut: StandardKey.FindNext
- onTriggered: view.searchForward()
- }
- ToolTip.visible: enabled && hovered
- ToolTip.delay: 2000
- ToolTip.text: "find next"
- }
- Label {
- Layout.fillWidth: true
- property size implicitPointSize: document.pagePointSize(view.currentPage)
- text: "page " + (view.currentPage + 1) + " of " + document.pageCount +
- " scale " + view.renderScale.toFixed(2) +
- " original " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + "pts"
- visible: document.status === PdfDocument.Ready
- }
- }
- }
-}
--- /dev/null
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Window
+import BaseModel
+
+Window {
+ title: "Moving Rectangle"
+ width: 800
+ height: 480
+ visible: true
+ id: mainWindow
+
+ Column {
+ spacing: 20
+ anchors.fill: parent
+ id: mainColumn
+ Text {
+ padding: 20
+ font.pointSize: 10
+ width: 600
+ wrapMode: Text.Wrap
+ text: "This example shows how to add, remove and move items inside a QML ListView.\n
+It shows and edits data via roles using QAbstractListModel on the Python side.\n
+Use the 'Middle click' on top of a rectangle to remove an item.\n
+'Left click' and drag to move the items."
+ }
+
+ Button {
+ anchors {
+ left: mainColumn.left
+ right: mainColumn.right
+ margins: 30
+ }
+ text: "Reset view"
+ onClicked: lv.model.reset()
+ }
+
+ Button {
+ anchors {
+ left: mainColumn.left
+ right: mainColumn.right
+ margins: 30
+ }
+ text: "Add element"
+ onClicked: lv.model.append()
+ }
+
+ ListView {
+ id: lv
+ anchors {
+ left: mainColumn.left
+ right: mainColumn.right
+ margins: 30
+ }
+
+ height: 200
+ model: BaseModel {}
+ orientation: ListView.Horizontal
+ displaced: Transition {
+ NumberAnimation {
+ properties: "x,y"
+ easing.type: Easing.OutQuad
+ }
+ }
+ delegate: DropArea {
+ id: droparea
+ width: ratio * lv.width
+ height: lv.height
+
+ onEntered: function (drag) {
+ let dragindex = drag.source.modelIndex
+ if (index === dragindex)
+ return
+ lv.model.move(dragindex, index)
+ }
+
+ MovingRectangle {
+ modelIndex: index
+ dragParent: lv
+ sizeParent: droparea
+ }
+ }
+
+ MouseArea {
+ id: lvMousearea
+ anchors.fill: lv
+ z: -1
+ }
+ Rectangle {
+ id: lvBackground
+ anchors.fill: lv
+ anchors.margins: -border.width
+ color: "white"
+ border.color: "black"
+ border.width: 5
+ z: -1
+ }
+ Component.onCompleted: {
+ lv.model.reset()
+ }
+ }
+ }
+}
--- /dev/null
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+
+import QtQuick
+import QtQuick.Controls
+
+Rectangle {
+ id: root
+ property int modelIndex
+ property Item dragParent
+ property Item sizeParent
+ property alias text: zone.text
+ property alias bgColor: root.color
+
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ }
+ color: backgroundColor
+ anchors.fill: sizeParent
+ border.color: "yellow"
+ border.width: 0
+ TextArea {
+ id: zone
+ anchors.centerIn: parent
+ text: display
+ onTextChanged: model.edit = text
+ }
+
+ MouseArea {
+ id: zoneMouseArea
+ anchors.fill: parent
+
+ acceptedButtons: Qt.MiddleButton
+ onClicked: function(mouse) {
+ if (mouse.button == Qt.MiddleButton)
+ lv.model.remove(index)
+ else
+ mouse.accepted = false
+ }
+ }
+ DragHandler {
+ id: dragHandler
+ xAxis {
+
+ enabled: true
+ minimum: 0
+ maximum: lv.width - droparea.width
+ }
+ yAxis.enabled: false
+ acceptedButtons: Qt.LeftButton
+ }
+ Drag.active: dragHandler.active
+ Drag.source: root
+ Drag.hotSpot.x: width / 2
+
+ states: [
+ State {
+ when: dragHandler.active
+ ParentChange {
+ target: root
+ parent: root.dragParent
+ }
+
+ AnchorChanges {
+ target: root
+ anchors.horizontalCenter: undefined
+ anchors.verticalCenter: undefined
+ }
+ PropertyChanges {
+ target: root
+ opacity: 0.6
+ border.width: 3
+ }
+ }
+ ]
+}
--- /dev/null
+module UsingModel
+Main 1.0 Main.qml
+MovingRectangle 1.0 MovingRectangle.qml
+++ /dev/null
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-
-import QtQuick
-import QtQuick.Controls
-
-Rectangle {
- id: root
- property int modelIndex
- property Item dragParent
- property Item sizeParent
- property alias text: zone.text
- property alias bgColor: root.color
-
- anchors {
- horizontalCenter: parent.horizontalCenter
- verticalCenter: parent.verticalCenter
- }
- color: backgroundColor
- anchors.fill: sizeParent
- border.color: "yellow"
- border.width: 0
- TextArea {
- id: zone
- anchors.centerIn: parent
- text: display
- onTextChanged: model.edit = text
- }
-
- MouseArea {
- id: zoneMouseArea
- anchors.fill: parent
-
- acceptedButtons: Qt.MiddleButton
- onClicked: function(mouse) {
- if (mouse.button == Qt.MiddleButton)
- lv.model.remove(index)
- else
- mouse.accepted = false
- }
- }
- DragHandler {
- id: dragHandler
- xAxis {
-
- enabled: true
- minimum: 0
- maximum: lv.width - droparea.width
- }
- yAxis.enabled: false
- acceptedButtons: Qt.LeftButton
- }
- Drag.active: dragHandler.active
- Drag.source: root
- Drag.hotSpot.x: width / 2
-
- states: [
- State {
- when: dragHandler.active
- ParentChange {
- target: root
- parent: root.dragParent
- }
-
- AnchorChanges {
- target: root
- anchors.horizontalCenter: undefined
- anchors.verticalCenter: undefined
- }
- PropertyChanges {
- target: root
- opacity: 0.6
- border.width: 3
- }
- }
- ]
-}
.. tags:: Android
-This example shows how to add, remove and move items inside a QML
-ListView, but showing and editing the data via roles using a
-QAbstractListModel from Python.
+This example shows how to add, remove and move items inside a
+`QML ListView`_, but showing and editing the data via roles using a
+:class:`~PySide6.QtCore.QAbstractListModel` from Python.
You can add new elements and reset the view using the two top buttons,
remove elements by 'middle click' the element, and move the elements
with a 'left click' plus dragging the item around.
.. image:: qabstractlistmodelqml.png
- :width: 400
+ :width: 800
:alt: QAbstractListModel/ListView Screenshot
+
+.. _`QML ListView`: https://doc.qt.io/qt-6/qml-qtquick-listview.html
import sys
from pathlib import Path
-from PySide6.QtCore import QUrl
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
- qml_file = Path(__file__).parent / "main.qml"
- engine.load(QUrl.fromLocalFile(qml_file))
+ engine.addImportPath(Path(__file__).parent)
+ engine.loadFromModule("EditingModel", "Main")
if not engine.rootObjects():
sys.exit(-1)
{
- "files": ["model.py","main.qml","main.py","MovingRectangle.qml"]
+ "files": ["main.py", "model.py", "EditingModel/Main.qml",
+ "EditingModel/MovingRectangle.qml", "EditingModel/qmldir"]
}
+++ /dev/null
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Window
-import BaseModel
-
-Window {
- title: "Moving Rectangle"
- width: 800
- height: 480
- visible: true
- id: mainWindow
-
- Column {
- spacing: 20
- anchors.fill: parent
- id: mainColumn
- Text {
- padding: 20
- font.pointSize: 10
- width: 600
- wrapMode: Text.Wrap
- text: "This example shows how to add, remove and move items inside a QML ListView.\n
-It shows and edits data via roles using QAbstractListModel on the Python side.\n
-Use the 'Middle click' on top of a rectangle to remove an item.\n
-'Left click' and drag to move the items."
- }
-
- Button {
- anchors {
- left: mainColumn.left
- right: mainColumn.right
- margins: 30
- }
- text: "Reset view"
- onClicked: lv.model.reset()
- }
-
- Button {
- anchors {
- left: mainColumn.left
- right: mainColumn.right
- margins: 30
- }
- text: "Add element"
- onClicked: lv.model.append()
- }
-
- ListView {
- id: lv
- anchors {
- left: mainColumn.left
- right: mainColumn.right
- margins: 30
- }
-
- height: 200
- model: BaseModel {}
- orientation: ListView.Horizontal
- displaced: Transition {
- NumberAnimation {
- properties: "x,y"
- easing.type: Easing.OutQuad
- }
- }
- delegate: DropArea {
- id: droparea
- width: ratio * lv.width
- height: lv.height
-
- onEntered: function (drag) {
- let dragindex = drag.source.modelIndex
- if (index === dragindex)
- return
- lv.model.move(dragindex, index)
- }
-
- MovingRectangle {
- modelIndex: index
- dragParent: lv
- sizeParent: droparea
- }
- }
-
- MouseArea {
- id: lvMousearea
- anchors.fill: lv
- z: -1
- }
- Rectangle {
- id: lvBackground
- anchors.fill: lv
- anchors.margins: -border.width
- color: "white"
- border.color: "black"
- border.width: 5
- z: -1
- }
- Component.onCompleted: {
- lv.model.reset()
- }
- }
- }
-}
--- /dev/null
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtQuick.Window
+import QtQuick.Controls.Material
+
+import io.qt.textproperties
+
+ApplicationWindow {
+ id: page
+ width: 800
+ height: 400
+ visible: true
+ Material.theme: Material.Dark
+ Material.accent: Material.Red
+
+ Bridge {
+ id: bridge
+ }
+
+ GridLayout {
+ id: grid
+ columns: 2
+ rows: 3
+
+ ColumnLayout {
+ spacing: 2
+ Layout.columnSpan: 1
+ Layout.preferredWidth: 400
+
+ Text {
+ id: leftlabel
+ Layout.alignment: Qt.AlignHCenter
+ color: "white"
+ font.pointSize: 16
+ text: "Qt for Python"
+ Layout.preferredHeight: 100
+ Material.accent: Material.Green
+ }
+
+ RadioButton {
+ id: italic
+ Layout.alignment: Qt.AlignLeft
+ text: "Italic"
+ onToggled: {
+ leftlabel.font.italic = bridge.getItalic(italic.text)
+ leftlabel.font.bold = bridge.getBold(italic.text)
+ leftlabel.font.underline = bridge.getUnderline(italic.text)
+
+ }
+ }
+ RadioButton {
+ id: bold
+ Layout.alignment: Qt.AlignLeft
+ text: "Bold"
+ onToggled: {
+ leftlabel.font.italic = bridge.getItalic(bold.text)
+ leftlabel.font.bold = bridge.getBold(bold.text)
+ leftlabel.font.underline = bridge.getUnderline(bold.text)
+ }
+ }
+ RadioButton {
+ id: underline
+ Layout.alignment: Qt.AlignLeft
+ text: "Underline"
+ onToggled: {
+ leftlabel.font.italic = bridge.getItalic(underline.text)
+ leftlabel.font.bold = bridge.getBold(underline.text)
+ leftlabel.font.underline = bridge.getUnderline(underline.text)
+ }
+ }
+ RadioButton {
+ id: noneradio
+ Layout.alignment: Qt.AlignLeft
+ text: "None"
+ checked: true
+ onToggled: {
+ leftlabel.font.italic = bridge.getItalic(noneradio.text)
+ leftlabel.font.bold = bridge.getBold(noneradio.text)
+ leftlabel.font.underline = bridge.getUnderline(noneradio.text)
+ }
+ }
+ }
+
+ ColumnLayout {
+ id: rightcolumn
+ spacing: 2
+ Layout.columnSpan: 1
+ Layout.preferredWidth: 400
+ Layout.preferredHeight: 400
+ Layout.fillWidth: true
+
+ RowLayout {
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
+
+
+ Button {
+ id: red
+ text: "Red"
+ highlighted: true
+ Material.accent: Material.Red
+ onClicked: {
+ leftlabel.color = bridge.getColor(red.text)
+ }
+ }
+ Button {
+ id: green
+ text: "Green"
+ highlighted: true
+ Material.accent: Material.Green
+ onClicked: {
+ leftlabel.color = bridge.getColor(green.text)
+ }
+ }
+ Button {
+ id: blue
+ text: "Blue"
+ highlighted: true
+ Material.accent: Material.Blue
+ onClicked: {
+ leftlabel.color = bridge.getColor(blue.text)
+ }
+ }
+ Button {
+ id: nonebutton
+ text: "None"
+ highlighted: true
+ Material.accent: Material.BlueGrey
+ onClicked: {
+ leftlabel.color = bridge.getColor(nonebutton.text)
+ }
+ }
+ }
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
+ Text {
+ id: rightlabel
+ color: "white"
+ Layout.alignment: Qt.AlignLeft
+ text: "Font size"
+ Material.accent: Material.White
+ }
+ Slider {
+ width: rightcolumn.width*0.6
+ Layout.alignment: Qt.AlignRight
+ id: slider
+ value: 0.5
+ onValueChanged: {
+ leftlabel.font.pointSize = bridge.getSize(value)
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+module Bars
+Main 1.0 Main.qml
Text Properties Example
=======================
-A Python application that demonstrates how to load a qml file
-using Material design, to change the look of text.
+A Python application that demonstrates how to load a QML file
+using **Material** design, to change the look of text
+(see :ref:`Styling Qt Quick Controls<Styling-Qt-Quick-Controls>`).
.. image:: textproperties.png
:width: 400
return "#a5d6a7"
elif s.lower() == "blue":
return "#90caf9"
- else:
- return "white"
+ return "white"
@Slot(float, result=int)
def getSize(self, s):
size = int(s * 34)
- if size <= 0:
- return 1
- else:
- return size
+ return max(1, size)
@Slot(str, result=bool)
def getItalic(self, s):
- if s.lower() == "italic":
- return True
- else:
- return False
+ return s.lower() == "italic"
@Slot(str, result=bool)
def getBold(self, s):
- if s.lower() == "bold":
- return True
- else:
- return False
+ return s.lower() == "bold"
@Slot(str, result=bool)
def getUnderline(self, s):
- if s.lower() == "underline":
- return True
- else:
- return False
+ return s.lower() == "underline"
if __name__ == '__main__':
QQuickStyle.setStyle("Material")
engine = QQmlApplicationEngine()
- # Get the path of the current directory, and then add the name
- # of the QML file, to load it.
- qml_file = Path(__file__).parent / 'view.qml'
- engine.load(qml_file)
+ engine.addImportPath(Path(__file__).parent)
+ engine.loadFromModule("TextProperties", "Main")
if not engine.rootObjects():
sys.exit(-1)
{
- "files": ["main.py", "view.qml"]
+ "files": ["main.py", "TextProperties/Main.qml", "TextProperties/qmldir"]
}
+++ /dev/null
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import QtQuick.Window
-import QtQuick.Controls.Material
-
-import io.qt.textproperties
-
-ApplicationWindow {
- id: page
- width: 800
- height: 400
- visible: true
- Material.theme: Material.Dark
- Material.accent: Material.Red
-
- Bridge {
- id: bridge
- }
-
- GridLayout {
- id: grid
- columns: 2
- rows: 3
-
- ColumnLayout {
- spacing: 2
- Layout.columnSpan: 1
- Layout.preferredWidth: 400
-
- Text {
- id: leftlabel
- Layout.alignment: Qt.AlignHCenter
- color: "white"
- font.pointSize: 16
- text: "Qt for Python"
- Layout.preferredHeight: 100
- Material.accent: Material.Green
- }
-
- RadioButton {
- id: italic
- Layout.alignment: Qt.AlignLeft
- text: "Italic"
- onToggled: {
- leftlabel.font.italic = bridge.getItalic(italic.text)
- leftlabel.font.bold = bridge.getBold(italic.text)
- leftlabel.font.underline = bridge.getUnderline(italic.text)
-
- }
- }
- RadioButton {
- id: bold
- Layout.alignment: Qt.AlignLeft
- text: "Bold"
- onToggled: {
- leftlabel.font.italic = bridge.getItalic(bold.text)
- leftlabel.font.bold = bridge.getBold(bold.text)
- leftlabel.font.underline = bridge.getUnderline(bold.text)
- }
- }
- RadioButton {
- id: underline
- Layout.alignment: Qt.AlignLeft
- text: "Underline"
- onToggled: {
- leftlabel.font.italic = bridge.getItalic(underline.text)
- leftlabel.font.bold = bridge.getBold(underline.text)
- leftlabel.font.underline = bridge.getUnderline(underline.text)
- }
- }
- RadioButton {
- id: noneradio
- Layout.alignment: Qt.AlignLeft
- text: "None"
- checked: true
- onToggled: {
- leftlabel.font.italic = bridge.getItalic(noneradio.text)
- leftlabel.font.bold = bridge.getBold(noneradio.text)
- leftlabel.font.underline = bridge.getUnderline(noneradio.text)
- }
- }
- }
-
- ColumnLayout {
- id: rightcolumn
- spacing: 2
- Layout.columnSpan: 1
- Layout.preferredWidth: 400
- Layout.preferredHeight: 400
- Layout.fillWidth: true
-
- RowLayout {
- Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
-
-
- Button {
- id: red
- text: "Red"
- highlighted: true
- Material.accent: Material.Red
- onClicked: {
- leftlabel.color = bridge.getColor(red.text)
- }
- }
- Button {
- id: green
- text: "Green"
- highlighted: true
- Material.accent: Material.Green
- onClicked: {
- leftlabel.color = bridge.getColor(green.text)
- }
- }
- Button {
- id: blue
- text: "Blue"
- highlighted: true
- Material.accent: Material.Blue
- onClicked: {
- leftlabel.color = bridge.getColor(blue.text)
- }
- }
- Button {
- id: nonebutton
- text: "None"
- highlighted: true
- Material.accent: Material.BlueGrey
- onClicked: {
- leftlabel.color = bridge.getColor(nonebutton.text)
- }
- }
- }
- RowLayout {
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
- Text {
- id: rightlabel
- color: "white"
- Layout.alignment: Qt.AlignLeft
- text: "Font size"
- Material.accent: Material.White
- }
- Slider {
- width: rightcolumn.width*0.6
- Layout.alignment: Qt.AlignRight
- id: slider
- value: 0.5
- onValueChanged: {
- leftlabel.font.pointSize = bridge.getSize(value)
- }
- }
- }
- }
- }
-}
component = QQmlComponent(engine)
component.loadFromModule("People", "Main")
- person = component.create()
- if person:
+ if person := component.create():
print(f"The person's name is {person.name}")
print(f"They wear a {person.shoe_size} sized shoe")
else:
name = guest.name
rsvp_date = None
- attached = qmlAttachedPropertiesObject(BirthdayParty, guest, False)
- if attached:
+ if attached := qmlAttachedPropertiesObject(BirthdayParty, guest, False):
rsvp_date = attached.rsvp.toString()
if rsvp_date:
print(f" {name} RSVP date: {rsvp_date}")
name = guest.name
rsvp_date = None
- attached = qmlAttachedPropertiesObject(BirthdayParty, guest, False)
- if attached:
+ if attached := qmlAttachedPropertiesObject(BirthdayParty, guest, False):
rsvp_date = attached.rsvp.toString()
if rsvp_date:
print(f" {name} RSVP date: {rsvp_date}")
name = guest.name
rsvp_date = None
- attached = qmlAttachedPropertiesObject(BirthdayParty, guest, False)
- if attached:
+ if attached := qmlAttachedPropertiesObject(BirthdayParty, guest, False):
rsvp_date = attached.rsvp.toString()
if rsvp_date:
print(f" {name} RSVP date: {rsvp_date}")
component = QQmlComponent(engine)
component.loadFromModule("People", "Main")
- party = component.create()
- if party:
+ if party := component.create():
print(f"{party.host} is having a birthday!\nThey are inviting:")
for g in range(party.guestCount()):
name = party.guest(g).name
--- /dev/null
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import PersonModel
+
+ListView {
+ width: 100
+ height: 100
+ anchors.fill: parent
+ model: PersonModel
+ delegate: Component {
+ Rectangle {
+ height: 25
+ width: 100
+ Text {
+ text: display + ": " + myrole
+ }
+ }
+ }
+}
--- /dev/null
+module UsingModel
+Main 1.0 Main.qml
.. tags:: Android
-A Python application that demonstrates how to use a :ref:`QAbstractListModel`
-with QML.
+A Python application that demonstrates how to use a
+:class:`~PySide6.QtCore.QAbstractListModel` with QML.
.. image:: usingmodel.png
- :width: 400
+ :width: 160
:alt: Using Model Screenshot
--- /dev/null
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
+
+from dataclasses import dataclass
+from pathlib import Path
+import sys
+from PySide6.QtCore import QAbstractListModel, Qt, QByteArray
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtQuick import QQuickView
+from PySide6.QtQml import QmlElement, QmlSingleton
+
+
+QML_IMPORT_NAME = "PersonModel"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@dataclass
+class Person:
+ name: str
+ myrole: str
+
+
+@QmlElement
+@QmlSingleton
+class PersonModel (QAbstractListModel):
+ MyRole = Qt.ItemDataRole.UserRole + 1
+
+ def __init__(self, data, parent=None):
+ super().__init__(parent)
+ self._data = data
+
+ def roleNames(self):
+ roles = {
+ PersonModel.MyRole: QByteArray(b'myrole'),
+ Qt.ItemDataRole.DisplayRole: QByteArray(b'display')
+ }
+ return roles
+
+ def rowCount(self, index):
+ return len(self._data)
+
+ def data(self, index, role):
+ d = self._data[index.row()]
+ if role == Qt.ItemDataRole.DisplayRole:
+ return d.name
+ if role == Qt.ItemDataRole.DecorationRole:
+ return Qt.GlobalColor.black
+ if role == PersonModel.MyRole:
+ return d.myrole
+ return None
+
+ @staticmethod
+ def create(engine):
+ data = [Person('Qt', 'myrole'), Person('PySide', 'role2')]
+ return PersonModel(data)
+
+
+if __name__ == '__main__':
+ app = QGuiApplication(sys.argv)
+ view = QQuickView()
+ view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView)
+ view.engine().addImportPath(Path(__file__).parent)
+ view.loadFromModule("UsingModel", "Main")
+ if view.status() == QQuickView.Status.Error:
+ sys.exit(-1)
+ view.show()
+
+ r = app.exec()
+ # Deleting the view before it goes out of scope is required to make sure all child QML instances
+ # are destroyed in the correct order.
+ del view
+ sys.exit(r)
+++ /dev/null
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import os
-from dataclasses import dataclass
-from pathlib import Path
-import sys
-from PySide6.QtCore import QAbstractListModel, Qt, QUrl, QByteArray
-from PySide6.QtGui import QGuiApplication
-from PySide6.QtQuick import QQuickView
-from PySide6.QtQml import QmlElement, QmlSingleton
-
-
-QML_IMPORT_NAME = "PersonModel"
-QML_IMPORT_MAJOR_VERSION = 1
-
-
-@dataclass
-class Person:
- name: str
- myrole: str
-
-
-@QmlElement
-@QmlSingleton
-class PersonModel (QAbstractListModel):
- MyRole = Qt.ItemDataRole.UserRole + 1
-
- def __init__(self, data, parent=None):
- super().__init__(parent)
- self._data = data
-
- def roleNames(self):
- roles = {
- PersonModel.MyRole: QByteArray(b'myrole'),
- Qt.ItemDataRole.DisplayRole: QByteArray(b'display')
- }
- return roles
-
- def rowCount(self, index):
- return len(self._data)
-
- def data(self, index, role):
- d = self._data[index.row()]
- if role == Qt.ItemDataRole.DisplayRole:
- return d.name
- if role == Qt.ItemDataRole.DecorationRole:
- return Qt.GlobalColor.black
- if role == PersonModel.MyRole:
- return d.myrole
- return None
-
- @staticmethod
- def create(engine):
- data = [Person('Qt', 'myrole'), Person('PySide', 'role2')]
- return PersonModel(data)
-
-
-if __name__ == '__main__':
- app = QGuiApplication(sys.argv)
- view = QQuickView()
- view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView)
-
- qml_file = os.fspath(Path(__file__).resolve().parent / 'view.qml')
- view.setSource(QUrl.fromLocalFile(qml_file))
- if view.status() == QQuickView.Status.Error:
- sys.exit(-1)
- view.show()
-
- r = app.exec()
- # Deleting the view before it goes out of scope is required to make sure all child QML instances
- # are destroyed in the correct order.
- del view
- sys.exit(r)
{
- "files": ["view.qml", "usingmodel.py"]
+ "files": ["main.py", "UsingModel/Main.qml", "UsingModel/qmldir"]
}
+++ /dev/null
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import PersonModel
-
-ListView {
- width: 100
- height: 100
- anchors.fill: parent
- model: PersonModel
- delegate: Component {
- Rectangle {
- height: 25
- width: 100
- Text {
- text: display + ": " + myrole
- }
- }
- }
-}
--- /dev/null
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ListView {
+ id: listview
+ width: 200; height: 320
+ required model
+ ScrollBar.vertical: ScrollBar { }
+
+ delegate: Rectangle {
+ width: listview.width; height: 25
+
+ required color
+ required property string name
+
+ Text { text: parent.name }
+ }
+}
--- /dev/null
+module ObjectListModel
+Main 1.0 Main.qml
.. tags:: Android
-A list of QObject values can also be used as a model.
+A list of :class:`~PySide6.QtCore.QObject` values can also be used as a model.
A list[QObject,] provides the properties of the objects in the list as roles.
The following application creates a DataObject class with Property values
that will be accessible as named roles when a list[DataObject,] is exposed to QML:
.. image:: objectlistmodel.png
- :width: 400
+ :width: 200
:alt: Object List Model Screenshot
--- /dev/null
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
+
+from pathlib import Path
+import sys
+from PySide6.QtCore import QObject, Property, Signal
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtQuick import QQuickView
+
+# This example illustrates exposing a list of QObjects as a model in QML
+
+
+class DataObject(QObject):
+
+ nameChanged = Signal()
+ colorChanged = Signal()
+
+ def __init__(self, name, color, parent=None):
+ super().__init__(parent)
+ self._name = name
+ self._color = color
+
+ def name(self):
+ return self._name
+
+ def setName(self, name):
+ if name != self._name:
+ self._name = name
+ self.nameChanged.emit()
+
+ def color(self):
+ return self._color
+
+ def setColor(self, color):
+ if color != self._color:
+ self._color = color
+ self.colorChanged.emit()
+
+ name = Property(str, name, setName, notify=nameChanged)
+ color = Property(str, color, setColor, notify=colorChanged)
+
+
+if __name__ == '__main__':
+ app = QGuiApplication(sys.argv)
+
+ dataList = [DataObject("Item 1", "red"),
+ DataObject("Item 2", "green"),
+ DataObject("Item 3", "blue"),
+ DataObject("Item 4", "yellow")]
+
+ view = QQuickView()
+ view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView)
+ view.setInitialProperties({"model": dataList})
+
+ qml_file = Path(__file__).parent / "view.qml"
+ view.engine().addImportPath(Path(__file__).parent)
+ view.loadFromModule("ObjectListModel", "Main")
+ view.show()
+
+ r = app.exec()
+ del view
+ sys.exit(r)
+++ /dev/null
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-from pathlib import Path
-import sys
-from PySide6.QtCore import QObject, QUrl, Property, Signal
-from PySide6.QtGui import QGuiApplication
-from PySide6.QtQuick import QQuickView
-
-# This example illustrates exposing a list of QObjects as a model in QML
-
-
-class DataObject(QObject):
-
- nameChanged = Signal()
- colorChanged = Signal()
-
- def __init__(self, name, color, parent=None):
- super().__init__(parent)
- self._name = name
- self._color = color
-
- def name(self):
- return self._name
-
- def setName(self, name):
- if name != self._name:
- self._name = name
- self.nameChanged.emit()
-
- def color(self):
- return self._color
-
- def setColor(self, color):
- if color != self._color:
- self._color = color
- self.colorChanged.emit()
-
- name = Property(str, name, setName, notify=nameChanged)
- color = Property(str, color, setColor, notify=colorChanged)
-
-
-if __name__ == '__main__':
- app = QGuiApplication(sys.argv)
-
- dataList = [DataObject("Item 1", "red"),
- DataObject("Item 2", "green"),
- DataObject("Item 3", "blue"),
- DataObject("Item 4", "yellow")]
-
- view = QQuickView()
- view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView)
- view.setInitialProperties({"model": dataList})
-
- qml_file = Path(__file__).parent / "view.qml"
- view.setSource(QUrl.fromLocalFile(qml_file))
- view.show()
-
- r = app.exec()
- del view
- sys.exit(r)
{
- "files": ["objectlistmodel.py", "view.qml"]
+ "files": ["main.py", "ObjectListModel/Main.qml", "ObjectListModel/qmldir"]
}
+++ /dev/null
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Controls
-
-ListView {
- id: listview
- width: 200; height: 320
- required model
- ScrollBar.vertical: ScrollBar { }
-
- delegate: Rectangle {
- width: listview.width; height: 25
-
- required color
- required property string name
-
- Text { text: parent.name }
- }
-}
settings = QSettings()
if not os.environ.get("QT_QUICK_CONTROLS_STYLE"):
- style_name = settings.value("style")
- if style_name:
+ if style_name := settings.value("style"):
QQuickStyle.setStyle(style_name)
engine = QQmlApplicationEngine()
anchors.leftMargin: !window.portraitMode ? drawer.width : undefined
ToolButton {
+ Accessible.name: qsTr("Toggle drawer")
action: navigateBackAction
visible: window.portraitMode
}
ToolButton {
action: optionsMenuAction
+ Accessible.name: qsTr("Open options menu")
Menu {
id: optionsMenu
enabled: !GalleryConfig.disabled
value: 0.5
anchors.horizontalCenter: parent.horizontalCenter
+ Accessible.name: qsTr("demo dial")
}
}
}
id: bar
value: 0.5
anchors.horizontalCenter: parent.horizontalCenter
+ Accessible.name: qsTr("demo progressbar")
}
ProgressBar {
indeterminate: true
anchors.horizontalCenter: parent.horizontalCenter
+ Accessible.name: qsTr("indeterminate progressbar")
}
}
}
first.value: 0.25
second.value: 0.75
anchors.horizontalCenter: parent.horizontalCenter
+ Accessible.name: qsTr("demo range slider")
}
RangeSlider {
first.value: 0.25
second.value: 0.75
anchors.horizontalCenter: parent.horizontalCenter
+ Accessible.name: qsTr("vertical demo slider")
}
}
}
enabled: !GalleryConfig.disabled
value: 0.5
anchors.horizontalCenter: parent.horizontalCenter
+ Accessible.name: qsTr("demo slider")
}
Slider {
orientation: Qt.Vertical
value: 0.5
anchors.horizontalCenter: parent.horizontalCenter
+ Accessible.name: qsTr("vertical demo slider")
}
}
}
value: 50
anchors.horizontalCenter: parent.horizontalCenter
editable: true
+ Accessible.name: qsTr("demo spinbox")
}
}
}
wrapMode: TextArea.Wrap
text: qsTr("TextArea\n...\n...\n...")
+ Accessible.name: qsTr("demo text area")
}
}
}
enabled: !GalleryConfig.disabled
placeholderText: qsTr("TextField")
anchors.horizontalCenter: parent.horizontalCenter
+ Accessible.name: qsTr("demo textfield")
}
}
}
# Resource object code (Python 3)
# Created by: object code
-# Created by: The Resource Compiler for Qt version 6.10.0
+# Created by: The Resource Compiler for Qt version 6.10.2
# WARNING! All changes made in this file will be lost!
from PySide6 import QtCore
qt_resource_data = b"\
-\x00\x00\x0b\xbc\
+\x00\x00\x0b\xe1\
\x00\
-\x004\xd3x\x9c\xbd\x1b]s\xdb\xb8\xf1]\xbf\x02\xa7\
-\xbe\xc8\x97\x88\x8a\x9d&\x0f\xba\xa67\xb6\x9c\x0fO\x9d\
-&\xb1\x9dKg2y\x80HHBM\x114\x00\xda\
-V\x12\xff\xf7\xee\x82\x04\xc5\x0f\x80\x96\xe9\xbbb&\x16\
-I,v\x17\x8b\xc5~\x01\x99L\xc8L\xa4\x1b\xc9\x97\
-+MF\xb3=r\xf0\xec`\x9f\x5c\xac\x18\xf9\xa4\xa1\
-g\x9d\xd2dCNu\x14\x0c&\x13r\xfe\xf1\xf8?\
-\xe3S\x1e\xb2D\xb1\xf1I\xc4\x12\xcd\x17\x9c\xc9))\
-\xbe\x9d\xb1\xc5\xf8\x93\x1e\xc3\xb05\x93!\xa71\xf9p\
-F\x8e\xce\x8f\xc7\xcf\xc7\xb3\x98f\x8a\x0d\x06\xa9\xa4\xcb\
-55\x98E\x02\x08\x8e\xd8\x8a^s\x018\x8eD\x96\
-D\x83\x01\x87\x0e\xa9\x81\xfaLH\xb6}\xfb\x94\xf1\xf0\
-\xb2\xf1\x1a\x9c\xd2\x8d\xc8\xb4j~\x9e\x89DK\x11\xab\
-\x12\xd90\x18\x12\xaa\xc8a\x9a\x0e\x06\xf0'\xe6!\xd5\
-\x5c$_x\x12\x89\x1b\xf2c@\xa0\xf1hJn\xcc\
-\x07\xf3z\xc3#\xbd\x9a\x92\xe7/\x9f\x99\xd7\x15C\x01\
-M\xc9\x8b\x83\xfc\xfd\x9a+>\x8f\xd9\x94h\x991\xf3\
-Es\x8d\xefW\xeaB\x8e\x86 ;\xc3\x0a\xb1\xac\x0c\
-\xf7\x06\x06j2\xf9\x85|\x15\x92\xc3\xcc\x0d\x0b\xdf\xcc\
-W\xc9h$\x92xCR)R&\xf5\x86\xcc\x85\x88\
-\x09\xf2.)\xd7\xefE\x04\x98\x7f\xa9\x0c\x9b\xadXx\
-y$n\x83\x10\x1fXD~\xfe,\x98\x0f\x0c\xe3\xe4\
-\x1f\xf65g\xdcC\xdb|^dI\x88\xaf0\xc78\
-\x1d\xed\x15\xe2\xc0\x163M\x22\xae\xd2\x98nx\xb2,\
-\xa6B^\x91\x98+\xfd\x07g7A\x98I\x09\xd8N\
-\x92\x88\xdd\x92_^\xbd\x22\xe3\xfd\xda\xe0\xa2\xbf\x18\xf9\
-o\xbaf0\xba\x85\xb1\x1c\x82\xed\xf7-\xf65L;\
-\x0e\x96L\x8f\x9c\x04\xf7\x02#\xf2@\x8bSq\xc3\xe4\
-\x8c*\x06\xdcO\xc9pX\xe3!\x93\xc8\xf2p\xa5u\
-\xaa\xa6\x93I$\xc2\xe0J\x07\x5cL\xae\xf4\xf8\xe5d\
-X#\xfe\x84\x8c\xba\xb93\x0c\x0e\xaf\xd6\xf1\xf8J_\
-\xe1\x02\x8f\xc3b\x81\x0f\xc6C\x18\xee\x98\xf0\x13\xd0\xbe\
-\x95^\xc7\xc3&\x22\xe0\xb4\x8d\x04P\xc3\xb43\x98\x96\
-\x19\xb3\xf7[9\xea\x93\x0e@7\x92\xcf2~}\xab\
-\x99Lh\x1coF0\xb9=\x03q7(\x14\x09\x10\
-J\xd0\x87R\x91\xae\xa9$\xf3\x8c\xc7 \xb3s\xbd\x89\
-\x99\xca\x01\xcf\x99\xd60GUYm\xdc\x00\xaa\xf8\x5c\
-~,\xf1(-\xa1\x03~\x00G\x95\xe2\xf9\x0a\x944\
-\xcct\x05\x91\x02&X\x1225%_\x87\xafU8\
-|J\x86G4\xbc\x1c~+AXBa\xf7 A\
-\x0d\x1dfi#\x96\x82\xde\xfe\x93l5H$\x87\xa0\
-\x98\xd7T#`\x02Vb\x09\x8f\x88\xe8\xd0\xe8k\x00\
-\x1c-\x97L\x8e\xf6\x1e\xc0\xcf\xb9\xa6IDe\xf4/\
-\xb6\x09\xde\x81\xbe\xe3J\xc1\x82\xa9on\xaa\xe5&\xc2\
-\x9dQ%\x93\xb3\xd0\x90^\x9b\xc7m7,q\x90\x80\
->8\xa7\x8cJ5G\x09\xa1RD\x92\x82:\x0f+\
-\x0c]\xe4\x13E\x86~\xd4\xb4\x88/\xc8\xc8\x81n\xaf\
-\x01f\x84P\x82\xa5\xc2\xce\xa5\xda\xdc{\xba\xb6\xa3\xcd\
-\xf4\x09\x8b\x15s\xe0\xcf\xb96*\xda\xc0~7\xa8?\
-uIP\xa4\xf8Q\xbdgI\xe6\x17\xe0p\x0d\xdd\x1e\
-\xf9T\x10Ty)h\xae\xc0\xce\xa2\xbb\x02\x1f\x10\x5c\
-\x80\x85=\x82\xcd\xb1e\xe0L\xdc\xe4\x0e\xa51=\x95\
-\xd2\x10t\x7fJ\x0a\xdbo\x1bMBP6\x15,x\
-\x1cOIJQlN\x80\x98-\xf4{*\x97<\x01\
-+^\xa8T\xd5\xb6\xc3\xf2\x17\xe2\xcb\x8d\xf7\x94\x80'\
-d\x0b\x9e\xb0hPChx\xce\xb4\xae\xc9\xad\xa4f\
-\x04\xd6\xa9\x85\xb6\x95\xde\xcb\xc1Lc\xedj\xaf\xa7t\
-\xceb\x07i\x5c:c\x8c\x0d@\xab[\xb3[p\x9d\
--\x0d\xd3l\x0d3o\x1bw\xfc\x0e\xde\x1a\x7f\x8fY\
-\xccp.`\xeb\x01\x07\xb1\xee\xf5-\x18?&7\xc3\
-\xb6\x1a/`?\x07)\xbfe\xf19\xff\xceZK\x86\
-\x8d\xc5\x1c\xdd\xa9a5x\x8d/g\xa5\x8b\xac6X\
-:\xfe\x1d\xd0\xd1\xf80\xe6\xcb\x04\x94\x0e&\x01V\xd8\
-\xbc\xbd\x9b\xc1+\x93m\xc9\x82\xb1\x84\xe8\xc25\xe4\x0f\
-\xcf\x90\x5c\xe7\x8c\x16}\xc9\x83\x8e2\xa6\xf0,\xc3N\
-j\xd0\xdeJ-P\xecs`\xc0\xd6\xd8\x8cN\x98[\
-\xab\xf4\x85\xda\x8e\xf3\xa0\xc9\x09\x0b\xea\x95\xa8\x85\x90\xeb\
-\x0f\xb0Wq\x1f\x98\x1dz!\xd2\x5c\xf6\xce1-\x0b\
-\xd1Bj\xf4*W\x09\xeb\xd0\x1c:a[\xcdPX\
-Ow\x0cQ\xaaX\xba\xec\x96mw\x8fg\x0e=\xcd\
-\xae\x8c\xb5]\xce_\xc0\xd0\xe1\x1cTnW\x8e(\x02\
-\xf7\x91\xd3\xdd\xc0\xfdV\xf3\x03\xc7\xc6\xf25\xfc@n\
-\x0e\xb7jQD\xe3\xef\xa9^\x05k\x9e\x8c\xaa\xb1\xee\
-\xd3z\xa8\xbbG&\xe49\xf9\x95\x1c\x94\x83m\xec\xde\
-\x8e\x88\xb1A\xb8E\xe3nK\xc8q\xdf\xe2\xbe\xbav\
-\x9bL\xb4bM7\xfc\x0a\xa2\xe1}\x0cG\x17\x14\x1c\
-\xe66\x9e\x12\x8a\xe7\xfb\xd3\x8d\xe7\x19\x8c\xd8z\xdc\xd2\
-N\xbb\xbc\xc6V:\xa7\x85\x05mF\x07\xd1\xd6\xe8\xd6\
-w\xd8B\x84\x99r\xd8\x99\xaa\xef\x9f6]\xbf\xd3\xdd\
-\xd5 L\xc0>5\xec \x83.O\x81}\xafc\x86\
-\x96\x91\xfc\xa8\xe7LG\x99\xda\x00e\xcc\xcd\x84\x84\xc8\
-\x97(\x91\xc9\x10\xfd\xfd\x95\x0c\xa7\x93\x94.\x99\x9a\xd4\
-\x80>\xc2\xa7\x00\x22\xe6\xa1C\xf7:\x09\xa1\xe9\xf4S\
-\xc0\xde\x9e\xa8m^\xe6Cn\xfb\xfb\xa2\x17\xeb\xb9\xe8\
-B_\xf4\xf7D\x0f\xde\x96n\xba\xa5S\x01\xe9K\x04\
-\x0c\x89\x17;\xf4=\x02\xadXv!\x16\xcb\xfeb1\
-A\x88\xea\x10\x8a\x01\xe8\x89\xff\x8d\x84\xb0\xd6\x87\xdbt\
-\xf6D\xfcV\x8a,\xedP\x17\xdb\xdf\x13=\xbap\x88\
-\xa0}\xd8\x8b\xee\xbe\xc8!\xf2Z\xbd\x95<\xf2\xa2\xb7\
-\x00=\x09\xe0\xb0{\xcdM\x0d\xa8/!)\x96\x92)\
-\xd5!\xaa\x0aHO\x22g4\xe2\xa2{\xebV@z\
-\x13I\x96\xec\x1c#f\xefL* =\x89\x9c\x87R\
-\xc4q\x87\xb0J\x80G\x11\xb8w\xe9\x1b`}\x891\
-*\xc3\xd5\x1b\xceb\xaf\x22W@\xfa\x12\xe9\x5c\x92\xc7\
-\xadF\xca\x93\x0e\x0bRt\xf7F\x1es\x13\x9d\xf8\xd1\
-\x17\x00}\x09\xd8\xa0\xccK\xc0\x02\xf4%p\xc3S\xd6\
-I\xc0\x02\xf4'\xa0\xc3U\x07v\xe8\xed\x89\xfa\x82\xce\
-;vY\xde\xdb\x1fu\xdc)\x96\x12\xa0/\x01\xc8e\
-\x0e%\xa3^\xfcE\xff#\xd0w\xee\xd9\x12\xa0/\x81\
-\xbc\xf8\xe4E\x9fw?\x02\xf9\x05O\xbb\x90Cw_\
-\xe4\x92u\xafl\xd1\xdf\x17}\xb6\x06\xcd\xf0\x0b&\xef\
-\xf6!o\xd4I\xa2\x22.\x9b\xd6jI\x9e\xfa\x95\x05\
-F\xd0\x16@\x91\x7f\xdaD+\xb8\xc6?\xeejG\x9e\
-f\x9bI\xb5KJ\x90y\xc6\x98}bZ]\x22\xe3\
-j\xb6\xad|\xb5K!\xed\x9a>\xe4\xa3\xf0\x0fr\xb4\
-\x1d`\xb1\xfeo\xf2\xb2\x1d`\x8b\x1a\xbf\x9bw/x\
-\xbeNm\xbeE2\x8b9\x1eN5\xab\xd6\xb6\xf9\xaa\
-\xd7\xde\xaa\x84d:\x93\x8e:\x166_\xfd\xda-'\
-l\x95\x8ax\xa6V\xa3|\x1an\xe2\xc8\xa9#\x03\xf7\
-sZ\x94s\xc3X\xe0\xa1\xd4\xbd\x85\x91\xdak#\xf0\
-\x08lIq\xda\xec\x81\xbd\xe3\xa9\xa9\x94n\xady\xbc\
-c\xbfo)v\x16\xb1\xff\xd4\x026O\xb8\x864\x0c\
-\x95|J>\xd2\xa4\xb9\x11\x91\xc1\x14>\xd7\xa5q\xb2\
-\x86\xbd\xee\xd9\xb2\x90\xd2\x09\xdfVEL\x01\xbd\xa6<\
-FWc\x0a\xabdR)\x0a\xd9f\x8bCu\xf8w\
-\xe6\xabs\x80\x95Ih\xaa\xb9'\x89\xb3\xf2_\x05\xb4\
-\xeb\x97\x97\x7f?,\x16\x8a\x01\xbd\xf1\x8bve\x1a\xd7\
- ?\xeb5\xb3\x0e>BN\xc0\xe45;T)\x0b\
-\xc1\xdf\xb4i\x946\x92\xaf\x8d}\xbc\xd2c\x14J\x90\
-&\xcba\x97\x8e\xf9\x0a\xf9\xd5\x1aa\xeb\x0c\x1bw\xfe\
-5\xc4\x92\x8aP,\x9f\x12\xb1 \xf6\xe0\x92\xe8\x15\xd5\
-$\xa4\x09\x993\x92)\xb0\x12Z\x98c\xc7\x08@\xd6\
-i\xcc\xc0\xec\x9a*\xda\x82\x860\x9e'\xc4b\x0f\x1c\
-UH+\xb9\xb5Q9\xe5\xac\xe2[\x18-\xd2\x5c\x11\
-\x82\xb9\x80\xd4\xa6m\xba\xab:\x5c\x16\xac\xf1\xc5\x0b)\
-\xadF\x18P\xe9<\x1c\xb0\xb09\xd1)\xa1R\xc2\x9e\
-\x00fv;F\xc8\x0f\x1f\x1ez\x92P\x19\xe5;L\
-\xb8\x914\xcdU(\x07\xfe\x02\xef]\x8a\xd0\xb5\xbd\xcc\
-\x9c\xeeU\xb9|\xe6-\x85\xab\x0aiw\xd1[q\x16\
-\xb0\x9e%}\xc0\xf1U\xfd\xc9\x96\x9cM5\xc8s\xf4\
-\x9dw\x96]\xb7E\xd5Y\xe2\xed\x94Q\xad\xf0l\x0f\
-<\xb0\xe2|\xb0\xd5\xe2MmD\xad\xe8\x0c\x80/\xb7\
-\x80E\xe5\xb9V\x85uUf\x1b\x09ey\xceQ\x02\
-\xa8\xe2P;O\xeeaxQ\xac\xffpI~\xda\xe7\
-\x19\x88\xb8\x12\x04\xe0)w\x08\x1e\xb7\xed\x9d\xad\x1c\x02\
-s\xd4\x0f\x1e\xd4\xfc\xe2]\x93\xe2j\x04\xc6\xbe\xb5\x11\
-\xc5i\x1c\x98\x89\x05_\x22\x949\xdb\xcfoz\xe0\xa3\
-\xb5\x1f\xcdK+N\xb2\x05\xbbM\xcfyW\xe1\xfc\x8c\
-\xfd\x17\xcc\xa1\x83s\xcbh#\x04(\xbf\x9b\x87\x93V\
-@p/\xe9\xf21\xcco\x0b\xe4.l&\xe2l\x9d\
-8O\x8c\xab\xfa\x94\x83\xd5\x09V\x0e\x94k\x1d\xbe\x13\
-\xe8\xda\xa0\xfdg\xed\x08\xc8g\xcf\xb1\xd5N\xc9P\x02\
-S\x87\xc9\xbdk\xe3\xb4\xc5\xe4\x8e\x13B+Y'@\
--\x5c\xdd\x8a\xbeu\xae`[qzPl\x98\xfa\x9d\
-\x15\x17|y\x85,\x80H\xb3p2\xdeX\x13\xdb\x96\
-\x07P\x0a\x08N\xa2Q]\xd7\x9f\xe2Q-l],\
-\xc6\xdc\xb2\xe8\xdc\x84\xb7\xfe\x18/\x0f_K\x94\xf9\xf5\
-'?86\x97b\xb6\xf5\xd16\xf79\xdf\x0e\xc7\xc5\
-\xed\xc1w\xbb\xeb\x19\xaek(b!\xcfa\x9b\xae\x1d\
-\xb2\xc7{\x80b\xcdJ\x0f\x9eO\x03\xa3\x03\x0e\xaeN\
-\xf3\xef\x0c\xa2\x01\x06\xc1\xba\x89Z\xf85\x83\xd4J\x81\
-\xcb\x22j\xa3\xf0h\x7f\x99A\x0c\x11Cl\xe8D-\
-\xc0\xef\x99\xf1\x05tJa]5\x0bZ\xb0\xe5\x15\xa2\
-\xaf\xc37q\x06R\xfd\xc2\x93\xcf'\xcf\xf1\xb2\xd1\x9b\
-Lq\x91\xe0\x13\xffp\x9e\xdf>R<\x1c~\x0bx\
-\x12\xc6\x19p3j\x9a\x0a\xb4i\xed\xa5\xb3\x06\xabc\
-\x07\xd0L\x8b\xd9VZN\xb8\xc2\xdcy\x16\x0a[\xed\
-h\x160:7h\x0f\xee\xc0)\x5c\xde\xc7]\x95\xf6\
-1\xc0\x13\xf4\xa2\x7f\x16\x03&\xd9}\x08\x07\xa7\xc6M\
-\xee\xccB\xee\xf1\xcc9\x86\x87\x0bv\x0bK\xae\xcc\xc9\
-\xadW\xfcs\xeb7\xabz\x0f^\x0a\xc2WP\x0e\xe7\
-\x90\xe2\x0a&+|\xeel\x85u\xefN\xeb\x83w\x11\
-\x95A\xfd\x9b\xdfB\x99z\x1e\x19\x85U\xdc\xae\xdb\x5c\
-\xd5\x16R\xc5\x9az8\xed\x1caH\x1980B`\
-\xf3>'\x97\x89\xb8qO\xb4\xda\xe6\x92\xd1K?\xf7\
-%3\x0d\xb5{\x183\xa8\x83\x7f\x1a'M\xfd{\x18\
-+\xa7\xce\xd0\xff\x81\xbc\xb8\xad86\xa0`\xec\xd0;\
-p\x90\xa0n[.\xd15\xf8\xb7\xcb\xfd\xf7,j\xaf\
-\x1d\xbb\xd4\x5c\xf2i_*\xee\xcc\x0a_\x1b\xb3\x0b^\
-(\x89THS\xd76-\xed]\xfd\xea\x83m\x0f\xbf\
-\xeft\xcf\x14<a\xa6\x9f/w\xc0\xda9\xed\xe3\x1c\
-\xa8z\x9f\xfb\xf1\xf3\xda%\x0b?c\x10\xddK]\x96\
-\xdf\x5c\xf2F\xcd\x81\x84\xeco\xec\xef\xfb\xec\xe0E;\
-\x0f\x13\x18:\xea\xcd\xd4\x13!c\xe4\xe2\x88\x91\xc9\xef\
-d?\xc0\xfb(\xcf\x82v\xfa\xfd\xff\xcdjw\x8cy\
-*`\xef\x8a\xb2\x8ec\x09\xeaO])a\xe5\xe2\xd3\
-\xe3\x92\xb6\xe6u+\x08\x82\xfd\x99d5\x91le\x8f\
-e\xe7\xa3nD\x15y\x8c\x15\x92\x99g\x9e\xa7\xd8\xdb\
-Q%h\xfe\xd9\x91\xdfT\x06\xed\x96\xdc\xf8\xb4\xbd\x98\
-I\xf5\x96Y\xbdn\xd7\xb9?\x8a\xff\xa9\xd2\xa8T\xe5\
-w\xe8\xb1\xa6\x0f\x9e_*\x13P&x\x83t\xc9\x12\
-&\x8d\xb5\xc32\x95\xdc\x16\xa5\xb6\xa5\xac9\xc5\xfa\x95\
-\xe8.Q\xdd[g\xc9\xf5\xa2~\x19u\xff\xa0\x8f5\
-x\x9c|N\x12S\x81\xa3\x92+\x98\x92\x16\x0ea\xed\
-?u|l[\x11lO\xc8\x90J\x886\x12\xf0\x1b\
-\x11\x88O,\xc8\x9a.\x13\xae!\xaa&\x8ac\x12&\
-\x9f\xe6^\x17\x1f\xc0I\x80\x13\x808^\xfeu2\xac\
-?\xdd\x0d\xee\x06\xff\x03\xeeC\x8b\x0c\
+\x005Ex\x9c\xbd\x1b]W\xdb:\xf2=\xbfB7\
+\xfb\x12n\x1b\xa7\xd0m\x1f\xd2\xed\xde\x03\xa1\x1f\x9c\xa5\
+K\x0b\xf4v\xcf\xe9\xe9\x83b+\x89\x16\xc72\x92\x0c\
+\xa4-\xff}gd\xcb\xf1\x87d\xc0\xf4\xae\xce)\xb1\
+\xad\xd1\xcch4\x9a/\xa9\x93\x09\x99\x89t#\xf9r\
+\xa5\xc9h\xb6C\xf6\x9e\xed\xed\x92\xf3\x15#\x9f4\xf4\
+\xacS\x9al\xc8\xb1\x8e\x82\xc1dB\xce>\x1e\xfeg\
+|\xccC\x96(6>\x8aX\xa2\xf9\x8239%\xc5\
+\xb7S\xb6\x18\x7f\xd2c\x18\xb6f2\xe44&'\xa7\
+\xe4\xe0\xecp\xfc|<\x8bi\xa6\xd8`\x90J\xba\x5c\
+S\x83Y$\x80\xe0\x80\xad\xe8\x15\x17\x80\xe3@dI\
+4\x18p\xe8\x90\x1a\xa8\xcf\x84d\xdb\xb7O\x19\x0f/\
+\x1a\xaf\xc11\xdd\x88L\xab\xe6\xe7\x99H\xb4\x14\xb1*\
+\x91\x0d\x83!\xa1\x8a\xec\xa7\xe9`\x00\x7fb\x1eR\xcd\
+E\xf2\x85'\x91\xb8&?\x06\x04\x1a\x8f\xa6\xe4\xda|\
+0\xaf\xd7<\xd2\xab)y\xfe\xf2\x99y]1\x14\xd0\
+\x94\xbc\xd8\xcb\xdf\xaf\xb8\xe2\xf3\x98M\x89\x96\x193_\
+4\xd7\xf8~\xa9\xce\xe5h\x08\xb23\xac\x10\xcb\xcap\
+g`\xa0&\x93\xdf\xc8W!9\xcc\xdc\xb0\xf0\xcd|\
+\x95\x8cF\x22\x897$\x95\x22eRo\xc8\x5c\x88\x98\
+ \xef\x92r\xfdAD\x80\xf9\xb7\xca\xb0\xd9\x8a\x85\x17\
+\x07\xe2&\x08\xf1\x81E\xe4\xe7\xcf\x82\xf9\xc00N\xfe\
+a_s\xc6=\xb4\xcd\xe7E\x96\x84\xf8\x0as\x8c\xd3\
+\xd1N!\x0el1\xd3$\xe2*\x8d\xe9\x86'\xcbb\
+*\xe45\x89\xb9\xd2\x7frv\x1d\x84\x99\x94\x80\xed(\
+\x89\xd8\x0d\xf9\xed\xf5k2\xde\xad\x0d.\xfa\x8b\x91\xff\
+\xa6k\x06\xa3[\x18\xcb!\xd8\xfe\xd8b_\xc3\xb4\xe3\
+`\xc9\xf4\xc8Ip'0\x22\x0f\xb48\x16\xd7L\xce\
+\xa8b\xc0\xfd\x94\x0c\x875\x1e2\x89,\x0fWZ\xa7\
+j:\x99D\x22\x0c.u\xc0\xc5\xe4R\x8f_N\x86\
+5\xe2O\xc8\xa8\x9b;\xc3\xe0\xf0r\x1d\x8f/\xf5%\
+.\xf08,\x16xo<\x84\xe1\x8e\x09?\x01\xed[\
+\xe9u<l\x22\x02N\xdbH\x005L;\x83i\x99\
+1;\xaf\xcaQ\x9ft\x00\xba\x91|\x96\xf1\x9b\x1b\xcd\
+dB\xe3x3\x82\xc9\xed\x18\x88\xdbA\xa1H\x80P\
+\x82>\x94\x8atE%\x99g<\x06\x99\x9d\xe9M\xcc\
+T\x0ex\xc6\xb4\x869\xaa\xcaj\xe3\x06P\xc5\xe7\xf2\
+c\x89Gi\x09\x1d\xf0\x038\xaa\x14\xcfV\xa0\xa4a\
+\xa6+\x88\x140\xc1\x92\x90\xa9)\xf9:|\xa3\xc2\xe1\
+S2<\xa0\xe1\xc5\xf0[\x09\xc2\x12\x0a\xbb\x07\x09j\
+\xe80K\x1b\xb1\x14\xf4\xf6\x9fd\xabA\x22\xd9\x07\xc5\
+\xbc\xa2\x1a\x01\x13\xb0\x12KxDD\xfbF_\x03\xe0\
+h\xb9dr\xb4\xf3\x00~\xce4M\x22*\xa3\x7f\xb1\
+M\xf0\x1e\xf4\x1dW\x0a\x16L}sS-7\x11\xee\
+\x8c*\x99\x9c\x85\x86\xf4\xda<n\xbba\x89\x83\x04\xf4\
+\xc19eT\xaa9J\x08\x95\x22\x92\x14\xd4yXa\
+\xe8<\x9f(2\xf4\xa3\xa6E|AF\x0et;\x0d\
+0#\x84\x12,\x15v.\xd5\xe6\xde\xd3\xb5\x1dm\xa6\
+OX\xac\x98\x03\x7f\xce\xb5Q\xd1\x06\xf6\xdbA\xfd\xa9\
+K\x82\x22\xc5\x8f\xea\x03K2\xbf\x00\x87k\xe8\xf6\xc8\
+\xa7\x82\xa0\xcaKAs\x05v\x16\xdd\x15\xf8\x80\xe0\x1c\
+,\xec\x01l\x8e-\x03\xa7\xe2:w(\x8d\xe9\xa9\x94\
+\x86\xa0\xfbSR\xd8~\xdbh\x12\x82\xb2\xa9`\xc1\xe3\
+xJR\x8abs\x02\xc4l\xa1?P\xb9\xe4\x09X\
+\xf1B\xa5\xaa\xb6\x1d\x96\xbf\x10_n\xbc\xa7\x04<!\
+[\xf0\x84E\x83\x1aB\xc3s\xa6uMn\xb6\xed\x87\
+\xa0\xe2\xc6+\x15r\xca]\xd1\xb9X.cV\x10\x18\
+\xb6\x17\x9e\x1a9w*\xafm\xa5\xd3s\xcc\xa1\xb1\xe4\
+\xb5\xd7c:g\xb1\x83c\x5cqc\xc3\x0d@\xab[\
+\xb3\x1b\xf0\xb8-\xc5\xd4l\x0d\x02k\xfb\x04\xfc\x0eN\
+\x1e\x7f\x0fY\xccp.\xe0\x22\x00\x07\xb1\xa2x\x076\
+\x93\xc9\x8dC\x08\x0b0\x03A\xcaoX|\xc6\xbf\xb3\
+\xd6Jcc1G/lX\x0d\xde\xe0\xcbi\xe9Y\
+\xab\x0dV\x9c\x7f\x07t4\xde\x8f\xf92\x01]\x85I\
+\x80\xf16o\xefg\xf0\xcad[\xb2`c!(q\
+\x0d\xf9\xd33$WU\xa3|_\xf2X\xa5\x0cE<\
+\xcb\xd0\xa9=V\x0d\xfc;\xd06\x8f\x9e\x9d\xc0n\xb3\
+\xa3\x89\xd9\xa1;\x83\xd6XD\xeb \x8e\xad\xb1\xfd\x9d\
+07v\x9b\x15\x1be\x9c\x87iNX\xd0\xccD-\
+\x84\x5c\x9f\x80u\xc0\x9dgl\xc2\xb9H\xf3es\x8e\
+i\xd9\xa4\x16R\xa3\x92\xf9\x84\xad\x0bu\xa8\x93m5\
+\xd3d}\xeb!\xc4\xc5b\xe9\xb2\x94\xb6\xdd>\x9e9\
+\xf4m\xf7e\xac\xed\xe4\xfe\x02\x86\xf6\xe7\xa0\xad\xf7\xe5\
+\x88\x22p\x1f9\xdd\x0e\xdco5\xcfshLa\xc3\
+\xf3\xe4\xf6q\xab\x16E\xfc\xff\x81\xeaU\xb0\xe6\xc9\xa8\
+\x1a]?\xad\x07\xd7;dB\x9e\x93\xdf\xc9^9\xd8\
+f\x0b\xed\x18\x1c\x1b\x04x4\xee6\xa2\x1c\xb7<n\
+\xc9+\xb7\xb5E\x03\xd8t\xfc\xaf!\xfe\xde\xc5\x00x\
+A\xc1Eo#8\xa1x\xbe\xb5\xddx\x9e\xc1\x88\xad\
+\x8f/M\xbc\xcbOm\xa5s\x5c\x18\xdff<\x12m\
+\xedu}\x87-D\x98)\x87\x89\xaaF\x1b\xd3f\xb0\
+\xe1t\xb05\x08\x93\x22L\x0d;\xc8\xa0\xcb\xc9`\xdf\
+\x9b\x98\xa1Q%?\xeaY\xdaA\xa66@\x19\xb3A\
+\x01\xae\xf1\x15Q\x22\x93!F\x18\x972\x9cNR\xba\
+djR\x03\xfa\x08\x9f\x02\x88\xd1\x87\x0e\xdd\xeb$\x84\
+V\xd7O\x01{{\xa2\xb6\x99\xa0\x0f\xb9\xed\xef\x8b^\
+\xac\xe7\xa2\x0b}\xd1\xdf\x13=8j\xba\xe9\x96N\x05\
+\xa4/\x110$^\xec\xd0\xf7\x08\xb4b\xd9\x85X,\
+\xfb\x8b\xc5\xc4/\xaaC(\x06\xa0'\xfe\xb7\x12\x1c\xb7\
+\x0f\xb7\xe9\xec\x89\xf8\x9d\x14Y\xda\xa1.\xb6\xbf'z\
+t\xe1\x10\xb3\xfb\xb0\x17\xdd}\x91C\xd0\xb6z'y\
+\xe4Eo\x01z\x12\xc0aw\x9a\x9b\x1aP_BR\
+,%\xc4h\x1d\xa2\xaa\x80\xf4$rJ#.\xba\xb7\
+n\x05\xa47\x91d\xc9\xce0\xd8\xf6\xce\xa4\x02\xd2\x93\
+\xc8Y(E\x1cw\x08\xab\x04x\x14\x81;\x97\xbe\x01\
+\xd6\x97\x18\xa32\x5c\xbd\xe5,\xf6*r\x05\xa4/\x91\
+\xce%y\xdcj\xa4<\xe9\xb0 Ewo\xe417\
+\xd1\x89\x1f}\x01\xd0\x97\x80\x0d\xca\xbc\x04,@_\x02\
+\xd7<e\x9d\x04,@\x7f\x02:\x5cu`\x87\xde\x9e\
+\xa8\xcf\xe9\xbcc\x97\xe5\xbd\xfdQ\xc7\x9db)\x01\xfa\
+\x12\x80\x5cf_2\xea\xc5_\xf4?\x02}\xe7\x9e-\
+\x01\xfa\x12\xc8\xcb]^\xf4y\xf7#\x90\x9f\xf3\xb4\x0b\
+9t\xf7E.Y\xf7\xca\x16\xfd}\xd1gk\xd0\x0c\
+\xbf`\xf2n\x1f\xf2F\x89%*\xe2\xb2i\xad\x0c\xe5\
+)}Y`\x04m\x01\x14\xf9\xa7M\xb4\x82+\xfc\xe3\
+\xaev\xe4i\xb6\x99T\xbb\x1a\x05\x99g\x8c\xd9'\xa6\
+\xd5%2\xaef\xdb\xa2Y\xbb\x14\xd2>E\x80|\x14\
+\xfeA\x8ev\x0fX<q0y\xd9=`\x8bS\x05\
+7\xef^\xf0|\x9d\xda|\x8bd\x16s<\x0ek\xd6\
+\xc9m\xf3\xd5\xcb\xbdU\x09\xc9t&\x13w\xb1\xc8W\
+1w\xcb\x09[\xa5\x06\x9f\xa9\xd5(\x9f\x86\x9b8r\
+\xea\xc8\xc0\xfd\x9c\x16\x05\xe40\x16x\x0cvga\xa4\
+\xf6\xda\x08<\x02[\x8d\x9c6{`\xefxj*\xa5\
+[k\x1e(\xd9\xef[\x8a\x9de\xf3_Z2\xe7\x09\
+\xd7\x90\x86\xa1\x92O\xc9G\x9a47\x222\x98\xc2\xe7\
+\xba4\x8e\xd6\xb0\xd7=[\x16R:\xe1\xdb\xaa\x88)\
+\xa0W\x94\xc7\xe8jLM\x96L*E!\xdblq\
+\xa8\x0e\xff\xde|u\x0e\xb02\x09M!\xf8(q\x9e\
+5T\x01\xed\xfa\xe5\x95\xe3\x93\xc5B1\xa07~\xd1\
+.j\xe3\x1a\xe4\xa7\xcbf\xd6\xc1G\xc8\x09\x98\xbcb\
+\xfb*e!\xf8\x9b6\x8d\xd2F\xf2\xb5\xb1\x8f\x97z\
+\x8cB\x09\xd2d9\xec\xd21\xdf\x19@\xb5F\xd8:\
+5\xc7\x9d\x7f\x05\xb1\xa4\x22\x14\xcb\xa7D,\x88=*\
+%zE5\x09iB\xe6\x8cd\x0a\xac\x84\x16\xe6\xa0\
+3\x02\x90u\x1a30\xbb\xa6\x8a\xb6\xa0!\x8c\xe7\x09\
+\xb1\xd8\x03\xd7!H!\xb9\xb5Q9\xe5<\x00\xb00\
+Z\xa4\xb9\x22\x04s\x01\xa9M\xdbtWu\xb8,X\
+\xe3\x8b\x17RZ\x8d0\xa0\xd2y\xae`as\xa2S\
+B\xa5\x84=\x01\xcc\xdc\xef\x04\x22?\xb7x\xe8!D\
+e\x94\xef\x1c\xe2Z\xd24W\xa1\x1c\xf8\x0b\xbcw)\
+B\xd7\xf62s\xbaS\xe5\xf2\x99\xb7\x14\xae*\xa4\xfb\
+\x8b\xde\x8a\xb3\x80\xf5,\xe9\x03N\xbe\xeaO\xb6\xe4l\
+\xaaA\x9e\xc3\xf6\xbc\xb3\xec\xba)\xaa\xce\x12\xef\xc3\x8c\
+j\x85g{\xe0\x81\x15\xe7\xbd\xad\x16oj#jE\
+g\x00|\xb9\x05,*\xcf\xb5*\xac\xab2\xdbH(\
+\xcbs\x8e\x12@\x15\xc7\xe8yr\x0f\xc3\x8bb\xfd\xc9\
+\x05\xf9i\x9fg \xe2J\x10\x80\xe7\xea!x\xdc\xb6\
+w\xb6r\x08\xcc\xe5\x02\xf0\xa0\xe6\x17o\xb7\x14\x971\
+0\xf6\xad\x8d(\x0e\xf2\xc0L,\xf8\x12\xa1\xccm\x82\
+\xfcn\x09>Z\xfb\xd1\xbc&\xe3$[\xb0\xdb\xf4\x9c\
+\xb7\x15\xceO\xd9\x7f\xc1\x1c:8\xb7\x8c6B\x80\xf2\
+\xbby8j\x05\x04w\x92.\x1f\xc3\xfc~B\xee\xc2\
+f\x22\xce\xd6\x89\xf3\x8c\xba\xaaO9X\x9d`\xe5\x08\
+\xbb\xd6\xe1;\xf3\xae\x0d\xda}\xd6\x8e\x80|\xf6\x1c[\
+\xed\x94\x0c%0u\x98\xdc\xdb6N[L\xee8!\
+\xb4\x92u\x02\xd4\xc2\xd5\xad\xe8[\xe7\x0a\xb6\x15\xa7\x07\
+\xc5\x86\xa9\xdf\x92q\xc1\x97\x97\xd6\x02\x884\x0b'\xe3\
+\x8d5\xb1my\x00\xa5\x80\xe0$\x1a\xd5u\xfd)\x9e\
+\xf2\xc2\xd6\xc5b\xcc\x0d\x8b\xceLx\xeb\x8f\xf1\xf2\xf0\
+\xb5D\x99_\xb8\xf2\x83cs)f[\x1fms\x9f\
+\xf3\xdd\xe3\xa4\xb9=\xf8\xf6\xfez\x86\xeb\x1a\x8aX\xc8\
+3\xd8\xa6k\x87\xec\xf1\xe6\xa1X\xb3\xd2\x83\xe7\xd3\xc0\
+\xe8\x80\x83\xab\xd3\xfc;\x83h\x80A\xb0n\xa2\x16~\
+\xc5 \xb5R\xe0\xb2\x88\xda(\xbc\x15\xb0\xcc \x86\x88\
+!6t\xa2\x16\xe0\xf7\xcc\xf8\x02:\xa5\xb0\xae\x9a\x05\
+-\xd8\xf2\xd2\xd2\xd7\xe1\xdb8\x03\xa9~\xe1\xc9\xe7\xa3\
+\xe7x\xbd\xe9m\xa6\xb8H\xf0\x89\x9f\x9c\xe5\xf7\x9d\x14\
+\x0f\x87\xdf\x02\x9e\x84q\x06\xdc\x8c\x9a\xa6\x02mZ{\
+\xe9\xac\xc1\xea\xd8\x014\xd3b\xb6\x95\x96\x13\xae0w\
+\x9e\x85\xc2V;\x9a\x05\x8c\xce\x0d\xda\x83;p\x0a\x17\
+wqW\xa5}\x08\xf0\x04\xbd\xe8\xafb\xc0$\xbb\x0f\
+\xe1\xe0\xd8\xb8\xc9{\xb3\x90{<s\x8e\xe1\xe1\x82\xdd\
+\xc0\x92+sr\xeb\x15\xff\xdc\xfa\xcd\xaa\xde\x83\x97\x82\
+\xf0\x15\x94\xc39\xa4\xb8\xf4\xc9\x0a\x9f;[a\xdd\xbb\
+\xd3\xfa\xe0\xedGeP\xbf\xf2[(S\xcf#\xa3\xb0\
+\x8a\xdbu\x7f\xac\xdaB\xaaXS\x0f\xa7\x9d#\x0c)\
+\x03\x07F\x08l\xde\xe7\xe4\x22\x11\xd7\xee\x89V\xdb\x5c\
+2z\xe1\xe7\xbed\xa6\xa1v\x0fc\x06u\xf0\x97q\
+\xd2\xd4\xbf\x87\xb1r\xec\x0c\xfd\x1f\xc8\x8b\xdb\x8ac\x03\
+\x0a\xc6\x0e\xbd\x07\x07\x09\xea\xb6\xe5\x12]\x83\x7f\xbb\xdc\
+}\xcf\xa2\xf6\xda\xb1K\xcd%\x9f\xf65\xe6\xce\xac\xf0\
+\x8d1\xbb\xe0\x85\x92H\x854um\xd3\xd2\xde\xd5\xaf\
+>\xd8\xf6\xf0\xabRwL\xc1\x13f\xfa\xf9r\x07\xac\
+\x9d\xd3>\xcc\x81\xaa7\xc8\x1f?\xaf\xfbd\xe1\xa7\x0c\
+\xa2{\xa9\xcb\xf2\x9bK\xde\xa89\x90\x90\xfd\x8d\xfd}\
+\x97\xed\xbdh\xe7a\x02CG\xbd\x99z\x22d\x8c\x5c\
+\x1c12\xf9\x83\xec\x06x\x1f\xe5Y\xd0N\xbf\xff\xbf\
+Y\xed=c\x9e\x0a\xd8\xfb\xa2\xac\xe3X\x82\xfaSW\
+JX\xb9\xf8\xf4\xb8\xa4\xady\xdd\x0a\x82`\x7f&Y\
+M$[\xd9c\xd9\xf9\xa8\x1bQE\x1ec\x85d\xe6\
+\x99\xe7)\xf6vT\x09\x9a\x7fv\xe47\x95A\xf7K\
+n|\xda^\xcc\xa4z\xcb\xac^\xb7\xeb\xdc\x1f\xc5\xff\
+\x8diT\xaa\xf2[\xfbX\xd3\x07\xcf/\x95\x09(\x13\
+\xbc|\xbad\x09\x93\xc6\xdaa\x99Jn\x8bR\xdbR\
+\xd6\x9cb\xfdJt\x97\xa8\xee\xac\xb3\xe4zQ\xbf\xc7\
+\xba\xbb\xd7\xc7\x1a<N>G\x89\xa9\xc0Q\xc9\x15L\
+I\x0b\x87\xb0v\x9f:>\xb6\xad\x08\xb6'dH%\
+D\x1b\x09\xf8\x8d\x08\xc4'\x16dM\x97\x09\xd7\x10U\
+\x13\xc51\x09\x93Os\xaf\x8b\x0f\xe0$\xc0\x09@\x1c\
+/\xff:\x19\xd6\x9fn\x07\xb7\x83\xff\x01f\x8c\xabf\
+\
\x00\x00\x00`\
[\
Material]\x0aPrimar\
ton GalleryConfi\
g 1.0 pages/Gall\
eryConfig.qml\x0a\
-\x00\x00\x03*\
+\x00\x00\x03\x5c\
/\
/ Copyright (C) \
2017 The Qt Comp\
er: parent.horiz\
ontalCenter\x0a \
editable\
-: true\x0a }\
-\x0a }\x0a}\x0a\
+: true\x0a \
+ Accessible.na\
+me: qsTr(\x22demo s\
+pinbox\x22)\x0a \
+ }\x0a }\x0a}\x0a\
\x00\x00\x02\x8a\
/\
/ Copyright (C) \
al: ScrollIndica\
tor { }\x0a }\x0a}\x0a\
\
-\x00\x00\x03U\
+\x00\x00\x03\xca\
/\
/ Copyright (C) \
2017 The Qt Comp\
anchors.horizon\
talCenter: paren\
t.horizontalCent\
-er\x0a }\x0a\x0a \
- ProgressBa\
-r {\x0a \
-indeterminate: t\
-rue\x0a \
-anchors.horizont\
-alCenter: parent\
-.horizontalCente\
-r\x0a }\x0a \
-}\x0a}\x0a\
+er\x0a A\
+ccessible.name: \
+qsTr(\x22demo progr\
+essbar\x22)\x0a \
+ }\x0a\x0a Prog\
+ressBar {\x0a \
+ indetermin\
+ate: true\x0a \
+ anchors.ho\
+rizontalCenter: \
+parent.horizonta\
+lCenter\x0a \
+ Accessible.n\
+ame: qsTr(\x22indet\
+erminate progres\
+sbar\x22)\x0a }\
+\x0a }\x0a}\x0a\
\x00\x00\x06\x87\
/\
/ Copyright (C) \
nt.horizontalCen\
ter\x0a }\x0a \
}\x0a}\x0a\
-\x00\x00\x02\xd4\
+\x00\x00\x03\x08\
/\
/ Copyright (C) \
2017 The Qt Comp\
text:\
qsTr(\x22TextArea\x5c\
n...\x5cn...\x5cn...\x22)\
-\x0a }\x0a }\
-\x0a}\x0a\
+\x0a Acc\
+essible.name: qs\
+Tr(\x22demo text ar\
+ea\x22)\x0a }\x0a \
+ }\x0a}\x0a\
\x00\x00\x02\xec\
/\
/ Copyright (C) \
\xe8\xd5\xed\xf5\xb0\xa1\xf9\xb7S\xfe\xffL\x9b\x7f\xc1o\
o;\xf1\xb3\xe7\xec\xe5\x7f9\x1fC\xa3_\xc4\x97s\
{s\xee\xcby\x8c\xfe\x02O\xe4\x17\xf2\
-\x00\x00\x03H\
+\x00\x00\x03\xb3\
/\
/ Copyright (C) \
2017 The Qt Comp\
anchors.horizont\
alCenter: parent\
.horizontalCente\
-r\x0a }\x0a\x0a \
- Slider {\x0a \
- enable\
-d: !GalleryConfi\
-g.disabled\x0a \
- orientati\
-on: Qt.Vertical\x0a\
- valu\
-e: 0.5\x0a \
- anchors.horiz\
-ontalCenter: par\
-ent.horizontalCe\
-nter\x0a }\x0a \
- }\x0a}\x0a\
+r\x0a Ac\
+cessible.name: q\
+sTr(\x22demo slider\
+\x22)\x0a }\x0a\x0a \
+ Slider {\x0a \
+ enabl\
+ed: !GalleryConf\
+ig.disabled\x0a \
+ orientat\
+ion: Qt.Vertical\
+\x0a val\
+ue: 0.5\x0a \
+ anchors.hori\
+zontalCenter: pa\
+rent.horizontalC\
+enter\x0a \
+ Accessible.nam\
+e: qsTr(\x22vertica\
+l demo slider\x22)\x0a\
+ }\x0a }\x0a\
+}\x0a\
\x00\x00\x02x\
/\
/ Copyright (C) \
d: true\x0a \
}\x0a }\x0a\
}\x0a}\x0a\
-\x00\x00\x02\x84\
+\x00\x00\x02\xb8\
/\
/ Copyright (C) \
2017 The Qt Comp\
nchors.horizonta\
lCenter: parent.\
horizontalCenter\
-\x0a }\x0a }\
-\x0a}\x0a\
+\x0a Acc\
+essible.name: qs\
+Tr(\x22demo textfie\
+ld\x22)\x0a }\x0a \
+ }\x0a}\x0a\
\x00\x00\x06S\
/\
/ Copyright (C) \
c\x12z}\x8c\xef\xe5\xc1J\xff\x0e|G7\xe5)\
\xfbu\xeb-\xaa\xafa7\x8c\x9fi\x22\xd9G\xc5\xbf\
\x07\xef\xc1\xfb\x17\xe7\x1f\xdf\x92\
-\x00\x00\x02\xfb\
+\x00\x00\x03*\
/\
/ Copyright (C) \
2017 The Qt Comp\
rizontalCenter: \
parent.horizonta\
lCenter\x0a \
-}\x0a }\x0a}\x0a\
+ Accessible.n\
+ame: qsTr(\x22demo \
+dial\x22)\x0a }\
+\x0a }\x0a}\x0a\
\x00\x00\x05-\
/\
/ Copyright (C) \
ysOn : ScrollBar\
.AsNeeded\x0a }\x0a\
}\x0a\
-\x00\x00\x03\xbf\
+\x00\x00\x040\
/\
/ Copyright (C) \
2017 The Qt Comp\
ors.horizontalCe\
nter: parent.hor\
izontalCenter\x0a \
- }\x0a\x0a \
- RangeSlider {\x0a \
- enabl\
-ed: !GalleryConf\
-ig.disabled\x0a \
- orientat\
-ion: Qt.Vertical\
-\x0a fir\
-st.value: 0.25\x0a \
- secon\
-d.value: 0.75\x0a \
- anchor\
-s.horizontalCent\
-er: parent.horiz\
-ontalCenter\x0a \
- }\x0a }\x0a}\x0a\
+ Access\
+ible.name: qsTr(\
+\x22demo range slid\
+er\x22)\x0a }\x0a\x0a\
+ RangeSli\
+der {\x0a \
+ enabled: !Gall\
+eryConfig.disabl\
+ed\x0a o\
+rientation: Qt.V\
+ertical\x0a \
+ first.value:\
+ 0.25\x0a \
+ second.value: \
+0.75\x0a \
+ anchors.horizon\
+talCenter: paren\
+t.horizontalCent\
+er\x0a A\
+ccessible.name: \
+qsTr(\x22vertical d\
+emo slider\x22)\x0a \
+ }\x0a }\x0a}\x0a\
\x00\x00\x05b\
/\
/ Copyright (C) \
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x90\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x0a\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xbc\
-\x00\x00\x01\x99{\x95\xd3\xc8\
-\x00\x00\x00,\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xc0\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x00t\x00\x00\x00\x00\x00\x01\x00\x00\x0c$\
-\x00\x00\x01\x99\xa0\x15<\x1b\
+\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xe1\
+\x00\x00\x01\x9b\xc6<\xa6)\
+\x00\x00\x00,\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xe5\
+\x00\x00\x01\x99-^\x9a:\
+\x00\x00\x00t\x00\x00\x00\x00\x00\x01\x00\x00\x0cI\
+\x00\x00\x01\x9b\xc6<(c\
\x00\x00\x00\x5c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x09\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x10\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x00t\x00\x00\x00\x00\x00\x01\x00\x01\x1d|\
-\x00\x00\x01\x99\xa0\x15<\x1b\
-\x00\x00\x070\x00\x00\x00\x00\x00\x01\x00\x01\x09\xe6\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x07l\x00\x00\x00\x00\x00\x01\x00\x01\x19\xca\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x06r\x00\x00\x00\x00\x00\x01\x00\x00\xd0@\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x07P\x00\x00\x00\x00\x00\x01\x00\x01\x0e\xa5\
-\x00\x00\x01\x87,\x09\xa2X\
-\x00\x00\x066\x00\x00\x00\x00\x00\x01\x00\x00\xa9M\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x06\x1e\x00\x00\x00\x00\x00\x01\x00\x00\xa8\x0a\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x07\x0e\x00\x00\x00\x00\x00\x01\x00\x00\xf6\x9a\
-\x00\x00\x01\x87,\x09\xa2X\
-\x00\x00\x06\xce\x00\x00\x00\x00\x00\x01\x00\x00\xd8`\
-\x00\x00\x01\x87,\x09\xa2X\
-\x00\x00\x06P\x00\x00\x00\x00\x00\x01\x00\x00\xab\x1b\
-\x00\x00\x01\x87,\x09\xa2X\
-\x00\x00\x06\xf0\x00\x00\x00\x00\x00\x01\x00\x00\xf4\xba\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x06\x92\x00\x00\x00\x00\x00\x01\x00\x00\xd2\xff\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x06\xb0\x00\x00\x00\x00\x00\x01\x00\x00\xd5\xf9\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x04\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x81\x99\
-\x00\x00\x01\x99\xa0\x15<\x1d\
-\x00\x00\x03\xf8\x00\x01\x00\x00\x00\x01\x00\x00d\xea\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x04\xa8\x00\x00\x00\x00\x00\x01\x00\x00|B\
-\x00\x00\x01\x99\xa0\x15<\x1d\
-\x00\x00\x02|\x00\x00\x00\x00\x00\x01\x00\x002\x85\
-\x00\x00\x01\x99\xa0\x15<\x1e\
-\x00\x00\x02Z\x00\x00\x00\x00\x00\x01\x00\x00/9\
-\x00\x00\x01\x99\xa0\x15<\x1d\
-\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x10-\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x02\xa0\x00\x00\x00\x00\x00\x01\x00\x005\x01\
-\x00\x00\x01\x99\xa0\x15<\x1e\
-\x00\x00\x03\x16\x00\x00\x00\x00\x00\x01\x00\x00K\xab\
-\x00\x00\x01\x99\xa0\x15<\x1e\
-\x00\x00\x04\x80\x00\x00\x00\x00\x00\x01\x00\x00v\xcd\
-\x00\x00\x01\x99\xa0\x15<\x1d\
-\x00\x00\x05\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x99[\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x03`\x00\x00\x00\x00\x00\x01\x00\x00W\x8a\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x02\xee\x00\x00\x00\x00\x00\x01\x00\x00C\xe3\
-\x00\x00\x01\x99\xa0\x15<\x1d\
-\x00\x00\x01j\x00\x00\x00\x00\x00\x01\x00\x00\x1bG\
-\x00\x00\x01\x99\xa0\x15<\x1d\
-\x00\x00\x05\xca\x00\x00\x00\x00\x00\x01\x00\x00\x9d3\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x03\xd2\x00\x00\x00\x00\x00\x01\x00\x00^\x93\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x01\xec\x00\x00\x00\x00\x00\x01\x00\x00'0\
-\x00\x00\x01\x99\xa0\x15<\x1e\
-\x00\x00\x05\xf6\x00\x00\x00\x00\x00\x01\x00\x00\xa0%\
-\x00\x00\x01\x99\xa0\x15<\x1d\
-\x00\x00\x00\xc4\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xff\
-\x00\x00\x01\x99\xa0\x15<\x1d\
-\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00 \xa5\
-\x00\x00\x01\x99\xa0\x15<\x1d\
-\x00\x00\x05\x82\x00\x01\x00\x00\x00\x01\x00\x00\x93\x8a\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00\x12\xbb\
-\x00\x00\x01\x99\xa0\x15<\x1e\
-\x00\x00\x05N\x00\x00\x00\x00\x00\x01\x00\x00\x8f\x07\
-\x00\x00\x01\x99\xa0\x15<\x1d\
-\x00\x00\x03\x88\x00\x00\x00\x00\x00\x01\x00\x00XN\
-\x00\x00\x01\x99\xa0\x15<\x1d\
-\x00\x00\x026\x00\x01\x00\x00\x00\x01\x00\x00,\xf8\
-\x00\x00\x01\x99\xa0\x15<\x1e\
-\x00\x00\x02\x12\x00\x00\x00\x00\x00\x01\x00\x00*\x08\
-\x00\x00\x01\x99\xa0\x15<\x1e\
-\x00\x00\x04<\x00\x00\x00\x00\x00\x01\x00\x00l\xd7\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x04\x5c\x00\x00\x00\x00\x00\x01\x00\x00r\x08\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x00\x1dL\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x04\x1e\x00\x00\x00\x00\x00\x01\x00\x00i\xd8\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x03>\x00\x01\x00\x00\x00\x01\x00\x00UB\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x01:\x00\x00\x00\x00\x00\x01\x00\x00\x18w\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x05\x22\x00\x00\x00\x00\x00\x01\x00\x00\x8a\xc2\
-\x00\x00\x01\x99\xa0\x15<\x1d\
-\x00\x00\x02\xc6\x00\x01\x00\x00\x00\x01\x00\x00@\xb9\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x04\xfc\x00\x00\x00\x00\x00\x01\x00\x00\x85\x5c\
-\x00\x00\x01\x99\xa0\x15<\x1c\
-\x00\x00\x03\xaa\x00\x00\x00\x00\x00\x01\x00\x00\x5c\x0b\
-\x00\x00\x01\x99\xa0\x15<\x1e\
+\x00\x00\x01\x9b\xc6D\x84W\
+\x00\x00\x00t\x00\x00\x00\x00\x00\x01\x00\x01\x1f\xbb\
+\x00\x00\x01\x975l\xc7\xef\
+\x00\x00\x070\x00\x00\x00\x00\x00\x01\x00\x01\x0c%\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x07l\x00\x00\x00\x00\x00\x01\x00\x01\x1c\x09\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x06r\x00\x00\x00\x00\x00\x01\x00\x00\xd2\x7f\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x07P\x00\x00\x00\x00\x00\x01\x00\x01\x10\xe4\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x066\x00\x00\x00\x00\x00\x01\x00\x00\xab\x8c\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x06\x1e\x00\x00\x00\x00\x00\x01\x00\x00\xaaI\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x07\x0e\x00\x00\x00\x00\x00\x01\x00\x00\xf8\xd9\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x06\xce\x00\x00\x00\x00\x00\x01\x00\x00\xda\x9f\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x06P\x00\x00\x00\x00\x00\x01\x00\x00\xadZ\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x06\xf0\x00\x00\x00\x00\x00\x01\x00\x00\xf6\xf9\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x06\x92\x00\x00\x00\x00\x00\x01\x00\x00\xd5>\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x06\xb0\x00\x00\x00\x00\x00\x01\x00\x00\xd88\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x04\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x83g\
+\x00\x00\x01\x9b\xc6CC\xeb\
+\x00\x00\x03\xf8\x00\x01\x00\x00\x00\x01\x00\x00f\x89\
+\x00\x00\x01\x99.H\x8e\xfe\
+\x00\x00\x04\xa8\x00\x00\x00\x00\x00\x01\x00\x00~\x10\
+\x00\x00\x01\x99.H\x8e\xff\
+\x00\x00\x02|\x00\x00\x00\x00\x00\x01\x00\x003\xf0\
+\x00\x00\x01\x99.H\x8f\x00\
+\x00\x00\x02Z\x00\x00\x00\x00\x00\x01\x00\x0009\
+\x00\x00\x01\x9b\xc6CC\xeb\
+\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x10\x84\
+\x00\x00\x01\x99.H\x8e\xfe\
+\x00\x00\x02\xa0\x00\x00\x00\x00\x00\x01\x00\x006l\
+\x00\x00\x01\x99.H\x8f\x00\
+\x00\x00\x03\x16\x00\x00\x00\x00\x00\x01\x00\x00M\x16\
+\x00\x00\x01\x99.H\x8e\xff\
+\x00\x00\x04\x80\x00\x00\x00\x00\x00\x01\x00\x00x\x9b\
+\x00\x00\x01\x99.H\x8e\xff\
+\x00\x00\x05\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x9b\x9a\
+\x00\x00\x01\x99.H\x8e\xfd\
+\x00\x00\x03`\x00\x00\x00\x00\x00\x01\x00\x00X\xf5\
+\x00\x00\x01\x99.H\x8e\xfe\
+\x00\x00\x02\xee\x00\x00\x00\x00\x00\x01\x00\x00EN\
+\x00\x00\x01\x99.H\x8e\xff\
+\x00\x00\x01j\x00\x00\x00\x00\x00\x01\x00\x00\x1b\x9e\
+\x00\x00\x01\x99-e\xb2 \
+\x00\x00\x05\xca\x00\x00\x00\x00\x00\x01\x00\x00\x9fr\
+\x00\x00\x01\x99.H\x8e\xfd\
+\x00\x00\x03\xd2\x00\x00\x00\x00\x00\x01\x00\x00`2\
+\x00\x00\x01\x99.H\x8e\xfd\
+\x00\x00\x01\xec\x00\x00\x00\x00\x00\x01\x00\x00'\xfc\
+\x00\x00\x01\x9b\xc6CC\xeb\
+\x00\x00\x05\xf6\x00\x00\x00\x00\x00\x01\x00\x00\xa2d\
+\x00\x00\x01\x99.H\x8e\xff\
+\x00\x00\x00\xc4\x00\x00\x00\x00\x00\x01\x00\x00\x0d$\
+\x00\x00\x01\x9b\xc6F\x1b\xb0\
+\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00!q\
+\x00\x00\x01\x99.H\x8e\xff\
+\x00\x00\x05\x82\x00\x01\x00\x00\x00\x01\x00\x00\x95\xc9\
+\x00\x00\x01\x99.H\x8e\xfe\
+\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00\x13\x12\
+\x00\x00\x01\x99.H\x8e\xff\
+\x00\x00\x05N\x00\x00\x00\x00\x00\x01\x00\x00\x91F\
+\x00\x00\x01\x99.H\x8e\xff\
+\x00\x00\x03\x88\x00\x00\x00\x00\x00\x01\x00\x00Y\xb9\
+\x00\x00\x01\x99.H\x8e\xff\
+\x00\x00\x026\x00\x01\x00\x00\x00\x01\x00\x00-\xf8\
+\x00\x00\x01\x99.H\x8f\x00\
+\x00\x00\x02\x12\x00\x00\x00\x00\x00\x01\x00\x00+\x08\
+\x00\x00\x01\x99.H\x8f\x00\
+\x00\x00\x04<\x00\x00\x00\x00\x00\x01\x00\x00n\xa5\
+\x00\x00\x01\x99.H\x8e\xfe\
+\x00\x00\x04\x5c\x00\x00\x00\x00\x00\x01\x00\x00s\xd6\
+\x00\x00\x01\x99.H\x8e\xfe\
+\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xa3\
+\x00\x00\x01\x9b\xc6CC\xeb\
+\x00\x00\x04\x1e\x00\x00\x00\x00\x00\x01\x00\x00kw\
+\x00\x00\x01\x9b\xc6CC\xeb\
+\x00\x00\x03>\x00\x01\x00\x00\x00\x01\x00\x00V\xad\
+\x00\x00\x01\x99.H\x8e\xfd\
+\x00\x00\x01:\x00\x00\x00\x00\x00\x01\x00\x00\x18\xce\
+\x00\x00\x01\x99.H\x8e\xfd\
+\x00\x00\x05\x22\x00\x00\x00\x00\x00\x01\x00\x00\x8d\x01\
+\x00\x00\x01\x99.H\x8e\xff\
+\x00\x00\x02\xc6\x00\x01\x00\x00\x00\x01\x00\x00B$\
+\x00\x00\x01\x99.H\x8e\xfe\
+\x00\x00\x04\xfc\x00\x00\x00\x00\x00\x01\x00\x00\x87\x9b\
+\x00\x00\x01\x99.H\x8e\xfe\
+\x00\x00\x03\xaa\x00\x00\x00\x00\x00\x01\x00\x00]v\
+\x00\x00\x01\x9b\xc6CC\xeb\
\x00\x00\x07\x8c\x00\x02\x00\x00\x00\x05\x00\x00\x00:\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x07\xb4\x00\x02\x00\x00\x00\x03\x00\x00\x00H\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x07\xc4\x00\x02\x00\x00\x00\x03\x00\x00\x00?\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x07\xd8\x00\x00\x00\x00\x00\x01\x00\x01\x1e?\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01!\x7f\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01#\x00\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01\x22y\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01\x1fM\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01 \xba\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01 4\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01%w\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01&\xb5\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01&3\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01#\xe3\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01$\xf8\
-\x00\x00\x01\x86\xe4%hu\
-\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01$y\
-\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x07\xd8\x00\x00\x00\x00\x00\x01\x00\x01 ~\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01#\xbe\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01%?\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01$\xb8\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01!\x8c\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01\x22\xf9\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01\x22s\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01'\xb6\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01(\xf4\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01(r\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01&\x22\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01'7\
+\x00\x00\x01\x975l\xc7\xf0\
+\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01&\xb8\
+\x00\x00\x01\x975l\xc7\xf0\
"
def qInitResources():
truck.arrive()
truck.printAvailableFlavors()
- result = truck.deliver()
- if result:
+ if result := truck.deliver():
print("All the kids got some icecream!")
else:
print("Aww, someone didn't get the flavor they wanted...")
void testFunction1();
- static constexpr auto TEST = QLatin1StringView("test");
-
private Q_SLOTS:
void slotCursorChanged();
void slotRunScript();
+++ /dev/null
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import QtWebEngine
-
-QtObject {
- id: root
-
- property WebEngineProfilePrototype defaultProfilePrototype : WebEngineProfilePrototype {
- storageName: "Profile"
- Component.onCompleted: {
- let fullVersionList = root.defaultProfilePrototype.instance().clientHints.fullVersionList;
- fullVersionList["QuickNanoBrowser"] = "1.0";
- root.defaultProfilePrototype.instance().clientHints.fullVersionList = fullVersionList;
- }
- }
-
- property WebEngineProfilePrototype otrPrototype : WebEngineProfilePrototype {
- }
-
- property Component browserWindowComponent: BrowserWindow {
- applicationRoot: root
- }
- property Component browserDialogComponent: BrowserDialog {
- onClosing: destroy()
- }
- function createWindow(profile) {
- var newWindow = browserWindowComponent.createObject(root) as BrowserWindow;
- newWindow.currentWebView.profile = profile;
- profile.downloadRequested.connect(newWindow.onDownloadRequested);
- return newWindow;
- }
- function createDialog(profile) {
- var newDialog = browserDialogComponent.createObject(root) as BrowserDialog;
- newDialog.currentWebView.profile = profile;
- return newDialog;
- }
- function load(url) {
- var browserWindow = createWindow(root.defaultProfilePrototype.instance());
- browserWindow.currentWebView.url = url;
- }
-}
+++ /dev/null
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Window
-import QtWebEngine
-
-Window {
- id: window
- property alias currentWebView: webView
- flags: Qt.Dialog
- width: 800
- height: 600
- visible: true
- onClosing: destroy()
- WebEngineView {
- id: webView
- anchors.fill: parent
-
- onGeometryChangeRequested: function(geometry) {
- window.x = geometry.x
- window.y = geometry.y
- window.width = geometry.width
- window.height = geometry.height
- }
- }
-}
--- /dev/null
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtWebEngine
+
+QtObject {
+ id: root
+
+ property WebEngineProfilePrototype defaultProfilePrototype : WebEngineProfilePrototype {
+ storageName: "Profile"
+ Component.onCompleted: {
+ let fullVersionList = root.defaultProfilePrototype.instance().clientHints.fullVersionList;
+ fullVersionList["QuickNanoBrowser"] = "1.0";
+ root.defaultProfilePrototype.instance().clientHints.fullVersionList = fullVersionList;
+ }
+ }
+
+ property WebEngineProfilePrototype otrPrototype : WebEngineProfilePrototype {
+ }
+
+ property Component browserWindowComponent: BrowserWindow {
+ applicationRoot: root
+ }
+ property Component browserDialogComponent: BrowserDialog {
+ onClosing: destroy()
+ }
+ function createWindow(profile) {
+ var newWindow = browserWindowComponent.createObject(root) as BrowserWindow;
+ newWindow.currentWebView.profile = profile;
+ profile.downloadRequested.connect(newWindow.onDownloadRequested);
+ return newWindow;
+ }
+ function createDialog(profile) {
+ var newDialog = browserDialogComponent.createObject(root) as BrowserDialog;
+ newDialog.currentWebView.profile = profile;
+ return newDialog;
+ }
+ function load(url) {
+ var browserWindow = createWindow(root.defaultProfilePrototype.instance());
+ browserWindow.currentWebView.url = url;
+ }
+}
--- /dev/null
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Window
+import QtWebEngine
+
+Window {
+ id: window
+ property alias currentWebView: webView
+ flags: Qt.Dialog
+ width: 800
+ height: 600
+ visible: true
+ onClosing: destroy()
+ WebEngineView {
+ id: webView
+ anchors.fill: parent
+
+ onGeometryChangeRequested: function(geometry) {
+ window.x = geometry.x
+ window.y = geometry.y
+ window.width = geometry.width
+ window.height = geometry.height
+ }
+ }
+}
--- /dev/null
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma ComponentBehavior: Bound
+
+import QtCore
+import QtQml
+import QtQuick
+import QtQuick.Controls.Fusion
+import QtQuick.Dialogs
+import QtQuick.Layouts
+import QtQuick.Window
+import QtWebEngine
+import BrowserUtils
+
+ApplicationWindow {
+ id: win
+ required property QtObject applicationRoot
+ property WebEngineView currentWebView: tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null
+ property int previousVisibility: Window.Windowed
+ property int createdTabs: 0
+ property bool lastTabClosing: false
+
+ width: 1300
+ height: 900
+ visible: true
+ title: win.currentWebView?.title ?? ""
+
+ // Make sure the Qt.WindowFullscreenButtonHint is set on OS X.
+ Component.onCompleted: flags = flags | Qt.WindowFullscreenButtonHint
+
+ onCurrentWebViewChanged: {
+ findBar.reset();
+ }
+
+ // When using style "mac", ToolButtons are not supposed to accept focus.
+ property bool platformIsMac: Qt.platform.os === "osx"
+
+ Settings {
+ id : appSettings
+ property alias autoLoadImages: loadImages.checked
+ property alias javaScriptEnabled: javaScriptEnabled.checked
+ property alias errorPageEnabled: errorPageEnabled.checked
+ property alias pluginsEnabled: pluginsEnabled.checked
+ property alias fullScreenSupportEnabled: fullScreenSupportEnabled.checked
+ property alias autoLoadIconsForPage: autoLoadIconsForPage.checked
+ property alias touchIconsEnabled: touchIconsEnabled.checked
+ property alias webRTCPublicInterfacesOnly : webRTCPublicInterfacesOnly.checked
+ property alias devToolsEnabled: devToolsEnabled.checked
+ property alias pdfViewerEnabled: pdfViewerEnabled.checked
+ property int imageAnimationPolicy: WebEngineSettings.ImageAnimationPolicy.Allow
+ property alias javascriptCanAccessClipboard: javascriptCanAccessClipboard.checked
+ property alias javascriptCanPaste: javascriptCanPaste.checked
+ }
+
+ Action {
+ shortcut: "Ctrl+D"
+ onTriggered: {
+ downloadView.visible = !downloadView.visible;
+ }
+ }
+ Action {
+ id: focus
+ shortcut: "Ctrl+L"
+ onTriggered: {
+ addressBar.forceActiveFocus();
+ addressBar.selectAll();
+ }
+ }
+ Action {
+ shortcut: StandardKey.Refresh
+ onTriggered: {
+ if (win.currentWebView)
+ win.currentWebView.reload();
+ }
+ }
+ Action {
+ shortcut: StandardKey.AddTab
+ onTriggered: {
+ tabBar.createTab(tabBar.count !== 0
+ ? win.currentWebView.profile
+ : (win.applicationRoot as ApplicationRoot).defaultProfilePrototype.instance());
+ addressBar.forceActiveFocus();
+ addressBar.selectAll();
+ }
+ }
+ Action {
+ shortcut: StandardKey.Close
+ onTriggered: {
+ win.currentWebView.triggerWebAction(WebEngineView.RequestClose);
+ }
+ }
+ Action {
+ shortcut: StandardKey.Quit
+ onTriggered: win.close()
+ }
+ Action {
+ shortcut: "Escape"
+ onTriggered: {
+ if (win.currentWebView.state === "FullScreen") {
+ win.visibility = win.previousVisibility;
+ fullScreenNotification.hide();
+ win.currentWebView.triggerWebAction(WebEngineView.ExitFullScreen);
+ }
+
+ if (findBar.visible)
+ findBar.visible = false;
+ }
+ }
+ Action {
+ shortcut: "Ctrl+0"
+ onTriggered: win.currentWebView.zoomFactor = 1.0
+ }
+ Action {
+ shortcut: StandardKey.ZoomOut
+ onTriggered: win.currentWebView.zoomFactor -= 0.1
+ }
+ Action {
+ shortcut: StandardKey.ZoomIn
+ onTriggered: win.currentWebView.zoomFactor += 0.1
+ }
+
+ Action {
+ shortcut: StandardKey.Copy
+ onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Copy)
+ }
+ Action {
+ shortcut: StandardKey.Cut
+ onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Cut)
+ }
+ Action {
+ shortcut: StandardKey.Paste
+ onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Paste)
+ }
+ Action {
+ shortcut: "Shift+"+StandardKey.Paste
+ onTriggered: win.currentWebView.triggerWebAction(WebEngineView.PasteAndMatchStyle)
+ }
+ Action {
+ shortcut: StandardKey.SelectAll
+ onTriggered: win.currentWebView.triggerWebAction(WebEngineView.SelectAll)
+ }
+ Action {
+ shortcut: StandardKey.Undo
+ onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Undo)
+ }
+ Action {
+ shortcut: StandardKey.Redo
+ onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Redo)
+ }
+ Action {
+ shortcut: StandardKey.Back
+ onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Back)
+ }
+ Action {
+ shortcut: StandardKey.Forward
+ onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Forward)
+ }
+ Action {
+ shortcut: StandardKey.Find
+ onTriggered: {
+ if (!findBar.visible)
+ findBar.visible = true;
+ }
+ }
+ Action {
+ shortcut: StandardKey.FindNext
+ onTriggered: findBar.findNext()
+ }
+ Action {
+ shortcut: StandardKey.FindPrevious
+ onTriggered: findBar.findPrevious()
+ }
+
+ menuBar: ToolBar {
+ id: navigationBar
+ RowLayout {
+ anchors.fill: parent
+ ToolButton {
+ enabled: win.currentWebView?.canGoBack || win.currentWebView?.canGoForward
+ onClicked: historyMenu.open()
+ text: qsTr("â–¼")
+ Menu {
+ id: historyMenu
+ Instantiator {
+ model: win.currentWebView?.history?.items
+ MenuItem {
+ required property var model
+ text: model.title
+ onTriggered: win.currentWebView.goBackOrForward(model.offset)
+ checkable: !enabled
+ checked: !enabled
+ enabled: model.offset
+ }
+
+ onObjectAdded: function(index, object) {
+ historyMenu.insertItem(index, object)
+ }
+ onObjectRemoved: function(index, object) {
+ historyMenu.removeItem(object)
+ }
+ }
+ }
+ }
+
+ ToolButton {
+ id: backButton
+ icon.source: "qrc:///icons/3rdparty/go-previous.png"
+ onClicked: win.currentWebView.goBack()
+ enabled: win.currentWebView?.canGoBack ?? false
+ activeFocusOnTab: !win.platformIsMac
+ }
+ ToolButton {
+ id: forwardButton
+ icon.source: "qrc:///icons/3rdparty/go-next.png"
+ onClicked: win.currentWebView.goForward()
+ enabled: win.currentWebView?.canGoForward ?? false
+ activeFocusOnTab: !win.platformIsMac
+ }
+ ToolButton {
+ id: reloadButton
+ icon.source: win.currentWebView?.loading
+ ? "qrc:///icons/3rdparty/process-stop.png"
+ : "qrc:///icons/3rdparty/view-refresh.png"
+ onClicked: win.currentWebView?.loading ? win.currentWebView.stop() : win.currentWebView.reload()
+ activeFocusOnTab: !win.platformIsMac
+ }
+ TextField {
+ id: addressBar
+ Image {
+ anchors.verticalCenter: addressBar.verticalCenter;
+ x: 5
+ z: 2
+ id: faviconImage
+ width: 16; height: 16
+ sourceSize: Qt.size(width, height)
+ source: win.currentWebView?.icon ? win.currentWebView.icon : ''
+ }
+ MouseArea {
+ id: textFieldMouseArea
+ acceptedButtons: Qt.RightButton
+ anchors.fill: parent
+ onClicked: {
+ var textSelectionStartPos = addressBar.selectionStart;
+ var textSelectionEndPos = addressBar.selectionEnd;
+ textFieldContextMenu.open();
+ addressBar.select(textSelectionStartPos, textSelectionEndPos);
+ }
+ Menu {
+ id: textFieldContextMenu
+ x: textFieldMouseArea.mouseX
+ y: textFieldMouseArea.mouseY
+ MenuItem {
+ text: qsTr("Cut")
+ onTriggered: addressBar.cut()
+ enabled: addressBar.selectedText.length > 0
+ }
+ MenuItem {
+ text: qsTr("Copy")
+ onTriggered: addressBar.copy()
+ enabled: addressBar.selectedText.length > 0
+ }
+ MenuItem {
+ text: qsTr("Paste")
+ onTriggered: addressBar.paste()
+ enabled: addressBar.canPaste
+ }
+ MenuItem {
+ text: qsTr("Delete")
+ onTriggered: addressBar.text = qsTr("")
+ enabled: addressBar.selectedText.length > 0
+ }
+ MenuSeparator {}
+ MenuItem {
+ text: qsTr("Select All")
+ onTriggered: addressBar.selectAll()
+ enabled: addressBar.text.length > 0
+ }
+ }
+ }
+ leftPadding: 26
+ focus: true
+ Layout.fillWidth: true
+ Binding on text {
+ when: win.currentWebView
+ value: win.currentWebView.url
+ }
+ onAccepted: win.currentWebView.url = Utils.fromUserInput(text)
+ selectByMouse: true
+ }
+ ToolButton {
+ id: settingsMenuButton
+ text: qsTr("â‹®")
+ onClicked: settingsMenu.open()
+ Menu {
+ id: settingsMenu
+ y: settingsMenuButton.height
+ MenuItem {
+ id: loadImages
+ text: "Autoload images"
+ checkable: true
+ checked: WebEngine.settings.autoLoadImages
+ }
+ MenuItem {
+ id: javaScriptEnabled
+ text: "JavaScript On"
+ checkable: true
+ checked: WebEngine.settings.javascriptEnabled
+ }
+ MenuItem {
+ id: errorPageEnabled
+ text: "ErrorPage On"
+ checkable: true
+ checked: WebEngine.settings.errorPageEnabled
+ }
+ MenuItem {
+ id: pluginsEnabled
+ text: "Plugins On"
+ checkable: true
+ checked: true
+ }
+ MenuItem {
+ id: fullScreenSupportEnabled
+ text: "FullScreen On"
+ checkable: true
+ checked: WebEngine.settings.fullScreenSupportEnabled
+ }
+ MenuItem {
+ id: offTheRecordEnabled
+ text: "Off The Record"
+ checkable: true
+ checked: win.currentWebView?.profile === (win.applicationRoot as ApplicationRoot).otrPrototype.instance()
+ onToggled: function() {
+ if (win.currentWebView) {
+ win.currentWebView.profile = offTheRecordEnabled.checked
+ ? (win.applicationRoot as ApplicationRoot).otrPrototype.instance()
+ : (win.applicationRoot as ApplicationRoot).defaultProfilePrototype.instance();
+ }
+ }
+ }
+ MenuItem {
+ id: httpDiskCacheEnabled
+ text: "HTTP Disk Cache"
+ checkable: !win.currentWebView?.profile?.offTheRecord ?? false
+ checked: win.currentWebView?.profile.httpCacheType === WebEngineProfile.DiskHttpCache
+ onToggled: function() {
+ if (win.currentWebView) {
+ win.currentWebView.profile.httpCacheType = httpDiskCacheEnabled.checked
+ ? WebEngineProfile.DiskHttpCache
+ : WebEngineProfile.MemoryHttpCache;
+ }
+ }
+ }
+ MenuItem {
+ id: autoLoadIconsForPage
+ text: "Icons On"
+ checkable: true
+ checked: WebEngine.settings.autoLoadIconsForPage
+ }
+ MenuItem {
+ id: touchIconsEnabled
+ text: "Touch Icons On"
+ checkable: true
+ checked: WebEngine.settings.touchIconsEnabled
+ enabled: autoLoadIconsForPage.checked
+ }
+ MenuItem {
+ id: webRTCPublicInterfacesOnly
+ text: "WebRTC Public Interfaces Only"
+ checkable: true
+ checked: WebEngine.settings.webRTCPublicInterfacesOnly
+ }
+ MenuItem {
+ id: devToolsEnabled
+ text: "Open DevTools"
+ checkable: true
+ checked: false
+ }
+ MenuItem {
+ id: pdfViewerEnabled
+ text: "PDF Viewer Enabled"
+ checkable: true
+ checked: WebEngine.settings.pdfViewerEnabled
+ }
+ Menu {
+ id: imageAnimationPolicy
+ title: "Image Animation Policy"
+
+ MenuItem {
+ id: disableImageAnimation
+ text: "Disable All Image Animation"
+ checkable: true
+ autoExclusive: true
+ checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Disallow
+ onTriggered: {
+ appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.Disallow
+ }
+ }
+
+ MenuItem {
+ id: allowImageAnimation
+ text: "Allow All Animated Images"
+ checkable: true
+ autoExclusive: true
+ checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Allow
+ onTriggered : {
+ appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.Allow
+ }
+ }
+
+ MenuItem {
+ id: animateImageOnce
+ text: "Animate Image Once"
+ checkable: true
+ autoExclusive: true
+ checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.AnimateOnce
+ onTriggered : {
+ appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.AnimateOnce
+ }
+ }
+ }
+
+ MenuItem {
+ id: javascriptCanAccessClipboard
+ text: "JavaScript can access clipboard"
+ checkable: true
+ checked: WebEngine.settings.javascriptCanAccessClipboard
+ }
+ MenuItem {
+ id: javascriptCanPaste
+ text: "JavaScript can paste"
+ checkable: true
+ checked: WebEngine.settings.javascriptCanPaste
+ }
+ }
+ }
+ }
+ ProgressBar {
+ id: progressBar
+ height: 3
+ anchors {
+ left: parent.left
+ top: parent.bottom
+ right: parent.right
+ leftMargin: parent.anchors.leftMargin
+ rightMargin: parent.anchors.rightMargin
+ }
+ background: Item {}
+ z: -2
+ from: 0
+ to: 100
+ value: (win.currentWebView?.loadProgress < 100) ? win.currentWebView.loadProgress : 0
+ }
+ }
+
+ StackLayout {
+ id: tabLayout
+ currentIndex: tabBar.currentIndex
+
+ anchors.top: tabBar.bottom
+ anchors.bottom: devToolsView.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ }
+
+ Component {
+ id: tabButtonComponent
+
+ TabButton {
+ id: tabButton
+ property color frameColor: "#999999"
+ property color fillColor: "#eeeeee"
+ property color nonSelectedColor: "#dddddd"
+ property string tabTitle: "New Tab"
+
+ contentItem: Rectangle {
+ id: tabRectangle
+ color: tabButton.down ? tabButton.fillColor : tabButton.nonSelectedColor
+ border.width: 1
+ border.color: tabButton.frameColor
+ implicitWidth: Math.max(text.width + 30, 80)
+ implicitHeight: Math.max(text.height + 10, 20)
+ Rectangle { height: 1 ; width: parent.width ; color: tabButton.frameColor}
+ Rectangle { height: parent.height ; width: 1; color: tabButton.frameColor}
+ Rectangle { x: parent.width - 2; height: parent.height ; width: 1; color: tabButton.frameColor}
+ Text {
+ id: text
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: 6
+ text: tabButton.tabTitle
+ elide: Text.ElideRight
+ color: tabButton.down ? "black" : tabButton.frameColor
+ width: parent.width - button.background.width
+ }
+ Button {
+ id: button
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.rightMargin: 4
+ height: 12
+ background: Rectangle {
+ implicitWidth: 12
+ implicitHeight: 12
+ color: button.hovered ? "#cccccc" : tabRectangle.color
+ Text {text: "x"; anchors.centerIn: parent; color: "gray"}
+ }
+ onClicked: tabButton.closeTab()
+ }
+ }
+
+ onClicked: addressBar.text = (tabLayout.itemAt(TabBar.index) as WebEngineView).url;
+ function closeTab() {
+ tabBar.tryCloseView(TabBar.index);
+ }
+ }
+ }
+
+ TabBar {
+ id: tabBar
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ Component.onCompleted: createTab((win.applicationRoot as ApplicationRoot).defaultProfilePrototype.instance())
+
+ function createTab(profile, focusOnNewTab = true, url = undefined) {
+ var webview = tabComponent.createObject(tabLayout, {index: tabBar.count , profile: profile});
+ var newTabButton = tabButtonComponent.createObject(tabBar, {tabTitle: Qt.binding(function () { return webview.title; })});
+ tabBar.addItem(newTabButton);
+ if (focusOnNewTab) {
+ tabBar.setCurrentIndex(tabBar.count - 1);
+ }
+ if (url !== undefined) {
+ webview.url = url;
+ }
+ return webview;
+ }
+
+ function tryCloseView(index) {
+ tabLayout.children[index].triggerWebAction(WebEngineView.RequestClose);
+ }
+
+ function removeView(index) {
+ if (tabBar.count > 1) {
+ tabBar.removeItem(tabBar.itemAt(index));
+ tabLayout.children[index].destroy();
+ } else {
+ win.lastTabClosing = true;
+ win.close();
+ }
+ }
+
+ Component {
+ id: tabComponent
+ WebEngineView {
+ id: webEngineView
+ property int index;
+ focus: true
+
+ onLinkHovered: function(hoveredUrl) {
+ if (hoveredUrl === "")
+ hideStatusText.start();
+ else {
+ statusText.text = hoveredUrl;
+ statusBubble.visible = true;
+ hideStatusText.stop();
+ }
+ }
+
+ states: [
+ State {
+ name: "FullScreen"
+ PropertyChanges {
+ target: tabBar
+ visible: false
+ height: 0
+ }
+ PropertyChanges {
+ target: navigationBar
+ visible: false
+ }
+ }
+ ]
+ settings.localContentCanAccessRemoteUrls: true
+ settings.localContentCanAccessFileUrls: false
+ settings.autoLoadImages: appSettings.autoLoadImages
+ settings.javascriptEnabled: appSettings.javaScriptEnabled
+ settings.errorPageEnabled: appSettings.errorPageEnabled
+ settings.pluginsEnabled: appSettings.pluginsEnabled
+ settings.fullScreenSupportEnabled: appSettings.fullScreenSupportEnabled
+ settings.autoLoadIconsForPage: appSettings.autoLoadIconsForPage
+ settings.touchIconsEnabled: appSettings.touchIconsEnabled
+ settings.webRTCPublicInterfacesOnly: appSettings.webRTCPublicInterfacesOnly
+ settings.pdfViewerEnabled: appSettings.pdfViewerEnabled
+ settings.imageAnimationPolicy: appSettings.imageAnimationPolicy
+ settings.screenCaptureEnabled: true
+ settings.javascriptCanAccessClipboard: appSettings.javascriptCanAccessClipboard
+ settings.javascriptCanPaste: appSettings.javascriptCanPaste
+
+ onWindowCloseRequested: function() {
+ tabBar.removeView(webEngineView.index);
+ }
+
+ onCertificateError: function(error) {
+ if (!error.isMainFrame) {
+ error.rejectCertificate();
+ return;
+ }
+
+ error.defer();
+ sslDialog.enqueue(error);
+ }
+
+ onNewWindowRequested: function(request) {
+ if (!request.userInitiated)
+ console.warn("Blocked a popup window.");
+ else if (request.destination === WebEngineNewWindowRequest.InNewTab) {
+ var tab = tabBar.createTab(win.currentWebView.profile, true, request.requestedUrl);
+ tab.acceptAsNewWindow(request);
+ } else if (request.destination === WebEngineNewWindowRequest.InNewBackgroundTab) {
+ var backgroundTab = tabBar.createTab(win.currentWebView.profile, false);
+ backgroundTab.acceptAsNewWindow(request);
+ } else if (request.destination === WebEngineNewWindowRequest.InNewDialog) {
+ var dialog = (win.applicationRoot as ApplicationRoot).createDialog(win.currentWebView.profile);
+ dialog.win.currentWebView.acceptAsNewWindow(request);
+ } else {
+ var window = (win.applicationRoot as ApplicationRoot).createWindow(win.currentWebView.profile);
+ window.win.currentWebView.acceptAsNewWindow(request);
+ }
+ }
+
+ onFullScreenRequested: function(request) {
+ if (request.toggleOn) {
+ webEngineView.state = "FullScreen";
+ win.previousVisibility = win.visibility;
+ win.showFullScreen();
+ fullScreenNotification.show();
+ } else {
+ webEngineView.state = "";
+ win.visibility = win.previousVisibility;
+ fullScreenNotification.hide();
+ }
+ request.accept();
+ }
+
+ onRegisterProtocolHandlerRequested: function(request) {
+ console.log("accepting registerProtocolHandler request for "
+ + request.scheme + " from " + request.origin);
+ request.accept();
+ }
+
+ onDesktopMediaRequested: function(request) {
+ // select the primary screen
+ request.selectScreen(request.screensModel.index(0, 0));
+ }
+
+ onRenderProcessTerminated: function(terminationStatus, exitCode) {
+ var status = "";
+ switch (terminationStatus) {
+ case WebEngineView.NormalTerminationStatus:
+ status = "(normal exit)";
+ break;
+ case WebEngineView.AbnormalTerminationStatus:
+ status = "(abnormal exit)";
+ break;
+ case WebEngineView.CrashedTerminationStatus:
+ status = "(crashed)";
+ break;
+ case WebEngineView.KilledTerminationStatus:
+ status = "(killed)";
+ break;
+ }
+
+ print("Render process exited with code " + exitCode + " " + status);
+ reloadTimer.running = true;
+ }
+
+ onSelectClientCertificate: function(selection) {
+ selection.certificates[0].select();
+ }
+
+ onFindTextFinished: function(result) {
+ if (!findBar.visible)
+ findBar.visible = true;
+
+ findBar.numberOfMatches = result.numberOfMatches;
+ findBar.activeMatch = result.activeMatch;
+ }
+
+ onLoadingChanged: function(loadRequest) {
+ if (loadRequest.status === WebEngineView.LoadStartedStatus)
+ findBar.reset();
+ }
+
+ onPermissionRequested: function(permission) {
+ permissionDialog.permission = permission;
+ permissionDialog.visible = true;
+ }
+ onWebAuthUxRequested: function(request) {
+ webAuthDialog.init(request);
+ }
+
+ Timer {
+ id: reloadTimer
+ interval: 0
+ running: false
+ repeat: false
+ onTriggered: win.currentWebView.reload()
+ }
+ }
+ }
+ }
+ WebEngineView {
+ id: devToolsView
+ visible: devToolsEnabled.checked
+ height: visible ? 400 : 0
+ inspectedView: visible && tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ onNewWindowRequested: function(request) {
+ var tab = tabBar.createTab(win.currentWebView.profile);
+ request.openIn(tab);
+ }
+
+ Timer {
+ id: hideTimer
+ interval: 0
+ running: false
+ repeat: false
+ onTriggered: devToolsEnabled.checked = false
+ }
+ onWindowCloseRequested: function() {
+ // Delay hiding for keep the inspectedView set to receive the ACK message of close.
+ hideTimer.running = true;
+ }
+ }
+ Dialog {
+ id: sslDialog
+ anchors.centerIn: parent
+ contentWidth: Math.max(mainTextForSSLDialog.width, detailedTextForSSLDialog.width)
+ contentHeight: mainTextForSSLDialog.height + detailedTextForSSLDialog.height
+ property var certErrors: []
+ // fixme: icon!
+ // icon: StandardIcon.Warning
+ standardButtons: Dialog.No | Dialog.Yes
+ title: "Server's certificate not trusted"
+ contentItem: Item {
+ Label {
+ id: mainTextForSSLDialog
+ text: "Do you wish to continue?"
+ }
+ Text {
+ id: detailedTextForSSLDialog
+ anchors.top: mainTextForSSLDialog.bottom
+ text: "If you wish so, you may continue with an unverified certificate.\n" +
+ "Accepting an unverified certificate means\n" +
+ "you may not be connected with the host you tried to connect to.\n" +
+ "Do you wish to override the security check and continue?"
+ }
+ }
+
+ onAccepted: {
+ certErrors.shift().acceptCertificate();
+ presentError();
+ }
+ onRejected: reject()
+
+ function reject(){
+ certErrors.shift().rejectCertificate();
+ presentError();
+ }
+ function enqueue(error){
+ certErrors.push(error);
+ presentError();
+ }
+ function presentError(){
+ visible = certErrors.length > 0
+ }
+ }
+ Dialog {
+ id: permissionDialog
+ anchors.centerIn: parent
+ width: Math.min(win.width, win.height) / 3 * 2
+ contentWidth: mainTextForPermissionDialog.width
+ contentHeight: mainTextForPermissionDialog.height
+ standardButtons: Dialog.No | Dialog.Yes
+ title: "Permission Request"
+
+ property var permission;
+
+ contentItem: Item {
+ Label {
+ id: mainTextForPermissionDialog
+ }
+ }
+
+ onAccepted: permission.grant()
+ onRejected: permission.deny()
+ onVisibleChanged: {
+ if (visible) {
+ mainTextForPermissionDialog.text = questionForPermissionType();
+ width = contentWidth + 20;
+ }
+ }
+
+ function questionForPermissionType() {
+ var question = "Allow " + permission.origin + " to "
+
+ switch (permission.permissionType) {
+ case WebEnginePermission.PermissionType.Geolocation:
+ question += "access your location information?";
+ break;
+ case WebEnginePermission.PermissionType.MediaAudioCapture:
+ question += "access your microphone?";
+ break;
+ case WebEnginePermission.PermissionType.MediaVideoCapture:
+ question += "access your webcam?";
+ break;
+ case WebEnginePermission.PermissionType.MediaAudioVideoCapture:
+ question += "access your microphone and webcam?";
+ break;
+ case WebEnginePermission.PermissionType.MouseLock:
+ question += "lock your mouse cursor?";
+ break;
+ case WebEnginePermission.PermissionType.DesktopVideoCapture:
+ question += "capture video of your desktop?";
+ break;
+ case WebEnginePermission.PermissionType.DesktopAudioVideoCapture:
+ question += "capture audio and video of your desktop?";
+ break;
+ case WebEnginePermission.PermissionType.Notifications:
+ question += "show notification on your desktop?";
+ break;
+ case WebEnginePermission.PermissionType.ClipboardReadWrite:
+ question += "read from and write to your clipboard?";
+ break;
+ case WebEnginePermission.PermissionType.LocalFontsAccess:
+ question += "access the fonts stored on your machine?";
+ break;
+ default:
+ question += "access unknown or unsupported permission type [" + permission.permissionType + "] ?";
+ break;
+ }
+
+ return question;
+ }
+ }
+
+ FullScreenNotification {
+ id: fullScreenNotification
+ }
+
+ DownloadView {
+ id: downloadView
+ visible: false
+ anchors.fill: parent
+ }
+
+ WebAuthDialog {
+ id: webAuthDialog
+ visible: false
+ }
+
+ MessageDialog {
+ id: downloadAcceptDialog
+ property var downloadRequest: downloadView.pendingDownloadRequest
+ title: "Download requested"
+ text: downloadRequest ? downloadRequest.suggestedFileName : ""
+ buttons: Dialog.No | Dialog.Yes
+ onAccepted: {
+ downloadView.visible = true;
+ downloadView.append(downloadRequest);
+ downloadRequest.accept();
+ }
+ onRejected: {
+ downloadRequest.cancel();
+ }
+ onButtonClicked: {
+ visible = false;
+ }
+ visible: false
+ }
+
+ function onDownloadRequested(download) {
+ downloadView.pendingDownloadRequest = download;
+ downloadAcceptDialog.visible = true;
+ }
+
+ FindBar {
+ id: findBar
+ visible: false
+ anchors.right: parent.right
+ anchors.rightMargin: 10
+ anchors.top: parent.top
+
+ onFindNext: {
+ if (text)
+ win.currentWebView?.findText(text);
+ else if (!visible)
+ visible = true;
+ }
+ onFindPrevious: {
+ if (text)
+ win.currentWebView?.findText(text, WebEngineView.FindBackward);
+ else if (!visible)
+ visible = true;
+ }
+ }
+
+
+ Rectangle {
+ id: statusBubble
+ color: "oldlace"
+ property int padding: 8
+ visible: false
+
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ width: statusText.paintedWidth + padding
+ height: statusText.paintedHeight + padding
+
+ Text {
+ id: statusText
+ anchors.centerIn: statusBubble
+ elide: Qt.ElideMiddle
+
+ Timer {
+ id: hideStatusText
+ interval: 750
+ onTriggered: {
+ statusText.text = "";
+ statusBubble.visible = false;
+ }
+ }
+ }
+ }
+
+ onClosing: function(closeEvent) {
+ if (lastTabClosing) {
+ return;
+ }
+ closeEvent.accepted = false
+ for (var i = 0; i < tabBar.count; i++) {
+ tabBar.tryCloseView(i);
+ }
+ }
+}
--- /dev/null
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls.Fusion
+
+Rectangle {
+ id: downloadView
+ color: "lightgray"
+ property var pendingDownloadRequest: null
+
+ ListModel {
+ id: downloadModel
+ property var downloads: []
+ }
+
+ function append(download) {
+ downloadModel.append(download);
+ downloadModel.downloads.push(download);
+ }
+
+ Component {
+ id: downloadItemDelegate
+
+ Rectangle {
+ id: downloadItem
+ width: listView.width
+ height: childrenRect.height
+ anchors.margins: 10
+ radius: 3
+ color: "transparent"
+ border.color: "black"
+
+ required property int index
+
+ Rectangle {
+ id: progressBar
+
+ property real progress: {
+ let d = downloadModel.downloads[downloadItem.index]
+ return d ? d.receivedBytes / d.totalBytes : 0
+ }
+
+ radius: 3
+ color: width === listView.width ? "green" : "#2b74c7"
+ width: listView.width * progress
+ height: cancelButton.height
+
+ Behavior on width {
+ SmoothedAnimation { duration: 100 }
+ }
+ }
+ Rectangle {
+ anchors {
+ left: parent.left
+ right: parent.right
+ leftMargin: 20
+ }
+ Label {
+ id: label
+ text: {
+ let d = downloadModel.downloads[downloadItem.index]
+ return d ? d.downloadDirectory + "/" + d.downloadFileName : qsTr("")
+ }
+ anchors {
+ verticalCenter: cancelButton.verticalCenter
+ left: parent.left
+ right: cancelButton.left
+ }
+ }
+ Button {
+ id: cancelButton
+ anchors.right: parent.right
+ icon.source: "qrc:///icons/3rdparty/process-stop.png"
+ onClicked: {
+ var download = downloadModel.downloads[downloadItem.index];
+
+ download.cancel();
+
+ downloadModel.downloads = downloadModel.downloads.filter(function (el) {
+ return el.id !== download.id;
+ });
+ downloadModel.remove(downloadItem.index);
+ }
+ }
+ }
+ }
+
+ }
+ ListView {
+ id: listView
+ anchors {
+ topMargin: 10
+ top: parent.top
+ bottom: parent.bottom
+ horizontalCenter: parent.horizontalCenter
+ }
+ width: parent.width - 20
+ spacing: 5
+
+ model: downloadModel
+ delegate: downloadItemDelegate
+
+ Text {
+ visible: !listView.count
+ horizontalAlignment: Text.AlignHCenter
+ height: 30
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ }
+ font.pixelSize: 20
+ text: "No active downloads."
+ }
+
+ Rectangle {
+ color: "gray"
+ anchors {
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ }
+ height: 30
+ Button {
+ id: okButton
+ text: "OK"
+ anchors.centerIn: parent
+ onClicked: {
+ downloadView.visible = false;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls.Fusion
+import QtQuick.Layouts
+
+Rectangle {
+ id: root
+
+ property int numberOfMatches: 0
+ property int activeMatch: 0
+ property alias text: findTextField.text
+
+ function reset() {
+ numberOfMatches = 0;
+ activeMatch = 0;
+ visible = false;
+ }
+
+ signal findNext()
+ signal findPrevious()
+
+ width: 250
+ height: 35
+ radius: 2
+
+ border.width: 1
+ border.color: "black"
+ color: "white"
+
+ onVisibleChanged: {
+ if (visible)
+ findTextField.forceActiveFocus();
+ }
+
+
+ RowLayout {
+ anchors.fill: parent
+ anchors.topMargin: 5
+ anchors.bottomMargin: 5
+ anchors.leftMargin: 10
+ anchors.rightMargin: 10
+
+ spacing: 5
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ TextField {
+ id: findTextField
+ anchors.fill: parent
+ color: "black"
+ background: Rectangle {
+ color: "transparent"
+ }
+
+ onAccepted: root.findNext()
+ onTextChanged: root.findNext()
+ onActiveFocusChanged: activeFocus ? selectAll() : deselect()
+ }
+ }
+
+ Label {
+ text: root.activeMatch + "/" + root.numberOfMatches
+ visible: findTextField.text !== ""
+ color: "black"
+ }
+
+ Rectangle {
+ border.width: 1
+ border.color: "#dddddd"
+ Layout.preferredWidth: 2
+ Layout.preferredHeight: parent.height
+ }
+
+ ToolButton {
+ id: findBtnLeft
+ text: "<"
+ enabled: root.numberOfMatches > 0
+ onClicked: root.findPrevious()
+ contentItem: Text {
+ color: "black"
+ text: findBtnLeft.text
+ }
+ }
+
+ ToolButton {
+ id: findBtnRight
+ text: ">"
+ enabled: root.numberOfMatches > 0
+ onClicked: root.findNext()
+ contentItem: Text {
+ color: "black"
+ text: findBtnRight.text
+ }
+ }
+
+ ToolButton {
+ id: findBtnClose
+ text: "x"
+ onClicked: root.visible = false
+ contentItem: Text {
+ color: "black"
+ text: findBtnClose.text
+ }
+ }
+ }
+}
--- /dev/null
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Rectangle {
+ id: fullScreenNotification
+ width: 500
+ height: 40
+ color: "white"
+ radius: 7
+
+ visible: false
+ opacity: 0
+
+ function show() {
+ visible = true;
+ opacity = 1;
+ reset.start();
+ }
+
+ function hide() {
+ reset.stop();
+ opacity = 0;
+ }
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 750
+ onStopped: {
+ if (fullScreenNotification.opacity === 0)
+ fullScreenNotification.visible = false;
+ }
+ }
+ }
+
+ Timer {
+ id: reset
+ interval: 5000
+ onTriggered: fullScreenNotification.hide()
+ }
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ y: 125
+
+ Text {
+ id: message
+ width: parent.width
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+
+ wrapMode: Text.WordWrap
+ elide: Text.ElideNone
+ clip: true
+
+ text: qsTr("You are now in fullscreen mode. Press ESC to quit!")
+ }
+}
--- /dev/null
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtWebEngine
+
+Dialog {
+ id: webAuthDialog
+ anchors.centerIn: parent
+ width: Math.min(parent.parent.width, parent.parent.height) / 3 * 2
+ contentWidth: verticalLayout.width +10;
+ contentHeight: verticalLayout.height +10;
+ standardButtons: Dialog.Cancel | Dialog.Apply
+ title: "WebAuth Request"
+
+ property var selectAccount;
+ property var authrequest: null;
+
+ Connections {
+ id: webauthConnection
+ ignoreUnknownSignals: true
+ function onStateChanged(state) {
+ webAuthDialog.setupUI(state);
+ }
+ }
+
+ onApplied: {
+ switch (webAuthDialog.authrequest.state) {
+ case WebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
+ webAuthDialog.authrequest.setPin(pinEdit.text);
+ break;
+ case WebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
+ webAuthDialog.authrequest.setSelectedAccount(webAuthDialog.selectAccount);
+ break;
+ default:
+ break;
+ }
+ }
+
+ onRejected: {
+ webAuthDialog.authrequest.cancel();
+ }
+
+ function init(request) {
+ pinLabel.visible = false;
+ pinEdit.visible = false;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ selectAccountModel.clear();
+ webAuthDialog.authrequest = request;
+ webauthConnection.target = request;
+ setupUI(webAuthDialog.authrequest.state)
+ webAuthDialog.visible = true;
+ pinEntryError.visible = false;
+ }
+
+ function setupUI(state) {
+ switch (state) {
+ case WebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
+ setupSelectAccountUI();
+ break;
+ case WebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
+ setupCollectPin();
+ break;
+ case WebEngineWebAuthUxRequest.WebAuthUxState.FinishTokenCollection:
+ setupFinishCollectToken();
+ break;
+ case WebEngineWebAuthUxRequest.WebAuthUxState.RequestFailed:
+ setupErrorUI();
+ break;
+ case WebEngineWebAuthUxRequest.WebAuthUxState.Completed:
+ webAuthDialog.close();
+ break;
+ }
+ }
+
+ ButtonGroup {
+ id : selectAccount;
+ exclusive: true;
+ }
+
+ ListModel {
+ id: selectAccountModel
+
+ }
+ contentItem: Item {
+ ColumnLayout {
+ id : verticalLayout
+ spacing : 10
+
+ Label {
+ id: heading
+ text: "";
+ }
+
+ Label {
+ id: description
+ text: "";
+ }
+
+ Row {
+ spacing : 10
+ Label {
+ id: pinLabel
+ text: "PIN";
+ }
+ TextInput {
+ id: pinEdit
+ text: "EnterPin"
+ enabled: true
+ focus: true
+ color: "white"
+ layer.sourceRect: Qt.rect(0, 0, 20, 20)
+ }
+ }
+
+ Row {
+ spacing : 10
+ Label {
+ id: confirmPinLabel
+ text: "Confirm PIN";
+ }
+ TextEdit {
+ id: confirmPinEdit
+ text: ""
+ }
+ }
+
+ Label {
+ id: pinEntryError
+ text: "";
+ }
+
+ Repeater {
+ id : selectAccountRepeater
+ model: selectAccountModel
+ Column {
+ id: selectAccountRepeaterColumn
+ required property string modelData
+ spacing : 5
+ RadioButton {
+ text: selectAccountRepeaterColumn.modelData
+ ButtonGroup.group : webAuthDialog.selectAccount;
+ onClicked: function(){
+ webAuthDialog.selectAccount = text;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ function setupSelectAccountUI() {
+ webAuthDialog.selectAccount = "";
+ heading.text = "Choose a passkey";
+ description.text = "Which passkey do you want to use for " + webAuthDialog.authrequest.relyingPartyId;
+
+ selectAccountModel.clear();
+ var userNames = webAuthDialog.authrequest.userNames;
+ for (var i = 0; i < userNames.length; i++) {
+ selectAccountModel.append( {"name" : userNames[i]});
+ }
+ pinLabel.visible = false;
+ pinEdit.visible = false;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ pinEntryError.visible = false;
+ standardButton(Dialog.Apply).visible = true;
+ standardButton(Dialog.Cancel).visible = true;
+ standardButton(Dialog.Cancel).text ="Cancel"
+ }
+
+ function setupCollectPin() {
+ var requestInfo = webAuthDialog.authrequest.pinRequest;
+
+ pinEdit.clear();
+
+ if (requestInfo.reason === WebEngineWebAuthUxRequest.PinEntryReason.Challenge) {
+ heading.text = "PIN required";
+ description.text = "Enter the PIN for your security key";
+ pinLabel.visible = true;
+ pinEdit.visible = true;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ } else if (requestInfo.reason === WebEngineWebAuthUxRequest.PinEntryReason.Set) {
+ heading.text = "Set PIN ";
+ description.text = "Set new PIN for your security key";
+ pinLabel.visible = true;
+ pinEdit.visible = true;
+ confirmPinLabel.visible = true;
+ confirmPinEdit.visible = true;
+ }
+ pinEntryError.text = getPINErrorDetails() + " " + requestInfo.remainingAttempts + " attempts reamining";
+ pinEntryError.visible = true;
+ selectAccountModel.clear();
+ standardButton(Dialog.Cancel).visible = true;
+ standardButton(Dialog.Cancel).text ="Cancel"
+ standardButton(Dialog.Apply).visible = true;
+ }
+
+ function getPINErrorDetails() {
+ var requestInfo = webAuthDialog.authrequest.pinRequest;
+ switch (requestInfo.error) {
+ case WebEngineWebAuthUxRequest.PinEntryError.NoError:
+ return "";
+ case WebEngineWebAuthUxRequest.PinEntryError.TooShort:
+ return "Too short";
+ case WebEngineWebAuthUxRequest.PinEntryError.InternalUvLocked:
+ return "Internal Uv locked";
+ case WebEngineWebAuthUxRequest.PinEntryError.WrongPin:
+ return "Wrong PIN";
+ case WebEngineWebAuthUxRequest.PinEntryError.InvalidCharacters:
+ return "Invalid characters";
+ case WebEngineWebAuthUxRequest.PinEntryError.SameAsCurrentPin:
+ return "Same as current PIN";
+ }
+ }
+
+ function getRequestFailureResaon() {
+ var requestFailureReason = webAuthDialog.authrequest.requestFailureReason;
+ switch (requestFailureReason) {
+ case WebEngineWebAuthUxRequest.RequestFailureReason.Timeout:
+ return " Request Timeout";
+ case WebEngineWebAuthUxRequest.RequestFailureReason.KeyNotRegistered:
+ return "Key not registered";
+ case WebEngineWebAuthUxRequest.RequestFailureReason.KeyAlreadyRegistered:
+ return "You already registered this device. You don't have to register it again\n"
+ + "Try again with different key or device.";
+ case WebEngineWebAuthUxRequest.RequestFailureReason.SoftPinBlock:
+ return "The security key is locked because the wrong PIN was entered too many times.\n"
+ + "To unlock it, remove and reinsert it.";
+ case WebEngineWebAuthUxRequest.RequestFailureReason.HardPinBlock:
+ return "The security key is locked because the wrong PIN was entered too many times.\n"
+ + "You'll need to reset the security key.";
+ case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorRemovedDuringPinEntry:
+ return "Authenticator removed during verification. Please reinsert and try again";
+ case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingResidentKeys:
+ return "Authenticator doesn't have resident key support";
+ case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingUserVerification:
+ return "Authenticator missing user verification";
+ case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingLargeBlob:
+ return "Authenticator missing Large Blob support";
+ case WebEngineWebAuthUxRequest.RequestFailureReason.NoCommonAlgorithms:
+ return "No common Algorithms";
+ case WebEngineWebAuthUxRequest.RequestFailureReason.StorageFull:
+ return "Storage full";
+ case WebEngineWebAuthUxRequest.RequestFailureReason.UserConsentDenied:
+ return "User consent denied";
+ case WebEngineWebAuthUxRequest.RequestFailureReason.WinUserCancelled:
+ return "User cancelled request";
+ }
+ }
+
+ function setupFinishCollectToken() {
+ heading.text = "Use your security key with " + webAuthDialog.authrequest.relyingPartyId;
+ description.text = "Touch your security key again to complete the request.";
+ pinLabel.visible = false;
+ pinEdit.visible = false;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ selectAccountModel.clear();
+ pinEntryError.visible = false;
+ standardButton(Dialog.Apply).visible = false;
+ standardButton(Dialog.Cancel).visible = true;
+ standardButton(Dialog.Cancel).text ="Cancel"
+ }
+
+ function setupErrorUI() {
+ heading.text = "Something went wrong";
+ description.text = getRequestFailureResaon();
+ pinLabel.visible = false;
+ pinEdit.visible = false;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ selectAccountModel.clear();
+ pinEntryError.visible = false;
+ standardButton(Dialog.Apply).visible = false;
+ standardButton(Dialog.Cancel).visible = true;
+ standardButton(Dialog.Cancel).text ="Close"
+ }
+}
--- /dev/null
+module BrowserUtils
+ApplicationRoot 254.0 ApplicationRoot.qml
+BrowserDialog 254.0 BrowserDialog.qml
+BrowserWindow 254.0 BrowserWindow.qml
+DownloadView 254.0 DownloadView.qml
+FindBar 254.0 FindBar.qml
+FullScreenNotification 254.0 FullScreenNotification.qml
+WebAuthDialog 254.0 WebAuthDialog.qml
+depends QtQuick
+++ /dev/null
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-pragma ComponentBehavior: Bound
-
-import QtCore
-import QtQml
-import QtQuick
-import QtQuick.Controls.Fusion
-import QtQuick.Dialogs
-import QtQuick.Layouts
-import QtQuick.Window
-import QtWebEngine
-import BrowserUtils
-
-ApplicationWindow {
- id: win
- required property QtObject applicationRoot
- property WebEngineView currentWebView: tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null
- property int previousVisibility: Window.Windowed
- property int createdTabs: 0
- property bool lastTabClosing: false
-
- width: 1300
- height: 900
- visible: true
- title: win.currentWebView?.title ?? ""
-
- // Make sure the Qt.WindowFullscreenButtonHint is set on OS X.
- Component.onCompleted: flags = flags | Qt.WindowFullscreenButtonHint
-
- onCurrentWebViewChanged: {
- findBar.reset();
- }
-
- // When using style "mac", ToolButtons are not supposed to accept focus.
- property bool platformIsMac: Qt.platform.os === "osx"
-
- Settings {
- id : appSettings
- property alias autoLoadImages: loadImages.checked
- property alias javaScriptEnabled: javaScriptEnabled.checked
- property alias errorPageEnabled: errorPageEnabled.checked
- property alias pluginsEnabled: pluginsEnabled.checked
- property alias fullScreenSupportEnabled: fullScreenSupportEnabled.checked
- property alias autoLoadIconsForPage: autoLoadIconsForPage.checked
- property alias touchIconsEnabled: touchIconsEnabled.checked
- property alias webRTCPublicInterfacesOnly : webRTCPublicInterfacesOnly.checked
- property alias devToolsEnabled: devToolsEnabled.checked
- property alias pdfViewerEnabled: pdfViewerEnabled.checked
- property int imageAnimationPolicy: WebEngineSettings.ImageAnimationPolicy.Allow
- property alias javascriptCanAccessClipboard: javascriptCanAccessClipboard.checked
- property alias javascriptCanPaste: javascriptCanPaste.checked
- }
-
- Action {
- shortcut: "Ctrl+D"
- onTriggered: {
- downloadView.visible = !downloadView.visible;
- }
- }
- Action {
- id: focus
- shortcut: "Ctrl+L"
- onTriggered: {
- addressBar.forceActiveFocus();
- addressBar.selectAll();
- }
- }
- Action {
- shortcut: StandardKey.Refresh
- onTriggered: {
- if (win.currentWebView)
- win.currentWebView.reload();
- }
- }
- Action {
- shortcut: StandardKey.AddTab
- onTriggered: {
- tabBar.createTab(tabBar.count !== 0
- ? win.currentWebView.profile
- : (win.applicationRoot as ApplicationRoot).defaultProfilePrototype.instance());
- addressBar.forceActiveFocus();
- addressBar.selectAll();
- }
- }
- Action {
- shortcut: StandardKey.Close
- onTriggered: {
- win.currentWebView.triggerWebAction(WebEngineView.RequestClose);
- }
- }
- Action {
- shortcut: StandardKey.Quit
- onTriggered: win.close()
- }
- Action {
- shortcut: "Escape"
- onTriggered: {
- if (win.currentWebView.state === "FullScreen") {
- win.visibility = win.previousVisibility;
- fullScreenNotification.hide();
- win.currentWebView.triggerWebAction(WebEngineView.ExitFullScreen);
- }
-
- if (findBar.visible)
- findBar.visible = false;
- }
- }
- Action {
- shortcut: "Ctrl+0"
- onTriggered: win.currentWebView.zoomFactor = 1.0
- }
- Action {
- shortcut: StandardKey.ZoomOut
- onTriggered: win.currentWebView.zoomFactor -= 0.1
- }
- Action {
- shortcut: StandardKey.ZoomIn
- onTriggered: win.currentWebView.zoomFactor += 0.1
- }
-
- Action {
- shortcut: StandardKey.Copy
- onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Copy)
- }
- Action {
- shortcut: StandardKey.Cut
- onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Cut)
- }
- Action {
- shortcut: StandardKey.Paste
- onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Paste)
- }
- Action {
- shortcut: "Shift+"+StandardKey.Paste
- onTriggered: win.currentWebView.triggerWebAction(WebEngineView.PasteAndMatchStyle)
- }
- Action {
- shortcut: StandardKey.SelectAll
- onTriggered: win.currentWebView.triggerWebAction(WebEngineView.SelectAll)
- }
- Action {
- shortcut: StandardKey.Undo
- onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Undo)
- }
- Action {
- shortcut: StandardKey.Redo
- onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Redo)
- }
- Action {
- shortcut: StandardKey.Back
- onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Back)
- }
- Action {
- shortcut: StandardKey.Forward
- onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Forward)
- }
- Action {
- shortcut: StandardKey.Find
- onTriggered: {
- if (!findBar.visible)
- findBar.visible = true;
- }
- }
- Action {
- shortcut: StandardKey.FindNext
- onTriggered: findBar.findNext()
- }
- Action {
- shortcut: StandardKey.FindPrevious
- onTriggered: findBar.findPrevious()
- }
-
- menuBar: ToolBar {
- id: navigationBar
- RowLayout {
- anchors.fill: parent
- ToolButton {
- enabled: win.currentWebView?.canGoBack || win.currentWebView?.canGoForward
- onClicked: historyMenu.open()
- text: qsTr("â–¼")
- Menu {
- id: historyMenu
- Instantiator {
- model: win.currentWebView?.history?.items
- MenuItem {
- required property var model
- text: model.title
- onTriggered: win.currentWebView.goBackOrForward(model.offset)
- checkable: !enabled
- checked: !enabled
- enabled: model.offset
- }
-
- onObjectAdded: function(index, object) {
- historyMenu.insertItem(index, object)
- }
- onObjectRemoved: function(index, object) {
- historyMenu.removeItem(object)
- }
- }
- }
- }
-
- ToolButton {
- id: backButton
- icon.source: "icons/3rdparty/go-previous.png"
- onClicked: win.currentWebView.goBack()
- enabled: win.currentWebView?.canGoBack ?? false
- activeFocusOnTab: !win.platformIsMac
- }
- ToolButton {
- id: forwardButton
- icon.source: "icons/3rdparty/go-next.png"
- onClicked: win.currentWebView.goForward()
- enabled: win.currentWebView?.canGoForward ?? false
- activeFocusOnTab: !win.platformIsMac
- }
- ToolButton {
- id: reloadButton
- icon.source: win.currentWebView?.loading
- ? "icons/3rdparty/process-stop.png"
- : "icons/3rdparty/view-refresh.png"
- onClicked: win.currentWebView?.loading ? win.currentWebView.stop() : win.currentWebView.reload()
- activeFocusOnTab: !win.platformIsMac
- }
- TextField {
- id: addressBar
- Image {
- anchors.verticalCenter: addressBar.verticalCenter;
- x: 5
- z: 2
- id: faviconImage
- width: 16; height: 16
- sourceSize: Qt.size(width, height)
- source: win.currentWebView?.icon ? win.currentWebView.icon : ''
- }
- MouseArea {
- id: textFieldMouseArea
- acceptedButtons: Qt.RightButton
- anchors.fill: parent
- onClicked: {
- var textSelectionStartPos = addressBar.selectionStart;
- var textSelectionEndPos = addressBar.selectionEnd;
- textFieldContextMenu.open();
- addressBar.select(textSelectionStartPos, textSelectionEndPos);
- }
- Menu {
- id: textFieldContextMenu
- x: textFieldMouseArea.mouseX
- y: textFieldMouseArea.mouseY
- MenuItem {
- text: qsTr("Cut")
- onTriggered: addressBar.cut()
- enabled: addressBar.selectedText.length > 0
- }
- MenuItem {
- text: qsTr("Copy")
- onTriggered: addressBar.copy()
- enabled: addressBar.selectedText.length > 0
- }
- MenuItem {
- text: qsTr("Paste")
- onTriggered: addressBar.paste()
- enabled: addressBar.canPaste
- }
- MenuItem {
- text: qsTr("Delete")
- onTriggered: addressBar.text = qsTr("")
- enabled: addressBar.selectedText.length > 0
- }
- MenuSeparator {}
- MenuItem {
- text: qsTr("Select All")
- onTriggered: addressBar.selectAll()
- enabled: addressBar.text.length > 0
- }
- }
- }
- leftPadding: 26
- focus: true
- Layout.fillWidth: true
- Binding on text {
- when: win.currentWebView
- value: win.currentWebView.url
- }
- onAccepted: win.currentWebView.url = Utils.fromUserInput(text)
- selectByMouse: true
- }
- ToolButton {
- id: settingsMenuButton
- text: qsTr("â‹®")
- onClicked: settingsMenu.open()
- Menu {
- id: settingsMenu
- y: settingsMenuButton.height
- MenuItem {
- id: loadImages
- text: "Autoload images"
- checkable: true
- checked: WebEngine.settings.autoLoadImages
- }
- MenuItem {
- id: javaScriptEnabled
- text: "JavaScript On"
- checkable: true
- checked: WebEngine.settings.javascriptEnabled
- }
- MenuItem {
- id: errorPageEnabled
- text: "ErrorPage On"
- checkable: true
- checked: WebEngine.settings.errorPageEnabled
- }
- MenuItem {
- id: pluginsEnabled
- text: "Plugins On"
- checkable: true
- checked: true
- }
- MenuItem {
- id: fullScreenSupportEnabled
- text: "FullScreen On"
- checkable: true
- checked: WebEngine.settings.fullScreenSupportEnabled
- }
- MenuItem {
- id: offTheRecordEnabled
- text: "Off The Record"
- checkable: true
- checked: win.currentWebView?.profile === (win.applicationRoot as ApplicationRoot).otrPrototype.instance()
- onToggled: function() {
- if (win.currentWebView) {
- win.currentWebView.profile = offTheRecordEnabled.checked
- ? (win.applicationRoot as ApplicationRoot).otrPrototype.instance()
- : (win.applicationRoot as ApplicationRoot).defaultProfilePrototype.instance();
- }
- }
- }
- MenuItem {
- id: httpDiskCacheEnabled
- text: "HTTP Disk Cache"
- checkable: !win.currentWebView?.profile?.offTheRecord ?? false
- checked: win.currentWebView?.profile.httpCacheType === WebEngineProfile.DiskHttpCache
- onToggled: function() {
- if (win.currentWebView) {
- win.currentWebView.profile.httpCacheType = httpDiskCacheEnabled.checked
- ? WebEngineProfile.DiskHttpCache
- : WebEngineProfile.MemoryHttpCache;
- }
- }
- }
- MenuItem {
- id: autoLoadIconsForPage
- text: "Icons On"
- checkable: true
- checked: WebEngine.settings.autoLoadIconsForPage
- }
- MenuItem {
- id: touchIconsEnabled
- text: "Touch Icons On"
- checkable: true
- checked: WebEngine.settings.touchIconsEnabled
- enabled: autoLoadIconsForPage.checked
- }
- MenuItem {
- id: webRTCPublicInterfacesOnly
- text: "WebRTC Public Interfaces Only"
- checkable: true
- checked: WebEngine.settings.webRTCPublicInterfacesOnly
- }
- MenuItem {
- id: devToolsEnabled
- text: "Open DevTools"
- checkable: true
- checked: false
- }
- MenuItem {
- id: pdfViewerEnabled
- text: "PDF Viewer Enabled"
- checkable: true
- checked: WebEngine.settings.pdfViewerEnabled
- }
- Menu {
- id: imageAnimationPolicy
- title: "Image Animation Policy"
-
- MenuItem {
- id: disableImageAnimation
- text: "Disable All Image Animation"
- checkable: true
- autoExclusive: true
- checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Disallow
- onTriggered: {
- appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.Disallow
- }
- }
-
- MenuItem {
- id: allowImageAnimation
- text: "Allow All Animated Images"
- checkable: true
- autoExclusive: true
- checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Allow
- onTriggered : {
- appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.Allow
- }
- }
-
- MenuItem {
- id: animateImageOnce
- text: "Animate Image Once"
- checkable: true
- autoExclusive: true
- checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.AnimateOnce
- onTriggered : {
- appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.AnimateOnce
- }
- }
- }
-
- MenuItem {
- id: javascriptCanAccessClipboard
- text: "JavaScript can access clipboard"
- checkable: true
- checked: WebEngine.settings.javascriptCanAccessClipboard
- }
- MenuItem {
- id: javascriptCanPaste
- text: "JavaScript can paste"
- checkable: true
- checked: WebEngine.settings.javascriptCanPaste
- }
- }
- }
- }
- ProgressBar {
- id: progressBar
- height: 3
- anchors {
- left: parent.left
- top: parent.bottom
- right: parent.right
- leftMargin: parent.anchors.leftMargin
- rightMargin: parent.anchors.rightMargin
- }
- background: Item {}
- z: -2
- from: 0
- to: 100
- value: (win.currentWebView?.loadProgress < 100) ? win.currentWebView.loadProgress : 0
- }
- }
-
- StackLayout {
- id: tabLayout
- currentIndex: tabBar.currentIndex
-
- anchors.top: tabBar.bottom
- anchors.bottom: devToolsView.top
- anchors.left: parent.left
- anchors.right: parent.right
- }
-
- Component {
- id: tabButtonComponent
-
- TabButton {
- id: tabButton
- property color frameColor: "#999999"
- property color fillColor: "#eeeeee"
- property color nonSelectedColor: "#dddddd"
- property string tabTitle: "New Tab"
-
- contentItem: Rectangle {
- id: tabRectangle
- color: tabButton.down ? tabButton.fillColor : tabButton.nonSelectedColor
- border.width: 1
- border.color: tabButton.frameColor
- implicitWidth: Math.max(text.width + 30, 80)
- implicitHeight: Math.max(text.height + 10, 20)
- Rectangle { height: 1 ; width: parent.width ; color: tabButton.frameColor}
- Rectangle { height: parent.height ; width: 1; color: tabButton.frameColor}
- Rectangle { x: parent.width - 2; height: parent.height ; width: 1; color: tabButton.frameColor}
- Text {
- id: text
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- anchors.leftMargin: 6
- text: tabButton.tabTitle
- elide: Text.ElideRight
- color: tabButton.down ? "black" : tabButton.frameColor
- width: parent.width - button.background.width
- }
- Button {
- id: button
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- anchors.rightMargin: 4
- height: 12
- background: Rectangle {
- implicitWidth: 12
- implicitHeight: 12
- color: button.hovered ? "#cccccc" : tabRectangle.color
- Text {text: "x"; anchors.centerIn: parent; color: "gray"}
- }
- onClicked: tabButton.closeTab()
- }
- }
-
- onClicked: addressBar.text = (tabLayout.itemAt(TabBar.index) as WebEngineView).url;
- function closeTab() {
- tabBar.tryCloseView(TabBar.index);
- }
- }
- }
-
- TabBar {
- id: tabBar
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- Component.onCompleted: createTab((win.applicationRoot as ApplicationRoot).defaultProfilePrototype.instance())
-
- function createTab(profile, focusOnNewTab = true, url = undefined) {
- var webview = tabComponent.createObject(tabLayout, {index: tabBar.count , profile: profile});
- var newTabButton = tabButtonComponent.createObject(tabBar, {tabTitle: Qt.binding(function () { return webview.title; })});
- tabBar.addItem(newTabButton);
- if (focusOnNewTab) {
- tabBar.setCurrentIndex(tabBar.count - 1);
- }
- if (url !== undefined) {
- webview.url = url;
- }
- return webview;
- }
-
- function tryCloseView(index) {
- tabLayout.children[index].triggerWebAction(WebEngineView.RequestClose);
- }
-
- function removeView(index) {
- if (tabBar.count > 1) {
- tabBar.removeItem(tabBar.itemAt(index));
- tabLayout.children[index].destroy();
- } else {
- win.lastTabClosing = true;
- win.close();
- }
- }
-
- Component {
- id: tabComponent
- WebEngineView {
- id: webEngineView
- property int index;
- focus: true
-
- onLinkHovered: function(hoveredUrl) {
- if (hoveredUrl === "")
- hideStatusText.start();
- else {
- statusText.text = hoveredUrl;
- statusBubble.visible = true;
- hideStatusText.stop();
- }
- }
-
- states: [
- State {
- name: "FullScreen"
- PropertyChanges {
- target: tabBar
- visible: false
- height: 0
- }
- PropertyChanges {
- target: navigationBar
- visible: false
- }
- }
- ]
- settings.localContentCanAccessRemoteUrls: true
- settings.localContentCanAccessFileUrls: false
- settings.autoLoadImages: appSettings.autoLoadImages
- settings.javascriptEnabled: appSettings.javaScriptEnabled
- settings.errorPageEnabled: appSettings.errorPageEnabled
- settings.pluginsEnabled: appSettings.pluginsEnabled
- settings.fullScreenSupportEnabled: appSettings.fullScreenSupportEnabled
- settings.autoLoadIconsForPage: appSettings.autoLoadIconsForPage
- settings.touchIconsEnabled: appSettings.touchIconsEnabled
- settings.webRTCPublicInterfacesOnly: appSettings.webRTCPublicInterfacesOnly
- settings.pdfViewerEnabled: appSettings.pdfViewerEnabled
- settings.imageAnimationPolicy: appSettings.imageAnimationPolicy
- settings.screenCaptureEnabled: true
- settings.javascriptCanAccessClipboard: appSettings.javascriptCanAccessClipboard
- settings.javascriptCanPaste: appSettings.javascriptCanPaste
-
- onWindowCloseRequested: function() {
- tabBar.removeView(webEngineView.index);
- }
-
- onCertificateError: function(error) {
- if (!error.isMainFrame) {
- error.rejectCertificate();
- return;
- }
-
- error.defer();
- sslDialog.enqueue(error);
- }
-
- onNewWindowRequested: function(request) {
- if (!request.userInitiated)
- console.warn("Blocked a popup window.");
- else if (request.destination === WebEngineNewWindowRequest.InNewTab) {
- var tab = tabBar.createTab(win.currentWebView.profile, true, request.requestedUrl);
- tab.acceptAsNewWindow(request);
- } else if (request.destination === WebEngineNewWindowRequest.InNewBackgroundTab) {
- var backgroundTab = tabBar.createTab(win.currentWebView.profile, false);
- backgroundTab.acceptAsNewWindow(request);
- } else if (request.destination === WebEngineNewWindowRequest.InNewDialog) {
- var dialog = (win.applicationRoot as ApplicationRoot).createDialog(win.currentWebView.profile);
- dialog.win.currentWebView.acceptAsNewWindow(request);
- } else {
- var window = (win.applicationRoot as ApplicationRoot).createWindow(win.currentWebView.profile);
- window.win.currentWebView.acceptAsNewWindow(request);
- }
- }
-
- onFullScreenRequested: function(request) {
- if (request.toggleOn) {
- webEngineView.state = "FullScreen";
- win.previousVisibility = win.visibility;
- win.showFullScreen();
- fullScreenNotification.show();
- } else {
- webEngineView.state = "";
- win.visibility = win.previousVisibility;
- fullScreenNotification.hide();
- }
- request.accept();
- }
-
- onRegisterProtocolHandlerRequested: function(request) {
- console.log("accepting registerProtocolHandler request for "
- + request.scheme + " from " + request.origin);
- request.accept();
- }
-
- onDesktopMediaRequested: function(request) {
- // select the primary screen
- request.selectScreen(request.screensModel.index(0, 0));
- }
-
- onRenderProcessTerminated: function(terminationStatus, exitCode) {
- var status = "";
- switch (terminationStatus) {
- case WebEngineView.NormalTerminationStatus:
- status = "(normal exit)";
- break;
- case WebEngineView.AbnormalTerminationStatus:
- status = "(abnormal exit)";
- break;
- case WebEngineView.CrashedTerminationStatus:
- status = "(crashed)";
- break;
- case WebEngineView.KilledTerminationStatus:
- status = "(killed)";
- break;
- }
-
- print("Render process exited with code " + exitCode + " " + status);
- reloadTimer.running = true;
- }
-
- onSelectClientCertificate: function(selection) {
- selection.certificates[0].select();
- }
-
- onFindTextFinished: function(result) {
- if (!findBar.visible)
- findBar.visible = true;
-
- findBar.numberOfMatches = result.numberOfMatches;
- findBar.activeMatch = result.activeMatch;
- }
-
- onLoadingChanged: function(loadRequest) {
- if (loadRequest.status === WebEngineView.LoadStartedStatus)
- findBar.reset();
- }
-
- onPermissionRequested: function(permission) {
- permissionDialog.permission = permission;
- permissionDialog.visible = true;
- }
- onWebAuthUxRequested: function(request) {
- webAuthDialog.init(request);
- }
-
- Timer {
- id: reloadTimer
- interval: 0
- running: false
- repeat: false
- onTriggered: win.currentWebView.reload()
- }
- }
- }
- }
- WebEngineView {
- id: devToolsView
- visible: devToolsEnabled.checked
- height: visible ? 400 : 0
- inspectedView: visible && tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- onNewWindowRequested: function(request) {
- var tab = tabBar.createTab(win.currentWebView.profile);
- request.openIn(tab);
- }
-
- Timer {
- id: hideTimer
- interval: 0
- running: false
- repeat: false
- onTriggered: devToolsEnabled.checked = false
- }
- onWindowCloseRequested: function() {
- // Delay hiding for keep the inspectedView set to receive the ACK message of close.
- hideTimer.running = true;
- }
- }
- Dialog {
- id: sslDialog
- anchors.centerIn: parent
- contentWidth: Math.max(mainTextForSSLDialog.width, detailedTextForSSLDialog.width)
- contentHeight: mainTextForSSLDialog.height + detailedTextForSSLDialog.height
- property var certErrors: []
- // fixme: icon!
- // icon: StandardIcon.Warning
- standardButtons: Dialog.No | Dialog.Yes
- title: "Server's certificate not trusted"
- contentItem: Item {
- Label {
- id: mainTextForSSLDialog
- text: "Do you wish to continue?"
- }
- Text {
- id: detailedTextForSSLDialog
- anchors.top: mainTextForSSLDialog.bottom
- text: "If you wish so, you may continue with an unverified certificate.\n" +
- "Accepting an unverified certificate means\n" +
- "you may not be connected with the host you tried to connect to.\n" +
- "Do you wish to override the security check and continue?"
- }
- }
-
- onAccepted: {
- certErrors.shift().acceptCertificate();
- presentError();
- }
- onRejected: reject()
-
- function reject(){
- certErrors.shift().rejectCertificate();
- presentError();
- }
- function enqueue(error){
- certErrors.push(error);
- presentError();
- }
- function presentError(){
- visible = certErrors.length > 0
- }
- }
- Dialog {
- id: permissionDialog
- anchors.centerIn: parent
- width: Math.min(win.width, win.height) / 3 * 2
- contentWidth: mainTextForPermissionDialog.width
- contentHeight: mainTextForPermissionDialog.height
- standardButtons: Dialog.No | Dialog.Yes
- title: "Permission Request"
-
- property var permission;
-
- contentItem: Item {
- Label {
- id: mainTextForPermissionDialog
- }
- }
-
- onAccepted: permission.grant()
- onRejected: permission.deny()
- onVisibleChanged: {
- if (visible) {
- mainTextForPermissionDialog.text = questionForPermissionType();
- width = contentWidth + 20;
- }
- }
-
- function questionForPermissionType() {
- var question = "Allow " + permission.origin + " to "
-
- switch (permission.permissionType) {
- case WebEnginePermission.PermissionType.Geolocation:
- question += "access your location information?";
- break;
- case WebEnginePermission.PermissionType.MediaAudioCapture:
- question += "access your microphone?";
- break;
- case WebEnginePermission.PermissionType.MediaVideoCapture:
- question += "access your webcam?";
- break;
- case WebEnginePermission.PermissionType.MediaAudioVideoCapture:
- question += "access your microphone and webcam?";
- break;
- case WebEnginePermission.PermissionType.MouseLock:
- question += "lock your mouse cursor?";
- break;
- case WebEnginePermission.PermissionType.DesktopVideoCapture:
- question += "capture video of your desktop?";
- break;
- case WebEnginePermission.PermissionType.DesktopAudioVideoCapture:
- question += "capture audio and video of your desktop?";
- break;
- case WebEnginePermission.PermissionType.Notifications:
- question += "show notification on your desktop?";
- break;
- case WebEnginePermission.PermissionType.ClipboardReadWrite:
- question += "read from and write to your clipboard?";
- break;
- case WebEnginePermission.PermissionType.LocalFontsAccess:
- question += "access the fonts stored on your machine?";
- break;
- default:
- question += "access unknown or unsupported permission type [" + permission.permissionType + "] ?";
- break;
- }
-
- return question;
- }
- }
-
- FullScreenNotification {
- id: fullScreenNotification
- }
-
- DownloadView {
- id: downloadView
- visible: false
- anchors.fill: parent
- }
-
- WebAuthDialog {
- id: webAuthDialog
- visible: false
- }
-
- MessageDialog {
- id: downloadAcceptDialog
- property var downloadRequest: downloadView.pendingDownloadRequest
- title: "Download requested"
- text: downloadRequest ? downloadRequest.suggestedFileName : ""
- buttons: Dialog.No | Dialog.Yes
- onAccepted: {
- downloadView.visible = true;
- downloadView.append(downloadRequest);
- downloadRequest.accept();
- }
- onRejected: {
- downloadRequest.cancel();
- }
- onButtonClicked: {
- visible = false;
- }
- visible: false
- }
-
- function onDownloadRequested(download) {
- downloadView.pendingDownloadRequest = download;
- downloadAcceptDialog.visible = true;
- }
-
- FindBar {
- id: findBar
- visible: false
- anchors.right: parent.right
- anchors.rightMargin: 10
- anchors.top: parent.top
-
- onFindNext: {
- if (text)
- win.currentWebView?.findText(text);
- else if (!visible)
- visible = true;
- }
- onFindPrevious: {
- if (text)
- win.currentWebView?.findText(text, WebEngineView.FindBackward);
- else if (!visible)
- visible = true;
- }
- }
-
-
- Rectangle {
- id: statusBubble
- color: "oldlace"
- property int padding: 8
- visible: false
-
- anchors.left: parent.left
- anchors.bottom: parent.bottom
- width: statusText.paintedWidth + padding
- height: statusText.paintedHeight + padding
-
- Text {
- id: statusText
- anchors.centerIn: statusBubble
- elide: Qt.ElideMiddle
-
- Timer {
- id: hideStatusText
- interval: 750
- onTriggered: {
- statusText.text = "";
- statusBubble.visible = false;
- }
- }
- }
- }
-
- onClosing: function(closeEvent) {
- if (lastTabClosing) {
- return;
- }
- closeEvent.accepted = false
- for (var i = 0; i < tabBar.count; i++) {
- tabBar.tryCloseView(i);
- }
- }
-}
+++ /dev/null
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import QtQuick.Controls.Fusion
-
-Rectangle {
- id: downloadView
- color: "lightgray"
- property var pendingDownloadRequest: null
-
- ListModel {
- id: downloadModel
- property var downloads: []
- }
-
- function append(download) {
- downloadModel.append(download);
- downloadModel.downloads.push(download);
- }
-
- Component {
- id: downloadItemDelegate
-
- Rectangle {
- id: downloadItem
- width: listView.width
- height: childrenRect.height
- anchors.margins: 10
- radius: 3
- color: "transparent"
- border.color: "black"
-
- required property int index
-
- Rectangle {
- id: progressBar
-
- property real progress: {
- let d = downloadModel.downloads[downloadItem.index]
- return d ? d.receivedBytes / d.totalBytes : 0
- }
-
- radius: 3
- color: width === listView.width ? "green" : "#2b74c7"
- width: listView.width * progress
- height: cancelButton.height
-
- Behavior on width {
- SmoothedAnimation { duration: 100 }
- }
- }
- Rectangle {
- anchors {
- left: parent.left
- right: parent.right
- leftMargin: 20
- }
- Label {
- id: label
- text: {
- let d = downloadModel.downloads[downloadItem.index]
- return d ? d.downloadDirectory + "/" + d.downloadFileName : qsTr("")
- }
- anchors {
- verticalCenter: cancelButton.verticalCenter
- left: parent.left
- right: cancelButton.left
- }
- }
- Button {
- id: cancelButton
- anchors.right: parent.right
- icon.source: "icons/3rdparty/process-stop.png"
- onClicked: {
- var download = downloadModel.downloads[downloadItem.index];
-
- download.cancel();
-
- downloadModel.downloads = downloadModel.downloads.filter(function (el) {
- return el.id !== download.id;
- });
- downloadModel.remove(downloadItem.index);
- }
- }
- }
- }
-
- }
- ListView {
- id: listView
- anchors {
- topMargin: 10
- top: parent.top
- bottom: parent.bottom
- horizontalCenter: parent.horizontalCenter
- }
- width: parent.width - 20
- spacing: 5
-
- model: downloadModel
- delegate: downloadItemDelegate
-
- Text {
- visible: !listView.count
- horizontalAlignment: Text.AlignHCenter
- height: 30
- anchors {
- top: parent.top
- left: parent.left
- right: parent.right
- }
- font.pixelSize: 20
- text: "No active downloads."
- }
-
- Rectangle {
- color: "gray"
- anchors {
- bottom: parent.bottom
- left: parent.left
- right: parent.right
- }
- height: 30
- Button {
- id: okButton
- text: "OK"
- anchors.centerIn: parent
- onClicked: {
- downloadView.visible = false;
- }
- }
- }
- }
-}
+++ /dev/null
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Controls.Fusion
-import QtQuick.Layouts
-
-Rectangle {
- id: root
-
- property int numberOfMatches: 0
- property int activeMatch: 0
- property alias text: findTextField.text
-
- function reset() {
- numberOfMatches = 0;
- activeMatch = 0;
- visible = false;
- }
-
- signal findNext()
- signal findPrevious()
-
- width: 250
- height: 35
- radius: 2
-
- border.width: 1
- border.color: "black"
- color: "white"
-
- onVisibleChanged: {
- if (visible)
- findTextField.forceActiveFocus();
- }
-
-
- RowLayout {
- anchors.fill: parent
- anchors.topMargin: 5
- anchors.bottomMargin: 5
- anchors.leftMargin: 10
- anchors.rightMargin: 10
-
- spacing: 5
-
- Rectangle {
- Layout.fillWidth: true
- Layout.fillHeight: true
-
- TextField {
- id: findTextField
- anchors.fill: parent
- color: "black"
- background: Rectangle {
- color: "transparent"
- }
-
- onAccepted: root.findNext()
- onTextChanged: root.findNext()
- onActiveFocusChanged: activeFocus ? selectAll() : deselect()
- }
- }
-
- Label {
- text: root.activeMatch + "/" + root.numberOfMatches
- visible: findTextField.text !== ""
- color: "black"
- }
-
- Rectangle {
- border.width: 1
- border.color: "#dddddd"
- Layout.preferredWidth: 2
- Layout.preferredHeight: parent.height
- }
-
- ToolButton {
- id: findBtnLeft
- text: "<"
- enabled: root.numberOfMatches > 0
- onClicked: root.findPrevious()
- contentItem: Text {
- color: "black"
- text: findBtnLeft.text
- }
- }
-
- ToolButton {
- id: findBtnRight
- text: ">"
- enabled: root.numberOfMatches > 0
- onClicked: root.findNext()
- contentItem: Text {
- color: "black"
- text: findBtnRight.text
- }
- }
-
- ToolButton {
- id: findBtnClose
- text: "x"
- onClicked: root.visible = false
- contentItem: Text {
- color: "black"
- text: findBtnClose.text
- }
- }
- }
-}
+++ /dev/null
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-
-Rectangle {
- id: fullScreenNotification
- width: 500
- height: 40
- color: "white"
- radius: 7
-
- visible: false
- opacity: 0
-
- function show() {
- visible = true;
- opacity = 1;
- reset.start();
- }
-
- function hide() {
- reset.stop();
- opacity = 0;
- }
-
- Behavior on opacity {
- NumberAnimation {
- duration: 750
- onStopped: {
- if (fullScreenNotification.opacity === 0)
- fullScreenNotification.visible = false;
- }
- }
- }
-
- Timer {
- id: reset
- interval: 5000
- onTriggered: fullScreenNotification.hide()
- }
-
- anchors.horizontalCenter: parent.horizontalCenter
- y: 125
-
- Text {
- id: message
- width: parent.width
-
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.verticalCenter: parent.verticalCenter
-
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
-
- wrapMode: Text.WordWrap
- elide: Text.ElideNone
- clip: true
-
- text: qsTr("You are now in fullscreen mode. Press ESC to quit!")
- }
-}
+++ /dev/null
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import QtWebEngine
-
-Dialog {
- id: webAuthDialog
- anchors.centerIn: parent
- width: Math.min(parent.parent.width, parent.parent.height) / 3 * 2
- contentWidth: verticalLayout.width +10;
- contentHeight: verticalLayout.height +10;
- standardButtons: Dialog.Cancel | Dialog.Apply
- title: "WebAuth Request"
-
- property var selectAccount;
- property var authrequest: null;
-
- Connections {
- id: webauthConnection
- ignoreUnknownSignals: true
- function onStateChanged(state) {
- webAuthDialog.setupUI(state);
- }
- }
-
- onApplied: {
- switch (webAuthDialog.authrequest.state) {
- case WebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
- webAuthDialog.authrequest.setPin(pinEdit.text);
- break;
- case WebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
- webAuthDialog.authrequest.setSelectedAccount(webAuthDialog.selectAccount);
- break;
- default:
- break;
- }
- }
-
- onRejected: {
- webAuthDialog.authrequest.cancel();
- }
-
- function init(request) {
- pinLabel.visible = false;
- pinEdit.visible = false;
- confirmPinLabel.visible = false;
- confirmPinEdit.visible = false;
- selectAccountModel.clear();
- webAuthDialog.authrequest = request;
- webauthConnection.target = request;
- setupUI(webAuthDialog.authrequest.state)
- webAuthDialog.visible = true;
- pinEntryError.visible = false;
- }
-
- function setupUI(state) {
- switch (state) {
- case WebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
- setupSelectAccountUI();
- break;
- case WebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
- setupCollectPin();
- break;
- case WebEngineWebAuthUxRequest.WebAuthUxState.FinishTokenCollection:
- setupFinishCollectToken();
- break;
- case WebEngineWebAuthUxRequest.WebAuthUxState.RequestFailed:
- setupErrorUI();
- break;
- case WebEngineWebAuthUxRequest.WebAuthUxState.Completed:
- webAuthDialog.close();
- break;
- }
- }
-
- ButtonGroup {
- id : selectAccount;
- exclusive: true;
- }
-
- ListModel {
- id: selectAccountModel
-
- }
- contentItem: Item {
- ColumnLayout {
- id : verticalLayout
- spacing : 10
-
- Label {
- id: heading
- text: "";
- }
-
- Label {
- id: description
- text: "";
- }
-
- Row {
- spacing : 10
- Label {
- id: pinLabel
- text: "PIN";
- }
- TextInput {
- id: pinEdit
- text: "EnterPin"
- enabled: true
- focus: true
- color: "white"
- layer.sourceRect: Qt.rect(0, 0, 20, 20)
- }
- }
-
- Row {
- spacing : 10
- Label {
- id: confirmPinLabel
- text: "Confirm PIN";
- }
- TextEdit {
- id: confirmPinEdit
- text: ""
- }
- }
-
- Label {
- id: pinEntryError
- text: "";
- }
-
- Repeater {
- id : selectAccountRepeater
- model: selectAccountModel
- Column {
- id: selectAccountRepeaterColumn
- required property string modelData
- spacing : 5
- RadioButton {
- text: selectAccountRepeaterColumn.modelData
- ButtonGroup.group : webAuthDialog.selectAccount;
- onClicked: function(){
- webAuthDialog.selectAccount = text;
- }
- }
- }
- }
- }
- }
-
- function setupSelectAccountUI() {
- webAuthDialog.selectAccount = "";
- heading.text = "Choose a passkey";
- description.text = "Which passkey do you want to use for " + webAuthDialog.authrequest.relyingPartyId;
-
- selectAccountModel.clear();
- var userNames = webAuthDialog.authrequest.userNames;
- for (var i = 0; i < userNames.length; i++) {
- selectAccountModel.append( {"name" : userNames[i]});
- }
- pinLabel.visible = false;
- pinEdit.visible = false;
- confirmPinLabel.visible = false;
- confirmPinEdit.visible = false;
- pinEntryError.visible = false;
- standardButton(Dialog.Apply).visible = true;
- standardButton(Dialog.Cancel).visible = true;
- standardButton(Dialog.Cancel).text ="Cancel"
- }
-
- function setupCollectPin() {
- var requestInfo = webAuthDialog.authrequest.pinRequest;
-
- pinEdit.clear();
-
- if (requestInfo.reason === WebEngineWebAuthUxRequest.PinEntryReason.Challenge) {
- heading.text = "PIN required";
- description.text = "Enter the PIN for your security key";
- pinLabel.visible = true;
- pinEdit.visible = true;
- confirmPinLabel.visible = false;
- confirmPinEdit.visible = false;
- } else if (requestInfo.reason === WebEngineWebAuthUxRequest.PinEntryReason.Set) {
- heading.text = "Set PIN ";
- description.text = "Set new PIN for your security key";
- pinLabel.visible = true;
- pinEdit.visible = true;
- confirmPinLabel.visible = true;
- confirmPinEdit.visible = true;
- }
- pinEntryError.text = getPINErrorDetails() + " " + requestInfo.remainingAttempts + " attempts reamining";
- pinEntryError.visible = true;
- selectAccountModel.clear();
- standardButton(Dialog.Cancel).visible = true;
- standardButton(Dialog.Cancel).text ="Cancel"
- standardButton(Dialog.Apply).visible = true;
- }
-
- function getPINErrorDetails() {
- var requestInfo = webAuthDialog.authrequest.pinRequest;
- switch (requestInfo.error) {
- case WebEngineWebAuthUxRequest.PinEntryError.NoError:
- return "";
- case WebEngineWebAuthUxRequest.PinEntryError.TooShort:
- return "Too short";
- case WebEngineWebAuthUxRequest.PinEntryError.InternalUvLocked:
- return "Internal Uv locked";
- case WebEngineWebAuthUxRequest.PinEntryError.WrongPin:
- return "Wrong PIN";
- case WebEngineWebAuthUxRequest.PinEntryError.InvalidCharacters:
- return "Invalid characters";
- case WebEngineWebAuthUxRequest.PinEntryError.SameAsCurrentPin:
- return "Same as current PIN";
- }
- }
-
- function getRequestFailureResaon() {
- var requestFailureReason = webAuthDialog.authrequest.requestFailureReason;
- switch (requestFailureReason) {
- case WebEngineWebAuthUxRequest.RequestFailureReason.Timeout:
- return " Request Timeout";
- case WebEngineWebAuthUxRequest.RequestFailureReason.KeyNotRegistered:
- return "Key not registered";
- case WebEngineWebAuthUxRequest.RequestFailureReason.KeyAlreadyRegistered:
- return "You already registered this device. You don't have to register it again\n"
- + "Try again with different key or device.";
- case WebEngineWebAuthUxRequest.RequestFailureReason.SoftPinBlock:
- return "The security key is locked because the wrong PIN was entered too many times.\n"
- + "To unlock it, remove and reinsert it.";
- case WebEngineWebAuthUxRequest.RequestFailureReason.HardPinBlock:
- return "The security key is locked because the wrong PIN was entered too many times.\n"
- + "You'll need to reset the security key.";
- case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorRemovedDuringPinEntry:
- return "Authenticator removed during verification. Please reinsert and try again";
- case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingResidentKeys:
- return "Authenticator doesn't have resident key support";
- case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingUserVerification:
- return "Authenticator missing user verification";
- case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingLargeBlob:
- return "Authenticator missing Large Blob support";
- case WebEngineWebAuthUxRequest.RequestFailureReason.NoCommonAlgorithms:
- return "No common Algorithms";
- case WebEngineWebAuthUxRequest.RequestFailureReason.StorageFull:
- return "Storage full";
- case WebEngineWebAuthUxRequest.RequestFailureReason.UserConsentDenied:
- return "User consent denied";
- case WebEngineWebAuthUxRequest.RequestFailureReason.WinUserCancelled:
- return "User cancelled request";
- }
- }
-
- function setupFinishCollectToken() {
- heading.text = "Use your security key with " + webAuthDialog.authrequest.relyingPartyId;
- description.text = "Touch your security key again to complete the request.";
- pinLabel.visible = false;
- pinEdit.visible = false;
- confirmPinLabel.visible = false;
- confirmPinEdit.visible = false;
- selectAccountModel.clear();
- pinEntryError.visible = false;
- standardButton(Dialog.Apply).visible = false;
- standardButton(Dialog.Cancel).visible = true;
- standardButton(Dialog.Cancel).text ="Cancel"
- }
-
- function setupErrorUI() {
- heading.text = "Something went wrong";
- description.text = getRequestFailureResaon();
- pinLabel.visible = false;
- pinEdit.visible = false;
- confirmPinLabel.visible = false;
- confirmPinEdit.visible = false;
- selectAccountModel.clear();
- pinEntryError.visible = false;
- standardButton(Dialog.Apply).visible = false;
- standardButton(Dialog.Cancel).visible = true;
- standardButton(Dialog.Cancel).text ="Close"
- }
-}
Nano Browser Example
====================
-A web browser implemented using the WebEngineView QML type.
+A web browser implemented using the `WebEngineView`_ QML type.
-.. image:: nanobrowser.png
- :width: 400
+.. image:: nanobrowser.webp
+ :width: 645
:alt: Nano Browser Screenshot
+
+.. _`WebEngineView`: https://doc.qt.io/qt-6/qml-qtwebengine-webengineview.html
--- /dev/null
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
+
+"""PySide6 WebEngine QtQuick 2 Example"""
+
+import sys
+from argparse import ArgumentParser, RawTextHelpFormatter
+from pathlib import Path
+
+from PySide6.QtCore import (QCoreApplication, QFileInfo, QMetaObject, QObject,
+ QUrl, Slot, Q_ARG)
+from PySide6.QtQml import QQmlApplicationEngine, QmlElement, QmlSingleton
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtWebEngineQuick import QtWebEngineQuick
+
+import rc_resources # noqa: F401
+
+
+# To be used on the @QmlElement decorator
+# (QML_IMPORT_MINOR_VERSION is optional)
+QML_IMPORT_NAME = "BrowserUtils"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+def url_from_user_input(user_input):
+ file_info = QFileInfo(user_input)
+ if file_info.exists():
+ return QUrl.fromLocalFile(file_info.absoluteFilePath())
+ return QUrl.fromUserInput(user_input)
+
+
+@QmlElement
+@QmlSingleton
+class Utils(QObject):
+
+ @Slot(str, result=QUrl)
+ def fromUserInput(self, user_input):
+ return url_from_user_input(user_input)
+
+
+if __name__ == '__main__':
+ QCoreApplication.setApplicationName("Quick Nano Browser")
+ QCoreApplication.setOrganizationName("QtProject")
+
+ QtWebEngineQuick.initialize()
+
+ argument_parser = ArgumentParser(description="Quick Nano Browser",
+ formatter_class=RawTextHelpFormatter)
+ argument_parser.add_argument("--single-process", "-s", action="store_true",
+ help="Run in single process mode (trouble shooting)")
+ argument_parser.add_argument("url", help="The URL to open",
+ nargs='?', type=str)
+ options = argument_parser.parse_args()
+
+ url = url_from_user_input(options.url) if options.url else QUrl("chrome://qt")
+
+ app_args = sys.argv
+ if options.single_process:
+ app_args.extend(["--webEngineArgs", "--single-process"])
+ app = QGuiApplication(app_args)
+ engine = QQmlApplicationEngine()
+ engine.addImportPath(Path(__file__).parent)
+ engine.loadFromModule("BrowserUtils", "ApplicationRoot")
+ if not engine.rootObjects():
+ sys.exit(-1)
+
+ QMetaObject.invokeMethod(engine.rootObjects()[0], "load", Q_ARG("QVariant", url))
+
+ exit_code = app.exec()
+ del engine
+ sys.exit(exit_code)
{
- "files": ["quicknanobrowser.py", "ApplicationRoot.qml",
- "BrowserDialog.qml", "BrowserWindow.qml", "DownloadView.qml",
- "FindBar.qml", "FullScreenNotification.qml", "WebAuthDialog.qml",
- "resources.qrc"]
+ "files": ["main.py", "BrowserUtils/ApplicationRoot.qml",
+ "BrowserUtils/BrowserDialog.qml", "BrowserUtils/BrowserWindow.qml",
+ "BrowserUtils/DownloadView.qml", "BrowserUtils/FindBar.qml",
+ "BrowserUtils/FullScreenNotification.qml", "BrowserUtils/WebAuthDialog.qml",
+ "BrowserUtils/qmldir", "resources.qrc"]
}
module BrowserUtils
+prefer :/qt/qml/BrowserUtils/
ApplicationRoot 254.0 ApplicationRoot.qml
BrowserDialog 254.0 BrowserDialog.qml
BrowserWindow 254.0 BrowserWindow.qml
+++ /dev/null
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-"""PySide6 WebEngine QtQuick 2 Example"""
-
-import os
-import sys
-from argparse import ArgumentParser, RawTextHelpFormatter
-from pathlib import Path
-
-from PySide6.QtCore import (QCoreApplication, QFileInfo, QMetaObject, QObject,
- QUrl, Slot, Q_ARG)
-from PySide6.QtQml import QQmlApplicationEngine, QmlElement, QmlSingleton
-from PySide6.QtGui import QGuiApplication
-from PySide6.QtWebEngineQuick import QtWebEngineQuick
-
-import rc_resources # noqa: F401
-
-
-# To be used on the @QmlElement decorator
-# (QML_IMPORT_MINOR_VERSION is optional)
-QML_IMPORT_NAME = "BrowserUtils"
-QML_IMPORT_MAJOR_VERSION = 1
-
-
-def url_from_user_input(user_input):
- file_info = QFileInfo(user_input)
- if file_info.exists():
- return QUrl.fromLocalFile(file_info.absoluteFilePath())
- return QUrl.fromUserInput(user_input)
-
-
-@QmlElement
-@QmlSingleton
-class Utils(QObject):
-
- @Slot(str, result=QUrl)
- def fromUserInput(self, user_input):
- return url_from_user_input(user_input)
-
-
-if __name__ == '__main__':
- QCoreApplication.setApplicationName("Quick Nano Browser")
- QCoreApplication.setOrganizationName("QtProject")
-
- QtWebEngineQuick.initialize()
-
- argument_parser = ArgumentParser(description="Quick Nano Browser",
- formatter_class=RawTextHelpFormatter)
- argument_parser.add_argument("--single-process", "-s", action="store_true",
- help="Run in single process mode (trouble shooting)")
- argument_parser.add_argument("url", help="The URL to open",
- nargs='?', type=str)
- options = argument_parser.parse_args()
-
- url = url_from_user_input(options.url) if options.url else QUrl("chrome://qt")
-
- app_args = sys.argv
- if options.single_process:
- app_args.extend(["--webEngineArgs", "--single-process"])
- app = QGuiApplication(app_args)
- engine = QQmlApplicationEngine()
- qml_file = os.fspath(Path(__file__).resolve().parent / 'ApplicationRoot.qml')
- engine.load(QUrl.fromLocalFile(qml_file))
- if not engine.rootObjects():
- sys.exit(-1)
-
- QMetaObject.invokeMethod(engine.rootObjects()[0], "load", Q_ARG("QVariant", url))
-
- exit_code = app.exec()
- del engine
- sys.exit(exit_code)
# Resource object code (Python 3)
# Created by: object code
-# Created by: The Resource Compiler for Qt version 6.10.0
+# Created by: The Resource Compiler for Qt version 6.10.2
# WARNING! All changes made in this file will be lost!
from PySide6 import QtCore
\x00o\xa6S\
\x00i\
\x00c\x00o\x00n\x00s\
+\x00\x08\
+\x08\xb6\x8e\xf9\
+\x003\
+\x00r\x00d\x00p\x00a\x00r\x00t\x00y\
\x00\x0b\
\x0c+\x1f\xc7\
\x00g\
qt_resource_struct = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x02\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00,\x00\x00\x00\x00\x00\x01\x00\x00\x03\xa6\
+\x00\x00\x00\x10\x00\x02\x00\x00\x00\x04\x00\x00\x00\x03\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00B\x00\x00\x00\x00\x00\x01\x00\x00\x03\xa6\
\x00\x00\x01\x975l\xc7\xfb\
-\x00\x00\x00R\x00\x00\x00\x00\x00\x01\x00\x00\x08\xfe\
+\x00\x00\x00h\x00\x00\x00\x00\x00\x01\x00\x00\x08\xfe\
\x00\x00\x01\x975l\xc7\xfb\
-\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x00&\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x975l\xc7\xfb\
-\x00\x00\x00x\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xfa\
+\x00\x00\x00\x8e\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xfa\
\x00\x00\x01\x975l\xc7\xfb\
"
<RCC>
- <qresource prefix="/icons">
- <file alias="go-next.png">icons/3rdparty/go-next.png</file>
- <file alias="go-previous.png">icons/3rdparty/go-previous.png</file>
- <file alias="process-stop.png">icons/3rdparty/process-stop.png</file>
- <file alias="view-refresh.png">icons/3rdparty/view-refresh.png</file>
+ <qresource>
+ <file>icons/3rdparty/go-next.png</file>
+ <file>icons/3rdparty/go-previous.png</file>
+ <file>icons/3rdparty/process-stop.png</file>
+ <file>icons/3rdparty/view-refresh.png</file>
</qresource>
</RCC>
@Slot()
def _zoom_in(self):
- tab = self.current_tab()
- if tab:
+ if tab := self.current_tab():
tab.setZoomFactor(tab.zoomFactor() + 0.1)
@Slot()
def _zoom_out(self):
- tab = self.current_tab()
- if tab:
+ if tab := self.current_tab():
tab.setZoomFactor(tab.zoomFactor() - 0.1)
@Slot()
def _reset_zoom(self):
- tab = self.current_tab()
- if tab:
+ if tab := self.current_tab():
tab.setZoomFactor(1)
@Slot()
self._progress_bar.setValue(0)
def handle_show_window_triggered(self):
- action = self.sender()
- if action:
+ if action := self.sender():
offset = action.data()
window = self._browser.windows()[offset]
window.activateWindow()
self.close_tab(i)
def close_tab(self, index):
- view = self.web_view(index)
- if view:
+ if view := self.web_view(index):
has_focus = view.hasFocus()
self.removeTab(index)
if has_focus and self.count() > 0:
view.deleteLater()
def clone_tab(self, index):
- view = self.web_view(index)
- if view:
+ if view := self.web_view(index):
tab = self.create_tab()
tab.setUrl(view.url())
def set_url(self, url):
- view = self.current_web_view()
- if view:
+ if view := self.current_web_view():
view.setUrl(url)
view.setFocus()
def trigger_web_page_action(self, action):
- web_view = self.current_web_view()
- if web_view:
+ if web_view := self.current_web_view():
web_view.triggerPageAction(action)
web_view.setFocus()
self.setCurrentIndex(next)
def reload_tab(self, index):
- view = self.web_view(index)
- if view:
+ if view := self.web_view(index):
view.reload()
return self._view
def handle_geometry_change_requested(self, newGeometry):
- window = self.windowHandle()
- if window:
+ if window := self.windowHandle():
self.setGeometry(newGeometry.marginsRemoved(window.frameMargins()))
self.show()
self._view.setFocus()
ToolButton {
id: backButton
icon.source: "qrc:/left-32.png"
+ icon.color: "transparent"
onClicked: webView.goBack()
enabled: webView.canGoBack
Layout.preferredWidth: navigationBar.height
ToolButton {
id: forwardButton
icon.source: "qrc:/right-32.png"
+ icon.color: "transparent"
onClicked: webView.goForward()
enabled: webView.canGoForward
Layout.preferredWidth: navigationBar.height
ToolButton {
id: reloadButton
icon.source: webView.loading ? "qrc:/stop-32.png" : "qrc:/refresh-32.png"
+ icon.color: "transparent"
onClicked: webView.loading ? webView.stop() : webView.reload()
Layout.preferredWidth: navigationBar.height
}
<file alias="stop-32.png">images/stop-32.png</file>
<file alias="refresh-32.png">images/refresh-32.png</file>
<file alias="right-32.png">images/right-32.png</file>
+ <file alias="settings-32.png">images/settings-32.png</file>
</qresource>
</RCC>
# Resource object code (Python 3)
# Created by: object code
-# Created by: The Resource Compiler for Qt version 6.7.2
+# Created by: The Resource Compiler for Qt version 6.10.2
# WARNING! All changes made in this file will be lost!
from PySide6 import QtCore
\xc1~\x98\xc8\xe5X\x00\xaa\x80\xa6|;$\x03p\x93\
\xdc|.\x1c\xe8\xc7\xe9\xbe\xdb_E\x87\x0e'\xe81\
\xfc\xef\x00\x00\x00\x00IEND\xaeB`\x82\
-\x00\x00\x04\xcf\
-\x00\
-\x00\x141x\xda\xc5X\xddS\xe36\x10\x7f\xf7_\xa1\
-\xe6\xe1&\xe9L\x1c>\xae\xd3\x99\xb4\xf4\x06B\xb9\xa3\
-\x93\x9b\x03\x02\xe5\xfa(l%VQ$#\xc9\x04z\
-\xbd\xff\xbd+\xc9vl\xd9\x0e\xa1\xf4\xa8\x9f\xa2\xdd\xf5\
-\xee\xea\xb7\x9f\xceh\x84&\x22}\x94t\x91h\xd4\x9f\
-\x0c\xd0\xde\xce\xee\x8f\xe82!\xe8\x5c\x03g\x99b\xfe\
-\x88\xa6:\x0e\x83\xd1\x08\xcd\xce\x8e?\x0f\xa74\x22\x5c\
-\x91\xe1iL\xb8\xa6sJ\xe4\x18\xe5\xb4\x0b2\x1f\x9e\
-\xeb!\xbc\xb6$2\xa2\x98\xa1O\x17\xe8hv<\xdc\
-\x1fN\x18\xce\x14\x09\x02\xbaL\x85\xd4\xa0\xfc<\xa3\xd1\
-\xadw\x0c'\x82k)\x98Z\xd3\xaf\xc9\xcd\xef\x94\xac\
-|\xc1)~\x14\x99VA\x10\x1c\xa6)\xa3\x11\xd6T\
-\xf0k\xcac\xb1B_\x02\x04\x0f\x8d\xc7he\x09\xf6\
-xO\x15\xbdad\x8c\xb4\xcc\x88\xa5<\x8c\x11\xe5T\
-\x83\x97\x9f\xed\xf9\xb1<\xffa\xcf+\x1a\xeb\xa4\xa4]\
-\x9b\x93\xa5'\xc4`U2>\xd8\xa3\xe5h\xaa\x8d\x85\
-\x95s9\xb4\xc7\xc0r\x96\x84gG\x18\x80\xba\x14\x82\
-\xc1\x8f\xdc\xc7\xc2O\x8e\xef\xe9\xc2^\x01x%\xe7B\
-\xac\xdc5+\xd2\xe6\xc1<J\x84T\xe1\x9c26F\
-)\x96\x10\x87\x9a\x80JqD\xf9b\x8cv\x82\x1a\xdd\
-\x1a\xcf\xb4\x16\xdc\xd3X\xf8q\x83\xa3['\xd0dG\
-\x82\x87Jd2\x82\x1b\xf6\xeed4\x1e12\xd7\xc3\
-\xfd\xbd0\xe5\x8b^C^\xf0\x09\x84\xe5\x96\xc4k<\
-\x16\xe2\x08\xf4\xf7\x07\x0dY\xc21\x84\xa6\x22\x19a\xfe\
-\xde\x0a7D\x1d a*\xc9\x9cHI\xe2k\x17\xa4\
-\x1a\x80a\xb2\x0eI\xf1|}&\x12s!WX\xc6\
-\xdb\x83a\x0b\xe8yh\x9c8\x1b[\x03\x92\xcb\xffo\
-\x98H\xc2\x04\xde\x0a\x92\xc2m#\x0f\x89\x88\xde\xe5 \
-)-\xd2\x02#T\x22G\xe6\x92\xa8\xe49\xd8\xad\xf5\
-\x16\x14\xa3\xb9?@k\x11\xe7l\x0b\xb6\xff\x11Z\xa7\
-\x9a,\xd1\x97.m?4\xc0%\x0f\xfa\x84\x12\x16\xb7\
-`\x9b\xeb0\xe5\x9c\xbf^\xf6(?\x04\x99dVK\
-\x93\xc9\xd3L\x7f$:\x11\xf1\x07\xca\xb5\x1aC\xa7\x0c\
-O\x97\xc9\x95d\x93\x04K\x1ci\x22\xd5'\xce\x1e\xd1\
-\xdf9\xe7\xcc\xba<\x15+\xe8\xd4X5\xcdi\xf0x\
-\x0d'\x18n\x09\xcca\x14\x91TW#\x03r\xe8\x00\
-e\x9a2\xe8OR,\xaf\x14\x91\xa7\xc6\xb7\xbe\xd1\xe7\
-E\xe3\x85\x90>\x95\xaf\xd0o\xdas\xd5]\xedN]\
-\xca~\xef\xbd\xe8\x0d6\xa5\x5cS\xb5y\x00\xc2\x0a\xe2\
-a\x04\x03\x8f\xea\x96dk\x91MhL:$\x9f\x06\
-\xb1\x88\x7f\xd8\x82\xa6\x05\xf4%\xf5\xad\x88\xd6PUj\
-\xfb\xa6W\xbc\xb1M\xed\xb6\x03Yh8\x96\x18\x121\
-\xb43\x17\xee\xdeo\xa5\xff\x82v\x06P\xf3;\xa8\x98\
-\xec9\xfd{\xb4;z\xfb\x04\x18\xafV\xfa\xbb;U\
-\xcb\x95\x9fgR,\xa0\xd1\xa9\xfa\xf8/\xd1Osv\
-\x9d\x93\xcf\xfa\x16\xf4\xcc\xf4-\xa6\x7fh\x0e\xcd4\x17\
-i)p# \xa6\xcb\x86\x88t\xabL.$\x1b7\
-/\x0c}\xc4rAy\xcd\x9c#\xb5k\xf4\xc4+\xb4\
-`S\x88\xf2\xcdj\xbfN\xfd\xcb\xf6\xb2\x94a\x0dC\
-y\x19\x0a\x85\x0e\x0e\x0eP\x0f\xf3X\x0a\x1a\xf7 !\
-\x86\xbb\x90\x11\xc3\xbd\xfakf\x97YH\x91q\xc0\xd6\
-E\xcc\xb3Vn\x84\x9e\xf6\xef\x8cv*T\x0f\xbdy\
-\xd3\xca\x83\xdc\x93\xda\xcbuS\xa6f\xdf\xf2\x02`\xd2\
-\xc1#\xdec\x96y#\xb2\xc8\x0c\xf4\xb3\x11\xafL\xb5\
-\x1a\xb3\xaa\xde]%\xcfKw\xbb\xa0\xad\x94]\xf9\x04\
-\xfe\xe6\xb8)\xec\x13\xc1\xb2%o]<\xa7\xf8\x86\xb0\
-\x96Tt\xfd\xb4\xf7\x1b\xbe\xc7\xb3H\xd2\xd4\x03\xa7\x8e\
-\xfb$!\xb0d\x8a\x87\x8e&\xf4g\xa9\xe4W\xb7\x04\
-\x15\xf2]V\xf3]\xa9\xb5\xf7\x98Wg\x1ak\x023\
-\x90/\xaas\xaa\x00(l\xd83\xdd'*_\x84\x5c\
-39`5\x91x\xb0\xe1^O\x803\x15\x11|\x0e\
-\xc1\x9a\x22\xf1\x82\xbc\x00\x1ff\xf4\xcc\x9c\x9a\xd7A\xa8\
-\xc5\xe27\xc2\xe8\x901\xf8|\x83=\x88 \x0c\x9b\x85\
-R/\xc0\x09\x1b]'\xa0\xea\xd0jz\x1d\xa8<\xa3\
-\xdf4\x95`\x1ak\xa8^\x04\x9f\x069Z\x0e\xb9\xab\
-\x8b\xa9zi~M\x9c\xee\x09\xe6\xee\x22\xe6J\xb0I\
-\xaaWL\xb8.\x17\xfe\x0d\xa6\xb5n\x99\xff\xa1\xe05\
-\xcc\xdc\x89\x92\x06\x8bV\xf9\x85\x7fU\xd9}\xbd\x0e\xea\
-\xad)\xb51\x5c\x88v\x0f\xea\xe2\x9f\x84\x9c\xe7m\x1e\
-\x82O\xddgN\x89\xd4<\xe3\x91YS\xfaf.\x5c\
-\x90\xbb\x8c(=\xf0\xe2H\xe7\xa8\xca\x0ea5\x11r\
-\xa6%\xe8i\xee\x8b\x90CJ0\xe2\x84:_\xfb)\
-hY\x84\xcc_C\x82\x1b\xb7!\xa8\xf0\x9b\x11\xdd\xdc\
-\xf2:\x9byX\x0d!\xda\xd0\x8c\xdfU\xc2\x8b\xec\xa8\
-\xbe\xe2\x91;\xd6,mh\x8b\x1d\xb6\xda\xda\xda\xb6\xd6\
-67\x97\x0e\x83~sx\xd6\xd5\xb6\xac\xc8Mw\xed\
-\xac\xa8m\x1c)J\xe8k\xf0\x0f\x96\x1c\x9e\xf1\
+\x00\x00\x17;\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x07\xc3zTXtRaw prof\
+ile type exif\x00\x00x\
+\xda\xadX[v\xec\xba\x0a\xfc\xd7(\xce\x10\xac\x07\x02\
+\x0d\x07=X\xeb\xce\xe0\x0e\xff\x14\xb2\xdd\xe9\xbcvr\
+\x92m\xa7-[\x96\x05\xa2\xa0@\x09\xeb\xff\xff\xb3\xf0\
+\x0f\x8e\xc4\xb5\x84B,\xb5\xd5z\xe0(\xad\xb4\xa4\xb8\
+\x91\xe3<t_\xe3Q\xf6u\x1f\xe9z\x85\xe7W\xfd\
+\xe1\xf1\x22\xa1+\xa3\xcd\xe7\xa3\xd4k\xfc\xdd\x1f\x1f\x13\
+\x9c\x8d\xe2\x8e\x9e&\x92q\xbd\xe8\xaf_\xb4r\xcd/\
+o&\xba\x04e\xd7\xc8\xb5\x98\xd7D\xed\x9a(\xa7\xf3\
+E\xbc&\xd0sYGm\xc2\xcfK\xe8\xebl\xe7\xbd\
+\x129\x7f\xc1/\x99\xcf\xa5\xdf\x93\xbc}.\x0c\xebM\
+BgNi\xe5\x98\x0f\x5cS.\xa7\x02\xd9\x7f)d\
+\xc5M\xc55\xe6\x8a\x81\x18\x84\xfb\xb4{J\xbe\x97\x0a\
+\x83|d\xa7\xe3I\xab\xf0\x16\x95\xc7]\xfc\xa4\xff\x0d\
+(\xb9\x9e\xfd\x01\x1d\xaf\x8dY\x1f\xed\x87\xfd\x91>6\
+~\xd8&~\x92\x9c\xc7C\xf2\xab~iG{\xbb\x9c\
+\xfbg6%\x98\xadsuZ*LZ\xafE\xddK\
+\xd9w\x18\xd8a\xf2\xbc?\xab8\x19?\xc2=\xef\xd3\
+EH\x80\xf7\x0e\xa03\x8fqt\x9c#\xb6\x98`q\
+\x8b%\xce\xa8\xd1\xe2\xda\xed\x88\x03*\x96\xb4\x12\xa3M\
+i\xa4\xbc\xfb$sji\xe4#\x00\xa7\xe2g\xb4\xc4\
+\xb9\xe5\x99\x05\x98\x0d\xc0\x9b\xd1\x9b\x1e\xba\xc4-\xb7m\
+q#\x0a\x04\xcf\x88\x91)b\xb2x\xc2\x9f\xfe\xce\xf9\
+\xe9Df\xee\xf21\x1e\xf2\xb0\x15\xf4J\xee\xa8P\xc3\
+\x91\xf3+F\x01\x90h\xb7\x1f\xd16\xf0}\xbe=\x1c\
+\xd7\x0c\x04i\x9bY\xb0@=\xfa9E\xa7x\xf9\x96\
+\xfbQ\xde@g\x0c$\xb4gXD\x9e\xd7\x040\x11\
+d\x13\x94\x89\x19\x08\x1c5f\x8a5\x1e\x9c\x12\xc7\x08\
+;\x0a\xf0QL$\x08\x9a\xd4\x01A$J\x13Z\xa6\
+\x92\x11-\x9c$\xb9l|\xc3q\x8fM\x94\xcenp\
+\x96\x87\x0f\xc2\x88\x01M\xcb\x0a\xac\x9c\xd8\xe0?\x5c\x04\
+>\xa4\x94\xa9\x10Q%&\xa1FZs-\x95j\xad\
+\x5c\x9d\xfc\x943\x17&\xae\xcc,\xdcX%K\x11\x92\
+*,\x12\xa4\x89\xb6\xd42\xc8\x91Zm\xdc\xa4\xb5\xa6\
+\x0a\xa1\x8a\x99\x15_+\x06\xa8\xf6\xd4s/\x9dz\xed\
+\xdc\xa5\xb7\xae\x03\xee3\xca\xa0Q\x07\x0f\x09\xa3\x0d\x9d\
+i\xe6\x09\x9e\x98u\xf2\x94\xd9\xa6\xae\xb8\xe0J\xab,\
+Zu\xf1\x92\xd5\x96\x1a\x5c\xcd\xb2\x15#\xab\xc6&\xd6\
+L\x1f\xa8\xc5p\xc2\xfa\xee\xfc>j\xf1F-m\xa4\
+| ?P\xc3\xa7\xcc\xf7\x14\xd1\xe9\x84\x1c3 \x96\
+J\x04\xe2\xec\x08\xc0\xa1\x93cvH,%\x05\x87\xce\
+1;ZBTP\x82\x96\xe4\xe0\xcc\xe8\x88\x01\xc1\xb2\
+b\x22\x8b\x0f\xec^\x90{\x85[(\xe5W\xb8\xa5\x1b\
+\xb9\xe0\xd0\xfd\x0d\xe4\x82C\xf7\x09r\xefq\xfb\x00\xb5\
+\xe9\xd9f\x1c9l\x84<\x0c\xdd\xa8GF\xf8a\xc0\
+\x12M\xa2\x9e\xd4\xben\xfb\x5c\xa9\x1f\x1a0\xffl\x04\
+\x85\xe7\x98\xda`l,m\x16;\xd8\x87\xcc\xd5\x1a\x89\
+M\xce\xd6\x95i0b\xd0:\x99\x80\xee\x18\xc9\xbec\
+\x19Um\xf5\xbc\x02lg\x06\xebCY\xdb\xac*\x89\
+eQ\xa45\xb8,\xcd>\x08\xac\x87\xd5\xda\xf4\x11#\
+\x83\x9b\xdbrP\xb8\x03\x9c=\xa4\xd9\x0c3\xb7Dr\
+t\x9e\x0a\x06\xb2\xe3Vv\xc4\xd5\xabF\xe8\xab\xd3`\
+`YE]\xef6;\x1d\xba\xd7\xc4\xf0\xac}\x07\xfe\
+\xd7p\xdf|\xdab\xcd\xea\xf6\xb0c~\xf2\xd0V\xeb\
+\x14\xe0\xa8\x04ua\x9ceqJ\x8f\x5c\x97P\xd2:\
+\xc90\xb6\x98vnm\x09[\x1d\xbe4\x98+z+\
+\x87+\xa66c\xe1I}\x04L\x02\xb70,S\x9c\
+]\x93`b\xa0\x8d\x95\xeb\x5c\x15\xc6\x81m\xe7\xb0\xc1\
+0M,\xd0\xb3[\xa1hT\x0d\xa9f\xc1\x1e\x0b\xce\
+\x91\xcd\xc2\xae<\x22\xdc\x19\xf2e\x8bw\xe1\x13\xc6\xdf\
+\xa2\xb7`&\x9a\xcfo\xa1\xda\xfd\xb6t \x02\xff\xd6\
+0l)d\xae<\x8c\xa5C\x5c\x1e\xb9\x8a\x1a',\
+\xac\x0b\xd2\xd2\xcb\xb3\x1c\x1f<\x9c\x0e\xd5\x03\x0c\xd6\xd1\
+\xb1\x0d\xf7'\x9b\x7f\xd5\x86\x9f~(\xb7N\xfdT%\
+\xfc^\x97\xb3\x0d\xbf\xd0\xe5\xb2\x8fq\xd3\xd1\x03\xc2\x1e\
+\x0d\x08y5\xdb\x0e\x8f\xe8\x85\xcf\x82\x13\xdcI\xdcW\
+\xba\xd66\xcb*\x1d\xfe\xb5P\x15\x92Q[d\xac\xb3\
+&\xb8\x11\xb9OL\x0e;\x96P\x9c\x18z\x9f\xe2\xcd\
+\xbdA\x85\xca\x84\xbbU\xcc\x1aW\xd9\xc1v\x86Zu\
+\xe8\xd4u\x82\xe4\x91\xf3\xe2\x84\xa5A\xd2\xdc\xda\xae\x0d\
+\xf9\x0f\x9f\x82vB\xd6\x1c\x1d\xfaZ\xcasP{\xc5\
+\x04\x97f\xf1R\x0ca\xe0D\x827l#>V\x06\
+\x02\x08\x17I\xf4'\x92\x10DG\xd1\xd1\xfc\xba\xa2\xb6\
+y\xac\x01N\xa9\x06q\xf0\xcft\xd2\x9aUP o\
+\xe3\xc3\x95\xe1\xd9\x08b\x18\x88\xeaL\xf0\xfb\xaa\x04\xaa\
+\x80\xabS\xce\xa9\xf5\xe2\x11\x0de\x9a\xebD^v?\
+Z\xaf\xbbA\xcd\x05\x8cm\xe0u\xd9\xf0\x1b\x1e\x84\xbf\
+\x8d\xfc\xaaM\xa5\x22'H\xedc\xe4\xb5\xbcX\x09.\
+@\xea\x96C^\xeb/\x10\x08D\xc6\x85\x9c\x02\x0e\x1c\
+\xd8<\xe5\xc9u\x1e\x88\xf3Y]8\xd2E%RP\
+(\x82_0\x06K\x92\x126a\xd6,\xc3zbZ\
+G\xca\xbc\xaa5'.gA\x10R\x96\xef\xe4\x80\xf0\
+\xddd\xf1\xa7\xb6a\x89!y\xb2\x00E\xa7\x85\x94\xe7\
+~)V\x18Uq\xe7d\xc3\x91\xf0QU\xfb\x14\xaf\
+G2\x10\xec\xa3\xf55\x1dBr\xfc\x1d\x10\xb8mx\
+\xb6\xcfo\xda\xf0\xf5@\x10\xb8E\x85\xdfd\xb4C\x17\
+(\x9e\x09\xae;\xc0\xb10\xbcY\x01\x105\x9co\xe1\
+H\xbe\xd1\x9a\xd8zU\xeb\x8c\xdc<K\xef{&&\
+d\xc3\xf7~\xf4\xb6\x0d_\x0d\xf8^\x1bK\x80\x9d\xf9\
+\xc5\xa4\xf0\xf9\x89\x84\xcd97D\x15J\x86y\xbc\x80\
+\x81\x0c\x0e\xb5\x9bUp\xca\xc0.\x08I\x1e\xe1\x99\xce\
+B! \xbb\x82 \xdf91\x8a\x18D\x99z\x81\xe3\
+yvc\x83B\xc0\x8e\xf5\x89)\xc3;\xdb\xf2\x98y\
+\x87\x11.\x9e}&\xb6E{~\xech\xde:\x10\xf6\
+Mv\x80\x15\x99Q\x8d \xdbO;\x1d\xa81P\xd0\
+\x1d\xe4\xc7\x0e\xe9\xb3(A\xbdQ2J\x00TX\xbb\
+\x04 B\x18O\xef\x13,\x0b\x08\xd6\x8e:\xdbz\xfe\
+\x0b\x0b;\xce\xa0\xfd\xc1\xc2\xc0\xe2\xd8{{p\xdav\
+\xed\x1e@p\xa8\x97\xfaBU\xd8,6\xc2:P\xbd\
+T9\xb5XD\x1dz\xa5Y\xe9,*\x90z\xa5t\
+wH\xd2\x08\x1d\xf1\xa9u|k\xa1]\xa5\xc3\xce\xfd\
+\xa8\x1b\xda\xcc\x9e0\xb4F\x99\xa3\x1a\x08\x16\x15\x1c\xcc\
+sQ\x00\xd2I\xfc\x90\xa8\xc2\x8f\x92\xa2\x9e\x15\xcbP\
+/\xd9\x18h0\x87z\xf4\x888\xae !\xc6\x1ft\
+s\xba\xc5\x89*\x7f8\xf77\xf0\xd6\x18^\xb8u\xd7\
+\xac\xef*\x09%\xce\x8a\xdd\x99\xaf\xf9\xbf\x8d\xc0|!\
+.\xec\xf7\xfc\xf9\x97\x0c\x10~\xf8!A\xa1\x8d\x85+\
+\x06\xf5V\xa0r\xea\x06\x9d~EI\xe1\xe7\x0a\xbd\x0e\
+\xff\x00\xf5N\x9dv\xea\xf8\xf3C\xb1\xe2{B\xd3\xb5\
+\xcb\xd2\xb2\x06\xc1\xa9\xc0\xb8;e7\x8f\x15\x14\xaf\xc3\
+Z\xdc\xc5\xeb\xdfK\xd9\xcd}\xc4\x0b\xd4\xb8\x0b\x02\x14\
+\xaf\x83P<\xdb\xf2\xba\xb8xU\xbbK\xf6-\xfb*\
+cPh\xb9\xf0]7\xdf\xc2\xb3\xbe\x0c\xb9\x94\xbb\xde\
+#\xd5!?2U6\x9a(\x8b\x1b|\x11\xae\xd6\xb5\
+y\xa6=\xcd\xc6\x1cG\xdd\xdet>\xc3\x02\xcf\x0f\xff\
+\x1d5B\xf9\x7fFtE\x8bp\xf6M^\x85\xef#\
+\x95t\xe4hS\x98\x1d\xa5\xc2\xe6\xb3\x042V\xc4*\
+6A]\x026w\xd3\xa0,R\x086\x8fp2\xcf\
+\xf9(\xbc\x11\x0bcu\xe4\xfbv\xd7\xf6\xfb_R\xc8\
+I\xd8\x8a\xca\xa4k\xe7\xb07\x0e0\x7f\x0b\xbes\xd8\
+[@P<\xaa\xc8\x15\xa1Z\xf1\x1d\x1bz=/\x14\
+\x88\xc5\xc0l\xd2\x1e\xdaF\xf2\x1dR\x5cP\x0c|\x81\
+\x82\x0d5E@\xf6M\xd8N\x9e,\x08\xee\xf9*\x83\
+\x019\xec\x14\x81\xd2\xbf\xe7\x93p\xb4\xfa4\x07=\x00\
+\x00\x01\x85iCCPICC profi\
+le\x00\x00x\x9c}\x91=H\xc3@\x18\x86\xdf\xa6\
+\xd6\x16\xa9(\xd8A\xc4!Cu\xb2 *\xe2\xa8U\
+(B\x85P+\xb4\xea`r\xe9\x1f4iHZ\x5c\
+\x1c\x05\xd7\x82\x83?\x8bU\x07\x17g]\x1d\x5c\x05A\
+\xf0\x07\xc4\xd1\xc9I\xd1EJ\xfc.)\xb4\x88\xf1\x8e\
+\xe3\x1e\xde\xfb\xde\x97\xbb\xef\x00\xa1Qf\x9a\xd55\x0e\
+hz\xd5L%\xe2b&\xbb*\x06_\xd1\x8d~\x84\
+h\x06df\x19s\x92\x94\x84\xe7\xf8\xba\x87\x8f\xefw\
+1\x9e\xe5]\xf7\xe7\xe8Us\x16\x03|\x22\xf1,3\
+\xcc*\xf1\x06\xf1\xf4f\xd5\xe0\xbcO\x1caEY%\
+>'\x1e3\xe9\x82\xc4\x8f\x5cW\x5c~\xe3\x5cpX\
+\xe0\x99\x113\x9d\x9a'\x8e\x10\x8b\x85\x0eV:\x98\x15\
+M\x8dx\x8a8\xaaj:\xe5\x0b\x19\x97U\xce[\x9c\
+\xb5r\x8d\xb5\xee\xc9_\x18\xce\xe9+\xcb\x5c\xa75\x8c\
+\x04\x16\xb1\x04\x09\x22\x14\xd4PB\x19U\xc4h\xd7I\
+\xb1\x90\xa2\xf3\xb8\x87\x7f\xc8\xf1K\xe4R\xc8U\x02#\
+\xc7\x02*\xd0 ;~\xf0?\xf8\xdd[+?9\xe1\
+&\x85\xe3@\xe0\xc5\xb6?F\x80\xe0.\xd0\xac\xdb\xf6\
+\xf7\xb1m7O\x00\xff3p\xa5\xb7\xfd\x95\x060\xf3\
+Iz\xbd\xadE\x8f\x80\xbem\xe0\xe2\xba\xad){\xc0\
+\xe5\x0e0\xf8d\xc8\xa6\xecH~ZB>\x0f\xbc\x9f\
+\xd17e\x81\x81[\xa0g\xcd\xed[\xeb\x1c\xa7\x0f@\
+\x9az\x95\xbc\x01\x0e\x0e\x81\xd1\x02e\xaf{\xbc;\xd4\
+\xd9\xb7\x7fkZ\xfd\xfb\x01\xda\xddrj\xb6}\xf5\xc0\
+\x00\x00\x0d\x1ciTXtXML:com.\
+adobe.xmp\x00\x00\x00\x00\x00<?\
+xpacket begin=\x22\xef\
+\xbb\xbf\x22 id=\x22W5M0MpCe\
+hiHzreSzNTczkc9d\
+\x22?>\x0a<x:xmpmeta x\
+mlns:x=\x22adobe:ns\
+:meta/\x22 x:xmptk=\
+\x22XMP Core 4.4.0-\
+Exiv2\x22>\x0a <rdf:RD\
+F xmlns:rdf=\x22htt\
+p://www.w3.org/1\
+999/02/22-rdf-sy\
+ntax-ns#\x22>\x0a <rd\
+f:Description rd\
+f:about=\x22\x22\x0a x\
+mlns:xmpMM=\x22http\
+://ns.adobe.com/\
+xap/1.0/mm/\x22\x0a \
+ xmlns:stEvt=\x22ht\
+tp://ns.adobe.co\
+m/xap/1.0/sType/\
+ResourceEvent#\x22\x0a\
+ xmlns:dc=\x22ht\
+tp://purl.org/dc\
+/elements/1.1/\x22\x0a\
+ xmlns:GIMP=\x22\
+http://www.gimp.\
+org/xmp/\x22\x0a xm\
+lns:tiff=\x22http:/\
+/ns.adobe.com/ti\
+ff/1.0/\x22\x0a xml\
+ns:xmp=\x22http://n\
+s.adobe.com/xap/\
+1.0/\x22\x0a xmpMM:D\
+ocumentID=\x22gimp:\
+docid:gimp:30715\
+408-8d32-4ae9-83\
+e2-d50b65d75fbc\x22\
+\x0a xmpMM:Instan\
+ceID=\x22xmp.iid:f2\
+632b44-ef61-4bb8\
+-8c52-9a17f84e9f\
+25\x22\x0a xmpMM:Ori\
+ginalDocumentID=\
+\x22xmp.did:91237de\
+5-bc1c-4a00-96b5\
+-5db5026b7be8\x22\x0a \
+ dc:Format=\x22ima\
+ge/png\x22\x0a GIMP:\
+API=\x222.0\x22\x0a GIM\
+P:Platform=\x22Mac \
+OS\x22\x0a GIMP:Time\
+Stamp=\x22165536800\
+5115995\x22\x0a GIMP\
+:Version=\x222.10.2\
+4\x22\x0a tiff:Orien\
+tation=\x221\x22\x0a xm\
+p:CreatorTool=\x22G\
+IMP 2.10\x22>\x0a <x\
+mpMM:History>\x0a \
+ <rdf:Seq>\x0a \
+ <rdf:li\x0a s\
+tEvt:action=\x22sav\
+ed\x22\x0a stEvt:\
+changed=\x22/\x22\x0a \
+ stEvt:instance\
+ID=\x22xmp.iid:182d\
+9bb3-865a-4e04-b\
+6ed-1e094e3b47d4\
+\x22\x0a stEvt:so\
+ftwareAgent=\x22Gim\
+p 2.10 (Mac OS)\x22\
+\x0a stEvt:whe\
+n=\x222022-06-16T10\
+:26:45+02:00\x22/>\x0a\
+ </rdf:Seq>\x0a \
+ </xmpMM:Histor\
+y>\x0a </rdf:Descr\
+iption>\x0a </rdf:R\
+DF>\x0a</x:xmpmeta>\
+\x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a\
+ \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \
+\x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a\
+ \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+\x0a<?xpacket end=\x22\
+w\x22?>=L\x95\x03\x00\x00\x00\x06bKGD\
+\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\x09pH\
+Ys\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\
+\x00\x00\x07tIME\x07\xe6\x06\x10\x08\x1a-(q\
+\x07\x16\x00\x00\x00@IDATX\xc3\xed\xd4\xb1\x0d\
+\x00 \x0c\x03\xc1\xc0\xc8\x19$+\xc3\x00\xd0\x22\x8a\xdc\
+\x97ilE\xf2G\xa0;\xe3v\xcc\xcc\xf5\x22\xac\xaa\
+\x8e\xbc\xf9\xfb\x03\x0aL3\x00\x11\xf1\x00\x11\x81\x88\x88\
+H\x013@{6\x18\xac\x0c\x1c\xa5\x14\xd1\xbc\x00\x00\
+\x00\x00IEND\xaeB`\x82\
+\x00\x00\x04\xba\
+(\
+\xb5/\xfd`]\x13\x85%\x00Vp\x8c%\xf0vt\
+\x94\xf0Z\x1e0\x91V\x10R\x93\xedo\xfe\x84\xc0v\
+>6\xd31Us\xf6\x80*\x80\x81\x97l\x22\xe3\xc0\
+E\x05\x84\x00\x7f\x00\x86\x00\xb0\x9b#%w\x10\x1a+\
+\xf6D\xdb\xef\xab\xe6\x9f\x92\xc2\xce\xc6\x1d\xd9R\xa26\
+3\xf39\xc9\xad)^\x0f\xee}\xf7\x16f\xd9\xc3\xf3\
+<\xf1\xb5.q\x99M\xb7\xa3\xa3\x1e\x11\xb6?\xf7\x14\
+\x85\xbc\xd56\xde;\xc3\xf6y4j\x0eN\x90\x01\x14\
+\xb1\x81HMFB\xb7\xcf\x15\x83~\x03\xaaT\x93\x92\
+\xebV\xfb\xac\x94\x05\xa4\x91\xf5m\x06\xfa\x88\xd0\xd8<\
+\x11\xf5\x99,a\x97a\xbf\x91zqd\x02\x01\xa6 \
+/\x93%\xff\xb2\xd6K\xb2\x9er` \xf1y+\xa8\
+\xbe^1^\xcb\x18\xbav\x92\x14\xd4\x9dx\x12E8\
+IL\xdf\x22/\x96+\xaaW\x9aY\x7ftH\x8b\xd7\
+5T\x9f\xa0\xdcm\xdeU\x8a^\xd1\xf5\xd4zP\xea\
+\xae\x95z\x16Q\xb0{\xb0\xe3X/\x16\x88\xb6(\x85\
+\x13wd\xe7\xef\x0d\xfbU,\xce\xa2\x02\x03\xe8+\x85\
+\x03\xd2s\x91JJ\xfa\x5c\xeb\x18q\xbf\x90\xe7:\xdc\
+\xff\xda\x83I\xc9\xe2}.\x8b\xcf\xea.K\x09\xc2\xad\
+\xa2\xe8}=v[k\xac\x1dq\x1c\x0b Q\xac\xb5\
+[\x05PJI\x00\xa5CV\xa8m\xa7\xd8U\x92\x81\
+\x81!cG\x98w\xf5\x0ef&\x06\x12\xaf\xe5\x0e\x03\
+\xeb\xff\xeed\x1de1\xaa[\xed\x1a\x18\x82\x83\x01\x82\
+\x14HL:\xe4\x05\x93\x0d\x96-r\xdd\x9cGD\x8b\
+\xf7_c\xa4\xd4^\x17\xcb/\x86\xf5\x8b.\x95\xa6\xa8\
+X\x9c#\x8a\xb4e\xb1p\x8d\x1c\xac\xbd\x8a\x22\xdc\xd7\
+\x92!)\xfdL\xdf{\xf8\xe8\x9e\xeb\xd6\x19\x0d\x94,\
+\x81\x9c\x11]\xff9w\xd7\x04\x9dsa\x16\xd1o\xa4\
+\x14O\xcacN:d\xf4\x0a\xb9\xb9\x1f\x05\x85\x1d\xb9\
+\x9f\xff\xc6\xdb]\xa3\xac\xf6\xf2Q5\x11\xd5@\x0b\xf7\
+\xfec\xad\xcapC\xb7\xbf\xbc)A\xd7<\xa0s\xd8\
+MS\xcch8\xec^>\xebs\xbf\x87\x09\x17\xa3\xbe\
+\xe7R\xe1V\xb95\x84\x0f\x1b\xa8\x96\xf3\xfe\x98\xd0\xbd\
+\xce\x5c(}\x91\xc5\xee\x9a\xf4\xddoT\x02\x84\xa4\xf5\
+\x9a\xb2\xf5\xb7q#\xee\xa2\x02\xb5\xe32\x8ci\xb0\xa7\
+g!5\xe8F\xfa\x9dw\x0b\x08\xc8J\xec\x81\x09\xa8\
+\xd1%B32#\x22)HRH\x07!\x08\x82p\
+,\xea\xbc:\x0f\xa2h@Jq\x18\x870 \xc2\x8c\
+\xa2D\xc0\xc8\xc8\x042\xa1\x88L\xaa\xa0\xb0\xd8FS\
+\xc0\xe8\xc8\xb0\xcaOr\xe5\xd4`*\xc6=\xed\x0c\x86\
+O\xb1d\xd5\xe9\x1b\x88\x0a\xea\xafqws\xddl\xc4\
+\xc5\x1c\xc8\xbcb,y\xe8\x84,=\xe9\xf6#\xa1\xfb\
+NI*\x8c!9\xd7a@\x85fD\xc2\x16\x8a\x10\
+\xc8x\x99\xbd3@\x0b\xd5\xa8S\xd7>\xe8\xf8\x1a\xe7\
+=\xc1\xba\x00\xc7\xda\xf8\xc2\xb54,\xaax\xe8\xf2n\
+;\xe2\x8e&\xbfeY1}\xd2\x95\xec\x1aT\x81$\
+\xa1\xadE\x8d9\xe9w\xcb\xea\x98B\x8f\xf2\x8a\xb9\xbe\
+:\xe1\xc5\x05?\x5c(4\x9f\x1c\xf2\x1e\x87%Vr\
+\xd6\x09\xbe\xd6f\x94P\xad\xbcX8\x84\x18\xc1\x15$\
+\xaa\xd9\xca\x9b\xdb\x03a\xbeq\x017\x04r\x92\x19\x91\
+\xa4\x92dU\x11\xcf\xfb\x8fK\x95\x1e9\xfd\xa9\x82L\
+\x82\x87\xd4YD\x1c\x85\xb9\xfe\xd3c\x16H$S\xc9\
+\xc6\xf1<\xfa\xd2P?{\xed\xea\x13\xad\xac\xa5\xf5\x17\
+\xdecS\x8aG'\xf2\x01M\xd4\x03e\x81\x0e\x19\x0d\
+\xea^\x05#\x122\x907>J\xaa\xde\x08\xac:s\
+\xcf2\x88\xc3\xf9Az\x15\xcb\x83Z\x96\xa6\xb0b\xd3\
+\xac\x9e\xb9\x06M\x89\x9f\xc548\xad\xef)\x89\xad\x88\
+e+\xc0\xbce{Q\x14\x05\x1a\xb6>\xcc\xaa&\x15\
+\xd4\x05\x06\xbe\x7f\xd0\xf9\xbf\x91\x82dj\x83Z\xecY\
+t\x02I\x12 \x94 \xc4\xd7\x1fh\xfaO-\x84\xe8\
+\xc2]\x09\xa0\xcd\xb2\xfc\x8b\xf5\x0e\xb8`\xc0\x04oz\
+\x0b\xd5\xe9d\xea*j@\xa7\x92\x16u\xd1\x00\xbe\x22\
+e\x93\x16\xdc\xf9\xe4\xac\xc3\x18\xe1\x84\x93\x87\xae\xd5N\
+.\xbc\xc5\x11\xca\xec\xa6\xb0V\x08#\x1bL\xb7\xe2s\
+\xeeW\x859?\xf8g\xa9\x8fk\x80\x929\xdb\xec\x84\
+\xea5p5)\x03[i\x1a\xb9\x00\x81\x02\xe7\xa3\xe2\
+\xa2\x22\x1ev\x84\xe7lA\x89\x92Z\x9f\x19\xd8\xc3|\
+aXp\x01@\x1a\xa7\xb3\xaa\x8d\xba\xc8R\xc0\x0c\xa7\
+\x9b\x9e\x80H\xd8 i\xd3>\x9f\x0b\x22\x10j\xe7\x98\
+#\x03\xaa\x13\x1a\xdcd\x7f\xc3\x95*m\xdc\x04>\x8f\
+8\x09\xc0\x5c\xd7\x9e\xff\xa5D\xc3\xdf=\x9c \x8c\xd5\
+\x0c\xc1\xfa?\xd9\xf4q\xbe\x16\xe9AJ,>\xb7\x8a\
+\x02 \xae\xf5O\xc4\xf5f\xc4\x00`\x8c\xec\xe1?$\
+\x0d\x91\xcdUt\xcf\xcc\xdb\x97\x85S\xb2\xac\xd3lJ\
+N^\xc3\x89\x86b\xb1r\xfbDq\x9e\xc1\xd9\xd7\xc2\
+eW\xa0Zp\xd2\xf2\xf6\x0f\
\x00\x00\x036\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x80-\xa7\
\x00l\
\x00e\x00f\x00t\x00-\x003\x002\x00.\x00p\x00n\x00g\
+\x00\x0f\
+\x07\x04,\xa7\
+\x00s\
+\x00e\x00t\x00t\x00i\x00n\x00g\x00s\x00-\x003\x002\x00.\x00p\x00n\x00g\
\x00\x08\
\x08\x01Z\x5c\
\x00m\
"
qt_resource_struct = b"\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x06\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00>\x00\x00\x00\x00\x00\x01\x00\x00\x07\x9c\
-\x00\x00\x01\x91\x08\xc1\x9ai\
+\x00\x00\x01\x975l\xc7\xfe\
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01\x91\x08\xc1\x9ai\
+\x00\x00\x01\x975l\xc7\xfe\
+\x00\x00\x00Z\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xdf\
+\x00\x00\x01\x9b\xc6\x0ai\xb3\
\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x01\x00\x00\x02\x83\
-\x00\x00\x01\x91\x08\xc1\x9ai\
-\x00\x00\x00Z\x00\x01\x00\x00\x00\x01\x00\x00\x0a\xdf\
-\x00\x00\x01\x91\x08\xc1\x9ai\
-\x00\x00\x00p\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xb2\
-\x00\x00\x01\x91\x08\xc1\x9ai\
+\x00\x00\x01\x975l\xc7\xfe\
+\x00\x00\x00~\x00\x04\x00\x00\x00\x01\x00\x00\x22\x1e\
+\x00\x00\x01\x9b\xc6\x06\x8a\x02\
+\x00\x00\x00\x94\x00\x00\x00\x00\x00\x01\x00\x00&\xdc\
+\x00\x00\x01\x975l\xc7\xfe\
"
def qInitResources():
from PySide6.QtCore import (QDir, QPoint, QRect, QStandardPaths, Qt, QTimer,
Slot)
-from PySide6.QtGui import QGuiApplication, QImageWriter
+from PySide6.QtGui import QImageWriter
from PySide6.QtWidgets import (QApplication, QCheckBox, QDialog, QFileDialog,
- QGridLayout, QGroupBox, QHBoxLayout, QLabel,
+ QFrame, QGridLayout, QGroupBox, QHBoxLayout, QLabel,
QMessageBox, QPushButton, QSizePolicy, QSpinBox,
QVBoxLayout, QWidget)
self.screenshot_label.setMinimumSize(
screen_geometry.width() / 8, screen_geometry.height() / 8
)
+ self.screenshot_label.setFrameShape(QFrame.Shape.Box)
main_layout = QVBoxLayout(self)
main_layout.addWidget(self.screenshot_label)
self.new_screenshot_button = QPushButton("New Screenshot", self)
self.new_screenshot_button.clicked.connect(self.new_screenshot)
buttons_layout.addWidget(self.new_screenshot_button)
- save_screenshot_button = QPushButton("Save Screenshot", self)
- save_screenshot_button.clicked.connect(self.save_screenshot)
- buttons_layout.addWidget(save_screenshot_button)
+ self.save_screenshot_button = QPushButton("Save Screenshot", self)
+ self.save_screenshot_button.clicked.connect(self.save_screenshot)
+ buttons_layout.addWidget(self.save_screenshot_button)
quit_screenshot_button = QPushButton("Quit", self)
quit_screenshot_button.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_Q)
quit_screenshot_button.clicked.connect(self.close)
self.resize(300, 200)
def resizeEvent(self, event):
- scaled_size = self.original_pixmap.size()
- scaled_size.scale(self.screenshot_label.size(), Qt.AspectRatioMode.KeepAspectRatio)
- if scaled_size != self.screenshot_label.pixmap().size():
- self.update_screenshot_label()
+ if not self.original_pixmap.isNull():
+ scaled_size = self.original_pixmap.size()
+ scaled_size.scale(self.screenshot_label.size(), Qt.AspectRatioMode.KeepAspectRatio)
+ if scaled_size != self.screenshot_label.pixmap().size():
+ self.update_screenshot_label()
@Slot()
def new_screenshot(self):
)
def shoot_screen(self):
- screen = QGuiApplication.primaryScreen()
- window = self.windowHandle()
- if window:
- screen = window.screen()
- if not screen:
- return
-
if self.delay_spinbox.value() != 0:
QApplication.beep()
- self.original_pixmap = screen.grabWindow(0)
+ self.original_pixmap = self.screen().grabWindow(0)
self.update_screenshot_label()
self.new_screenshot_button.setDisabled(False)
self.hide_this_window_checkbox.setDisabled(False)
def update_screenshot_label(self):
- self.screenshot_label.setPixmap(
- self.original_pixmap.scaled(
- self.screenshot_label.size(),
- Qt.AspectRatioMode.KeepAspectRatio,
- Qt.TransformationMode.SmoothTransformation,
+ if self.original_pixmap.isNull():
+ self.save_screenshot_button.setEnabled(False)
+ self.screenshot_label.setText(f'Grabbing "{self.screen().name()}" failed.')
+ else:
+ self.save_screenshot_button.setEnabled(True)
+ self.screenshot_label.setPixmap(
+ self.original_pixmap.scaled(
+ self.screenshot_label.size(),
+ Qt.AspectRatioMode.KeepAspectRatio,
+ Qt.TransformationMode.SmoothTransformation,
+ )
)
- )
if __name__ == "__main__":
return class_str
module = class_str[0:dot]
class_name = class_str[dot + 1:]
- class_list = module_imports.get(module)
- if class_list:
+ if class_list := module_imports.get(module):
if class_name not in class_list:
class_list.append(class_name)
else:
if base_class:
base_class = add_import(base_class)
- signals = self.field('signals')
- if signals:
+ if signals := self.field('signals'):
add_import('PySide6.QtCore.Signal')
property_types = []
if property_names:
add_import('PySide6.QtCore.Property')
- signals = self.field('signals')
- if signals:
+ if signals := self.field('signals'):
add_import('PySide6.QtCore.Signal')
property_types = []
if base_class:
block += f'({base_class})'
block += ':\n'
- description = self.field('description')
- if description:
+ if description := self.field('description'):
block += f' """{description}"""\n'
if signals:
@Slot()
def _add(self):
- new_item = self._create_new_item()
- if new_item:
+ if new_item := self._create_new_item():
self._list.addItem(new_item)
def _create_new_item(self):
options_value = self._font_options.value()
options = QFontDialog.FontDialogOptions(options_value)
- description = self._font_label.text()
default_font = QFont()
- if description:
+ if description := self._font_label.text():
default_font.fromString(description)
ok, font = QFontDialog.getFont(default_font, self, "Select Font", options)
src_dir = Path(__file__).resolve().parent
try:
- translation = gettext.translation('example', localedir=src_dir / 'locales')
- if translation:
+ if translation := gettext.translation('example', localedir=src_dir / 'locales'):
translation.install()
_ = translation.gettext
ngettext = translation.ngettext
model: QAbstractItemModel = self.view.model()
column: int = self.view.selectionModel().currentIndex().column()
- changed: bool = model.insertColumn(column + 1)
- if changed:
+ if model.insertColumn(column + 1):
model.setHeaderData(column + 1, Qt.Orientation.Horizontal, "[No header]",
Qt.ItemDataRole.EditRole)
if not parent_item:
return QModelIndex()
- child_item: TreeItem = parent_item.child(row)
- if child_item:
+ if child_item := parent_item.child(row):
return self.createIndex(row, column, child_item)
return QModelIndex()
if not index.isValid():
return QModelIndex()
- child_item: TreeItem = self.get_item(index)
- if child_item:
+ if child_item := self.get_item(index):
parent_item: TreeItem = child_item.parent()
else:
parent_item = None
col_first = self._table.column(first)
col_last = self._table.column(last)
- current = self._table.currentItem()
-
- if current:
+ if current := self._table.currentItem():
row_cur = self._table.row(current)
col_cur = self._table.column(current)
cell2 = "C2"
out = "C3"
- current = self._table.currentItem()
- if current:
+ if self._table.currentItem():
out = SpreadSheetItem.encode_pos(self._table.currentRow(), self._table.currentColumn())
if self.run_input_dialog(title, "Cell 1", "Cell 2", op, "Output to:", cell1, cell2, out):
def set_model_data(self, editor: QWidget,
model: QAbstractItemModel, index: QModelIndex) -> None:
- edit = QLineEdit(editor)
- if edit:
+ if edit := QLineEdit(editor):
model.setData(index, edit.text())
return
- dateEditor = QDateTimeEdit(editor)
- if dateEditor:
+ if dateEditor := QDateTimeEdit(editor):
model.setData(index, dateEditor.date().toString("dd/M/yyyy"))
def open(self):
file_name, _ = QFileDialog.getOpenFileName(self)
if file_name:
- existing = self.find_mdi_child(file_name)
- if existing:
+ if existing := self.find_mdi_child(file_name):
self._mdi_area.setActiveSubWindow(existing)
else:
self.load(file_name)
settings.setValue('geometry', self.saveGeometry())
def active_mdi_child(self):
- active_sub_window = self._mdi_area.activeSubWindow()
- if active_sub_window:
+ if active_sub_window := self._mdi_area.activeSubWindow():
return active_sub_window.widget()
return None
@Slot()
def on_color_clicked(self):
-
- color = QColorDialog.getColor(self.color, self)
-
- if color:
+ if color := QColorDialog.getColor(self.color, self):
self.set_color(color)
def set_color(self, color: QColor = Qt.GlobalColor.black):
self._action_paste.setShortcut(QKeySequence.StandardKey.Paste)
tb.addAction(self._action_paste)
- md = QGuiApplication.clipboard().mimeData()
- if md:
+ if md := QGuiApplication.clipboard().mimeData():
self._action_paste.setEnabled(md.hasText())
def setup_text_actions(self):
@Slot()
def cursor_position_changed(self):
self.alignment_changed(self._text_edit.alignment())
- list = self._text_edit.textCursor().currentList()
- if list:
- style = list.format().style()
+ if current_list := self._text_edit.textCursor().currentList():
+ style = current_list.format().style()
if style == QTextListFormat.ListDisc:
self._combo_style.setCurrentIndex(1)
elif style == QTextListFormat.ListCircle:
# plugins to be removed from the --include-qt-plugins option because these plugins
# don't exist in site-package under PySide6/Qt/plugins
-PLUGINS_TO_REMOVE = ["accessiblebridge", "platforms/darwin", "networkaccess", "scenegraph"]
+PLUGINS_TO_REMOVE = ["accessiblebridge", "platforms/darwin", "networkaccess",
+ "scenegraph", "wayland-inputdevice-integration"]
def get_all_pyside_modules():
import sys
import os
from pathlib import Path
-from argparse import ArgumentParser, RawTextHelpFormatter
+from argparse import ArgumentParser, Namespace, RawTextHelpFormatter
from project_lib import (QmlProjectData, check_qml_decorators, is_python_file, migrate_pyproject,
QMLDIR_FILE, MOD_CMD, METATYPES_JSON_SUFFIX, SHADER_SUFFIXES,
self._regenerate_qmldir()
- def run(self) -> int:
+ def run(self, args: list) -> int:
"""Runs the project"""
self.build()
cmd = [sys.executable, str(self.project.main_file)]
+ cmd.extend(args)
return run_command(cmd, cwd=self.project.project_file.parent)
def _clean_file(self, source: Path):
run_command(cmd, cwd=project_dir)
-def main(mode: str = None, dry_run: bool = False, quiet: bool = False, force: bool = False,
- qml_module: bool = None, project_dir: str = None, project_path: str = None,
- legacy_pyproject: bool = False):
- cl_options = ClOptions(dry_run=dry_run, quiet=quiet, # noqa: F841
- force=force, qml_module=qml_module)
+def main(args: Namespace):
+ mode = args.mode
+ project_dir = getattr(args, "project_dir", None)
+ project_path = getattr(args, "project_path", None)
+ legacy_pyproject = getattr(args, "legacy_pyproject", False)
+ cl_options = ClOptions(dry_run=args.dry_run, quiet=args.quiet, # noqa: F841
+ force=args.force, qml_module=args.qml_module)
if new_project_type := NewProjectTypes.find_by_command(mode):
if not project_dir:
if mode == "build":
project.build()
elif mode == "run":
- sys.exit(project.run())
+ sys.exit(project.run(args.arguments))
elif mode == "clean":
project.clean()
elif mode == "qmllint":
for op_mode, op_help in OPERATION_HELP.items():
op_parser = subparsers.add_parser(op_mode, help=op_help)
op_parser.add_argument("project_path", nargs="?", type=str, help="Path to the project file")
+ if op_mode == "run":
+ op_parser.add_argument('arguments', nargs='*', help="Arguments")
- args = parser.parse_args()
-
- main(args.mode, args.dry_run, args.quiet, args.force, args.qml_module,
- getattr(args, "project_dir", None), getattr(args, "project_path", None),
- getattr(args, "legacy_pyproject", None))
+ main(parser.parse_args())
set(pyside_MAJOR_VERSION "6")
set(pyside_MINOR_VERSION "10")
-set(pyside_MICRO_VERSION "2")
+set(pyside_MICRO_VERSION "3")
set(pyside_PRE_RELEASE_VERSION_TYPE "")
set(pyside_PRE_RELEASE_VERSION "")
set(QtHttpServer_SRC
${QtHttpServer_GEN_DIR}/qabstracthttpserver_wrapper.cpp
+ ${QtHttpServer_GEN_DIR}/qhttpserverconfiguration_wrapper.cpp
${QtHttpServer_GEN_DIR}/qfuturehttpserverresponse_wrapper.cpp
${QtHttpServer_GEN_DIR}/qhttpserver_wrapper.cpp
${QtHttpServer_GEN_DIR}/qhttpserverresponder_wrapper.cpp
snippet="qhttpserver-addafterrequesthandler"/>
</add-function>
</object-type>
+ <value-type name="QHttpServerConfiguration" since="6.9"/>
<object-type name="QHttpServerResponder">
<enum-type name="StatusCode"/>
</object-type>
<enum-type name="SavePageFormat"/>
</object-type>
- <object-type name="QWebEngineExtensionInfo" since="6.10"/>
+ <value-type name="QWebEngineExtensionInfo" since="6.10"/>
<object-type name="QWebEngineExtensionManager" since="6.10"/>
<value-type name="QWebEngineFileSystemAccessRequest">
website.
* **Python**: 3.9+ `[official Python website] <https://www.python.org/downloads/>`_
-* **Qt:** 6.8+ `[online installer] <https://download.qt.io/official_releases/online_installers/>`_
+* **Qt:** 6.10+ `[online installer] <https://download.qt.io/official_releases/online_installers/>`_
* **CMake:** 3.18+ `[official CMake website] <https://cmake.org/download/>`_
* **Git:** 2.0+. `[official Git website] <https://git-scm.com/downloads>`_
-* **libclang:** The libclang library, recommended: version 18+ for 6.8+.
+* **libclang:** The libclang library, recommended: version 18+ for 6.10+.
Prebuilt versions for each OS can be `downloaded here`_.
* Check the `Supported Platforms of Qt`_
python testrunner.py test > testlog.txt
.. note:: On Windows, don't forget to have qtpaths in your path
- (:command:`set PATH=C:\\\Path\\\to\\\Qt\\\6.4.0\\\msvc2019_64\\\bin;%PATH%`)
+ (:command:`set PATH=C:\\\Path\\\to\\\Qt\\\6.10.0\\\msvc2022_64\\\bin;%PATH%`)
You can also run a specific test (for example ``qpainter_test``) by running::
need to copy several system files (sysroot). We recommend a Linux OS that has
the latest Qt versions, like `Manjaro ARM`_ or `Archlinux ARM`_.
-* (target) Install Qt 6.3+ on the system using the package manager.
-* (host) Install Qt 6.3+ on the system using the package manager or Qt
+* (target) Install Qt 6.10+ on the system using the package manager.
+* (host) Install Qt 6.10+ on the system using the package manager or Qt
Installer.
* (target, host) Install the library and development packages that provide
C++ headers, linkers, libraries, and compilers.
-* (target, host) Install Python interpreter v3.7 or later
+* (target, host) Install Python interpreter v3.9 or later
* (target, host) Install CMake 3.17+
After installing these prerequisites, copy the ``target`` sysroot to your
python setup.py bdist_wheel \
--parallel=8 --ignore-git --reuse-build --standalone --limited-api=yes \
--cmake-toolchain-file=/opt/toolchain-aarch64.cmake \
- --qt-host-path=/opt/Qt/6.3.0/gcc_64 \
+ --qt-host-path=/opt/Qt/6.10.0/gcc_64 \
--plat-name=linux_aarch64
Depending on the target platform, you could use ``linux_armv7``,
.. code-block:: bash
- PySide6-6.3.0-6.3.0-cp36-abi3-manylinux2014_aarch64.whl
- shiboken6-6.3.0-6.3.0-cp36-abi3-manylinux2014_aarch64.whl
+ PySide6-6.10.0-cp310-abi3-manylinux_2_42_aarch64.whl
+ shiboken6-6.10.0cp310-abi3-manylinux_2_42_aarch64.whl
Troubleshooting
* ``create_changelog.py``: Script used to create the CHANGELOG that you can find in the ``dist/``
directory. Usage::
- python create_changelog.py -r 6.0.1 -v v6.0.0..6.0 -t bug-fix
+ python create_changelog.py -r 6.10.1
* ``debug_windows.py``: This script can be used to find out why PySide modules
fail to load with various DLL errors like Missing DLL or Missing symbol in DLL.
regarding available modules and classses. This content is displayed in our `wiki page`_,
and can be used as follows::
- python missing_bindings.py --qt-version 6.0.1 -w all
+ python missing_bindings.py --qt-version 6.10.0 -w all
.. note:: The script relies on BeautifulSoup to parse the content and generate a list of the
missing bindings.
git clone https://code.qt.io/pyside/pyside-setup
-Checking out the version that we want to build, for example 6.8::
+Checking out the version that we want to build, for example 6.10::
- cd pyside-setup && git checkout 6.8
+ cd pyside-setup && git checkout 6.10
Install the general dependencies::
pip install -r requirements-doc.txt
.. note:: Keep in mind you need to use the same version as your Qt installation.
- Additionally, :command:`git checkout -b 6.8 --track origin/6.8` could be a better option
+ Additionally, :command:`git checkout -b 6.10 --track origin/6.10` could be a better option
in case you want to work on it.
.. note:: With `uv`_, use `uv pip install ...`
wheels using the `pip` command.
Check your Qt installation path, to specifically use that version of qtpaths to build PySide.
-for example, :command:`/opt/Qt/6.8.0/gcc_64/bin/qtpaths`.
+for example, :command:`/opt/Qt/6.10.0/gcc_64/bin/qtpaths`.
Build can take a few minutes, so it is recommended to use more than one CPU core::
- python setup.py build --qtpaths=/opt/Qt/6.8.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8
+ python setup.py build --qtpaths=/opt/Qt/6.10.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8
With `uv`_, the build command becomes::
- uv run setup.py build --qtpaths=/opt/Qt/6.8.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8
+ uv run setup.py build --qtpaths=/opt/Qt/6.10.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8
To create the wheels, just run::
git clone https://code.qt.io/pyside/pyside-setup
-Checking out the version that we want to build, for example, 6.8::
+Checking out the version that we want to build, for example, 6.10::
- cd pyside-setup && git checkout 6.8
+ cd pyside-setup && git checkout 6.10
Install the general dependencies::
~~~~~~~~~~~~~~~
Check your Qt installation path, to specifically use that version of qtpaths to build PySide.
-for example, ``/opt/Qt/6.8.0/gcc_64/bin/qtpaths``.
+for example, ``/opt/Qt/6.10.0/gcc_64/bin/qtpaths``.
Build can take a few minutes, so it is recommended to use more than one CPU core::
- python setup.py build --qtpaths=/opt/Qt/6.8.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8
+ python setup.py build --qtpaths=/opt/Qt/6.10.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8
With `uv`_, this command becomes::
- uv run setup.py build --qtpaths=/opt/Qt/6.8.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8
+ uv run setup.py build --qtpaths=/opt/Qt/6.10.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8
Installing PySide
git clone https://code.qt.io/pyside/pyside-setup
-Checking out the version that we want to build, for example, 6.8::
+Checking out the version that we want to build, for example, 6.10::
- cd pyside-setup && git checkout 6.8
+ cd pyside-setup && git checkout 6.10
Install the general dependencies::
~~~~~~~~~~~~~~~
Check your Qt installation path, to specifically use that version of qtpaths to build PySide.
-for example, ``C:\Qt\6.8.0\msvc2019_64\bin\qtpaths.exe``.
+for example, ``C:\Qt\6.10.0\msvc2022_64\bin\qtpaths.exe``.
Build can take a few minutes, so it is recommended to use more than one CPU core::
A ``requirements.txt`` file will be provided alongside the wheels, in order to
simplify the installation step::
- pip install -r /path/to/Qt/QtForPython/6.6.0/requirements.txt
+ cd /path/to/Qt/QtForPython/6.10.0/
+ pip install --no-index --find-links=. -r requirements.txt
Complementary to the wheels, you will be able to download the sources
as well.
Additionally, here is where you can find the *Qt for automation (M2M
Protocols)* packages.
-* **Qt for Python (Commercial LTS)** where you will find the 5.15.x LTS and 6.2.x
- LTS releases.
+* **Qt for Python (Commercial LTS)** where you will find the 6.8.x LTS releases.
Once you select any of those, you will be able to select the version of the
packages you want to download for the **Qt for Python Commercial wheels** packages:
the version, and assumes that you are running it on the same directory where
the wheels are::
- pip install --no-index --find-links=. PySide6==6.4.0.commercial
+ pip install --no-index --find-links=. PySide6==6.10.0.commercial
.. note:: As described in the :ref:`package_details` page, the dependency
of the packages requires you to first install the ``shiboken6``
automatically detect the NDK and SDK from this cache directory.
If you want to try to download the NDK and SDK manually, you can do so from the following steps
-(for Qt 6.8):
+(for Qt 6.10):
1. Download the sdkmanager using the instructions provided in the `Android Studio
documentation <https://developer.android.com/studio/command-line/sdkmanager>`_.
-2. Using the sdkmanager download the following packages (for Qt 6.8)::
+2. Using the sdkmanager download the following packages (for Qt 6.10)::
"platform-tools", "platforms;android-34", "build-tools;35.0.0"
- and install the NDK using the following command (for Qt 6.8)::
+ and install the NDK using the following command (for Qt 6.10)::
"ndk;26.1.10909125"
git clone https://code.qt.io/pyside/pyside-setup
-#. Check out the version that you want to build, for example 6.7. The version checked out has
+#. Check out the version that you want to build, for example 6.10. The version checked out has
to correspond to the Qt version downloaded in Step 1::
- cd pyside-setup && git checkout 6.7
+ cd pyside-setup && git checkout 6.10
#. Installing the dependencies::
#. Run the cross-compilation Python script.::
- python tools/cross_compile_android/main.py --plat-name=aarch64 --qt-install-path=/opt/Qt/6.7.0
+ python tools/cross_compile_android/main.py --plat-name=aarch64 --qt-install-path=/opt/Qt/6.10.0
--auto-accept-license --skip-update
- *--qt-install-path* refers to the path where Qt 6.7.0 is installed. *--auto-accept-license* and
+ *--qt-install-path* refers to the path where Qt 6.10.0 is installed. *--auto-accept-license* and
*--skip-update* are required for downloading and installing Android NDK and SDK if not already
specified through command line options or if they don't already exist in the
``pyside6-android-deploy`` cache. Use --help to see all the other available options::
## Issues
-- [Receiver Leak PYSIDE-1057](https://bugreports.qt.io/browse/PYSIDE-1057)
-- [Partial function receiver Leak PYSIDE-2793](https://bugreports.qt.io/browse/PYSIDE-2793)
+- [Receiver Leak PYSIDE-1057](https://qt-project.atlassian.net/browse/PYSIDE-1057)
+- [Partial function receiver Leak PYSIDE-2793](https://qt-project.atlassian.net/browse/PYSIDE-2793)
- Various issues related to threading and object deletion, solved by workarounds
- ([PYSIDE-2646](https://bugreports.qt.io/browse/PYSIDE-2646))
+ ([PYSIDE-2646](https://qt-project.atlassian.net/browse/PYSIDE-2646))
- Complicated code, hard to maintain
- Disconnect does not work for `QObject.connect()` with context argument; it also
leaks methods
.. note:: A set of Qt Quick-based UI controls is also available to create user interfaces.
See :mod:`PySide6.QtQuickControls2` for more information.
-If you're new to QML and Qt Quick, please see :ref:`QML-Applications` for an
-introduction to writing QML applications.
+If you're new to QML and Qt Quick, please see
+:ref:`Getting started with Qt Quick applications <Getting-started-with-Qt-Quick-applications>`
+for an introduction to writing QML applications.
Using the Module
^^^^^^^^^^^^^^^^
When you install ``PySide6`` you might have notice that also ``Shiboken6``
is installed as a dependency::
+https://download.qt.io/snapshots/ci/pyside/6.10/latest/pyside6/shiboken6-6.10.0a1.dev1771851518-cp39-abi3-manylinux_2_34_x86_64.whl
$ pip install pyside6
Collecting pyside6
- Downloading PySide6-6.0.0-6.0.0-cp36.cp37.cp38.cp39-abi3-manylinux1_x86_64.whl (170.5 MB)
+ Downloading PySide6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl (170.5 MB)
|████████████████████████████████| 170.5 MB 42 kB/s
- Collecting shiboken6==6.0.0
- Downloading shiboken6-6.0.0-6.0.0-cp36.cp37.cp38.cp39-abi3-manylinux1_x86_64.whl (964 kB)
+ Collecting shiboken6==6.10.0
+ Downloading shiboken6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl (964 kB)
|████████████████████████████████| 964 kB 29.3 MB/s
Installing collected packages: shiboken6, pyside6
- Successfully installed pyside6-6.0.0 shiboken6-6.0.0
+ Successfully installed pyside6-6.10.0 shiboken6-6.10.0
That installed package is also called **Shiboken Module**, and it contains
some utilities for PySide to properly work.
Before you can install |project|, first you must install the following software:
-* `Official <https://www.python.org/downloads/>`_ Python 3.8+
+* `Official <https://www.python.org/downloads/>`_ Python 3.9+
* We **highly** recommend using a virtual environment, such as
`venv <https://docs.python.org/3/library/venv.html>`_ or
`virtualenv <https://virtualenv.pypa.io/en/latest>`_
pip install pyside6
- * For a specific version, like 6.4.1::
+ * For a specific version, like 6.10.1::
- pip install pyside6==6.4.1
+ pip install pyside6==6.10.1
* It is also possible to install a specific snapshot from our servers.
To do so, you can use the following command::
- pip install --index-url=https://download.qt.io/snapshots/ci/pyside/6.4/latest pyside6 --trusted-host download.qt.io
+ pip install --index-url=https://download.qt.io/snapshots/ci/pyside/6.10/latest pyside6 --trusted-host download.qt.io
* **Test your installation**
This project is available under the LGPLv3/GPLv3 and the `Qt commercial license`_.
.. _Qt: https://doc.qt.io
-.. _`Qt commercial license`: https://www.qt.io/licensing/`
+.. _`Qt commercial license`: https://www.qt.io/licensing/
Quick Start
===========
.. grid-item-card:: :mod:`Qt Charts <PySide6.QtCharts>`
- UI Components for displaying visually pleasing charts,
- driven by static or dynamic data models.
+ Deprecated since Qt 6.10 in favor of :mod:`Qt Graphs <PySide6.QtGraphs>`.
+ UI Components for displaying charts.
.. grid-item-card:: :mod:`Qt Concurrent <PySide6.QtConcurrent>`
.. grid-item-card:: :mod:`Qt Data Visualization <PySide6.QtDataVisualization>`
- UI Components for creating stunning 3D data visualizations.
+ Deprecated since Qt 6.10 in favor of :mod:`Qt Graphs <PySide6.QtGraphs>`.
+ UI Components for creating 3D data visualizations.
.. grid-item-card:: :mod:`Qt D-Bus <PySide6.QtDBus>`
The licenses for the third-party sources used by Qt itself are listed
in
-`Qt documentation <https://doc.qt.io/qt-5/licenses-used-in-qt.html>`_.
+`Qt documentation <https://doc.qt.io/qt-6/licenses-used-in-qt.html>`_.
The following table lists parts of Qt for Python that incorporates
code licensed under third-party opensource licenses:
(see :ref:`tutorial_uifiles`, :ref:`tutorial_qrcfiles`).
*run*
- Builds the project and runs the main.
+ Builds the project and runs the main. Additional command line arguments
+ can be passed following the project file argument.
*deploy*
Deploys the application (see see :ref:`pyside6-deploy`).
.. image:: player-new.png
:alt: New Multimedia Player Qt Example
-.. _`Qt Resource System`: https://doc.qt.io/qt-5/resources.html
+.. _`Qt Resource System`: https://doc.qt.io/qt-6/resources.html
require a way to communicate, and that's the reason for this mechanism to
be a **central feature of Qt**.
-In simple terms, you can understand **Signal and Slots** in the same way you
+
+In simple terms, you can understand **Signals and Slots** in the same way you
interact with the lights in your house. When you move the light switch
-(signal) you get a result which may be that your light bulbs are switched
-on/off (slot).
+(signal), the result is that your light bulbs are switched on or off (slot).
While developing interfaces, you can get a real example by the effect of
clicking a button: the 'click' will be the signal, and the slot will be what
(single line input field) has a slot named ``clear()``.
So, a text input field with a button to clear the text
could be implemented by placing a :class:`~PySide6.QtWidgets.QToolButton`
-to the right of the ``QLineEdit`` and connecting its ``clicked()`` signal to the slot
-``clear()``. This is done using the :meth:`~PySide6.QtCore.Signal.connect`
-method of the signal:
+to the right of the ``QLineEdit`` and connecting its ``clicked()`` signal to
+the slot ``clear()``. This is done using the
+:meth:`~PySide6.QtCore.Signal.connect` method of the signal:
.. code-block:: python
.. code-block:: python
- # TODO
signal5 = Signal(int, name='rangeChanged')
# ...
rangeChanged.emit(...)
This is particularly important for ``QObject`` classes registered with QML, where
missing decorators can introduce bugs.
-Missing decorators can be diagnosed by setting activating warnings of the
-logging category ``qt.pyside.libpyside``; for example by setting the
+Missing decorators can be diagnosed by activating warnings of the
+logging category ``qt.pyside.libpyside``; for example, by setting the
environment variable:
.. code-block:: bash
Overloading Signals and Slots with Different Types
--------------------------------------------------
-It is actually possible to use signals and slots of the same name with different
-parameter type lists. This is legacy from Qt 5 and not recommended for new code.
-In Qt 6, signals have distinct names for different types.
+
+It is possible to use signals and slots of the same name with different
+parameter type lists. This is legacy from Qt 5 and is not recommended for new code.
+In Qt 6, signals should have distinct names for different types.
The following example uses two handlers for a Signal and a Slot to showcase
the different functionality.
button.connect(SIGNAL("clicked(Qt::MouseButton)"),
action_handler, SLOT("action1(Qt::MouseButton)"))
+
This is not normally recommended; it is only needed
for a few cases where signals are only accessible via ``QMetaObject``
-(``QAxObject``, ``QAxWidget``, ``QDBusInterface`` or ``QWizardPage::registerField()``):
+(``QAxObject``, ``QAxWidget``, ``QDBusInterface``, or ``QWizardPage::registerField()``):
.. code-block:: python
*************************************************************************************
This page describes the use of
-`Qt Widgets Designer <https://doc.qt.io/qt-6/qtdesigner-manual.html>`_ to create
+`Qt Widgets Designer`_ to create
graphical interfaces based on Qt Widgets for your Qt for Python project.
*Qt Widgets Designer* is a graphical UI design tool which is available as a
standalone binary (``pyside6-designer``) or embedded into the
-`Qt Creator IDE <https://doc.qt.io/qtcreator>`_. Its use within *Qt Creator*
-is described at
-`Using Qt Widgets Designer <https://doc.qt.io/qtcreator/creator-using-qt-designer.html>`_.
+`Qt Creator IDE`_. Its use within *Qt Creator* is described at
+`Using Qt Widgets Designer`_.
.. image:: uifiles.png
:alt: Designer and the equivalent code
The designs are stored in ``.ui`` files, which is an XML-based format. It will
be converted to Python or C++ code populating a widget instance at project build
-time by the `pyside6-uic <https://doc.qt.io/qt-6/uic.html>`_ tool.
+time by the `pyside6-uic`_ tool.
To create a new Qt Design Form in *Qt Creator*, choose
``File/New File Or Project`` and "Main Window" for template. Save it as
You must run ``pyside6-uic`` again every time you make changes
to the **UI file**.
+Resources
+---------
+
+To avoid path resolution issues for image files used in icons, they can be
+embedded into Qt resource files (see `Qt Resource System`_,
+`Editing Resources with Qt Widgets Designer`_ and :ref:`tutorial_qrcfiles`).
+
+When building the project, the tool ``pyside6-rcc`` generates a Python file
+loading them from the ``.qrc`` file. It is typically named ``rc_<name>.py``
+and is imported by the Python file generated by ``pyside6-uic``. For this
+to work, the resource file and the ``.ui`` file should be in the same
+directory.
+
+If that is not the case, the import statement generated by ``pyside6-uic``
+can be tailored by a number of command line options
+(see ``pyside6-uic --help``):
+
+.. code-block:: text
+
+ --from-imports Python: generate imports relative to '.'
+ --absolute-imports Python: generate absolute imports
+ --python-paths <pathlist> Python paths for --absolute-imports.
+
+Typically, this will be a combination of ``--absolute-imports`` and
+``--python-paths``, passing the project directory to the latter.
+
Option B: Loading it directly
=============================
only after *Qt Widgets Designer* has terminated.
- When building Qt for Python, be sure to set the ``--standalone`` option
for the plugin to be properly installed.
+
+.. _`Qt Resource System`: https://doc.qt.io/qt-6/resources.html
+.. _`Qt Creator IDE`: https://doc.qt.io/qtcreator
+.. _`pyside6-uic`: https://doc.qt.io/qt-6/uic.html
+.. _`Qt Widgets Designer`: https://doc.qt.io/qt-6/qtdesigner-manual.html
+.. _`Editing Resources with Qt Widgets Designer`: https://doc.qt.io/qt-6/designer-resources.html
+.. _`Using Qt Widgets Designer`: https://doc.qt.io/qtcreator/creator-using-qt-designer.html
page.
-.. _`Qt Style Sheets Reference`: https://doc.qt.io/qt-5/stylesheet-reference.html
-.. _`Qt Style Sheet Examples`: https://doc.qt.io/qt-5/stylesheet-examples.html
+.. _`Qt Style Sheets Reference`: https://doc.qt.io/qt-6/stylesheet-reference.html
+.. _`Qt Style Sheet Examples`: https://doc.qt.io/qt-6/stylesheet-examples.html
Qt Style Sheets
---------------
==========================================
You can now think of presenting your data in a UI. A
-class:`~PySide6.QtWidgets.QMainWindow` provides a
+:class:`~PySide6.QtWidgets.QMainWindow` provides a
convenient structure for GUI applications, such as a menu bar and status bar.
The following image shows the layout that QMainWindow offers out-of-the box:
window size is defined based on available screen width (80%) and height (70%).
.. note:: You can achieve a similar structure using other Qt elements like
- class:`~PySide6.QtWidgets.QMenuBar`, class:`~PySide6.QtWidgets.QWidget`,
- and class:`~PySide6.QtWidgets.QStatusBar`. Refer the QMainWindow layout for
+ :class:`~PySide6.QtWidgets.QMenuBar`, class:`~PySide6.QtWidgets.QWidget`,
+ and :class:`~PySide6.QtWidgets.QStatusBar`. Refer the QMainWindow layout for
guidance.
.. literalinclude:: datavisualize3/main_window.py
applications. Use a table view to display your data.
The first step is to add a horizontal layout with just a
-class:`~PySide6.QtWidgets.QTableView`. You can create a QTableView object
-and place it inside a class:`~PySide6.QtWidgets.QHBoxLayout`. Once the
+:class:`~PySide6.QtWidgets.QTableView`. You can create a QTableView object
+and place it inside a :class:`~PySide6.QtWidgets.QHBoxLayout`. Once the
QWidget is properly built, pass the object to the QMainWindow as its central
widget.
Remember that a QTableView needs a model to display information. In this case,
-you can use a class:`~PySide6.QtCore.QAbstractTableModel` instance.
+you can use a :class:`~PySide6.QtCore.QAbstractTableModel` instance.
.. note:: You could also use the default item model that comes with a
- class:`~PySide6.QtWidgets.QTableWidget` instead. QTableWidget is a
+ :class:`~PySide6.QtWidgets.QTableWidget` instead. QTableWidget is a
convenience class that reduces your codebase considerably as you don't need
to implement a data model. However, it's less flexible than a QTableView,
as QTableWidget cannot be used with just any data. For more insight about
The Date column provides data in UTC format (for example,
2018-12-11T21:14:44.682Z), so you could easily map it to a
-class:`~PySide6.QtCore.QDateTime` object
+:class:`~PySide6.QtCore.QDateTime` object
defining the structure of the string. Additionally, you can adapt the time
based on the timezone you are in, using QTimeZone.
---------------------
The `QMainWindow` enables us to set a central widget that will be displayed when showing the window
-(`read more <https://doc.qt.io/qt-5/qmainwindow.html#details>`_).
+(`read more <https://doc.qt.io/qt-6/qmainwindow.html#details>`_).
This central widget could be another class derived from `QWidget`.
Additionally, you will define example data to visualize later.
:emphasize-lines: 8
You can read more about this configuration file
- `here <https://doc.qt.io/qt-5/qtquickcontrols2-configuration.html>`_.
+ `here <https://doc.qt.io/qt-6/qtquickcontrols2-configuration.html>`_.
The final look of your application will be:
On the other hand, the `Layout.fillHeight`_ statement in the `ListView`_ enables it to occupy the
remaining space that is left after accommodating the Pane.
-.. _Item Positioners: https://doc.qt.io/qt-5/qtquick-positioning-layouts.html
-.. _Qt Quick Layouts: https://doc.qt.io/qt-5/qtquicklayouts-index.html
-.. _Row: https://doc.qt.io/qt-5/qml-qtquick-row.html
-.. _Column: https://doc.qt.io/qt-5/qml-qtquick-column.html
-.. _ColumnLayout: https://doc.qt.io/qt-5/qml-qtquick-layouts-columnlayout.html
-.. _ListView: https://doc.qt.io/qt-5/qml-qtquick-listview.html
-.. _Pane: https://doc.qt.io/qt-5/qml-qtquick-controls2-pane.html
-.. _Frame: https://doc.qt.io/qt-5/qml-qtquick-controls2-frame.html
-.. _attached properties: https://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html
-.. _Layout.fillWidth: https://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html#fillWidth-attached-prop
-.. _Layout.fillHeight: https://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html#fillHeight-attached-prop
-.. _Qt Quick QML Types: https://doc.qt.io/qt-5/qtquick-qmlmodule.html
+.. _Item Positioners: https://doc.qt.io/qt-6/qtquick-positioning-layouts.html
+.. _Qt Quick Layouts: https://doc.qt.io/qt-6/qtquicklayouts-index.html
+.. _Row: https://doc.qt.io/qt-6/qml-qtquick-row.html
+.. _Column: https://doc.qt.io/qt-6/qml-qtquick-column.html
+.. _ColumnLayout: https://doc.qt.io/qt-6/qml-qtquick-layouts-columnlayout.html
+.. _ListView: https://doc.qt.io/qt-6/qml-qtquick-listview.html
+.. _Pane: https://doc.qt.io/qt-6/qml-qtquick-controls2-pane.html
+.. _Frame: https://doc.qt.io/qt-6/qml-qtquick-controls2-frame.html
+.. _attached properties: https://doc.qt.io/qt-6/qml-qtquick-layouts-layout.html
+.. _Layout.fillWidth: https://doc.qt.io/qt-6/qml-qtquick-layouts-layout.html#fillWidth-attached-prop
+.. _Layout.fillHeight: https://doc.qt.io/qt-6/qml-qtquick-layouts-layout.html#fillHeight-attached-prop
+.. _Qt Quick QML Types: https://doc.qt.io/qt-6/qtquick-qmlmodule.html
Let's look at the ``Listview`` in detail:
``sqlDialog.py``, since we're just having a mock up example here and there is only one possible
recipient and one possible sender for this conversation we're just using strings here.
-.. _displayMarginBeginning: https://doc.qt.io/qt-5/qml-qtquick-listview.html#displayMarginBeginning-prop
-.. _displayMarginEnd: https://doc.qt.io/qt-5/qml-qtquick-listview.html#displayMarginEnd-prop
-.. _TextArea: https://doc.qt.io/qt-5/qml-qtquick-controls2-textarea.html
+.. _displayMarginBeginning: https://doc.qt.io/qt-6/qml-qtquick-listview.html#displayMarginBeginning-prop
+.. _displayMarginEnd: https://doc.qt.io/qt-6/qml-qtquick-listview.html#displayMarginEnd-prop
+.. _TextArea: https://doc.qt.io/qt-6/qml-qtquick-controls2-textarea.html
``Main.qml`` needs to be put into a directory named :code:`App` along
with a file named ``qmldir`` to describe a basic QML module:
static const char *Slot_SignatureStrings[] = {
"PySide6.QtCore.Slot(self,*types:typing.Union[type,str],name:str=nullptr,result:typing.Union[type,str]=nullptr)",
- "PySide6.QtCore.Slot.__call__(self,function:collections.abc.Callable[...,typing.Any])->typing.Any",
+ "PySide6.QtCore.Slot.__call__(self,function:_SlotFunc)->_SlotFunc",
nullptr}; // Sentinel
void init(PyObject *module)
{
// PYSIDE-813: When PYSIDE-164 was solved by adding some thread allowance,
// this code was no longer protected. It was hard to find this connection.
- // See the website https://bugreports.qt.io/browse/PYSIDE-813 for details.
+ // See PYSIDE-813 for details.
Shiboken::GilState gil;
Py_XINCREF(m_me);
}
'''
Unit tests for attribute cache in Python 3
-This is the original code from the bug report
-https://bugreports.qt.io/browse/PYSIDE-60
+This is the original code from the bug report PYSIDE-60
'''
import os
"""
emoji-string-test.py
-This is the original code from the bug report
-https://bugreports.qt.io/browse/PYSIDE-336
+This is the original code from the bug report PYSIDE-336
The only changes are the emoji constant creation which avoids unicode in the
source itself, utf8 encoding in line 1 and a short plausibility test to make
from helper.usesqapplication import UsesQApplication
from PySide6.QtCore import QTimer
+from PySide6.QtGui import QGuiApplication # noqa: F401
+from PySide6.QtMultimedia import QMediaDevices
try:
from PySide6.QtTextToSpeech import QTextToSpeech
engines[0], engines[1] = engines[1], engines[0]
if not engines:
print('No QTextToSpeech engines available')
+ elif not QMediaDevices.audioOutputs():
+ print('No audio outputs available')
else:
speech = QTextToSpeech(engines[0])
speech.stateChanged.connect(self._slotStateChanged)
from __future__ import annotations
"""
-This is the example from https://bugreports.qt.io/browse/PYSIDE-772
-with no interaction as a unittest.
+This is the example from PYSIDE-772 with no interaction as a unittest.
"""
import os
-To run these tests is necessary some manuall input (most of then not supported by QTest[1]),
+To run these tests, some manual input is necessary (most of them not supported by QTest[1]),
because of that this is not part of automatic test context.
-[1]http://bugreports.qt.nokia.com/browse/QTBUG-13397
+[1]https://qt-project.atlassian.net/browse/QTBUG-13397
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
from __future__ import annotations
-''' PYSIDE-315: https://bugreports.qt.io/browse/PYSIDE-315
- Test that all signals and slots of a class (including any mixin classes)
+''' PYSIDE-315: Test that all signals and slots of a class (including any mixin classes)
are registered at type parsing time. Also test that the signal and slot
indices do not change after signal connection or emission. '''
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
from __future__ import annotations
-""" PYSIDE-1755: https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-1755
- Tests that a unittest.mock.MagicMock() can be used as a slot for quick
+""" PYSIDE-1755: Tests that a unittest.mock.MagicMock() can be used as a slot for quick
prototyping. """
import os
def test_1093(self):
# Test that without creating a QApplication staticMetaObject still exists.
- # Please see https://bugreports.qt.io/browse/PYSIDE-1093 for explanation.
+ # Please see PYSIDE-1093 for explanation.
# Note: This test must run first, otherwise we would be mislead!
assert self._test_1093_is_first
from PySide6 import QtCore
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
from __future__ import annotations
-''' PYSIDE-315: https://bugreports.qt.io/browse/PYSIDE-315
- Test that creating a signal in the wrong order triggers a warning. '''
+''' PYSIDE-315: Test that creating a signal in the wrong order triggers a warning. '''
import os
import sys
We supply a `tp_descr_get` slot for the signal type.
That creates the `__get__` method via `PyType_Ready`.
-The original test script was converted to a unittest.
-See https://bugreports.qt.io/browse/PYSIDE-68 .
+The original test script was converted to a unittest (see PYSIDE-68).
Created: 16 May '12 21:25
Updated: 17 Sep '20 17:02
"xcbglintegrations"]
# Plugins that needs to be passed to Nuitka
plugins_nuitka = "networkinformation,platforminputcontexts,qml,qmltooling"
+ data_dir = str(self.temp_example_qml / "EditingModel")
self.expected_run_cmd = (
f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports"
f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet"
f" {self.dlls_ignore_nuitka}"
" --noinclude-dlls=*/qml/QtQuickEffectMaker/*"
f" --include-qt-plugins={plugins_nuitka}"
- f" --include-data-files={str(self.temp_example_qml / self.first_qml_file)}="
- f"./main.qml --include-data-files="
- f"{str(self.temp_example_qml / self.second_qml_file)}=./MovingRectangle.qml"
+ f" --include-data-dir={data_dir}=./EditingModel"
)
if sys.platform != "win32":
self.assertEqual(config_obj.get_value("app", "exec_directory"), ".")
self.assertEqual(config_obj.get_value("python", "packages"),
"Nuitka==2.7.11")
- self.assertEqual(config_obj.get_value("qt", "qml_files"),
- "MovingRectangle.qml,main.qml")
+ expected_qml_files = f"EditingModel{os.sep}Main.qml,EditingModel{os.sep}MovingRectangle.qml"
+ self.assertEqual(config_obj.get_value("qt", "qml_files"), expected_qml_files)
equ_base = "--quiet --noinclude-qt-translations"
equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base
self.assertEqual(config_obj.get_value("nuitka", "extra_args"), equ_value)
# Plugins that needs to be passed to Nuitka
plugins_nuitka = "networkinformation,platforminputcontexts,qml,qmltooling"
qml_files = [
- "ApplicationRoot.qml",
- "BrowserDialog.qml",
- "BrowserWindow.qml",
- "DownloadView.qml",
- "FindBar.qml",
- "FullScreenNotification.qml",
- "WebAuthDialog.qml"
+ f"BrowserUtils{os.sep}ApplicationRoot.qml",
+ f"BrowserUtils{os.sep}BrowserDialog.qml",
+ f"BrowserUtils{os.sep}BrowserWindow.qml",
+ f"BrowserUtils{os.sep}DownloadView.qml",
+ f"BrowserUtils{os.sep}FindBar.qml",
+ f"BrowserUtils{os.sep}FullScreenNotification.qml",
+ f"BrowserUtils{os.sep}WebAuthDialog.qml"
]
data_files_cmd = " ".join(
[
with open(self.temp_example_qml / "deploy_main.py", "r") as file:
# check if 516 lines start with # nuitka-project:
self.assertEqual(len([line for line in file.readlines()
- if line.startswith("# nuitka-project:")]), 516)
+ if line.startswith("# nuitka-project:")]), 517)
@unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11,
set(shiboken_MAJOR_VERSION "6")
set(shiboken_MINOR_VERSION "10")
-set(shiboken_MICRO_VERSION "2")
+set(shiboken_MICRO_VERSION "3")
set(shiboken_PRE_RELEASE_VERSION_TYPE "")
set(shiboken_PRE_RELEASE_VERSION "")
// https://github.com/darlinghq/darling/issues/204
#endif
"-Wno-constant-logical-operand",
-#if CINDEX_VERSION_MAJOR > 0 || CINDEX_VERSION_MINOR >= 64 // Clang 21
- // QTBUG-141204: Suppress "character-conversion" warnings in Qt: qchar.h:... warning: implicit
- // conversion from 'const char16_t' to 'char32_t' may change the meaning of the represented code unit.
- "-Wno-character-conversion",
-#endif
"-x",
"c++" // Treat .h as C++, not C
};
General Requirements
^^^^^^^^^^^^^^^^^^^^
-* **Python**: 3.7+
-* **Qt:** 6.0+
-* **libclang:** The libclang library, recommended: version 10 for 6.0+.
+* **Python**: 3.9+
+* **Qt:** 6.10+
+* **libclang:** The libclang library, recommended: version 18+ for Qt 6.10+.
Prebuilt versions of it can be `downloaded here`_.
-* **CMake:** 3.1+ is needed.
+* **CMake:** 3.18+ is needed.
.. _downloaded here: https://download.qt.io/development_releases/prebuilt/libclang/
const auto &basicName = type.basicPrimitiveName();
if (AbstractMetaType::cppSignedIntTypes().contains(basicName)
|| AbstractMetaType::cppUnsignedIntTypes().contains(basicName)) {
+ if (basicName == charT)
+ return u"single character string"_s;
return intT;
}
if (AbstractMetaType::cppFloatTypes().contains(basicName))
const auto &type = arg.modifiedType();
const QString pyArgName = pythonArgsAt(pyArgIndex);
s << "\n|| ";
- s << '(' << pyArgName << " != nullptr && !";
+ s << '(' << pyArgName << " != nullptr && !(";
writeTypeCheck(s, type, pyArgName, isNumber(type.typeEntry()), {});
- s << ')';
+ s << "))";
}
s << outdent << ") {\n" << indent
<< "Py_XINCREF(errInfo.object());\n" // PYSIDE-3133, 0 if conversion fails
# PlaceholderType fix to avoid the '~' from TypeVar.__repr__
if "~PlaceholderType" in source:
source = source.replace("~PlaceholderType", "PlaceholderType")
+ if "~_SlotFunc" in source:
+ source = source.replace("~_SlotFunc", "_SlotFunc")
# Replace all "NoneType" strings by "None" which is a typing convention.
return source.replace("NoneType", "None")
# PlaceholderType fix to avoid the '~' from TypeVar.__repr__
if "~PlaceholderType" in source:
source = source.replace("~PlaceholderType", "PlaceholderType")
+ if "~_SlotFunc" in source:
+ source = source.replace("~_SlotFunc", "_SlotFunc")
+
while match := cls.opt_uni_searcher.search(source):
start = match.start()
if import_name == "PySide6.QtCore":
wr.print("PlaceholderType = typing.TypeVar(\"PlaceholderType\", "
"bound=PySide6.QtCore.QObject)")
+ wr.print("_SlotFunc = typing.TypeVar(\"_SlotFunc\", "
+ "bound=collections.abc.Callable[..., object])")
wr.print()
else:
wr.print(line)
# Later it will be bound to a QObject, within the QtCore types extensions
PlaceholderType = TypeVar("PlaceholderType")
+_SlotFunc = TypeVar("_SlotFunc")
+
_S = TypeVar("_S")
MultiMap = typing.DefaultDict[str, typing.List[str]]
return nullptr;
}
+int asciiCode(char character)
+{
+ return character;
+}
+
unsigned int doubleUnsignedInt(unsigned int value)
{
return value * 2;
LIBSAMPLE_API GlobalOverloadFuncEnum overloadedFunc(int val);
LIBSAMPLE_API GlobalOverloadFuncEnum overloadedFunc(double val);
+LIBSAMPLE_API int asciiCode(char character = 'a');
LIBSAMPLE_API unsigned int doubleUnsignedInt(unsigned int value);
LIBSAMPLE_API long long doubleLongLong(long long value);
LIBSAMPLE_API unsigned long long doubleUnsignedLongLong(unsigned long long value);
#include "polygon.h"
+#include <algorithm>
+
Polygon::Polygon(double x, double y) : m_points({Point(x, y)})
{
}
{
delete polygon;
}
+
+bool Polygon::contains(Point needle) const
+{
+ return std::find(m_points.cbegin(), m_points.cend(), needle) != m_points.cend();
+}
// This method invalidates the argument to be used in a call to doublePolygonScale(Polygon).
static void stealOwnershipFromPython(Polygon *polygon);
+ bool contains(Point needle) const;
+
private:
PointList m_points;
};
from shiboken_paths import init_paths
init_paths()
-from sample import SampleNamespace, Point, ObjectType, ObjectModel
+from sample import asciiCode, SampleNamespace, Point, ObjectType, ObjectModel
class DecisorTest(unittest.TestCase):
self.assertNotEqual(ObjectModel.receivesObjectTypeFamily(objectmodel),
ObjectModel.MethodCalled.ObjectTypeCalled)
+ def testKeywordArguments(self):
+ '''PYSIDE-3281: Test the complex SbkChar type check expression in conjunction with
+ keyword arguments.'''
+ self.assertEqual(asciiCode("a"), ord('a')) # Default parameter "a"
+ self.assertEqual(asciiCode("b"), ord('b')) # Positional
+ self.assertEqual(asciiCode(character="b"), ord('b')) # Keyword
+
if __name__ == '__main__':
unittest.main()
self.assertIsNone(sample.SampleNamespace.optionalMultiply(v1, None))
self.assertIsNone(sample.SampleNamespace.optionalMultiply(None, v2))
+ def testSequenceProtocol(self):
+ """Test slot Py_sq_contains of Polygon."""
+ point1 = sample.Point(1, 2)
+ point2 = sample.Point(3, 4)
+ polygon = sample.Polygon()
+ polygon.addPoint(point1)
+ polygon.addPoint(point2)
+ self.assertTrue(point1 in polygon)
+ self.assertFalse(sample.Point(5, 6) in polygon)
+
if __name__ == '__main__':
unittest.main()
PyObject *args = Py_BuildValue("(dd)", %CPPSELF.x(), %CPPSELF.y());
%PYARG_0 = Py_BuildValue("(OO)", type, args);
// @snippet point-reduce
+
+// @snippet polygon-contains
+auto needle = %CONVERTTOCPP[Point](_value);
+return %CPPSELF.contains(needle) ? 1 : 0;
+// @snippet polygon-contains
<function signature="transmuteComplexIntoPoint(const Complex&)" />
<function signature="transmutePointIntoComplex(const Point&)" />
<function signature="sumComplexPair(std::pair<Complex, Complex>)" />
+ <function signature="asciiCode(char)"/>
<function signature="doubleUnsignedInt(unsigned int)" />
<function signature="doubleLongLong(long long)" />
<function signature="doubleUnsignedLongLong(unsigned long long)" />
<define-ownership owner="c++"/>
</modify-argument>
</modify-function>
+ <add-function signature="__contains__(PyObject*@needle@)" return-type="int">
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="polygon-contains"/>
+ </add-function>
</value-type>
<value-type name="Time">
Documentation = "https://doc.qt.io/qtforpython"
Repository = "https://code.qt.io/cgit/pyside/pyside-setup.git/"
Changelog = "https://code.qt.io/cgit/pyside/pyside-setup.git/tree/doc/changelogs"
-Tracker = "https://bugreports.qt.io/projects/PYSIDE"
+Tracker = "https://qt-project.atlassian.net/browse/PYSIDE"
# PROJECT_SCRIPTS