diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ project(plasma-desktop) -set(PROJECT_VERSION "5.17.90") +set(PROJECT_VERSION "5.18.80") set(PROJECT_VERSION_MAJOR 5) set(QT_MIN_VERSION "5.12.0") @@ -81,6 +81,7 @@ find_package(KF5ItemModels CONFIG REQUIRED) find_package(KF5Emoticons CONFIG REQUIRED) find_package(KF5 REQUIRED COMPONENTS SysGuard) +find_package(KDED CONFIG REQUIRED) find_package(KF5Baloo ${KF5_MIN_VERSION}) set_package_properties(KF5Baloo PROPERTIES DESCRIPTION "File Searching" diff --git a/HACKING b/HACKING --- a/HACKING +++ b/HACKING @@ -17,5 +17,5 @@ https://community.kde.org/Policies/Kdelibs_Coding_Style Existing code that does not follow this style should be migrated over during -editting. +editing. diff --git a/applets/kicker/package/metadata.desktop b/applets/kicker/package/metadata.desktop --- a/applets/kicker/package/metadata.desktop +++ b/applets/kicker/package/metadata.desktop @@ -53,6 +53,7 @@ Name[zh_TW]=應用程式選單 Comment=A launcher based on cascading popup menus Comment[ar]=مطلق مبنيّ على قوائم منبثقة متتالية +Comment[ast]=Un llanzador basáu nos menús emerxentes en cascada Comment[bs]=Pokretač baziran na kaskadnim iskakajucim menijima Comment[ca]=Un llançador basat en menús emergents en cascada Comment[ca@valencia]=Un llançador basat en menús emergents en cascada diff --git a/applets/kimpanel/backend/ibus/emojier/org.kde.plasma.emojier.desktop b/applets/kimpanel/backend/ibus/emojier/org.kde.plasma.emojier.desktop --- a/applets/kimpanel/backend/ibus/emojier/org.kde.plasma.emojier.desktop +++ b/applets/kimpanel/backend/ibus/emojier/org.kde.plasma.emojier.desktop @@ -4,6 +4,7 @@ Name[ast]=Seleutor de fustaxes Name[ca]=Selector dels emoji Name[cs]=Selektor emoji +Name[da]=Emoji-vælger Name[de]=Emoji-Auswahl Name[en_GB]=Emoji Selector Name[es]=Selector de emojis diff --git a/applets/minimizeall/package/contents/ui/main.qml b/applets/minimizeall/package/contents/ui/main.qml --- a/applets/minimizeall/package/contents/ui/main.qml +++ b/applets/minimizeall/package/contents/ui/main.qml @@ -149,7 +149,7 @@ PlasmaCore.ToolTipArea { id: tooltip anchors.fill: parent - mainText : i18n("Minimize Windows") + mainText : i18n("Minimize all Windows") subText : i18n("Show the desktop by minimizing all windows") icon : plasmoid.configuration.icon diff --git a/applets/minimizeall/package/metadata.desktop b/applets/minimizeall/package/metadata.desktop --- a/applets/minimizeall/package/metadata.desktop +++ b/applets/minimizeall/package/metadata.desktop @@ -15,6 +15,7 @@ Name[hu]=Összes ablak minimalizálása Name[id]=Minimalkan Semua Window Name[it]=Minimizza tutte le finestre +Name[ja]=すべてのウィンドウを最小化 Name[ko]=모든 창 최소화 Name[lt]=Suskleisti visus langus Name[ml]=എല്ലാ ജാലകങ്ങളും ചെറുതാക്കുക @@ -48,6 +49,7 @@ Comment[hu]=Megjeleníti az asztalt az összes ablak minimalizálásával Comment[id]=Menampilkan desktop dengan meminimalkan semua window Comment[it]=Mostra il desktop minimizzando tutte le finestre +Comment[ja]=すべてのウィンドウを最小化してデスクトップを表示 Comment[ko]=모든 창을 최소화하여 바탕 화면 표시 Comment[lt]=Suskleidžiant visus langus, rodo darbalaukį Comment[nl]=Toont het bureaublad door alle vensters te minimaliseren diff --git a/applets/taskmanager/package/contents/ui/ToolTipInstance.qml b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml --- a/applets/taskmanager/package/contents/ui/ToolTipInstance.qml +++ b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml @@ -196,6 +196,7 @@ id: albumArtBackground source: albumArt anchors.fill: parent + anchors.margins: 1 // Otherwise it can cover up the entire tooltip effect fillMode: Image.PreserveAspectCrop visible: albumArtImage.available layer.enabled: true @@ -212,7 +213,8 @@ // also Image.Loading to prevent loading thumbnails just because the album art takes a split second to load readonly property bool available: status === Image.Ready || status === Image.Loading - height: thumbnail.height - playerControlsLoader.realHeight + height: albumArtBackground.height - playerControlsLoader.realHeight + anchors.top: albumArtBackground.top anchors.horizontalCenter: parent.horizontalCenter sourceSize: Qt.size(parent.width, parent.height) @@ -249,6 +251,10 @@ property real realHeight: item? item.realHeight : 0 anchors.fill: thumbnail + // Otherwise it can cover up the entire tooltip effect + anchors.leftMargin: 1 + anchors.rightMargin: 1 + anchors.bottomMargin: 1 sourceComponent: hasPlayer ? playerControlsComp : undefined } diff --git a/desktoppackage/contents/explorer/WidgetExplorer.qml b/desktoppackage/contents/explorer/WidgetExplorer.qml --- a/desktoppackage/contents/explorer/WidgetExplorer.qml +++ b/desktoppackage/contents/explorer/WidgetExplorer.qml @@ -305,9 +305,7 @@ NumberAnimation { properties: "x" from: -list.width - to: 0 duration: units.shortDuration * 3 - } } @@ -328,10 +326,22 @@ //moved due to filtering displaced: Transition { NumberAnimation { - properties: "y" + properties: "x,y" duration: units.shortDuration * 3 } } + + PlasmaExtras.Heading { + anchors.fill: parent + anchors.margins: units.largeSpacing + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + level: 2 + text: searchInput.text.length > 0 ? i18n("No widgets matched the search terms") : i18n("No widgets available") + enabled: false + visible: list.count == 0 + } } } diff --git a/kcms/activities/ActivitiesTab.h b/kcms/activities/ActivitiesTab.h --- a/kcms/activities/ActivitiesTab.h +++ b/kcms/activities/ActivitiesTab.h @@ -34,14 +34,6 @@ explicit ActivitiesTab(QWidget *parent); ~ActivitiesTab() override; -public Q_SLOTS: - void defaults(); - void load(); - void save(); - -Q_SIGNALS: - void changed(); - private: D_PTR; }; diff --git a/kcms/activities/ActivitiesTab.cpp b/kcms/activities/ActivitiesTab.cpp --- a/kcms/activities/ActivitiesTab.cpp +++ b/kcms/activities/ActivitiesTab.cpp @@ -61,14 +61,3 @@ { } -void ActivitiesTab::defaults() -{ -} - -void ActivitiesTab::load() -{ -} - -void ActivitiesTab::save() -{ -} diff --git a/kcms/activities/BlacklistedApplicationsModel.h b/kcms/activities/BlacklistedApplicationsModel.h --- a/kcms/activities/BlacklistedApplicationsModel.h +++ b/kcms/activities/BlacklistedApplicationsModel.h @@ -49,7 +49,8 @@ QHash roleNames() const override; Q_SIGNALS: - void changed(); + void changed(bool changed); + void defaulted(bool isDefault); void enabledChanged(bool enabled); public Q_SLOTS: diff --git a/kcms/activities/BlacklistedApplicationsModel.cpp b/kcms/activities/BlacklistedApplicationsModel.cpp --- a/kcms/activities/BlacklistedApplicationsModel.cpp +++ b/kcms/activities/BlacklistedApplicationsModel.cpp @@ -29,9 +29,8 @@ #include #include -#include -#include -#include + +#include "kactivitymanagerd_plugins_settings.h" #include @@ -48,16 +47,22 @@ QList applications; QSqlDatabase database; - - KSharedConfig::Ptr pluginConfig; + + KActivityManagerdPluginsSettings *pluginConfig; bool enabled; }; BlacklistedApplicationsModel::BlacklistedApplicationsModel(QObject *parent) : QAbstractListModel(parent) { d->enabled = false; - d->pluginConfig = KSharedConfig::openConfig(QStringLiteral("kactivitymanagerd-pluginsrc")); + d->pluginConfig = new KActivityManagerdPluginsSettings; + + const QString path + = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + + QStringLiteral("/kactivitymanagerd/resources/database"); + d->database = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("plugins_sqlite_db_resources")); + d->database.setDatabaseName(path); } BlacklistedApplicationsModel::~BlacklistedApplicationsModel() @@ -77,22 +82,13 @@ void BlacklistedApplicationsModel::load() { // Loading plugin configuration - - const auto config = d->pluginConfig->group(SQLITE_PLUGIN_CONFIG_KEY); - - const auto defaultBlockedValue = config.readEntry("blocked-by-default", false); - auto blockedApplications = QSet::fromList(config.readEntry("blocked-applications", QStringList())); - auto allowedApplications = QSet::fromList(config.readEntry("allowed-applications", QStringList())); + d->pluginConfig->load(); + const auto defaultBlockedValue = d->pluginConfig->defaultBlockedByDefaultValue(); + auto blockedApplications = QSet::fromList(d->pluginConfig->blockedApplications()); + auto allowedApplications = QSet::fromList(d->pluginConfig->allowedApplications()); // Reading new applications from the database - const QString path - = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) - + QStringLiteral("/kactivitymanagerd/resources/database"); - - d->database = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("plugins_sqlite_db_resources")); - d->database.setDatabaseName(path); - if (!d->database.open()) { // qDebug() << "Failed to open the database" << path << d->database.lastError(); return; @@ -150,17 +146,8 @@ void BlacklistedApplicationsModel::save() { - auto config = d->pluginConfig->group(SQLITE_PLUGIN_CONFIG_KEY); - QStringList blockedApplications; - QStringList allowedApplications; - - for (int i = 0; i < rowCount(); i++) { - (d->applications[i].blocked ? blockedApplications : allowedApplications) - << d->applications[i].name; - } - - config.writeEntry("allowed-applications", allowedApplications); - config.writeEntry("blocked-applications", blockedApplications); + d->pluginConfig->save(); + emit changed(false); } void BlacklistedApplicationsModel::defaults() @@ -171,6 +158,8 @@ dataChanged(QAbstractListModel::index(0), QAbstractListModel::index(rowCount() - 1)); + + emit defaulted(true); } void BlacklistedApplicationsModel::toggleApplicationBlocked(int index) @@ -183,14 +172,35 @@ dataChanged(QAbstractListModel::index(index), QAbstractListModel::index(index)); - emit changed(); + QStringList blockedApplications; + QStringList allowedApplications; + + for (int i = 0; i < rowCount(); i++) { + const auto name = d->applications[i].name; + if (d->applications[i].blocked) { + blockedApplications << name; + } else { + allowedApplications << name; + } + } + + d->pluginConfig->setBlockedApplications(blockedApplications); + d->pluginConfig->setAllowedApplications(allowedApplications); + + const auto allowedApplicationsItem = d->pluginConfig->findItem("allowedApplications"); + Q_ASSERT(allowedApplicationsItem); + const auto blockedApplicationsItem = d->pluginConfig->findItem("allowedApplications"); + Q_ASSERT(blockedApplicationsItem); + + emit changed(blockedApplicationsItem->isSaveNeeded() && allowedApplicationsItem->isSaveNeeded()); + emit defaulted(blockedApplicationsItem->isDefault() && allowedApplicationsItem->isDefault()); } QVariant BlacklistedApplicationsModel::headerData(int section, Qt::Orientation orientation, int role) const { - Q_UNUSED(section); - Q_UNUSED(orientation); - Q_UNUSED(role); + Q_UNUSED(section) + Q_UNUSED(orientation) + Q_UNUSED(role) return QVariant(); } @@ -224,7 +234,7 @@ int BlacklistedApplicationsModel::rowCount(const QModelIndex &parent) const { - Q_UNUSED(parent); + Q_UNUSED(parent) return d->applications.size(); } diff --git a/kcms/activities/MainConfigurationWidget.h b/kcms/activities/MainConfigurationWidget.h --- a/kcms/activities/MainConfigurationWidget.h +++ b/kcms/activities/MainConfigurationWidget.h @@ -42,12 +42,6 @@ void load() override; void save() override; -private Q_SLOTS: - void onChanged(); - -private: - void checkDefault(); - private: D_PTR; }; diff --git a/kcms/activities/MainConfigurationWidget.cpp b/kcms/activities/MainConfigurationWidget.cpp --- a/kcms/activities/MainConfigurationWidget.cpp +++ b/kcms/activities/MainConfigurationWidget.cpp @@ -46,48 +46,37 @@ d->tabs->insertTab(0, d->tabActivities = new ActivitiesTab(d->tabs), i18n("Activities")); d->tabs->insertTab(1, d->tabSwitching = new SwitchingTab(d->tabs), i18n("Switching")); d->tabs->insertTab(2, d->tabPrivacy = new PrivacyTab(d->tabs), i18n("Privacy")); - - connect(d->tabActivities, &ActivitiesTab::changed, this, &MainConfigurationWidget::onChanged); - connect(d->tabSwitching, &SwitchingTab::changed, this, &MainConfigurationWidget::onChanged); - connect(d->tabPrivacy, &PrivacyTab::changed, this, &MainConfigurationWidget::onChanged); + + addConfig(d->tabPrivacy->pluginConfig(), d->tabPrivacy); + addConfig(d->tabSwitching->mainConfig(), d->tabSwitching); + + connect (d->tabPrivacy, &PrivacyTab::blackListModelChanged, this, &MainConfigurationWidget::unmanagedWidgetChangeState); + connect (d->tabPrivacy, &PrivacyTab::blackListModelDefaulted, this, &MainConfigurationWidget::unmanagedWidgetDefaultState); } MainConfigurationWidget::~MainConfigurationWidget() { } -void MainConfigurationWidget::checkDefault() -{ - defaulted(d->tabSwitching->isDefault() && d->tabPrivacy->isDefault()); -} - -void MainConfigurationWidget::onChanged() -{ - checkDefault(); - markAsChanged(); -} - void MainConfigurationWidget::defaults() { - d->tabActivities->defaults(); + KCModule::defaults(); + d->tabPrivacy->defaults(); - d->tabSwitching->defaults(); } void MainConfigurationWidget::load() { - d->tabActivities->load(); + KCModule::load(); + d->tabPrivacy->load(); - d->tabSwitching->load(); - - checkDefault(); } void MainConfigurationWidget::save() { - d->tabActivities->save(); + KCModule::save(); + d->tabPrivacy->save(); - d->tabSwitching->save(); } #include "MainConfigurationWidget.moc" diff --git a/kcms/activities/PrivacyTab.h b/kcms/activities/PrivacyTab.h --- a/kcms/activities/PrivacyTab.h +++ b/kcms/activities/PrivacyTab.h @@ -25,6 +25,8 @@ #include +class KCoreConfigSkeleton; + /** * PrivacyTab */ @@ -35,7 +37,8 @@ ~PrivacyTab() override; bool isDefault(); - + KCoreConfigSkeleton *pluginConfig(); + public Q_SLOTS: void defaults(); void load(); @@ -51,7 +54,8 @@ void spinKeepHistoryValueChanged(int value); Q_SIGNALS: - void changed(); + void blackListModelChanged(bool changed); + void blackListModelDefaulted(bool isDefault); private: enum WhatToRemember { diff --git a/kcms/activities/PrivacyTab.cpp b/kcms/activities/PrivacyTab.cpp --- a/kcms/activities/PrivacyTab.cpp +++ b/kcms/activities/PrivacyTab.cpp @@ -63,18 +63,23 @@ } }; +KCoreConfigSkeleton *PrivacyTab::pluginConfig() +{ + return d->pluginConfig; +} + PrivacyTab::PrivacyTab(QWidget *parent) : QWidget(parent) , d(this) { d->setupUi(this); // Keep history initialization - d->spinKeepHistory->setRange(0, INT_MAX); - d->spinKeepHistory->setSpecialValueText(i18nc("unlimited number of months", "Forever")); + d->kcfg_keepHistoryFor->setRange(0, INT_MAX); + d->kcfg_keepHistoryFor->setSpecialValueText(i18nc("unlimited number of months", "Forever")); - connect(d->spinKeepHistory, SIGNAL(valueChanged(int)), + connect(d->kcfg_keepHistoryFor, SIGNAL(valueChanged(int)), this, SLOT(spinKeepHistoryValueChanged(int))); spinKeepHistoryValueChanged(0); @@ -96,6 +101,8 @@ // Blacklist applications d->blacklistedApplicationsModel = new BlacklistedApplicationsModel(this); + connect(d->blacklistedApplicationsModel, &BlacklistedApplicationsModel::changed, this, &PrivacyTab::blackListModelChanged); + connect(d->blacklistedApplicationsModel, &BlacklistedApplicationsModel::defaulted, this, &PrivacyTab::blackListModelDefaulted); new QGridLayout(d->viewBlacklistedApplicationsContainer); @@ -108,74 +115,36 @@ // React to changes - connect(d->radioRememberAllApplications, &QAbstractButton::toggled, this, &PrivacyTab::changed); - connect(d->radioDontRememberApplications, &QAbstractButton::toggled, this, &PrivacyTab::changed); - connect(d->spinKeepHistory, SIGNAL(valueChanged(int)), this, SIGNAL(changed())); - connect(d->blacklistedApplicationsModel, &BlacklistedApplicationsModel::changed, this, &PrivacyTab::changed); - connect(d->radioRememberSpecificApplications, &QAbstractButton::toggled, d->blacklistedApplicationsModel, &BlacklistedApplicationsModel::setEnabled); - - connect(d->checkBlacklistAllNotOnList, &QAbstractButton::toggled, this, &PrivacyTab::changed); - - defaults(); - - d->checkBlacklistAllNotOnList->setEnabled(false); d->blacklistedApplicationsModel->setEnabled(false); - d->viewBlacklistedApplicationsContainer->setEnabled(false); d->messageWidget->setVisible(false); } PrivacyTab::~PrivacyTab() { } -bool PrivacyTab::isDefault() -{ - return d->radioRememberAllApplications->isChecked() && - d->spinKeepHistory->value() == d->pluginConfig->defaultKeepHistoryForValue() && - d->checkBlacklistAllNotOnList->isChecked() == d->pluginConfig->defaultBlockedByDefaultValue(); -} - void PrivacyTab::defaults() { d->blacklistedApplicationsModel->defaults(); - - d->radioRememberAllApplications->click(); - d->spinKeepHistory->setValue(d->pluginConfig->defaultKeepHistoryForValue()); - d->checkBlacklistAllNotOnList->setChecked(d->pluginConfig->defaultBlockedByDefaultValue()); } void PrivacyTab::load() { d->blacklistedApplicationsModel->load(); - - const auto whatToRemember = static_cast(d->pluginConfig->whatToRemember()); - - d->radioRememberAllApplications->setChecked(whatToRemember == AllApplications); - d->radioRememberSpecificApplications->setChecked(whatToRemember == SpecificApplications); - d->radioDontRememberApplications->setChecked(whatToRemember == NoApplications); - - d->spinKeepHistory->setValue(d->pluginConfig->keepHistoryFor()); - d->checkBlacklistAllNotOnList->setChecked(d->pluginConfig->blockedByDefault()); } void PrivacyTab::save() { d->blacklistedApplicationsModel->save(); - + const auto whatToRemember = d->radioRememberSpecificApplications->isChecked() ? SpecificApplications : d->radioDontRememberApplications->isChecked() ? NoApplications : /* otherwise */ AllApplications; - - d->pluginConfig->setWhatToRemember(static_cast(whatToRemember)); - d->pluginConfig->setKeepHistoryFor(d->spinKeepHistory->value()); - d->pluginConfig->setBlockedByDefault(d->checkBlacklistAllNotOnList->isChecked()); - - d->pluginConfig->save(); - + d->mainConfig->setResourceScoringEnabled(whatToRemember != NoApplications); d->mainConfig->save(); } @@ -216,8 +185,8 @@ " month", " months"); if (value) { - d->spinKeepHistory->setPrefix( + d->kcfg_keepHistoryFor->setPrefix( i18nc("for in 'keep history for 5 months'", "For ")); - d->spinKeepHistory->setSuffix(months.subs(value).toString()); + d->kcfg_keepHistoryFor->setSuffix(months.subs(value).toString()); } } diff --git a/kcms/activities/SwitchingTab.h b/kcms/activities/SwitchingTab.h --- a/kcms/activities/SwitchingTab.h +++ b/kcms/activities/SwitchingTab.h @@ -26,6 +26,7 @@ #include class QKeySequence; +class KCoreConfigSkeleton; /** * SwitchingTab @@ -36,19 +37,11 @@ explicit SwitchingTab(QWidget *parent); ~SwitchingTab() override; - bool isDefault(); - -public Q_SLOTS: - void defaults(); - void load(); - void save(); + KCoreConfigSkeleton *mainConfig(); private Q_SLOTS: void shortcutChanged(const QKeySequence &sequence); -Q_SIGNALS: - void changed(); - private: D_PTR; }; diff --git a/kcms/activities/SwitchingTab.cpp b/kcms/activities/SwitchingTab.cpp --- a/kcms/activities/SwitchingTab.cpp +++ b/kcms/activities/SwitchingTab.cpp @@ -33,7 +33,7 @@ class SwitchingTab::Private : public Ui::SwitchingTabBase { public: - KActivityManagerdSettings mainConfig; + KActivityManagerdSettings *mainConfig; KActionCollection *mainActionCollection; KActivities::Consumer activities; @@ -49,7 +49,8 @@ } Private() - : mainActionCollection(nullptr) + : mainConfig(new KActivityManagerdSettings), + mainActionCollection(nullptr) { } }; @@ -74,22 +75,15 @@ d->scActivities->setActionTypes(KShortcutsEditor::GlobalAction); d->scActivities->addCollection(d->mainActionCollection); - - connect(d->scActivities, &KShortcutsEditor::keyChange, - this, [this] { changed(); }); - connect(d->checkRememberVirtualDesktop, &QAbstractButton::toggled, - this, &SwitchingTab::changed); - - defaults(); } SwitchingTab::~SwitchingTab() { } -bool SwitchingTab::isDefault() +KCoreConfigSkeleton *SwitchingTab::mainConfig() { - return !d->checkRememberVirtualDesktop->isChecked(); + return d->mainConfig; } void SwitchingTab::shortcutChanged(const QKeySequence &sequence) @@ -105,23 +99,5 @@ KGlobalAccel::self()->setShortcut(action, { sequence }, KGlobalAccel::NoAutoloading); d->mainActionCollection->writeSettings(); - - emit changed(); -} - -void SwitchingTab::defaults() -{ - d->checkRememberVirtualDesktop->setChecked(false); } -void SwitchingTab::load() -{ - - d->checkRememberVirtualDesktop->setChecked(d->mainConfig.virtualDesktopSwitchEnabled()); -} - -void SwitchingTab::save() -{ - d->mainConfig.setVirtualDesktopSwitchEnabled(d->checkRememberVirtualDesktop->isChecked()); - d->mainConfig.save(); -} diff --git a/kcms/activities/kactivitymanagerd_plugins_settings.kcfg b/kcms/activities/kactivitymanagerd_plugins_settings.kcfg --- a/kcms/activities/kactivitymanagerd_plugins_settings.kcfg +++ b/kcms/activities/kactivitymanagerd_plugins_settings.kcfg @@ -16,6 +16,12 @@ 0 - + + + + + + + diff --git a/kcms/activities/ui/PrivacyTabBase.ui b/kcms/activities/ui/PrivacyTabBase.ui --- a/kcms/activities/ui/PrivacyTabBase.ui +++ b/kcms/activities/ui/PrivacyTabBase.ui @@ -30,41 +30,17 @@ Qt::AlignHCenter|Qt::AlignTop - - - - Remember opened documents: - - - - - - - For a&ll applications - - - true - - - - - - - &Do not remember - - - - - + + - O&nly for specific applications: + Keep history: - + @@ -79,10 +55,46 @@ - - + + + + + + + true + + + + + + For a&ll applications + + + true + + + + + + + &Do not remember + + + + + + + O&nly for specific applications: + + + + + + + + - Keep history: + Remember opened documents: @@ -104,7 +116,7 @@ 6 - + Blacklist applications not on the list @@ -142,7 +154,7 @@ radioRememberSpecificApplications toggled(bool) - checkBlacklistAllNotOnList + kcfg_blockedByDefault setEnabled(bool) diff --git a/kcms/activities/ui/SwitchingTabBase.ui b/kcms/activities/ui/SwitchingTabBase.ui --- a/kcms/activities/ui/SwitchingTabBase.ui +++ b/kcms/activities/ui/SwitchingTabBase.ui @@ -17,7 +17,7 @@ Qt::AlignHCenter|Qt::AlignTop - + Remember for each activity (needs restart) @@ -47,10 +47,13 @@ Qt::Vertical + + QSizePolicy::Expanding + - 20 - 195 + 0 + 0 diff --git a/kcms/baloo/filteredfoldermodel.h b/kcms/baloo/filteredfoldermodel.h --- a/kcms/baloo/filteredfoldermodel.h +++ b/kcms/baloo/filteredfoldermodel.h @@ -1,6 +1,7 @@ /* * This file is part of the KDE Baloo project * Copyright (C) 2014 Vishesh Handa + * Copyright (c) 2020 Benjamin Port * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -23,15 +24,15 @@ #include +class BalooSettings; + class FilteredFolderModel : public QAbstractListModel { Q_OBJECT public: - explicit FilteredFolderModel(QObject* parent); + explicit FilteredFolderModel(BalooSettings *settings, QObject *parent); - void setDirectoryList(const QStringList& includeDirs, const QStringList& exclude); QStringList includeFolders() const; - QStringList excludeFolders() const; enum Roles { Folder = Qt::UserRole + 1, @@ -44,9 +45,9 @@ Q_INVOKABLE void addFolder(const QString& folder); Q_INVOKABLE void removeFolder(int row); QHash roleNames() const override; -Q_SIGNALS: - void folderAdded(); - void folderRemoved(); + +public slots: + void updateDirectoryList(); private: QString folderDisplayName(const QString& url) const; @@ -62,10 +63,7 @@ */ QString iconName(QString path) const; - /** - * @brief Widget with the list of directories. - * - */ + BalooSettings *m_settings; QStringList m_mountPoints; QStringList m_excludeList; }; diff --git a/kcms/baloo/filteredfoldermodel.cpp b/kcms/baloo/filteredfoldermodel.cpp --- a/kcms/baloo/filteredfoldermodel.cpp +++ b/kcms/baloo/filteredfoldermodel.cpp @@ -2,6 +2,7 @@ * This file is part of the KDE Baloo Project * Copyright (C) 2014 Vishesh Handa * Copyright (C) 2019 Tomaz Canabrava + * Copyright (c) 2020 Benjamin Port * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -33,6 +34,8 @@ #include #include +#include "baloo/baloosettings.h" + namespace { QStringList addTrailingSlashes(const QStringList& input) { QStringList output = input; @@ -52,12 +55,13 @@ } } -FilteredFolderModel::FilteredFolderModel(QObject* parent) +FilteredFolderModel::FilteredFolderModel(BalooSettings *settings, QObject *parent) : QAbstractListModel(parent) + , m_settings(settings) { } -void FilteredFolderModel::setDirectoryList(const QStringList& include, const QStringList& exclude) +void FilteredFolderModel::updateDirectoryList() { beginResetModel(); @@ -79,16 +83,16 @@ m_mountPoints.append(QDir::homePath()); m_mountPoints = addTrailingSlashes(m_mountPoints); - QStringList includeList = addTrailingSlashes(include); + QStringList includeList = addTrailingSlashes(m_settings->folders()); - m_excludeList = addTrailingSlashes(exclude); + m_excludeList = addTrailingSlashes(m_settings->excludedFolders()); // This algorithm seems bogus. verify later. for (const QString& mountPath : m_mountPoints) { if (includeList.contains(mountPath)) continue; - if (exclude.contains(mountPath)) + if (m_settings->excludedFolders().contains(mountPath)) continue; if (!m_excludeList.contains(mountPath)) { @@ -135,11 +139,6 @@ return mountPointSet.values(); } -QStringList FilteredFolderModel::excludeFolders() const -{ - return m_excludeList; -} - QString FilteredFolderModel::fetchMountPoint(const QString& url) const { QString mountPoint; @@ -155,22 +154,29 @@ void FilteredFolderModel::addFolder(const QString& url) { - if (m_excludeList.contains(url)) { + auto excluded = m_settings->excludedFolders(); + if (excluded.contains(url)) { return; } - beginResetModel(); - m_excludeList.append(QUrl(url).toLocalFile()); - std::sort(std::begin(m_excludeList), std::end(m_excludeList)); - endResetModel(); - Q_EMIT folderAdded(); + excluded.append(QUrl(url).toLocalFile()); + std::sort(std::begin(excluded), std::end(excluded)); + m_settings->setExcludedFolders(excluded); } void FilteredFolderModel::removeFolder(int row) { - beginRemoveRows(QModelIndex(), row, row); - m_excludeList.removeAt(row); - endRemoveRows(); - Q_EMIT folderRemoved(); + auto url = m_excludeList.at(row); + auto excluded = addTrailingSlashes(m_settings->excludedFolders()); + auto included = addTrailingSlashes(m_settings->folders()); + if (excluded.contains(url)) { + excluded.removeAll(url); + std::sort(std::begin(excluded), std::end(excluded)); + m_settings->setExcludedFolders(excluded); + } else if (m_mountPoints.contains(url) && !included.contains(url)) { + included.append(url); + std::sort(std::begin(included), std::end(included)); + m_settings->setFolders(included); + } } diff --git a/kcms/baloo/kcm.h b/kcms/baloo/kcm.h --- a/kcms/baloo/kcm.h +++ b/kcms/baloo/kcm.h @@ -1,6 +1,7 @@ /* This file is part of the KDE Project Copyright (c) 2007 Sebastian Trueg Copyright (c) 2012-2014 Vishesh Handa + Copyright (c) 2020 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public @@ -20,50 +21,43 @@ #ifndef _BALOO_FILE_KCM_H_ #define _BALOO_FILE_KCM_H_ -#include +#include #include "filteredfoldermodel.h" +class BalooSettings; + namespace Baloo { -class ServerConfigModule : public KQuickAddons::ConfigModule +class ServerConfigModule : public KQuickAddons::ManagedConfigModule { Q_OBJECT Q_PROPERTY(FilteredFolderModel *filteredModel READ filteredModel CONSTANT) - Q_PROPERTY(bool indexing READ indexing WRITE setIndexing NOTIFY indexingChanged) - Q_PROPERTY(bool fileContents READ fileContents WRITE setFileContents NOTIFY fileContentsChanged) + Q_PROPERTY(BalooSettings *balooSettings READ balooSettings CONSTANT) public: ServerConfigModule(QObject* parent, const QVariantList& args); virtual ~ServerConfigModule() override; - bool indexing() const; - void setIndexing(bool indexing); - Q_SIGNAL void indexingChanged(bool indexing); - - bool fileContents() const; - void setFileContents(bool fileContents); - Q_SIGNAL void fileContentsChanged(bool fileContents); - + BalooSettings *balooSettings() const; FilteredFolderModel *filteredModel() const; public Q_SLOTS: void load() override; void save() override; - void defaults() override; private: /** * @brief Check if all mount points are in the excluded from indexing list. * * @return True if all mount points are excluded. False otherwise. */ bool allMountPointsExcluded(); + + BalooSettings *m_settings; FilteredFolderModel *m_filteredFolderModel; bool m_previouslyEnabled; - bool m_indexing; - bool m_fileContents; }; } diff --git a/kcms/baloo/kcm.cpp b/kcms/baloo/kcm.cpp --- a/kcms/baloo/kcm.cpp +++ b/kcms/baloo/kcm.cpp @@ -1,6 +1,7 @@ /* This file is part of the KDE Project Copyright (c) 2007-2010 Sebastian Trueg Copyright (c) 2012-2014 Vishesh Handa + Copyright (c) 2020 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public @@ -23,7 +24,6 @@ #include #include #include -#include #include #include @@ -36,26 +36,19 @@ #include #include +#include K_PLUGIN_FACTORY_WITH_JSON(KCMColorsFactory, "kcm_baloofile.json", registerPlugin();) -namespace -{ - QStringList defaultFolders() - { - return { QDir::homePath() }; - } -} // namespace - using namespace Baloo; ServerConfigModule::ServerConfigModule(QObject* parent, const QVariantList& args) - : KQuickAddons::ConfigModule(parent, args) - , m_filteredFolderModel(new FilteredFolderModel(this)) - , m_indexing(false) - , m_fileContents(false) + : KQuickAddons::ManagedConfigModule(parent, args) + , m_settings(new BalooSettings(this)) + , m_filteredFolderModel(new FilteredFolderModel(m_settings, this)) { qmlRegisterType(); + qmlRegisterType(); KAboutData* about = new KAboutData( QStringLiteral("kcm_baloofile"), i18n("File Search"), @@ -69,44 +62,32 @@ setAboutData(about); setButtons(Help | Apply | Default); - connect(m_filteredFolderModel, &FilteredFolderModel::folderAdded, this, [this]{ setNeedsSave(true); }); - connect(m_filteredFolderModel, &FilteredFolderModel::folderRemoved, this, [this]{ setNeedsSave(true); }); + connect(m_settings, &BalooSettings::excludedFoldersChanged, m_filteredFolderModel, &FilteredFolderModel::updateDirectoryList); + connect(m_settings, &BalooSettings::foldersChanged, m_filteredFolderModel, &FilteredFolderModel::updateDirectoryList); + m_filteredFolderModel->updateDirectoryList(); } ServerConfigModule::~ServerConfigModule() { } void ServerConfigModule::load() { - Baloo::IndexerConfig config; - m_indexing = config.fileIndexingEnabled(); - m_previouslyEnabled = m_indexing; - m_fileContents = !config.onlyBasicIndexing(); - - m_filteredFolderModel->setDirectoryList(config.includeFolders(), config.excludeFolders()); - - emit indexingChanged(m_indexing); - emit fileContentsChanged(m_fileContents); + ManagedConfigModule::load(); + m_previouslyEnabled = m_settings->indexingEnabled(); } void ServerConfigModule::save() { - bool enabled = m_indexing && !allMountPointsExcluded(); + ManagedConfigModule::save(); Baloo::IndexerConfig config; - config.setFileIndexingEnabled(enabled); - config.setIncludeFolders(m_filteredFolderModel->includeFolders()); - config.setExcludeFolders(m_filteredFolderModel->excludeFolders()); - config.setOnlyBasicIndexing(!m_fileContents); - config.setFirstRun(false); - - if (m_previouslyEnabled != enabled) { - config.setFirstRun(true); - } + config.setFirstRun(m_previouslyEnabled != m_settings->indexingEnabled()); + + m_previouslyEnabled = m_settings->indexingEnabled(); // Start Baloo - if (enabled) { + if (m_settings->indexingEnabled() && !allMountPointsExcluded()) { const QString exe = QStandardPaths::findExecutable(QStringLiteral("baloo_file")); QProcess::startDetached(exe, QStringList()); } @@ -129,51 +110,24 @@ config.refresh(); } -void ServerConfigModule::defaults() -{ - m_filteredFolderModel->setDirectoryList(defaultFolders(), QStringList()); -} - -bool ServerConfigModule::indexing() const -{ - return m_indexing; -} - -bool ServerConfigModule::fileContents() const +FilteredFolderModel *ServerConfigModule::filteredModel() const { - return m_fileContents; -} - -FilteredFolderModel *ServerConfigModule::filteredModel() const { return m_filteredFolderModel; } -void ServerConfigModule::setIndexing(bool indexing) -{ - if (m_indexing != indexing) { - m_indexing = indexing; - Q_EMIT indexingChanged(indexing); - setNeedsSave(true); - } -} - -void ServerConfigModule::setFileContents(bool fileContents) -{ - if (m_fileContents != fileContents) { - m_fileContents = fileContents; - Q_EMIT fileContentsChanged(fileContents); - setNeedsSave(true); - } -} - bool ServerConfigModule::allMountPointsExcluded() { QStringList mountPoints; for (const QStorageInfo &si : QStorageInfo::mountedVolumes()) { mountPoints.append(si.rootPath()); } - return m_filteredFolderModel->excludeFolders().toSet() == mountPoints.toSet(); + return m_settings->excludedFolders().toSet() == mountPoints.toSet(); +} + +BalooSettings *ServerConfigModule::balooSettings() const +{ + return m_settings; } #include "kcm.moc" diff --git a/kcms/baloo/package/contents/ui/main.qml b/kcms/baloo/package/contents/ui/main.qml --- a/kcms/baloo/package/contents/ui/main.qml +++ b/kcms/baloo/package/contents/ui/main.qml @@ -42,18 +42,19 @@ QQC2.CheckBox { id: fileSearchEnabled text: i18n("Enable File Search") - checked: kcm.indexing + enabled: !kcm.balooSettings.isImmutable("indexingEnabled") + checked: kcm.balooSettings.indexingEnabled onCheckStateChanged: { - kcm.indexing = checked + kcm.balooSettings.indexingEnabled = checked } } QQC2.CheckBox { id: indexFileContents text: i18n("Also index file content") - enabled: fileSearchEnabled.checked - checked: kcm.fileContents - onCheckStateChanged: kcm.fileContents = checked + enabled: fileSearchEnabled.checked && !kcm.balooSettings.isImmutable("onlyBasicIndexing") + checked: !kcm.balooSettings.onlyBasicIndexing + onCheckStateChanged: kcm.balooSettings.onlyBasicIndexing = !checked } Item { Layout.preferredHeight: Kirigami.Units.gridUnit diff --git a/kcms/baloo/package/metadata.desktop b/kcms/baloo/package/metadata.desktop --- a/kcms/baloo/package/metadata.desktop +++ b/kcms/baloo/package/metadata.desktop @@ -2,6 +2,7 @@ Name=Search Name[ca]=Cerca Name[cs]=Hledat +Name[da]=Søg Name[de]=Suche Name[en_GB]=Search Name[es]=Buscar @@ -32,6 +33,7 @@ Comment[ast]=Configura l'indizador de ficheros Comment[ca]=Configura l'indexador de fitxers Comment[cs]=Nastavit indexování souborů +Comment[da]=Indstil filindeksering Comment[de]=Datei-Indizierung einrichten Comment[en_GB]=Configure the File Indexer Comment[es]=Configurar el indexador de archivos diff --git a/kcms/colors/colorschemes.knsrc b/kcms/colors/colorschemes.knsrc --- a/kcms/colors/colorschemes.knsrc +++ b/kcms/colors/colorschemes.knsrc @@ -38,7 +38,7 @@ Name[uk]=Схеми кольорів Name[x-test]=xxColor Schemesxx Name[zh_CN]=配色方案 -Name[zh_TW]=顏色機制 +Name[zh_TW]=配色 ProvidersUrl=https://download.kde.org/ocs/providers.xml TargetDir=color-schemes diff --git a/kcms/colors/editor/org.kde.kcolorschemeeditor.desktop b/kcms/colors/editor/org.kde.kcolorschemeeditor.desktop --- a/kcms/colors/editor/org.kde.kcolorschemeeditor.desktop +++ b/kcms/colors/editor/org.kde.kcolorschemeeditor.desktop @@ -38,7 +38,7 @@ GenericName[uk]=Редактор схем кольорів GenericName[x-test]=xxColor scheme editorxx GenericName[zh_CN]=配色编辑器 -GenericName[zh_TW]=顏色機制編輯器 +GenericName[zh_TW]=配色編輯器 Name=KColorSchemeEditor Name[ast]=KColorSchemeEditor Name[ca]=KColorSchemeEditor @@ -118,7 +118,7 @@ Comment[uk]=Редактор схем кольорів Плазми Comment[x-test]=xxPlasma color scheme editorxx Comment[zh_CN]=Plasma 配色编辑器 -Comment[zh_TW]=Plasma 顏色機制編輯器 +Comment[zh_TW]=Plasma 配色編輯器 Exec=kcolorschemeeditor StartupNotify=true Icon=preferences-desktop-color diff --git a/kcms/componentchooser/browserconfig_ui.ui b/kcms/componentchooser/browserconfig_ui.ui --- a/kcms/componentchooser/browserconfig_ui.ui +++ b/kcms/componentchooser/browserconfig_ui.ui @@ -24,99 +24,7 @@ 0 - - - <qt>Open <b>http</b> and <b>https</b> URLs</qt> - - - - - - - in an application based on the contents of the URL - - - true - - - - - - - in the following application: - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 20 - 0 - - - - - - - - false - - - - - - - - - with the following command: - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 20 - 0 - - - - - - - - false - - - - - - - false - - - ... - - - - + @@ -136,62 +44,6 @@ - - - KLineEdit - QLineEdit -
KLineEdit
-
-
- - - radioService - toggled(bool) - browserCombo - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - - radioExec - toggled(bool) - lineExec - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - - radioExec - toggled(bool) - btnSelectApplication - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - + diff --git a/kcms/componentchooser/componentchooser.cpp b/kcms/componentchooser/componentchooser.cpp --- a/kcms/componentchooser/componentchooser.cpp +++ b/kcms/componentchooser/componentchooser.cpp @@ -203,6 +203,7 @@ if (configWidget) { configContainer->setCurrentWidget(configWidget); const auto plugin = dynamic_cast(configWidget); + headerGroupBox->setTitle(it->text()); plugin->load(&cfg); emit defaulted(plugin->isDefaults()); } diff --git a/kcms/componentchooser/componentchooser_ui.ui b/kcms/componentchooser/componentchooser_ui.ui --- a/kcms/componentchooser/componentchooser_ui.ui +++ b/kcms/componentchooser/componentchooser_ui.ui @@ -15,7 +15,7 @@ 0 - + 0 diff --git a/kcms/componentchooser/componentchooserbrowser.h b/kcms/componentchooser/componentchooserbrowser.h --- a/kcms/componentchooser/componentchooserbrowser.h +++ b/kcms/componentchooser/componentchooserbrowser.h @@ -31,15 +31,13 @@ bool isDefaults() const override; protected Q_SLOTS: - void selectBrowser(); - void configChanged(); - void selectBrowserApp(); + void selectBrowser(int index); Q_SIGNALS: void changed(bool); private: - QString m_browserExec; - KService::Ptr m_browserService; + int m_currentIndex = -1; + int m_falkonIndex = -1; }; #endif /* COMPONENTCHOOSERBROWSER_H */ diff --git a/kcms/componentchooser/componentchooserbrowser.cpp b/kcms/componentchooser/componentchooserbrowser.cpp --- a/kcms/componentchooser/componentchooserbrowser.cpp +++ b/kcms/componentchooser/componentchooserbrowser.cpp @@ -1,9 +1,9 @@ /*************************************************************************** componentchooserbrowser.cpp ------------------- - copyright : (C) 2002 by Joseph Wenninger - email : jowenn@kde.org - ***************************************************************************/ + copyright : (C) 2002 by Joseph Wenninger + copyright : (C) 2020 by Méven Car +***************************************************************************/ /*************************************************************************** * * @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -29,163 +30,119 @@ : QWidget(parent), Ui::BrowserConfig_UI(),CfgPlugin() { setupUi(this); - connect(lineExec, &KLineEdit::textChanged, this, &CfgBrowser::configChanged); - connect(radioKIO, &QRadioButton::toggled, this, &CfgBrowser::configChanged); - connect(radioService, &QRadioButton::toggled, this, &CfgBrowser::selectBrowserApp); - connect(browserCombo, static_cast(&QComboBox::activated), this, &CfgBrowser::selectBrowserApp); - connect(radioExec, &QRadioButton::toggled, this, &CfgBrowser::configChanged); - connect(btnSelectApplication, &QToolButton::clicked, this, &CfgBrowser::selectBrowser); + connect(browserCombo, static_cast(&QComboBox::activated), this, &CfgBrowser::selectBrowser); + } CfgBrowser::~CfgBrowser() { } -void CfgBrowser::selectBrowserApp() +void CfgBrowser::selectBrowser(int index) { - const QString &storageId = browserCombo->currentData().toString(); - m_browserService = KService::serviceByStorageId(storageId); - m_browserExec.clear(); - configChanged(); -} + if (index == browserCombo->count() -1) { + QList urlList; + KOpenWithDialog dlg(QStringLiteral("x-scheme-handler/http"), QString(), this); + dlg.setSaveNewApplications(true); + if (dlg.exec() != QDialog::Accepted) { + browserCombo->setCurrentIndex(m_currentIndex); + return; + } -void CfgBrowser::configChanged() -{ - bool hasChanged = false; - const BrowserSettings settings; - const QString exec = settings.browserApplication(); + const auto service = dlg.service(); - if (exec.isEmpty()) { - hasChanged |= !radioKIO->isChecked(); - } else { - if (exec.startsWith('!')) { - hasChanged |= lineExec->text() != exec; + // check if the selected service is already in the list + const auto matching = browserCombo->model()->match(browserCombo->model()->index(0,0), Qt::UserRole, service->storageId()); + if (!matching.isEmpty()) { + const int index = matching.at(0).row(); + browserCombo->setCurrentIndex(index); + emit changed(index != m_currentIndex); } else { - hasChanged |= KService::serviceByStorageId(lineExec->text()) != KService::serviceByStorageId( exec ); + const QString icon = !service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript"); + browserCombo->insertItem(browserCombo->count() -1, QIcon::fromTheme(icon), service->name(), service->storageId()); + browserCombo->setCurrentIndex(browserCombo->count() - 2); + + emit changed(true); } - } - emit changed(hasChanged); + } else { + emit changed(index != m_currentIndex); + } } void CfgBrowser::defaults() { - emit changed(!radioKIO->isChecked()); - radioKIO->setChecked(true); + if (m_falkonIndex != -1) { + browserCombo->setCurrentIndex(m_falkonIndex); + } } bool CfgBrowser::isDefaults() const { - return radioKIO->isChecked(); + return m_falkonIndex == -1 || m_falkonIndex == browserCombo->currentIndex(); } void CfgBrowser::load(KConfig *) { - const BrowserSettings settings; - const QString exec = settings.browserApplication(); - if (exec.isEmpty()) { - radioKIO->setChecked(true); - m_browserExec = exec; - m_browserService = nullptr; - } else { - radioExec->setChecked(true); - if (exec.startsWith('!')) { - m_browserExec = exec.mid(1); - m_browserService = nullptr; - } else { - m_browserService = KService::serviceByStorageId( exec ); - if (m_browserService) { - m_browserExec = m_browserService->desktopEntryName(); - } else { - m_browserExec.clear(); - } - } - } - - lineExec->setText(m_browserExec); + const auto browser = KMimeTypeTrader::self()->preferredService("x-scheme-handler/http"); browserCombo->clear(); + m_currentIndex = -1; + m_falkonIndex = -1; const auto constraint = QStringLiteral("'WebBrowser' in Categories and" " ('x-scheme-handler/http' in ServiceTypes or 'x-scheme-handler/https' in ServiceTypes)"); const auto browsers = KServiceTypeTrader::self()->query(QStringLiteral("Application"), constraint); for (const auto &service : browsers) { browserCombo->addItem(QIcon::fromTheme(service->icon()), service->name(), service->storageId()); - if ((m_browserService && m_browserService->storageId() == service->storageId()) || service->exec() == m_browserExec) { + if (browser->storageId() == service->storageId()) { browserCombo->setCurrentIndex(browserCombo->count() - 1); - radioService->setChecked(true); + m_currentIndex = browserCombo->count() - 1; + } + if (service->storageId() == QStringLiteral("org.kde.falkon.desktop")) { + m_falkonIndex = browserCombo->count() - 1; } } + if (browser && m_currentIndex == -1) { + // we have a browser specified by the user + browserCombo->addItem(QIcon::fromTheme(QStringLiteral("application-x-shellscript")), browser->name(), browser->storageId()); + browserCombo->setCurrentIndex(browserCombo->count() - 1); + m_currentIndex = browserCombo->count() - 1; + } + + // add a other option to add a new browser + browserCombo->addItem(QIcon::fromTheme(QStringLiteral("application-x-shellscript")), i18n("Other..."), QStringLiteral()); + emit changed(false); } void CfgBrowser::save(KConfig *) { + const QString browserStorageId = browserCombo->currentData().toString(); + BrowserSettings settings; - QString exec; - if (radioService->isChecked()) { - if (m_browserService) { - exec = m_browserService->storageId(); - } - } else if (radioExec->isChecked()) { - exec = lineExec->text(); - if (m_browserService && (exec == m_browserExec)) { - exec = m_browserService->storageId(); // Use service - } else if (!exec.isEmpty()) { - exec = '!' + exec; // Literal command - } - } - settings.setBrowserApplication(exec); + settings.setBrowserApplication(browserStorageId); settings.save(); // Save the default browser as scheme handler for http(s) in mimeapps.list KSharedConfig::Ptr mimeAppList = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); if (mimeAppList->isConfigWritable(true /*warn user if not writable*/)) { KConfigGroup defaultApp(mimeAppList, "Default Applications"); - - if (m_browserService) { - defaultApp.writeXdgListEntry(QStringLiteral("x-scheme-handler/http"), QStringList(m_browserService->storageId())); - defaultApp.writeXdgListEntry(QStringLiteral("x-scheme-handler/https"), QStringList(m_browserService->storageId())); - } else { - defaultApp.deleteEntry(QStringLiteral("x-scheme-handler/http")); - defaultApp.deleteEntry(QStringLiteral("x-scheme-handler/https")); - } + defaultApp.writeXdgListEntry(QStringLiteral("x-scheme-handler/http"), QStringList(browserStorageId)); + defaultApp.writeXdgListEntry(QStringLiteral("x-scheme-handler/https"), QStringList(browserStorageId)); mimeAppList->sync(); KBuildSycocaProgressDialog::rebuildKSycoca(this); - } - QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.klauncher5"), - QStringLiteral("/KLauncher"), - QStringLiteral("org.kde.KLauncher"), - QStringLiteral("reparseConfiguration")); - QDBusConnection::sessionBus().send(message); + QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.klauncher5"), + QStringLiteral("/KLauncher"), + QStringLiteral("org.kde.KLauncher"), + QStringLiteral("reparseConfiguration")); + QDBusConnection::sessionBus().send(message); - emit changed(false); -} + m_currentIndex = browserCombo->currentIndex(); -void CfgBrowser::selectBrowser() -{ - QList urlList; - KOpenWithDialog dlg(urlList, i18n("Select preferred Web browser application:"), QString(), this); - if (dlg.exec() != QDialog::Accepted) - return; - m_browserService = dlg.service(); - if (m_browserService) { - // check if we have listed it in the browser combo, if so, put it there instead - const int index = browserCombo->findData(m_browserService->storageId()); - if (index > -1) { - browserCombo->setCurrentIndex(index); - radioService->setChecked(true); - } else { - m_browserExec = m_browserService->desktopEntryName(); - if (m_browserExec.isEmpty()) { - m_browserExec = m_browserService->exec(); - } - } - } else { - m_browserExec = dlg.text(); + emit changed(false); } - lineExec->setText(m_browserExec); } diff --git a/kcms/componentchooser/componentchooseremail.h b/kcms/componentchooser/componentchooseremail.h --- a/kcms/componentchooser/componentchooseremail.h +++ b/kcms/componentchooser/componentchooseremail.h @@ -34,11 +34,11 @@ private: KEMailSettings *pSettings; - KService::Ptr m_emailClientService; + int m_currentIndex = -1; + int m_kmailIndex = -1; protected Q_SLOTS: - void selectEmailClient(); - void configChanged(); + void selectEmailClient(int index); Q_SIGNALS: void changed(bool); }; diff --git a/kcms/componentchooser/componentchooseremail.cpp b/kcms/componentchooser/componentchooseremail.cpp --- a/kcms/componentchooser/componentchooseremail.cpp +++ b/kcms/componentchooser/componentchooseremail.cpp @@ -1,8 +1,8 @@ /*************************************************************************** componentchooseremail.cpp ------------------- - copyright : (C) 2002 by Joseph Wenninger - email : jowenn@kde.org + copyright : (C) 2002 by Joseph Wenninger + copyright : (C) 2020 by Méven Car ***************************************************************************/ /*************************************************************************** @@ -22,132 +22,179 @@ #include #include #include +#include +#include +#include +#include + #include #include #include #include #include -// for chmod: -#include -#include -#include - +namespace { + static const char s_AddedAssociations[] = "Added Associations"; + static const auto s_mimetype = QStringLiteral("x-scheme-handler/mailto"); +} CfgEmailClient::CfgEmailClient(QWidget *parent) : QWidget(parent), Ui::EmailClientConfig_UI(), CfgPlugin() { setupUi( this ); pSettings = new KEMailSettings(); - connect(kmailCB, &QRadioButton::toggled, this, &CfgEmailClient::configChanged); - connect(txtEMailClient, &KLineEdit::textChanged, this, &CfgEmailClient::configChanged); -#ifdef Q_OS_UNIX - connect(chkRunTerminal, &QCheckBox::clicked, this, &CfgEmailClient::configChanged); -#else - chkRunTerminal->hide(); -#endif - connect(btnSelectEmail, &QToolButton::clicked, this, &CfgEmailClient::selectEmailClient); + connect(emailClientsCombo, static_cast(&QComboBox::activated), this, &CfgEmailClient::selectEmailClient); } CfgEmailClient::~CfgEmailClient() { delete pSettings; } void CfgEmailClient::defaults() { - kmailCB->setChecked(true); - txtEMailClient->clear(); - chkRunTerminal->setChecked(false); + // select kmail if installed + if (m_kmailIndex != -1) { + emailClientsCombo->setCurrentIndex(m_kmailIndex); + } } bool CfgEmailClient::isDefaults() const { - return kmailCB->isChecked(); + // if kmail is installed and is selected + if (m_kmailIndex != -1) { + return emailClientsCombo->currentIndex() == m_kmailIndex; + } + + return true; } void CfgEmailClient::load(KConfig *) { - QString emailClient = pSettings->getSetting(KEMailSettings::ClientProgram); - bool useKMail = (emailClient.isEmpty()); + const KService::Ptr emailClientService = KMimeTypeTrader::self()->preferredService(s_mimetype); - kmailCB->setChecked(useKMail); - otherCB->setChecked(!useKMail); - txtEMailClient->setText(emailClient); - txtEMailClient->setFixedHeight(txtEMailClient->sizeHint().height()); - chkRunTerminal->setChecked((pSettings->getSetting(KEMailSettings::ClientTerminal) == QLatin1String("true"))); + const auto emailClients = KServiceTypeTrader::self()->query(QStringLiteral("Application"), + QStringLiteral("'Email' in Categories and 'x-scheme-handler/mailto' in ServiceTypes")); - emit changed(false); + emailClientsCombo->clear(); + m_kmailIndex = -1; + m_currentIndex = -1; -} + for (const auto &service : emailClients) { -void CfgEmailClient::configChanged() -{ - emit changed(true); + emailClientsCombo->addItem(QIcon::fromTheme(service->icon()), service->name(), service->storageId()); + + if (emailClientService && emailClientService->storageId() == service->storageId()) { + emailClientsCombo->setCurrentIndex(emailClientsCombo->count() - 1); + m_currentIndex = emailClientsCombo->count() - 1; + } + if (service->storageId() == QStringLiteral("org.kde.kmail2.desktop") || + service->storageId() == QStringLiteral("org.kde.kmail.desktop")) { + m_kmailIndex = emailClientsCombo->count() - 1; + } + } + + // add the Added association to x-scheme-handler/mailto from the mimeapps.list file + const KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); + const KConfigGroup addedApps(profile, s_AddedAssociations); + const auto addedList = addedApps.readXdgListEntry(s_mimetype); + + for (const auto &addedApp : addedList) { + // without .desktop extension + auto service = KService::serviceByStorageId(addedApp.mid(0, addedApp.length() -8)); + if (!service) { + service = KService::serviceByStorageId(addedApp); + } + if (!service) { + continue; + } + // avoid duplicates entry when email clients are present in mimeapps.list's Added Associations too + const bool isServiceAlreadyInserted = std::none_of(emailClients.constBegin(), emailClients.constEnd(), [service] (const KService::Ptr &serv) { return service->storageId() == serv->storageId(); }); + if (isServiceAlreadyInserted) { + const auto icon = QIcon::fromTheme(!service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript")); + emailClientsCombo->addItem(icon, service->name() + " (" + KShell::tildeCollapse(service->entryPath()) + ")", service->storageId()); + + if (emailClientService && emailClientService->storageId() == service->storageId()) { + emailClientsCombo->setCurrentIndex(emailClientsCombo->count() - 1); + m_currentIndex = emailClientsCombo->count() - 1; + } + } + } + + // add a other option to add a new email client with KOpenWithDialog + emailClientsCombo->addItem(QIcon::fromTheme(QStringLiteral("application-x-shellscript")), i18n("Other..."), QStringLiteral()); + + emit changed(false); } -void CfgEmailClient::selectEmailClient() +void CfgEmailClient::selectEmailClient(int index) { - QList urlList; - KOpenWithDialog dlg(urlList, i18n("Select preferred email client:"), QString(), this); - // hide "Do not &close when command exits" here, we don't need it for a mail client - dlg.hideNoCloseOnExit(); - if (dlg.exec() != QDialog::Accepted) return; - QString client = dlg.text(); - m_emailClientService = dlg.service(); - - // get the preferred Terminal Application - KConfigGroup confGroup( KSharedConfig::openConfig(), QStringLiteral("General") ); - QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); - preferredTerminal += QLatin1String(" -e "); - - int len = preferredTerminal.length(); - bool b = client.left(len) == preferredTerminal; - if (b) client = client.mid(len); - if (!client.isEmpty()) - { - chkRunTerminal->setChecked(b); - txtEMailClient->setText(client); + if (index == emailClientsCombo->count() -1) { + // Other option + + KOpenWithDialog dlg(s_mimetype, QString(), this); + dlg.setSaveNewApplications(true); + + if (dlg.exec() != QDialog::Accepted) { + // restore previous setting + emailClientsCombo->setCurrentIndex(m_currentIndex); + emit changed(false); + } else { + const auto service = dlg.service(); + + const auto icon = QIcon::fromTheme(!service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript")); + emailClientsCombo->insertItem(emailClientsCombo->count() - 1, icon, service->name() + " (" + KShell::tildeCollapse(service->entryPath()) + ")", service->storageId()); + + // select newly inserted email client + emailClientsCombo->setCurrentIndex(emailClientsCombo->count() - 2); + + emit changed(true); + return; + } + } else { + emit changed(m_currentIndex != index); } } - void CfgEmailClient::save(KConfig *) { - if (kmailCB->isChecked()) - { + const QString &storageId = emailClientsCombo->currentData().toString(); + const KService::Ptr emailClientService = KService::serviceByStorageId(storageId); + + const bool kmailSelected = m_kmailIndex != -1 && emailClientsCombo->currentIndex() == m_kmailIndex; + if (kmailSelected) { pSettings->setSetting(KEMailSettings::ClientProgram, QString()); pSettings->setSetting(KEMailSettings::ClientTerminal, QStringLiteral("false")); - } - else - { - pSettings->setSetting(KEMailSettings::ClientProgram, txtEMailClient->text()); - pSettings->setSetting(KEMailSettings::ClientTerminal, (chkRunTerminal->isChecked()) ? "true" : "false"); + } else { + pSettings->setSetting(KEMailSettings::ClientProgram, emailClientService->storageId()); + pSettings->setSetting(KEMailSettings::ClientTerminal, emailClientService->terminal() ? QStringLiteral("true") : QStringLiteral("false")); } - // Save the default email client in mimeapps.list into the group [Default Applications] + // Save the default email client in mimeapps.list KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); - if (profile->isConfigWritable(true)) { + if (profile->isConfigWritable(true) && emailClientService) { + + KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); + + // Save the default application according to mime-apps-spec 1.0 KConfigGroup defaultApp(profile, "Default Applications"); - if (kmailCB->isChecked()) { - QString kmailDesktop = QStringLiteral("org.kde.kmail.desktop"); - if (KService::serviceByDesktopName(QStringLiteral("org.kde.kmail2"))) { - kmailDesktop = QStringLiteral("org.kde.kmail2.desktop"); - } - defaultApp.writeXdgListEntry("x-scheme-handler/mailto", QStringList(kmailDesktop)); - } else if (m_emailClientService) { - defaultApp.writeXdgListEntry("x-scheme-handler/mailto", QStringList(m_emailClientService->storageId())); - } + defaultApp.writeXdgListEntry(s_mimetype, {emailClientService->storageId()}); + + KConfigGroup addedApps(profile, "Added Associations"); + QStringList apps = addedApps.readXdgListEntry(s_mimetype); + apps.removeAll(emailClientService->storageId()); + apps.prepend(emailClientService->storageId()); // make it the preferred app, i.e first in list + addedApps.writeXdgListEntry(s_mimetype, apps); + profile->sync(); - } - // insure proper permissions -- contains sensitive data - QString cfgName(QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("emails"))); - if (!cfgName.isEmpty()) - ::chmod(QFile::encodeName(cfgName), 0600); - QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/Component"), QStringLiteral("org.kde.Kcontrol"), QStringLiteral("KDE_emailSettingsChanged") ); - QDBusConnection::sessionBus().send(message); - emit changed(false); + m_currentIndex = emailClientsCombo->currentIndex(); + + // refresh cache + KBuildSycocaProgressDialog::rebuildKSycoca(this); + + emit changed(false); + } } diff --git a/kcms/componentchooser/componentchooserfilemanager.h b/kcms/componentchooser/componentchooserfilemanager.h --- a/kcms/componentchooser/componentchooserfilemanager.h +++ b/kcms/componentchooser/componentchooserfilemanager.h @@ -36,14 +36,14 @@ bool isDefaults() const override; protected Q_SLOTS: - void slotAddFileManager(); - void configChanged(); + void selectFileManager(int index); Q_SIGNALS: void changed(bool); private: - QList mDynamicRadioButtons; + int m_currentIndex = -1; + int m_dolphinIndex = -1; }; #endif diff --git a/kcms/componentchooser/componentchooserfilemanager.cpp b/kcms/componentchooser/componentchooserfilemanager.cpp --- a/kcms/componentchooser/componentchooserfilemanager.cpp +++ b/kcms/componentchooser/componentchooserfilemanager.cpp @@ -1,5 +1,6 @@ /* This file is part of the KDE project Copyright (C) 2008 David Faure + Copyright (C) 2020 Méven Car 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 @@ -22,101 +23,118 @@ #include #include #include +#include #include #include #include #include -namespace { - -QRadioButton *findDolphinRadio(const QList &radioButtons) -{ - auto it = std::find_if(radioButtons.begin(), radioButtons.end(), [=](QRadioButton *radio) { - return radio->property("storageId") == QStringLiteral("org.kde.dolphin.desktop"); - }); - if (it == radioButtons.end()) { - return nullptr; - } - return *it; -} - -} - CfgFileManager::CfgFileManager(QWidget *parent) : QWidget(parent), Ui::FileManagerConfig_UI(),CfgPlugin() { setupUi(this); - connect(btnSelectFileManager, &QToolButton::clicked, this, &CfgFileManager::slotAddFileManager); + connect(combofileManager, static_cast(&QComboBox::activated), this, &CfgFileManager::selectFileManager); } CfgFileManager::~CfgFileManager() { } -void CfgFileManager::configChanged() -{ - emit changed(true); -} - void CfgFileManager::defaults() { - load(nullptr); - - const auto radio = ::findDolphinRadio(mDynamicRadioButtons); - if (radio) { - radio->setChecked(true); + if (m_dolphinIndex != -1) { + combofileManager->setCurrentIndex(m_dolphinIndex); } } bool CfgFileManager::isDefaults() const { - const auto dolphinRadio = ::findDolphinRadio(mDynamicRadioButtons); - // When dolphin is not present, we can't assume any default value - return !dolphinRadio || dolphinRadio->isChecked(); + return m_dolphinIndex == -1 || m_dolphinIndex == combofileManager->currentIndex(); } -static KService::List appOffers() +void CfgFileManager::selectFileManager(int index) { - return KMimeTypeTrader::self()->query(QStringLiteral("inode/directory"), QStringLiteral("Application")); + if (index == combofileManager->count() -1) { + + KOpenWithDialog dlg({}, i18n("Select preferred file manager:"), QString(), this); + dlg.setSaveNewApplications(true); + if (dlg.exec() != QDialog::Accepted) { + combofileManager->setCurrentIndex(m_currentIndex); + return; + } + + const auto service = dlg.service(); + + // if the selected service is already in the list + const auto matching = combofileManager->model()->match(combofileManager->model()->index(0,0), Qt::UserRole, service->storageId()); + if (!matching.isEmpty()) { + const int index = matching.at(0).row(); + combofileManager->setCurrentIndex(index); + changed(index != m_currentIndex); + } else { + const QString icon = !service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript"); + combofileManager->insertItem(combofileManager->count() -1, QIcon::fromTheme(icon), service->name(), service->storageId()); + combofileManager->setCurrentIndex(combofileManager->count() - 2); + + changed(true); + } + } else { + changed(index != m_currentIndex); + } } -void CfgFileManager::load(KConfig *) { - qDeleteAll(mDynamicRadioButtons); - mDynamicRadioButtons.clear(); - const KService::List apps = appOffers(); - bool first = true; - for (const KService::Ptr &service : apps) { - QRadioButton *button = new QRadioButton(service->name(), this); - connect(button, &QRadioButton::toggled, this, &CfgFileManager::configChanged); - button->setProperty("storageId", service->storageId()); - radioLayout->addWidget(button); - if (first) { - button->setChecked(true); - first = false; +static const QString mime = QStringLiteral("inode/directory"); + +void CfgFileManager::load(KConfig *) +{ + combofileManager->clear(); + m_currentIndex = -1; + + const KService::Ptr fileManager = KMimeTypeTrader::self()->preferredService(mime); + + const auto constraint = QStringLiteral("'FileManager' in Categories and 'inode/directory' in ServiceTypes"); + const KService::List fileManagers = KServiceTypeTrader::self()->query(QStringLiteral("Application"), constraint); + for (const KService::Ptr &service : fileManagers) { + combofileManager->addItem(QIcon::fromTheme(service->icon()), service->name(), service->storageId()); + + if (fileManager->storageId() == service->storageId()) { + combofileManager->setCurrentIndex(combofileManager->count() -1); + m_currentIndex = combofileManager->count() -1; + } + if (service->storageId() == QStringLiteral("org.kde.dolphin.desktop")) { + m_dolphinIndex = combofileManager->count() -1; } - mDynamicRadioButtons << button; } + // in case of a service not associated with FileManager Category + if (m_currentIndex == -1 && !fileManager->storageId().isEmpty()) { + const KService::Ptr service = KService::serviceByStorageId(fileManager->storageId()); + + const QString icon = !service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript"); + combofileManager->addItem(QIcon::fromTheme(icon), service->name(), service->storageId()); + combofileManager->setCurrentIndex(combofileManager->count() -1); + m_currentIndex = combofileManager->count() -1; + } + + // add a other option to add a new file manager with KOpenWithDialog + combofileManager->addItem(QIcon::fromTheme(QStringLiteral("application-x-shellscript")), i18n("Other..."), QStringLiteral()); + emit changed(false); } static const char s_DefaultApplications[] = "Default Applications"; static const char s_AddedAssociations[] = "Added Associations"; void CfgFileManager::save(KConfig *) { - QString storageId; - for (QRadioButton *button : qAsConst(mDynamicRadioButtons)) { - if (button->isChecked()) { - storageId = button->property("storageId").toString(); - } - } - + const QString storageId = combofileManager->currentData().toString(); if (!storageId.isEmpty()) { + + m_currentIndex = combofileManager->currentIndex(); + // This is taken from filetypes/mimetypedata.cpp KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); if (!profile->isConfigWritable(true)) // warn user if mimeapps.list is root-owned (#155126/#94504) return; - const QString mime = QStringLiteral("inode/directory"); KConfigGroup addedApps(profile, s_AddedAssociations); QStringList userApps = addedApps.readXdgListEntry(mime); userApps.removeAll(storageId); // remove if present, to make it first in the list @@ -130,17 +148,6 @@ profile->sync(); KBuildSycocaProgressDialog::rebuildKSycoca(this); - } - - emit changed(false); -} - -void CfgFileManager::slotAddFileManager() -{ - KProcess proc; - proc << QStringLiteral("keditfiletype5"); - proc << QStringLiteral("inode/directory"); - if (proc.execute() == 0) { - load(nullptr); + emit changed(false); } } diff --git a/kcms/componentchooser/componentchooserterminal.h b/kcms/componentchooser/componentchooserterminal.h --- a/kcms/componentchooser/componentchooserterminal.h +++ b/kcms/componentchooser/componentchooserterminal.h @@ -1,8 +1,8 @@ /*************************************************************************** componentchooserterminal.h - description ------------------- - copyright : (C) 2002 by Joseph Wenninger - email : jowenn@kde.org + copyright : (C) 2002 by Joseph Wenninger + copyright : (C) 2020 by Méven Car ***************************************************************************/ /*************************************************************************** @@ -33,11 +33,15 @@ bool isDefaults() const override; protected Q_SLOTS: - void selectTerminalApp(); - void configChanged(); + void selectTerminalApp(); + void selectTerminalEmulator(int index); Q_SIGNALS: void changed(bool); + +private: + int m_currentIndex = -1; + int m_konsoleIndex = -1; }; #endif diff --git a/kcms/componentchooser/componentchooserterminal.cpp b/kcms/componentchooser/componentchooserterminal.cpp --- a/kcms/componentchooser/componentchooserterminal.cpp +++ b/kcms/componentchooser/componentchooserterminal.cpp @@ -1,8 +1,8 @@ /*************************************************************************** componentchooser.cpp - description ------------------- - copyright : (C) 2002 by Joseph Wenninger - email : jowenn@kde.org + copyright : (C) 2002 by Joseph Wenninger + copyright : (C) 2020 by Méven Car ***************************************************************************/ /*************************************************************************** @@ -29,84 +29,119 @@ #include #include #include +#include #include CfgTerminalEmulator::CfgTerminalEmulator(QWidget *parent) : QWidget(parent), Ui::TerminalEmulatorConfig_UI(), CfgPlugin() { - setupUi(this); - connect(terminalLE, &QLineEdit::textChanged, this, &CfgTerminalEmulator::configChanged); - connect(terminalCB, &QRadioButton::toggled, this, &CfgTerminalEmulator::configChanged); - connect(otherCB, &QRadioButton::toggled, this, &CfgTerminalEmulator::configChanged); - connect(btnSelectTerminal, &QToolButton::clicked, this, &CfgTerminalEmulator::selectTerminalApp); - + setupUi(this); + connect(terminalCombo, static_cast(&QComboBox::activated), this, &CfgTerminalEmulator::selectTerminalEmulator); } CfgTerminalEmulator::~CfgTerminalEmulator() { } -void CfgTerminalEmulator::configChanged() +void CfgTerminalEmulator::selectTerminalEmulator(int index) { - emit changed(true); + if (index == terminalCombo->count() - 1) { + selectTerminalApp(); + } else { + emit changed(m_currentIndex != index); + } } void CfgTerminalEmulator::defaults() { - load(nullptr); - terminalCB->setChecked(true); + if (m_konsoleIndex != -1) { + terminalCombo->setCurrentIndex(m_konsoleIndex); + } } bool CfgTerminalEmulator::isDefaults() const { - return terminalCB->isChecked(); + return m_konsoleIndex == -1 || m_konsoleIndex == terminalCombo->currentIndex(); } - void CfgTerminalEmulator::load(KConfig *) { TerminalSettings settings; - QString terminal = settings.terminalApplication(); - if (terminal == QLatin1String("konsole")) - { - terminalLE->setText(QStringLiteral("xterm")); - terminalCB->setChecked(true); - } - else - { - terminalLE->setText(terminal); - otherCB->setChecked(true); - } + const QString terminal = settings.terminalApplication(); + + m_currentIndex = -1; + terminalCombo->clear(); + + const auto constraint = QStringLiteral("'TerminalEmulator' in Categories AND (not exist NoDisplay OR NoDisplay == false)"); + const auto terminalEmulators = KServiceTypeTrader::self()->query(QStringLiteral("Application"), constraint); + for (const auto &service : terminalEmulators) { + terminalCombo->addItem(QIcon::fromTheme(service->icon()), service->name(), service->exec()); + + if (!terminal.isEmpty() && service->exec() == terminal) { + terminalCombo->setCurrentIndex(terminalCombo->count() - 1); + m_currentIndex = terminalCombo->count() - 1; + } + if (service->exec() == QStringLiteral("konsole")) { + m_konsoleIndex = terminalCombo->count() - 1; + } + } + + if (!terminal.isEmpty() && m_currentIndex == -1) { + // we have a terminal specified by the user + terminalCombo->addItem(QIcon::fromTheme(QStringLiteral("application-x-shellscript")), terminal, terminal); + terminalCombo->setCurrentIndex(terminalCombo->count() - 1); + m_currentIndex = terminalCombo->count() - 1; + } + + // add a other option to add a new terminal emulator with KOpenWithDialog + terminalCombo->addItem(QIcon::fromTheme(QStringLiteral("application-x-shellscript")), i18n("Other..."), QStringLiteral()); emit changed(false); } void CfgTerminalEmulator::save(KConfig *) { + const QString terminal = terminalCombo->currentData().toString(); + m_currentIndex = terminalCombo->currentIndex(); + TerminalSettings settings; - settings.setTerminalApplication(terminalCB->isChecked() ? settings.defaultTerminalApplicationValue() : terminalLE->text()); + settings.setTerminalApplication(terminal); settings.save(); QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.klauncher5"), QStringLiteral("/KLauncher"), QStringLiteral("org.kde.KLauncher"), QStringLiteral("reparseConfiguration")); QDBusConnection::sessionBus().send(message); - emit changed(false); + emit changed(false); } void CfgTerminalEmulator::selectTerminalApp() { QList urlList; KOpenWithDialog dlg(urlList, i18n("Select preferred terminal application:"), QString(), this); // hide "Run in &terminal" here, we don't need it for a Terminal Application - dlg.hideRunInTerminal(); - if (dlg.exec() != QDialog::Accepted) return; - QString client = dlg.text(); - - if (!client.isEmpty()) - { - terminalLE->setText(client); - } + dlg.hideRunInTerminal(); + dlg.setSaveNewApplications(true); + if (dlg.exec() != QDialog::Accepted) { + terminalCombo->setCurrentIndex(m_currentIndex); + return; + } + const auto service = dlg.service(); + + // if the selected service is already in the list + const auto matching = terminalCombo->model()->match(terminalCombo->model()->index(0,0), Qt::DisplayRole, service->exec()); + if (!matching.isEmpty()) { + const int index = matching.at(0).row(); + terminalCombo->setCurrentIndex(index); + changed(index != m_currentIndex); + } else { + const QString icon = !service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript"); + terminalCombo->insertItem(terminalCombo->count() -1, QIcon::fromTheme(icon), service->name(), service->exec()); + terminalCombo->setCurrentIndex(terminalCombo->count() - 2); + + changed(true); + } + } // vim: sw=4 ts=4 noet diff --git a/kcms/componentchooser/emailclientconfig_ui.ui b/kcms/componentchooser/emailclientconfig_ui.ui --- a/kcms/componentchooser/emailclientconfig_ui.ui +++ b/kcms/componentchooser/emailclientconfig_ui.ui @@ -2,76 +2,27 @@ EmailClientConfig_UI + + + 0 + 0 + 240 + 148 + + - + + 0 + + + 0 + + + 0 + + 0 - - - - Kmail is the standard Mail program for the Plasma desktop. - - - &Use KMail as preferred email client - - - - - - - Select this option if you want to use any other mail program. - - - Use a different &email client: - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 20 - 0 - - - - - - - - false - - - Optional placeholders: <ul> <li>%t: Recipient's address</li> <li>%s: Subject</li> <li>%c: Carbon Copy (CC)</li> <li>%b: Blind Carbon Copy (BCC)</li> <li>%B: Template body text</li> <li>%A: Attachment </li> <li>%u: Full mailto: URL </li></ul> - - - Press this button to select your favorite email client. Please note that the file you select has to have the executable attribute set in order to be accepted.<br/> You can also use several placeholders which will be replaced with the actual values when the email client is called:<ul> <li>%t: Recipient's address</li> <li>%s: Subject</li> <li>%c: Carbon Copy (CC)</li> <li>%b: Blind Carbon Copy (BCC)</li> <li>%B: Template body text</li> <li>%A: Attachment </li> </ul> - - - - - - - false - - - Click here to browse for the mail program file. - - - ... - - - - - @@ -91,17 +42,7 @@ - - - false - - - Activate this option if you want the selected email client to be executed in a terminal (e.g. <em>Konsole</em>). - - - &Run in terminal - - + @@ -123,62 +64,6 @@
- - - KLineEdit - QLineEdit -
KLineEdit
-
-
- - - otherCB - toggled(bool) - chkRunTerminal - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - - otherCB - toggled(bool) - txtEMailClient - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - - otherCB - toggled(bool) - btnSelectEmail - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - + diff --git a/kcms/componentchooser/filemanagerconfig_ui.ui b/kcms/componentchooser/filemanagerconfig_ui.ui --- a/kcms/componentchooser/filemanagerconfig_ui.ui +++ b/kcms/componentchooser/filemanagerconfig_ui.ui @@ -2,47 +2,29 @@ FileManagerConfig_UI + + + 0 + 0 + 339 + 94 + + - + + 0 + + + 0 + + + 0 + + 0 - - - Browse directories using the following file manager: - - - - - - - - - - - - Other: click Add... in the dialog shown here: - - - - - - - Qt::Horizontal - - - - - - - false - - - ... - - - - + @@ -60,22 +42,5 @@ - - - radioExec - toggled(bool) - btnSelectFileManager - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - + diff --git a/kcms/componentchooser/terminalemulatorconfig_ui.ui b/kcms/componentchooser/terminalemulatorconfig_ui.ui --- a/kcms/componentchooser/terminalemulatorconfig_ui.ui +++ b/kcms/componentchooser/terminalemulatorconfig_ui.ui @@ -2,66 +2,29 @@ TerminalEmulatorConfig_UI + + + 0 + 0 + 246 + 118 + + - + + 0 + + + 0 + + + 0 + + 0 - - - &Use Konsole as terminal application - - - - - - - Use a different &terminal program: - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 20 - 0 - - - - - - - - false - - - Press this button to select your favorite terminal client. Please note that the file you select has to have the executable attribute set in order to be accepted.<br/> Also note that some programs that utilize Terminal Emulator will not work if you add command line arguments (Example: konsole -ls). - - - - - - - false - - - Click here to browse for terminal program. - - - ... - - - - + @@ -82,38 +45,5 @@ - - - otherCB - toggled(bool) - terminalLE - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - - otherCB - toggled(bool) - btnSelectTerminal - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - + diff --git a/kcms/hardware/joystick/joystick.cpp b/kcms/hardware/joystick/joystick.cpp --- a/kcms/hardware/joystick/joystick.cpp +++ b/kcms/hardware/joystick/joystick.cpp @@ -43,7 +43,7 @@ Joystick::Joystick(QWidget *parent, const QVariantList &) : KCModule(parent) { - setButtons(Help); + setButtons(Help | Default); setAboutData(new KAboutData(QStringLiteral("kcmjoystick"), i18n("KDE Joystick Control Module"), QStringLiteral("1.0"), i18n("KDE System Settings Module to test Joysticks"), KAboutLicense::GPL, i18n("(c) 2004, Martin Koller"), @@ -88,8 +88,6 @@ void Joystick::defaults() { joyWidget->resetCalibration(); - - emit changed(true); } //--------------------------------------------------------------------------------------------- diff --git a/kcms/kded/CMakeLists.txt b/kcms/kded/CMakeLists.txt --- a/kcms/kded/CMakeLists.txt +++ b/kcms/kded/CMakeLists.txt @@ -1,11 +1,29 @@ # KI18N Translation Domain for this library add_definitions(-DTRANSLATION_DOMAIN=\"kcm5_kded\") -add_library(kcm_kded MODULE kcmkded.cpp) -target_link_libraries(kcm_kded KF5::ConfigWidgets KF5::Service KF5::I18n Qt5::DBus) +set(kcm_kded_SRCS + kcmkded.cpp + modulesmodel.cpp + filterproxymodel.cpp +) -install(TARGETS kcm_kded DESTINATION ${KDE_INSTALL_PLUGINDIR} ) +qt5_add_dbus_interface(kcm_kded_SRCS ${KDED_DBUS_INTERFACE} kded_interface) + +ecm_qt_declare_logging_category(kcm_kded_SRCS HEADER debug.h + IDENTIFIER KCM_KDED + CATEGORY_NAME kcm_kded + DEFAULT_SEVERITY Info) + +add_library(kcm_kded MODULE ${kcm_kded_SRCS}) + +kcoreaddons_desktop_to_json(kcm_kded "kcmkded.desktop") + +target_link_libraries(kcm_kded KF5::QuickAddons KF5::Service KF5::I18n Qt5::DBus) + +install(TARGETS kcm_kded DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms ) ########### install files ############### install( FILES kcmkded.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) + +kpackage_install_package(package kcm5_kded kcms) diff --git a/kcms/kded/filterproxymodel.h b/kcms/kded/filterproxymodel.h new file mode 100644 --- /dev/null +++ b/kcms/kded/filterproxymodel.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 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 "kcmkded.h" + +class FilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged) + Q_PROPERTY(KDEDConfig::ModuleStatus statusFilter WRITE setStatusFilter NOTIFY statusFilterChanged) + +public: + FilterProxyModel(QObject *parent = nullptr); + ~FilterProxyModel() override; + + QString query() const; + void setQuery(const QString &query); + + KDEDConfig::ModuleStatus statusFilter() const; + void setStatusFilter(KDEDConfig::ModuleStatus statusFilter); + + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +Q_SIGNALS: + void queryChanged(); + void statusFilterChanged(); + +private: + QString m_query; + KDEDConfig::ModuleStatus m_statusFilter = KDEDConfig::UnknownStatus; // "all" + +}; diff --git a/kcms/kded/filterproxymodel.cpp b/kcms/kded/filterproxymodel.cpp new file mode 100644 --- /dev/null +++ b/kcms/kded/filterproxymodel.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 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 "filterproxymodel.h" + +#include "modulesmodel.h" + +FilterProxyModel::FilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) +{ + +} + +FilterProxyModel::~FilterProxyModel() = default; + +QString FilterProxyModel::query() const +{ + return m_query; +} + +void FilterProxyModel::setQuery(const QString &query) +{ + if (m_query != query) { + m_query = query; + invalidateFilter(); + emit queryChanged(); + } +} + +KDEDConfig::ModuleStatus FilterProxyModel::statusFilter() const +{ + return m_statusFilter; +} + +void FilterProxyModel::setStatusFilter(KDEDConfig::ModuleStatus statusFilter) +{ + if (m_statusFilter != statusFilter) { + m_statusFilter = statusFilter; + invalidateFilter(); + emit statusFilterChanged(); + } +} + +bool FilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + const QModelIndex idx = sourceModel()->index(source_row, 0, source_parent); + + if (!m_query.isEmpty()) { + if (!idx.data(Qt::DisplayRole).toString().contains(m_query, Qt::CaseInsensitive) + && !idx.data(ModulesModel::ModuleNameRole).toString().contains(m_query, Qt::CaseInsensitive)) { + return false; + } + } + + if (m_statusFilter != KDEDConfig::UnknownStatus) { + const auto status = static_cast(idx.data(ModulesModel::StatusRole).toInt()); + if (m_statusFilter != status) { + return false; + } + } + + return true; +} diff --git a/kcms/kded/kcmkded.h b/kcms/kded/kcmkded.h --- a/kcms/kded/kcmkded.h +++ b/kcms/kded/kcmkded.h @@ -1,5 +1,6 @@ /* This file is part of the KDE project Copyright (C) 2002 Daniel Molkentin + Copyright (C) 2020 Kai Uwe Broulik This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public @@ -19,50 +20,87 @@ #ifndef KCMKDED_H #define KCMKDED_H -#include +#include -#include +class QDBusServiceWatcher; -class QPushButton; -class QTreeWidget; -class QTreeWidgetItem; class KConfig; class KPluginMetaData; -Q_DECLARE_LOGGING_CATEGORY(KCM_KDED) +class ModulesModel; +class FilterProxyModel; -class KDEDConfig : public KCModule +class OrgKdeKded5Interface; + +namespace org { + namespace kde { + using kded5 = ::OrgKdeKded5Interface; + } +} + +class KDEDConfig : public KQuickAddons::ConfigModule { -Q_OBJECT + Q_OBJECT + + Q_PROPERTY(ModulesModel *model READ model CONSTANT) + Q_PROPERTY(FilterProxyModel *filteredModel READ filteredModel CONSTANT) + + Q_PROPERTY(bool kdedRunning READ kdedRunning NOTIFY kdedRunningChanged) + public: - explicit KDEDConfig(QWidget* parent, const QVariantList& foo = QVariantList()); + explicit KDEDConfig(QObject* parent, const QVariantList& foo = QVariantList()); ~KDEDConfig() override {} - void load() override; - void save() override; - void defaults() override; + enum ModuleType { + UnknownType = -1, + AutostartType, + OnDemandType + }; + Q_ENUM(ModuleType) + + enum ModuleStatus { + UnknownStatus = -1, + NotRunning, + Running + }; + Q_ENUM(ModuleStatus) + + ModulesModel *model() const; + FilterProxyModel *filteredModel() const; + + bool kdedRunning() const; -protected Q_SLOTS: - void slotReload(); - void slotStartService(); - void slotStopService(); - void slotServiceRunningToggled(); - void slotStartupItemSelected(); - void slotLodItemSelected(); - void slotItemChecked(QTreeWidgetItem *item, int column); - void getServiceStatus(); + Q_INVOKABLE void startModule(const QString &moduleName); + Q_INVOKABLE void stopModule(const QString &moduleName); - bool autoloadEnabled(KConfig *config, const KPluginMetaData &filename); - void setAutoloadEnabled(KConfig *config, const KPluginMetaData &filename, bool b); + void load() override; + void save() override; + void defaults() override; + +signals: + void kdedRunningChanged(); + + void errorMessage(const QString &errorString); + void showSelfDisablingModulesHint(); + void showRunningModulesChangedAfterSaveHint(); private: - QTreeWidget *_lvLoD; - QTreeWidget *_lvStartup; - QPushButton *_pbStart; - QPushButton *_pbStop; - - QString RUNNING; - QString NOT_RUNNING; + void setKdedRunning(bool kdedRunning); + + void getModuleStatus(); + void startOrStopModule(const QString &moduleName, ModuleStatus status /*better than a bool*/); + + ModulesModel *m_model; + FilterProxyModel *m_filteredModel; + + org::kde::kded5 *m_kdedInterface; + + QDBusServiceWatcher *m_kdedWatcher; + bool m_kdedRunning = false; + + QString m_lastStartedModule; + QStringList m_runningModulesBeforeReconfigure; + }; #endif // KCMKDED_H diff --git a/kcms/kded/kcmkded.cpp b/kcms/kded/kcmkded.cpp --- a/kcms/kded/kcmkded.cpp +++ b/kcms/kded/kcmkded.cpp @@ -1,6 +1,7 @@ // vim: noexpandtab shiftwidth=4 tabstop=4 /* This file is part of the KDE project Copyright (C) 2002 Daniel Molkentin + Copyright (C) 2020 Kai Uwe Broulik This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public @@ -20,486 +21,251 @@ #include "kcmkded.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include +#include "debug.h" +#include +#include +#include +#include + +#include +#include #include #include #include #include #include #include -K_PLUGIN_FACTORY(KDEDFactory, - registerPlugin(); - ) -K_EXPORT_PLUGIN(KDEDFactory("kcmkded")) - -Q_LOGGING_CATEGORY(KCM_KDED, "kcm_kded") +#include +#include "modulesmodel.h" +#include "filterproxymodel.h" -enum OnDemandColumns -{ - OnDemandService = 0, - OnDemandStatus = 1, - OnDemandDescription = 2 -}; - -enum StartupColumns -{ - StartupUse = 0, - StartupService = 1, - StartupStatus = 2, - StartupDescription = 3 -}; +#include "kded_interface.h" +K_PLUGIN_FACTORY_WITH_JSON(KCMStyleFactory, "kcmkded.json", registerPlugin();) +static const QString s_kdedServiceName = QStringLiteral("org.kde.kded5"); -static const int LibraryRole = Qt::UserRole + 1; - -KDEDConfig::KDEDConfig(QWidget* parent, const QVariantList &) : - KCModule( parent ) +KDEDConfig::KDEDConfig(QObject* parent, const QVariantList &args) + : KQuickAddons::ConfigModule(parent, args) + , m_model(new ModulesModel(this)) + , m_filteredModel(new FilterProxyModel(this)) + , m_kdedInterface(new org::kde::kded5(s_kdedServiceName, + QStringLiteral("/kded"), + QDBusConnection::sessionBus())) + , m_kdedWatcher(new QDBusServiceWatcher(s_kdedServiceName, + QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForOwnerChange, this)) { - KAboutData *about = - new KAboutData( I18N_NOOP( "kcmkded" ), i18n( "KDE Service Manager" ), - QStringLiteral("1.0"), QString(), KAboutLicense::GPL, - i18n( "(c) 2002 Daniel Molkentin" ) ); + qmlRegisterUncreatableType("org.kde.private.kcms.style", 1, 0, "KCM", QStringLiteral("Cannot create instances of KCM")); + // FIXME Qt 5.14 qmlRegisterAnonymousType + qmlRegisterType(); + qmlRegisterType(); + + KAboutData *about = new KAboutData(QStringLiteral("kcm5_kded"), i18n("Background Services"), + QStringLiteral("2.0"), QString(), KAboutLicense::GPL, + i18n("(c) 2002 Daniel Molkentin, (c) 2020 Kai Uwe Broulik") + ); about->addAuthor(i18n("Daniel Molkentin"), QString(),QStringLiteral("molkentin@kde.org")); - setAboutData( about ); - setButtons(Apply|Default|Help); - setQuickHelp( i18n("

Service Manager

This module allows you to have an overview of all plugins of the " - "KDE Daemon, also referred to as KDE Services. Generally, there are two types of service:

" - "
  • Services invoked at startup
  • Services called on demand
" - "

The latter are only listed for convenience. The startup services can be started and stopped. " - "In Administrator mode, you can also define whether services should be loaded at startup.

" - "

Use this with care: some services are vital for Plasma; do not deactivate services if you" - " do not know what you are doing.

")); - - RUNNING = i18n("Running")+' '; - NOT_RUNNING = i18n("Not running")+' '; - - QVBoxLayout *lay = new QVBoxLayout( this ); - lay->setContentsMargins( 0, 0, 0, 0 ); - - QGroupBox *gb = new QGroupBox( i18n("Load-on-Demand Services"), this ); - gb->setWhatsThis( i18n("This is a list of available KDE services which will " - "be started on demand. They are only listed for convenience, as you " - "cannot manipulate these services.")); - lay->addWidget( gb ); - - QVBoxLayout *gblay = new QVBoxLayout( gb ); - - _lvLoD = new QTreeWidget( gb ); - QStringList cols; - cols.append( i18n("Service") ); - cols.append( i18n("Status") ); - cols.append( i18n("Description") ); - _lvLoD->setHeaderLabels( cols ); - _lvLoD->setAllColumnsShowFocus(true); - _lvLoD->setRootIsDecorated( false ); - _lvLoD->setSortingEnabled(true); - _lvLoD->sortByColumn(OnDemandService, Qt::AscendingOrder); - _lvLoD->header()->setStretchLastSection(true); - gblay->addWidget( _lvLoD ); - - gb = new QGroupBox( i18n("Startup Services"), this ); - gb->setWhatsThis( i18n("This shows all KDE services that can be loaded " - "on Plasma startup. Checked services will be invoked on next startup. " - "Be careful with deactivation of unknown services.")); - lay->addWidget( gb ); - - gblay = new QVBoxLayout( gb ); - - _lvStartup = new QTreeWidget( gb ); - cols.clear(); - cols.append( i18n("Use") ); - cols.append( i18n("Service") ); - cols.append( i18n("Status") ); - cols.append( i18n("Description") ); - _lvStartup->setHeaderLabels( cols ); - _lvStartup->setAllColumnsShowFocus(true); - _lvStartup->setRootIsDecorated( false ); - _lvStartup->setSortingEnabled(true); - _lvStartup->sortByColumn(StartupService, Qt::AscendingOrder); - _lvStartup->header()->setStretchLastSection(true); - gblay->addWidget( _lvStartup ); - - QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Horizontal, gb); - _pbStart = buttonBox->addButton( i18n("Start") , QDialogButtonBox::ActionRole ); - _pbStop = buttonBox->addButton( i18n("Stop") , QDialogButtonBox::ActionRole ); - gblay->addWidget( buttonBox ); - - _pbStart->setEnabled( false ); - _pbStop->setEnabled( false ); - - connect(_pbStart, &QPushButton::clicked, this, &KDEDConfig::slotStartService); - connect(_pbStop, &QPushButton::clicked, this, &KDEDConfig::slotStopService); - connect(_lvLoD, &QTreeWidget::itemSelectionChanged, this, &KDEDConfig::slotLodItemSelected); - connect(_lvStartup, &QTreeWidget::itemSelectionChanged, this, &KDEDConfig::slotStartupItemSelected); - connect(_lvStartup, &QTreeWidget::itemChanged, this, &KDEDConfig::slotItemChecked); - + about->addAuthor(i18n("Kai Uwe Broulik"), QString(),QStringLiteral("kde@broulik.de")); + setAboutData(about); + setButtons(Apply|Default|Help); + + m_filteredModel->setSourceModel(m_model); + + connect(m_model, &ModulesModel::autoloadedModulesChanged, this, [this] { + setNeedsSave(true); + }); + + connect(m_kdedWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, + [this](const QString &service, const QString &oldOwner, const QString &newOwner) { + Q_UNUSED(service) + Q_UNUSED(oldOwner) + setKdedRunning(!newOwner.isEmpty()); + }); + setKdedRunning(QDBusConnection::sessionBus().interface()->isServiceRegistered(s_kdedServiceName)); } -QString setModuleGroup(const KPluginMetaData &module) +ModulesModel *KDEDConfig::model() const { - return QStringLiteral("Module-%1").arg(module.pluginId()); + return m_model; } -bool KDEDConfig::autoloadEnabled(KConfig *config, const KPluginMetaData &module) +FilterProxyModel *KDEDConfig::filteredModel() const { - KConfigGroup cg(config, setModuleGroup(module)); - return cg.readEntry("autoload", true); + return m_filteredModel; } -void KDEDConfig::setAutoloadEnabled(KConfig *config, const KPluginMetaData &module, bool b) +bool KDEDConfig::kdedRunning() const { - KConfigGroup cg(config, setModuleGroup(module)); - return cg.writeEntry("autoload", b); + return m_kdedRunning; } -// This code was copied from kded.cpp -// TODO: move this KCM to the KDED framework and share the code? -static QVector availableModules() +void KDEDConfig::setKdedRunning(bool kdedRunning) { - QVector plugins = KPluginLoader::findPlugins(QStringLiteral("kf5/kded")); - QSet moduleIds; - foreach (const KPluginMetaData &md, plugins) { - moduleIds.insert(md.pluginId()); - } - // also search for old .desktop based kded modules - KPluginInfo::List oldStylePlugins = KPluginInfo::fromServices(KServiceTypeTrader::self()->query(QStringLiteral("KDEDModule"))); - foreach (const KPluginInfo &info, oldStylePlugins) { - if (moduleIds.contains(info.pluginName())) { - qCWarning(KCM_KDED).nospace() << "kded module " << info.pluginName() << " has already been found using " - "JSON metadata, please don't install the now unneeded .desktop file (" << info.entryPath() << ")."; - } else { - qCDebug(KCM_KDED).nospace() << "kded module " << info.pluginName() << " still uses .desktop files (" - << info.entryPath() << "). Please port it to JSON metadata."; - plugins.append(info.toMetaData()); - } - } - return plugins; + if (m_kdedRunning == kdedRunning) { + return; + } + + m_kdedRunning = kdedRunning; + emit kdedRunningChanged(); + + if (kdedRunning) { + getModuleStatus(); + } else { + m_model->setRunningModulesKnown(false); + } } -// this code was copied from kded.cpp -static bool isModuleLoadedOnDemand(const KPluginMetaData &module) +void KDEDConfig::startModule(const QString &moduleName) { - bool loadOnDemand = true; - // use toVariant() since it could be string or bool in the json and QJsonObject does not convert - QVariant p = module.rawData().value(QStringLiteral("X-KDE-Kded-load-on-demand")).toVariant(); - if (p.isValid() && p.canConvert() && (p.toBool() == false)) { - loadOnDemand = false; - } - return loadOnDemand; + startOrStopModule(moduleName, Running); } -void KDEDConfig::load() +void KDEDConfig::stopModule(const QString &moduleName) { - KConfig kdedrc(QStringLiteral("kded5rc"), KConfig::NoGlobals); - - _lvStartup->clear(); - _lvLoD->clear(); - - QTreeWidgetItem* treeitem = nullptr; - const auto modules = availableModules(); - for (const KPluginMetaData &mod : modules) { - QString servicePath = mod.metaDataFileName(); - - // autoload defaults to false if it is not found - const bool autoload = mod.rawData().value(QStringLiteral("X-KDE-Kded-autoload")).toVariant().toBool(); - // keep estimating dbusModuleName in sync with KDEDModule (kdbusaddons) and kded (kded) - // currently (KF5) the module name in the D-Bus object path is set by the pluginId - const QString dbusModuleName = mod.pluginId(); - qCDebug(KCM_KDED) << "reading kded info from" << servicePath << "autoload =" << autoload << "dbus module name =" << dbusModuleName; - - // The logic has to be identical to Kded::initModules. - // They interpret X-KDE-Kded-autoload as false if not specified - // X-KDE-Kded-load-on-demand as true if not specified - if (autoload) { - treeitem = new QTreeWidgetItem(); - treeitem->setCheckState(StartupUse, autoloadEnabled(&kdedrc, mod) ? Qt::Checked : Qt::Unchecked); - treeitem->setText(StartupService, mod.name()); - treeitem->setText(StartupDescription, mod.description()); - treeitem->setText(StartupStatus, NOT_RUNNING); - treeitem->setData(StartupService, LibraryRole, dbusModuleName); - _lvStartup->addTopLevelItem(treeitem); - } - else if (isModuleLoadedOnDemand(mod)) { - treeitem = new QTreeWidgetItem(); - treeitem->setText(OnDemandService, mod.name() ); - treeitem->setText(OnDemandDescription, mod.description()); - treeitem->setText(OnDemandStatus, NOT_RUNNING); - treeitem->setData(OnDemandService, LibraryRole, dbusModuleName); - _lvLoD->addTopLevelItem(treeitem); - } - else { - qCWarning(KCM_KDED) << "kcmkded: Module " << mod.name() << "from file" << mod.metaDataFileName() << " not loaded on demand or startup! Skipping."; - } - } - - _lvStartup->resizeColumnToContents(StartupUse); - _lvStartup->resizeColumnToContents(StartupService); - _lvStartup->resizeColumnToContents(StartupStatus); - - _lvLoD->resizeColumnToContents(OnDemandService); - _lvLoD->resizeColumnToContents(OnDemandStatus); - - getServiceStatus(); - - emit changed(false); + startOrStopModule(moduleName, NotRunning); } -void KDEDConfig::save() +void KDEDConfig::startOrStopModule(const QString &moduleName, ModuleStatus status) { - KConfig kdedrc(QStringLiteral("kded5rc"), KConfig::NoGlobals); - - const auto modules = availableModules(); - for (const KPluginMetaData &mod : modules) { - qCDebug(KCM_KDED) << "saving settings for kded module" << mod.pluginId(); - // autoload defaults to false if it is not found - const bool autoload = mod.rawData().value(QStringLiteral("X-KDE-Kded-autoload")).toVariant().toBool(); - if (autoload) { - const QString libraryName = mod.pluginId(); - int count = _lvStartup->topLevelItemCount(); - for(int i = 0; i < count; ++i) { - QTreeWidgetItem *treeitem = _lvStartup->topLevelItem(i); - if ( treeitem->data(StartupService, LibraryRole ).toString() == libraryName) { - // we found a match, now compare and see what changed - setAutoloadEnabled(&kdedrc, mod, treeitem->checkState( StartupUse ) == Qt::Checked); - break; - } - } - } - } - kdedrc.sync(); - - emit changed(false); - - QDBusInterface kdedInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5")); - kdedInterface.call(QStringLiteral("reconfigure")); - QTimer::singleShot(0, this, &KDEDConfig::slotServiceRunningToggled); + auto call = (status == NotRunning ? m_kdedInterface->unloadModule(moduleName) + : m_kdedInterface->loadModule(moduleName)); + + QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(call, this); + connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this, moduleName, status](QDBusPendingCallWatcher *watcher) { + QDBusPendingReply reply = *watcher; + watcher->deleteLater(); + + if (reply.isError()) { + if (status == NotRunning) { + emit errorMessage(i18n("Failed to stop service: %1", reply.error().message())); + } else { + emit errorMessage(i18n("Failed to start service: %1", reply.error().message())); + } + return; + } + + if (!reply.value()) { + if (status == NotRunning) { + emit errorMessage(i18n("Failed to stop service.")); + } else { + emit errorMessage(i18n("Failed to start service.")); + } + return; + } + + qCDebug(KCM_KDED) << "Successfully" << (status == Running ? "started" : "stopped") << moduleName; + if (status == Running) { + m_lastStartedModule = moduleName; + } else { + m_lastStartedModule.clear(); + } + getModuleStatus(); + }); } - -void KDEDConfig::defaults() +void KDEDConfig::getModuleStatus() { - int count = _lvStartup->topLevelItemCount(); - for( int i = 0; i < count; ++i ) - { - _lvStartup->topLevelItem( i )->setCheckState( StartupUse, Qt::Checked ); - } - - getServiceStatus(); - - emit changed(true); + auto call = m_kdedInterface->loadedModules(); + + QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(call, this); + connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { + QDBusPendingReply reply = *watcher; + watcher->deleteLater(); + + if (reply.isError()) { + qCWarning(KCM_KDED) << "Failed to get loaded modules" << reply.error().name() << reply.error().message(); + return; + } + + QStringList runningModules = reply.value(); + m_model->setRunningModules(runningModules); + m_model->setRunningModulesKnown(true); + + // Check if the user just tried starting a module that then disabled itself again. + // Some kded modules disable themselves on start when they deem themselves unnecessary + // based on some configuration independ of kded or the current environment. + // At least provide some feedback and not leave the user wondering why the service doesn't start. + if (!m_lastStartedModule.isEmpty() && !runningModules.contains(m_lastStartedModule)) { + emit showSelfDisablingModulesHint(); + } + m_lastStartedModule.clear(); + + // Check if any modules got started/stopped as a result of reloading kded + if (!m_runningModulesBeforeReconfigure.isEmpty()) { + std::sort(m_runningModulesBeforeReconfigure.begin(), m_runningModulesBeforeReconfigure.end()); + std::sort(runningModules.begin(), runningModules.end()); + + if (m_runningModulesBeforeReconfigure != runningModules) { + emit showRunningModulesChangedAfterSaveHint(); + } + } + m_runningModulesBeforeReconfigure.clear(); + }); } - -void KDEDConfig::getServiceStatus() +void KDEDConfig::load() { - QStringList modules; - QDBusInterface kdedInterface( QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5") ); - QDBusReply reply = kdedInterface.call( QStringLiteral("loadedModules") ); - - if ( reply.isValid() ) { - modules = reply.value(); - } - else { - _lvLoD->setEnabled( false ); - _lvStartup->setEnabled( false ); - KMessageBox::error(this, i18n("Unable to contact KDED.")); - return; - } - qCDebug(KCM_KDED) << "Loaded kded modules:" << modules; - - // Initialize - int count = _lvLoD->topLevelItemCount(); - for( int i = 0; i < count; ++i ) - _lvLoD->topLevelItem( i )->setText( OnDemandStatus, NOT_RUNNING ); - count = _lvStartup->topLevelItemCount(); - for( int i = 0; i < count; ++i ) - _lvStartup->topLevelItem( i )->setText( StartupStatus, NOT_RUNNING ); - - // Fill - foreach( const QString& module, modules ) - { - bool found = false; - - count = _lvLoD->topLevelItemCount(); - for( int i = 0; i < count; ++i ) - { - QTreeWidgetItem *treeitem = _lvLoD->topLevelItem( i ); - if ( treeitem->data( OnDemandService, LibraryRole ).toString() == module ) - { - treeitem->setText( OnDemandStatus, RUNNING ); - found = true; - break; - } - } - - count = _lvStartup->topLevelItemCount(); - for( int i = 0; i < count; ++i ) - { - QTreeWidgetItem *treeitem = _lvStartup->topLevelItem( i ); - if ( treeitem->data( StartupService, LibraryRole ).toString() == module ) - { - treeitem->setText( StartupStatus, RUNNING ); - found = true; - break; - } - } - - if (!found) - { - qCDebug(KCM_KDED) << "Could not relate module " << module; -#ifndef NDEBUG - qCDebug(KCM_KDED) << "Candidates were:"; - count = _lvLoD->topLevelItemCount(); - for( int i = 0; i < count; ++i ) - { - QTreeWidgetItem *treeitem = _lvLoD->topLevelItem( i ); - qCDebug(KCM_KDED) << treeitem->data( OnDemandService, LibraryRole ).toString(); - } - - count = _lvStartup->topLevelItemCount(); - for( int i = 0; i < count; ++i ) - { - QTreeWidgetItem *treeitem = _lvStartup->topLevelItem( i ); - qCDebug(KCM_KDED) << treeitem->data( StartupService, LibraryRole ).toString(); - } -#endif - } - - } + m_model->load(); + setNeedsSave(false); } -void KDEDConfig::slotReload() +void KDEDConfig::save() { - QString current; - if ( !_lvStartup->selectedItems().isEmpty() ) - current = _lvStartup->selectedItems().at(0)->data( StartupService, LibraryRole ).toString(); - load(); - if ( !current.isEmpty() ) - { - int count = _lvStartup->topLevelItemCount(); - for( int i = 0; i < count; ++i ) - { - QTreeWidgetItem *treeitem = _lvStartup->topLevelItem( i ); - if ( treeitem->data( StartupService, LibraryRole ).toString() == current ) - { - _lvStartup->setCurrentItem( treeitem, 0, QItemSelectionModel::ClearAndSelect ); - break; - } - } - } -} + KConfig kdedrc(QStringLiteral("kded5rc"), KConfig::NoGlobals); -void KDEDConfig::slotStartupItemSelected() -{ - if ( _lvStartup->selectedItems().isEmpty() ) { - // Disable the buttons - _pbStart->setEnabled( false ); - _pbStop->setEnabled( false ); - return; - } - - // Deselect a currently selected element in the "load on demand" treeview - _lvLoD->setCurrentItem(nullptr, 0, QItemSelectionModel::Clear); - - QTreeWidgetItem *item = _lvStartup->selectedItems().at(0); - if ( item->text(StartupStatus) == RUNNING ) { - _pbStart->setEnabled( false ); - _pbStop->setEnabled( true ); - } - else if ( item->text(StartupStatus) == NOT_RUNNING ) { - _pbStart->setEnabled( true ); - _pbStop->setEnabled( false ); - } - else // Error handling, better do nothing - { - _pbStart->setEnabled( false ); - _pbStop->setEnabled( false ); - } - - getServiceStatus(); -} + for (int i = 0; i < m_model->rowCount(); ++i) { + const QModelIndex idx = m_model->index(i, 0); -void KDEDConfig::slotLodItemSelected() -{ - if ( _lvLoD->selectedItems().isEmpty() ) - return; + const auto type = static_cast(idx.data(ModulesModel::TypeRole).toInt()); + if (type != AutostartType) { + continue; + } - // Deselect a currently selected element in the "load on startup" treeview - _lvStartup->setCurrentItem(nullptr, 0, QItemSelectionModel::Clear); -} + const QString moduleName = idx.data(ModulesModel::ModuleNameRole).toString(); -void KDEDConfig::slotServiceRunningToggled() -{ - getServiceStatus(); - slotStartupItemSelected(); -} + const bool autoloadEnabled = idx.data(ModulesModel::AutoloadEnabledRole).toBool(); + KConfigGroup cg(&kdedrc, QStringLiteral("Module-%1").arg(moduleName)); + cg.writeEntry("autoload", autoloadEnabled); + } -void KDEDConfig::slotStartService() -{ - QString service = _lvStartup->selectedItems().at(0)->data( StartupService, LibraryRole ).toString(); - - QDBusInterface kdedInterface( QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"),QStringLiteral("org.kde.kded5") ); - QDBusReply reply = kdedInterface.call( QStringLiteral("loadModule"), service ); - - if ( reply.isValid() ) { - if ( reply.value() ) - slotServiceRunningToggled(); - else - KMessageBox::error(this, "" + i18n("Unable to start server %1.", service) + ""); - } - else { - KMessageBox::error(this, "" + i18n("Unable to start service %1.

Error: %2", - service, reply.error().message()) + "
" ); - } -} + kdedrc.sync(); -void KDEDConfig::slotStopService() -{ - QString service = _lvStartup->selectedItems().at(0)->data( StartupService, LibraryRole ).toString(); - qCDebug(KCM_KDED) << "Stopping: " << service; - - QDBusInterface kdedInterface( QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5") ); - QDBusReply reply = kdedInterface.call( QStringLiteral("unloadModule"), service ); - - if ( reply.isValid() ) { - if ( reply.value() ) - slotServiceRunningToggled(); - else - KMessageBox::error(this, "" + i18n("Unable to stop service %1.", service) + ""); - } - else { - KMessageBox::error(this, "" + i18n("Unable to stop service %1.

Error: %2", - service, reply.error().message()) + "
" ); - } + setNeedsSave(false); + + m_runningModulesBeforeReconfigure = m_model->runningModules(); + + // Is all of this really necessary? I would also think it to be fire and forget... + // Only if changing autoload for a module may load/unload it, otherwise there's no point. + // Autoload doesn't affect a running session and reloading the running modules is also useless then. + auto call = m_kdedInterface->reconfigure(); + QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(call, this); + connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { + QDBusPendingReply reply = *watcher; + watcher->deleteLater(); + + if (reply.isError()) { + emit errorMessage(i18n("Failed to notify KDE Service Manager (kded5) of saved changed: %1", reply.error().message())); + return; + } + + qCDebug(KCM_KDED) << "Successfully reconfigured kded"; + getModuleStatus(); + }); } -void KDEDConfig::slotItemChecked(QTreeWidgetItem*, int column) +void KDEDConfig::defaults() { - // We only listen to changes the user did. - if (column==StartupUse) { - emit changed(true); - } + for (int i = 0; i < m_model->rowCount(); ++i) { + const QModelIndex idx = m_model->index(i, 0); + if (m_model->setData(idx, true, ModulesModel::AutoloadEnabledRole)) { + setNeedsSave(true); + } + } } #include "kcmkded.moc" diff --git a/kcms/kded/kcmkded.desktop b/kcms/kded/kcmkded.desktop --- a/kcms/kded/kcmkded.desktop +++ b/kcms/kded/kcmkded.desktop @@ -24,7 +24,7 @@ Name[en_GB]=Background Services Name[es]=Servicios en segundo plano Name[et]=Taustateenused -Name[eu]=Hondoko zerbitzuak +Name[eu]=Atzeko planoko zerbitzuak Name[fi]=Taustapalvelut Name[fr]=Services d'arrière plan Name[gl]=Servizos en segundo plano @@ -56,51 +56,21 @@ Name[x-test]=xxBackground Servicesxx Name[zh_CN]=后台服务 Name[zh_TW]=背景服務 -Comment=Background Services -Comment[ar]=خدمات الخلفيّة -Comment[ast]=Servicios en segundu planu -Comment[bs]=Pozadinski servisi -Comment[ca]=Serveis en segon pla -Comment[ca@valencia]=Serveis en segon pla -Comment[cs]=Služby na pozadí -Comment[da]=Baggrundstjenester -Comment[de]=Hintergrunddienste -Comment[el]=Υπηρεσίες παρασκηνίου -Comment[en_GB]=Background Services -Comment[es]=Servicios en segundo plano -Comment[et]=Taustateenused -Comment[eu]=Hondoko zerbitzuak -Comment[fi]=Taustapalvelut -Comment[fr]=Services d'arrière plan -Comment[gl]=Servizos en segundo plano -Comment[he]=שירותי רקע -Comment[hu]=Háttérszolgáltatások -Comment[id]=Layanan Latarbelakang -Comment[is]=Bakgrunnsþjónustur -Comment[it]=Servizi in background -Comment[ko]=배경 서비스 -Comment[lt]=Foninės tarnybos -Comment[nb]=Bakgrunnstjenester -Comment[nds]=Achtergrunddeensten -Comment[nl]=Achtergrondservices -Comment[nn]=Bakgrunnstenester -Comment[pa]=ਬੈਕਗਰਾਊਂਡ ਸੇਵਾਵਾਂ -Comment[pl]=Usługi w tle -Comment[pt]=Serviços de Segundo Plano -Comment[pt_BR]=Serviços de segundo plano +Comment=Configure background services +Comment[ca]=Configura els serveis en segon pla +Comment[es]=Configurar los servicios en segundo plano +Comment[et]=Taustateenuste seadistamine +Comment[eu]=Konfiguratu atzeko planoko zerbitzuak +Comment[lt]=Konfigūruoti fonines tarnybas +Comment[nl]=Achtergrondservices configureren +Comment[pt]=Configurar os serviços de segundo plano +Comment[pt_BR]=Configurar os serviços de segundo plano Comment[ru]=Настройка служб KDE -Comment[sk]=Služby pozadia -Comment[sl]=Storitve v ozadju -Comment[sr]=Позадински сервиси -Comment[sr@ijekavian]=Позадински сервиси -Comment[sr@ijekavianlatin]=Pozadinski servisi -Comment[sr@latin]=Pozadinski servisi -Comment[sv]=Bakgrundstjänster -Comment[tr]=Arkaplan Servisleri -Comment[uk]=Фонові служби -Comment[x-test]=xxBackground Servicesxx -Comment[zh_CN]=后台服务 -Comment[zh_TW]=背景服務 +Comment[sk]=Nastaviť služby na pozadí +Comment[sv]=Anpassa bakgrundstjänster +Comment[uk]=Налаштування фонових служб +Comment[x-test]=xxConfigure background servicesxx +Comment[zh_TW]=設定背景服務 X-KDE-Keywords=KDED,Daemon,Services X-KDE-Keywords[ast]=KDED,Degorriu,Servicios X-KDE-Keywords[bg]=KDED,Daemon,Services,Услуги diff --git a/kcms/kded/modulesmodel.h b/kcms/kded/modulesmodel.h new file mode 100644 --- /dev/null +++ b/kcms/kded/modulesmodel.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 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 "kcmkded.h" + +struct ModulesModelData +{ + QString display; + QString description; + KDEDConfig::ModuleType type; + bool autoloadEnabled; + QString moduleName; +}; +Q_DECLARE_TYPEINFO(ModulesModelData, Q_MOVABLE_TYPE); + +class ModulesModel : public QAbstractListModel +{ + Q_OBJECT + +public: + ModulesModel(QObject *parent); + ~ModulesModel() override; + + enum Roles { + DescriptionRole = Qt::UserRole + 1, + TypeRole, + AutoloadEnabledRole, + StatusRole, + ModuleNameRole + }; + + int rowCount(const QModelIndex &parent = QModelIndex()) 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; + + void load(); + + bool runningModulesKnown() const; + void setRunningModulesKnown(bool known); + + QStringList runningModules() const; + void setRunningModules(const QStringList &runningModules); + +signals: + void autoloadedModulesChanged(); + +private: + QVector m_data; + + bool m_runningModulesKnown = false; + QStringList m_runningModules; + +}; diff --git a/kcms/kded/modulesmodel.cpp b/kcms/kded/modulesmodel.cpp new file mode 100644 --- /dev/null +++ b/kcms/kded/modulesmodel.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2020 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 "modulesmodel.h" + +#include + +#include +#include +#include +#include +#include + +#include + +#include "debug.h" + +ModulesModel::ModulesModel(QObject *parent) : QAbstractListModel(parent) +{ + +} + +ModulesModel::~ModulesModel() = default; + +int ModulesModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return m_data.count(); +} + +QVariant ModulesModel::data(const QModelIndex &index, int role) const +{ + if (!checkIndex(index)) { + return QVariant(); + } + + const auto &item = m_data.at(index.row()); + + switch (role) { + case Qt::DisplayRole: return item.display; + case DescriptionRole: return item.description; + case TypeRole: return item.type; + case AutoloadEnabledRole: + if (item.type == KDEDConfig::AutostartType) { + return item.autoloadEnabled; + } + return QVariant(); + case StatusRole: { + if (!m_runningModulesKnown) { + return KDEDConfig::UnknownStatus; + } + if (m_runningModules.contains(item.moduleName)) { + return KDEDConfig::Running; + } + return KDEDConfig::NotRunning; + } + case ModuleNameRole: return item.moduleName; + } + + return QVariant(); +} + +bool ModulesModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + bool dirty = false; + + if (!checkIndex(index)) { + return dirty; + } + + auto &item = m_data[index.row()]; + + switch (role) { + case AutoloadEnabledRole: + const bool autoloadEnabled = value.toBool(); + if (item.type == KDEDConfig::AutostartType + && item.autoloadEnabled != autoloadEnabled) { + item.autoloadEnabled = value.toBool(); + dirty = true; + + emit autoloadedModulesChanged(); + } + break; + } + + if (dirty) { + emit dataChanged(index, index, {role}); + } + + return dirty; +} + +QHash ModulesModel::roleNames() const +{ + return { + {Qt::DisplayRole, QByteArrayLiteral("display")}, + {DescriptionRole, QByteArrayLiteral("description")}, + {TypeRole, QByteArrayLiteral("type")}, + {AutoloadEnabledRole, QByteArrayLiteral("autoloadEnabled")}, + {StatusRole, QByteArrayLiteral("status")}, + {ModuleNameRole, QByteArrayLiteral("moduleName")}, + }; +} + +// This code was copied from kded.cpp +// TODO: move this KCM to the KDED framework and share the code? +static QVector availableModules() +{ + QVector plugins = KPluginLoader::findPlugins(QStringLiteral("kf5/kded")); + QSet moduleIds; + for (const KPluginMetaData &md : qAsConst(plugins)) { + moduleIds.insert(md.pluginId()); + } + // also search for old .desktop based kded modules + const KPluginInfo::List oldStylePlugins = KPluginInfo::fromServices(KServiceTypeTrader::self()->query(QStringLiteral("KDEDModule"))); + for (const KPluginInfo &info : oldStylePlugins) { + if (moduleIds.contains(info.pluginName())) { + qCWarning(KCM_KDED).nospace() << "kded module " << info.pluginName() << " has already been found using " + "JSON metadata, please don't install the now unneeded .desktop file (" << info.entryPath() << ")."; + } else { + qCDebug(KCM_KDED).nospace() << "kded module " << info.pluginName() << " still uses .desktop files (" + << info.entryPath() << "). Please port it to JSON metadata."; + plugins.append(info.toMetaData()); + } + } + return plugins; +} + +// this code was copied from kded.cpp +static bool isModuleLoadedOnDemand(const KPluginMetaData &module) +{ + bool loadOnDemand = true; + // use toVariant() since it could be string or bool in the json and QJsonObject does not convert + QVariant p = module.rawData().value(QStringLiteral("X-KDE-Kded-load-on-demand")).toVariant(); + if (p.isValid() && p.canConvert() && (p.toBool() == false)) { + loadOnDemand = false; + } + return loadOnDemand; +} + +void ModulesModel::load() +{ + beginResetModel(); + + m_data.clear(); + + KConfig kdedrc(QStringLiteral("kded5rc"), KConfig::NoGlobals); + + QStringList knownModules; + + QVector autostartModules; + QVector onDemandModules; + + const auto modules = availableModules(); + for (const KPluginMetaData &module : modules) { + QString servicePath = module.metaDataFileName(); + + // autoload defaults to false if it is not found + const bool autoload = module.rawData().value(QStringLiteral("X-KDE-Kded-autoload")).toVariant().toBool(); + // keep estimating dbusModuleName in sync with KDEDModule (kdbusaddons) and kded (kded) + // currently (KF5) the module name in the D-Bus object path is set by the pluginId + const QString dbusModuleName = module.pluginId(); + qCDebug(KCM_KDED) << "reading kded info from" << servicePath << "autoload =" << autoload << "dbus module name =" << dbusModuleName; + + if (knownModules.contains(dbusModuleName)) { + continue; + } + + knownModules.append(dbusModuleName); + + KConfigGroup cg(&kdedrc, QStringLiteral("Module-%1").arg(dbusModuleName)); + const bool autoloadEnabled = cg.readEntry("autoload", true); + + ModulesModelData data{ + module.name(), + module.description(), + KDEDConfig::UnknownType, + autoloadEnabled, + dbusModuleName + }; + + // The logic has to be identical to Kded::initModules. + // They interpret X-KDE-Kded-autoload as false if not specified + // X-KDE-Kded-load-on-demand as true if not specified + if (autoload) { + data.type = KDEDConfig::AutostartType; + autostartModules << data; + } else if (isModuleLoadedOnDemand(module)) { + data.type = KDEDConfig::OnDemandType; + onDemandModules << data; + } else { + qCWarning(KCM_KDED) << "kcmkded: Module " << module.name() << "from file" << module.metaDataFileName() << " not loaded on demand or startup! Skipping."; + continue; + } + } + + QCollator collator; + // Otherwise "Write" daemon with quotes will be at the top + collator.setIgnorePunctuation(true); + auto sortAlphabetically = [&collator](const ModulesModelData &a, const ModulesModelData &b) { + return collator.compare(a.display, b.display) < 0; + }; + + std::sort(autostartModules.begin(), autostartModules.end(), sortAlphabetically); + std::sort(onDemandModules.begin(), onDemandModules.end(), sortAlphabetically); + + m_data << autostartModules << onDemandModules; + + endResetModel(); +} + +bool ModulesModel::runningModulesKnown() const +{ + return m_runningModulesKnown; +} + +void ModulesModel::setRunningModulesKnown(bool known) +{ + if (m_runningModulesKnown != known) { + m_runningModulesKnown = known; + emit dataChanged(index(0, 0), index(m_data.count() - 1, 0), {StatusRole}); + } +} + +QStringList ModulesModel::runningModules() const +{ + return m_runningModules; +} + +void ModulesModel::setRunningModules(const QStringList &runningModules) +{ + if (m_runningModules == runningModules) { + return; + } + + m_runningModules = runningModules; + if (m_runningModulesKnown) { + emit dataChanged(index(0, 0), index(m_data.count() - 1, 0), {StatusRole}); + } +} diff --git a/kcms/kded/package/contents/ui/main.qml b/kcms/kded/package/contents/ui/main.qml new file mode 100644 --- /dev/null +++ b/kcms/kded/package/contents/ui/main.qml @@ -0,0 +1,269 @@ +/* + * Copyright 2020 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.Controls 2.3 as QtControls +import org.kde.kirigami 2.10 as Kirigami +import org.kde.kcm 1.2 as KCM +import org.kde.private.kcms.style 1.0 as Private + +KCM.ScrollViewKCM { + id: root + + KCM.ConfigModule.quickHelp: i18n("

This module allows you to have an overview of all plugins of the +KDE Daemon, also referred to as KDE Services. Generally, there are two types of service:

+
  • Services invoked at startup
  • Services called on demand
+

The latter are only listed for convenience. The startup services can be started and stopped. +You can also define whether services should be loaded at startup.

+

Use this with care: some services are vital for Plasma; do not deactivate services if you + do not know what you are doing.

") + + // TODO immutable somehow? + + Binding { + target: kcm.filteredModel + property: "query" + value: searchField.text + } + + Binding { + target: kcm.filteredModel + property: "statusFilter" + value: filterCombo.model[filterCombo.currentIndex].statusFilter + } + + header: ColumnLayout { + Kirigami.InlineMessage { + Layout.fillWidth: true + text: i18n("The background services manager (kded5) is currently not running. Make sure it is installed correctly."); + type: Kirigami.MessageType.Error + showCloseButton: false + visible: !kcm.kdedRunning + } + + Kirigami.InlineMessage { + id: selfDisablingModulesHint + Layout.fillWidth: true + text: i18n("Some services disable themselves again when manually started if they are not useful in the current environment.") + type: Kirigami.MessageType.Information + showCloseButton: true + visible: false + } + + Kirigami.InlineMessage { + id: runningModulesChangedAfterSaveHint + Layout.fillWidth: true + text: i18n("Some services were automatically started/stopped when the background services manager (kded5) was restarted to apply your changes.") + type: Kirigami.MessageType.Information + showCloseButton: true + visible: false + } + + Kirigami.InlineMessage { + id: errorMessage + Layout.fillWidth: true + + type: Kirigami.MessageType.Error + showCloseButton: true + visible: false + + Connections { + target: kcm + onErrorMessage: { + errorMessage.text = errorString; + errorMessage.visible = true; + } + onShowSelfDisablingModulesHint: { + selfDisablingModulesHint.visible = true; + } + onShowRunningModulesChangedAfterSaveHint: { + runningModulesChangedAfterSaveHint.visible = true; + } + } + } + + RowLayout { + Layout.fillWidth: true + + Kirigami.SearchField { + id: searchField + Layout.fillWidth: true + } + + QtControls.ComboBox { + id: filterCombo + textRole: "text" + enabled: kcm.kdedRunning || currentIndex > 0 + model: [ + {text: i18n("All Services"), statusFilter: Private.KCM.UnknownStatus}, + {text: i18nc("List running services", "Running"), statusFilter: Private.KCM.Running}, + {text: i18nc("List not running services", "Not Running"), statusFilter: Private.KCM.NotRunning} + ] + + // 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: ListView { + id: list + clip: true + activeFocusOnTab: true + + model: kcm.filteredModel + + section.property: "type" + section.delegate: Kirigami.ListSectionHeader { + width: list.width + label: { + switch (Number(section)) { + case Private.KCM.AutostartType: return i18n("Startup Services"); + case Private.KCM.OnDemandType: return i18n("Load-on-Demand Services"); + } + } + } + + Component { + id: listDelegateComponent + + Kirigami.AbstractListItem { + id: delegate + // FIXME why does the padding logic to dodge the ScrollBars not work here? + text: model.display + checkable: model.type !== Private.KCM.OnDemandType + checked: model.autoloadEnabled === true + hoverEnabled: checkable + focusPolicy: Qt.ClickFocus + Accessible.description: i18n("Toggle automatically loading this service on startup") + onClicked: { + if (checkable) { + model.autoloadEnabled = !model.autoloadEnabled; + } + } + + Component.onCompleted: { + // Checkable Kirigami.ListItem has blue background which we don't want + // as we have a dedicated CheckBox. Still using those properties for accessibility. + background.color = Qt.binding(function() { + return delegate.highlighted || (delegate.supportsMouseEvents && delegate.pressed) + ? delegate.activeBackgroundColor + : delegate.backgroundColor + }); + } + + contentItem: RowLayout { + QtControls.CheckBox { + id: autoloadCheck + // Keep focus on the delegate + focusPolicy: Qt.NoFocus + checked: delegate.checked + visible: delegate.checkable + onToggled: model.autoloadEnabled = !model.autoloadEnabled + + QtControls.ToolTip { + text: delegate.Accessible.description + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + QtControls.Label { + id: displayLabel + Layout.fillWidth: true + text: delegate.text + elide: Text.ElideRight + textFormat: Text.PlainText + color: (delegate.highlighted || (delegate.pressed && delegate.supportsMouseEvents)) ? delegate.activeTextColor : delegate.textColor + } + + QtControls.Label { + Layout.fillWidth: true + text: model.description + // FIXME do we have a descriptive label component? + opacity: delegate.hovered ? 0.8 : 0.6 + wrapMode: Text.WordWrap + textFormat: Text.PlainText + color: displayLabel.color + } + } + + QtControls.Label { + id: statusLabel + horizontalAlignment: Text.AlignRight + opacity: model.status === Private.KCM.Running ? 1 : delegate.hovered ? 0.8 : 0.6 + color: model.status === Private.KCM.Running ? Kirigami.Theme.positiveTextColor : displayLabel.color + visible: kcm.kdedRunning && model.type !== Private.KCM.OnDemandType + text: { + switch (model.status) { + case Private.KCM.NotRunning: return i18n("Not running"); + case Private.KCM.Running: return i18n("Running"); + } + return ""; + } + } + + QtControls.Button { + icon.name: model.status === Private.KCM.Running ? "media-playback-pause" : "media-playback-start" + visible: kcm.kdedRunning && model.status !== Private.KCM.UnknownStatus && model.type !== Private.KCM.OnDemandType + onClicked: { + errorMessage.visible = false; + + console.log("DELEGATE", delegate) + if (model.status === Private.KCM.Running) { + kcm.stopModule(model.moduleName); + } else { + kcm.startModule(model.moduleName); + } + } + Accessible.name: model.status === Private.KCM.Running ? i18n("Stop Service") : i18n("Start Service") + + QtControls.ToolTip { + text: parent.Accessible.name + } + } + } + } + } + + delegate: Kirigami.DelegateRecycler { + width: list.width + sourceComponent: listDelegateComponent + } + } +} diff --git a/kcms/kded/package/metadata.desktop b/kcms/kded/package/metadata.desktop new file mode 100644 --- /dev/null +++ b/kcms/kded/package/metadata.desktop @@ -0,0 +1,74 @@ +[Desktop Entry] +Name=Background Services +Name[ar]=خدمات الخلفيّة +Name[ast]=Servicios en segundu planu +Name[bs]=Pozadinski servisi +Name[ca]=Serveis en segon pla +Name[ca@valencia]=Serveis en segon pla +Name[cs]=Služby na pozadí +Name[da]=Baggrundstjenester +Name[de]=Hintergrunddienste +Name[el]=Υπηρεσίες παρασκηνίου +Name[en_GB]=Background Services +Name[es]=Servicios en segundo plano +Name[et]=Taustateenused +Name[eu]=Atzeko planoko zerbitzuak +Name[fi]=Taustapalvelut +Name[fr]=Services d'arrière plan +Name[gl]=Servizos en segundo plano +Name[he]=שירותי רקע +Name[hu]=Háttérszolgáltatások +Name[id]=Layanan Latarbelakang +Name[is]=Bakgrunnsþjónustur +Name[it]=Servizi in background +Name[ko]=배경 서비스 +Name[lt]=Foninės tarnybos +Name[nb]=Bakgrunnstjenester +Name[nds]=Achtergrunddeensten +Name[nl]=Achtergrondservices +Name[nn]=Bakgrunnstenester +Name[pa]=ਬੈਕਗਰਾਊਂਡ ਸੇਵਾਵਾਂ +Name[pl]=Usługi w tle +Name[pt]=Serviços de Segundo Plano +Name[pt_BR]=Serviços de segundo plano +Name[ru]=Управление службами +Name[sk]=Služby pozadia +Name[sl]=Storitve v ozadju +Name[sr]=Позадински сервиси +Name[sr@ijekavian]=Позадински сервиси +Name[sr@ijekavianlatin]=Pozadinski servisi +Name[sr@latin]=Pozadinski servisi +Name[sv]=Bakgrundstjänster +Name[tr]=Arkaplan Servisleri +Name[uk]=Фонові служби +Name[x-test]=xxBackground Servicesxx +Name[zh_CN]=后台服务 +Name[zh_TW]=背景服務 +Comment=Configure background services +Comment[ca]=Configura els serveis en segon pla +Comment[es]=Configurar los servicios en segundo plano +Comment[et]=Taustateenuste seadistamine +Comment[eu]=Konfiguratu atzeko planoko zerbitzuak +Comment[lt]=Konfigūruoti fonines tarnybas +Comment[nl]=Achtergrondservices configureren +Comment[pt]=Configurar os serviços de segundo plano +Comment[pt_BR]=Configurar os serviços de segundo plano +Comment[ru]=Настройка служб KDE +Comment[sk]=Nastaviť služby na pozadí +Comment[sv]=Anpassa bakgrundstjänster +Comment[uk]=Налаштування фонових служб +Comment[x-test]=xxConfigure background servicesxx +Comment[zh_TW]=設定背景服務 + +Icon=preferences-system-session-services +Type=Service +X-KDE-PluginInfo-Author=Kai Uwe Broulik +X-KDE-PluginInfo-Email=kdebroulik.de +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-Name=kcm_style +X-KDE-PluginInfo-Version=2.0 +X-KDE-PluginInfo-Website= +X-KDE-ServiceTypes=Plasma/Generic +X-Plasma-API=declarativeappletscript + +X-Plasma-MainScript=ui/main.qml diff --git a/kcms/kfontinst/kio/KioFonts.h b/kcms/kfontinst/kio/KioFonts.h --- a/kcms/kfontinst/kio/KioFonts.h +++ b/kcms/kfontinst/kio/KioFonts.h @@ -58,7 +58,7 @@ void del(const QUrl &url, bool isFile) override; void copy(const QUrl &src, const QUrl &dest, int mode, KIO::JobFlags flags) override; void rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) override; - void statFont(const QUrl &url); + void stat(const QUrl &url) override; void special(const QByteArray &a) override; private: diff --git a/kcms/kfontinst/kio/KioFonts.cpp b/kcms/kfontinst/kio/KioFonts.cpp --- a/kcms/kfontinst/kio/KioFonts.cpp +++ b/kcms/kfontinst/kio/KioFonts.cpp @@ -500,7 +500,7 @@ error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } -void CKioFonts::statFont(const QUrl &url) +void CKioFonts::stat(const QUrl &url) { KFI_DBUG << url; diff --git a/kcms/launch/kcm_launchfeedback.desktop b/kcms/launch/kcm_launchfeedback.desktop --- a/kcms/launch/kcm_launchfeedback.desktop +++ b/kcms/launch/kcm_launchfeedback.desktop @@ -14,6 +14,7 @@ Name=Launch Feedback Name[af]=Lanseer Terugvoer Name[ar]=إطلاق تغذية استرجاعيّة +Name[ast]=Indicador de llanzamientu Name[be@latin]=Vyjaŭleńnie ŭklučeńnia Name[bg]=Обратна връзка Name[bn]=লঞ্চ ফীডব্যাক @@ -99,6 +100,7 @@ Comment=Application Launch Feedback Comment[ar]=تطبيق لإطلاق تغذية استرجاعيّة +Comment[ast]=Indicador del llanzamientu d'aplicaciones Comment[bs]=Povratna veza pokretanja aplikacije Comment[ca]=Reacció en iniciar les aplicacions Comment[ca@valencia]=Reacció en iniciar les aplicacions diff --git a/kcms/launch/package/metadata.desktop b/kcms/launch/package/metadata.desktop --- a/kcms/launch/package/metadata.desktop +++ b/kcms/launch/package/metadata.desktop @@ -2,6 +2,7 @@ Name=Launch Feedback Name[af]=Lanseer Terugvoer Name[ar]=إطلاق تغذية استرجاعيّة +Name[ast]=Indicador de llanzamientu Name[be@latin]=Vyjaŭleńnie ŭklučeńnia Name[bg]=Обратна връзка Name[bn]=লঞ্চ ফীডব্যাক @@ -87,6 +88,7 @@ Comment=Application Launch Feedback Comment[ar]=تطبيق لإطلاق تغذية استرجاعيّة +Comment[ast]=Indicador del llanzamientu d'aplicaciones Comment[bs]=Povratna veza pokretanja aplikacije Comment[ca]=Reacció en iniciar les aplicacions Comment[ca@valencia]=Reacció en iniciar les aplicacions diff --git a/kcms/nightcolor/kcm_nightcolor.desktop b/kcms/nightcolor/kcm_nightcolor.desktop --- a/kcms/nightcolor/kcm_nightcolor.desktop +++ b/kcms/nightcolor/kcm_nightcolor.desktop @@ -31,6 +31,7 @@ Name[hu]=Éjszakai színek Name[id]=Warna Malam Name[it]=Colore notturno +Name[ja]=Night Color Name[ko]=야간 색상 Name[lt]=Naktinė spalva Name[nl]=Nachtkleur @@ -68,6 +69,7 @@ Comment[hu]=A színhőmérséklet igazítása éjszaka a szem fáradásának csökkentésére Comment[id]=Sesuaikan suhu warna saat malam hari untuk mengurangi ketegangan mata Comment[it]=Regola la temperatura dei colori nelle ore notturne per ridurre l'affaticamento degli occhi +Comment[ja]=夜間に色温度を調整することで目の負担を軽減します Comment[ko]=눈의 피로를 줄이기 위하여 야간에 색 온도 조절 Comment[lt]=Reguliuoti naktį spalvos temperatūrą, kad būtų sumažinta akių įtampa Comment[nl]=Kleurtemperatuur bij nacht aanpassen on vermoeidheid van de ogen te vermijden diff --git a/kcms/nightcolor/package/metadata.desktop b/kcms/nightcolor/package/metadata.desktop --- a/kcms/nightcolor/package/metadata.desktop +++ b/kcms/nightcolor/package/metadata.desktop @@ -17,6 +17,7 @@ Name[hu]=Éjszakai színek Name[id]=Warna Malam Name[it]=Colore notturno +Name[ja]=Night Color Name[ko]=야간 색상 Name[lt]=Naktinė spalva Name[nl]=Nachtkleur @@ -54,6 +55,7 @@ Comment[hu]=A színhőmérséklet igazítása éjszaka a szem fáradásának csökkentésére Comment[id]=Sesuaikan suhu warna saat malam hari untuk mengurangi ketegangan mata Comment[it]=Regola la temperatura dei colori nelle ore notturne per ridurre l'affaticamento degli occhi +Comment[ja]=夜間に色温度を調整することで目の負担を軽減します Comment[ko]=눈의 피로를 줄이기 위하여 야간에 색 온도 조절 Comment[lt]=Reguliuoti naktį spalvos temperatūrą, kad būtų sumažinta akių įtampa Comment[nl]=Kleurtemperatuur bij nacht aanpassen on vermoeidheid van de ogen te vermijden diff --git a/kcms/notifications/kcm.h b/kcms/notifications/kcm.h --- a/kcms/notifications/kcm.h +++ b/kcms/notifications/kcm.h @@ -20,7 +20,7 @@ #pragma once -#include +#include #include @@ -31,16 +31,24 @@ namespace NotificationManager { class Settings; +class DoNotDisturbSettings; +class NotificationSettings; +class JobSettings; +class BadgeSettings; } -class KCMNotifications : public KQuickAddons::ConfigModule +class KCMNotifications : public KQuickAddons::ManagedConfigModule { Q_OBJECT Q_PROPERTY(SourcesModel *sourcesModel READ sourcesModel CONSTANT) Q_PROPERTY(FilterProxyModel *filteredModel READ filteredModel CONSTANT) Q_PROPERTY(NotificationManager::Settings *settings READ settings CONSTANT) + Q_PROPERTY(NotificationManager::DoNotDisturbSettings *dndSettings READ dndSettings CONSTANT) + Q_PROPERTY(NotificationManager::NotificationSettings *notificationSettings READ notificationSettings CONSTANT) + Q_PROPERTY(NotificationManager::JobSettings *jobSettings READ jobSettings CONSTANT) + Q_PROPERTY(NotificationManager::BadgeSettings *badgeSettings READ badgeSettings CONSTANT) Q_PROPERTY(QKeySequence toggleDoNotDisturbShortcut READ toggleDoNotDisturbShortcut @@ -60,6 +68,10 @@ FilterProxyModel *filteredModel() const; NotificationManager::Settings *settings() const; + NotificationManager::DoNotDisturbSettings *dndSettings() const; + NotificationManager::NotificationSettings *notificationSettings() const; + NotificationManager::JobSettings *jobSettings() const; + NotificationManager::BadgeSettings *badgeSettings() const; QKeySequence toggleDoNotDisturbShortcut() const; void setToggleDoNotDisturbShortcut(const QKeySequence &shortcut); @@ -93,6 +105,10 @@ FilterProxyModel *m_filteredModel; NotificationManager::Settings *m_settings; + NotificationManager::DoNotDisturbSettings *m_dndSettings; + NotificationManager::NotificationSettings *m_notificationSettings; + NotificationManager::JobSettings *m_jobSettings; + NotificationManager::BadgeSettings *m_badgeSettings; QAction *m_toggleDoNotDisturbAction; QKeySequence m_toggleDoNotDisturbShortcut; diff --git a/kcms/notifications/kcm.cpp b/kcms/notifications/kcm.cpp --- a/kcms/notifications/kcm.cpp +++ b/kcms/notifications/kcm.cpp @@ -45,22 +45,34 @@ #include "filterproxymodel.h" #include +#include +#include +#include +#include K_PLUGIN_FACTORY_WITH_JSON(KCMNotificationsFactory, "kcm_notifications.json", registerPlugin();) KCMNotifications::KCMNotifications(QObject *parent, const QVariantList &args) - : KQuickAddons::ConfigModule(parent, args) + : KQuickAddons::ManagedConfigModule(parent, args) , m_sourcesModel(new SourcesModel(this)) , m_filteredModel(new FilterProxyModel(this)) , m_settings(new NotificationManager::Settings(this)) + , m_dndSettings(new NotificationManager::DoNotDisturbSettings(this)) + , m_notificationSettings(new NotificationManager::NotificationSettings(this)) + , m_jobSettings(new NotificationManager::JobSettings(this)) + , m_badgeSettings(new NotificationManager::BadgeSettings(this)) , m_toggleDoNotDisturbAction(new QAction(this)) { const char uri[] = "org.kde.private.kcms.notifications"; qmlRegisterUncreatableType(uri, 1, 0, "SourcesModel", QStringLiteral("Cannot create instances of SourcesModel")); qmlRegisterType(); qmlRegisterType(); + qmlRegisterType(); + qmlRegisterType(); + qmlRegisterType(); + qmlRegisterType(); qmlProtectModule(uri, 1); KAboutData *about = new KAboutData(QStringLiteral("kcm_notifications"), i18n("Notifications"), @@ -121,6 +133,26 @@ return m_settings; } +NotificationManager::DoNotDisturbSettings *KCMNotifications::dndSettings() const +{ + return m_dndSettings; +} + +NotificationManager::NotificationSettings *KCMNotifications::notificationSettings() const +{ + return m_notificationSettings; +} + +NotificationManager::JobSettings *KCMNotifications::jobSettings() const +{ + return m_jobSettings; +} + +NotificationManager::BadgeSettings *KCMNotifications::badgeSettings() const +{ + return m_badgeSettings; +} + QKeySequence KCMNotifications::toggleDoNotDisturbShortcut() const { return m_toggleDoNotDisturbShortcut; @@ -221,6 +253,7 @@ void KCMNotifications::load() { + ManagedConfigModule::load(); m_settings->load(); const QKeySequence toggleDoNotDisturbShortcut = KGlobalAccel::self()->globalShortcut( @@ -238,6 +271,7 @@ void KCMNotifications::save() { + ManagedConfigModule::save(); m_settings->save(); if (m_toggleDoNotDisturbShortcutDirty) { @@ -252,6 +286,7 @@ void KCMNotifications::defaults() { + ManagedConfigModule::defaults(); m_settings->defaults(); setToggleDoNotDisturbShortcut(QKeySequence()); diff --git a/kcms/notifications/package/contents/ui/PopupPositionPage.qml b/kcms/notifications/package/contents/ui/PopupPositionPage.qml --- a/kcms/notifications/package/contents/ui/PopupPositionPage.qml +++ b/kcms/notifications/package/contents/ui/PopupPositionPage.qml @@ -30,7 +30,7 @@ ScreenPositionSelector { anchors.horizontalCenter: parent.horizontalCenter - selectedPosition: kcm.settings.popupPosition - onSelectedPositionChanged: kcm.settings.popupPosition = selectedPosition + selectedPosition: kcm.notificationSettings.popupPosition + onSelectedPositionChanged: kcm.notificationSettings.popupPosition = selectedPosition } } diff --git a/kcms/notifications/package/contents/ui/main.qml b/kcms/notifications/package/contents/ui/main.qml --- a/kcms/notifications/package/contents/ui/main.qml +++ b/kcms/notifications/package/contents/ui/main.qml @@ -30,7 +30,7 @@ KCM.SimpleKCM { id: root KCM.ConfigModule.quickHelp: i18n("This module lets you manage application and system notifications.") - KCM.ConfigModule.buttons: KCM.ConfigModule.Help | KCM.ConfigModule.Apply + // Sidebar on SourcesPage is 1/3 of the width at a minimum of 12, so assume 3 * 12 = 36 as preferred implicitWidth: Kirigami.Units.gridUnit * 36 @@ -83,16 +83,16 @@ QtControls.CheckBox { Kirigami.FormData.label: i18n("Do Not Disturb mode:") text: i18nc("Do not disturb when screens are mirrored", "Enable when screens are mirrored") - checked: kcm.settings.inhibitNotificationsWhenScreensMirrored - onClicked: kcm.settings.inhibitNotificationsWhenScreensMirrored = checked - enabled: root.notificationsAvailable + checked: kcm.dndSettings.whenScreensMirrored + onClicked: kcm.dndSettings.whenScreensMirrored = checked + enabled: root.notificationsAvailable && !kcm.dndSettings.isImmutable("WhenScreensMirrored") } QtControls.CheckBox { text: i18n("Show critical notifications") - checked: kcm.settings.criticalPopupsInDoNotDisturbMode - onClicked: kcm.settings.criticalPopupsInDoNotDisturbMode = checked - enabled: root.notificationsAvailable + checked: kcm.notificationSettings.criticalInDndMode + onClicked: kcm.notificationSettings.criticalInDndMode = checked + enabled: root.notificationsAvailable && !kcm.notificationSettings.isImmutable("CriticalInDndMode") } RowLayout { @@ -115,9 +115,9 @@ QtControls.CheckBox { Kirigami.FormData.label: i18n("Critical notifications:") text: i18n("Always keep on top") - checked: kcm.settings.keepCriticalAlwaysOnTop - onClicked: kcm.settings.keepCriticalAlwaysOnTop = checked - enabled: root.notificationsAvailable + checked: kcm.notificationSettings.criticalAlwaysOnTop + onClicked: kcm.notificationSettings.criticalAlwaysOnTop = checked + enabled: root.notificationsAvailable && !kcm.notificationSettings.isImmutable("CriticalAlwaysOnTop") } Item { @@ -127,16 +127,16 @@ QtControls.CheckBox { Kirigami.FormData.label: i18n("Low priority notifications:") text: i18n("Show popup") - checked: kcm.settings.lowPriorityPopups - onClicked: kcm.settings.lowPriorityPopups = checked - enabled: root.notificationsAvailable + checked: kcm.notificationSettings.lowPriorityPopups + onClicked: kcm.notificationSettings.lowPriorityPopups = checked + enabled: root.notificationsAvailable && !kcm.notificationSettings.isImmutable("LowPriorityPopups") } QtControls.CheckBox { text: i18n("Show in history") - checked: kcm.settings.lowPriorityHistory - onClicked: kcm.settings.lowPriorityHistory = checked - enabled: root.notificationsAvailable + checked: kcm.notificationSettings.lowPriorityHistory + onClicked: kcm.notificationSettings.lowPriorityHistory = checked + enabled: root.notificationsAvailable && !kcm.notificationSettings.isImmutable("LowPriorityHistory") } QtControls.ButtonGroup { @@ -152,20 +152,20 @@ id: positionCloseToWidget Kirigami.FormData.label: i18n("Popup:") text: i18nc("Popup position near notification plasmoid", "Show near notification icon") // "widget" - checked: kcm.settings.popupPosition === NotificationManager.Settings.CloseToWidget + checked: kcm.notificationSettings.popupPosition === NotificationManager.Settings.CloseToWidget // Force binding re-evaluation when user returns from position selector + kcm.currentIndex * 0 - onClicked: kcm.settings.popupPosition = NotificationManager.Settings.CloseToWidget - enabled: root.notificationsAvailable + onClicked: kcm.notificationSettings.popupPosition = NotificationManager.Settings.CloseToWidget + enabled: root.notificationsAvailable && !kcm.notificationSettings.isImmutable("PopupPosition") } RowLayout { spacing: 0 - enabled: root.notificationsAvailable + enabled: root.notificationsAvailable && !kcm.notificationSettings.isImmutable("PopupPosition") QtControls.RadioButton { id: positionCustomPosition - checked: kcm.settings.popupPosition !== NotificationManager.Settings.CloseToWidget + checked: kcm.notificationSettings.popupPosition !== NotificationManager.Settings.CloseToWidget + kcm.currentIndex * 0 activeFocusOnTab: false @@ -203,16 +203,16 @@ from: 1000 // 1 second to: 120000 // 2 minutes stepSize: 1000 - value: kcm.settings.popupTimeout - enabled: root.notificationsAvailable + value: kcm.notificationSettings.popupTimeout + enabled: root.notificationsAvailable && !kcm.notificationSettings.isImmutable("PopupTimeout") editable: true valueFromText: function(text, locale) { return parseInt(text) * 1000; } textFromValue: function(value, locale) { return i18np("%1 second", "%1 seconds", Math.round(value / 1000)); } - onValueModified: kcm.settings.popupTimeout = value + onValueModified: kcm.notificationSettings.popupTimeout = value } } @@ -223,25 +223,27 @@ QtControls.CheckBox { Kirigami.FormData.label: i18n("Application progress:") text: i18n("Show in task manager") - checked: kcm.settings.jobsInTaskManager - onClicked: kcm.settings.jobsInTaskManager = checked + checked: kcm.jobSettings.inTaskManager + onClicked: kcm.jobSettings.inTaskManager = checked + enabled: !kcm.jobSettings.isImmutable("InTaskManager") } QtControls.CheckBox { id: applicationJobsEnabledCheck text: i18nc("Show application jobs in notification widget", "Show in notifications") - checked: kcm.settings.jobsInNotifications - onClicked: kcm.settings.jobsInNotifications = checked + checked: kcm.jobSettings.inNotifications + onClicked: kcm.jobSettings.inNotifications = checked + enabled: !kcm.jobSettings.isImmutable("InNotifications") } RowLayout { // just for indentation QtControls.CheckBox { Layout.leftMargin: mirrored ? 0 : indicator.width Layout.rightMargin: mirrored ? indicator.width : 0 text: i18nc("Keep application job popup open for entire duration of job", "Keep popup open during progress") - enabled: applicationJobsEnabledCheck.checked - checked: kcm.settings.permanentJobPopups - onClicked: kcm.settings.permanentJobPopups = checked + enabled: applicationJobsEnabledCheck.checked && !kcm.jobSettings.isImmutable("PermanentPopups") + checked: kcm.jobSettings.permanentPopups + onClicked: kcm.jobSettings.permanentPopups = checked } } @@ -252,8 +254,9 @@ QtControls.CheckBox { Kirigami.FormData.label: i18n("Notification badges:") text: i18n("Show in task manager") - checked: kcm.settings.badgesInTaskManager - onClicked: kcm.settings.badgesInTaskManager = checked + checked: kcm.badgeSettings.inTaskManager + onClicked: kcm.badgeSettings.inTaskManager = checked + enabled: !kcm.badgeSettings.isImmutable("InTaskManager") } Kirigami.Separator { diff --git a/kcms/solid_actions/solid-actions.desktop b/kcms/solid_actions/solid-actions.desktop --- a/kcms/solid_actions/solid-actions.desktop +++ b/kcms/solid_actions/solid-actions.desktop @@ -12,6 +12,7 @@ Name=Device Actions Name[ar]=إجراءات الجهاز +Name[ast]=Aiciones con preseos Name[bg]=Действия за устройства Name[bn]=ডিভাইস অ্যাকশন Name[bs]=Radnje uređaja diff --git a/kcms/style/CMakeLists.txt b/kcms/style/CMakeLists.txt --- a/kcms/style/CMakeLists.txt +++ b/kcms/style/CMakeLists.txt @@ -21,6 +21,8 @@ add_library(kcm_style MODULE ${kcm_style_PART_SRCS}) +target_compile_definitions(kcm_style PUBLIC CMAKE_INSTALL_FULL_LIBEXECDIR="${CMAKE_INSTALL_FULL_LIBEXECDIR}") + target_link_libraries(kcm_style Qt5::X11Extras Qt5::DBus @@ -41,7 +43,7 @@ kcoreaddons_desktop_to_json(kcm_style "kcm_style.desktop") install(FILES stylesettings.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) -install(FILES cgctheme.knsrc cgcgtk3.knsrc DESTINATION ${KDE_INSTALL_KNSRCDIR}) +install(FILES gtk2_themes.knsrc gtk3_themes.knsrc DESTINATION ${KDE_INSTALL_KNSRCDIR}) install(FILES kcm_style.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(TARGETS kcm_style DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms) diff --git a/kcms/style/cgctheme.knsrc b/kcms/style/gtk2_themes.knsrc rename from kcms/style/cgctheme.knsrc rename to kcms/style/gtk2_themes.knsrc --- a/kcms/style/cgctheme.knsrc +++ b/kcms/style/gtk2_themes.knsrc @@ -1,12 +1,17 @@ [KNewStuff3] Name=GTK 2.x Themes Name[ca]=Temes GTK 2.x +Name[cs]=Motivy GTK 2.x +Name[da]=GTK 2.x-temaer Name[es]=Temas de GTK 2.x Name[et]=GTK 2.x teemad +Name[eu]=GTK 2.x gaiak Name[lt]=GTK 2.x apipavidalinimai +Name[nl]=GTK 2.x thema's Name[pt]=Temas do GTK 2.x Name[pt_BR]=Temas GTK 2.x Name[ru]=Темы GTK 2.x +Name[sk]=GTK 3.x Témy Name[sv]=GTK 2.x-teman Name[uk]=Теми GTK 2.x Name[x-test]=xxGTK 2.x Themesxx diff --git a/kcms/style/cgcgtk3.knsrc b/kcms/style/gtk3_themes.knsrc rename from kcms/style/cgcgtk3.knsrc rename to kcms/style/gtk3_themes.knsrc --- a/kcms/style/cgcgtk3.knsrc +++ b/kcms/style/gtk3_themes.knsrc @@ -1,12 +1,17 @@ [KNewStuff3] Name=GTK 3.x Themes Name[ca]=Temes GTK 3.x +Name[cs]=Motivy GTK 3.x +Name[da]=GTK 3.x-temaer Name[es]=Temas de GTK 3.x Name[et]=GTK 3.x teemad +Name[eu]=GTK 3.x gaiak Name[lt]=GTK 3.x apipavidalinimai +Name[nl]=GTK 3.x thema's Name[pt]=Temas do GTK 3.x Name[pt_BR]=Temas GTK 3.x Name[ru]=Темы GTK 3.x +Name[sk]=GTK 2.x Témy Name[sv]=GTK 3.x-teman Name[uk]=Теми GTK 3.x Name[x-test]=xxGTK 3.x Themesxx diff --git a/kcms/style/gtkpage.h b/kcms/style/gtkpage.h --- a/kcms/style/gtkpage.h +++ b/kcms/style/gtkpage.h @@ -22,6 +22,7 @@ #include #include +#include #include "gtkthemesmodel.h" @@ -44,14 +45,16 @@ QString gtk2ThemeFromConfig(); QString gtk3ThemeFromConfig(); + bool gtk2PreviewAvailable(); + bool gtk3PreviewAvailable(); + void showGtk2Preview(); void showGtk3Preview(); void installGtkThemeFromFile(const QUrl &fileUrl); - void installGtk2ThemeFromGHNS(); - void installGtk3ThemeFromGHNS(); void onThemeRemoved(); + void onGhnsEntriesChanged(const QQmlListReference &changedEnties); Q_SIGNALS: void gtk2ThemesModelChanged(GtkThemesModel *model); diff --git a/kcms/style/gtkpage.cpp b/kcms/style/gtkpage.cpp --- a/kcms/style/gtkpage.cpp +++ b/kcms/style/gtkpage.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -69,35 +70,24 @@ return dbusReply.value(); } -void GtkPage::showGtk2Preview() +bool GtkPage::gtk2PreviewAvailable() { - gtkConfigInterface.call(QStringLiteral("showGtk2ThemePreview"), m_gtk2ThemesModel->selectedTheme()); + return !QStandardPaths::findExecutable(QStringLiteral("gtk_preview"), {CMAKE_INSTALL_FULL_LIBEXECDIR}).isEmpty(); } -void GtkPage::showGtk3Preview() +bool GtkPage::gtk3PreviewAvailable() { - gtkConfigInterface.call(QStringLiteral("showGtk3ThemePreview"), m_gtk3ThemesModel->selectedTheme()); + return !QStandardPaths::findExecutable(QStringLiteral("gtk3_preview"), {CMAKE_INSTALL_FULL_LIBEXECDIR}).isEmpty(); } - -void GtkPage::installGtk2ThemeFromGHNS() +void GtkPage::showGtk2Preview() { - KNS3::DownloadDialog downloadDialog(QStringLiteral("cgctheme.knsrc")); - downloadDialog.setWindowTitle(i18n("Download New GNOME/GTK2 Application Styles")); - downloadDialog.setWindowModality(Qt::WindowModal); - if (downloadDialog.exec()) { - load(); - } + gtkConfigInterface.call(QStringLiteral("showGtk2ThemePreview"), m_gtk2ThemesModel->selectedTheme()); } -void GtkPage::installGtk3ThemeFromGHNS() +void GtkPage::showGtk3Preview() { - KNS3::DownloadDialog downloadDialog(QStringLiteral("cgcgtk3.knsrc")); - downloadDialog.setWindowTitle(i18n("Download New GNOME/GTK3 Application Styles")); - downloadDialog.setWindowModality(Qt::WindowModal); - if (downloadDialog.exec()) { - load(); - } + gtkConfigInterface.call(QStringLiteral("showGtk3ThemePreview"), m_gtk3ThemesModel->selectedTheme()); } void GtkPage::onThemeRemoved() @@ -107,6 +97,15 @@ save(); } +void GtkPage::onGhnsEntriesChanged(const QQmlListReference &changedEnties) +{ + if (changedEnties.count() == 0) { + return; + } + + load(); +} + void GtkPage::installGtkThemeFromFile(const QUrl &fileUrl) { QString themesInstallDirectoryPath(QDir::homePath() + QStringLiteral("/.themes")); diff --git a/kcms/style/gtkthemesmodel.h b/kcms/style/gtkthemesmodel.h --- a/kcms/style/gtkthemesmodel.h +++ b/kcms/style/gtkthemesmodel.h @@ -33,7 +33,6 @@ public: GtkThemesModel(QObject *parent = nullptr); - ~GtkThemesModel() override = default; enum Roles { ThemeNameRole = Qt::UserRole + 1, diff --git a/kcms/style/package/contents/ui/GtkStylePage.qml b/kcms/style/package/contents/ui/GtkStylePage.qml --- a/kcms/style/package/contents/ui/GtkStylePage.qml +++ b/kcms/style/package/contents/ui/GtkStylePage.qml @@ -24,6 +24,7 @@ import QtQuick.Controls 2.10 as QtControls import org.kde.kirigami 2.10 as Kirigami import org.kde.private.kcms.style 1.0 as Private +import org.kde.newstuff 1.62 as NewStuff import org.kde.kcm 1.2 as KCM Kirigami.Page { @@ -88,6 +89,7 @@ icon.name: "preview" text: i18n("Preview...") onClicked: kcm.gtkPage.showGtk2Preview() + visible: kcm.gtkPage.gtk2PreviewAvailable() } } } @@ -127,6 +129,7 @@ icon.name: "preview" text: i18n("Preview...") onClicked: kcm.gtkPage.showGtk3Preview() + visible: kcm.gtkPage.gtk3PreviewAvailable() } } @@ -151,29 +154,48 @@ QtControls.Button { icon.name: "get-hot-new-stuff" - text: i18n("Get New GNOME/GTK Application Styles...") + text: i18n("Download New GNOME/GTK Application Styles...") onClicked: ghnsMenu.open() QtControls.Menu { id: ghnsMenu QtControls.MenuItem { icon.name: "get-hot-new-stuff" - text: i18n("Get New GNOME/GTK2 Application Styles...") + text: i18n("Download New GNOME/GTK2 Application Styles...") onClicked: function() { ghnsMenu.close() - kcm.gtkPage.installGtk2ThemeFromGHNS() + gtk2NewStuffButton.showDialog() + } + + NewStuff.Button { + id: gtk2NewStuffButton + downloadNewWhat: i18n("GNOME/GTK2 Application Styles") + configFile: "gtk2_themes.knsrc" + viewMode: NewStuff.Page.ViewMode.Preview + onChangedEntriesChanged: kcm.gtkPage.onGhnsEntriesChanged(gtk2NewStuffButton.changedEntries); + visible: false } } QtControls.MenuItem { icon.name: "get-hot-new-stuff" - text: i18n("Get New GNOME/GTK3 Application Styles...") + text: i18n("Download New GNOME/GTK3 Application Styles...") onClicked: function() { ghnsMenu.close() - kcm.gtkPage.installGtk3ThemeFromGHNS() + gtk3NewStuffButton.showDialog() + } + + NewStuff.Button { + id: gtk3NewStuffButton + downloadNewWhat: i18n("GNOME/GTK3 Application Styles") + configFile: "gtk3_themes.knsrc" + viewMode: NewStuff.Page.ViewMode.Preview + onChangedEntriesChanged: kcm.gtkPage.onGhnsEntriesChanged(gtk3NewStuffButton.changedEntries); + visible: false } } } + } } } diff --git a/kcms/workspaceoptions/kcm_workspace.desktop b/kcms/workspaceoptions/kcm_workspace.desktop --- a/kcms/workspaceoptions/kcm_workspace.desktop +++ b/kcms/workspaceoptions/kcm_workspace.desktop @@ -24,6 +24,7 @@ Name[hu]=Általános működés Name[id]=Perilaku Umum Name[it]=Comportamento generale +Name[ja]=全般的な挙動 Name[ko]=일반 행동 Name[lt]=Bendra elgsena Name[nl]=Algemeen gedrag @@ -56,6 +57,7 @@ Comment[hu]=A munkaterület általános működésének beállítása Comment[id]=Konfigurasikan perilaku ruang-kerja umum Comment[it]=Configura il comportamento generale dello spazio di lavoro +Comment[ja]=全般的なワークスペースの挙動を設定 Comment[ko]=일반 작업 공간 행동 설정 Comment[lt]=Konfigūruoti bendra darbo srities elgseną Comment[nl]=Algemeen gedrag in werkruimte configureren @@ -73,13 +75,16 @@ Comment[zh_TW]=設定一般工作空間行為 X-KDE-Keywords=plasma,workspace,shell,formfactor,dashboard,tooltips,informational tips,tooltips,click,single click,double click,animation speed X-KDE-Keywords[ca]=plasma,espai de treball,àrea de treball,factor de format,tauler,consells d'eines,consells informatius,consells d'eines,clic normal,doble clic,velocitat d'animació +X-KDE-Keywords[da]=plasma,arbejdsområde,skal,formfaktor,dashboard,instrumentbræt,værktøjstips,informationstip,klik,enkeltklik,dobbeltklik,animationshastighed X-KDE-Keywords[es]=plasma,espacio de trabajo,consola,factor de forma,tablero de mandos,ayudas emergentes,ayudas informativas,clic,clic sencillo,doble clic,velocidad de animación X-KDE-Keywords[et]=plasma,töötsoon,shell,dashboard,vidinavaade,konteiner,kohtspikrid,klõps,ühekordne klõps,topeltklõps,animatsiooni kiirus +X-KDE-Keywords[eu]=plasma,langunea,shell,forma faktorea,aginte-mahaia,tresnen argibideak,informatzeko argibideak,klik, klik bakuna,klik bikoitza,animazio-abiadura X-KDE-Keywords[lt]=plasma,darbo sritis,darbo erdvė,darbo erdve,apvalkalas,formos faktorius,skydelis,paaiškinimai,paaiskinimai,informaciniai paaiškinimai,informaciniai paaiskinimai,patarimai,spustelėjimas,spustelejimas,spragtelėjimas,spragtelejimas,vienkartis spustelėjimas,vienkartis spustelejimas,vienkartis spragtelėjimas,vienkartis spragtelejimas,dvikartis spustelėjimas,dvikartis spustelejimas,dvikartis spragtelėjimas,dvikartis spragtelejimas,animacijos greitis,animacijos sparta X-KDE-Keywords[nl]=plasma,werkruimte,shell,vormfactor,dashboard,tekstballonnen,informatieve tips,tekstballonnen,klik,dubbelklik,animatiesnelheid X-KDE-Keywords[pt]=plasma,área de trabalho,consola,formato de ecrã,painel,dicas,dicas informativas,click,click simples,duplo-click,velocidade da animação X-KDE-Keywords[pt_BR]=plasma,área de trabalho,shell,formato de tela,painel,dicas,dicas informativas,clique simples,clique duplo,velocidade de animação X-KDE-Keywords[ru]=plasma,workspace,shell,formfactor,dashboard,tooltips,informational tips,tooltips,click,single click,double click,плазма,рабочий стол,рабочая среда,рабочее окружение,окружение рабочего стола,среда рабочего стола,приборная доска,подсказки,всплывающие подсказки,информационные подсказки,оболочка рабочего стола,щелчок,одиночный клик,одиночный щелчок,двойной клик,двойной щелчок,скорость анимации +X-KDE-Keywords[sk]=plasma,pracovná plocha,shell,formfactor,nástenka,nástrojové tipy,informačné tipy,klik,jeden klik,dvojitý klik,rýchlosť animácií X-KDE-Keywords[sv]=plasma,arbetsyta,skal,formfaktor,instrumentpanel,verktygstips,informationstips,klick,enkelklick,dubbelklick,animeringshastighet X-KDE-Keywords[uk]=plasma,workspace,shell,formfactor,dashboard,tooltips,informational tips,tooltips,click,single click,double click,animation speed,плазма,робочий простір,оболонка,форм-фактор,панель,підказки,інформація,відомості,клацання,одинарне клацання,подвійне клацання,швидкість анімації X-KDE-Keywords[x-test]=xxplasmaxx,xxworkspacexx,xxshellxx,xxformfactorxx,xxdashboardxx,xxtooltipsxx,xxinformational tipsxx,xxtooltipsxx,xxclickxx,xxsingle clickxx,xxdouble clickxx,xxanimation speedxx diff --git a/kcms/workspaceoptions/package/metadata.desktop b/kcms/workspaceoptions/package/metadata.desktop --- a/kcms/workspaceoptions/package/metadata.desktop +++ b/kcms/workspaceoptions/package/metadata.desktop @@ -15,6 +15,7 @@ Name[hu]=Általános működés Name[id]=Perilaku Umum Name[it]=Comportamento generale +Name[ja]=全般的な挙動 Name[ko]=일반 행동 Name[lt]=Bendra elgsena Name[nl]=Algemeen gedrag @@ -47,6 +48,7 @@ Comment[hu]=A munkaterület általános működésének beállítása Comment[id]=Konfigurasikan perilaku ruang-kerja umum Comment[it]=Configura il comportamento generale dello spazio di lavoro +Comment[ja]=全般的なワークスペースの挙動を設定 Comment[ko]=일반 작업 공간 행동 설정 Comment[lt]=Konfigūruoti bendra darbo srities elgseną Comment[nl]=Algemeen gedrag in werkruimte configureren diff --git a/solid-device-automounter/kcm/device_automounter_kcm.desktop b/solid-device-automounter/kcm/device_automounter_kcm.desktop --- a/solid-device-automounter/kcm/device_automounter_kcm.desktop +++ b/solid-device-automounter/kcm/device_automounter_kcm.desktop @@ -91,6 +91,7 @@ Name[zh_TW]=可移除裝置 Comment=Configure automatic handling of removable storage media Comment[ar]=اضبط التّعامل الآليّ من مساحات تخزين الوسائط القابلة للإزالة +Comment[ast]=Configura'l montaxe automáticu d'almacenamientos estrayibles Comment[ca]=Configura l'administració automàtica dels suports d'emmagatzematge extraïbles Comment[ca@valencia]=Configura l'administració automàtica dels suports d'emmagatzematge extraïbles Comment[cs]=Nastavit automatické zacházení s odpojitelnými médii