/* SPDX-FileCopyrightText: 2012-2016 Ivan Cukic SPDX-License-Identifier: GPL-2.0-or-later */ // Self #include "activitiesmodel.h" #include "activitiesmodel_p.h" // Qt #include #include #include #include #include #include #include // Local #include "utils/remove_if.h" namespace KActivities { namespace Private { template struct ActivityPosition { ActivityPosition() : isValid(false) , index(0) , iterator() { } ActivityPosition(unsigned int index, typename _Container::const_iterator iterator) : isValid(true) , index(index) , iterator(iterator) { } operator bool() const { return isValid; } const bool isValid; const unsigned int index; const typename _Container::const_iterator iterator; typedef typename _Container::value_type ContainerElement; }; /** * Returns whether the activity has a desired state. * If the state is 0, returns true */ template inline bool matchingState(ActivitiesModelPrivate::InfoPtr activity, const T &states) { return states.empty() || states.contains(activity->state()); } /** * Searches for the activity. * Returns an option(index, iterator) for the found activity. */ template inline ActivityPosition<_Container> activityPosition(const _Container &container, const QString &activityId) { auto position = std::find_if(container.begin(), container.end(), [&](const typename ActivityPosition<_Container>::ContainerElement &activity) { return activity->id() == activityId; }); return (position != container.end()) ? ActivityPosition<_Container>(position - container.begin(), position) : ActivityPosition<_Container>(); } /** * Notifies the model that an activity was updated */ template inline void emitActivityUpdated(_Model *model, const _Container &container, const QString &activity, int role) { auto position = Private::activityPosition(container, activity); if (position) { Q_EMIT model->q->dataChanged(model->q->index(position.index), model->q->index(position.index), role == Qt::DecorationRole ? QList{role, ActivitiesModel::ActivityIconSource} : QList{role}); } } /** * Notifies the model that an activity was updated */ template inline void emitActivityUpdated(_Model *model, const _Container &container, QObject *activityInfo, int role) { const auto activity = static_cast(activityInfo); emitActivityUpdated(model, container, activity->id(), role); } } ActivitiesModelPrivate::ActivitiesModelPrivate(ActivitiesModel *parent) : q(parent) { } ActivitiesModel::ActivitiesModel(QObject *parent) : QAbstractListModel(parent) , d(new ActivitiesModelPrivate(this)) { // Initializing role names for qml connect(&d->activities, &Consumer::serviceStatusChanged, this, [this](Consumer::ServiceStatus status) { d->setServiceStatus(status); }); connect(&d->activities, &Consumer::activityAdded, this, [this](const QString &activity) { d->onActivityAdded(activity); }); connect(&d->activities, &Consumer::activityRemoved, this, [this](const QString &activity) { d->onActivityRemoved(activity); }); connect(&d->activities, &Consumer::currentActivityChanged, this, [this](const QString &activity) { d->onCurrentActivityChanged(activity); }); d->setServiceStatus(d->activities.serviceStatus()); } ActivitiesModel::ActivitiesModel(QList shownStates, QObject *parent) : QAbstractListModel(parent) , d(new ActivitiesModelPrivate(this)) { d->shownStates = shownStates; // Initializing role names for qml connect(&d->activities, &Consumer::serviceStatusChanged, this, [this](Consumer::ServiceStatus status) { d->setServiceStatus(status); }); connect(&d->activities, &Consumer::activityAdded, this, [this](const QString &activity) { d->onActivityAdded(activity); }); connect(&d->activities, &Consumer::activityRemoved, this, [this](const QString &activity) { d->onActivityRemoved(activity); }); connect(&d->activities, &Consumer::currentActivityChanged, this, [this](const QString &activity) { d->onCurrentActivityChanged(activity); }); d->setServiceStatus(d->activities.serviceStatus()); } ActivitiesModel::~ActivitiesModel() = default; QHash ActivitiesModel::roleNames() const { return {{ActivityName, "name"}, {ActivityState, "state"}, {ActivityId, "id"}, {ActivityIconSource, "iconSource"}, {ActivityDescription, "description"}, {ActivityBackground, "background"}, {ActivityIsCurrent, "isCurrent"}}; } void ActivitiesModelPrivate::setServiceStatus(Consumer::ServiceStatus) { replaceActivities(activities.activities()); } void ActivitiesModelPrivate::replaceActivities(const QStringList &activities) { q->beginResetModel(); knownActivities.clear(); shownActivities.clear(); for (const QString &activity : activities) { onActivityAdded(activity, false); } q->endResetModel(); } void ActivitiesModelPrivate::onActivityAdded(const QString &id, bool notifyClients) { auto info = registerActivity(id); showActivity(info, notifyClients); } void ActivitiesModelPrivate::onActivityRemoved(const QString &id) { hideActivity(id); unregisterActivity(id); } void ActivitiesModelPrivate::onCurrentActivityChanged(const QString &id) { Q_UNUSED(id); for (const auto &activity : shownActivities) { Private::emitActivityUpdated(this, shownActivities, activity->id(), ActivitiesModel::ActivityIsCurrent); } } ActivitiesModelPrivate::InfoPtr ActivitiesModelPrivate::registerActivity(const QString &id) { auto position = Private::activityPosition(knownActivities, id); if (position) { return *(position.iterator); } else { auto activityInfo = std::make_shared(id); auto ptr = activityInfo.get(); connect(ptr, &Info::nameChanged, this, &ActivitiesModelPrivate::onActivityNameChanged); connect(ptr, &Info::descriptionChanged, this, &ActivitiesModelPrivate::onActivityDescriptionChanged); connect(ptr, &Info::iconChanged, this, &ActivitiesModelPrivate::onActivityIconChanged); connect(ptr, &Info::stateChanged, this, &ActivitiesModelPrivate::onActivityStateChanged); knownActivities.insert(InfoPtr(activityInfo)); return activityInfo; } } void ActivitiesModelPrivate::unregisterActivity(const QString &id) { auto position = Private::activityPosition(knownActivities, id); if (position) { if (auto shown = Private::activityPosition(shownActivities, id)) { q->beginRemoveRows(QModelIndex(), shown.index, shown.index); shownActivities.removeAt(shown.index); q->endRemoveRows(); } knownActivities.removeAt(position.index); } } void ActivitiesModelPrivate::showActivity(InfoPtr activityInfo, bool notifyClients) { // Should it really be shown? if (!Private::matchingState(activityInfo, shownStates)) { return; } // Is it already shown? if (std::binary_search(shownActivities.cbegin(), shownActivities.cend(), activityInfo, InfoPtrComparator())) { return; } auto registeredPosition = Private::activityPosition(knownActivities, activityInfo->id()); if (!registeredPosition) { qDebug() << "Got a request to show an unknown activity, ignoring"; return; } const auto activityInfoPtr = *(registeredPosition.iterator); // In C++17, this would be: // const auto [iterator, index, found] = shownActivities.insert(...); const auto _result = shownActivities.insert(activityInfoPtr); // const auto iterator = std::get<0>(_result); const auto index = std::get<1>(_result); if (notifyClients) { q->beginInsertRows(QModelIndex(), index, index); q->endInsertRows(); } } void ActivitiesModelPrivate::hideActivity(const QString &id) { auto position = Private::activityPosition(shownActivities, id); if (position) { q->beginRemoveRows(QModelIndex(), position.index, position.index); shownActivities.removeAt(position.index); q->endRemoveRows(); } } // clang-format off #define CREATE_SIGNAL_EMITTER(What,Role) \ void ActivitiesModelPrivate::onActivity##What##Changed(const QString &) \ { \ Private::emitActivityUpdated(this, shownActivities, sender(), Role); \ } // clang-format on CREATE_SIGNAL_EMITTER(Name, Qt::DisplayRole) CREATE_SIGNAL_EMITTER(Description, ActivitiesModel::ActivityDescription) CREATE_SIGNAL_EMITTER(Icon, Qt::DecorationRole) #undef CREATE_SIGNAL_EMITTER void ActivitiesModelPrivate::onActivityStateChanged(Info::State state) { if (shownStates.empty()) { Private::emitActivityUpdated(this, shownActivities, sender(), ActivitiesModel::ActivityState); } else { auto info = findActivity(sender()); if (!info) { return; } if (shownStates.contains(state)) { showActivity(info, true); } else { hideActivity(info->id()); } } } void ActivitiesModel::setShownStates(const QList &states) { d->shownStates = states; d->replaceActivities(d->activities.activities()); Q_EMIT shownStatesChanged(states); } QList ActivitiesModel::shownStates() const { return d->shownStates; } int ActivitiesModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return d->shownActivities.size(); } QVariant ActivitiesModel::data(const QModelIndex &index, int role) const { const int row = index.row(); const auto &item = d->shownActivities.at(row); switch (role) { case Qt::DisplayRole: case ActivityName: return item->name(); case ActivityId: return item->id(); case ActivityState: return item->state(); case Qt::DecorationRole: case ActivityIconSource: { const QString &icon = item->icon(); // We need a default icon for activities return icon.isEmpty() ? QStringLiteral("activities") : icon; } case ActivityDescription: return item->description(); case ActivityIsCurrent: return d->activities.currentActivity() == item->id(); default: return QVariant(); } } QVariant ActivitiesModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section); Q_UNUSED(orientation); Q_UNUSED(role); return QVariant(); } ActivitiesModelPrivate::InfoPtr ActivitiesModelPrivate::findActivity(QObject *ptr) const { auto info = std::find_if(knownActivities.cbegin(), knownActivities.cend(), [ptr](const InfoPtr &info) { return ptr == info.get(); }); if (info == knownActivities.end()) { return nullptr; } else { return *info; } } } // namespace KActivities #include "moc_activitiesmodel.cpp" #include "moc_activitiesmodel_p.cpp"