aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2024-01-23 15:45:57 +0100
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2024-03-07 11:01:05 +0100
commite4481a8700cc21d71ba646353512e01e1520b1c2 (patch)
tree00d218e4feb401ff7a12b2ff2a480ae7baeaa82b
parentb8d29e0381235785f1726a8cc28b8b7579c32e03 (diff)
Port the colorpaletteclient example
Task-number: PYSIDE-2497 Change-Id: Ic57785fa221afa7d3d5cd5f3550c5a6e2d38f08b Reviewed-by: Shyamnath Premnadh <Shyamnath.Premnadh@qt.io>
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml71
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml139
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/ColorView.qml381
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/Main.qml62
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml241
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml139
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/qmldir7
-rw-r--r--examples/demos/colorpaletteclient/QtExampleStyle/Button.qml48
-rw-r--r--examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt54
-rw-r--r--examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml27
-rw-r--r--examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml22
-rw-r--r--examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml29
-rw-r--r--examples/demos/colorpaletteclient/QtExampleStyle/qmldir5
-rw-r--r--examples/demos/colorpaletteclient/abstractresource.py24
-rw-r--r--examples/demos/colorpaletteclient/basiclogin.py100
-rw-r--r--examples/demos/colorpaletteclient/colorpaletteclient.pyproject21
-rw-r--r--examples/demos/colorpaletteclient/colorpaletteclient.qrc17
-rw-r--r--examples/demos/colorpaletteclient/doc/colorpaletteclient.rst79
-rw-r--r--examples/demos/colorpaletteclient/doc/colorpaletteclient.webpbin0 -> 28034 bytes
-rw-r--r--examples/demos/colorpaletteclient/icons/close.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/delete.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/dots.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/edit.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/login.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/logout.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/ok.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/plus.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/qt.pngbin0 -> 2963 bytes
-rw-r--r--examples/demos/colorpaletteclient/icons/qt_attribution.json14
-rw-r--r--examples/demos/colorpaletteclient/icons/testserver.pngbin0 -> 6803 bytes
-rw-r--r--examples/demos/colorpaletteclient/icons/update.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/user.svg4
-rw-r--r--examples/demos/colorpaletteclient/icons/userMask.svg3
-rw-r--r--examples/demos/colorpaletteclient/main.py33
-rw-r--r--examples/demos/colorpaletteclient/paginatedresource.py278
-rw-r--r--examples/demos/colorpaletteclient/rc_colorpaletteclient.py1098
-rw-r--r--examples/demos/colorpaletteclient/restservice.py53
37 files changed, 2976 insertions, 0 deletions
diff --git a/examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml
new file mode 100644
index 000000000..0fd26e4d0
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml
@@ -0,0 +1,71 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import QtExampleStyle
+
+Popup {
+ id: colorDeleter
+ padding: 10
+ modal: true
+ focus: true
+ anchors.centerIn: parent
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
+ signal deleteClicked(int cid)
+
+ property int colorId: -1
+
+ property string colorName: ""
+
+ function maybeDelete(color_id, name) {
+ colorName = name
+ colorId = color_id
+ open()
+ }
+
+
+ ColumnLayout {
+ anchors.fill: parent
+ spacing: 10
+
+ Text {
+ color: "#222222"
+ text: qsTr("Delete Color?")
+ font.pixelSize: 16
+ font.bold: true
+ }
+
+ Text {
+ color: "#222222"
+ text: qsTr("Are you sure, you want to delete color") + " \"" + colorDeleter.colorName + "\"?"
+ font.pixelSize: 12
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 10
+
+ Button {
+ Layout.fillWidth: true
+ text: qsTr("Cancel")
+ onClicked: colorDeleter.close()
+ }
+
+ Button {
+ Layout.fillWidth: true
+ text: qsTr("Delete")
+
+ buttonColor: "#CC1414"
+ textColor: "#FFFFFF"
+
+ onClicked: {
+ colorDeleter.deleteClicked(colorDeleter.colorId)
+ colorDeleter.close()
+ }
+ }
+ }
+ }
+}
diff --git a/examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml
new file mode 100644
index 000000000..cba6e5a76
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml
@@ -0,0 +1,139 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Dialogs
+
+import QtExampleStyle
+
+Popup {
+ id: colorEditor
+ // Popup for adding or updating a color
+ padding: 10
+ modal: true
+ focus: true
+ anchors.centerIn: parent
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
+ signal colorAdded(string name, string color, string pantone_value)
+ signal colorUpdated(string name, string color, string pantone_value, int cid)
+
+ property bool newColor: true
+ property int colorId: -1
+ property alias currentColor: colordialogButton.buttonColor
+
+ function createNewColor() {
+ newColor = true
+ colorNameField.text = "cute green"
+ colorRGBField.text = "#41cd52"
+ colorPantoneField.text = "PMS 802C"
+ open()
+ }
+
+ function updateColor(color_id, name, color, pantone_value) {
+ newColor = false
+ colorNameField.text = name
+ currentColor = color
+ colorPantoneField.text = pantone_value
+ colorId = color_id
+ open()
+ }
+
+ ColorDialog {
+ id: colorDialog
+ title: qsTr("Choose a color")
+ onAccepted: {
+ colorEditor.currentColor = Qt.color(colorDialog.selectedColor)
+ colorDialog.close()
+ }
+ onRejected: {
+ colorDialog.close()
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ spacing: 10
+
+ GridLayout {
+ columns: 2
+ rowSpacing: 10
+ columnSpacing: 10
+
+ Label {
+ text: qsTr("Color Name")
+ }
+ TextField {
+ id: colorNameField
+ padding: 10
+ }
+
+ Label {
+ text: qsTr("Pantone Value")
+ }
+ TextField {
+ id: colorPantoneField
+ padding: 10
+ }
+
+ Label {
+ text: qsTr("Rgb Value")
+ }
+
+ TextField {
+ id: colorRGBField
+ text: colorEditor.currentColor.toString()
+ readOnly: true
+ padding: 10
+ }
+ }
+
+ Button {
+ id: colordialogButton
+ Layout.fillWidth: true
+ Layout.preferredHeight: 30
+ text: qsTr("Set Color")
+ textColor: isColorDark(buttonColor) ? "#E6E6E6" : "#191919"
+
+ onClicked: colorDialog.open()
+
+ function isColorDark(color) {
+ return (0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b) < 0.5;
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 10
+
+ Button {
+ text: qsTr("Cancel")
+ onClicked: colorEditor.close()
+ Layout.fillWidth: true
+ }
+
+ Button {
+ Layout.fillWidth: true
+ text: colorEditor.newColor ? qsTr("Add") : qsTr("Update")
+
+ buttonColor: "#2CDE85"
+ textColor: "#FFFFFF"
+
+ onClicked: {
+ if (colorEditor.newColor) {
+ colorEditor.colorAdded(colorNameField.text,
+ colorRGBField.text,
+ colorPantoneField.text)
+ } else {
+ colorEditor.colorUpdated(colorNameField.text,
+ colorRGBField.text,
+ colorPantoneField.text,
+ colorEditor.colorId)
+ }
+ colorEditor.close()
+ }
+ }
+ }
+ }
+}
diff --git a/examples/demos/colorpaletteclient/ColorPalette/ColorView.qml b/examples/demos/colorpaletteclient/ColorPalette/ColorView.qml
new file mode 100644
index 000000000..c6ad36f80
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/ColorView.qml
@@ -0,0 +1,381 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Effects
+import QtQuick.Shapes
+
+import QtExampleStyle
+import ColorPalette
+
+Item {
+ id: root
+ required property BasicLogin loginService
+ required property PaginatedColorsResource colors
+ required property PaginatedColorUsersResource colorViewUsers
+
+ ColorDialogEditor {
+ id: colorPopup
+ onColorAdded: (colorNameField, colorRGBField, colorPantoneField) => {
+ root.colors.add({"name" : colorNameField,
+ "color" : colorRGBField,
+ "pantone_value" : colorPantoneField})
+ }
+
+ onColorUpdated: (colorNameField, colorRGBField, colorPantoneField, cid) => {
+ root.colors.update({"name" : colorNameField,
+ "color" : colorRGBField,
+ "pantone_value" : colorPantoneField},
+ cid)
+ }
+ }
+
+ ColorDialogDelete {
+ id: colorDeletePopup
+ onDeleteClicked: (cid) => {
+ root.colors.remove(cid)
+ }
+ }
+
+ ColumnLayout {
+ // The main application layout
+ anchors.fill :parent
+
+ ToolBar {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 25 + 4
+
+ UserMenu {
+ id: userMenu
+
+ userMenuUsers: root.colorViewUsers
+ userLoginService: root.loginService
+ }
+
+ RowLayout {
+ anchors.fill: parent
+ Text {
+ text: qsTr("QHTTP Server")
+ font.pixelSize: 8
+ color: "#667085"
+ }
+ Item { Layout.fillWidth: true }
+
+ AbstractButton {
+ id: loginButton
+ Layout.preferredWidth: 25
+ Layout.preferredHeight: 25
+ Item {
+ id: userImageCliped
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ width: 25
+ height: 25
+
+ Image {
+ id: userImage
+ anchors.fill: parent
+ source: getCurrentUserImage()
+ visible: false
+
+ function getCurrentUserImage() {
+ if (root.loginService.loggedIn)
+ return users.avatarForEmail(loginService.user)
+ return "qrc:/qt/qml/ColorPalette/icons/user.svg";
+ }
+ }
+
+ Image {
+ id: userMask
+ source: "qrc:/qt/qml/ColorPalette/icons/userMask.svg"
+ anchors.fill: userImage
+ anchors.margins: 4
+ visible: false
+ }
+
+ MultiEffect {
+ source: userImage
+ anchors.fill: userImage
+ maskSource: userMask
+ maskEnabled: true
+ }
+ }
+
+ onClicked: {
+ userMenu.open()
+ var pos = mapToGlobal(Qt.point(x, y))
+ pos = userMenu.parent.mapFromGlobal(pos)
+ userMenu.x = x - userMenu.width + 25 + 3
+ userMenu.y = y + 25 + 3
+ }
+
+ Shape {
+ id: bubble
+ x: -text.width - 25
+ anchors.margins: 3
+
+ preferredRendererType: Shape.CurveRenderer
+
+ visible: !root.loginService.loggedIn
+
+ ShapePath {
+ strokeWidth: 0
+ fillColor: "#667085"
+ startX: 5; startY: 0
+ PathLine { x: 5 + text.width + 6; y: 0 }
+ PathArc { x: 10 + text.width + 6; y: 5; radiusX: 5; radiusY: 5}
+ // arrow
+ PathLine { x: 10 + text.width + 6; y: 8 + text.height / 2 - 6 }
+ PathLine { x: 10 + text.width + 6 + 6; y: 8 + text.height / 2 }
+ PathLine { x: 10 + text.width + 6; y: 8 + text.height / 2 + 6}
+ PathLine { x: 10 + text.width + 6; y: 5 + text.height + 6 }
+ // end arrow
+ PathArc { x: 5 + text.width + 6; y: 10 + text.height + 6 ; radiusX: 5; radiusY: 5}
+ PathLine { x: 5; y: 10 + text.height + 6 }
+ PathArc { x: 0; y: 5 + text.height + 6 ; radiusX: 5; radiusY: 5}
+ PathLine { x: 0; y: 5 }
+ PathArc { x: 5; y: 0 ; radiusX: 5; radiusY: 5}
+ }
+ Text {
+ x: 8
+ y: 8
+ id: text
+ color: "white"
+ text: qsTr("Log in to edit")
+ font.bold: true
+ horizontalAlignment: Qt.AlignHCenter
+ verticalAlignment: Qt.AlignVCenter
+ }
+ }
+ }
+ }
+
+ Image {
+ anchors.centerIn: parent
+ source: "qrc:/qt/qml/ColorPalette/icons/qt.png"
+ fillMode: Image.PreserveAspectFit
+ height: 25
+ }
+
+ }
+ ToolBar {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 32
+
+ RowLayout {
+ anchors.fill: parent
+ Text {
+ Layout.alignment: Qt.AlignVCenter
+ text: qsTr("Color Palette")
+ font.pixelSize: 14
+ font.bold: true
+ color: "#667085"
+ }
+
+ Item { Layout.fillWidth: true }
+
+ AbstractButton {
+ Layout.preferredWidth: 25
+ Layout.preferredHeight: 25
+ Layout.alignment: Qt.AlignVCenter
+
+ Rectangle {
+ anchors.fill: parent
+ radius: 4
+ color: "#192CDE85"
+ border.color: "#DDE2E8"
+ border.width: 1
+ }
+
+ Image {
+ source: UIStyle.iconPath("plus")
+ fillMode: Image.PreserveAspectFit
+ anchors.fill: parent
+ sourceSize.width: width
+ sourceSize.height: height
+
+ }
+ visible: root.loginService.loggedIn
+ onClicked: colorPopup.createNewColor()
+ }
+
+ AbstractButton {
+ Layout.preferredWidth: 25
+ Layout.preferredHeight: 25
+ Layout.alignment: Qt.AlignVCenter
+
+ Rectangle {
+ anchors.fill: parent
+ radius: 4
+ color: "#192CDE85"
+ border.color: "#DDE2E8"
+ border.width: 1
+ }
+
+ Image {
+ source: UIStyle.iconPath("update")
+ fillMode: Image.PreserveAspectFit
+ anchors.fill: parent
+ sourceSize.width: width
+ sourceSize.height: height
+ }
+
+ onClicked: {
+ root.colors.refreshCurrentPage()
+ root.colorViewUsers.refreshCurrentPage()
+ }
+ }
+ }
+ }
+
+
+
+ //! [View and model]
+ ListView {
+ id: colorListView
+
+ model: root.colors.model
+ //! [View and model]
+ footerPositioning: ListView.OverlayFooter
+ spacing: 15
+ clip: true
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ header: Rectangle {
+ height: 32
+ width: parent.width
+ color: "#F0F1F3"
+
+ RowLayout {
+ anchors.fill: parent
+
+ component HeaderText : Text {
+ Layout.alignment: Qt.AlignVCenter
+ horizontalAlignment: Qt.AlignHCenter
+
+ font.pixelSize: 12
+ color: "#667085"
+ }
+ HeaderText {
+ id: headerName
+ text: qsTr("Color Name")
+ Layout.preferredWidth: colorListView.width * 0.3
+ }
+ HeaderText {
+ id: headerRgb
+ text: qsTr("Rgb Value")
+ Layout.preferredWidth: colorListView.width * 0.25
+ }
+ HeaderText {
+ id: headerPantone
+ text: qsTr("Pantone Value")
+ Layout.preferredWidth: colorListView.width * 0.25
+ }
+ HeaderText {
+ id: headerAction
+ text: qsTr("Action")
+ Layout.preferredWidth: colorListView.width * 0.2
+ }
+ }
+ }
+
+ delegate: Item {
+ id: colorInfo
+
+ required property int color_id
+ required property string name
+ required property string color
+ required property string pantone_value
+
+ width: colorListView.width
+ height: 25
+ RowLayout {
+ anchors.fill: parent
+ anchors.leftMargin: 5
+ anchors.rightMargin: 5
+
+ Rectangle {
+ id: colorSample
+ Layout.alignment: Qt.AlignVCenter
+ implicitWidth: 36
+ implicitHeight: 21
+ radius: 6
+ color: colorInfo.color
+ }
+
+ Text {
+ Layout.preferredWidth: colorInfo.width * 0.3 - colorSample.width
+ horizontalAlignment: Qt.AlignLeft
+ leftPadding: 5
+ text: colorInfo.name
+ }
+
+ Text {
+ Layout.preferredWidth: colorInfo.width * 0.25
+ horizontalAlignment: Qt.AlignHCenter
+ text: colorInfo.color
+ }
+
+ Text {
+ Layout.preferredWidth: colorInfo.width * 0.25
+ horizontalAlignment: Qt.AlignHCenter
+ text: colorInfo.pantone_value
+ }
+
+ Item {
+ Layout.maximumHeight: 28
+ implicitHeight: buttonBox.implicitHeight
+ implicitWidth: buttonBox.implicitWidth
+
+ RowLayout {
+ id: buttonBox
+ anchors.fill: parent
+ ToolButton {
+ icon.source: UIStyle.iconPath("delete")
+ enabled: root.loginService.loggedIn
+ onClicked: colorDeletePopup.maybeDelete(color_id, name)
+ }
+ ToolButton {
+ icon.source: UIStyle.iconPath("edit")
+ enabled: root.loginService.loggedIn
+ onClicked: colorPopup.updateColor(color_id, name, color, pantone_value)
+ }
+ }
+ }
+ }
+ }
+
+ footer: ToolBar {
+ // Paginate buttons if more than one page
+ visible: root.colors.pages > 1
+ implicitWidth: parent.width
+
+ RowLayout {
+ anchors.fill: parent
+
+ Item { Layout.fillWidth: true /* spacer */ }
+
+ Repeater {
+ model: root.colors.pages
+
+ ToolButton {
+ text: page
+ font.bold: root.colors.page === page
+
+ required property int index
+ readonly property int page: (index + 1)
+
+ onClicked: root.colors.page = page
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/examples/demos/colorpaletteclient/ColorPalette/Main.qml b/examples/demos/colorpaletteclient/ColorPalette/Main.qml
new file mode 100644
index 000000000..ae1e85533
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/Main.qml
@@ -0,0 +1,62 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+
+import ColorPalette
+
+Window {
+ id: window
+ width: 500
+ height: 400
+ visible: true
+ title: qsTr("Color Palette Client")
+
+ enum DataView {
+ UserView = 0,
+ ColorView = 1
+ }
+
+ ServerSelection {
+ id: serverview
+ anchors.fill: parent
+ onServerSelected: {colorview.visible = true; serverview.visible = false}
+ colorResources: colors
+ restPalette: paletteService
+ colorUsers: users
+ }
+
+ ColorView {
+ id: colorview
+ anchors.fill: parent
+ visible: false
+ loginService: colorLogin
+ colors: colors
+ colorViewUsers: users
+ }
+
+ //! [RestService QML element]
+ RestService {
+ id: paletteService
+
+ PaginatedColorUsersResource {
+ id: users
+ path: "/api/users"
+ }
+
+ PaginatedColorsResource {
+ id: colors
+ path: "/api/unknown"
+ }
+
+ BasicLogin {
+ id: colorLogin
+ loginPath: "/api/login"
+ logoutPath: "/api/logout"
+ }
+ }
+ //! [RestService QML element]
+
+}
diff --git a/examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml b/examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml
new file mode 100644
index 000000000..c170773cc
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml
@@ -0,0 +1,241 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import ColorPalette
+import QtExampleStyle
+
+pragma ComponentBehavior: Bound
+
+Item {
+ id: root
+ // A popup for selecting the server URL
+
+ signal serverSelected()
+
+ required property PaginatedColorsResource colorResources
+ required property PaginatedColorUsersResource colorUsers
+ required property RestService restPalette
+
+ Connections {
+ target: root.colorResources
+ // Closes the URL selection popup once we have received data successfully
+ function onDataUpdated() {
+ fetchTester.stop()
+ root.serverSelected()
+ }
+ }
+
+
+ ListModel {
+ id: server
+ ListElement {
+ title: qsTr("Public REST API Test Server")
+ url: "https://reqres.in"
+ icon: "qrc:/qt/qml/ColorPalette/icons/testserver.png"
+ }
+ ListElement {
+ title: qsTr("Qt-based REST API server")
+ url: "http://127.0.0.1:49425"
+ icon: "qrc:/qt/qml/ColorPalette/icons/qt.png"
+ }
+ }
+
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 20
+ spacing: 10
+
+ Image {
+ Layout.alignment: Qt.AlignHCenter
+ source: "qrc:/qt/qml/ColorPalette/icons/qt.png"
+ fillMode: Image.PreserveAspectFit
+ Layout.preferredWidth: 20
+ }
+
+ Label {
+ text: qsTr("Choose a server")
+ Layout.alignment: Qt.AlignHCenter
+ font.pixelSize: 24
+ }
+
+ component ServerListDelegate: Rectangle {
+ id: serverListDelegate
+ required property string title
+ required property string url
+ required property string icon
+ required property int index
+
+ radius: 10
+ color: "#00000000"
+
+ border.color: ListView.view.currentIndex === index ? "#2CDE85" : "#E0E2E7"
+ border.width: 2
+
+ implicitWidth: 180
+ implicitHeight: 100
+
+ Rectangle {
+ id: img
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.topMargin: 10
+ anchors.leftMargin: 20
+
+ width: 30
+ height: 30
+ radius: 200
+ border. color: "#E7F4EE"
+ border.width: 5
+
+ Image {
+ anchors.centerIn: parent
+ source: serverListDelegate.icon
+ width: 15
+ height: 15
+ fillMode: Image.PreserveAspectFit
+ smooth: true
+ }
+ }
+
+ Text {
+ text: parent.url
+
+ anchors.left: parent.left
+ anchors.top: img.bottom
+ anchors.topMargin: 10
+ anchors.leftMargin: 20
+ color: "#667085"
+ font.pixelSize: 13
+ }
+ Text {
+ text: parent.title
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 10
+ color: "#222222"
+ font.pixelSize: 11
+ font.bold: true
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: serverList.currentIndex = serverListDelegate.index;
+ }
+ }
+
+ ListView {
+ id: serverList
+ Layout.alignment: Qt.AlignHCenter
+ Layout.minimumWidth: 180 * server.count + 20
+ Layout.minimumHeight: 100
+ orientation: ListView.Horizontal
+
+ model: server
+ spacing: 20
+
+ delegate: ServerListDelegate {}
+ }
+
+ Button {
+ Layout.alignment: Qt.AlignHCenter
+ text: restPalette.sslSupported ? qsTr("Connect (SSL)") : qsTr("Connect")
+
+ buttonColor: "#2CDE85"
+ textColor: "#FFFFFF"
+
+ onClicked: {
+ busyIndicatorPopup.title = (serverList.currentItem as ServerListDelegate).title
+ busyIndicatorPopup.icon = (serverList.currentItem as ServerListDelegate).icon
+ busyIndicatorPopup.open()
+
+ fetchTester.test((serverList.currentItem as ServerListDelegate).url)
+ }
+ }
+
+ Timer {
+ id: fetchTester
+ interval: 2000
+
+ function test(url) {
+ root.restPalette.url = url
+ root.colorResources.refreshCurrentPage()
+ root.colorUsers.refreshCurrentPage()
+ start()
+ }
+ onTriggered: busyIndicatorPopup.close()
+ }
+ }
+
+ onVisibleChanged: {if (!visible) busyIndicatorPopup.close();}
+
+ Popup {
+ id: busyIndicatorPopup
+ padding: 10
+ modal: true
+ focus: true
+ anchors.centerIn: parent
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
+
+ property alias title: titleText.text
+ property alias icon: titleImg.source
+
+ ColumnLayout {
+ id: fetchIndicator
+ anchors.fill: parent
+
+ RowLayout {
+ Rectangle {
+ Layout.preferredWidth: 50
+ Layout.preferredHeight: 50
+ radius: 200
+ border. color: "#E7F4EE"
+ border.width: 5
+
+ Image {
+ id: titleImg
+ anchors.centerIn: parent
+ width: 25
+ height: 25
+ fillMode: Image.PreserveAspectFit
+ }
+ }
+
+ Label {
+ id: titleText
+ text:""
+ font.pixelSize: 18
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: false
+ Layout.alignment: Qt.AlignHCenter
+ BusyIndicator {
+ running: visible
+ Layout.fillWidth: true
+ }
+
+ Label {
+ text: qsTr("Testing URL")
+ font.pixelSize: 18
+ }
+ }
+
+ Button {
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("Cancel")
+ onClicked: {
+ busyIndicatorPopup.close()
+ }
+ }
+
+ }
+
+ }
+}
diff --git a/examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml b/examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml
new file mode 100644
index 000000000..6c4b25683
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml
@@ -0,0 +1,139 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Effects
+
+import QtExampleStyle
+import ColorPalette
+
+Popup {
+ id: userMenu
+
+ required property BasicLogin userLoginService
+ required property PaginatedColorUsersResource userMenuUsers
+
+ width: 280
+ height: 270
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ ListView {
+ id: userListView
+
+ model: userMenu.userMenuUsers.model
+ spacing: 5
+ footerPositioning: ListView.PullBackFooter
+ clip: true
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ delegate: Rectangle {
+ id: userInfo
+
+ required property string email
+ required property string avatar
+
+ height: 30
+ width: userListView.width
+
+
+ readonly property bool logged: (email === loginService.user)
+
+ Rectangle {
+ id: userImageCliped
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ width: 30
+ height: 30
+
+ Image {
+ id: userImage
+ anchors.fill: parent
+ source: userInfo.avatar
+ visible: false
+ }
+
+ Image {
+ id: userMask
+ source: "qrc:/qt/qml/ColorPalette/icons/userMask.svg"
+ anchors.fill: userImage
+ anchors.margins: 4
+ visible: false
+ }
+
+ MultiEffect {
+ source: userImage
+ anchors.fill: userImage
+ maskSource: userMask
+ maskEnabled: true
+ }
+ }
+
+ Text {
+ id: userMailLabel
+ anchors.left: userImageCliped.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.margins: 5
+ text: userInfo.email
+ font.bold: userInfo.logged
+ }
+
+ ToolButton {
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.margins: 5
+
+ icon.source: UIStyle.iconPath(userInfo.logged
+ ? "logout" : "login")
+ enabled: userInfo.logged || !userMenu.userLoginService.loggedIn
+
+ onClicked: {
+ if (userInfo.logged) {
+ userMenu.userLoginService.logout()
+ } else {
+ //! [Login]
+ userMenu.userLoginService.login({"email" : userInfo.email,
+ "password" : "apassword",
+ "id" : userInfo.id})
+ //! [Login]
+ userMenu.close()
+ }
+ }
+ }
+
+ }
+ footer: ToolBar {
+ // Paginate buttons if more than one page
+ visible: userMenu.userMenuUsers.pages > 1
+ implicitWidth: parent.width
+
+ RowLayout {
+ anchors.fill: parent
+
+ Item { Layout.fillWidth: true /* spacer */ }
+
+ Repeater {
+ model: userMenu.userMenuUsers.pages
+
+ ToolButton {
+ text: page
+ font.bold: userMenu.userMenuUsers.page === page
+
+ required property int index
+ readonly property int page: (index + 1)
+
+ onClicked: userMenu.userMenuUsers.page = page
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/examples/demos/colorpaletteclient/ColorPalette/qmldir b/examples/demos/colorpaletteclient/ColorPalette/qmldir
new file mode 100644
index 000000000..7a153fea8
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/qmldir
@@ -0,0 +1,7 @@
+module ColorPalette
+Main 1.0 Main.qml
+ColorDialogDelete 1.0 ColorDialogDelete.qml
+ColorDialogEditor 1.0 ColorDialogEditor.qml
+ColorView 1.0 ColorView.qml
+ServerSelection 1.0 ServerSelection.qml
+UserMenu 1.0 UserMenu.qml
diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/Button.qml b/examples/demos/colorpaletteclient/QtExampleStyle/Button.qml
new file mode 100644
index 000000000..6b3f922a1
--- /dev/null
+++ b/examples/demos/colorpaletteclient/QtExampleStyle/Button.qml
@@ -0,0 +1,48 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+
+T.Button {
+ id: control
+
+ property alias buttonColor: rect.color
+ property alias textColor: label.color
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitContentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + topPadding + bottomPadding)
+
+ leftPadding: 15
+ rightPadding: 15
+ topPadding: 10
+ bottomPadding: 10
+
+ background: Rectangle {
+ id: rect
+ radius: 8
+ border.color: "#E0E2E7"
+ border.width: 1
+ color: "#FFFFFF"
+ }
+
+ icon.width: 24
+ icon.height: 24
+ icon.color: control.palette.buttonText
+
+ contentItem: IconLabel {
+ id: label
+ spacing: control.spacing
+ mirrored: control.mirrored
+ display: control.display
+
+ icon: control.icon
+ text: control.text
+ font.pixelSize: 14
+ color: "#667085"
+ }
+}
diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt b/examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt
new file mode 100644
index 000000000..a911f8742
--- /dev/null
+++ b/examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt
@@ -0,0 +1,54 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(qtexamplestyle LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/quickcontrols/colorpaletteclient/QtExampleStyle")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick QuickControls2)
+
+set_source_files_properties(UIStyle.qml
+ PROPERTIES
+ QT_QML_SINGLETON_TYPE TRUE
+)
+
+qt_policy(SET QTP0001 NEW)
+qt_add_qml_module(qtexamplestyle
+ URI QtExampleStyle
+ PLUGIN_TARGET qtexamplestyle
+ QML_FILES
+ Button.qml
+ Popup.qml
+ UIStyle.qml
+ TextField.qml
+)
+
+target_link_libraries(qtexamplestyle PUBLIC
+ Qt6::Core
+ Qt6::Gui
+ Qt6::Quick
+ Qt6::QuickControls2
+)
+
+if(UNIX AND NOT APPLE AND CMAKE_CROSSCOMPILING)
+ find_package(Qt6 REQUIRED COMPONENTS QuickTemplates2)
+
+ # Work around QTBUG-86533
+ target_link_libraries(qtexamplestyle PRIVATE Qt6::QuickTemplates2)
+endif()
+
+install(TARGETS qtexamplestyle
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qmldir
+ DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml b/examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml
new file mode 100644
index 000000000..a3132bcea
--- /dev/null
+++ b/examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml
@@ -0,0 +1,27 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Templates as T
+
+T.Popup {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitContentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + topPadding + bottomPadding)
+
+ leftPadding: 15
+ rightPadding: 15
+ topPadding: 10
+ bottomPadding: 10
+
+ background: Rectangle {
+ id: bg
+ radius: 8
+ border.color: "#E0E2E7"
+ border.width: 2
+ color: "#FFFFFF"
+ }
+}
diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml b/examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml
new file mode 100644
index 000000000..7db2d4f98
--- /dev/null
+++ b/examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml
@@ -0,0 +1,22 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Templates as T
+
+T.TextField {
+ id: control
+ placeholderText: ""
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ contentHeight + topPadding + bottomPadding)
+
+ background: Rectangle {
+ implicitWidth: 200
+ implicitHeight: 40
+ radius: 8
+ color: control.enabled ? "transparent" : "#353637"
+ border.color: "#E0E2E7"
+ }
+}
diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml b/examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml
new file mode 100644
index 000000000..3c4741d7f
--- /dev/null
+++ b/examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml
@@ -0,0 +1,29 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma Singleton
+
+import QtQuick
+
+QtObject {
+ id: uiStyle
+
+ // Font Sizes
+ readonly property int fontSizeXXS: 10
+ readonly property int fontSizeXS: 15
+ readonly property int fontSizeS: 20
+ readonly property int fontSizeM: 25
+ readonly property int fontSizeL: 30
+ readonly property int fontSizeXL: 35
+ readonly property int fontSizeXXL: 40
+
+ // Color Scheme
+ // Green
+ readonly property color colorQtPrimGreen: "#41cd52"
+ readonly property color colorQtAuxGreen1: "#21be2b"
+ readonly property color colorQtAuxGreen2: "#17a81a"
+
+ function iconPath(baseImagePath) {
+ return `qrc:/qt/qml/ColorPalette/icons/${baseImagePath}.svg`
+ }
+}
diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/qmldir b/examples/demos/colorpaletteclient/QtExampleStyle/qmldir
new file mode 100644
index 000000000..7bdfb44d8
--- /dev/null
+++ b/examples/demos/colorpaletteclient/QtExampleStyle/qmldir
@@ -0,0 +1,5 @@
+module QtExampleStyle
+Button 1.0 Button.qml
+Popup 1.0 Popup.qml
+TextField 1.0 TextField.qml
+singleton UIStyle 1.0 UIStyle.qml
diff --git a/examples/demos/colorpaletteclient/abstractresource.py b/examples/demos/colorpaletteclient/abstractresource.py
new file mode 100644
index 000000000..3f3a7ed6a
--- /dev/null
+++ b/examples/demos/colorpaletteclient/abstractresource.py
@@ -0,0 +1,24 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import QObject
+from PySide6.QtQml import QmlAnonymous
+
+
+QML_IMPORT_NAME = "ColorPalette"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlAnonymous
+class AbstractResource(QObject):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.m_manager = None # QRestAccessManager
+ self.m_api = None # QNetworkRequestFactory
+
+ def setAccessManager(self, manager):
+ self.m_manager = manager
+
+ def setServiceApi(self, serviceApi):
+ self.m_api = serviceApi
diff --git a/examples/demos/colorpaletteclient/basiclogin.py b/examples/demos/colorpaletteclient/basiclogin.py
new file mode 100644
index 000000000..b9139c2e2
--- /dev/null
+++ b/examples/demos/colorpaletteclient/basiclogin.py
@@ -0,0 +1,100 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from functools import partial
+from dataclasses import dataclass
+
+from PySide6.QtCore import Property, Signal, Slot
+from PySide6.QtNetwork import QHttpHeaders
+from PySide6.QtQml import QmlElement
+
+from abstractresource import AbstractResource
+
+
+tokenField = "token"
+emailField = "email"
+idField = "id"
+
+
+QML_IMPORT_NAME = "ColorPalette"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+class BasicLogin(AbstractResource):
+ @dataclass
+ class User:
+ email: str
+ token: bytes
+ id: int
+
+ userChanged = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.m_user = None
+ self.m_loginPath = ""
+ self.m_logoutPath = ""
+ self.m_user = None
+
+ @Property(str, notify=userChanged)
+ def user(self):
+ return self.m_user.email if self.m_user else ""
+
+ @Property(bool, notify=userChanged)
+ def loggedIn(self):
+ return bool(self.m_user)
+
+ @Property(str)
+ def loginPath(self):
+ return self.m_loginPath
+
+ @loginPath.setter
+ def loginPath(self, p):
+ self.m_loginPath = p
+
+ @Property(str)
+ def logoutPath(self):
+ return self.m_logoutPath
+
+ @logoutPath.setter
+ def logoutPath(self, p):
+ self.m_logoutPath = p
+
+ @Slot("QVariantMap")
+ def login(self, data):
+ request = self.m_api.createRequest(self.m_loginPath)
+ self.m_manager.post(request, data, self, partial(self.loginReply, data))
+
+ def loginReply(self, data, reply):
+ self.m_user = None
+ if not reply.isSuccess():
+ print("login: ", reply.errorString(), file=sys.stderr)
+ (json, error) = reply.readJson()
+ if json and json.isObject():
+ json_object = json.object()
+ token = json_object.get(tokenField)
+ if token:
+ email = data[emailField]
+ token = json_object[tokenField]
+ id = data[idField]
+ self.m_user = BasicLogin.User(email, token, id)
+
+ headers = QHttpHeaders()
+ headers.append("token", self.m_user.token if self.m_user else "")
+ self.m_api.setCommonHeaders(headers)
+ self.userChanged.emit()
+
+ @Slot()
+ def logout(self):
+ request = self.m_api.createRequest(self.m_logoutPath)
+ self.m_manager.post(request, b"", self, self.logoutReply)
+
+ def logoutReply(self, reply):
+ if reply.isSuccess():
+ self.m_user = None
+ self.m_api.clearCommonHeaders() # clears 'token' header
+ self.userChanged.emit()
+ else:
+ print("logout: ", reply.errorString(), file=sys.stderr)
diff --git a/examples/demos/colorpaletteclient/colorpaletteclient.pyproject b/examples/demos/colorpaletteclient/colorpaletteclient.pyproject
new file mode 100644
index 000000000..d05f7cb29
--- /dev/null
+++ b/examples/demos/colorpaletteclient/colorpaletteclient.pyproject
@@ -0,0 +1,21 @@
+{
+ "files": [
+ "abstractresource.py",
+ "basiclogin.py",
+ "main.py",
+ "paginatedresource.py",
+ "restservice.py",
+ "colorpaletteclient.qrc",
+ "ColorPalette/ColorDialogDelete.qml",
+ "ColorPalette/ColorDialogEditor.qml",
+ "ColorPalette/ColorView.qml",
+ "ColorPalette/Main.qml",
+ "ColorPalette/ServerSelection.qml",
+ "ColorPalette/UserMenu.qml",
+ "QtExampleStyle/Button.qml",
+ "QtExampleStyle/Popup.qml",
+ "QtExampleStyle/TextField.qml",
+ "QtExampleStyle/UIStyle.qml",
+ "colorpaletteclient.qrc"
+ ]
+}
diff --git a/examples/demos/colorpaletteclient/colorpaletteclient.qrc b/examples/demos/colorpaletteclient/colorpaletteclient.qrc
new file mode 100644
index 000000000..16260cbd7
--- /dev/null
+++ b/examples/demos/colorpaletteclient/colorpaletteclient.qrc
@@ -0,0 +1,17 @@
+<RCC>
+ <qresource prefix="/qt/qml/ColorPalette">
+ <file>icons/close.svg</file>
+ <file>icons/delete.svg</file>
+ <file>icons/dots.svg</file>
+ <file>icons/edit.svg</file>
+ <file>icons/login.svg</file>
+ <file>icons/logout.svg</file>
+ <file>icons/ok.svg</file>
+ <file>icons/plus.svg</file>
+ <file>icons/qt.png</file>
+ <file>icons/testserver.png</file>
+ <file>icons/update.svg</file>
+ <file>icons/user.svg</file>
+ <file>icons/userMask.svg</file>
+ </qresource>
+</RCC>
diff --git a/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst b/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst
new file mode 100644
index 000000000..0dcb91d4e
--- /dev/null
+++ b/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst
@@ -0,0 +1,79 @@
+RESTful API client
+==================
+
+Example of how to create a RESTful API QML client.
+
+This example shows how to create a basic QML RESTful API client with an
+imaginary color palette service. The application uses RESTful communication
+with the selected server to request and send data. The REST service is provided
+as a QML element whose child elements wrap the individual JSON data APIs
+provided by the server.
+
+Application functionality
+-------------------------
+
+The example provides the following basic functionalities:
+* Select the server to communicate with
+* List users and colors
+* Login and logout users
+* Modify and create new colors
+
+Server selection
+----------------
+
+At start the application presents the options for the color palette server to communicate
+with. The predefined options are:
+
+* ``https://reqres.in``, a publicly available REST API test service
+* A Qt-based REST API server example in ``QtHttpServer``
+
+Once selected, the RESTful API client issues a test HTTP GET to the color API
+to check if the service is accessible.
+
+One major difference between the two predefined API options is that the
+Qt-based REST API server example is a stateful application which allows
+modifying colors, whereas the ``reqres.in`` is a stateless API testing service.
+In other words, when using the ``reqres.in`` backend, modifying the colors has
+no lasting impact.
+
+The users and colors are paginated resources on the server-side. This means
+that the server provides the data in chunks called pages. The UI listing
+reflects this pagination and views the data on pages.
+
+Viewing the data on UI is done with standard QML views where the model are
+QAbstractListModel-derived classes representing JSON data received from the
+server.
+
+Logging in happens via the login function provided by the login popup. Under
+the hood the login sends a HTTP POST request. Upon receiving a successful
+response the authorization token is extracted from the response, which in turn
+is then used in subsequent HTTP requests which require the token.
+
+Editing and adding new colors is done in a popup. Note that uploading the color
+changes to the server requires that a user has logged in.
+
+REST implementation
+-------------------
+
+The example illustrates one way to compose a REST service from individual resource elements. In
+this example the resources are the paginated user and color resources plus the login service.
+The resource elements are bound together by the base URL (server URL) and the shared network access
+manager.
+
+The basis of the REST service is the RestService QML element whose children items
+compose the actual service.
+
+Upon instantiation the RestService element loops its children elements and sets
+them up to use the same network access manager. This way the individual
+resources share the same access details such as the server URL and
+authorization token.
+
+The actual communication is done with a rest access manager which implements
+some convenience functionality to deal specifically with HTTP REST APIs and
+effectively deals with sending and receiving the ``QNetworkRequest`` and
+``QNetworkReply`` as needed.
+
+.. image:: colorpaletteclient.webp
+ :width: 90%
+ :align: center
+ :alt: RESTful API client
diff --git a/examples/demos/colorpaletteclient/doc/colorpaletteclient.webp b/examples/demos/colorpaletteclient/doc/colorpaletteclient.webp
new file mode 100644
index 000000000..8f4d9a621
--- /dev/null
+++ b/examples/demos/colorpaletteclient/doc/colorpaletteclient.webp
Binary files differ
diff --git a/examples/demos/colorpaletteclient/icons/close.svg b/examples/demos/colorpaletteclient/icons/close.svg
new file mode 100644
index 000000000..3a0d4be65
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/close.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M12.4501 37.65L10.3501 35.55L21.9001 24L10.3501 12.45L12.4501 10.35L24.0001 21.9L35.5501 10.35L37.6501 12.45L26.1001 24L37.6501 35.55L35.5501 37.65L24.0001 26.1L12.4501 37.65Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/delete.svg b/examples/demos/colorpaletteclient/icons/delete.svg
new file mode 100644
index 000000000..8f04948c8
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/delete.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M13.05 42C12.225 42 11.5187 41.7062 10.9313 41.1188C10.3438 40.5312 10.05 39.825 10.05 39V10.5H8V7.5H17.4V6H30.6V7.5H40V10.5H37.95V39C37.95 39.8 37.65 40.5 37.05 41.1C36.45 41.7 35.75 42 34.95 42H13.05ZM34.95 10.5H13.05V39H34.95V10.5ZM18.35 34.7H21.35V14.75H18.35V34.7ZM26.65 34.7H29.65V14.75H26.65V34.7Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/dots.svg b/examples/demos/colorpaletteclient/icons/dots.svg
new file mode 100644
index 000000000..49df163fd
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/dots.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M10.3929 26.4C9.73097 26.4 9.16667 26.1643 8.7 25.6929C8.23333 25.2215 8 24.6548 8 23.9929C8 23.3309 8.2357 22.7666 8.7071 22.3C9.17847 21.8333 9.74513 21.6 10.4071 21.6C11.069 21.6 11.6333 21.8357 12.1 22.3071C12.5667 22.7784 12.8 23.3451 12.8 24.0071C12.8 24.669 12.5643 25.2333 12.0929 25.7C11.6215 26.1666 11.0549 26.4 10.3929 26.4ZM23.9929 26.4C23.331 26.4 22.7667 26.1643 22.3 25.6929C21.8333 25.2215 21.6 24.6548 21.6 23.9929C21.6 23.3309 21.8357 22.7666 22.3071 22.3C22.7785 21.8333 23.3451 21.6 24.0071 21.6C24.669 21.6 25.2333 21.8357 25.7 22.3071C26.1667 22.7784 26.4 23.3451 26.4 24.0071C26.4 24.669 26.1643 25.2333 25.6929 25.7C25.2215 26.1666 24.6549 26.4 23.9929 26.4ZM37.5929 26.4C36.931 26.4 36.3667 26.1643 35.9 25.6929C35.4333 25.2215 35.2 24.6548 35.2 23.9929C35.2 23.3309 35.4357 22.7666 35.9071 22.3C36.3785 21.8333 36.9451 21.6 37.6071 21.6C38.269 21.6 38.8333 21.8357 39.3 22.3071C39.7667 22.7784 40 23.3451 40 24.0071C40 24.669 39.7643 25.2333 39.2929 25.7C38.8215 26.1666 38.2549 26.4 37.5929 26.4Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/edit.svg b/examples/demos/colorpaletteclient/icons/edit.svg
new file mode 100644
index 000000000..1cfc2a73a
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/edit.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9 39H11.2L33.35 16.85L31.15 14.65L9 36.8V39ZM39.7 14.7L33.3 8.29998L35.4 6.19998C35.9667 5.63331 36.6667 5.34998 37.5 5.34998C38.3333 5.34998 39.0333 5.63331 39.6 6.19998L41.8 8.39998C42.3667 8.96664 42.65 9.66664 42.65 10.5C42.65 11.3333 42.3667 12.0333 41.8 12.6L39.7 14.7ZM37.6 16.8L12.4 42H6V35.6L31.2 10.4L37.6 16.8ZM32.25 15.75L31.15 14.65L33.35 16.85L32.25 15.75Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/login.svg b/examples/demos/colorpaletteclient/icons/login.svg
new file mode 100644
index 000000000..c8fe5bc54
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/login.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M24.45 42V39H39V9H24.45V6H39C39.8 6 40.5 6.3 41.1 6.9C41.7 7.5 42 8.2 42 9V39C42 39.8 41.7 40.5 41.1 41.1C40.5 41.7 39.8 42 39 42H24.45ZM20.55 32.75L18.4 30.6L23.5 25.5H6V22.5H23.4L18.3 17.4L20.45 15.25L29.25 24.05L20.55 32.75Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/logout.svg b/examples/demos/colorpaletteclient/icons/logout.svg
new file mode 100644
index 000000000..91d4fd869
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/logout.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9 42C8.2 42 7.5 41.7 6.9 41.1C6.3 40.5 6 39.8 6 39V9C6 8.2 6.3 7.5 6.9 6.9C7.5 6.3 8.2 6 9 6H23.55V9H9V39H23.55V42H9ZM33.3 32.75L31.15 30.6L36.25 25.5H18.75V22.5H36.15L31.05 17.4L33.2 15.25L42 24.05L33.3 32.75Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/ok.svg b/examples/demos/colorpaletteclient/icons/ok.svg
new file mode 100644
index 000000000..506e2d690
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/ok.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M18.9002 35.7L7.7002 24.5L9.8502 22.35L18.9002 31.4L38.1002 12.2L40.2502 14.35L18.9002 35.7Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/plus.svg b/examples/demos/colorpaletteclient/icons/plus.svg
new file mode 100644
index 000000000..81837784a
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/plus.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M22.5 38V25.5H10V22.5H22.5V10H25.5V22.5H38V25.5H25.5V38H22.5Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/qt.png b/examples/demos/colorpaletteclient/icons/qt.png
new file mode 100644
index 000000000..abd3a4f14
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/qt.png
Binary files differ
diff --git a/examples/demos/colorpaletteclient/icons/qt_attribution.json b/examples/demos/colorpaletteclient/icons/qt_attribution.json
new file mode 100644
index 000000000..44633c474
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/qt_attribution.json
@@ -0,0 +1,14 @@
+{
+ "Id": "colorpaletteclient",
+ "Name": "Selected Material Icons",
+ "QDocModule": "qtdoc",
+ "QtUsage": "Used in Color Palette Client example in QtDoc",
+ "QtParts": [
+ "examples"
+ ],
+ "Files": "close.svg delete.svg dots.svg edit.svg login.svg logout.svg ok.svg update.svg user.svg",
+ "Homepage": "https://fonts.google.com/icons",
+ "License": "Apache License Version 2.0",
+ "LicenseId": "Apache-2.0",
+ "Copyright": "Copyright 2018 Google, Inc. All Rights Reserved."
+}
diff --git a/examples/demos/colorpaletteclient/icons/testserver.png b/examples/demos/colorpaletteclient/icons/testserver.png
new file mode 100644
index 000000000..0890e5e4b
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/testserver.png
Binary files differ
diff --git a/examples/demos/colorpaletteclient/icons/update.svg b/examples/demos/colorpaletteclient/icons/update.svg
new file mode 100644
index 000000000..303ff4d3d
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/update.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M24 40C19.5667 40 15.7917 38.4417 12.675 35.325C9.55833 32.2083 8 28.4333 8 24C8 19.5667 9.55833 15.7917 12.675 12.675C15.7917 9.55833 19.5667 8 24 8C26.8333 8 29.3167 8.575 31.45 9.725C33.5833 10.875 35.4333 12.45 37 14.45V8H40V20.7H27.3V17.7H35.7C34.4333 15.7 32.8167 14.0833 30.85 12.85C28.8833 11.6167 26.6 11 24 11C20.3667 11 17.2917 12.2583 14.775 14.775C12.2583 17.2917 11 20.3667 11 24C11 27.6333 12.2583 30.7083 14.775 33.225C17.2917 35.7417 20.3667 37 24 37C26.7667 37 29.3 36.2083 31.6 34.625C33.9 33.0417 35.5 30.95 36.4 28.35H39.5C38.5333 31.85 36.6167 34.6667 33.75 36.8C30.8833 38.9333 27.6333 40 24 40Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/user.svg b/examples/demos/colorpaletteclient/icons/user.svg
new file mode 100644
index 000000000..ed782385e
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/user.svg
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0 12C0 5.37258 5.37258 0 12 0C18.6274 0 24 5.37258 24 12C24 18.6274 18.6274 24 12 24C5.37258 24 0 18.6274 0 12Z" fill="#E6E6E6"/>
+<path d="M15.5 12C16.3284 12 17 12.6716 17 13.5V14C17 15.9714 15.1405 18 12 18C8.85951 18 7 15.9714 7 14V13.5C7 12.6716 7.67157 12 8.5 12H15.5ZM15.5 13H8.5C8.22386 13 8 13.2239 8 13.5V14C8 15.4376 9.43216 17 12 17C14.5678 17 16 15.4376 16 14V13.5C16 13.2239 15.7761 13 15.5 13ZM12 5.5C13.5188 5.5 14.75 6.73122 14.75 8.25C14.75 9.76878 13.5188 11 12 11C10.4812 11 9.25 9.76878 9.25 8.25C9.25 6.73122 10.4812 5.5 12 5.5ZM12 6.5C11.0335 6.5 10.25 7.2835 10.25 8.25C10.25 9.2165 11.0335 10 12 10C12.9665 10 13.75 9.2165 13.75 8.25C13.75 7.2835 12.9665 6.5 12 6.5Z" fill="#616161"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/userMask.svg b/examples/demos/colorpaletteclient/icons/userMask.svg
new file mode 100644
index 000000000..5e3065d7f
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/userMask.svg
@@ -0,0 +1,3 @@
+<svg width="30" height="30" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg">
+<ellipse cx="15" cy="15" rx="13" ry="13" fill="black"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/main.py b/examples/demos/colorpaletteclient/main.py
new file mode 100644
index 000000000..a249b9fa2
--- /dev/null
+++ b/examples/demos/colorpaletteclient/main.py
@@ -0,0 +1,33 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 port of the Qt RESTful API client demo from Qt v6.x"""
+
+import os
+import sys
+from pathlib import Path
+
+from PySide6.QtCore import QUrl
+from PySide6.QtGui import QIcon, QGuiApplication
+from PySide6.QtQml import QQmlApplicationEngine
+
+from basiclogin import BasicLogin # noqa: F401
+from paginatedresource import PaginatedResource # noqa: F401
+from restservice import RestService # noqa: F401
+import rc_colorpaletteclient # noqa: F401
+
+if __name__ == "__main__":
+ app = QGuiApplication(sys.argv)
+ QIcon.setThemeName("colorpaletteclient")
+
+ engine = QQmlApplicationEngine()
+ app_dir = Path(__file__).parent
+ app_dir_url = QUrl.fromLocalFile(os.fspath(app_dir))
+ engine.addImportPath(os.fspath(app_dir))
+ engine.loadFromModule("ColorPalette", "Main")
+ if not engine.rootObjects():
+ sys.exit(-1)
+
+ ex = app.exec()
+ del engine
+ sys.exit(ex)
diff --git a/examples/demos/colorpaletteclient/paginatedresource.py b/examples/demos/colorpaletteclient/paginatedresource.py
new file mode 100644
index 000000000..b7f036c4e
--- /dev/null
+++ b/examples/demos/colorpaletteclient/paginatedresource.py
@@ -0,0 +1,278 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from dataclasses import dataclass
+from PySide6.QtCore import (QAbstractListModel, QByteArray,
+ QUrlQuery, Property, Signal, Slot, Qt)
+from PySide6.QtQml import QmlAnonymous, QmlElement
+
+from abstractresource import AbstractResource
+
+
+QML_IMPORT_NAME = "ColorPalette"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+totalPagesField = "total_pages"
+currentPageField = "page"
+
+
+@dataclass
+class ColorUser:
+ id: int
+ email: str
+ avatar: str # URL
+
+
+@QmlElement
+class ColorUserModel (QAbstractListModel):
+ IdRole = Qt.UserRole + 1
+ EmailRole = Qt.UserRole + 2
+ AvatarRole = Qt.UserRole + 3
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._users = []
+
+ def clear(self):
+ self.set_data([])
+
+ def set_data(self, json_list):
+ if not self._users and not json_list:
+ return
+ self.beginResetModel()
+ self._users.clear()
+ for e in json_list:
+ self._users.append(ColorUser(int(e["id"]), e["email"], e["avatar"]))
+ self.endResetModel()
+
+ def roleNames(self):
+ roles = {
+ ColorUserModel.IdRole: QByteArray(b'id'),
+ ColorUserModel.EmailRole: QByteArray(b'email'),
+ ColorUserModel.AvatarRole: QByteArray(b'avatar')
+ }
+ return roles
+
+ def rowCount(self, index):
+ return len(self._users)
+
+ def data(self, index, role):
+ if index.isValid():
+ d = self._users[index.row()]
+ if role == ColorUserModel.IdRole:
+ return d.id
+ if role == ColorUserModel.EmailRole:
+ return d.email
+ if role == ColorUserModel.AvatarRole:
+ return d.avatar
+ return None
+
+ def avatarForEmail(self, email):
+ for e in self._users:
+ if e.email == email:
+ return e.avatar
+ return ""
+
+
+@dataclass
+class Color:
+ id: int
+ color: str
+ name: str
+ pantone_value: str
+
+
+@QmlElement
+class ColorModel (QAbstractListModel):
+ IdRole = Qt.UserRole + 1
+ ColorRole = Qt.UserRole + 2
+ NameRole = Qt.UserRole + 3
+ PantoneValueRole = Qt.UserRole + 4
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._colors = []
+
+ def clear(self):
+ self.set_data([])
+
+ def set_data(self, json_list):
+ if not self._colors and not json_list:
+ return
+ self.beginResetModel()
+ self._colors.clear()
+ for e in json_list:
+ self._colors.append(Color(int(e["id"]), e["color"],
+ e["name"], e["pantone_value"]))
+ self.endResetModel()
+
+ def roleNames(self):
+ roles = {
+ ColorModel.IdRole: QByteArray(b'color_id'),
+ ColorModel.ColorRole: QByteArray(b'color'),
+ ColorModel.NameRole: QByteArray(b'name'),
+ ColorModel.PantoneValueRole: QByteArray(b'pantone_value')
+ }
+ return roles
+
+ def rowCount(self, index):
+ return len(self._colors)
+
+ def data(self, index, role):
+ if index.isValid():
+ d = self._colors[index.row()]
+ if role == ColorModel.IdRole:
+ return d.id
+ if role == ColorModel.ColorRole:
+ return d.color
+ if role == ColorModel.NameRole:
+ return d.name
+ if role == ColorModel.PantoneValueRole:
+ return d.pantone_value
+ return None
+
+
+@QmlAnonymous
+class PaginatedResource(AbstractResource):
+ """This class manages a simple paginated Crud resource,
+ where the resource is a paginated list of JSON items."""
+
+ dataUpdated = Signal()
+ pageUpdated = Signal()
+ pagesUpdated = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ # The total number of pages as reported by the server responses
+ self.m_pages = 0
+ # The default page we request if the user hasn't set otherwise
+ self.m_currentPage = 1
+ self.m_path = ""
+
+ def _clearModel(self):
+ pass
+
+ def _populateModel(self, json_list):
+ pass
+
+ @Property(str)
+ def path(self):
+ return self.m_path
+
+ @path.setter
+ def path(self, p):
+ self.m_path = p
+
+ @Property(int, notify=pagesUpdated)
+ def pages(self):
+ return self.m_pages
+
+ @Property(int, notify=pageUpdated)
+ def page(self):
+ return self.m_currentPage
+
+ @page.setter
+ def page(self, page):
+ if self.m_currentPage == page or page < 1:
+ return
+ self.m_currentPage = page
+ self.pageUpdated.emit()
+ self.refreshCurrentPage()
+
+ @Slot()
+ def refreshCurrentPage(self):
+ query = QUrlQuery()
+ query.addQueryItem("page", str(self.m_currentPage))
+ request = self.m_api.createRequest(self.m_path, query)
+ self.m_manager.get(request, self, self.refreshCurrentPageReply)
+
+ def refreshCurrentPageReply(self, reply):
+ if not reply.isSuccess():
+ print("PaginatedResource: ", reply.errorString(), file=sys.stderr)
+ (json, error) = reply.readJson()
+ if json:
+ self.refreshRequestFinished(json)
+ else:
+ self.refreshRequestFailed()
+
+ def refreshRequestFinished(self, json):
+ json_object = json.object()
+ self._populateModel(json_object["data"])
+ self.m_pages = int(json_object[totalPagesField])
+ self.m_currentPage = int(json_object[currentPageField])
+ self.pageUpdated.emit()
+ self.pagesUpdated.emit()
+ self.dataUpdated.emit()
+
+ def refreshRequestFailed(self):
+ if self.m_currentPage != 1:
+ # A failed refresh. If we weren't on page 1, try that.
+ # Last resource on currentPage might have been deleted, causing a failure
+ self.setPage(1)
+ else:
+ # Refresh failed and we we're already on page 1 => clear data
+ self.m_pages = 0
+ self.pagesUpdated.emit()
+ self._clearModel()
+ self.dataUpdated.emit()
+
+ @Slot("QVariantMap", int)
+ def update(self, data, id):
+ request = self.m_api.createRequest(f"{self.m_path}/{id}")
+ self.m_manager.put(request, self, self.updateReply)
+
+ def updateReply(self, reply):
+ if reply.isSuccess():
+ self.refreshCurrentPage()
+
+ @Slot("QVariantMap")
+ def add(self, data):
+ request = self.m_api.createRequest(self.m_path)
+ self.m_manager.post(request, data, self, self.updateReply)
+
+ @Slot(int)
+ def remove(self, id):
+ request = self.m_api.createRequest(f"{self.m_path}/{id}")
+ self.m_manager.deleteResource(request, self, self.updateReply)
+
+
+@QmlElement
+class PaginatedColorUsersResource(PaginatedResource):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.m_model = ColorUserModel(self)
+
+ @Property(ColorUserModel, constant=True)
+ def model(self):
+ return self.m_model
+
+ def _clearModel(self):
+ self.m_model.clear()
+
+ def _populateModel(self, json_list):
+ self.m_model.set_data(json_list)
+
+ @Slot(str, result=str)
+ def avatarForEmail(self, email):
+ return self.m_model.avatarForEmail(email)
+
+
+@QmlElement
+class PaginatedColorsResource(PaginatedResource):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.m_model = ColorModel(self)
+
+ @Property(ColorModel, constant=True)
+ def model(self):
+ return self.m_model
+
+ def _clearModel(self):
+ self.m_model.clear()
+
+ def _populateModel(self, json_list):
+ self.m_model.set_data(json_list)
diff --git a/examples/demos/colorpaletteclient/rc_colorpaletteclient.py b/examples/demos/colorpaletteclient/rc_colorpaletteclient.py
new file mode 100644
index 000000000..74b3eaf13
--- /dev/null
+++ b/examples/demos/colorpaletteclient/rc_colorpaletteclient.py
@@ -0,0 +1,1098 @@
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 6.7.0
+# WARNING! All changes made in this file will be lost!
+
+from PySide6 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x00\xc0\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M22.5 38\
+V25.5H10V22.5H22\
+.5V10H25.5V22.5H\
+38V25.5H25.5V38H\
+22.5Z\x22 fill=\x22#66\
+7085\x22/>\x0a</svg>\x0a\
+\x00\x00\x00\x94\
+<\
+svg width=\x2230\x22 h\
+eight=\x2230\x22 viewB\
+ox=\x220 0 30 30\x22 x\
+mlns=\x22http://www\
+.w3.org/2000/svg\
+\x22>\x0a<ellipse cx=\x22\
+15\x22 cy=\x2215\x22 rx=\x22\
+13\x22 ry=\x2213\x22 fill\
+=\x22black\x22/>\x0a</svg\
+>\x0a\x0a\
+\x00\x00\x0b\x93\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00d\x00\x00\x00H\x08\x06\x00\x00\x00\x00\x8cP\x19\
+\x00\x00\x01\x84iCCPICC prof\
+ile\x00\x00(\x91}\x91=H\xc3@\x1c\xc5_\
+S\xa5*-\x0ev\x90\xe2\x90\xa1:Y\x10\x15q\xd4\
+*\x14\xa1B\xa8\x15Zu0\xb9\xf4\x0b\x9a\x18\x92\x14\
+\x17G\xc1\xb5\xe0\xe0\xc7b\xd5\xc1\xc5YW\x07WA\
+\x10\xfc\x00quqRt\x91\x12\xff\x97\x14Z\xc4x\
+p\xdc\x8fw\xf7\x1ew\xef\x00\xa1Qe\x9a\xd55\x06\
+h\xbamfRI1\x97_\x11C\xaf\x08#\x82^\
+\xc4\x11\x93\x99e\xccJR\x1a\xbe\xe3\xeb\x1e\x01\xbe\xde\
+%x\x96\xff\xb9?GD-X\x0c\x08\x88\xc43\xcc\
+0m\xe2u\xe2\xa9M\xdb\xe0\xbcO\x1ceeY%\
+>'\x1e5\xe9\x82\xc4\x8f\x5cW<~\xe3\x5crY\
+\xe0\x99Q3\x9b\x99#\x8e\x12\x8b\xa5\x0eV:\x98\x95\
+M\x8dx\x928\xaej:\xe5\x0b9\x8fU\xce[\x9c\
+\xb5j\x8d\xb5\xee\xc9_\x18.\xe8\xcbK\x5c\xa79\x84\
+\x14\x16\xb0\x08\x09\x22\x14\xd4PA\x156\x12\xb4\xea\xa4\
+X\xc8\xd0~\xd2\xc7\x1fs\xfd\x12\xb9\x14rU\xc0\xc8\
+1\x8f\x0dh\x90]?\xf8\x1f\xfc\xee\xd6*N\x8c{\
+I\xe1$\xd0\xfd\xe28\x1f\xc3@h\x17h\xd6\x1d\xe7\
+\xfb\xd8q\x9a'@\xf0\x19\xb8\xd2\xdb\xfe\x8d\x060\xfd\
+Iz\xbd\xad\xc5\x8f\x80\xfem\xe0\xe2\xba\xad){\xc0\
+\xe5\x0e0\xf8d\xc8\xa6\xecJA\x9aB\xb1\x08\xbc\x9f\
+\xd17\xe5\x81\x81[\xa0o\xd5\xeb\xad\xb5\x8f\xd3\x07 \
+K]\xa5o\x80\x83C`\xa4D\xd9k>\xef\xee\xe9\
+\xec\xed\xdf3\xad\xfe~\x00a\xaer\xa0\xbc\xa9O\xc0\
+\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\
+\xa7\x93\x00\x00\x00\x09pHYs\x00\x00.#\x00\x00\
+.#\x01x\xa5?v\x00\x00\x00\x07tIME\x07\
+\xe7\x0c\x0d\x09$9Q3\xe6g\x00\x00\x00\x19tE\
+XtComment\x00Create\
+d with GIMPW\x81\x0e\x17\x00\
+\x00\x09kIDATx\xda\xed]ilT\xd7\x19\
+=\xe7\xbe\x99\xb1\xcd\x1aC\xc0\xac\xb6\xb1\x81\xda\x1eC\
+T\x11\x10\xa4i\x135\x91\x12E\xa4KT\x14\xda&\
+$]\x14W(\xa9\x02\x81\x006\x84Q\xf0B\x96\x06\
+*D\xd5\xf6\x17\x0a\xaa\xda\x84F)R\xc2\xa2$\x85\
+&M\xd3\x85%\xc2cV\x8f1aKR\xb0qX\
+\xec\x99y\xef~\xfdAAN\xeb\xfb\xbc\xf0f\xf0x\
+\xe6\xfe\x9b\xf9\xde\xdc\xef\xbe{\xde\xf9\xb6{\xef\x1b\xa2\
+\x87\xad\xbc\xf9\x85Rh{'\x80\xf1\xc8\xb4\x844\x81\
+\x08{\x04F\xa4\xfav\xa1\xbcA\xa8\xfc\xcc\xb4%\x12\
+\x10\xe8n\x01\x09\x9e\xa8-\xa3-;AL\xc8LY\
+\xe2\x01Q\xdd1\x03\x8e\xb3=\x03F\xf2\x9a\x91!e\
+\xc7\xd6\x06\x95\xa5w\x00\x92\x01\xe3f3$\x18y~\
+&\xad\xf8\xb6\x0c\x18\xfd\x80!e'\xd6\x06\x95\xed\xec\
+\x043\xd1\xd4MgH0\xf2\xfcL\xda\xf6\xf6\x0c\x18\
+\xfd\x80!\xa5\x8d5\xe5\x16\xb1#\x03F?`H0\
+R7SQ\xb6e\xc0\xe8\x07\x0c)\xfd\xa4\xa6\x5c\xd9\
+\xd8I`\xdcM}:D\x84\x8a\xed\x10DE\xc4\x01\
+\x00\x92>\x00\xd9\x02\xc9\x22\xc8t`\x88O\xd9\xfam\
+B\xdd\x0c0: \xd8\x01\xca\x87\x8e\xb6\x22>\xb2\xd1\
+\xa6s\xae\xfdB\xbc}\xd8\xd8\xc1q\x00\xf8\xe2\xece\
+\x7fN\xae?'\x00=&\xae\xd5d\x05N\x15p6\
+!w\x01\x18:\x10A\xf1\x11jB\x12i\x00\x90{\
+\x04\xd8t\x99\xf1\xdf5\x17\x85.\xf4\xe0W\x17\x00\x9c\
+\x05\xb0\xffz\xc2\xdaT\x93G\xe1\x0f5\xf4\xcfHN\
+\x19P&\xab\xbc\xa9\xc6\x01\xa0\x92\x10=\xb4A\xb8(\
+\x90\x13{m\xef\xb8\xd0\x15/\xfa\x9c|\xec\xa9\xac,\
+k\xecRB\x9e\x060r \x98\xac\xe4\x00B\x1c\x12\
+\xc5\xef5\x14T\x1eLD\xf7\xc1\xc6\xda\xafQ\xc9\xab\
+\x00\x8az9\x03\xbfQ>l\xe9J\xe4h\xf5\x14E\
+\x7f;\xe9>$\x09Z\xc2v4\xfe\xe0\xe1\xd2Ps\
+\xa2T4L\xae\xfc\xb04\xb2\xe6~K\xa9w (\
+\xe8\xf9\xd0\xe4\xf0\x81\x82\x95\xefu]\xad\xa8y\x087\
+!\x8cP\x09\x06\xe3\x1c\xfc\xbe\x1f%\x12\x8ck\xedP\
+\xf1\xaacZ\xe1'W\x1dU\x0a;\xf5\xc4Z*\xd9\
+P\x9f\xbflOw\xd7\x956U\x17(\x8d\x87I\x94\
+\x80\x9c\x00\x91\x5c\x90\x1a@\xab\x10'\xe8H\x03rr\
+\xde\x0c\x8f\x7f\xe6\xa4[?\x07\x0b\xaa\xde\x9bv\xbc\xfa\
+%\x11<\x9b\x01\xe4\xff\xd1\x88\x5c\xa4\xfd\xa2\xdb%e\
+\x91\xd5\xf9\x14\xff*\x80?\xa6\xea\xc4\xd6N)\x07\x05\
+\x80\x22$\xda\xb1\xa6<R\xf3{;v\xa9\xeapi\
+\xddyS\x9f1\xcb\xffK\xbf\x1d\x7f\x14\xe0\xd8T\x04\
+$a&K\x93\xbfj\x9e\x14\xea0\xc9o;V\xf7\
+U\xa5\xb2vQ\xf1\xa7\xec\xc18\x08\x0c\x03Qae\
+\x0d\xf9\xdb\xb4\xa3!\xa3\xf3>\x92\xbf\xec\x8ch\xbc\x91\
+\xaa\x0cI\x0c \xe4yZ\xd9\x9bL\xe2\xf2\xa6\xf5y\
+\xb6\x92\xcd\x10)\xea=\xf10U\xfc\xfe\xcd\x08\x85\x8c\
+c\x8f_\xb17^\x0dZ2\x80\xfc\xd7\x99\xebm\x0d\
+\x13\x17\xb7\x98\xf1\xbaRAJ\xf0\x06\x82\x85;\xca\x17\
+\xf8\x17\x99\xc4G\xa7\x87\x0e\x83\xf8G\x06\x90k\x13\xae\
+\xf5\x9fM\xb2\x92C+F\x8a\xc8\x93\x1e\xa8y\xa4\xac\
+!\x140\x9aL-[3N\xfd*\x1cq\xfa\xf8\x17\
+\xa3B\xdf\xe0\x07\x00\x8c\xf2\x80\x86\xb7I\x8eo6\x80\
+\xf7\xbb\x1c\x85e\xfd\x13Z\x0b:/\xc2i\xfc\x09\xc4\
+\xc7\xd7>Z\x1a\x7f7>\xa9\x16\xdf\x16G>7j\
+W\x14\x8a~\x04\xf0\xb6t\x93\x00@t\xd3C\x05\xce\
+\x89\x03&\xb1\x85\xbb\xbcrT\x8ab\x04$\x1e\xf5\x1f\
+\x08\xf8;\xda\x00\xder}\x12\xfd\xd6\x9b\x0d\x05\xcb_\
+\xedI\xef\xf5\x85\x95\xdb\x00ls\xad\x104U\xcf!\
+0\xa5_\x9b,R\xed\x0b1\xa4]\xae\xb8\xd3\xbb\xc8\
+\x9a\xb3\x8d~d\xea\x92\xf3\x00\x1b\xd3\xde\x87h\xc8'\
+&\xd9\xf4OC\xa3\x05\xe2\xd9\x13%\x22s\xdc\xb2R\
+\x11}4\xed\x01\x11\x91SFY{\xa0\x94\xa0\xf2\x8e\
+\x8d\xcc\x9bu\xa8\xd6\x5c\xe5U\xea\xb3\xb4\x07\xc4\xd2f\
+@\xe8\xa0\xd0[\xf4\xc1+\x01=\xc9\xa8O\xe4\x5c\xda\
+GY\x9a\xbe\xd3\xc6\xf9\xf3#\x0f\x8e\xe7\x9c\xcc\x07`\
+\xa8\x97\xa9\xcf:\xe7\x87t\xf4\xbc`\xa4\xfaz\xfe#\
+\xd4o\x1d,z\xee\x83.\x93\xd7\xc6\xba\xef\x0a\x9d\xd9\
+\xdd\xf8\xcb\xa9^\xd72\xbd\x8f\xb2\x02h5\x82e\xcb\
+\x08\xafW\xc6\x85j\x84\x0b\x83Z\xbf\x5cB\x97\xb9$\
+\xe7v\x8a]\xcf\x02\xf8\xa0\xeb~\xf5\xbd$\x17vc\
+\x9fS\xc0\x87\xd8\xd1K.\xda\x02\xdeW\x05d\x88Q\
+\x9dO\xda\xd2\xdc\x87\x88\x040\xbc\xc3E\xec\xf7\x1c\x0f\
+ \xdbl?}\x97\xd3\x1a\x10\x01\xe5\x92\xdd\xe6\x96\x83\
+x\xbe\x06G*\xa3\xd9\xb5\xe3\x8e\xa49C2\xad_\
+\x01B\x08\x87\xf8\xdc\xfaL\x80\x17\x14\x89\x1bo.\xa0\
+\xac4g\x08\x19s\xb3\xe9.\x93\xd7\xf7\xca\x806\xfb\
+,mv\xf8\xe9\x93\xa9kk\xa8\x99A\xaa\xc3\xf3D\
+JY_\x98}\x9a=,\xed\x01\xf1Y\xd6\xad.6\
+\xad\xd5\xfb[\xd0-.\xa3\xb9%\xc3\x10\xcaD\x17\xf1\
+Y\xaf\xf5\xd96O\x9a\xf1\xd7y\x99(\xcb\x11\xe3\xd1\
+ij\xe7\x90\xb7a\xb68\x83\x06\xc7\x8d\xd5e\x0ao\
+\xcd\x00\x224n\xde\x0e\x0c\xca;\x04 \xe6]T\x87\
+S{\xc7\xae>o\x1e\x8a\x1e\x9f\xf6\x80\x08\xcd\x15\xdd\
+\xbd\xe3*\xae@\xe4\x80\x87q\xf6>\x90]\x87\xd2\x12\
+R\x04K\xd3\x1e\x10\x12\xe5\x85\xc7C\xd9.\x80\xed\xf0\
+L\x97\xa6qgIY\xf3\xa0\xd1\x00\x8a3&K$\
+?K0\xd5\x05\xb1\xbfz\xe4@lZx\xc7\x18_\
+\x89=K\x80\x9c\xbe\xa3-z`\x00\x02\xfa\x03\xf0\x1b\
+\xd7\x11\xa2\xf6\xa7\xbb\xa9e\xbf\x07\xd1\xdc\xbb\x07\x0a*\
+\xf7\xbb\xc8\xef\xe8\x9e\xcdb\xb9\x00~e\x80\x00\x02\x80\
+\xf8\x8eI\xd48eCT,\xeb\x85\x1b\x84#F\x91\
+\x8d&\xffQ&\xa1\x80\x80\xdd\x9e\xed\xd0B\xbf\x8b\xe9\
+m\x1b0\x80\x88\xe0\xbe\x92\xb3uF\xe7\x1e\x9e\xb4\xe2\
+5\x81\xbc\xd2wk\xc5\x17\xc2\xc5\xab\xde2\xdeTS\
+`.DJ\xba\xbfy\x0e3\x97dxn\xe00\x04\
+P\xbev\xbd\xd8\xed\x82\xecI\xa3\x96\x8b\xc8\x22\x11\xb9\
+\xd8\x0b\xea}\x0e\x8d\x9f7\x14U=g\xbc$\x14R\
+\xa0<\xdeC\xb3g<\xec\xaa\xc4\xb7\x0f\x80=P\x00\
+\x01\xc0\x87\xbf\xd2\xbc\xc6\xb8\x01a/+\xe2\x0d\xc5+\
+\xd7S\x10\xa4\xe23B\xd9!W7%H\xa7\xc4O\
+@i\x01e\x1b\xa1\x9f\xd4\xa2f\x86'Wmp\xd3\
+\x1a|\xd4?\x1f\x90\xb9=\xb3\xac,7\xb2\xb8x\xd9\
+\x1e\x00\x95\xc0\x97\xcb=\x22\x22\x00\xa2\x22\xb8\xe8eN\
+u}L\x09=c(\xfcC\xb8\xb8\xf2\xfb\xbd\xb0u\
+\xaa\xe4t].cz\x88\x95\x95\xa3y>v\xa9~\
+Z\xb4\x0d\xae\x1b\xef:\xf9\x8e\xc8\xea|K\x05v\x8b\
+`R\xcfL\x1f:Z\xb3\xe3#\xcf\xb8\x1cB\x9ds\
+rQN\x8b3b\x8c\xdf\x1ed\xfb%\xda\x11\x8d\xc5\
+\xda\x01\xc4\x0en\x81]\xbe\xc0\xbf\x0b\xc07\xbc\xab<\
+$\xe1\xd0\xa7\x08\x167\x14W\xadK4\xd5g\x9c\x09\
+\x0d\x8av\xf8\xdf\x050\xa7w&B\xcf;P\xb4\xea\
+\x8f}\xd1\x19l\xaa\xddJ\xc8\xb7\xbc\x04$\xf1\xc7\xa1\
+\x89\x9a\xe9\xc7\xea\x16&RGis\xcd\xd8h\xbbo\
+ko\xc1\x00\x00\x07j\xfe\x0d\xa4*\x17\xbd\xbe\x97d\
+,\xe1\xe6hKo\x9c\x16\xa9]7\xbd\xf1\xa5\xd1^\
+w^r\xaav\xa4%\xb2\x1d\xe4\xbd}\x8b\xd0\xf1`\
+\xb0y\xcd7\xfb6{LI@\xaeE4Ok+\
+\xf6Q0R\xbdp\xc6\x99\x90gU\xd8\xc3\xe3W\xb4\
+@X\x7f\x03]\x04 \xd6\xe6\xe9\xc7\xeb\xee\xef\xf5/\
+\xb5\x8e\xa6\x96S7\xb7\x16@\xb6\x13|\x1fb}\xe4\
+\x14u\x1c9\xc8\x909b\x91y\x16\xb8\xc5\xb8\xe7\xb1\
+\xbc\xa9&\x0f\x90\x8f\x01\x8e\xe9{.\x8bvM\xbe\xae\
+\xb4\xf3\xfa\xf9\x9c\xd1\xbb\xcf\x8c\xab\xe8\xd2\xd1\xdf\xbd\xeb\
+n\xdf\xbf'\xdeWH\x89\xcf\x86\xa5*\xbc\xdc\xcd\x9f\
+\xbc79t?\x92\x8b\xa04\x0bp\x9a\x1am\xa2\xd0\
+\x01R(\xcc!0N(A\x08\xe6\x87\x8b\xaa\x8c\xb5\
+\xab\xe0\xf1\xea\xc7(\xdc\xe4\xd1\x88.\x028,\x82\x93\
+\x10}\x89\xca\x22\x94\xce\x15\x07\x93\x14Y(\xc0\xe0\xc4\
+LC\x7f\x01\xa4g\xcf\xf0\xbf\xe2g.\xdcs\xe4\xce\
+\x17\xbb\xb4\xdb3\xf6<\xe1\xef\xc8\xcd\xdfN\xf2\x1e\xa4\
+hKJ\x94\xe5\xe1pg\xfa\xf2\x86/0&\x9a\xb7\
+\xff6\xaeb\xbeE\x90\xe4g\xd7)\xe9\xd4=\xe1\x88\
+\x85\xba\x92\xe3\xa1B\x93\xbc\xbety=\xc8\xd5\x19@\
+\x92\xd7\x86\xfa$\xb0\xd6\x95G\xed\xb1_\x03\x08\xa7\xa2\
+\xb9\x22\xb8,\x05\xb7\x92\xca\xbci\xcd\xb5\x0f\x98\xa4\x0d\
+\xc1P\x8b\x80U\xa9\xf4\x12\x1a\x11\x11\xa5di\xb8\xa8\
+\xf2\xe5T\xdc\xdb\xabD\xcb\xcb3\xf6<\xd1\xe5Z\xc6\
+\xe4c\xb5\xa3D\xcb\x18\x08/\xa5\x06\x18\xd0\xb4\xac\xa5\
+\xf5\x85+_\x01\x12\xfc6\xa0DVKb\xb9\x05k\
+\x00,\xbf\xf6\xc5\xacS\xb5#\xdb\xa3\xfaY\x81\xfc \
+U\xdeU/\x22\x02\xc5%\xe1\xc2\x15\xebnvb\xe8\
+Ek\xd5\x96\xf5\xf5\xac\x98\x16[\xe11!*\x00\x19\
+\x9e:>C\x1c\x82\xcb\xc2EU\xbf\xe8\xfc}\xaa2\
+\x04\x00ri\xdb[c\x8a\xa3\xc9\xd4zC\xe9\xd55\
+\x15.\x0dwQ\x05Oe@@2\xe5\xb6\xf9\x08\xc4\
+!\xb9,\x5c\xd4\xf5\x92DJ\x03\x92r`\xb80#\
+U\xf3\x90\xd4\x05\x03\xe2(\xa8%\xdd-\xd6e\x18\x92\
+4f`I}q\xe5\xfa\x81\x96\xa9\xa7(3\xb0\xa4\
+\xa1x\xe5\xfa\x9e\x5c\xef\x03\xe4q\xe9\xc1\x9f\x83eZ\
+\x1f\x03\x0f\x9b-\xf5S\xcd{\xc8\xfe\xb7\xfd\x07:\xcc\
+\xccF\x8ay\xc7t\x00\x00\x00\x00IEND\xaeB\
+`\x82\
+\x00\x00\x02\xed\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M24 40C1\
+9.5667 40 15.791\
+7 38.4417 12.675\
+ 35.325C9.55833 \
+32.2083 8 28.433\
+3 8 24C8 19.5667\
+ 9.55833 15.7917\
+ 12.675 12.675C1\
+5.7917 9.55833 1\
+9.5667 8 24 8C26\
+.8333 8 29.3167 \
+8.575 31.45 9.72\
+5C33.5833 10.875\
+ 35.4333 12.45 3\
+7 14.45V8H40V20.\
+7H27.3V17.7H35.7\
+C34.4333 15.7 32\
+.8167 14.0833 30\
+.85 12.85C28.883\
+3 11.6167 26.6 1\
+1 24 11C20.3667 \
+11 17.2917 12.25\
+83 14.775 14.775\
+C12.2583 17.2917\
+ 11 20.3667 11 2\
+4C11 27.6333 12.\
+2583 30.7083 14.\
+775 33.225C17.29\
+17 35.7417 20.36\
+67 37 24 37C26.7\
+667 37 29.3 36.2\
+083 31.6 34.625C\
+33.9 33.0417 35.\
+5 30.95 36.4 28.\
+35H39.5C38.5333 \
+31.85 36.6167 34\
+.6667 33.75 36.8\
+C30.8833 38.9333\
+ 27.6333 40 24 4\
+0Z\x22 fill=\x22#66708\
+5\x22/>\x0a</svg>\x0a\
+\x00\x00\x01\xb3\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M13.05 4\
+2C12.225 42 11.5\
+187 41.7062 10.9\
+313 41.1188C10.3\
+438 40.5312 10.0\
+5 39.825 10.05 3\
+9V10.5H8V7.5H17.\
+4V6H30.6V7.5H40V\
+10.5H37.95V39C37\
+.95 39.8 37.65 4\
+0.5 37.05 41.1C3\
+6.45 41.7 35.75 \
+42 34.95 42H13.0\
+5ZM34.95 10.5H13\
+.05V39H34.95V10.\
+5ZM18.35 34.7H21\
+.35V14.75H18.35V\
+34.7ZM26.65 34.7\
+H29.65V14.75H26.\
+65V34.7Z\x22 fill=\x22\
+#667085\x22/>\x0a</svg\
+>\x0a\
+\x00\x00\x01\xf7\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M9 39H11\
+.2L33.35 16.85L3\
+1.15 14.65L9 36.\
+8V39ZM39.7 14.7L\
+33.3 8.29998L35.\
+4 6.19998C35.966\
+7 5.63331 36.666\
+7 5.34998 37.5 5\
+.34998C38.3333 5\
+.34998 39.0333 5\
+.63331 39.6 6.19\
+998L41.8 8.39998\
+C42.3667 8.96664\
+ 42.65 9.66664 4\
+2.65 10.5C42.65 \
+11.3333 42.3667 \
+12.0333 41.8 12.\
+6L39.7 14.7ZM37.\
+6 16.8L12.4 42H6\
+V35.6L31.2 10.4L\
+37.6 16.8ZM32.25\
+ 15.75L31.15 14.\
+65L33.35 16.85L3\
+2.25 15.75Z\x22 fil\
+l=\x22#667085\x22/>\x0a</\
+svg>\x0a\x0a\
+\x00\x00\x1a\x93\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00J\x00\x00\x00H\x08\x06\x00\x00\x00Q\x18cz\
+\x00\x00\x01\x85iCCPICC prof\
+ile\x00\x00(\x91}\x91=H\xc3@\x18\x86\xdf\
+\xa6\x8a\x22-\x0ev\x10q\xc8P\x1d\xa4\x05Q\x11G\
+\xadB\x11*\x84Z\xa1U\x07\x93K\xff\xa0IC\x92\
+\xe2\xe2(\xb8\x16\x1c\xfcY\xac:\xb88\xeb\xea\xe0*\
+\x08\x82? \xae.N\x8a.R\xe2wI\xa1E\x8c\
+w\x1c\xf7\xf0\xde\xf7\xbe\xdc}\x07\x08\x8d\x0a\xd3\xac\xae\
+q@\xd3m3\x9dL\x88\xd9\xdc\xaa\xd8\xf3\x8a\x10\xc2\
+4\xc7\x10\x93\x99e\xccIR\x0a\xbe\xe3\xeb\x1e\x01\xbe\
+\xdf\xc5y\x96\x7f\xdd\x9f#\xac\xe6-\x06\x04D\xe2Y\
+f\x986\xf1\x06\xf1\xf4\xa6mp\xde'\x8e\xb0\x92\xac\
+\x12\x9f\x13\xc7L\xba \xf1#\xd7\x15\x8f\xdf8\x17]\
+\x16xf\xc4\xcc\xa4\xe7\x89#\xc4b\xb1\x83\x95\x0ef\
+%S#\x9e\x22\x8e\xaa\x9aN\xf9B\xd6c\x95\xf3\x16\
+g\xadRc\xad{\xf2\x17\x86\xf2\xfa\xca2\xd7i\x0d\
+#\x89E,A\x82\x08\x055\x94Q\x81\x8d8\xed:\
+)\x16\xd2t\x9e\xf0\xf1\x0f\xb9~\x89\x5c\x0a\xb9\xca`\
+\xe4X@\x15\x1ad\xd7\x0f\xfe\x07\xbf{k\x15&'\
+\xbc\xa4P\x02\xe8~q\x9c\x8f\x11\xa0g\x17h\xd6\x1d\
+\xe7\xfb\xd8q\x9a'@\xf0\x19\xb8\xd2\xdb\xfej\x03\x98\
+\xf9$\xbd\xde\xd6\xa2G@\xff6pq\xdd\xd6\x94=\
+\xe0r\x07\x18|2dSv\xa5 -\xa1P\x00\xde\
+\xcf\xe8\x9br\xc0\xc0-\xd0\xb7\xe6\xf5\xadu\x8e\xd3\x07\
+ C\xbdJ\xdd\x00\x07\x87\xc0h\x91\xb2\xd7}\xde\xdd\
+\xdb\xd9\xb7\x7fkZ\xfd\xfb\x01\xa7vr\xbc\xf6x\x14\
+.\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\
+\xbd\xa7\x93\x00\x00\x00\x09pHYs\x00\x00.#\x00\
+\x00.#\x01x\xa5?v\x00\x00\x00\x07tIME\
+\x07\xe7\x0c\x0d\x0d+\x19\xea\xccr\xbc\x00\x00\x00\x19t\
+EXtComment\x00Creat\
+ed with GIMPW\x81\x0e\x17\
+\x00\x00\x18jIDATx\xda\xed[y`T\xd5\
+\xd5\xff\xdd\xfb\xde\xac\x99}\xb2/da\x93E@P\
+\xb1\xdaZZ\xfd\x94-\xc8\xe2\x04\x14\xb7JK\xe5\xf3\
+\x0b&\x80\x88\xd5\xb6Q\x8b~\x22\x90\x80b-\xa2b\
+QI\x18\x11H \x91\x8a\x15k\xddQPY\x0ck\
+\x12\xb2'\x93\x99\xc9\xccd\x96\xf7\xde\xbd\xdf\x1f\x81@\
+\x08\x89Qh\xc1\xaf\x9c?g\xee\xbcw\xde\xef\x9d\xfb\
+;\xe7\xfc\xce\x1d\xe0\x92]\xb2Kv\xc9.Y\xaf\xcd\
+\x91\xb7O\xedpl\x10.6\xbf\xc8\xc5\xe2\xc8\xf4\xe7\
+\xf8H@~\x82s\x0c\x03\xe1!\x0a\xfa1\x04\xe9\x0f\
+\x85st\x15\x97\x80\x02\xe0X\xe2\x8f\xa7:\xed\xef\x08\
+\xf8o8\xa0=\xe3\xebVp\xaca\xb2\xb8\xd89\x8f\
+\xb4\xfcG\x02\xe5\xd8\xc0\x05\xda \xfd\x0f\x07\xfd=\x01\
+\xb7\xf7\xec%\xa9\x00g\x8f\x17e\xab_\xf9\x8f\x02j\
+\xc6Jy\x02'\xfc\x7f\x01\x0c\xfd>\xbf\xe3\xe0\xdf\x10\
+\x8aYE\xf7\xab?\xff\x7f\x0d\xd4\xa4\xa7\x03\x89:\xbd\
+\xfae\x007\x9f\xcbu8\xe7\xaf\x8bT\xf5\xc8\x1b\xff\
+C*\xff_\x015\xe9in\xd4F\xc9\x8f\x10\x86\x5c\
+\x10\xa8\xbb[\xa7\x84$\xb8\x0f\xd6B\xd0\xa9aJ\x8d\
+\x86\xa0V\xf5t\xd9V\x0e\xb6\xbc\xd1\xa5^\xbc3\x8f\
+\xc8?z\xa0\xb2V\x86o#Dx\x0e\xe0\xb6n#\
+Da\xf0V6\xc1_\xed\x02g\x1c\x00@U\x14\xa6\
+\x94\x18\x18R\xec \x84\xf4\xc8_DQ\xe6\x15>\xa0\
+\xd9\xf4\xa3\x04\xea\xd6\x95\xd2\xcfD\x82\xa5\x1c\xb8\xba\x87\
+-\x84`S+\xdc\x07\xeb\xc0d\xe5\xackD\xad\x1a\
+\x96~q\xd0E\x9b\xbekC~\xaa\x12Tw\xbf\xf6\
+\xdf\xa4\xfcG\x01\xd4\xac5\xdc\xe6o\x93\x0a@\xc8\x9d\
+=\xad\x0b{\xdb\xe0>T\x0f\xc9\x1f\xec\xd5u5\x96\
+(\xd8.K\x84\xa8U\x7f\x17\xe1\xbf\xc0\x15\xd5S\xce\
+\x1cRuQ\x025s%7)T\x9e\xc3\x81\x05\xe0\
+\x88\xee\x96\x87\x22\x12Z+\x9b\xe1\xaf\xf9ae\x911\
+\xd9\x0eC\xb2\x1d\xa2\xb6g\xfe\x22\x9c,PY\x847\
+\xd6\xddE\x02\x17\x0dP\x8e\x15\xd2X\x22`\x09\xe1\xb8\
+\xbc\xdb7-3\xf8j\x5ch\xadl\xea\xe0\xa1\x1fj\
+\x82F\x05sZ,\xa2\xe2-=?\x01A9!\xe4\
+\xd1\xc2\xfb\xc57/8P\xb3\xd7*s\xbd>\xb6\xa2\
+\xa75A\x97\x0f\xde\xa3\x8d\x90\x02\xa1\xf3\xba\xcdU\x06\
+-,\x19q\xd0\xda\x0c=p=`\x8c\xa2\x8b^\xfc\
+\x95\xf0\xf4\xb9\xdc\x8b\x9e\xab\xb3\xdc\xef\xbf\x22%^\x80\
+^wv\xcc\x99\xac\xa0\xad\xa9\xf5\xbc\x83\x04\x00\x92?\
+\x04_\x8d\x0b\xbc\x9bD\xa0\xd3\x12$\xc4P\xc0\xe7\x1b\
+p\xae\xf7\x12\xcf\xf5\x02\xfb\xfe~\x14q\xe96)\xed\
+\x8ax\xc1\xa0\x17\xa9\xdb\xc3 \x9d\xe67\x15\x05\xd8/\
+K\x82!\xc1\x0a\xcf\x91zDZ\x83\xe7'\x9a\xa2\xb4\
+\xb0\xf4\x8b\x87\xd6\x1a\xd5\xf5\xa1\x04\xc0f\xa6\xa0R\x84\
+\x1d\xfe\xb0\x96\xb9\xaa[\xc9\x05\x07\x0a h\xa8p\xab\
+\x9a\xab[\xa5\xb8t\x0bRG$ (\x11\xea\xf2\xb0\
+\xceY\xcb\xacG\xdc\xc8\x0c\x04\xea=\xf0\x1ci\x00\x93\
+~X\x8dHE\x0aC\xb2\x1d\xa6\x94h\x10\x81v\xe1\
+\x11\xb3\x91\xc2\xa8\xe7J\xcd\xfe&\xd4|\xdbDeI\
+\x11A\x08\xbf\xe0@q\xc2\x09\x01\x81\x22+\xaa\xdaC\
+.\xb8j[\x91:4NJ\xedkS\xb5x\x19|\
+\x81\xce>F\xc5[\xa0\xb5F\xc1_\xebFke\xd3\
+\xf7\xcbxIv\x18S\xa3!\xa8\xbb\xba\x1d\xa5#\xb0\
+[(\x9a\x8e\xb5(\x07\xf6\xd4\x139$\xd3\x19\xd7\xd7\
+`o\xa5\x11{+M\xb8\x08\x22\xea\x8c\xfa( \xe1\
+\xe0\xa7\xd5\xaa\xe3\x07\x9a\xe4!?M\x859^+6\
+63Dd\xde9k\xa5\xc7\x22*\xc1\x0a\xf7\xc1:\
+\x84Z|=^Sm\xd2\xc1\xda/\x01j\x93\xae\xeb\
+\x03\x88@\x9cM\x80\x1c\x0c\xc9{\xdf\xad\x15\xbc\x8d~\
+\xe1Z\xa1\x12aQ\xc4\xc8\x0c/v~c\x07\x01\xf8\
+E\x00\x14\xc5\xd9\xfc\x08\xb6\x86\xc5]\xa5\x07\x11\x93j\
+\x8e\xf4\x1b\x95\xa8\x0eC\x84\xdb\xcbp:\xef\x8aZ\x15\
+b\x86\xf5A\xd8\xdb\x86\x96\xf2Z\xc8m\xe1\xceWV\
+\x09\xb0\x0dL\x82\xd6n\xe8\xd2\xc6\x10\x02D[)t\
+*\x8e\xf2\x8f\xab\xe4\xe6*\x8fh\x89mC\xd2\xc00\
+\xa6T\xee\xc7\xb2\xc8O\xb1pmR\xfbZ\x8e\x8b/\
+\xa2\xce\xb4\xa6J\xaf\xba\xb9\xaa\x15\xa9\xc3b\x918(\
+\x16\xfe \xe0\xf6v\xe5\xaf\x84\xab\xfa\xc1_\xe3\x82\xb7\
+\xaa\x19\x5cV`H\xb1\xc3\x9c\x1a\x03Bi\xd7t\xaf\
+\xa7\xb0Y\x08\xea\xca]\xf8\xfa\xabz\xc8\x92\x22\x02\xc0\
+\xe8[\x8e\xe2\xc0G\x09x04\xaes\xe6\x05\xbb\xf8\
+\x81:\xd9\xd3U|\xd5\x80\xfa\xc3nd\x8cL@j\
+\x8a\x19\xcdn\x86@\x90wbbC\xb2\x1d\xfa8\x0b\
+\x00\x0e\xaa\xea\xea\x9aVC\x10k\xa3\x084\x07\xf0\xd9\
+\xa6*\x84\xdb\xa4N\xdf\xbf\xf3\xca`0\x99\x9e\xa5\x96\
+\x12\xd8\x85\x07J\x11\x9f\xe7\x824\x86\x00\xa9\xdf\xb54\
+\x14\x88`\xff\x07\x95\x88\xb2j1\xf8\xfa4XLj\
+4\xba\x14\x9c\x9e\x00\xa9J8k\xba\xb7[(\x88,\
+\xe1\xdb\x0f\xea\xe0\xaa\xf6vS\xb3\x9d\xb5,t\x83\x93\
+\xd7.\x8a\x16\xe6g\xd9\x87b\xd4\x11\xfaN\xc8\x13\x18\
+\xce\xe5\xde\xbf\xbc\xf8~v\xa4\x0f\x8bE\x88\x8b\xf0\xb6\
+v\xae\xbf\x00\x80\x12\xc0j\xa6\xd0\xab9\xaa\xbei@\
+Mys\xaf\xdb\x1f\x22P\xe8l\x86]A\x8f\xe7\x96\
+\x0f\x0bG\xd6^P\xa0&M\xcd\xb9\x8ed\xdc`\xd3\
+\xa5\x8e]\xc6\x81\xferH\x82\xf7h\x03\xda\x1a\xbd\xbd\
+w\x80\x12\xf4\xbd2\x11q\x196x\xfd\x1c\xa1\x08\xc0\
+\x19\xa0\xd5\x00V\x13Ec\x85\x1b\xc7\xf6\xd4A\x0a\xf6\
+\xbe\xee\xd2Z\xa3`\x1d\x98t\xa2q\xe6\xfb\xa5\xa3\xa5\
+\x8fJ\xc7w\x86J\xde,(\xfb\xc1\xfd\xe5\xb9\x005\
+h\xf4X_\xca\x15\xe3WP\x8dqTDj\xaf\xc2\
+\xf51&hLzH\xbe\x10\x98\xa4\xf4\x82\xc0\x80\x96\
+\x1a\x1f\x9a+\xbd0[T\xb0YE\x18t\x80\xec\x0f\
+\xe2\xdb\x0f\xabPw\xd0\x05v\x22J\x09%P\xa9E\
+0\x85u_F\xf4O\x809#\x0eTl\x7f4\x83\
+\x9e\xc6\x98-\xa6\x18_\xf9\xae%\xe5\xe5\x1fF.\xd8\
+\xd6\xbb\xf9\xd7\xdf\xbe\x9c:<a\xa6!\xce\xa8nv\
+3\x84#\xbc\x03\x80@\x83\x1b\xde\x8a&(!\xa9\xf7\
+\x0e\x11\x02\x10t\xd9b\xa2\x8a\x22q`4\x98\xc2P\
+}\xa0\xf9\x8cj]\x801\xc5\x0ec\xb2\xbd\xa3Z\xd7\
+\xaa\x09l\x16\x8a\xb0'\xc0\x8f|Q\xf7\xca\xf6\xd5\x03\
+f\xfd[\x9a\xe2\x89Y\xb9\x99\x9c\xf3.\xc0\xfaZB\
+d\xef\xcec\xea\x8a]\xc7\x11mP\x10k\xa3\x10\x84\
+\xf6W\x10\x15oE\xdc\x88t\x18\x12\xad=\xcb\xb9g\
+d\xc83A\x8a\xefgCB\x7f\x1b\xd4:\xb1\xcb\xab\
+5&\xd9\x10\x7fu_\x98Rc@\x04\x0a\x81\x021\
+V\x0a\xabNa\x15_TG\xbe\xdaq\x94\xfb\x5cm\
+g\xa7\x8e\xdbr\xc7\x8e\x1a5[u~\xb3\x1e'\xfa\
+\xcc\xacy\xaf\xdd2#\xf7\x85-\x85\xf9\x1f\x9c\xf6`\
+\x84\x10\x82\xfa#n4W\xb5\xca\xf1\xfd\xedH\x19\x1c\
+#\xb6I\x14\xeeV\x06A\xab\x82u@\x22\x8c)v\
+\xb8\x0f7 \xe4\xf2\xf5\xfa\x96\xf6d#\x0cV\x1d\xa8\
+H\xc1\x15\x8eHPF\x9b'\xdc\xa1x\x9a\xd3c\xa1\
+1\xeb;\xd6[M\x14\xe6(\xa0j_#\xaf\xf9\xb6\
+\x99&\x98\xfcj\xa3VDk[g,&e\xcd\xbf\
+^\x01\xbf\x1b2w~\xf1\xc5j\xe9\xbcr\xd4\xc1\xfd\
+\x1f\xef\x1b\xf3\xd3a%\x9e\x88\xe6\xa6\x81\x83G\xcfM\
+\xed{\xe5gG\xcb?\xf3\xa5\x0c\xb9o2\x01\x19\x01\
+\x00\x8cq\xda\xda\x14\xa0\xcdU^\x18\xcdj%!Y\
+G\x15\x05\x88H\x00U\x89\x88\x8a3C\x15\xa5\x85\xe4\
+\x0bv\xf0NO\x16\xdb\xc7\x02A-\x00\x1c\x08\xfa\x22\
+\xa8-w!\x12a\xb0\x0dJ\x82%=\xb6C\x16\xd6\
+k\x09\xe2\xec\x02B\xcd\xbe\xc8\xde\x7fT\xb0`u\xb3\
+@\xa1 \xff\xb7\xfbP^mD\x83[\xfb\xe5\xf1\x03\
+/\x14\xdf2c^J\xff!\xa3W3\x10\xbdMC\
+\xff\xe0\x5c\xbf\xfc\xc0\x0f\xe6\xa8\x89\x8e\xdc$\x86\xb0R\
+\xea|\xbe\xbe\xbb\x1f\x8d\x9d\x91\x93&*4\x1bP\x94\
+\x16vW\x02 \xdcq\xd6&6Z\x8b\x81\xa3\x93!\
+\x1a\xf4\x9d\xea%\xce\x18\x02u\x1ex\x8e5v\xab%\
+\x01@\x9f\xa1\xb1\xa0\x94\xa0j\x7f\xbb*jL\xb6\xc1\
+\xd4'\x16Dlg\x0c\x81\x02q\xd1\x02 EP\xfe\
+Q5<\x0d~P\x91\xe1\x09\xe1\x1d\xbc-\x0fDs\
+\xa2\x1d\x07\x8e\x1bA\x10*\xb2\x08\xeb?\x07H\x7fY\
+\xc6c\xdb\xdf*\xa8\xeb\xee\x9e\x93'\xe7X\x14\x9d \
+\x96\xac_\xd6\xdc#Pcg\xe4\xa4\x89\x8c\xe4\x82\x12\
+\xf7\xd6\xf5\xcb\x1e\xebI\xa2\x980}\xc1P\x9f\xf2_\
+\xaf\xcb<nX\x8f\xf5R\x7f;\xd2\x87\xc7#\xa4P\
+\xb4x\x19\xd8\x89`\x92\xc32|UM\xdd\xea\xe7\xa2\
+F\x80\x1cV\xa0\x8b5\xc3\xd6/\x1e\xf4\x84j@\x08\
+`5R\x18u\x1c\x87v\xd5\xa0\xf1\xa8\x1b\x9c\x03\xa3\
+\xc6\x1d\x83\x1c\x11 \xbc'\xe2+\x16\x0f\xe5\x04\x05\xab\
+H\xcd\xa1(\xf1ow\x96\x16\xae\xf8\xb4;\x1f\x07;\
+\x1c\xea\x0c\xd2'\x87\x83\xa5\x12\xae<\xbd\xd5\xb9\xb2\xaa\
+\x0bP\xe3o\x9f\x9f\x0a\xa6\x5c_ZX\xb0\xeeTd\
+-\xbc\x1a\x90\x9e\x86\xa0\xfcik\xe1\xcaw;\xed\xf1\
+I\x0b\x8d\x8aV\x9aJ\xb4\xd8\xe1\x0a\xdc\xb3\x98\x80\xdf\
+\xfd\x9dd\xa8\x11\x91<(\x06\xc9\x83b\xe0\xf5sx\
+|\xa7\xb6\x9e\x1c\x8c\xa0yo\x15\xa4@\xb8\x8b8g\
+\xed\x1f\x0f\x8d\xe5\x948g\xd4S\xd8\xad\x04\xf5\x87[\
+plw\x1d\xe4\xc8\xa9\x88\xec{E#*\xf7FC\
+\x96\xe8\x19\x09\x82\xbd\xa4a\x7f]`R\xd3\x89\x08y\
+\xb6\x14\x17\xbf\xdc\x89('d\xe5\xdeH\x81\xf9\x0cX\
+\xb6mC\xfe\x8eS\x810\x7f\xaa:\x12z\x7f\xd3\xa6\
+U\xae\x8e\x88\x1a\x9f\x95\x9bE9\xee\x0c3e\xde;\
+\x1bW\x1e:\xf9yfVn.\x07\x1f!F\xf0\xc7\
+\xcd\x9b\x0b*&N\x9f?\x93@\x99*+\xe4/T\
+\xe0\xd3\x82\xca\xa8)!~yL\xaf\x8bA\x83\x1a\xe9\
+#\x13`O6\xa3\xc1\xc5\x10\x0a\x9f\x0a\xd8\x90\xcb\x87\
+@\x83\x17 \x04\xfaX\x13tv\xe3\xa9\xc6Y\x0d\xc4\
+X\x05\xb4\xb5\xf8q\xf0\xd3\x1a\x04[\xc3\xbd/@\xc9\
+\xber=\xdd\xb5\x9b\x82?\xc79\x99\xc7)/\xdbZ\
+T\xb0f\x92#\xb7\x1f'\xe4I\x0e\xfew\x1d\xaf~\
+\xd9\xe9tF\x00`\xe2\xf4\x9c\x91\x9c\x93\xc7\x05NV\
+\x17;\x97\x17w\xd9zc\xc6\xe4i\x8d\xd1\xde<F\
+\x01U\x84\xbf\xb0ysA\x05\x00\xdc4eA\xacF\
+%?\xcaA\x86\x80\xe3\xb9\xad\xce\xfcMc\xc6\xe4\x89\
+;w\xe6\xc9\xe3\xb2r\xae\x8b\xb0Q\xeb\xc2HOg\
+\xdc\xd0k\xe7\xcd1Q\x18\xf4\xb3>P\x04\x15\x5c\xee\
+\xce\xfd^\xa7lC\xdb\xfb<\x15\x14\x1c\xdb]\x87\x86\
+c\xee\xefQ\x93\x85\xa1Ay\xb9\x06_\xe6\x96\xbdY\
+P\x06p\x02\x10\x9e9}\xc1-\xe0\xca\x03\x9c\xa0B\
+\x11\xcd9e\xaf\xe7\xb5\x02\x80\xc3\x91\x1d\xd3F\xc4\xf9\
+\x04\x90h\xc8\xbb\xe4\xf4\xc8#\xdd\x91\xb5\xc00\x8fp\
+ZQw\xd4\xff\xec\xe9)t\xf2\xe4\x1c\x8b\xac\xa1\x0f\
+\x83\xf3\x0cF\xe8\xf6\xd2\xc2\xa5/e=\x17\x99\xc9\x82\
+\x81\xb5\xad\xb5~\xc1_\xe3\x06\xbe\xc78*iP4\
+\xfa\x0c\x8aAP\x11\xe1\xf6\x9d\xe2/B\x00\x8b\x91\xc2\
+\xa8\xe5\xa8\xda\xd7\x88\x9a\xf2\xe6^e\xca\x93\x15|T\
+\x82\x0d\x86D\xbdG\xd4\xeb\xe7\x1fy\xf5\x81uI\xe9\
+Q\xd3\x18\xe5\xb7\x81\xd2}\xac5\xb4\xb2\xb4\xf4T\xb2\
+\x1a\x959[\x9f\xa0\x8f\x9a\x0b\xf0\xc1r\x18\x7fx\xfb\
+D\x80\xf4\xba2\x9f8c\xee\x0dP\xc4\x1c\x85\xf0\xd5\
+\xa3\x07\x9b\xb7}\xbe\xdf{'%\xb8\x81\x81?\xb5\xad\
+\xa8\xe0@fV\xcem\xa6A\x93\x17(1\xd7\x8d\xe4\
+'\xb0\x91C\x11\xb8\x0f\xd5!\xe4\xf2\xf7^\xc2P\x0b\
+\xe8;*\x11\xf1}m\x08\x8698\x07t\x1a\xc0]\
+\xef\xc7\xc1\x8f\xab\x11n\xeb}\xe7\xa1\xb1\xe8a\x1b\x90\
+\x08Q\xaf\xe9h\xac\xa9\xeb\xa3\xc3\xad\x07\x8a\x1f/)\
+\x5c\xb6n\xe2\xb4\x07\xfbC\x94\xf38\xc7\xdf\xb6m\xc8\
+\x7fuR\xd6\xbc_0\xce\xfe\x9b\x13a\xcd\xb6\x0d\xcb\
+\xb6\xff\xe0\x16&//\x8f\xee\xda\xef\x99\x03\x90\xe1\xe0\
+\xec\xdd\xad\xce\x15E\x9d\xe6z\xcfz^\xb6$\x9a~\
+\xe5\xf20\xf8\xdbNER[c+\xbcG\x1b \x87\
+z\xff\x90z\x93\x16\xd6D#\x08\x01\xbcM\x01\xf8\x9a\
+\xdb\xce\xd8\xaez\x08j\x01-5]\x8bV\x95N\x03\
+Sz\x0c\xf4\xb1\xe6S\xd7\xd3\x12\xd8\xcc\x94\xb5\xd6\xb5\
+\xbe\xf2\xe2\x03\x96_\x9f\xbe~|Vn\x16!\xb8\x9d\
+3|X\xea\xcc\x7f\xe6_\xde\xeb];u\xf7+\xe6\
+X\xc3\xed\xfd\xaeNV\x04\xbdF\xe7\xf2(\x88H'\
+\xeb%\x0e\x7fm\x0b|\xc7\x9b\xa1\x84\x7f\xf8\xc9\x9c(\
+\x8b\x16\x1a\xbd\x0a\xb6$#B\xfeH\xa7^\x8f\x88\x14\
+\xa6d;\x8c\xa91\x1dm\x92Z\x0d\xd8\xcd\x14\x92\xaf\
+\x8d\x1f\xdeU\xc7}M\x81W?z\xeb\x8a{/\xf8\
+\xb8\xca\xdb\x14P\x7fQz\x90\xc5gXy\xea\xb0X\
+\x22\x11\x15<^\x06\x09\x04\xc6d;t\xd1&\xb4V\
+4\xa2\xad\xc1\x0b\xce{\xcf_*\xad\x08\xbdI\x03[\
+\x92\x11L\xe1\xa7\x9a\xe6\x93\x11\x13k\x86)=\x16*\
+\x9d\xba\x83\xd7lf\x0a\xad\xa0\xb0\xea\xbd\xf5\xbc\xf6p\
+\x0ba\x92B/\xaaq\x158\xa7\xf5GZ\xe0\xae\xf5\
+)\x89\x03\xa3\x95\xf8\x016Uk\x1b!\xbe\x00\x87\xa8\
+U\xc1vY\x12\x0c\xc96x\x0e7 \xec\xe9\xdd\xb9\
+\x89\xc4~6\x08j\x01\x9cs\x10\x00\x8d\x15\x1e\xb4y\
+\xc3P\x1bu\xb0\xf4\x8d\x87\xc6r\xaa\xcf3\x1b\x08\xac\
+f\x8a\xe3{\x1bP}\xa0\x99\xca\x11\x05\xf7\xdcp\x1c\
+\xbb\x8e\x98\xb1\xb7\xe2b\x1cW\x05%\xe1\xd8\x9e:\xa1\
+\xee\xb0\x0b}\xafLTR\x12LB\xb3\x87\xa1-\xc8\
+\xa16\xe8\x10;\x22\x0dm\xf5\x1ex*\x1a\xbfS~\
+\xe1'\xc8\xc1u\xdc\x87\x90/\x0cY\xe1\xed\x873\x12\
+\xac \x94\x9c\xd0\xd1\x81h\x8b\x80\xa0\xbbM\xda\xb3\xbd\
+\x96\xf8\x9a\xdb\xc4\x1b\x85ChS\xa9\xd17>\x88\xd2\
+/b/\xeeqU\xc8\x1f\xc1\xbe\x9d\x15\x82%\xde\xc8\
+/\xbb&\x89\x98\xa2Tp\xb9\xdb\xe5^}\xbc\x05Z\
+\xbb\x11m\x0d^x\x8e\xd6w+\xef\x06\xbda\x04\x03\
+\x11\x04\xdc!\x18R\xec\x88I\x89\xee\xd0\xd4E\xa1}\
+\x5c%0\x19\xe5\x1fT\xa1\xa5\xd6\xa7\x8a\xe9\xe3\x83u\
+h\x04\xd7\x1d\xaa\xc4\x9f\xa5k\xf0\xfb\xd7\x0d?\x9eq\
+\x95\xa7\xdeG>\xd9\xfc-\x92\x07\xc5 mh\x0c\x02\
+\x92\x00\x97\x97\x81\xaa\x04\x18\x92m\xd0F\x1b\xe1=\xd6\
+\x80\xb6\x86\xae\xf2q\xd3q/\xb46\x03\xe2\xae\xea\x0b\
+\xd5\x89tO\x08`6PX\x8d@\xf5\xfe&T\xed\
+k\x84,\xb5\xd7W#n\xa8\xc2W\xef\xa5\xe0\xb1\xf0\
+\x8d?\xceq\x15\x00T\x1fhBs\x95\x07)\x83b\
+\xd0g@4\xbc\xbe\xf6~O\xd4\xaa`\x1f\x94\xdc\xae\
+W\x1d\xac\xeb8\xc4!\xea\xd5\xb0\xf6K\xe8t\xa4\xc7\
+\xa8oW-=5^|\xb2\xa3\x06\x913t\xf4\x1d\
+k\x07\xe3,\xda\x22\x08\xa8r\xee)\xeb\x1c\xed\xea)\
+{\x06\x08\x14\xdb\x09\x90\xf6}\xfa\xbdacR\xa16\
+\xe9P\xdb\xd8\xb9}\xe1\x0a\x03g\xbc\xd3\xd8J%\x02\
+16\x01,\x10\xc4\xa1\xcfk\xe1i\xec\xfd!:\x0e\
+\xd4sQ\xf8\xe5'E\x97\x1f\xb8\xa0\x11uE\xe6p\
+\x11L6\xc7\x86\x9b\xb0\xe7\xcb\x16\xb8<\xdf]/\x85\
+\xfc\x11|\xb6\xf5\x10\xe2\xd2\xcc\xc8\x18\x99\x88\xc8\x89q\
+\xbb\xa4\xb4\x8f\x99\xc8\x09\x8c(m\xaf\x87\xf4*\x86\xa3\
+\xbb\xabQ{\xa8\xf7\xc7\x19\x0dz\x8a\xd1\xd7\xc6\xc2\xad\
+\xb1\xb5IP\x91O\x8a.\xf0\xd6k\x0bJ\xf3\xef\xbf\
+Y\xb4\x8e\xee\x9b\x00\x97'\x1a\xafnl\xc0\xc62W\
+\xaf~\xdbP\xe1EC\x85\x17\x19#\xe2\x918\xc0\x0e\
+\x7f\x84\xb6O\x8fy{6\xb3\x18\x08j\xcb\x9b\xf0\xf5\
+7M\x90\xa5\xde\xed\x1eB\x80\x9f\x8f6c\xd1\x9c\x14\
+\x18\xa3\x04\xbc\xb7\x9fe\xbc\xf4\xbe\x94\x0d`\xce\xbfe\
+\xb80e\xca\x82\xd8\xb3}\xae3\xa8\x1e[\xf5\x8e\xb2\
+\xf1/\x7fW\xc0\x04\x15\xe6\xcdJ\xc6k\xf9\x031\xa4\
+\x7fT\xaf\x9d8\xba\xa7\x1e\xbbJ\x0e\x22\xd4\xe4\x81U\
+-\xc1\x1e\xa5\x00~?v\x95\x94\xe3\xc8\x97\xf5\x1d \
+\x09\x22\x85F\xd7\xfd,`\xf8 =\xfe\xfcD?,\
+^\x90\x06\x85\x08X\xfb\x0f\x86Wv\xb2\xf7\x94\xb0\xea\
+\xac\xc7\x12\xc7M\xcf\xee{\xde8j\xd2\x1d\x0f\x0cT\
+$\xb2\x88pb\xe4\xe0\xc7\xb9,\x15\x94\xbe\xb5\xaa\xe3\
+\xaf\x15\x99\xd3\xe7]\xc3\x81\x1c\xebe\xe3+Y\xdc\xf5\
+YY\xa3\x85\xb4_\x0e\xa60h\x81\xb7\xdfwcM\
+Q=\xea\x1a#\xe7\x9c\x0cD\x95\x80\xb4\x11q]Z\
+\x18\x00\x88\xb6\x8a\xf8\xf5\x8cxd\xde`GH\x02J\
+\xf70\xbc\xb3W)\x8f4~\xb3\xb1\xf1\xcb\xd7b\x14\
+N\x0b\xca\x9c\xcb\xf6\x9f\x5c\xefp\xe4\xdaB\x94\xcfa\
+\x0c\xfd\x08A\x1d\xe1\xf2\x8b%\xce\xe7\x8e\xfd \xa0&\
+O\xce\xb1DT\xf4^B\x90f\x12\xb4\x8b\xd7\xaf\x7f\
+\xb2a\xe2\xed\x0f\xf6\xe7\xb24\x1f\xa0\x9f\xe99\xdf\x1e\
+$\xec>\x80\xc8V\x8d\xb8t\xdd\xba\xa5\x81)3\x1f\
+\x9c\xa8\xc9\xb8%;~\xe05\x03\xa6]I\xd2~:\
+\x90\xa2-\xc8\xb0nS\x03\xde\xda\xee\x82?\xf0\xfd\x93\
+\x0f\xa1\x04\x89\xfd\xed\x105\x02T\x1a\x01\xa1@\x04\xd5\
+\xfb\xdb\x81\xd2i)\x1c\xe3\xa3q\xf7\xb4xh5\x04\
+\xdf\x1c\xe7X\xff\x11\xf3\x1c\xfe\xe6\x93j\xb9b\xdb\xd2\
+M\xaf?\xf5\xea\xa4I\xf7\x1ae\xad\xe9a\x01\x84B\
+\x91\xd6BP\xdf\xc4\xc1\xae&\x8a\xf0\xa7\x92\x8d\xcb\xbe\
+\xbd\xe9\xce\xfbbUa\xdd\x22\x01\xc4\x0dAY]\xbc\
+~EC\xaf\x80\x1a\xecp\xa8\xd3\x912\x0b`#\x04\
+\x82\xbf\x16o(\xf8\x10\x00~\xe2\xc8\xd5Y\x18\xb9\xac\
+l\xe3\xf2\xdd\x99Y9\x93\x01:\x81P\xe9\xf9\xe2\xc2\
+gw\xdf<\xfd\xfe\x91\x22W\xcd\xa6\x1c\xc7I\xb8u\
+\xa5j\xf8\xaf\xfb\xa9\xd5\xdam\xa3\xaf\x1a\x16?q\x04\
+%}\xe3\x08\xea\x1a#xiC=\xde~\xbf]\xdf\
+\xee\xdd\xb8\xca\x04\x83U\xdb1\xaeR\x14\x0e_s\x00\
+\x9e\x86\x00~>\xda\x8c;\xa7\xc4bP?=\x8e4\
+rl\xfa\x9c){\xf6|\xed\xd5\xb1\xc6\xc9\x87\x8f~\
+\xf2\xb9\xa1\xadu\x16\xe7,\x81I\xaaW\xca6?s\
+d\xc2\xd4\xdc\x0c\x22\xe07\x10\xc8\x8e\xad\x85\xcb\xdf\xbd\
+yZ\xee\x90\xe6\x8a\xc0\xc1\x93Z\xdb\xcd\xb7\xdd?D\
+T\xd4\xb3\x00\xb2w[\xd1\xb2W\xce\xec\x0f;\x015\
+nF\xf6\x15\x22\x13~\xcb\x18\xd9\xa9'\xd5N\xa7\xd3\
+\xa9\x00@\xe6\xad\xf3\xc7q\xca&\x11\x10?\xe7\x5c\xab\
+\x92#y\x9b6\xadr\x8d\x9b\x99m\xa2\xb2\xb0\x10\x5c\
+p\xcb\xb2\xef\x8d\xedo\xad\xae\x9b\xe0\xc8\x99\x03JF\
+1\x905\x833\x97)M\xae\x06\xe7O\x86D\xa7\xce\
+\xbcN\x80Q\x0b\xec\xfa\xc6\x8f\xd5o\xd4c\xdf\xa1\xef\
+N\xf1\xa9Cc!\xa8\x050\x99\xc1\xef\x0e\xa1\xf9\xb8\
+\x17}Su\x98{w\x02\xae\x1cfDD\x02\xde\xda\
+\xc5\xb0\xe3\xcb\x96\xe3\x01n\xb9\xab\xed\x1f\x0b\xea\x18!\
+\xd9\x9c\x93\xdd\xdb\x9c\xf9/\x8d\x9d\x91\x93\xa6b\xe4>\
+\x80\x8a\x11\xce\x9e\xdc\xee\xcco\xc9\xbcm~4g\xca\
+\xc3\x04\x82\x9f16\x94\x10\xf2\xd2\xd6\x0d\xcbKO\xd3\
+\xcf\xef\xa6\x84\x8f!\x84\xac*.\xcc\xdf\xd5\x09\xa8\x89\
+\x8e\xb9}\x00q\x11'Ju B\x9e\xdf\xb9\xb9\xc0\
+\x03\x00\x99\xb7\xe5\x0e\xe1\x0a\xf2@\xf0\x191\x07V\x95\
+\xac^\xdd6ujnFD\x85\xdf\x00\xd0\x81\xa3\x0f\
+\xa7d\xe9\xb6\xc2\xe5\x1fMt\xe4\xfc\x84\x80\xdc\xa3\x00\
+\x9bJ\x9d\xf9o\x8fsd\x0f\x16\x88\xf0\x18\x08\xf9\x22\
+i\xdc3\x95Q\x22\x16\xffb0I\x1f;\x9c\x82\x12\
+\xa0\xec\xbd\x16\xac\xd9P\x8f\xc6f\xa9\xc7q\x95,)\
+\xa8;\xd4\x02\xabI\xc0\xad\xe3\xa2\xe1\x98\x10\x03\xbd\x96\
+\xa2\xec+\x86\xed_\xf3\xa3m\x11\xb6\xb8n\xc7\xc3j\
+p>L&l\xc9\xdb\x85\x05\x15\x93\xa6\xcfs0\xce\
+o\x13\x80\xe7\xb7l\xc8\xdf1\xc9\xf1\xc0@\x85\x0a\x8b\
+\x08\xb8\x97p\xae\x85\x22\x14\x94l\x5c\xf6\xed\xec\xd9\xb3\
+U5n\xfd\xaf\x08\xc1U\x22\xc5\xe2\xcd\x85\xed\xca\xe6\
+\x09\x15\xf7^\x02>\x98\x04\xd9#\xc5\xc5+\x1a\xc8\xc9\
+Y\x9e \x10\xba\xa5p\xf9q\x00\xb8a\xca\xc3v\x9d\
+*2\x0b\xe0\x86\x88D\x9f\xfb\xdb\xa6\xa5\x8d]\xce\x1c\
+L\xbb\x7f\xc8\xf6\x8d\xab\xf6M\xbd}~jD\xe6\x0f\
+r\xa242\xd1Z`\x88\xec\x0b\x04y\x9f\xf9 J\
+\x9c\xa4`\xe9\xf6\xb7\x0a\xea&L\xcf\x19\xa4QG=\
+\xa2\x1f>\xab6.>%w\xc6\xb5T\xbc2\x9d\xc2\
+\xed\x95\xb1\xe5\x1d\x17^,\xac\xefV\xf9\x94%\x05Y\
+\xe3\xa3qoV<\x8cQ\x02\x0e\xd6q\xbc\xf6!\xe3\
+\x87k\xfck\x02\xbb\x96E\x0b\x01\xdf#\x9b6\x15\x1c\
+\x18;#'\x8d*\xc8\x11\x08\x91}\x8d\xbb\x17\x99L\
+W\xeb\x98F\xba\x97\x10\x0cP\xe4\xc8\x92\xd2\xb7VU\
+\x8ew\xe4\x8c(u\x16\xec9\xfb\x88\x8e\xce\xe1`j\
+I#>\xfa\xb7uK\x03'?\xf7\xd3\xb0\xf7\x9fo\
+\xfc\xd9\xdd\x85\xa3\xc6e\xe5\x5c'p\xba\x90P\xf2\xa7\
+\x92\xa2e\xdd\xfe\xd3r\xdc\xb8l\x8dh\x14\xe7\x03$\
+>\xc2y\xdevg~\xcb\xa4\xe993\x19\xe8\x14\x81\
+(\xcfl)\x5c\xf1\xe9\x98\xc99\x16\x83\x9a>\x03\xc2\
+\x9bi\xd0\xfbdq\xf1\xcb\xbe\x19\xf9\xc14\xb5V\x95\
+wy\x1f\xdc}\xe7u\x14q\xe6v\xfeZ\xfab\x0d\
+>\xd9\xdd\xda\xe9\x1e\x83\xfa\xea\x913+\x09C\x07\xe8\
+\xd1\x1a\x04^\xda\xa9\xe0\xcbc\xfc\xcd\x18\x8b\xf8P\xfe\
+LrtT\xe6l}\xbc\xce\x90\x0d\xf0>\x8c\xcby\
+e\xceg\x9bnq\xe4^\xce\x80<F\xe1\xdcV\x94\
+_8~\xea\xfd\xa9\x82\xa8\xcef\x04\x81\xab\x06\x99\x1f\
+\xcb\xcb\xcb\xeb\xb6\xf1\x9b4)\xb7\x1f\xd7\xf0\xa7\x89@\
+\x9e:}\xdbu\x9f\xf58'=\x0f>s\xa6\x82\x93\
+\xe9\x84\xd3g\xb7:\x97\xfd\xb3}\xac5o\x1a\xe7\xfc\
+A\x05\xda\xcc2\xe7SM\xe3\xa7\xde\x9fJ\x05\xf5\xfb\
+\x80r\xc7V\xe7\xca\x7f\x9ey\x8d9/\xf1\x81~I\
+.\xbaa0\x1d>\xf5J\x0a\xa3\x0e\xd8s \x80\xaf\
+\xf7\x07\x00\x02\x0c\xe9\xaf\xc3\xa8\xcb\x8d\x88\xc8@\xc9n\
+\x86w\xf6\xf1c\x01?\xbf\xf7\xb5l\xd5\xce.\xfe\xdc\
+>\x7f\x14\x95\x95\x22\xceC?\xdf\xea|\xa1\xe6F\xc7\
+Cf-\x09\xbfK\x08\x7f\xb1\xa4h\xc5_\x00`\xca\
+\xf4\x05C%\xae<N\x08^-)\xca\xdf\xf2/\xed\
+\xf5\xc6:\xe6\x0c\x14\x89\xf6)\x02\xea,\xd9\xb0l\xfd\
+YC\x98\x93\xc79\xc8\x97\xdb\x8a\x96\x17\xdc\xe8\x98\xdb\
+GK\xe8\xef\xc1P\xedo\xde\xb3x\xe7\xce\x9d]\xfa\
+\x9b\xd9/+sT\x94/\x1c?\x82\xa4\x8d\x1f\xde\xb9\
+\xfe\xfd\xaa\x92\xe3/\x7fW<A\x89>\xf6\xcb\xb4\xa6\
+\x17\xef\xba9>p\xe6x\xcd\x10\xeb]\x00\xf0t\x95\
+\xa4[\xb8i\xd3\x93-\x99\x8e\xdc;\x002Y\xa0\xe2\
+\xc2\xcdE\xcf\x1c\xe9\x02jV\xce\xbd\x00\xb9\x99E\xc4\
+\xdf\x95m\xee\xfa\xfd9\x035qz\xce\xf3`\xdcK\
+\xc3\x9a'\x8b\x8b\x97t{$e\xf2\x8cEi2\x0f\
+\x95\x10\xd0\x87K\x8a\x96o\x05\x80\x89\xd3\xe6\xf6\x87 \
+\xfcU\xe0X\xb2\xc5\x99\xdf\xe5\x9f\x9a\xab\xde\xe3\x86\x0f\
+\xf6\xcb\xf3\x93\xcc\xe4\xd11\x83\x89\xa8\x12\x80\xcf\x8e2\
+^\xd9L\xfe\xaa\x88B\xce\xda_\x11\xcf\xd9&D\x9c\
+\x09\x8fPB\x1f:I\x11\x99\xd3\xe7M\x04\xf8\x1f\x05\
+\xc2\x1d'\x89\xf9l6*s\xb6>!*\xeaa\xae\
+\x10\xc2\xfc\xd2\x13ee\xcf\x86\xcf\x1bP'\x07\x9e\xdd\
+n\xc7\x09s\xac\xd4\xa0\xcf\x01W\xe2|\x11,:\x99\
+9o\xbf}\x8e\xb5U\xd1<\x0a\x0e\x9f\xe2\xcbx\xaa\
+\xacln\xb7N-\xde\xc2\xe3\x0e7*\xd7\x1850\
+E\x10\xdc\xfe\xc2]\xc6\xc6\xee\xd6\xdes\xcf=\xda\xe6\
+\xa0\xed1\xce\xb9A\xa4\xe4\x7fO&\xa2)S\xee\x8b\
+\x8d\xa8u\xbf'\x9c\xb8\xfc*q\xcd\xce\xd7\x97Tw\
+\xcf\xb3+5=\xf9s^e\x961c\xc6\x88\xa6\xd8\
+Q3\x15\xb0_\x10EY\xbc\xf5\xc48~\xd4\xa8\xd9\
+\xaa\x84\x8c\xa8\xdf\x82\xf0\xab\x08\x17\xf3J\x9cK\x8f\xfd\
++t\xae\x89w>\xd4\x07\xe1\xf0\x13`d\xe7\xd67\
+\xf3\xd7\x9e\x94[\xc7\xde\xb1p\xa0\x18\x91\x1e\x22\x9c\xff\
+\xa3\xc4Y\xb0\xf6\x82\xeaQ\xe3fd_!0\xf1\x11\
+\xae\xf0\xd2m\x1b\x0b^\xee\xe8\xffn\xcd\xbd\x89S>\
+\x93\x02\xabOV\xf6\xffj\x9bp\xeb\xdck\x09\x11\x1e\
+\xa6`/\x16;W\x14\x9fJ<\x0f8\x08\xa7\xf7@\
+\x22\xbf\xdb\xbai\xf9W\x17\x06\xa8\x99\x0b\x93=\x11\xc9\
+\xf5\xb13?\x08\x00\xe3\x1c\xd9\x83E*\xce\x03#\xc7\
+J\x9c\xcb\x17\xe3\xdfl\xb3g\xcfV\xd5y\xa3~\xc3\
+\x19\x06\x10H+N6\xba?q\xe4\xeabDf\xea\
+\xae\x8f\xfb\xb7l\xbd\x8e\x1a\xc4\x91;V!d\x8c\x10\
+R\xf2\x8b\x8b\x7f\xb8C\xe7\xc3\xc69\xb2c\x04\xaaZ\
+\xc4\x99\xf2\xfe\xb6\xd3\xa2\xeb\xe2\xb0\xb3\x89\xd5\x17\xd8\xf2\
+\xf2\xf2(.\xd9%\xbbd\x97\xec?\xc0\xfe\x0f\x14\xd5\
+\xea\x92\xe4\xa3Oi\x00\x00\x00\x00IEND\xaeB\
+`\x82\
+\x00\x00\x012\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M12.4501\
+ 37.65L10.3501 3\
+5.55L21.9001 24L\
+10.3501 12.45L12\
+.4501 10.35L24.0\
+001 21.9L35.5501\
+ 10.35L37.6501 1\
+2.45L26.1001 24L\
+37.6501 35.55L35\
+.5501 37.65L24.0\
+001 26.1L12.4501\
+ 37.65Z\x22 fill=\x22#\
+667085\x22/>\x0a</svg>\
+\x0a\
+\x00\x00\x04\x83\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M10.3929\
+ 26.4C9.73097 26\
+.4 9.16667 26.16\
+43 8.7 25.6929C8\
+.23333 25.2215 8\
+ 24.6548 8 23.99\
+29C8 23.3309 8.2\
+357 22.7666 8.70\
+71 22.3C9.17847 \
+21.8333 9.74513 \
+21.6 10.4071 21.\
+6C11.069 21.6 11\
+.6333 21.8357 12\
+.1 22.3071C12.56\
+67 22.7784 12.8 \
+23.3451 12.8 24.\
+0071C12.8 24.669\
+ 12.5643 25.2333\
+ 12.0929 25.7C11\
+.6215 26.1666 11\
+.0549 26.4 10.39\
+29 26.4ZM23.9929\
+ 26.4C23.331 26.\
+4 22.7667 26.164\
+3 22.3 25.6929C2\
+1.8333 25.2215 2\
+1.6 24.6548 21.6\
+ 23.9929C21.6 23\
+.3309 21.8357 22\
+.7666 22.3071 22\
+.3C22.7785 21.83\
+33 23.3451 21.6 \
+24.0071 21.6C24.\
+669 21.6 25.2333\
+ 21.8357 25.7 22\
+.3071C26.1667 22\
+.7784 26.4 23.34\
+51 26.4 24.0071C\
+26.4 24.669 26.1\
+643 25.2333 25.6\
+929 25.7C25.2215\
+ 26.1666 24.6549\
+ 26.4 23.9929 26\
+.4ZM37.5929 26.4\
+C36.931 26.4 36.\
+3667 26.1643 35.\
+9 25.6929C35.433\
+3 25.2215 35.2 2\
+4.6548 35.2 23.9\
+929C35.2 23.3309\
+ 35.4357 22.7666\
+ 35.9071 22.3C36\
+.3785 21.8333 36\
+.9451 21.6 37.60\
+71 21.6C38.269 2\
+1.6 38.8333 21.8\
+357 39.3 22.3071\
+C39.7667 22.7784\
+ 40 23.3451 40 2\
+4.0071C40 24.669\
+ 39.7643 25.2333\
+ 39.2929 25.7C38\
+.8215 26.1666 38\
+.2549 26.4 37.59\
+29 26.4Z\x22 fill=\x22\
+#667085\x22/>\x0a</svg\
+>\x0a\
+\x00\x00\x037\
+<\
+svg width=\x2224\x22 h\
+eight=\x2224\x22 viewB\
+ox=\x220 0 24 24\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M0 12C0 \
+5.37258 5.37258 \
+0 12 0C18.6274 0\
+ 24 5.37258 24 1\
+2C24 18.6274 18.\
+6274 24 12 24C5.\
+37258 24 0 18.62\
+74 0 12Z\x22 fill=\x22\
+#E6E6E6\x22/>\x0a<path\
+ d=\x22M15.5 12C16.\
+3284 12 17 12.67\
+16 17 13.5V14C17\
+ 15.9714 15.1405\
+ 18 12 18C8.8595\
+1 18 7 15.9714 7\
+ 14V13.5C7 12.67\
+16 7.67157 12 8.\
+5 12H15.5ZM15.5 \
+13H8.5C8.22386 1\
+3 8 13.2239 8 13\
+.5V14C8 15.4376 \
+9.43216 17 12 17\
+C14.5678 17 16 1\
+5.4376 16 14V13.\
+5C16 13.2239 15.\
+7761 13 15.5 13Z\
+M12 5.5C13.5188 \
+5.5 14.75 6.7312\
+2 14.75 8.25C14.\
+75 9.76878 13.51\
+88 11 12 11C10.4\
+812 11 9.25 9.76\
+878 9.25 8.25C9.\
+25 6.73122 10.48\
+12 5.5 12 5.5ZM1\
+2 6.5C11.0335 6.\
+5 10.25 7.2835 1\
+0.25 8.25C10.25 \
+9.2165 11.0335 1\
+0 12 10C12.9665 \
+10 13.75 9.2165 \
+13.75 8.25C13.75\
+ 7.2835 12.9665 \
+6.5 12 6.5Z\x22 fil\
+l=\x22#616161\x22/>\x0a</\
+svg>\x0a\x0a\
+\x00\x00\x00\xdf\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M18.9002\
+ 35.7L7.7002 24.\
+5L9.8502 22.35L1\
+8.9002 31.4L38.1\
+002 12.2L40.2502\
+ 14.35L18.9002 3\
+5.7Z\x22 fill=\x22#667\
+085\x22/>\x0a</svg>\x0a\
+\x00\x00\x01V\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M9 42C8.\
+2 42 7.5 41.7 6.\
+9 41.1C6.3 40.5 \
+6 39.8 6 39V9C6 \
+8.2 6.3 7.5 6.9 \
+6.9C7.5 6.3 8.2 \
+6 9 6H23.55V9H9V\
+39H23.55V42H9ZM3\
+3.3 32.75L31.15 \
+30.6L36.25 25.5H\
+18.75V22.5H36.15\
+L31.05 17.4L33.2\
+ 15.25L42 24.05L\
+33.3 32.75Z\x22 fil\
+l=\x22#667085\x22/>\x0a</\
+svg>\x0a\
+\x00\x00\x01f\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M24.45 4\
+2V39H39V9H24.45V\
+6H39C39.8 6 40.5\
+ 6.3 41.1 6.9C41\
+.7 7.5 42 8.2 42\
+ 9V39C42 39.8 41\
+.7 40.5 41.1 41.\
+1C40.5 41.7 39.8\
+ 42 39 42H24.45Z\
+M20.55 32.75L18.\
+4 30.6L23.5 25.5\
+H6V22.5H23.4L18.\
+3 17.4L20.45 15.\
+25L29.25 24.05L2\
+0.55 32.75Z\x22 fil\
+l=\x22#667085\x22/>\x0a</\
+svg>\x0a\
+"
+
+qt_resource_name = b"\
+\x00\x02\
+\x00\x00\x07\x84\
+\x00q\
+\x00t\
+\x00\x03\
+\x00\x00x<\
+\x00q\
+\x00m\x00l\
+\x00\x0c\
+\x0fN\xa7E\
+\x00C\
+\x00o\x00l\x00o\x00r\x00P\x00a\x00l\x00e\x00t\x00t\x00e\
+\x00\x05\
+\x00o\xa6S\
+\x00i\
+\x00c\x00o\x00n\x00s\
+\x00\x08\
+\x03\xc6T'\
+\x00p\
+\x00l\x00u\x00s\x00.\x00s\x00v\x00g\
+\x00\x0c\
+\x07\x11\xd4\xa7\
+\x00u\
+\x00s\x00e\x00r\x00M\x00a\x00s\x00k\x00.\x00s\x00v\x00g\
+\x00\x06\
+\x07\x87WG\
+\x00q\
+\x00t\x00.\x00p\x00n\x00g\
+\x00\x0a\
+\x08\xab\xd7\x87\
+\x00u\
+\x00p\x00d\x00a\x00t\x00e\x00.\x00s\x00v\x00g\
+\x00\x0a\
+\x0c\xad\x02\x87\
+\x00d\
+\x00e\x00l\x00e\x00t\x00e\x00.\x00s\x00v\x00g\
+\x00\x08\
+\x0b\x07W\xa7\
+\x00e\
+\x00d\x00i\x00t\x00.\x00s\x00v\x00g\
+\x00\x0e\
+\x05\x92p\xc7\
+\x00t\
+\x00e\x00s\x00t\x00s\x00e\x00r\x00v\x00e\x00r\x00.\x00p\x00n\x00g\
+\x00\x09\
+\x06\x98\x8e\xa7\
+\x00c\
+\x00l\x00o\x00s\x00e\x00.\x00s\x00v\x00g\
+\x00\x08\
+\x06\xb6W\xa7\
+\x00d\
+\x00o\x00t\x00s\x00.\x00s\x00v\x00g\
+\x00\x08\
+\x09\xc5UG\
+\x00u\
+\x00s\x00e\x00r\x00.\x00s\x00v\x00g\
+\x00\x06\
+\x07^Z\xc7\
+\x00o\
+\x00k\x00.\x00s\x00v\x00g\
+\x00\x0a\
+\x06\xc91\x07\
+\x00l\
+\x00o\x00g\x00o\x00u\x00t\x00.\x00s\x00v\x00g\
+\x00\x09\
+\x0e\x01\xbcg\
+\x00l\
+\x00o\x00g\x00i\x00n\x00.\x00s\x00v\x00g\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x0a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x16\x00\x02\x00\x00\x00\x01\x00\x00\x00\x04\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x004\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x05\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00D\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x13\x96\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00\xf6\x00\x00\x00\x00\x00\x01\x00\x00.-\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x01\x0e\x00\x00\x00\x00\x00\x01\x00\x00/c\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x01L\x00\x00\x00\x00\x00\x01\x00\x008\x08\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00Z\x00\x00\x00\x00\x00\x01\x00\x00\x00\xc4\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x01:\x00\x00\x00\x00\x00\x01\x00\x007%\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00x\x00\x00\x00\x00\x00\x01\x00\x00\x01\x5c\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xf3\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x01$\x00\x00\x00\x00\x00\x01\x00\x003\xea\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x11\x9b\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xe4\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x01f\x00\x00\x00\x00\x00\x01\x00\x009b\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+"
+
+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/demos/colorpaletteclient/restservice.py b/examples/demos/colorpaletteclient/restservice.py
new file mode 100644
index 000000000..d334ecd03
--- /dev/null
+++ b/examples/demos/colorpaletteclient/restservice.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Property, Signal, ClassInfo
+from PySide6.QtNetwork import (QNetworkAccessManager, QRestAccessManager,
+ QNetworkRequestFactory, QSslSocket)
+from PySide6.QtQml import QmlElement, QPyQmlParserStatus, ListProperty
+from abstractresource import AbstractResource
+
+QML_IMPORT_NAME = "ColorPalette"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+@ClassInfo(DefaultProperty="resources")
+class RestService(QPyQmlParserStatus):
+
+ urlChanged = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.m_resources = []
+ self.m_qnam = QNetworkAccessManager()
+ self.m_qnam.setAutoDeleteReplies(True)
+ self.m_manager = QRestAccessManager(self.m_qnam)
+ self.m_serviceApi = QNetworkRequestFactory()
+
+ @Property(str, notify=urlChanged)
+ def url(self):
+ return self.m_serviceApi.baseUrl()
+
+ @url.setter
+ def url(self, url):
+ if self.m_serviceApi.baseUrl() != url:
+ self.m_serviceApi.setBaseUrl(url)
+ self.urlChanged.emit()
+
+ @Property(bool, constant=True)
+ def sslSupported(self):
+ return QSslSocket.supportsSsl()
+
+ def classBegin(self):
+ pass
+
+ def componentComplete(self):
+ for resource in self.m_resources:
+ resource.setAccessManager(self.m_manager)
+ resource.setServiceApi(self.m_serviceApi)
+
+ def appendResource(self, r):
+ self.m_resources.append(r)
+
+ resources = ListProperty(AbstractResource, appendResource)