Warning, file /plasma/plasma-workspace/kcms/icons/main.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 main.cpp 0003 0004 SPDX-FileCopyrightText: 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org> 0005 SPDX-FileCopyrightText: 2000 Antonio Larrosa <larrosa@kde.org> 0006 SPDX-FileCopyrightText: 2000 Geert Jansen <jansen@kde.org> 0007 SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@privat.broulik.de> 0008 SPDX-FileCopyrightText: 2019 Benjamin Port <benjamin.port@enioka.com> 0009 0010 KDE Frameworks 5 port: 0011 SPDX-FileCopyrightText: 2013 Jonathan Riddell <jr@jriddell.org> 0012 0013 SPDX-License-Identifier: GPL-2.0-or-later 0014 */ 0015 0016 #include "main.h" 0017 #include "../kcms-common_p.h" 0018 0019 #include <QDBusConnection> 0020 #include <QDBusMessage> 0021 #include <QGuiApplication> 0022 #include <QPainter> 0023 #include <QPixmapCache> 0024 #include <QProcess> 0025 #include <QQuickItem> 0026 #include <QQuickWindow> 0027 #include <QStringList> 0028 #include <QSvgRenderer> 0029 0030 #include <KConfigGroup> 0031 #include <KIconLoader> 0032 #include <KIconTheme> 0033 #include <KJobUiDelegate> 0034 #include <KLocalizedString> 0035 #include <KPluginFactory> 0036 #include <KSharedConfig> 0037 #include <KTar> 0038 0039 #include <KIO/DeleteJob> 0040 #include <KIO/FileCopyJob> 0041 0042 #include <algorithm> 0043 #include <unistd.h> // for unlink 0044 0045 #include "iconsdata.h" 0046 #include "iconsizecategorymodel.h" 0047 #include "iconsmodel.h" 0048 #include "iconssettings.h" 0049 0050 #include "config.h" // for CMAKE_INSTALL_FULL_LIBEXECDIR 0051 0052 K_PLUGIN_FACTORY_WITH_JSON(IconsFactory, "kcm_icons.json", registerPlugin<IconModule>(); registerPlugin<IconsData>();) 0053 0054 IconModule::IconModule(QObject *parent, const KPluginMetaData &data, const QVariantList &args) 0055 : KQuickAddons::ManagedConfigModule(parent, data, args) 0056 , m_data(new IconsData(this)) 0057 , m_model(new IconsModel(m_data->settings(), this)) 0058 , m_iconSizeCategoryModel(new IconSizeCategoryModel(this)) 0059 { 0060 auto uri = "org.kde.private.kcms.icons"; 0061 qmlRegisterAnonymousType<IconsSettings>(uri, 1); 0062 qmlRegisterAnonymousType<IconsModel>(uri, 1); 0063 qmlRegisterAnonymousType<IconSizeCategoryModel>(uri, 1); 0064 0065 // to be able to access its enums 0066 qmlRegisterUncreatableType<KIconLoader>(uri, 1, 0, "KIconLoader", QString()); 0067 0068 setButtons(Apply | Default | Help); 0069 0070 connect(m_model, &IconsModel::pendingDeletionsChanged, this, &IconModule::settingsChanged); 0071 0072 // When user has a lot of themes installed, preview pixmaps might get evicted prematurely 0073 QPixmapCache::setCacheLimit(50 * 1024); // 50 MiB 0074 } 0075 0076 IconModule::~IconModule() 0077 { 0078 } 0079 0080 IconsSettings *IconModule::iconsSettings() const 0081 { 0082 return m_data->settings(); 0083 } 0084 0085 IconsModel *IconModule::iconsModel() const 0086 { 0087 return m_model; 0088 } 0089 0090 IconSizeCategoryModel *IconModule::iconSizeCategoryModel() const 0091 { 0092 return m_iconSizeCategoryModel; 0093 } 0094 0095 bool IconModule::downloadingFile() const 0096 { 0097 return m_tempCopyJob; 0098 } 0099 0100 QList<int> IconModule::availableIconSizes(int group) const 0101 { 0102 const auto themeName = iconsSettings()->theme(); 0103 if (!m_kiconThemeCache.contains(iconsSettings()->theme())) { 0104 m_kiconThemeCache.insert(themeName, new KIconTheme(themeName)); 0105 } 0106 return m_kiconThemeCache[themeName]->querySizes(static_cast<KIconLoader::Group>(group)); 0107 } 0108 0109 void IconModule::load() 0110 { 0111 ManagedConfigModule::load(); 0112 m_model->load(); 0113 // Model has been cleared so pretend the theme name changed to force view update 0114 Q_EMIT iconsSettings()->ThemeChanged(); 0115 } 0116 0117 void IconModule::save() 0118 { 0119 // keep track of Group of icons size that has changed 0120 QList<int> notifyList; 0121 for (int i = 0; i < m_iconSizeCategoryModel->rowCount(); ++i) { 0122 const QModelIndex index = m_iconSizeCategoryModel->index(i, 0); 0123 const QString key = index.data(IconSizeCategoryModel::ConfigKeyRole).toString(); 0124 if (iconsSettings()->findItem(key)->isSaveNeeded()) { 0125 notifyList << index.data(IconSizeCategoryModel::KIconLoaderGroupRole).toInt(); 0126 } 0127 } 0128 0129 ManagedConfigModule::save(); 0130 0131 processPendingDeletions(); 0132 0133 // Notify the group(s) where icon sizes have changed 0134 for (auto group : qAsConst(notifyList)) { 0135 KIconLoader::emitChange(KIconLoader::Group(group)); 0136 } 0137 } 0138 0139 bool IconModule::isSaveNeeded() const 0140 { 0141 return !m_model->pendingDeletions().isEmpty(); 0142 } 0143 0144 void IconModule::processPendingDeletions() 0145 { 0146 const QStringList pendingDeletions = m_model->pendingDeletions(); 0147 0148 for (const QString &themeName : pendingDeletions) { 0149 Q_ASSERT(themeName != iconsSettings()->theme()); 0150 0151 KIconTheme theme(themeName); 0152 auto *job = KIO::del(QUrl::fromLocalFile(theme.dir()), KIO::HideProgressInfo); 0153 // needs to block for it to work on "OK" where the dialog (kcmshell) closes 0154 job->exec(); 0155 } 0156 0157 m_model->removeItemsPendingDeletion(); 0158 } 0159 0160 void IconModule::ghnsEntriesChanged() 0161 { 0162 // reload the display icontheme items 0163 KIconTheme::reconfigure(); 0164 KIconLoader::global()->newIconLoader(); 0165 load(); 0166 QPixmapCache::clear(); 0167 } 0168 0169 void IconModule::installThemeFromFile(const QUrl &url) 0170 { 0171 if (url.isLocalFile()) { 0172 installThemeFile(url.toLocalFile()); 0173 return; 0174 } 0175 0176 if (m_tempCopyJob) { 0177 return; 0178 } 0179 0180 m_tempInstallFile.reset(new QTemporaryFile()); 0181 if (!m_tempInstallFile->open()) { 0182 Q_EMIT showErrorMessage(i18n("Unable to create a temporary file.")); 0183 m_tempInstallFile.reset(); 0184 return; 0185 } 0186 0187 m_tempCopyJob = KIO::file_copy(url, QUrl::fromLocalFile(m_tempInstallFile->fileName()), -1, KIO::Overwrite); 0188 m_tempCopyJob->uiDelegate()->setAutoErrorHandlingEnabled(true); 0189 Q_EMIT downloadingFileChanged(); 0190 0191 connect(m_tempCopyJob, &KIO::FileCopyJob::result, this, [this, url](KJob *job) { 0192 if (job->error() != KJob::NoError) { 0193 Q_EMIT showErrorMessage(i18n("Unable to download the icon theme archive: %1", job->errorText())); 0194 return; 0195 } 0196 0197 installThemeFile(m_tempInstallFile->fileName()); 0198 m_tempInstallFile.reset(); 0199 }); 0200 connect(m_tempCopyJob, &QObject::destroyed, this, &IconModule::downloadingFileChanged); 0201 } 0202 0203 void IconModule::installThemeFile(const QString &path) 0204 { 0205 const QStringList themesNames = findThemeDirs(path); 0206 if (themesNames.isEmpty()) { 0207 Q_EMIT showErrorMessage(i18n("The file is not a valid icon theme archive.")); 0208 return; 0209 } 0210 0211 if (!installThemes(themesNames, path)) { 0212 Q_EMIT showErrorMessage(i18n("A problem occurred during the installation process; however, most of the themes in the archive have been installed")); 0213 return; 0214 } 0215 0216 Q_EMIT showSuccessMessage(i18n("Theme installed successfully.")); 0217 0218 KIconLoader::global()->newIconLoader(); 0219 m_model->load(); 0220 } 0221 0222 QStringList IconModule::findThemeDirs(const QString &archiveName) 0223 { 0224 QStringList foundThemes; 0225 0226 KTar archive(archiveName); 0227 archive.open(QIODevice::ReadOnly); 0228 const KArchiveDirectory *themeDir = archive.directory(); 0229 0230 KArchiveEntry *possibleDir = nullptr; 0231 KArchiveDirectory *subDir = nullptr; 0232 0233 // iterate all the dirs looking for an index.theme or index.desktop file 0234 const QStringList entries = themeDir->entries(); 0235 for (const QString &entry : entries) { 0236 possibleDir = const_cast<KArchiveEntry *>(themeDir->entry(entry)); 0237 if (!possibleDir->isDirectory()) { 0238 continue; 0239 } 0240 0241 subDir = dynamic_cast<KArchiveDirectory *>(possibleDir); 0242 if (!subDir) { 0243 continue; 0244 } 0245 0246 if (subDir->entry(QStringLiteral("index.theme")) || subDir->entry(QStringLiteral("index.desktop"))) { 0247 foundThemes.append(subDir->name()); 0248 } 0249 } 0250 0251 archive.close(); 0252 return foundThemes; 0253 } 0254 0255 bool IconModule::installThemes(const QStringList &themes, const QString &archiveName) 0256 { 0257 bool everythingOk = true; 0258 const QString localThemesDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/icons/./")); 0259 0260 Q_EMIT showProgress(i18n("Installing icon themes…")); 0261 0262 KTar archive(archiveName); 0263 archive.open(QIODevice::ReadOnly); 0264 qApp->processEvents(QEventLoop::ExcludeUserInputEvents); 0265 0266 const KArchiveDirectory *rootDir = archive.directory(); 0267 0268 KArchiveDirectory *currentTheme = nullptr; 0269 for (const QString &theme : themes) { 0270 Q_EMIT showProgress(i18n("Installing %1 theme…", theme)); 0271 0272 qApp->processEvents(QEventLoop::ExcludeUserInputEvents); 0273 0274 currentTheme = dynamic_cast<KArchiveDirectory *>(const_cast<KArchiveEntry *>(rootDir->entry(theme))); 0275 if (!currentTheme) { 0276 // we tell back that something went wrong, but try to install as much 0277 // as possible 0278 everythingOk = false; 0279 continue; 0280 } 0281 0282 currentTheme->copyTo(localThemesDir + theme); 0283 } 0284 0285 archive.close(); 0286 0287 Q_EMIT hideProgress(); 0288 return everythingOk; 0289 } 0290 0291 QVariantList IconModule::previewIcons(const QString &themeName, int size, qreal dpr, int limit) 0292 { 0293 static QVector<QStringList> s_previewIcons{ 0294 {QStringLiteral("system-run"), QStringLiteral("exec")}, 0295 {QStringLiteral("folder")}, 0296 {QStringLiteral("document"), QStringLiteral("text-x-generic")}, 0297 {QStringLiteral("user-trash"), QStringLiteral("user-trash-empty")}, 0298 {QStringLiteral("help-browser"), QStringLiteral("system-help"), QStringLiteral("help-about"), QStringLiteral("help-contents")}, 0299 {QStringLiteral("preferences-system"), QStringLiteral("systemsettings"), QStringLiteral("configure")}, 0300 0301 {QStringLiteral("text-html")}, 0302 {QStringLiteral("image-x-generic"), QStringLiteral("image-png"), QStringLiteral("image-jpeg")}, 0303 {QStringLiteral("video-x-generic"), QStringLiteral("video-x-theora+ogg"), QStringLiteral("video-mp4")}, 0304 {QStringLiteral("x-office-document")}, 0305 {QStringLiteral("x-office-spreadsheet")}, 0306 {QStringLiteral("x-office-presentation"), QStringLiteral("application-presentation")}, 0307 0308 {QStringLiteral("user-home")}, 0309 {QStringLiteral("user-desktop"), QStringLiteral("desktop")}, 0310 {QStringLiteral("folder-image"), QStringLiteral("folder-images"), QStringLiteral("folder-pictures"), QStringLiteral("folder-picture")}, 0311 {QStringLiteral("folder-documents")}, 0312 {QStringLiteral("folder-download"), QStringLiteral("folder-downloads")}, 0313 {QStringLiteral("folder-video"), QStringLiteral("folder-videos")}}; 0314 0315 // created on-demand as it is quite expensive to do and we don't want to do it every loop iteration either 0316 std::unique_ptr<KIconTheme> theme; 0317 0318 QVariantList pixmaps; 0319 0320 for (const QStringList &iconNames : s_previewIcons) { 0321 const QString cacheKey = themeName + QLatin1Char('@') + QString::number(size) + QLatin1Char('@') + QString::number(dpr, 'f', 1) + QLatin1Char('@') 0322 + iconNames.join(QLatin1Char(',')); 0323 0324 QPixmap pix; 0325 if (!QPixmapCache::find(cacheKey, &pix)) { 0326 if (!theme) { 0327 theme.reset(new KIconTheme(themeName)); 0328 } 0329 0330 pix = getBestIcon(*theme.get(), iconNames, size, dpr); 0331 0332 // Inserting a pixmap even if null so we know whether we searched for it already 0333 QPixmapCache::insert(cacheKey, pix); 0334 } 0335 0336 if (pix.isNull()) { 0337 continue; 0338 } 0339 0340 pixmaps.append(pix); 0341 0342 if (limit > -1 && pixmaps.count() >= limit) { 0343 break; 0344 } 0345 } 0346 0347 return pixmaps; 0348 } 0349 0350 QPixmap IconModule::getBestIcon(KIconTheme &theme, const QStringList &iconNames, int size, qreal dpr) 0351 { 0352 QSvgRenderer renderer; 0353 0354 const int iconSize = size * dpr; 0355 0356 // not using initializer list as we want to unwrap inherits() 0357 const QStringList themes = QStringList() << theme.internalName() << theme.inherits(); 0358 for (const QString &themeName : themes) { 0359 KIconTheme theme(themeName); 0360 0361 for (const QString &iconName : iconNames) { 0362 const QString pixmapPath = theme.iconPath(QStringLiteral("%1.png").arg(iconName), iconSize, KIconLoader::MatchBest); 0363 QPixmap pixmap(pixmapPath); 0364 if (!pixmap.isNull()) { 0365 pixmap.setDevicePixelRatio(dpr); 0366 if (pixmap.width() >= iconSize && pixmap.height() >= iconSize) { 0367 return pixmap; 0368 } 0369 } 0370 0371 // could not find the .png, try loading the .svg or .svgz 0372 QString scalablePath = theme.iconPath(QStringLiteral("%1.svg").arg(iconName), iconSize, KIconLoader::MatchBest); 0373 if (scalablePath.isEmpty()) { 0374 scalablePath = theme.iconPath(QStringLiteral("%1.svgz").arg(iconName), iconSize, KIconLoader::MatchBest); 0375 } 0376 0377 if (scalablePath.isEmpty()) { 0378 if (!pixmap.isNull()) { 0379 return pixmap; 0380 } 0381 0382 continue; 0383 } 0384 0385 if (!renderer.load(scalablePath)) { 0386 continue; 0387 } 0388 0389 QPixmap svgPixmap(iconSize, iconSize); 0390 svgPixmap.setDevicePixelRatio(dpr); 0391 svgPixmap.fill(QColor(Qt::transparent)); 0392 QPainter p(&svgPixmap); 0393 p.setViewport(0, 0, size, size); 0394 renderer.render(&p); 0395 return svgPixmap; 0396 } 0397 } 0398 0399 return QPixmap(); 0400 } 0401 0402 int IconModule::pluginIndex(const QString &themeName) const 0403 { 0404 const auto results = m_model->match(m_model->index(0, 0), ThemeNameRole, themeName, 1, Qt::MatchExactly); 0405 if (results.count() == 1) { 0406 return results.first().row(); 0407 } 0408 return -1; 0409 } 0410 0411 void IconModule::defaults() 0412 { 0413 for (int i = 0, count = m_model->rowCount(QModelIndex()); i < count; ++i) { 0414 m_model->setData(m_model->index(i), false, IconsModel::Roles::PendingDeletionRole); 0415 } 0416 ManagedConfigModule::defaults(); 0417 } 0418 0419 #include "main.moc"