File fcitx5-configtool-revert-drop-qt5.patch of Package fcitx5-configtool

diff -urN fcitx5-configtool-5.1.12/CMakeLists.txt fcitx5-configtool-5.1.12.new/CMakeLists.txt
--- fcitx5-configtool-5.1.12/CMakeLists.txt	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/CMakeLists.txt	2026-02-08 22:16:45.455817979 +0800
@@ -13,13 +13,24 @@
 
 project(fcitx5-configtool VERSION 5.1.12)
 
+option(USE_QT6 "Build with Qt6" On)
+
+if (USE_QT6)
 set(QT_MIN_VERSION "6.4.0")
 set(QT_MAJOR_VERSION 6)
+else()
+set(QT_MIN_VERSION "5.12.0")
+set(QT_MAJOR_VERSION 5)
+endif()
 
 find_package(ECM 5.68.0 REQUIRED)
 set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR})
 find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Gui Widgets Concurrent)
 
+if (QT_MAJOR_VERSION STREQUAL 5)
+find_package(Qt${QT_MAJOR_VERSION}X11Extras ${QT_MIN_VERSION} CONFIG REQUIRED)
+endif()
+
 include(KDEInstallDirs)
 include(KDECMakeSettings)
 include(KDECompilerSettings)
@@ -53,10 +64,20 @@
     find_package(KF${QT_MAJOR_VERSION}Declarative REQUIRED)
     find_package(KF${QT_MAJOR_VERSION}IconThemes REQUIRED)
     find_package(XKBCommon REQUIRED COMPONENTS XKBCommon)
-    find_package(KF${QT_MAJOR_VERSION}Kirigami REQUIRED)
-    find_package(Plasma REQUIRED)
-    find_package(KF${QT_MAJOR_VERSION}Svg REQUIRED)
-    find_package(KF${QT_MAJOR_VERSION}KCMUtils REQUIRED)
+    if (QT_MAJOR_VERSION STREQUAL 5)
+        find_package(KF${QT_MAJOR_VERSION}Plasma REQUIRED)
+        find_package(KF${QT_MAJOR_VERSION}Kirigami2 5.68 REQUIRED)
+        if (KF${QT_MAJOR_VERSION}Kirigami2_VERSION VERSION_LESS 5.76)
+            set(DISABLE_UNDER_KIRIGAMI2_5_76 "//Needs Kirigami2 5.76 ")
+        else()
+            set(DISABLE_UNDER_KIRIGAMI2_5_76 "")
+        endif()
+    elseif (QT_MAJOR_VERSION STREQUAL 6)
+        find_package(KF${QT_MAJOR_VERSION}Kirigami REQUIRED)
+        find_package(Plasma REQUIRED)
+        find_package(KF${QT_MAJOR_VERSION}Svg REQUIRED)
+        find_package(KF${QT_MAJOR_VERSION}KCMUtils REQUIRED)
+    endif()
 endif()
 
 find_package(Fcitx5Core 5.1.13 REQUIRED)
