Commit 0117b7c2 authored by Kai Uwe Broulik's avatar Kai Uwe Broulik 🍇

Implement new Notifications KCM

It's only UI with no actual notification reading/writing right now but the design
is pretty much done.

It has settings for Do Not Disturb mode (time-based, activity-based, etc) and moves some of the
settings that are currently spread out all over Plasma into a central place (e.g. task manager
badges and progress as well as popup position settings, as the new notification system will
only have one popup group in total, not one per applet)

There's also support for third-party apps (just server-side popup on/off, no elaborate notifyrc stuff)
and various other bits and pieces.
parent 0333058f
......@@ -44,9 +44,10 @@ add_subdirectory(icons)
add_subdirectory(kded)
add_subdirectory(knotify)
add_subdirectory(formats)
add_subdirectory(spellchecking)
add_subdirectory(notifications)
add_subdirectory(phonon)
add_subdirectory(runners)
add_subdirectory(spellchecking)
add_subdirectory(qtquicksettings)
add_subdirectory(workspaceoptions)
......
# KI18N Translation Domain for this library
add_definitions(-DTRANSLATION_DOMAIN=\"kcm_notifications\")
set(kcm_notifications_SRCS
kcm.cpp
sourcesmodel.cpp
filterproxymodel.cpp
)
add_library(kcm_notifications MODULE ${kcm_notifications_SRCS})
target_link_libraries(kcm_notifications
#Qt5::DBus
KF5::KCMUtils
KF5::Activities
KF5::CoreAddons
KF5::Declarative
KF5::GuiAddons
KF5::I18n
KF5::QuickAddons
KF5::Service
)
kcoreaddons_desktop_to_json(kcm_notifications "kcm_notifications.desktop")
install(FILES kcm_notifications.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR})
install(TARGETS kcm_notifications DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms)
kpackage_install_package(package kcm_notifications kcms)
#! /usr/bin/env bash
$XGETTEXT `find . -name "*.cpp" -o -name "*.qml"` -o $podir/kcm_notifications.pot
/*
* Copyright (C) 2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "filterproxymodel.h"
#include "sourcesmodel.h"
FilterProxyModel::FilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
{
setRecursiveFilteringEnabled(true);
}
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();
}
}
QPersistentModelIndex FilterProxyModel::makePersistentModelIndex(int row) const
{
return QPersistentModelIndex(index(row, 0));
}
bool FilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (m_query.isEmpty()) {
return true;
}
const QModelIndex idx = source_parent.child(source_row, 0);
const QString display = idx.data(Qt::DisplayRole).toString();
if (display.contains(m_query, Qt::CaseInsensitive)) {
return true;
}
return false;
}
/*
* Copyright (c) 2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QSortFilterProxyModel>
class FilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged)
public:
FilterProxyModel(QObject *parent = nullptr);
~FilterProxyModel() override;
QString query() const;
void setQuery(const QString &query);
Q_INVOKABLE QPersistentModelIndex makePersistentModelIndex(int row) const;
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
Q_SIGNALS:
void queryChanged();
private:
QString m_query;
};
/*
* Copyright (C) 2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "kcm.h"
#include <QGuiApplication>
#include <QStandardPaths>
#include <KAboutData>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KActivities/ActivitiesModel>
#include <algorithm>
#include "sourcesmodel.h"
#include "filterproxymodel.h"
K_PLUGIN_FACTORY_WITH_JSON(KCMNotificationsFactory, "kcm_notifications.json", registerPlugin<KCMNotifications>();)
KCMNotifications::KCMNotifications(QObject *parent, const QVariantList &args)
: KQuickAddons::ConfigModule(parent, args)
, m_sourcesModel(new SourcesModel(this))
, m_filteredModel(new FilterProxyModel(this))
, m_activitiesModel(new KActivities::ActivitiesModel(this))
{
const char uri[] = "org.kde.private.kcms.notifications";
qmlRegisterUncreatableType<SourcesModel>(uri, 1, 0, "SourcesModel",
QStringLiteral("Cannot create instances of SourcesModel"));
qmlRegisterType<FilterProxyModel>();
qmlProtectModule(uri, 1);
qmlRegisterType<KActivities::ActivitiesModel>();
KAboutData *about = new KAboutData(QStringLiteral("kcm_notifications"), i18n("Notifications"),
QStringLiteral("5.0"), QString(), KAboutLicense::GPL);
about->addAuthor(i18n("Kai Uwe Broulik"), QString(), QStringLiteral("kde@privat.broulik.de"));
setAboutData(about);
m_filteredModel->setSourceModel(m_sourcesModel);
}
KCMNotifications::~KCMNotifications()
{
}
SourcesModel *KCMNotifications::sourcesModel() const
{
return m_sourcesModel;
}
FilterProxyModel *KCMNotifications::filteredModel() const
{
return m_filteredModel;
}
KActivities::ActivitiesModel *KCMNotifications::activitiesModel() const
{
return m_activitiesModel;
}
void KCMNotifications::load()
{
m_sourcesModel->load();
//m_config->markAsClean();
//m_config->reparseConfiguration();
}
void KCMNotifications::save()
{
setNeedsSave(false);
}
void KCMNotifications::defaults()
{
setNeedsSave(true);
}
#include "kcm.moc"
/*
* Copyright (c) 2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QScopedPointer>
#include <QPointer>
#include <KQuickAddons/ConfigModule>
class SourcesModel;
class FilterProxyModel;
namespace KActivities {
class ActivitiesModel;
}
class KCMNotifications : public KQuickAddons::ConfigModule
{
Q_OBJECT
Q_PROPERTY(SourcesModel *sourcesModel READ sourcesModel CONSTANT)
Q_PROPERTY(FilterProxyModel *filteredModel READ filteredModel CONSTANT)
Q_PROPERTY(KActivities::ActivitiesModel *activitiesModel READ activitiesModel CONSTANT)
public:
KCMNotifications(QObject *parent, const QVariantList &args);
~KCMNotifications() override;
enum Roles {
SchemeNameRole = Qt::UserRole + 1,
PaletteRole,
RemovableRole,
PendingDeletionRole
};
SourcesModel *sourcesModel() const;
FilterProxyModel *filteredModel() const;
KActivities::ActivitiesModel *activitiesModel() const;
public Q_SLOTS:
void load() override;
void save() override;
void defaults() override;
private:
SourcesModel *m_sourcesModel;
FilterProxyModel *m_filteredModel;
KActivities::ActivitiesModel *m_activitiesModel;
};
[Desktop Entry]
Exec=kcmshell5 kcm_notifications
Icon=preferences-desktop-notification-bell
Type=Service
X-KDE-ServiceTypes=KCModule
X-DocPath=kcontrol/notifications/index.html
X-KDE-Library=kcm_notifications
X-KDE-ParentApp=kcontrol
X-KDE-System-Settings-Parent-Category=personalization
X-KDE-Weight=40
Name=Notifications
Comment=Event Notifications and Actions
X-KDE_Keywords=System sounds,Audio,Sound,Notify,Alerts,Notification,popups,disturb,do not disturb,quiet,concentrate,concentration
Categories=Qt;KDE;X-KDE-settings-sound;
/*
* Copyright 2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.9
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.3 as QtControls
import org.kde.kirigami 2.4 as Kirigami
import org.kde.kcm 1.2 as KCM
KCM.SimpleKCM {
title: i18n("Do not Disturb Times")
Kirigami.FormLayout {
width: parent.width
Repeater {
model: 7
Rectangle {
property int dayNumber: (Qt.locale().firstDayOfWeek + index) % 7
Kirigami.FormData.label: Qt.locale().dayName(dayNumber)
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
Layout.fillWidth: true
color: Kirigami.Theme.backgroundColor
height: Kirigami.Units.gridUnit
border {
width: 1
color: Kirigami.Theme.textColor
}
}
}
// FIXME fix formatting (no seconds and timezone), make separate component
QtControls.SpinBox {
enabled: dndTimeCheck.checked
from: 0
to: 23 * 60 + 59
value: 22 * 60
stepSize: 15
textFromValue: function(value, locale) {
return new Date(1,1,2019,Math.floor(value / 60),value%60,0).toLocaleTimeString(locale);
}
}
QtControls.Label {
text: i18nc("Enable between hh:mm and hh:mm", "and")
}
QtControls.SpinBox {
enabled: dndTimeCheck.checked
from: 0
to: 23 * 60 + 59
value: 6 * 60
stepSize: 15
textFromValue: function(value, locale) {
return new Date(1,1,2019,Math.floor(value / 60),value%60,0).toLocaleTimeString(locale);
}
}
}
}
/*
* Copyright 2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.9
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.3 as QtControls
import org.kde.kirigami 2.7 as Kirigami
ColumnLayout {
id: detailsLayout
property alias model: actionsRepeater.model
property Component actionSettingsSound: RowLayout {
QtControls.Button {
icon.name: "media-playback-start"
}
QtControls.TextField {
//text: "Oxygen-Sys-Trash-Emptied"//model.sound
//textFormat: Text.PlainText
//elide: Text.ElideMiddle
//enabled: false
}
QtControls.Button {
icon.name: "folder-open"
}
}
property Component actionSettingsLogfile: RowLayout {
QtControls.TextField {
}
QtControls.Button {
icon.name: "folder-open"
}
}
property Component actionSettingsExecute: RowLayout {
QtControls.TextField {
}
QtControls.Button {
icon.name: "folder-open"
}
}
Repeater {
id: actionsRepeater
model: eventsColumn.actions
RowLayout {
Layout.fillWidth: true
QtControls.CheckBox {
id: actionCheck
Layout.fillWidth: true
text: modelData.label
icon.name: modelData.icon
checked: eventDelegate.isActionEnabled(modelData.key)
onClicked: eventDelegate.setActionEnabled(modelData.key, checked)
enabled: modelData.key !== "Popup" || showPopupsCheck.checked
contentItem: RowLayout {
Item {
width: actionCheck.indicator.width
}
Kirigami.Icon {
source: actionCheck.icon.name
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
}
QtControls.Label {
Layout.fillWidth: true
text: actionCheck.text
elide: Text.ElideRight
}
}
}
Loader {
sourceComponent: detailsLayout["actionSettings" + modelData.key]
}
}
}
}
/*
* Copyright 2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.9
import QtQml.Models 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.3 as QtControls
import org.kde.kirigami 2.7 as Kirigami
import org.kde.kcm 1.2 as KCM
ColumnLayout {
id: eventsColumn
property var rootIndex
property bool customActivitySettings: false
readonly property var actions: [
{key: "Popup", label: i18n("Show popup"), icon: "dialog-information"},
{key: "Sound", label: i18n("Play sound"), icon: "folder-sound"},// "media-playback-start"},
{key: "Logfile", label: i18n("Log to a file"), icon: "text-x-generic"},
{key: "Taskbar", label: i18n("Mark taskbar entry"), icon: "services"},
{key: "Execute", label: i18n("Run command"), icon: "system-run"},
{key: "TTS", label: i18n("Speech"), icon: "text-speak"} // FIXME only when available
]
spacing: Kirigami.Units.smallSpacing
Kirigami.FormLayout {
Layout.fillWidth: true
QtControls.CheckBox {
id: showPopupsCheck
text: i18n("Show popups")
checked: true
}
RowLayout {
Item {
width: Kirigami.Units.gridUnit
}
QtControls.CheckBox {
text: i18n("Show in do not disturb mode")
enabled: showPopupsCheck.checked
}
}
QtControls.CheckBox {
text: i18n("Notification badges")
enabled: !!eventsColumn.desktopEntry
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
visible: eventsList.count > 0
QtControls.ScrollView {
id: eventsScroll
anchors.fill: parent
activeFocusOnTab: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
enabled: !eventsColumn.customActivitySettings
Component.onCompleted: background.visible = true
QtControls.ScrollBar.horizontal.visible: false
ListView {
id: eventsList
anchors {
fill: parent