diff --git a/kcms/notifications/CMakeLists.txt b/kcms/notifications/CMakeLists.txt index a46825e2b..a4233eab5 100644 --- a/kcms/notifications/CMakeLists.txt +++ b/kcms/notifications/CMakeLists.txt @@ -1,29 +1,30 @@ # 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 KF5::KCMUtils KF5::CoreAddons KF5::Declarative + KF5::GlobalAccel KF5::GuiAddons KF5::I18n KF5::QuickAddons KF5::NotifyConfig KF5::Service PW::LibNotificationManager ) 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) diff --git a/kcms/notifications/kcm.cpp b/kcms/notifications/kcm.cpp index a77fae621..ec6833b80 100644 --- a/kcms/notifications/kcm.cpp +++ b/kcms/notifications/kcm.cpp @@ -1,209 +1,260 @@ /* * Copyright (C) 2019 Kai Uwe Broulik * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kcm.h" +#include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include "sourcesmodel.h" #include "filterproxymodel.h" #include K_PLUGIN_FACTORY_WITH_JSON(KCMNotificationsFactory, "kcm_notifications.json", registerPlugin();) KCMNotifications::KCMNotifications(QObject *parent, const QVariantList &args) : KQuickAddons::ConfigModule(parent, args) , m_sourcesModel(new SourcesModel(this)) , m_filteredModel(new FilterProxyModel(this)) , m_settings(new NotificationManager::Settings(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(); qmlProtectModule(uri, 1); 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); + // for KGlobalAccel... + // keep in sync with globalshortcuts.cpp in notification plasmoid! + m_toggleDoNotDisturbAction->setObjectName(QStringLiteral("toggle do not disturb")); + m_toggleDoNotDisturbAction->setProperty("componentName", QStringLiteral("plasmashell")); + m_toggleDoNotDisturbAction->setText(i18n("Toggle do not disturb")); + m_toggleDoNotDisturbAction->setIcon(QIcon::fromTheme(QStringLiteral("notifications-disabled"))); + QStringList stringArgs; stringArgs.reserve(args.count() + 1); // need to add a fake argv[0] for QCommandLineParser stringArgs.append(QStringLiteral("kcm_notifications")); for (const QVariant &arg : args) { stringArgs.append(arg.toString()); } QCommandLineParser parser; QCommandLineOption desktopEntryOption(QStringLiteral("desktop-entry"), QString(), QStringLiteral("desktop-entry")); parser.addOption(desktopEntryOption); QCommandLineOption notifyRcNameOption(QStringLiteral("notifyrc"), QString(), QStringLiteral("notifyrcname")); parser.addOption(notifyRcNameOption); QCommandLineOption eventIdOption(QStringLiteral("event-id"), QString(), QStringLiteral("event-id")); parser.addOption(eventIdOption); parser.parse(stringArgs); setInitialDesktopEntry(parser.value(desktopEntryOption)); setInitialNotifyRcName(parser.value(notifyRcNameOption)); setInitialEventId(parser.value(eventIdOption)); } KCMNotifications::~KCMNotifications() { } SourcesModel *KCMNotifications::sourcesModel() const { return m_sourcesModel; } FilterProxyModel *KCMNotifications::filteredModel() const { return m_filteredModel; } NotificationManager::Settings *KCMNotifications::settings() const { return m_settings; } +QKeySequence KCMNotifications::toggleDoNotDisturbShortcut() const +{ + return m_toggleDoNotDisturbShortcut; +} + +void KCMNotifications::setToggleDoNotDisturbShortcut(const QKeySequence &shortcut) +{ + if (m_toggleDoNotDisturbShortcut == shortcut) { + return; + } + + m_toggleDoNotDisturbShortcut = shortcut; + m_toggleDoNotDisturbShortcutDirty = true; + emit toggleDoNotDisturbShortcutChanged(); + setNeedsSave(true); +} + QString KCMNotifications::initialDesktopEntry() const { return m_initialDesktopEntry; } void KCMNotifications::setInitialDesktopEntry(const QString &desktopEntry) { if (m_initialDesktopEntry != desktopEntry) { m_initialDesktopEntry = desktopEntry; emit initialDesktopEntryChanged(); } } QString KCMNotifications::initialNotifyRcName() const { return m_initialNotifyRcName; } void KCMNotifications::setInitialNotifyRcName(const QString ¬ifyRcName) { if (m_initialNotifyRcName != notifyRcName) { m_initialNotifyRcName = notifyRcName; emit initialNotifyRcNameChanged(); } } QString KCMNotifications::initialEventId() const { return m_initialEventId; } void KCMNotifications::setInitialEventId(const QString &eventId) { if (m_initialEventId != eventId) { m_initialEventId = eventId; emit initialEventIdChanged(); } } void KCMNotifications::configureEvents(const QString ¬ifyRcName, const QString &eventId, QQuickItem *ctx) { // We're not using KNotifyConfigWidget::configure here as we want to handle the // saving ourself (so we Apply with all other KCM settings) but there's no way // to access the config object :( // We also need access to the QDialog so we can set the KCM as transient parent. QDialog *dialog = new QDialog(nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("Configure Notifications")); if (ctx && ctx->window()) { dialog->winId(); // so it creates windowHandle dialog->windowHandle()->setTransientParent(QQuickRenderControl::renderWindowFor(ctx->window())); dialog->setModal(true); } KNotifyConfigWidget *w = new KNotifyConfigWidget(dialog); QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel); buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(w); layout->addWidget(buttonBox); dialog->setLayout(layout); // TODO we should only save settings when clicking Apply in the main UI connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, w, &KNotifyConfigWidget::save); connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, w, &KNotifyConfigWidget::save); connect(w, &KNotifyConfigWidget::changed, buttonBox->button(QDialogButtonBox::Apply), &QPushButton::setEnabled); connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); w->setApplication(notifyRcName); w->selectEvent(eventId); dialog->show(); } void KCMNotifications::load() { m_settings->load(); + + const QKeySequence toggleDoNotDisturbShortcut = KGlobalAccel::self()->globalShortcut( + m_toggleDoNotDisturbAction->property("componentName").toString(), + m_toggleDoNotDisturbAction->objectName()).value(0); + + if (m_toggleDoNotDisturbShortcut != toggleDoNotDisturbShortcut) { + m_toggleDoNotDisturbShortcut = toggleDoNotDisturbShortcut; + emit toggleDoNotDisturbShortcutChanged(); + } + + m_toggleDoNotDisturbShortcutDirty = false; + setNeedsSave(false); } void KCMNotifications::save() { m_settings->save(); + + if (m_toggleDoNotDisturbShortcutDirty) { + // KeySequenceItem will already have checked whether the shortcut is available + KGlobalAccel::self()->setShortcut(m_toggleDoNotDisturbAction, + {m_toggleDoNotDisturbShortcut}, + KGlobalAccel::NoAutoloading); + } + + setNeedsSave(false); } void KCMNotifications::defaults() { m_settings->defaults(); + + setToggleDoNotDisturbShortcut(QKeySequence()); } #include "kcm.moc" diff --git a/kcms/notifications/kcm.h b/kcms/notifications/kcm.h index feccafef3..8d09ab0ef 100644 --- a/kcms/notifications/kcm.h +++ b/kcms/notifications/kcm.h @@ -1,88 +1,105 @@ /* * Copyright (c) 2019 Kai Uwe Broulik * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include +#include + +class QAction; + class SourcesModel; class FilterProxyModel; namespace NotificationManager { class Settings; } class KCMNotifications : public KQuickAddons::ConfigModule { 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(QKeySequence toggleDoNotDisturbShortcut + READ toggleDoNotDisturbShortcut + WRITE setToggleDoNotDisturbShortcut + NOTIFY toggleDoNotDisturbShortcutChanged) + // So it can show the respective settings module right away Q_PROPERTY(QString initialDesktopEntry READ initialDesktopEntry WRITE setInitialDesktopEntry NOTIFY initialDesktopEntryChanged) Q_PROPERTY(QString initialNotifyRcName READ initialNotifyRcName WRITE setInitialNotifyRcName NOTIFY initialNotifyRcNameChanged) Q_PROPERTY(QString initialEventId READ initialEventId WRITE setInitialEventId NOTIFY initialEventIdChanged) public: KCMNotifications(QObject *parent, const QVariantList &args); ~KCMNotifications() override; SourcesModel *sourcesModel() const; FilterProxyModel *filteredModel() const; NotificationManager::Settings *settings() const; + QKeySequence toggleDoNotDisturbShortcut() const; + void setToggleDoNotDisturbShortcut(const QKeySequence &shortcut); + Q_SIGNAL void toggleDoNotDisturbShortcutChanged(); + QString initialDesktopEntry() const; void setInitialDesktopEntry(const QString &desktopEntry); QString initialNotifyRcName() const; void setInitialNotifyRcName(const QString ¬ifyRcName); QString initialEventId() const; void setInitialEventId(const QString &eventId); Q_INVOKABLE void configureEvents(const QString ¬ifyRcName, const QString &eventId, QQuickItem *ctx = nullptr); public Q_SLOTS: void load() override; void save() override; void defaults() override; signals: void initialDesktopEntryChanged(); void initialNotifyRcNameChanged(); void initialEventIdChanged(); private: void processPendingDeletions(); SourcesModel *m_sourcesModel; FilterProxyModel *m_filteredModel; NotificationManager::Settings *m_settings; + QAction *m_toggleDoNotDisturbAction; + QKeySequence m_toggleDoNotDisturbShortcut; + bool m_toggleDoNotDisturbShortcutDirty = false; + QString m_initialDesktopEntry; QString m_initialNotifyRcName; QString m_initialEventId; }; diff --git a/kcms/notifications/package/contents/ui/main.qml b/kcms/notifications/package/contents/ui/main.qml index 6bcf3fcdf..c7f3abd66 100644 --- a/kcms/notifications/package/contents/ui/main.qml +++ b/kcms/notifications/package/contents/ui/main.qml @@ -1,236 +1,248 @@ /* * Copyright 2019 Kai Uwe Broulik * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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.kquickcontrols 2.0 as KQuickControls import org.kde.kcm 1.2 as KCM import org.kde.notificationmanager 1.0 as NotificationManager 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 readonly property string ourServerVendor: "KDE" readonly property string ourServerName: "Plasma" readonly property NotificationManager.ServerInfo currentOwnerInfo: NotificationManager.Server.currentOwner readonly property bool notificationsAvailable: currentOwnerInfo.status === NotificationManager.ServerInfo.Running && currentOwnerInfo.vendor === ourServerVendor && currentOwnerInfo.name === ourServerName function openSourcesSettings() { // TODO would be nice to re-use the current SourcesPage instead of pushing a new one that lost all state // but there's no pageAt(index) method in KConfigModuleQml kcm.push("SourcesPage.qml"); } Binding { target: kcm property: "needsSave" value: kcm.settings.dirty // TODO or other stuff } Kirigami.FormLayout { Kirigami.InlineMessage { Kirigami.FormData.isSection: true Layout.fillWidth: true type: Kirigami.MessageType.Error text: i18n("Could not find a 'Notifications' widget which is required for displaying notifications."); visible: currentOwnerInfo.status === NotificationManager.ServerInfo.NotRunning } Kirigami.InlineMessage { Kirigami.FormData.isSection: true Layout.fillWidth: true type: Kirigami.MessageType.Information text: { if (currentOwnerInfo.vendor && currentOwnerInfo.name) { return i18nc("Vendor and product name", "Notifications are currently provided by '%1 %2' instead of Plasma.", currentOwnerInfo.vendor, currentOwnerInfo.name); } return i18n("Notifications are currently not provided by Plasma."); } visible: root.currentOwnerInfo.status === NotificationManager.ServerInfo.Running && (currentOwnerInfo.vendor !== root.ourServerVendor || currentOwnerInfo.name !== root.ourServerName) } QtControls.CheckBox { Kirigami.FormData.label: i18n("Do not disturb:") text: i18nc("Do not disturb when screens are mirrored", "When screens are mirrored") checked: kcm.settings.inhibitNotificationsWhenScreensMirrored onClicked: kcm.settings.inhibitNotificationsWhenScreensMirrored = checked enabled: root.notificationsAvailable } QtControls.CheckBox { text: i18n("Show critical notifications") checked: kcm.settings.criticalPopupsInDoNotDisturbMode onClicked: kcm.settings.criticalPopupsInDoNotDisturbMode = checked enabled: root.notificationsAvailable } + RowLayout { + QtControls.Label { + text: i18nc("Turn do not disturb mode on/off with keyboard shortcut", "Toggle with:") + } + + KQuickControls.KeySequenceItem { + keySequence: kcm.toggleDoNotDisturbShortcut + onKeySequenceChanged: kcm.toggleDoNotDisturbShortcut = keySequence + } + } + 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 } 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 } QtControls.CheckBox { text: i18n("Show in history") checked: kcm.settings.lowPriorityHistory onClicked: kcm.settings.lowPriorityHistory = checked enabled: root.notificationsAvailable } QtControls.ButtonGroup { id: positionGroup buttons: [positionCloseToWidget, positionCustomPosition] } QtControls.RadioButton { id: positionCloseToWidget Kirigami.FormData.label: i18n("Popup position:") text: i18nc("Popup position near notification plasmoid", "Near the notification icon") // "widget" checked: kcm.settings.popupPosition === NotificationManager.Settings.CloseToWidget onClicked: kcm.settings.popupPosition = NotificationManager.Settings.CloseToWidget enabled: root.notificationsAvailable } RowLayout { spacing: 0 enabled: root.notificationsAvailable QtControls.RadioButton { id: positionCustomPosition checked: kcm.settings.popupPosition !== NotificationManager.Settings.CloseToWidget activeFocusOnTab: false MouseArea { anchors.fill: parent onClicked: positionCustomButton.clicked() } } QtControls.Button { id: positionCustomButton text: i18n("Choose Custom Position...") icon.name: "preferences-desktop-display" onClicked: kcm.push("PopupPositionPage.qml") } } TextMetrics { id: timeoutSpinnerMetrics font: timeoutSpinner.font text: i18np("%1 second", "%1 seconds", 888) } QtControls.SpinBox { id: timeoutSpinner Kirigami.FormData.label: i18n("Hide popup after:") Layout.preferredWidth: timeoutSpinnerMetrics.width + leftPadding + rightPadding from: 1000 // 1 second to: 120000 // 2 minutes stepSize: 1000 value: kcm.settings.popupTimeout enabled: root.notificationsAvailable 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 } Kirigami.Separator { Kirigami.FormData.isSection: true } QtControls.CheckBox { Kirigami.FormData.label: i18n("Application progress:") text: i18n("Show in task manager") checked: kcm.settings.jobsInTaskManager onClicked: kcm.settings.jobsInTaskManager = checked } QtControls.CheckBox { id: applicationJobsEnabledCheck text: i18nc("Show application jobs in notification widget", "Show in notifications") checked: kcm.settings.jobsInNotifications onClicked: kcm.settings.jobsInNotifications = checked } 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 } } QtControls.CheckBox { Kirigami.FormData.label: i18n("Notification badges:") text: i18n("Show in task manager") checked: kcm.settings.badgesInTaskManager onClicked: kcm.settings.badgesInTaskManager = checked } Kirigami.Separator { Kirigami.FormData.isSection: true } QtControls.Button { Kirigami.FormData.label: i18n("Applications:") text: i18n("Configure...") icon.name: "configure" enabled: root.notificationsAvailable onClicked: root.openSourcesSettings() } } Component.onCompleted: { if (kcm.initialDesktopEntry || kcm.initialNotifyRcName) { // FIXME doing that right in onCompleted doesn't work Qt.callLater(root.openSourcesSettings); } } }