diff --git a/kcms/colors/colors.cpp b/kcms/colors/colors.cpp index 590ad3598..5ac2c086e 100644 --- a/kcms/colors/colors.cpp +++ b/kcms/colors/colors.cpp @@ -1,491 +1,494 @@ /* * Copyright (C) 2007 Matthew Woehlke * Copyright (C) 2007 Jeremy Whiting * Copyright (C) 2016 Olivier Churlaud * Copyright (C) 2019 Kai Uwe Broulik * Copyright (c) 2019 Cyril Rossi * * 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 . */ #include "colors.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if KNEWSTUFFCORE_VERSION_MAJOR==5 && KNEWSTUFFCORE_VERSION_MINOR>=68 #include #endif #include #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(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("org.kde.private.kcms.colors", 1, 0, "KCM", QStringLiteral("Cannot create instances of KCM")); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); 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 a new theme was installed, select the first color file in it if (changedEntries.count() > 0) { QStringList installedThemes; const QString suffix = QStringLiteral(".colors"); for (int i = 0; i < changedEntries.count(); ++i) { KNSCore::EntryWrapper* entry = qobject_cast(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()) { // The list is sorted by (potentially translated) name // but that would require us parse every file, so this should be close enough std::sort(installedThemes.begin(), installedThemes.end()); m_model->setSelectedScheme(installedThemes.constFirst()); } // Only do this for the first newly installed theme we find 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; } // Ideally we copied the file into the proper location right away but // (for some reason) we determine the file name from the "Name" inside the file 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; } // Do not overwrite another scheme 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; } // Update name 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::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(); // would be cool to just reload/add the changed/new ones // If the currently active scheme was edited, consider settings dirty even if the scheme itself didn't change 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()) { // QQuickWidget, used for embedding QML KCMs, renders everything into an offscreen window // Qt is able to resolve this on its own when setting transient parents in-process. // However, since we pass the ID to an external process which has no idea of this // we need to resolve the actual window we end up showing in. if (QWindow *actualWindow = QQuickRenderControl::renderWindowFor(ctx->window())) { if (KWindowSystem::isPlatformX11()) { // TODO wayland: once we have foreign surface support 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 the scheme named in kdeglobals doesn't exist, show a warning and use default scheme if (m_model->indexOfScheme(schemeName) == -1) { m_model->setSelectedScheme(m_settings->defaultColorSchemeValue()); // These are normally synced but initially the model doesn't emit a change to avoid the // Apply button from being enabled without any user interaction. Sync manually here. 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); } // If need save is true at the end of load() function, it will stay disabled forever. // setSelectedScheme() call due to unexisting scheme name in kdeglobals will trigger a need to save. // this following call ensure the apply button will work properly. setNeedsSave(false); } void KCMColors::save() { // We need to save the colors change first, to avoid a situation, // when we announced that the color scheme has changed, but // the colors themselves in the color scheme have not yet 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{ + const QStringList colorSetGroupList { QStringLiteral("Colors:View"), QStringLiteral("Colors:Window"), QStringLiteral("Colors:Button"), QStringLiteral("Colors:Selection"), QStringLiteral("Colors:Tooltip"), - QStringLiteral("Colors:Complementary") + QStringLiteral("Colors:Complementary"), + QStringLiteral("Colors:Header") }; - const QList colorSchemes{ + const QList 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) + KColorScheme(QPalette::Active, KColorScheme::Complementary, config), + KColorScheme(QPalette::Active, KColorScheme::Header, 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()); } - + + //TODO: Remove the code for the WM group colors when Colors:Header has completely replaced it KConfigGroup groupWMTheme(config, "WM"); KConfigGroup groupWMOut(m_config, "WM"); - const QStringList colorItemListWM{ + const QStringList colorItemListWM { QStringLiteral("activeBackground"), QStringLiteral("activeForeground"), QStringLiteral("inactiveBackground"), QStringLiteral("inactiveForeground"), QStringLiteral("activeBlend"), QStringLiteral("inactiveBlend") }; - const QVector 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) + const QVector defaultWMColors { // Default to Header colors + colorSchemes[KColorScheme::Header].background(KColorScheme::NormalBackground).color(), + colorSchemes[KColorScheme::Header].foreground(KColorScheme::NormalText).color(), + colorSchemes[KColorScheme::Header].background(KColorScheme::AlternateBackground).color(), + colorSchemes[KColorScheme::Header].foreground(KColorScheme::InactiveText).color(), + colorSchemes[KColorScheme::Header].background(KColorScheme::NormalBackground).color(), + colorSchemes[KColorScheme::Header].background(KColorScheme::AlternateBackground).color() }; int i = 0; for (const QString &coloritem : colorItemListWM) { groupWMOut.writeEntry(coloritem, groupWMTheme.readEntry(coloritem, defaultWMColors.value(i))); ++i; } - const QStringList groupNameList{ + const QStringList groupNameList { QStringLiteral("ColorEffects:Inactive"), QStringLiteral("ColorEffects:Disabled") }; - const QStringList effectList{ + 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, //previous KGlobalSettings::PaletteChanged. This is now private API in khintsettings 0 //unused in palette changed but needed for the DBus signature }); 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); // needs to block for it to work on "OK" where the dialog (kcmshell) closes job->exec(); } m_model->removeItemsPendingDeletion(); } #include "colors.moc" diff --git a/kcms/colors/colorsmodel.cpp b/kcms/colors/colorsmodel.cpp index c9373fa11..5e6346fec 100644 --- a/kcms/colors/colorsmodel.cpp +++ b/kcms/colors/colorsmodel.cpp @@ -1,241 +1,240 @@ /* * Copyright (C) 2007 Matthew Woehlke * Copyright (C) 2007 Jeremy Whiting * Copyright (C) 2016 Olivier Churlaud * Copyright (C) 2019 Kai Uwe Broulik * * 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 . */ #include "colorsmodel.h" #include #include #include #include #include #include #include ColorsModel::ColorsModel(QObject *parent) : QAbstractListModel(parent) { } ColorsModel::~ColorsModel() = default; int ColorsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_data.count(); } QVariant ColorsModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_data.count()) { return QVariant(); } const auto &item = m_data.at(index.row()); switch (role) { case Qt::DisplayRole: return item.display; case SchemeNameRole: return item.schemeName; case PaletteRole: return item.palette; - case ActiveTitleBarBackgroundRole: return item.activeTitleBarBackground; - case ActiveTitleBarForegroundRole: return item.activeTitleBarForeground; + case HeaderBackgroundRole: return item.headerBackground; + case HeaderTextRole: return item.headerText; case PendingDeletionRole: return item.pendingDeletion; case RemovableRole: return item.removable; } return QVariant(); } bool ColorsModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || index.row() >= m_data.count()) { return false; } if (role == PendingDeletionRole) { auto &item = m_data[index.row()]; const bool pendingDeletion = value.toBool(); if (item.pendingDeletion != pendingDeletion) { item.pendingDeletion = pendingDeletion; emit dataChanged(index, index, {PendingDeletionRole}); if (index.row() == selectedSchemeIndex() && pendingDeletion) { // move to the next non-pending theme const auto nonPending = match(index, PendingDeletionRole, false); if (!nonPending.isEmpty()) { setSelectedScheme(nonPending.first().data(SchemeNameRole).toString()); } } emit pendingDeletionsChanged(); return true; } } return false; } QHash ColorsModel::roleNames() const { return { {Qt::DisplayRole, QByteArrayLiteral("display")}, {SchemeNameRole, QByteArrayLiteral("schemeName")}, {PaletteRole, QByteArrayLiteral("palette")}, - {ActiveTitleBarBackgroundRole, QByteArrayLiteral("activeTitleBarBackground")}, - {ActiveTitleBarForegroundRole, QByteArrayLiteral("activeTitleBarForeground")}, + {HeaderBackgroundRole, QByteArrayLiteral("headerBackground")}, + {HeaderTextRole, QByteArrayLiteral("headerText")}, {RemovableRole, QByteArrayLiteral("removable")}, {PendingDeletionRole, QByteArrayLiteral("pendingDeletion")} }; } QString ColorsModel::selectedScheme() const { return m_selectedScheme; } void ColorsModel::setSelectedScheme(const QString &scheme) { if (m_selectedScheme == scheme) { return; } m_selectedScheme = scheme; emit selectedSchemeChanged(scheme); emit selectedSchemeIndexChanged(); } int ColorsModel::indexOfScheme(const QString &scheme) const { auto it = std::find_if(m_data.begin(), m_data.end(), [ &scheme](const ColorsModelData &item) { return item.schemeName == scheme; }); if (it != m_data.end()) { return std::distance(m_data.begin(), it); } return -1; } int ColorsModel::selectedSchemeIndex() const { return indexOfScheme(m_selectedScheme); } void ColorsModel::load() { beginResetModel(); const int oldCount = m_data.count(); m_data.clear(); QStringList schemeFiles; const QStringList schemeDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("color-schemes"), QStandardPaths::LocateDirectory); for (const QString &dir : schemeDirs) { const QStringList fileNames = QDir(dir).entryList(QStringList{QStringLiteral("*.colors")}); for (const QString &file : fileNames) { const QString suffixedFileName = QLatin1String("color-schemes/") + file; // can't use QSet because of the transform below (passing const QString as this argument discards qualifiers) if (!schemeFiles.contains(suffixedFileName)) { schemeFiles.append(suffixedFileName); } } } std::transform(schemeFiles.begin(), schemeFiles.end(), schemeFiles.begin(), [](const QString &item) { return QStandardPaths::locate(QStandardPaths::GenericDataLocation, item); }); for (const QString &schemeFile : schemeFiles) { const QFileInfo fi(schemeFile); const QString baseName = fi.baseName(); KSharedConfigPtr config = KSharedConfig::openConfig(schemeFile, KConfig::SimpleConfig); KConfigGroup group(config, "General"); const QString name = group.readEntry("Name", baseName); const QPalette palette = KColorScheme::createApplicationPalette(config); - // from kwin/decorations/decorationpalette.cpp - KConfigGroup wmConfig(config, QStringLiteral("WM")); - const QColor activeTitleBarBackground = wmConfig.readEntry("activeBackground", palette.color(QPalette::Active, QPalette::Highlight)); - const QColor activeTitleBarForeground = wmConfig.readEntry("activeForeground", palette.color(QPalette::Active, QPalette::HighlightedText)); + KColorScheme headerColorScheme(QPalette::Active, KColorScheme::Header, config); + const QColor headerBackground = headerColorScheme.background().color(); + const QColor headerText = headerColorScheme.foreground().color(); - ColorsModelData item{ + ColorsModelData item { name, baseName, palette, - activeTitleBarBackground, - activeTitleBarForeground, + headerBackground, + headerText, fi.isWritable(), false, // pending deletion }; m_data.append(item); } QCollator collator; std::sort(m_data.begin(), m_data.end(), [&collator](const ColorsModelData &a, const ColorsModelData &b) { return collator.compare(a.display, b.display) < 0; }); endResetModel(); // an item might have been added before the currently selected one if (oldCount != m_data.count()) { emit selectedSchemeIndexChanged(); } } QStringList ColorsModel::pendingDeletions() const { QStringList pendingDeletions; for (const auto &item : m_data) { if (item.pendingDeletion) { pendingDeletions.append(item.schemeName); } } return pendingDeletions; } void ColorsModel::removeItemsPendingDeletion() { for (int i = m_data.count() - 1; i >= 0; --i) { if (m_data.at(i).pendingDeletion) { beginRemoveRows(QModelIndex(), i, i); m_data.remove(i); endRemoveRows(); } } } diff --git a/kcms/colors/colorsmodel.h b/kcms/colors/colorsmodel.h index 82424d11f..cc0305a78 100644 --- a/kcms/colors/colorsmodel.h +++ b/kcms/colors/colorsmodel.h @@ -1,91 +1,91 @@ /* * Copyright (C) 2007 Matthew Woehlke * Copyright (C) 2007 Jeremy Whiting * Copyright (C) 2016 Olivier Churlaud * Copyright (C) 2019 Kai Uwe Broulik * * 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 . */ #pragma once #include #include #include #include struct ColorsModelData { QString display; QString schemeName; QPalette palette; - QColor activeTitleBarBackground; - QColor activeTitleBarForeground; + QColor headerBackground; + QColor headerText; bool removable; bool pendingDeletion; }; Q_DECLARE_TYPEINFO(ColorsModelData, Q_MOVABLE_TYPE); class ColorsModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(QString selectedScheme READ selectedScheme WRITE setSelectedScheme NOTIFY selectedSchemeChanged) Q_PROPERTY(int selectedSchemeIndex READ selectedSchemeIndex NOTIFY selectedSchemeIndexChanged) public: ColorsModel(QObject *parent); ~ColorsModel() override; enum Roles { SchemeNameRole = Qt::UserRole + 1, PaletteRole, // Colors which aren't in QPalette - ActiveTitleBarBackgroundRole, - ActiveTitleBarForegroundRole, + HeaderBackgroundRole, + HeaderTextRole, RemovableRole, PendingDeletionRole }; int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; QHash roleNames() const override; QString selectedScheme() const; void setSelectedScheme(const QString &scheme); int indexOfScheme(const QString &scheme) const; int selectedSchemeIndex() const; QStringList pendingDeletions() const; void removeItemsPendingDeletion(); void load(); Q_SIGNALS: void selectedSchemeChanged(const QString &scheme); void selectedSchemeIndexChanged(); void pendingDeletionsChanged(); private: QString m_selectedScheme; QVector m_data; }; diff --git a/kcms/colors/package/contents/ui/main.qml b/kcms/colors/package/contents/ui/main.qml index be00417e3..1102ce0ab 100644 --- a/kcms/colors/package/contents/ui/main.qml +++ b/kcms/colors/package/contents/ui/main.qml @@ -1,347 +1,347 @@ /* * Copyright 2018 Kai Uwe Broulik * * 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 . */ import QtQuick 2.6 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import QtQuick.Dialogs 1.0 as QtDialogs import QtQuick.Controls 2.3 as QtControls import org.kde.kirigami 2.8 as Kirigami import org.kde.newstuff 1.62 as NewStuff import org.kde.kcm 1.1 as KCM import org.kde.private.kcms.colors 1.0 as Private KCM.GridViewKCM { id: root KCM.ConfigModule.quickHelp: i18n("This module lets you choose the color scheme.") view.model: kcm.filteredModel view.currentIndex: kcm.filteredModel.selectedSchemeIndex Binding { target: kcm.filteredModel property: "query" value: searchField.text } Binding { target: kcm.filteredModel property: "filter" value: filterCombo.model[filterCombo.currentIndex].filter } enabled: !kcm.downloadingFile && !kcm.colorsSettings.isImmutable("colorScheme") Component.onCompleted: { // The thumbnails are a bit more elaborate and need more room, especially when translated view.implicitCellWidth = Kirigami.Units.gridUnit * 13; view.implicitCellHeight = Kirigami.Units.gridUnit * 12; } DropArea { anchors.fill: parent onEntered: { if (!drag.hasUrls) { drag.accepted = false; } } onDropped: { infoLabel.visible = false; kcm.installSchemeFromFile(drop.urls[0]); } } // putting the InlineMessage as header item causes it to show up initially despite visible false header: ColumnLayout { Kirigami.InlineMessage { id: notInstalledWarning Layout.fillWidth: true type: Kirigami.MessageType.Warning showCloseButton: true visible: false Connections { target: kcm onShowSchemeNotInstalledWarning: { notInstalledWarning.text = i18n("The color scheme '%1' is not installed. Selecting the default theme instead.", schemeName) notInstalledWarning.visible = true; } } } RowLayout { Layout.fillWidth: true Kirigami.SearchField { id: searchField Layout.fillWidth: true } QtControls.ComboBox { id: filterCombo textRole: "text" model: [ {text: i18n("All Schemes"), filter: Private.KCM.AllSchemes}, {text: i18n("Light Schemes"), filter: Private.KCM.LightSchemes}, {text: i18n("Dark Schemes"), filter: Private.KCM.DarkSchemes} ] // HACK QQC2 doesn't support icons, so we just tamper with the desktop style ComboBox's background // and inject a nice little filter icon. Component.onCompleted: { if (!background || !background.hasOwnProperty("properties")) { // not a KQuickStyleItem return; } var props = background.properties || {}; background.properties = Qt.binding(function() { var newProps = props; newProps.currentIcon = "view-filter"; newProps.iconColor = Kirigami.Theme.textColor; return newProps; }); } } } } view.delegate: KCM.GridDelegate { id: delegate text: model.display thumbnailAvailable: true thumbnail: Rectangle { anchors.fill: parent opacity: model.pendingDeletion ? 0.3 : 1 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration } } color: model.palette.window Kirigami.Theme.highlightColor: model.palette.highlight Kirigami.Theme.highlightedTextColor: model.palette.highlightedText Kirigami.Theme.linkColor: model.palette.link Kirigami.Theme.textColor: model.palette.text Rectangle { id: windowTitleBar width: parent.width height: Math.round(Kirigami.Units.gridUnit * 1.5) gradient: Gradient { // from Breeze Decoration::paintTitleBar - GradientStop { position: 0.0; color: Qt.lighter(model.activeTitleBarBackground, 1.2) } - GradientStop { position: 0.8; color: model.activeTitleBarBackground } + GradientStop { position: 0.0; color: Qt.lighter(model.headerBackground, 1.2) } + GradientStop { position: 0.8; color: model.headerBackground } } - color: model.activeTitleBarBackground + color: model.headerBackground QtControls.Label { anchors { fill: parent leftMargin: Kirigami.Units.smallSpacing rightMargin: Kirigami.Units.smallSpacing } horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - color: model.activeTitleBarForeground + color: model.headerText text: i18n("Window Title") elide: Text.ElideRight } } ColumnLayout { anchors { left: parent.left right: parent.right top: windowTitleBar.bottom bottom: parent.bottom margins: Kirigami.Units.smallSpacing } RowLayout { Layout.fillWidth: true QtControls.Label { Layout.fillWidth: true Layout.fillHeight: true verticalAlignment: Text.AlignVCenter text: i18n("Window text") elide: Text.ElideRight color: model.palette.windowText } QtControls.Button { Layout.alignment: Qt.AlignBottom text: i18n("Button") Kirigami.Theme.backgroundColor: model.palette.button Kirigami.Theme.textColor: model.palette.buttonText activeFocusOnTab: false } } QtControls.Frame { Layout.fillWidth: true Layout.fillHeight: true Kirigami.Theme.backgroundColor: model.palette.base // FIXME Make Frame still visible but without any inner padding padding: 1 activeFocusOnTab: false Column { id: listPreviewColumn readonly property string demoText: " %2 %4" .arg(i18nc("Hyperlink", "link")) .arg(model.palette.linkVisited) .arg(i18nc("Visited hyperlink", "visited")) width: parent.width QtControls.ItemDelegate { width: parent.width text: i18n("Normal text") + listPreviewColumn.demoText activeFocusOnTab: false } QtControls.ItemDelegate { width: parent.width highlighted: true // TODO use proper highlighted link color text: i18n("Highlighted text") + listPreviewColumn.demoText activeFocusOnTab: false } QtControls.ItemDelegate { width: parent.width enabled: false text: i18n("Disabled text") + listPreviewColumn.demoText activeFocusOnTab: false } } } } // Make the preview non-clickable but still reacting to hover MouseArea { anchors.fill: parent onClicked: delegate.clicked() onDoubleClicked: delegate.doubleClicked() } } actions: [ Kirigami.Action { iconName: "document-edit" tooltip: i18n("Edit Color Scheme...") enabled: !model.pendingDeletion onTriggered: kcm.editScheme(model.schemeName, root) }, Kirigami.Action { iconName: "edit-delete" tooltip: i18n("Remove Color Scheme") enabled: model.removable visible: !model.pendingDeletion onTriggered: model.pendingDeletion = true }, Kirigami.Action { iconName: "edit-undo" tooltip: i18n("Restore Color Scheme") visible: model.pendingDeletion onTriggered: model.pendingDeletion = false } ] onClicked: { kcm.model.selectedScheme = model.schemeName; view.forceActiveFocus(); } onDoubleClicked: { kcm.save(); } } footer: ColumnLayout { Kirigami.InlineMessage { id: infoLabel Layout.fillWidth: true showCloseButton: true Connections { target: kcm onShowSuccessMessage: { infoLabel.type = Kirigami.MessageType.Positive; infoLabel.text = message; infoLabel.visible = true; // Avoid dual message widgets notInstalledWarning.visible = false; } onShowErrorMessage: { infoLabel.type = Kirigami.MessageType.Error; infoLabel.text = message; infoLabel.visible = true; notInstalledWarning.visible = false; } } } RowLayout { Layout.alignment: Qt.AlignRight QtControls.Button { text: i18n("Install from File...") icon.name: "document-import" onClicked: fileDialogLoader.active = true } NewStuff.Button { id: newStuffButton text: i18n("Get New Color Schemes...") configFile: "colorschemes.knsrc" viewMode: NewStuff.Page.ViewMode.Tiles onChangedEntriesChanged: kcm.reloadModel(newStuffButton.changedEntries); } } } Loader { id: fileDialogLoader active: false sourceComponent: QtDialogs.FileDialog { title: i18n("Open Color Scheme") folder: shortcuts.home nameFilters: [ i18n("Color Scheme Files (*.colors)") ] Component.onCompleted: open() onAccepted: { infoLabel.visible = false; kcm.installSchemeFromFile(fileUrls[0]) fileDialogLoader.active = false } onRejected: { fileDialogLoader.active = false } } } }