diff --git a/kcms/icons/main.h b/kcms/icons/main.h --- a/kcms/icons/main.h +++ b/kcms/icons/main.h @@ -72,7 +72,8 @@ Q_INVOKABLE void setIconSize(int group, int size); Q_INVOKABLE QList availableIconSizes(int group) const; - Q_INVOKABLE QVariantList/*QList*/ previewIcons(const QString &themeName, int size, qreal dpr) const; + // QML doesn't understand QList, hence wrapped in a QVariantList + Q_INVOKABLE QVariantList previewIcons(const QString &themeName, int size, qreal dpr, int limit = -1); signals: void iconSizesChanged(); @@ -94,6 +95,8 @@ void exportToKDE4(); + static QPixmap getBestIcon(KIconTheme &theme, const QStringList &iconNames, int size, qreal dpr); + IconsModel *m_model; // so we avoid launching changeicon process when theme didn't change (but only e.g. pending deletions) bool m_selectedThemeDirty = false; diff --git a/kcms/icons/main.cpp b/kcms/icons/main.cpp --- a/kcms/icons/main.cpp +++ b/kcms/icons/main.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -100,6 +101,9 @@ connect(m_model, &IconsModel::pendingDeletionsChanged, this, [this] { setNeedsSave(true); }); + + // When user has a lot of themes installed, preview pixmaps might get evicted prematurely + QPixmapCache::setCacheLimit(50 * 1024); // 50 MiB } IconModule::~IconModule() @@ -284,6 +288,7 @@ // reload the display icontheme items KIconLoader::global()->newIconLoader(); m_model->load(); + QPixmapCache::clear(); }); } @@ -477,83 +482,110 @@ return everythingOk; } -QVariantList IconModule::previewIcons(const QString &themeName, int size, qreal dpr) const +QVariantList IconModule::previewIcons(const QString &themeName, int size, qreal dpr, int limit) { - KIconTheme theme(themeName); - QSvgRenderer renderer; + static QVector s_previewIcons{ + {QStringLiteral("system-run"), QStringLiteral("exec")}, + {QStringLiteral("folder")}, + {QStringLiteral("document"), QStringLiteral("text-x-generic")}, + {QStringLiteral("user-trash"), QStringLiteral("user-trash-empty")}, + {QStringLiteral("system-help"), QStringLiteral("help-about"), QStringLiteral("help-contents")}, + {QStringLiteral("preferences-system"), QStringLiteral("systemsettings"), QStringLiteral("configure")}, + + {QStringLiteral("text-html")}, + {QStringLiteral("image-x-generic"), QStringLiteral("image-png"), QStringLiteral("image-jpeg")}, + {QStringLiteral("video-x-generic"), QStringLiteral("video-x-theora+ogg"), QStringLiteral("video-mp4")}, + {QStringLiteral("x-office-document")}, + {QStringLiteral("x-office-spreadsheet")}, + {QStringLiteral("x-office-presentation"), QStringLiteral("application-presentation")}, + + {QStringLiteral("user-home")}, + {QStringLiteral("user-desktop"), QStringLiteral("desktop")}, + {QStringLiteral("folder-image"), QStringLiteral("folder-images"), QStringLiteral("folder-pictures"), QStringLiteral("folder-picture")}, + {QStringLiteral("folder-documents")}, + {QStringLiteral("folder-download"), QStringLiteral("folder-downloads")}, + {QStringLiteral("folder-video"), QStringLiteral("folder-videos")} + }; - auto getBestIcon = [&](const QStringList &iconNames) { - const int iconSize = size * dpr; + // created on-demand as it is quite expensive to do and we don't want to do it every loop iteration either + QScopedPointer theme; - // not using initializer list as we want to unwrap inherits() - const QStringList themes = QStringList() << theme.internalName() << theme.inherits(); - for (const QString &themeName : themes) { - KIconTheme theme(themeName); + QVariantList pixmaps; - for (const QString &iconName : iconNames) { - QString path = theme.iconPath(QStringLiteral("%1.png").arg(iconName), iconSize, KIconLoader::MatchBest); - if (!path.isEmpty()) { - QPixmap pixmap(path); - pixmap.setDevicePixelRatio(dpr); - return pixmap; - } + for (const QStringList &iconNames : s_previewIcons) { + const QString cacheKey = themeName + QLatin1Char('@') + QString::number(size) + QLatin1Char('@') + + QString::number(dpr,'f',1) + QLatin1Char('@') + iconNames.join(QLatin1Char(',')); - //could not find the .png, try loading the .svg or .svgz - path = theme.iconPath(QStringLiteral("%1.svg").arg(iconName), iconSize, KIconLoader::MatchBest); - if (path.isEmpty()) { - path = theme.iconPath(QStringLiteral("%1.svgz").arg(iconName), iconSize, KIconLoader::MatchBest); - } + QPixmap pix; + if (!QPixmapCache::find(cacheKey, pix)) { + if (!theme) { + theme.reset(new KIconTheme(themeName)); + } - if (path.isEmpty()) { - continue; - } + pix = getBestIcon(*theme.data(), iconNames, size, dpr); - if (!renderer.load(path)) { - continue; - } + // Inserting a pixmap even if null so we know whether we searched for it already + QPixmapCache::insert(cacheKey, pix); + } - QPixmap pixmap(iconSize, iconSize); + if (pix.isNull()) { + continue; + } + + pixmaps.append(pix); + + if (limit > -1 && pixmaps.count() >= limit) { + break; + } + } + + return pixmaps; +} + +QPixmap IconModule::getBestIcon(KIconTheme &theme, const QStringList &iconNames, int size, qreal dpr) +{ + QSvgRenderer renderer; + + const int iconSize = size * dpr; + + // not using initializer list as we want to unwrap inherits() + const QStringList themes = QStringList() << theme.internalName() << theme.inherits(); + for (const QString &themeName : themes) { + KIconTheme theme(themeName); + + for (const QString &iconName : iconNames) { + QString path = theme.iconPath(QStringLiteral("%1.png").arg(iconName), iconSize, KIconLoader::MatchBest); + if (!path.isEmpty()) { + QPixmap pixmap(path); pixmap.setDevicePixelRatio(dpr); - pixmap.fill(QColor(Qt::transparent)); - QPainter p(&pixmap); - p.setViewport(0, 0, size, size); - renderer.render(&p); return pixmap; } - } - return QPixmap(); - }; + //could not find the .png, try loading the .svg or .svgz + path = theme.iconPath(QStringLiteral("%1.svg").arg(iconName), iconSize, KIconLoader::MatchBest); + if (path.isEmpty()) { + path = theme.iconPath(QStringLiteral("%1.svgz").arg(iconName), iconSize, KIconLoader::MatchBest); + } - QVariantList pixmaps{ - getBestIcon({QStringLiteral("system-run"), QStringLiteral("exec")}), - getBestIcon({QStringLiteral("folder")}), - getBestIcon({QStringLiteral("document"), QStringLiteral("text-x-generic")}), - getBestIcon({QStringLiteral("user-trash"), QStringLiteral("user-trash-empty")}), - getBestIcon({QStringLiteral("system-help"), QStringLiteral("help-about"), QStringLiteral("help-contents")}), - getBestIcon({QStringLiteral("preferences-system"), QStringLiteral("systemsettings"), QStringLiteral("configure")}), - - getBestIcon({QStringLiteral("text-html")}), - getBestIcon({QStringLiteral("image-x-generic"), QStringLiteral("image-png"), QStringLiteral("image-jpeg")}), - getBestIcon({QStringLiteral("video-x-generic"), QStringLiteral("video-x-theora+ogg"), QStringLiteral("video-mp4")}), - getBestIcon({QStringLiteral("x-office-document")}), - getBestIcon({QStringLiteral("x-office-spreadsheet")}), - getBestIcon({QStringLiteral("x-office-presentation"), QStringLiteral("application-presentation")}), - - getBestIcon({QStringLiteral("user-home")}), - getBestIcon({QStringLiteral("user-desktop"), QStringLiteral("desktop")}), - getBestIcon({QStringLiteral("folder-image"), QStringLiteral("folder-images"), QStringLiteral("folder-pictures"), QStringLiteral("folder-picture")}), - getBestIcon({QStringLiteral("folder-documents")}), - getBestIcon({QStringLiteral("folder-download"), QStringLiteral("folder-downloads")}), - getBestIcon({QStringLiteral("folder-video"), QStringLiteral("folder-videos")}) - }; + if (path.isEmpty()) { + continue; + } + + if (!renderer.load(path)) { + continue; + } - // remove missing icons - pixmaps.erase(std::remove_if(pixmaps.begin(), pixmaps.end(), [](const QVariant &pixmapVariant) { - return pixmapVariant.value().isNull(); - }), pixmaps.end()); + QPixmap pixmap(iconSize, iconSize); + pixmap.setDevicePixelRatio(dpr); + pixmap.fill(QColor(Qt::transparent)); + QPainter p(&pixmap); + p.setViewport(0, 0, size, size); + renderer.render(&p); + return pixmap; + } + } - return pixmaps; + return QPixmap(); } #include "main.moc" diff --git a/kcms/icons/package/contents/ui/main.qml b/kcms/icons/package/contents/ui/main.qml --- a/kcms/icons/package/contents/ui/main.qml +++ b/kcms/icons/package/contents/ui/main.qml @@ -89,6 +89,11 @@ } } onTriggered: { + if (!thumbFlow.allPreviesLoaded) { + thumbFlow.loadPreviews(-1 /*no limit*/); + thumbFlow.allPreviesLoaded = true; + } + ++thumbFlow.currentPage; if (thumbFlow.currentPage >= thumbFlow.pageCount) { stop(); @@ -102,6 +107,8 @@ // undefined is "didn't load preview yet" // empty array is "no preview available" property var previews + // initially we only load 6 and when the animation starts we'll load the rest + property bool allPreviesLoaded: false property int currentPage readonly property int pageCount: Math.ceil(thumbRepeater.count / (thumbFlow.columns * thumbFlow.rows)) @@ -112,6 +119,10 @@ readonly property int columns: 3 readonly property int rows: 2 + function loadPreviews(limit) { + previews = kcm.previewIcons(model.themeName, Math.min(thumbFlow.iconWidth, thumbFlow.iconHeight), Screen.devicePixelRatio, limit); + } + width: parent.width y: -currentPage * iconHeight * rows @@ -142,7 +153,8 @@ Component.onCompleted: { // avoid reloading it when icon sizes or dpr changes on startup Qt.callLater(function() { - previews = kcm.previewIcons(model.themeName, Math.min(thumbFlow.iconWidth, thumbFlow.iconHeight), Screen.devicePixelRatio) + // We show 6 icons initially (3x2 grid), only load those + thumbFlow.loadPreviews(6 /*limit*/); }); } }