/* SPDX-FileCopyrightText: 2019 Kai Uwe Broulik SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "jobsmodel.h" #include "jobsmodel_p.h" #include "utils_p.h" #include #include #include #include "job.h" #include "job_p.h" using namespace NotificationManager; JobsModel::JobsModel() : QAbstractListModel(nullptr) , d(new JobsModelPrivate(this)) { connect(d, &JobsModelPrivate::jobViewAboutToBeAdded, this, [this](int row, Job *job) { Q_UNUSED(job); beginInsertRows(QModelIndex(), row, row); }); connect(d, &JobsModelPrivate::jobViewAdded, this, [this](int row) { Q_UNUSED(row); endInsertRows(); }); connect(d, &JobsModelPrivate::jobViewAboutToBeRemoved, this, [this](int row) { beginRemoveRows(QModelIndex(), row, row); }); connect(d, &JobsModelPrivate::jobViewRemoved, this, [this](int row) { Q_UNUSED(row); endRemoveRows(); }); connect(d, &JobsModelPrivate::jobViewChanged, this, [this](int row, Job *job, const QList &roles) { Q_UNUSED(job); const QModelIndex idx = index(row, 0); Q_EMIT dataChanged(idx, idx, roles); }); connect(d, &JobsModelPrivate::serviceOwnershipLost, this, &JobsModel::serviceOwnershipLost); } JobsModel::~JobsModel() = default; JobsModel::Ptr JobsModel::createJobsModel() { static std::weak_ptr s_instance; if (s_instance.expired()) { std::shared_ptr ptr(new JobsModel()); s_instance = ptr; return ptr; } return s_instance.lock(); } bool JobsModel::init() { return d->init(); } bool JobsModel::isValid() const { return d->m_valid; } QVariant JobsModel::data(const QModelIndex &index, int role) const { if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { return QVariant(); } Job *job = d->m_jobViews.at(index.row()); switch (role) { case Notifications::IdRole: return job->id(); case Notifications::TypeRole: return Notifications::JobType; // basically when it started case Notifications::CreatedRole: if (job->created().isValid()) { return job->created(); } break; // basically when it finished case Notifications::UpdatedRole: if (job->updated().isValid()) { return job->updated(); } break; case Notifications::SummaryRole: return job->summary(); case Notifications::BodyRole: return job->text(); case Qt::AccessibleDescriptionRole: return i18nc("@info %1 notification body %2 job name", "%1 from %2", job->text(), job->applicationName()); case Notifications::DesktopEntryRole: return job->desktopEntry(); case Notifications::ApplicationNameRole: return job->applicationName(); case Notifications::ApplicationIconNameRole: return job->applicationIconName(); case Notifications::JobStateRole: return job->state(); case Notifications::PercentageRole: return job->percentage(); case Notifications::JobErrorRole: return job->error(); case Notifications::SuspendableRole: return job->suspendable(); case Notifications::KillableRole: return job->killable(); case Notifications::JobDetailsRole: return QVariant::fromValue(job); // successfully finished jobs timeout like a regular notifiation // whereas running or error'd jobs are persistent case Notifications::TimeoutRole: return job->state() == Notifications::JobStateStopped && !job->error() ? -1 : 0; case Notifications::ClosableRole: return job->state() == Notifications::JobStateStopped; case Notifications::ConfigurableRole: return false; case Notifications::ExpiredRole: return job->expired(); case Notifications::DismissedRole: return job->dismissed(); // A job is usually either a long lasting operation you're aware about // or a quick job you don't care about. // When it's running, it's there, when it failed, it's persistent. // There's hardly a reason why it should show up as "unread". case Notifications::ReadRole: return true; } return QVariant(); } bool JobsModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { return false; } Job *job = d->m_jobViews.at(index.row()); switch (role) { case Notifications::DismissedRole: if (value.toBool() != job->dismissed()) { job->setDismissed(value.toBool()); return true; } break; } return false; } int JobsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return d->m_jobViews.count(); } QHash JobsModel::roleNames() const { return Utils::roleNames(); } void JobsModel::close(const QModelIndex &idx) { if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { d->removeAt(idx.row()); } } void JobsModel::expire(const QModelIndex &idx) { if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { d->m_jobViews.at(idx.row())->setExpired(true); } } void JobsModel::suspend(const QModelIndex &idx) { if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { d->m_jobViews.at(idx.row())->suspend(); } } void JobsModel::resume(const QModelIndex &idx) { if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { d->m_jobViews.at(idx.row())->resume(); } } void JobsModel::kill(const QModelIndex &idx) { if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { d->m_jobViews.at(idx.row())->kill(); } } void JobsModel::clear(Notifications::ClearFlags flags) { if (d->m_jobViews.isEmpty()) { return; } for (int i = d->m_jobViews.count() - 1; i >= 0; --i) { Job *job = d->m_jobViews.at(i); bool clear = (flags.testFlag(Notifications::ClearExpired) && job->expired()); // Compared to notifications, the number of jobs is typically small // so for simplicity we can just delete one item at a time if (clear) { d->removeAt(i); } } }