diff options
Diffstat (limited to 'examples/qml/tutorials/extending-qml')
27 files changed, 1041 insertions, 0 deletions
diff --git a/examples/qml/tutorials/extending-qml/chapter1-basics/app.qml b/examples/qml/tutorials/extending-qml/chapter1-basics/app.qml new file mode 100644 index 000000000..6feef5633 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter1-basics/app.qml @@ -0,0 +1,26 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import Charts +import QtQuick + +Item { + width: 300; height: 200 + + PieChart { + id: aPieChart + anchors.centerIn: parent + width: 100; height: 100 + name: "A simple pie chart" + color: "red" + } + + Text { + anchors { + bottom: parent.bottom; + horizontalCenter: parent.horizontalCenter; + bottomMargin: 20 + } + text: aPieChart.name + } +} diff --git a/examples/qml/tutorials/extending-qml/chapter1-basics/basics.py b/examples/qml/tutorials/extending-qml/chapter1-basics/basics.py new file mode 100644 index 000000000..47d0a0e0c --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter1-basics/basics.py @@ -0,0 +1,68 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the qml/tutorials/extending-qml/chapter1-basics example from Qt v5.x""" + +import os +from pathlib import Path +import sys + +from PySide6.QtCore import Property, Signal, QUrl +from PySide6.QtGui import QGuiApplication, QPen, QPainter, QColor +from PySide6.QtQml import QmlElement +from PySide6.QtQuick import QQuickPaintedItem, QQuickView + +# To be used on the @QmlElement decorator +# (QML_IMPORT_MINOR_VERSION is optional) +QML_IMPORT_NAME = "Charts" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class PieChart (QQuickPaintedItem): + + nameChanged = Signal() + + def __init__(self, parent=None): + QQuickPaintedItem.__init__(self, parent) + self._name = u'' + self._color = QColor() + + def paint(self, painter): + pen = QPen(self.color, 2) + painter.setPen(pen) + painter.setRenderHints(QPainter.Antialiasing, True) + painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16) + + @Property(QColor, final=True) + def color(self): + return self._color + + @color.setter + def color(self, value): + self._color = value + + @Property(str, notify=nameChanged, final=True) + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + +if __name__ == '__main__': + app = QGuiApplication(sys.argv) + + view = QQuickView() + view.setResizeMode(QQuickView.SizeRootObjectToView) + qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml') + view.setSource(QUrl.fromLocalFile(qml_file)) + if view.status() == QQuickView.Error: + sys.exit(-1) + view.show() + res = app.exec() + # Deleting the view before it goes out of scope is required to make sure all child QML instances + # are destroyed in the correct order. + del view + sys.exit(res) diff --git a/examples/qml/tutorials/extending-qml/chapter1-basics/chapter1-basics.pyproject b/examples/qml/tutorials/extending-qml/chapter1-basics/chapter1-basics.pyproject new file mode 100644 index 000000000..869556bb8 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter1-basics/chapter1-basics.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["basics.py", "app.qml"] +} diff --git a/examples/qml/tutorials/extending-qml/chapter1-basics/doc/chapter1-basics.rst b/examples/qml/tutorials/extending-qml/chapter1-basics/doc/chapter1-basics.rst new file mode 100644 index 000000000..38233aae2 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter1-basics/doc/chapter1-basics.rst @@ -0,0 +1,107 @@ +.. _qml-chapter1-basics: + +Extending QML - Creating a New Type +=================================== + +This is the first of a series of 6 examples forming a tutorial +about extending QML with Python. + +The Qt QML module provides a set of APIs for extending QML through Python +extensions. You can write extensions to add your own QML types, extend existing +Qt types, or call Python functions that are not accessible from ordinary QML +code. + +This tutorial shows how to write a QML extension using Python that includes +core QML features, including properties, signals and bindings. It also shows +how extensions can be deployed through plugins. + +A common task when extending QML is to provide a new QML type that supports +some custom functionality beyond what is provided by the built-in Qt Quick +types. For example, this could be done to implement particular data models, or +provide types with custom painting and drawing capabilities, or access system +features like network programming that are not accessible through built-in QML +features. + +In this tutorial, we will show how to use the C++ classes in the Qt Quick +module to extend QML. The end result will be a simple Pie Chart display +implemented by several custom QML types connected together through QML features +like bindings and signals, and made available to the QML runtime through a +plugin. + +To begin with, let's create a new QML type called ``PieChart`` that has two +properties: a name and a color. We will make it available in an importable type +namespace called ``Charts``, with a version of 1.0. + +We want this ``PieChart`` type to be usable from QML like this: + +.. code-block:: javascript + + import Charts 1.0 + + PieChart { + width: 100; height: 100 + name: "A simple pie chart" + color: "red" + } + +To do this, we need a C++ class that encapsulates this ``PieChart`` type and +its two properties. Since QML makes extensive use of Qt's Meta-Object System +this new class must: + +* Inherit from ``QObject`` +* Declare its properties using the ``Property`` decorator + +Class Implementation +-------------------- + +Here is our ``PieChart`` class, defined in ``basics.py``: + +.. literalinclude:: basics.py + :lineno-start: 21 + :lines: 21-51 + +The class inherits from ``QQuickPaintedItem`` because we want to override +``QQuickPaintedItem.paint()`` to perform drawing operations with the +``QPainter`` API. If the class just represented some data type and was not an +item that actually needed to be displayed, it could simply inherit from +``QObject``. Or, if we want to extend the functionality of an existing +``QObject``-based class, it could inherit from that class instead. +Alternatively, if we want to create a visual item that doesn't need to perform +drawing operations with the ``QPainter`` API, we can just subclass +``QQuickItem``. + +The ``PieChart`` class defines the two properties, ``name`` and ``color``, with +the ``Property`` decorator, and overrides ``QQuickPaintedItem.paint()``. The +``PieChart`` class is registered using the ``QmlElement`` decorator, to allow +it to be used from QML. If you don't register the class, ``app.qml`` won't be +able to create a ``PieChart``. + +QML Usage +--------- + +Now that we have defined the ``PieChart`` type, we will use it from QML. The +``app.qml`` file creates a ``PieChart`` item and displays the pie chart's details +using a standard QML ``Text`` item: + +.. literalinclude:: app.qml + :lineno-start: 7 + :lines: 7-26 + +Notice that although the color is specified as a string in QML, it is +automatically converted to a ``QColor`` object for the PieChart ``color`` +property. Automatic conversions are provided for various other QML value types. +For example, a string like "640x480" can be automatically converted to a +``QSize`` value. + +We'll also create a main function that uses a ``QQuickView`` to run and display +``app.qml``. Here is the application ``basics.py``: + +.. literalinclude:: basics.py + :lineno-start: 54 + :lines: 54-68 + +.. note:: You may see a warning `Expression ... depends on non-NOTIFYable properties: + PieChart.name`. This happens because we add a binding to the writable ``name`` + property, but haven't yet defined a notify signal for it. The QML engine therefore + cannot update the binding if the ``name`` value changes. This is addressed in + the following chapters. diff --git a/examples/qml/tutorials/extending-qml/chapter2-methods/app.qml b/examples/qml/tutorials/extending-qml/chapter2-methods/app.qml new file mode 100644 index 000000000..d9477e253 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter2-methods/app.qml @@ -0,0 +1,32 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import Charts +import QtQuick + +Item { + width: 300; height: 200 + + PieChart { + id: aPieChart + anchors.centerIn: parent + width: 100; height: 100 + color: "red" + + onChartCleared: console.log("The chart has been cleared") + } + + MouseArea { + anchors.fill: parent + onClicked: aPieChart.clearChart() + } + + Text { + anchors { + bottom: parent.bottom; + horizontalCenter: parent.horizontalCenter; + bottomMargin: 20 + } + text: "Click anywhere to clear the chart" + } +} diff --git a/examples/qml/tutorials/extending-qml/chapter2-methods/chapter2-methods.pyproject b/examples/qml/tutorials/extending-qml/chapter2-methods/chapter2-methods.pyproject new file mode 100644 index 000000000..cdf33be7f --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter2-methods/chapter2-methods.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["methods.py", "app.qml"] +} diff --git a/examples/qml/tutorials/extending-qml/chapter2-methods/doc/chapter2-methods.rst b/examples/qml/tutorials/extending-qml/chapter2-methods/doc/chapter2-methods.rst new file mode 100644 index 000000000..245d0ddb2 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter2-methods/doc/chapter2-methods.rst @@ -0,0 +1,36 @@ +.. _qml-chapter2-methods: + +Extending QML - Connecting to C++ Methods and Signals +===================================================== + +This is the second of a series of 6 examples forming a tutorial about extending +QML with Python. + +Suppose we want ``PieChart`` to have a ``clearChart()`` method that erases the +chart and then emits a ``chartCleared`` signal. Our ``app.qml`` would be able +to call ``clearChart()`` and receive ``chartCleared()`` signals like this: + +.. literalinclude:: app.qml + :lineno-start: 4 + :lines: 4-32 + +To do this, we add a ``clearChart()`` method and a ``chartCleared()`` signal +to our C++ class: + +.. literalinclude:: methods.py + :lineno-start: 54 + :lines: 54-58 + +The use of the ``Slot`` decorator makes the ``clearChart()`` method available +to the Qt Meta-Object system, and in turn, to QML. The method simply changes +the color to ``Qt::transparent``, repaints the chart, then emits the +``chartCleared()`` signal: + +.. literalinclude:: methods.py + :lineno-start: 21 + :lines: 21-24 + +Now when we run the application and click the window, the pie chart disappears, +and the application outputs:: + + qml: The chart has been cleared diff --git a/examples/qml/tutorials/extending-qml/chapter2-methods/methods.py b/examples/qml/tutorials/extending-qml/chapter2-methods/methods.py new file mode 100644 index 000000000..d455c317b --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter2-methods/methods.py @@ -0,0 +1,75 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the qml/tutorials/extending-qml/chapter2-methods example from Qt v5.x""" + +import os +from pathlib import Path +import sys + +from PySide6.QtCore import Property, Signal, Slot, Qt, QUrl +from PySide6.QtGui import QGuiApplication, QPen, QPainter, QColor +from PySide6.QtQml import QmlElement +from PySide6.QtQuick import QQuickPaintedItem, QQuickView + +# To be used on the @QmlElement decorator +# (QML_IMPORT_MINOR_VERSION is optional) +QML_IMPORT_NAME = "Charts" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class PieChart(QQuickPaintedItem): + + chartCleared = Signal() + nameChanged = Signal() + + def __init__(self, parent=None): + QQuickPaintedItem.__init__(self, parent) + self._name = u'' + self._color = QColor() + + def paint(self, painter): + pen = QPen(self.color, 2) + painter.setPen(pen) + painter.setRenderHints(QPainter.Antialiasing, True) + painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16) + + @Property(QColor, final=True) + def color(self): + return self._color + + @color.setter + def color(self, value): + self._color = value + + @Property(str, notify=nameChanged, final=True) + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + @Slot() # This should be something like @Invokable + def clearChart(self): + self.color = Qt.transparent + self.update() + self.chartCleared.emit() + + +if __name__ == '__main__': + app = QGuiApplication(sys.argv) + + view = QQuickView() + view.setResizeMode(QQuickView.SizeRootObjectToView) + qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml') + view.setSource(QUrl.fromLocalFile(qml_file)) + if view.status() == QQuickView.Error: + sys.exit(-1) + view.show() + res = app.exec() + # Deleting the view before it goes out of scope is required to make sure all child QML instances + # are destroyed in the correct order. + del view + sys.exit(res) diff --git a/examples/qml/tutorials/extending-qml/chapter3-bindings/app.qml b/examples/qml/tutorials/extending-qml/chapter3-bindings/app.qml new file mode 100644 index 000000000..f1530516a --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter3-bindings/app.qml @@ -0,0 +1,40 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import Charts +import QtQuick + +Item { + width: 300; height: 200 + + Row { + anchors.centerIn: parent + spacing: 20 + + PieChart { + id: chartA + width: 100; height: 100 + color: "red" + } + + PieChart { + id: chartB + width: 100; height: 100 + color: chartA.color + } + } + + MouseArea { + anchors.fill: parent + onClicked: { chartA.color = "blue" } + } + + Text { + anchors { + bottom: parent.bottom; + horizontalCenter: parent.horizontalCenter; + bottomMargin: 20 + } + text: "Click anywhere to change the chart color" + } +} diff --git a/examples/qml/tutorials/extending-qml/chapter3-bindings/bindings.py b/examples/qml/tutorials/extending-qml/chapter3-bindings/bindings.py new file mode 100644 index 000000000..a9b61e7f1 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter3-bindings/bindings.py @@ -0,0 +1,79 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the qml/tutorials/extending-qml/chapter3-bindings example from Qt v5.x""" + +import os +from pathlib import Path +import sys + +from PySide6.QtCore import Property, Signal, Slot, QUrl, Qt +from PySide6.QtGui import QGuiApplication, QPen, QPainter, QColor +from PySide6.QtQml import QmlElement +from PySide6.QtQuick import QQuickPaintedItem, QQuickView + +# To be used on the @QmlElement decorator +# (QML_IMPORT_MINOR_VERSION is optional) +QML_IMPORT_NAME = "Charts" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class PieChart (QQuickPaintedItem): + + chartCleared = Signal() + nameChanged = Signal() + colorChanged = Signal() + + def __init__(self, parent=None): + QQuickPaintedItem.__init__(self, parent) + self._name = u'' + self._color = QColor() + + def paint(self, painter): + pen = QPen(self._color, 2) + painter.setPen(pen) + painter.setRenderHints(QPainter.Antialiasing, True) + painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16) + + @Property(QColor, notify=colorChanged, final=True) + def color(self): + return self._color + + @color.setter + def color(self, value): + if value != self._color: + self._color = value + self.update() + self.colorChanged.emit() + + @Property(str, notify=nameChanged, final=True) + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + @Slot() # This should be something like @Invokable + def clearChart(self): + self.color = Qt.transparent + self.update() + self.chartCleared.emit() + + +if __name__ == '__main__': + app = QGuiApplication(sys.argv) + + view = QQuickView() + view.setResizeMode(QQuickView.SizeRootObjectToView) + qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml') + view.setSource(QUrl.fromLocalFile(qml_file)) + if view.status() == QQuickView.Error: + sys.exit(-1) + view.show() + res = app.exec() + # Deleting the view before it goes out of scope is required to make sure all child QML instances + # are destroyed in the correct order. + del view + sys.exit(res) diff --git a/examples/qml/tutorials/extending-qml/chapter3-bindings/chapter3-bindings.pyproject b/examples/qml/tutorials/extending-qml/chapter3-bindings/chapter3-bindings.pyproject new file mode 100644 index 000000000..6e21f86f9 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter3-bindings/chapter3-bindings.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["app.qml", "bindings.py"] +} diff --git a/examples/qml/tutorials/extending-qml/chapter3-bindings/doc/chapter3-bindings.rst b/examples/qml/tutorials/extending-qml/chapter3-bindings/doc/chapter3-bindings.rst new file mode 100644 index 000000000..3b7191191 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter3-bindings/doc/chapter3-bindings.rst @@ -0,0 +1,54 @@ +.. _qml-chapter3-bindings: + +Extending QML - Adding Property Bindings +======================================== + +This is the third of a series of 6 examples forming a tutorial about extending +QML with Python. + +Property binding is a powerful feature of QML that allows values of different +types to be synchronized automatically. It uses signals to notify and update +other types' values when property values are changed. + +Let's enable property bindings for the ``color`` property. That means if we +have code like this: + +.. literalinclude:: app.qml + :lineno-start: 7 + :lines: 7-40 + +The ``color: chartA.color`` statement binds the ``color`` value of ``chartB`` +to the ``color`` of ``chartA.`` Whenever ``chartA`` 's ``color`` value changes, +``chartB`` 's ``color`` value updates to the same value. When the window is +clicked, the ``onClicked`` handler in the ``MouseArea`` changes the color of +``chartA`` , thereby changing both charts to the color blue. + +It's easy to enable property binding for the ``color`` property. We add a +``notify`` parameter to its ``Property`` decorator to indicate that a +``colorChanged`` signal is emitted whenever the value changes. + +.. literalinclude:: bindings.py + :lineno-start: 39 + :lines: 39-39 + +.. literalinclude:: bindings.py + :lineno-start: 21 + :lines: 21-26 + +Then, we emit this signal in ``setColor()``: + +.. literalinclude:: bindings.py + :lineno-start: 43 + :lines: 43-48 + +It's important for ``setColor()`` to check that the color value has actually +changed before emitting ``colorChanged().`` This ensures the signal is not +emitted unnecessarily and also prevents loops when other types respond to the +value change. + +The use of bindings is essential to QML. You should always add ``notify`` +signals for properties if they are able to be implemented, so that your +properties can be used in bindings. Properties that cannot be bound cannot be +automatically updated and cannot be used as flexibly in QML. Also, since +bindings are invoked so often and relied upon in QML usage, users of your +custom QML types may see unexpected behavior if bindings are not implemented. diff --git a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/app.qml b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/app.qml new file mode 100644 index 000000000..a5c5ff9fa --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/app.qml @@ -0,0 +1,22 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import Charts +import QtQuick + +Item { + width: 300; height: 200 + + PieChart { + id: chart + anchors.centerIn: parent + width: 100; height: 100 + + pieSlice: PieSlice { + anchors.fill: parent + color: "red" + } + } + + Component.onCompleted: console.log("The pie is colored " + chart.pieSlice.color) +} diff --git a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/chapter4-customPropertyTypes.pyproject b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/chapter4-customPropertyTypes.pyproject new file mode 100644 index 000000000..af1cfefb7 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/chapter4-customPropertyTypes.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["app.qml", "customPropertyTypes.py"] +} diff --git a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py new file mode 100644 index 000000000..659850f38 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py @@ -0,0 +1,83 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the qml/tutorials/extending-qml/chapter4-customPropertyTypes example + from Qt v5.x""" + +import os +from pathlib import Path +import sys + +from PySide6.QtCore import Property, QUrl +from PySide6.QtGui import QGuiApplication, QPen, QPainter, QColor +from PySide6.QtQml import QmlElement +from PySide6.QtQuick import QQuickPaintedItem, QQuickView, QQuickItem + +# To be used on the @QmlElement decorator +# (QML_IMPORT_MINOR_VERSION is optional) +QML_IMPORT_NAME = "Charts" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class PieSlice (QQuickPaintedItem): + + def __init__(self, parent=None): + QQuickPaintedItem.__init__(self, parent) + self._color = QColor() + + @Property(QColor, final=True) + def color(self): + return self._color + + @color.setter + def color(self, value): + self._color = value + + def paint(self, painter): + pen = QPen(self._color, 2) + painter.setPen(pen) + painter.setRenderHints(QPainter.Antialiasing, True) + painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16) + + +@QmlElement +class PieChart (QQuickItem): + def __init__(self, parent=None): + QQuickItem.__init__(self, parent) + self._name = None + self._pieSlice = None + + @Property(str, final=True) + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + @Property(PieSlice, final=True) + def pieSlice(self): + return self._pieSlice + + @pieSlice.setter + def pieSlice(self, value): + self._pieSlice = value + self._pieSlice.setParentItem(self) + + +if __name__ == '__main__': + app = QGuiApplication(sys.argv) + + view = QQuickView() + view.setResizeMode(QQuickView.SizeRootObjectToView) + qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml') + view.setSource(QUrl.fromLocalFile(qml_file)) + if view.status() == QQuickView.Error: + sys.exit(-1) + view.show() + res = app.exec() + # Deleting the view before it goes out of scope is required to make sure all child QML instances + # are destroyed in the correct order. + del view + sys.exit(res) diff --git a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/doc/chapter4-customPropertyTypes.rst b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/doc/chapter4-customPropertyTypes.rst new file mode 100644 index 000000000..f7c3efb11 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/doc/chapter4-customPropertyTypes.rst @@ -0,0 +1,73 @@ +.. _qml-chapter4-custompropertytypes: + +Extending QML - Using Custom Property Types +=========================================== + +This is the fourth of a series of 6 examples forming a tutorial about extending +QML with Python. + +The ``PieChart`` type currently has a string-type property and a color-type property. +It could have many other types of properties. For example, it could have an +int-type property to store an identifier for each chart: + +.. code-block:: python + + class PieChart(QQuickPaintedItem): + chartIdChanged = Signal() + + @Property(int, notify=chartIdChanged) + def chartId(self): + pass + + @chartId.setter + def setChartId(self, chartId): + pass + +.. code-block:: javascript + + // QML + PieChart { + ... + chartId: 100 + } + +Aside from ``int``, we could use various other property types. Many of the Qt +data types such as ``QColor``, ``QSize`` and ``QRect`` are automatically +supported from QML. + +If we want to create a property whose type is not supported by QML by default, +we need to register the type with the QML engine. + +For example, let's replace the use of the ``property`` with a type called +``PieSlice`` that has a ``color`` property. Instead of assigning a color, +we assign an ``PieSlice`` value which itself contains a ``color``: + +.. literalinclude:: app.qml + :lineno-start: 4 + :lines: 4-22 + +Like ``PieChart``, this new ``PieSlice`` type inherits from +``QQuickPaintedItem``, is exposed via the ``QmlElement`` decorator and declares +its properties with the ``Property`` decorator: + +.. literalinclude:: customPropertyTypes.py + :lineno-start: 21 + :lines: 21-40 + +To use it in ``PieChart``, we modify the ``color`` property declaration +and associated method signatures: + +.. literalinclude:: customPropertyTypes.py + :lineno-start: 58 + :lines: 58-65 + +There is one thing to be aware of when implementing ``setPieSlice()``. The +``PieSlice`` is a visual item, so it must be set as a child of the ``PieChart`` +using ``QQuickItem.setParentItem()`` so that the ``PieChart`` knows to paint +this child item when its contents are drawn. + +As with ``PieChart``, we add the ``Charts`` type namespace, version 1.0: + +.. literalinclude:: customPropertyTypes.py + :lineno-start: 15 + :lines: 15-18 diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/app.qml b/examples/qml/tutorials/extending-qml/chapter5-listproperties/app.qml new file mode 100644 index 000000000..ac99d5a40 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter5-listproperties/app.qml @@ -0,0 +1,32 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import Charts +import QtQuick + +Item { + width: 300; height: 200 + + PieChart { + anchors.centerIn: parent + width: 100; height: 100 + + slices: [ + PieSlice { + anchors.fill: parent + color: "red" + fromAngle: 0; angleSpan: 110 + }, + PieSlice { + anchors.fill: parent + color: "black" + fromAngle: 110; angleSpan: 50 + }, + PieSlice { + anchors.fill: parent + color: "blue" + fromAngle: 160; angleSpan: 100 + } + ] + } +} diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/chapter5-listproperties.pyproject b/examples/qml/tutorials/extending-qml/chapter5-listproperties/chapter5-listproperties.pyproject new file mode 100644 index 000000000..a3f89d575 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter5-listproperties/chapter5-listproperties.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["app.qml", "listproperties.py"] +} diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst b/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst new file mode 100644 index 000000000..a98f18c81 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst @@ -0,0 +1,47 @@ +.. _qml-chapter5-listproperties: + +Extending QML - Using List Property Types +========================================= + +This is the fifth of a series of 6 examples forming a tutorial about extending +QML with Python. + +Right now, a ``PieChart`` can only have one ``PieSlice.`` Ideally a chart would +have multiple slices, with different colors and sizes. To do this, we could +have a ``slices`` property that accepts a list of ``PieSlice`` items: + +.. literalinclude:: app.qml + :lineno-start: 4 + :lines: 4-32 + +To do this, we replace the ``pieSlice`` property in ``PieChart`` with a +``slices`` property, declared as a class variable of the +:class:`~PySide6.QtQml.ListProperty` type. +The ``ListProperty`` class enables the creation of list properties in +QML extensions. We replace the ``pieSlice()`` function with a ``slices()`` +function that returns a list of slices, and add an internal ``appendSlice()`` +function (discussed below). We also use a list to store the internal list of +slices as ``_slices``: + +.. literalinclude:: listproperties.py + :lineno-start: 62 + :lines: 62-65 + +.. literalinclude:: listproperties.py + :lineno-start: 75 + :lines: 75-79 + +Although the ``slices`` property does not have an associated setter, it is +still modifiable because of the way ``ListProperty`` works. We indicate +that the internal ``PieChart.appendSlice()`` function is to be called whenever +a request is made from QML to add items to the list. + +The ``appendSlice()`` function simply sets the parent item as before, and adds +the new item to the ``_slices`` list. As you can see, the append function for +a ``ListProperty`` is called with two arguments: the list property, and the +item that is to be appended. + +The ``PieSlice`` class has also been modified to include ``fromAngle`` and +``angleSpan`` properties and to draw the slice according to these values. This +is a straightforward modification if you have read the previous pages in this +tutorial, so the code is not shown here. diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py b/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py new file mode 100644 index 000000000..98952cef1 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py @@ -0,0 +1,97 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the qml/tutorials/extending-qml/chapter5-listproperties example from Qt v5.x""" + +import os +from pathlib import Path +import sys + +from PySide6.QtCore import Property, QUrl +from PySide6.QtGui import QGuiApplication, QPen, QPainter, QColor +from PySide6.QtQml import QmlElement, ListProperty +from PySide6.QtQuick import QQuickPaintedItem, QQuickView, QQuickItem + +# To be used on the @QmlElement decorator +# (QML_IMPORT_MINOR_VERSION is optional) +QML_IMPORT_NAME = "Charts" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class PieSlice (QQuickPaintedItem): + def __init__(self, parent=None): + QQuickPaintedItem.__init__(self, parent) + self._color = QColor() + self._fromAngle = 0 + self._angleSpan = 0 + + @Property(QColor, final=True) + def color(self): + return self._color + + @color.setter + def color(self, value): + self._color = value + + @Property(int, final=True) + def fromAngle(self): + return self._angle + + @fromAngle.setter + def fromAngle(self, value): + self._fromAngle = value + + @Property(int, final=True) + def angleSpan(self): + return self._angleSpan + + @angleSpan.setter + def angleSpan(self, value): + self._angleSpan = value + + def paint(self, painter): + pen = QPen(self._color, 2) + painter.setPen(pen) + painter.setRenderHints(QPainter.Antialiasing, True) + painter.drawPie( + self.boundingRect().adjusted(1, 1, -1, -1), self._fromAngle * 16, self._angleSpan * 16) + + +@QmlElement +class PieChart (QQuickItem): + def __init__(self, parent=None): + QQuickItem.__init__(self, parent) + self._name = u'' + self._slices = [] + + @Property(str, final=True) + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + def appendSlice(self, _slice): + _slice.setParentItem(self) + self._slices.append(_slice) + + slices = ListProperty(PieSlice, appendSlice, final=True) + + +if __name__ == '__main__': + app = QGuiApplication(sys.argv) + + view = QQuickView() + view.setResizeMode(QQuickView.SizeRootObjectToView) + qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml') + view.setSource(QUrl.fromLocalFile(qml_file)) + if view.status() == QQuickView.Error: + sys.exit(-1) + view.show() + res = app.exec() + # Deleting the view before it goes out of scope is required to make sure all child QML instances + # are destroyed in the correct order. + del view + sys.exit(res) diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py new file mode 100644 index 000000000..3ab8bcc08 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py @@ -0,0 +1,41 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Property +from PySide6.QtQml import QmlElement, ListProperty +from PySide6.QtQuick import QQuickItem + +from pieslice import PieSlice + +# To be used on the @QmlElement decorator +# (QML_IMPORT_MINOR_VERSION is optional) +QML_IMPORT_NAME = "Charts" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class PieChart(QQuickItem): + def __init__(self, parent=None): + super().__init__(parent) + self._slices = [] + self._name = '' + + @Property(str, final=True) + def name(self): + return self._name + + @name.setter + def name(self, name): + self._name = name + + def slice(self, n): + return self._slices[n] + + def sliceCount(self): + return len(self._slices) + + def append_and_setparent(self, slice): + self._slices.append(slice) + slice.setParentItem(self) + + slices = ListProperty(PieSlice, append_and_setparent) diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py new file mode 100644 index 000000000..6f82f1f10 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py @@ -0,0 +1,53 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Property, QRectF +from PySide6.QtGui import QColor, QPainter, QPen +from PySide6.QtQuick import QQuickPaintedItem +from PySide6.QtQml import QmlElement + +# To be used on the @QmlElement decorator +# (QML_IMPORT_MINOR_VERSION is optional) +QML_IMPORT_NAME = "Charts" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class PieSlice(QQuickPaintedItem): + def __init__(self, parent=None): + super().__init__(parent) + + self._color = QColor() + self._from_angle = 0 + self._angle_span = 0 + + @Property(QColor, final=True) + def color(self): + return self._color + + @color.setter + def color(self, color): + self._color = QColor(color) + + @Property(int, final=True) + def fromAngle(self): + return self._from_angle + + @fromAngle.setter + def fromAngle(self, fromAngle): + self._from_angle = fromAngle + + @Property(int, final=True) + def angleSpan(self): + return self._angle_span + + @angleSpan.setter + def angleSpan(self, angleSpan): + self._angle_span = angleSpan + + def paint(self, painter): + painter.setPen(QPen(self._color, 2)) + painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) + + rect = QRectF(0, 0, self.width(), self.height()).adjusted(1, 1, -1, -1) + painter.drawPie(rect, self._from_angle * 16, self._angle_span * 16) diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/plugins.png b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/plugins.png Binary files differnew file mode 100644 index 000000000..8992e89c0 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/plugins.png diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/app.qml b/examples/qml/tutorials/extending-qml/chapter6-plugins/app.qml new file mode 100644 index 000000000..1a4772e15 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/app.qml @@ -0,0 +1,32 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import Charts 1.0 + +Item { + width: 300; height: 200 + + PieChart { + anchors.centerIn: parent + width: 100; height: 100 + + slices: [ + PieSlice { + anchors.fill: parent + color: "red" + fromAngle: 0; angleSpan: 110 + }, + PieSlice { + anchors.fill: parent + color: "black" + fromAngle: 110; angleSpan: 50 + }, + PieSlice { + anchors.fill: parent + color: "blue" + fromAngle: 160; angleSpan: 100 + } + ] + } +} diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/chapter6-plugins.pyproject b/examples/qml/tutorials/extending-qml/chapter6-plugins/chapter6-plugins.pyproject new file mode 100644 index 000000000..cc684401f --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/chapter6-plugins.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["app.qml", "Charts/piechart.py", "Charts/pieslice.py"] +} diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/doc/chapter6-plugins.rst b/examples/qml/tutorials/extending-qml/chapter6-plugins/doc/chapter6-plugins.rst new file mode 100644 index 000000000..a9d100812 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/doc/chapter6-plugins.rst @@ -0,0 +1,26 @@ +.. _qml-chapter6-plugins-example: + +Extending QML - Plugins Example +=============================== + +This is the last of a series of 6 examples forming a tutorial +about extending QML with Python. + +This example refers to the Python version of using a QML plugin in Python. The +idea of plugins in Python is non-existent because Python modules are +dynamically loaded anyway. We use this idea and our QML type registration +decorators - ``QmlELement``/``QmlNamedElement`` - to register the QML modules as they +are imported. The ``pyside6-qml`` tool does this for you by simply pointing to the +``.qml`` file. + +.. image:: plugins.png + :width: 400 + :alt: Plugins Example + + +Running the Example +------------------- + +.. code-block:: shell + + pyside6-qml examples/qml/tutorials/extending-qml/chapter6-plugins/app.qml -I examples/qml/tutorials/extending-qml/chapter6-plugins/Charts diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/doc/plugins.png b/examples/qml/tutorials/extending-qml/chapter6-plugins/doc/plugins.png Binary files differnew file mode 100644 index 000000000..8992e89c0 --- /dev/null +++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/doc/plugins.png |