diff -urN fcitx5-configtool-5.1.12/layout/CMakeLists.txt fcitx5-configtool-5.1.12.new/layout/CMakeLists.txt
--- fcitx5-configtool-5.1.12/layout/CMakeLists.txt	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/layout/CMakeLists.txt	2026-02-08 22:16:45.455986583 +0800
@@ -10,6 +10,9 @@
 add_library(layoutlib STATIC keyboardlayoutwidget.cpp)
 set_target_properties(layoutlib PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
 target_link_libraries(layoutlib PUBLIC Fcitx5::Utils Qt${QT_MAJOR_VERSION}::Widgets PRIVATE X11Import X11XkblibImport PkgConfig::XkbFile)
+if (QT_MAJOR_VERSION STREQUAL 5)
+target_link_libraries(layoutlib PUBLIC Qt${QT_MAJOR_VERSION}::X11Extras)
+endif()
 target_include_directories(layoutlib INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
 
 set(kbd_layout_viewer_SOURCES
diff -urN fcitx5-configtool-5.1.12/layout/keyboardlayoutwidget.cpp fcitx5-configtool-5.1.12.new/layout/keyboardlayoutwidget.cpp
--- fcitx5-configtool-5.1.12/layout/keyboardlayoutwidget.cpp	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/layout/keyboardlayoutwidget.cpp	2026-02-08 22:16:45.456187447 +0800
@@ -12,6 +12,9 @@
 #include <QPainter>
 #include <QPainterPath>
 #include <QVector2D>
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+#include <QX11Info>
+#endif
 #include <qmath.h>
 
 #include <fcitx-utils/key.h>
@@ -44,6 +47,12 @@
 namespace kcm {
 
 namespace {
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+bool isPlatformX11() { return QX11Info::isPlatformX11(); }
+
+auto *getXDisplay() { return QX11Info::display(); }
+#else
 bool isPlatformX11() {
     return qGuiApp->nativeInterface<QNativeInterface::QX11Application>();
 }
@@ -52,6 +61,7 @@
     return qGuiApp->nativeInterface<QNativeInterface::QX11Application>()
         ->display();
 }
+#endif
 
 } // namespace
 
diff -urN fcitx5-configtool-5.1.12/layout/main.cpp fcitx5-configtool-5.1.12.new/layout/main.cpp
--- fcitx5-configtool-5.1.12/layout/main.cpp	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/layout/main.cpp	2026-02-08 22:16:45.456320876 +0800
@@ -49,6 +49,10 @@
         variant = parser.value("variant");
     }
 
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    app.setAttribute(Qt::AA_UseHighDpiPixmaps);
+#endif
+
     QMainWindow mainWindow;
     mainWindow.setWindowIcon(QIcon::fromTheme("input-keyboard"));
     mainWindow.setWindowTitle(_("Keyboard Layout viewer"));
diff -urN fcitx5-configtool-5.1.12/src/configtool/mainwindow.cpp fcitx5-configtool-5.1.12.new/src/configtool/mainwindow.cpp
--- fcitx5-configtool-5.1.12/src/configtool/mainwindow.cpp	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/configtool/mainwindow.cpp	2026-02-08 22:21:32.614381000 +0800
@@ -21,7 +21,6 @@
 #include <QPushButton>
 #include <QSessionManager>
 #include <QWidget>
-#include <QtVersionChecks>
 #include <fcitx-utils/i18n.h>
 
 namespace fcitx::kcm {
diff -urN fcitx5-configtool-5.1.12/src/kcm/CMakeLists.txt fcitx5-configtool-5.1.12.new/src/kcm/CMakeLists.txt
--- fcitx5-configtool-5.1.12/src/kcm/CMakeLists.txt	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/CMakeLists.txt	2026-02-08 22:16:45.456367202 +0800
@@ -1,5 +1,42 @@
-kcmutils_add_qml_kcm(kcm_fcitx5 SOURCES main.cpp)
-target_link_libraries(kcm_fcitx5 KF${QT_MAJOR_VERSION}::KCMUtilsQuick)
+if (QT_MAJOR_VERSION STREQUAL 5)
+    add_library(kcm_fcitx5 MODULE
+        main.cpp
+    )
+    target_link_libraries(kcm_fcitx5 KF${QT_MAJOR_VERSION}::QuickAddons KF${QT_MAJOR_VERSION}::Declarative)
+
+    file(GLOB_RECURSE inFiles RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
+    "${CMAKE_CURRENT_SOURCE_DIR}/package/*")
+    foreach(infileName ${inFiles})
+    configure_file(
+        "${CMAKE_CURRENT_SOURCE_DIR}/${infileName}"
+        "${CMAKE_CURRENT_BINARY_DIR}/${infileName}" @ONLY
+    )
+    endforeach()
+
+    # This is a hack against kpackage_install_package
+    # 1. kpackage_install_package only accept relative path to ${CMAKE_CURRENT_SOURCE_DIR}
+    # 2. It need metadata.desktop to run properly, but we only have untranslated file at that time.
+    #    So we just delete the file during the cmake.
+    file(RELATIVE_PATH RELATIVE_BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
+    if (KF5Declarative_VERSION VERSION_GREATER_EQUAL 5.104.0)
+      install(TARGETS kcm_fcitx5 DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/kcms/systemsettings)
+      kpackage_install_package(${RELATIVE_BINARY_DIR}/package kcm_fcitx5 kcms)
+      fcitx5_translate_desktop_file(kcm_fcitx5.desktop.in
+        kcm_fcitx5.desktop PO_DIRECTORY ${PROJECT_SOURCE_DIR}/po/kcm_fcitx5)
+      install(FILES "${CMAKE_CURRENT_BINARY_DIR}/kcm_fcitx5.desktop"
+        DESTINATION ${KDE_INSTALL_APPDIR})
+    else()
+      install(TARGETS kcm_fcitx5 DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms)
+      fcitx5_translate_desktop_file(kcm_fcitx5_kservice.desktop.in
+        kcm_fcitx5_kservice.desktop PO_DIRECTORY ${PROJECT_SOURCE_DIR}/po/kcm_fcitx5)
+      install(FILES "${CMAKE_CURRENT_BINARY_DIR}/kcm_fcitx5_kservice.desktop"
+        DESTINATION ${KDE_INSTALL_KSERVICES5DIR} RENAME kcm_fcitx5.desktop)
+    endif()
+    
+elseif(QT_MAJOR_VERSION STREQUAL 6)
+    kcmutils_add_qml_kcm(kcm_fcitx5 SOURCES main.cpp)
+    target_link_libraries(kcm_fcitx5 KF${QT_MAJOR_VERSION}::KCMUtilsQuick)
+endif()
 
 target_link_libraries(kcm_fcitx5
   Qt${QT_MAJOR_VERSION}::Quick
diff -urN fcitx5-configtool-5.1.12/src/kcm/kcm_fcitx5.desktop.in fcitx5-configtool-5.1.12.new/src/kcm/kcm_fcitx5.desktop.in
--- fcitx5-configtool-5.1.12/src/kcm/kcm_fcitx5.desktop.in	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/kcm_fcitx5.desktop.in	2026-02-08 22:16:45.456395886 +0800
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Icon=fcitx
+Type=Application
+X-KDE-AliasFor=systemsettings
+Exec=systemsettings kcm_fcitx5
+NoDisplay=True
+Name=Input Method
+Comment=Configure Input Method
\ 文件末尾没有换行符
diff -urN fcitx5-configtool-5.1.12/src/kcm/kcm_fcitx5_kservice.desktop.in fcitx5-configtool-5.1.12.new/src/kcm/kcm_fcitx5_kservice.desktop.in
--- fcitx5-configtool-5.1.12/src/kcm/kcm_fcitx5_kservice.desktop.in	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/kcm_fcitx5_kservice.desktop.in	2026-02-08 22:16:45.456425801 +0800
@@ -0,0 +1,16 @@
+[Desktop Entry]
+Icon=fcitx
+Type=Service
+
+X-KDE-ServiceTypes=KCModule
+X-KDE-Library=kcm_fcitx5
+X-KDE-ParentApp=kcontrol
+
+X-KDE-System-Settings-Parent-Category=regionalsettings
+
+Name=Input Method
+Comment=Configure Input Method
+
+X-KDE-Keywords=keyboard,input,im,fcitx
+
+Categories=Qt;KDE;X-KDE-settings-fcitx;
diff -urN fcitx5-configtool-5.1.12/src/kcm/main.cpp fcitx5-configtool-5.1.12.new/src/kcm/main.cpp
--- fcitx5-configtool-5.1.12/src/kcm/main.cpp	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/main.cpp	2026-02-08 22:16:45.456485903 +0800
@@ -36,7 +36,6 @@
 #include <fcitx-utils/misc.h>
 #include <fcitx-utils/standardpaths.h>
 #include <fcitxqtdbustypes.h>
-#include <kquickconfigmodule.h>
 
 namespace fcitx::kcm {
 
@@ -67,16 +66,44 @@
 }
 } // namespace
 
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
 FcitxModule::FcitxModule(QObject *parent, const KPluginMetaData &metaData)
-    : KQuickConfigModule(parent, metaData), dbus_(new DBusProvider(this)),
+    : FcitxKCMBase(parent, metaData),
+#elif defined(FCITX_USE_NEW_KDECLARATIVE)
+FcitxModule::FcitxModule(QObject *parent, const KPluginMetaData &metaData,
+                         const QVariantList &args)
+    : FcitxKCMBase(parent, metaData, args),
+#else
+FcitxModule::FcitxModule(QObject *parent, const QVariantList &args)
+    : FcitxKCMBase(parent, args),
+#endif
+      dbus_(new DBusProvider(this)),
       imConfig_(new IMConfig(dbus_, IMConfig::Flatten, this)),
       layoutProvider_(new LayoutProvider(dbus_, this)),
       addonModel_(new FlatAddonModel(this)),
       addonProxyModel_(new AddonProxyModel(this)) {
+#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
+    qmlRegisterType<FilteredIMModel>();
+    qmlRegisterType<IMProxyModel>();
+    qmlRegisterType<LanguageModel>();
+#else
     qmlRegisterAnonymousType<FilteredIMModel>("", 1);
     qmlRegisterAnonymousType<IMProxyModel>("", 1);
     qmlRegisterAnonymousType<LanguageModel>("", 1);
+#endif
 
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) &&                                  \
+    !defined(FCITX_USE_NEW_KDECLARATIVE)
+    KAboutData *about =
+        new KAboutData("kcm_fcitx5", i18n("Fcitx 5"), PROJECT_VERSION,
+                       i18n("Configure Fcitx 5"), KAboutLicense::GPL_V2,
+                       i18n("Copyright 2017 Xuetian Weng"), QString(),
+                       QString(), "wengxt@gmail.com");
+
+    about->addAuthor(i18n("Xuetian Weng"), i18n("Author"), "wengxt@gmail.com");
+
+    setAboutData(about);
+#endif
     addonProxyModel_->setSourceModel(addonModel_);
     addonProxyModel_->sort(0);
 
@@ -100,14 +127,13 @@
                                                XKB_KEYMAP_COMPILE_NO_FLAGS));
     xkbState_.reset(xkb_state_new(xkbKeymap_.get()));
 
-    connect(this, &KQuickConfigModule::pagePushed, this,
-            [this](QQuickItem *page) {
-                pages_[currentIndex() + 1] = page;
-                if (page->property("needsSave").isValid()) {
-                    connect(page, SIGNAL(needsSaveChanged()), this,
-                            SLOT(pageNeedsSaveChanged()));
-                }
-            });
+    connect(this, &FcitxKCMBase::pagePushed, this, [this](QQuickItem *page) {
+        pages_[currentIndex() + 1] = page;
+        if (page->property("needsSave").isValid()) {
+            connect(page, SIGNAL(needsSaveChanged()), this,
+                    SLOT(pageNeedsSaveChanged()));
+        }
+    });
 }
 
 FcitxModule::~FcitxModule() {}
diff -urN fcitx5-configtool-5.1.12/src/kcm/main.h fcitx5-configtool-5.1.12.new/src/kcm/main.h
--- fcitx5-configtool-5.1.12/src/kcm/main.h	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/main.h	2026-02-08 22:16:45.456541106 +0800
@@ -11,8 +11,6 @@
 #include "dbusprovider.h"
 #include "font.h"
 #include "imconfig.h"
-#include <KPluginMetaData>
-#include <KQuickConfigModule>
 #include <QFont>
 #include <QMap>
 #include <QObject>
@@ -23,9 +21,29 @@
 #include <memory>
 #include <xkbcommon/xkbcommon.h>
 
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+#include <KPluginMetaData>
+#include <KQuickConfigModule>
+#define FCITX_USE_NEW_KDECLARATIVE
+#else
+#include <KQuickAddons/ConfigModule>
+#include <kdeclarative_version.h>
+
+#if KDECLARATIVE_VERSION >= QT_VERSION_CHECK(5, 104, 0)
+#define FCITX_USE_NEW_KDECLARATIVE
+#endif
+
+#endif
+
 namespace fcitx::kcm {
 
-class FcitxModule : public KQuickConfigModule {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+using FcitxKCMBase = KQuickConfigModule;
+#else
+using FcitxKCMBase = KQuickAddons::ConfigModule;
+#endif
+
+class FcitxModule : public FcitxKCMBase {
     Q_OBJECT
     Q_PROPERTY(IMConfig *imConfig READ imConfig CONSTANT)
     Q_PROPERTY(LayoutProvider *layoutProvider READ layoutProvider CONSTANT)
@@ -33,7 +51,14 @@
     Q_PROPERTY(bool availability READ availability NOTIFY availabilityChanged)
     Q_PROPERTY(bool canRestart READ canRestart NOTIFY canRestartChanged)
 public:
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
     FcitxModule(QObject *parent, const KPluginMetaData &metaData);
+#elif defined(FCITX_USE_NEW_KDECLARATIVE)
+    FcitxModule(QObject *parent, const KPluginMetaData &metaData,
+                const QVariantList &args);
+#else
+    FcitxModule(QObject *parent, const QVariantList &args);
+#endif
     virtual ~FcitxModule() override;
 
     auto imConfig() const { return imConfig_; }
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/AddIMPage.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/AddIMPage.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/AddIMPage.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/AddIMPage.qml	2026-02-08 22:16:45.456605316 +0800
@@ -0,0 +1,82 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.12
+import org.kde.kirigami 2.10 as Kirigami
+import org.kde.kcm 1.2 as KCM
+
+KCM.ScrollViewKCM {
+    title: i18n("Add Input Method")
+
+    Component.onCompleted: {
+        search.forceActiveFocus();
+    }
+
+    Binding {
+        property: "filterText"
+        target: kcm.imConfig.availIMModel
+        value: search.text
+    }
+
+    footer: RowLayout {
+        CheckBox {
+            checked: true
+            text: i18n("Only &Show Current Language")
+            visible: search.text.length === 0
+
+            onClicked: {
+                kcm.imConfig.availIMModel.showOnlyCurrentLanguage = checked;
+            }
+        }
+        Item {
+            Layout.fillWidth: true
+        }
+        Button {
+            icon.name: "list-add-symbolic"
+            text: i18n("Add")
+
+            onClicked: {
+                if (availIMView.currentIndex === -1) {
+                    return;
+                }
+                kcm.imConfig.addIM(availIMView.currentIndex);
+                if (kcm.imConfig.currentIMModel.count === 1) {
+                    kcm.mainUi.checkInputMethod();
+                }
+                kcm.pop();
+            }
+        }
+    }
+    header: RowLayout {
+        TextField {
+            id: search
+            Layout.fillWidth: true
+            placeholderText: i18n("Search...")
+        }
+    }
+    view: ListView {
+        id: availIMView
+        model: kcm.imConfig.availIMModel
+
+        section {
+            property: "language"
+
+            delegate: Kirigami.ListSectionHeader {
+                label: section
+            }
+        }
+
+        delegate: Kirigami.BasicListItem {
+            label: model.name
+
+            onClicked: {
+                availIMView.currentIndex = index;
+            }
+        }
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/AddonPage.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/AddonPage.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/AddonPage.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/AddonPage.qml	2026-02-08 22:16:45.456648446 +0800
@@ -0,0 +1,168 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.12
+import org.kde.kirigami 2.10 as Kirigami
+import org.kde.kcm 1.2 as KCM
+
+KCM.ScrollViewKCM {
+    id: addonPage
+    property bool needsSave: false
+    property string reenableAddon
+
+    title: i18n("Addons")
+
+    function defaults() {
+    }
+    function load() {
+        kcm.loadAddon();
+        needsSave = false;
+    }
+    function save() {
+        kcm.saveAddon();
+        needsSave = false;
+    }
+    function showWarning() {
+        dialog.open();
+    }
+
+    Binding {
+        property: "filterText"
+        target: kcm.addonModel
+        value: search.text
+    }
+    SaveWarningDialog {
+        id: dialog
+    }
+
+    header: ColumnLayout {
+        Kirigami.InlineMessage {
+            id: showOptionWarning
+            Layout.fillWidth: true
+            visible: showEnable.checked
+            type: Kirigami.MessageType.Warning
+            text: i18n("The feature of enabling/disabling addons is only intended for advanced users who understand the potential implication. Fcitx needs to be restarted to make the changes to enable/disable to take effect.")
+        }
+        Kirigami.InlineMessage {
+            id: disableAddonWarning
+            Layout.fillWidth: true
+            showCloseButton: true
+            type: Kirigami.MessageType.Warning
+
+            actions: [
+                Kirigami.Action {
+                    displayHint: Kirigami.Action.DisplayHint.KeepVisible
+                    iconName: "edit-undo"
+                    text: i18n("Re-Enable")
+
+                    onTriggered: {
+                        kcm.addonModel.sourceModel.enable(reenableAddon);
+                        disableAddonWarning.visible = false;
+                    }
+                }
+            ]
+        }
+        RowLayout {
+            TextField {
+                id: search
+                Layout.fillWidth: true
+                placeholderText: i18n("Search...")
+            }
+        }
+    }
+    view: ListView {
+        model: kcm.addonModel
+
+        section {
+            property: "categoryName"
+
+            delegate: Kirigami.ListSectionHeader {
+                label: section
+            }
+        }
+
+        delegate: Kirigami.SwipeListItem {
+            id: listItem
+            RowLayout {
+                CheckBox {
+                    id: itemChecked
+                    Layout.alignment: Qt.AlignVCenter
+                    Layout.leftMargin: Kirigami.Units.gridUnit
+                    checked: model.enabled
+                    visible: showEnable.checked
+
+                    onClicked: {
+                        model.enabled = !model.enabled;
+                        if (!model.enabled) {
+                            var dependencies = model.dependencies;
+                            var optionalDependencies = model.optionalDependencies;
+                            if (dependencies.length > 0 || optionalDependencies.length > 0) {
+                                reenableAddon = model.uniqueName;
+                                var sep = i18nc("Separator of a comma list", ", ");
+                                var depWarning = "";
+                                if (dependencies.length > 0) {
+                                    var addonNames = [];
+                                    for (var i = 0; i < dependencies.length; i++) {
+                                        var name = kcm.addonModel.sourceModel.addonName(dependencies[i]);
+                                        if (name) {
+                                            addonNames.push(name);
+                                        }
+                                    }
+                                    depWarning = depWarning.concat(i18n("- Disable %1\n", addonNames.join(sep)));
+                                }
+                                if (optionalDependencies.length > 0) {
+                                    var addonNames = [];
+                                    for (var i = 0; i < optionalDependencies.length; i++) {
+                                        var name = kcm.addonModel.sourceModel.addonName(optionalDependencies[i]);
+                                        if (name) {
+                                            addonNames.push(name);
+                                        }
+                                    }
+                                    depWarning = depWarning.concat(i18n("- Disable some features in %1\n", addonNames.join(sep)));
+                                }
+                                disableAddonWarning.text = i18n("Disabling %1 will also:\n%2\nAre you sure you want to disable it?", model.name, depWarning);
+                                disableAddonWarning.visible = true;
+                            }
+                        }
+                        needsSave = true;
+                    }
+                }
+                ColumnLayout {
+                    Kirigami.Heading {
+                        Layout.fillWidth: true
+                        elide: Text.ElideRight
+                        level: 5
+                        text: model.enabled ? model.name : i18n("%1 (Disabled)", model.name)
+                    }
+                    Label {
+                        opacity: listItem.hovered ? 0.8 : 0.6
+                        text: model.comment
+                        visible: model.comment.length > 0
+                    }
+                }
+            }
+
+            actions: [
+                Kirigami.Action {
+                    icon.name: "configure"
+                    visible: model.configurable
+
+                    onTriggered: kcm.pushConfigPage(model.name, "fcitx://config/addon/" + model.uniqueName)
+                }
+            ]
+        }
+    }
+    footer: CheckBox {
+        id: showEnable
+        text: i18n("Show &Advanced options")
+
+        onClicked: {
+            showOptionWarning.visible = true;
+        }
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/BoolOption.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/BoolOption.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/BoolOption.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/BoolOption.qml	2026-02-08 22:16:45.456676228 +0800
@@ -0,0 +1,26 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+
+CheckBox {
+    property bool needsSave: value !== checked
+    property variant rawValue
+    property bool value: rawValue === "True"
+
+    function load(rawValue) {
+        checked = rawValue === "True";
+    }
+    function save() {
+        rawValue = checked ? "True" : "False";
+    }
+
+    Component.onCompleted: {
+        load(rawValue);
+        save();
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/ColorOption.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/ColorOption.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/ColorOption.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/ColorOption.qml	2026-02-08 22:16:45.456702818 +0800
@@ -0,0 +1,50 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Dialogs 1.1 as QtDialogs
+import org.kde.kirigami 2.10 as Kirigami
+
+RowLayout {
+    property bool needsSave: button.text != rawValue
+    property variant rawValue
+
+    function load(rawValue) {
+        colorDialog.color = kcm.parseColor(rawValue);
+    }
+    function save() {
+        rawValue = button.text;
+    }
+
+    Component.onCompleted: {
+        load(rawValue);
+        save();
+    }
+
+    Button {
+        id: button
+        icon.name: "document-edit"
+        text: kcm.colorToString(colorDialog.color)
+
+        Layout.fillWidth: true
+
+        onClicked: colorDialog.open()
+
+        QtDialogs.ColorDialog {
+            id: colorDialog
+            modality: Qt.ApplicationModal
+            showAlphaChannel: true
+            title: i18nc("@title:window", "Select Color")
+        }
+    }
+    Rectangle {
+        color: colorDialog.color
+        height: button.height
+        width: height
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/ConfigGroup.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/ConfigGroup.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/ConfigGroup.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/ConfigGroup.qml	2026-02-08 22:16:45.456732312 +0800
@@ -0,0 +1,85 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import org.kde.kirigami 2.10 as Kirigami
+import "utils.js" as Utils
+
+Kirigami.FormLayout {
+    id: configGroup
+    property bool needsSave
+    property variant rawValue
+    property variant typeMap
+    property string typeName
+
+    function defaults() {
+        for (var i = 0; i < repeater.count; i++) {
+            var loader = repeater.itemAt(i);
+            if (loader.status == Loader.Ready) {
+                loader.item.load(loader.option.defaultValue);
+            }
+        }
+    }
+    function load() {
+        for (var i = 0; i < repeater.count; i++) {
+            var loader = repeater.itemAt(i);
+            if (loader.status == Loader.Ready) {
+                loader.item.load(loader.item.rawValue);
+            }
+        }
+        needsSave = false;
+    }
+    function save() {
+        var rawValue = {};
+        for (var i = 0; i < repeater.count; i++) {
+            var loader = repeater.itemAt(i);
+            if (loader.status == Loader.Ready) {
+                loader.item.save();
+                if (loader.item.hasOwnProperty("rawValue")) {
+                    Utils.setRawValue(rawValue, loader.option.name, loader.item.rawValue);
+                }
+            }
+        }
+        configGroup.rawValue = rawValue;
+        configGroup.needsSave = false;
+    }
+    function setRawValue(rawValue) {
+        for (var i = 0; i < repeater.count; i++) {
+            var loader = repeater.itemAt(i);
+            if (loader.status == Loader.Ready) {
+                loader.item.load(Utils.getRawValue(rawValue, loader.option.name));
+            }
+        }
+    }
+
+    Repeater {
+        id: repeater
+        model: typeMap[typeName]
+
+        OptionLoader {
+            id: loader
+            Layout.fillWidth: option.type !== "Boolean"
+            Kirigami.FormData.isSection: modelData.isSection
+            Kirigami.FormData.label: modelData.isSection ? modelData.description : i18n("%1:", modelData.description)
+            @DISABLE_UNDER_KIRIGAMI2_5_76@ Kirigami.FormData.labelAlignment: !modelData.isSection && modelData.type.startsWith("List|") ? (height > Kirigami.Units.gridUnit * 2 ? Qt.AlignTop : 0) : 0
+            option: modelData
+            rawValue: modelData.isSection ? null : Utils.getRawValue(configGroup.rawValue, modelData.name)
+
+            Connections {
+                enabled: loader.status == Loader.Ready
+                target: loader.status == Loader.Ready ? loader.item : null
+
+                function onNeedsSaveChanged() {
+                    if (target.needsSave) {
+                        configGroup.needsSave = true;
+                    }
+                }
+            }
+        }
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/ConfigPage.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/ConfigPage.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/ConfigPage.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/ConfigPage.qml	2026-02-08 22:16:45.456758662 +0800
@@ -0,0 +1,55 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import org.kde.kirigami 2.10 as Kirigami
+import org.kde.kcm 1.1 as KCM
+
+Kirigami.ScrollablePage {
+    id: configPage
+    property alias needsSave: configGroup.needsSave
+    property alias rawValue: configGroup.rawValue
+    property alias typeMap: configGroup.typeMap
+    property alias typeName: configGroup.typeName
+    property string uri
+
+    function defaults() {
+        configGroup.defaults();
+    }
+    function load() {
+        configGroup.load();
+    }
+    function save() {
+        configGroup.save();
+        kcm.saveConfig(uri, rawValue);
+    }
+    function showWarning() {
+        dialog.open();
+    }
+
+    Component.onCompleted: positionTimer.start()
+
+    ConfigGroup {
+        id: configGroup
+        visible: false
+        width: parent.width
+
+        SaveWarningDialog {
+            id: dialog
+            parent: configPage
+        }
+
+        // Hack for force relayout the scrollview
+        Timer {
+            id: positionTimer
+            interval: 0
+            repeat: false
+
+            onTriggered: configGroup.visible = true
+        }
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/EnumOption.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/EnumOption.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/EnumOption.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/EnumOption.qml	2026-02-08 22:16:45.456788818 +0800
@@ -0,0 +1,86 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import org.kde.kirigami 2.10 as Kirigami
+
+Row {
+    property bool needsSave: value != comboBox.currentIndex
+    property variant properties
+    property variant rawValue
+    property int value: computeValue(rawValue)
+
+    function computeValue(rawValue) {
+        for (var i = 0; i < listModel.count; i++) {
+            if (listModel.get(i).value == rawValue) {
+                return i;
+            }
+        }
+        return 0;
+    }
+    function load(rawValue) {
+        comboBox.currentIndex = computeValue(rawValue);
+    }
+    function save() {
+        rawValue = properties["Enum"][comboBox.currentIndex.toString()];
+    }
+
+    Component.onCompleted: {
+        var i = 0;
+        while (true) {
+            if (!properties.hasOwnProperty("Enum")) {
+                break;
+            }
+            if (!properties["Enum"].hasOwnProperty(i.toString())) {
+                break;
+            }
+            var value = properties["Enum"][i.toString()];
+            var text = "";
+            if (properties.hasOwnProperty("EnumI18n")) {
+                if (properties["EnumI18n"].hasOwnProperty(i.toString())) {
+                    text = properties["EnumI18n"][i.toString()];
+                }
+            }
+            if (text == "") {
+                text = value;
+            }
+            var subconfigpath = "";
+            if (properties.hasOwnProperty("SubConfigPath")) {
+                if (properties["SubConfigPath"].hasOwnProperty(i.toString())) {
+                    subconfigpath = properties["SubConfigPath"][i.toString()];
+                }
+            }
+            listModel.append({
+                    "text": text,
+                    "value": value,
+                    "subconfigpath": subconfigpath
+                });
+            i++;
+        }
+        load(rawValue);
+        save();
+    }
+
+    ComboBox {
+        id: comboBox
+        implicitWidth: Kirigami.Units.gridUnit * 14
+        textRole: "text"
+
+        model: ListModel {
+            id: listModel
+        }
+    }
+    ToolButton {
+        id: configureButton
+        icon.name: "configure"
+        visible: listModel.get(comboBox.currentIndex).subconfigpath !== ""
+
+        onClicked: {
+            kcm.pushConfigPage(listModel.get(comboBox.currentIndex).text, listModel.get(comboBox.currentIndex).subconfigpath);
+        }
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/ExternalOption.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/ExternalOption.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/ExternalOption.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/ExternalOption.qml	2026-02-08 22:16:45.456824795 +0800
@@ -0,0 +1,37 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+
+Button {
+    property string description
+    readonly property bool needsSave: false
+    property variant properties
+    property bool subConfig: false
+
+    icon.name: "configure"
+
+    function defaults() {
+    }
+    function load(rawValue) {
+    }
+    function save() {
+    }
+
+    Component.onCompleted: {
+        if (properties.hasOwnProperty("LaunchSubConfig") && properties["LaunchSubConfig"] == "True") {
+            subConfig = true;
+        }
+    }
+    onClicked: {
+        if (subConfig) {
+            kcm.pushConfigPage(description, properties.External);
+        } else {
+            kcm.launchExternal(properties.External);
+        }
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/FontOption.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/FontOption.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/FontOption.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/FontOption.qml	2026-02-08 22:16:45.456860361 +0800
@@ -0,0 +1,37 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Dialogs 1.1 as QtDialogs
+import org.kde.kirigami 2.10 as Kirigami
+
+Button {
+    property bool needsSave: text !== rawValue
+    property variant rawValue
+
+    icon.name: "document-edit"
+    text: kcm.fontToString(fontDialog.font)
+
+    function load(rawValue) {
+        fontDialog.font = kcm.parseFont(rawValue);
+    }
+    function save() {
+        rawValue = text;
+    }
+
+    Component.onCompleted: {
+        load(rawValue);
+        save();
+    }
+    onClicked: fontDialog.open()
+
+    QtDialogs.FontDialog {
+        id: fontDialog
+        title: i18nc("@title:window", "Select Font")
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/IntegerOption.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/IntegerOption.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/IntegerOption.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/IntegerOption.qml	2026-02-08 22:16:45.456886430 +0800
@@ -0,0 +1,40 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+
+SpinBox {
+    property bool needsSave: value !== oldValue
+    property int oldValue: parseInt(rawValue)
+    property variant properties
+    property variant rawValue
+
+    from: validator.bottom
+    to: validator.top
+
+    function load(rawValue) {
+        value = parseInt(rawValue);
+    }
+    function save() {
+        rawValue = value.toString();
+    }
+
+    Component.onCompleted: {
+        if (properties.hasOwnProperty("IntMin")) {
+            validator.bottom = parseInt(properties.IntMin);
+        }
+        if (properties.hasOwnProperty("IntMax")) {
+            validator.top = parseInt(properties.IntMax);
+        }
+        load(rawValue);
+        save();
+    }
+
+    validator: IntValidator {
+        id: validator
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/KeyListOption.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/KeyListOption.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/KeyListOption.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/KeyListOption.qml	2026-02-08 22:16:45.456918860 +0800
@@ -0,0 +1,105 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.12
+import org.kde.kirigami 2.10 as Kirigami
+
+RowLayout {
+    id: keyList
+    property alias hovered: addButton.hovered
+    property bool needsSave
+    property variant properties
+    property variant rawValue
+
+    function load(rawValue) {
+        var diff = false;
+        if (listModel.count !== 0) {
+            listModel.remove(0, listModel.count);
+        }
+        var i = 0;
+        while (true) {
+            if (!rawValue.hasOwnProperty(i.toString())) {
+                break;
+            }
+            var value = rawValue[i.toString()];
+            listModel.append({
+                    "key": value
+                });
+            if (!keyList.rawValue.hasOwnProperty(i.toString()) || rawValue[i.toString()] !== keyList.rawValue[i.toString()]) {
+                diff = true;
+            }
+            i++;
+        }
+        if (keyList.rawValue.hasOwnProperty(i.toString())) {
+            diff = true;
+        }
+        needsSave = diff;
+    }
+    function save() {
+        var newRawValue = {};
+        var j = 0;
+        for (var i = 0; i < listModel.count; i++) {
+            if (listModel.get(i).key !== "") {
+                newRawValue[j.toString()] = listModel.get(i).key;
+                j++;
+            }
+        }
+        rawValue = newRawValue;
+        needsSave = false;
+    }
+
+    Component.onCompleted: {
+        load(rawValue);
+        save();
+    }
+
+    ColumnLayout {
+        visible: listModel.count > 0
+        Repeater {
+            model: listModel
+
+            delegate: RowLayout {
+                KeyOption {
+                    Layout.fillWidth: true
+                    Layout.minimumWidth: Kirigami.Units.gridUnit * 9
+                    properties: keyList.properties.hasOwnProperty("ListConstrain") ? keyList.properties.ListConstrain : {}
+                    rawValue: model.key
+
+                    onKeyStringChanged: {
+                        model.key = keyString;
+                        keyList.needsSave = true;
+                    }
+                }
+                ToolButton {
+                    icon.name: "edit-delete-symbolic"
+
+                    onClicked: {
+                        // Need to happen before real remove.
+                        keyList.needsSave = true;
+                        listModel.remove(model.index);
+                    }
+                }
+            }
+        }
+    }
+    ToolButton {
+        id: addButton
+        Layout.alignment: Qt.AlignTop | Qt.AlignLeft
+        icon.name: "list-add-symbolic"
+
+        onClicked: {
+            keyList.needsSave = true;
+            listModel.append({
+                    "key": ""
+                });
+        }
+    }
+    ListModel {
+        id: listModel
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/KeyOption.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/KeyOption.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/KeyOption.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/KeyOption.qml	2026-02-08 22:16:45.456957221 +0800
@@ -0,0 +1,151 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import "utils.js" as Utils
+
+Button {
+    id: root
+    property bool allowModifierLess: false
+    property bool allowModifierOnly: false
+    property string currentKeyString: ""
+    property bool grab: false
+    property string keyString: ""
+    property bool needsSave: keyString !== rawValue
+    property variant properties
+    property variant rawValue
+
+    down: grab
+    flat: false
+    focus: grab
+    text: prettyKeyString(grab ? currentKeyString : keyString)
+
+    function accept() {
+        kcm.ungrabKeyboard(root);
+        grab = false;
+    }
+    function isOkWhenModifierless(event) {
+        var isSpecialKey = event.key == Qt.Key_Return || event.key == Qt.Key_Space || event.key == Qt.Key_Tab || event.key == Qt.Key_Backtab || event.key == Qt.Key_Backspace || event.key == Qt.Key_Delete;
+        if (isSpecialKey) {
+            if ((event.modifiers & Qt.ShiftModifier) != 0) {
+                return true;
+            }
+            return false;
+        } else if (event.text.length === 1) {
+            return false;
+        }
+        return true;
+    }
+    function load(rawValue) {
+        keyString = rawValue;
+    }
+    function prettyKeyString(keyString) {
+        if (keyString === "") {
+            if (grab) {
+                return i18n("...");
+            } else {
+                return i18n("Empty");
+            }
+        }
+        return kcm.localizedKeyString(keyString);
+    }
+    function save() {
+        rawValue = keyString;
+    }
+
+    Component.onCompleted: {
+        if (properties.hasOwnProperty("AllowModifierLess")) {
+            allowModifierLess = properties.AllowModifierLess == "True";
+        }
+        if (properties.hasOwnProperty("AllowModifierOnly")) {
+            allowModifierOnly = properties.AllowModifierOnly == "True";
+        }
+        load(rawValue);
+        save();
+    }
+    Keys.onPressed: {
+        event.accepted = true;
+        if (!grab) {
+            return;
+        }
+        var done = true;
+        currentKeyString = kcm.eventToString(keyCodeAction.checked);
+        if (text === "") {
+            done = false;
+        }
+        var modifiers = event.modifiers & (Qt.ShiftModifier | Qt.ControlModifier | Qt.AltModifier | Qt.MetaModifier);
+        if ((modifiers & ~Qt.ShiftModifier) == 0) {
+            if (!isOkWhenModifierless(event) && !allowModifierLess) {
+                done = false;
+            }
+        }
+        if ((event.key == Qt.Key_Shift || event.key == Qt.Key_Control || event.key == Qt.Key_Meta || event.key == Qt.Key_Super_L || event.key == Qt.Key_Super_R || event.key == Qt.Key_Hyper_L || event.key == Qt.Key_Hyper_R || event.key == Qt.Key_Alt)) {
+            done = false;
+        }
+        if (done) {
+            keyString = currentKeyString;
+            accept();
+        }
+    }
+    Keys.onReleased: {
+        event.accepted = true;
+        if (!grab) {
+            return;
+        }
+        var done = false;
+        if (allowModifierOnly && (event.key == Qt.Key_Shift || event.key == Qt.Key_Control || event.key == Qt.Key_Meta || event.key == Qt.Key_Super_L || event.key == Qt.Key_Super_R || event.key == Qt.Key_Hyper_L || event.key == Qt.Key_Hyper_R || event.key == Qt.Key_Alt)) {
+            done = true;
+        }
+        var keyStr = kcm.eventToString(keyCodeAction.checked);
+        if (keyStr === "") {
+            done = false;
+        }
+        if (done) {
+            keyString = keyStr;
+            accept();
+        } else {
+            if (event.modifiers == 0) {
+                currentKeyString = "";
+            } else {
+                currentKeyString = keyStr;
+            }
+        }
+    }
+    Keys.onShortcutOverride: {
+        event.accepted = true;
+        Keys.onPressed(event);
+    }
+    onClicked: {
+        if (down) {
+            keyString = "";
+            accept();
+        } else {
+            grab = true;
+            currentKeyString = "";
+            kcm.grabKeyboard(root);
+        }
+    }
+    onPressAndHold: {
+        contextMenu.popup();
+    }
+
+    Menu {
+        id: contextMenu
+        Action {
+            id: keyCodeAction
+            checkable: true
+            text: i18n("Key code mode")
+        }
+        Action {
+            id: voidSymbolAction
+            text: i18n("Void Symbol")
+            onTriggered: {
+                keyString = "VoidSymbol";
+            }
+        }
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/ListOption.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/ListOption.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/ListOption.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/ListOption.qml	2026-02-08 22:16:45.457008026 +0800
@@ -0,0 +1,255 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.12
+import org.kde.kirigami 2.10 as Kirigami
+
+ColumnLayout {
+    id: listOption
+    property alias hovered: addButton.hovered
+    property bool needsSave: false
+    property variant properties
+    property variant rawValue
+    readonly property string subTypeName: typeName.substr(5)
+    property string typeName
+
+    function getOption() {
+        var option = {};
+        option.isSection = false;
+        option.type = subTypeName;
+        option.properties = properties;
+        option.defaultValue = "";
+        option.name = [];
+        return option;
+    }
+    function load(rawValue) {
+        needsSave = (rawValue !== listOption.rawValue);
+        if (listModel.count !== 0) {
+            listModel.remove(0, listModel.count);
+        }
+        var i = 0;
+        while (true) {
+            if (!rawValue.hasOwnProperty(i.toString())) {
+                break;
+            }
+            var value = rawValue[i.toString()];
+            listModel.append({
+                    "value": value
+                });
+            i++;
+        }
+    }
+    function prettify(value, subType) {
+        if (subType == "Integer") {
+            return value;
+        } else if (subType == "String") {
+            return value;
+        } else if (subType == "Boolean") {
+            return value == "True" ? i18n("Yes") : i18n("No");
+        } else if (subType == "Key") {
+            return kcm.localizedKeyString(value);
+        } else if (subType == "Enum") {
+            var i = 0;
+            var enumMap = {};
+            while (true) {
+                if (!properties.Enum.hasOwnProperty(i.toString())) {
+                    break;
+                }
+                var enumString = properties.Enum[i.toString()];
+                var text = enumString;
+                if (properties.hasOwnProperty("EnumI18n") && properties.EnumI18n.hasOwnProperty(i.toString())) {
+                    text = properties.EnumI18n[i.toString()];
+                }
+                enumMap[enumString] = text;
+                i++;
+            }
+            return enumMap[value];
+        } else if (subType.startsWith("List|")) {
+            var i = 0;
+            subSubType = subType.substr(5);
+            var strs = [];
+            while (true) {
+                if (!value.hasOwnProperty(i.toString())) {
+                    break;
+                }
+                var subValue = prettify(value[i.toString()]);
+                strs.push(subValue);
+                i++;
+            }
+            return i18n("[%1]", strs.join(" "));
+        } else if (configPage.typeMap.hasOwnProperty(subType)) {
+            for (var i = 0; i < configPage.typeMap[subTypeName].length; ++i) {
+                var option = configPage.typeMap[subTypeName][i];
+                if (option.name.length === 1 && option.name[0] === properties.ListDisplayOption) {
+                    return prettify(value[properties.ListDisplayOption], option.type);
+                }
+            }
+        }
+        return "";
+    }
+    function save() {
+        var newRawValue = {};
+        var j = 0;
+        for (var i = 0; i < listModel.count; i++) {
+            if (listModel.get(i).value !== "") {
+                newRawValue[j.toString()] = listModel.get(i).value;
+                j++;
+            }
+        }
+        rawValue = newRawValue;
+    }
+
+    Component.onCompleted: {
+        load(rawValue);
+        save();
+    }
+
+    Component {
+        id: delegateComponent
+        Kirigami.SwipeListItem {
+            id: listItem
+            RowLayout {
+                Kirigami.ListItemDragHandle {
+                    listItem: listItem
+                    listView: optionView
+
+                    onMoveRequested: {
+                        needsSave = true;
+                        listModel.move(oldIndex, newIndex, 1);
+                    }
+                }
+                Label {
+                    Layout.fillWidth: true
+                    color: listItem.checked || (listItem.pressed && !listItem.checked && !listItem.sectionDelegate) ? listItem.activeTextColor : listItem.textColor
+                    height: Math.max(implicitHeight, Kirigami.Units.iconSizes.smallMedium)
+                    text: model !== null ? prettify(model.value, subTypeName) : ""
+                    elide: Text.ElideRight
+                }
+            }
+
+            actions: [
+                Kirigami.Action {
+                    iconName: "document-edit"
+                    text: i18n("edit")
+
+                    onTriggered: {
+                        sheet.edit(model.index);
+                    }
+                },
+                Kirigami.Action {
+                    iconName: "list-remove-symbolic"
+                    text: i18n("Remove")
+
+                    onTriggered: {
+                        needsSave = true;
+                        listModel.remove(model.index);
+                    }
+                }
+            ]
+        }
+    }
+    ListModel {
+        id: listModel
+    }
+    ScrollView {
+        Layout.fillWidth: true
+        Kirigami.Theme.colorSet: Kirigami.Theme.View
+        Kirigami.Theme.inherit: false
+        ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+        implicitHeight: optionView.contentHeight
+        implicitWidth: Kirigami.Units.gridUnit * 10
+
+        ListView {
+            id: optionView
+            model: listModel
+
+            delegate: Kirigami.DelegateRecycler {
+                sourceComponent: delegateComponent
+                width: optionView.width
+            }
+        }
+    }
+    RowLayout {
+        ToolButton {
+            id: addButton
+            icon.name: "list-add-symbolic"
+            text: i18n("Add")
+
+            onClicked: {
+                sheet.edit(listModel.count);
+            }
+        }
+    }
+    Kirigami.OverlaySheet {
+        id: sheet
+        property int editIndex: -1
+        readonly property bool isSubConfig: configPage.typeMap.hasOwnProperty(subTypeName)
+
+        parent: configPage
+
+        function edit(index) {
+            editIndex = index;
+            if (editIndex < listModel.count) {
+                if (isSubConfig) {
+                    item().setRawValue(listModel.get(sheet.editIndex).value);
+                } else {
+                    item().load(listModel.get(sheet.editIndex).value);
+                }
+            } else {
+                if (isSubConfig) {
+                    item().defaults();
+                } else {
+                    item().load("");
+                }
+            }
+            sheet.open();
+        }
+        function item() {
+            return isSubConfig ? optionEditor.item : optionEditor.item.item;
+        }
+
+        Loader {
+            id: optionEditor
+            sourceComponent: sheet.isSubConfig ? group : option
+        }
+
+        footer: DialogButtonBox {
+            standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
+
+            onAccepted: {
+                sheet.item().save();
+                // Add a new one.
+                if (sheet.editIndex == listModel.count) {
+                    listModel.append({
+                            "value": sheet.item().rawValue
+                        });
+                } else {
+                    listModel.get(sheet.editIndex).value = sheet.item().rawValue;
+                }
+                sheet.close();
+                needsSave = true;
+            }
+            onRejected: sheet.close()
+        }
+    }
+    Component {
+        id: option
+        OptionLoader {
+            option: getOption()
+            rawValue: ""
+        }
+    }
+    Component {
+        id: group
+        ConfigGroup {
+            rawValue: null
+            typeMap: configPage.typeMap
+            typeName: subTypeName
+        }
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/main.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/main.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/main.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/main.qml	2026-02-08 22:16:45.457070813 +0800
@@ -0,0 +1,371 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.12
+import org.kde.kirigami 2.10 as Kirigami
+import org.kde.kcm 1.2 as KCM
+
+KCM.ScrollViewKCM {
+    id: root
+    Kirigami.ColumnView.fillWidth: true
+    implicitHeight: Kirigami.Units.gridUnit * 36
+    implicitWidth: Kirigami.Units.gridUnit * 50
+
+    function checkInputMethod() {
+        var firstIM = imList.model.imAt(0);
+        inputMethodNotMatchWarning.visible = false;
+        if (firstIM.startsWith("keyboard-")) {
+            layoutNotMatchWarning.visible = (firstIM.substr(9) != kcm.imConfig.defaultLayout);
+        } else {
+            layoutNotMatchWarning.visible = false;
+        }
+    }
+
+    SelectLayoutSheet {
+        id: selectLayoutSheet
+        parent: root
+    }
+    Component {
+        id: delegateComponent
+        Kirigami.SwipeListItem {
+            id: listItem
+            RowLayout {
+                Kirigami.ListItemDragHandle {
+                    listItem: listItem
+                    listView: imList
+
+                    onMoveRequested: {
+                        imList.model.move(oldIndex, newIndex, 1);
+                        checkInputMethod();
+                    }
+                }
+                Label {
+                    Layout.fillWidth: true
+                    color: listItem.checked || (listItem.pressed && !listItem.checked && !listItem.sectionDelegate) ? listItem.activeTextColor : listItem.textColor
+                    height: Math.max(implicitHeight, Kirigami.Units.iconSizes.smallMedium)
+                    text: model !== null ? model.name : ""
+                }
+            }
+
+            actions: [
+                Kirigami.Action {
+                    iconName: "configure"
+                    text: i18n("Configure")
+                    visible: model !== null ? model.configurable : false
+
+                    onTriggered: kcm.pushConfigPage(model.name, "fcitx://config/inputmethod/" + model.uniqueName)
+                },
+                Kirigami.Action {
+                    iconName: "input-keyboard"
+                    text: i18n("Select Layout")
+                    visible: model !== null ? !model.uniqueName.startsWith("keyboard-") : false
+
+                    onTriggered: selectLayoutSheet.selectLayout(i18n("Select layout for %1", model.name), model.uniqueName, (model.layout !== "" ? model.layout : kcm.imConfig.defaultLayout))
+                },
+                Kirigami.Action {
+                    iconName: "list-remove-symbolic"
+                    text: i18n("Remove")
+
+                    onTriggered: {
+                        imList.model.remove(model.index);
+                        checkInputMethod();
+                    }
+                }
+            ]
+        }
+    }
+    Kirigami.OverlaySheet {
+        id: addGroupSheet
+        parent: root
+
+        Kirigami.FormLayout {
+            implicitWidth: Kirigami.Units.gridUnit * 15
+
+            TextField {
+                id: groupName
+                Kirigami.FormData.label: i18n("Name:")
+                placeholderText: i18n("Group Name")
+            }
+        }
+
+        footer: RowLayout {
+            Item {
+                Layout.fillWidth: true
+            }
+            Button {
+                text: i18n("Ok")
+
+                onClicked: {
+                    if (groupName.text.length) {
+                        kcm.imConfig.addGroup(groupName.text);
+                        addGroupSheet.close();
+                    }
+                }
+            }
+        }
+        header: Kirigami.Heading {
+            text: i18n("Add Group")
+        }
+    }
+    Connections {
+        property int oldIndex: 0
+
+        target: kcm
+
+        function onCurrentIndexChanged(idx) {
+            if (idx < oldIndex) {
+                while (kcm.depth > idx + 1) {
+                    var page = kcm.pageNeedsSave(kcm.depth - 1);
+                    if (page === null) {
+                        kcm.pop();
+                    } else {
+                        kcm.currentIndex = kcm.depth - 1;
+                        page.showWarning();
+                        break;
+                    }
+                }
+            }
+        }
+        function onPagePushed() {
+            oldIndex = kcm.depth;
+        }
+    }
+    Dialog {
+        id: confirmGroupChangeDialog
+        property string nextGroup
+        property string prevGroup
+
+        closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
+        focus: true
+        modal: true
+        standardButtons: Dialog.Yes | Dialog.No
+        title: i18n("Current group changed")
+        x: (root.width - width) / 2
+        y: root.height / 2 - height
+
+        onAccepted: {
+            kcm.imConfig.currentGroup = nextGroup;
+        }
+        onRejected: {
+            var groups = kcm.imConfig.groups;
+            for (var i = 0; i < groups.length; i++) {
+                if (groups[i] == prevGroup) {
+                    groupComboBox.currentIndex = i;
+                    return;
+                }
+            }
+        }
+
+        Label {
+            text: i18n("Do you want to change group? Changes to current group will be lost!")
+        }
+
+        Overlay.modal: Rectangle {
+            color: "#99000000"
+        }
+    }
+
+    footer: ColumnLayout {
+        RowLayout {
+            Button {
+                icon.name: "input-keyboard"
+                text: i18n("Select system layout...")
+
+                onClicked: {
+                    selectLayoutSheet.selectLayout(i18n("Select system layout for group %1", groupComboBox.currentText), "", kcm.imConfig.defaultLayout);
+                }
+
+                ToolTip {
+                    text: i18n("Select system keyboard layout...")
+                    visible: parent.hovered
+                }
+            }
+            Label {
+                text: kcm.layoutProvider.layoutDescription(kcm.imConfig.defaultLayout)
+            }
+            TextField {
+                Layout.fillWidth: true
+                placeholderText: i18n("Test Input")
+            }
+        }
+        RowLayout {
+            enabled: kcm.availability
+
+            Button {
+                icon.name: "configure"
+                text: i18n("Configure global options...")
+
+                onClicked: kcm.pushConfigPage(i18n("Global Options"), "fcitx://config/global")
+            }
+            Button {
+                icon.name: "configure"
+                text: i18n("Configure addons...")
+
+                onClicked: kcm.push("AddonPage.qml")
+            }
+            Item {
+                Layout.fillWidth: true
+            }
+            Button {
+                icon.name: "list-add-symbolic"
+                text: i18n("Add Input Method...")
+
+                onClicked: kcm.push("AddIMPage.qml")
+            }
+        }
+    }
+    header: ColumnLayout {
+        Kirigami.InlineMessage {
+            id: fcitxNotAvailableWarning
+            Layout.fillWidth: true
+            text: i18n("Cannot connect to Fcitx by DBus, is Fcitx running?")
+            type: Kirigami.MessageType.Warning
+            visible: !kcm.availability
+
+            actions: [
+                Kirigami.Action {
+                    displayHint: Kirigami.Action.DisplayHint.KeepVisible
+                    iconName: "system-run"
+                    text: i18n("Run Fcitx")
+
+                    onTriggered: {
+                        kcm.runFcitx();
+                    }
+                }
+            ]
+        }
+        Kirigami.InlineMessage {
+            id: fcitxNeedUpdateMessage
+            Layout.fillWidth: true
+            showCloseButton: true
+            text: i18n("Found updates to fcitx installation. Do you want to check for newly installed input methods and addons? To update already loaded addons, fcitx would need to be restarted.")
+            type: Kirigami.MessageType.Information
+            visible: kcm.imConfig.needUpdate
+
+            actions: [
+                Kirigami.Action {
+                    displayHint: Kirigami.Action.DisplayHint.KeepVisible
+                    iconName: "update-none"
+                    text: i18n("Update")
+
+                    onTriggered: {
+                        kcm.imConfig.refresh();
+                    }
+                },
+                Kirigami.Action {
+                    displayHint: Kirigami.Action.DisplayHint.KeepVisible
+                    iconName: "system-run"
+                    text: i18n("Restart")
+                    visible: kcm.canRestart
+
+                    onTriggered: {
+                        kcm.imConfig.restart();
+                    }
+                }
+            ]
+        }
+        Kirigami.InlineMessage {
+            id: layoutNotMatchWarning
+            Layout.fillWidth: true
+            showCloseButton: true
+            text: i18n("Your currently configured input method does not match your layout, do you want to change the layout setting?")
+            type: Kirigami.MessageType.Warning
+            visible: false
+
+            actions: [
+                Kirigami.Action {
+                    text: i18n("Fix")
+
+                    onTriggered: {
+                        kcm.fixLayout();
+                        layoutNotMatchWarning.visible = false;
+                    }
+                }
+            ]
+        }
+        Kirigami.InlineMessage {
+            id: inputMethodNotMatchWarning
+            Layout.fillWidth: true
+            showCloseButton: true
+            text: i18n("Your currently configured input method does not match your selected layout, do you want to add the corresponding input method for the layout?")
+            type: Kirigami.MessageType.Warning
+            visible: false
+
+            actions: [
+                Kirigami.Action {
+                    text: i18n("Fix")
+
+                    onTriggered: {
+                        kcm.fixInputMethod();
+                    }
+                }
+            ]
+        }
+        RowLayout {
+            enabled: kcm.availability
+
+            Label {
+                text: i18n("Group:")
+            }
+            ComboBox {
+                id: groupComboBox
+                Layout.fillWidth: true
+                model: kcm.imConfig.groups
+
+                onActivated: {
+                    if (kcm.imConfig.needSave && kcm.imConfig.currentGroup !== currentText && kcm.imConfig.currentGroup !== "") {
+                        confirmGroupChangeDialog.nextGroup = currentText;
+                        confirmGroupChangeDialog.prevGroup = kcm.imConfig.currentGroup;
+                        confirmGroupChangeDialog.open();
+                    } else {
+                        kcm.imConfig.currentGroup = currentText;
+                    }
+                }
+            }
+            Button {
+                icon.name: "list-add-symbolic"
+
+                onClicked: {
+                    groupName.text = "";
+                    addGroupSheet.open();
+                }
+            }
+            Button {
+                icon.name: "list-remove-symbolic"
+                visible: kcm.imConfig.groups && kcm.imConfig.groups.length > 1
+
+                onClicked: {
+                    kcm.imConfig.deleteGroup(groupComboBox.currentText);
+                }
+            }
+        }
+    }
+    view: ListView {
+        id: imList
+        enabled: kcm.availability
+        model: kcm.imConfig.currentIMModel
+
+        section {
+            property: "active"
+            delegate: Kirigami.ListSectionHeader {
+                text: section == "inactive" ? i18n("Input Method Off") : i18n("Input Method On")
+            }
+        }
+
+        delegate: Kirigami.DelegateRecycler {
+            sourceComponent: delegateComponent
+            width: imList.width
+        }
+        moveDisplaced: Transition {
+            YAnimator {
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
+            }
+        }
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/OptionLoader.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/OptionLoader.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/OptionLoader.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/OptionLoader.qml	2026-02-08 22:16:45.457110387 +0800
@@ -0,0 +1,66 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import org.kde.kirigami 2.10 as Kirigami
+import "utils.js" as Utils
+
+Loader {
+    id: loader
+    property variant option
+    property variant rawValue
+
+    ToolTip.delay: Kirigami.Units.toolTipDelay
+    ToolTip.text: option.properties && option.properties.hasOwnProperty("Tooltip") ? option.properties["Tooltip"] : ""
+    ToolTip.visible: option.properties && option.properties.hasOwnProperty("Tooltip") && loader.item ? loader.item.hovered : false
+
+    function optionSource(data) {
+        if (data.type == "Boolean") {
+            return "BoolOption.qml";
+        } else if (data.type == "Integer") {
+            return "IntegerOption.qml";
+        } else if (data.type == "Enum") {
+            return "EnumOption.qml";
+        } else if (data.type == "String") {
+            if (data.properties.hasOwnProperty("Font") && data.properties.Font == "True") {
+                return "FontOption.qml";
+            } else if (data.properties.hasOwnProperty("IsEnum") && data.properties.IsEnum == "True") {
+                return "EnumOption.qml";
+            } else {
+                return "StringOption.qml";
+            }
+        } else if (data.type == "List|Key") {
+            return "KeyListOption.qml";
+        } else if (data.type == "Key") {
+            return "KeyOption.qml";
+        } else if (data.type == "Color") {
+            return "ColorOption.qml";
+        } else if (data.type.startsWith("List|")) {
+            return "ListOption.qml";
+        } else if (data.type == "External") {
+            return "ExternalOption.qml";
+        } else {
+            console.log(data.type + " Not supported");
+            return "";
+        }
+    }
+
+    Component.onCompleted: {
+        if (!option.isSection) {
+            var prop = {
+                "name": option.name,
+                "typeName": option.type,
+                "description": option.description,
+                "defaultValue": option.defaultValue,
+                "properties": option.properties,
+                "rawValue": rawValue
+            };
+            loader.setSource(optionSource(option), prop);
+        }
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/SaveWarningDialog.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/SaveWarningDialog.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/SaveWarningDialog.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/SaveWarningDialog.qml	2026-02-08 22:16:45.457138629 +0800
@@ -0,0 +1,27 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+
+Dialog {
+    id: notSavedDialog
+    closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
+    focus: true
+    modal: true
+    standardButtons: Dialog.Ok
+    title: i18n("Some config is not saved")
+    x: (parent.width - width) / 2
+    y: parent.height / 2 - height
+
+    Label {
+        text: i18n("Current page is not yet saved. Please save the config first.")
+    }
+
+    Overlay.modal: Rectangle {
+        color: "#99000000"
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/SelectLayoutSheet.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/SelectLayoutSheet.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/SelectLayoutSheet.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/SelectLayoutSheet.qml	2026-02-08 22:16:45.457173344 +0800
@@ -0,0 +1,104 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.12
+import org.kde.kirigami 2.10 as Kirigami
+
+Kirigami.OverlaySheet {
+    id: selectLayoutSheet
+    property string im: ""
+
+    function selectLayout(title, im, layout) {
+        heading.text = title;
+        languageComboBox.currentIndex = 0;
+        selectLayoutSheet.im = im;
+        var layoutIndex = kcm.layoutProvider.layoutIndex(layout);
+        layoutComboBox.currentIndex = layoutIndex;
+        var variantIndex = kcm.layoutProvider.variantIndex(layout);
+        variantComboBox.currentIndex = variantIndex;
+        open();
+    }
+
+    Kirigami.FormLayout {
+        implicitWidth: Kirigami.Units.gridUnit * 45
+
+        ComboBox {
+            id: languageComboBox
+            Kirigami.FormData.label: i18n("Language:")
+            implicitWidth: Kirigami.Units.gridUnit * 15
+            model: kcm.layoutProvider.languageModel
+            textRole: "name"
+
+            onCurrentIndexChanged: {
+                kcm.layoutProvider.layoutModel.language = model.language(currentIndex);
+                layoutComboBox.currentIndex = 0;
+                kcm.layoutProvider.setVariantInfo(kcm.layoutProvider.layoutModel.layoutInfo(layoutComboBox.currentIndex));
+                variantComboBox.currentIndex = 0;
+            }
+        }
+        ComboBox {
+            id: layoutComboBox
+            Kirigami.FormData.label: i18n("Layout:")
+            implicitWidth: Kirigami.Units.gridUnit * 15
+            model: kcm.layoutProvider.layoutModel
+            textRole: "name"
+
+            onCurrentIndexChanged: {
+                if (currentIndex < 0) {
+                    return;
+                }
+                kcm.layoutProvider.setVariantInfo(model.layoutInfo(currentIndex));
+                variantComboBox.currentIndex = 0;
+            }
+        }
+        ComboBox {
+            id: variantComboBox
+            Kirigami.FormData.label: i18n("Variant:")
+            implicitWidth: Kirigami.Units.gridUnit * 15
+            model: kcm.layoutProvider.variantModel
+            textRole: "name"
+        }
+    }
+
+    footer: RowLayout {
+        Item {
+            Layout.fillWidth: true
+        }
+        Button {
+            text: i18n("Clear")
+            visible: im !== ""
+
+            onClicked: {
+                kcm.imConfig.setLayout(im, "");
+                selectLayoutSheet.close();
+            }
+        }
+        Button {
+            text: i18n("Ok")
+
+            onClicked: {
+                if (layoutComboBox.currentIndex >= 0 && variantComboBox.currentIndex >= 0) {
+                    var layout = kcm.layoutProvider.layout(layoutComboBox.currentIndex, variantComboBox.currentIndex);
+                    if (im.length === 0) {
+                        kcm.imConfig.defaultLayout = layout;
+                        if (kcm.imConfig.currentIMModel.count > 0 && "keyboard-%0".arg(layout) != kcm.imConfig.currentIMModel.imAt(0)) {
+                            layoutNotMatchWarning.visible = false;
+                            inputMethodNotMatchWarning.visible = true;
+                        }
+                    } else {
+                        kcm.imConfig.setLayout(im, layout);
+                    }
+                    selectLayoutSheet.close();
+                }
+            }
+        }
+    }
+    header: Kirigami.Heading {
+        id: heading
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/StringOption.qml fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/StringOption.qml
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/StringOption.qml	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/StringOption.qml	2026-02-08 22:16:45.457197359 +0800
@@ -0,0 +1,25 @@
+/*
+ * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+
+TextField {
+    property bool needsSave: text !== rawValue
+    property string rawValue
+
+    function load(rawValue) {
+        text = rawValue;
+    }
+    function save() {
+        rawValue = text;
+    }
+
+    Component.onCompleted: {
+        load(rawValue);
+        save();
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/utils.js fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/utils.js
--- fcitx5-configtool-5.1.12/src/kcm/package/contents/ui/utils.js	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/contents/ui/utils.js	2026-02-08 22:16:45.457221233 +0800
@@ -0,0 +1,29 @@
+function getRawValue(rawValue, name) {
+    if (name.length == 0) {
+        return "";
+    }
+    if (rawValue === null) {
+        return "";
+    }
+    for (var i = 0; i < name.length; i++) {
+        if (rawValue.hasOwnProperty(name[i])) {
+            rawValue = rawValue[name[i]];
+        } else {
+            return "";
+        }
+    }
+    return rawValue;
+}
+
+function setRawValue(rawValue, name, value) {
+    for (var i = 0; i < name.length; i++) {
+        if (i + 1 == name.length) {
+            rawValue[name[i]] = value;
+        } else {
+            if (!rawValue.hasOwnProperty(name[i])) {
+                rawValue[name[i]] = {};
+            }
+            rawValue = rawValue[name[i]];
+        }
+    }
+}
diff -urN fcitx5-configtool-5.1.12/src/kcm/package/metadata.json fcitx5-configtool-5.1.12.new/src/kcm/package/metadata.json
--- fcitx5-configtool-5.1.12/src/kcm/package/metadata.json	1970-01-01 08:00:00.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/kcm/package/metadata.json	2026-02-08 22:16:45.457252892 +0800
@@ -0,0 +1,46 @@
+{
+    "KPlugin": {
+        "Authors": [
+            {
+                "Email": "fcitx-dev@googlegroups.com.",
+                "Name": "Weng Xuetian"
+            }
+        ],
+        "Description": "Configure Input Method",
+        "Description[ca]": "Configura el mètode d'entrada",
+        "Description[da]": "Konfigurer inputmetode",
+        "Description[de]": "Eingabemethode konfigurieren",
+        "Description[es]": "Configurar método de entrada",
+        "Description[fr]": "Configurer la méthode d'entrée",
+        "Description[he]": "הגדרת שיטת קלט",
+        "Description[ja]": "入力メソッドの設定",
+        "Description[ko]": "입력기 구성하기",
+        "Description[ru]": "Настроить метод ввода",
+        "Description[zh_CN]": "配置输入法",
+        "Description[zh_TW]": "設定輸入法",
+        "Icon": "fcitx",
+        "Id": "kcm_fcitx5",
+        "License": "GPL",
+        "Name": "Input Method",
+        "Name[ca]": "Mètode d'entrada",
+        "Name[da]": "Inputmetode",
+        "Name[de]": "Eingabemethode",
+        "Name[es]": "Método de entrada",
+        "Name[fr]": "Méthode de saisie",
+        "Name[he]": "שיטת קלט",
+        "Name[ja]": "入力メソッド",
+        "Name[ko]": "입력기",
+        "Name[ru]": "Метод ввода",
+        "Name[tr]": "Giriş Yöntemi",
+        "Name[vi]": "Kiểu gõ",
+        "Name[zh_CN]": "输入法",
+        "Name[zh_TW]": "輸入法",
+        "ServiceTypes": [
+            "Plasma/Generic"
+        ],
+        "Version": "",
+        "Website": ""
+    },
+    "X-Plasma-API": "declarativeappletscript",
+    "X-Plasma-MainScript": "ui/main.qml"
+}
diff -urN fcitx5-configtool-5.1.12/src/lib/configlib/addonmodel.cpp fcitx5-configtool-5.1.12.new/src/lib/configlib/addonmodel.cpp
--- fcitx5-configtool-5.1.12/src/lib/configlib/addonmodel.cpp	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/lib/configlib/addonmodel.cpp	2026-02-08 22:16:45.457288939 +0800
@@ -420,12 +420,16 @@
         QProcess::startDetached(wrapperToUse, args);
     } else {
         // Assume this is a program path.
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
         QStringList args = QProcess::splitCommand(uri);
         if (args.isEmpty()) {
             return;
         }
         QString program = args.takeFirst();
         QProcess::startDetached(program, args);
+#else
+        QProcess::startDetached(uri);
+#endif
     }
 }
 
diff -urN fcitx5-configtool-5.1.12/src/lib/configlib/model.cpp fcitx5-configtool-5.1.12.new/src/lib/configlib/model.cpp
--- fcitx5-configtool-5.1.12/src/lib/configlib/model.cpp	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/lib/configlib/model.cpp	2026-02-08 22:16:45.457341778 +0800
@@ -15,7 +15,6 @@
 #include <QString>
 #include <QStringLiteral>
 #include <Qt>
-#include <QtTypes>
 #include <algorithm>
 #include <fcitx-utils/i18n.h>
 #include <fcitxqtdbustypes.h>
@@ -42,9 +41,9 @@
                 }
                 if (items.size() > 1) {
                     QLocale locale(langCode);
-                    if (locale.territory() != QLocale::AnyTerritory) {
+                    if (locale.country() != QLocale::AnyCountry) {
                         territory =
-                            QLocale::territoryToString(locale.territory());
+                            QLocale::countryToString(locale.country());
                     }
                     if (!territory.isEmpty()) {
                         territory =
diff -urN fcitx5-configtool-5.1.12/src/lib/configwidgetslib/addonselector.cpp fcitx5-configtool-5.1.12.new/src/lib/configwidgetslib/addonselector.cpp
--- fcitx5-configtool-5.1.12/src/lib/configwidgetslib/addonselector.cpp	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/lib/configwidgetslib/addonselector.cpp	2026-02-08 22:16:45.457392552 +0800
@@ -46,9 +46,15 @@
 
 protected:
     QList<QWidget *> createItemWidgets(const QModelIndex &index) const override;
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
     void updateItemWidgets(const QList<QWidget *> &widgets,
                            const QStyleOptionViewItem &option,
                            const QPersistentModelIndex &index) const override;
+#else
+    void updateItemWidgets(const QList<QWidget *> widgets,
+                           const QStyleOptionViewItem &option,
+                           const QPersistentModelIndex &index) const override;
+#endif
 
 private Q_SLOTS:
     void checkBoxClicked(bool state);
@@ -210,9 +216,15 @@
     return widgetList;
 }
 
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
 void AddonDelegate::updateItemWidgets(
     const QList<QWidget *> &widgets, const QStyleOptionViewItem &option,
     const QPersistentModelIndex &index) const {
+#else
+void AddonDelegate::updateItemWidgets(
+    const QList<QWidget *> widgets, const QStyleOptionViewItem &option,
+    const QPersistentModelIndex &index) const {
+#endif
     if (index.data(RowTypeRole).toInt() == CategoryType) {
         return;
     }
diff -urN fcitx5-configtool-5.1.12/src/lib/configwidgetslib/listoptionwidget.cpp fcitx5-configtool-5.1.12.new/src/lib/configwidgetslib/listoptionwidget.cpp
--- fcitx5-configtool-5.1.12/src/lib/configwidgetslib/listoptionwidget.cpp	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/lib/configwidgetslib/listoptionwidget.cpp	2026-02-08 22:16:45.457438348 +0800
@@ -104,7 +104,11 @@
                            index.parent(), index.row() - 1)) {
             return;
         }
+#if (QT_VERSION < QT_VERSION_CHECK(5, 13, 0))
+        values_.swap(index.row() - 1, index.row());
+#else
         values_.swapItemsAt(index.row() - 1, index.row());
+#endif
         endMoveRows();
     }
 
@@ -117,7 +121,11 @@
                            index.parent(), index.row() + 2)) {
             return;
         }
+#if (QT_VERSION < QT_VERSION_CHECK(5, 13, 0))
+        values_.swap(index.row(), index.row() + 1);
+#else
         values_.swapItemsAt(index.row(), index.row() + 1);
+#endif
         endMoveRows();
     }
 
diff -urN fcitx5-configtool-5.1.12/src/lib/configwidgetslib/varianthelper.cpp fcitx5-configtool-5.1.12.new/src/lib/configwidgetslib/varianthelper.cpp
--- fcitx5-configtool-5.1.12/src/lib/configwidgetslib/varianthelper.cpp	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/lib/configwidgetslib/varianthelper.cpp	2026-02-08 22:16:45.457472421 +0800
@@ -82,7 +82,11 @@
             iter = map.insert(path[depth], QVariantMap());
         }
 
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
         if (iter->typeId() != QMetaType::QVariantMap) {
+#else
+        if (iter->type() != QVariant::Map) {
+#endif
             auto oldValue = *iter;
             *iter = QVariantMap({{"", oldValue}});
         }
diff -urN fcitx5-configtool-5.1.12/src/plasmathemegenerator/CMakeLists.txt fcitx5-configtool-5.1.12.new/src/plasmathemegenerator/CMakeLists.txt
--- fcitx5-configtool-5.1.12/src/plasmathemegenerator/CMakeLists.txt	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/plasmathemegenerator/CMakeLists.txt	2026-02-08 22:16:45.457505843 +0800
@@ -6,8 +6,13 @@
     Fcitx5::Utils
     Fcitx5::Config)
 
+if (QT_MAJOR_VERSION STREQUAL 5)
+target_link_libraries(fcitx5-plasma-theme-generator
+    KF${QT_MAJOR_VERSION}::Plasma)
+elseif (QT_MAJOR_VERSION STREQUAL 6)
 target_link_libraries(fcitx5-plasma-theme-generator
     Plasma::Plasma
     KF${QT_MAJOR_VERSION}::Svg)
+endif()
 
 install(TARGETS fcitx5-plasma-theme-generator DESTINATION ${CMAKE_INSTALL_BINDIR})
diff -urN fcitx5-configtool-5.1.12/src/plasmathemegenerator/main.cpp fcitx5-configtool-5.1.12.new/src/plasmathemegenerator/main.cpp
--- fcitx5-configtool-5.1.12/src/plasmathemegenerator/main.cpp	2025-12-22 14:18:08.000000000 +0800
+++ fcitx5-configtool-5.1.12.new/src/plasmathemegenerator/main.cpp	2026-02-08 22:16:45.457551278 +0800
@@ -7,9 +7,6 @@
 #include "config.h"
 #include <KIconLoader>
 #include <KLocalizedString>
-#include <KSvg/FrameSvg>
-#include <KSvg/ImageSet>
-#include <KSvg/Svg>
 #include <Plasma/Theme>
 #include <QBitmap>
 #include <QColor>
@@ -35,8 +32,18 @@
 #include <fcntl.h>
 #include <memory>
 #include <string>
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+#include <KSvg/FrameSvg>
+#include <KSvg/ImageSet>
+#include <KSvg/Svg>
 using FrameSvg = KSvg::FrameSvg;
 using Svg = KSvg::Svg;
+#else
+#include <Plasma/FrameSvg>
+using FrameSvg = Plasma::FrameSvg;
+using Svg = Plasma::Svg;
+#endif
 
 namespace {
 bool fd_is_valid(int fd) { return fcntl(fd, F_GETFD) != -1 || errno != EBADF; }
@@ -126,6 +133,7 @@
             theme_ = std::make_unique<Plasma::Theme>();
         }
 
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
         if (parser.isSet("theme") && !monitorMode()) {
             imageSet_ = std::make_unique<KSvg::ImageSet>(theme_->themeName());
         } else {
@@ -134,6 +142,7 @@
             // theme's global change like composite.
             imageSet_ = std::make_unique<KSvg::ImageSet>();
         }
+#endif
 
         if (monitorMode()) {
             socketNotifier_ =
@@ -146,11 +155,13 @@
                         }
                     });
             connect(theme_.get(), &Plasma::Theme::themeChanged, this, [this]() {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
                 auto selectors = imageSet_->selectors();
                 // Force invalidate the cache to workaround a bug in ksvg.
                 // FIXME: remove this once fix is merged.
                 imageSet_->setSelectors({"bad"});
                 imageSet_->setSelectors(selectors);
+#endif
                 if (!generateTheme()) {
                     qDebug() << "Failed to generate theme.";
                 }
@@ -400,14 +411,21 @@
 
     template <typename T>
     void setThemeToSvg(T &svg) {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
         svg.setImageSet(imageSet_.get());
+#else
+        svg.setTheme(theme_.get());
+#endif
     }
 
 private:
     QSocketNotifier *socketNotifier_ = nullptr;
     int fd_ = -1;
     std::unique_ptr<Plasma::Theme> theme_;
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
     std::unique_ptr<KSvg::ImageSet> imageSet_;
+#else
+#endif
     QString outputPath_;
 };
 
openSUSE Build Service is sponsored by