From: Stuart Prescott Date: Tue, 7 Apr 2026 04:51:34 +0000 (+1000) Subject: New upstream version 6.10.3 X-Git-Tag: archive/raspbian/6.10.3-2+rpi1^2~18^2 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=9bb829816307c957ef3b7e1790b91639ea08be5d;p=pyside6.git New upstream version 6.10.3 --- diff --git a/LICENSES/BSD-3-Clause.txt b/LICENSES/BSD-3-Clause.txt index b91bbd89..23e8629f 100644 --- a/LICENSES/BSD-3-Clause.txt +++ b/LICENSES/BSD-3-Clause.txt @@ -1,4 +1,4 @@ -Copyright (c) . +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: diff --git a/README.md b/README.md index bb98271d..4d8cc05b 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ using `setup.py build`: ## 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 diff --git a/README.pyside6.md b/README.pyside6.md index 95e75a4c..39dbf9da 100644 --- a/README.pyside6.md +++ b/README.pyside6.md @@ -78,7 +78,7 @@ You can find more information about the PySide6 module API in the [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 diff --git a/README.pyside6_addons.md b/README.pyside6_addons.md index e232565a..9121d661 100644 --- a/README.pyside6_addons.md +++ b/README.pyside6_addons.md @@ -57,7 +57,7 @@ You can find more information about the PySide6 module API in the [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 diff --git a/README.pyside6_essentials.md b/README.pyside6_essentials.md index 73d8b66c..5c0424fa 100644 --- a/README.pyside6_essentials.md +++ b/README.pyside6_essentials.md @@ -41,7 +41,7 @@ You can find more information about the PySide6 module API in the [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 diff --git a/README.pyside6_examples.md b/README.pyside6_examples.md index b176139d..3fa59bbb 100644 --- a/README.pyside6_examples.md +++ b/README.pyside6_examples.md @@ -18,7 +18,7 @@ You can find more information about the PySide6 module API in the [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 diff --git a/build_history/blacklist.txt b/build_history/blacklist.txt index adfb0c60..b2aaecb8 100644 --- a/build_history/blacklist.txt +++ b/build_history/blacklist.txt @@ -15,7 +15,6 @@ darwin ci # PYSIDE-474 [QtWebEngineWidgets::pyside-474-qtwebengineview] - py2 py3 [QtCore::qthread_test] # PYSIDE-754 diff --git a/build_scripts/main.py b/build_scripts/main.py index 1819b7e4..02ad4020 100644 --- a/build_scripts/main.py +++ b/build_scripts/main.py @@ -667,8 +667,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): # 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']: diff --git a/build_scripts/wheel_files.py b/build_scripts/wheel_files.py index 6aaa6656..79bd6fa0 100644 --- a/build_scripts/wheel_files.py +++ b/build_scripts/wheel_files.py @@ -594,6 +594,13 @@ def module_QtQuick() -> ModuleData: 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 diff --git a/coin/dependencies.yaml b/coin/dependencies.yaml index 9d0157f7..3c24055f 100644 --- a/coin/dependencies.yaml +++ b/coin/dependencies.yaml @@ -1,6 +1,6 @@ product_dependency: ../../qt/qt5: - ref: "abfb3788bcfd08d6192899eb39a8ff34d7546da8" + ref: "6417867c2da7aa2b1b94fb9ab084b514fb7009bb" dependency_source: supermodule dependencies: [ "../../qt/qt3d", diff --git a/doc/changelogs/changes-6.10.3 b/doc/changelogs/changes-6.10.3 new file mode 100644 index 00000000..649880f0 --- /dev/null +++ b/doc/changelogs/changes-6.10.3 @@ -0,0 +1,40 @@ +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. diff --git a/examples/corelib/mimetypesbrowser/mimetypemodel.py b/examples/corelib/mimetypesbrowser/mimetypemodel.py index 12df5c86..9c6ff1d0 100644 --- a/examples/corelib/mimetypesbrowser/mimetypemodel.py +++ b/examples/corelib/mimetypesbrowser/mimetypemodel.py @@ -98,8 +98,7 @@ class MimeTypeModel(QStandardItemModel): @staticmethod def formatMimeTypeInfo(t: QMimeType): out = f"

{t.name()}


