File indexing completed on 2024-05-19 05:38:07
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 "iconsmodel.h" 0047 #include "iconssettings.h" 0048 0049 #include "config.h" // for CMAKE_INSTALL_FULL_LIBEXECDIR 0050 0051 K_PLUGIN_FACTORY_WITH_JSON(IconsFactory, "kcm_icons.json", registerPlugin<IconModule>(); registerPlugin<IconsData>();) 0052 0053 IconModule::IconModule(QObject *parent, const KPluginMetaData &data) 0054 : KQuickManagedConfigModule(parent, data) 0055 , m_data(new IconsData(this)) 0056 , m_model(new IconsModel(m_data->settings(), this)) 0057 { 0058 auto uri = "org.kde.private.kcms.icons"; 0059 qmlRegisterAnonymousType<IconsSettings>(uri, 1); 0060 qmlRegisterAnonymousType<IconsModel>(uri, 1); 0061 0062 // to be able to access its enums 0063 qmlRegisterUncreatableType<KIconLoader>(uri, 1, 0, "KIconLoader", QString()); 0064 0065 setButtons(Apply | Default | Help); 0066 0067 connect(m_model, &IconsModel::pendingDeletionsChanged, this, &IconModule::settingsChanged); 0068 0069 // When user has a lot of themes installed, preview pixmaps might get evicted prematurely 0070 QPixmapCache::setCacheLimit(50 * 1024); // 50 MiB 0071 } 0072 0073 IconModule::~IconModule() 0074 { 0075 } 0076 0077 IconsSettings *IconModule::iconsSettings() const 0078 { 0079 return m_data->settings(); 0080 } 0081 0082 IconsModel *IconModule::iconsModel() const 0083 { 0084 return m_model; 0085 } 0086 0087 bool IconModule::downloadingFile() const 0088 { 0089 return m_tempCopyJob; 0090 } 0091 0092 void IconModule::load() 0093 { 0094 KQuickManagedConfigModule::load(); 0095 m_model->load(); 0096 // Model has been cleared so pretend the theme name changed to force view update 0097 Q_EMIT iconsSettings()->ThemeChanged(); 0098 } 0099 0100 void IconModule::save() 0101 { 0102 KQuickManagedConfigModule::save(); 0103 0104 processPendingDeletions(); 0105 } 0106 0107 bool IconModule::isSaveNeeded() const 0108 { 0109 return !m_model->pendingDeletions().isEmpty(); 0110 } 0111 0112 void IconModule::processPendingDeletions() 0113 { 0114 const QStringList pendingDeletions = m_model->pendingDeletions(); 0115 0116 for (const QString &themeName : pendingDeletions) { 0117 Q_ASSERT(themeName != iconsSettings()->theme()); 0118 0119 KIconTheme theme(themeName); 0120 auto *job = KIO::del(QUrl::fromLocalFile(theme.dir()), KIO::HideProgressInfo); 0121 // needs to block for it to work on "OK" where the dialog (kcmshell) closes 0122 job->exec(); 0123 } 0124 0125 m_model->removeItemsPendingDeletion(); 0126 } 0127 0128 void IconModule::ghnsEntriesChanged() 0129 { 0130 // reload the display icontheme items 0131 KIconTheme::reconfigure(); 0132 KIconLoader::global()->newIconLoader(); 0133 load(); 0134 QPixmapCache::clear(); 0135 } 0136 0137 void IconModule::installThemeFromFile(const QUrl &url) 0138 { 0139 if (url.isLocalFile()) { 0140 installThemeFile(url.toLocalFile()); 0141 return; 0142 } 0143 0144 if (m_tempCopyJob) { 0145 return; 0146 } 0147 0148 m_tempInstallFile.reset(new QTemporaryFile()); 0149 if (!m_tempInstallFile->open()) { 0150 Q_EMIT showErrorMessage(i18n("Unable to create a temporary file.")); 0151 m_tempInstallFile.reset(); 0152 return; 0153 } 0154 0155 m_tempCopyJob = KIO::file_copy(url, QUrl::fromLocalFile(m_tempInstallFile->fileName()), -1, KIO::Overwrite); 0156 m_tempCopyJob->uiDelegate()->setAutoErrorHandlingEnabled(true); 0157 Q_EMIT downloadingFileChanged(); 0158 0159 connect(m_tempCopyJob, &KIO::FileCopyJob::result, this, [this, url](KJob *job) { 0160 if (job->error() != KJob::NoError) { 0161 Q_EMIT showErrorMessage(i18n("Unable to download the icon theme archive: %1", job->errorText())); 0162 return; 0163 } 0164 0165 installThemeFile(m_tempInstallFile->fileName()); 0166 m_tempInstallFile.reset(); 0167 }); 0168 connect(m_tempCopyJob, &QObject::destroyed, this, &IconModule::downloadingFileChanged); 0169 } 0170 0171 void IconModule::installThemeFile(const QString &path) 0172 { 0173 const QStringList themesNames = findThemeDirs(path); 0174 if (themesNames.isEmpty()) { 0175 Q_EMIT showErrorMessage(i18n("The file is not a valid icon theme archive.")); 0176 return; 0177 } 0178 0179 if (!installThemes(themesNames, path)) { 0180 Q_EMIT showErrorMessage(i18n("A problem occurred during the installation process; however, most of the themes in the archive have been installed")); 0181 return; 0182 } 0183 0184 Q_EMIT showSuccessMessage(i18n("Theme installed successfully.")); 0185 0186 KIconLoader::global()->newIconLoader(); 0187 m_model->load(); 0188 } 0189 0190 QStringList IconModule::findThemeDirs(const QString &archiveName) 0191 { 0192 QStringList foundThemes; 0193 0194 KTar archive(archiveName); 0195 archive.open(QIODevice::ReadOnly); 0196 const KArchiveDirectory *themeDir = archive.directory(); 0197 0198 KArchiveEntry *possibleDir = nullptr; 0199 KArchiveDirectory *subDir = nullptr; 0200 0201 // iterate all the dirs looking for an index.theme or index.desktop file 0202 const QStringList entries = themeDir->entries(); 0203 for (const QString &entry : entries) { 0204 possibleDir = const_cast<KArchiveEntry *>(themeDir->entry(entry)); 0205 if (!possibleDir->isDirectory()) { 0206 continue; 0207 } 0208 0209 subDir = dynamic_cast<KArchiveDirectory *>(possibleDir); 0210 if (!subDir) { 0211 continue; 0212 } 0213 0214 if (subDir->entry(QStringLiteral("index.theme")) || subDir->entry(QStringLiteral("index.desktop"))) { 0215 foundThemes.append(subDir->name()); 0216 } 0217 } 0218 0219 archive.close(); 0220 return foundThemes; 0221 } 0222 0223 bool IconModule::installThemes(const QStringList &themes, const QString &archiveName) 0224 { 0225 bool everythingOk = true; 0226 const QString localThemesDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/icons/./")); 0227 0228 Q_EMIT showProgress(i18n("Installing icon themes…")); 0229 0230 KTar archive(archiveName); 0231 archive.open(QIODevice::ReadOnly); 0232 qApp->processEvents(QEventLoop::ExcludeUserInputEvents); 0233 0234 const KArchiveDirectory *rootDir = archive.directory(); 0235 0236 KArchiveDirectory *currentTheme = nullptr; 0237 for (const QString &theme : themes) { 0238 Q_EMIT showProgress(i18n("Installing %1 theme…", theme)); 0239 0240 qApp->processEvents(QEventLoop::ExcludeUserInputEvents); 0241 0242 currentTheme = dynamic_cast<KArchiveDirectory *>(const_cast<KArchiveEntry *>(rootDir->entry(theme))); 0243 if (!currentTheme) { 0244 // we tell back that something went wrong, but try to install as much 0245 // as possible 0246 everythingOk = false; 0247 continue; 0248 } 0249 0250 currentTheme->copyTo(localThemesDir + theme); 0251 } 0252 0253 archive.close(); 0254 0255 Q_EMIT hideProgress(); 0256 return everythingOk; 0257 } 0258 0259 QVariantList IconModule::previewIcons(const QString &themeName, int size, qreal dpr, int limit) 0260 { 0261 static QList<QStringList> s_previewIcons{ 0262 {QStringLiteral("system-run"), QStringLiteral("exec")}, 0263 {QStringLiteral("folder")}, 0264 {QStringLiteral("document"), QStringLiteral("text-x-generic")}, 0265 {QStringLiteral("user-trash"), QStringLiteral("user-trash-empty")}, 0266 {QStringLiteral("help-browser"), QStringLiteral("system-help"), QStringLiteral("help-about"), QStringLiteral("help-contents")}, 0267 {QStringLiteral("preferences-system"), QStringLiteral("systemsettings"), QStringLiteral("configure")}, 0268 0269 {QStringLiteral("text-html")}, 0270 {QStringLiteral("image-x-generic"), QStringLiteral("image-png"), QStringLiteral("image-jpeg")}, 0271 {QStringLiteral("video-x-generic"), QStringLiteral("video-x-theora+ogg"), QStringLiteral("video-mp4")}, 0272 {QStringLiteral("x-office-document")}, 0273 {QStringLiteral("x-office-spreadsheet")}, 0274 {QStringLiteral("x-office-presentation"), QStringLiteral("application-presentation")}, 0275 0276 {QStringLiteral("user-home")}, 0277 {QStringLiteral("user-desktop"), QStringLiteral("desktop")}, 0278 {QStringLiteral("folder-image"), QStringLiteral("folder-images"), QStringLiteral("folder-pictures"), QStringLiteral("folder-picture")}, 0279 {QStringLiteral("folder-documents")}, 0280 {QStringLiteral("folder-download"), QStringLiteral("folder-downloads")}, 0281 {QStringLiteral("folder-video"), QStringLiteral("folder-videos")}}; 0282 0283 // created on-demand as it is quite expensive to do and we don't want to do it every loop iteration either 0284 std::unique_ptr<KIconTheme> theme; 0285 0286 QVariantList pixmaps; 0287 0288 for (const QStringList &iconNames : s_previewIcons) { 0289 const QString cacheKey = themeName + QLatin1Char('@') + QString::number(size) + QLatin1Char('@') + QString::number(dpr, 'f', 1) + QLatin1Char('@') 0290 + iconNames.join(QLatin1Char(',')); 0291 0292 QPixmap pix; 0293 if (!QPixmapCache::find(cacheKey, &pix)) { 0294 if (!theme) { 0295 theme.reset(new KIconTheme(themeName)); 0296 } 0297 0298 pix = getBestIcon(*theme.get(), iconNames, size, dpr); 0299 0300 // Inserting a pixmap even if null so we know whether we searched for it already 0301 QPixmapCache::insert(cacheKey, pix); 0302 } 0303 0304 if (pix.isNull()) { 0305 continue; 0306 } 0307 0308 pixmaps.append(pix); 0309 0310 if (limit > -1 && pixmaps.count() >= limit) { 0311 break; 0312 } 0313 } 0314 0315 return pixmaps; 0316 } 0317 0318 QPixmap IconModule::getBestIcon(KIconTheme &theme, const QStringList &iconNames, int size, qreal dpr) 0319 { 0320 QSvgRenderer renderer; 0321 0322 const int iconSize = size * dpr; 0323 0324 // not using initializer list as we want to unwrap inherits() 0325 const QStringList themes = QStringList() << theme.internalName() << theme.inherits(); 0326 for (const QString &themeName : themes) { 0327 KIconTheme theme(themeName); 0328 0329 for (const QString &iconName : iconNames) { 0330 const QString pixmapPath = theme.iconPath(QStringLiteral("%1.png").arg(iconName), iconSize, KIconLoader::MatchBest); 0331 QPixmap pixmap(pixmapPath); 0332 if (!pixmap.isNull()) { 0333 pixmap.setDevicePixelRatio(dpr); 0334 if (pixmap.width() >= iconSize && pixmap.height() >= iconSize) { 0335 return pixmap; 0336 } 0337 } 0338 0339 // could not find the .png, try loading the .svg or .svgz 0340 QString scalablePath = theme.iconPath(QStringLiteral("%1.svg").arg(iconName), iconSize, KIconLoader::MatchBest); 0341 if (scalablePath.isEmpty()) { 0342 scalablePath = theme.iconPath(QStringLiteral("%1.svgz").arg(iconName), iconSize, KIconLoader::MatchBest); 0343 } 0344 0345 if (scalablePath.isEmpty()) { 0346 if (!pixmap.isNull()) { 0347 return pixmap; 0348 } 0349 0350 continue; 0351 } 0352 0353 if (!renderer.load(scalablePath)) { 0354 continue; 0355 } 0356 0357 QPixmap svgPixmap(iconSize, iconSize); 0358 svgPixmap.setDevicePixelRatio(dpr); 0359 svgPixmap.fill(QColor(Qt::transparent)); 0360 QPainter p(&svgPixmap); 0361 p.setViewport(0, 0, size, size); 0362 renderer.render(&p); 0363 return svgPixmap; 0364 } 0365 } 0366 0367 return QPixmap(); 0368 } 0369 0370 int IconModule::pluginIndex(const QString &themeName) const 0371 { 0372 const auto results = m_model->match(m_model->index(0, 0), ThemeNameRole, themeName, 1, Qt::MatchExactly); 0373 if (results.count() == 1) { 0374 return results.first().row(); 0375 } 0376 return -1; 0377 } 0378 0379 void IconModule::defaults() 0380 { 0381 for (int i = 0, count = m_model->rowCount(QModelIndex()); i < count; ++i) { 0382 m_model->setData(m_model->index(i), false, IconsModel::Roles::PendingDeletionRole); 0383 } 0384 KQuickManagedConfigModule::defaults(); 0385 } 0386 0387 #include "main.moc"