For the purposes of this tutorial, we will create the application on Linux.
To use Python together with QML, we can use either PySide, the official Python bindings for the Qt framework or PyQt, 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 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/:
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:
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 python3import osimport sysimport signalfrom PySide6.QtGui import QGuiApplicationfrom PySide6.QtCore import QUrlfrom PySide6.QtQml import QQmlApplicationEnginedefmain():"""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"""ifnot 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)iflen(engine.rootObjects())==0:quit() app.exec()if__name__=="__main__":main()
We have just created a QGuiApplication object that initializes the application and contains the main event loop. The QQmlApplicationEngine object loads the main.qml file.
Create a new src/qml/main.qml file that specifies the UI of the application:
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:
python3simplemdviewer_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:
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 sourceTextChangedsignal 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 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:
python3-mpipinstallmarkdown
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 python3import osimport sysimport signalfrom PySide6.QtGui import QGuiApplicationfrom PySide6.QtCore import QUrlfrom PySide6.QtQml import QQmlApplicationEnginefrom md_converter import MdConverter # noqa: F401defmain():"""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"""ifnot 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)iflen(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.