Nova Flow OS
KDE Developer Platform
KDE Developer Platform
  • KDE Developer Platform
    • Getting started
      • Building KDE software
        • KDE software
        • Where to find the development team
        • Learning more
        • Choose what to work on
        • Source code cross-referencing
        • Installing build dependencies
        • Set up a development environment
        • Building KDE software with kdesrc-build
        • Basic troubleshooting
        • Tips and tricks
        • IDE Configuration
          • Setting up an IDE for KDE development
          • Visual Studio Code
          • Qt Creator
          • Kate
          • KDevelop
          • CLion
          • Sublime Text
        • Building KDE software manually
        • Building KDE software with distrobox and podman
      • Kirigami
        • KDE is ours
        • Setting up and getting started
        • Explaining pages
        • Layouts, ListViews, and Cards
        • Adding actions
        • Adding a dialog
        • Using separate files
        • Next steps
        • Colors and themes in Kirigami
        • Typography
        • Actions based components
        • Page rows and page stacks
        • Scrollable pages and list views
        • Cards
        • Drawers
        • Chips
        • Dialog types
        • Controls and interactive elements
        • Form layouts
        • Inline messages
        • Action toolbars
        • Progress bars and indicators
        • List views
        • Understanding CMakeLists
        • Figuring out main.cpp
        • Connect logic to your QML user interface
        • Connect models to your QML user interface
        • About page
        • Introduction to Kirigami Addons
        • FormCard About pages
        • Form delegates in your settings pages
      • KXmlGui
        • Getting started with KXmlGui
        • Hello World!
        • Creating the main window
        • Using actions
        • Saving and loading
        • Command line interface
      • Python with Kirigami
        • Apps with QML and Python
        • Your first Python + Kirigami application
        • Creating a Python package
        • Creating a Flatpak
      • Common programming mistakes
      • Adding a new KDE project
    • Features
      • Icons
      • Configuration
        • The KConfig Framework
        • Introduction to KConfig
        • Using KConfig XT
        • KDE Frameworks 6 porting guide
        • Settings module (KCM) development
        • KConfigDialog
      • D-Bus
        • What is D-Bus practically useful for?
        • Introduction to D-Bus
        • Accessing D-Bus interfaces
        • Intermediate D-Bus
        • Creating D-Bus interfaces
        • Using custom types with D-Bus
        • D-Bus autostart services
      • Create your own mouse cursor theme
      • Session management
      • Archives
      • Desktop file
      • KAuth
        • Privilege Escalation
        • Using actions in your applications
      • KIdleTime
      • Akonadi: personal information management
        • Debugging Akonadi Resources
        • Using Akonadi in applications
      • Concurrent programming
      • Solid
      • Sonnet
    • Plasma themes and plugins
      • Getting started
      • Plasma Widget tutorial
        • How to create a plasmoid
        • Setup
        • Porting Plasmoids to KF6
        • Testing
        • QML
        • Plasma's QML API
        • Widget Properties
        • Configuration
        • Translations / i18n
        • Examples
        • C++ API
      • KWin Effects
      • Plasma Desktop scripting
        • Javascript Interaction With Plasma Shells
        • Templates
        • Examples
        • API documentation
        • Configuration keys
      • Plasma Style tutorial
        • Creating a Plasma Style quickstart
        • Understanding Plasma Styles
        • SVG elements and Inkscape
        • Background SVG format
        • System and accent colors
        • Theme elements reference
        • Porting themes to Plasma 5
        • Porting themes to Plasma 6
      • Aurorae window decorations
      • KWin scripting tutorial
        • Quick start
        • KWin scripting API
      • Wallpapers
      • Plasma comic
        • Tutorial
        • Testing and debugging
        • Examples
      • Create a custom Window Switcher
      • KRunner C++ Plugin
        • Basic Anatomy of a Runner
        • KRunner metadata format
    • Applications
      • Creating sensor faces
      • Dolphin
        • Creating Dolphin service menus
      • Kate
        • Kate plugin tutorial
      • KMines
        • Making a KMines theme
      • Writing tests
        • Appium automation testing
    • Packaging
      • Android
        • KDE on Android
        • Building applications for Android
        • Packaging and publishing applications for Android
        • Publishing on Google Play
          • Introduction
          • Packaging your app
          • Adding your app to Google Play
          • Publishing your app
          • Releasing new versions of old apps
        • Porting applications to Android
          • Basic porting
          • Making applications run well on Android
          • Metadata
      • Windows
        • Packaging and publishing applications for Windows
        • Publish your app in the Microsoft Store
          • Packaging your app for the Microsoft Store
          • Submitting your app to the Microsoft Store
      • Plasma Mobile
        • KDE on mobile devices
        • Porting a new device to Plasma Mobile
        • KDE Telephony stack
          • General Overview
          • Kernel layer
          • System daemons
            • General overview
            • Developing Telephony functionality
            • ModemManager Telephony functions
          • Session daemons
          • QML declarative plugin layer
          • KDE application layer
        • Execute applications
      • Distributing KDE software as Flatpak
        • Your first Flatpak
        • Extending your package
        • Nightly Flatpaks and Flathub
        • Testing your Flatpak
    • System administration
      • Shell scripting with KDE dialogs
      • Kiosk: Simple configuration management for large deployment
        • Abstract
        • Introduction to Kiosk
        • Kiosk keys
    • Contribute to the documentation
    • About
      • Readme
      • License
        • Creative Commons Attribution-ShareAlike 4.0 International
        • GNU General Public License 3.0 or later
Powered by GitBook
On this page
  • Prerequisites
  • Structure
  • Development
  1. KDE Developer Platform
  2. Getting started
  3. Python with Kirigami

Your first Python + Kirigami application

Learn how to write an application with PyQt/PySide.

PreviousApps with QML and PythonNextCreating a Python package

Last updated 8 months ago

Prerequisites

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

To use Python together with QML, we can use either , the official Python bindings for the Qt framework or , 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 later.

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

Tip

To quickly generate this folder structure, just run: mkdir -p simplemdviewer/src/qml/

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:

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

Activate it using the activate script:

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:

#!/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()

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

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
                }
            }
    	}
    }
}

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.

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

python3 simplemdviewer_app.py

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

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.

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

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)

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.

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

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:

#!/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()

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:

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:

python3 simplemdviewer_app.py

Play with adding some Markdown text:

Hooray!

,

We have just created a object that initializes the application and contains the main event loop. The object loads the main.qml file.

OK, let’s add some Python logic: a simple Markdown converter in a Python, derivative class.

When setting the sourceText property, the sourceTextChanged 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 so as to be invokable by the QML code.

sudo pacman -S python-pyqt6 kirigami flatpak-builder qqc2-desktop-style appstream
sudo zypper install python3-qt6 kf6-kirigami-devel flatpak-builder qqc2-desktop-style AppStream-compose
sudo dnf install python3-pyqt6 kf6-kirigami-devel flatpak-builder qqc2-desktop-style appstream-compose
PySide
PyQt
Python virtual environment
QGuiApplication
QQmlApplicationEngine
QObject
signal
slot
Manjaro
Arch
OpenSUSE
Fedora