[office/crow-translate] /: Use KIconThemes for icon loading with theme fallback

Mauritius Clemens <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit db0775f7a38fb92e732639ed4bf61b4ac2870b5e by Mauritius Clemens.
Committed on 16/08/2026 at 21:39.
Pushed by pillowtrucker into branch 'master'.

Use KIconThemes for icon loading with theme fallback

Some icon names used by crow-translate (e.g. the Clear button's
"edit-clear-all") are breeze-era names that older themes such as Oxygen
do not provide, leaving the affected widgets without an icon (bug 509329).

Link KIconThemes (WITH_KICONTHEMES, default ON) and:

- call KIconTheme::initTheme() before constructing the application object;
  it installs KIconEngine so that ALL QIcon::fromTheme() lookups (including
  the ones uic generates from .ui files) resolve through the full KDE theme
  chain: the configured theme, its Inherits chain and the fallback theme
- route the app/tray icon loads through a small IconUtils helper using
  KIconLoader, falling back to QIcon::fromTheme when KIconThemes is not
  available or a lookup comes up empty (canReturnNull, no placeholder)
- load the window icon after the application object exists, as
  KIconLoader::global() requires a live QGuiApplication

CCBUG: 509329

M  +1    -0    .kde-ci.yml
M  +9    -0    CMakeLists.txt
A  +54   -0    src/iconutils.h     [License: GPL(v3.0+)]
M  +20   -1    src/main.cpp
M  +4    -3    src/trayicon.cpp

https://invent.kde.org/office/crow-translate/-/commit/db0775f7a38fb92e732639ed4bf61b4ac2870b5e

diff --git a/.kde-ci.yml b/.kde-ci.yml
index 7d3d9ae5..2df79048 100644
--- a/.kde-ci.yml
+++ b/.kde-ci.yml
@@ -5,6 +5,7 @@ Dependencies:
 - 'on': ['@all']
   'require':
     'frameworks/extra-cmake-modules': '@stable-kf6'
+    'frameworks/kiconthemes': '@stable-kf6'
 
 - 'on': ['Linux', 'FreeBSD']
   'require':
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6f262b0c..d0e5f0b6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -70,6 +70,7 @@ else()
     option(WITH_PORTABLE_MODE "Enable portable functionality" OFF)
 endif()
 option(WITH_KWAYLAND "Use KWayland for better Wayland integration" ON)
+option(WITH_KICONTHEMES "Use KIconThemes for proper icon theme fallback" ON)
 
 option(WITH_PIPER_TTS "Enable Piper neural TTS provider (requires onnxruntime and espeak-ng)" ON)
 option(WITH_TTS "Enable text-to-speech support (Mozhi and Qt providers, plus Piper if WITH_PIPER_TTS)" ON)
@@ -96,6 +97,9 @@ add_subdirectory(src/3rdparty/qhotkey)
 find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Network LinguistTools Concurrent StateMachine TextToSpeech)
 
 find_package(Tesseract REQUIRED)
+if(WITH_KICONTHEMES)
+    find_package(KF6 REQUIRED COMPONENTS IconThemes)
+endif()
 if(UNIX)
     find_package(Qt6 REQUIRED COMPONENTS DBus)
     if(NOT APPLE)
@@ -444,6 +448,11 @@ target_link_libraries(${PROJECT_NAME}-lib PUBLIC
 
 target_link_libraries(${PROJECT_NAME}-lib PUBLIC Qt6::TextToSpeech)
 
+if(WITH_KICONTHEMES)
+    target_link_libraries(${PROJECT_NAME}-lib PUBLIC KF6::IconThemes)
+    target_compile_definitions(${PROJECT_NAME}-lib PUBLIC WITH_KICONTHEMES)
+endif()
+
 # Link Piper dependencies if enabled
 if(WITH_PIPER_TTS)
     if(NOT ONNXRuntime_USE_STATIC)
diff --git a/src/iconutils.h b/src/iconutils.h
new file mode 100644
index 00000000..2d8199c4
--- /dev/null
+++ b/src/iconutils.h
@@ -0,0 +1,54 @@
+/*
+ * SPDX-FileCopyrightText: 2025 Mauritius Clemens <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <QIcon>
+#include <QPixmap>
+#include <QString>
+
+#ifdef WITH_KICONTHEMES
+#include <KIconLoader>
+#endif
+
+namespace IconUtils
+{
+/*
+ * Icon loading helpers with theme fallback.
+ *
+ * Some icon names crow-translate uses (e.g. "edit-clear-all") are breeze
+ * names that older themes like Oxygen do not provide (bug 509329). When
+ * KIconThemes is available we resolve through KIconLoader, which walks the
+ * full KDE theme chain (configured theme -> its Inherits -> fallback theme
+ * -> the themes compiled into the binary via qrc). KIconLoader requires a
+ * living QGuiApplication, so only call these after the app object exists.
+ *
+ * canReturnNull=true makes KIconLoader return a null pixmap instead of the
+ * "unknown" placeholder when nothing is found, so the QIcon::fromTheme
+ * fallback can engage.
+ */
+inline QIcon load(const QString &name)
+{
+#ifdef WITH_KICONTHEMES
+    const QPixmap pixmap = KIconLoader::global()->loadIcon(name, KIconLoader::Panel, 22, KIconLoader::DefaultState, QStringList(), nullptr, true);
+    if (!pixmap.isNull()) {
+        return QIcon(pixmap);
+    }
+#endif
+    return QIcon::fromTheme(name);
+}
+
+inline QIcon loadDesktop(const QString &name)
+{
+#ifdef WITH_KICONTHEMES
+    const QPixmap pixmap = KIconLoader::global()->loadIcon(name, KIconLoader::Desktop, 512, KIconLoader::DefaultState, QStringList(), nullptr, true);
+    if (!pixmap.isNull()) {
+        return QIcon(pixmap);
+    }
+#endif
+    return QIcon::fromTheme(name);
+}
+} // namespace IconUtils
diff --git a/src/main.cpp b/src/main.cpp
index 0102c7aa..ac0719b8 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -7,12 +7,17 @@
 
 #include "cli.h"
 #include "cmake.h"
