diff --git a/kcms/CMakeLists.txt b/kcms/CMakeLists.txt index 672d5cfcf..667292edd 100644 --- a/kcms/CMakeLists.txt +++ b/kcms/CMakeLists.txt @@ -1,71 +1,72 @@ remove_definitions(-DQT_NO_CAST_FROM_ASCII -DQT_STRICT_ITERATORS -DQT_NO_CAST_FROM_BYTEARRAY -DQT_NO_KEYWORDS) find_package(Freetype) set_package_properties(Freetype PROPERTIES DESCRIPTION "A font rendering engine" URL "http://www.freetype.org" TYPE OPTIONAL PURPOSE "Needed to build kfontinst, a simple font installer." ) set(libkxftconfig_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/fonts/kxftconfig.cpp ) if(X11_Xkb_FOUND AND XCB_XKB_FOUND) add_subdirectory( keyboard ) endif() if (EVDEV_FOUND AND XORGLIBINPUT_FOUND AND X11_Xinput_FOUND) add_subdirectory( mouse ) endif() add_subdirectory( access ) add_subdirectory( dateandtime ) add_subdirectory( autostart ) add_subdirectory( ksplash ) add_subdirectory( launch ) add_subdirectory( colors ) add_subdirectory( krdb ) add_subdirectory( style ) add_subdirectory( desktoptheme ) add_subdirectory( standard_actions ) add_subdirectory( keys ) add_subdirectory( ksmserver ) add_subdirectory( lookandfeel ) add_subdirectory( nightcolor ) add_subdirectory( hardware ) add_subdirectory( desktoppaths ) add_subdirectory(activities) add_subdirectory(componentchooser) add_subdirectory(emoticons) 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) if (KF5Baloo_FOUND) add_subdirectory(baloo) endif() add_subdirectory(solid_actions) add_subdirectory(cursortheme) if (SYNAPTICS_FOUND AND X11_Xinput_FOUND) add_subdirectory(touchpad) endif() if(FONTCONFIG_FOUND AND FREETYPE_FOUND) add_subdirectory( kfontinst ) endif() if( FREETYPE_FOUND ) if(FONTCONFIG_FOUND ) add_subdirectory( fonts ) endif() endif() diff --git a/kcms/notifications/CMakeLists.txt b/kcms/notifications/CMakeLists.txt new file mode 100644 index 000000000..56c14ce7d --- /dev/null +++ b/kcms/notifications/CMakeLists.txt @@ -0,0 +1,29 @@ +# 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) + diff --git a/kcms/notifications/Messages.sh b/kcms/notifications/Messages.sh new file mode 100644 index 000000000..ee640ee85 --- /dev/null +++ b/kcms/notifications/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name "*.cpp" -o -name "*.qml"` -o $podir/kcm_notifications.pot diff --git a/kcms/notifications/filterproxymodel.cpp b/kcms/notifications/filterproxymodel.cpp new file mode 100644 index 000000000..c2a7af3d6 --- /dev/null +++ b/kcms/notifications/filterproxymodel.cpp @@ -0,0 +1,66 @@ +/* + * 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 "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; +} diff --git a/kcms/notifications/filterproxymodel.h b/kcms/notifications/filterproxymodel.h new file mode 100644 index 000000000..e786fb911 --- /dev/null +++ b/kcms/notifications/filterproxymodel.h @@ -0,0 +1,48 @@ +/* + * 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 + +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; + +}; diff --git a/kcms/notifications/kcm.cpp b/kcms/notifications/kcm.cpp new file mode 100644 index 000000000..d3c44e8cd --- /dev/null +++ b/kcms/notifications/kcm.cpp @@ -0,0 +1,101 @@ +/* + * 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 "sourcesmodel.h" +#include "filterproxymodel.h" + +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_activitiesModel(new KActivities::ActivitiesModel(this)) +{ + const char uri[] = "org.kde.private.kcms.notifications"; + qmlRegisterUncreatableType(uri, 1, 0, "SourcesModel", + QStringLiteral("Cannot create instances of SourcesModel")); + qmlRegisterType(); + qmlProtectModule(uri, 1); + + qmlRegisterType(); + + 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" diff --git a/kcms/notifications/kcm.h b/kcms/notifications/kcm.h new file mode 100644 index 000000000..9a784b58a --- /dev/null +++ b/kcms/notifications/kcm.h @@ -0,0 +1,70 @@ +/* + * 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 + +#include + +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; + +}; diff --git a/kcms/notifications/kcm_notifications.desktop b/kcms/notifications/kcm_notifications.desktop new file mode 100644 index 000000000..5b830b334 --- /dev/null +++ b/kcms/notifications/kcm_notifications.desktop @@ -0,0 +1,19 @@ +[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; diff --git a/kcms/notifications/package/contents/ui/DndTimePage.qml b/kcms/notifications/package/contents/ui/DndTimePage.qml new file mode 100644 index 000000000..ad122c22d --- /dev/null +++ b/kcms/notifications/package/contents/ui/DndTimePage.qml @@ -0,0 +1,79 @@ +/* + * 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.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); + } + } + } +} diff --git a/kcms/notifications/package/contents/ui/EventDetails.qml b/kcms/notifications/package/contents/ui/EventDetails.qml new file mode 100644 index 000000000..9675f421b --- /dev/null +++ b/kcms/notifications/package/contents/ui/EventDetails.qml @@ -0,0 +1,105 @@ +/* + * 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.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] + } + } + } +} diff --git a/kcms/notifications/package/contents/ui/EventsPage.qml b/kcms/notifications/package/contents/ui/EventsPage.qml new file mode 100644 index 000000000..1bba990c8 --- /dev/null +++ b/kcms/notifications/package/contents/ui/EventsPage.qml @@ -0,0 +1,230 @@ +/* + * 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 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 + margins: 2 + //leftMargin: sourcesScroll.QtControls.ScrollBar.vertical.visible ? 2 : internal.scrollBarSpace/2 + 2 + } + clip: true + activeFocusOnTab: true + + keyNavigationEnabled: true + keyNavigationWraps: true + highlightMoveDuration: 0 + + model: DelegateModel { + id: eventsModel + model: eventsColumn.rootIndex ? kcm.filteredModel : null + rootIndex: eventsColumn.rootIndex + onRootIndexChanged: eventsList.currentIndex = -1 + delegate: QtControls.ItemDelegate { + id: eventDelegate + + function isActionEnabled(action) { + return model.actions.indexOf(action) > -1; + } + + function setActionEnabled(action, enable) { + var actions = model.actions; + var idx = actions.indexOf(action); + if (enable && idx === -1) { + actions.push(action); + } else if (!enable && idx > -1) { + actions.splice(idx, 1); + } + model.actions = actions; + } + + width: eventsList.width + text: model.display + onClicked: eventsList.currentIndex = (eventsList.currentIndex === index ? -1 : index) + + contentItem: RowLayout { + //Kirigami.Theme.textColor: eventDelegate.highlighted || eventDelegate.checked || (eventDelegate.pressed && !eventDelegate.checked && !eventDelegate.sectionDelegate) ? Kirigami.Theme.highlightedTextColor : (eventDelegate.enabled ? Kirigami.Theme.textColor : Kirigami.Theme.disabledTextColor) + + /*QtControls.ToolTip { + visible: eventDelegate.hovered + text: model.comment + }*/ + + Kirigami.Icon { + Layout.alignment: Qt.AlignTop + Layout.preferredWidth: Kirigami.Units.iconSizes.small + Layout.preferredHeight: Kirigami.Units.iconSizes.small + source: model.decoration + } + + ColumnLayout { + Layout.fillWidth: true + + RowLayout { + Layout.fillWidth: true + + QtControls.Label { + Layout.fillWidth: true + text: eventDelegate.text + font: eventDelegate.font + //color: + elide: Text.ElideRight + } + + Repeater { + model: eventsColumn.actions + + // TODO use abstract button? + QtControls.AbstractButton { + id: actionStripButton + Layout.preferredWidth: Kirigami.Units.iconSizes.small + Layout.preferredHeight: Kirigami.Units.iconSizes.small + icon.name: modelData.icon + checkable: true + checked: eventDelegate.isActionEnabled(modelData.key) + onClicked: { + eventDelegate.setActionEnabled(modelData.key, checked) + if (checked) { + // Some actons might need configuration (e.g. sound needs a filename) + // FIXME check this and expand and focus if needed + } + } + + contentItem: Kirigami.Icon { + anchors.fill: parent + source: modelData.icon + opacity: actionStripButton.checked ? 1 : (actionStripButton.hovered ? 0.5 : 0.1) + } + + QtControls.ToolTip { + text: modelData.label + visible: parent.hovered + } + } + } + } + + Loader { + Layout.fillWidth: true + active: eventDelegate.ListView.isCurrentItem + visible: active + sourceComponent: EventDetails { + Layout.fillWidth: true + model: eventsColumn.actions + } + } + } + } + } + } + } + } + + QtControls.Label { + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + right: parent.right + margins: Kirigami.Units.smallSpacing + } + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + text: i18n("Application events cannot be configured on a per-activity basis.") + visible: eventsColumn.customActivitySettings + } + } + + // compact layout when event list isnt't shown + Item { + Layout.fillHeight: true + visible: eventsList.count === 0 + } +} diff --git a/kcms/notifications/package/contents/ui/PopupPositionPage.qml b/kcms/notifications/package/contents/ui/PopupPositionPage.qml new file mode 100644 index 000000000..cf51bba10 --- /dev/null +++ b/kcms/notifications/package/contents/ui/PopupPositionPage.qml @@ -0,0 +1,32 @@ +/* + * 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.7 as Kirigami + +Kirigami.Page { + title: i18n("Popup Position") + + ScreenPositionSelector { + anchors.fill: parent + } +} diff --git a/kcms/notifications/package/contents/ui/ScreenPositionSelector.qml b/kcms/notifications/package/contents/ui/ScreenPositionSelector.qml new file mode 100644 index 000000000..4f238b594 --- /dev/null +++ b/kcms/notifications/package/contents/ui/ScreenPositionSelector.qml @@ -0,0 +1,276 @@ +/* + * Copyright 2015 (C) Martin Klapetek + * Copyright 2019 (C) 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 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.0 +import QtQuick.Window 2.1 +import QtQuick.Controls 2.2 as QtControls +import org.kde.kirigami 2.4 as Kirigami +import org.kde.plasma.core 2.0 as PlasmaCore + +import org.kde.plasma.private.notifications 1.0 + +Item { + id: monitorPanel + + property int baseUnit: Kirigami.Units.gridUnit// Math.round(Kirigami.Units.gridUnit / 1.5) + + implicitWidth: baseUnit * 13 + baseUnit * 2 + implicitHeight: (screenRatio * baseUnit * 13) + (baseUnit * 2) + basePart.height + + //flat: true + + property int selectedPosition + property var disabledPositions: [] + property real screenRatio: Screen.height / Screen.width + + onEnabledChanged: { + if (!enabled) { + positionRadios.current = null + } + + selectedPosition = NotificationsHelper.Default + } + + PlasmaCore.Svg { + id: monitorSvg + imagePath: "widgets/monitor" + } + + PlasmaCore.SvgItem { + id: topleftPart + anchors { + left: parent.left + top: parent.top + } + svg: monitorSvg + elementId: "topleft" + width: baseUnit + height: baseUnit + } + + PlasmaCore.SvgItem { + id: topPart + anchors { + top: parent.top + left: topleftPart.right + right: toprightPart.left + } + svg: monitorSvg + elementId: "top" + height: baseUnit + } + + PlasmaCore.SvgItem { + id: toprightPart + anchors { + right: parent.right + top: parent.top + } + svg: monitorSvg + elementId: "topright" + width: baseUnit + height: baseUnit + } + + PlasmaCore.SvgItem { + id: leftPart + anchors { + left: parent.left + top: topleftPart.bottom + bottom: bottomleftPart.top + } + svg: monitorSvg + elementId: "left" + width: baseUnit + } + + PlasmaCore.SvgItem { + id: rightPart + anchors { + right: parent.right + top: toprightPart.bottom + bottom: bottomrightPart.top + } + svg: monitorSvg + elementId: "right" + width: baseUnit + } + + PlasmaCore.SvgItem { + id: bottomleftPart + anchors { + left: parent.left + bottom: basePart.top + } + svg: monitorSvg + elementId: "bottomleft" + width: baseUnit + height: baseUnit + } + + PlasmaCore.SvgItem { + id: bottomPart + anchors { + bottom: basePart.top + left: bottomleftPart.right + right: bottomrightPart.left + } + svg: monitorSvg + elementId: "bottom" + height: baseUnit + } + + PlasmaCore.SvgItem { + id: bottomrightPart + anchors { + right: parent.right + bottom: basePart.top + } + svg: monitorSvg + elementId: "bottomright" + width: baseUnit + height: baseUnit + } + + PlasmaCore.SvgItem { + id: basePart + anchors { + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + } + width: 120 + height: 60 + svg: monitorSvg + elementId: "base" + } + + QtControls.ButtonGroup { + id: positionRadios + } + + /*QtControls.ExclusiveGroup { + id: positionRadios + + onCurrentChanged: { + monitorPanel.selectedPosition = current.position; + } + }*/ + + QtControls.RadioButton { + anchors { + top: topPart.bottom + left: leftPart.right + margins: Kirigami.Units.smallSpacing + } + readonly property int position: NotificationsHelper.TopLeft + checked: monitorPanel.selectedPosition == position + visible: monitorPanel.disabledPositions.indexOf(position) == -1 + QtControls.ButtonGroup.group: positionRadios + } + QtControls.RadioButton { + anchors { + top: topPart.bottom + horizontalCenter: topPart.horizontalCenter + margins: Kirigami.Units.smallSpacing + } + readonly property int position: NotificationsHelper.TopCenter + checked: monitorPanel.selectedPosition == position + visible: monitorPanel.disabledPositions.indexOf(position) == -1 + QtControls.ButtonGroup.group: positionRadios + } + QtControls.RadioButton { + anchors { + top: topPart.bottom + right: rightPart.left + margins: Kirigami.Units.smallSpacing + } + readonly property int position: NotificationsHelper.TopRight + checked: monitorPanel.selectedPosition == position + visible: monitorPanel.disabledPositions.indexOf(position) == -1 + QtControls.ButtonGroup.group: positionRadios + } + QtControls.RadioButton { + anchors { + left: leftPart.right + verticalCenter: leftPart.verticalCenter + margins: Kirigami.Units.smallSpacing + } + readonly property int position: NotificationsHelper.Left + checked: monitorPanel.selectedPosition == position + visible: monitorPanel.disabledPositions.indexOf(position) == -1 + QtControls.ButtonGroup.group: positionRadios + } + QtControls.RadioButton { + anchors { + horizontalCenter: topPart.horizontalCenter + verticalCenter: leftPart.verticalCenter + margins: Kirigami.Units.smallSpacing + } + readonly property int position: NotificationsHelper.Center + checked: monitorPanel.selectedPosition == position + visible: monitorPanel.disabledPositions.indexOf(position) == -1 + QtControls.ButtonGroup.group: positionRadios + } + QtControls.RadioButton { + anchors { + right: rightPart.left + verticalCenter: rightPart.verticalCenter + margins: Kirigami.Units.smallSpacing + } + readonly property int position: NotificationsHelper.Right + checked: monitorPanel.selectedPosition == position + visible: monitorPanel.disabledPositions.indexOf(position) == -1 + QtControls.ButtonGroup.group: positionRadios + } + QtControls.RadioButton { + anchors { + bottom: bottomPart.top + left: leftPart.right + margins: Kirigami.Units.smallSpacing + } + readonly property int position: NotificationsHelper.BottomLeft + checked: monitorPanel.selectedPosition == position + visible: monitorPanel.disabledPositions.indexOf(position) == -1 + QtControls.ButtonGroup.group: positionRadios + } + QtControls.RadioButton { + anchors { + bottom: bottomPart.top + horizontalCenter: bottomPart.horizontalCenter + margins: Kirigami.Units.smallSpacing + } + readonly property int position: NotificationsHelper.BottomCenter + checked: monitorPanel.selectedPosition == position + visible: monitorPanel.disabledPositions.indexOf(position) == -1 + QtControls.ButtonGroup.group: positionRadios + } + QtControls.RadioButton { + anchors { + bottom: bottomPart.top + right: rightPart.left + margins: Kirigami.Units.smallSpacing + } + readonly property int position: NotificationsHelper.BottomRight + checked: monitorPanel.selectedPosition == position + visible: monitorPanel.disabledPositions.indexOf(position) == -1 + QtControls.ButtonGroup.group: positionRadios + } +} diff --git a/kcms/notifications/package/contents/ui/SourcesPage.qml b/kcms/notifications/package/contents/ui/SourcesPage.qml new file mode 100644 index 000000000..7dd7c703f --- /dev/null +++ b/kcms/notifications/package/contents/ui/SourcesPage.qml @@ -0,0 +1,243 @@ +/* + * 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.7 as Kirigami +import org.kde.kcm 1.2 as KCM + +import org.kde.private.kcms.notifications 1.0 as Private + +Kirigami.Page { + id: sourcesPage + title: i18n("Application Settings") + + readonly property bool hasActivities: activityTabsRepeater.count > 1 + + Binding { + target: kcm.filteredModel + property: "query" + value: searchField.text + } + + ColumnLayout { + id: rootColumn + anchors.fill: parent + spacing: 0 + + QtControls.TabBar { + id: activitiesTabBar + Layout.fillWidth: true + visible: sourcesPage.hasActivities + + Repeater { + id: activityTabsRepeater + model: kcm.activitiesModel + + QtControls.TabButton { + readonly property bool isCurrent: model.isCurrent + text: model.name + // FIXME support icon in tab button + //icon.name: model.iconSource + + // Switch to current activity initially + Component.onCompleted: { + if (model.isCurrent) { + activitiesTabBar.currentIndex = index; + } + } + } + } + } + + QtControls.Frame { + Layout.fillWidth: true + Layout.fillHeight: true + + padding: sourcesPage.hasActivities ? 6 : 0 // magic number comes from qqc2-desktop-style + + Component.onCompleted: { + background.visible = Qt.binding(function() { + return sourcesPage.hasActivities; + }); + } + + ColumnLayout { + anchors.fill: parent + + Kirigami.FormLayout { + Layout.fillWidth: false // keep left-aligned + visible: sourcesPage.hasActivities + + QtControls.RadioButton { + id: activityDefaultCheck + Kirigami.FormData.label: i18n("In this activity:") + text: i18n("Use default settings") + checked: true + } + + QtControls.RadioButton { + id: activityCustomCheck + text: i18n("Custom settings") + } + } + + RowLayout { + Layout.fillWidth: true + + ColumnLayout { + Layout.minimumWidth: Kirigami.Units.gridUnit * 12 + Layout.preferredWidth: Math.round(rootColumn.width / 3) + + /*Kirigami.SearchField { + Layout.fillWidth: true + }*/ + QtControls.TextField { // FIXME search field + id: searchField + Layout.fillWidth: true + placeholderText: i18n("Search...") + // TODO autofocus this? + + Shortcut { + sequence: StandardKey.Find + onActivated: searchField.forceActiveFocus() + } + } + + QtControls.ScrollView { + id: sourcesScroll + Layout.fillWidth: true + Layout.fillHeight: true + activeFocusOnTab: false + Kirigami.Theme.colorSet: Kirigami.Theme.View + Kirigami.Theme.inherit: false + + Component.onCompleted: background.visible = true + + ListView { + id: sourcesList + anchors { + fill: parent + margins: 2 + //leftMargin: sourcesScroll.QtControls.ScrollBar.vertical.visible ? 2 : internal.scrollBarSpace/2 + 2 + } + clip: true + activeFocusOnTab: true + // FIXME fix current index when we filter + //currentIndex: -1 + + keyNavigationEnabled: true + keyNavigationWraps: true + highlightMoveDuration: 0 + + section { + criteria: ViewSection.FullString + property: "sourceType" + delegate: QtControls.ItemDelegate { + id: sourceSection + width: sourcesList.width + text: { + switch (Number(section)) { + case Private.SourcesModel.ServiceType: return i18n("System Services"); + case Private.SourcesModel.KNotifyAppType: return i18n("Applications"); + case Private.SourcesModel.FdoAppType: return i18n("Other Applications"); + } + } + + // unset "disabled" text color... + contentItem: QtControls.Label { + text: sourceSection.text + // FIXME why does none of this work :( + //Kirigami.Theme.colorGroup: Kirigami.Theme.Active + //color: Kirigami.Theme.textColor + color: rootColumn.Kirigami.Theme.textColor + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + enabled: false + } + } + + model: kcm.filteredModel + + delegate: QtControls.ItemDelegate { + id: sourceDelegate + width: sourcesList.width + text: model.display + highlighted: ListView.isCurrentItem + onClicked: { + eventsConfiguration.rootIndex = kcm.filteredModel.makePersistentModelIndex(index, 0) + // FIXME move as we filter + sourcesList.currentIndex = index + } + + contentItem: RowLayout { + Kirigami.Icon { + Layout.preferredWidth: Kirigami.Units.iconSizes.small + Layout.preferredHeight: Kirigami.Units.iconSizes.small + source: model.decoration + } + + QtControls.Label { + Layout.fillWidth: true + text: sourceDelegate.text + font: sourceDelegate.font + color: sourceDelegate.highlighted || sourceDelegate.checked || (sourceDelegate.pressed && !sourceDelegate.checked && !sourceDelegate.sectionDelegate) ? Kirigami.Theme.highlightedTextColor : (sourceDelegate.enabled ? Kirigami.Theme.textColor : Kirigami.Theme.disabledTextColor) + elide: Text.ElideRight + } + } + } + } + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.preferredWidth: Math.round(rootColumn.width / 3 * 2) + + EventsPage { + id: eventsConfiguration + anchors.fill: parent + //rootIndex: + customActivitySettings: activityCustomCheck.checked + visible: !!rootIndex + } + + QtControls.Label { + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + right: parent.right + margins: Kirigami.Units.smallSpacing + } + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + text: i18n("No application or event matches your search term.") + visible: sourcesList.count === 0 && searchField.length > 0 + } + } + } + } + } + } +} diff --git a/kcms/notifications/package/contents/ui/main.qml b/kcms/notifications/package/contents/ui/main.qml new file mode 100644 index 000000000..eaf39f5a6 --- /dev/null +++ b/kcms/notifications/package/contents/ui/main.qml @@ -0,0 +1,208 @@ +/* + * 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.Window 2.2 +//import QtQuick.Dialogs 1.0 as QtDialogs +import QtQuick.Controls 2.3 as QtControls +import org.kde.kirigami 2.4 as Kirigami +//import org.kde.kconfig 1.0 // for KAuthorized +import org.kde.kcm 1.2 as KCM + +KCM.SimpleKCM { + KCM.ConfigModule.quickHelp: i18n("This module lets you manage application and system notifications.") + KCM.ConfigModule.buttons: KCM.ConfigModule.Help/* | KCM.ConfigModule.Default*/ | KCM.ConfigModule.Apply + + implicitHeight: 550 // HACK FIXME + + Kirigami.FormLayout { + RowLayout { + Kirigami.FormData.label: i18n("Do not disturb mode:") + + QtControls.CheckBox { + id: dndTimeCheck + text: i18nc("Enable between hh:mm and hh:mm", "Automatically enable") + } + + QtControls.Button { + text: i18nc("Set times for automatic do not disturb mode", "Set Times...") + icon.name: "preferences-system-time" + onClicked: kcm.push("DndTimePage.qml") + enabled: dndTimeCheck.checked + } + } + + QtControls.CheckBox { + text: i18nc("Apps can enable do not disturb mode", "Applications can enable") + checked: true + } + + RowLayout { + QtControls.Label { + text: i18n("Keyboard Shortcut:") + } + + // TODO keysequence thing + QtControls.Button { + icon.name: "configure" + text: i18n("Meta+N") + } + + QtControls.Button { + icon.name: "edit-clear" + } + } + + ColumnLayout { + visible: activitiesDndRepeater.count > 1 + + QtControls.Label { + text: i18n("Automatically enable in the following activities:") + } + + Repeater { + id: activitiesDndRepeater + model: kcm.activitiesModel + + QtControls.CheckBox { + id: activityCheck + + text: model.name + icon.name: model.iconSource || "preferences-activities" + + // FIXME make icons work in QQC2 desktop style CheckBox + contentItem: RowLayout { + Kirigami.Icon { + Layout.leftMargin: indicator.width + Kirigami.Units.smallSpacing + source: activityCheck.icon.name + Layout.preferredWidth: Kirigami.Units.iconSizes.small + Layout.preferredHeight: Kirigami.Units.iconSizes.small + } + + QtControls.Label { + text: activityCheck.text + } + } + } + } + } + + Kirigami.Separator { + Kirigami.FormData.isSection: true + } + + QtControls.CheckBox { + Kirigami.FormData.label: i18n("Critical notifications:") + text: i18n("Show in do not disturb mode") + checked: true + } + + QtControls.CheckBox { + text: i18n("Keep always on top") + checked: true + } + + QtControls.CheckBox { + Kirigami.FormData.label: i18n("Low priority notifications:") + text: i18n("Show popup") + } + + QtControls.ButtonGroup { + id: positionGroup + buttons: [positionCloseToPanel, positionCustomPosition] + } + + QtControls.RadioButton { + id: positionCloseToPanel + Kirigami.FormData.label: i18n("Popup position:") + text: i18n("Near the notification icon") // "widget" + checked: true + } + + RowLayout { + QtControls.RadioButton { + id: positionCustomPosition + text: i18n("Custom Position") + } + QtControls.Button { + text: i18n("Choose...") + onClicked: kcm.push("PopupPositionPage.qml") + enabled: positionCustomPosition.checked + } + } + + QtControls.ComboBox { + Layout.fillWidth: false + Kirigami.FormData.label: i18n("Hide popup after:") + textRole: "label" + model: [ + {label: i18n("5 seconds"), value: 5}, + {label: i18n("7 seconds"), value: 7}, + {label: i18n("10 seconds"), value: 10}, + {label: i18n("15 seconds"), value: 15}, + {label: i18n("30 seconds"), value: 30}, + {label: i18n("1 minute"), value: 60} + ] + // FIXME proper sizing, especially for the popup + Layout.maximumWidth: Kirigami.Units.gridUnit * 6 + } + + Kirigami.Separator { + Kirigami.FormData.isSection: true + } + + QtControls.CheckBox { + Kirigami.FormData.label: i18n("Application progress:") + text: i18n("Show in task manager") + checked: true + } + + QtControls.CheckBox { + id: applicationJobsEnabledCheck + text: i18n("Show popup") + checked: true + } + + RowLayout { // HACK just for indentation + QtControls.CheckBox { + Layout.leftMargin: indicator.width + text: i18n("Hide when running") + enabled: applicationJobsEnabledCheck.checked + } + } + + QtControls.CheckBox { + Kirigami.FormData.label: i18n("Notification badges:") + text: i18n("Show in task manager") + checked: true + } + + Kirigami.Separator { + Kirigami.FormData.isSection: true + } + + QtControls.Button { + text: i18n("Application Settings") + icon.name: "preferences-desktop-notification" + onClicked: kcm.push("SourcesPage.qml") + } + } +} diff --git a/kcms/notifications/package/metadata.desktop b/kcms/notifications/package/metadata.desktop new file mode 100644 index 000000000..fc057d9ff --- /dev/null +++ b/kcms/notifications/package/metadata.desktop @@ -0,0 +1,16 @@ +[Desktop Entry] +Name=Notifications +Comment=Event Notifications and Actions + +Icon=preferences-desktop-notification-bell +Type=Service +X-KDE-PluginInfo-Author=Kai Uwe Broulik +X-KDE-PluginInfo-Email=kde@privat.broulik.de +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-Name=kcm_notifications +X-KDE-PluginInfo-Version= +X-KDE-PluginInfo-Website= +X-KDE-ServiceTypes=Plasma/Generic +X-Plasma-API=declarativeappletscript + +X-Plasma-MainScript=ui/main.qml diff --git a/kcms/notifications/sourcesmodel.cpp b/kcms/notifications/sourcesmodel.cpp new file mode 100644 index 000000000..e71b54582 --- /dev/null +++ b/kcms/notifications/sourcesmodel.cpp @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2007 Matthew Woehlke + * Copyright (C) 2007 Jeremy Whiting + * Copyright (C) 2016 Olivier Churlaud + * 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 "sourcesmodel.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +SourcesModel::SourcesModel(QObject *parent) : QAbstractItemModel(parent) +{ + +} + +SourcesModel::~SourcesModel() = default; + +QPersistentModelIndex SourcesModel::makePersistentModelIndex(int row) const +{ + return QPersistentModelIndex(index(row, 0)); +} + +int SourcesModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +int SourcesModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) { + return 0; + } + + if (!parent.isValid()) { + return m_data.count(); + } + + if (parent.internalId()) { + return 0; + } + + return m_data.at(parent.row()).events.count(); +} + +QVariant SourcesModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + if (index.internalId()) { + const auto &event = m_data.at(index.internalId() - 1).events.at(index.row()); + + switch (role) { + case Qt::DisplayRole: return event.name; + case Qt::DecorationRole: return event.iconName; + case EventIdRole: return event.eventId; + case ActionsRole: return event.actions; + } + + return QVariant(); + } + + const auto &source = m_data.at(index.row()); + + switch (role) { + case Qt::DisplayRole: return source.display(); + case Qt::DecorationRole: return source.iconName; + case SourceTypeRole: + if (!source.notifyRcName.isEmpty()) { + if (!source.desktopEntry.isEmpty()) { + return KNotifyAppType; + } + return ServiceType; + } + return FdoAppType; + case NotifyRcNameRole: return source.notifyRcName; + case DesktopEntryRole: return source.desktopEntry; + } + + return QVariant(); +} + +bool SourcesModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) { + return false; + } + + if (!index.internalId()) { + return false; + } + + bool dirty = false; + + auto &event = m_data[index.internalId() - 1].events[index.row()]; + switch (role) { + case ActionsRole: + event.actions = value.toStringList(); + dirty = true; + break; + } + + if (dirty) { + emit dataChanged(index, index, {role}); + } + + return dirty; +} + +QModelIndex SourcesModel::index(int row, int column, const QModelIndex &parent) const +{ + if (row < 0 || column != 0) { + return QModelIndex(); + } + + if (parent.isValid()) { + const auto events = m_data.at(parent.row()).events; + if (row < events.count()) { + return createIndex(row, column, parent.row() + 1); + } + + return QModelIndex(); + } + + if (row < m_data.count()) { + return createIndex(row, column, nullptr); + } + + return QModelIndex(); +} + +QModelIndex SourcesModel::parent(const QModelIndex &child) const +{ + if (child.internalId()) { + return createIndex(child.internalId() - 1, 0, nullptr); + } + + return QModelIndex(); +} + +QHash SourcesModel::roleNames() const +{ + return { + {Qt::DisplayRole, QByteArrayLiteral("display")}, + {Qt::DecorationRole, QByteArrayLiteral("decoration")}, + {SourceTypeRole, QByteArrayLiteral("sourceType")}, + {NotifyRcNameRole, QByteArrayLiteral("notifyRcName")}, + {DesktopEntryRole, QByteArrayLiteral("desktopEntry")}, + {EventIdRole, QByteArrayLiteral("eventId")}, + {ActionsRole, QByteArrayLiteral("actions")} + }; +} + +void SourcesModel::load() +{ + beginResetModel(); + + m_data.clear(); + + QVector servicesData; + QVector knotifyAppsData; + QVector fdoAppsData; + + QStringList notifyRcFiles; + + const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, + QStringLiteral("knotifications5"), QStandardPaths::LocateDirectory); + + for (const QString &dir : dirs) { + const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.notifyrc")); + for (const QString &file : fileNames) { + if (notifyRcFiles.contains(file)) { + continue; + } + + notifyRcFiles.append(file); + + //const QString path = dir + QLatin1Char('/') + file; + + //KConfig config(path, KConfig::NoGlobals, QStandardPaths::DataLocation); + + KConfig *config = new KConfig(file, KConfig::NoGlobals); + config->addConfigSources(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, + QStringLiteral("knotifications5/") + file)); + + KConfigGroup globalGroup(config, QLatin1String("Global")); + + const QRegularExpression regExp(QStringLiteral("^Event/([^/]*)$")); + const QStringList groups = config->groupList().filter(regExp); + + const QString notifyRcName = file.section(QLatin1Char('.'), 0, -2); + + SourceData source{ + // The old KCM read the Name and Comment from global settings disregarding + // any user settings and just used user-specific files for actions config + // I'm pretty sure there's a readEntry equivalent that does that without + // reading the config stuff twice, assuming we care about this to begin with + globalGroup.readEntry(QStringLiteral("Name")), + globalGroup.readEntry(QStringLiteral("Comment")), + globalGroup.readEntry(QStringLiteral("IconName")), + notifyRcName, + globalGroup.readEntry(QStringLiteral("DesktopEntry")), + {}, // events + config + }; + + QVector events; + for (const QString &group : groups) { + KConfigGroup cg(config, group); + + const QString eventId = regExp.match(group).captured(1); + // TODO context stuff + + // TODO load defaults thing + + EventData event{ + cg.readEntry("Name"), + cg.readEntry("Comment"), + cg.readEntry("IconName"), + eventId, + // TODO Flags? + cg.readEntry("Action").split(QLatin1Char('|')) + }; + events.append(event); + } + + source.events = events; + + if (!source.desktopEntry.isEmpty()) { + knotifyAppsData.append(source); + } else { + servicesData.append(source); + } + } + } + + const auto services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), + QStringLiteral("exist Exec and TRUE == [X-GNOME-UsesNotifications]")); + for (const auto &service : services) { + SourceData source{ + service->name(), + service->comment(), + service->icon(), + QString(), //notifyRcFile + service->desktopEntryName(), + {}, + nullptr + }; + fdoAppsData.append(source); + } + + QCollator collator; + + auto sortData = [&collator](const SourceData &a, const SourceData &b) { + return collator.compare(a.display(), b.display()) < 0; + }; + + std::sort(servicesData.begin(), servicesData.end(), sortData); + std::sort(knotifyAppsData.begin(), knotifyAppsData.end(), sortData); + std::sort(fdoAppsData.begin(), fdoAppsData.end(), sortData); + + m_data << servicesData << knotifyAppsData << fdoAppsData; + + endResetModel(); +} diff --git a/kcms/notifications/sourcesmodel.h b/kcms/notifications/sourcesmodel.h new file mode 100644 index 000000000..2ca11318d --- /dev/null +++ b/kcms/notifications/sourcesmodel.h @@ -0,0 +1,103 @@ +/* + * 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 +#include +#include + +class KConfig; + +struct EventData +{ + QString name; + QString comment; + QString iconName; + QString eventId; + QStringList actions; +}; + +// FIXME add constructors for KService and KConfigGroup +struct SourceData +{ + QString name; + QString comment; + QString iconName; + + QString notifyRcName; + QString desktopEntry; + + QVector events; + + KConfig *config; // KSharedConfig::Ptr? + + QString display() const + { + return !name.isEmpty() ? name : comment; + } +}; + +class SourcesModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + SourcesModel(QObject *parent = nullptr); + ~SourcesModel() override; + + enum Roles { + SourceTypeRole = Qt::UserRole + 1, + NotifyRcNameRole, + DesktopEntryRole, + + EventIdRole, + ActionsRole + }; + + enum Type { + ServiceType, + KNotifyAppType, + FdoAppType + }; + Q_ENUM(Type) + + Q_INVOKABLE QPersistentModelIndex makePersistentModelIndex(int row) const; + + int columnCount(const QModelIndex &parent) const override; + int rowCount(const QModelIndex &parent) const override; + + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &child) const override; + + QHash roleNames() const override; + + void load(); + +Q_SIGNALS: + +private: + QVector m_data; + +};