# Your first Python + Kirigami application

### Prerequisites

For the purposes of this tutorial, we will create the application on Linux.

To use Python together with QML, we can use either [PySide](https://doc.qt.io/qtforpython-6/), the official Python bindings for the Qt framework or [PyQt](https://riverbankcomputing.com/software/pyqt/intro), a project by Riverbank Computing that allows you to write Qt applications using Python.

You will need Python installed, and that will be the case in any major Linux distribution. But instead of using `pip` to install PySide/PyQt and Kirigami, you will need to install them from your distribution. This ensures PySide/PyQt and Kirigami will have been built for the same Qt version, allowing you to package it easily. Any other dependencies can be installed from `pip` in a [Python virtual environment](https://docs.python.org/3/library/venv.html) later.

<table data-header-hidden><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><a href="https://software.manjaro.org/package/python-pyqt6">Manjaro</a>, <a href="https://archlinux.org/packages/?q=python-pyqt6">Arch</a></td><td><pre><code>sudo pacman -S python-pyqt6 kirigami flatpak-builder qqc2-desktop-style appstream
</code></pre></td></tr><tr><td><a href="https://software.opensuse.org/package/python3-qt6">OpenSUSE</a></td><td><pre><code>sudo zypper install python3-qt6 kf6-kirigami-devel flatpak-builder qqc2-desktop-style AppStream-compose
</code></pre></td></tr><tr><td><a href="https://packages.fedoraproject.org/pkgs/python3-pyqt6/python3-pyqt6/">Fedora</a></td><td><pre><code>sudo dnf install python3-pyqt6 kf6-kirigami-devel flatpak-builder qqc2-desktop-style appstream-compose
</code></pre></td></tr></tbody></table>

### Structure

The application will be a simple Markdown viewer called `simplemdviewer`.

By the end of the tutorial, the project will look like this:

```
simplemdviewer/
├── README.md
├── LICENSE.txt
├── MANIFEST.in                        # To add our QML file
├── pyproject.toml                     # To declare the tools needed to build
├── setup.py                           # To import setuptools
├── setup.cfg                          # The setuptools metadata
├── org.kde.simplemdviewer.desktop
├── org.kde.simplemdviewer.json
├── org.kde.simplemdviewer.svg
├── org.kde.simplemdviewer.metainfo.xml
└── src/
    ├── __init__.py                    # To import the src/ directory as a package
    ├── __main__.py                    # To signal simplemdviewer_app as the entrypoint
    ├── simplemdviewer_app.py
    ├── md_converter.py
    └── qml/
        └── main.qml
```

All of the metadata will be in the root folder, while the actual code will be in `src/`:

```
simplemdviewer/
└── src/
    ├── simplemdviewer_app.py
    ├── md_converter.py
    └── qml/
        └── main.qml
```

{% hint style="success" %}
Tip

To quickly generate this folder structure, just run: `mkdir -p simplemdviewer/src/qml/`
{% endhint %}

### Development

The UI will be created in QML and the logic in Python. Users will write some Markdown text, press a button, and the formatted text will be shown below it.

It is recommended to use a virtual environment. The `venv` module provides support for virtual environments with their own site directories, optionally isolated from system site directories.

Create a directory and a virtual environment for the project:

```bash
mkdir simplemdviewer
cd simplemdviewer
python3 -m venv --system-site-packages env/
```

Activate it using the activate script:

```bash
source env/bin/activate
```

We can verify that we are working in a virtual environment by checking the `VIRTUAL_ENV` environment variable with `env | grep VIRTUAL_ENV`.

It’s time to write some code. At first the application will consist of two files: a file with the QML description of the user interface, and a Python file that loads the QML file.

Create a new directory `simplemdviewer/src/` and add a new `simplemdviewer_app.py` file in this directory:

{% tabs %}
{% tab title="PySide6" %}

```python
#!/usr/bin/env python3

import os
import sys
import signal
from PySide6.QtGui import QGuiApplication
from PySide6.QtCore import QUrl
from PySide6.QtQml import QQmlApplicationEngine


def main():
    """Initializes and manages the application execution"""
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    """Needed to close the app with Ctrl+C"""
    signal.signal(signal.SIGINT, signal.SIG_DFL)

    """Needed to get proper KDE style outside of Plasma"""
    if not os.environ.get("QT_QUICK_CONTROLS_STYLE"):
        os.environ["QT_QUICK_CONTROLS_STYLE"] = "org.kde.desktop"

    base_path = os.path.abspath(os.path.dirname(__file__))
    url = QUrl(f"file://{base_path}/qml/main.qml")
    engine.load(url)

    if len(engine.rootObjects()) == 0:
        quit()

    app.exec()


if __name__ == "__main__":
    main()
```

{% endtab %}

{% tab title="PyQt6" %}

```python
```

{% endtab %}
{% endtabs %}

We have just created a [QGuiApplication](https://doc.qt.io/qtforpython-6/PySide6/QtGui/QGuiApplication.html#PySide6.QtGui.QGuiApplication) object that initializes the application and contains the main event loop. The [QQmlApplicationEngine](https://doc.qt.io/qtforpython-6/PySide6/QtQml/QQmlApplicationEngine.html#PySide6.QtQml.QQmlApplicationEngine) object loads the `main.qml` file.

Create a new `src/qml/main.qml` file that specifies the UI of the application:

```qml
import QtQuick
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
import QtQuick.Layouts

Kirigami.ApplicationWindow {
    id: root

    title: qsTr("Simple Markdown viewer")

    minimumWidth: Kirigami.Units.gridUnit * 20
    minimumHeight: Kirigami.Units.gridUnit * 20
    width: minimumWidth
    height: minimumHeight

    pageStack.initialPage: initPage

    Component {
        id: initPage

        Kirigami.Page {
            title: qsTr("Markdown Viewer")

            ColumnLayout {
                anchors {
                    top: parent.top
                    left: parent.left
                    right: parent.right
                }
                Controls.TextArea {
                    id: sourceArea

                    placeholderText: qsTr("Write some Markdown code here")
                    wrapMode: Text.WrapAnywhere
                    Layout.fillWidth: true
                    Layout.minimumHeight: Kirigami.Units.gridUnit * 5 
                }

                RowLayout {
                    Layout.fillWidth: true

                    Controls.Button {
                        text: qsTr("Format")

                        onClicked: formattedText.text = sourceArea.text
                    }

                    Controls.Button {
                        text: qsTr("Clear")

                        onClicked: {
                            sourceArea.text = ""
                            formattedText.text = ""
                        }
                    }
                } 

                Text {
                    id: formattedText

                    textFormat: Text.RichText
                    wrapMode: Text.WordWrap
                    text: sourceArea.text

                    Layout.fillWidth: true
                    Layout.minimumHeight: Kirigami.Units.gridUnit * 5
                }
            }
    	}
    }
}
```

{% hint style="warning" %}
Warning Older distributions such as Debian or Ubuntu LTS that do not have an up-to-date Kirigami might require lowering the Kirigami import version from \`3.20\` to \`3.15\` to run.
{% endhint %}

We have just created a new QML-Kirigami-Python application. Run it:

```bash
python3 simplemdviewer_app.py
```

![](https://2854466518-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2YSWk59V1RtNco6tGNDq%2Fuploads%2Fgit-blob-cec8ca6d793ba379bb13a2a098708f972f5e777d%2Fsimplemdviewer1.webp?alt=media)

At the moment we have not used any interesting Python stuff. In reality, the application can also run as a standalone QML one:

```bash
QT_QUICK_CONTROLS_STYLE=org.kde.desktop qml main.qml
```

It does not format anything; if we click on "Format" it just spits the unformatted text into a text element.

![](https://2854466518-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2YSWk59V1RtNco6tGNDq%2Fuploads%2Fgit-blob-9cfb45e40bc1a089f52acfaf7d3691e5546bb1ef%2Fsimplemdviewer2.webp?alt=media)

OK, let’s add some Python logic: a simple Markdown converter in a Python, [QObject](https://doc.qt.io/qtforpython-6/PySide6/QtCore/QObject.html) derivative class.

* Create a new `md_converter.py` file in the `simplemdviewer` directory:

{% tabs %}
{% tab title="PySide6" %}

```python
from markdown import markdown
from PySide6.QtCore import QObject, Signal, Slot, Property
from PySide6.QtQml import QmlElement

QML_IMPORT_NAME = "org.kde.simplemdviewer"
QML_IMPORT_MAJOR_VERSION = 1


@QmlElement
class MdConverter(QObject):
    """A simple markdown converter"""

    sourceTextChanged = Signal()

    def __init__(self, _source_text=""):
        super().__init__()
        self._source_text = _source_text

    @Property(str, notify=sourceTextChanged)
    def sourceText(self):
        return self._source_text

    @sourceText.setter
    def sourceText(self, val):
        self._source_text = val
        self.sourceTextChanged.emit()

    @Slot(result=str)
    def mdFormat(self):
        return markdown(self._source_text)
```

{% endtab %}

{% tab title="PyQt6" %}

```python
```

{% endtab %}
{% endtabs %}

The `MdConverter` class contains the `_source_text` member variable. The `sourceText` property exposes `_source_text` to the QML system through the `readSourceText()` getter and the `setSourceText()` setter functions in PyQt. In PySide, Python-like setters and getters are used for this purpose.

When setting the `sourceText` property, the `sourceTextChanged` [signal](https://doc.qt.io/qtforpython-6/overviews/signalsandslots.html#signals) is emitted to let QML know that the property has changed. The `mdFormat()` function returns the Markdown-formatted text and it has been declared as a [slot](https://doc.qt.io/qtforpython-6/overviews/signalsandslots.html#slots) so as to be invokable by the QML code.

The `markdown` Python package takes care of formatting. Let’s install it in our virtual environment:

```bash
python3 -m pip install markdown
```

It is worth noting that in PySide, the Python decorator `@QmlElement`, along with the `QML_IMPORT_NAME` and `QML_IMPORT_MAJOR_VERSION` takes care of registering the class `MdConveter` with QML. In PyQt, this is done through the function `qmlRegisterType()` inside `simplemdviewer_app.py` as seen below.

Update the `simplemdviewer_app.py` file to:

{% tabs %}
{% tab title="PySide6" %}

```python
#!/usr/bin/env python3

import os
import sys
import signal
from PySide6.QtGui import QGuiApplication
from PySide6.QtCore import QUrl
from PySide6.QtQml import QQmlApplicationEngine

from md_converter import MdConverter  # noqa: F401


def main():
    """Initializes and manages the application execution"""
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    """Needed to close the app with Ctrl+C"""
    signal.signal(signal.SIGINT, signal.SIG_DFL)

    """Needed to get proper KDE style outside of Plasma"""
    if not os.environ.get("QT_QUICK_CONTROLS_STYLE"):
        os.environ["QT_QUICK_CONTROLS_STYLE"] = "org.kde.desktop"

    base_path = os.path.abspath(os.path.dirname(__file__))
    url = QUrl(f"file://{base_path}/qml/main.qml")
    engine.load(url)

    if len(engine.rootObjects()) == 0:
        quit()

    app.exec()


if __name__ == "__main__":
    main()
```

{% endtab %}

{% tab title="PyQt6" %}

{% endtab %}
{% endtabs %}

In PyQt, the `qmlRegisterType()` function has registered the `MdConverter` type in the QML system, in the library `org.kde.simplemdviewer`, version 1.0. In PySide, this registration is done in the file where the class is defined i.e. `md_converter.py` through the `@QmlElement` decorator. The import name and version of `MdConverter` type is set through the variables `QML_IMPORT_NAME` and `QML_IMPORT_MAJOR_VERSION`. Finally, the Python import `from md_converter import MdConverter` in PySide's `simplemdviewer_app.py` takes care of making Python and QML engine aware of the `@QmlElement` decorator.

Change `main.qml` to:

```qml
import QtQuick
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
import QtQuick.Layouts
import org.kde.simplemdviewer 1.0

Kirigami.ApplicationWindow {
    id: root

    title: qsTr("Simple Markdown viewer")

    minimumWidth: Kirigami.Units.gridUnit * 20
    minimumHeight: Kirigami.Units.gridUnit * 20
    width: minimumWidth
    height: minimumHeight

    pageStack.initialPage: initPage

    Component {
        id: initPage

        Kirigami.Page {
            title: qsTr("Markdown Viewer")

            MdConverter {
                id: mdconverter

                sourceText: sourceArea.text
            }
  
            ColumnLayout {
                anchors {
                    top: parent.top
                    left: parent.left
                    right: parent.right
                }
                Controls.TextArea {
                    id: sourceArea

                    placeholderText: qsTr("Write some Markdown code here")
                    wrapMode: Text.WrapAnywhere
                    Layout.fillWidth: true
                    Layout.minimumHeight: Kirigami.Units.gridUnit * 5 
                }

                RowLayout {
                    Layout.fillWidth: true

                    Controls.Button {
                        text: qsTr("Format")

                        onClicked: formattedText.text = mdconverter.mdFormat()
                    }

                    Controls.Button {
                        text: qsTr("Clear")

                        onClicked: {
                            sourceArea.text = ""
                            formattedText.text = ""
                        }
                    }
                } 

                Text {
                    id: formattedText

                    textFormat: Text.RichText
                    wrapMode: Text.WordWrap

                    Layout.fillWidth: true
                    Layout.minimumHeight: Kirigami.Units.gridUnit * 5
                }
            }
    	}
    }
}
```

The updated QML code:

1. imports the `org.kde.simplemdviewer` library
2. creates an `MdConverter` object
3. updates the `onClicked` signal handler of the `Format` button to call the `mdFormat()` function of the converter object

Finally, test your new application:

```bash
python3 simplemdviewer_app.py
```

Play with adding some Markdown text:

![](https://2854466518-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2YSWk59V1RtNco6tGNDq%2Fuploads%2Fgit-blob-0f3d186f0d09224859883ed78692371b5a296799%2Fsimplemdviewer3.webp?alt=media)

Hooray!
