* main.cpp
*
* Copyright (c) 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
* Copyright (c) 2000 Antonio Larrosa <larrosa@kde.org>
* Copyright (C) 2000 Geert Jansen <jansen@kde.org>
* KDE Frameworks 5 port Copyright (C) 2013 Jonathan Riddell <jr@jriddell.org>
* Copyright (C) 2018 Kai Uwe Broulik <kde@privat.broulik.de>
*
* Requires the Qt widget libraries, available at no cost at
* http://www.troll.no/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "main.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <QGuiApplication>
#include <QPainter>
#include <QPixmapCache>
#include <QProcess>
#include <QQuickItem>
#include <QQuickWindow>
#include <QStringList>
#include <QSvgRenderer>
#include <KAboutData>
#include <KConfigGroup>
#include <Kdelibs4Migration>
#include <KIconLoader>
#include <KIconTheme>
#include <KJobUiDelegate>
#include <KLocalizedString>
#include <KSharedConfig>
#include <KPluginFactory>
#include <KTar>
#include <KIO/DeleteJob>
#include <KIO/FileCopyJob>
#include <algorithm>
#include <unistd.h>
#include "iconsmodel.h"
#include "config.h"
static const QVector<int> s_defaultIconSizes = { 32, 22, 22, 16, 48, 32 };
static const QString s_defaultThemeName = QStringLiteral("breeze");
K_PLUGIN_FACTORY_WITH_JSON(IconsFactory, "kcm_icons.json", registerPlugin<IconModule>();)
IconModule::IconModule(QObject *parent, const QVariantList &args)
: KQuickAddons::ConfigModule(parent, args)
, m_model(new IconsModel(this))
, m_iconGroups{
QStringLiteral("Desktop"),
QStringLiteral("Toolbar"),
QStringLiteral("MainToolbar"),
QStringLiteral("Small"),
QStringLiteral("Panel"),
QStringLiteral("Dialog")
}
{
qmlRegisterType<IconsModel>();
qmlRegisterUncreatableType<KIconLoader>("org.kde.private.kcms.icons", 1, 0, "KIconLoader", QString());
KAboutData* about = new KAboutData(QStringLiteral("kcm5_icons"), i18n("Icons"), QStringLiteral("1.0"),
i18n("Icons Control Panel Module"), KAboutLicense::GPL,
i18n("(c) 2000-2003 Geert Jansen"));
about->addAuthor(i18n("Geert Jansen"), QString(), QStringLiteral("jansen@kde.org"));
about->addAuthor(i18n("Antonio Larrosa Jimenez"), QString(), QStringLiteral("larrosa@kde.org"));
about->addCredit(i18n("Torsten Rahn"), QString(), QStringLiteral("torsten@kde.org"));
about->addAuthor(i18n("Jonathan Riddell"), QString(), QStringLiteral("jr@jriddell.org"));
about->addAuthor(i18n("Kai Uwe Broulik"), QString(), QStringLiteral("kde@privat.broulik.de>"));
setAboutData(about);
setButtons(Apply | Default);
connect(m_model, &IconsModel::selectedThemeChanged, this, [this] {
m_selectedThemeDirty = true;
setNeedsSave(true);
});
connect(m_model, &IconsModel::pendingDeletionsChanged, this, [this] {
setNeedsSave(true);
});
QPixmapCache::setCacheLimit(50 * 1024);
}
IconModule::~IconModule()
{
}
IconsModel *IconModule::iconsModel() const
{
return m_model;
}
QStringList IconModule::iconGroups() const
{
return m_iconGroups;
}
int IconModule::iconSize(int group) const
{
return m_iconSizes[group];
}
void IconModule::setIconSize(int group, int size)
{
if (iconSize(group) == size) {
return;
}
m_iconSizes[group] = size;
setNeedsSave(true);
m_iconSizesDirty = true;
emit iconSizesChanged();
}
QList<int> IconModule::availableIconSizes(int group) const
{
return KIconLoader::global()->theme()->querySizes(static_cast<KIconLoader::Group>(group));
}
void IconModule::load()
{
m_model->load();
loadIconSizes();
m_model->setSelectedTheme(KIconTheme::current());
setNeedsSave(false);
m_selectedThemeDirty = false;
m_iconSizesDirty = false;
}
void IconModule::save()
{
if (m_selectedThemeDirty) {
QProcess::startDetached(CMAKE_INSTALL_FULL_LIBEXECDIR "/plasma-changeicons", {m_model->selectedTheme()});
}
if (m_iconSizesDirty || m_revertIconEffects) {
auto cfg = KSharedConfig::openConfig();
for (int i = 0; i < m_iconGroups.count(); ++i) {
const QString &group = m_iconGroups.at(i);
KConfigGroup cg(cfg, group + QLatin1String("Icons"));
cg.writeEntry("Size", m_iconSizes.at(i), KConfig::Normal | KConfig::Global);
if (m_revertIconEffects) {
cg.revertToDefault("Animated");
const QStringList states = {
QStringLiteral("Default"),
QStringLiteral("Active"),
QStringLiteral("Disabled")
};
const QStringList keys = {
QStringLiteral("Effect"),
QStringLiteral("Value"),
QStringLiteral("Color"),
QStringLiteral("Color2"),
QStringLiteral("SemiTransparent")
};
for (const QString &state : states) {
for (const QString &key : keys) {
cg.revertToDefault(state + key);
}
}
}
}
cfg->sync();
}
if (m_selectedThemeDirty || m_iconSizesDirty || m_revertIconEffects) {
exportToKDE4();
}
processPendingDeletions();
KIconLoader::global()->newIconLoader();
setNeedsSave(false);
m_selectedThemeDirty = false;
m_iconSizesDirty = false;
m_revertIconEffects = false;
}
void IconModule::processPendingDeletions()
{
const QStringList pendingDeletions = m_model->pendingDeletions();
for (const QString &themeName : pendingDeletions) {
Q_ASSERT(themeName != m_model->selectedTheme());
KIconTheme theme(themeName);
auto *job = KIO::del(QUrl::fromLocalFile(theme.dir()), KIO::HideProgressInfo);
job->exec();
}
m_model->removeItemsPendingDeletion();
}
void IconModule::defaults()
{
if (m_iconSizes != s_defaultIconSizes) {
m_iconSizes = s_defaultIconSizes;
emit iconSizesChanged();
}
auto setThemeIfAvailable = [this](const QString &themeName) {
const auto results = m_model->match(m_model->index(0, 0), ThemeNameRole, themeName);
if (results.isEmpty()) {
return false;
}
m_model->setSelectedTheme(themeName);
return true;
};
if (!setThemeIfAvailable(KIconTheme::defaultThemeName())) {
setThemeIfAvailable(QStringLiteral("breeze"));
}
m_revertIconEffects = true;
setNeedsSave(true);
}
void IconModule::loadIconSizes()
{
auto cfg = KSharedConfig::openConfig();
QVector<int> iconSizes(6, 0);
int i = KIconLoader::FirstGroup;
for (const QString &group : qAsConst(m_iconGroups)) {
int size = KIconLoader::global()->theme()->defaultSize(static_cast<KIconLoader::Group>(i));
KConfigGroup iconGroup(cfg, group + QLatin1String("Icons"));
size = iconGroup.readEntry("Size", size);
iconSizes[i] = size;
++i;
}
if (m_iconSizes != iconSizes) {
m_iconSizes = iconSizes;
emit iconSizesChanged();
}
}
void IconModule::getNewStuff(QQuickItem *ctx)
{
if (!m_newStuffDialog) {
m_newStuffDialog = new KNS3::DownloadDialog(QStringLiteral("icons.knsrc"));
m_newStuffDialog->setWindowTitle(i18n("Download New Icon Themes"));
m_newStuffDialog->setWindowModality(Qt::WindowModal);
m_newStuffDialog->winId();
connect(m_newStuffDialog.data(), &KNS3::DownloadDialog::accepted, this, [this] {
if (m_newStuffDialog->changedEntries().isEmpty()) {
return;
}
KIconLoader::global()->newIconLoader();
m_model->load();
QPixmapCache::clear();
});
}
if (ctx && ctx->window()) {
m_newStuffDialog->windowHandle()->setTransientParent(ctx->window());
}
m_newStuffDialog->show();
}
void IconModule::installThemeFromFile(const QUrl &url)
{
if (url.isLocalFile()) {
installThemeFile(url.toLocalFile());
return;
}
m_tempInstallFile.reset(new QTemporaryFile());
if (!m_tempInstallFile->open()) {
emit showErrorMessage(i18n("Unable to create a temporary file."));
m_tempInstallFile.reset();
return;
}
KIO::FileCopyJob *job = KIO::file_copy(url,QUrl::fromLocalFile(m_tempInstallFile->fileName()),
-1, KIO::Overwrite);
job->uiDelegate()->setAutoErrorHandlingEnabled(true);
connect(job, &KIO::FileCopyJob::result, this, [this, url](KJob *job) {
if (job->error() != KJob::NoError) {
emit showErrorMessage(i18n("Unable to download the icon theme archive: %1", job->errorText()));
return;
}
installThemeFile(m_tempInstallFile->fileName());
m_tempInstallFile.reset();
});
}
void IconModule::installThemeFile(const QString &path)
{
const QStringList themesNames = findThemeDirs(path);
if (themesNames.isEmpty()) {
emit showErrorMessage(i18n("The file is not a valid icon theme archive."));
return;
}
if (!installThemes(themesNames, path)) {
emit showErrorMessage(i18n("A problem occurred during the installation process; however, most of the themes in the archive have been installed"));
return;
}
emit showSuccessMessage(i18n("Theme installed successfully."));
KIconLoader::global()->newIconLoader();
m_model->load();
}
void IconModule::exportToKDE4()
{
Kdelibs4Migration migration;
QString configFilePath = migration.saveLocation("config");
if (configFilePath.isEmpty()) {
return;
}
configFilePath += QLatin1String("kdeglobals");
KSharedConfigPtr kglobalcfg = KSharedConfig::openConfig(QStringLiteral("kdeglobals"));
KConfig kde4config(configFilePath, KConfig::SimpleConfig);
KConfigGroup kde4IconGroup(&kde4config, "Icons");
kde4IconGroup.writeEntry("Theme", m_model->selectedTheme());
for (const QString &group : qAsConst(m_iconGroups)) {
const QString groupName = group + QLatin1String("Icons");
KConfigGroup cg(kglobalcfg, groupName);
KConfigGroup kde4Cg(&kde4config, groupName);
const QStringList keys = cg.keyList() + kde4Cg.keyList();
for (const QString &key : keys) {
kde4Cg.revertToDefault(key);
}
cg.copyTo(&kde4Cg);
}
kde4config.sync();
QProcess *cachePathProcess = new QProcess(this);
connect(cachePathProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
[cachePathProcess](int exitCode, QProcess::ExitStatus status) {
if (status == QProcess::NormalExit && exitCode == 0) {
QString path = cachePathProcess->readAllStandardOutput().trimmed();
path.append(QLatin1String("icon-cache.kcache"));
QFile::remove(path);
}
for (int i = 0; i < KIconLoader::LastGroup; i++) {
KIconLoader::emitChange(KIconLoader::Group(i));
QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KGlobalSettings"),
QStringLiteral("org.kde.KGlobalSettings"),
QStringLiteral("notifyChange"));
message.setArguments({
4,
KIconLoader::Group(i)
});
QDBusConnection::sessionBus().send(message);
}
cachePathProcess->deleteLater();
});
cachePathProcess->start(QStringLiteral("kde4-config --path cache"));
}
QStringList IconModule::findThemeDirs(const QString &archiveName)
{
QStringList foundThemes;
KTar archive(archiveName);
archive.open(QIODevice::ReadOnly);
const KArchiveDirectory *themeDir = archive.directory();
KArchiveEntry *possibleDir = nullptr;
KArchiveDirectory *subDir = nullptr;
const QStringList entries = themeDir->entries();
for (const QString &entry : entries) {
possibleDir = const_cast<KArchiveEntry*>(themeDir->entry(entry));
if (!possibleDir->isDirectory()) {
continue;
}
subDir = dynamic_cast<KArchiveDirectory *>(possibleDir);
if (!subDir) {
continue;
}
if (subDir->entry(QStringLiteral("index.theme"))
|| subDir->entry(QStringLiteral("index.desktop"))) {
foundThemes.append(subDir->name());
}
}
archive.close();
return foundThemes;
}
bool IconModule::installThemes(const QStringList &themes, const QString &archiveName)
{
bool everythingOk = true;
const QString localThemesDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/icons/./"));
emit showProgress(i18n("Installing icon themes..."));
KTar archive(archiveName);
archive.open(QIODevice::ReadOnly);
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
const KArchiveDirectory *rootDir = archive.directory();
KArchiveDirectory *currentTheme = nullptr;
for (const QString &theme : themes) {
emit showProgress(i18n("Installing %1 theme...", theme));
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
currentTheme = dynamic_cast<KArchiveDirectory*>(const_cast<KArchiveEntry*>(rootDir->entry(theme)));
if (!currentTheme) {
everythingOk = false;
continue;
}
currentTheme->copyTo(localThemesDir + theme);
}
archive.close();
emit hideProgress();
return everythingOk;
}
QVariantList IconModule::previewIcons(const QString &themeName, int size, qreal dpr, int limit)
{
static QVector<QStringList> 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")}
};
QScopedPointer<KIconTheme> theme;
QVariantList pixmaps;
for (const QStringList &iconNames : s_previewIcons) {
const QString cacheKey = themeName + QLatin1Char('@') + QString::number(size) + QLatin1Char('@')
+ QString::number(dpr,'f',1) + QLatin1Char('@') + iconNames.join(QLatin1Char(','));
QPixmap pix;
if (!QPixmapCache::find(cacheKey, pix)) {
if (!theme) {
theme.reset(new KIconTheme(themeName));
}
pix = getBestIcon(*theme.data(), iconNames, size, dpr);
QPixmapCache::insert(cacheKey, pix);
}
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;
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);
return pixmap;
}
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);
}
if (path.isEmpty()) {
continue;
}
if (!renderer.load(path)) {
continue;
}
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 QPixmap();
}
#include "main.moc"