aboutsummaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/bluetooth/btscanner/btscanner.pyproject3
-rw-r--r--examples/bluetooth/btscanner/device.py166
-rw-r--r--examples/bluetooth/btscanner/device.ui111
-rw-r--r--examples/bluetooth/btscanner/doc/btscanner.rst4
-rw-r--r--examples/bluetooth/btscanner/main.py55
-rw-r--r--examples/bluetooth/btscanner/service.py85
-rw-r--r--examples/bluetooth/btscanner/service.ui71
-rw-r--r--examples/bluetooth/btscanner/ui_device.py89
-rw-r--r--examples/bluetooth/btscanner/ui_service.py56
-rw-r--r--examples/charts/areachart/areachart.py104
-rw-r--r--examples/charts/areachart/areachart.pyproject3
-rw-r--r--examples/charts/areachart/doc/areachart.pngbin0 -> 15491 bytes
-rw-r--r--examples/charts/areachart/doc/areachart.rst8
-rw-r--r--examples/charts/barchart/barchart.py106
-rw-r--r--examples/charts/barchart/barchart.pyproject3
-rw-r--r--examples/charts/barchart/doc/barchart.pngbin0 -> 15717 bytes
-rw-r--r--examples/charts/barchart/doc/barchart.rst8
-rw-r--r--examples/datavisualization/surface/doc/surface.rst23
-rw-r--r--examples/datavisualization/surface/doc/surface_mountain.pngbin0 -> 152571 bytes
-rw-r--r--examples/datavisualization/surface/main.py218
-rw-r--r--examples/datavisualization/surface/mountain.pngbin0 -> 34540 bytes
-rw-r--r--examples/datavisualization/surface/surface.pyproject4
-rw-r--r--examples/datavisualization/surface/surfacegraph.py275
-rw-r--r--examples/declarative/editingmodel/MovingRectangle.qml115
-rw-r--r--examples/declarative/editingmodel/doc/editingmodel.rst14
-rw-r--r--examples/declarative/editingmodel/doc/qabstractlistmodelqml.pngbin0 -> 45810 bytes
-rw-r--r--examples/declarative/editingmodel/main.py59
-rw-r--r--examples/declarative/editingmodel/main.pyproject3
-rw-r--r--examples/declarative/editingmodel/main.qml143
-rw-r--r--examples/declarative/editingmodel/model.py187
-rw-r--r--examples/declarative/openglunderqml/doc/openglunderqml.rst21
-rw-r--r--examples/declarative/openglunderqml/doc/squircle.pngbin0 -> 37963 bytes
-rw-r--r--examples/declarative/openglunderqml/main.py66
-rw-r--r--examples/declarative/openglunderqml/main.qml86
-rw-r--r--examples/declarative/openglunderqml/openglunderqml.pyproject3
-rw-r--r--examples/declarative/openglunderqml/squircle.py107
-rw-r--r--examples/declarative/openglunderqml/squirclerenderer.py141
-rw-r--r--examples/declarative/referenceexamples/adding/adding.pyproject5
-rw-r--r--examples/declarative/referenceexamples/adding/doc/adding.rst65
-rw-r--r--examples/declarative/referenceexamples/adding/example.qml56
-rw-r--r--examples/declarative/referenceexamples/adding/main.py67
-rw-r--r--examples/declarative/referenceexamples/adding/person.py72
-rw-r--r--examples/declarative/rendercontrol/rendercontrol_opengl/cuberenderer.py223
-rw-r--r--examples/declarative/rendercontrol/rendercontrol_opengl/demo.qml208
-rw-r--r--examples/declarative/rendercontrol/rendercontrol_opengl/doc/rendercontrol_opengl.rst5
-rw-r--r--examples/declarative/rendercontrol/rendercontrol_opengl/main.py57
-rw-r--r--examples/declarative/rendercontrol/rendercontrol_opengl/rendercontrol_opengl.pyproject6
-rw-r--r--examples/declarative/rendercontrol/rendercontrol_opengl/window_singlethreaded.py308
-rw-r--r--examples/declarative/stringlistmodel/doc/stringlistmodel.pngbin79 -> 1978 bytes
-rw-r--r--examples/declarative/textproperties/doc/textproperties.rst2
-rw-r--r--examples/installer_test/hello.py3
-rw-r--r--examples/quick/painteditem/main.qml2
-rw-r--r--examples/quick/painteditem/painteditem.py8
-rw-r--r--examples/quickcontrols2/gallery/gallery.py4
-rw-r--r--examples/scriptableapplication/pythonutils.cpp2
-rw-r--r--examples/webenginewidgets/markdowneditor/document.py61
-rw-r--r--examples/webenginewidgets/markdowneditor/main.py57
-rw-r--r--examples/webenginewidgets/markdowneditor/mainwindow.py173
-rw-r--r--examples/webenginewidgets/markdowneditor/mainwindow.ui107
-rw-r--r--examples/webenginewidgets/markdowneditor/markdowneditor.pyproject9
-rw-r--r--examples/webenginewidgets/markdowneditor/previewpage.py55
-rw-r--r--examples/webenginewidgets/markdowneditor/rc_markdowneditor.py852
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt16
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt19
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css260
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js1514
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json35
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/default.md12
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/index.html32
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc8
-rw-r--r--examples/webenginewidgets/markdowneditor/ui_mainwindow.py115
-rw-r--r--examples/webenginewidgets/simplebrowser/simplebrowser.py2
-rw-r--r--examples/widgets/dialogs/tabdialog/doc/tabdialog.pngbin0 -> 13222 bytes
-rw-r--r--examples/widgets/dialogs/tabdialog/doc/tabdialog.rst13
-rw-r--r--examples/widgets/dialogs/tabdialog/tabdialog.py215
-rw-r--r--examples/widgets/dialogs/tabdialog/tabdialog.pyproject3
-rw-r--r--examples/widgets/layouts/borderlayout/borderlayout.py285
-rw-r--r--examples/widgets/layouts/borderlayout/borderlayout.pyproject3
-rw-r--r--examples/widgets/layouts/borderlayout/doc/borderlayout.pngbin0 -> 5450 bytes
-rw-r--r--examples/widgets/layouts/borderlayout/doc/borderlayout.rst10
-rw-r--r--examples/widgets/layouts/flowlayout/flowlayout.py33
-rw-r--r--examples/widgets/painting/plot/plot.py105
-rw-r--r--examples/widgets/painting/plot/plot.pyproject3
-rw-r--r--examples/widgets/state-machine/ping_pong/ping_pong.py (renamed from examples/widgets/state-machine/pingpong/pingpong.py)0
-rw-r--r--examples/widgets/state-machine/ping_pong/ping_pong.pyproject3
-rw-r--r--examples/widgets/state-machine/pingpong/pingpong.pyproject3
-rw-r--r--examples/widgets/thread_signals/thread_signals.py (renamed from examples/widgets/threads/thread_signals.py)0
-rw-r--r--examples/widgets/thread_signals/thread_signals.pyproject (renamed from examples/widgets/threads/thread_signals.pyproject)0
88 files changed, 7401 insertions, 32 deletions
diff --git a/examples/bluetooth/btscanner/btscanner.pyproject b/examples/bluetooth/btscanner/btscanner.pyproject
new file mode 100644
index 000000000..208487fe7
--- /dev/null
+++ b/examples/bluetooth/btscanner/btscanner.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["main.py", "device.py", "service.py", "device.ui", "service.ui"]
+}
diff --git a/examples/bluetooth/btscanner/device.py b/examples/bluetooth/btscanner/device.py
new file mode 100644
index 000000000..ba221b155
--- /dev/null
+++ b/examples/bluetooth/btscanner/device.py
@@ -0,0 +1,166 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+from PySide6.QtCore import QPoint, Qt, Slot
+from PySide6.QtGui import QColor
+from PySide6.QtWidgets import QDialog, QListWidgetItem, QListWidget, QMenu
+from PySide6.QtBluetooth import (QBluetoothAddress, QBluetoothDeviceDiscoveryAgent,
+ QBluetoothDeviceInfo, QBluetoothLocalDevice)
+
+from ui_device import Ui_DeviceDiscovery
+from service import ServiceDiscoveryDialog
+
+
+class DeviceDiscoveryDialog(QDialog):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._local_device = QBluetoothLocalDevice()
+ self._ui = Ui_DeviceDiscovery()
+ self._ui.setupUi(self)
+ # In case of multiple Bluetooth adapters it is possible to set adapter
+ # which will be used. Example code:
+ #
+ # address = QBluetoothAddress("XX:XX:XX:XX:XX:XX")
+ # discoveryAgent = QBluetoothDeviceDiscoveryAgent(address)
+
+ self._discovery_agent = QBluetoothDeviceDiscoveryAgent()
+
+ self._ui.scan.clicked.connect(self.start_scan)
+ self._discovery_agent.deviceDiscovered.connect(self.add_device)
+ self._discovery_agent.finished.connect(self.scan_finished)
+ self._ui.list.itemActivated.connect(self.item_activated)
+ self._local_device.hostModeStateChanged.connect(self.host_mode_state_changed)
+
+ self.host_mode_state_changed(self._local_device.hostMode())
+ # add context menu for devices to be able to pair device
+ self._ui.list.setContextMenuPolicy(Qt.CustomContextMenu)
+ self._ui.list.customContextMenuRequested.connect(self.display_pairing_menu)
+ self._local_device.pairingFinished.connect(self.pairing_done)
+
+ @Slot(QBluetoothDeviceInfo)
+ def add_device(self, info):
+ a = info.address().toString()
+ label = f"{a} {info.name()}"
+ items = self._ui.list.findItems(label, Qt.MatchExactly)
+ if not items:
+ item = QListWidgetItem(label)
+ pairing_status = self._local_device.pairingStatus(info.address())
+ if (pairing_status == QBluetoothLocalDevice.Paired
+ or pairing_status == QBluetoothLocalDevice.AuthorizedPaired):
+ item.setForeground(QColor(Qt.green))
+ else:
+ item.setForeground(QColor(Qt.black))
+ self._ui.list.addItem(item)
+
+ @Slot()
+ def start_scan(self):
+ self._discovery_agent.start()
+ self._ui.scan.setEnabled(False)
+
+ @Slot()
+ def scan_finished(self):
+ self._ui.scan.setEnabled(True)
+
+ @Slot(QListWidgetItem)
+ def item_activated(self, item):
+ text = item.text()
+ index = text.find(' ')
+ if index == -1:
+ return
+
+ address = QBluetoothAddress(text[0:index])
+ name = text[index + 1:]
+
+ d = ServiceDiscoveryDialog(name, address)
+ d.exec()
+
+ @Slot(bool)
+ def on_discoverable_clicked(self, clicked):
+ if clicked:
+ self._local_device.setHostMode(QBluetoothLocalDevice.HostDiscoverable)
+ else:
+ self._local_device.setHostMode(QBluetoothLocalDevice.HostConnectable)
+
+ @Slot(bool)
+ def on_power_clicked(self, clicked):
+ if clicked:
+ self._local_device.powerOn()
+ else:
+ self._local_device.setHostMode(QBluetoothLocalDevice.HostPoweredOff)
+
+ @Slot(QBluetoothLocalDevice.HostMode)
+ def host_mode_state_changed(self, mode):
+ self._ui.power.setChecked(mode != QBluetoothLocalDevice.HostPoweredOff)
+ self._ui.discoverable.setChecked(mode == QBluetoothLocalDevice.HostDiscoverable)
+
+ on = mode != QBluetoothLocalDevice.HostPoweredOff
+ self._ui.scan.setEnabled(on)
+ self._ui.discoverable.setEnabled(on)
+
+ @Slot(QPoint)
+ def display_pairing_menu(self, pos):
+ if self._ui.list.count() == 0:
+ return
+ menu = QMenu(self)
+ pair_action = menu.addAction("Pair")
+ remove_pair_action = menu.addAction("Remove Pairing")
+ chosen_action = menu.exec(self._ui.list.viewport().mapToGlobal(pos))
+ current_item = self._ui.list.currentItem()
+
+ text = current_item.text()
+ index = text.find(' ')
+ if index == -1:
+ return
+
+ address = QBluetoothAddress(text[0:index])
+ if chosen_action == pair_action:
+ self._local_device.requestPairing(address, QBluetoothLocalDevice.Paired)
+ elif chosen_action == remove_pair_action:
+ self._local_device.requestPairing(address, QBluetoothLocalDevice.Unpaired)
+
+ @Slot(QBluetoothAddress, QBluetoothLocalDevice.Pairing)
+ def pairing_done(self, address, pairing):
+ items = self._ui.list.findItems(address.toString(), Qt.MatchContains)
+
+ color = QColor(Qt.red)
+ if pairing == QBluetoothLocalDevice.Paired or pairing == QBluetoothLocalDevice.AuthorizedPaired:
+ color = QColor(Qt.green)
+ for item in items:
+ item.setForeground(color)
diff --git a/examples/bluetooth/btscanner/device.ui b/examples/bluetooth/btscanner/device.ui
new file mode 100644
index 000000000..fa81c5cb4
--- /dev/null
+++ b/examples/bluetooth/btscanner/device.ui
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DeviceDiscovery</class>
+ <widget class="QDialog" name="DeviceDiscovery">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>411</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Bluetooth Scanner</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QListWidget" name="list"/>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Local Device</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="power">
+ <property name="text">
+ <string>Bluetooth Powered On</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="discoverable">
+ <property name="text">
+ <string>Discoverable</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="scan">
+ <property name="text">
+ <string>Scan</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="clear">
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="quit">
+ <property name="text">
+ <string>Quit</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>quit</sender>
+ <signal>clicked()</signal>
+ <receiver>DeviceDiscovery</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>323</x>
+ <y>275</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>396</x>
+ <y>268</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>clear</sender>
+ <signal>clicked()</signal>
+ <receiver>list</receiver>
+ <slot>clear()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>188</x>
+ <y>276</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>209</x>
+ <y>172</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/bluetooth/btscanner/doc/btscanner.rst b/examples/bluetooth/btscanner/doc/btscanner.rst
new file mode 100644
index 000000000..d99af3be5
--- /dev/null
+++ b/examples/bluetooth/btscanner/doc/btscanner.rst
@@ -0,0 +1,4 @@
+Bluetooth Scanner Example
+=========================
+
+An example showing how to locate Bluetooth devices.
diff --git a/examples/bluetooth/btscanner/main.py b/examples/bluetooth/btscanner/main.py
new file mode 100644
index 000000000..ff364f55e
--- /dev/null
+++ b/examples/bluetooth/btscanner/main.py
@@ -0,0 +1,55 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+"""PySide6 port of the bluetooth/btscanner example from Qt v6.x"""
+
+import sys
+
+from PySide6.QtCore import Qt
+from PySide6.QtWidgets import QApplication, QWidget
+
+from device import DeviceDiscoveryDialog
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ d = DeviceDiscoveryDialog()
+ d.exec()
+ sys.exit(0)
diff --git a/examples/bluetooth/btscanner/service.py b/examples/bluetooth/btscanner/service.py
new file mode 100644
index 000000000..e3916082e
--- /dev/null
+++ b/examples/bluetooth/btscanner/service.py
@@ -0,0 +1,85 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+from PySide6.QtCore import Qt, Slot
+from PySide6.QtWidgets import QDialog
+from PySide6.QtBluetooth import (QBluetoothAddress, QBluetoothServiceInfo,
+ QBluetoothServiceDiscoveryAgent, QBluetoothLocalDevice)
+
+from ui_service import Ui_ServiceDiscovery
+
+
+class ServiceDiscoveryDialog(QDialog):
+ def __init__(self, name, address, parent=None):
+ super().__init__(parent)
+ self._ui = Ui_ServiceDiscovery()
+ self._ui.setupUi(self)
+
+ # Using default Bluetooth adapter
+ local_device = QBluetoothLocalDevice()
+ adapter_address = QBluetoothAddress(local_device.address())
+
+ # In case of multiple Bluetooth adapters it is possible to
+ # set which adapter will be used by providing MAC Address.
+ # Example code:
+ #
+ # adapterAddress = QBluetoothAddress("XX:XX:XX:XX:XX:XX")
+ # discoveryAgent = QBluetoothServiceDiscoveryAgent(adapterAddress)
+
+ self._discovery_agent = QBluetoothServiceDiscoveryAgent(adapter_address)
+ self._discovery_agent.setRemoteAddress(address)
+
+ self.setWindowTitle(name)
+
+ self._discovery_agent.serviceDiscovered.connect(self.add_service)
+ self._discovery_agent.finished.connect(self._ui.status.hide)
+ self._discovery_agent.start()
+
+ @Slot(QBluetoothServiceInfo)
+ def add_service(self, info):
+ line = info.serviceName()
+ if not line:
+ return
+
+ if info.serviceDescription():
+ line += "\n\t" + info.serviceDescription()
+ if info.serviceProvider():
+ line += "\n\t" + info.serviceProvider()
+ self._ui.list.addItem(line)
diff --git a/examples/bluetooth/btscanner/service.ui b/examples/bluetooth/btscanner/service.ui
new file mode 100644
index 000000000..4ca12ee05
--- /dev/null
+++ b/examples/bluetooth/btscanner/service.ui
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ServiceDiscovery</class>
+ <widget class="QDialog" name="ServiceDiscovery">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>539</width>
+ <height>486</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Available Services</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QListWidget" name="list"/>
+ </item>
+ <item>
+ <widget class="QLabel" name="status">
+ <property name="text">
+ <string>Querying...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ServiceDiscovery</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>396</x>
+ <y>457</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>535</x>
+ <y>443</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ServiceDiscovery</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>339</x>
+ <y>464</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>535</x>
+ <y>368</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/bluetooth/btscanner/ui_device.py b/examples/bluetooth/btscanner/ui_device.py
new file mode 100644
index 000000000..f351854d7
--- /dev/null
+++ b/examples/bluetooth/btscanner/ui_device.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'device.ui'
+##
+## Created by: Qt User Interface Compiler version 6.2.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QApplication, QCheckBox, QDialog, QGroupBox,
+ QHBoxLayout, QListWidget, QListWidgetItem, QPushButton,
+ QSizePolicy, QVBoxLayout)
+
+class Ui_DeviceDiscovery(object):
+ def setupUi(self, DeviceDiscovery):
+ if not DeviceDiscovery.objectName():
+ DeviceDiscovery.setObjectName(u"DeviceDiscovery")
+ DeviceDiscovery.resize(400, 411)
+ self.verticalLayout = QVBoxLayout(DeviceDiscovery)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.list = QListWidget(DeviceDiscovery)
+ self.list.setObjectName(u"list")
+
+ self.verticalLayout.addWidget(self.list)
+
+ self.groupBox = QGroupBox(DeviceDiscovery)
+ self.groupBox.setObjectName(u"groupBox")
+ self.horizontalLayout_2 = QHBoxLayout(self.groupBox)
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.power = QCheckBox(self.groupBox)
+ self.power.setObjectName(u"power")
+ self.power.setChecked(True)
+
+ self.horizontalLayout_2.addWidget(self.power)
+
+ self.discoverable = QCheckBox(self.groupBox)
+ self.discoverable.setObjectName(u"discoverable")
+ self.discoverable.setChecked(True)
+
+ self.horizontalLayout_2.addWidget(self.discoverable)
+
+
+ self.verticalLayout.addWidget(self.groupBox)
+
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.scan = QPushButton(DeviceDiscovery)
+ self.scan.setObjectName(u"scan")
+
+ self.horizontalLayout.addWidget(self.scan)
+
+ self.clear = QPushButton(DeviceDiscovery)
+ self.clear.setObjectName(u"clear")
+
+ self.horizontalLayout.addWidget(self.clear)
+
+ self.quit = QPushButton(DeviceDiscovery)
+ self.quit.setObjectName(u"quit")
+
+ self.horizontalLayout.addWidget(self.quit)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout)
+
+
+ self.retranslateUi(DeviceDiscovery)
+ self.quit.clicked.connect(DeviceDiscovery.accept)
+ self.clear.clicked.connect(self.list.clear)
+
+ QMetaObject.connectSlotsByName(DeviceDiscovery)
+ # setupUi
+
+ def retranslateUi(self, DeviceDiscovery):
+ DeviceDiscovery.setWindowTitle(QCoreApplication.translate("DeviceDiscovery", u"Bluetooth Scanner", None))
+ self.groupBox.setTitle(QCoreApplication.translate("DeviceDiscovery", u"Local Device", None))
+ self.power.setText(QCoreApplication.translate("DeviceDiscovery", u"Bluetooth Powered On", None))
+ self.discoverable.setText(QCoreApplication.translate("DeviceDiscovery", u"Discoverable", None))
+ self.scan.setText(QCoreApplication.translate("DeviceDiscovery", u"Scan", None))
+ self.clear.setText(QCoreApplication.translate("DeviceDiscovery", u"Clear", None))
+ self.quit.setText(QCoreApplication.translate("DeviceDiscovery", u"Quit", None))
+ # retranslateUi
diff --git a/examples/bluetooth/btscanner/ui_service.py b/examples/bluetooth/btscanner/ui_service.py
new file mode 100644
index 000000000..c5a37a933
--- /dev/null
+++ b/examples/bluetooth/btscanner/ui_service.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'service.ui'
+##
+## Created by: Qt User Interface Compiler version 6.2.1
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox,
+ QLabel, QListWidget, QListWidgetItem, QSizePolicy,
+ QVBoxLayout)
+
+class Ui_ServiceDiscovery(object):
+ def setupUi(self, ServiceDiscovery):
+ if not ServiceDiscovery.objectName():
+ ServiceDiscovery.setObjectName(u"ServiceDiscovery")
+ ServiceDiscovery.resize(539, 486)
+ self.verticalLayout = QVBoxLayout(ServiceDiscovery)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.list = QListWidget(ServiceDiscovery)
+ self.list.setObjectName(u"list")
+
+ self.verticalLayout.addWidget(self.list)
+
+ self.status = QLabel(ServiceDiscovery)
+ self.status.setObjectName(u"status")
+
+ self.verticalLayout.addWidget(self.status)
+
+ self.buttonBox = QDialogButtonBox(ServiceDiscovery)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Close)
+
+ self.verticalLayout.addWidget(self.buttonBox)
+
+
+ self.retranslateUi(ServiceDiscovery)
+ self.buttonBox.accepted.connect(ServiceDiscovery.accept)
+ self.buttonBox.rejected.connect(ServiceDiscovery.reject)
+
+ QMetaObject.connectSlotsByName(ServiceDiscovery)
+ # setupUi
+
+ def retranslateUi(self, ServiceDiscovery):
+ ServiceDiscovery.setWindowTitle(QCoreApplication.translate("ServiceDiscovery", u"Available Services", None))
+ self.status.setText(QCoreApplication.translate("ServiceDiscovery", u"Querying...", None))
+ # retranslateUi
diff --git a/examples/charts/areachart/areachart.py b/examples/charts/areachart/areachart.py
new file mode 100644
index 000000000..a65cf2c86
--- /dev/null
+++ b/examples/charts/areachart/areachart.py
@@ -0,0 +1,104 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+"""PySide6 port of the areachart example from Qt v6.x"""
+
+import sys
+from PySide6.QtCore import QPointF, Qt
+from PySide6.QtWidgets import QMainWindow, QApplication
+from PySide6.QtCharts import QChart, QChartView, QLineSeries, QAreaSeries
+from PySide6.QtGui import QGradient, QPen, QLinearGradient, QPainter
+
+
+class TestChart(QMainWindow):
+ def __init__(self):
+ super().__init__()
+
+ self.series_0 = QLineSeries()
+ self.series_1 = QLineSeries()
+
+ self.series_0.append(QPointF(1, 5))
+ self.series_0.append(QPointF(3, 7))
+ self.series_0.append(QPointF(7, 6))
+ self.series_0.append(QPointF(9, 7))
+ self.series_0.append(QPointF(12, 6))
+ self.series_0.append(QPointF(16, 7))
+ self.series_0.append(QPointF(18, 5))
+
+ self.series_1.append(QPointF(1, 3))
+ self.series_1.append(QPointF(3, 4))
+ self.series_1.append(QPointF(7, 3))
+ self.series_1.append(QPointF(8, 2))
+ self.series_1.append(QPointF(12, 3))
+ self.series_1.append(QPointF(16, 4))
+ self.series_1.append(QPointF(18, 3))
+
+ self.series = QAreaSeries(self.series_0, self.series_1)
+ self.series.setName("Batman")
+ self.pen = QPen(0x059605)
+ self.pen.setWidth(3)
+ self.series.setPen(self.pen)
+
+ self.gradient = QLinearGradient(QPointF(0, 0), QPointF(0, 1))
+ self.gradient.setColorAt(0.0, 0x3cc63c)
+ self.gradient.setColorAt(1.0, 0x26f626)
+ self.gradient.setCoordinateMode(QGradient.ObjectBoundingMode)
+ self.series.setBrush(self.gradient)
+
+ self.chart = QChart()
+ self.chart.addSeries(self.series)
+ self.chart.setTitle("Simple areachart example")
+ self.chart.createDefaultAxes()
+ self.chart.axes(Qt.Horizontal)[0].setRange(0, 20)
+ self.chart.axes(Qt.Vertical)[0].setRange(0, 10)
+
+ self._chart_view = QChartView(self.chart)
+ self._chart_view.setRenderHint(QPainter.Antialiasing)
+
+ self.setCentralWidget(self._chart_view)
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+
+ window = TestChart()
+ window.show()
+ window.resize(400, 300)
+ sys.exit(app.exec())
diff --git a/examples/charts/areachart/areachart.pyproject b/examples/charts/areachart/areachart.pyproject
new file mode 100644
index 000000000..b4a6f9d65
--- /dev/null
+++ b/examples/charts/areachart/areachart.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["areachart.py"]
+}
diff --git a/examples/charts/areachart/doc/areachart.png b/examples/charts/areachart/doc/areachart.png
new file mode 100644
index 000000000..d03838443
--- /dev/null
+++ b/examples/charts/areachart/doc/areachart.png
Binary files differ
diff --git a/examples/charts/areachart/doc/areachart.rst b/examples/charts/areachart/doc/areachart.rst
new file mode 100644
index 000000000..c6cd718d7
--- /dev/null
+++ b/examples/charts/areachart/doc/areachart.rst
@@ -0,0 +1,8 @@
+Area Chart Example
+==================
+
+The example shows how to create an area Chart
+
+.. image:: areachart.png
+ :width: 400
+ :alt: Area Chart Screenshot
diff --git a/examples/charts/barchart/barchart.py b/examples/charts/barchart/barchart.py
new file mode 100644
index 000000000..8ae0dc5ff
--- /dev/null
+++ b/examples/charts/barchart/barchart.py
@@ -0,0 +1,106 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+"""PySide6 port of the linechart example from Qt v6.x"""
+
+import sys
+
+from PySide6.QtCharts import (QBarCategoryAxis, QBarSeries, QBarSet, QChart,
+ QChartView, QValueAxis)
+from PySide6.QtCore import Qt
+from PySide6.QtGui import QPainter
+from PySide6.QtWidgets import QApplication, QMainWindow
+
+
+class TestChart(QMainWindow):
+ def __init__(self):
+ super().__init__()
+
+ self.set_0 = QBarSet("Jane")
+ self.set_1 = QBarSet("John")
+ self.set_2 = QBarSet("Axel")
+ self.set_3 = QBarSet("Mary")
+ self.set_4 = QBarSet("Samantha")
+
+ self.set_0.append([1, 2, 3, 4, 5, 6])
+ self.set_1.append([5, 0, 0, 4, 0, 7])
+ self.set_2.append([3, 5, 8, 13, 8, 5])
+ self.set_3.append([5, 6, 7, 3, 4, 5])
+ self.set_4.append([9, 7, 5, 3, 1, 2])
+
+ self.series = QBarSeries()
+ self.series.append(self.set_0)
+ self.series.append(self.set_1)
+ self.series.append(self.set_2)
+ self.series.append(self.set_3)
+ self.series.append(self.set_4)
+
+ self.chart = QChart()
+ self.chart.addSeries(self.series)
+ self.chart.setTitle("Simple barchart example")
+ self.chart.setAnimationOptions(QChart.SeriesAnimations)
+
+ self.categories = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
+ self.axis_x = QBarCategoryAxis()
+ self.axis_x.append(self.categories)
+ self.chart.addAxis(self.axis_x, Qt.AlignBottom)
+ self.series.attachAxis(self.axis_x)
+
+ self.axis_y = QValueAxis()
+ self.axis_y.setRange(0, 15)
+ self.chart.addAxis(self.axis_y, Qt.AlignLeft)
+ self.series.attachAxis(self.axis_y)
+
+ self.chart.legend().setVisible(True)
+ self.chart.legend().setAlignment(Qt.AlignBottom)
+
+ self._chart_view = QChartView(self.chart)
+ self._chart_view.setRenderHint(QPainter.Antialiasing)
+
+ self.setCentralWidget(self._chart_view)
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+
+ window = TestChart()
+ window.show()
+ window.resize(420, 300)
+ sys.exit(app.exec())
diff --git a/examples/charts/barchart/barchart.pyproject b/examples/charts/barchart/barchart.pyproject
new file mode 100644
index 000000000..4ca819426
--- /dev/null
+++ b/examples/charts/barchart/barchart.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["barchart.py"]
+}
diff --git a/examples/charts/barchart/doc/barchart.png b/examples/charts/barchart/doc/barchart.png
new file mode 100644
index 000000000..da08217fc
--- /dev/null
+++ b/examples/charts/barchart/doc/barchart.png
Binary files differ
diff --git a/examples/charts/barchart/doc/barchart.rst b/examples/charts/barchart/doc/barchart.rst
new file mode 100644
index 000000000..b9a499721
--- /dev/null
+++ b/examples/charts/barchart/doc/barchart.rst
@@ -0,0 +1,8 @@
+Bar Chart Example
+==================
+
+The example shows how to create a Bar chart.
+
+.. image:: barchart.png
+ :width: 400
+ :alt: Bar Chart Screenshot
diff --git a/examples/datavisualization/surface/doc/surface.rst b/examples/datavisualization/surface/doc/surface.rst
new file mode 100644
index 000000000..65674bf91
--- /dev/null
+++ b/examples/datavisualization/surface/doc/surface.rst
@@ -0,0 +1,23 @@
+Surface Example
+===============
+
+Using Q3DSurface in a widget application.
+
+The surface example shows how to make a simple 3D surface graph using
+Q3DSurface and combining the use of widgets for adjusting several adjustable
+qualities. This example demonstrates the following features:
+
+* How to set up a basic QSurfaceDataProxy and set data for it.
+* How to use QHeightMapSurfaceDataProxy for showing 3D height maps.
+* Three different selection modes for studying the graph.
+* Axis range usage for displaying selected portions of the graph.
+* Changing theme.
+* How to set a custom surface gradient.
+
+For instructions about how to interact with the graph, see `this page`_.
+
+.. image:: surface_mountain.png
+ :width: 400
+ :alt: Surface Example Screenshot
+
+.. _`this page`: https://doc.qt.io/qt-6/qtdatavisualization-interacting-with-data.html
diff --git a/examples/datavisualization/surface/doc/surface_mountain.png b/examples/datavisualization/surface/doc/surface_mountain.png
new file mode 100644
index 000000000..ba9652394
--- /dev/null
+++ b/examples/datavisualization/surface/doc/surface_mountain.png
Binary files differ
diff --git a/examples/datavisualization/surface/main.py b/examples/datavisualization/surface/main.py
new file mode 100644
index 000000000..3b0bd095a
--- /dev/null
+++ b/examples/datavisualization/surface/main.py
@@ -0,0 +1,218 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import sys
+
+from PySide6.QtCore import QSize, Qt
+from PySide6.QtDataVisualization import Q3DSurface
+from PySide6.QtGui import QBrush, QIcon, QLinearGradient, QPainter, QPixmap
+from PySide6.QtWidgets import (QApplication, QComboBox, QGroupBox, QHBoxLayout,
+ QLabel, QMessageBox, QPushButton, QRadioButton,
+ QSizePolicy, QSlider, QVBoxLayout, QWidget)
+
+from surfacegraph import SurfaceGraph
+
+THEMES = ["Qt", "Primary Colors", "Digia", "Stone Moss", "Army Blue", "Retro", "Ebony", "Isabelle"]
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ graph = Q3DSurface()
+ container = QWidget.createWindowContainer(graph)
+
+ if not graph.hasContext():
+ msgBox = QMessageBox()
+ msgBox.setText("Couldn't initialize the OpenGL context.")
+ msgBox.exec()
+ sys.exit(-1)
+
+ screenSize = graph.screen().size()
+ container.setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.6))
+ container.setMaximumSize(screenSize)
+ container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+ container.setFocusPolicy(Qt.StrongFocus)
+
+ widget = QWidget()
+ hLayout = QHBoxLayout(widget)
+ vLayout = QVBoxLayout()
+ hLayout.addWidget(container, 1)
+ hLayout.addLayout(vLayout)
+ vLayout.setAlignment(Qt.AlignTop)
+
+ widget.setWindowTitle("Surface example")
+
+ modelGroupBox = QGroupBox("Model")
+
+ sqrtSinModelRB = QRadioButton(widget)
+ sqrtSinModelRB.setText("Sqrt& Sin")
+ sqrtSinModelRB.setChecked(False)
+
+ heightMapModelRB = QRadioButton(widget)
+ heightMapModelRB.setText("Height Map")
+ heightMapModelRB.setChecked(False)
+
+ modelVBox = QVBoxLayout()
+ modelVBox.addWidget(sqrtSinModelRB)
+ modelVBox.addWidget(heightMapModelRB)
+ modelGroupBox.setLayout(modelVBox)
+
+ selectionGroupBox = QGroupBox("Selection Mode")
+
+ modeNoneRB = QRadioButton(widget)
+ modeNoneRB.setText("No selection")
+ modeNoneRB.setChecked(False)
+
+ modeItemRB = QRadioButton(widget)
+ modeItemRB.setText("Item")
+ modeItemRB.setChecked(False)
+
+ modeSliceRowRB = QRadioButton(widget)
+ modeSliceRowRB.setText("Row Slice")
+ modeSliceRowRB.setChecked(False)
+
+ modeSliceColumnRB = QRadioButton(widget)
+ modeSliceColumnRB.setText("Column Slice")
+ modeSliceColumnRB.setChecked(False)
+
+ selectionVBox = QVBoxLayout()
+ selectionVBox.addWidget(modeNoneRB)
+ selectionVBox.addWidget(modeItemRB)
+ selectionVBox.addWidget(modeSliceRowRB)
+ selectionVBox.addWidget(modeSliceColumnRB)
+ selectionGroupBox.setLayout(selectionVBox)
+
+ axisMinSliderX = QSlider(Qt.Horizontal, widget)
+ axisMinSliderX.setMinimum(0)
+ axisMinSliderX.setTickInterval(1)
+ axisMinSliderX.setEnabled(True)
+ axisMaxSliderX = QSlider(Qt.Horizontal, widget)
+ axisMaxSliderX.setMinimum(1)
+ axisMaxSliderX.setTickInterval(1)
+ axisMaxSliderX.setEnabled(True)
+ axisMinSliderZ = QSlider(Qt.Horizontal, widget)
+ axisMinSliderZ.setMinimum(0)
+ axisMinSliderZ.setTickInterval(1)
+ axisMinSliderZ.setEnabled(True)
+ axisMaxSliderZ = QSlider(Qt.Horizontal, widget)
+ axisMaxSliderZ.setMinimum(1)
+ axisMaxSliderZ.setTickInterval(1)
+ axisMaxSliderZ.setEnabled(True)
+
+ themeList = QComboBox(widget)
+ themeList.addItems(THEMES)
+
+ colorGroupBox = QGroupBox("Custom gradient")
+
+ grBtoY = QLinearGradient(0, 0, 1, 100)
+ grBtoY.setColorAt(1.0, Qt.black)
+ grBtoY.setColorAt(0.67, Qt.blue)
+ grBtoY.setColorAt(0.33, Qt.red)
+ grBtoY.setColorAt(0.0, Qt.yellow)
+
+ pm = QPixmap(24, 100)
+ pmp = QPainter(pm)
+ pmp.setBrush(QBrush(grBtoY))
+ pmp.setPen(Qt.NoPen)
+ pmp.drawRect(0, 0, 24, 100)
+ pmp.end()
+
+ gradientBtoYPB = QPushButton(widget)
+ gradientBtoYPB.setIcon(QIcon(pm))
+ gradientBtoYPB.setIconSize(QSize(24, 100))
+
+ grGtoR = QLinearGradient(0, 0, 1, 100)
+ grGtoR.setColorAt(1.0, Qt.darkGreen)
+ grGtoR.setColorAt(0.5, Qt.yellow)
+ grGtoR.setColorAt(0.2, Qt.red)
+ grGtoR.setColorAt(0.0, Qt.darkRed)
+ pmp.begin(pm)
+ pmp.setBrush(QBrush(grGtoR))
+ pmp.drawRect(0, 0, 24, 100)
+ pmp.end()
+
+ gradientGtoRPB = QPushButton(widget)
+ gradientGtoRPB.setIcon(QIcon(pm))
+ gradientGtoRPB.setIconSize(QSize(24, 100))
+
+ colorHBox = QHBoxLayout()
+ colorHBox.addWidget(gradientBtoYPB)
+ colorHBox.addWidget(gradientGtoRPB)
+ colorGroupBox.setLayout(colorHBox)
+
+ vLayout.addWidget(modelGroupBox)
+ vLayout.addWidget(selectionGroupBox)
+ vLayout.addWidget(QLabel("Column range"))
+ vLayout.addWidget(axisMinSliderX)
+ vLayout.addWidget(axisMaxSliderX)
+ vLayout.addWidget(QLabel("Row range"))
+ vLayout.addWidget(axisMinSliderZ)
+ vLayout.addWidget(axisMaxSliderZ)
+ vLayout.addWidget(QLabel("Theme"))
+ vLayout.addWidget(themeList)
+ vLayout.addWidget(colorGroupBox)
+
+ widget.show()
+
+ modifier = SurfaceGraph(graph)
+
+ heightMapModelRB.toggled.connect(modifier.enableHeightMapModel)
+ sqrtSinModelRB.toggled.connect(modifier.enableSqrtSinModel)
+ modeNoneRB.toggled.connect(modifier.toggleModeNone)
+ modeItemRB.toggled.connect(modifier.toggleModeItem)
+ modeSliceRowRB.toggled.connect(modifier.toggleModeSliceRow)
+ modeSliceColumnRB.toggled.connect(modifier.toggleModeSliceColumn)
+ axisMinSliderX.valueChanged.connect(modifier.adjustXMin)
+ axisMaxSliderX.valueChanged.connect(modifier.adjustXMax)
+ axisMinSliderZ.valueChanged.connect(modifier.adjustZMin)
+ axisMaxSliderZ.valueChanged.connect(modifier.adjustZMax)
+ themeList.currentIndexChanged[int].connect(modifier.changeTheme)
+ gradientBtoYPB.pressed.connect(modifier.setBlackToYellowGradient)
+ gradientGtoRPB.pressed.connect(modifier.setGreenToRedGradient)
+
+ modifier.setAxisMinSliderX(axisMinSliderX)
+ modifier.setAxisMaxSliderX(axisMaxSliderX)
+ modifier.setAxisMinSliderZ(axisMinSliderZ)
+ modifier.setAxisMaxSliderZ(axisMaxSliderZ)
+
+ sqrtSinModelRB.setChecked(True)
+ modeItemRB.setChecked(True)
+ themeList.setCurrentIndex(2)
+
+ sys.exit(app.exec())
diff --git a/examples/datavisualization/surface/mountain.png b/examples/datavisualization/surface/mountain.png
new file mode 100644
index 000000000..9138c710a
--- /dev/null
+++ b/examples/datavisualization/surface/mountain.png
Binary files differ
diff --git a/examples/datavisualization/surface/surface.pyproject b/examples/datavisualization/surface/surface.pyproject
new file mode 100644
index 000000000..598a6541f
--- /dev/null
+++ b/examples/datavisualization/surface/surface.pyproject
@@ -0,0 +1,4 @@
+{
+ "files": ["main.py",
+ "surfacegraph.py"]
+}
diff --git a/examples/datavisualization/surface/surfacegraph.py b/examples/datavisualization/surface/surfacegraph.py
new file mode 100644
index 000000000..8af18b550
--- /dev/null
+++ b/examples/datavisualization/surface/surfacegraph.py
@@ -0,0 +1,275 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import math
+
+from PySide6.QtCore import QObject, Qt, Slot
+from PySide6.QtDataVisualization import (Q3DTheme, QAbstract3DGraph,
+ QHeightMapSurfaceDataProxy,
+ QSurface3DSeries, QSurfaceDataItem,
+ QSurfaceDataProxy, QValue3DAxis)
+from PySide6.QtGui import QImage, QLinearGradient, QVector3D
+from PySide6.QtWidgets import QSlider
+
+sampleCountX = 50
+sampleCountZ = 50
+heightMapGridStepX = 6
+heightMapGridStepZ = 6
+sampleMin = -8.0
+sampleMax = 8.0
+
+
+class SurfaceGraph(QObject):
+ def __init__(self, surface, parent=None):
+ QObject.__init__(self, parent)
+
+ self.m_graph = surface
+ self.m_graph.setAxisX(QValue3DAxis())
+ self.m_graph.setAxisY(QValue3DAxis())
+ self.m_graph.setAxisZ(QValue3DAxis())
+
+ self.m_sqrtSinProxy = QSurfaceDataProxy()
+ self.m_sqrtSinSeries = QSurface3DSeries(self.m_sqrtSinProxy)
+ self.fillSqrtSinProxy()
+
+ heightMapImage = QImage("mountain.png")
+ self.m_heightMapProxy = QHeightMapSurfaceDataProxy(heightMapImage)
+ self.m_heightMapSeries = QSurface3DSeries(self.m_heightMapProxy)
+ self.m_heightMapSeries.setItemLabelFormat("(@xLabel, @zLabel): @yLabel")
+ self.m_heightMapProxy.setValueRanges(34.0, 40.0, 18.0, 24.0)
+
+ self.m_heightMapWidth = heightMapImage.width()
+ self.m_heightMapHeight = heightMapImage.height()
+
+ self.m_axisMinSliderX = QSlider()
+ self.m_axisMaxSliderX = QSlider()
+ self.m_axisMinSliderZ = QSlider()
+ self.m_axisMaxSliderZ = QSlider()
+ self.m_rangeMinX = 0.0
+ self.m_rangeMinZ = 0.0
+ self.m_stepX = 0.0
+ self.m_stepZ = 0.0
+
+ def fillSqrtSinProxy(self):
+ stepX = (sampleMax - sampleMin) / float(sampleCountX - 1)
+ stepZ = (sampleMax - sampleMin) / float(sampleCountZ - 1)
+
+ dataArray = []
+ for i in range(sampleCountZ):
+ newRow = []
+ # Keep values within range bounds, since just adding step can cause
+ # minor drift due to the rounding errors.
+ z = min(sampleMax, (i * stepZ + sampleMin))
+ for j in range(sampleCountX):
+ x = min(sampleMax, (j * stepX + sampleMin))
+ R = math.sqrt(z * z + x * x) + 0.01
+ y = (math.sin(R) / R + 0.24) * 1.61
+ newRow.append(QSurfaceDataItem(QVector3D(x, y, z)))
+ dataArray.append(newRow)
+
+ self.m_sqrtSinProxy.resetArray(dataArray)
+
+ def enableSqrtSinModel(self, enable):
+ if enable:
+ self.m_sqrtSinSeries.setDrawMode(QSurface3DSeries.DrawSurfaceAndWireframe)
+ self.m_sqrtSinSeries.setFlatShadingEnabled(True)
+
+ self.m_graph.axisX().setLabelFormat("%.2f")
+ self.m_graph.axisZ().setLabelFormat("%.2f")
+ self.m_graph.axisX().setRange(sampleMin, sampleMax)
+ self.m_graph.axisY().setRange(0.0, 2.0)
+ self.m_graph.axisZ().setRange(sampleMin, sampleMax)
+ self.m_graph.axisX().setLabelAutoRotation(30)
+ self.m_graph.axisY().setLabelAutoRotation(90)
+ self.m_graph.axisZ().setLabelAutoRotation(30)
+
+ self.m_graph.removeSeries(self.m_heightMapSeries)
+ self.m_graph.addSeries(self.m_sqrtSinSeries)
+
+ # Reset range sliders for Sqrt&Sin
+ self.m_rangeMinX = sampleMin
+ self.m_rangeMinZ = sampleMin
+ self.m_stepX = (sampleMax - sampleMin) / float(sampleCountX - 1)
+ self.m_stepZ = (sampleMax - sampleMin) / float(sampleCountZ - 1)
+ self.m_axisMinSliderX.setMaximum(sampleCountX - 2)
+ self.m_axisMinSliderX.setValue(0)
+ self.m_axisMaxSliderX.setMaximum(sampleCountX - 1)
+ self.m_axisMaxSliderX.setValue(sampleCountX - 1)
+ self.m_axisMinSliderZ.setMaximum(sampleCountZ - 2)
+ self.m_axisMinSliderZ.setValue(0)
+ self.m_axisMaxSliderZ.setMaximum(sampleCountZ - 1)
+ self.m_axisMaxSliderZ.setValue(sampleCountZ - 1)
+
+ def enableHeightMapModel(self, enable):
+ if enable:
+ self.m_heightMapSeries.setDrawMode(QSurface3DSeries.DrawSurface)
+ self.m_heightMapSeries.setFlatShadingEnabled(False)
+
+ self.m_graph.axisX().setLabelFormat("%.1f N")
+ self.m_graph.axisZ().setLabelFormat("%.1f E")
+ self.m_graph.axisX().setRange(34.0, 40.0)
+ self.m_graph.axisY().setAutoAdjustRange(True)
+ self.m_graph.axisZ().setRange(18.0, 24.0)
+
+ self.m_graph.axisX().setTitle("Latitude")
+ self.m_graph.axisY().setTitle("Height")
+ self.m_graph.axisZ().setTitle("Longitude")
+
+ self.m_graph.removeSeries(self.m_sqrtSinSeries)
+ self.m_graph.addSeries(self.m_heightMapSeries)
+
+ # Reset range sliders for height map
+ mapGridCountX = self.m_heightMapWidth / heightMapGridStepX
+ mapGridCountZ = self.m_heightMapHeight / heightMapGridStepZ
+ self.m_rangeMinX = 34.0
+ self.m_rangeMinZ = 18.0
+ self.m_stepX = 6.0 / float(mapGridCountX - 1)
+ self.m_stepZ = 6.0 / float(mapGridCountZ - 1)
+ self.m_axisMinSliderX.setMaximum(mapGridCountX - 2)
+ self.m_axisMinSliderX.setValue(0)
+ self.m_axisMaxSliderX.setMaximum(mapGridCountX - 1)
+ self.m_axisMaxSliderX.setValue(mapGridCountX - 1)
+ self.m_axisMinSliderZ.setMaximum(mapGridCountZ - 2)
+ self.m_axisMinSliderZ.setValue(0)
+ self.m_axisMaxSliderZ.setMaximum(mapGridCountZ - 1)
+ self.m_axisMaxSliderZ.setValue(mapGridCountZ - 1)
+
+ def adjustXMin(self, minimum):
+ minX = self.m_stepX * float(minimum) + self.m_rangeMinX
+
+ maximum = self.m_axisMaxSliderX.value()
+ if minimum >= maximum:
+ maximum = minimum + 1
+ self.m_axisMaxSliderX.setValue(maximum)
+ maxX = self.m_stepX * maximum + self.m_rangeMinX
+
+ self.setAxisXRange(minX, maxX)
+
+ def adjustXMax(self, maximum):
+ maxX = self.m_stepX * float(maximum) + self.m_rangeMinX
+
+ minimum = self.m_axisMinSliderX.value()
+ if maximum <= minimum:
+ minimum = maximum - 1
+ self.m_axisMinSliderX.setValue(minimum)
+ minX = self.m_stepX * minimum + self.m_rangeMinX
+
+ self.setAxisXRange(minX, maxX)
+
+ def adjustZMin(self, minimum):
+ minZ = self.m_stepZ * float(minimum) + self.m_rangeMinZ
+
+ maximum = self.m_axisMaxSliderZ.value()
+ if minimum >= maximum:
+ maximum = minimum + 1
+ self.m_axisMaxSliderZ.setValue(maximum)
+ maxZ = self.m_stepZ * maximum + self.m_rangeMinZ
+
+ self.setAxisZRange(minZ, maxZ)
+
+ def adjustZMax(self, maximum):
+ maxX = self.m_stepZ * float(maximum) + self.m_rangeMinZ
+
+ minimum = self.m_axisMinSliderZ.value()
+ if maximum <= minimum:
+ minimum = maximum - 1
+ self.m_axisMinSliderZ.setValue(minimum)
+ minX = self.m_stepZ * minimum + self.m_rangeMinZ
+
+ self.setAxisZRange(minX, maxX)
+
+ def setAxisXRange(self, minimum, maximum):
+ self.m_graph.axisX().setRange(minimum, maximum)
+
+ def setAxisZRange(self, minimum, maximum):
+ self.m_graph.axisZ().setRange(minimum, maximum)
+
+ @Slot()
+ def changeTheme(self, theme):
+ self.m_graph.activeTheme().setType(Q3DTheme.Theme(theme))
+
+ def setBlackToYellowGradient(self):
+ gr = QLinearGradient()
+ gr.setColorAt(0.0, Qt.black)
+ gr.setColorAt(0.33, Qt.blue)
+ gr.setColorAt(0.67, Qt.red)
+ gr.setColorAt(1.0, Qt.yellow)
+
+ self.m_graph.seriesList()[0].setBaseGradient(gr)
+ self.m_graph.seriesList()[0].setColorStyle(Q3DTheme.ColorStyleRangeGradient)
+
+ def setGreenToRedGradient(self):
+ gr = QLinearGradient()
+ gr.setColorAt(0.0, Qt.darkGreen)
+ gr.setColorAt(0.5, Qt.yellow)
+ gr.setColorAt(0.8, Qt.red)
+ gr.setColorAt(1.0, Qt.darkRed)
+
+ series = self.m_graph.seriesList()[0]
+ series.setBaseGradient(gr)
+ series.setColorStyle(Q3DTheme.ColorStyleRangeGradient)
+
+ def toggleModeNone(self):
+ self.m_graph.setSelectionMode(QAbstract3DGraph.SelectionNone)
+
+ def toggleModeItem(self):
+ self.m_graph.setSelectionMode(QAbstract3DGraph.SelectionItem)
+
+ def toggleModeSliceRow(self):
+ self.m_graph.setSelectionMode(
+ QAbstract3DGraph.SelectionItemAndRow | QAbstract3DGraph.SelectionSlice
+ )
+
+ def toggleModeSliceColumn(self):
+ self.m_graph.setSelectionMode(
+ QAbstract3DGraph.SelectionItemAndColumn | QAbstract3DGraph.SelectionSlice
+ )
+
+ def setAxisMinSliderX(self, slider):
+ self.m_axisMinSliderX = slider
+
+ def setAxisMaxSliderX(self, slider):
+ self.m_axisMaxSliderX = slider
+
+ def setAxisMinSliderZ(self, slider):
+ self.m_axisMinSliderZ = slider
+
+ def setAxisMaxSliderZ(self, slider):
+ self.m_axisMaxSliderZ = slider
diff --git a/examples/declarative/editingmodel/MovingRectangle.qml b/examples/declarative/editingmodel/MovingRectangle.qml
new file mode 100644
index 000000000..0d835af1c
--- /dev/null
+++ b/examples/declarative/editingmodel/MovingRectangle.qml
@@ -0,0 +1,115 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt for Python examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+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/declarative/editingmodel/doc/editingmodel.rst b/examples/declarative/editingmodel/doc/editingmodel.rst
new file mode 100644
index 000000000..d76bebc22
--- /dev/null
+++ b/examples/declarative/editingmodel/doc/editingmodel.rst
@@ -0,0 +1,14 @@
+QAbstractListModel in QML
+=========================
+
+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.
+
+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
+ :alt: QAbstractListModel/ListView Screenshot
diff --git a/examples/declarative/editingmodel/doc/qabstractlistmodelqml.png b/examples/declarative/editingmodel/doc/qabstractlistmodelqml.png
new file mode 100644
index 000000000..6e181fba1
--- /dev/null
+++ b/examples/declarative/editingmodel/doc/qabstractlistmodelqml.png
Binary files differ
diff --git a/examples/declarative/editingmodel/main.py b/examples/declarative/editingmodel/main.py
new file mode 100644
index 000000000..6aee0d224
--- /dev/null
+++ b/examples/declarative/editingmodel/main.py
@@ -0,0 +1,59 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import sys
+from pathlib import Path
+
+from PySide6.QtCore import QUrl
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType
+
+from model import BaseModel
+
+if __name__ == "__main__":
+ app = QGuiApplication(sys.argv)
+ qmlRegisterType(BaseModel, "BaseModel", 1, 0, "BaseModel")
+ engine = QQmlApplicationEngine()
+ qml_file = Path(__file__).parent / "main.qml"
+ engine.load(QUrl.fromLocalFile(qml_file))
+
+ if not engine.rootObjects():
+ sys.exit(-1)
+ sys.exit(app.exec())
diff --git a/examples/declarative/editingmodel/main.pyproject b/examples/declarative/editingmodel/main.pyproject
new file mode 100644
index 000000000..71272a973
--- /dev/null
+++ b/examples/declarative/editingmodel/main.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["model.py","main.qml","main.py","MovingRectangle.qml"]
+}
diff --git a/examples/declarative/editingmodel/main.qml b/examples/declarative/editingmodel/main.qml
new file mode 100644
index 000000000..8624be6cf
--- /dev/null
+++ b/examples/declarative/editingmodel/main.qml
@@ -0,0 +1,143 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt for Python examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+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/declarative/editingmodel/model.py b/examples/declarative/editingmodel/model.py
new file mode 100644
index 000000000..99736e714
--- /dev/null
+++ b/examples/declarative/editingmodel/model.py
@@ -0,0 +1,187 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+
+from PySide6.QtCore import (QAbstractListModel, QByteArray, QModelIndex, Qt,
+ Slot)
+from PySide6.QtGui import QColor
+
+
+class BaseModel(QAbstractListModel):
+
+ RatioRole = Qt.UserRole + 1
+
+ def __init__(self, parent=None):
+ super().__init__(parent=parent)
+ self.db = []
+
+ def rowCount(self, parent=QModelIndex()):
+ return len(self.db)
+
+ def roleNames(self):
+ default = super().roleNames()
+ default[self.RatioRole] = QByteArray(b"ratio")
+ default[Qt.BackgroundRole] = QByteArray(b"backgroundColor")
+ return default
+
+ def data(self, index, role: int):
+ if not self.db:
+ ret = None
+ elif not index.isValid():
+ ret = None
+ elif role == Qt.DisplayRole:
+ ret = self.db[index.row()]["text"]
+ elif role == Qt.BackgroundRole:
+ ret = self.db[index.row()]["bgColor"]
+ elif role == self.RatioRole:
+ ret = self.db[index.row()]["ratio"]
+ else:
+ ret = None
+ return ret
+
+ def setData(self, index, value, role):
+ if not index.isValid():
+ return False
+ if role == Qt.EditRole:
+ self.db[index.row()]["text"] = value
+ return True
+
+ @Slot(result=bool)
+ def append(self):
+ """Slot to append a row at the end"""
+ return self.insertRow(self.rowCount())
+
+ def insertRow(self, row):
+ """Insert a single row at row"""
+ return self.insertRows(row, 0)
+
+ def insertRows(self, row: int, count, index=QModelIndex()):
+ """Insert n rows (n = 1 + count) at row"""
+
+ self.beginInsertRows(QModelIndex(), row, row + count)
+
+ # start database work
+ if len(self.db):
+ newid = max(x["id"] for x in self.db) + 1
+ else:
+ newid = 1
+ for i in range(count + 1): # at least one row
+ self.db.insert(
+ row, {"id": newid, "text": "new", "bgColor": QColor("purple"), "ratio": 0.2}
+ )
+ # end database work
+ self.endInsertRows()
+ return True
+
+ @Slot(int, int, result=bool)
+ def move(self, source: int, target: int):
+ """Slot to move a single row from source to target"""
+ return self.moveRow(QModelIndex(), source, QModelIndex(), target)
+
+ def moveRow(self, sourceParent, sourceRow, dstParent, dstChild):
+ """Move a single row"""
+ return self.moveRows(sourceParent, sourceRow, 0, dstParent, dstChild)
+
+ def moveRows(self, sourceParent, sourceRow, count, dstParent, dstChild):
+ """Move n rows (n=1+ count) from sourceRow to dstChild"""
+
+ if sourceRow == dstChild:
+ return False
+
+ elif sourceRow > dstChild:
+ end = dstChild
+
+ else:
+ end = dstChild + 1
+
+ self.beginMoveRows(QModelIndex(), sourceRow, sourceRow + count, QModelIndex(), end)
+
+ # start database work
+ pops = self.db[sourceRow : sourceRow + count + 1]
+ if sourceRow > dstChild:
+ self.db = (
+ self.db[:dstChild]
+ + pops
+ + self.db[dstChild:sourceRow]
+ + self.db[sourceRow + count + 1 :]
+ )
+ else:
+ start = self.db[:sourceRow]
+ middle = self.db[dstChild : dstChild + 1]
+ endlist = self.db[dstChild + count + 1 :]
+ self.db = start + middle + pops + endlist
+ # end database work
+
+ self.endMoveRows()
+ return True
+
+ @Slot(int, result=bool)
+ def remove(self, row: int):
+ """Slot to remove one row"""
+ return self.removeRow(row)
+
+ def removeRow(self, row, parent=QModelIndex()):
+ """Remove one row at index row"""
+ return self.removeRows(row, 0, parent)
+
+ def removeRows(self, row: int, count: int, parent=QModelIndex()):
+ """Remove n rows (n=1+count) starting at row"""
+ self.beginRemoveRows(QModelIndex(), row, row + count)
+
+ # start database work
+ self.db = self.db[:row] + self.db[row + count + 1 :]
+ # end database work
+
+ self.endRemoveRows()
+ return True
+
+ @Slot(result=bool)
+ def reset(self):
+ self.beginResetModel()
+ self.resetInternalData() # should work without calling it ?
+ self.endResetModel()
+ return True
+
+ def resetInternalData(self):
+ self.db = [
+ {"id": 3, "bgColor": QColor("red"), "ratio": 0.15, "text": "first"},
+ {"id": 1, "bgColor": QColor("blue"), "ratio": 0.1, "text": "second"},
+ {"id": 2, "bgColor": QColor("green"), "ratio": 0.2, "text": "third"},
+ ]
diff --git a/examples/declarative/openglunderqml/doc/openglunderqml.rst b/examples/declarative/openglunderqml/doc/openglunderqml.rst
new file mode 100644
index 000000000..6a89a99d9
--- /dev/null
+++ b/examples/declarative/openglunderqml/doc/openglunderqml.rst
@@ -0,0 +1,21 @@
+OpenGL under QML Squircle
+=========================
+
+The OpenGL under QML example shows how an application can make use of the
+QQuickWindow::beforeRendering() signal to draw custom OpenGL content under a Qt
+Quick scene. This signal is emitted at the start of every frame, before the
+scene graph starts its rendering, thus any OpenGL draw calls that are made as
+a response to this signal, will stack under the Qt Quick items.
+
+As an alternative, applications that wish to render OpenGL content on top of
+the Qt Quick scene, can do so by connecting to the
+QQuickWindow::afterRendering() signal.
+
+In this example, we will also see how it is possible to have values that are
+exposed to QML which affect the OpenGL rendering. We animate the threshold
+value using a NumberAnimation in the QML file and this value is used by the
+OpenGL shader program that draws the squircles.
+
+.. image:: squircle.png
+ :width: 400
+ :alt: Squircle Screenshot
diff --git a/examples/declarative/openglunderqml/doc/squircle.png b/examples/declarative/openglunderqml/doc/squircle.png
new file mode 100644
index 000000000..c099a6b7e
--- /dev/null
+++ b/examples/declarative/openglunderqml/doc/squircle.png
Binary files differ
diff --git a/examples/declarative/openglunderqml/main.py b/examples/declarative/openglunderqml/main.py
new file mode 100644
index 000000000..26e059f93
--- /dev/null
+++ b/examples/declarative/openglunderqml/main.py
@@ -0,0 +1,66 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import sys
+from pathlib import Path
+
+from PySide6.QtCore import QUrl
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtQml import qmlRegisterType
+from PySide6.QtQuick import QQuickView, QQuickWindow, QSGRendererInterface
+
+from squircle import Squircle
+
+if __name__ == "__main__":
+ app = QGuiApplication(sys.argv)
+
+ QQuickWindow.setGraphicsApi(QSGRendererInterface.OpenGLRhi)
+ qmlRegisterType(Squircle, "OpenGLUnderQML", 1, 0, "Squircle")
+
+ view = QQuickView()
+ view.setResizeMode(QQuickView.SizeRootObjectToView)
+ qml_file = Path(__file__).parent / "main.qml"
+ view.setSource(QUrl.fromLocalFile(qml_file))
+
+ if view.status() == QQuickView.Error:
+ sys.exit(-1)
+ view.show()
+
+ sys.exit(app.exec())
diff --git a/examples/declarative/openglunderqml/main.qml b/examples/declarative/openglunderqml/main.qml
new file mode 100644
index 000000000..7edcf523b
--- /dev/null
+++ b/examples/declarative/openglunderqml/main.qml
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import OpenGLUnderQML
+
+Item {
+
+ width: 320
+ height: 480
+
+ Squircle {
+ SequentialAnimation on t {
+ NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad }
+ NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad }
+ loops: Animation.Infinite
+ running: true
+ }
+ }
+ Rectangle {
+ color: Qt.rgba(1, 1, 1, 0.7)
+ radius: 10
+ border.width: 1
+ border.color: "white"
+ anchors.fill: label
+ anchors.margins: -10
+ }
+
+ Text {
+ id: label
+ color: "black"
+ wrapMode: Text.WordWrap
+ text: "The background here is a squircle rendered with raw OpenGL using the 'beforeRender()' signal in QQuickWindow. This text label and its border is rendered using QML"
+ anchors.right: parent.right
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ anchors.margins: 20
+ }
+}
diff --git a/examples/declarative/openglunderqml/openglunderqml.pyproject b/examples/declarative/openglunderqml/openglunderqml.pyproject
new file mode 100644
index 000000000..e7cfbc570
--- /dev/null
+++ b/examples/declarative/openglunderqml/openglunderqml.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": [ "main.py", "main.qml", "squircle.py", "squirclerenderer.py"]
+}
diff --git a/examples/declarative/openglunderqml/squircle.py b/examples/declarative/openglunderqml/squircle.py
new file mode 100644
index 000000000..8d2cbca84
--- /dev/null
+++ b/examples/declarative/openglunderqml/squircle.py
@@ -0,0 +1,107 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+from PySide6.QtCore import Property, QRunnable, Qt, Signal, Slot
+from PySide6.QtQuick import QQuickItem, QQuickWindow
+
+from squirclerenderer import SquircleRenderer
+
+
+class CleanupJob(QRunnable):
+ def __init__(self, renderer):
+ super().__init__()
+ self._renderer = renderer
+
+ def run(self):
+ del self._renderer
+
+
+class Squircle(QQuickItem):
+
+ tChanged = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._t = 0.0
+ self._renderer = None
+ self.windowChanged.connect(self.handleWindowChanged)
+
+ def t(self):
+ return self._t
+
+ def setT(self, value):
+ if self._t == value:
+ return
+ self._t = value
+ self.tChanged.emit()
+ if self.window():
+ self.window().update()
+
+ @Slot(QQuickWindow)
+ def handleWindowChanged(self, win):
+ if win:
+ win.beforeSynchronizing.connect(self.sync, type=Qt.DirectConnection)
+ win.sceneGraphInvalidated.connect(self.cleanup, type=Qt.DirectConnection)
+ win.setColor(Qt.black)
+ self.sync()
+
+ def cleanup(self):
+ del self._renderer
+ self._renderer = None
+
+ @Slot()
+ def sync(self):
+ if not self._renderer:
+ self._renderer = SquircleRenderer()
+ self.window().beforeRendering.connect(self._renderer.init, Qt.DirectConnection)
+ self.window().beforeRenderPassRecording.connect(
+ self._renderer.paint, Qt.DirectConnection
+ )
+ self._renderer.setViewportSize(self.window().size() * self.window().devicePixelRatio())
+ self._renderer.setT(self._t)
+ self._renderer.setWindow(self.window())
+
+ def releaseResources(self):
+ self.window().scheduleRenderJob(
+ CleanupJob(self._renderer), QQuickWindow.BeforeSynchronizingStage
+ )
+ self._renderer = None
+
+ t = Property(float, t, setT, notify=tChanged)
diff --git a/examples/declarative/openglunderqml/squirclerenderer.py b/examples/declarative/openglunderqml/squirclerenderer.py
new file mode 100644
index 000000000..12cd93bb8
--- /dev/null
+++ b/examples/declarative/openglunderqml/squirclerenderer.py
@@ -0,0 +1,141 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+from textwrap import dedent
+
+import numpy as np
+from OpenGL.GL import (GL_ARRAY_BUFFER, GL_BLEND, GL_DEPTH_TEST, GL_FLOAT,
+ GL_ONE, GL_SRC_ALPHA, GL_TRIANGLE_STRIP)
+from PySide6.QtCore import QSize, Slot
+from PySide6.QtGui import QOpenGLFunctions
+from PySide6.QtOpenGL import (QOpenGLShader, QOpenGLShaderProgram,
+ QOpenGLVersionProfile)
+from PySide6.QtQuick import QQuickWindow, QSGRendererInterface
+
+VERTEX_SHADER = dedent(
+ """\
+ attribute highp vec4 vertices;
+ varying highp vec2 coords;
+ void main() {
+ gl_Position = vertices;
+ coords = vertices.xy;
+ }
+ """
+)
+FRAGMENT_SHADER = dedent(
+ """\
+ uniform lowp float t;
+ varying highp vec2 coords;
+ void main() {
+ lowp float i = 1. - (pow(abs(coords.x), 4.) + pow(abs(coords.y), 4.));
+ i = smoothstep(t - 0.8, t + 0.8, i);
+ i = floor(i * 20.) / 20.;
+ gl_FragColor = vec4(coords * .5 + .5, i, i);
+ }
+ """
+)
+
+
+class SquircleRenderer(QOpenGLFunctions):
+ def __init__(self):
+ QOpenGLFunctions.__init__(self)
+ self._viewport_size = QSize()
+ self._t = 0.0
+ self._program = None
+ self._window = QQuickWindow()
+ self.profile = QOpenGLVersionProfile()
+ self.gl = None
+
+ def setT(self, t):
+ self._t = t
+
+ def setViewportSize(self, size):
+ self._viewport_size = size
+
+ def setWindow(self, window):
+ self._window = window
+
+ @Slot()
+ def init(self):
+ if not self._program:
+ rif = self._window.rendererInterface()
+ assert (
+ rif.graphicsApi() == QSGRendererInterface.OpenGL
+ or rif.graphicsApi() == QSGRendererInterface.OpenGLRhy
+ )
+ self.initializeOpenGLFunctions()
+ self._program = QOpenGLShaderProgram()
+ self._program.addCacheableShaderFromSourceCode(QOpenGLShader.Vertex, VERTEX_SHADER)
+ self._program.addCacheableShaderFromSourceCode(QOpenGLShader.Fragment, FRAGMENT_SHADER)
+ self._program.bindAttributeLocation("vertices", 0)
+ self._program.link()
+
+ @Slot()
+ def paint(self):
+ # Play nice with the RHI. Not strictly needed when the scenegraph uses
+ # OpenGL directly.
+ self._window.beginExternalCommands()
+
+ self._program.bind()
+
+ self._program.enableAttributeArray(0)
+
+ values = np.array([-1, -1, 1, -1, -1, 1, 1, 1], dtype="single")
+
+ # This example relies on (deprecated) client-side pointers for the vertex
+ # input. Therefore, we have to make sure no vertex buffer is bound.
+ self.glBindBuffer(GL_ARRAY_BUFFER, 0)
+
+ self._program.setAttributeArray(0, GL_FLOAT, values, 2)
+ self._program.setUniformValue1f("t", self._t)
+
+ self.glViewport(0, 0, self._viewport_size.width(), self._viewport_size.height())
+
+ self.glDisable(GL_DEPTH_TEST)
+
+ self.glEnable(GL_BLEND)
+ self.glBlendFunc(GL_SRC_ALPHA, GL_ONE)
+
+ self.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
+
+ self._program.disableAttributeArray(0)
+ self._program.release()
+
+ self._window.endExternalCommands()
diff --git a/examples/declarative/referenceexamples/adding/adding.pyproject b/examples/declarative/referenceexamples/adding/adding.pyproject
new file mode 100644
index 000000000..46df4b253
--- /dev/null
+++ b/examples/declarative/referenceexamples/adding/adding.pyproject
@@ -0,0 +1,5 @@
+{
+ "files": ["example.qml",
+ "main.py",
+ "person.py"]
+}
diff --git a/examples/declarative/referenceexamples/adding/doc/adding.rst b/examples/declarative/referenceexamples/adding/doc/adding.rst
new file mode 100644
index 000000000..b060f3c2c
--- /dev/null
+++ b/examples/declarative/referenceexamples/adding/doc/adding.rst
@@ -0,0 +1,65 @@
+Extending QML - Adding Types Example
+====================================
+
+The Adding Types Example shows how to add a new object type, ``Person``, to QML.
+The ``Person`` type can be used from QML like this:
+
+.. code-block:: javascript
+
+ import examples.adding.people
+
+ Person {
+ name: "Bob Jones"
+ shoe_size: 12
+ }
+
+Declare the Person Class
+------------------------
+
+All QML types map to C++ types. Here we declare a basic C++ Person class
+with the two properties we want accessible on the QML type - name and shoeSize.
+Although in this example we use the same name for the C++ class as the QML
+type, the C++ class can be named differently, or appear in a namespace.
+
+The Person class implementation is quite basic. The property accessors simply
+return members of the object instance.
+
+.. code-block:: python
+
+ from PySide6.QtCore import QObject, Property
+ from PySide6.QtQml import QmlElement
+
+ # To be used on the @QmlElement decorator
+ # (QML_IMPORT_MINOR_VERSION is optional)
+ QML_IMPORT_NAME = "examples.adding.people"
+ QML_IMPORT_MAJOR_VERSION = 1
+
+
+ @QmlElement
+ class Person(QObject):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._name = ''
+ self._shoe_size = 0
+
+ @Property(str)
+ def name(self):
+ return self._name
+
+ @name.setter
+ def name(self, n):
+ self._name = n
+
+ @Property(int)
+ def shoe_size(self):
+ return self._shoe_size
+
+ @shoe_size.setter
+ def shoe_size(self, s):
+ self._shoe_size = s
+
+Running the Example
+-------------------
+
+The main.py file in the example includes a simple shell application that
+loads and runs the QML snippet shown at the beginning of this page.
diff --git a/examples/declarative/referenceexamples/adding/example.qml b/examples/declarative/referenceexamples/adding/example.qml
new file mode 100644
index 000000000..e452b2283
--- /dev/null
+++ b/examples/declarative/referenceexamples/adding/example.qml
@@ -0,0 +1,56 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt for Python examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import examples.adding.people
+
+Person {
+ name: "Bob Jones"
+ shoe_size: 12
+}
diff --git a/examples/declarative/referenceexamples/adding/main.py b/examples/declarative/referenceexamples/adding/main.py
new file mode 100644
index 000000000..ffa10e9d3
--- /dev/null
+++ b/examples/declarative/referenceexamples/adding/main.py
@@ -0,0 +1,67 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+"""PySide6 port of the qml/examples/qml/referenceexamples/adding example from Qt v6.x"""
+
+from pathlib import Path
+import sys
+
+from PySide6.QtCore import QCoreApplication, QUrl
+from PySide6.QtQml import QQmlComponent, QQmlEngine
+
+from person import Person
+
+
+if __name__ == '__main__':
+ app = QCoreApplication(sys.argv)
+
+ qml_file = Path(__file__).parent / "example.qml"
+ url = QUrl.fromLocalFile(qml_file)
+ engine = QQmlEngine()
+ component = QQmlComponent(engine, url)
+
+ person = component.create()
+ if person:
+ print(f"The person's name is {person.name}")
+ print(f"They wear a {person.shoe_size} sized shoe")
+ else:
+ print(component.errors())
+ del engine
+ sys.exit(0)
diff --git a/examples/declarative/referenceexamples/adding/person.py b/examples/declarative/referenceexamples/adding/person.py
new file mode 100644
index 000000000..23ac5378d
--- /dev/null
+++ b/examples/declarative/referenceexamples/adding/person.py
@@ -0,0 +1,72 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+from PySide6.QtCore import QObject, Property
+from PySide6.QtQml import QmlElement
+
+# To be used on the @QmlElement decorator
+# (QML_IMPORT_MINOR_VERSION is optional)
+QML_IMPORT_NAME = "examples.adding.people"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+class Person(QObject):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._name = ''
+ self._shoe_size = 0
+
+ @Property(str)
+ def name(self):
+ return self._name
+
+ @name.setter
+ def name(self, n):
+ self._name = n
+
+ @Property(int)
+ def shoe_size(self):
+ return self._shoe_size
+
+ @shoe_size.setter
+ def shoe_size(self, s):
+ self._shoe_size = s
+
diff --git a/examples/declarative/rendercontrol/rendercontrol_opengl/cuberenderer.py b/examples/declarative/rendercontrol/rendercontrol_opengl/cuberenderer.py
new file mode 100644
index 000000000..90cd78e65
--- /dev/null
+++ b/examples/declarative/rendercontrol/rendercontrol_opengl/cuberenderer.py
@@ -0,0 +1,223 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import ctypes
+import numpy
+from OpenGL.GL import (GL_COLOR_BUFFER_BIT, GL_CULL_FACE, GL_CW,
+ GL_DEPTH_BUFFER_BIT, GL_DEPTH_TEST, GL_FALSE, GL_FLOAT,
+ GL_TEXTURE_2D, GL_TRIANGLES)
+
+from PySide6.QtGui import (QMatrix4x4, QOffscreenSurface, QOpenGLContext,
+ QOpenGLFunctions, QWindow)
+from PySide6.QtOpenGL import (QOpenGLBuffer, QOpenGLShader,
+ QOpenGLShaderProgram, QOpenGLVertexArrayObject)
+from shiboken6 import VoidPtr
+
+
+VERTEXSHADER_SOURCE = """attribute highp vec4 vertex;
+attribute lowp vec2 coord;
+varying lowp vec2 v_coord;
+uniform highp mat4 matrix;
+void main() {
+ v_coord = coord;
+ gl_Position = matrix * vertex;
+}
+"""
+
+
+FRAGMENTSHADER_SOURCE = """varying lowp vec2 v_coord;
+uniform sampler2D sampler;
+void main() {
+ gl_FragColor = vec4(texture2D(sampler, v_coord).rgb, 1.0);
+}
+"""
+
+
+FLOAT_SIZE = ctypes.sizeof(ctypes.c_float)
+
+
+VERTEXES = numpy.array([-0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5,
+ 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
+ -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5,
+ 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5,
+
+ 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5,
+ 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5,
+ -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5,
+ -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5,
+
+ 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5,
+ -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5,
+ -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5,
+ 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5],
+ dtype=numpy.float32)
+
+
+TEX_COORDS = numpy.array([0.0, 0.0, 1.0, 1.0, 1.0, 0.0,
+ 1.0, 1.0, 0.0, 0.0, 0.0, 1.0,
+ 1.0, 1.0, 1.0, 0.0, 0.0, 1.0,
+ 0.0, 0.0, 0.0, 1.0, 1.0, 0.0,
+
+ 1.0, 1.0, 1.0, 0.0, 0.0, 1.0,
+ 0.0, 0.0, 0.0, 1.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0, 1.0, 1.0, 0.0,
+ 1.0, 1.0, 0.0, 0.0, 0.0, 1.0,
+
+ 0.0, 1.0, 1.0, 0.0, 1.0, 1.0,
+ 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
+ 1.0, 0.0, 1.0, 1.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0, 0.0, 1.0, 1.0], dtype=numpy.float32)
+
+
+class CubeRenderer():
+ def __init__(self, offscreenSurface):
+ self.m_angle = 0
+ self.m_offscreenSurface = offscreenSurface
+ self.m_context = None
+ self.m_program = None
+ self.m_vbo = None
+ self.m_vao = None
+ self.m_matrixLoc = 0
+ self.m_proj = QMatrix4x4()
+
+ def __del__(self):
+ # Use a temporary offscreen surface to do the cleanup. There may not
+ # be a native window surface available anymore at self stage.
+ self.m_context.makeCurrent(self.m_offscreenSurface)
+ del self.m_program
+ del self.m_vbo
+ del self.m_vao
+ self.m_context.doneCurrent()
+
+ def init(self, w, share):
+ self.m_context = QOpenGLContext()
+ self.m_context.setShareContext(share)
+ self.m_context.setFormat(w.requestedFormat())
+ self.m_context.create()
+ if not self.m_context.makeCurrent(w):
+ return
+
+ f = self.m_context.functions()
+ f.glClearColor(0.0, 0.1, 0.25, 1.0)
+ f.glViewport(0, 0, w.width() * w.devicePixelRatio(),
+ w.height() * w.devicePixelRatio())
+
+ self.m_program = QOpenGLShaderProgram()
+ self.m_program.addCacheableShaderFromSourceCode(QOpenGLShader.Vertex,
+ VERTEXSHADER_SOURCE)
+ self.m_program.addCacheableShaderFromSourceCode(QOpenGLShader.Fragment,
+ FRAGMENTSHADER_SOURCE)
+ self.m_program.bindAttributeLocation("vertex", 0)
+ self.m_program.bindAttributeLocation("coord", 1)
+ self.m_program.link()
+ self.m_matrixLoc = self.m_program.uniformLocation("matrix")
+
+ self.m_vao = QOpenGLVertexArrayObject()
+ self.m_vao.create()
+ vaoBinder = QOpenGLVertexArrayObject.Binder(self.m_vao)
+
+ self.m_vbo = QOpenGLBuffer()
+ self.m_vbo.create()
+ self.m_vbo.bind()
+
+ vertexCount = 36
+ self.m_vbo.allocate(FLOAT_SIZE * vertexCount * 5)
+ vertex_data = VERTEXES.tobytes()
+ tex_coord_data = TEX_COORDS.tobytes()
+ self.m_vbo.write(0, VoidPtr(vertex_data),
+ FLOAT_SIZE * vertexCount * 3)
+ self.m_vbo.write(FLOAT_SIZE * vertexCount * 3,
+ VoidPtr(tex_coord_data),
+ FLOAT_SIZE * vertexCount * 2)
+ self.m_vbo.release()
+
+ if self.m_vao.isCreated():
+ self.setupVertexAttribs()
+
+ def resize(self, w, h):
+ self.m_proj.setToIdentity()
+ self.m_proj.perspective(45, w / float(h), 0.01, 100.0)
+
+ def setupVertexAttribs(self):
+ self.m_vbo.bind()
+ self.m_program.enableAttributeArray(0)
+ self.m_program.enableAttributeArray(1)
+ f = self.m_context.functions()
+
+ null = VoidPtr(0)
+ pointer = VoidPtr(36 * 3 * FLOAT_SIZE)
+ f.glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, null)
+ f.glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, pointer)
+ self.m_vbo.release()
+
+ def render(self, w, share, texture):
+ if not self.m_context:
+ self.init(w, share)
+
+ if not self.m_context.makeCurrent(w):
+ return
+
+ f = self.m_context.functions()
+ f.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+
+ if texture:
+ f.glBindTexture(GL_TEXTURE_2D, texture)
+ f.glFrontFace(GL_CW) # because our cube's vertex data is such
+ f.glEnable(GL_CULL_FACE)
+ f.glEnable(GL_DEPTH_TEST)
+
+ self.m_program.bind()
+ vaoBinder = QOpenGLVertexArrayObject.Binder(self.m_vao)
+ # If VAOs are not supported, set the vertex attributes every time.
+ if not self.m_vao.isCreated():
+ self.setupVertexAttribs()
+
+ m = QMatrix4x4()
+ m.translate(0, 0, -2)
+ m.rotate(90, 0, 0, 1)
+ m.rotate(self.m_angle, 0.5, 1, 0)
+ self.m_angle += 0.5
+
+ self.m_program.setUniformValue(self.m_matrixLoc, self.m_proj * m)
+
+ # Draw the cube.
+ f.glDrawArrays(GL_TRIANGLES, 0, 36)
+
+ self.m_context.swapBuffers(w)
diff --git a/examples/declarative/rendercontrol/rendercontrol_opengl/demo.qml b/examples/declarative/rendercontrol/rendercontrol_opengl/demo.qml
new file mode 100644
index 000000000..aeffc7646
--- /dev/null
+++ b/examples/declarative/rendercontrol/rendercontrol_opengl/demo.qml
@@ -0,0 +1,208 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt for Python examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtQuick.Particles 2.0
+
+Rectangle {
+ id: root
+
+ gradient: Gradient {
+ GradientStop { position: 0; color: mouse.pressed ? "lightsteelblue" : "steelblue" }
+ GradientStop { position: 1; color: "black" }
+ }
+
+ Text {
+ anchors.centerIn: parent
+ text: "Qt Quick in a texture"
+ font.pointSize: 40
+ color: "white"
+
+ SequentialAnimation on rotation {
+ PauseAnimation { duration: 2500 }
+ NumberAnimation { from: 0; to: 360; duration: 5000; easing.type: Easing.InOutCubic }
+ loops: Animation.Infinite
+ }
+ }
+
+ ParticleSystem {
+ id: particles
+ anchors.fill: parent
+
+ ImageParticle {
+ id: smoke
+ system: particles
+ anchors.fill: parent
+ groups: ["A", "B"]
+ source: "qrc:///particleresources/glowdot.png"
+ colorVariation: 0
+ color: "#00111111"
+ }
+ ImageParticle {
+ id: flame
+ anchors.fill: parent
+ system: particles
+ groups: ["C", "D"]
+ source: "qrc:///particleresources/glowdot.png"
+ colorVariation: 0.1
+ color: "#00ff400f"
+ }
+
+ Emitter {
+ id: fire
+ system: particles
+ group: "C"
+
+ y: parent.height
+ width: parent.width
+
+ emitRate: 350
+ lifeSpan: 3500
+
+ acceleration: PointDirection { y: -17; xVariation: 3 }
+ velocity: PointDirection {xVariation: 3}
+
+ size: 24
+ sizeVariation: 8
+ endSize: 4
+ }
+
+ TrailEmitter {
+ id: fireSmoke
+ group: "B"
+ system: particles
+ follow: "C"
+ width: root.width
+ height: root.height - 68
+
+ emitRatePerParticle: 1
+ lifeSpan: 2000
+
+ velocity: PointDirection {y:-17*6; yVariation: -17; xVariation: 3}
+ acceleration: PointDirection {xVariation: 3}
+
+ size: 36
+ sizeVariation: 8
+ endSize: 16
+ }
+
+ TrailEmitter {
+ id: fireballFlame
+ anchors.fill: parent
+ system: particles
+ group: "D"
+ follow: "E"
+
+ emitRatePerParticle: 120
+ lifeSpan: 180
+ emitWidth: TrailEmitter.ParticleSize
+ emitHeight: TrailEmitter.ParticleSize
+ emitShape: EllipseShape{}
+
+ size: 16
+ sizeVariation: 4
+ endSize: 4
+ }
+
+ TrailEmitter {
+ id: fireballSmoke
+ anchors.fill: parent
+ system: particles
+ group: "A"
+ follow: "E"
+
+ emitRatePerParticle: 128
+ lifeSpan: 2400
+ emitWidth: TrailEmitter.ParticleSize
+ emitHeight: TrailEmitter.ParticleSize
+ emitShape: EllipseShape{}
+
+ velocity: PointDirection {yVariation: 16; xVariation: 16}
+ acceleration: PointDirection {y: -16}
+
+ size: 24
+ sizeVariation: 8
+ endSize: 8
+ }
+
+ Emitter {
+ id: balls
+ system: particles
+ group: "E"
+
+ y: parent.height
+ width: parent.width
+
+ emitRate: 2
+ lifeSpan: 7000
+
+ velocity: PointDirection {y:-17*4*2; xVariation: 6*6}
+ acceleration: PointDirection {y: 17*2; xVariation: 6*6}
+
+ size: 8
+ sizeVariation: 4
+ }
+
+ Turbulence { //A bit of turbulence makes the smoke look better
+ anchors.fill: parent
+ groups: ["A","B"]
+ strength: 32
+ system: particles
+ }
+ }
+
+ onWidthChanged: particles.reset()
+ onHeightChanged: particles.reset()
+
+ MouseArea {
+ id: mouse
+ anchors.fill: parent
+ }
+}
diff --git a/examples/declarative/rendercontrol/rendercontrol_opengl/doc/rendercontrol_opengl.rst b/examples/declarative/rendercontrol/rendercontrol_opengl/doc/rendercontrol_opengl.rst
new file mode 100644
index 000000000..f47567f52
--- /dev/null
+++ b/examples/declarative/rendercontrol/rendercontrol_opengl/doc/rendercontrol_opengl.rst
@@ -0,0 +1,5 @@
+QQuickRenderControl OpenGL Example
+==================================
+
+The QQuickRenderControl OpenGL Example shows how to render a Qt Quick scene into a
+texture that is then used by a non-Quick based OpenGL renderer.
diff --git a/examples/declarative/rendercontrol/rendercontrol_opengl/main.py b/examples/declarative/rendercontrol/rendercontrol_opengl/main.py
new file mode 100644
index 000000000..84a857838
--- /dev/null
+++ b/examples/declarative/rendercontrol/rendercontrol_opengl/main.py
@@ -0,0 +1,57 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import sys
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtQuick import QQuickWindow, QSGRendererInterface
+
+from window_singlethreaded import WindowSingleThreaded
+
+
+if __name__ == "__main__":
+ app = QGuiApplication(sys.argv)
+ # only functional when Qt Quick is also using OpenGL
+ QQuickWindow.setGraphicsApi(QSGRendererInterface.OpenGLRhi)
+ window = WindowSingleThreaded()
+ window.resize(1024, 768)
+ window.show()
+ ex = app.exec()
+ del window
+ sys.exit(ex)
diff --git a/examples/declarative/rendercontrol/rendercontrol_opengl/rendercontrol_opengl.pyproject b/examples/declarative/rendercontrol/rendercontrol_opengl/rendercontrol_opengl.pyproject
new file mode 100644
index 000000000..b2e80ab23
--- /dev/null
+++ b/examples/declarative/rendercontrol/rendercontrol_opengl/rendercontrol_opengl.pyproject
@@ -0,0 +1,6 @@
+{
+ "files": ["cuberenderer.py",
+ "main.py",
+ "window_singlethreaded.py",
+ "demo.qml"]
+}
diff --git a/examples/declarative/rendercontrol/rendercontrol_opengl/window_singlethreaded.py b/examples/declarative/rendercontrol/rendercontrol_opengl/window_singlethreaded.py
new file mode 100644
index 000000000..08b3236a2
--- /dev/null
+++ b/examples/declarative/rendercontrol/rendercontrol_opengl/window_singlethreaded.py
@@ -0,0 +1,308 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import numpy
+from pathlib import Path
+import sys
+import weakref
+from OpenGL.GL import (GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER,
+ GL_NEAREST, GL_RGBA, GL_TEXTURE_2D, GL_UNSIGNED_BYTE)
+
+from PySide6.QtGui import (QMatrix4x4, QMouseEvent, QOffscreenSurface,
+ QOpenGLContext, QOpenGLFunctions, QScreen, QSurface,
+ QSurfaceFormat, QWindow)
+from PySide6.QtOpenGL import (QOpenGLFramebufferObject, QOpenGLTexture,
+ QOpenGLShaderProgram, QOpenGLVertexArrayObject,
+ QOpenGLBuffer)
+from PySide6.QtQml import QQmlComponent, QQmlEngine
+from PySide6.QtQuick import (QQuickGraphicsDevice,
+ QQuickItem, QQuickRenderControl,
+ QQuickRenderTarget, QQuickWindow)
+from PySide6.QtCore import QCoreApplication, QTimer, QUrl
+from shiboken6 import VoidPtr
+
+from cuberenderer import CubeRenderer
+
+
+class RenderControl(QQuickRenderControl):
+ def __init__(self, window=None):
+ super().__init__()
+ self._window = window
+
+ def renderWindow(self, offset):
+ return self._window() # Dereference the weak reference
+
+
+class WindowSingleThreaded(QWindow):
+
+ def __init__(self):
+ super().__init__()
+ self.m_rootItem = None
+ self.m_device = None
+ self.m_texture_ids = numpy.array([0], dtype=numpy.uint32)
+
+ self.m_quickInitialized = False
+ self.m_quickReady = False
+ self.m_dpr = 0
+ self.m_status_conn_id = None
+ self.setSurfaceType(QSurface.OpenGLSurface)
+
+ format = QSurfaceFormat()
+ # Qt Quick may need a depth and stencil buffer. Always make sure these
+ # are available.
+ format.setDepthBufferSize(16)
+ format.setStencilBufferSize(8)
+ self.setFormat(format)
+
+ self.m_context = QOpenGLContext()
+ self.m_context.setFormat(format)
+ self.m_context.create()
+
+ self.m_offscreenSurface = QOffscreenSurface()
+ # Pass m_context.format(), not format. Format does not specify and
+ # color buffer sizes, while the context, that has just been created,
+ # reports a format that has these values filled in. Pass self to the
+ # offscreen surface to make sure it will be compatible with the
+ # context's configuration.
+ self.m_offscreenSurface.setFormat(self.m_context.format())
+ self.m_offscreenSurface.create()
+
+ self.m_cubeRenderer = CubeRenderer(self.m_offscreenSurface)
+
+ self.m_renderControl = RenderControl(weakref.ref(self))
+
+ # Create a QQuickWindow that is associated with out render control.
+ # Note that this window never gets created or shown, meaning that
+ # will never get an underlying native (platform) window.
+ self.m_quickWindow = QQuickWindow(self.m_renderControl)
+
+ # Create a QML engine.
+ self.m_qmlEngine = QQmlEngine()
+ if not self.m_qmlEngine.incubationController():
+ c = self.m_quickWindow.incubationController()
+ self.m_qmlEngine.setIncubationController(c)
+
+ # When Quick says there is a need to render, we will not render
+ # immediately. Instead, a timer with a small interval is used
+ # to get better performance.
+ self.m_updateTimer = QTimer()
+ self.m_updateTimer.setSingleShot(True)
+ self.m_updateTimer.setInterval(5)
+ self.m_updateTimer.timeout.connect(self.render)
+
+ # Now hook up the signals. For simplicy we don't differentiate between
+ # renderRequested (only render is needed, no sync) and sceneChanged
+ # (polish and sync is needed too).
+ self.m_quickWindow.sceneGraphInitialized.connect(self.createTexture)
+ self.m_quickWindow.sceneGraphInvalidated.connect(self.destroyTexture)
+ self.m_renderControl.renderRequested.connect(self.requestUpdate)
+ self.m_renderControl.sceneChanged.connect(self.requestUpdate)
+
+ # Just recreating the texture on resize is not sufficient, when moving
+ # between screens with different devicePixelRatio the QWindow size may
+ # remain the same but the texture dimension is to change regardless.
+ self.screenChanged.connect(self.handleScreenChange)
+
+ def __del__(self):
+ # Make sure the context is current while doing cleanup. Note that
+ # we use the offscreen surface here because passing 'self' at self
+ # point is not safe: the underlying platform window may already be
+ # destroyed. To avoid all the trouble, use another surface that is
+ # valid for sure.
+ self.m_context.makeCurrent(self.m_offscreenSurface)
+
+ del self.m_qmlComponent
+ del self.m_qmlEngine
+ del self.m_quickWindow
+ del self.m_renderControl
+
+ if self.texture_id():
+ self.m_context.functions().glDeleteTextures(1, self.m_texture_ids)
+
+ self.m_context.doneCurrent()
+
+ def texture_id(self):
+ return self.m_texture_ids[0]
+
+ def set_texture_id(self, texture_id):
+ self.m_texture_ids[0] = texture_id
+
+ def createTexture(self):
+ # The scene graph has been initialized. It is now time to create a
+ # texture and associate it with the QQuickWindow.
+ self.m_dpr = self.devicePixelRatio()
+ self.m_textureSize = self.size() * self.m_dpr
+ f = self.m_context.functions()
+ f.glGenTextures(1, self.m_texture_ids)
+ f.glBindTexture(GL_TEXTURE_2D, self.texture_id())
+
+ f.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
+ f.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
+ null = VoidPtr(0)
+ f.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.m_textureSize.width(),
+ self.m_textureSize.height(), 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, null)
+ target = QQuickRenderTarget.fromOpenGLTexture(self.texture_id(),
+ self.m_textureSize)
+ self.m_quickWindow.setRenderTarget(target)
+
+ def destroyTexture(self):
+ self.m_context.functions().glDeleteTextures(1, self.m_texture_ids)
+ self.set_texture_id(0)
+
+ def render(self):
+ if not self.m_context.makeCurrent(self.m_offscreenSurface):
+ return
+
+ # Polish, synchronize and render the next frame (into our texture).
+ # In this example everything happens on the same thread and therefore
+ # all three steps are performed in succession from here. In a threaded
+ # setup the render() call would happen on a separate thread.
+ self.m_renderControl.beginFrame()
+ self.m_renderControl.polishItems()
+ self.m_renderControl.sync()
+ self.m_renderControl.render()
+ self.m_renderControl.endFrame()
+
+ QOpenGLFramebufferObject.bindDefault()
+ self.m_context.functions().glFlush()
+
+ self.m_quickReady = True
+
+ # Get something onto the screen.
+ texture_id = self.texture_id() if self.m_quickReady else 0
+ self.m_cubeRenderer.render(self, self.m_context, texture_id)
+
+ def requestUpdate(self):
+ if not self.m_updateTimer.isActive():
+ self.m_updateTimer.start()
+
+ def run(self):
+ if self.m_status_conn_id:
+ self.m_qmlComponent.statusChanged.disconnect(self.m_status_conn_id)
+ self.m_status_conn_id = None
+
+ if self.m_qmlComponent.isError():
+ for error in self.m_qmlComponent.errors():
+ print(error.url().toString(), error.line(), error.toString())
+ return
+
+ self.m_rootItem = self.m_qmlComponent.create()
+ if self.m_qmlComponent.isError():
+ for error in self.m_qmlComponent.errors():
+ print(error.url().toString(), error.line(), error.toString())
+ return
+
+ if not self.m_rootItem:
+ print("run: Not a QQuickItem")
+ del self.m_rootItem
+
+ # The root item is ready. Associate it with the window.
+ self.m_rootItem.setParentItem(self.m_quickWindow.contentItem())
+
+ # Update item and rendering related geometries.
+ self.updateSizes()
+
+ # Initialize the render control and our OpenGL resources.
+ self.m_context.makeCurrent(self.m_offscreenSurface)
+ self.m_device = QQuickGraphicsDevice.fromOpenGLContext(self.m_context)
+ self.m_quickWindow.setGraphicsDevice(self.m_device)
+ self.m_renderControl.initialize()
+ self.m_quickInitialized = True
+
+ def updateSizes(self):
+ # Behave like SizeRootObjectToView.
+ w = self.width()
+ h = self.height()
+ self.m_rootItem.setWidth(w)
+ self.m_rootItem.setHeight(h)
+ self.m_quickWindow.setGeometry(0, 0, w, h)
+ self.m_cubeRenderer.resize(w, h)
+
+ def startQuick(self, filename):
+ url = QUrl.fromLocalFile(filename)
+ self.m_qmlComponent = QQmlComponent(self.m_qmlEngine, url)
+ if self.m_qmlComponent.isLoading():
+ self.m_status_conn_id = self.m_qmlComponent.statusChanged.connect(self.run)
+ else:
+ self.run()
+
+ def exposeEvent(self, event):
+ if self.isExposed() and not self.m_quickInitialized:
+ texture_id = self.texture_id() if self.m_quickReady else 0
+ self.m_cubeRenderer.render(self, self.m_context, texture_id)
+ qml_file = Path(__file__).parent / "demo.qml"
+ self.startQuick(qml_file)
+
+ def resizeTexture(self):
+ if self.m_rootItem and self.m_context.makeCurrent(self.m_offscreenSurface):
+ self.m_context.functions().glDeleteTextures(1, self.m_texture_ids)
+ self.set_texture_id(0)
+ self.createTexture()
+ self.m_context.doneCurrent()
+ self.updateSizes()
+ self.render()
+
+ def resizeEvent(self, event):
+ # If self is a resize after the scene is up and running, recreate the
+ # texture and the Quick item and scene.
+ if (self.texture_id()
+ and self.m_textureSize != self.size() * self.devicePixelRatio()):
+ self.resizeTexture()
+
+ def handleScreenChange(self):
+ if self.m_dpr != self.devicePixelRatio():
+ self.resizeTexture()
+
+ def mousePressEvent(self, e):
+ # Use the constructor taking position and globalPosition. That puts
+ # position into the event's position and scenePosition, and
+ # globalPosition into the event's globalPosition. This way the
+ # scenePosition in `e` is ignored and is replaced by position.
+ # This is necessary because QQuickWindow thinks of itself as
+ # a top-level window always.
+ mappedEvent = QMouseEvent(e.type(), e.position(), e.globalPosition(),
+ e.button(), e.buttons(), e.modifiers())
+ QCoreApplication.sendEvent(self.m_quickWindow, mappedEvent)
+
+ def mouseReleaseEvent(self, e):
+ mappedEvent = QMouseEvent(e.type(), e.position(), e.globalPosition(),
+ e.button(), e.buttons(), e.modifiers())
+ QCoreApplication.sendEvent(self.m_quickWindow, mappedEvent)
diff --git a/examples/declarative/stringlistmodel/doc/stringlistmodel.png b/examples/declarative/stringlistmodel/doc/stringlistmodel.png
index 6058c5930..eeb9b518a 100644
--- a/examples/declarative/stringlistmodel/doc/stringlistmodel.png
+++ b/examples/declarative/stringlistmodel/doc/stringlistmodel.png
Binary files differ
diff --git a/examples/declarative/textproperties/doc/textproperties.rst b/examples/declarative/textproperties/doc/textproperties.rst
index a67e35f0f..81829dfdc 100644
--- a/examples/declarative/textproperties/doc/textproperties.rst
+++ b/examples/declarative/textproperties/doc/textproperties.rst
@@ -4,6 +4,6 @@ Text Properties Example
A Python application that demonstrates how to load a qml file
using Material design, to change the look of text.
-.. image:: scrolling.png
+.. image:: textproperties.png
:width: 400
:alt: Text Properties Screenshot
diff --git a/examples/installer_test/hello.py b/examples/installer_test/hello.py
index 94d82d4ab..775fe1532 100644
--- a/examples/installer_test/hello.py
+++ b/examples/installer_test/hello.py
@@ -62,8 +62,7 @@ from PySide6.QtWidgets import (QApplication, QLabel, QPushButton,
from PySide6.QtCore import Slot, Qt, QTimer
is_compiled = "__compiled__" in globals() # Nuitka
-uses_embedding = sys.pyside_uses_embedding # PyInstaller
-auto_quit = "Nuitka" if is_compiled else "PyInst" if uses_embedding else False
+auto_quit = "Nuitka" if is_compiled else "PyInst"
class MyWidget(QWidget):
diff --git a/examples/quick/painteditem/main.qml b/examples/quick/painteditem/main.qml
index 182fdc986..3b5999ae0 100644
--- a/examples/quick/painteditem/main.qml
+++ b/examples/quick/painteditem/main.qml
@@ -62,7 +62,7 @@ Item {
anchors.top: parent.top
id: balloonView
delegate: TextBalloon {
- anchors.right: index % 2 == 0 ? undefined : parent.right
+ anchors.right: index % 2 == 0 ? undefined : balloonView.contentItem.right
height: 60
rightAligned: index % 2 == 0 ? false : true
width: balloonWidth
diff --git a/examples/quick/painteditem/painteditem.py b/examples/quick/painteditem/painteditem.py
index ff744063a..04a9116a8 100644
--- a/examples/quick/painteditem/painteditem.py
+++ b/examples/quick/painteditem/painteditem.py
@@ -1,6 +1,6 @@
#############################################################################
##
-## Copyright (C) 2020 The Qt Company Ltd.
+## Copyright (C) 2021 The Qt Company Ltd.
## Contact: http://www.qt.io/licensing/
##
## This file is part of the Qt for Python examples of the Qt Toolkit.
@@ -38,6 +38,7 @@
##
#############################################################################
+from pathlib import Path
import sys
from PySide6.QtGui import QPainter, QBrush, QColor
@@ -60,7 +61,7 @@ class TextBalloon(QQuickPaintedItem):
return self._rightAligned
@rightAligned.setter
- def rightAlignedSet(self, value):
+ def rightAligned(self, value):
self._rightAligned = value
self.rightAlignedChanged.emit()
@@ -97,7 +98,8 @@ if __name__ == "__main__":
view = QQuickView()
view.setResizeMode(QQuickView.SizeRootObjectToView)
qmlRegisterType(TextBalloon, "TextBalloonPlugin", 1, 0, "TextBalloon")
- view.setSource(QUrl.fromLocalFile("main.qml"))
+ qml_file = Path(__file__).parent / "main.qml"
+ view.setSource(QUrl.fromLocalFile(qml_file))
if view.status() == QQuickView.Error:
sys.exit(-1)
diff --git a/examples/quickcontrols2/gallery/gallery.py b/examples/quickcontrols2/gallery/gallery.py
index f4c3f3795..adb6896f8 100644
--- a/examples/quickcontrols2/gallery/gallery.py
+++ b/examples/quickcontrols2/gallery/gallery.py
@@ -60,7 +60,7 @@ import sys
import platform
from PySide6.QtGui import QGuiApplication, QIcon
-from PySide6.QtCore import QSettings
+from PySide6.QtCore import QSettings, QUrl
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtQuickControls2 import QQuickStyle
@@ -88,7 +88,7 @@ if __name__ == "__main__":
built_in_styles.append("Windows")
engine.setInitialProperties({"builtInStyles": built_in_styles})
- engine.load(":/gallery.qml")
+ engine.load(QUrl.fromLocalFile(":/gallery.qml"))
rootObjects = engine.rootObjects()
if not rootObjects:
sys.exit(-1)
diff --git a/examples/scriptableapplication/pythonutils.cpp b/examples/scriptableapplication/pythonutils.cpp
index 18ac35112..c23bc46e9 100644
--- a/examples/scriptableapplication/pythonutils.cpp
+++ b/examples/scriptableapplication/pythonutils.cpp
@@ -135,7 +135,7 @@ bool bindAppObject(const QString &moduleName, const QString &name,
return false;
PyTypeObject *typeObject = SbkAppLibTypes[index];
- PyObject *po = Shiboken::Conversions::pointerToPython(reinterpret_cast<SbkObjectType *>(typeObject), o);
+ PyObject *po = Shiboken::Conversions::pointerToPython(typeObject, o);
if (!po) {
qWarning() << __FUNCTION__ << "Failed to create wrapper for" << o;
return false;
diff --git a/examples/webenginewidgets/markdowneditor/document.py b/examples/webenginewidgets/markdowneditor/document.py
new file mode 100644
index 000000000..348323704
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/document.py
@@ -0,0 +1,61 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+
+from PySide6.QtCore import QObject, Property, Signal
+
+
+class Document(QObject):
+
+ textChanged = Signal(str)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._text = ''
+
+ def text(self):
+ return self._text
+
+ def setText(self, t):
+ if t != self._text:
+ self._text = t
+ self.textChanged.emit(t)
+
+ text = Property(str, text, setText, notify=textChanged)
diff --git a/examples/webenginewidgets/markdowneditor/main.py b/examples/webenginewidgets/markdowneditor/main.py
new file mode 100644
index 000000000..08d07a036
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/main.py
@@ -0,0 +1,57 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+"""PySide6 Markdown Editor Example"""
+
+import sys
+
+from PySide6.QtCore import QCoreApplication
+from PySide6.QtWidgets import QApplication
+
+from mainwindow import MainWindow
+import rc_markdowneditor
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ QCoreApplication.setOrganizationName("QtExamples")
+ window = MainWindow()
+ window.show()
+ sys.exit(app.exec())
diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.py b/examples/webenginewidgets/markdowneditor/mainwindow.py
new file mode 100644
index 000000000..4f19f4323
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/mainwindow.py
@@ -0,0 +1,173 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+
+from PySide6.QtCore import QDir, QFile, QIODevice, QUrl, Qt, Slot
+from PySide6.QtGui import QFontDatabase
+from PySide6.QtWebChannel import QWebChannel
+from PySide6.QtWidgets import QDialog, QFileDialog, QMainWindow, QMessageBox
+
+from ui_mainwindow import Ui_MainWindow
+from document import Document
+from previewpage import PreviewPage
+
+
+class MainWindow(QMainWindow):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.m_file_path = ''
+ self.m_content = Document()
+ self._ui = Ui_MainWindow()
+ self._ui.setupUi(self)
+ font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
+ self._ui.editor.setFont(font)
+ self._ui.preview.setContextMenuPolicy(Qt.NoContextMenu)
+ self._page = PreviewPage(self)
+ self._ui.preview.setPage(self._page)
+
+ self._ui.editor.textChanged.connect(self.plainTextEditChanged)
+
+ self._channel = QWebChannel(self)
+ self._channel.registerObject("content", self.m_content)
+ self._page.setWebChannel(self._channel)
+
+ self._ui.preview.setUrl(QUrl("qrc:/index.html"))
+
+ self._ui.actionNew.triggered.connect(self.onFileNew)
+ self._ui.actionOpen.triggered.connect(self.onFileOpen)
+ self._ui.actionSave.triggered.connect(self.onFileSave)
+ self._ui.actionSaveAs.triggered.connect(self.onFileSaveAs)
+ self._ui.actionExit.triggered.connect(self.close)
+
+ self._ui.editor.document().modificationChanged.connect(self._ui.actionSave.setEnabled)
+
+ defaultTextFile = QFile(":/default.md")
+ defaultTextFile.open(QIODevice.ReadOnly)
+ data = defaultTextFile.readAll()
+ self._ui.editor.setPlainText(data.data().decode('utf8'))
+
+ @Slot(str)
+ def plainTextEditChanged(self):
+ self.m_content.setText(self._ui.editor.toPlainText())
+
+ @Slot(str)
+ def openFile(self, path):
+ f = QFile(path)
+ name = QDir.toNativeSeparators(path)
+ if not f.open(QIODevice.ReadOnly):
+ error = f.errorString()
+ QMessageBox.warning(self, self.windowTitle(),
+ f"Could not open file {name}: {error}")
+ return
+ self.m_file_path = path
+ data = f.readAll()
+ self._ui.editor.setPlainText(data.data().decode('utf8'))
+ self.statusBar().showMessage(f"Opened {name}")
+
+ def isModified(self):
+ return self._ui.editor.document().isModified()
+
+ @Slot()
+ def onFileNew(self):
+ if self.isModified():
+ m = "You have unsaved changes. Do you want to create a new document anyway?"
+ button = QMessageBox.question(self, self.windowTitle(), m)
+ if button != QMessageBox.Yes:
+ return
+
+ self.m_file_path = ''
+ self._ui.editor.setPlainText(tr("## New document"))
+ self._ui.editor.document().setModified(False)
+
+ @Slot()
+ def onFileOpen(self):
+ if self.isModified():
+ m = "You have unsaved changes. Do you want to open a new document anyway?"
+ button = QMessageBox.question(self, self.windowTitle(), m)
+ if button != QMessageBox.Yes:
+ return
+ dialog = QFileDialog(self)
+ dialog.setWindowTitle("Open MarkDown File")
+ dialog.setMimeTypeFilters(["text/markdown"])
+ dialog.setAcceptMode(QFileDialog.AcceptOpen)
+ if dialog.exec() == QDialog.Accepted:
+ self.openFile(dialog.selectedFiles()[0])
+
+ @Slot()
+ def onFileSave(self):
+ if not self.m_file_path:
+ self.onFileSaveAs()
+ if not self.m_file_path:
+ return
+
+ f = QFile(self.m_file_path)
+ name = QDir.toNativeSeparators(self.m_file_path)
+ if not f.open(QIODevice.WriteOnly | QIODevice.Text):
+ error = f.errorString()
+ QMessageBox.warning(self, windowTitle(),
+ f"Could not write to file {name}: {error}")
+ return
+ text = self._ui.editor.toPlainText()
+ f.write(bytes(text, encoding='utf8'))
+ f.close()
+ self.statusBar().showMessage(f"Wrote {name}")
+
+ @Slot()
+ def onFileSaveAs(self):
+ dialog = QFileDialog(self)
+ dialog.setWindowTitle("Open MarkDown File")
+ dialog.setMimeTypeFilters(["text/markdown"])
+ dialog.setAcceptMode(QFileDialog.AcceptSave)
+ dialog.setDefaultSuffix("md")
+ if dialog.exec() != QDialog.Accepted:
+ return
+ path = dialog.selectedFiles()[0]
+ self.m_file_path = path
+ self.onFileSave()
+
+ def closeEvent(self, event):
+ if self.isModified():
+ m = "You have unsaved changes. Do you want to exit anyway?"
+ button = QMessageBox.question(self, self.windowTitle(), m)
+ if button != QMessageBox.Yes:
+ event.ignore()
+ else:
+ event.accept()
diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.ui b/examples/webenginewidgets/markdowneditor/mainwindow.ui
new file mode 100644
index 000000000..f4e29ad95
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/mainwindow.ui
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MarkDown Editor</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QPlainTextEdit" name="editor"/>
+ <widget class="QWebEngineView" name="preview" native="true"/>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>26</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menu_File">
+ <property name="title">
+ <string>&amp;File</string>
+ </property>
+ <addaction name="actionNew"/>
+ <addaction name="actionOpen"/>
+ <addaction name="actionSave"/>
+ <addaction name="actionSaveAs"/>
+ <addaction name="separator"/>
+ <addaction name="actionExit"/>
+ </widget>
+ <addaction name="menu_File"/>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ <action name="actionOpen">
+ <property name="text">
+ <string>&amp;Open...</string>
+ </property>
+ <property name="toolTip">
+ <string>Open document</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+O</string>
+ </property>
+ </action>
+ <action name="actionSave">
+ <property name="text">
+ <string>&amp;Save</string>
+ </property>
+ <property name="toolTip">
+ <string>Save current document</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+S</string>
+ </property>
+ </action>
+ <action name="actionExit">
+ <property name="text">
+ <string>E&amp;xit</string>
+ </property>
+ <property name="toolTip">
+ <string>Exit editor</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Q</string>
+ </property>
+ </action>
+ <action name="actionSaveAs">
+ <property name="text">
+ <string>Save &amp;As...</string>
+ </property>
+ <property name="toolTip">
+ <string>Save document under different name</string>
+ </property>
+ </action>
+ <action name="actionNew">
+ <property name="text">
+ <string>&amp;New</string>
+ </property>
+ <property name="toolTip">
+ <string>Create new document</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+N</string>
+ </property>
+ </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/examples/webenginewidgets/markdowneditor/markdowneditor.pyproject b/examples/webenginewidgets/markdowneditor/markdowneditor.pyproject
new file mode 100644
index 000000000..b10b12512
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/markdowneditor.pyproject
@@ -0,0 +1,9 @@
+{
+ "files": ["document.py",
+ "main.py",
+ "mainwindow.py",
+ "mainwindow.ui",
+ "previewpage.py",
+ "resources/markdowneditor.qrc",
+ "ui_mainwindow.py"]
+}
diff --git a/examples/webenginewidgets/markdowneditor/previewpage.py b/examples/webenginewidgets/markdowneditor/previewpage.py
new file mode 100644
index 000000000..e28af5b1a
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/previewpage.py
@@ -0,0 +1,55 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+from PySide6.QtGui import QDesktopServices
+from PySide6.QtWebEngineCore import QWebEnginePage
+
+
+class PreviewPage(QWebEnginePage):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ def acceptNavigationRequest(self, url, type, isMainFrame):
+ # Only allow qrc:/index.html.
+ if url.scheme() == "qrc":
+ return True
+ QDesktopServices.openUrl(url)
+ return False
diff --git a/examples/webenginewidgets/markdowneditor/rc_markdowneditor.py b/examples/webenginewidgets/markdowneditor/rc_markdowneditor.py
new file mode 100644
index 000000000..aa4f38a45
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/rc_markdowneditor.py
@@ -0,0 +1,852 @@
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 6.2.0
+# WARNING! All changes made in this file will be lost!
+
+from PySide6 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x01\xdd\
+#\
+# WebEngine Mark\
+down Editor Exam\
+ple\x0a\x0aThis exampl\
+e uses [QWebEngi\
+neView](http://d\
+oc.qt.io/qt-5/qw\
+ebengineview.htm\
+l)\x0ato preview te\
+xt written using\
+ the [Markdown](\
+https://en.wikip\
+edia.org/wiki/Ma\
+rkdown)\x0asyntax.\x0a\
+\x0a### Acknowledgm\
+ents\x0a\x0aThe conver\
+sion from Markdo\
+wn to HTML is do\
+ne with the help\
+ of the\x0a[marked \
+JavaScript libra\
+ry](https://gith\
+ub.com/chjj/mark\
+ed) by _Christop\
+her Jeffrey_.\x0aTh\
+e [style sheet](\
+https://kevinbur\
+ke.bitbucket.io/\
+markdowncss/)\x0awa\
+s created by _Ke\
+vin Burke_.\x0a\
+\x00\x00\x02\xb2\
+<\
+!doctype html>\x0a<\
+html lang=\x22en\x22>\x0a\
+<meta charset=\x22u\
+tf-8\x22>\x0a<head>\x0a \
+<link rel=\x22style\
+sheet\x22 type=\x22tex\
+t/css\x22 href=\x223rd\
+party/markdown.c\
+ss\x22>\x0a <script s\
+rc=\x223rdparty/mar\
+ked.js\x22></script\
+>\x0a <script src=\
+\x22qrc:/qtwebchann\
+el/qwebchannel.j\
+s\x22></script>\x0a</h\
+ead>\x0a<body>\x0a <d\
+iv id=\x22placehold\
+er\x22></div>\x0a <sc\
+ript>\x0a 'use str\
+ict';\x0a\x0a var pla\
+ceholder = docum\
+ent.getElementBy\
+Id('placeholder'\
+);\x0a\x0a var update\
+Text = function(\
+text) {\x0a pl\
+aceholder.innerH\
+TML = marked(tex\
+t);\x0a }\x0a\x0a new Q\
+WebChannel(qt.we\
+bChannelTranspor\
+t,\x0a function(\
+channel) {\x0a \
+ var content = c\
+hannel.objects.c\
+ontent;\x0a up\
+dateText(content\
+.text);\x0a co\
+ntent.textChange\
+d.connect(update\
+Text);\x0a }\x0a )\
+;\x0a </script>\x0a</\
+body>\x0a</html>\x0a\x0a\x0a\
+\x0a\
+\x00\x00\x06V\
+\x00\
+\x00\x17ex\x9c\xb5XKs\xdb6\x10\xbe\xfbW\xec\
+\xd8\x93\xc6\xce\x90\x0e%Q\xb2DM\x0f\x99\x1c\xda\x1c\
+\xda\xe9!\xb7\xc6\x07\x90\x04E\x8c)\x82\x05!\xcbN\
+\xc6\xff\xbd\x8b\x07)\xf0\xa1D\x89\x12\xd9z\x10\x0b\xec\
+.\xbe\xdd\xfd\x16d\xcc\xd3\xe7/\x17\x80\xaf-\x11\x1b\
+VF\x10\x00\xd9I\xbe\xd6c\x19/\xa5\x9f\x91-+\
+\x9e#\xf8\x83r\x9cA<\xf8\x87\x14D\xb2\x92{P\
+S\xc1235\xe1\x05\x17\x11\x5c\x85\xfae\xc6\x0aV\
+R?\xa7l\x93\xcb\x08&kk\xe6\xc9\xdf\xb3T\xe6\
+\x11\xac\x16A\xf5dF+\x92\xa6\xac\xdcD0\xd3C\
+/\x17\xf9\xc4\x83|\x8a\xef\x19\xbeC\xf8\xd2\xb11\xd1\
+/\xc7\xc5\xbd\xb5\x11\x06\xc1p1\xbe\xe7\x1eT\xe0n\
+\xd3\x8f\xb9\x94|\x1b\xc14\x1c\xb8`U\xd8\xf9Z\x7f\
+\xcd>S\xd4\xbe\xb4\xbeM\x87\xb2\xd9\xa2\xd1\xf3\xf6\x0d\
+|\xcc)\x18\x03\xd6\x1c\xb0\x1a\xea-)\x8a[\xf8 \
+_\xd7\x90\xd2\x9amJ\x9a\x82\xe4\x10S\xd8\xd5\xf8s\
+\xcfd\x0e\x1bA\x9eaK%\x01I\x9f\xa4\xd6\x07o\
+pJ\xc1\xf7@\xa0\xe2\xb5\x04\xc9dAo\xe1\xcd\xdb\
+N\xd4\xd4>0t\x0b\xeb\xe1l\xe8\xa1\xd9)\xca\xc2\
+\x11\xd9\xc4\xca\xe6C\xd9\xc4\xee\x9a\xf4\x82\x10\x04\xabU\
+\x96\xad\xbb\xb93\x84R]?R!YB\x0a\x9f\x14\
+\xb8\xed\x08bRS\x95\x1bZm\x94s\x94[\xe5j\
+\xd7~J\x13.0\xc38N-\xb9\x9a\xe6\xda\xcd\xb2\
+\xc5\xc2\x84\x99D\x8f\xacf\x12\xb1\xebxV\xedDU\
+h\xdd\xbb\xc2\x03^Xi\xdf)\xc7\xe7\x97\x8b\x82\xd9\
+Y\x9d\x94m C\xa9R\xa5\xbf\xbaiT\xd0\xcc\x99\
+Wy\xd01\xe9\xa2\xd8\xe6\xc7\x88\x85^]\xccC[\
+\x04\x95\xa0\x03\xe71\xcc\xa3\x8b\x96A[L\xfb\x1cA\
+\xf1\xeb\x8a$h\x18u\xf8{A*\xa5/\xe1)u\
+]k*\xfb=/k^\x90\xda\x83\xbfxI\x12,\
+\xecweJ\x0a\xaa.\xf1b\x8b\x9fZ\xdbXU\xdf\
+\xce\xd7\x83\xbd\xcel\xc6\xd4\xac\xb5\x97\xb2\xba*\x08\xda\
+\x8a\x0b\x9e<\xd8%\x05'\xa8B(M\xd6s\xb3\x97\
+\xd9\xca\x02\xa0'\xff\xb7\xe3\xb2Q\x13s\x91Ra`\
+\xbf\x9d\xd3-\xa0\xdf,\x85+Ji?\xf5`J\xb7\
+\xebA\xac\x82\x01p\xe1\xddb`\x0c\x12&\xe9 \x88\
+\x93p,\x86\xd3\x16w\x93\x7fWq\xa6\xfez\x1a\x95\
+\xc2(\xa6\x19oC\x9a\xa0^Z\xe2\xfe_\x7f\x9a\x06\
+\x93\x10>\x05\xc1\xbb\xe0\xb5Z\xe6\xae\xabzU\xb7X\
+,\x86;X4\xa4\xd9\x94Q7\x91\xda\xc2\xb2\xe5\xa7\
+\x80\xe8\xd5\x80\xe6|\xfc\x0a\xba\xb5\xb6Z\xad\xb4C\xc8\
+i\xefU\xee\x18\x22\x929\xd2\x99\x82@\xd1Z\xc2\xab\
+g\x1dA\xf8\x88\x0c&\xb1\x92?\x94\x89\xa6\xa7\x8bx\
+\x87\x14Xz\x17\xac\xacv\xd2\xbb\xc0\x9a\xa7\x09~+\
+W\x88\xa0\x86O\xdc\xb4\x09\x82W\xca|\x87L\x8eS\
+\x07\x12c_\xb8eij*\xdf\x9a\x06mZ\x1b\xea\
+dm\xc9\x05\xb2\xb1\xd6\xa1\xc8\x07\x13q\x1f\x81\x22\x93\
+\xd8]\x1eE\xfe\x96\x7f\xf63\x9e\xecj\x9f\x95%\x15\
+V\xe1P\xa0M\x98\xe4\xb4\x8ew\xdbI\x07\x8a\x7f\xe5\
+sE\x7f\xbf4c\x97\xf7\xddQAk*\xfb\x83\xf5\
+.\xde2\x1c\xd5f\x92\x9d\xa85\xcfq\x86\x19$\x94\
+1l\x80\xf1\x03\xc3\x00W\x15%\x82\x94\xaa\xf0\x8dz\
+e\xdc\xd1\x94\xe44y\x88\xf9\xd3\xbd\xdd\x89\x19\x15$\
+e\xfc\x88\xf2\x17\x15|\x05\x92P\xc5\x9c\xd2\x8c\xec\x0a\
+\x09I.\xf8\x96\xc2o\x901\x81I\xfd\x84'\x01\x89\
+G\x82M\xad\x02o0*\xb9\xbc\xb6\xee\xb3-\xd9\xd0\
+\xcb\xfb\x1b\x0f:\xb1o\xbcF\x7fT\x0ah\xb4lU\
+\xa81\xbd1\x85\xf3W\xe4GE\x98\xb4\x1d\x00\x11\x96\
+$\xb7\x00\x8e\xa1\xa5\x1c\xcb\x18-\xd2\xf5/wl\xcc\
+/\xcc(k\xd3\x8c8=\xf0\xa8\xcb\xa65bw\x22\
+X\x96'TY\xc3\xf6\x97\x7f\xd2\xe2\x91\xaa\xc2\x81\xbf\
+\xe9\x8e^z\xd0\x0e \xf1\x0bF\xb0\x85\xd5\xa4\xac\xfd\
+\xf6t7\xe0\xf6\xde\xb1\xebPNG\xaa\xacw\xe8j\
+\x8e\x14g%\xe6@k\xd0S\xa9\x10\xe8\xd6QE\xea\
+z\x8fEz\x7f\x80\xa7\x01L\xdbh\x9b\x14+\xf5F\
+\xda^e\xf9t:\xb1|\xda\x16w\xe8\xa0\xf1\xfd\x00\
+\x19\x1c\x00\xfa\xd7\x0d\xfd.\x03\xf5\xb7v\xa8\x05\xcfi\
+M\xcbK\x92\xa4\x9b\xab\xba3*\xc4v5\xb6\xd0\xca\
+M\xd31\xd1\xd8\xe8K\x93>n\x102VP\x13\x83\
+\xf6\xd4rgTt\x8f2wFC'\xf1\x1a\xa1\xb9\
+\xa50\x9dd#\xe83p\xa4e\xc4:\xa19/\xd0\
+\x0d\xcd\x1b\x86U\x9dQ\x13x\x0b\xc6\xa1\xad\x1e\x8aE\
+{y\xc2\x8a\x93\xd3b\xb4z\x1ak\x12\x0b\x0f\xcf\x9b\
+\xfa\x5cj\xd0\xd3\x08\x10\x01\xc1\xed\x14\x0fO\xba\xe6s\
+\x92b\x8bt\xc6\xdb0\xfc\xf8\xf23\x0c\xbbL\xa6g\
+\xa8\xe4F\xb2\xc6^\xafr\x09\xa3\x0eb\x13\x93\xeb\xc0\
+\x03\xfb\x7f;\xb9\xe9\x12\xdcw-\xfb\xde\x15\xc3\x8a\x8d\
+t[\xedd`\x1b\xa0F\xd6\x84\xc7\x5c\xeb aB\
+\xa9\xbd\x1f\xee\x18l~\xdbd\xd0\xb6\x97xO8Y\
+,=\x98\xce\x16\xca\x81\xe5\xcd\x8fc\xe4\xa9\xd3\x12,\
+\x1bY_\xf5\xe2GA<I\xefOWi*\xd3\x1c\
+\x19t-\x9a\x9f_gE\x97\x05a\xe2R\xe1Om\
+5\xa3\x94y\x84\xf6\xc2\xe3\xb4\x17\x8e\xd2^\xd8W7\
+\x0el`\xa0\x9b\xce\xe7\x08[\xfb\x81U\xa6\xc1U3\
+\xa6#\xd0\x07\xf3og\xc1Y\x9a\x7f\x8dR\x92<l\
+\x04\xdf\x95\xa9\x7f\xb8\xd7_\x84I\xda\x13\x0a\x8ag\x11\
+u\xff\xa6\xbf\xfd\xa7\x9eX\x1f\xfa\x22\xf0\x1fr\xb9-\
+\xfc\x8d\x02\x1c\x0fA\xd7\x86\xa0<}\x13\x02\x92W\xf6\
+\x97i\xe2\x1edx\xae\xbc\xbe\x0a\xc2U\x92\xc6\xea\xbc\
+\xc8\xaf\xad\xf1\x9b\xbeo\x8d~\x05\xaeQz0\xa2\xf5\
+Z-^\xe3\xfeQ\x05\xf5y\xebm\xea\x9c\xbaC\x8d\
+\xa9_\xa3\xe0:x\xd5Z\xb9\xe9\x08\xd4\xad\xd0\xc1\xee\
+7\x0c\x9f\xe5<?k\xf9\xe9k\x0fOot\x99\xeb\
+\xdb\xd1&s\x03\xf0\x9d\xa4u\x12r\xdad\xf9\xe0\xf0\
+\x13\x04a\xbc\x22N=\x1bt\x9d\x84\x9de\xcb\x89[\
+\xd9n\x0fER\xac\x9bNI\x8ab\xb4C\x8f\xcc\xf9\
+\x86\xb8\xdbp\xec\xe6\xdd\xef\xc6\xa5\x91\xce\xd4\xe1\xebS\
+\x8644\xed\x8d\xea\xe1\x11Z\x0fg'd\x15o}\
+G\xbc\xe7\x86\xf7\x8e<ok\x15\x93D\xb2G:\xbc\
+[\xebq\x8e\xea;wc\xbdg~\x16?\x9e\xa7\xf6\
+gk<\xfeX@\xe3\xd3}\xbe\xe8<\x0bx\xb9\xf8\
+\x1f\xcb!7\x81\
+\x00\x00&L\
+\x00\
+\x00\x91Dx\x9c\xed=ks\xdbF\x92\xdf\xf5+F\
+r\xd6\x00\xf8\x94\xe4\xacoC=\x98\xac\xb3\xb7\x9b+\
+g\xedJ\xbcUwGP2HB$l\x10\xe0\x02\
+\xa0%\x9d \xff\xf6\xeb\xeey\xe3AIvruU\
+w\xde\x8dM\xce\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4t\x0f\x87\
+\x9d\xce\x1e\xeb\xb0u\x90}\x0c\x17\xac\xcf\x02\xfa\xb8H\
+\xaf\x13\xb6\x09\xb2<\xcc\xb0\xf6U\xba\xb9\xcd\xa2\xe5\xaa\
+`\xee\xdcc\xc7\x87GG}\xf8\xeb\xdb\x1e{\xb5\xca\
+\xa2\xbcH7\xab0c\xff\x16^]e\xe1\xed\x80\xb9\
+?\xff\xf4\x8e\xbd\x8e\xe6a\x92\x87\x0b\x0f\xdb\xaf\x8ab\
+\x93\x8f\x86\xc3eT\xac\xb6\xb3\xc1<]\x0fy\x87\x1f\
+r\xf1\x01\xa0\x86{{'\xee\xd56\x99\x17Q\x9a\xb8\
+Y\x9a\x16\x1e\xbb\xdbs\xb6y\xc8\xf2\x22\x8b\xe6\x85s\
+\xb2\xb77\xe4\xe4\xfe9N\xe7\x1f\xfb\xaf\xc3Oa\xcc\
+\xfe\x9a\x05k@\xc21|\x0a26\xc3Jv\x06\x8d\
+\x19K\xc2\xeb8J\xc2\x11\x1b^\xf8Iw\xd8\x83\xa2\
+y\xba\xa0\xef.\xbb\xfb\xf6~\x02\xc5\xd3\xae\x9ft<\
+^y\x15&\xf30\x1f\xb1$M7\xf8}\x95!(\
+\xbb;\xec\xbd\xb8w\xdd\xf1\xa8\xcf:\xde\xdd\x8b\xde}\
+\x09\x9f/\x8d\xcf~G|\xf1\xf0K\xd2-\xbf\xf1\x08\
+\xdf*\x0c\x16Q\xb2$$\x1d\xf7\xd9\xddQ\xef\xe5\xbd\
+\x07\x9fx\xbfc\xfc8\x1e=\xebB\xe3\xb1\xdd0\xd9\
+\x14\xc1,\x0e5%4\xaa\x7fn\xd3B\x12\x8f$\x9d\
+\xb3\xb1\x0b\xd3\x14,\xb3`\xb3*\x09i\x87S\x00x\
+\xc4\x90b\x98!\xde\x04\xaaf\xdb8\xf6\xd8\xc4\xcf\xfd\
+_\xa1{\x80\x5ce\xe5\x22\xbc*\xfd\xe4\xee\xb8w\xef\
+\x8e\xf7\x194\xdf\xf7\x8f\x10\x8ey\xc0\x97\xd2\xcf;r\
+,\xc5:\x1e1Grc<r\xd8p\xc8\xd2\x0d\xce\
+W\x10\xb3(Y\x84I\x11\xe07\x00f\xac\xcb\x9cS\
+7\x9fg\xd1\xa6(7YX\xe6\xc5m\x1cz\x13\xdf\
+\xcf\xcf\xa7\xf8\xb7\x0f$t\x90\x84\xd3\xa1\xef\x1f\x9d\x03\
+\xf1H\xbd\xcfy@\xa8\xdd#Ob*Af\xd6\x80\
+^@\xb9\x16\xd8\xb1\x06;\xf5\xfd\xb1F\x0e_\xce\x01\
+\xb2\xc3\xc1^\x18`\xfb\x93\x1f\xfa\xffi\x90a\x80}\
+k\x82\xf9\xfe\xe4\xd5\x8f?\xbc\xfb\x01\xfe5\xd1N\xe1\
+\xffF\x93?\x1aM\x86c\xb7\x08\x968\x09\xac[\x02\
+H9\x1c\x9f{\xd6x}\xcelE\xfeK\xa350\
+\xbf\xc62w\x12\xf4\xff\x0bi\xbd\xee\xf3\xd9\x0d\x0aX\
+\x0f\xb3m\x11z\x9d1H\xfd\xf8\xdc\x1d\x9f\xf9\xfe\x0a\
+y\xb7\xbb\xa7\x7f\xf1`\xba\xc2\x84\x01\x81\x06\xc1M}\
+\x9a]\x02\xc6\xceS\xba\x98\xc7i\x0e2o\xf6\xe29\
+(@ hz5\xf9\x137\x0efa\xec\xf9\xd3\x11\
+\xeb\xf8\x09\x8c\xe4t\x8c\xeb\x02\xc4\xa3\xeb\x9d#n\xe2\
+!\xd5\x94\x08\x81\xf2[D\x05P\xe7\x8di\xdd\x18\xcb\
+\xa5\xb2Xbc\xdd\x89\xb5\xe6!\x02\xf7\xac\xec{H\
+o\xb5\xbdZDF\x0b\x82\x00\xe6\xc0\x12\x11\xe8J\x89\
+\xb7\x14\xcb\x0f\xe4m\x88\x94\x1a3\x9e\x94>\xce8N\
+\xe5\xa8\xca\xd6r\xbf\xdf\xf7<AOGP\x1e\xde\xd0\
+\xf2\xe4\xa5\xc3\xbd{\xd0r\xb4\xd6\x07\x97\xc4\x1fPd\
+8C0\x07\xfe\x94\x96\xb6?\x81\xffM\xa7\xb8\xda\xf1\
+_X\xe5'\xb2\x01q\x877\x18\x1d\x10\xec\xc1\x18\xe0\
+\x0e@Z\xbd\xceA\xe9L.\x1cZ=80\xfe\x19\
+\xc8\x00\x06;\xa5\x0fcv=\x98k\xcfS\xe8`\xba\
+\x00W\xb8\x88\x0aW\x15\xa0\xa8\x0e\xb2p\x13\x07\xf3\xd0\
+u\x88@\xa7\xc7Lzm\x08\xa2HC\xf0\xe9C\x88\
+eX\xfc\x12.\xc3\x1b\xd7S\xe3E\x9d\x13\x16\x82\xfc\
+I\xa7\xdb\x9f\x96\xfe\xa2\xeb\x0f4EQ\x11\xae\xb1\xde\
+\xd2e\x17jH\x86\xe6\x12\xaa\xb0Smj\x8c\x06K\
+z\xccY\xae\x1d\x8b\xe4!\x22\x18.%\xc9\x9c\xa86\
+\x92Q\xb7\xdaX\xb1\xe4\x91\xf8\x14\x93V\x19p\xc8A\
+\x95F\xab\xecH\xc8~\xeb^co6\x5c\x8a={\
+\x14\x0e\xcc\x95\x81\xd4\x81E\xa8\xe6p\x90\xa7\xdbl\x1e\
+\xf2u\xd96\xb2K\x10i\x18\x99\x13,\x16Y\x98\xe7\
+e\x90\x15\xd1\x1c$8\xc8\xa3EX\xce\x82\x9c\xffu\
+\x95&E\xa9\xb7\xa6r\x96.n\xcby@{\x82\xb3\
+'T7\xe8\xed0\x03\x0d\x1e\xe3\x7f\xcb,\xddn\xca\
+\xc5\x02\xb6\x9d\x22\x88\xe2\xbc\x5cDA\x9c.\xe1\x1f\xd8\
+\x89\xa2O\xe5\x22.\x17Ey\x15\x85\xf1\x22\x0f\xf1\xc3\
+\xb2\x82\x0eJ\xb6\xb0\xa0\xae\xc0@\x00\xacWi\xb6.\
+\xaf\xc0\x04\x08\xf9\xdf\xd8f59\xea\xbf\x9c\xd2\xa2\xa5\
+\xbf\x00\x0c\xd70l_eD@\x12U\x0c\x83N\x16\
+e\x1c\xc1\xff\x93\x8f\xe5:\x88\x92\x12v\x99-\xfd\x85\
+\xf2\x01\x1f\x8a\xa0L\x82Oe\x92r\xf4%\x8c\x02\xb6\
+<>\x8a\xd4\x22lS\xa2\x16Y\x97yH&L\xc9\
+\xd9\x5c\xe6[4OnKRQeA\x1c*\x16e\
+\x81\x03(\x8b\x15\xfc\x1f\xe9\xa4\x95Q\x16\x99DVd\
+\xc1\xfcc\xb9\x8d\x1d\xb5\xb8\xc5\x0e\x88\xf2\x7f\x0az\x04\
+\x84\xbdO;KN\xaa\xb8\xdf?W\xb2\x8e#\xb5\xa5\
+\x12K@\x1a\xa2\x8a\x90\x08\x94z}\x8a\x82\xca\x1a\x0e\
+\x96\xc6\x0a\x06Mg\xd5\xaa\xcd\x08`\x86\xac\x8b\x1b\x07\
+l\xad\xa3K\xd8=\xae\x07\xa3>-M\xd69c\x9d\
+\x03TD\xb0(\x0fJ\xfa*\x95\x91\xc3\xbf\xa2\xe2?\
+p\xceN\xcf\xdf\x83N\x1a\x0f\xdb\xc4R\xe9i{|\
+\xaa\xb8aa\x09\x0ed\x95*\xae\xc8u=\xff^\xd1\
+oU\xa8\xb8\x11\xac\xc6 \xdc\x09\xd1zf\xf3 a\
+\xb3\x10L#\x10\xd5l\xbb)\xc0\xc4\x9e\xdd\xb2\xe2v\
+\x13\xe2\xb6O\x16\x15o\x97\xb7\xe9D\xb5\xb4\xec\x01\xeb\
+r\x9b\x14\xc5\x08E\x90\xcd\x1a\xab\x0baL\xff\x1d\x96\
+\x10\xd8odS\xdb\xd64\xc7\x90\xf0\xfa3\xb6\x0e\xb3\
+e\xe8\xde\xdd\x0b\xd4\x06\x8a\xbf\xfe\xeb\xcf\xed\xed\x97W\
+\xebzc\x81\xb5GV\xba\xb4\xba\xc9H~Oz\xee\
+3\xe9\xb7\x09\xf3\x07h\xed\xfd\xda\xc5-\x1f4\xbc\x14\
+w\x8f\xcc\x02\xffh\xe7>\xbe\xc3\x02\xef\x1a\x16\xf8\xb3\
+\x8e\x89e\xef^s\x1f(\x7f\xa2\xc4\xc1\xaaD\xb5\x8b\
+\xff\x08\xdbGc\xe2\xa3\x14\xdaW7\x01\x85\xcf5\xf5\
+\xb1\xe3\xd1\xd2\xb7[\xe2\x86\xd2\xde\xe6\x85l\xd3:\xbb\
+85]\xf6\x0e\x95O\xde>G\x05\xaf\xafO\x13\x10\
+\xce\xe7H\x1dH\x88\x8f\x93\x0b\xb0t\xd8t\xd0\xf1\xcb\
+A\x87\x9bV\x93\xfeh\x0a\x07\x19\xbf\x9c\xf4K6\x92\
+\xe7\x10<4\x0d`u\x9fspu6\xe9\xd0\xe1\xc2\
+\xb6\xde\x10\xb3_\xba\x03n\xab\xf9\xe5\x18\xb6y\xc2Z\
+\xc3\xc8va\xe43(\x86\xff6\x5c\x04\x09l]l\
+Y\x1f\xf4F\xd6\xed\x96N~\xee\xa1\xb9\xa7\x99q.\
+H^\xa4>\xee\xf0M\xb8\xf4\xe9\xa4$'\x0fmy\
+T\x05\xd2X\xee\x8e\xf91G\x82\x93\xd1,\x9a\xa0\xba\
+@\xa3\x19\xb4\x83e\x99\xc3\x174\xe4@o\xa2\xd6\xf4\
+Ac\xfa\x0e\x98h\x0e\xb6\xc3\xcf\x07C8\x83\xe4h\
+\xe6\x8c\xc1\xe4l\xc0\xcc\xb7\xf7\xc7j|\xd3h\x81\xae\
+\xd1fAA\xc6\x83\x1e\xd5\x12UA\x09\xbb\x22\x9c\xc7\
+S0\x82s`Q\x5c\xe6\xe5\x1c\xb6\xca\xf2\x9f\xe5\xe2\
+*)\x83\xd9\x0c\xb6\xf1\x006\xcd\x22\x82-\x19\xcf\xda\
+%\x9c\xc9\xcb<Xo\xca\x8f\xb3\x05\xec\x863\x03\x1b\
+|\xdd\x94Q9+a\xcb\x0d\xb2\x8fe\xb6\x9d\xdd\x96\
+YQf\x9br\xb6\x80\xf2EZ\xe6\x9b )\x01\xe9\
+5\xfc\x17%`.\x84\xb0\x8b\xaf\x97\x9e\x81\xc5\xf7g\
+\x1e\x1cU\xc0\xd0\xd9\x1f\xa1Y\xec_\xc3\xf0\xbf\x9fv\
+\xbe\x87\xd2\x99d\x81^\x1b\xe6Q\xa4\x03\xc7\x10h1\
+\xc5\xf3\x01\x9eC\xaaG\x10T\x14\x07\xee\x94+\x8b\xc9\
+\x817\xad\x9c=,Y\xe3\xab\xebux\x13\x0a1\x93\
+\xde\x0c^\xe6rs!\xf7H\xaa\x8aU\x94\x0f\x8a\xf4\
+c\x98\xe0\xb2\x9bLO\xec\xb2\x01Z$Xsw\xaf\
+jD{(\x94\x9f\xcaRxn\xd0\xae\x0b\xb6q\x91\
++\xe0l\xcb\x17\xb4)\xcf@*c\xd1\x15sMt\
+j\x19p\xba\x1a\x9bK\x10\xc4~\xcf\xc28\x0f\xebh\
+@SH\x0c\xb5:\xae^duc\x17\x1c\xe4\x84\x00\
+D\x17;\xa0\xa13\x01\x8a\x14\xed\xdd\xab9\xf8\xcb\xcd\
+\x06\x16\x92\x98\x8a_\xb0\x0d\x9f\x0a\x9a\x01\x1b\x89\x9e\xb8\
+_\xd1m1\xc7Yb?\x87\xc5*]\x98mb(\
+=c\xca1\x95g\xf3\x1e\xb3f\x12}N1\x82\x02\
+X\x12^W&\x1b\xc9\xcc\xc2b\x9b%\x1c\x08\xf1!\
+\x12\xa8\xb87\xd4\x14\xac\xbc,\x85\xed\x01O\xcef\xef\
+PZ\xa4h04\xd0\xc1\xbb\x87\x0fP\x0e\x7fW\x96\
+\xb0\x9f\xa1R\xcah\x19\xfbIU\x13\x0c\xfd\x82jH\
+\xa1\xd5\xea\xb6\x87\x87\xc1!\xafo\xa8<\xfe\xf6\xf8[\
+\x85\x96dJ\x0cP\xcb/gS\x91mC{\x9c0\
+\xa8\xd6\x01R\xc3\x1a\xab\x8btS\x19\xa7&\xe5\x82u\
+\xbf\x19.\xf1\x14\xe7\x10\x9fq&\x128O\xf7\x84\xe0\
+\xc4)\xc8\x82\xfc\x02\xc7\x08\xf9\x11\x0fa\xea\xb3\xfc@\
+\xe7A\xf1\x19\x94\xce\x5c5\x8c\xe4\x07P\x8a\x0a\xb3\xaa\
+\xcc\xd3\x0c\x0e\x18\xe1B\x17\x14A\xfeQ\x7f\x9b\xaf\xc2\
+9,Pb\xd3\xf5*\x82\xf3\xb9\x9e:\x86\x9a_\xb8\
+(\xd5\xc2\x01:a\x9cZ\xe6\x07\x02`\x00\xec\x9aS\
+[\xbd\x8e4O@\xaf\xa2\x974Yb\xfb\xc9\xe1\x14\
+\xc4%Y\x16+\xefD\x12\xc21\xeb\x1av\xce\x8e4\
+\x22[\xf7l\xb6\xf9\xca\xd5U\x8c,\xd6\x11s\x88-\
+\x8e*\xbfW\xd8\xef\xc5Z\x94cB\xbd\xdf> \xac\
+\xfd\xba\xd1p\x94\xa2\xce\x10\x87\xbbo\xefMyx`\
+\x5cbTH\x8e\xd3\xd3\xa5\xe4\x90\xd9oT\x91\x06K\
+\xc6\xd8\xbd\xb1*\x12\x10E\xea\xd8\x80\x19!\x8cd\x91\
+&\x1e\xce\xccQ\xb2\x0dO*L\xe3\xf6!sQ\x91\
+\xb63OX\x91_\xc5\xbe'\xf3$\x0e\xd0\x82Fd\
+\xc7\xd3*\xa7\xb0\xf4\xc5\x14\xb7\x22\xc7y\xf4X\x85U\
+\xde>L\x01\xf0{\x8fS\x1d\xf0T\xc5\x22\xdc\x14+\
+>\xaa#\x89\xb0i\xc8\xc7\xd3G\x0f\x96\xb66\x96\xa4\
+\xb0\x01Pol\x13\xe1\x01\xd0\x9agPs\xec\xf9\xf3\
+\xa6\xd5\xcf\xedn\x83\x11\x9a\x13\xc2\xa9U\x1b\x15\xb50\
+\xc6\xc4\x9d #PkqT\xbc\x0a\xe38w\xc5\xf8\
+\x8c\xb5CNV\xf8\xef\x1bR\xed\x8e\xe7\xe9\xf6A\x1c\
+-\x139l\xbb\x8d\xd9b@\xf8\xdd!\xc734\x10\
+\xcc\xb1O%*c\xf1\xc1\x5c=b\xf1\x08\x14\xb4\xb3\
+\xc0\xf2\x99(&\x9f\xec\x19\x8a\x0c\xc7=\xe0\x83\x92\xda\
+\xec\xec\xec\x8c\xd81 Z\xa5 \x18\xacy\x8c\xd4(\
+\xe0\xab4\x83^\x00\xfe\xf0\x84E\xec\xb4\x8e\x19\x8a\xbb\
+]\x13='\x0cY\xd2\xef\x8e\x90%\x83\x22\xcc\x0bW\
+7\x9cDS\xcfn\xc0\x98U\x8b\xde6\xbadsN\
+\x0c \xc3\xdaB\xe4\xa3\xaf\xc1\xce\x1dq\x0f\xa0\xffb\
+\xecqx\xd5H\xfa\xeef\x09l\xc5V#\xbd\xbb<\
+0\x1d$S;\xa6C\xc1\xf0\x8e\x0c\xd9\xb7\xaaz\xac\
+.M\xdeI\x13\x155m\x82\x0dM\xa9\xb1\xd7\x7f}\
+W\x5ce;\xf4]\xf6\xbb\xab\xba\xec\xf1\xeaY\xbb\x97\
+\xda)\xd60_J\xf9\xa3I\xd7]]\x82u\x95\x15\
+\xe6@\x1e\xb0\x08:\xe7l\xacM\x02\x01\x0c#|\x1b\
+\xe49{\x0fZ\xf7=X\x98\xecc\x18n\x80\x8e\x90\
+\xcd\xb7Y\x06\xabD\xc3\x1d\x00H\x8c\xf7\xcc\x07\x0c\xba\
+\x86\xb1\xb2w@.\xd8u,\xbc\x09\xe6E|\xabA\
+W\xe9\xb5\xba<\x1flbv\x9df\x1f\xf3Am\x90\
+\xc8\x06n\xd6~\x19\x07\xc2d\xd14\xfe\x96\x99D\xf7\
+Q\xfb\x1c\x92s\xe9\xab\xe4\x8e\xae[\xce\xc4\xd6\xa0\x8c\
+Mi\x14\xe3Q\x0b\x00\x0c\x93\xf3\x09cF\xe2\xc4|\
+\xeb\x9dD \x1e\xd5\x0do\xc6\x08\xd6\xa8\x81\x8d\xa6K\
+\xf4\x8dl\xd3DO\xd9_\xc3\x82\x85\xc1|\x85\xd3\xd1\
+\xa7i\xe6\xda\xa0I\xa8\xd6A1_\xb9\x06\xf3\xec\xe5\
+\x8f\xe7\x0e<\xbc\x04\xa0\xf3$\x1f\x04g\xa4\x92\x92\xdc\
+!E&\xdb\x91n\xe3j-\xae\xa91\xb1\xc1#\x05\
+\xd1\xd4\xd04@\xfa/\xe1:\xfd\x14\x92\xd0\xd2-\x14\
+\x82:9\xe3\xf7K&`\x9eB\x1d\x0al\x1e\xc2\xf1\
+*\xc8\xa9\x09QK\xdc\x1fh\xf6\xa1u\xcf\xc46j\
+\x93\xac(\xa1:sy\xb9\xe6E\x1d\xeb\x0e\xed\x85F\
+\x04\xbc\xd9\x16\x18\x19\x00\xa7\x1fX?\x9f\xe0\x98\x0c\x04\
+\x98\xf5\x8a|\x12\xe2 \x02\x81`\x7f\x0b\xe6\x1fo5\
+i(\xbc\x9f\xa9o\x8c2\xb8ys\x85\x16\x02\x1cM\
+m\x95\xcf\x07\xd0o\x19\x81\x1a\xc3\x83\xa6=\x1a\xf7\xd6\
+@\xf1T\xffK\xb8\xfc\xcb\xcd\xc6\xc5\xf0\x87\xa3\x1e\xde\
+\xa7\xf1\xde\xba\xcc\xb9w\xc45b\xd5\xfc\xc7\x03@\x95\
+a\xd0\xb8~F\xb1\xb6\x19\xe0\xc8\x8f!\xec\xd3k8\
+\xf3\x01\xcf\xc2b\xc5Y\xc6\xe7L3k\x16\xc6i\xb2\
+\xcc\xc1\xb8\xcb\xc2\x81\xd9\xfa\xcf\xc0;\x1cU\x8cl\x83\
+\xa9_\xa4p\xaaH\xd2B\xb4`\x11?\xa8\x13*\x9b\
+\xc5\x16cr\xd0e\xc5k\x80\xc9\xd10\x8d\xd8>X\
+X1\xeb\xdb\xa7FP\x00\xca'\xc3E\x8f\xab\x13\x92\
+X`\xce\xd1\xd4\x83\xb5cM\x02\xf4C\x8b\x12\xf1\xcd\
+\x10\xf5\xbe[\xd1\x11X83\x8f\xa9U\xdb\x83\xab'\
+\x5cYy\x1c\x01_\xa9'o\xf0!\x8d\x12a:v\
+\x11\xe6\xc4j\x84\xcb\x8e\xe8\x7f\xd0\xdch\x9c\x01\xe29\
+r\x0d}\x0a\xa0\x86\x90\xa3\x16\xdb\xff\x91\xa3\x0b\xdbE\
+\xc7\xb8G\x8142\xb6\x88_LS\x18\x8d\x09\x8f\x0b\
+\x7f\x11\xe5\xf3t\x9b\xe0\xd4\xac\x82OQ\x9ai\x8c\xbc\
+\x9f3>\xebp\xb8\x1aZ\x88\xb4\x85f\x08\x11\x99\xc6\
+m\xf3$\xf4\x13\xb7}VA\xf6\x830\xf0\x04\x9b\x09\
+\x1e\x8dh\xe4_u\xbe\xf6\x89\x18\xcf\xa2\xa9Mv_\
+\xa1\xbf\x83\x06\x87~\x10-\xaf\xb9&\x93<$t\x8f\
+\xefO&\xec\xe6\xdf\xa7\xfe\x94\xb5\x8cH\xbaO\x00z\
+\x0b+\xff\x0afdQ\x190a\xab\xd8~F3\xc4\
+\x08'\x1db\x8b\xc3\x9c\x06}PY\xa2\x9a&\xa5\xcd\
+\x1aF\xfa\x08w\x09q\xab\xa2V\x1c*\xbc\xc4.m\
+S\x86\xff\x91\xbb\x9eQ\xdf3\xf1\xc2HG\x15\x0f\x13\
+\xfe\x11\xa3\x1d\xe9\x81k\x92\xbd\xea\xb6\x01&Nn(\
+\x0c\xc30\xe1\x11\x11\xb4\x83\x99\xad\x1e\xe3\x19\xd2T\x1b\
+\xe6\x89\xed!z\xda\xbe\xff4+\x07\xafhv\xd8\xd6\
+P\xfb;[\xd7\xb6\xe6\x0c\x92\xa8\x88\xfe\xcb\x9c{\x98\
+y}5k\x94\xa3]\x0e\xd4\x19\xb3\xbc\xc9\xc2\xaa\xf7\
+I\xe2\xcb\x8c\x86\xc2W\x80\x92M\xab\x16\x9a9\xa8&\
+\xcc2\x1e~T/\xc6P$\xc7krj\x1c>\xde\
+\xa9\x01\x8bQq\xbc\xd5y\x81\xd1&M\x8e\x8b\xa7\xfb\
+-_L=\xe9>8\x93\xee\x03\xdd\xf6\xa8'\xcb\x0c\
+u\xa6&\x8f\x22Y\x84\xc3\xa3H_\xa7\xd7a\xf6*\
+\xc8C\xd73\x1c\x10yW\xba\xbb\xcd\x9e\xf7k72\
+\x13\xc06m\xf3\x9bj\x08\xcb+\x83a\xa4x\xdfT\
+\xf5\x9fAc\x8c\xfa\x90~\x11\xbdj\xec\x03\xe4C\x8e\
+\xa5G:\x92\xfe\x0f\xb8\x91\xe8f\x18\x1by\xe3\xff\xf7\
+(\xfd\xbfGI4j\xda\xbe\x7fc\x8fR\x9d \x09\
+g\x899\x8afey\xf4\xeaM\xff'\x5cQ\xf1\x83\
+\x0e\xf8\xf8\x7f\x83\x07\xfeXlYg\x0e\xac\xf6#X\
+\xbe\xc7M\xbb\xd6\xd1\x13\x5c\xf1\xea\x9c\xaf\xb6\xe3\x875\
+\xa7\x02\xfd\xca\xbd\xecaf\x18\xe1[\x8d\xe3\x94\x96\xbb\
+u;a\xdb\xee\x96\xd1!\xe0\xf8)\xe9\xb0\xc7\xfaG\
+\xb5\xeb\xa9'q\x0fHi\x97\x18\xacm\x92\x16h\xf8\
+N\xb1=_\xa5\xdbx\x01'\x08t\x04d\xe4{1\
+\x0f\xb0\xbf%3\x91\x9eF>>\xd6\xca\xc1Q\x1aw\
+\xb5\xd8g\x96^\xd3\x1d\xff_\xb2,\xcd\x5c\xe7\xa7\x04\
+N$\xb0\xf6\xd0\xd6\xdf\xb04a\xb3[\xcc\xcfp\xf8\
+\xf9\x93f\xebU\xba\x08a\xc6\x0e=\xcf\x0cX\xd8k\
+\xb83\xcf\xad\x9b\xf2\x9f\x12\xbc\xf4m\xcbq\x89\xa8V\
+l\xdaa\x0e\xa3\xe29.\xbe;\xd9?x\xf6\xcd\x1f\
+\x9e;\xae\xd7\xe9\xf6\xfc\xfe`8:9=;\x1f\x7f\
+\x8f\xc1\xe2\xbe\x7fq\xf9\xfe\xae\xbc\xff<\xe5qW\xc1\
+\xb6H\xd1f\xc1\xa6\x98\xab\xb1\x0a\xd7\xe1\x08\xe3_\xfc\
+\x9b\xc3\xc3\xbe\x7fstuz>\xed\x94\xe1:\x88b\
+\xef\x9c\x9al\xb3X\x87\xd9\x83\xa5\x83\x09!2\xa8\x88\
+\xc6\x87Q=\x17\xa7C\x11\x07J9\x04#\x99D@\
+\x91Ny\x18_\xf5\x1br\x04\xa0\x95\xd9\x88G\x8e\x9a\
+\xa9\x0e\x88b8>\x17Y'\x954\x86\x8bz\xe6\x07\
+\x01\xea\xa8\x0a\xe0\x19\x08\xd4\x96B\x0bz,\x1c,\x07\
+\xect\xbcYm\xd8\xf8\xdc\xc0\xb2/i\xe8b\x90\x95\
+\xce\x0fq\xb8\xcd;\x8fa\x85\x9a\x18\xf6\x7f|\xf3\xea\
+\xdd\x7f\xbc\xfd\x0b\x1dA,D\xbbRG0V\x16\x8e\
+\xcaX\xcdD\xb8\xf0\x1ef\xea\xf0\xa9\xd8\x1f\x1b\xc9\x11\
+\xbe\x8b\xc6#F\x1c\xe5]\x95\xfe\x80\xd9\x00|\x0a\xa1\
+\xae\xb9\xd9D'\x0d\x18Y\x03c\x916\x80\x19\x01\x18\
+\xf2\xc4s\x8dR\x0b\x85\xd5n\x22\xd2\x0c\xa0\xa0\xac\xa5\
+\x1et\x00\x03\x01\x01\xf4\x98P\xf1\xc80DuyI\
+\x91TS\x19\xb1I_<(\x1d\xef_z\xe5\x85\xdf\
+\xf1;M\x00T\x0e\x14t\x00F\xa2hje\x00R\
+\xc7\xe1\x9a:\xad\xa3\xbc\x84\xe6\xa25\xaf\xbd\xac\x92\xa4\
+p\xd7\x1bw\xb0\x1fI\x8e\x00\xe8\xd4(V\xf4\xba\x95\
+\x0eU\x0b\xcf\xa2Ue\xa0\xbd\x87)\xc8;\xae\xc6\xf7\
+~:\xc6\x12\xff\x08\xa0\xdfs\xe0\x99\xc8@;\xee\xdd\
+\x1b\xfe\x1f\x1e\xc3f.E\x99F\xa2R\xbb\xce@\xea\
+@\x0a'\xef;0s\xb3\xcbR\xa0\xe0Qk\xa0g\
+\xb8\x0a\x19\x5cr\xed\x81QQ\xc3'\xa8\x8f\xa5\x81\x81\
++\x0eD\xa0\x160\xff\xf7\xb0\xff]w\xd0\x9f\xde\x1d\
+\xf5^\x1c\xdd\x0fOt\x97\xa8P\x0cx\x80\x1b\xecS\
+\xb7\x9d\xee\xf0l,z\xe9O\xbb\xee\xf7\x9e\x06AQ\
+\xd3\xdf\x00\xeda\xef\xe5\xd1\xbdQ\xcfs\xe8\x06Oj\
+\xe1a\xd4\xe0\xa4\x0f\x93\xa6\xe9\x93jQF\xfcV\x8a\
+\xed\x88_>xX\xd067l \x1a\xb0\x01\xc35\
+\xea^5dWV+\xadG\x93\x927\x07\xd5\x83 \
+\x9c\xc1\x7f*<\x94\x7f\xc5\xb0z\x0c\xaa\x17_+a\
+\xf5\xba\x0f~(6\x87W\x8b\xec\x7f\x5cv\x80\x19\xff\
+_\xa3\xbf}\x88FnS\xb3\x92\x19\x97\xefq=t\
+\xde\x1b\x1a\xab36\x84\x08\xd5\x22\xe7O\xc7=\xe5*\
+\x0ev+Rp\xf9\xe99\x81\x9f\xf3t\x99\x89\xebA\
+\xb9\xefZ;\x9b\xeb\x01\x08h\xd1\xb2^\x0a\xfd\x18\xd2\
+\xa0\xb3\xaa\x9aR\xaa\xa8\xc8\xc1\x22\x87\x8a0\x95\x8a\xca\
+|\x0f\x0b9:\xdf38\xdf Yu\xa9\x92\x99U\
+\x16\xbb\xaa\xc9\x0d\x94\xe2c\xb2\xa39\xf7\xca\x1aG\xeb\
+\x8c\x88\x8d\xa4B\x99(},q;\xd2\x0b\xb8=c\
+[2\x02AC\x82\x01\xafi\x8a\xe3\xde\x81\xa71\x9e\
+\xdb\xea\x83\x07t[\x9b\xd4\xf8\xcc\xff\xd5S*\x18>\
+\xdb\x9bM\xad\xbae\xdf\xa9\xa3\xd1{@\x03\x0e\x03\x03\
+\xdf{\x89\xe9\xb5\xad\xdf\x1d`\xaa\x837\xac\x86q\xb7\
+\xce@-\xd0Y\xd9\x07\x8d\x1d\xe4*\xfe\x19\xb7\xf2/\
+\xec\xc6\x8a\x81\xc6t\x83\x1dST\xcd\x05i\x98\x1di\
+\xcd\x9ab\xc8\xcb\xb4\xbf\xce\x99zx\xe9\xf6\xb9\x84\x7f\
+\xbd\xca\x88\xc9B\x15\xa3\xc5\xa5xUlJJ@\x1f\
+{#\x7f\xe8\x0f\xcb\xeb\xebk\x7f\xe0Y[\x83\x0f{\
+\x8d?\x80\xcd\x80\x94\x07\x98\xbc\x17\xa4\xa1k\x0ci\xd5\
+\xe35\xbe_\xce\xe4-\xdc\x88\xe73^\x8c\xf7\x07\xbd\
+\xd1I\xe7\xf2\xb3\xeb=\x9fvI\x1dQ\xc2e\xf9\x1c\
+w \xbd'uO\xbe\x01\xa5\xa4\xa0=LD\xddW\
+\xd9\xdc\xb4\xe9\x0f/>\x7f\xaeK\xd5\xe7\xcfFB\xa9\
+\xa5\xd9\xa1\xa4:\x92iI\x0c\x9c\x96\xb5\x14\x01\xaa\x10\
+\x1c\x1b\x0d\x87%\xf0\x0f\xffA\xae\xf9\x83\xb2\xbac\xfb\
+z\xcb\xf6}\xbei\x7f_\xd6c\xeek2\xd2e\xaf\
+QF\xfe\x0cG\xc0\x8f\xf9.\x89\x99q\x88\x06\xa1Q\
+\xa9)h\x22\x99\xe3\x9de\x86\xa4\xa0\xd5\x83#\xea\xd4\
+\x04\xa5\xce(\xcc\xd2!f=\xd4\xdc\x1a\x8e \x9e\xc2\
+\x99\xd9s\xf6*]o\xa2\xb8\x96\x02\xc0\xa1xl8\
+\xf9\x89+Q\xe4O\x8f\xf0\x97\x19\x02\xf4\xaf*\x95!\
+\xee\xd6\xca\xd2\xb5a\x82\xa1\x11\x99<\xb9\xcb+\x06U\
+\x0e\xfd\xf1\xdbn\xfe\xdd\xf5jM\x0d\x22M\x14*\xb7\
+`_\x13\xa73\x0a*\xe7\xe6w<\xef!\xc8\xb2\xe0\
+\x16T\xd4?\xb7Q\x06D\x07\xec=5{\x8f\xa7\xb6\
+M\x98\x15\xb7\x03\xee\x8e\xbf\x7fz\xdeBeK ,\
+_\x90\xb8\xc0\xa5\xaf%q\xc1\x92Pq\xba\xdf\x91\xb9\
+\xa0\x85\xcc\xf4\x04\xd4R\x17\x848\x19\xb9\x0b\x86\xe8T\
+\x905\xa50\xc0Qw\xc8e\x10\x0f\xbdf>\x83\x89\
+'\xdd\x16\x9bmQ\x8b\xb6o\x92L\xcb\xdf\x80\x93\xb8\
+C\x94O\xb4cC\x0c\x97wT\xcfx\xa8RZ'\
+Qg\x064\x12\xab\x89K\xa9\xceQ\xbe\x1e\xa4I\xc5\
+\xec\x1b\xe9\x00h\x22\xa9r4\x86\x8c\xd4\x80\xf6\xf8|\
+\xbe\xf7\xb4\xbb\xbdx\xfd\xd7\xb9Iq\x04]yG\xf5\
+\x90\x17N\x9eC\xdaI\x92\x10\xbfM\xca\x80t\xc2~\
+\xefX\xf7^<\x9e\x80\x8f\x9e/\x9au\x90,\xe3P\
+8'=\xc3s-Lu\x07w\xcb\x22\x1d\xa1{\xac\
+0B\x08jn}\x1b\xb7\xc0WCg\xa1\xb0\x19i\
++,d\x05yQzt9\xd0\xa3\x96\x0f:;\xc1\
+\x8e\xa8\x5c\xadq\xcd\x16%\xaf\xd1Nn\xf4\x14C\x9b\
+F\x1f1g\xb0\x0d\xab-\x04\x1d2sh\x85\xcb\xfc\
+>S%\xfa\xf9-g\xc7B'i\x91\xd7\xcd`8\
+\x0c\x1c\xfb&E\xf6\x88F\x06\xd8\x16\xd5\x1e\x1b/z\
+\x1a&]O\xfb\xef0\xfd\xd2\xb9\xd8\xec\xea\x0e\x1a\xef\
+E\x9a\x84dxq\x1a\xb0a\xc4/\xc0\x04\xb3j\xf7\
+\xc7\x02\xfc\x8c2\xa6*\x8cW{\x92\x85\xd3\x1f\x06\xe7\
+\x8f\xc6j\x05%\xde?Y\xbaL\xae\xb6\xc68\x8c\x9b\
+\xeb\xcd\x98\x856\x109\x00\x03tT\x11\xaf=]a\
+y\xf0\xdb\xc3^w\xe9\xc7\xaf\xd7\x8d\xed\xb3&\xe4\xb4\
+\x12\x10\xfb\x90\xed\x82\x7f\xc4\xf1\x9b\xde\x9fq\x0e\xa6\x1d\
+\xe1^\xcc\xbb\xee\x04\xbez.&Z\x1f\x0f9\xddt\
+\xda7\xee\xfb\xb0\x03:\xa87\xad\x19\xac0\xf6\x15\xa2\
+_\xf84\xa8\xea\xc5t\xe7\xca\x93\xb0\x8e\xd3\xb0\xf0j\
+\xcaA\x00W\xef\xe8\xf9\x15\xd4\x11]AQ<nU\
+\x1c\x05\xad\xf8\xcf\x008\xbf6\xa33.N\xe59\xc7\
+;\xa7k\xfdo\x8e\x9cf\xe9$C\x01g\x85GY\
+k\xbax\x04\x86i^\x08\xbf'ge\xcf\x1e@3\
+\xa4r\xa0\x10\xdd-\xb2`\xad\xb56\xf1\x14\xa7\xf2\x9e\
+p\xbd+I\xad\x8b\xaa\x804\xa4UQ\x0a\xa6zC\
+\xbaPu\xe3\xff\x12\xe9\x16\x92(\xb7\x14\x15?\xb4;\
+`F\xb4\xd2\xe6\xff\x84H\xb1\x22n\xac\x15\xb1O-\
+\x00;}\x18\xd0D\x18Sf\x18E\x87\xea\xe6\xf3\xd0\
+\xd8iT\x18(\xd6\x1b\xc1@\xb5\xf0\xcf\xe6\xdb\xf0]\
+\xabx\x97P\xd1:\xfb\x8a\xc9\xe7\xee\xa7v\xed\xc4\xeb\
+\x7f\x13\x83\xd2\xde\x089b\xd7\x18\x14\xb5\xfeV\xcd\xf0\
+\x0b\xf5\xc9\x9a\xf5\x07\xf7\xcap\xbd\xc3<^\xff\x0e#\
+\x09\xd7\xb5Q\xbcT\x14\xffQ}\xfa\xfa\x91\xfd\xde\x89\
+\xac\x8d\xa3C\xb4\xf8\xec\x81k\xec\x81\xc7S\xa1\x15E\
+V\xf5\xc3)<;\x92\x8df_\x99l\xd4H\xf5L\
+8\x0bv\x11\xb5\x08\xab6uC\xc8\xe0W\xc6j6\
+\x12\x07Xk\x12\xf3\x98\xf9\xff\xb2\x88\x87\xaf$\x16\xd1\
+\xba\xe6\xc1\x8aB\xf1oA\x22\x8a\x5c\x19{\xff\x0bB\
+\x17\x80x~\xa4o\xd8,\xcd\xc3:\xd9\xdbD\x89\x8c\
+y\xc0\x13\xc4\x98\xfeQ;J\xcd\xc9\xa1n&\xc5~\
+\x0fV\x037\xfe\xb5\xc7D\xb8\xda\xd0\x95\xf8\xf1a\xff\
+\x81\xd4\xd1\x92,\xad\xcc\x95'\xc10\x98\x065o\x81\
+\xac\xe0_\xc6\xd2B\xd5eH\x22\x8f>\xd3C\xad\xee\
+^<\x0e]\xbc\x0e4n?\xa9p\xff\x04k\x92Y\
+j:\xaa4\x8d\xd6\xc12\xb4\xdb\xda\xc7g\xdb\xfb\xf2\
+\xab\x96(\xf6.\x0b\x92\x1c\xdfQ\xa3\xc0\x86\x06\x9f\x93\
+f\xa4!\x88\xcd\x13\xacO@V\x1a\x09o\xe2\x99\xf3\
+\x7fb\x8b\x83\xdeJ\xfa\x8b _\x85<]@\xdb\x1b\
+\xfd~\x9f?9\xb1\xc5\x97g\x85\x83\x19\xe1\x936x\
+\x0d\xfeB\x83c\xdc\x08\xba\xc50 $\xae5r/\
+\xcaI\x9f\xf70t\xfd\xc9\xdd\x01\x1a\xe0\x0e!\xfa\xe6\
+\x88\xca\xff\xa4Q\xc9\x10\x16\x81\x8a=g\xc1&\xc5-\
+vS\xa7\xc6\xd1\xc4|W'f\x91ng\x0f\x13\xc3\
+\xfbG\x8a\x0eL\x8a\xe6u\x8a\x9a\xf1\x1dh\x1a\x16\x06\
+\xff\xe28\xda\xe45`\x7fp\xf7\xe2^58~\xe9\
+\xd8\xe2\xf33yzh\xdd\xed\x94\x17\xee\x11z\xac\xa8\
+p\xe8\x9a\x944\xba\xf8\x84+\xa0\x92\xa0O\x11\x9d\xca\
+\xab\xb7\xa2\x85\xd8\x9a`7_I$\x86\xb2\x8b\x84\xae\
+C\xea~\x0e\x8a\xd5\x00\x16\xc7\x22\x85}\x97\x9d\xb3\xc3\
+\xc1\x1f\x0d\x97\x0e\xb6vnP_\xceW`\xe4\xfe*\
+\x8c\xcf\x97\x86\xb2T\xfa\xddy\xfe\x8c\x03bp\xd0\x89\
+\xa3|\xdaUE*\xd8+\xbd\xef\x95+\x04\xe5\x94\xff\
+\xd2\x8b\x03\xe8S\xe20&\x09\xcd\x0dK/\xc2\xf7\x1e\
+=\xf9 u\xc8B\xcf\x985a\xabh\xb9\x8a1\xc0\
+X\xb2EOU3\x9c\x81\xdb\xe03\xb6\xd8\xe7\x81\xbb\
+\xe8\xdd\xe0_\xcf\xc8\xf6\xd2\xfc\x16\x94T\xectA;\
+1\xd0\xda\xa2\xf8\xf9\x02\xfb\x11\x18\x04\xab\x9d\xd3M\x16\
+\x9e\x9fb\xbbs\xfd\x84\x94+\xb1\x8f9F\xed~ \
+z\xb9\xc5\xa5\xa0\x9d\xd3!\xb5?\x1d\x22\xae\xdal\x1a\
+]\xc0\x92\x0c\xf2\xfc\xec@\x86\xc6Y\x5cA\xea\xde\x82\
+\xc2\x8enD\xad\xdcP\x88\xf3\xd4\xa7\xa8p\x0e\xce%\
+\x86\xc7SZ\xa5\x93\xf2\xbaP\xc8\x1aD\xc0zfP\
+\x09\x02\x7f_\xd0\xdc\xb8\x9dS\x0d\x89\x08\xa1\x13\xde\x8a\
+:\xb3\xebZ;\x13\xcfS\xaan\xf0\xbb\xd5\x0b\x16\xb4\
+\xb7\x16ohTt\x0a\x08\x15FJ\xf6X\x16\x5c\xb7\
+I+\xc5U\xff\xb4\xc8k\x22\xb1\xd2\x92@X\xf4L\
+\xb3h!g\xaf6\x7f\x1c\x9f1\x83\x08\x01\xdd\xb7\xe5\
+\x98L.\xfc\xeb)?5\xf7\x1dC\x9a\x0e\x0cAT\
+[\xa1\x98\xbfv\xca\xceE\x9e\x1eJ<\xe8\xf1h\x99\
+\xa4Y\xc8~\xfa1\xdf\xb3\x06&\x1bR\x13\xe9h\x95\
+\xb8\xad\xca\x9dS\x96\x99\xfc\xb6m9\x93%74\xb7\
+c\xec9\x1b\x92\x80\x8c\xe8\xf3N\xe4\xe2\x11]\x85\x1e\
+_H\xed\xc9D\xef\x1e\xcf\xea\xd6&\x1a=\x9ey\xc6\
+t\x8a\xb7\x93\xc6\xd4\xcdVgQQ\x93\xa0@\xac\xae\
+\x04\x04\xc5B\xc5\xa4Z\x8e<h\xe8:\xbc\x04&\x18\
+\x8d^\xaa\xc4\xe9p\xb4\x83J1\x928\x87=w5\
+r\xc14|\xa9\x11(\xe6<5\xe0vs\x14\x07-\
+\xf2n\xda\xedd\xe74\x8e*\x93\x06\x05\xbb\xd0R\xfa\
+\xdf,\xb5^\x0a\x13)\x81\x15\xccQ\x82WjJ\xad\
+\xc8\x84I\xe0\xa7\xf8xvp\xc0\x88\xb1\x8e\xd2'\x8b\
+(\xc7\x94 \xaa\xc2\xee\xce\x0ed\x7fJ\xc3\xb9\xcd\xf2\
+\xc0\x86\x15T\xe7\x98\x8a\xd9\xbc\x1f\x99/n\xee\xe2\xcd\
+\xa6\xc2\x9a\xcdN\xce\xf0\x5c)S\xed\xd0\xf2\xed\xd1\xe4\
+i\x8d\xc1\xbf\xd1\x84\xc2\xb6}J\xcf\xf5\x9e\xdbs,\
+\xcaN,\x95O\xe8\xcfe\xfc=\xc2\xd1\xbb\xbeF\x09\
+\xefO\xab\xe6j=b5j%\xbe\xdd\x03\xc2C\x9d\
+\xb5u'\x05F\xe8\xd9\x8c*2!\xa7\xa2Z\xf4\xb0\
+{U\x12z\xccbi\xc0\xdfcWq\xb0\xcck\x8b\
+\x92J\x85^\xc4Y/V4\xeb\xc5\xc2\x91v\x1c\x0f\
+;\xe4p\x94\xb3#\xceA\xd6\x0asx\xa6\x18_\x97\
+\x06\xac\xa9,G\x95&\xe7\xe6z\xc5^*\xa3m^\
+\x98\xe8\x8d\x83\xa3\x89\xd0\x82\xf20\xd5\xc4\x0e\xee;{\
+@\x229PE,ea+\xa7\x1fT\x02\xe1\xba\x82\
+\x12\x0b\xdaU\x80\xf0\x1e=\x80\x94[@\x16Z^\xd4\
+n$<y\x13\x98\xc1&\xc0\xb7\x80Y\xb6\x03\xf1\x22\
+\x8c\x1f \x16 *\xb4R\xc9\x0e\xedj\x1f\xf5\xed3\
+\xb5u\x04i\xbc\x92R!%\xd9\xad2@Q|\xb1\
+\x07\xc0\xbb\x08\x91U\xff\xf8\xe5't@\xa4\x09\x08\x99\
+\xbbM\x84\x1dF^l\xed\xa4\xb7\xb7\xff\xd1T\xa4}\
+\xe9z\xcbX\x10F,\x9b\xe3[#`\xebi\xf3\xb7\
+r&\x92\xe7\x0b\x1c\x01\x12\xa5\xdf\xc5\xf8\x10|\x0ax\
+\xe2\xef\xc8\xe1YA\x87x\x16\xb0\x81>\xcd\x1e\x04\xc1\
+G\x5cU\xf5\x03t\xdc7\xf1\x12\x9f\x87\xffGF\x16\
+\xfd~\x9aE\xcb(\xf9\x09poB\xfa\x99\x10\xa8\xe0\
+w\x97\x9c]\x02\xbdp\xbfda\x9e\xc6\x9f\xb0q#\
+\xc6\x1e\x13\xb7_\xbcc=G\xa2y\x98\x88\xd9\xe1\xc8\
+\xf5\x0c\xfc\xe1\xf8\x8f\xc4\xfe?\x88\xa8\xa2\x1a\x9b+\x83\
+\xbb\xb7\x0f\x9f\xa7\x01\xf5\xc0\xd5\x929\xdb\xdcj8\x91\
+L\xe0\xae\xa0;\xeb\xfc\xc7e\x8f7\xe5\xfe#\xd5\x06\
+{\x91`\x15!\x0f,\xad\xa6N\x89\x0d\x02O\xee\x9f\
+/\x91\xf8\xff\xa9Y2\xf9\x18\xad\x97\xe8c\xe4\xdc \
+l\xc8\x0cP\xfa\xc2\x14\x93\x0c\xf8Mx\xda\xac\x9a\x84\
+^z4\x7fE\xcc\xc3n\x97\xa6u\x86\x7f\x07\x05\xc6\
+9^\xc0\xe5,M\xe2[z\xce\x05[l\x03JD\
+,Xz\xc5\xcb0.\xaer\xea7\x11\xa1\xd6\xbd\xa7\
+m+IY\x12\x82\xbd\x86\xde\x0d\xfe\x83G\xf6\x1e\x96\
+\xef\xed\x99\x0d\x1bv\xb2\xb6z\xdc\x8e\xda\xea\xf4\xde\xd2\
+\x06A\xba\xbc\xad\xb2\xc2E\xd6\xca\xc6V\xe2\xb9bo\
+\xab}\xec*\x90\xfb\x8a\x0e<i\xefq\xc7\xae\xe78\
+\xa63\xfam\x90\x91\xf7MF\x80\xaa\x9865\xdc\xb7\
+\xf4\x83ZOy\x06\x9a\xe9t\xe6\xa7\xc7\x87\xd6b;\
+\xbf<\xe6\xb3\xadi\x0d\xb058\xf4\xbe\x1a\xa9H\xcc\
+\xb0\xc2\x139{\x06\xf4\xb3c\xb5\xc0\xc4ZD\x22\xff\
+u2\x11\x91X\xe1\xac\xb1\xa87\x06\xd2\x86g\x97\xa9\
+\xaf\xd7i\xba\xb1I0\xcf\x225b\x8c\x89k\x0d\x8b\
+\xc4+\x14\x11\x1ai\xf2\x81(\xc3\xd82@\x1aX1\
+\xc1\xec:*V,\xb0V;\xbeL\x07_\xb3`^\
+\xb0\xcd6\x0b\xa5w\xc0\xe8\xfa\x1d_Q\xd5\xeeIO\
+j\x1a\xe8\xab\x8e\x9f6)\xea\xb1;9w#Bc\
+\xab\x9b{\xb4T\xbc\x93\x9a\xa0\xf2g\x98?\x85\x19\xb7\
+Zl\x05OJU\xc4RR3|/\xc8\xf5*\xfa\
+[\x22tu\x88o\x8b;\xf4\xef8\xc8wZ5\xd6\
+f)\xa9h\xe7\xbam*W\x93\x955\x9cn\xdc\xda\
++\xdc\x9f\x22\xe0\xc1\x83\x1dn\xc2\xf0\xe3#:\xcc'\
+\xd6\xeb *[\x9b\xae\xa0\x0f\x1b$\xf1\x9d\xea8\xdf\
+%\x90\xef\x1a\xc7K\xbf}\xc7\x0f\xae\xba\xdb\x01\xd7o\
+\x95\x09A\xfa]o\xc0\x8fmx!E\xb9\xd2r\x82\
+\xf8Q\x97\xa7\x93K\xa7\x17\x9f\xc2\x816\x8d*\xe3\xb5\
+\xa3}\xe9\x14\xdd0\xbeW\xfcy\xc5\x9d\xbc\x05\xb2\xeb\
+c\xcbay\xa0\xa9f\x0e\x0c\x80\x95+\x1fl\x0e\xf9\
+<\xf5\xa8j\xab\xcaX\xa3{\x03t\x95\xd5\xe1lE\
+\xb6R\xd7\xd9V;\xf1Z\xc1C\x8d9\x98~\x17\xa2\
+\x81G\x9592C\x90t\x0d\xbd\x85\xa0k\xd4)\xa3\
+\xa2\x02\xdapz\x0dC\xa0\xa7\x9d\x1f\xa0\x1fa\xaa\xc8\
+\x1a\xe9#_uS\x85\xbc8\xa8\xf7\xcf_\x95\x19Y\
+\xe7*\xe120ox\x94 V\x0b#\xf3K\x96^\
+[\x0fa\x85\xfaaw\xfc\xf3\xc1|\xec\xd1p\xc0p\
+H+\xa6\xad\xfa\x22\x881\x16\xebA\x8e\xda\xc3 \x84\
+\xa9~\x87/\xdd'\xe6\xdb \xbb\xa5\x80w\x83/\xa3\
+\x98C\xb8S\xcf\xee\xa0o\xbf'\x1f\xd11\xda\xa9\xb7\
+O\xf4K'\x95\x87\xd9\xe5\xd3=-d\x02\x13]$\
+U\x07\x14\xee`\xc6\xceGR\xb8;\xaa\x0am\xbf]\
+Y\xe3\xbc\xe8\xee\x03\xef\xee\x03t\x07hT\x0f\x1f\xaa\
+\xcf\xb0<\x81\xdf\x8d\x1c\x07\xe4\x93\x0f\xd3\xca\xc3+\x9a\
+\xcb\x14\xc2\xd5\xce\xe6\x0f&\x9b\x0dF[\xcf\xb3H\x05\
+\xba\x9b\xd7\xf6\x0c5\xaeCjb{*\xeb\x0b\xaa\xf6\
+:\xae^[j\x01)\xfe\xd7\xb7e\xbe\x0bPXB\
+\xe5\x95Y\x93\xef\xd6\x90\xd4\xaem\x8d\xbbq\x04\x1a\xa5\
+\xdbB\xbe\xf1\xcck#\xe1\xbc\x80\xcc\x0b\xf5\xa8\xac1\
+-\xb5W`\x85\xfb\xdf\x86\xa2\xa2\xc71A=?\xf7\
+[\x0c\x1f\x915_h\xb40\xc2x\xf9o\xd74\xaa\
+s\xbc\xd0\xcf\x95'\x10\x9b\x05P:\xeb\xcd\x96\xf2r\
+\xa06\x9c\x87y\xa4\x9e\xfb\xdb\xc5(\xb1Q\x1bV\x86\
+1Q\x22\xfeE\xd94\xae\x1d\x02\xfe%\xccF\xa2Z\
+%\xad\xfa\xf6\xe2\x17/\x94\xc7\x8f\xff\xb7\xa2\x9d^\x09\
+\xd4\xf4\xe2\x9b:o~|\xc3\x8f72;K:\xbc\
+A8\xe8\x97\x08\xf1\x0dS\xf5\x22\xf5\xd9\xd1N\x83\x05\
+\xd0\xd7\xac\x87:\x15\xfa}\xa2\x07\x0c\x08\x05\xe86h\
+\xe0\x9a\x95\xd2`$\xa0\xa4<\xad\x0fC\x8a\xacx5\
+\xc3\x08\xfd[\x18o\xd05b\x1f\xcb\xa5+\x8f~#\
+\x91;\x0fk\xd7\xd1\x84Oy\x12\xf79\x14\xc8\xef\x10\
+\x93w\x9f\x8d\xfd\xeb\xee\x897\x5c\x82\xcc\x0e\x9f\x93\x8b\
+\xf1y\xb0\xde\x9c\xd4~:\xe7\x94\xd7\xc5E\xbd\xea\x9c\
+W-\x1b\xaax\xe0\xcesT\xa2\xf5J\x1eY\xf4\xfc\
+\xd9\x8b\xefN(F\xc7\x18\x97vI\xab\x0bv\x0c\xf8\
+\xb9\xd9\xc4\xd1<*\xe2[F/Z\xa33;\xa2\xe4\
+\xebUx\x03\xfb^\xb2`\x09\x88\xce\x82\xfd\xed\xdd\xcf\
+\xaf\x81\x1d \x95Q\x98\xdb\xdc\xd0\xdd?w\x9f\xe1k\
+\x07\x8b\xae\x87O-<\xbb\x99\x1c\xf6\xbf\xfb\xa1\xff\xaf\
+A\xffj\xca\x8b\x8030\x1d\xe3a\x04d*\x9b\xfe\
+\xb2\xc7\x12\xb9h\xc8\xc7\xd1\xe4\x05G\x15\x97p\xbd1\
+O\xe3\x14\x9f\xef\x93\x06\xfd\xc81@\x8c\xd8:\x02~\
+f\xacG\xd1@\xc1\xc8w\xb2n\xb4\x16\x1a3\x1e\xb9\
+3\xb8\xca\xd2\xf5+\x11\x08\xe4\x920\xfd\x94\x14\x80^\
+\x87p\x1e{=v\xf4\xd23\x93Q\x9a\xdav\x13+\
+\x18\xdd\x92n\xebHr_\x990JE\xce0\xc3\x98\
+\xfc\x1dR\x08\x97\xf4\xc3O\xf4\xaf\xfc\xe9V8>\xd2\
+w\xc4\x02\x90\xdc\x1f\xc4\x7f\x82\xc5\xf0}H\xa76\xcd\
+\xd5H\xb3\x1f\xe7\xb7\x07;j\xec\x19F8\xdad\xf0\
+\xb7\xd1\xc3\xa7@\xbd\xa3\xa7\xab\xad\xf0\xb4\x0b\x7f2\xf5\
+\xfc\x0b\x11\x8e\xa63\x01l\x9a\xd5+\xda\xaa\xdb\x93\xfa\
+\xb2\x16L\xe2\xdb\xb8L\xb4\x1eU\x8f\x81\xe6\x9c\xea7\
+\xb9\x0d\x96\x19\x0b\xdf\xe6\xad\xe1\xadF\x07\xb5\xf0N\xeb\
+@4\xe1\xb5\xce'\x0e\x85\xc0\xe2W\xf5\xcc'\xac\x9a\
+\xeb\x90~\xcbSDY\xa0\xe3\xe3\x16\xa8\xe6\x8fi#\
+,\x0b\xae\x0a\xf1Lw\x1c\xe4\x05\xcb\xe1\xef\x15zu\
+\xa3\x22\x07e\x0c\xc7\xfe\xb9\xbc*\xeaI\x9c\xb3m\x81\
+x\xd7\x18\x18\xc5\x9d\xb9E\xca\x82\xc5\x82]\x16\xab\xa0\
+\xb8\x94`\x94\x89?\x1a\x0e\x8b4\x05\xc3;\x0a\x8b+\
+\xb0y\x96C\x5c\x87\xc3\xecj\xfe\xe2\xbb?\xbd|&\
+\xde\x90\xea\xbfP\xcb\x02\x7fPz4\xed\x8e\xfcag\
+r1\x9c\xaa'\x18\x91X#R\xb9i\xd80q4\
+\xa4.s\x86\xf2\xe8l\xa7\xf9\xeche\xdewQ\xb7\
+\xc6\x13\xca\xf2\xaa\x88\xb0\x9f5bQY\xe4\x94\x07\xa4\
+\xde\xad;\x16\xebv8T\x8b[\x88\x81\xdd\xe7H\xa4\
+\x09a\xaf#z\x06\x1c\xf1\xf0\xcb\x1e\x95LG\xa8+\
+J\xe3\x01\xbc\xae\xe2\xa3g\xf4@Q\xc9\xb5.jh\
+L\x10\x90Ir\xd3\x88\xa13\xfe\xfb~\xdc\xb8\xad_\
+\xbeP6\xd87\xe5\x05\xff5v\xf8K<\xa8\xd4\x19\
+A\xd9\xf8\xd9t\x18\x9d\x18\x22\x8eOA\xf1\x0b\x02\xfc\
+Dq\xe9\xa8[\xe1\xb3\x09\xc5\xdd\x80\xe9\xec\x83\x91\xdc\
+\x0d`G\xfaG\xce\xa0^\x1d\xf6?\x86\xb7\xd5 J\
+\xa8\xdf\xe23@\x8d\x87@\xde\x1a\xf0)(:\xfca\
+\x15\xa1\x00|\xf4\xfe<\x81\xd9\x89\x93of\x1f@\x8a\
+\xcd\xa0\xa3 \x7fs\x9d\xbc\x95O\x01\xcc\x038\xdc\x09\
+\xea\x90.+\xdf\x11\xc63\x812J\xb1%\x10\xfaf\
+\x9f\xb1j\xe1\xeb\xb3\x0f\xb6\x8e0\x1e\xf4\xa4\x83\xd7/\
+\xe9u\x8f\xd1\xfb\xec\x9aWt\xa0\xa5^8\x80!#\
+\xf8\xcb\x93\xa0\x0eK\xa1\x0eYi<e\xdb\xb5\x9f\xb2\
+\xd5?\x01Ay\x05\xc6\x91\x9a\x9d\x9b=\x8a'o\x09\
+\xcd\x1cc\xfc\xb0\xa6&k\xc2T\xb5\xd0\x9cJ4\xbc\
+\x94^Ht\x8c\xd7\x14\x8c\x09m=\xd1\x1bo\x9d\xd6\
+\x9f3\xf5}1\xd2Rb\xd51\xef\x08l\xba\xfc\x7f\
+\xa6\x1b\x8a\x8a\xd9\xc5\xaf-\x94w\x1f_p\x8ec\xcc\
+\x89V\xa6\x0a\xcfY\x081_\x01\x85\x86,CP\xa9\
+\x09N\x15\xed\xae\x8c\x82\x98\x04\x17Qf\xa0\x96r/\
+pI\xab\xf7\xe2\xe9\x01lY\x9c\xd0\x8f\xc6\x0ba\xad\
+\xe6D\x08\x92\xbc\x11GlX\xd1Q\xae\xdf\x9f\xa7\xc7\
+\xff\x01\x8d\x1ew\xa5\xfb}\xfe\xca6R\xe8|Y_\
+x\x1d\x88\x87\x0d\x1d\xfdW[\x1d2\xe2\x98\xaf\x0b\xba\
+\x96\x00\x85\xdd\x93\x9c\x013\x0f\xe0a\xf0\xf6\x03\x1a\x92\
+\xc7\xc8\x12A1\xd9\x0fH\xb1\x9c\x19E3m\x8c\xf6\
+\xac\x90XH\x1cdu\xc85\xc6\xcd\x10\xfd\xf8\xae8\
+\xee\xf0b}\xf7P\xb9\xac\xeaI\xc3\xe5N\xbd\xf2N\
+\xfe@\x190\xcc\xfb\xd0\x01\xc4\x86\xaf\x91\xbc\xe4\xc6\xbb\
+\xe9\xa05#\xd3\x19i\xfe\xcc\x8a\x19\xc7\xa1\xee0\xd4\
+\xcf{*\x19\xf4\xc4f\xd7\x1a~!\x87\xee\x86\x9e5\
+J\xd19\xea\x05\xd3\xd7o\x8ch\x91&\xd6=\x12H\
+\xb5\xad\xfe\xac\x02f\x8f\x1a\x1a\xaa\xcf\xda\xe1T\xa3\x09\
+0\xd4\x0e\x9b\xe2ZF\xff\xec\x8e\xc1\x09&.l\xcc\
+\x8b7W\xf0\xd5\xb0\xa6\x9a9\xc2pab\xb4E\xad\
+\xcb\x1d\xa4\xdb\xdc\x04\x04\x86\x19n\x8d\xc30\xb1U9\
+O\xde\x07\x92%\xefOt\xae\xd3\xbe\xee\x10\x84I}\
+\xd1\xda\xf0Em.qF\x5c{\x1a\x17a\x0c+\xd0\
+\x1e\x80\xd9\x89\x98f\xaf\x8aB\xefo\xc2]j\x09\x81\
+\xed$uu\x0c\x01B\x99\x0c\xe5\x8f\x10+\x97\xc9>\
+?\xfa,\xc2\xca\xeb\x09\xa2\xf3~_J\x1d\x8c\xd8\x1c\
+\x0c\x1fP\x05Z\x87\xe0\x1b\x1e}f8\xf1-\xd1\xec\
+U\x82\xef-!5\x07o\xca\x9c\x04\xe21\xf9\x22\x9e\
+\x1fs-\xf9w\xb16\x06\xfa\x1a\xfeiC2\x07\xc5\
+\x0c\x5c\x8cg\x0a\x9c\xd4*\x9b3\x06\xf0\xcfn\xd6i\
+\xb1\xf7\xc4j@\x9f\xbc%(\x9c^\xa9\xfe\xf5\x9a\xa2\
+l\x06<\xb9=F\xed\x09\x94b\xec\xd6\x22lPM\
+\xbaI}5\x86\x83u\x98\xe7\x18\xfb\xc0o\xed\xde\xc6\
+!\xee\x95\xb0W\xa7YA\xc7+<Z\xc8\x83\xc42\
+*V\xdb\xd9\x00\x0e$CN\xd6\x87\x5c|\x18\x18\xe7\
+jW(\xe6\x0a\xe5`\xd2\x80\xb1\x91\x14\xb5\xe5\x84\xf1\
+\xb8?$b\xb7N\xe7\xf4Cj\x8b\x11F\xe5R:\
+\x84>p\xab\xd8)\x83j\xbc\xe212\x1e8\x98c\
+\xe4W\xc8\xc9\x17&\x81\xb4\xa5\xa5\x81\xf1fc\xa4\xae\
+\x09\x8aU$\x82,\xc8\xc3\xe2\x8d\x8aNP\xd2\xae\x0e\
+\xda|\xb2Z'J\x0c\x93\xd7\xf3\xabM\x01\x0b\xd6\xe6\
+\x8f\x02\xdc\x0an\xb1\x9cH\x9c[\xc2\xee\xe7i\x82|\
+\xab\xe2\xafG\xc9{\x07~\xfc\xbdZ\x8b\xdb\x1e\xfa\xaa\
+\xf2\x16\xea\x85<\xf9`\xa4n\xc8\xd4*7{\xd0y\
+&\xf8\xe3*\xf0e\x0b<\xef\x8b\x16<\xe7\xca\xc4,\
+\x9f\xa6\xb0H\xb2c\x04t|\x00\xaf\x95a\x93V\x13\
+\xf5\xbc\x87I\x0b\x17\x1e\x1bN\xfd\xceT\xbd\x98\xe7\x0d\
+Z\xe5\xfcw\xc2M\x82)\xa8K\xc0\xec\xc9\x83\x7fe\
+\x1eq1\xd6f\xcb|\xa5\x92?\xbee\x09\xd0[\x19\
+a\xc2?\x9c\xc8\xf2\x8d]\xce\xbfkqP\xd1\x1bg\
+\x8aO\xaa\xa9\x15\xddqf\xc5Zh\x04\xaf\xc5o\x89\
+\xd3\xbf\xaail\x96\xe27\xdd\xc0\x0c#93\xe3@\
+T\xe3\xa8\x0dB\xb8d5.\x19\xf0\x22\xe5|\xcf\xb0\
+m\xd7\xe9b\x1b\x8b}\xc9\xb0\xae\x9f?\x97\xa6$\x18\
+\x9d\xa0prnN\xa6d\xb1\x8a\xbd\x8b7\x1d(\x00\
+\xbd\x8e\x8cGn8\x12\x8e\xb7b\x92b'\xbcb\x10\
+\xacE*\x03\xff\xee\x9a.\xa2\xca*\xe5\x1e6}T\
+\xca\xd2\xb4\x18\xf0:\x93\x84=T\xf4\xa8$\xf1E\x0d\
+A\xc5u\x94,@\xd3T\xc7:\x96\x15#\xb6\x8c\xd3\
+Y\x10\xa3{\xef\xbf\x01\x9e\xed\xc5)\
+"
+
+qt_resource_name = b"\
+\x00\x0a\
+\x08\xce\x22\xb4\
+\x00d\
+\x00e\x00f\x00a\x00u\x00l\x00t\x00.\x00m\x00d\
+\x00\x08\
+\x08\xb6\x8e\xf9\
+\x003\
+\x00r\x00d\x00p\x00a\x00r\x00t\x00y\
+\x00\x0a\
+\x0c\xba\xf2|\
+\x00i\
+\x00n\x00d\x00e\x00x\x00.\x00h\x00t\x00m\x00l\
+\x00\x0c\
+\x08\xd0i\xc3\
+\x00m\
+\x00a\x00r\x00k\x00d\x00o\x00w\x00n\x00.\x00c\x00s\x00s\
+\x00\x09\
+\x09\x1b\x92\x13\
+\x00m\
+\x00a\x00r\x00k\x00e\x00d\x00.\x00j\x00s\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x1a\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01z.[\x95V\
+\x00\x00\x000\x00\x00\x00\x00\x00\x01\x00\x00\x01\xe1\
+\x00\x00\x01z.[\x95V\
+\x00\x00\x00J\x00\x01\x00\x00\x00\x01\x00\x00\x04\x97\
+\x00\x00\x01z.[\x95V\
+\x00\x00\x00h\x00\x01\x00\x00\x00\x01\x00\x00\x0a\xf1\
+\x00\x00\x01z.[\x95V\
+"
+
+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/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt b/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt
new file mode 100644
index 000000000..23c52cc43
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt
@@ -0,0 +1,16 @@
+Copyright 2011 Kevin Burke unless otherwise noted.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Some content is copyrighted by Twitter, Inc., and also released under an
+Apache License; these sections are noted in the source.
diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt b/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt
new file mode 100644
index 000000000..8e3ba0e0a
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css b/examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css
new file mode 100644
index 000000000..24fc2ffe2
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css
@@ -0,0 +1,260 @@
+body{
+ margin: 0 auto;
+ font-family: Georgia, Palatino, serif;
+ color: #444444;
+ line-height: 1;
+ max-width: 960px;
+ padding: 30px;
+}
+h1, h2, h3, h4 {
+ color: #111111;
+ font-weight: 400;
+}
+h1, h2, h3, h4, h5, p {
+ margin-bottom: 24px;
+ padding: 0;
+}
+h1 {
+ font-size: 48px;
+}
+h2 {
+ font-size: 36px;
+ /* The bottom margin is small. It's designed to be used with gray meta text
+ * below a post title. */
+ margin: 24px 0 6px;
+}
+h3 {
+ font-size: 24px;
+}
+h4 {
+ font-size: 21px;
+}
+h5 {
+ font-size: 18px;
+}
+a {
+ color: #0099ff;
+ margin: 0;
+ padding: 0;
+ vertical-align: baseline;
+}
+a:hover {
+ text-decoration: none;
+ color: #ff6600;
+}
+a:visited {
+ color: purple;
+}
+ul, ol {
+ padding: 0;
+ margin: 0;
+}
+li {
+ line-height: 24px;
+}
+li ul, li ul {
+ margin-left: 24px;
+}
+p, ul, ol {
+ font-size: 16px;
+ line-height: 24px;
+ max-width: 540px;
+}
+pre {
+ padding: 0px 24px;
+ max-width: 800px;
+ white-space: pre-wrap;
+}
+code {
+ font-family: Consolas, Monaco, Andale Mono, monospace;
+ line-height: 1.5;
+ font-size: 13px;
+}
+aside {
+ display: block;
+ float: right;
+ width: 390px;
+}
+blockquote {
+ border-left:.5em solid #eee;
+ padding: 0 2em;
+ margin-left:0;
+ max-width: 476px;
+}
+blockquote cite {
+ font-size:14px;
+ line-height:20px;
+ color:#bfbfbf;
+}
+blockquote cite:before {
+ content: '\2014 \00A0';
+}
+
+blockquote p {
+ color: #666;
+ max-width: 460px;
+}
+hr {
+ width: 540px;
+ text-align: left;
+ margin: 0 auto 0 0;
+ color: #999;
+}
+
+/* Code below this line is copyright Twitter Inc. */
+
+button,
+input,
+select,
+textarea {
+ font-size: 100%;
+ margin: 0;
+ vertical-align: baseline;
+ *vertical-align: middle;
+}
+button, input {
+ line-height: normal;
+ *overflow: visible;
+}
+button::-moz-focus-inner, input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ cursor: pointer;
+ -webkit-appearance: button;
+}
+input[type=checkbox], input[type=radio] {
+ cursor: pointer;
+}
+/* override default chrome & firefox settings */
+input:not([type="image"]), textarea {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+input[type="search"] {
+ -webkit-appearance: textfield;
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+label,
+input,
+select,
+textarea {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: normal;
+ margin-bottom: 18px;
+}
+input[type=checkbox], input[type=radio] {
+ cursor: pointer;
+ margin-bottom: 0;
+}
+input[type=text],
+input[type=password],
+textarea,
+select {
+ display: inline-block;
+ width: 210px;
+ padding: 4px;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 18px;
+ height: 18px;
+ color: #808080;
+ border: 1px solid #ccc;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+select, input[type=file] {
+ height: 27px;
+ line-height: 27px;
+}
+textarea {
+ height: auto;
+}
+
+/* grey out placeholders */
+:-moz-placeholder {
+ color: #bfbfbf;
+}
+::-webkit-input-placeholder {
+ color: #bfbfbf;
+}
+
+input[type=text],
+input[type=password],
+select,
+textarea {
+ -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+ transition: border linear 0.2s, box-shadow linear 0.2s;
+ -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+input[type=text]:focus, input[type=password]:focus, textarea:focus {
+ outline: none;
+ border-color: rgba(82, 168, 236, 0.8);
+ -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
+ -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
+}
+
+/* buttons */
+button {
+ display: inline-block;
+ padding: 4px 14px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ background-color: #0064cd;
+ background-repeat: repeat-x;
+ background-image: -khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));
+ background-image: -moz-linear-gradient(top, #049cdb, #0064cd);
+ background-image: -ms-linear-gradient(top, #049cdb, #0064cd);
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));
+ background-image: -webkit-linear-gradient(top, #049cdb, #0064cd);
+ background-image: -o-linear-gradient(top, #049cdb, #0064cd);
+ background-image: linear-gradient(top, #049cdb, #0064cd);
+ color: #fff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ border: 1px solid #004b9a;
+ border-bottom-color: #003f81;
+ -webkit-transition: 0.1s linear all;
+ -moz-transition: 0.1s linear all;
+ transition: 0.1s linear all;
+ border-color: #0064cd #0064cd #003f81;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+}
+button:hover {
+ color: #fff;
+ background-position: 0 -15px;
+ text-decoration: none;
+}
+button:active {
+ -webkit-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+button::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js b/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js
new file mode 100644
index 000000000..33c02d9cf
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js
@@ -0,0 +1,1514 @@
+/**
+ * marked - a markdown parser
+ * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
+ * https://github.com/markedjs/marked
+ */
+
+;(function(root) {
+'use strict';
+
+/**
+ * Block-Level Grammar
+ */
+
+var block = {
+ newline: /^\n+/,
+ code: /^( {4}[^\n]+\n*)+/,
+ fences: noop,
+ hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
+ heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/,
+ nptable: noop,
+ blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,
+ list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
+ html: '^ {0,3}(?:' // optional indentation
+ + '<(script|pre|style)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1)
+ + '|comment[^\\n]*(\\n+|$)' // (2)
+ + '|<\\?[\\s\\S]*?\\?>\\n*' // (3)
+ + '|<![A-Z][\\s\\S]*?>\\n*' // (4)
+ + '|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>\\n*' // (5)
+ + '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:\\n{2,}|$)' // (6)
+ + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag
+ + '|</(?!script|pre|style)[a-z][\\w-]*\\s*>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag
+ + ')',
+ def: /^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,
+ table: noop,
+ lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
+ paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/,
+ text: /^[^\n]+/
+};
+
+block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/;
+block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/;
+block.def = edit(block.def)
+ .replace('label', block._label)
+ .replace('title', block._title)
+ .getRegex();
+
+block.bullet = /(?:[*+-]|\d+\.)/;
+block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
+block.item = edit(block.item, 'gm')
+ .replace(/bull/g, block.bullet)
+ .getRegex();
+
+block.list = edit(block.list)
+ .replace(/bull/g, block.bullet)
+ .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))')
+ .replace('def', '\\n+(?=' + block.def.source + ')')
+ .getRegex();
+
+block._tag = 'address|article|aside|base|basefont|blockquote|body|caption'
+ + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption'
+ + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe'
+ + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option'
+ + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr'
+ + '|track|ul';
+block._comment = /<!--(?!-?>)[\s\S]*?-->/;
+block.html = edit(block.html, 'i')
+ .replace('comment', block._comment)
+ .replace('tag', block._tag)
+ .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/)
+ .getRegex();
+
+block.paragraph = edit(block.paragraph)
+ .replace('hr', block.hr)
+ .replace('heading', block.heading)
+ .replace('lheading', block.lheading)
+ .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks
+ .getRegex();
+
+block.blockquote = edit(block.blockquote)
+ .replace('paragraph', block.paragraph)
+ .getRegex();
+
+/**
+ * Normal Block Grammar
+ */
+
+block.normal = merge({}, block);
+
+/**
+ * GFM Block Grammar
+ */
+
+block.gfm = merge({}, block.normal, {
+ fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/,
+ paragraph: /^/,
+ heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/
+});
+
+block.gfm.paragraph = edit(block.paragraph)
+ .replace('(?!', '(?!'
+ + block.gfm.fences.source.replace('\\1', '\\2') + '|'
+ + block.list.source.replace('\\1', '\\3') + '|')
+ .getRegex();
+
+/**
+ * GFM + Tables Block Grammar
+ */
+
+block.tables = merge({}, block.gfm, {
+ nptable: /^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/,
+ table: /^ *\|(.+)\n *\|?( *[-:]+[-| :]*)(?:\n((?: *[^>\n ].*(?:\n|$))*)\n*|$)/
+});
+
+/**
+ * Pedantic grammar
+ */
+
+block.pedantic = merge({}, block.normal, {
+ html: edit(
+ '^ *(?:comment *(?:\\n|\\s*$)'
+ + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag
+ + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))')
+ .replace('comment', block._comment)
+ .replace(/tag/g, '(?!(?:'
+ + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub'
+ + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)'
+ + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b')
+ .getRegex(),
+ def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/
+});
+
+/**
+ * Block Lexer
+ */
+
+function Lexer(options) {
+ this.tokens = [];
+ this.tokens.links = {};
+ this.options = options || marked.defaults;
+ this.rules = block.normal;
+
+ if (this.options.pedantic) {
+ this.rules = block.pedantic;
+ } else if (this.options.gfm) {
+ if (this.options.tables) {
+ this.rules = block.tables;
+ } else {
+ this.rules = block.gfm;
+ }
+ }
+}
+
+/**
+ * Expose Block Rules
+ */
+
+Lexer.rules = block;
+
+/**
+ * Static Lex Method
+ */
+
+Lexer.lex = function(src, options) {
+ var lexer = new Lexer(options);
+ return lexer.lex(src);
+};
+
+/**
+ * Preprocessing
+ */
+
+Lexer.prototype.lex = function(src) {
+ src = src
+ .replace(/\r\n|\r/g, '\n')
+ .replace(/\t/g, ' ')
+ .replace(/\u00a0/g, ' ')
+ .replace(/\u2424/g, '\n');
+
+ return this.token(src, true);
+};
+
+/**
+ * Lexing
+ */
+
+Lexer.prototype.token = function(src, top) {
+ src = src.replace(/^ +$/gm, '');
+ var next,
+ loose,
+ cap,
+ bull,
+ b,
+ item,
+ space,
+ i,
+ tag,
+ l,
+ isordered,
+ istask,
+ ischecked;
+
+ while (src) {
+ // newline
+ if (cap = this.rules.newline.exec(src)) {
+ src = src.substring(cap[0].length);
+ if (cap[0].length > 1) {
+ this.tokens.push({
+ type: 'space'
+ });
+ }
+ }
+
+ // code
+ if (cap = this.rules.code.exec(src)) {
+ src = src.substring(cap[0].length);
+ cap = cap[0].replace(/^ {4}/gm, '');
+ this.tokens.push({
+ type: 'code',
+ text: !this.options.pedantic
+ ? cap.replace(/\n+$/, '')
+ : cap
+ });
+ continue;
+ }
+
+ // fences (gfm)
+ if (cap = this.rules.fences.exec(src)) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'code',
+ lang: cap[2],
+ text: cap[3] || ''
+ });
+ continue;
+ }
+
+ // heading
+ if (cap = this.rules.heading.exec(src)) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'heading',
+ depth: cap[1].length,
+ text: cap[2]
+ });
+ continue;
+ }
+
+ // table no leading pipe (gfm)
+ if (top && (cap = this.rules.nptable.exec(src))) {
+ item = {
+ type: 'table',
+ header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+ cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : []
+ };
+
+ if (item.header.length === item.align.length) {
+ src = src.substring(cap[0].length);
+
+ for (i = 0; i < item.align.length; i++) {
+ if (/^ *-+: *$/.test(item.align[i])) {
+ item.align[i] = 'right';
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
+ item.align[i] = 'center';
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
+ item.align[i] = 'left';
+ } else {
+ item.align[i] = null;
+ }
+ }
+
+ for (i = 0; i < item.cells.length; i++) {
+ item.cells[i] = splitCells(item.cells[i], item.header.length);
+ }
+
+ this.tokens.push(item);
+
+ continue;
+ }
+ }
+
+ // hr
+ if (cap = this.rules.hr.exec(src)) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'hr'
+ });
+ continue;
+ }
+
+ // blockquote
+ if (cap = this.rules.blockquote.exec(src)) {
+ src = src.substring(cap[0].length);
+
+ this.tokens.push({
+ type: 'blockquote_start'
+ });
+
+ cap = cap[0].replace(/^ *> ?/gm, '');
+
+ // Pass `top` to keep the current
+ // "toplevel" state. This is exactly
+ // how markdown.pl works.
+ this.token(cap, top);
+
+ this.tokens.push({
+ type: 'blockquote_end'
+ });
+
+ continue;
+ }
+
+ // list
+ if (cap = this.rules.list.exec(src)) {
+ src = src.substring(cap[0].length);
+ bull = cap[2];
+ isordered = bull.length > 1;
+
+ this.tokens.push({
+ type: 'list_start',
+ ordered: isordered,
+ start: isordered ? +bull : ''
+ });
+
+ // Get each top-level item.
+ cap = cap[0].match(this.rules.item);
+
+ next = false;
+ l = cap.length;
+ i = 0;
+
+ for (; i < l; i++) {
+ item = cap[i];
+
+ // Remove the list item's bullet
+ // so it is seen as the next token.
+ space = item.length;
+ item = item.replace(/^ *([*+-]|\d+\.) +/, '');
+
+ // Outdent whatever the
+ // list item contains. Hacky.
+ if (~item.indexOf('\n ')) {
+ space -= item.length;
+ item = !this.options.pedantic
+ ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
+ : item.replace(/^ {1,4}/gm, '');
+ }
+
+ // Determine whether the next list item belongs here.
+ // Backpedal if it does not belong in this list.
+ if (this.options.smartLists && i !== l - 1) {
+ b = block.bullet.exec(cap[i + 1])[0];
+ if (bull !== b && !(bull.length > 1 && b.length > 1)) {
+ src = cap.slice(i + 1).join('\n') + src;
+ i = l - 1;
+ }
+ }
+
+ // Determine whether item is loose or not.
+ // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
+ // for discount behavior.
+ loose = next || /\n\n(?!\s*$)/.test(item);
+ if (i !== l - 1) {
+ next = item.charAt(item.length - 1) === '\n';
+ if (!loose) loose = next;
+ }
+
+ // Check for task list items
+ istask = /^\[[ xX]\] /.test(item);
+ ischecked = undefined;
+ if (istask) {
+ ischecked = item[1] !== ' ';
+ item = item.replace(/^\[[ xX]\] +/, '');
+ }
+
+ this.tokens.push({
+ type: loose
+ ? 'loose_item_start'
+ : 'list_item_start',
+ task: istask,
+ checked: ischecked
+ });
+
+ // Recurse.
+ this.token(item, false);
+
+ this.tokens.push({
+ type: 'list_item_end'
+ });
+ }
+
+ this.tokens.push({
+ type: 'list_end'
+ });
+
+ continue;
+ }
+
+ // html
+ if (cap = this.rules.html.exec(src)) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: this.options.sanitize
+ ? 'paragraph'
+ : 'html',
+ pre: !this.options.sanitizer
+ && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
+ text: cap[0]
+ });
+ continue;
+ }
+
+ // def
+ if (top && (cap = this.rules.def.exec(src))) {
+ src = src.substring(cap[0].length);
+ if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1);
+ tag = cap[1].toLowerCase().replace(/\s+/g, ' ');
+ if (!this.tokens.links[tag]) {
+ this.tokens.links[tag] = {
+ href: cap[2],
+ title: cap[3]
+ };
+ }
+ continue;
+ }
+
+ // table (gfm)
+ if (top && (cap = this.rules.table.exec(src))) {
+ item = {
+ type: 'table',
+ header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+ cells: cap[3] ? cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') : []
+ };
+
+ if (item.header.length === item.align.length) {
+ src = src.substring(cap[0].length);
+
+ for (i = 0; i < item.align.length; i++) {
+ if (/^ *-+: *$/.test(item.align[i])) {
+ item.align[i] = 'right';
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
+ item.align[i] = 'center';
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
+ item.align[i] = 'left';
+ } else {
+ item.align[i] = null;
+ }
+ }
+
+ for (i = 0; i < item.cells.length; i++) {
+ item.cells[i] = splitCells(
+ item.cells[i].replace(/^ *\| *| *\| *$/g, ''),
+ item.header.length);
+ }
+
+ this.tokens.push(item);
+
+ continue;
+ }
+ }
+
+ // lheading
+ if (cap = this.rules.lheading.exec(src)) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'heading',
+ depth: cap[2] === '=' ? 1 : 2,
+ text: cap[1]
+ });
+ continue;
+ }
+
+ // top-level paragraph
+ if (top && (cap = this.rules.paragraph.exec(src))) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'paragraph',
+ text: cap[1].charAt(cap[1].length - 1) === '\n'
+ ? cap[1].slice(0, -1)
+ : cap[1]
+ });
+ continue;
+ }
+
+ // text
+ if (cap = this.rules.text.exec(src)) {
+ // Top-level should never reach here.
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'text',
+ text: cap[0]
+ });
+ continue;
+ }
+
+ if (src) {
+ throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
+ }
+ }
+
+ return this.tokens;
+};
+
+/**
+ * Inline-Level Grammar
+ */
+
+var inline = {
+ escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,
+ autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/,
+ url: noop,
+ tag: '^comment'
+ + '|^</[a-zA-Z][\\w:-]*\\s*>' // self-closing tag
+ + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag
+ + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?>
+ + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html>
+ + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>', // CDATA section
+ link: /^!?\[(label)\]\(href(?:\s+(title))?\s*\)/,
+ reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,
+ nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,
+ strong: /^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)|^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)/,
+ em: /^_([^\s][\s\S]*?[^\s_])_(?!_)|^_([^\s_][\s\S]*?[^\s])_(?!_)|^\*([^\s][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*][\s\S]*?[^\s])\*(?!\*)|^_([^\s_])_(?!_)|^\*([^\s*])\*(?!\*)/,
+ code: /^(`+)\s*([\s\S]*?[^`]?)\s*\1(?!`)/,
+ br: /^ {2,}\n(?!\s*$)/,
+ del: noop,
+ text: /^[\s\S]+?(?=[\\<!\[`*]|\b_| {2,}\n|$)/
+};
+
+inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g;
+
+inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/;
+inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/;
+inline.autolink = edit(inline.autolink)
+ .replace('scheme', inline._scheme)
+ .replace('email', inline._email)
+ .getRegex();
+
+inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/;
+
+inline.tag = edit(inline.tag)
+ .replace('comment', block._comment)
+ .replace('attribute', inline._attribute)
+ .getRegex();
+
+inline._label = /(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/;
+inline._href = /\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?)/;
+inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/;
+
+inline.link = edit(inline.link)
+ .replace('label', inline._label)
+ .replace('href', inline._href)
+ .replace('title', inline._title)
+ .getRegex();
+
+inline.reflink = edit(inline.reflink)
+ .replace('label', inline._label)
+ .getRegex();
+
+/**
+ * Normal Inline Grammar
+ */
+
+inline.normal = merge({}, inline);
+
+/**
+ * Pedantic Inline Grammar
+ */
+
+inline.pedantic = merge({}, inline.normal, {
+ strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
+ em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,
+ link: edit(/^!?\[(label)\]\((.*?)\)/)
+ .replace('label', inline._label)
+ .getRegex(),
+ reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/)
+ .replace('label', inline._label)
+ .getRegex()
+});
+
+/**
+ * GFM Inline Grammar
+ */
+
+inline.gfm = merge({}, inline.normal, {
+ escape: edit(inline.escape).replace('])', '~|])').getRegex(),
+ url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/)
+ .replace('email', inline._email)
+ .getRegex(),
+ _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,
+ del: /^~~(?=\S)([\s\S]*?\S)~~/,
+ text: edit(inline.text)
+ .replace(']|', '~]|')
+ .replace('|', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|')
+ .getRegex()
+});
+
+/**
+ * GFM + Line Breaks Inline Grammar
+ */
+
+inline.breaks = merge({}, inline.gfm, {
+ br: edit(inline.br).replace('{2,}', '*').getRegex(),
+ text: edit(inline.gfm.text).replace('{2,}', '*').getRegex()
+});
+
+/**
+ * Inline Lexer & Compiler
+ */
+
+function InlineLexer(links, options) {
+ this.options = options || marked.defaults;
+ this.links = links;
+ this.rules = inline.normal;
+ this.renderer = this.options.renderer || new Renderer();
+ this.renderer.options = this.options;
+
+ if (!this.links) {
+ throw new Error('Tokens array requires a `links` property.');
+ }
+
+ if (this.options.pedantic) {
+ this.rules = inline.pedantic;
+ } else if (this.options.gfm) {
+ if (this.options.breaks) {
+ this.rules = inline.breaks;
+ } else {
+ this.rules = inline.gfm;
+ }
+ }
+}
+
+/**
+ * Expose Inline Rules
+ */
+
+InlineLexer.rules = inline;
+
+/**
+ * Static Lexing/Compiling Method
+ */
+
+InlineLexer.output = function(src, links, options) {
+ var inline = new InlineLexer(links, options);
+ return inline.output(src);
+};
+
+/**
+ * Lexing/Compiling
+ */
+
+InlineLexer.prototype.output = function(src) {
+ var out = '',
+ link,
+ text,
+ href,
+ title,
+ cap;
+
+ while (src) {
+ // escape
+ if (cap = this.rules.escape.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += cap[1];
+ continue;
+ }
+
+ // autolink
+ if (cap = this.rules.autolink.exec(src)) {
+ src = src.substring(cap[0].length);
+ if (cap[2] === '@') {
+ text = escape(this.mangle(cap[1]));
+ href = 'mailto:' + text;
+ } else {
+ text = escape(cap[1]);
+ href = text;
+ }
+ out += this.renderer.link(href, null, text);
+ continue;
+ }
+
+ // url (gfm)
+ if (!this.inLink && (cap = this.rules.url.exec(src))) {
+ cap[0] = this.rules._backpedal.exec(cap[0])[0];
+ src = src.substring(cap[0].length);
+ if (cap[2] === '@') {
+ text = escape(cap[0]);
+ href = 'mailto:' + text;
+ } else {
+ text = escape(cap[0]);
+ if (cap[1] === 'www.') {
+ href = 'http://' + text;
+ } else {
+ href = text;
+ }
+ }
+ out += this.renderer.link(href, null, text);
+ continue;
+ }
+
+ // tag
+ if (cap = this.rules.tag.exec(src)) {
+ if (!this.inLink && /^<a /i.test(cap[0])) {
+ this.inLink = true;
+ } else if (this.inLink && /^<\/a>/i.test(cap[0])) {
+ this.inLink = false;
+ }
+ src = src.substring(cap[0].length);
+ out += this.options.sanitize
+ ? this.options.sanitizer
+ ? this.options.sanitizer(cap[0])
+ : escape(cap[0])
+ : cap[0]
+ continue;
+ }
+
+ // link
+ if (cap = this.rules.link.exec(src)) {
+ src = src.substring(cap[0].length);
+ this.inLink = true;
+ href = cap[2];
+ if (this.options.pedantic) {
+ link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
+
+ if (link) {
+ href = link[1];
+ title = link[3];
+ } else {
+ title = '';
+ }
+ } else {
+ title = cap[3] ? cap[3].slice(1, -1) : '';
+ }
+ href = href.trim().replace(/^<([\s\S]*)>$/, '$1');
+ out += this.outputLink(cap, {
+ href: InlineLexer.escapes(href),
+ title: InlineLexer.escapes(title)
+ });
+ this.inLink = false;
+ continue;
+ }
+
+ // reflink, nolink
+ if ((cap = this.rules.reflink.exec(src))
+ || (cap = this.rules.nolink.exec(src))) {
+ src = src.substring(cap[0].length);
+ link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
+ link = this.links[link.toLowerCase()];
+ if (!link || !link.href) {
+ out += cap[0].charAt(0);
+ src = cap[0].substring(1) + src;
+ continue;
+ }
+ this.inLink = true;
+ out += this.outputLink(cap, link);
+ this.inLink = false;
+ continue;
+ }
+
+ // strong
+ if (cap = this.rules.strong.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1]));
+ continue;
+ }
+
+ // em
+ if (cap = this.rules.em.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1]));
+ continue;
+ }
+
+ // code
+ if (cap = this.rules.code.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.codespan(escape(cap[2].trim(), true));
+ continue;
+ }
+
+ // br
+ if (cap = this.rules.br.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.br();
+ continue;
+ }
+
+ // del (gfm)
+ if (cap = this.rules.del.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.del(this.output(cap[1]));
+ continue;
+ }
+
+ // text
+ if (cap = this.rules.text.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.text(escape(this.smartypants(cap[0])));
+ continue;
+ }
+
+ if (src) {
+ throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
+ }
+ }
+
+ return out;
+};
+
+InlineLexer.escapes = function(text) {
+ return text ? text.replace(InlineLexer.rules._escapes, '$1') : text;
+}
+
+/**
+ * Compile Link
+ */
+
+InlineLexer.prototype.outputLink = function(cap, link) {
+ var href = link.href,
+ title = link.title ? escape(link.title) : null;
+
+ return cap[0].charAt(0) !== '!'
+ ? this.renderer.link(href, title, this.output(cap[1]))
+ : this.renderer.image(href, title, escape(cap[1]));
+};
+
+/**
+ * Smartypants Transformations
+ */
+
+InlineLexer.prototype.smartypants = function(text) {
+ if (!this.options.smartypants) return text;
+ return text
+ // em-dashes
+ .replace(/---/g, '\u2014')
+ // en-dashes
+ .replace(/--/g, '\u2013')
+ // opening singles
+ .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
+ // closing singles & apostrophes
+ .replace(/'/g, '\u2019')
+ // opening doubles
+ .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
+ // closing doubles
+ .replace(/"/g, '\u201d')
+ // ellipses
+ .replace(/\.{3}/g, '\u2026');
+};
+
+/**
+ * Mangle Links
+ */
+
+InlineLexer.prototype.mangle = function(text) {
+ if (!this.options.mangle) return text;
+ var out = '',
+ l = text.length,
+ i = 0,
+ ch;
+
+ for (; i < l; i++) {
+ ch = text.charCodeAt(i);
+ if (Math.random() > 0.5) {
+ ch = 'x' + ch.toString(16);
+ }
+ out += '&#' + ch + ';';
+ }
+
+ return out;
+};
+
+/**
+ * Renderer
+ */
+
+function Renderer(options) {
+ this.options = options || marked.defaults;
+}
+
+Renderer.prototype.code = function(code, lang, escaped) {
+ if (this.options.highlight) {
+ var out = this.options.highlight(code, lang);
+ if (out != null && out !== code) {
+ escaped = true;
+ code = out;
+ }
+ }
+
+ if (!lang) {
+ return '<pre><code>'
+ + (escaped ? code : escape(code, true))
+ + '</code></pre>';
+ }
+
+ return '<pre><code class="'
+ + this.options.langPrefix
+ + escape(lang, true)
+ + '">'
+ + (escaped ? code : escape(code, true))
+ + '</code></pre>\n';
+};
+
+Renderer.prototype.blockquote = function(quote) {
+ return '<blockquote>\n' + quote + '</blockquote>\n';
+};
+
+Renderer.prototype.html = function(html) {
+ return html;
+};
+
+Renderer.prototype.heading = function(text, level, raw) {
+ if (this.options.headerIds) {
+ return '<h'
+ + level
+ + ' id="'
+ + this.options.headerPrefix
+ + raw.toLowerCase().replace(/[^\w]+/g, '-')
+ + '">'
+ + text
+ + '</h'
+ + level
+ + '>\n';
+ }
+ // ignore IDs
+ return '<h' + level + '>' + text + '</h' + level + '>\n';
+};
+
+Renderer.prototype.hr = function() {
+ return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
+};
+
+Renderer.prototype.list = function(body, ordered, start) {
+ var type = ordered ? 'ol' : 'ul',
+ startatt = (ordered && start !== 1) ? (' start="' + start + '"') : '';
+ return '<' + type + startatt + '>\n' + body + '</' + type + '>\n';
+};
+
+Renderer.prototype.listitem = function(text) {
+ return '<li>' + text + '</li>\n';
+};
+
+Renderer.prototype.checkbox = function(checked) {
+ return '<input '
+ + (checked ? 'checked="" ' : '')
+ + 'disabled="" type="checkbox"'
+ + (this.options.xhtml ? ' /' : '')
+ + '> ';
+}
+
+Renderer.prototype.paragraph = function(text) {
+ return '<p>' + text + '</p>\n';
+};
+
+Renderer.prototype.table = function(header, body) {
+ if (body) body = '<tbody>' + body + '</tbody>';
+
+ return '<table>\n'
+ + '<thead>\n'
+ + header
+ + '</thead>\n'
+ + body
+ + '</table>\n';
+};
+
+Renderer.prototype.tablerow = function(content) {
+ return '<tr>\n' + content + '</tr>\n';
+};
+
+Renderer.prototype.tablecell = function(content, flags) {
+ var type = flags.header ? 'th' : 'td';
+ var tag = flags.align
+ ? '<' + type + ' align="' + flags.align + '">'
+ : '<' + type + '>';
+ return tag + content + '</' + type + '>\n';
+};
+
+// span level renderer
+Renderer.prototype.strong = function(text) {
+ return '<strong>' + text + '</strong>';
+};
+
+Renderer.prototype.em = function(text) {
+ return '<em>' + text + '</em>';
+};
+
+Renderer.prototype.codespan = function(text) {
+ return '<code>' + text + '</code>';
+};
+
+Renderer.prototype.br = function() {
+ return this.options.xhtml ? '<br/>' : '<br>';
+};
+
+Renderer.prototype.del = function(text) {
+ return '<del>' + text + '</del>';
+};
+
+Renderer.prototype.link = function(href, title, text) {
+ if (this.options.sanitize) {
+ try {
+ var prot = decodeURIComponent(unescape(href))
+ .replace(/[^\w:]/g, '')
+ .toLowerCase();
+ } catch (e) {
+ return text;
+ }
+ if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
+ return text;
+ }
+ }
+ if (this.options.baseUrl && !originIndependentUrl.test(href)) {
+ href = resolveUrl(this.options.baseUrl, href);
+ }
+ try {
+ href = encodeURI(href).replace(/%25/g, '%');
+ } catch (e) {
+ return text;
+ }
+ var out = '<a href="' + escape(href) + '"';
+ if (title) {
+ out += ' title="' + title + '"';
+ }
+ out += '>' + text + '</a>';
+ return out;
+};
+
+Renderer.prototype.image = function(href, title, text) {
+ if (this.options.baseUrl && !originIndependentUrl.test(href)) {
+ href = resolveUrl(this.options.baseUrl, href);
+ }
+ var out = '<img src="' + href + '" alt="' + text + '"';
+ if (title) {
+ out += ' title="' + title + '"';
+ }
+ out += this.options.xhtml ? '/>' : '>';
+ return out;
+};
+
+Renderer.prototype.text = function(text) {
+ return text;
+};
+
+/**
+ * TextRenderer
+ * returns only the textual part of the token
+ */
+
+function TextRenderer() {}
+
+// no need for block level renderers
+
+TextRenderer.prototype.strong =
+TextRenderer.prototype.em =
+TextRenderer.prototype.codespan =
+TextRenderer.prototype.del =
+TextRenderer.prototype.text = function (text) {
+ return text;
+}
+
+TextRenderer.prototype.link =
+TextRenderer.prototype.image = function(href, title, text) {
+ return '' + text;
+}
+
+TextRenderer.prototype.br = function() {
+ return '';
+}
+
+/**
+ * Parsing & Compiling
+ */
+
+function Parser(options) {
+ this.tokens = [];
+ this.token = null;
+ this.options = options || marked.defaults;
+ this.options.renderer = this.options.renderer || new Renderer();
+ this.renderer = this.options.renderer;
+ this.renderer.options = this.options;
+}
+
+/**
+ * Static Parse Method
+ */
+
+Parser.parse = function(src, options) {
+ var parser = new Parser(options);
+ return parser.parse(src);
+};
+
+/**
+ * Parse Loop
+ */
+
+Parser.prototype.parse = function(src) {
+ this.inline = new InlineLexer(src.links, this.options);
+ // use an InlineLexer with a TextRenderer to extract pure text
+ this.inlineText = new InlineLexer(
+ src.links,
+ merge({}, this.options, {renderer: new TextRenderer()})
+ );
+ this.tokens = src.reverse();
+
+ var out = '';
+ while (this.next()) {
+ out += this.tok();
+ }
+
+ return out;
+};
+
+/**
+ * Next Token
+ */
+
+Parser.prototype.next = function() {
+ return this.token = this.tokens.pop();
+};
+
+/**
+ * Preview Next Token
+ */
+
+Parser.prototype.peek = function() {
+ return this.tokens[this.tokens.length - 1] || 0;
+};
+
+/**
+ * Parse Text Tokens
+ */
+
+Parser.prototype.parseText = function() {
+ var body = this.token.text;
+
+ while (this.peek().type === 'text') {
+ body += '\n' + this.next().text;
+ }
+
+ return this.inline.output(body);
+};
+
+/**
+ * Parse Current Token
+ */
+
+Parser.prototype.tok = function() {
+ switch (this.token.type) {
+ case 'space': {
+ return '';
+ }
+ case 'hr': {
+ return this.renderer.hr();
+ }
+ case 'heading': {
+ return this.renderer.heading(
+ this.inline.output(this.token.text),
+ this.token.depth,
+ unescape(this.inlineText.output(this.token.text)));
+ }
+ case 'code': {
+ return this.renderer.code(this.token.text,
+ this.token.lang,
+ this.token.escaped);
+ }
+ case 'table': {
+ var header = '',
+ body = '',
+ i,
+ row,
+ cell,
+ j;
+
+ // header
+ cell = '';
+ for (i = 0; i < this.token.header.length; i++) {
+ cell += this.renderer.tablecell(
+ this.inline.output(this.token.header[i]),
+ { header: true, align: this.token.align[i] }
+ );
+ }
+ header += this.renderer.tablerow(cell);
+
+ for (i = 0; i < this.token.cells.length; i++) {
+ row = this.token.cells[i];
+
+ cell = '';
+ for (j = 0; j < row.length; j++) {
+ cell += this.renderer.tablecell(
+ this.inline.output(row[j]),
+ { header: false, align: this.token.align[j] }
+ );
+ }
+
+ body += this.renderer.tablerow(cell);
+ }
+ return this.renderer.table(header, body);
+ }
+ case 'blockquote_start': {
+ body = '';
+
+ while (this.next().type !== 'blockquote_end') {
+ body += this.tok();
+ }
+
+ return this.renderer.blockquote(body);
+ }
+ case 'list_start': {
+ body = '';
+ var ordered = this.token.ordered,
+ start = this.token.start;
+
+ while (this.next().type !== 'list_end') {
+ body += this.tok();
+ }
+
+ return this.renderer.list(body, ordered, start);
+ }
+ case 'list_item_start': {
+ body = '';
+
+ if (this.token.task) {
+ body += this.renderer.checkbox(this.token.checked);
+ }
+
+ while (this.next().type !== 'list_item_end') {
+ body += this.token.type === 'text'
+ ? this.parseText()
+ : this.tok();
+ }
+
+ return this.renderer.listitem(body);
+ }
+ case 'loose_item_start': {
+ body = '';
+
+ while (this.next().type !== 'list_item_end') {
+ body += this.tok();
+ }
+
+ return this.renderer.listitem(body);
+ }
+ case 'html': {
+ // TODO parse inline content if parameter markdown=1
+ return this.renderer.html(this.token.text);
+ }
+ case 'paragraph': {
+ return this.renderer.paragraph(this.inline.output(this.token.text));
+ }
+ case 'text': {
+ return this.renderer.paragraph(this.parseText());
+ }
+ }
+};
+
+/**
+ * Helpers
+ */
+
+function escape(html, encode) {
+ return html
+ .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#39;');
+}
+
+function unescape(html) {
+ // explicitly match decimal, hex, and named HTML entities
+ return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) {
+ n = n.toLowerCase();
+ if (n === 'colon') return ':';
+ if (n.charAt(0) === '#') {
+ return n.charAt(1) === 'x'
+ ? String.fromCharCode(parseInt(n.substring(2), 16))
+ : String.fromCharCode(+n.substring(1));
+ }
+ return '';
+ });
+}
+
+function edit(regex, opt) {
+ regex = regex.source || regex;
+ opt = opt || '';
+ return {
+ replace: function(name, val) {
+ val = val.source || val;
+ val = val.replace(/(^|[^\[])\^/g, '$1');
+ regex = regex.replace(name, val);
+ return this;
+ },
+ getRegex: function() {
+ return new RegExp(regex, opt);
+ }
+ };
+}
+
+function resolveUrl(base, href) {
+ if (!baseUrls[' ' + base]) {
+ // we can ignore everything in base after the last slash of its path component,
+ // but we might need to add _that_
+ // https://tools.ietf.org/html/rfc3986#section-3
+ if (/^[^:]+:\/*[^/]*$/.test(base)) {
+ baseUrls[' ' + base] = base + '/';
+ } else {
+ baseUrls[' ' + base] = base.replace(/[^/]*$/, '');
+ }
+ }
+ base = baseUrls[' ' + base];
+
+ if (href.slice(0, 2) === '//') {
+ return base.replace(/:[\s\S]*/, ':') + href;
+ } else if (href.charAt(0) === '/') {
+ return base.replace(/(:\/*[^/]*)[\s\S]*/, '$1') + href;
+ } else {
+ return base + href;
+ }
+}
+var baseUrls = {};
+var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
+
+function noop() {}
+noop.exec = noop;
+
+function merge(obj) {
+ var i = 1,
+ target,
+ key;
+
+ for (; i < arguments.length; i++) {
+ target = arguments[i];
+ for (key in target) {
+ if (Object.prototype.hasOwnProperty.call(target, key)) {
+ obj[key] = target[key];
+ }
+ }
+ }
+
+ return obj;
+}
+
+function splitCells(tableRow, count) {
+ var cells = tableRow.replace(/([^\\])\|/g, '$1 |').split(/ +\| */),
+ i = 0;
+
+ if (cells.length > count) {
+ cells.splice(count);
+ } else {
+ while (cells.length < count) cells.push('');
+ }
+
+ for (; i < cells.length; i++) {
+ cells[i] = cells[i].replace(/\\\|/g, '|');
+ }
+ return cells;
+}
+
+/**
+ * Marked
+ */
+
+function marked(src, opt, callback) {
+ // throw error in case of non string input
+ if (typeof src === 'undefined' || src === null) {
+ throw new Error('marked(): input parameter is undefined or null');
+ }
+ if (typeof src !== 'string') {
+ throw new Error('marked(): input parameter is of type '
+ + Object.prototype.toString.call(src) + ', string expected');
+ }
+
+ if (callback || typeof opt === 'function') {
+ if (!callback) {
+ callback = opt;
+ opt = null;
+ }
+
+ opt = merge({}, marked.defaults, opt || {});
+
+ var highlight = opt.highlight,
+ tokens,
+ pending,
+ i = 0;
+
+ try {
+ tokens = Lexer.lex(src, opt)
+ } catch (e) {
+ return callback(e);
+ }
+
+ pending = tokens.length;
+
+ var done = function(err) {
+ if (err) {
+ opt.highlight = highlight;
+ return callback(err);
+ }
+
+ var out;
+
+ try {
+ out = Parser.parse(tokens, opt);
+ } catch (e) {
+ err = e;
+ }
+
+ opt.highlight = highlight;
+
+ return err
+ ? callback(err)
+ : callback(null, out);
+ };
+
+ if (!highlight || highlight.length < 3) {
+ return done();
+ }
+
+ delete opt.highlight;
+
+ if (!pending) return done();
+
+ for (; i < tokens.length; i++) {
+ (function(token) {
+ if (token.type !== 'code') {
+ return --pending || done();
+ }
+ return highlight(token.text, token.lang, function(err, code) {
+ if (err) return done(err);
+ if (code == null || code === token.text) {
+ return --pending || done();
+ }
+ token.text = code;
+ token.escaped = true;
+ --pending || done();
+ });
+ })(tokens[i]);
+ }
+
+ return;
+ }
+ try {
+ if (opt) opt = merge({}, marked.defaults, opt);
+ return Parser.parse(Lexer.lex(src, opt), opt);
+ } catch (e) {
+ e.message += '\nPlease report this to https://github.com/markedjs/marked.';
+ if ((opt || marked.defaults).silent) {
+ return '<p>An error occurred:</p><pre>'
+ + escape(e.message + '', true)
+ + '</pre>';
+ }
+ throw e;
+ }
+}
+
+/**
+ * Options
+ */
+
+marked.options =
+marked.setOptions = function(opt) {
+ merge(marked.defaults, opt);
+ return marked;
+};
+
+marked.getDefaults = function () {
+ return {
+ baseUrl: null,
+ breaks: false,
+ gfm: true,
+ headerIds: true,
+ headerPrefix: '',
+ highlight: null,
+ langPrefix: 'language-',
+ mangle: true,
+ pedantic: false,
+ renderer: new Renderer(),
+ sanitize: false,
+ sanitizer: null,
+ silent: false,
+ smartLists: false,
+ smartypants: false,
+ tables: true,
+ xhtml: false
+ };
+}
+
+marked.defaults = marked.getDefaults();
+
+/**
+ * Expose
+ */
+
+marked.Parser = Parser;
+marked.parser = Parser.parse;
+
+marked.Renderer = Renderer;
+marked.TextRenderer = TextRenderer;
+
+marked.Lexer = Lexer;
+marked.lexer = Lexer.lex;
+
+marked.InlineLexer = InlineLexer;
+marked.inlineLexer = InlineLexer.output;
+
+marked.parse = marked;
+
+if (typeof module !== 'undefined' && typeof exports === 'object') {
+ module.exports = marked;
+} else if (typeof define === 'function' && define.amd) {
+ define(function() { return marked; });
+} else {
+ root.marked = marked;
+}
+})(this || (typeof window !== 'undefined' ? window : global));
diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json b/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json
new file mode 100644
index 000000000..de5458eff
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json
@@ -0,0 +1,35 @@
+[
+ {
+ "Id": "markdowneditor-marked",
+ "Name": "Marked (WebEngine Markdown Editor example)",
+ "QDocModule": "qtwebengine",
+ "QtUsage": "Marked is used in the WebEngine MarkDown Editor example",
+ "QtParts": [ "examples" ],
+ "Files": "marked.js",
+ "Description": "A full-featured markdown parser and compiler, written in JavaScript. Built for speed.",
+ "Homepage": "https://github.com/chjj/marked",
+ "Version": "0.4.0",
+ "DownloadLocation": "https://github.com/markedjs/marked/blob/0.4.0/lib/marked.js",
+ "Copyright": "Copyright (c) 2011-2018, Christopher Jeffrey",
+ "License": "MIT License",
+ "LicenseId": "MIT",
+ "LicenseFile": "MARKED-LICENSE.txt"
+ },
+ {
+ "Id": "markdowneditor-markdowncss",
+ "Name": "Markdown.css (WebEngine Markdown Editor example)",
+ "QDocModule": "qtwebengine",
+ "QtUsage": "markdown.css is used in the WebEngine MarkDown Editor example",
+ "QtParts": [ "examples" ],
+ "Files": "markdown.css",
+ "Description": "Markdown.css is better default styling for your Markdown files.",
+ "Homepage": "https://kevinburke.bitbucket.io/markdowncss/",
+ "Version": "188530e4b5d020d7e237fc6b26be13ebf4a8def3",
+ "DownloadLocation": "https://bitbucket.org/kevinburke/markdowncss/src/188530e4b5d020d7e237fc6b26be13ebf4a8def3/markdown.css",
+ "Copyright": "Copyright 2011 Kevin Burke
+ Copyright Twitter Inc.",
+ "License": "Apache License 2.0",
+ "LicenseId": "Apache-2.0",
+ "LicenseFile": "MARKDOWN-LICENSE.txt"
+ }
+]
diff --git a/examples/webenginewidgets/markdowneditor/resources/default.md b/examples/webenginewidgets/markdowneditor/resources/default.md
new file mode 100644
index 000000000..af835fa4d
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/default.md
@@ -0,0 +1,12 @@
+## WebEngine Markdown Editor Example
+
+This example uses [QWebEngineView](http://doc.qt.io/qt-5/qwebengineview.html)
+to preview text written using the [Markdown](https://en.wikipedia.org/wiki/Markdown)
+syntax.
+
+### Acknowledgments
+
+The conversion from Markdown to HTML is done with the help of the
+[marked JavaScript library](https://github.com/chjj/marked) by _Christopher Jeffrey_.
+The [style sheet](https://kevinburke.bitbucket.io/markdowncss/)
+was created by _Kevin Burke_.
diff --git a/examples/webenginewidgets/markdowneditor/resources/index.html b/examples/webenginewidgets/markdowneditor/resources/index.html
new file mode 100644
index 000000000..289a2110b
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/index.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang="en">
+<meta charset="utf-8">
+<head>
+ <link rel="stylesheet" type="text/css" href="3rdparty/markdown.css">
+ <script src="3rdparty/marked.js"></script>
+ <script src="qrc:/qtwebchannel/qwebchannel.js"></script>
+</head>
+<body>
+ <div id="placeholder"></div>
+ <script>
+ 'use strict';
+
+ var placeholder = document.getElementById('placeholder');
+
+ var updateText = function(text) {
+ placeholder.innerHTML = marked(text);
+ }
+
+ new QWebChannel(qt.webChannelTransport,
+ function(channel) {
+ var content = channel.objects.content;
+ updateText(content.text);
+ content.textChanged.connect(updateText);
+ }
+ );
+ </script>
+</body>
+</html>
+
+
+
diff --git a/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc b/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc
new file mode 100644
index 000000000..bc738f1cf
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc
@@ -0,0 +1,8 @@
+<RCC>
+ <qresource prefix="/">
+ <file>default.md</file>
+ <file>index.html</file>
+ <file>3rdparty/markdown.css</file>
+ <file>3rdparty/marked.js</file>
+ </qresource>
+</RCC>
diff --git a/examples/webenginewidgets/markdowneditor/ui_mainwindow.py b/examples/webenginewidgets/markdowneditor/ui_mainwindow.py
new file mode 100644
index 000000000..305108324
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/ui_mainwindow.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'mainwindow.ui'
+##
+## Created by: Qt User Interface Compiler version 6.2.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
+ QCursor, QFont, QFontDatabase, QGradient,
+ QIcon, QImage, QKeySequence, QLinearGradient,
+ QPainter, QPalette, QPixmap, QRadialGradient,
+ QTransform)
+from PySide6.QtWebEngineWidgets import QWebEngineView
+from PySide6.QtWidgets import (QApplication, QHBoxLayout, QMainWindow, QMenu,
+ QMenuBar, QPlainTextEdit, QSizePolicy, QSplitter,
+ QStatusBar, QWidget)
+
+class Ui_MainWindow(object):
+ def setupUi(self, MainWindow):
+ if not MainWindow.objectName():
+ MainWindow.setObjectName(u"MainWindow")
+ MainWindow.resize(800, 600)
+ self.actionOpen = QAction(MainWindow)
+ self.actionOpen.setObjectName(u"actionOpen")
+ self.actionSave = QAction(MainWindow)
+ self.actionSave.setObjectName(u"actionSave")
+ self.actionExit = QAction(MainWindow)
+ self.actionExit.setObjectName(u"actionExit")
+ self.actionSaveAs = QAction(MainWindow)
+ self.actionSaveAs.setObjectName(u"actionSaveAs")
+ self.actionNew = QAction(MainWindow)
+ self.actionNew.setObjectName(u"actionNew")
+ self.centralwidget = QWidget(MainWindow)
+ self.centralwidget.setObjectName(u"centralwidget")
+ self.horizontalLayout = QHBoxLayout(self.centralwidget)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.splitter = QSplitter(self.centralwidget)
+ self.splitter.setObjectName(u"splitter")
+ self.splitter.setOrientation(Qt.Horizontal)
+ self.editor = QPlainTextEdit(self.splitter)
+ self.editor.setObjectName(u"editor")
+ self.splitter.addWidget(self.editor)
+ self.preview = QWebEngineView(self.splitter)
+ self.preview.setObjectName(u"preview")
+ self.splitter.addWidget(self.preview)
+
+ self.horizontalLayout.addWidget(self.splitter)
+
+ MainWindow.setCentralWidget(self.centralwidget)
+ self.menubar = QMenuBar(MainWindow)
+ self.menubar.setObjectName(u"menubar")
+ self.menubar.setGeometry(QRect(0, 0, 800, 26))
+ self.menu_File = QMenu(self.menubar)
+ self.menu_File.setObjectName(u"menu_File")
+ MainWindow.setMenuBar(self.menubar)
+ self.statusbar = QStatusBar(MainWindow)
+ self.statusbar.setObjectName(u"statusbar")
+ MainWindow.setStatusBar(self.statusbar)
+
+ self.menubar.addAction(self.menu_File.menuAction())
+ self.menu_File.addAction(self.actionNew)
+ self.menu_File.addAction(self.actionOpen)
+ self.menu_File.addAction(self.actionSave)
+ self.menu_File.addAction(self.actionSaveAs)
+ self.menu_File.addSeparator()
+ self.menu_File.addAction(self.actionExit)
+
+ self.retranslateUi(MainWindow)
+
+ QMetaObject.connectSlotsByName(MainWindow)
+ # setupUi
+
+ def retranslateUi(self, MainWindow):
+ MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MarkDown Editor", None))
+ self.actionOpen.setText(QCoreApplication.translate("MainWindow", u"&Open...", None))
+#if QT_CONFIG(tooltip)
+ self.actionOpen.setToolTip(QCoreApplication.translate("MainWindow", u"Open document", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionOpen.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+O", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionSave.setText(QCoreApplication.translate("MainWindow", u"&Save", None))
+#if QT_CONFIG(tooltip)
+ self.actionSave.setToolTip(QCoreApplication.translate("MainWindow", u"Save current document", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionSave.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+S", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionExit.setText(QCoreApplication.translate("MainWindow", u"E&xit", None))
+#if QT_CONFIG(tooltip)
+ self.actionExit.setToolTip(QCoreApplication.translate("MainWindow", u"Exit editor", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionExit.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+Q", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionSaveAs.setText(QCoreApplication.translate("MainWindow", u"Save &As...", None))
+#if QT_CONFIG(tooltip)
+ self.actionSaveAs.setToolTip(QCoreApplication.translate("MainWindow", u"Save document under different name", None))
+#endif // QT_CONFIG(tooltip)
+ self.actionNew.setText(QCoreApplication.translate("MainWindow", u"&New", None))
+#if QT_CONFIG(tooltip)
+ self.actionNew.setToolTip(QCoreApplication.translate("MainWindow", u"Create new document", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionNew.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+N", None))
+#endif // QT_CONFIG(shortcut)
+ self.menu_File.setTitle(QCoreApplication.translate("MainWindow", u"&File", None))
+ # retranslateUi
+
diff --git a/examples/webenginewidgets/simplebrowser/simplebrowser.py b/examples/webenginewidgets/simplebrowser/simplebrowser.py
index 021d5311e..e3f45356b 100644
--- a/examples/webenginewidgets/simplebrowser/simplebrowser.py
+++ b/examples/webenginewidgets/simplebrowser/simplebrowser.py
@@ -98,7 +98,7 @@ class MainWindow(QMainWindow):
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWin = MainWindow()
- availableGeometry = app.desktop().availableGeometry(mainWin)
+ availableGeometry = mainWin.screen().availableGeometry()
mainWin.resize(availableGeometry.width() * 2 / 3, availableGeometry.height() * 2 / 3)
mainWin.show()
sys.exit(app.exec())
diff --git a/examples/widgets/dialogs/tabdialog/doc/tabdialog.png b/examples/widgets/dialogs/tabdialog/doc/tabdialog.png
new file mode 100644
index 000000000..a92af5f9c
--- /dev/null
+++ b/examples/widgets/dialogs/tabdialog/doc/tabdialog.png
Binary files differ
diff --git a/examples/widgets/dialogs/tabdialog/doc/tabdialog.rst b/examples/widgets/dialogs/tabdialog/doc/tabdialog.rst
new file mode 100644
index 000000000..162316f54
--- /dev/null
+++ b/examples/widgets/dialogs/tabdialog/doc/tabdialog.rst
@@ -0,0 +1,13 @@
+Tab Dialog Example
+===================
+
+Shows how to construct a tab dialog using the QTabWidget class.
+
+The Tab Dialog example consists of a single TabDialog class
+that provides three tabs, each containing information about
+a particular file, and two standard push buttons that are
+used to accept or reject the contents of the dialog.
+
+.. image:: tabdialog.png
+ :width: 753
+ :alt: tabdialog screenshot
diff --git a/examples/widgets/dialogs/tabdialog/tabdialog.py b/examples/widgets/dialogs/tabdialog/tabdialog.py
new file mode 100644
index 000000000..986c5af21
--- /dev/null
+++ b/examples/widgets/dialogs/tabdialog/tabdialog.py
@@ -0,0 +1,215 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+"""PySide6 port of the widgets/dialogs/tabdialog example from Qt v6.x"""
+
+import sys
+
+from PySide6.QtCore import QFileInfo
+from PySide6.QtWidgets import (
+ QWidget,
+ QVBoxLayout,
+ QLabel,
+ QCheckBox,
+ QApplication,
+ QDialog,
+ QTabWidget,
+ QLineEdit,
+ QDialogButtonBox,
+ QFrame,
+ QListWidget,
+ QGroupBox,
+)
+
+
+class TabDialog(QDialog):
+ def __init__(self, file_name: str, parent: QWidget = None):
+ super().__init__(parent)
+
+ file_info = QFileInfo(file_name)
+
+ tab_widget = QTabWidget()
+ tab_widget.addTab(GeneralTab(file_info, self), "General")
+ tab_widget.addTab(PermissionsTab(file_info, self), "Permissions")
+ tab_widget.addTab(ApplicationsTab(file_info, self), "Applications")
+
+ button_box = QDialogButtonBox(
+ QDialogButtonBox.Ok | QDialogButtonBox.Cancel
+ )
+
+ button_box.accepted.connect(self.accept)
+ button_box.rejected.connect(self.reject)
+
+ main_layout = QVBoxLayout()
+ main_layout.addWidget(tab_widget)
+ main_layout.addWidget(button_box)
+ self.setLayout(main_layout)
+ self.setWindowTitle("Tab Dialog")
+
+
+class GeneralTab(QWidget):
+ def __init__(self, file_info: QFileInfo, parent: QWidget):
+ super().__init__(parent)
+
+ file_name_label = QLabel("File Name:")
+ file_name_edit = QLineEdit(file_info.fileName())
+
+ path_label = QLabel("Path:")
+ path_value_label = QLabel(file_info.absoluteFilePath())
+ path_value_label.setFrameStyle(QFrame.Panel | QFrame.Sunken)
+
+ size_label = QLabel("Size:")
+ size = file_info.size() / 1024
+ size_value_label = QLabel(f"{size} K")
+ size_value_label.setFrameStyle(QFrame.Panel | QFrame.Sunken)
+
+ last_read_label = QLabel("Last Read:")
+ last_read_value_label = QLabel(file_info.lastRead().toString())
+ last_read_value_label.setFrameStyle(QFrame.Panel | QFrame.Sunken)
+
+ last_mod_label = QLabel("Last Modified:")
+ last_mod_value_label = QLabel(file_info.lastModified().toString())
+ last_mod_value_label.setFrameStyle(QFrame.Panel | QFrame.Sunken)
+
+ main_layout = QVBoxLayout()
+ main_layout.addWidget(file_name_label)
+ main_layout.addWidget(file_name_edit)
+ main_layout.addWidget(path_label)
+ main_layout.addWidget(path_value_label)
+ main_layout.addWidget(size_label)
+ main_layout.addWidget(size_value_label)
+ main_layout.addWidget(last_read_label)
+ main_layout.addWidget(last_read_value_label)
+ main_layout.addWidget(last_mod_label)
+ main_layout.addWidget(last_mod_value_label)
+ main_layout.addStretch(1)
+ self.setLayout(main_layout)
+
+
+class PermissionsTab(QWidget):
+ def __init__(self, file_info: QFileInfo, parent: QWidget):
+ super().__init__(parent)
+
+ permissions_group = QGroupBox("Permissions")
+
+ readable = QCheckBox("Readable")
+ if file_info.isReadable():
+ readable.setChecked(True)
+
+ writable = QCheckBox("Writable")
+ if file_info.isWritable():
+ writable.setChecked(True)
+
+ executable = QCheckBox("Executable")
+ if file_info.isExecutable():
+ executable.setChecked(True)
+
+ owner_group = QGroupBox("Ownership")
+
+ owner_label = QLabel("Owner")
+ owner_value_label = QLabel(file_info.owner())
+ owner_value_label.setFrameStyle(QFrame.Panel | QFrame.Sunken)
+
+ group_label = QLabel("Group")
+ group_value_label = QLabel(file_info.group())
+ group_value_label.setFrameStyle(QFrame.Panel | QFrame.Sunken)
+
+ permissions_layout = QVBoxLayout()
+ permissions_layout.addWidget(readable)
+ permissions_layout.addWidget(writable)
+ permissions_layout.addWidget(executable)
+ permissions_group.setLayout(permissions_layout)
+
+ owner_layout = QVBoxLayout()
+ owner_layout.addWidget(owner_label)
+ owner_layout.addWidget(owner_value_label)
+ owner_layout.addWidget(group_label)
+ owner_layout.addWidget(group_value_label)
+ owner_group.setLayout(owner_layout)
+
+ main_layout = QVBoxLayout()
+ main_layout.addWidget(permissions_group)
+ main_layout.addWidget(owner_group)
+ main_layout.addStretch(1)
+ self.setLayout(main_layout)
+
+
+class ApplicationsTab(QWidget):
+ def __init__(self, file_info: QFileInfo, parent: QWidget):
+ super().__init__(parent)
+
+ top_label = QLabel("Open with:")
+
+ applications_list_box = QListWidget()
+ applications = []
+
+ for i in range(1, 31):
+ applications.append(f"Application {i}")
+ applications_list_box.insertItems(0, applications)
+
+ if not file_info.suffix():
+ always_check_box = QCheckBox(
+ "Always use this application to open this type of file"
+ )
+ else:
+ always_check_box = QCheckBox(
+ f"Always use this application to open files "
+ f"with the extension {file_info.suffix()}"
+ )
+
+ layout = QVBoxLayout()
+ layout.addWidget(top_label)
+ layout.addWidget(applications_list_box)
+ layout.addWidget(always_check_box)
+ self.setLayout(layout)
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+
+ if len(sys.argv) >= 2:
+ file_name = sys.argv[1]
+ else:
+ file_name = "."
+
+ tab_dialog = TabDialog(file_name)
+ tab_dialog.show()
+
+ sys.exit(app.exec())
diff --git a/examples/widgets/dialogs/tabdialog/tabdialog.pyproject b/examples/widgets/dialogs/tabdialog/tabdialog.pyproject
new file mode 100644
index 000000000..f121cd804
--- /dev/null
+++ b/examples/widgets/dialogs/tabdialog/tabdialog.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["tabdialog.py"]
+}
diff --git a/examples/widgets/layouts/borderlayout/borderlayout.py b/examples/widgets/layouts/borderlayout/borderlayout.py
new file mode 100644
index 000000000..74d5524e4
--- /dev/null
+++ b/examples/widgets/layouts/borderlayout/borderlayout.py
@@ -0,0 +1,285 @@
+############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+"""PySide6 port of the widgets/layouts/borderlayout example from Qt v5.x"""
+
+from dataclasses import dataclass
+from enum import IntEnum, auto
+
+from PySide6.QtCore import QRect, QSize, Qt
+from PySide6.QtWidgets import (
+ QApplication,
+ QFrame,
+ QLabel,
+ QLayout,
+ QLayoutItem,
+ QTextBrowser,
+ QWidget,
+ QWidgetItem,
+)
+import sys
+
+
+class Position(IntEnum):
+ West = auto()
+ North = auto()
+ South = auto()
+ East = auto()
+ Center = auto()
+
+
+class SizeType(IntEnum):
+ MinimumSize = auto()
+ SizeHint = auto()
+
+
+@dataclass
+class ItemWrapper:
+ item: QLayoutItem
+ position: Position
+
+
+class BorderLayout(QLayout):
+ def __init__(self, parent=None, spacing: int = -1):
+ super().__init__(parent)
+
+ self._list: list[ItemWrapper] = []
+
+ self.setSpacing(spacing)
+
+ if parent is not None:
+ self.setParent(parent)
+
+ def __del__(self):
+ item = self.takeAt(0)
+ while item:
+ item = self.takeAt(0)
+
+ def addItem(self, item: QLayoutItem):
+ self.add(item, Position.West)
+
+ def addWidget(self, widget: QWidget, position: Position):
+ self.add(QWidgetItem(widget), position)
+
+ def expandingDirections(self) -> Qt.Orientations:
+ return Qt.Horizontal | Qt.Vertical
+
+ def hasHeightForWidth(self) -> bool:
+ return False
+
+ def count(self) -> int:
+ return len(self._list)
+
+ def itemAt(self, index: int) -> QLayoutItem:
+ if index < len(self._list):
+ wrapper: ItemWrapper = self._list[index]
+ return wrapper.item
+ return None
+
+ def minimumSize(self) -> QSize:
+ return self.calculate_size(SizeType.MinimumSize)
+
+ def setGeometry(self, rect: QRect):
+ center: ItemWrapper = None
+ east_width = 0
+ west_width = 0
+ north_height = 0
+ south_height = 0
+
+ super().setGeometry(rect)
+
+ for wrapper in self._list:
+ item: QLayoutItem = wrapper.item
+ position: Position = wrapper.position
+
+ if position == Position.North:
+ item.setGeometry(
+ QRect(
+ rect.x(), north_height, rect.width(), item.sizeHint().height()
+ )
+ )
+
+ north_height += item.geometry().height() + self.spacing()
+
+ elif position == Position.South:
+ item.setGeometry(
+ QRect(
+ item.geometry().x(),
+ item.geometry().y(),
+ rect.width(),
+ item.sizeHint().height(),
+ )
+ )
+
+ south_height += item.geometry().height() + self.spacing()
+
+ item.setGeometry(
+ QRect(
+ rect.x(),
+ rect.y() + rect.height() - south_height + self.spacing(),
+ item.geometry().width(),
+ item.geometry().height(),
+ )
+ )
+ elif position == Position.Center:
+ center = wrapper
+
+ center_height = rect.height() - north_height - south_height
+
+ for wrapper in self._list:
+ item: QLayoutItem = wrapper.item
+ position: Position = wrapper.position
+
+ if position == Position.West:
+ item.setGeometry(
+ QRect(
+ rect.x() + west_width,
+ north_height,
+ item.sizeHint().width(),
+ center_height,
+ )
+ )
+
+ west_width += item.geometry().width() + self.spacing()
+
+ elif position == Position.East:
+ item.setGeometry(
+ QRect(
+ item.geometry().x(),
+ item.geometry().y(),
+ item.sizeHint().width(),
+ center_height,
+ )
+ )
+
+ east_width += item.geometry().width() + self.spacing()
+
+ item.setGeometry(
+ QRect(
+ rect.x() + rect.width() - east_width + self.spacing(),
+ north_height,
+ item.geometry().width(),
+ item.geometry().height(),
+ )
+ )
+
+ if center:
+ center.item.setGeometry(
+ QRect(
+ west_width,
+ north_height,
+ rect.width() - east_width - west_width,
+ center_height,
+ )
+ )
+
+ def sizeHint(self) -> QSize:
+ return self.calculate_size(SizeType.SizeHint)
+
+ def takeAt(self, index: int):
+ if 0 <= index < len(self._list):
+ layout_struct: ItemWrapper = self._list.pop(index)
+ return layout_struct.item
+ return None
+
+ def add(self, item: QLayoutItem, position: Position):
+ self._list.append(ItemWrapper(item, position))
+
+ def calculate_size(self, size_type: SizeType):
+ total_size = QSize()
+
+ for wrapper in self._list:
+ position = wrapper.position
+
+ item_size: QSize
+ if size_type == SizeType.MinimumSize:
+ item_size = wrapper.item.minimumSize()
+ else:
+ item_size = wrapper.item.sizeHint()
+
+ if position in (Position.North, Position.South, Position.Center):
+ total_size.setHeight(total_size.height() + item_size.height())
+
+ if position in (Position.West, Position.East, Position.Center):
+ total_size.setWidth(total_size.width() + item_size.width())
+
+ return total_size
+
+
+class Window(QWidget):
+ def __init__(self):
+ super().__init__()
+ self.central_widget = QTextBrowser()
+ self.central_widget.setPlainText("Central widget")
+
+ border_layout = BorderLayout()
+ border_layout.addWidget(self.central_widget, Position.Center)
+
+ label_north = self.create_label("North")
+ border_layout.addWidget(label_north, Position.North)
+
+ label_west = self.create_label("West")
+ border_layout.addWidget(label_west, Position.West)
+
+ label_east1 = self.create_label("East 1")
+ border_layout.addWidget(label_east1, Position.East)
+
+ label_east2 = self.create_label("East 2")
+ border_layout.addWidget(label_east2, Position.East)
+
+ label_south = self.create_label("South")
+ border_layout.addWidget(label_south, Position.South)
+
+ self.setLayout(border_layout)
+
+ self.setWindowTitle("Border Layout")
+
+ @staticmethod
+ def create_label(text: str):
+ label = QLabel(text)
+ label.setFrameStyle(QFrame.Box | QFrame.Raised)
+ return label
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ window = Window()
+ window.show()
+ sys.exit(app.exec())
diff --git a/examples/widgets/layouts/borderlayout/borderlayout.pyproject b/examples/widgets/layouts/borderlayout/borderlayout.pyproject
new file mode 100644
index 000000000..fc0280348
--- /dev/null
+++ b/examples/widgets/layouts/borderlayout/borderlayout.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["borderlayout.py"]
+}
diff --git a/examples/widgets/layouts/borderlayout/doc/borderlayout.png b/examples/widgets/layouts/borderlayout/doc/borderlayout.png
new file mode 100644
index 000000000..8599b9d1c
--- /dev/null
+++ b/examples/widgets/layouts/borderlayout/doc/borderlayout.png
Binary files differ
diff --git a/examples/widgets/layouts/borderlayout/doc/borderlayout.rst b/examples/widgets/layouts/borderlayout/doc/borderlayout.rst
new file mode 100644
index 000000000..652f84daa
--- /dev/null
+++ b/examples/widgets/layouts/borderlayout/doc/borderlayout.rst
@@ -0,0 +1,10 @@
+Border Layout Example
+=======================
+
+Shows how to arrange child widgets along a border.
+
+Border Layout implements a layout that arranges child widgets to surround the main area.
+
+.. image:: borderlayout.png
+ :width: 473
+ :alt: border layout Screenshot
diff --git a/examples/widgets/layouts/flowlayout/flowlayout.py b/examples/widgets/layouts/flowlayout/flowlayout.py
index 431515687..f58dd7e96 100644
--- a/examples/widgets/layouts/flowlayout/flowlayout.py
+++ b/examples/widgets/layouts/flowlayout/flowlayout.py
@@ -1,4 +1,3 @@
-
############################################################################
##
## Copyright (C) 2013 Riverbank Computing Limited.
@@ -40,12 +39,11 @@
##
#############################################################################
-"""PySide6 port of the widgets/layouts/flowlayout example from Qt v5.x"""
+"""PySide6 port of the widgets/layouts/flowlayout example from Qt v6.x"""
import sys
from PySide6.QtCore import Qt, QMargins, QPoint, QRect, QSize
-from PySide6.QtWidgets import (QApplication, QLayout, QPushButton,
- QSizePolicy, QWidget)
+from PySide6.QtWidgets import QApplication, QLayout, QPushButton, QSizePolicy, QWidget
class Window(QWidget):
@@ -72,9 +70,9 @@ class FlowLayout(QLayout):
self._item_list = []
def __del__(self):
- item = self.take_at(0)
+ item = self.takeAt(0)
while item:
- item = self.take_at(0)
+ item = self.takeAt(0)
def addItem(self, item):
self._item_list.append(item)
@@ -83,19 +81,19 @@ class FlowLayout(QLayout):
return len(self._item_list)
def itemAt(self, index):
- if index >= 0 and index < len(self._item_list):
+ if 0 <= index < len(self._item_list):
return self._item_list[index]
return None
def takeAt(self, index):
- if index >= 0 and index < len(self._item_list):
+ if 0 <= index < len(self._item_list):
return self._item_list.pop(index)
return None
def expandingDirections(self):
- return Qt.Orientations(Qt.Orientation(0))
+ return Qt.Orientation(0)
def hasHeightForWidth(self):
return True
@@ -117,8 +115,7 @@ class FlowLayout(QLayout):
for item in self._item_list:
size = size.expandedTo(item.minimumSize())
- size += QSize(2 * self.contentsMargins().top(),
- 2 * self.contentsMargins().top())
+ size += QSize(2 * self.contentsMargins().top(), 2 * self.contentsMargins().top())
return size
def _do_layout(self, rect, test_only):
@@ -129,12 +126,12 @@ class FlowLayout(QLayout):
for item in self._item_list:
style = item.widget().style()
- layout_spacing_x = style.layoutSpacing(QSizePolicy.PushButton,
- QSizePolicy.PushButton,
- Qt.Horizontal)
- layout_spacing_y = style.layoutSpacing(QSizePolicy.PushButton,
- QSizePolicy.PushButton,
- Qt.Vertical)
+ layout_spacing_x = style.layoutSpacing(
+ QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal
+ )
+ layout_spacing_y = style.layoutSpacing(
+ QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical
+ )
space_x = spacing + layout_spacing_x
space_y = spacing + layout_spacing_y
next_x = x + item.sizeHint().width() + space_x
@@ -153,7 +150,7 @@ class FlowLayout(QLayout):
return y + line_height - rect.y()
-if __name__ == '__main__':
+if __name__ == "__main__":
app = QApplication(sys.argv)
main_win = Window()
main_win.show()
diff --git a/examples/widgets/painting/plot/plot.py b/examples/widgets/painting/plot/plot.py
new file mode 100644
index 000000000..156a6408d
--- /dev/null
+++ b/examples/widgets/painting/plot/plot.py
@@ -0,0 +1,105 @@
+#############################################################################
+##
+## Copyright (C) 2021 The Qt Company Ltd.
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of the Qt for Python examples of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import math
+import sys
+
+from PySide6.QtWidgets import QWidget, QApplication
+from PySide6.QtCore import QPoint, QRect, QTimer, Qt, Slot
+from PySide6.QtGui import (QColor, QPainter, QPaintEvent, QPen, QPointList,
+ QTransform)
+
+
+WIDTH = 680
+HEIGHT = 480
+
+
+class PlotWidget(QWidget):
+ """Illustrates the use of opaque containers. QPointList
+ wraps a C++ QList<QPoint> directly, removing the need to convert
+ a Python list in each call to QPainter.drawPolyline()."""
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._timer = QTimer(self)
+ self._timer.setInterval(20)
+ self._timer.timeout.connect(self.shift)
+
+ self._points = QPointList()
+ self._x = 0
+ self._delta_x = 0.05
+ self._half_height = HEIGHT / 2
+ self._factor = 0.8 * self._half_height
+
+ for i in range(WIDTH):
+ self._points.append(QPoint(i, self.next_point()))
+
+ self.setFixedSize(WIDTH, HEIGHT)
+
+ self._timer.start()
+
+ def next_point(self):
+ result = self._half_height - self._factor * math.sin(self._x)
+ self._x += self._delta_x
+ return result
+
+ def shift(self):
+ last_x = self._points[WIDTH - 1].x()
+ self._points.pop_front()
+ self._points.append(QPoint(last_x + 1, self.next_point()))
+ self.update()
+
+ def paintEvent(self, event):
+ painter = QPainter()
+ painter.begin(self)
+ rect = QRect(QPoint(0, 0), self.size())
+ painter.fillRect(rect, Qt.white)
+ painter.translate(-self._points[0].x(), 0)
+ painter.drawPolyline(self._points)
+ painter.end()
+
+
+if __name__ == "__main__":
+
+ app = QApplication(sys.argv)
+
+ w = PlotWidget()
+ w.show()
+ sys.exit(app.exec())
diff --git a/examples/widgets/painting/plot/plot.pyproject b/examples/widgets/painting/plot/plot.pyproject
new file mode 100644
index 000000000..0ac776c83
--- /dev/null
+++ b/examples/widgets/painting/plot/plot.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["plot.py"]
+}
diff --git a/examples/widgets/state-machine/pingpong/pingpong.py b/examples/widgets/state-machine/ping_pong/ping_pong.py
index cd047a29d..cd047a29d 100644
--- a/examples/widgets/state-machine/pingpong/pingpong.py
+++ b/examples/widgets/state-machine/ping_pong/ping_pong.py
diff --git a/examples/widgets/state-machine/ping_pong/ping_pong.pyproject b/examples/widgets/state-machine/ping_pong/ping_pong.pyproject
new file mode 100644
index 000000000..7fb430352
--- /dev/null
+++ b/examples/widgets/state-machine/ping_pong/ping_pong.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["ping_pong.py"]
+}
diff --git a/examples/widgets/state-machine/pingpong/pingpong.pyproject b/examples/widgets/state-machine/pingpong/pingpong.pyproject
deleted file mode 100644
index 67b48e5ab..000000000
--- a/examples/widgets/state-machine/pingpong/pingpong.pyproject
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "files": ["pingpong.py"]
-}
diff --git a/examples/widgets/threads/thread_signals.py b/examples/widgets/thread_signals/thread_signals.py
index 43c79f4b7..43c79f4b7 100644
--- a/examples/widgets/threads/thread_signals.py
+++ b/examples/widgets/thread_signals/thread_signals.py
diff --git a/examples/widgets/threads/thread_signals.pyproject b/examples/widgets/thread_signals/thread_signals.pyproject
index e36f7633c..e36f7633c 100644
--- a/examples/widgets/threads/thread_signals.pyproject
+++ b/examples/widgets/thread_signals/thread_signals.pyproject