* Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
* Copyright (C) 2007 Jeremy Whiting <jpwhiting@kde.org>
* Copyright (C) 2016 Olivier Churlaud <olivier@churlaud.com>
* Copyright (C) 2019 Kai Uwe Broulik <kde@privat.broulik.de>
* Copyright (c) 2019 Cyril Rossi <cyril.rossi@enioka.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "colors.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <QFileInfo>
#include <QGuiApplication>
#include <QProcess>
#include <QStandardItemModel>
#include <QStandardPaths>
#include <QQuickItem>
#include <QQuickRenderControl>
#include <QQuickWindow>
#include <KAboutData>
#include <KColorUtils>
#include <KColorScheme>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KWindowSystem>
#include <KIO/FileCopyJob>
#include <KIO/DeleteJob>
#include <KIO/JobUiDelegate>
#include <knewstuffcore_version.h>
#if KNEWSTUFFCORE_VERSION_MAJOR==5 && KNEWSTUFFCORE_VERSION_MINOR>=68
#include <KNSCore/EntryWrapper>
#endif
#include <algorithm>
#include "../krdb/krdb.h"
#include "colorsmodel.h"
#include "filterproxymodel.h"
#include "colorssettings.h"
K_PLUGIN_FACTORY_WITH_JSON(KCMColorsFactory, "kcm_colors.json", registerPlugin<KCMColors>();)
KCMColors::KCMColors(QObject *parent, const QVariantList &args)
: KQuickAddons::ManagedConfigModule(parent, args)
, m_model(new ColorsModel(this))
, m_filteredModel(new FilterProxyModel(this))
, m_settings(new ColorsSettings(this))
, m_config(KSharedConfig::openConfig(QStringLiteral("kdeglobals")))
{
qmlRegisterUncreatableType<KCMColors>("org.kde.private.kcms.colors", 1, 0, "KCM", QStringLiteral("Cannot create instances of KCM"));
qmlRegisterType<ColorsModel>();
qmlRegisterType<FilterProxyModel>();
qmlRegisterType<ColorsSettings>();
KAboutData *about = new KAboutData(QStringLiteral("kcm_colors"), i18n("Colors"),
QStringLiteral("2.0"), QString(), KAboutLicense::GPL);
about->addAuthor(i18n("Kai Uwe Broulik"), QString(), QStringLiteral("kde@privat.broulik.de"));
setAboutData(about);
connect(m_model, &ColorsModel::pendingDeletionsChanged, this, &KCMColors::settingsChanged);
connect(m_model, &ColorsModel::selectedSchemeChanged, this, [this](const QString &scheme) {
m_selectedSchemeDirty = true;
m_settings->setColorScheme(scheme);
});
connect(m_settings, &ColorsSettings::colorSchemeChanged, this, [this] {
m_model->setSelectedScheme(m_settings->colorScheme());
});
connect(m_model, &ColorsModel::selectedSchemeChanged, m_filteredModel, &FilterProxyModel::setSelectedScheme);
m_filteredModel->setSourceModel(m_model);
}
KCMColors::~KCMColors()
{
m_config->markAsClean();
}
ColorsModel *KCMColors::model() const
{
return m_model;
}
FilterProxyModel *KCMColors::filteredModel() const
{
return m_filteredModel;
}
ColorsSettings *KCMColors::colorsSettings() const
{
return m_settings;
}
bool KCMColors::downloadingFile() const
{
return m_tempCopyJob;
}
void KCMColors::reloadModel(const QQmlListReference &changedEntries)
{
m_model->load();
#if KNEWSTUFFCORE_VERSION_MAJOR==5 && KNEWSTUFFCORE_VERSION_MINOR>=68
if (changedEntries.count() > 0) {
QStringList installedThemes;
const QString suffix = QStringLiteral(".colors");
for (int i = 0; i < changedEntries.count(); ++i) {
KNSCore::EntryWrapper* entry = qobject_cast<KNSCore::EntryWrapper*>(changedEntries.at(i));
if (entry && entry->entry().status() == KNS3::Entry::Installed) {
for (const QString &path : entry->entry().installedFiles()) {
const QString fileName = path.section(QLatin1Char('/'), -1, -1);
const int suffixPos = fileName.indexOf(suffix);
if (suffixPos != fileName.length() - suffix.length()) {
continue;
}
installedThemes.append(fileName.left(suffixPos));
}
if (!installedThemes.isEmpty()) {
std::sort(installedThemes.begin(), installedThemes.end());
m_model->setSelectedScheme(installedThemes.constFirst());
}
break;
}
}
}
#endif
}
void KCMColors::installSchemeFromFile(const QUrl &url)
{
if (url.isLocalFile()) {
installSchemeFile(url.toLocalFile());
return;
}
if (m_tempCopyJob) {
return;
}
m_tempInstallFile.reset(new QTemporaryFile());
if (!m_tempInstallFile->open()) {
emit showErrorMessage(i18n("Unable to create a temporary file."));
m_tempInstallFile.reset();
return;
}
m_tempCopyJob = KIO::file_copy(url, QUrl::fromLocalFile(m_tempInstallFile->fileName()),
-1, KIO::Overwrite);
m_tempCopyJob->uiDelegate()->setAutoErrorHandlingEnabled(true);
emit downloadingFileChanged();
connect(m_tempCopyJob, &KIO::FileCopyJob::result, this, [this, url](KJob *job) {
if (job->error() != KJob::NoError) {
emit showErrorMessage(i18n("Unable to download the color scheme: %1", job->errorText()));
return;
}
installSchemeFile(m_tempInstallFile->fileName());
m_tempInstallFile.reset();
});
connect(m_tempCopyJob, &QObject::destroyed, this, &KCMColors::downloadingFileChanged);
}
void KCMColors::installSchemeFile(const QString &path)
{
KSharedConfigPtr config = KSharedConfig::openConfig(path, KConfig::SimpleConfig);
KConfigGroup group(config, "General");
const QString name = group.readEntry("Name");
if (name.isEmpty()) {
emit showErrorMessage(i18n("This file is not a color scheme file."));
return;
}
int increment = 0;
QString newName = name;
QString testpath;
do {
if (increment) {
newName = name + QString::number(increment);
}
testpath = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
QStringLiteral("color-schemes/%1.colors").arg(newName));
increment++;
} while (!testpath.isEmpty());
QString newPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/color-schemes/");
if (!QDir().mkpath(newPath)) {
emit showErrorMessage(i18n("Failed to create 'color-scheme' data folder."));
return;
}
newPath += newName + QLatin1String(".colors");
if (!QFile::copy(path, newPath)) {
emit showErrorMessage(i18n("Failed to copy color scheme into 'color-scheme' data folder."));
return;
}
KSharedConfigPtr config2 = KSharedConfig::openConfig(newPath, KConfig::SimpleConfig);
KConfigGroup group2(config2, "General");
group2.writeEntry("Name", newName);
config2->sync();
m_model->load();
const auto results = m_model->match(m_model->index(0, 0), ColorsModel::SchemeNameRole, newName);
if (!results.isEmpty()) {
m_model->setSelectedScheme(newName);
}
emit showSuccessMessage(i18n("Color scheme installed successfully."));
}
void KCMColors::editScheme(const QString &schemeName, QQuickItem *ctx)
{
if (m_editDialogProcess) {
return;
}
QModelIndex idx = m_model->index(m_model->indexOfScheme(schemeName), 0);
m_editDialogProcess = new QProcess(this);
connect(m_editDialogProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
[this](int exitCode, QProcess::ExitStatus exitStatus) {
Q_UNUSED(exitCode);
Q_UNUSED(exitStatus);
const auto savedThemes = QString::fromUtf8(m_editDialogProcess->readAllStandardOutput()).split(QLatin1Char('\n'), QString::SkipEmptyParts);
if (!savedThemes.isEmpty()) {
m_model->load();
if (savedThemes.contains(m_settings->colorScheme())) {
m_activeSchemeEdited = true;
settingsChanged();
}
m_model->setSelectedScheme(savedThemes.last());
}
m_editDialogProcess->deleteLater();
m_editDialogProcess = nullptr;
});
QStringList args;
args << idx.data(ColorsModel::SchemeNameRole).toString();
if (idx.data(ColorsModel::RemovableRole).toBool()) {
args << QStringLiteral("--overwrite");
}
if (ctx && ctx->window()) {
if (QWindow *actualWindow = QQuickRenderControl::renderWindowFor(ctx->window())) {
if (KWindowSystem::isPlatformX11()) {
args << QStringLiteral("--attach") << (QStringLiteral("x11:") + QString::number(actualWindow->winId()));
}
}
}
m_editDialogProcess->start(QStringLiteral("kcolorschemeeditor"), args);
}
bool KCMColors::isSaveNeeded() const
{
return m_activeSchemeEdited || !m_model->match(m_model->index(0, 0), ColorsModel::PendingDeletionRole, true).isEmpty();
}
void KCMColors::load()
{
ManagedConfigModule::load();
m_model->load();
m_config->markAsClean();
m_config->reparseConfiguration();
const QString schemeName = m_settings->colorScheme();
if (m_model->indexOfScheme(schemeName) == -1) {
m_model->setSelectedScheme(m_settings->defaultColorSchemeValue());
m_filteredModel->setSelectedScheme(m_settings->defaultColorSchemeValue());
emit showSchemeNotInstalledWarning(schemeName);
} else {
m_model->setSelectedScheme(schemeName);
m_filteredModel->setSelectedScheme(schemeName);
}
{
KConfig cfg(QStringLiteral("kcmdisplayrc"), KConfig::NoGlobals);
KConfigGroup group(m_config, "General");
group = KConfigGroup(&cfg, "X11");
m_applyToAlien = group.readEntry("exportKDEColors", true);
}
setNeedsSave(false);
}
void KCMColors::save()
{
if (m_selectedSchemeDirty || m_activeSchemeEdited) {
saveColors();
}
ManagedConfigModule::save();
m_activeSchemeEdited = false;
processPendingDeletions();
}
void KCMColors::saveColors()
{
const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
QStringLiteral("color-schemes/%1.colors").arg(m_model->selectedScheme()));
KSharedConfigPtr config = KSharedConfig::openConfig(path);
const QStringList colorSetGroupList{
QStringLiteral("Colors:View"),
QStringLiteral("Colors:Window"),
QStringLiteral("Colors:Button"),
QStringLiteral("Colors:Selection"),
QStringLiteral("Colors:Tooltip"),
QStringLiteral("Colors:Complementary")
};
const QList<KColorScheme> colorSchemes{
KColorScheme(QPalette::Active, KColorScheme::View, config),
KColorScheme(QPalette::Active, KColorScheme::Window, config),
KColorScheme(QPalette::Active, KColorScheme::Button, config),
KColorScheme(QPalette::Active, KColorScheme::Selection, config),
KColorScheme(QPalette::Active, KColorScheme::Tooltip, config),
KColorScheme(QPalette::Active, KColorScheme::Complementary, config)
};
for (int i = 0; i < colorSchemes.length(); ++i) {
KConfigGroup group(m_config, colorSetGroupList.value(i));
group.writeEntry("BackgroundNormal", colorSchemes[i].background(KColorScheme::NormalBackground).color());
group.writeEntry("BackgroundAlternate", colorSchemes[i].background(KColorScheme::AlternateBackground).color());
group.writeEntry("ForegroundNormal", colorSchemes[i].foreground(KColorScheme::NormalText).color());
group.writeEntry("ForegroundInactive", colorSchemes[i].foreground(KColorScheme::InactiveText).color());
group.writeEntry("ForegroundActive", colorSchemes[i].foreground(KColorScheme::ActiveText).color());
group.writeEntry("ForegroundLink", colorSchemes[i].foreground(KColorScheme::LinkText).color());
group.writeEntry("ForegroundVisited", colorSchemes[i].foreground(KColorScheme::VisitedText).color());
group.writeEntry("ForegroundNegative", colorSchemes[i].foreground(KColorScheme::NegativeText).color());
group.writeEntry("ForegroundNeutral", colorSchemes[i].foreground(KColorScheme::NeutralText).color());
group.writeEntry("ForegroundPositive", colorSchemes[i].foreground(KColorScheme::PositiveText).color());
group.writeEntry("DecorationFocus", colorSchemes[i].decoration(KColorScheme::FocusColor).color());
group.writeEntry("DecorationHover", colorSchemes[i].decoration(KColorScheme::HoverColor).color());
}
KConfigGroup groupWMTheme(config, "WM");
KConfigGroup groupWMOut(m_config, "WM");
const QStringList colorItemListWM{
QStringLiteral("activeBackground"),
QStringLiteral("activeForeground"),
QStringLiteral("inactiveBackground"),
QStringLiteral("inactiveForeground"),
QStringLiteral("activeBlend"),
QStringLiteral("inactiveBlend")
};
const QVector<QColor> defaultWMColors{
QColor(71,80,87),
QColor(239,240,241),
QColor(239,240,241),
QColor(189,195,199),
QColor(255,255,255),
QColor(75,71,67)
};
int i = 0;
for (const QString &coloritem : colorItemListWM) {
groupWMOut.writeEntry(coloritem, groupWMTheme.readEntry(coloritem, defaultWMColors.value(i)));
++i;
}
const QStringList groupNameList{
QStringLiteral("ColorEffects:Inactive"),
QStringLiteral("ColorEffects:Disabled")
};
const QStringList effectList{
QStringLiteral("Enable"),
QStringLiteral("ChangeSelectionColor"),
QStringLiteral("IntensityEffect"),
QStringLiteral("IntensityAmount"),
QStringLiteral("ColorEffect"),
QStringLiteral("ColorAmount"),
QStringLiteral("Color"),
QStringLiteral("ContrastEffect"),
QStringLiteral("ContrastAmount")
};
for (const QString &groupName : groupNameList) {
KConfigGroup groupEffectOut(m_config, groupName);
KConfigGroup groupEffectTheme(config, groupName);
for (const QString &effect : effectList) {
groupEffectOut.writeEntry(effect, groupEffectTheme.readEntry(effect));
}
}
m_config->sync();
runRdb(KRdbExportQtColors | KRdbExportGtkTheme | (m_applyToAlien ? KRdbExportColors : 0));
QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KGlobalSettings"),
QStringLiteral("org.kde.KGlobalSettings"),
QStringLiteral("notifyChange"));
message.setArguments({
0,
0
});
QDBusConnection::sessionBus().send(message);
m_selectedSchemeDirty = false;
}
void KCMColors::processPendingDeletions()
{
const QStringList pendingDeletions = m_model->pendingDeletions();
for (const QString &schemeName : pendingDeletions) {
Q_ASSERT(schemeName != m_model->selectedScheme());
const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
QStringLiteral("color-schemes/%1.colors").arg(schemeName));
auto *job = KIO::del(QUrl::fromLocalFile(path), KIO::HideProgressInfo);
job->exec();
}
m_model->removeItemsPendingDeletion();
}
#include "colors.moc"