mirror of https://github.com/cutefishos/core
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
7.8 KiB
C++
269 lines
7.8 KiB
C++
/*
|
|
* SPDX-FileCopyrightText: 2021 Reion Wong <reion@cutefishos.com>
|
|
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
|
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
|
*/
|
|
|
|
#include "notificationsmodel.h"
|
|
#include "historymodel.h"
|
|
#include "notification.h"
|
|
#include "settings.h"
|
|
|
|
#include <QMetaEnum>
|
|
#include <QDebug>
|
|
|
|
static const int s_notificationsLimit = 1000;
|
|
static NotificationsModel *NOTIFICATIONS_MODEL = nullptr;
|
|
|
|
NotificationsModel *NotificationsModel::self()
|
|
{
|
|
if (!NOTIFICATIONS_MODEL)
|
|
NOTIFICATIONS_MODEL = new NotificationsModel;
|
|
|
|
return NOTIFICATIONS_MODEL;
|
|
}
|
|
|
|
NotificationsModel::NotificationsModel(QObject *parent)
|
|
: QAbstractListModel(parent)
|
|
{
|
|
m_pendingRemovalTimer.setSingleShot(true);
|
|
m_pendingRemovalTimer.setInterval(50);
|
|
|
|
connect(&m_pendingRemovalTimer, &QTimer::timeout, this, [this] {
|
|
QVector<int> rowsToBeRemoved;
|
|
rowsToBeRemoved.reserve(m_pendingRemovals.count());
|
|
for (uint id : qAsConst(m_pendingRemovals)) {
|
|
int row = rowOfNotification(id); // oh the complexity...
|
|
if (row == -1) {
|
|
continue;
|
|
}
|
|
rowsToBeRemoved.append(row);
|
|
}
|
|
|
|
removeRows(rowsToBeRemoved);
|
|
});
|
|
|
|
connect(NotificationServer::self(), &NotificationServer::notificationAdded, this, &NotificationsModel::onNotificationAdded);
|
|
connect(NotificationServer::self(), &NotificationServer::notificationReplaced, this, &NotificationsModel::onNotificationReplaced);
|
|
connect(NotificationServer::self(), &NotificationServer::notificationRemoved, this, &NotificationsModel::onNotificationRemoved);
|
|
}
|
|
|
|
QVariant NotificationsModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
const Notification ¬ification = m_notifications.at(index.row());
|
|
|
|
switch (role) {
|
|
case NotificationsModel::IdRole:
|
|
return notification.id;
|
|
case NotificationsModel::SummaryRole:
|
|
return notification.summary;
|
|
case NotificationsModel::ImageRole:
|
|
return "";
|
|
case NotificationsModel::CreatedRole:
|
|
return notification.created;
|
|
case NotificationsModel::BodyRole:
|
|
return notification.body;
|
|
case NotificationsModel::IconNameRole:
|
|
return notification.appIcon;
|
|
case NotificationsModel::HasDefaultActionRole:
|
|
return notification.actions.contains("default");
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
bool NotificationsModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
{
|
|
// Notification ¬ification = m_notifications[index.row()];
|
|
bool dirty = false;
|
|
return dirty;
|
|
}
|
|
|
|
int NotificationsModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
if (parent.isValid())
|
|
return 0;
|
|
|
|
return m_notifications.size();
|
|
}
|
|
|
|
QHash<int, QByteArray> NotificationsModel::roleNames() const
|
|
{
|
|
static QHash<int, QByteArray> s_roles;
|
|
|
|
if (s_roles.isEmpty()) {
|
|
// This generates role names from the Roles enum in the form of: FooRole -> foo
|
|
const QMetaEnum e = QMetaEnum::fromType<NotificationsModel::Roles>();
|
|
|
|
// Qt built-in roles we use
|
|
s_roles.insert(Qt::DisplayRole, QByteArrayLiteral("display"));
|
|
s_roles.insert(Qt::DecorationRole, QByteArrayLiteral("decoration"));
|
|
|
|
for (int i = 0; i < e.keyCount(); ++i) {
|
|
const int value = e.value(i);
|
|
|
|
QByteArray key(e.key(i));
|
|
key[0] = key[0] + 32; // lower case first letter
|
|
key.chop(4); // strip "Role" suffix
|
|
|
|
s_roles.insert(value, key);
|
|
}
|
|
|
|
s_roles.insert(NotificationsModel::IdRole, QByteArrayLiteral("notificationId")); // id is QML-reserved
|
|
}
|
|
|
|
return s_roles;
|
|
}
|
|
|
|
void NotificationsModel::expired(uint id)
|
|
{
|
|
int row = rowOfNotification(id);
|
|
|
|
if (row > -1) {
|
|
onNotificationRemoved(id, NotificationServer::CloseReason::Expired);
|
|
}
|
|
}
|
|
|
|
void NotificationsModel::close(uint id)
|
|
{
|
|
if (rowOfNotification(id) > -1) {
|
|
NotificationServer::self()->closeNotification(id, NotificationServer::CloseReason::DismissedByUser);
|
|
}
|
|
}
|
|
|
|
void NotificationsModel::invokeDefaultAction(uint notificationId)
|
|
{
|
|
const int row = rowOfNotification(notificationId);
|
|
|
|
if (row == -1) {
|
|
return;
|
|
}
|
|
|
|
const Notification ¬ification = m_notifications.at(row);
|
|
if (!notification.actions.contains("default")) {
|
|
return;
|
|
}
|
|
|
|
NotificationServer::self()->InvokeAction(notificationId, "default");
|
|
}
|
|
|
|
int NotificationsModel::rowOfNotification(uint id) const
|
|
{
|
|
auto it = std::find_if(m_notifications.constBegin(), m_notifications.constEnd(), [id](const Notification &item) {
|
|
return item.id == id;
|
|
});
|
|
|
|
if (it == m_notifications.constEnd()) {
|
|
return -1;
|
|
}
|
|
|
|
return std::distance(m_notifications.constBegin(), it);
|
|
}
|
|
|
|
void NotificationsModel::removeRows(const QVector<int> &rows)
|
|
{
|
|
if (rows.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QVector<int> rowsToBeRemoved(rows);
|
|
std::sort(rowsToBeRemoved.begin(), rowsToBeRemoved.end());
|
|
|
|
QVector<QPair<int, int>> clearQueue;
|
|
|
|
QPair<int, int> clearRange{rowsToBeRemoved.first(), rowsToBeRemoved.first()};
|
|
|
|
for (int row : rowsToBeRemoved) {
|
|
if (row > clearRange.second + 1) {
|
|
clearQueue.append(clearRange);
|
|
clearRange.first = row;
|
|
}
|
|
|
|
clearRange.second = row;
|
|
}
|
|
|
|
if (clearQueue.isEmpty() || clearQueue.last() != clearRange) {
|
|
clearQueue.append(clearRange);
|
|
}
|
|
|
|
int rowsRemoved = 0;
|
|
|
|
for (int i = clearQueue.count() - 1; i >= 0; --i) {
|
|
const auto &range = clearQueue.at(i);
|
|
|
|
beginRemoveRows(QModelIndex(), range.first, range.second);
|
|
for (int j = range.second; j >= range.first; --j) {
|
|
m_notifications.removeAt(j);
|
|
++rowsRemoved;
|
|
}
|
|
endRemoveRows();
|
|
}
|
|
|
|
Q_ASSERT(rowsRemoved == rowsToBeRemoved.count());
|
|
|
|
m_pendingRemovals.clear();
|
|
}
|
|
|
|
void NotificationsModel::onNotificationAdded(const Notification ¬ification)
|
|
{
|
|
// Do Not Disturb Mode:
|
|
// Add directly to the historical model.
|
|
if (Settings::self()->doNotDisturb()) {
|
|
HistoryModel::self()->add(notification);
|
|
return;
|
|
}
|
|
|
|
if (m_notifications.size() >= s_notificationsLimit) {
|
|
const int cleanupCount = s_notificationsLimit / 2;
|
|
beginRemoveRows(QModelIndex(), 0, cleanupCount - 1);
|
|
for (int i = 0; i < cleanupCount; ++i) {
|
|
m_notifications.removeAt(0);
|
|
}
|
|
endRemoveRows();
|
|
}
|
|
|
|
beginInsertRows(QModelIndex(), m_notifications.size(), m_notifications.size());
|
|
m_notifications.append(std::move(notification));
|
|
endInsertRows();
|
|
}
|
|
|
|
void NotificationsModel::onNotificationReplaced(uint replacedId, const Notification ¬ification)
|
|
{
|
|
}
|
|
|
|
void NotificationsModel::onNotificationRemoved(uint removedId, NotificationServer::CloseReason reason)
|
|
{
|
|
const int row = rowOfNotification(removedId);
|
|
|
|
if (row == -1) {
|
|
return;
|
|
}
|
|
|
|
// When a notification expired, keep it around in the history and mark it as such
|
|
if (reason == NotificationServer::CloseReason::Expired) {
|
|
const QModelIndex idx = NotificationsModel::index(row, 0);
|
|
Notification ¬ification = m_notifications[row];
|
|
// notification.setExpired(true);
|
|
notification.actions.clear();
|
|
emit dataChanged(idx, idx);
|
|
|
|
HistoryModel::self()->add(notification);
|
|
|
|
// return;
|
|
}
|
|
|
|
// Otherwise if explicitly closed by either user or app, mark it for removal
|
|
// some apps are notorious for closing a bunch of notifications at once
|
|
// causing newer notifications to move up and have a dialogs created for them
|
|
// just to then be discarded causing excess CPU usage
|
|
if (!m_pendingRemovals.contains(removedId)) {
|
|
m_pendingRemovals.append(removedId);
|
|
}
|
|
|
|
if (!m_pendingRemovalTimer.isActive()) {
|
|
m_pendingRemovalTimer.start();
|
|
}
|
|
}
|