Merge branch 'work/nvrwhere/recent_qml' into 'master'

Convert Recent Files KCM to full QML

See merge request plasma/plasma-desktop!4153
This commit is contained in:
James Graham 2026-11-03 19:08:26 +00:00
commit a2404ddf82
18 changed files with 421 additions and 763 deletions

View file

@ -1,88 +1,35 @@
# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
# SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
# SPDX-License-Identifier: BSD-2-Clause
add_definitions(-DTRANSLATION_DOMAIN=\"kcm_recentFiles\")
find_package (Qt6 REQUIRED NO_MODULE COMPONENTS Core Quick Sql Qml DBus)
find_package (KF6KCMUtils ${KF6_MIN_VERSION} CONFIG REQUIRED)
find_package (KF6XmlGui ${KF6_MIN_VERSION} CONFIG REQUIRED)
find_package (KF6GlobalAccel ${KF6_MIN_VERSION} CONFIG REQUIRED)
find_package (PlasmaActivities ${PROJECT_DEP_VERSION} CONFIG REQUIRED)
set (KAMD_KCM_DATADIR
${KDE_INSTALL_DATADIR}/kcm_recentFiles/workspace/settings/
)
if (NOT IS_ABSOLUTE "${KDE_INSTALL_DATADIR}")
set (KAMD_KCM_DATADIR "${CMAKE_INSTALL_PREFIX}/${KAMD_KCM_DATADIR}")
endif ()
configure_file (recentFiles-kcm-features.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/recentFiles-kcm-features.h)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
set (KAMD_KCM_SRCS
kcm_recentFiles.cpp
ExcludedApplicationsModel.cpp
)
ki18n_wrap_ui (
KAMD_KCM_SRCS
ui/RecentFiles.ui
)
qt_add_dbus_interface (
KAMD_KCM_SRCS
common/dbus/org.kde.ActivityManager.ResourceScoring.xml
resourcescoring_interface
)
kcmutils_generate_module_data(
KAMD_KCM_SRCS
MODULE_DATA_HEADER kactivitiesdata.h
MODULE_DATA_CLASS_NAME KActivitiesData
SETTINGS_HEADERS kactivitymanagerd_plugins_settings.h
SETTINGS_CLASSES KActivityManagerdPluginsSettings
kcmutils_add_qml_kcm(kcm_recentfiles
SOURCES
kcm_recentfiles.cpp
kcm_recentfiles.h
excludedapplicationsmodel.cpp
excludedapplicationsmodel.h
)
kconfig_add_kcfg_files(KAMD_KCM_SRCS kactivitymanagerd_plugins_settings.kcfgc GENERATE_MOC)
kconfig_add_kcfg_files(kcm_recentfiles kactivitymanagerd_plugins_settings.kcfgc GENERATE_MOC)
add_library (kcm_recentFiles MODULE ${KAMD_KCM_SRCS})
ecm_qt_declare_logging_category(kcm_recentfiles
HEADER kcms_recentfiles_logging.h
IDENTIFIER KCM_RECENTFILES
CATEGORY_NAME kcm_recentFiles
DESCRIPTION "KCM for recent file usage tracking"
EXPORT KCM_RECENTFILES
)
kcmutils_generate_desktop_file(kcm_recentFiles)
target_link_libraries (kcm_recentFiles
qt_add_dbus_interface(kcm_recentfiles_DBUS_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/org.kde.ActivityManager.ResourceScoring.xml resourcescoring_interface)
target_sources(kcm_recentfiles PRIVATE ${kcm_recentfiles_DBUS_SRCS})
target_link_libraries (kcm_recentfiles
Qt::Quick
Qt::QuickWidgets
Qt::Core
Qt::Sql
Qt::Qml
Qt::DBus
Qt::Sql
KF6::KCMUtils
KF6::I18n
KF6::Service
KF6::XmlGui
Plasma::Activities
)
install (
TARGETS kcm_recentFiles
DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/kcms/systemsettings_qwidgets
)
install (
DIRECTORY
qml/recentFiles
DESTINATION ${KAMD_KCM_DATADIR}/qml
)
install(FILES kactivitymanagerd_plugins_settings.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR})
include(ECMQtDeclareLoggingCategory)
ecm_qt_declare_logging_category(kcm_recentFiles
HEADER kcms-recentfiles-debug.h
IDENTIFIER "LOG_KCMS_RECENTFILES"
CATEGORY_NAME "org.kde.kcms.recentFiles"
)

View file

@ -1,19 +0,0 @@
/*
SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
// clang-format off
#pragma once
#define KAMD_DBUS_SERVICE "org.kde.ActivityManager"
#define KAMD_DBUS_ACTIVITYMANAGER_PATH "/ActivityManager"
#define KAMD_DBUS_FEATURES_PATH KAMD_DBUS_ACTIVITYMANAGER_PATH"/Features"
#define KAMD_DBUS_RESOURCES_PATH KAMD_DBUS_ACTIVITYMANAGER_PATH"/Resources"
#define KAMD_DBUS_RESOURCES_SCORING_PATH KAMD_DBUS_RESOURCES_PATH"/Scoring"

View file

@ -4,46 +4,21 @@
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "ExcludedApplicationsModel.h"
#include "excludedapplicationsmodel.h"
#include <QDebug>
#include <QList>
#include <QScopeGuard>
#include <QSet>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QVariant>
#include <KService>
#include "kactivitymanagerd_plugins_settings.h"
#include <utils/d_ptr_implementation.h>
#include "definitions.h"
class ExcludedApplicationsModel::Private
{
public:
struct ApplicationData {
QString name;
QString title;
QString icon;
bool blocked;
};
QList<ApplicationData> applications;
KActivityManagerdPluginsSettings *pluginConfig;
bool enabled;
};
using namespace Qt::StringLiterals;
ExcludedApplicationsModel::ExcludedApplicationsModel(QObject *parent)
: QAbstractListModel(parent)
, m_settings(new KActivityManagerdPluginsSettings(this))
{
d->enabled = false;
d->pluginConfig = new KActivityManagerdPluginsSettings;
}
ExcludedApplicationsModel::~ExcludedApplicationsModel()
@ -52,44 +27,40 @@ ExcludedApplicationsModel::~ExcludedApplicationsModel()
QHash<int, QByteArray> ExcludedApplicationsModel::roleNames() const
{
return {{ApplicationIdRole, QByteArrayLiteral("name")},
{Qt::DecorationRole, QByteArrayLiteral("icon")},
{Qt::DisplayRole, QByteArrayLiteral("title")},
{BlockedApplicationRole, QByteArrayLiteral("blocked")}};
return {{ApplicationIdRole, "name"_ba}, {Qt::DecorationRole, "iconSource"_ba}, {Qt::DisplayRole, "title"_ba}, {BlockedApplicationRole, "blocked"_ba}};
}
void ExcludedApplicationsModel::load()
{
QSqlDatabase database;
const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kactivitymanagerd/resources/database");
database = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("plugins_sqlite_db_resources"));
const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kactivitymanagerd/resources/database"_L1;
database = QSqlDatabase::addDatabase("QSQLITE"_L1, "plugins_sqlite_db_resources"_L1);
database.setDatabaseName(path);
auto databaseRemove = qScopeGuard([&] {
QSqlDatabase::removeDatabase(database.connectionName());
});
// Loading plugin configuration
d->pluginConfig->load();
const auto defaultBlockedValue = d->pluginConfig->defaultBlockedByDefaultValue();
const auto &blockedAppList = d->pluginConfig->blockedApplications();
m_settings->load();
const auto defaultBlockedValue = m_settings->defaultBlockedByDefaultValue();
const auto &blockedAppList = m_settings->blockedApplications();
auto blockedApplications = QSet<QString>(blockedAppList.cbegin(), blockedAppList.cend());
const auto &allowedAppList = d->pluginConfig->allowedApplications();
const auto &allowedAppList = m_settings->allowedApplications();
auto allowedApplications = QSet<QString>(allowedAppList.cbegin(), allowedAppList.cend());
// Reading new applications from the database
if (!database.open()) {
// qDebug() << "Failed to open the database" << path << d->database.lastError();
return;
}
QSqlQuery query(database);
query.exec(QStringLiteral("SELECT DISTINCT(initiatingAgent) FROM ResourceScoreCache ORDER BY initiatingAgent"));
query.exec("SELECT DISTINCT(initiatingAgent) FROM ResourceScoreCache ORDER BY initiatingAgent"_L1);
if (d->applications.length() > 0) {
beginRemoveRows(QModelIndex(), 0, d->applications.length() - 1);
d->applications.clear();
if (m_applications.length() > 0) {
beginRemoveRows(QModelIndex(), 0, m_applications.length() - 1);
m_applications.clear();
endRemoveRows();
}
@ -119,9 +90,9 @@ void ExcludedApplicationsModel::load()
const auto blocked = blockedApplications.contains(name);
if (service) {
d->applications << Private::ApplicationData{name, service->name(), service->icon(), blocked};
m_applications << ApplicationData{name, service->name(), service->icon(), blocked};
} else {
d->applications << Private::ApplicationData{name, name, QString(), blocked};
m_applications << ApplicationData{name, name, QString(), blocked};
}
}
@ -131,14 +102,14 @@ void ExcludedApplicationsModel::load()
void ExcludedApplicationsModel::save()
{
d->pluginConfig->save();
m_settings->save();
Q_EMIT changed(false);
}
void ExcludedApplicationsModel::defaults()
{
for (int i = 0; i < rowCount(); i++) {
d->applications[i].blocked = false;
m_applications[i].blocked = false;
}
Q_EMIT dataChanged(QAbstractListModel::index(0), QAbstractListModel::index(rowCount() - 1));
@ -152,27 +123,27 @@ void ExcludedApplicationsModel::toggleApplicationBlocked(int index)
return;
}
d->applications[index].blocked = !d->applications[index].blocked;
m_applications[index].blocked = !m_applications[index].blocked;
Q_EMIT dataChanged(QAbstractListModel::index(index), QAbstractListModel::index(index));
QStringList blockedApplications;
QStringList allowedApplications;
for (int i = 0; i < rowCount(); i++) {
const auto name = d->applications[i].name;
if (d->applications[i].blocked) {
const auto name = m_applications[i].name;
if (m_applications[i].blocked) {
blockedApplications << name;
} else {
allowedApplications << name;
}
}
d->pluginConfig->setBlockedApplications(blockedApplications);
d->pluginConfig->setAllowedApplications(allowedApplications);
m_settings->setBlockedApplications(blockedApplications);
m_settings->setAllowedApplications(allowedApplications);
const auto allowedApplicationsItem = d->pluginConfig->findItem("allowedApplications");
const auto allowedApplicationsItem = m_settings->findItem("allowedApplications");
Q_ASSERT(allowedApplicationsItem);
const auto blockedApplicationsItem = d->pluginConfig->findItem("allowedApplications");
const auto blockedApplicationsItem = m_settings->findItem("allowedApplications");
Q_ASSERT(blockedApplicationsItem);
Q_EMIT changed(blockedApplicationsItem->isSaveNeeded() && allowedApplicationsItem->isSaveNeeded());
@ -195,7 +166,7 @@ QVariant ExcludedApplicationsModel::data(const QModelIndex &modelIndex, int role
return QVariant();
}
const auto &application = d->applications[index];
const auto &application = m_applications[index];
switch (role) {
default:
@ -208,7 +179,7 @@ QVariant ExcludedApplicationsModel::data(const QModelIndex &modelIndex, int role
return application.title;
case Qt::DecorationRole:
return application.icon.isEmpty() ? QStringLiteral("application-x-executable") : application.icon;
return application.icon.isEmpty() ? "application-x-executable"_L1 : application.icon;
case BlockedApplicationRole:
return application.blocked;
@ -218,20 +189,7 @@ QVariant ExcludedApplicationsModel::data(const QModelIndex &modelIndex, int role
int ExcludedApplicationsModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return d->applications.size();
return m_applications.size();
}
bool ExcludedApplicationsModel::enabled() const
{
return d->enabled;
}
void ExcludedApplicationsModel::setEnabled(bool enabled)
{
d->enabled = enabled;
Q_EMIT enabledChanged(enabled);
}
// #include <ExcludedApplicationsModel.moc>
#include "moc_ExcludedApplicationsModel.cpp"
#include "moc_excludedapplicationsmodel.cpp"

View file

@ -8,7 +8,7 @@
#include <QAbstractListModel>
#include <utils/d_ptr.h>
#include "kactivitymanagerd_plugins_settings.h"
/**
* ExcludedApplicationsModel
@ -17,8 +17,6 @@ class ExcludedApplicationsModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
public:
explicit ExcludedApplicationsModel(QObject *parent = nullptr);
~ExcludedApplicationsModel() override;
@ -37,18 +35,22 @@ public:
Q_SIGNALS:
void changed(bool changed);
void defaulted(bool isDefault);
void enabledChanged(bool enabled);
public Q_SLOTS:
void toggleApplicationBlocked(int index);
void setEnabled(bool);
bool enabled() const;
void load();
void save();
void defaults();
private:
D_PTR;
struct ApplicationData {
QString name;
QString title;
QString icon;
bool blocked;
};
QList<ApplicationData> m_applications;
KActivityManagerdPluginsSettings *m_settings;
};

View file

@ -1,231 +0,0 @@
/*
SPDX-FileCopyrightText: 2012-2016 Ivan Cukic <ivan.cukic@kde.org>
SPDX-FileCopyrightText: 2022 Méven Car <meven.car@kdenet.net>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "kcm_recentFiles.h"
#include "kactivitymanagerd_plugins_settings.h"
#include "resourcescoring_interface.h"
#include <QDBusConnection>
#include <QDBusPendingCall>
#include <QMenu>
#include <QQmlComponent>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickWidget>
#include <KConfigDialogManager>
#include <KConfigGroup>
#include <KMessageWidget>
#include <KSharedConfig>
#include "ExcludedApplicationsModel.h"
#include "ui_RecentFiles.h"
#include <utils/d_ptr_implementation.h>
#include "recentFiles-kcm-features.h"
#include "common/dbus/common.h"
#include "kactivitiesdata.h"
#include "kcms-recentfiles-debug.h"
K_PLUGIN_FACTORY_WITH_JSON(KActivityManagerKCMFactory, "kcm_recentFiles.json", registerPlugin<RecentFilesKcm>(); registerPlugin<KActivitiesData>();)
class RecentFilesKcm::Private : public Ui::RecentFiles
{
public:
KActivityManagerdPluginsSettings *pluginConfig;
ExcludedApplicationsModel *excludedApplicationsModel;
explicit Private(QObject *parent)
: pluginConfig(new KActivityManagerdPluginsSettings(parent))
, excludedApplicationsModel(new ExcludedApplicationsModel(parent))
{
}
void setDefaultIndicatorVisible(QWidget *widget, bool visible)
{
widget->setProperty("_kde_highlight_neutral", visible);
widget->update();
}
void updateUiDefaultIndicator(bool visible)
{
setDefaultIndicatorVisible(radioDontRememberApplications, visible && radioDontRememberApplications->isChecked());
setDefaultIndicatorVisible(radioRememberSpecificApplications, visible && radioRememberSpecificApplications->isChecked());
}
};
RecentFilesKcm::RecentFilesKcm(QObject *parent, const KPluginMetaData &data)
: KCModule(parent, data)
, d(this)
{
d->setupUi(widget());
// Keep history initialization
d->kcfg_keepHistoryFor->setRange(0, INT_MAX);
d->kcfg_keepHistoryFor->setSpecialValueText(i18nc("unlimited number of months", "Forever"));
connect(d->kcfg_keepHistoryFor, SIGNAL(valueChanged(int)), this, SLOT(spinKeepHistoryValueChanged(int)));
spinKeepHistoryValueChanged(0);
// Clear recent history button
auto menu = new QMenu(widget());
connect(menu->addAction(i18n("Forget the last hour")), &QAction::triggered, this, &RecentFilesKcm::forgetLastHour);
connect(menu->addAction(i18n("Forget the last two hours")), &QAction::triggered, this, &RecentFilesKcm::forgetTwoHours);
connect(menu->addAction(i18n("Forget a day")), &QAction::triggered, this, &RecentFilesKcm::forgetDay);
connect(menu->addAction(i18n("Forget everything")), &QAction::triggered, this, &RecentFilesKcm::forgetAll);
d->buttonClearRecentHistory->setMenu(menu);
// Excluded applications
d->excludedApplicationsModel = new ExcludedApplicationsModel(this);
connect(d->excludedApplicationsModel, &ExcludedApplicationsModel::changed, this, &RecentFilesKcm::unmanagedWidgetChangeState);
connect(d->excludedApplicationsModel, &ExcludedApplicationsModel::defaulted, this, &RecentFilesKcm::unmanagedWidgetDefaultState);
d->viewExcludeddApplications->setClearColor(QGuiApplication::palette().window().color());
d->viewExcludeddApplications->rootContext()->setContextProperty(QStringLiteral("applicationModel"), d->excludedApplicationsModel);
d->viewExcludeddApplications->setSource(QUrl::fromLocalFile(KAMD_KCM_DATADIR + QStringLiteral("/qml/recentFiles/ExcludedApplicationView.qml")));
// React to changes
connect(d->radioRememberSpecificApplications, &QAbstractButton::toggled, d->excludedApplicationsModel, &ExcludedApplicationsModel::setEnabled);
connect(d->radioRememberSpecificApplications, &QAbstractButton::toggled, d->kcfg_blockedByDefault, &QCheckBox::setEnabled);
// By default the KCModule (and eventually the kconfigdialogmanager) use
// the index of checked radio button as the value of the setting. In this
// case however we have a mismatch, the radio button "Do not remember"
// (radioDontRememberApplications) has index 1 but in the WhatToRemember
// enum has the value 2! So...
// 1. Let kconfigwidgets know that this QGroupBox will be using a custom
// property to indicate the setting's value ("kcfg_value")
d->kcfg_whatToRemember->setProperty("kcfg_property", QByteArray("kcfg_value"));
// 2. Every time any radio button is clicked, we need to reset the
// kcfg_value and potentially set the dialog to "needs saving" state
// NOTE: Do not use "toggled" since it fires for two buttons each time!
connect(d->radioRememberSpecificApplications, &QAbstractButton::clicked, this, &RecentFilesKcm::whatToRememberWidgetChanged);
connect(d->radioRememberAllApplications, &QAbstractButton::clicked, this, &RecentFilesKcm::whatToRememberWidgetChanged);
connect(d->radioDontRememberApplications, &QAbstractButton::clicked, this, &RecentFilesKcm::whatToRememberWidgetChanged);
// Initial state
d->excludedApplicationsModel->setEnabled(false);
d->messageWidget->setVisible(false);
connect(this, &RecentFilesKcm::defaultsIndicatorsVisibleChanged, this, [this]() {
d->updateUiDefaultIndicator(defaultsIndicatorsVisible());
for (KConfigDialogManager *config : configs()) {
config->setDefaultsIndicatorsVisible(defaultsIndicatorsVisible());
}
});
addConfig(d->pluginConfig, widget());
}
RecentFilesKcm::~RecentFilesKcm()
{
}
void RecentFilesKcm::defaults()
{
d->excludedApplicationsModel->defaults();
// Click and not setChecked to trigger whatToRememberWidgetChanged()
d->radioRememberAllApplications->click();
KCModule::defaults();
}
void RecentFilesKcm::load()
{
d->excludedApplicationsModel->load();
KCModule::load();
auto wtr = d->pluginConfig->whatToRemember();
d->radioRememberSpecificApplications->setChecked(wtr == SpecificApplications);
d->radioDontRememberApplications->setChecked(wtr == NoApplications);
d->radioRememberAllApplications->setChecked(wtr == AllApplications);
d->excludedApplicationsModel->setEnabled(d->radioRememberSpecificApplications->isChecked());
d->kcfg_blockedByDefault->setEnabled(d->radioRememberSpecificApplications->isChecked());
d->updateUiDefaultIndicator(defaultsIndicatorsVisible());
}
void RecentFilesKcm::whatToRememberWidgetChanged(bool)
{
// See ctor for details: tl;dr: reset the what-to-rememeber value
// and set the dialog to "changed" (if save is needed)
// clang-format off
const auto whatToRemember =
d->radioRememberSpecificApplications->isChecked() ? SpecificApplications :
d->radioDontRememberApplications->isChecked() ? NoApplications :
/* otherwise */ AllApplications;
// clang-format on
qCDebug(LOG_KCMS_RECENTFILES) << "whatToRememberWidgetChangeState: " << whatToRemember;
d->kcfg_whatToRemember->setProperty("kcfg_value", whatToRemember);
setNeedsSave(whatToRemember != d->pluginConfig->whatToRemember());
d->updateUiDefaultIndicator(defaultsIndicatorsVisible());
}
void RecentFilesKcm::save()
{
d->excludedApplicationsModel->save();
KCModule::save();
}
void RecentFilesKcm::forget(int count, const QString &what)
{
org::kde::ActivityManager::ResourcesScoring rankingsservice(QStringLiteral(KAMD_DBUS_SERVICE),
QStringLiteral(KAMD_DBUS_RESOURCES_SCORING_PATH),
QDBusConnection::sessionBus());
rankingsservice.DeleteRecentStats(QString(), count, what);
d->messageWidget->animatedShow();
}
void RecentFilesKcm::forgetLastHour()
{
forget(1, QStringLiteral("h"));
}
void RecentFilesKcm::forgetTwoHours()
{
forget(2, QStringLiteral("h"));
}
void RecentFilesKcm::forgetDay()
{
forget(1, QStringLiteral("d"));
}
void RecentFilesKcm::forgetAll()
{
forget(0, QStringLiteral("everything"));
}
void RecentFilesKcm::spinKeepHistoryValueChanged(int value)
{
static auto months = ki18ncp("unit of time. months to keep the history", " month", " months");
if (value) {
d->kcfg_keepHistoryFor->setPrefix(i18nc("for in 'keep history for 5 months'", "For "));
d->kcfg_keepHistoryFor->setSuffix(months.subs(value).toString());
}
}
#include "kcm_recentFiles.moc"
#include "moc_kcm_recentFiles.cpp"

View file

@ -1,52 +0,0 @@
/*
SPDX-FileCopyrightText: 2012-2016 Ivan Cukic <ivan.cukic@kde.org>
SPDX-FileCopyrightText: 2022 Méven Car <meven.car@kdenet.net>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include <KCModule>
#include <KPluginFactory>
#include <utils/d_ptr.h>
class KCoreConfigSkeleton;
/**
* RecentFilesKcm
*/
class RecentFilesKcm : public KCModule
{
Q_OBJECT
public:
explicit RecentFilesKcm(QObject *parent, const KPluginMetaData &data);
~RecentFilesKcm() override;
KCoreConfigSkeleton *pluginConfig();
public Q_SLOTS:
void defaults() override;
void load() override;
void save() override;
private Q_SLOTS:
void forget(int count, const QString &what);
void forgetLastHour();
void forgetTwoHours();
void forgetDay();
void forgetAll();
void spinKeepHistoryValueChanged(int value);
void whatToRememberWidgetChanged(bool checked);
private:
enum WhatToRemember {
AllApplications = 0,
SpecificApplications = 1,
NoApplications = 2,
};
D_PTR;
};

View file

@ -0,0 +1,108 @@
/*
SPDX-FileCopyrightText: 2012-2016 Ivan Cukic <ivan.cukic@kde.org>
SPDX-FileCopyrightText: 2022 Méven Car <meven.car@kdenet.net>
SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "kcm_recentfiles.h"
#include "resourcescoring_interface.h"
using namespace Qt::StringLiterals;
K_PLUGIN_CLASS_WITH_JSON(RecentFilesKcm, "kcm_recentfiles.json")
namespace
{
constexpr inline auto ActivityManagerDBusKey = "org.kde.ActivityManager"_L1;
constexpr inline auto ResourceScoringPathKey = "/ActivityManager/Resources/Scoring"_L1;
}
RecentFilesKcm::RecentFilesKcm(QObject *parent, const KPluginMetaData &data)
: KQuickConfigModule(parent, data)
, m_settings(new KActivityManagerdPluginsSettings(this))
, m_excludedApplicationsModel(new ExcludedApplicationsModel(this))
{
connect(m_excludedApplicationsModel, &ExcludedApplicationsModel::changed, this, [this](bool changed) {
if (changed) {
setNeedsSave(true);
}
});
}
int RecentFilesKcm::whatToRemember() const
{
return m_settings->whatToRemember();
}
void RecentFilesKcm::setWhatToRemember(int whatToRemember)
{
if (whatToRemember == m_settings->whatToRemember()) {
return;
}
m_settings->setWhatToRemember(whatToRemember);
setNeedsSave(true);
Q_EMIT whatToRememberChanged();
}
int RecentFilesKcm::keepHistoryFor() const
{
return m_settings->keepHistoryFor();
}
void RecentFilesKcm::setKeepHistoryFor(int keepHistoryFor)
{
if (keepHistoryFor == m_settings->keepHistoryFor()) {
return;
}
m_settings->setKeepHistoryFor(keepHistoryFor);
setNeedsSave(true);
Q_EMIT keepHistoryForChanged();
}
bool RecentFilesKcm::blockByDefault() const
{
return m_settings->blockedByDefault();
}
void RecentFilesKcm::setBlockByDefault(bool blockByDefault)
{
if (blockByDefault == m_settings->blockedByDefault()) {
return;
}
m_settings->setBlockedByDefault(blockByDefault);
setNeedsSave(true);
Q_EMIT blockByDefaultChanged();
}
ExcludedApplicationsModel *RecentFilesKcm::excludedApplicationsModel() const
{
return m_excludedApplicationsModel;
}
void RecentFilesKcm::defaults()
{
m_excludedApplicationsModel->defaults();
setWhatToRemember(0);
}
void RecentFilesKcm::load()
{
m_excludedApplicationsModel->load();
}
void RecentFilesKcm::save()
{
m_excludedApplicationsModel->save();
m_settings->save();
}
void RecentFilesKcm::forget(int count, const QString &what)
{
org::kde::ActivityManager::ResourcesScoring rankingsservice(ActivityManagerDBusKey, ResourceScoringPathKey, QDBusConnection::sessionBus());
rankingsservice.DeleteRecentStats(QString(), count, what);
}
#include "kcm_recentfiles.moc"

View file

@ -0,0 +1,63 @@
/*
SPDX-FileCopyrightText: 2012-2016 Ivan Cukic <ivan.cukic@kde.org>
SPDX-FileCopyrightText: 2022 Méven Car <meven.car@kdenet.net>
SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include <KQuickConfigModule>
#include "excludedapplicationsmodel.h"
#include "kactivitymanagerd_plugins_settings.h"
/**
* RecentFilesKcm
*/
class RecentFilesKcm : public KQuickConfigModule
{
Q_OBJECT
Q_PROPERTY(int keepHistoryFor READ keepHistoryFor WRITE setKeepHistoryFor NOTIFY keepHistoryForChanged)
Q_PROPERTY(int whatToRemember READ whatToRemember WRITE setWhatToRemember NOTIFY whatToRememberChanged)
Q_PROPERTY(bool blockByDefault READ blockByDefault WRITE setBlockByDefault NOTIFY blockByDefaultChanged)
Q_PROPERTY(ExcludedApplicationsModel *excludedApplicationsModel READ excludedApplicationsModel CONSTANT)
public:
enum WhatToRemember {
AllApplications = 0,
SpecificApplications = 1,
NoApplications = 2,
};
explicit RecentFilesKcm(QObject *parent, const KPluginMetaData &data);
int whatToRemember() const;
void setWhatToRemember(int whatToRemember);
int keepHistoryFor() const;
void setKeepHistoryFor(int keepHistoryFor);
bool blockByDefault() const;
void setBlockByDefault(bool blockByDefault);
ExcludedApplicationsModel *excludedApplicationsModel() const;
void defaults() override;
void load() override;
void save() override;
Q_INVOKABLE void forget(int count, const QString &what);
Q_SIGNALS:
void whatToRememberChanged();
void keepHistoryForChanged();
void blockByDefaultChanged();
private:
KActivityManagerdPluginsSettings *m_settings;
ExcludedApplicationsModel *m_excludedApplicationsModel;
};

View file

@ -1,56 +0,0 @@
/* vim:set foldenable foldmethod=marker:
SPDX-FileCopyrightText: 2012 Ivan Cukic <ivan.cukic@kde.org>
SPDX-FileCopyrightText: 2025 Nate Graham <nate@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
QQC2.ScrollView {
id: scrollview
readonly property bool narrowMode: width < Kirigami.Units.gridUnit * 30
enabled: applicationModel.enabled
Kirigami.StyleHints.showFramedBackground: true
GridLayout {
id: grid
// The delegate brings its own
rowSpacing: 0
columnSpacing: 0
columns: scrollview.narrowMode ? 1 : 4
clip: true
Repeater {
model: applicationModel
delegate: QQC2.CheckDelegate {
Layout.preferredWidth: Math.round(scrollview.availableWidth/ grid.columns) - 1
Layout.preferredHeight: scrollview.narrowMode ? -1 : Kirigami.Units.gridUnit * 5
hoverEnabled: applicationModel.enabled
display: scrollview.narrowMode ? QQC2.AbstractButton.TextBesideIcon : QQC2.ItemDelegate.TextUnderIcon
icon.name: model.icon
icon.height: Kirigami.Settings.isMobile || display === QQC2.ItemDelegate.TextUnderIcon ? Kirigami.Units.iconSizes.medium : Kirigami.Units.iconSizes.smallMedium
text: model.title
opacity: model.blocked ? 0.3 : 1.0
Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } }
checked: !model.blocked
onToggled: applicationModel.toggleApplicationBlocked(model.index)
}
}
}
}

View file

@ -1,6 +0,0 @@
#ifndef KCM_CONFIG_FEATURES_H_
#define KCM_CONFIG_FEATURES_H_
#cmakedefine KAMD_KCM_DATADIR "@KAMD_KCM_DATADIR@"
#endif

View file

@ -0,0 +1,59 @@
/* vim:set foldenable foldmethod=marker:
SPDX-FileCopyrightText: 2012 Ivan Cukic <ivan.cukic@kde.org>
SPDX-FileCopyrightText: 2025 Nate Graham <nate@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
QQC2.ScrollView {
id: root
required property var model
required property real pageWidth
implicitWidth: grid.cellWidth * (pageWidth < Kirigami.Units.gridUnit * 55 ? 2 : 4)
leftPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
Kirigami.StyleHints.showFramedBackground: true
GridView {
id: grid
cellWidth: Kirigami.Units.gridUnit * 10
cellHeight: Kirigami.Units.gridUnit * 5
model: root.model
delegate: QQC2.CheckDelegate {
id: applicationDelegate
required property int index
required property string title
required property string iconSource
required property bool blocked
width: GridView.view.cellWidth
height: GridView.view.cellHeight
hoverEnabled: root.enabled
display: QQC2.ItemDelegate.TextUnderIcon
icon.name: applicationDelegate.iconSource
icon.height: Kirigami.Settings.isMobile || display === QQC2.ItemDelegate.TextUnderIcon ? Kirigami.Units.iconSizes.medium : Kirigami.Units.iconSizes.smallMedium
text: applicationDelegate.title
opacity: applicationDelegate.blocked ? 0.3 : 1.0
Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } }
checked: !applicationDelegate.blocked
onToggled: root.model.toggleApplicationBlocked(applicationDelegate.index)
}
}
}

View file

@ -1,167 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RecentFiles</class>
<widget class="QWidget" name="RecentFiles">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>629</width>
<height>500</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="KMessageWidget" name="messageWidget">
<property name="text">
<string>Cleared the activity history.</string>
</property>
<property name="messageType">
<enum>KMessageWidget::Positive</enum>
</property>
<property name="icon">
<iconset theme="dialog-ok-apply"/>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="formAlignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="labelKeepHistory">
<property name="text">
<string>Keep history:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSpinBox" name="kcfg_keepHistoryFor"/>
</item>
<item>
<widget class="QPushButton" name="buttonClearRecentHistory">
<property name="text">
<string>Clear History</string>
</property>
<property name="icon">
<iconset theme="edit-clear-history"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="kcfg_whatToRemember">
<property name="title">
<string/>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="radioRememberAllApplications">
<property name="text">
<string>For a&amp;ll applications</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioDontRememberApplications">
<property name="text">
<string>&amp;Do not remember</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioRememberSpecificApplications">
<property name="text">
<string>O&amp;nly for specific applications:</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelRememberDocuments">
<property name="text">
<string>Remember opened documents:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QQuickWidget" name="viewExcludeddApplications">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>200</height>
</size>
</property>
<property name="resizeMode">
<enum>QQuickWidget::SizeRootObjectToView</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_1">
<property name="leftMargin">
<number>6</number>
</property>
<item>
<widget class="QCheckBox" name="kcfg_blockedByDefault">
<property name="text">
<string>Exclude applications not on the list</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KMessageWidget</class>
<extends>QFrame</extends>
<header>kmessagewidget.h</header>
</customwidget>
<customwidget>
<class>QQuickWidget</class>
<extends>QWidget</extends>
<header location="global">QtQuickWidgets/QQuickWidget</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>radioRememberSpecificApplications</sender>
<signal>toggled(bool)</signal>
<receiver>kcfg_blockedByDefault</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>352</x>
<y>167</y>
</hint>
<hint type="destinationlabel">
<x>252</x>
<y>481</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -0,0 +1,130 @@
/*
* SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
*
* GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami.forms as Forms
import org.kde.kcmutils as KCMUtils
import org.kde.plasma.private.kcm_recentfiles as RecentFiles
KCMUtils.SimpleKCM {
id: root
Forms.Form {
Forms.FormGroup {
Forms.FormEntry {
title: i18ndc("kcm_recentFiles", "@label", "Keep History:")
contentItem: RowLayout {
QQC2.SpinBox {
from: 0
to: 999
value: root.KCMUtils.ConfigModule.keepHistoryFor
onValueModified: root.KCMUtils.ConfigModule.keepHistoryFor = value
readonly property regexp numberExtractionRegExp: /\D*?(-?\d*\.?\d*)\D*$/
textFromValue: (value) => {
return i18ndcp("kcm_recentFiles", "@option The number of months to remember recent file history", "For %1 month", "For %1 months", value)
}
valueFromText: (text, locale) => {
return Number.fromLocaleString(locale, numberExtractionRegExp.exec(text)[1])
}
}
QQC2.ComboBox {
displayText: i18ndc("kcm_recentFiles", "@label", "Clear history")
textRole: "name"
valueRole: "value"
model: [
{
name: i18ndc("kcm_recentFiles", "@option", "Forget the last hour"),
value: 0,
amount: 1,
type: "h"
},
{
name: i18ndc("kcm_recentFiles", "@option", "Forget the last two hours"),
value: 1,
amount: 2,
type: "h"
},
{
name: i18ndc("kcm_recentFiles", "@option", "Forget the last day"),
value: 2,
amount: 1,
type: "d"
},
{
name: i18ndc("kcm_recentFiles", "@option", "Forget everything"),
value: 3,
amount: 0,
type: "everything"
}
]
onActivated: console.warn(currentValue, model[currentValue].amount, model[currentValue].type)
}
}
}
QQC2.ButtonGroup {
id: applicationHistoryGroup
}
Forms.FormEntry {
title: i18ndc("kcm_recentFiles", "@label", "Remember opened documents:")
contentItem: QQC2.RadioButton {
text: i18nc("@option:radio", "For all applications")
checked: root.KCMUtils.ConfigModule.whatToRemember === 0
onToggled: if (checked) {
root.KCMUtils.ConfigModule.whatToRemember = 0
}
QQC2.ButtonGroup.group: applicationHistoryGroup
}
}
Forms.FormEntry {
contentItem: QQC2.RadioButton {
text: i18nc("@option:radio", "Do not remember")
checked: root.KCMUtils.ConfigModule.whatToRemember === 2
onToggled: if (checked) {
root.KCMUtils.ConfigModule.whatToRemember = 2
}
QQC2.ButtonGroup.group: applicationHistoryGroup
}
}
Forms.FormEntry {
contentItem: QQC2.RadioButton {
text: i18nc("@option:radio", "Only for specific applications")
checked: root.KCMUtils.ConfigModule.whatToRemember === 1
onToggled: if (checked) {
root.KCMUtils.ConfigModule.whatToRemember = 1
}
QQC2.ButtonGroup.group: applicationHistoryGroup
}
}
Forms.FormEntry {
fullWidth: true
contentItem: ExcludedApplicationView {
Layout.fillWidth: true
enabled: root.KCMUtils.ConfigModule.whatToRemember === 1
model: root.KCMUtils.ConfigModule.excludedApplicationsModel
pageWidth: root.width
}
}
Forms.FormEntry {
fullWidth: true
contentItem: QQC2.CheckBox {
enabled: root.KCMUtils.ConfigModule.whatToRemember === 1
text: i18nc("@option:radio", "Exclude applications not on the list")
checked: root.KCMUtils.ConfigModule.blockByDefault
onToggled: root.KCMUtils.ConfigModule.blockByDefault = !root.KCMUtils.ConfigModule.blockByDefault
}
}
}
}
}

View file

@ -1,38 +0,0 @@
/*
SPDX-FileCopyrightText: 2012-2016 Ivan Cukic <ivan.cukic@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include <memory>
namespace kamd
{
namespace utils
{
template<typename T>
class d_ptr
{
private:
std::unique_ptr<T> d;
public:
d_ptr();
template<typename... Args>
d_ptr(Args &&...);
~d_ptr();
T *operator->() const;
};
#define D_PTR \
class Private; \
friend class Private; \
const ::kamd::utils::d_ptr<Private> d
} // namespace utils
} // namespace kamd

View file

@ -1,40 +0,0 @@
/*
SPDX-FileCopyrightText: 2012-2016 Ivan Cukic <ivan.cukic@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include <utility>
namespace kamd
{
namespace utils
{
template<typename T>
d_ptr<T>::d_ptr()
: d(new T())
{
}
template<typename T>
template<typename... Args>
d_ptr<T>::d_ptr(Args &&...args)
: d(new T(std::forward<Args>(args)...))
{
}
template<typename T>
d_ptr<T>::~d_ptr()
{
}
template<typename T>
T *d_ptr<T>::operator->() const
{
return d.get();
}
} // namespace utils
} // namespace kamd