[plasma/plasma-sdk] themeexplorer/src: Fix plasmathemeexplorer

Tobias Fella <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit ffe34c617390006a38c10ed2e4b27a71b6eb86e8 by Tobias Fella.
Committed on 05/08/2026 at 14:05.
Pushed by tfella into branch 'master'.

Fix plasmathemeexplorer

M  +3    -3    themeexplorer/src/qml/Main.qml
M  +12   -10   themeexplorer/src/themelistmodel.cpp
M  +95   -58   themeexplorer/src/thememodel.cpp
M  +4    -1    themeexplorer/src/thememodel.h

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

diff --git a/themeexplorer/src/qml/Main.qml b/themeexplorer/src/qml/Main.qml
index 8daef8f7..70e5b8a2 100644
--- a/themeexplorer/src/qml/Main.qml
+++ b/themeexplorer/src/qml/Main.qml
@@ -66,7 +66,7 @@ Kirigami.AbstractApplicationWindow {
             ToolButton {
                 ToolTip.text: i18n("Edit Metadata…")
                 icon.name: "configure"
-                enabled: view.currentItem?.modelData.isWritable ?? false
+                enabled: themeModel.isWritable
                 onClicked: {
                     if (!root.metadataEditor) {
                         root.metadataEditor = metadataEditorComponent.createObject(root);
@@ -83,7 +83,7 @@ Kirigami.AbstractApplicationWindow {
             ToolButton {
                 ToolTip.text: i18n("Edit Colors…")
                 icon.name: "color"
-                enabled: view.currentItem?.modelData.isWritable ?? false
+                enabled: themeModel.isWritable
                 onClicked: {
                     if (!root.colorEditor) {
                         root.colorEditor = colorEditorComponent.createObject(root);
@@ -260,7 +260,7 @@ Kirigami.AbstractApplicationWindow {
             }
             Button {
                 text: view.currentItem && view.currentItem.modelData.usesFallback ? i18n("Create with Editor…") : i18n("Open In Editor…")
-                enabled: view.currentItem?.modelData.isWritable ?? false
+                enabled: themeModel.isWritable
                 Layout.alignment: Qt.AlignHCenter
                 onClicked: {
                     print(view.currentItem.modelData.svgAbsolutePath)
diff --git a/themeexplorer/src/themelistmodel.cpp b/themeexplorer/src/themelistmodel.cpp
index 1b1cd5c6..61838350 100644
--- a/themeexplorer/src/themelistmodel.cpp
+++ b/themeexplorer/src/themelistmodel.cpp
@@ -17,10 +17,12 @@
 #include <QApplication>
 #include <QDir>
 #include <QFile>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
 #include <QPainter>
 #include <QStandardPaths>
 
-#include <KConfigGroup>
 #include <KDesktopFile>
 
 #include <Plasma/Theme>
@@ -68,19 +70,19 @@ void ThemeListModel::reload()
         const auto themeNameSepIndex = themeRoot.lastIndexOf('/', -1);
         const auto packageName = themeRoot.right(themeRoot.length() - themeNameSepIndex - 1);
 
-        KDesktopFile desktopFile(theme);
+        QFile file(theme);
+        (void)file.open(QFile::ReadOnly);
+        const auto json = QJsonDocument::fromJson(file.readAll()).object();
 
-        if (desktopFile.noDisplay()) {
-            continue;
-        }
-
-        QString name = desktopFile.readName();
+        auto name = json["KPlugin"]["Name"].toString();
         if (name.isEmpty()) {
             name = packageName;
         }
-        const QString comment = desktopFile.readComment();
-        const QString author = desktopFile.desktopGroup().readEntry("X-KDE-PluginInfo-Author", QString());
-        const QString version = desktopFile.desktopGroup().readEntry("X-KDE-PluginInfo-Version", QString());
+        const auto comment = json["KPlugin"]["Description"].toString();
+
+        auto authors = json["KPlugin"]["Author"].toArray();
+        const auto author = authors.size() > 0 ? authors[0].toObject()["Name"].toString() : QString();
+        const auto version = json["KPlugin"]["Version"].toString();
 
         ThemeInfo info;
         info.package = packageName;
diff --git a/themeexplorer/src/thememodel.cpp b/themeexplorer/src/thememodel.cpp
index 92ea0a72..74bdadac 100644
--- a/themeexplorer/src/thememodel.cpp
+++ b/themeexplorer/src/thememodel.cpp
@@ -18,14 +18,14 @@
 
 #include <KAboutData>
 #include <KCompressionDevice>
-#include <KConfigGroup>
 #include <KIO/FileCopyJob>
 #include <KIO/MkdirJob>
 #include <KProcess>
 
 #include <Plasma/Theme>
+#include <kio/mkdirjob.h>
 
-using namespace Qt::Literals::StringLiterals;
+using namespace Qt::StringLiterals;
 
 ThemeModel::ThemeModel(QObject *parent)
     : QAbstractListModel(parent)
@@ -43,13 +43,19 @@ ThemeModel::ThemeModel(QObject *parent)
     m_roleNames.insert(Delegate, "delegate");
     m_roleNames.insert(UsesFallback, "usesFallback");
     m_roleNames.insert(SvgAbsolutePath, "svgAbsolutePath");
-    m_roleNames.insert(IsWritable, "isWritable");
     m_roleNames.insert(IconElements, "iconElements");
     m_roleNames.insert(FrameSvgPrefixes, "frameSvgPrefixes");
 
     load();
 }
 
+QString compactName(const QString &name)
+{
+    auto compactName = name.toLower();
+    compactName.replace(' ', QString());
+    return compactName;
+}
+
 ThemeModel::~ThemeModel()
 {
 }
@@ -99,8 +105,6 @@ QVariant ThemeModel::data(const QModelIndex &index, int role) const
         }
         return path;
     }
-    case IsWritable:
-        return QFile::exists(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/plasma/desktoptheme/" + m_themeName);
     case IconElements:
     case FrameSvgPrefixes: {
         QString path = m_imageSet.imagePath(value.value("imagePath").toString());
@@ -156,6 +160,7 @@ void ThemeModel::load()
     }
 
     endResetModel();
+    Q_EMIT isWritableChanged();
 }
 
 QString ThemeModel::theme() const
@@ -166,13 +171,13 @@ QString ThemeModel::theme() const
 QString ThemeModel::author() const
 {
     const QList<KAboutPerson> authors = m_theme->metadata().authors();
-    return authors.isEmpty() ? authors.at(0).name() : QString();
+    return authors.isEmpty() ? QString() : authors.at(0).name();
 }
 
 QString ThemeModel::email() const
 {
     const QList<KAboutPerson> authors = m_theme->metadata().authors();
-    return authors.isEmpty() ? authors.at(0).emailAddress() : QString();
+    return authors.isEmpty() ? QString() : authors.at(0).emailAddress();
 }
 
 QString ThemeModel::license() const
@@ -211,7 +216,8 @@ void ThemeModel::editElement(const QString &imagePath)
     if (m_imageSet.currentImageSetHasImage(imagePath)) {
         finalFile = file;
     } else {
-        finalFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/plasma/desktoptheme/" + m_themeName + "/" + imagePath + ".svgz";
+        finalFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/plasma/desktoptheme/" + compactName(m_themeName) + "/" + imagePath
+            + ".svgz";
         const QString dirPath = QFileInfo(finalFile).absoluteDir().absolutePath();
         KIO::mkdir(QUrl::fromLocalFile(dirPath))->exec();
 
@@ -222,20 +228,19 @@ void ThemeModel::editElement(const QString &imagePath)
     }
 
     auto process = new QProcess(this);
-    process->execute("bash",
-                     {u"-c"_s,
-                      u""
-                      "cd %2;"
-                      "inkscape \"%1.svgz\";"
-                      "mv \"%1.svgz\" \"%1.svg.gz\";"
-                      "gunzip \"%1.svg.gz\";"
-                      "/usr/bin/perl -p -i -e \"s/color:#[^;]*;fill:currentColor/fill:currentColor/g\" \"%1.svg\";"
-                      "gzip  \"%1.svg\";"
-                      "mv \"%1.svg.gz\" \"%1.svgz\""
-                      ""_s.arg(finalFile.mid(finalFile.lastIndexOf("/") + 1).split(".")[0], finalFile.left(finalFile.lastIndexOf("/")))});
+    process->start("bash",
+                   {u"-c"_s,
+                    u""
+                    "cd %2;"
+                    "inkscape \"%1.svgz\";"
+                    "mv \"%1.svgz\" \"%1.svg.gz\";"
+                    "gunzip \"%1.svg.gz\";"
+                    "/usr/bin/perl -p -i -e \"s/color:#[^;]*;fill:currentColor/fill:currentColor/g\" \"%1.svg\";"
+                    "gzip  \"%1.svg\";"
+                    "mv \"%1.svg.gz\" \"%1.svgz\""
+                    ""_s.arg(finalFile.mid(finalFile.lastIndexOf("/") + 1).split(".")[0], finalFile.left(finalFile.lastIndexOf("/")))});
 
     connect(process, &QProcess::finished, this, &ThemeModel::processFinished);
-    process->start();
 }
 
 void ThemeModel::processFinished()
@@ -243,11 +248,14 @@ void ThemeModel::processFinished()
     /*We increment the microversion of the theme: keeps track and will force the cache to be
       discarded in order to reload immediately the graphics*/
     const QString metadataPath(
-        QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("plasma/desktoptheme/") % m_themeName % QLatin1String("/metadata.desktop")));
-    KConfig c(metadataPath);
-    KConfigGroup cg(&c, "Desktop Entry");
+        QStandardPaths::locate(QStandardPaths::GenericDataLocation, "plasma/desktoptheme/"_L1 % compactName(m_themeName) % "/metadata.json"_L1));
+
+    QFile file(metadataPath);
+    (void)file.open(QFile::ReadWrite);
+    auto json = QJsonDocument::fromJson(file.readAll()).object();
+
+    auto version = json["KPlugin"_L1]["Version"_L1].toString().split('.');
 
-    QStringList version = cg.readEntry("X-KDE-PluginInfo-Version", "0.0").split('.');
     if (version.length() < 2) {
         version << QLatin1String("0");
     }
@@ -255,52 +263,76 @@ void ThemeModel::processFinished()
         version << QLatin1String("0");
     }
 
-    cg.writeEntry("X-KDE-PluginInfo-Version",
-                  QString(version.first() + QLatin1String(".") + version[1] + QLatin1String(".") + QString::number(version.last().toInt() + 1)));
-    cg.sync();
+    QString newVersion = version.first() + u"."_s + version[1] + u"."_s + QString::number(version.last().toInt() + 1);
+    auto kPlugin = json["KPlugin"].toObject();
+    kPlugin["Version"] = newVersion;
+    json["KPlugin"] = kPlugin;
+    file.seek(0);
+    file.resize(0);
+    file.write(QJsonDocument(json).toJson());
+    file.close();
 }
 
 void ThemeModel::editThemeMetaData(const QString &name, const QString &author, const QString &email, const QString &license, const QString &website)
 {
-    QString compactName = name.toLower();
-    compactName.replace(' ', QString());
-    const QString metadataPath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QLatin1String("/plasma/desktoptheme/") % compactName
-                               % QLatin1String("/metadata.desktop"));
-    KConfig c(metadataPath);
-
-    KConfigGroup cg(&c, "Desktop Entry");
-    cg.writeEntry("X-KDE-PluginInfo-Name", name);
-    cg.writeEntry("X-KDE-PluginInfo-Author", author);
-    cg.writeEntry("X-KDE-PluginInfo-Email", email);
-    cg.writeEntry("X-KDE-PluginInfo-Website", website);
-    cg.writeEntry("X-KDE-PluginInfo-Category", "Plasma Theme");
-    cg.writeEntry("X-KDE-PluginInfo-License", license);
-    cg.writeEntry("X-KDE-PluginInfo-EnabledByDefault", "true");
-    cg.writeEntry("X-Plasma-API", "5.0");
-    cg.writeEntry("X-KDE-PluginInfo-Version", "0.1");
-    cg.sync();
-
-    KConfigGroup cg2(&c, "ContrastEffect");
-    cg2.writeEntry("enabled", "true");
-    cg2.writeEntry("contrast", "0.2");
-    cg2.writeEntry("intensity", "2.0");
-    cg2.writeEntry("saturation", "1.7");
-    cg2.sync();
+    const auto metadataPath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % "/plasma/desktoptheme/"_L1 % compactName(name)
+                            % "/metadata.json"_L1);
+
+    QFile file(metadataPath);
+    (void)file.open(QFile::ReadWrite);
+
+    auto json = QJsonDocument::fromJson(file.readAll()).object();
+
+    json[u"X-Plasma-API"_s] = u"5.0"_s;
+
+    auto kPlugin = json[u"KPlugin"_s].toObject();
+    auto authors = kPlugin[u"Authors"_s].toArray();
+    authors = QJsonArray{QJsonObject{
+        {u"Email"_s, email},
+        {u"Name"_s, author},
+    }};
+    kPlugin["Authors"] = authors;
+    kPlugin["Name"] = name;
+    kPlugin["Category"] = u"Plasma Theme"_s;
+
+    if (!kPlugin.contains("Description")) {
+        kPlugin["Description"] = QString();
+    }
+
+    kPlugin["EnabledByDefault"] = true;
+    if (!kPlugin.contains("Id")) {
+        kPlugin[u"Id"_s] = compactName(name);
+    }
+
+    kPlugin["Website"] = website;
+    kPlugin["License"] = license;
+
+    if (!kPlugin.contains("Version")) {
+        kPlugin["Version"] = "1.0";
+    }
+
+    json["KPlugin"] = kPlugin;
+
+    file.seek(0);
+    file.resize(0);
+    file.write(QJsonDocument(json).toJson());
+    file.close();
 }
 
 void ThemeModel::createNewTheme(const QString &name, const QString &author, const QString &email, const QString &license, const QString &website)
 {
+    auto dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + u"/plasma/desktoptheme/"_s + compactName(name);
+    QDir().mkdir(dir);
     editThemeMetaData(name, author, email, license, website);
 
-    QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, +"/plasma/desktoptheme/default/colors");
+    const auto colorsFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "/plasma/desktoptheme/breeze-light/colors");
 