+#include "iconutils.h"
 #include "instancepingerdialog.h"
 #include "language.h"
 #include "mainwindow.h"
 #include "singleapplication.h"
 #include "settings/appsettings.h"
 
+#ifdef WITH_KICONTHEMES
+#include <KIconTheme>
+#endif
+
 #ifdef Q_OS_UNIX
 #include "ocr/ocr.h"
 
@@ -44,7 +49,16 @@ int launchGui(int argc, char *argv[])
     Q_INIT_RESOURCE(engines);
     Q_INIT_RESOURCE(icon_theme);
 
-    QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral(APPLICATION_ID)));
+#ifdef WITH_KICONTHEMES
+    // Set up KDE icon theming before the application object exists (required;
+    // it installs a startup hook). This makes QIcon::fromTheme() calls go
+    // through KIconEngine, which resolves icons through the full KDE theme
+    // chain (configured theme -> its Inherits -> fallback), so names that only
+    // exist in newer themes (e.g. "edit-clear-all" under Oxygen, bug 509329)
+    // still resolve. On the KDE platform theme it is a no-op.
+    KIconTheme::initTheme();
+#endif
+
 #if defined(Q_OS_WIN)
     QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
 #endif
@@ -56,6 +70,11 @@ int launchGui(int argc, char *argv[])
 
     const SingleApplication app(argc, argv, true);
 
+    // KIconLoader::global() requires a live QGuiApplication; set the window
+    // icon only after the application object exists (loading it earlier
+    // crashes in the color-scheme lookup on a null application).
+    QGuiApplication::setWindowIcon(IconUtils::loadDesktop(QStringLiteral(APPLICATION_ID)));
+
     AppSettings settings;
     settings.loadCustomLanguageRegistry(); // Load persisted custom languages
     Language::setCustomLanguageRegistryChangedCallback(&AppSettings::onCustomLanguageRegistryChanged);
diff --git a/src/trayicon.cpp b/src/trayicon.cpp
index af85748c..01de5612 100644
--- a/src/trayicon.cpp
+++ b/src/trayicon.cpp
@@ -7,6 +7,7 @@
 
 #include "trayicon.h"
 
+#include "iconutils.h"
 #include "mainwindow.h"
 
 #include <QAction>
@@ -22,9 +23,9 @@
 TrayIcon::TrayIcon(MainWindow *parent)
     : QSystemTrayIcon(parent)
     , m_trayMenu(new QMenu(parent))
-    , m_showMainWindowAction(m_trayMenu->addAction(QIcon::fromTheme(QStringLiteral(APPLICATION_ID "-tray")), tr("Show window"), parent, &MainWindow::open))
-    , m_openSettingsAction(m_trayMenu->addAction(QIcon::fromTheme(QStringLiteral("preferences-other")), tr("Settings"), parent, &MainWindow::openSettings))
-    , m_quitAction(m_trayMenu->addAction(QIcon::fromTheme(QStringLiteral("application-exit")), tr("Quit"), parent, &MainWindow::quit))
+    , m_showMainWindowAction(m_trayMenu->addAction(IconUtils::load(QStringLiteral(APPLICATION_ID "-tray")), tr("Show window"), parent, &MainWindow::open))
+    , m_openSettingsAction(m_trayMenu->addAction(IconUtils::load(QStringLiteral("preferences-other")), tr("Settings"), parent, &MainWindow::openSettings))
+    , m_quitAction(m_trayMenu->addAction(IconUtils::load(QStringLiteral("application-exit")), tr("Quit"), parent, &MainWindow::quit))
 {
     m_reportBugAction = m_trayMenu->addAction(tr("Report Bug…"));
     connect(m_reportBugAction, &QAction::triggered, this, []() {
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.