[plasma/plasma-sdk] /: Introducing kqml: QML runtime with initialized KLocalizedContext

ivan tkachenko null at kde.org
Mon Jun 26 21:36:21 BST 2023


Git commit d6ca40935647ea12f58cae0cf361280ced9fe2b0 by ivan tkachenko.
Committed on 26/06/2023 at 17:59.
Pushed by ratijas into branch 'master'.

Introducing kqml: QML runtime with initialized KLocalizedContext

Useful for testing scratch files that either directly or via imported
modules assume that KLocalizedString domain has been set and
KLocalizedContext initialized, so they can rely on family of i18n*
functions being available in QML scope. In other words, for loading any
real-world KDE QML source file without getting these errors:

> ReferenceError: i18n is not defined

This simple application is very similar to `qml` and `qmlscene` tools
provided by qt-declarative package. But unlike those tools kqml is very
minimal, without any of the advanced options, custom containment
configs or things like OS X specific platform workarounds. Those things
can always be added back later if desired, but so far I personally
never heard of anyone who would use any of `qml` options beyond passing
it a single *.qml file. Except… maybe a --slow-animations which we
can't implement anyway without including private API bits.

This new tool comes with documentation (man page) and zsh completions.

M  +1    -0    CMakeLists.txt
A  +23   -0    kqml/CMakeLists.txt
A  +7    -0    kqml/Messages.sh
A  +11   -0    kqml/completions/kqml.zsh
A  +189  -0    kqml/main.cpp     [License: GPL(v2.0+)]
A  +98   -0    kqml/man-kqml.1.docbook