-    QString compactName = name.toLower();
-    compactName.replace(' ', QString());
-    QString finalFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/plasma/desktoptheme/" + compactName + "/colors";
+    const auto finalColorsFile =
+        QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/plasma/desktoptheme/" + compactName(name) + "/colors";
 
-    KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(file), QUrl::fromLocalFile(finalFile));
+    KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(colorsFile), QUrl::fromLocalFile(finalColorsFile));
     if (!job->exec()) {
-        qWarning() << "Error copying" << file << "to" << finalFile;
+        qWarning() << "Error copying" << colorsFile << "to" << finalColorsFile;
     }
 
     m_themeListModel->reload();
@@ -308,7 +340,12 @@ void ThemeModel::createNewTheme(const QString &name, const QString &author, cons
 
 QString ThemeModel::themeFolder()
 {
-    return QStandardPaths::locate(QStandardPaths::GenericDataLocation, +"plasma/desktoptheme/" + m_themeName, QStandardPaths::LocateDirectory);
+    return QStandardPaths::locate(QStandardPaths::GenericDataLocation, +"plasma/desktoptheme/" + compactName(m_themeName), QStandardPaths::LocateDirectory);
+}
+
+bool ThemeModel::isWritable() const
+{
+    return QFile::exists(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/plasma/desktoptheme/" + compactName(m_themeName));
 }
 
 #include "moc_thememodel.cpp"
diff --git a/themeexplorer/src/thememodel.h b/themeexplorer/src/thememodel.h
index e92a5367..3697468d 100644
--- a/themeexplorer/src/thememodel.h
+++ b/themeexplorer/src/thememodel.h
@@ -34,6 +34,7 @@ class ThemeModel : public QAbstractListModel
     Q_PROPERTY(QString email READ email NOTIFY themeChanged)
     Q_PROPERTY(QString license READ license NOTIFY themeChanged)
     Q_PROPERTY(QString website READ website NOTIFY themeChanged)
+    Q_PROPERTY(bool isWritable READ isWritable NOTIFY isWritableChanged)
 
     Q_PROPERTY(QString themeFolder READ themeFolder NOTIFY themeChanged)
 public:
@@ -43,7 +44,6 @@ public:
         Delegate,
         UsesFallback,
         SvgAbsolutePath,
-        IsWritable,
         IconElements,
         FrameSvgPrefixes,
     };
@@ -66,6 +66,8 @@ public:
     QString license() const;
     QString website() const;
 
+    bool isWritable() const;
+
     void load();
 
     Q_INVOKABLE void editElement(const QString &imagePath);
@@ -76,6 +78,7 @@ public:
 
 Q_SIGNALS:
     void themeChanged();
+    void isWritableChanged();
 
 private Q_SLOTS:
     void processFinished();
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.