" - aliases_str = ", ".join(t.aliases()) - if aliases_str: + if aliases_str := ", ".join(t.aliases()): out += f"" ) - filter_str = t.filterString() - if filter_str: + if filter_str := t.filterString(): out += f"" - patterns_str = ", ".join(t.globPatterns()) - if patterns_str: + if patterns_str := ", ".join(t.globPatterns()): out += f"" - parentMimeTypes_str = ", ".join(t.parentMimeTypes()) - if parentMimeTypes_str: + if parentMimeTypes_str := ", ".join(t.parentMimeTypes()): out += f"" - suffixes = t.suffixes() - if suffixes: + if suffixes := t.suffixes(): out += "
Aliases: ({aliases_str})" out += ( @@ -108,23 +107,18 @@ class MimeTypeModel(QStandardItemModel): f"
Generic icon name{t.genericIconName()}
Filter:{filter_str}
Glob patterns:{patterns_str}
Parent types:{parentMimeTypes_str}
Suffixes:" - preferredSuffix = t.preferredSuffix() - if preferredSuffix: + if preferredSuffix := t.preferredSuffix(): suffixes.remove(preferredSuffix) out += f"{preferredSuffix} " suffixes_str = ", ".join(suffixes) diff --git a/examples/corelib/settingseditor/settingseditor.py b/examples/corelib/settingseditor/settingseditor.py index 7da39a77..960c513d 100644 --- a/examples/corelib/settingseditor/settingseditor.py +++ b/examples/corelib/settingseditor/settingseditor.py @@ -578,8 +578,7 @@ class SettingsTree(QTreeWidget): 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)) @@ -666,8 +665,7 @@ class VariantDelegate(QItemDelegate): 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 diff --git a/examples/demos/colorpaletteclient/basiclogin.py b/examples/demos/colorpaletteclient/basiclogin.py index 1090f4d0..d8c43370 100644 --- a/examples/demos/colorpaletteclient/basiclogin.py +++ b/examples/demos/colorpaletteclient/basiclogin.py @@ -75,8 +75,7 @@ class BasicLogin(AbstractResource): (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] diff --git a/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst b/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst index 0dcb91d4..87e5016a 100644 --- a/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst +++ b/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst @@ -13,6 +13,7 @@ Application functionality ------------------------- The example provides the following basic functionalities: + * Select the server to communicate with * List users and colors * Login and logout users @@ -25,7 +26,7 @@ At start the application presents the options for the color palette server to co 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. @@ -70,10 +71,14 @@ authorization token. 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 diff --git a/examples/demos/colorpaletteclient/paginatedresource.py b/examples/demos/colorpaletteclient/paginatedresource.py index db399a7d..24be77fc 100644 --- a/examples/demos/colorpaletteclient/paginatedresource.py +++ b/examples/demos/colorpaletteclient/paginatedresource.py @@ -190,19 +190,30 @@ class PaginatedResource(AbstractResource): 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() @@ -222,7 +233,7 @@ class PaginatedResource(AbstractResource): @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(): diff --git a/examples/demos/documentviewer/imageviewer/imageviewer.py b/examples/demos/documentviewer/imageviewer/imageviewer.py index a2155aca..6843c5d6 100644 --- a/examples/demos/documentviewer/imageviewer/imageviewer.py +++ b/examples/demos/documentviewer/imageviewer/imageviewer.py @@ -18,7 +18,7 @@ def imageFormats(): 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 diff --git a/examples/demos/documentviewer/mainwindow.py b/examples/demos/documentviewer/mainwindow.py index d72cca28..d35f76e6 100644 --- a/examples/demos/documentviewer/mainwindow.py +++ b/examples/demos/documentviewer/mainwindow.py @@ -52,8 +52,7 @@ class MainWindow(QMainWindow): 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) diff --git a/examples/graphs/common/graphprinting/GraphPrintingExample/Graph2D.qml b/examples/graphs/common/graphprinting/GraphPrintingExample/Graph2D.qml new file mode 100644 index 00000000..738f7176 --- /dev/null +++ b/examples/graphs/common/graphprinting/GraphPrintingExample/Graph2D.qml @@ -0,0 +1,75 @@ +// 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 } + } + } +} diff --git a/examples/graphs/common/graphprinting/GraphPrintingExample/Graph3D.qml b/examples/graphs/common/graphprinting/GraphPrintingExample/Graph3D.qml new file mode 100644 index 00000000..4ba0e18a --- /dev/null +++ b/examples/graphs/common/graphprinting/GraphPrintingExample/Graph3D.qml @@ -0,0 +1,64 @@ +// 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" } + } + } + } +} diff --git a/examples/graphs/common/graphprinting/GraphPrintingExample/Main.qml b/examples/graphs/common/graphprinting/GraphPrintingExample/Main.qml new file mode 100644 index 00000000..28e9bb81 --- /dev/null +++ b/examples/graphs/common/graphprinting/GraphPrintingExample/Main.qml @@ -0,0 +1,259 @@ +// 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("%1").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 + } + } +} diff --git a/examples/graphs/common/graphprinting/GraphPrintingExample/box_left.svg b/examples/graphs/common/graphprinting/GraphPrintingExample/box_left.svg new file mode 100644 index 00000000..8da6693d --- /dev/null +++ b/examples/graphs/common/graphprinting/GraphPrintingExample/box_left.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/graphs/common/graphprinting/GraphPrintingExample/box_left_fill.svg b/examples/graphs/common/graphprinting/GraphPrintingExample/box_left_fill.svg new file mode 100644 index 00000000..9b5b2f04 --- /dev/null +++ b/examples/graphs/common/graphprinting/GraphPrintingExample/box_left_fill.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/graphs/common/graphprinting/GraphPrintingExample/documents.svg b/examples/graphs/common/graphprinting/GraphPrintingExample/documents.svg new file mode 100644 index 00000000..94a33ff4 --- /dev/null +++ b/examples/graphs/common/graphprinting/GraphPrintingExample/documents.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/graphs/common/graphprinting/GraphPrintingExample/documents_fill.svg b/examples/graphs/common/graphprinting/GraphPrintingExample/documents_fill.svg new file mode 100644 index 00000000..fc638c5d --- /dev/null +++ b/examples/graphs/common/graphprinting/GraphPrintingExample/documents_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/graphs/common/graphprinting/GraphPrintingExample/flatten.svg b/examples/graphs/common/graphprinting/GraphPrintingExample/flatten.svg new file mode 100644 index 00000000..5f16abbd --- /dev/null +++ b/examples/graphs/common/graphprinting/GraphPrintingExample/flatten.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/graphs/common/graphprinting/GraphPrintingExample/flatten_square_fill.svg b/examples/graphs/common/graphprinting/GraphPrintingExample/flatten_square_fill.svg new file mode 100644 index 00000000..60bf0ab6 --- /dev/null +++ b/examples/graphs/common/graphprinting/GraphPrintingExample/flatten_square_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/graphs/common/graphprinting/GraphPrintingExample/print.svg b/examples/graphs/common/graphprinting/GraphPrintingExample/print.svg new file mode 100644 index 00000000..2213a48b --- /dev/null +++ b/examples/graphs/common/graphprinting/GraphPrintingExample/print.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/graphs/common/graphprinting/GraphPrintingExample/print_fill.svg b/examples/graphs/common/graphprinting/GraphPrintingExample/print_fill.svg new file mode 100644 index 00000000..3778bc0b --- /dev/null +++ b/examples/graphs/common/graphprinting/GraphPrintingExample/print_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/graphs/common/graphprinting/GraphPrintingExample/qmldir b/examples/graphs/common/graphprinting/GraphPrintingExample/qmldir new file mode 100644 index 00000000..0cd6a5ba --- /dev/null +++ b/examples/graphs/common/graphprinting/GraphPrintingExample/qmldir @@ -0,0 +1,4 @@ +module GraphPrintingExample +Main 1.0 Main.qml +Graph2D 1.0 Graph2D.qml +Graph3D 1.0 Graph3D.qml diff --git a/examples/graphs/common/graphprinting/doc/graphprinting.rst b/examples/graphs/common/graphprinting/doc/graphprinting.rst new file mode 100644 index 00000000..8179a128 --- /dev/null +++ b/examples/graphs/common/graphprinting/doc/graphprinting.rst @@ -0,0 +1,19 @@ +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 diff --git a/examples/graphs/common/graphprinting/doc/graphprinting.webp b/examples/graphs/common/graphprinting/doc/graphprinting.webp new file mode 100644 index 00000000..b0f61319 Binary files /dev/null and b/examples/graphs/common/graphprinting/doc/graphprinting.webp differ diff --git a/examples/graphs/common/graphprinting/graphprinter.py b/examples/graphs/common/graphprinting/graphprinter.py new file mode 100644 index 00000000..a508c3ad --- /dev/null +++ b/examples/graphs/common/graphprinting/graphprinter.py @@ -0,0 +1,67 @@ +# 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() diff --git a/examples/graphs/common/graphprinting/graphprinting.pyproject b/examples/graphs/common/graphprinting/graphprinting.pyproject new file mode 100644 index 00000000..1d916e5b --- /dev/null +++ b/examples/graphs/common/graphprinting/graphprinting.pyproject @@ -0,0 +1,18 @@ +{ + "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"] +} diff --git a/examples/graphs/common/graphprinting/main.py b/examples/graphs/common/graphprinting/main.py new file mode 100644 index 00000000..51fea496 --- /dev/null +++ b/examples/graphs/common/graphprinting/main.py @@ -0,0 +1,38 @@ +# 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) diff --git a/examples/multimedia/audiooutput/audiooutput.py b/examples/multimedia/audiooutput/audiooutput.py index cadfe2d0..9f1088cd 100644 --- a/examples/multimedia/audiooutput/audiooutput.py +++ b/examples/multimedia/audiooutput/audiooutput.py @@ -212,8 +212,7 @@ class AudioTest(QMainWindow): 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() diff --git a/examples/multimedia/camera/camera.py b/examples/multimedia/camera/camera.py index 12971b4d..ad596c76 100644 --- a/examples/multimedia/camera/camera.py +++ b/examples/multimedia/camera/camera.py @@ -2,16 +2,14 @@ # 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 @@ -46,8 +44,6 @@ class Camera(QMainWindow): 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 @@ -348,8 +344,7 @@ class Camera(QMainWindow): 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) diff --git a/examples/multimedia/camera/camera.pyproject b/examples/multimedia/camera/camera.pyproject index 9067b1df..f3cbe640 100644 --- a/examples/multimedia/camera/camera.pyproject +++ b/examples/multimedia/camera/camera.pyproject @@ -8,5 +8,6 @@ "metadatadialog.py", "videosettings.py", "videosettings.ui", - "videosettings_mobile.ui"] + "videosettings_mobile.ui", + "camera.qrc"] } diff --git a/examples/multimedia/camera/camera.qrc b/examples/multimedia/camera/camera.qrc new file mode 100644 index 00000000..a915eb59 --- /dev/null +++ b/examples/multimedia/camera/camera.qrc @@ -0,0 +1,5 @@ + + + images/shutter.svg + + diff --git a/examples/multimedia/camera/camera.ui b/examples/multimedia/camera/camera.ui index 1852c3e9..0ebd1b52 100644 --- a/examples/multimedia/camera/camera.ui +++ b/examples/multimedia/camera/camera.ui @@ -46,6 +46,14 @@ Capture Photo + + + :/images/shutter.svg:/images/shutter.svg + + + + :/images/shutter.svg:/images/shutter.svg + @@ -292,7 +300,9 @@ 1 - + + + recordButton diff --git a/examples/multimedia/camera/camera_mobile.ui b/examples/multimedia/camera/camera_mobile.ui index f36722ff..514d8c96 100644 --- a/examples/multimedia/camera/camera_mobile.ui +++ b/examples/multimedia/camera/camera_mobile.ui @@ -325,7 +325,9 @@ 1 - + + + recordButton diff --git a/examples/multimedia/camera/images/shutter.svg b/examples/multimedia/camera/images/shutter.svg new file mode 100644 index 00000000..18493361 --- /dev/null +++ b/examples/multimedia/camera/images/shutter.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/examples/multimedia/camera/rc_camera.py b/examples/multimedia/camera/rc_camera.py new file mode 100644 index 00000000..02eb3541 --- /dev/null +++ b/examples/multimedia/camera/rc_camera.py @@ -0,0 +1,131 @@ +# 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\x0a\x0a\ +\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() diff --git a/examples/multimedia/camera/shutter.svg b/examples/multimedia/camera/shutter.svg deleted file mode 100644 index 18493361..00000000 --- a/examples/multimedia/camera/shutter.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - diff --git a/examples/multimedia/camera/ui_camera.py b/examples/multimedia/camera/ui_camera.py index ec7dfcec..985f1a6b 100644 --- a/examples/multimedia/camera/ui_camera.py +++ b/examples/multimedia/camera/ui_camera.py @@ -3,7 +3,7 @@ ################################################################################ ## 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! ################################################################################ @@ -21,6 +21,7 @@ from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QLabel, QMainWindow, QMenu, QMenuBar, QPushButton, QSizePolicy, QSpacerItem, QStackedWidget, QStatusBar, QTabWidget, QWidget) +import rc_camera class Ui_Camera(object): def setupUi(self, Camera): @@ -54,6 +55,10 @@ class Ui_Camera(object): 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) diff --git a/examples/multimedia/camera/ui_camera_mobile.py b/examples/multimedia/camera/ui_camera_mobile.py index eff3310a..4d931fc7 100644 --- a/examples/multimedia/camera/ui_camera_mobile.py +++ b/examples/multimedia/camera/ui_camera_mobile.py @@ -3,7 +3,7 @@ ################################################################################ ## 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! ################################################################################ @@ -22,6 +22,7 @@ from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QHBoxLayout, QPushButton, QSizePolicy, QSlider, QSpacerItem, QStackedWidget, QStatusBar, QTabWidget, QVBoxLayout, QWidget) +import rc_camera class Ui_Camera(object): def setupUi(self, Camera): diff --git a/examples/multimedia/camera/ui_videosettings_mobile.py b/examples/multimedia/camera/ui_videosettings_mobile.py index ed9b01f7..fa52906c 100644 --- a/examples/multimedia/camera/ui_videosettings_mobile.py +++ b/examples/multimedia/camera/ui_videosettings_mobile.py @@ -3,7 +3,7 @@ ################################################################################ ## 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! ################################################################################ diff --git a/examples/multimedia/screencapture/screencapturepreview.py b/examples/multimedia/screencapture/screencapturepreview.py index 498803f6..ac36aaf3 100644 --- a/examples/multimedia/screencapture/screencapturepreview.py +++ b/examples/multimedia/screencapture/screencapturepreview.py @@ -90,8 +90,7 @@ class ScreenCapturePreview(QWidget): @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() @@ -101,8 +100,7 @@ class ScreenCapturePreview(QWidget): @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?" diff --git a/examples/network/googlesuggest/googlesuggest.py b/examples/network/googlesuggest/googlesuggest.py index bb19647e..f2699043 100644 --- a/examples/network/googlesuggest/googlesuggest.py +++ b/examples/network/googlesuggest/googlesuggest.py @@ -103,8 +103,7 @@ class GSuggestCompletion(QObject): 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() diff --git a/examples/pdf/quickpdfviewer/SinglePageModule/Viewer.qml b/examples/pdf/quickpdfviewer/SinglePageModule/Viewer.qml new file mode 100644 index 00000000..5342a287 --- /dev/null +++ b/examples/pdf/quickpdfviewer/SinglePageModule/Viewer.qml @@ -0,0 +1,307 @@ +// 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 + } + } + } +} diff --git a/examples/pdf/quickpdfviewer/SinglePageModule/qmldir b/examples/pdf/quickpdfviewer/SinglePageModule/qmldir new file mode 100644 index 00000000..1f9b6e17 --- /dev/null +++ b/examples/pdf/quickpdfviewer/SinglePageModule/qmldir @@ -0,0 +1,2 @@ +module SinglePageModule +Viewer 254.0 Viewer.qml diff --git a/examples/pdf/quickpdfviewer/main.py b/examples/pdf/quickpdfviewer/main.py index 388c590c..68cb2128 100644 --- a/examples/pdf/quickpdfviewer/main.py +++ b/examples/pdf/quickpdfviewer/main.py @@ -40,8 +40,8 @@ if __name__ == "__main__": 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) diff --git a/examples/pdf/quickpdfviewer/quickpdfviewer.pyproject b/examples/pdf/quickpdfviewer/quickpdfviewer.pyproject index 437e2555..e0198747 100644 --- a/examples/pdf/quickpdfviewer/quickpdfviewer.pyproject +++ b/examples/pdf/quickpdfviewer/quickpdfviewer.pyproject @@ -1,3 +1,4 @@ { - "files": ["main.py", "viewer.qml", "viewer.qrc", "resources/test.pdf"] + "files": ["main.py", "SinglePageModule/Viewer.qml", "SinglePageModule/qmldir", + "viewer.qrc", "resources/test.pdf"] } diff --git a/examples/pdf/quickpdfviewer/viewer.qml b/examples/pdf/quickpdfviewer/viewer.qml deleted file mode 100644 index 998c41d2..00000000 --- a/examples/pdf/quickpdfviewer/viewer.qml +++ /dev/null @@ -1,307 +0,0 @@ -// 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 - } - } - } -} diff --git a/examples/qml/editingmodel/EditingModel/Main.qml b/examples/qml/editingmodel/EditingModel/Main.qml new file mode 100644 index 00000000..2318ae8b --- /dev/null +++ b/examples/qml/editingmodel/EditingModel/Main.qml @@ -0,0 +1,106 @@ +// 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() + } + } + } +} diff --git a/examples/qml/editingmodel/EditingModel/MovingRectangle.qml b/examples/qml/editingmodel/EditingModel/MovingRectangle.qml new file mode 100644 index 00000000..b99a5f4d --- /dev/null +++ b/examples/qml/editingmodel/EditingModel/MovingRectangle.qml @@ -0,0 +1,78 @@ +// 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 + } + } + ] +} diff --git a/examples/qml/editingmodel/EditingModel/qmldir b/examples/qml/editingmodel/EditingModel/qmldir new file mode 100644 index 00000000..1ba31075 --- /dev/null +++ b/examples/qml/editingmodel/EditingModel/qmldir @@ -0,0 +1,3 @@ +module UsingModel +Main 1.0 Main.qml +MovingRectangle 1.0 MovingRectangle.qml diff --git a/examples/qml/editingmodel/MovingRectangle.qml b/examples/qml/editingmodel/MovingRectangle.qml deleted file mode 100644 index b99a5f4d..00000000 --- a/examples/qml/editingmodel/MovingRectangle.qml +++ /dev/null @@ -1,78 +0,0 @@ -// 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 - } - } - ] -} diff --git a/examples/qml/editingmodel/doc/editingmodel.rst b/examples/qml/editingmodel/doc/editingmodel.rst index 2b45b23f..f045842f 100644 --- a/examples/qml/editingmodel/doc/editingmodel.rst +++ b/examples/qml/editingmodel/doc/editingmodel.rst @@ -3,14 +3,16 @@ QAbstractListModel in QML .. 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 diff --git a/examples/qml/editingmodel/main.py b/examples/qml/editingmodel/main.py index cccbd2b0..cb2696ef 100644 --- a/examples/qml/editingmodel/main.py +++ b/examples/qml/editingmodel/main.py @@ -5,7 +5,6 @@ from __future__ import annotations import sys from pathlib import Path -from PySide6.QtCore import QUrl from PySide6.QtGui import QGuiApplication from PySide6.QtQml import QQmlApplicationEngine @@ -14,8 +13,8 @@ from model import BaseModel # noqa: F401 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) diff --git a/examples/qml/editingmodel/main.pyproject b/examples/qml/editingmodel/main.pyproject index 71272a97..654a781f 100644 --- a/examples/qml/editingmodel/main.pyproject +++ b/examples/qml/editingmodel/main.pyproject @@ -1,3 +1,4 @@ { - "files": ["model.py","main.qml","main.py","MovingRectangle.qml"] + "files": ["main.py", "model.py", "EditingModel/Main.qml", + "EditingModel/MovingRectangle.qml", "EditingModel/qmldir"] } diff --git a/examples/qml/editingmodel/main.qml b/examples/qml/editingmodel/main.qml deleted file mode 100644 index 2318ae8b..00000000 --- a/examples/qml/editingmodel/main.qml +++ /dev/null @@ -1,106 +0,0 @@ -// 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() - } - } - } -} diff --git a/examples/qml/textproperties/TextProperties/Main.qml b/examples/qml/textproperties/TextProperties/Main.qml new file mode 100644 index 00000000..c722eb4e --- /dev/null +++ b/examples/qml/textproperties/TextProperties/Main.qml @@ -0,0 +1,160 @@ +// 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) + } + } + } + } + } +} diff --git a/examples/qml/textproperties/TextProperties/qmldir b/examples/qml/textproperties/TextProperties/qmldir new file mode 100644 index 00000000..2a7c478a --- /dev/null +++ b/examples/qml/textproperties/TextProperties/qmldir @@ -0,0 +1,2 @@ +module Bars +Main 1.0 Main.qml diff --git a/examples/qml/textproperties/doc/textproperties.rst b/examples/qml/textproperties/doc/textproperties.rst index 81829dfd..c8927954 100644 --- a/examples/qml/textproperties/doc/textproperties.rst +++ b/examples/qml/textproperties/doc/textproperties.rst @@ -1,8 +1,9 @@ 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`). .. image:: textproperties.png :width: 400 diff --git a/examples/qml/textproperties/main.py b/examples/qml/textproperties/main.py index 802e9f94..cefa0cc6 100644 --- a/examples/qml/textproperties/main.py +++ b/examples/qml/textproperties/main.py @@ -28,37 +28,24 @@ class Bridge(QObject): 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__': @@ -66,10 +53,8 @@ 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) diff --git a/examples/qml/textproperties/textproperties.pyproject b/examples/qml/textproperties/textproperties.pyproject index e6f087cc..39d17ac7 100644 --- a/examples/qml/textproperties/textproperties.pyproject +++ b/examples/qml/textproperties/textproperties.pyproject @@ -1,3 +1,3 @@ { - "files": ["main.py", "view.qml"] + "files": ["main.py", "TextProperties/Main.qml", "TextProperties/qmldir"] } diff --git a/examples/qml/textproperties/view.qml b/examples/qml/textproperties/view.qml deleted file mode 100644 index c722eb4e..00000000 --- a/examples/qml/textproperties/view.qml +++ /dev/null @@ -1,160 +0,0 @@ -// 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) - } - } - } - } - } -} diff --git a/examples/qml/tutorials/extending-qml-advanced/adding/main.py b/examples/qml/tutorials/extending-qml-advanced/adding/main.py index 4c556c63..419d195c 100644 --- a/examples/qml/tutorials/extending-qml-advanced/adding/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/adding/main.py @@ -21,8 +21,7 @@ if __name__ == '__main__': 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: diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/main.py index c914fe7d..fa2e53fa 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/main.py @@ -37,8 +37,7 @@ for g in range(party.guestCount()): 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}") diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/main.py index eb693134..0efae722 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/main.py @@ -38,8 +38,7 @@ for g in range(party.guestCount()): 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}") diff --git a/examples/qml/tutorials/extending-qml-advanced/binding/main.py b/examples/qml/tutorials/extending-qml-advanced/binding/main.py index 45d0fd3f..f139d8b0 100644 --- a/examples/qml/tutorials/extending-qml-advanced/binding/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/binding/main.py @@ -37,8 +37,7 @@ if __name__ == "__main__": 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}") diff --git a/examples/qml/tutorials/extending-qml-advanced/properties/main.py b/examples/qml/tutorials/extending-qml-advanced/properties/main.py index f481be16..20a6d42f 100644 --- a/examples/qml/tutorials/extending-qml-advanced/properties/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/properties/main.py @@ -22,8 +22,7 @@ if __name__ == '__main__': 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 diff --git a/examples/qml/usingmodel/UsingModel/Main.qml b/examples/qml/usingmodel/UsingModel/Main.qml new file mode 100644 index 00000000..e8b1fb2f --- /dev/null +++ b/examples/qml/usingmodel/UsingModel/Main.qml @@ -0,0 +1,21 @@ +// 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 + } + } + } +} diff --git a/examples/qml/usingmodel/UsingModel/qmldir b/examples/qml/usingmodel/UsingModel/qmldir new file mode 100644 index 00000000..27b7840a --- /dev/null +++ b/examples/qml/usingmodel/UsingModel/qmldir @@ -0,0 +1,2 @@ +module UsingModel +Main 1.0 Main.qml diff --git a/examples/qml/usingmodel/doc/usingmodel.rst b/examples/qml/usingmodel/doc/usingmodel.rst index 06a1b27b..06f442df 100644 --- a/examples/qml/usingmodel/doc/usingmodel.rst +++ b/examples/qml/usingmodel/doc/usingmodel.rst @@ -3,9 +3,9 @@ Using Model Example .. 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 diff --git a/examples/qml/usingmodel/main.py b/examples/qml/usingmodel/main.py new file mode 100644 index 00000000..f96d30fe --- /dev/null +++ b/examples/qml/usingmodel/main.py @@ -0,0 +1,73 @@ +# 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) diff --git a/examples/qml/usingmodel/usingmodel.py b/examples/qml/usingmodel/usingmodel.py deleted file mode 100644 index e73eafef..00000000 --- a/examples/qml/usingmodel/usingmodel.py +++ /dev/null @@ -1,75 +0,0 @@ -# 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) diff --git a/examples/qml/usingmodel/usingmodel.pyproject b/examples/qml/usingmodel/usingmodel.pyproject index 600cdb40..7b45eb7d 100644 --- a/examples/qml/usingmodel/usingmodel.pyproject +++ b/examples/qml/usingmodel/usingmodel.pyproject @@ -1,3 +1,3 @@ { - "files": ["view.qml", "usingmodel.py"] + "files": ["main.py", "UsingModel/Main.qml", "UsingModel/qmldir"] } diff --git a/examples/qml/usingmodel/view.qml b/examples/qml/usingmodel/view.qml deleted file mode 100644 index e8b1fb2f..00000000 --- a/examples/qml/usingmodel/view.qml +++ /dev/null @@ -1,21 +0,0 @@ -// 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 - } - } - } -} diff --git a/examples/quick/models/objectlistmodel/ObjectListModel/Main.qml b/examples/quick/models/objectlistmodel/ObjectListModel/Main.qml new file mode 100644 index 00000000..3cb072dd --- /dev/null +++ b/examples/quick/models/objectlistmodel/ObjectListModel/Main.qml @@ -0,0 +1,21 @@ +// 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 } + } +} diff --git a/examples/quick/models/objectlistmodel/ObjectListModel/qmldir b/examples/quick/models/objectlistmodel/ObjectListModel/qmldir new file mode 100644 index 00000000..6d2da4dc --- /dev/null +++ b/examples/quick/models/objectlistmodel/ObjectListModel/qmldir @@ -0,0 +1,2 @@ +module ObjectListModel +Main 1.0 Main.qml diff --git a/examples/quick/models/objectlistmodel/doc/objectlistmodel.rst b/examples/quick/models/objectlistmodel/doc/objectlistmodel.rst index a4af6270..6d2504d4 100644 --- a/examples/quick/models/objectlistmodel/doc/objectlistmodel.rst +++ b/examples/quick/models/objectlistmodel/doc/objectlistmodel.rst @@ -3,12 +3,12 @@ Object List Model Example .. 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 diff --git a/examples/quick/models/objectlistmodel/main.py b/examples/quick/models/objectlistmodel/main.py new file mode 100644 index 00000000..17d754c5 --- /dev/null +++ b/examples/quick/models/objectlistmodel/main.py @@ -0,0 +1,63 @@ +# 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) diff --git a/examples/quick/models/objectlistmodel/objectlistmodel.py b/examples/quick/models/objectlistmodel/objectlistmodel.py deleted file mode 100644 index 0f0da3e3..00000000 --- a/examples/quick/models/objectlistmodel/objectlistmodel.py +++ /dev/null @@ -1,62 +0,0 @@ -# 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) diff --git a/examples/quick/models/objectlistmodel/objectlistmodel.pyproject b/examples/quick/models/objectlistmodel/objectlistmodel.pyproject index 556e399f..7670cc86 100644 --- a/examples/quick/models/objectlistmodel/objectlistmodel.pyproject +++ b/examples/quick/models/objectlistmodel/objectlistmodel.pyproject @@ -1,3 +1,3 @@ { - "files": ["objectlistmodel.py", "view.qml"] + "files": ["main.py", "ObjectListModel/Main.qml", "ObjectListModel/qmldir"] } diff --git a/examples/quick/models/objectlistmodel/view.qml b/examples/quick/models/objectlistmodel/view.qml deleted file mode 100644 index 3cb072dd..00000000 --- a/examples/quick/models/objectlistmodel/view.qml +++ /dev/null @@ -1,21 +0,0 @@ -// 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 } - } -} diff --git a/examples/quickcontrols/gallery/gallery.py b/examples/quickcontrols/gallery/gallery.py index a7660087..74f28d5a 100644 --- a/examples/quickcontrols/gallery/gallery.py +++ b/examples/quickcontrols/gallery/gallery.py @@ -29,8 +29,7 @@ if __name__ == "__main__": 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() diff --git a/examples/quickcontrols/gallery/gallery.qml b/examples/quickcontrols/gallery/gallery.qml index 4b699038..1decfc69 100644 --- a/examples/quickcontrols/gallery/gallery.qml +++ b/examples/quickcontrols/gallery/gallery.qml @@ -76,6 +76,7 @@ ApplicationWindow { anchors.leftMargin: !window.portraitMode ? drawer.width : undefined ToolButton { + Accessible.name: qsTr("Toggle drawer") action: navigateBackAction visible: window.portraitMode } @@ -92,6 +93,7 @@ ApplicationWindow { ToolButton { action: optionsMenuAction + Accessible.name: qsTr("Open options menu") Menu { id: optionsMenu diff --git a/examples/quickcontrols/gallery/pages/DialPage.qml b/examples/quickcontrols/gallery/pages/DialPage.qml index 2ccd1fa9..2f6d636e 100644 --- a/examples/quickcontrols/gallery/pages/DialPage.qml +++ b/examples/quickcontrols/gallery/pages/DialPage.qml @@ -23,6 +23,7 @@ ScrollablePage { enabled: !GalleryConfig.disabled value: 0.5 anchors.horizontalCenter: parent.horizontalCenter + Accessible.name: qsTr("demo dial") } } } diff --git a/examples/quickcontrols/gallery/pages/ProgressBarPage.qml b/examples/quickcontrols/gallery/pages/ProgressBarPage.qml index 2a3f7158..48c0e2e2 100644 --- a/examples/quickcontrols/gallery/pages/ProgressBarPage.qml +++ b/examples/quickcontrols/gallery/pages/ProgressBarPage.qml @@ -23,11 +23,13 @@ ScrollablePage { 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") } } } diff --git a/examples/quickcontrols/gallery/pages/RangeSliderPage.qml b/examples/quickcontrols/gallery/pages/RangeSliderPage.qml index 83dced76..7f73ce17 100644 --- a/examples/quickcontrols/gallery/pages/RangeSliderPage.qml +++ b/examples/quickcontrols/gallery/pages/RangeSliderPage.qml @@ -23,6 +23,7 @@ ScrollablePage { first.value: 0.25 second.value: 0.75 anchors.horizontalCenter: parent.horizontalCenter + Accessible.name: qsTr("demo range slider") } RangeSlider { @@ -31,6 +32,7 @@ ScrollablePage { first.value: 0.25 second.value: 0.75 anchors.horizontalCenter: parent.horizontalCenter + Accessible.name: qsTr("vertical demo slider") } } } diff --git a/examples/quickcontrols/gallery/pages/SliderPage.qml b/examples/quickcontrols/gallery/pages/SliderPage.qml index b7d83fdf..97e169e8 100644 --- a/examples/quickcontrols/gallery/pages/SliderPage.qml +++ b/examples/quickcontrols/gallery/pages/SliderPage.qml @@ -22,6 +22,7 @@ ScrollablePage { enabled: !GalleryConfig.disabled value: 0.5 anchors.horizontalCenter: parent.horizontalCenter + Accessible.name: qsTr("demo slider") } Slider { @@ -29,6 +30,7 @@ ScrollablePage { orientation: Qt.Vertical value: 0.5 anchors.horizontalCenter: parent.horizontalCenter + Accessible.name: qsTr("vertical demo slider") } } } diff --git a/examples/quickcontrols/gallery/pages/SpinBoxPage.qml b/examples/quickcontrols/gallery/pages/SpinBoxPage.qml index 4536e8a9..6c99c89e 100644 --- a/examples/quickcontrols/gallery/pages/SpinBoxPage.qml +++ b/examples/quickcontrols/gallery/pages/SpinBoxPage.qml @@ -24,6 +24,7 @@ ScrollablePage { value: 50 anchors.horizontalCenter: parent.horizontalCenter editable: true + Accessible.name: qsTr("demo spinbox") } } } diff --git a/examples/quickcontrols/gallery/pages/TextAreaPage.qml b/examples/quickcontrols/gallery/pages/TextAreaPage.qml index d4f3ca0f..c7d3aef7 100644 --- a/examples/quickcontrols/gallery/pages/TextAreaPage.qml +++ b/examples/quickcontrols/gallery/pages/TextAreaPage.qml @@ -25,6 +25,7 @@ ScrollablePage { wrapMode: TextArea.Wrap text: qsTr("TextArea\n...\n...\n...") + Accessible.name: qsTr("demo text area") } } } diff --git a/examples/quickcontrols/gallery/pages/TextFieldPage.qml b/examples/quickcontrols/gallery/pages/TextFieldPage.qml index ba61145e..5ec946f9 100644 --- a/examples/quickcontrols/gallery/pages/TextFieldPage.qml +++ b/examples/quickcontrols/gallery/pages/TextFieldPage.qml @@ -22,6 +22,7 @@ ScrollablePage { enabled: !GalleryConfig.disabled placeholderText: qsTr("TextField") anchors.horizontalCenter: parent.horizontalCenter + Accessible.name: qsTr("demo textfield") } } } diff --git a/examples/quickcontrols/gallery/rc_gallery.py b/examples/quickcontrols/gallery/rc_gallery.py index c7107ae2..f092308b 100644 --- a/examples/quickcontrols/gallery/rc_gallery.py +++ b/examples/quickcontrols/gallery/rc_gallery.py @@ -1,201 +1,204 @@ # 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\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\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\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\xb7E\x80|\x14\ +\xfeA\x8ev\x0fXZ\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{\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\x1aG\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\ @@ -222,7 +225,7 @@ odule App\x0asingle\ ton GalleryConfi\ g 1.0 pages/Gall\ eryConfig.qml\x0a\ -\x00\x00\x03*\ +\x00\x00\x03\x5c\ /\ / Copyright (C) \ 2017 The Qt Comp\ @@ -273,8 +276,11 @@ s.horizontalCent\ 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) \ @@ -494,7 +500,7 @@ Indicator.vertic\ al: ScrollIndica\ tor { }\x0a }\x0a}\x0a\ \ -\x00\x00\x03U\ +\x00\x00\x03\xca\ /\ / Copyright (C) \ 2017 The Qt Comp\ @@ -540,16 +546,23 @@ ressBar {\x0a \ 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) \ @@ -657,7 +670,7 @@ ntalCenter: pare\ nt.horizontalCen\ ter\x0a }\x0a \ }\x0a}\x0a\ -\x00\x00\x02\xd4\ +\x00\x00\x03\x08\ /\ / Copyright (C) \ 2017 The Qt Comp\ @@ -703,8 +716,11 @@ talCenter\x0a\x0a \ 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) \ @@ -792,7 +808,7 @@ JN\xa1\xe7I-2Ca\xf4KHN\x1cn]\ \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\ @@ -833,20 +849,27 @@ fig.disabled\x0a \ 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) \ @@ -1530,7 +1553,7 @@ bled\x0a \ d: true\x0a \ }\x0a }\x0a\ }\x0a}\x0a\ -\x00\x00\x02\x84\ +\x00\x00\x02\xb8\ /\ / Copyright (C) \ 2017 The Qt Comp\ @@ -1571,8 +1594,11 @@ placeholderText:\ 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) \ @@ -1758,7 +1784,7 @@ k\x7f\x06\x1d\xea\x93\x91`\xbfA\xd8Ih\xdc>\xfe\ 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\ @@ -1807,7 +1833,10 @@ alue: 0.5\x0a \ rizontalCenter: \ parent.horizonta\ lCenter\x0a \ -}\x0a }\x0a}\x0a\ + Accessible.n\ +ame: qsTr(\x22demo \ +dial\x22)\x0a }\ +\x0a }\x0a}\x0a\ \x00\x00\x05-\ /\ / Copyright (C) \ @@ -2150,7 +2179,7 @@ heckBox.checked \ ysOn : ScrollBar\ .AsNeeded\x0a }\x0a\ }\x0a\ -\x00\x00\x03\xbf\ +\x00\x00\x040\ /\ / Copyright (C) \ 2017 The Qt Comp\ @@ -2196,22 +2225,29 @@ ond.value: 0.75\x0a\ 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) \ @@ -5166,112 +5202,112 @@ qt_resource_struct = b"\ \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\ @@ -5282,32 +5318,32 @@ qt_resource_struct = b"\ \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(): diff --git a/examples/samplebinding/main.py b/examples/samplebinding/main.py index 125073d2..7195b7e8 100644 --- a/examples/samplebinding/main.py +++ b/examples/samplebinding/main.py @@ -44,9 +44,8 @@ if __name__ == '__main__': 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...") diff --git a/examples/scriptableapplication/mainwindow.h b/examples/scriptableapplication/mainwindow.h index 98af12eb..47d7dff5 100644 --- a/examples/scriptableapplication/mainwindow.h +++ b/examples/scriptableapplication/mainwindow.h @@ -17,8 +17,6 @@ public: void testFunction1(); - static constexpr auto TEST = QLatin1StringView("test"); - private Q_SLOTS: void slotCursorChanged(); void slotRunScript(); diff --git a/examples/webenginequick/nanobrowser/ApplicationRoot.qml b/examples/webenginequick/nanobrowser/ApplicationRoot.qml deleted file mode 100644 index ec571620..00000000 --- a/examples/webenginequick/nanobrowser/ApplicationRoot.qml +++ /dev/null @@ -1,45 +0,0 @@ -// 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; - } -} diff --git a/examples/webenginequick/nanobrowser/BrowserDialog.qml b/examples/webenginequick/nanobrowser/BrowserDialog.qml deleted file mode 100644 index 7af347ec..00000000 --- a/examples/webenginequick/nanobrowser/BrowserDialog.qml +++ /dev/null @@ -1,27 +0,0 @@ -// 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 - } - } -} diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/ApplicationRoot.qml b/examples/webenginequick/nanobrowser/BrowserUtils/ApplicationRoot.qml new file mode 100644 index 00000000..ec571620 --- /dev/null +++ b/examples/webenginequick/nanobrowser/BrowserUtils/ApplicationRoot.qml @@ -0,0 +1,45 @@ +// 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; + } +} diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/BrowserDialog.qml b/examples/webenginequick/nanobrowser/BrowserUtils/BrowserDialog.qml new file mode 100644 index 00000000..7af347ec --- /dev/null +++ b/examples/webenginequick/nanobrowser/BrowserUtils/BrowserDialog.qml @@ -0,0 +1,27 @@ +// 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 + } + } +} diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/BrowserWindow.qml b/examples/webenginequick/nanobrowser/BrowserUtils/BrowserWindow.qml new file mode 100644 index 00000000..c4e28b8e --- /dev/null +++ b/examples/webenginequick/nanobrowser/BrowserUtils/BrowserWindow.qml @@ -0,0 +1,948 @@ +// 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); + } + } +} diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/DownloadView.qml b/examples/webenginequick/nanobrowser/BrowserUtils/DownloadView.qml new file mode 100644 index 00000000..463d146f --- /dev/null +++ b/examples/webenginequick/nanobrowser/BrowserUtils/DownloadView.qml @@ -0,0 +1,137 @@ +// 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; + } + } + } + } +} diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/FindBar.qml b/examples/webenginequick/nanobrowser/BrowserUtils/FindBar.qml new file mode 100644 index 00000000..013f28e8 --- /dev/null +++ b/examples/webenginequick/nanobrowser/BrowserUtils/FindBar.qml @@ -0,0 +1,110 @@ +// 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 + } + } + } +} diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/FullScreenNotification.qml b/examples/webenginequick/nanobrowser/BrowserUtils/FullScreenNotification.qml new file mode 100644 index 00000000..cdf154c7 --- /dev/null +++ b/examples/webenginequick/nanobrowser/BrowserUtils/FullScreenNotification.qml @@ -0,0 +1,62 @@ +// 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!") + } +} diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/WebAuthDialog.qml b/examples/webenginequick/nanobrowser/BrowserUtils/WebAuthDialog.qml new file mode 100644 index 00000000..8d5cf598 --- /dev/null +++ b/examples/webenginequick/nanobrowser/BrowserUtils/WebAuthDialog.qml @@ -0,0 +1,285 @@ +// 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" + } +} diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/qmldir b/examples/webenginequick/nanobrowser/BrowserUtils/qmldir new file mode 100644 index 00000000..d4d0abdc --- /dev/null +++ b/examples/webenginequick/nanobrowser/BrowserUtils/qmldir @@ -0,0 +1,9 @@ +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 diff --git a/examples/webenginequick/nanobrowser/BrowserWindow.qml b/examples/webenginequick/nanobrowser/BrowserWindow.qml deleted file mode 100644 index 474968b8..00000000 --- a/examples/webenginequick/nanobrowser/BrowserWindow.qml +++ /dev/null @@ -1,948 +0,0 @@ -// 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); - } - } -} diff --git a/examples/webenginequick/nanobrowser/DownloadView.qml b/examples/webenginequick/nanobrowser/DownloadView.qml deleted file mode 100644 index ef0c7f5a..00000000 --- a/examples/webenginequick/nanobrowser/DownloadView.qml +++ /dev/null @@ -1,137 +0,0 @@ -// 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; - } - } - } - } -} diff --git a/examples/webenginequick/nanobrowser/FindBar.qml b/examples/webenginequick/nanobrowser/FindBar.qml deleted file mode 100644 index 013f28e8..00000000 --- a/examples/webenginequick/nanobrowser/FindBar.qml +++ /dev/null @@ -1,110 +0,0 @@ -// 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 - } - } - } -} diff --git a/examples/webenginequick/nanobrowser/FullScreenNotification.qml b/examples/webenginequick/nanobrowser/FullScreenNotification.qml deleted file mode 100644 index cdf154c7..00000000 --- a/examples/webenginequick/nanobrowser/FullScreenNotification.qml +++ /dev/null @@ -1,62 +0,0 @@ -// 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!") - } -} diff --git a/examples/webenginequick/nanobrowser/WebAuthDialog.qml b/examples/webenginequick/nanobrowser/WebAuthDialog.qml deleted file mode 100644 index 8d5cf598..00000000 --- a/examples/webenginequick/nanobrowser/WebAuthDialog.qml +++ /dev/null @@ -1,285 +0,0 @@ -// 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" - } -} diff --git a/examples/webenginequick/nanobrowser/doc/nanobrowser.png b/examples/webenginequick/nanobrowser/doc/nanobrowser.png deleted file mode 100644 index 3dddb05d..00000000 Binary files a/examples/webenginequick/nanobrowser/doc/nanobrowser.png and /dev/null differ diff --git a/examples/webenginequick/nanobrowser/doc/nanobrowser.rst b/examples/webenginequick/nanobrowser/doc/nanobrowser.rst index 85098507..a05211f2 100644 --- a/examples/webenginequick/nanobrowser/doc/nanobrowser.rst +++ b/examples/webenginequick/nanobrowser/doc/nanobrowser.rst @@ -1,8 +1,10 @@ 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 diff --git a/examples/webenginequick/nanobrowser/doc/nanobrowser.webp b/examples/webenginequick/nanobrowser/doc/nanobrowser.webp new file mode 100644 index 00000000..076154f8 Binary files /dev/null and b/examples/webenginequick/nanobrowser/doc/nanobrowser.webp differ diff --git a/examples/webenginequick/nanobrowser/main.py b/examples/webenginequick/nanobrowser/main.py new file mode 100644 index 00000000..df0dd5e9 --- /dev/null +++ b/examples/webenginequick/nanobrowser/main.py @@ -0,0 +1,72 @@ +# 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) diff --git a/examples/webenginequick/nanobrowser/nanobrowser.pyproject b/examples/webenginequick/nanobrowser/nanobrowser.pyproject index 392748d6..fb03fd6c 100644 --- a/examples/webenginequick/nanobrowser/nanobrowser.pyproject +++ b/examples/webenginequick/nanobrowser/nanobrowser.pyproject @@ -1,6 +1,7 @@ { - "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"] } diff --git a/examples/webenginequick/nanobrowser/qmldir b/examples/webenginequick/nanobrowser/qmldir index d4d0abdc..036b2e22 100644 --- a/examples/webenginequick/nanobrowser/qmldir +++ b/examples/webenginequick/nanobrowser/qmldir @@ -1,4 +1,5 @@ module BrowserUtils +prefer :/qt/qml/BrowserUtils/ ApplicationRoot 254.0 ApplicationRoot.qml BrowserDialog 254.0 BrowserDialog.qml BrowserWindow 254.0 BrowserWindow.qml diff --git a/examples/webenginequick/nanobrowser/quicknanobrowser.py b/examples/webenginequick/nanobrowser/quicknanobrowser.py deleted file mode 100644 index f68cc2e3..00000000 --- a/examples/webenginequick/nanobrowser/quicknanobrowser.py +++ /dev/null @@ -1,73 +0,0 @@ -# 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) diff --git a/examples/webenginequick/nanobrowser/rc_resources.py b/examples/webenginequick/nanobrowser/rc_resources.py index b517c39a..d831f0c0 100644 --- a/examples/webenginequick/nanobrowser/rc_resources.py +++ b/examples/webenginequick/nanobrowser/rc_resources.py @@ -1,6 +1,6 @@ # 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 @@ -306,6 +306,10 @@ qt_resource_name = b"\ \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\ @@ -327,15 +331,17 @@ qt_resource_name = b"\ 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\ " diff --git a/examples/webenginequick/nanobrowser/resources.qrc b/examples/webenginequick/nanobrowser/resources.qrc index 829285ec..f44bc2bf 100644 --- a/examples/webenginequick/nanobrowser/resources.qrc +++ b/examples/webenginequick/nanobrowser/resources.qrc @@ -1,8 +1,8 @@ - - icons/3rdparty/go-next.png - icons/3rdparty/go-previous.png - icons/3rdparty/process-stop.png - icons/3rdparty/view-refresh.png + + icons/3rdparty/go-next.png + icons/3rdparty/go-previous.png + icons/3rdparty/process-stop.png + icons/3rdparty/view-refresh.png diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.py b/examples/webenginewidgets/simplebrowser/browserwindow.py index ad28304e..1d27fcbc 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.py +++ b/examples/webenginewidgets/simplebrowser/browserwindow.py @@ -204,20 +204,17 @@ class BrowserWindow(QMainWindow): @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() @@ -476,8 +473,7 @@ class BrowserWindow(QMainWindow): 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() diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.py b/examples/webenginewidgets/simplebrowser/tabwidget.py index 4a5a933b..3e931b86 100644 --- a/examples/webenginewidgets/simplebrowser/tabwidget.py +++ b/examples/webenginewidgets/simplebrowser/tabwidget.py @@ -196,8 +196,7 @@ class TabWidget(QTabWidget): 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: @@ -207,20 +206,17 @@ class TabWidget(QTabWidget): 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() @@ -237,6 +233,5 @@ class TabWidget(QTabWidget): self.setCurrentIndex(next) def reload_tab(self, index): - view = self.web_view(index) - if view: + if view := self.web_view(index): view.reload() diff --git a/examples/webenginewidgets/simplebrowser/webpopupwindow.py b/examples/webenginewidgets/simplebrowser/webpopupwindow.py index 5b5ed173..68e0186f 100644 --- a/examples/webenginewidgets/simplebrowser/webpopupwindow.py +++ b/examples/webenginewidgets/simplebrowser/webpopupwindow.py @@ -46,8 +46,7 @@ class WebPopupWindow(QWidget): 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() diff --git a/examples/webview/minibrowser/main.qml b/examples/webview/minibrowser/main.qml index f647c626..e30a0027 100644 --- a/examples/webview/minibrowser/main.qml +++ b/examples/webview/minibrowser/main.qml @@ -21,6 +21,7 @@ ApplicationWindow { ToolButton { id: backButton icon.source: "qrc:/left-32.png" + icon.color: "transparent" onClicked: webView.goBack() enabled: webView.canGoBack Layout.preferredWidth: navigationBar.height @@ -29,6 +30,7 @@ ApplicationWindow { ToolButton { id: forwardButton icon.source: "qrc:/right-32.png" + icon.color: "transparent" onClicked: webView.goForward() enabled: webView.canGoForward Layout.preferredWidth: navigationBar.height @@ -37,6 +39,7 @@ ApplicationWindow { 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 } diff --git a/examples/webview/minibrowser/qml.qrc b/examples/webview/minibrowser/qml.qrc index f3604716..49e81fd5 100644 --- a/examples/webview/minibrowser/qml.qrc +++ b/examples/webview/minibrowser/qml.qrc @@ -5,5 +5,6 @@ images/stop-32.png images/refresh-32.png images/right-32.png + images/settings-32.png diff --git a/examples/webview/minibrowser/rc_qml.py b/examples/webview/minibrowser/rc_qml.py index 104eadba..39db3b02 100644 --- a/examples/webview/minibrowser/rc_qml.py +++ b/examples/webview/minibrowser/rc_qml.py @@ -1,6 +1,6 @@ # 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 @@ -186,85 +186,458 @@ C\xaa]\x15\x0d\xf4\x5c{\x8d\xddV\xb0\x8cb\x00v\ \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\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\xe36\ +~\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\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\x0a\x0a \x0a \x0a \x0a \ + \x0a \ + \x0a\ + \x0a \ + \x0a \x0a \x0a\ +\x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a\ + \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \ +\x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a\ + \ + \ + \ + \ + \ + \ + \x0a \ + \ +\x0a=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\ @@ -334,6 +707,10 @@ qt_resource_name = b"\ \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\ @@ -345,18 +722,20 @@ qt_resource_name = b"\ " 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(): diff --git a/examples/widgets/desktop/screenshot/screenshot.py b/examples/widgets/desktop/screenshot/screenshot.py index fba1f71b..bbeacfc8 100644 --- a/examples/widgets/desktop/screenshot/screenshot.py +++ b/examples/widgets/desktop/screenshot/screenshot.py @@ -8,9 +8,9 @@ import sys 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) @@ -29,6 +29,7 @@ class Screenshot(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) @@ -53,9 +54,9 @@ class Screenshot(QWidget): 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) @@ -70,10 +71,11 @@ class Screenshot(QWidget): 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): @@ -115,17 +117,10 @@ class Screenshot(QWidget): ) 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) @@ -141,13 +136,18 @@ class Screenshot(QWidget): 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__": diff --git a/examples/widgets/dialogs/classwizard/classwizard.py b/examples/widgets/dialogs/classwizard/classwizard.py index d7014313..1fcd5ae3 100644 --- a/examples/widgets/dialogs/classwizard/classwizard.py +++ b/examples/widgets/dialogs/classwizard/classwizard.py @@ -95,8 +95,7 @@ class ClassWizard(QWizard): 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: @@ -114,8 +113,7 @@ class ClassWizard(QWizard): 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 = [] @@ -130,8 +128,7 @@ class ClassWizard(QWizard): 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 = [] @@ -160,8 +157,7 @@ class ClassWizard(QWizard): 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: diff --git a/examples/widgets/dialogs/classwizard/listchooser.py b/examples/widgets/dialogs/classwizard/listchooser.py index f7239ae3..8f3445f0 100644 --- a/examples/widgets/dialogs/classwizard/listchooser.py +++ b/examples/widgets/dialogs/classwizard/listchooser.py @@ -133,8 +133,7 @@ class ListChooser(QGroupBox): @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): diff --git a/examples/widgets/dialogs/standarddialogs/standarddialogs.py b/examples/widgets/dialogs/standarddialogs/standarddialogs.py index 541aab30..fa773cfc 100644 --- a/examples/widgets/dialogs/standarddialogs/standarddialogs.py +++ b/examples/widgets/dialogs/standarddialogs/standarddialogs.py @@ -286,9 +286,8 @@ class Dialog(QDialog): 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) diff --git a/examples/widgets/gettext/main.py b/examples/widgets/gettext/main.py index 6710a236..a1cd7564 100644 --- a/examples/widgets/gettext/main.py +++ b/examples/widgets/gettext/main.py @@ -44,8 +44,7 @@ if __name__ == '__main__': 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 diff --git a/examples/widgets/itemviews/editabletreemodel/mainwindow.py b/examples/widgets/itemviews/editabletreemodel/mainwindow.py index e33220e8..6bf98cdd 100644 --- a/examples/widgets/itemviews/editabletreemodel/mainwindow.py +++ b/examples/widgets/itemviews/editabletreemodel/mainwindow.py @@ -106,8 +106,7 @@ class MainWindow(QMainWindow): 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) diff --git a/examples/widgets/itemviews/editabletreemodel/treemodel.py b/examples/widgets/itemviews/editabletreemodel/treemodel.py index 98e4d475..dd7c8a51 100644 --- a/examples/widgets/itemviews/editabletreemodel/treemodel.py +++ b/examples/widgets/itemviews/editabletreemodel/treemodel.py @@ -59,8 +59,7 @@ class TreeModel(QAbstractItemModel): 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() @@ -89,8 +88,7 @@ class TreeModel(QAbstractItemModel): 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 diff --git a/examples/widgets/itemviews/spreadsheet/spreadsheet.py b/examples/widgets/itemviews/spreadsheet/spreadsheet.py index 88357ae1..34dfb5ac 100644 --- a/examples/widgets/itemviews/spreadsheet/spreadsheet.py +++ b/examples/widgets/itemviews/spreadsheet/spreadsheet.py @@ -341,9 +341,7 @@ class SpreadSheet(QMainWindow): 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) @@ -364,8 +362,7 @@ class SpreadSheet(QMainWindow): 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): diff --git a/examples/widgets/itemviews/spreadsheet/spreadsheetdelegate.py b/examples/widgets/itemviews/spreadsheet/spreadsheetdelegate.py index a6d81223..d2fcec4d 100644 --- a/examples/widgets/itemviews/spreadsheet/spreadsheetdelegate.py +++ b/examples/widgets/itemviews/spreadsheet/spreadsheetdelegate.py @@ -57,11 +57,9 @@ class SpreadSheetDelegate(QStyledItemDelegate): 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")) diff --git a/examples/widgets/mainwindows/mdi/mdi.py b/examples/widgets/mainwindows/mdi/mdi.py index db3abe34..e5edfc8d 100644 --- a/examples/widgets/mainwindows/mdi/mdi.py +++ b/examples/widgets/mainwindows/mdi/mdi.py @@ -165,8 +165,7 @@ class MainWindow(QMainWindow): 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) @@ -399,8 +398,7 @@ class MainWindow(QMainWindow): 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 diff --git a/examples/widgets/painting/painter/painter.py b/examples/widgets/painting/painter/painter.py index 4b009b30..4b638887 100644 --- a/examples/widgets/painting/painter/painter.py +++ b/examples/widgets/painting/painter/painter.py @@ -178,10 +178,7 @@ class MainWindow(QMainWindow): @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): diff --git a/examples/widgets/richtext/textedit/textedit.py b/examples/widgets/richtext/textedit/textedit.py index 8cd330a7..20ced2c5 100644 --- a/examples/widgets/richtext/textedit/textedit.py +++ b/examples/widgets/richtext/textedit/textedit.py @@ -188,8 +188,7 @@ class TextEdit(QMainWindow): 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): @@ -648,9 +647,8 @@ class TextEdit(QMainWindow): @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: diff --git a/sources/pyside-tools/deploy_lib/__init__.py b/sources/pyside-tools/deploy_lib/__init__.py index 3f6190b1..0f1f45ac 100644 --- a/sources/pyside-tools/deploy_lib/__init__.py +++ b/sources/pyside-tools/deploy_lib/__init__.py @@ -46,7 +46,8 @@ HELP_EXTRA_MODULES = dedent(""" # 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(): diff --git a/sources/pyside-tools/project.py b/sources/pyside-tools/project.py index 762e76f3..edc8813a 100644 --- a/sources/pyside-tools/project.py +++ b/sources/pyside-tools/project.py @@ -5,7 +5,7 @@ from __future__ import annotations 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, @@ -186,10 +186,11 @@ class Project: 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): @@ -266,11 +267,13 @@ class Project: 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: @@ -301,7 +304,7 @@ def main(mode: str = None, dry_run: bool = False, quiet: bool = False, force: bo 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": @@ -340,9 +343,7 @@ if __name__ == "__main__": 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()) diff --git a/sources/pyside6/.cmake.conf b/sources/pyside6/.cmake.conf index ffe43a7b..60d72e84 100644 --- a/sources/pyside6/.cmake.conf +++ b/sources/pyside6/.cmake.conf @@ -1,5 +1,5 @@ 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 "") diff --git a/sources/pyside6/PySide6/QtHttpServer/CMakeLists.txt b/sources/pyside6/PySide6/QtHttpServer/CMakeLists.txt index b2a4fda8..f5fc048a 100644 --- a/sources/pyside6/PySide6/QtHttpServer/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtHttpServer/CMakeLists.txt @@ -5,6 +5,7 @@ project(QtHttpServer) 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 diff --git a/sources/pyside6/PySide6/QtHttpServer/typesystem_httpserver.xml b/sources/pyside6/PySide6/QtHttpServer/typesystem_httpserver.xml index 66b2ff9e..f923d194 100644 --- a/sources/pyside6/PySide6/QtHttpServer/typesystem_httpserver.xml +++ b/sources/pyside6/PySide6/QtHttpServer/typesystem_httpserver.xml @@ -26,6 +26,7 @@ snippet="qhttpserver-addafterrequesthandler"/> + diff --git a/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml b/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml index 3c15f4ee..ddaa5e42 100644 --- a/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml +++ b/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml @@ -52,7 +52,7 @@ - + diff --git a/sources/pyside6/doc/building_from_source/index.rst b/sources/pyside6/doc/building_from_source/index.rst index a2014372..2b8a42d0 100644 --- a/sources/pyside6/doc/building_from_source/index.rst +++ b/sources/pyside6/doc/building_from_source/index.rst @@ -28,10 +28,10 @@ you might get them with ``brew``, and on **Windows** you can download the instal website. * **Python**: 3.9+ `[official Python website] `_ -* **Qt:** 6.8+ `[online installer] `_ +* **Qt:** 6.10+ `[online installer] `_ * **CMake:** 3.18+ `[official CMake website] `_ * **Git:** 2.0+. `[official Git website] `_ -* **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`_ @@ -128,7 +128,7 @@ Using the ``--build-tests`` option will enable us to run all the auto tests insi 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:: @@ -177,12 +177,12 @@ First and foremost, you need to have access to the target device because you 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 @@ -354,7 +354,7 @@ An example of the ``setup.py`` invocation might look like the following: 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``, @@ -369,8 +369,8 @@ directory, for example: .. 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 @@ -536,7 +536,7 @@ A set of tools can be found under the ``tools/`` directory inside the ``pyside-s * ``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. @@ -558,7 +558,7 @@ A set of tools can be found under the ``tools/`` directory inside the ``pyside-s 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. diff --git a/sources/pyside6/doc/building_from_source/linux.rst b/sources/pyside6/doc/building_from_source/linux.rst index 80d56f44..4ef0df9a 100644 --- a/sources/pyside6/doc/building_from_source/linux.rst +++ b/sources/pyside6/doc/building_from_source/linux.rst @@ -56,9 +56,9 @@ Cloning the official repository can be done by:: 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:: @@ -69,7 +69,7 @@ For building the documentation:: 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 ...` @@ -82,15 +82,15 @@ uses `create_wheels.py`_ to create the wheels. Once the wheels are created, you 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:: diff --git a/sources/pyside6/doc/building_from_source/macOS.rst b/sources/pyside6/doc/building_from_source/macOS.rst index 0e10d6cf..051eb6cf 100644 --- a/sources/pyside6/doc/building_from_source/macOS.rst +++ b/sources/pyside6/doc/building_from_source/macOS.rst @@ -60,9 +60,9 @@ Cloning the official repository can be done by:: 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:: @@ -80,15 +80,15 @@ Building PySide ~~~~~~~~~~~~~~~ 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 diff --git a/sources/pyside6/doc/building_from_source/windows.rst b/sources/pyside6/doc/building_from_source/windows.rst index 0732953d..c4ca5ab4 100644 --- a/sources/pyside6/doc/building_from_source/windows.rst +++ b/sources/pyside6/doc/building_from_source/windows.rst @@ -77,9 +77,9 @@ Cloning the official repository can be done by:: 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:: @@ -97,7 +97,7 @@ Building PySide ~~~~~~~~~~~~~~~ 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:: diff --git a/sources/pyside6/doc/commercial/index.rst b/sources/pyside6/doc/commercial/index.rst index d3062629..de3da932 100644 --- a/sources/pyside6/doc/commercial/index.rst +++ b/sources/pyside6/doc/commercial/index.rst @@ -107,7 +107,8 @@ be installed into a virtual environment rather than the default interpreter. 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. @@ -133,8 +134,7 @@ There are two options that will list a different set of packages: 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: @@ -170,7 +170,7 @@ Alternatively for Windows, you can specify the following command which includes 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`` diff --git a/sources/pyside6/doc/deployment/deployment-pyside6-android-deploy.rst b/sources/pyside6/doc/deployment/deployment-pyside6-android-deploy.rst index 7d2064c7..0ce9717a 100644 --- a/sources/pyside6/doc/deployment/deployment-pyside6-android-deploy.rst +++ b/sources/pyside6/doc/deployment/deployment-pyside6-android-deploy.rst @@ -55,16 +55,16 @@ directory as a directory called ``.pyside6-android-deploy``. ``pyside6-android-d 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 `_. -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" @@ -268,10 +268,10 @@ to cross-compile Qt for Python Android wheels. 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:: @@ -280,10 +280,10 @@ to cross-compile Qt for Python Android wheels. #. 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:: diff --git a/sources/pyside6/doc/developer/signalmanager.md b/sources/pyside6/doc/developer/signalmanager.md index 1161f6d5..bba12183 100644 --- a/sources/pyside6/doc/developer/signalmanager.md +++ b/sources/pyside6/doc/developer/signalmanager.md @@ -39,10 +39,10 @@ kept for `self`. ## 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 diff --git a/sources/pyside6/doc/extras/QtQuick.rst b/sources/pyside6/doc/extras/QtQuick.rst index 1aa38500..4db151bb 100644 --- a/sources/pyside6/doc/extras/QtQuick.rst +++ b/sources/pyside6/doc/extras/QtQuick.rst @@ -14,8 +14,9 @@ QML applications with Python code. .. 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 ` +for an introduction to writing QML applications. Using the Module ^^^^^^^^^^^^^^^^ diff --git a/sources/pyside6/doc/faq/whatisshiboken.rst b/sources/pyside6/doc/faq/whatisshiboken.rst index bf6d19ab..55e1e7d9 100644 --- a/sources/pyside6/doc/faq/whatisshiboken.rst +++ b/sources/pyside6/doc/faq/whatisshiboken.rst @@ -5,16 +5,17 @@ Binding Generation: What Is Shiboken? 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. diff --git a/sources/pyside6/doc/gettingstarted.rst b/sources/pyside6/doc/gettingstarted.rst index 9f4b97f8..ef2d2c05 100644 --- a/sources/pyside6/doc/gettingstarted.rst +++ b/sources/pyside6/doc/gettingstarted.rst @@ -15,7 +15,7 @@ Requirements Before you can install |project|, first you must install the following software: -* `Official `_ Python 3.8+ +* `Official `_ Python 3.9+ * We **highly** recommend using a virtual environment, such as `venv `_ or `virtualenv `_ @@ -61,14 +61,14 @@ Installation 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** diff --git a/sources/pyside6/doc/index.rst b/sources/pyside6/doc/index.rst index 8c614978..a6f7b2cd 100644 --- a/sources/pyside6/doc/index.rst +++ b/sources/pyside6/doc/index.rst @@ -29,7 +29,7 @@ PySide2 applications. 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 =========== diff --git a/sources/pyside6/doc/modules.rst b/sources/pyside6/doc/modules.rst index d3e4f7bb..46b34a8e 100644 --- a/sources/pyside6/doc/modules.rst +++ b/sources/pyside6/doc/modules.rst @@ -16,8 +16,8 @@ Qt Modules Supported by Qt for Python .. grid-item-card:: :mod:`Qt Charts ` - 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 `. + UI Components for displaying charts. .. grid-item-card:: :mod:`Qt Concurrent ` @@ -30,7 +30,8 @@ Qt Modules Supported by Qt for Python .. grid-item-card:: :mod:`Qt Data Visualization ` - UI Components for creating stunning 3D data visualizations. + Deprecated since Qt 6.10 in favor of :mod:`Qt Graphs `. + UI Components for creating 3D data visualizations. .. grid-item-card:: :mod:`Qt D-Bus ` diff --git a/sources/pyside6/doc/qtattributionsscannertorst.py b/sources/pyside6/doc/qtattributionsscannertorst.py index 1a46cb03..a030a435 100644 --- a/sources/pyside6/doc/qtattributionsscannertorst.py +++ b/sources/pyside6/doc/qtattributionsscannertorst.py @@ -47,7 +47,7 @@ Third-party Licenses The licenses for the third-party sources used by Qt itself are listed in -`Qt documentation `_. +`Qt documentation `_. The following table lists parts of Qt for Python that incorporates code licensed under third-party opensource licenses: diff --git a/sources/pyside6/doc/tools/pyside-project.rst b/sources/pyside6/doc/tools/pyside-project.rst index c6913f36..4b65b06a 100644 --- a/sources/pyside6/doc/tools/pyside-project.rst +++ b/sources/pyside6/doc/tools/pyside-project.rst @@ -75,7 +75,8 @@ It is also possible to specify a directory containing the project file. (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`). diff --git a/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst b/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst index 2666f854..32025f00 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst @@ -177,4 +177,4 @@ Run the application by calling ``python main.py`` to checkout the new icon-set: .. 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 diff --git a/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst b/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst index c1f56df2..30aa3c22 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst @@ -8,10 +8,10 @@ Due to the nature of Qt, :class:`~PySide6.QtCore.QObject`\s 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 @@ -51,9 +51,9 @@ has a ``clicked()`` signal and :class:`~PySide6.QtWidgets.QLineEdit` (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 @@ -130,7 +130,6 @@ as the variable that it is being assigned to. .. code-block:: python - # TODO signal5 = Signal(int, name='rangeChanged') # ... rangeChanged.emit(...) @@ -179,8 +178,8 @@ due to the method being added to the ``QMetaObject`` when creating the connectio 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 @@ -211,9 +210,10 @@ main thread. 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. @@ -272,9 +272,10 @@ strings passed through the ``SIGNAL()`` and/or ``SLOT()`` functions: 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 diff --git a/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst index 6d17f039..335d3c3b 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst @@ -5,20 +5,19 @@ Using ``.ui`` files from Designer or QtCreator with ``QUiLoader`` and ``pyside6- ************************************************************************************* This page describes the use of -`Qt Widgets Designer `_ 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 `_. Its use within *Qt Creator* -is described at -`Using Qt Widgets Designer `_. +`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 `_ 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 @@ -140,6 +139,32 @@ file: 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_.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 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 ============================= @@ -337,3 +362,10 @@ Troubleshooting the Qt Widgets Designer Plugin 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 diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.rst b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.rst index 8f9e3ae9..08bb7a01 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.rst @@ -80,8 +80,8 @@ More examples can be found in the `Qt Style Sheet Examples`_ documentation 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 --------------- diff --git a/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst b/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst index 9073e679..637082a4 100644 --- a/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst +++ b/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst @@ -5,7 +5,7 @@ Chapter 3 - Create an empty QMainWindow ========================================== 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: @@ -25,8 +25,8 @@ the resolution you currently have. In the following snippet, you will see how 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 diff --git a/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst b/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst index b3041349..3d8f20ab 100644 --- a/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst +++ b/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst @@ -9,16 +9,16 @@ interface. Usually, a QWidget is used to display data in most data-driven 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 diff --git a/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst b/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst index 4edde69c..c5c8cf5e 100644 --- a/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst +++ b/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst @@ -18,7 +18,7 @@ avoid faulty data or unexpected behavior. 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. diff --git a/sources/pyside6/doc/tutorials/expenses/expenses.rst b/sources/pyside6/doc/tutorials/expenses/expenses.rst index 22723af1..8edada0e 100644 --- a/sources/pyside6/doc/tutorials/expenses/expenses.rst +++ b/sources/pyside6/doc/tutorials/expenses/expenses.rst @@ -76,7 +76,7 @@ Empty widget and data --------------------- The `QMainWindow` enables us to set a central widget that will be displayed when showing the window -(`read more `_). +(`read more `_). This central widget could be another class derived from `QWidget`. Additionally, you will define example data to visualize later. diff --git a/sources/pyside6/doc/tutorials/qmlintegration/qmlintegration.rst b/sources/pyside6/doc/tutorials/qmlintegration/qmlintegration.rst index 1caf18d4..f47e8800 100644 --- a/sources/pyside6/doc/tutorials/qmlintegration/qmlintegration.rst +++ b/sources/pyside6/doc/tutorials/qmlintegration/qmlintegration.rst @@ -127,7 +127,7 @@ application and PySide6 integration: :emphasize-lines: 8 You can read more about this configuration file - `here `_. + `here `_. The final look of your application will be: diff --git a/sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst b/sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst index 85db25de..e025f718 100644 --- a/sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst +++ b/sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst @@ -126,18 +126,18 @@ so this results in each item consuming the entire width of the layout. 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: @@ -183,9 +183,9 @@ Lastly, we have a button that allows us to call the ``send_message`` method we d ``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: diff --git a/sources/pyside6/libpyside/pysideslot.cpp b/sources/pyside6/libpyside/pysideslot.cpp index beb8a670..9698446d 100644 --- a/sources/pyside6/libpyside/pysideslot.cpp +++ b/sources/pyside6/libpyside/pysideslot.cpp @@ -173,7 +173,7 @@ DataList *dataListFromCapsule(PyObject *capsule) 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) diff --git a/sources/pyside6/libpyside/signalmanager.cpp b/sources/pyside6/libpyside/signalmanager.cpp index d8fd4118..07c1f116 100644 --- a/sources/pyside6/libpyside/signalmanager.cpp +++ b/sources/pyside6/libpyside/signalmanager.cpp @@ -116,7 +116,7 @@ PyObjectWrapper::PyObjectWrapper() { // 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); } diff --git a/sources/pyside6/tests/QtCore/attr_cache_py3k.py b/sources/pyside6/tests/QtCore/attr_cache_py3k.py index edcfe08e..be817869 100644 --- a/sources/pyside6/tests/QtCore/attr_cache_py3k.py +++ b/sources/pyside6/tests/QtCore/attr_cache_py3k.py @@ -5,8 +5,7 @@ from __future__ import annotations ''' 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 diff --git a/sources/pyside6/tests/QtCore/emoji_string_test.py b/sources/pyside6/tests/QtCore/emoji_string_test.py index e8d4864d..94f7c419 100644 --- a/sources/pyside6/tests/QtCore/emoji_string_test.py +++ b/sources/pyside6/tests/QtCore/emoji_string_test.py @@ -5,8 +5,7 @@ from __future__ import annotations """ 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 diff --git a/sources/pyside6/tests/QtTextToSpeech/qtexttospeech_test.py b/sources/pyside6/tests/QtTextToSpeech/qtexttospeech_test.py index 5624ba76..53374588 100644 --- a/sources/pyside6/tests/QtTextToSpeech/qtexttospeech_test.py +++ b/sources/pyside6/tests/QtTextToSpeech/qtexttospeech_test.py @@ -17,6 +17,8 @@ init_test_paths(False) 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 @@ -33,6 +35,8 @@ class QTextToSpeechTestCase(UsesQApplication): 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) diff --git a/sources/pyside6/tests/QtWidgets/private_mangle_test.py b/sources/pyside6/tests/QtWidgets/private_mangle_test.py index a6b2574d..e02efd46 100644 --- a/sources/pyside6/tests/QtWidgets/private_mangle_test.py +++ b/sources/pyside6/tests/QtWidgets/private_mangle_test.py @@ -3,8 +3,7 @@ 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 diff --git a/sources/pyside6/tests/manually/README.txt b/sources/pyside6/tests/manually/README.txt index da20e382..37fd4cad 100644 --- a/sources/pyside6/tests/manually/README.txt +++ b/sources/pyside6/tests/manually/README.txt @@ -1,5 +1,5 @@ -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 diff --git a/sources/pyside6/tests/pysidetest/mixin_signal_slots_test.py b/sources/pyside6/tests/pysidetest/mixin_signal_slots_test.py index 7e1ddbb1..e4f0ee22 100644 --- a/sources/pyside6/tests/pysidetest/mixin_signal_slots_test.py +++ b/sources/pyside6/tests/pysidetest/mixin_signal_slots_test.py @@ -3,8 +3,7 @@ # 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. ''' diff --git a/sources/pyside6/tests/pysidetest/mock_as_slot_test.py b/sources/pyside6/tests/pysidetest/mock_as_slot_test.py index f6504a78..c8435b95 100644 --- a/sources/pyside6/tests/pysidetest/mock_as_slot_test.py +++ b/sources/pyside6/tests/pysidetest/mock_as_slot_test.py @@ -3,8 +3,7 @@ # 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 diff --git a/sources/pyside6/tests/pysidetest/qapp_like_a_macro_test.py b/sources/pyside6/tests/pysidetest/qapp_like_a_macro_test.py index 0d1ae375..875bd642 100644 --- a/sources/pyside6/tests/pysidetest/qapp_like_a_macro_test.py +++ b/sources/pyside6/tests/pysidetest/qapp_like_a_macro_test.py @@ -52,7 +52,7 @@ class qAppMacroTest(unittest.TestCase): 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 diff --git a/sources/pyside6/tests/pysidetest/signal_slot_warning.py b/sources/pyside6/tests/pysidetest/signal_slot_warning.py index e087333c..7711230f 100644 --- a/sources/pyside6/tests/pysidetest/signal_slot_warning.py +++ b/sources/pyside6/tests/pysidetest/signal_slot_warning.py @@ -3,8 +3,7 @@ # 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 diff --git a/sources/pyside6/tests/pysidetest/signal_tp_descr_get_test.py b/sources/pyside6/tests/pysidetest/signal_tp_descr_get_test.py index e9be966c..e45e3290 100644 --- a/sources/pyside6/tests/pysidetest/signal_tp_descr_get_test.py +++ b/sources/pyside6/tests/pysidetest/signal_tp_descr_get_test.py @@ -9,8 +9,7 @@ PYSIDE-68: Test that signals have a `__get__` function after all. 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 diff --git a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py index a4d41275..9334e000 100644 --- a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py +++ b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py @@ -264,6 +264,7 @@ class TestPySide6DeployQml(DeployTestBase): "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" @@ -271,9 +272,7 @@ class TestPySide6DeployQml(DeployTestBase): 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": @@ -318,8 +317,8 @@ class TestPySide6DeployQml(DeployTestBase): 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) @@ -384,13 +383,13 @@ class TestPySide6DeployWebEngine(DeployTestBase): # 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( [ @@ -500,7 +499,7 @@ class TestLongCommand(DeployTestBase): 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, diff --git a/sources/shiboken6/.cmake.conf b/sources/shiboken6/.cmake.conf index 7bd76c7c..b2ed939d 100644 --- a/sources/shiboken6/.cmake.conf +++ b/sources/shiboken6/.cmake.conf @@ -1,5 +1,5 @@ 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 "") diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp index 4c63c3e8..fe5af06f 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp +++ b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp @@ -249,11 +249,6 @@ static CXTranslationUnit createTranslationUnit(CXIndex index, // 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 }; diff --git a/sources/shiboken6/doc/gettingstarted.rst b/sources/shiboken6/doc/gettingstarted.rst index cad49086..552c39a6 100644 --- a/sources/shiboken6/doc/gettingstarted.rst +++ b/sources/shiboken6/doc/gettingstarted.rst @@ -11,11 +11,11 @@ need to continue if you already have a built PySide. 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/ diff --git a/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp index 3875c53f..5ff16afe 100644 --- a/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp +++ b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp @@ -909,6 +909,8 @@ QString QtDocGenerator::translateToPythonType(const AbstractMetaType &type, 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)) diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp index 36f34de5..f6f69544 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp @@ -3799,9 +3799,9 @@ void CppGenerator::writeNamedArgumentResolution(TextStream &s, 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 diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py index cca7ae74..4bedf3f6 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py @@ -166,6 +166,8 @@ class Formatter(Writer): # 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") @@ -176,6 +178,9 @@ class Formatter(Writer): # 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() @@ -415,6 +420,8 @@ def generate_pyi(import_name, outpath, options): 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) diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py index 9d1c3fd5..c0fdf1c3 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py @@ -55,6 +55,8 @@ NoneType = type(None) # 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]] diff --git a/sources/shiboken6/tests/libsample/functions.cpp b/sources/shiboken6/tests/libsample/functions.cpp index ad2f4dd5..bf8fcc3f 100644 --- a/sources/shiboken6/tests/libsample/functions.cpp +++ b/sources/shiboken6/tests/libsample/functions.cpp @@ -89,6 +89,11 @@ Event *returnNullValueTypePointer() return nullptr; } +int asciiCode(char character) +{ + return character; +} + unsigned int doubleUnsignedInt(unsigned int value) { return value * 2; diff --git a/sources/shiboken6/tests/libsample/functions.h b/sources/shiboken6/tests/libsample/functions.h index ef88e543..446dfb64 100644 --- a/sources/shiboken6/tests/libsample/functions.h +++ b/sources/shiboken6/tests/libsample/functions.h @@ -47,6 +47,7 @@ LIBSAMPLE_API Event *returnNullValueTypePointer(); 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); diff --git a/sources/shiboken6/tests/libsample/polygon.cpp b/sources/shiboken6/tests/libsample/polygon.cpp index 6af59719..838f1a3e 100644 --- a/sources/shiboken6/tests/libsample/polygon.cpp +++ b/sources/shiboken6/tests/libsample/polygon.cpp @@ -3,6 +3,8 @@ #include "polygon.h" +#include + Polygon::Polygon(double x, double y) : m_points({Point(x, y)}) { } @@ -37,3 +39,8 @@ void Polygon::stealOwnershipFromPython(Polygon *polygon) { delete polygon; } + +bool Polygon::contains(Point needle) const +{ + return std::find(m_points.cbegin(), m_points.cend(), needle) != m_points.cend(); +} diff --git a/sources/shiboken6/tests/libsample/polygon.h b/sources/shiboken6/tests/libsample/polygon.h index 2424ddd5..ff07122c 100644 --- a/sources/shiboken6/tests/libsample/polygon.h +++ b/sources/shiboken6/tests/libsample/polygon.h @@ -32,6 +32,8 @@ public: // 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; }; diff --git a/sources/shiboken6/tests/samplebinding/decisor_test.py b/sources/shiboken6/tests/samplebinding/decisor_test.py index 9aeeeed2..9b336992 100644 --- a/sources/shiboken6/tests/samplebinding/decisor_test.py +++ b/sources/shiboken6/tests/samplebinding/decisor_test.py @@ -14,7 +14,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) 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): @@ -48,6 +48,13 @@ 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() diff --git a/sources/shiboken6/tests/samplebinding/sample_test.py b/sources/shiboken6/tests/samplebinding/sample_test.py index 43b84d36..8b3559da 100644 --- a/sources/shiboken6/tests/samplebinding/sample_test.py +++ b/sources/shiboken6/tests/samplebinding/sample_test.py @@ -86,6 +86,16 @@ class ModuleTest(unittest.TestCase): 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() diff --git a/sources/shiboken6/tests/samplebinding/samplesnippets.cpp b/sources/shiboken6/tests/samplebinding/samplesnippets.cpp index a6cd6e05..19d86e9f 100644 --- a/sources/shiboken6/tests/samplebinding/samplesnippets.cpp +++ b/sources/shiboken6/tests/samplebinding/samplesnippets.cpp @@ -302,3 +302,8 @@ PyObject *type = PyObject_Type(%PYSELF); 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 diff --git a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml index 73f2e142..0655da80 100644 --- a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml +++ b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml @@ -356,6 +356,7 @@ + @@ -1657,6 +1658,10 @@ + + + diff --git a/wheel_artifacts/pyproject.toml.base b/wheel_artifacts/pyproject.toml.base index b652f3e2..dd27eb75 100644 --- a/wheel_artifacts/pyproject.toml.base +++ b/wheel_artifacts/pyproject.toml.base @@ -48,7 +48,7 @@ Homepage = "https://pyside.org" 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