diff --git a/CMakeLists.txt b/CMakeLists.txt index f0dba817..c642d0d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,6 +297,17 @@ target_precompile_headers(lemon PUBLIC ${CMAKE_SOURCE_DIR}/src/pch.h) target_link_libraries(lemon PRIVATE ${LEMON_QT_LIBS} lemon-core lemon-base SingleApplication::SingleApplication spdlog::spdlog) +# Linking this static Qt plugin also imports its generated registration object. +find_package(${LEMON_QT_LIBNAME} COMPONENTS Svg REQUIRED) +qt_add_plugin(lemon-svg-icon STATIC + PLUGIN_TYPE iconengines + CLASS_NAME LemonSvgIconPlugin + src/plugins/lemonsvgiconplugin.cpp + src/plugins/lemonsvgiconplugin.json + ) +target_link_libraries(lemon-svg-icon PRIVATE ${LEMON_QT_LIBNAME}::Gui ${LEMON_QT_LIBNAME}::Svg) +target_link_libraries(lemon PRIVATE lemon-svg-icon) + if(ENABLE_LTO) include(CheckIPOSupported) check_ipo_supported(RESULT LTO_SUPPORTED OUTPUT error) diff --git a/assets/pics/acrobat.svg b/assets/pics/acrobat.svg index bde6bc15..4144418c 100644 --- a/assets/pics/acrobat.svg +++ b/assets/pics/acrobat.svg @@ -1,3 +1,5 @@ - - - + + + + + diff --git a/assets/pics/list-add.svg b/assets/pics/list-add.svg index 8779062a..ce2b1fff 100644 --- a/assets/pics/list-add.svg +++ b/assets/pics/list-add.svg @@ -7,7 +7,7 @@ diff --git a/assets/pics/list-remove.svg b/assets/pics/list-remove.svg index dd977fe0..f3f70cf1 100644 --- a/assets/pics/list-remove.svg +++ b/assets/pics/list-remove.svg @@ -7,7 +7,7 @@ diff --git a/resource.qrc b/resource.qrc index af75dcc7..9c4683af 100755 --- a/resource.qrc +++ b/resource.qrc @@ -9,37 +9,37 @@ assets/icons/lemon-lime.png - assets/pics/code-function.svg - assets/pics/configure.svg - assets/pics/dialog-cancel.svg - assets/pics/dialog-ok-apply.svg - assets/pics/document-edit.svg - assets/pics/edit-clear.svg - assets/pics/edit-delete.svg - assets/pics/edit-find-replace.svg - assets/pics/edit-find.svg - assets/pics/go-down.svg - assets/pics/go-up.svg - assets/pics/list-add.svg - assets/pics/list-remove.svg - assets/pics/media-playback-stop.svg - assets/pics/media-skip-forward.svg - assets/pics/paint-none.svg - assets/pics/view-refresh.svg - assets/pics/view-sort.svg - assets/pics/application-exit.svg - assets/pics/deletecell.svg - assets/pics/document-close.svg - assets/pics/document-export.svg - assets/pics/document-new.svg - assets/pics/document-save.svg - assets/pics/globe.svg - assets/pics/go-parent-folder.svg - assets/pics/help-about.svg - assets/pics/layer-new.svg - assets/pics/paint-unknown.svg - assets/pics/quickopen-file.svg - assets/pics/system-help.svg - assets/pics/acrobat.svg + assets/pics/code-function.svg + assets/pics/configure.svg + assets/pics/dialog-cancel.svg + assets/pics/dialog-ok-apply.svg + assets/pics/document-edit.svg + assets/pics/edit-clear.svg + assets/pics/edit-delete.svg + assets/pics/edit-find-replace.svg + assets/pics/edit-find.svg + assets/pics/go-down.svg + assets/pics/go-up.svg + assets/pics/list-add.svg + assets/pics/list-remove.svg + assets/pics/media-playback-stop.svg + assets/pics/media-skip-forward.svg + assets/pics/paint-none.svg + assets/pics/view-refresh.svg + assets/pics/view-sort.svg + assets/pics/application-exit.svg + assets/pics/deletecell.svg + assets/pics/document-close.svg + assets/pics/document-export.svg + assets/pics/document-new.svg + assets/pics/document-save.svg + assets/pics/globe.svg + assets/pics/go-parent-folder.svg + assets/pics/help-about.svg + assets/pics/layer-new.svg + assets/pics/paint-unknown.svg + assets/pics/quickopen-file.svg + assets/pics/system-help.svg + assets/pics/acrobat.svg diff --git a/src/forms/addtestcaseswizard.ui b/src/forms/addtestcaseswizard.ui index 336e0314..a48a9874 100755 --- a/src/forms/addtestcaseswizard.ui +++ b/src/forms/addtestcaseswizard.ui @@ -415,7 +415,7 @@ - :/icon/list-add.svg:/icon/list-add.svg + :/icon/list-add.llsvg:/icon/list-add.llsvg @@ -438,7 +438,7 @@ - :/icon/list-remove.svg:/icon/list-remove.svg + :/icon/list-remove.llsvg:/icon/list-remove.llsvg diff --git a/src/forms/advancedcompilersettingsdialog.ui b/src/forms/advancedcompilersettingsdialog.ui index 28f0cece..c42f4706 100755 --- a/src/forms/advancedcompilersettingsdialog.ui +++ b/src/forms/advancedcompilersettingsdialog.ui @@ -339,7 +339,7 @@ - :/icon/edit-delete.svg:/icon/edit-delete.svg + :/icon/edit-delete.llsvg:/icon/edit-delete.llsvg diff --git a/src/forms/compilersettings.ui b/src/forms/compilersettings.ui index b498202e..6147a493 100755 --- a/src/forms/compilersettings.ui +++ b/src/forms/compilersettings.ui @@ -77,7 +77,7 @@ - :/icon/go-up.svg:/icon/go-up.svg + :/icon/go-up.llsvg:/icon/go-up.llsvg false @@ -106,7 +106,7 @@ - :/icon/go-down.svg:/icon/go-down.svg + :/icon/go-down.llsvg:/icon/go-down.llsvg false @@ -148,7 +148,7 @@ - :/icon/list-add.svg:/icon/list-add.svg + :/icon/list-add.llsvg:/icon/list-add.llsvg false @@ -177,7 +177,7 @@ - :/icon/list-remove.svg:/icon/list-remove.svg + :/icon/list-remove.llsvg:/icon/list-remove.llsvg false diff --git a/src/forms/detaildialog.ui b/src/forms/detaildialog.ui index 7c26f483..19c4843f 100755 --- a/src/forms/detaildialog.ui +++ b/src/forms/detaildialog.ui @@ -56,7 +56,7 @@ - :/icon/paint-none.svg:/icon/paint-none.svg + :/icon/paint-none.llsvg:/icon/paint-none.llsvg diff --git a/src/forms/environmentvariablesdialog.ui b/src/forms/environmentvariablesdialog.ui index e023aaba..199957d7 100755 --- a/src/forms/environmentvariablesdialog.ui +++ b/src/forms/environmentvariablesdialog.ui @@ -83,7 +83,7 @@ - :/icon/list-add.svg:/icon/list-add.svg + :/icon/list-add.llsvg:/icon/list-add.llsvg @@ -102,7 +102,7 @@ - :/icon/document-edit.svg:/icon/document-edit.svg + :/icon/document-edit.llsvg:/icon/document-edit.llsvg @@ -121,7 +121,7 @@ - :/icon/edit-delete.svg:/icon/edit-delete.svg + :/icon/edit-delete.llsvg:/icon/edit-delete.llsvg diff --git a/src/forms/judgingdialog.ui b/src/forms/judgingdialog.ui index 24489131..6bb5f80d 100755 --- a/src/forms/judgingdialog.ui +++ b/src/forms/judgingdialog.ui @@ -115,7 +115,7 @@ - :/icon/media-skip-forward.svg:/icon/media-skip-forward.svg + :/icon/media-skip-forward.llsvg:/icon/media-skip-forward.llsvg false @@ -156,7 +156,7 @@ - :/icon/media-playback-stop.svg:/icon/media-playback-stop.svg + :/icon/media-playback-stop.llsvg:/icon/media-playback-stop.llsvg false diff --git a/src/forms/lemon.ui b/src/forms/lemon.ui index 1c9a8fd0..3878b42d 100755 --- a/src/forms/lemon.ui +++ b/src/forms/lemon.ui @@ -135,7 +135,7 @@ - :/icon/go-down.svg:/icon/go-down.svg + :/icon/go-down.llsvg:/icon/go-down.llsvg @@ -149,7 +149,7 @@ - :/icon/go-up.svg:/icon/go-up.svg + :/icon/go-up.llsvg:/icon/go-up.llsvg @@ -281,7 +281,7 @@ - :/icon/view-sort.svg:/icon/view-sort.svg + :/icon/view-sort.llsvg:/icon/view-sort.llsvg @@ -320,7 +320,7 @@ - :/icon/view-refresh.svg:/icon/view-refresh.svg + :/icon/view-refresh.llsvg:/icon/view-refresh.llsvg @@ -352,7 +352,7 @@ - :/icon/edit-find-replace.svg:/icon/edit-find-replace.svg + :/icon/edit-find-replace.llsvg:/icon/edit-find-replace.llsvg @@ -381,7 +381,7 @@ - :/icon/edit-find.svg:/icon/edit-find.svg + :/icon/edit-find.llsvg:/icon/edit-find.llsvg @@ -410,7 +410,7 @@ - :/icon/code-function.svg:/icon/code-function.svg + :/icon/code-function.llsvg:/icon/code-function.llsvg @@ -520,7 +520,7 @@ - :/icon/system-help.svg:/icon/system-help.svg + :/icon/system-help.llsvg:/icon/system-help.llsvg @@ -539,7 +539,7 @@ - :/icon/document-new.svg:/icon/document-new.svg + :/icon/document-new.llsvg:/icon/document-new.llsvg &New Contest @@ -551,7 +551,7 @@ - :/icon/quickopen-file.svg:/icon/quickopen-file.svg + :/icon/quickopen-file.llsvg:/icon/quickopen-file.llsvg &Open Existing Contest @@ -563,7 +563,7 @@ - :/icon/application-exit.svg:/icon/application-exit.svg + :/icon/application-exit.llsvg:/icon/application-exit.llsvg E&xit @@ -575,7 +575,7 @@ - :/icon/configure.svg:/icon/configure.svg + :/icon/configure.llsvg:/icon/configure.llsvg &Settings @@ -587,7 +587,7 @@ - :/icon/help-about.svg:/icon/help-about.svg + :/icon/help-about.llsvg:/icon/help-about.llsvg &About @@ -602,7 +602,7 @@ - :/icon/edit-find.svg:/icon/edit-find.svg + :/icon/edit-find.llsvg:/icon/edit-find.llsvg &Judge Selected @@ -617,7 +617,7 @@ - :/icon/code-function.svg:/icon/code-function.svg + :/icon/code-function.llsvg:/icon/code-function.llsvg Judge &All @@ -629,7 +629,7 @@ - :/icon/document-close.svg:/icon/document-close.svg + :/icon/document-close.llsvg:/icon/document-close.llsvg &Close Current Contest @@ -649,7 +649,7 @@ - :/icon/layer-new.svg:/icon/layer-new.svg + :/icon/layer-new.llsvg:/icon/layer-new.llsvg Add &Tasks Automatically @@ -672,7 +672,7 @@ - :/icon/document-export.svg:/icon/document-export.svg + :/icon/document-export.llsvg:/icon/document-export.llsvg &Export Result @@ -687,7 +687,7 @@ - :/icon/edit-find-replace.svg:/icon/edit-find-replace.svg + :/icon/edit-find-replace.llsvg:/icon/edit-find-replace.llsvg Judge &Unjudged @@ -699,7 +699,7 @@ - :/icon/go-parent-folder.svg:/icon/go-parent-folder.svg + :/icon/go-parent-folder.llsvg:/icon/go-parent-folder.llsvg Open Current Contest &Folder @@ -711,7 +711,7 @@ - :/icon/document-save.svg:/icon/document-save.svg + :/icon/document-save.llsvg:/icon/document-save.llsvg &Save Current Contest @@ -784,7 +784,7 @@ - :/icon/globe.svg:/icon/globe.svg + :/icon/globe.llsvg:/icon/globe.llsvg More @@ -799,7 +799,7 @@ - :/icon/paint-unknown.svg:/icon/paint-unknown.svg + :/icon/paint-unknown.llsvg:/icon/paint-unknown.llsvg Judge &No Source @@ -814,7 +814,7 @@ - :/icon/deletecell.svg:/icon/deletecell.svg + :/icon/deletecell.llsvg:/icon/deletecell.llsvg Judge &Compile Failed @@ -826,7 +826,7 @@ - :/icon/document-edit.svg:/icon/document-edit.svg + :/icon/document-edit.llsvg:/icon/document-edit.llsvg C&hange Contest Name @@ -841,7 +841,7 @@ - :/icon/view-sort.svg:/icon/view-sort.svg + :/icon/view-sort.llsvg:/icon/view-sort.llsvg Clean Up &Files @@ -856,7 +856,7 @@ - :/icon/view-refresh.svg:/icon/view-refresh.svg + :/icon/view-refresh.llsvg:/icon/view-refresh.llsvg &Refresh @@ -879,7 +879,7 @@ - :/icon/document-export.svg:/icon/document-export.svg + :/icon/document-export.llsvg:/icon/document-export.llsvg Export &Statistics @@ -888,7 +888,7 @@ - :/icon/acrobat.svg:/icon/acrobat.svg + :/icon/acrobat.llsvg:/icon/acrobat.llsvg Manual diff --git a/src/forms/opencontestwidget.ui b/src/forms/opencontestwidget.ui index 29d167e9..d73eb859 100755 --- a/src/forms/opencontestwidget.ui +++ b/src/forms/opencontestwidget.ui @@ -147,7 +147,7 @@ - :/icon/list-add.svg:/icon/list-add.svg + :/icon/list-add.llsvg:/icon/list-add.llsvg Qt::ToolButtonTextBesideIcon @@ -177,7 +177,7 @@ - :/icon/list-remove.svg:/icon/list-remove.svg + :/icon/list-remove.llsvg:/icon/list-remove.llsvg Qt::ToolButtonTextBesideIcon diff --git a/src/forms/taskeditwidget.ui b/src/forms/taskeditwidget.ui index 4676cf37..cc87ad52 100755 --- a/src/forms/taskeditwidget.ui +++ b/src/forms/taskeditwidget.ui @@ -266,7 +266,7 @@ - :/icon/list-add.svg:/icon/list-add.svg + :/icon/list-add.llsvg:/icon/list-add.llsvg @@ -282,7 +282,7 @@ - :/icon/list-remove.svg:/icon/list-remove.svg + :/icon/list-remove.llsvg:/icon/list-remove.llsvg @@ -822,7 +822,7 @@ - :/icon/list-add.svg:/icon/list-add.svg + :/icon/list-add.llsvg:/icon/list-add.llsvg @@ -838,7 +838,7 @@ - :/icon/list-remove.svg:/icon/list-remove.svg + :/icon/list-remove.llsvg:/icon/list-remove.llsvg diff --git a/src/forms/testcaseeditwidget.ui b/src/forms/testcaseeditwidget.ui index c89b96ca..07c3fb30 100755 --- a/src/forms/testcaseeditwidget.ui +++ b/src/forms/testcaseeditwidget.ui @@ -184,7 +184,7 @@ - :/icon/list-add.svg:/icon/list-add.svg + :/icon/list-add.llsvg:/icon/list-add.llsvg @@ -247,7 +247,7 @@ - :/icon/edit-clear.svg:/icon/edit-clear.svg + :/icon/edit-clear.llsvg:/icon/edit-clear.llsvg diff --git a/src/forms/visualmainsettings.ui b/src/forms/visualmainsettings.ui index 1343a281..182ccb13 100644 --- a/src/forms/visualmainsettings.ui +++ b/src/forms/visualmainsettings.ui @@ -54,7 +54,7 @@ - :/icon/list-add.svg:/icon/list-add.svg + :/icon/list-add.llsvg:/icon/list-add.llsvg @@ -78,7 +78,7 @@ - :/icon/edit-delete.svg:/icon/edit-delete.svg + :/icon/edit-delete.llsvg:/icon/edit-delete.llsvg diff --git a/src/plugins/README.md b/src/plugins/README.md new file mode 100644 index 00000000..4f2188ad --- /dev/null +++ b/src/plugins/README.md @@ -0,0 +1,10 @@ + + +# SVG icon plugin + +本插件参考 KIconThemes 的[语义配色](https://github.com/KDE/kiconthemes/blob/f4f5e4e01c22fba7eff981887c68724b456de2c2/src/kiconcolors.cpp)和 [SVG 样式替换](https://github.com/KDE/kiconthemes/blob/f4f5e4e01c22fba7eff981887c68724b456de2c2/src/kiconloader.cpp)设计。 + +上游参考文件采用 `LGPL-2.0-only`。本插件为独立实现,采用 `GPL-3.0-or-later`,未引入 KDE 源码或库依赖。 diff --git a/src/plugins/lemonsvgiconplugin.cpp b/src/plugins/lemonsvgiconplugin.cpp new file mode 100644 index 00000000..133f41d6 --- /dev/null +++ b/src/plugins/lemonsvgiconplugin.cpp @@ -0,0 +1,170 @@ +/* + * SPDX-FileCopyrightText: 2026 Project LemonLime + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Design reference: KDE KIconThemes, KIconColors::stylesheet() and + * KIconLoaderPrivate::processSvg(), revision f4f5e4e01c22fba7eff981887c68724b456de2c2. + * Independently implemented here; no KIconThemes source is incorporated. + * See README.md in this directory for reference URLs and licensing details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + QString iconStyleSheet(const QPalette &palette, QPalette::ColorGroup group, QIcon::Mode mode) { + const QColor text = + palette.color(group, mode == QIcon::Selected ? QPalette::HighlightedText : QPalette::ButtonText); + const bool dark = palette.color(group, QPalette::Button).lightness() < + palette.color(group, QPalette::ButtonText).lightness(); + const QColor negative = mode == QIcon::Disabled || mode == QIcon::Selected ? text + : dark ? QColor(255, 128, 139) + : QColor(218, 68, 83); + // Keep application-specific semantic colors here, separate from SVG geometry. + return QStringLiteral(".ColorScheme-Text { color: %1; }\n" + ".ColorScheme-NegativeText { color: %2; }\n") + .arg(text.name(), negative.name()); + } + + QByteArray applyStyleSheet(const QByteArray &svg, const QString &styleSheet) { + QXmlStreamReader reader(svg); + QByteArray result; + QXmlStreamWriter writer(&result); + bool themed = false; + while (! reader.atEnd()) { + reader.readNext(); + if (reader.hasError()) + return {}; + writer.writeCurrentToken(reader); + if (reader.isStartElement() && reader.name() == QLatin1String("style") && + (reader.namespaceUri().isEmpty() || + reader.namespaceUri() == QLatin1String("http://www.w3.org/2000/svg")) && + reader.attributes().value(QLatin1String("id")) == QLatin1String("current-color-scheme")) { + writer.writeCharacters(styleSheet); + reader.skipCurrentElement(); + writer.writeEndElement(); + themed = true; + } + } + if (reader.hasError() || writer.hasError()) + return {}; + return themed ? result : svg; + } + + class LemonSvgIconEngine : public QIconEngine { + public: + explicit LemonSvgIconEngine(const QString &fileName) { + addFile(fileName, {}, QIcon::Normal, QIcon::Off); + } + QIconEngine *clone() const override { return new LemonSvgIconEngine(*this); } + QString key() const override { return QStringLiteral("llsvg"); } + bool isNull() override { return svg.isEmpty(); } + + void addFile(const QString &name, const QSize &, QIcon::Mode, QIcon::State) override { + fileName = name; + svg.clear(); + sourceId.clear(); + lastStyleSheet.clear(); + themedSvg.clear(); + defaultSize = {}; + if (name.isEmpty()) + return; + QFile file(name); + if (! file.open(QIODevice::ReadOnly)) + return; + const QByteArray data = file.readAll(); + QSvgRenderer renderer(data); + if (! renderer.isValid()) + return; + svg = data; + defaultSize = renderer.defaultSize(); + sourceId = + QString::fromLatin1(QCryptographicHash::hash(data, QCryptographicHash::Sha256).toHex()); + } + + QSize actualSize(const QSize &size, QIcon::Mode, QIcon::State) override { + return defaultSize.isEmpty() ? QSize() : defaultSize.scaled(size, Qt::KeepAspectRatio); + } + + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override { + return scaledPixmap(size, mode, state, 1.0); + } + + QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override { + if (isNull() || size.isEmpty() || scale <= 0) + return {}; + const QPalette palette = QGuiApplication::palette(); + const auto group = mode == QIcon::Disabled ? QPalette::Disabled + : QGuiApplication::applicationState() == Qt::ApplicationActive + ? QPalette::Active + : QPalette::Inactive; + const QString cacheKey = QStringLiteral("lemon-svg:%1:%2:%3:%4:%5x%6:%7:%8") + .arg(sourceId) + .arg(palette.cacheKey()) + .arg(int(group)) + .arg(int(mode)) + .arg(size.width()) + .arg(size.height()) + .arg(scale, 0, 'g', 17) + .arg(int(state)); + QPixmap result; + if (QPixmapCache::find(cacheKey, &result)) + return result; + + const QString styleSheet = iconStyleSheet(palette, group, mode); + if (styleSheet != lastStyleSheet) { + themedSvg = applyStyleSheet(svg, styleSheet); + lastStyleSheet = styleSheet; + } + QSvgRenderer renderer(themedSvg); + if (! renderer.isValid()) + return {}; + renderer.setAspectRatioMode(Qt::KeepAspectRatio); + result = QPixmap(size * scale); + result.setDevicePixelRatio(scale); + result.fill(Qt::transparent); + { + QPainter painter(&result); + renderer.render(&painter, QRectF(QPointF(), QSizeF(size))); + } + QPixmapCache::insert(cacheKey, result); + return result; + } + + void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override { + painter->drawPixmap( + rect, scaledPixmap(rect.size(), mode, state, painter->device()->devicePixelRatioF())); + } + + private: + QString fileName; + QByteArray svg; + QString sourceId; + QSize defaultSize; + QString lastStyleSheet; + QByteArray themedSvg; + }; +} // namespace + +class LemonSvgIconPlugin : public QIconEnginePlugin { + Q_OBJECT + Q_PLUGIN_METADATA(IID QIconEngineFactoryInterface_iid FILE "lemonsvgiconplugin.json") + + public: + QIconEngine *create(const QString &fileName = QString()) override { + return new LemonSvgIconEngine(fileName); + } +}; + +#include "lemonsvgiconplugin.moc" diff --git a/src/plugins/lemonsvgiconplugin.json b/src/plugins/lemonsvgiconplugin.json new file mode 100644 index 00000000..6077c518 --- /dev/null +++ b/src/plugins/lemonsvgiconplugin.json @@ -0,0 +1,3 @@ +{ + "Keys": ["llsvg"] +} diff --git a/src/plugins/lemonsvgiconplugin.json.license b/src/plugins/lemonsvgiconplugin.json.license new file mode 100644 index 00000000..a85428ec --- /dev/null +++ b/src/plugins/lemonsvgiconplugin.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2026 Project LemonLime +SPDX-License-Identifier: GPL-3.0-or-later