https://invent.kde.org/plasma/plasma-sdk/-/commit/d6ca40935647ea12f58cae0cf361280ced9fe2b0

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 959d5ce0..0f719309 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -77,6 +77,7 @@ ecm_set_disabled_deprecation_versions(
 
 add_subdirectory(cuttlefish)
 add_subdirectory(engineexplorer)
+add_subdirectory(kqml)
 add_subdirectory(plasmoidviewer)
 add_subdirectory(themeexplorer)
 add_subdirectory(lookandfeelexplorer)
diff --git a/kqml/CMakeLists.txt b/kqml/CMakeLists.txt
new file mode 100644
index 00000000..57e1454b
--- /dev/null
+++ b/kqml/CMakeLists.txt
@@ -0,0 +1,23 @@
+# SPDX-FileCopyrightText: 2023 ivan tkachenko <me at ratijas.tk>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(kqml_SRCS
+    main.cpp
+)
+
+add_executable(kqml ${kqml_SRCS})
+target_compile_definitions(kqml PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}")
+
+target_link_libraries(kqml
+    Qt::Quick
+    Qt::Gui
+    KF6::I18n
+)
+
+set_target_properties(kqml PROPERTIES WIN32_EXECUTABLE FALSE)
+
+install(TARGETS kqml ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
+
+kdoctools_create_manpage (man-kqml.1.docbook 1 INSTALL_DESTINATION ${KDE_INSTALL_MANDIR})
+install(FILES completions/kqml.zsh RENAME _kqml DESTINATION ${KDE_INSTALL_ZSHAUTOCOMPLETEDIR})
diff --git a/kqml/Messages.sh b/kqml/Messages.sh
new file mode 100755
index 00000000..97d3215e
--- /dev/null
+++ b/kqml/Messages.sh
@@ -0,0 +1,7 @@
+#! /usr/bin/env bash
+
+# SPDX-FileCopyrightText: 2023 ivan tkachenko <me at ratijas.tk>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+$XGETTEXT *.cpp -o $podir/kqml.pot
diff --git a/kqml/completions/kqml.zsh b/kqml/completions/kqml.zsh
new file mode 100644
index 00000000..18a4434e
--- /dev/null
+++ b/kqml/completions/kqml.zsh
@@ -0,0 +1,11 @@
+#compdef kqml
+
+# SPDX-FileCopyrightText: 2023 ivan tkachenko <me at ratijas.tk>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+_arguments \
+  '(-d --domain)'{-d,--domain=}"[The main localization domain]:application's main domain:" \
+  '(-)'{-h,--help}'[Displays this help]' \
+  '(-)'{-v,--version}'[Displays version information]' \
+  '*:files:_files -g "*.qml"'
diff --git a/kqml/main.cpp b/kqml/main.cpp
new file mode 100644
index 00000000..b36288b4
--- /dev/null
+++ b/kqml/main.cpp
@@ -0,0 +1,189 @@
+/*
+    SPDX-FileCopyrightText: 2023 ivan tkachenko <me at ratijas.tk>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <QCommandLineOption>
+#include <QCommandLineParser>
+#include <QCoreApplication>
+#include <QDir>
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QQmlComponent>
+#include <QQmlContext>
+#include <QQuickItem>
+#include <QQuickWindow>
+#include <QScopedPointer>
+#include <QStringList>
+
+#include <KLocalizedContext>
+#include <KLocalizedString>
+
+#include <stdio.h>
+
+void noFilesGiven()
+{
+    printf("kqml: No files specified. Terminating.\n");
+    exit(1);
+}
+
+// Listens to the appEngine signals to determine if all files failed to load
+class LoadWatcher : public QObject
+{
+    Q_OBJECT
+
+public:
+    explicit LoadWatcher(QQmlApplicationEngine *engine, int expected);
+
+    int returnCode = 0;
+    bool earlyExit = false;
+
+public Q_SLOTS:
+    void checkFinished(QObject *object, const QUrl &url);
+    void quit();
+    void exit(int retCode);
+
+private:
+    void contain(QQuickItem *item);
+
+private:
+    int expectedFileCount;
+    int createdObjects;
+};
+
+LoadWatcher::LoadWatcher(QQmlApplicationEngine *engine, int expected)
+    : QObject(engine)
+    , expectedFileCount(expected)
+    , createdObjects(0)
+{
+    connect(engine, &QQmlApplicationEngine::objectCreated, this, &LoadWatcher::checkFinished);
+    // QQmlApplicationEngine also connects quit() to QCoreApplication::quit
+    // and exit() to QCoreApplication::exit but if called before exec()
+    // then QCoreApplication::quit or QCoreApplication::exit does nothing
+    connect(engine, &QQmlEngine::quit, this, &LoadWatcher::quit);
+    connect(engine, &QQmlEngine::exit, this, &LoadWatcher::exit);
+}
+
+void LoadWatcher::checkFinished(QObject *object, const QUrl &url)
+{
+    Q_UNUSED(url);
+
+    expectedFileCount -= 1;
+
+    if (object) {
+        createdObjects += 1;
+        if (auto item = qobject_cast<QQuickItem *>(object)) {
+            contain(item);
+        }
+    }
+
+    if (expectedFileCount == 0 && createdObjects == 0) {
+        printf("kqml: Did not load any objects, exiting.\n");
+        exit(2);
+        QCoreApplication::exit(2);
+    }
+}
+
+void LoadWatcher::quit()
+{
+    // Will be checked before calling exec()
+    earlyExit = true;
+    returnCode = 0;
+}
+
+void LoadWatcher::exit(int retCode)
+{
+    earlyExit = true;
+    returnCode = retCode;
+}
+
+void LoadWatcher::contain(QQuickItem *item)
+{
+    if (!item) {
+        return;
+    }
+
+    auto window = new QQuickWindow;
+    auto contentItem = window->contentItem();
+
+    window->setWidth(item->width());
+    window->setHeight(item->height());
+    item->setParentItem(contentItem);
+    item->bindableWidth().setBinding(contentItem->bindableWidth().makeBinding());
+    item->bindableHeight().setBinding(contentItem->bindableHeight().makeBinding());
+    window->setVisible(true);
+    static_cast<QObject *>(window)->setParent(this);
+}
+
+int main(int argc, char *argv[])
+{
+    QGuiApplication app(argc, argv);
+
+    app.setApplicationName("KDE QML Runtime");
+    app.setOrganizationName("KDE");
+    app.setOrganizationDomain("kde.org");
+    QCoreApplication::setApplicationVersion(QLatin1String(PROJECT_VERSION));
+
+    QStringList files;
+
+    // Handle main arguments
+    QCommandLineParser parser;
+    parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
+    parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments);
+    parser.addHelpOption();
+    parser.addVersionOption();
+
+    QCommandLineOption applicationDomainOption(QStringList{QStringLiteral("d"), QStringLiteral("domain")},
+                                               QStringLiteral("The main localization domain"),
+                                               QStringLiteral("domain"));
+    parser.addOption(applicationDomainOption);
+
+    // Positional arguments
+    parser.addPositionalArgument("files", QStringLiteral("Any number of QML files can be loaded. They will share the same engine."), "[files...]");
+    parser.addPositionalArgument("args",
+                                 QStringLiteral("Arguments after '--' are ignored, but passed through to the application.arguments variable in QML."),
+                                 "[-- args...]");
+
+    parser.process(app);
+
+    QQmlApplicationEngine engine;
+
+    QString applicationDomain;
+    if (parser.isSet(applicationDomainOption)) {
+        applicationDomain = parser.value(applicationDomainOption);
+    } else {
+        applicationDomain = QStringLiteral("kqml");
+    }
+    KLocalizedString::setApplicationDomain(qPrintable(applicationDomain));
+
+    for (QString posArg : parser.positionalArguments()) {
+        if (posArg == QLatin1String("--")) {
+            break;
+        } else {
+            files.append(posArg);
+        }
+    }
+
+    if (files.size() <= 0) {
+        noFilesGiven();
+    }
+
+    engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
+
+    // Load files
+    QScopedPointer<LoadWatcher> lw(new LoadWatcher(&engine, files.size()));
+
+    for (const QString &path : std::as_const(files)) {
+        QUrl url = QUrl::fromUserInput(path, QDir::currentPath(), QUrl::AssumeLocalFile);
+        engine.load(url);
+    }
+
+    if (lw->earlyExit) {
+        return lw->returnCode;
+    }
+
+    return app.exec();
+}
+
+#include "main.moc"
diff --git a/kqml/man-kqml.1.docbook b/kqml/man-kqml.1.docbook
new file mode 100644
index 00000000..fc85ebdb
--- /dev/null
+++ b/kqml/man-kqml.1.docbook
@@ -0,0 +1,98 @@
+<?xml version="1.0" ?>
+<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
+<!ENTITY % English "INCLUDE">
+]>
+<!--
+    SPDX-FileCopyrightText: 2023 ivan tkachenko <me at ratijas.tk>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+-->
+
+<refentry lang="&language;">
+<refentryinfo>
+<title>kqml User's Manual</title>
+<date>2023-06-10</date>
+<releaseinfo>Plasma 6.0</releaseinfo>
+<productname>KDE Plasma</productname>
+</refentryinfo>
+
+<refmeta>
+<refentrytitle><command>kqml</command></refentrytitle>
+<manvolnum>1</manvolnum>
+</refmeta>
+
+<refnamediv>
+<refname><command>kqml</command></refname>
+<refpurpose>QML Runtime with KLocalizedContext</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+<cmdsynopsis>
+<command>kqml</command>
+
+<group choice="opt"><option>-v, --version</option></group>
+<group choice="opt"><option>-h, --help</option></group>
+<group choice="opt"><option>-d, --domain</option> <replaceable>domain</replaceable></group>
+<arg choice="opt"><replaceable>files</replaceable>...</arg>
+<arg choice="opt"><option>--</option> <replaceable>args</replaceable>...</arg>
+</cmdsynopsis>
+</refsynopsisdiv>
+
+<refsect1>
+<title>Description</title>
+<para><command>kqml</command> is a QML runtime with initialized KLocalizedContext.</para>
+
+<para>Unlike the qml tool that comes with qt-declarative package, kqml
+initializes KLocalizedContext that allows testing real-world applications and
+scratch files with i18n calls throughout their code and imported libraries.</para>
+</refsect1>
+
+<refsect1>
+<title>Options</title>
+
+<variablelist>
+<varlistentry>
+<term><option>-v</option>, <option>--version</option></term>
+<listitem><para>Displays version information.</para></listitem>
+</varlistentry>
+<varlistentry>
+<term><option>-d</option>, <option>--domain</option> <replaceable>domain</replaceable></term>
+<listitem><para>The main localization domain.</para></listitem>
+</varlistentry>
+<varlistentry>
+<term><option>-h</option>, <option>--help</option></term>
+<listitem><para>Displays this help.</para></listitem>
+</varlistentry>
+<varlistentry>
+<term><replaceable>files</replaceable>...</term>
+<listitem><para>Any number of QML files can be loaded.
+They will share the same engine.</para></listitem>
+</varlistentry>
+<varlistentry>
+<term><replaceable>args</replaceable>...</term>
+<listitem><para>Arguments after '<option>--</option>' are ignored, but passed through to the
+application.arguments variable in QML.</para></listitem>
+</varlistentry>
+
+</variablelist>
+
+</refsect1>
+
+<refsect1>
+<title>See Also</title>
+
+<para>More detailed user documentation is available from <ulink
+url="help:/plasma-desktop">help:/plasma-desktop</ulink>
+(either enter this <acronym>URL</acronym> into &konqueror;, or run
+<userinput><command>khelpcenter</command>
+<parameter>help:/plasma-desktop</parameter></userinput>).</para>
+
+</refsect1>
+
+<refsect1>
+<title>Authors</title>
+<para><command>kqml</command> was written by
+<personname><firstname>ivan</firstname><surname>tkachenko</surname></personname> <email>me at raitjas.tk</email>.</para>
+</refsect1>
+
+</refentry>


More information about the kde-doc-english mailing list