Commit be33b5f1 authored by Kai Uwe Broulik's avatar Kai Uwe Broulik 🍇

Finishing touches

- Make current index handling and filtering work
  Store a persistent model index and update the current index if neccessary
- Add some explanatory labels here and there
- Have two categories: "Applications" at the top and "System Services" ad the bottom
- Drop custom events configuration, use old KNotifyConfig for code simplicity right now
parent f210fb10
......@@ -15,6 +15,7 @@ target_link_libraries(kcm_notifications
KF5::GuiAddons
KF5::I18n
KF5::QuickAddons
KF5::NotifyConfig
KF5::Service
PW::LibNotificationManager
)
......
......@@ -40,39 +40,16 @@ void FilterProxyModel::setQuery(const QString &query)
m_query = query;
invalidateFilter();
emit queryChanged();
emit currentIndexChanged();
}
}
int FilterProxyModel::currentIndex() const
{
if (m_currentIndex.isValid()) {
return m_currentIndex.row();
}
return -1;
}
void FilterProxyModel::setCurrentIndex(const QPersistentModelIndex &idx)
{
const int oldIndex = currentIndex();
m_currentIndex = idx;
if (oldIndex != currentIndex()) {
emit currentIndexChanged();
}
}
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 QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
const QString display = idx.data(Qt::DisplayRole).toString();
if (display.contains(m_query, Qt::CaseInsensitive)) {
......
......@@ -28,8 +28,6 @@ class FilterProxyModel : public QSortFilterProxyModel
Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged)
Q_PROPERTY(int currentIndex READ currentIndex NOTIFY currentIndexChanged)
public:
FilterProxyModel(QObject *parent = nullptr);
~FilterProxyModel() override;
......@@ -37,20 +35,12 @@ public:
QString query() const;
void setQuery(const QString &query);
int currentIndex() const;
Q_INVOKABLE void setCurrentIndex(const QPersistentModelIndex &idx);
Q_INVOKABLE QPersistentModelIndex makePersistentModelIndex(int row) const;
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
Q_SIGNALS:
void queryChanged();
void currentIndexChanged();
private:
QString m_query;
QPersistentModelIndex m_currentIndex;
};
......@@ -20,12 +20,20 @@
#include "kcm.h"
#include <QGuiApplication>
#include <QCommandLineParser>
#include <QDialog>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QQuickItem>
#include <QQuickWindow>
#include <QStandardPaths>
#include <QVBoxLayout>
#include <QWindow>
#include <KAboutData>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KNotifyConfigWidget>
#include <KPluginFactory>
#include <algorithm>
......@@ -60,6 +68,29 @@ KCMNotifications::KCMNotifications(QObject *parent, const QVariantList &args)
connect(m_sourcesModel, &SourcesModel::pendingDeletionsChanged, this, [this] {
setNeedsSave(true);
});
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()
......@@ -82,26 +113,102 @@ NotificationManager::Settings *KCMNotifications::settings() const
return m_settings;
}
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 &notifyRcName)
{
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 &notifyRcName, 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(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();
m_sourcesModel->load();
//m_config->markAsClean();
//m_config->reparseConfiguration();
}
void KCMNotifications::save()
{
processPendingDeletions();
m_settings->save();
//setNeedsSave(false);
}
void KCMNotifications::defaults()
{
m_settings->defaults();
//setNeedsSave(true);
}
void KCMNotifications::processPendingDeletions()
......
......@@ -20,9 +20,6 @@
#pragma once
#include <QScopedPointer>
#include <QPointer>
#include <KQuickAddons/ConfigModule>
class SourcesModel;
......@@ -41,6 +38,11 @@ class KCMNotifications : public KQuickAddons::ConfigModule
Q_PROPERTY(NotificationManager::Settings *settings READ settings CONSTANT)
// 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;
......@@ -57,11 +59,27 @@ public:
NotificationManager::Settings *settings() const;
QString initialDesktopEntry() const;
void setInitialDesktopEntry(const QString &desktopEntry);
QString initialNotifyRcName() const;
void setInitialNotifyRcName(const QString &notifyRcName);
QString initialEventId() const;
void setInitialEventId(const QString &eventId);
Q_INVOKABLE void configureEvents(const QString &notifyRcName, 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();
......@@ -70,4 +88,8 @@ private:
NotificationManager::Settings *m_settings;
QString m_initialDesktopEntry;
QString m_initialNotifyRcName;
QString m_initialEventId;
};
/*
* 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
import org.kde.notificationmanager 1.0 as NotificationManager
import org.kde.private.kcms.notifications 1.0 as Private
ColumnLayout {
id: configColumn
property var rootIndex
readonly property string appDisplayName: kcm.sourcesModel.data(rootIndex, Qt.DisplayRole) || ""
readonly property string appIconName: kcm.sourcesModel.data(rootIndex, Qt.DecorationRole) || ""
readonly property string desktopEntry: kcm.sourcesModel.data(rootIndex, Private.SourcesModel.DesktopEntryRole) || ""
readonly property string notifyRcName: kcm.sourcesModel.data(rootIndex, Private.SourcesModel.NotifyRcNameRole) || ""
property int behavior: {
if (configColumn.desktopEntry) {
return kcm.settings.applicationBehavior(configColumn.desktopEntry);
} else if (configColumn.notifyRcName) {
return kcm.settings.serviceBehavior(configColumn.notifyRcName);
}
return -1;
}
function setBehavior(flag, enable) {
var newBehavior = behavior;
if (enable) {
newBehavior |= flag;
} else {
newBehavior &= ~flag;
}
if (configColumn.desktopEntry) {
return kcm.settings.setApplicationBehavior(configColumn.desktopEntry, newBehavior);
} else if (configColumn.notifyRcName) {
return kcm.settings.setServiceBehavior(configColumn.notifyRcName, newBehavior);
}
}
function configureEvents(eventId) {
kcm.configureEvents(configColumn.notifyRcName, eventId, this)
}
spacing: Kirigami.Units.smallSpacing
Kirigami.FormLayout {
id: form
RowLayout {
Kirigami.FormData.isSection: true
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
source: configColumn.appIconName
}
Kirigami.Heading {
level: 2
text: configColumn.appDisplayName
elide: Text.ElideRight
textFormat: Text.PlainText
}
}
QtControls.CheckBox {
id: showPopupsCheck
text: i18n("Show popups")
checked: configColumn.behavior & NotificationManager.Settings.ShowPopups
onClicked: configColumn.setBehavior(NotificationManager.Settings.ShowPopups, checked)
}
RowLayout {
Item {
width: Kirigami.Units.gridUnit
}
QtControls.CheckBox {
text: i18n("Show in do not disturb mode")
enabled: showPopupsCheck.checked
checked: configColumn.behavior & NotificationManager.Settings.ShowPopupsInDoNotDisturbMode
onClicked: configColumn.setBehavior(NotificationManager.Settings.ShowPopupsInDoNotDisturbMode, checked)
}
}
QtControls.CheckBox {
text: i18n("Show in history")
checked: configColumn.behavior & NotificationManager.Settings.ShowInHistory
onClicked: configColumn.setBehavior(NotificationManager.Settings.ShowInHistory, checked)
}
QtControls.CheckBox {
text: i18n("Show notification badges")
enabled: !!configColumn.desktopEntry
checked: configColumn.behavior & NotificationManager.Settings.ShowBadges
onClicked: configColumn.setBehavior(NotificationManager.Settings.ShowBadges, checked)
}
Kirigami.Separator {
Kirigami.FormData.isSection: true
}
QtControls.Button {
id: configureEventsButton
text: i18n("Configure Events...")
icon.name: "preferences-desktop-notification"
visible: !!configColumn.notifyRcName
onClicked: configColumn.configureEvents()
}
}
QtControls.Label {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: form.implicitWidth
text: i18n("This application does not support configuring notifications on a per-event basis.");
wrapMode: Text.WordWrap
visible: !configColumn.notifyRcName
}
// compact layout
Item {
Layout.fillHeight: true
}
}
/*
* 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]
}
}
}
}
This diff is collapsed.
......@@ -22,7 +22,7 @@ import QtQuick 2.9
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.3 as QtControls
import org.kde.kirigami 2.7 as Kirigami
import org.kde.kirigami 2.8 as Kirigami
import org.kde.kcm 1.2 as KCM
import org.kde.private.kcms.notifications 1.0 as Private
......@@ -31,12 +31,41 @@ Kirigami.Page {
id: sourcesPage
title: i18n("Application Settings")
Component.onCompleted: {
// TODO the page is push'd in Qt.callLater, why is the model still not loaded when we get here?
Qt.callLater(function() {
if (kcm.initialDesktopEntry) {
appConfiguration.rootIndex = kcm.sourcesModel.persistentIndexForDesktopEntry(kcm.initialDesktopEntry);
} else if (kcm.initialNotifyRcName) {
appConfiguration.rootIndex = kcm.sourcesModel.persistentIndexForNotifyRcName(kcm.initialNotifyRcName);
if (kcm.initialEventId) {
appConfiguration.configureEvents(kcm.initialEventId);
}
}
kcm.initialDesktopEntry = "";
kcm.initialNotifyRcName = "";
kcm.initialEventId = "";
});
}
Binding {
target: kcm.filteredModel
property: "query"
value: searchField.text
}
// We need to manually keep track of the index as we store the sourceModel index
// and then use a proxy model to filter it. We don't get any QML change signals anywhere
// and ListView needs a currentIndex number
Connections {
target: kcm.filteredModel
onRowsRemoved: sourcesList.updateCurrentIndex()
onRowsInserted: sourcesList.updateCurrentIndex()
// TODO re-create model index if possible
onModelReset: appConfiguration.rootIndex = undefined
}
RowLayout {
id: rootRow
anchors.fill: parent
......@@ -45,19 +74,9 @@ Kirigami.Page {
Layout.minimumWidth: Kirigami.Units.gridUnit * 12
Layout.preferredWidth: Math.round(rootRow.width / 3)
/*Kirigami.SearchField {
Layout.fillWidth: true
}*/
QtControls.TextField { // FIXME search field
Kirigami.SearchField {
id: searchField
Layout.fillWidth: true
placeholderText: i18n("Search...")
// TODO autofocus this?
Shortcut {
sequence: StandardKey.Find
onActivated: searchField.forceActiveFocus()
}
}
QtControls.ScrollView {
......@@ -75,16 +94,17 @@ Kirigami.Page {
anchors {
fill: parent
margins: 2