File indexing completed on 2024-12-22 05:13:39

0001 /*
0002     SPDX-FileCopyrightText: 2012-2016 Ivan Cukic <ivan.cukic(at)kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // Self
0008 #include "activitiesmodel.h"
0009 #include "activitiesmodel_p.h"
0010 
0011 // Qt
0012 #include <QByteArray>
0013 #include <QDBusPendingCall>
0014 #include <QDBusPendingCallWatcher>
0015 #include <QDebug>
0016 #include <QFutureWatcher>
0017 #include <QHash>
0018 #include <QModelIndex>
0019 
0020 // Local
0021 #include "utils/remove_if.h"
0022 
0023 namespace KActivities
0024 {
0025 namespace Private
0026 {
0027 template<typename _Container>
0028 struct ActivityPosition {
0029     ActivityPosition()
0030         : isValid(false)
0031         , index(0)
0032         , iterator()
0033     {
0034     }
0035 
0036     ActivityPosition(unsigned int index, typename _Container::const_iterator iterator)
0037         : isValid(true)
0038         , index(index)
0039         , iterator(iterator)
0040     {
0041     }
0042 
0043     operator bool() const
0044     {
0045         return isValid;
0046     }
0047 
0048     const bool isValid;
0049     const unsigned int index;
0050     const typename _Container::const_iterator iterator;
0051 
0052     typedef typename _Container::value_type ContainerElement;
0053 };
0054 
0055 /**
0056  * Returns whether the activity has a desired state.
0057  * If the state is 0, returns true
0058  */
0059 template<typename T>
0060 inline bool matchingState(ActivitiesModelPrivate::InfoPtr activity, const T &states)
0061 {
0062     return states.empty() || states.contains(activity->state());
0063 }
0064 
0065 /**
0066  * Searches for the activity.
0067  * Returns an option(index, iterator) for the found activity.
0068  */
0069 template<typename _Container>
0070 inline ActivityPosition<_Container> activityPosition(const _Container &container, const QString &activityId)
0071 {
0072     auto position = std::find_if(container.begin(), container.end(), [&](const typename ActivityPosition<_Container>::ContainerElement &activity) {
0073         return activity->id() == activityId;
0074     });
0075 
0076     return (position != container.end()) ? ActivityPosition<_Container>(position - container.begin(), position) : ActivityPosition<_Container>();
0077 }
0078 
0079 /**
0080  * Notifies the model that an activity was updated
0081  */
0082 template<typename _Model, typename _Container>
0083 inline void emitActivityUpdated(_Model *model, const _Container &container, const QString &activity, int role)
0084 {
0085     auto position = Private::activityPosition(container, activity);
0086 
0087     if (position) {
0088         Q_EMIT model->q->dataChanged(model->q->index(position.index),
0089                                      model->q->index(position.index),
0090                                      role == Qt::DecorationRole ? QList<int>{role, ActivitiesModel::ActivityIconSource} : QList<int>{role});
0091     }
0092 }
0093 
0094 /**
0095  * Notifies the model that an activity was updated
0096  */
0097 template<typename _Model, typename _Container>
0098 inline void emitActivityUpdated(_Model *model, const _Container &container, QObject *activityInfo, int role)
0099 {
0100     const auto activity = static_cast<Info *>(activityInfo);
0101     emitActivityUpdated(model, container, activity->id(), role);
0102 }
0103 
0104 }
0105 
0106 ActivitiesModelPrivate::ActivitiesModelPrivate(ActivitiesModel *parent)
0107     : q(parent)
0108 {
0109 }
0110 
0111 ActivitiesModel::ActivitiesModel(QObject *parent)
0112     : QAbstractListModel(parent)
0113     , d(new ActivitiesModelPrivate(this))
0114 {
0115     // Initializing role names for qml
0116     connect(&d->activities, &Consumer::serviceStatusChanged, this, [this](Consumer::ServiceStatus status) {
0117         d->setServiceStatus(status);
0118     });
0119 
0120     connect(&d->activities, &Consumer::activityAdded, this, [this](const QString &activity) {
0121         d->onActivityAdded(activity);
0122     });
0123     connect(&d->activities, &Consumer::activityRemoved, this, [this](const QString &activity) {
0124         d->onActivityRemoved(activity);
0125     });
0126     connect(&d->activities, &Consumer::currentActivityChanged, this, [this](const QString &activity) {
0127         d->onCurrentActivityChanged(activity);
0128     });
0129 
0130     d->setServiceStatus(d->activities.serviceStatus());
0131 }
0132 
0133 ActivitiesModel::ActivitiesModel(QList<Info::State> shownStates, QObject *parent)
0134     : QAbstractListModel(parent)
0135     , d(new ActivitiesModelPrivate(this))
0136 {
0137     d->shownStates = shownStates;
0138 
0139     // Initializing role names for qml
0140     connect(&d->activities, &Consumer::serviceStatusChanged, this, [this](Consumer::ServiceStatus status) {
0141         d->setServiceStatus(status);
0142     });
0143 
0144     connect(&d->activities, &Consumer::activityAdded, this, [this](const QString &activity) {
0145         d->onActivityAdded(activity);
0146     });
0147     connect(&d->activities, &Consumer::activityRemoved, this, [this](const QString &activity) {
0148         d->onActivityRemoved(activity);
0149     });
0150     connect(&d->activities, &Consumer::currentActivityChanged, this, [this](const QString &activity) {
0151         d->onCurrentActivityChanged(activity);
0152     });
0153 
0154     d->setServiceStatus(d->activities.serviceStatus());
0155 }
0156 
0157 ActivitiesModel::~ActivitiesModel() = default;
0158 
0159 QHash<int, QByteArray> ActivitiesModel::roleNames() const
0160 {
0161     return {{ActivityName, "name"},
0162             {ActivityState, "state"},
0163             {ActivityId, "id"},
0164             {ActivityIconSource, "iconSource"},
0165             {ActivityDescription, "description"},
0166             {ActivityBackground, "background"},
0167             {ActivityIsCurrent, "isCurrent"}};
0168 }
0169 
0170 void ActivitiesModelPrivate::setServiceStatus(Consumer::ServiceStatus)
0171 {
0172     replaceActivities(activities.activities());
0173 }
0174 
0175 void ActivitiesModelPrivate::replaceActivities(const QStringList &activities)
0176 {
0177     q->beginResetModel();
0178 
0179     knownActivities.clear();
0180     shownActivities.clear();
0181 
0182     for (const QString &activity : activities) {
0183         onActivityAdded(activity, false);
0184     }
0185 
0186     q->endResetModel();
0187 }
0188 
0189 void ActivitiesModelPrivate::onActivityAdded(const QString &id, bool notifyClients)
0190 {
0191     auto info = registerActivity(id);
0192 
0193     showActivity(info, notifyClients);
0194 }
0195 
0196 void ActivitiesModelPrivate::onActivityRemoved(const QString &id)
0197 {
0198     hideActivity(id);
0199     unregisterActivity(id);
0200 }
0201 
0202 void ActivitiesModelPrivate::onCurrentActivityChanged(const QString &id)
0203 {
0204     Q_UNUSED(id);
0205 
0206     for (const auto &activity : shownActivities) {
0207         Private::emitActivityUpdated(this, shownActivities, activity->id(), ActivitiesModel::ActivityIsCurrent);
0208     }
0209 }
0210 
0211 ActivitiesModelPrivate::InfoPtr ActivitiesModelPrivate::registerActivity(const QString &id)
0212 {
0213     auto position = Private::activityPosition(knownActivities, id);
0214 
0215     if (position) {
0216         return *(position.iterator);
0217 
0218     } else {
0219         auto activityInfo = std::make_shared<Info>(id);
0220 
0221         auto ptr = activityInfo.get();
0222 
0223         connect(ptr, &Info::nameChanged, this, &ActivitiesModelPrivate::onActivityNameChanged);
0224         connect(ptr, &Info::descriptionChanged, this, &ActivitiesModelPrivate::onActivityDescriptionChanged);
0225         connect(ptr, &Info::iconChanged, this, &ActivitiesModelPrivate::onActivityIconChanged);
0226         connect(ptr, &Info::stateChanged, this, &ActivitiesModelPrivate::onActivityStateChanged);
0227 
0228         knownActivities.insert(InfoPtr(activityInfo));
0229 
0230         return activityInfo;
0231     }
0232 }
0233 
0234 void ActivitiesModelPrivate::unregisterActivity(const QString &id)
0235 {
0236     auto position = Private::activityPosition(knownActivities, id);
0237 
0238     if (position) {
0239         if (auto shown = Private::activityPosition(shownActivities, id)) {
0240             q->beginRemoveRows(QModelIndex(), shown.index, shown.index);
0241             shownActivities.removeAt(shown.index);
0242             q->endRemoveRows();
0243         }
0244 
0245         knownActivities.removeAt(position.index);
0246     }
0247 }
0248 
0249 void ActivitiesModelPrivate::showActivity(InfoPtr activityInfo, bool notifyClients)
0250 {
0251     // Should it really be shown?
0252     if (!Private::matchingState(activityInfo, shownStates)) {
0253         return;
0254     }
0255 
0256     // Is it already shown?
0257     if (std::binary_search(shownActivities.cbegin(), shownActivities.cend(), activityInfo, InfoPtrComparator())) {
0258         return;
0259     }
0260 
0261     auto registeredPosition = Private::activityPosition(knownActivities, activityInfo->id());
0262 
0263     if (!registeredPosition) {
0264         qDebug() << "Got a request to show an unknown activity, ignoring";
0265         return;
0266     }
0267 
0268     const auto activityInfoPtr = *(registeredPosition.iterator);
0269 
0270     // In C++17, this would be:
0271     // const auto [iterator, index, found] = shownActivities.insert(...);
0272     const auto _result = shownActivities.insert(activityInfoPtr);
0273     // const auto iterator = std::get<0>(_result);
0274     const auto index = std::get<1>(_result);
0275 
0276     if (notifyClients) {
0277         q->beginInsertRows(QModelIndex(), index, index);
0278         q->endInsertRows();
0279     }
0280 }
0281 
0282 void ActivitiesModelPrivate::hideActivity(const QString &id)
0283 {
0284     auto position = Private::activityPosition(shownActivities, id);
0285 
0286     if (position) {
0287         q->beginRemoveRows(QModelIndex(), position.index, position.index);
0288         shownActivities.removeAt(position.index);
0289         q->endRemoveRows();
0290     }
0291 }
0292 
0293 // clang-format off
0294 #define CREATE_SIGNAL_EMITTER(What,Role)                                      \
0295     void ActivitiesModelPrivate::onActivity##What##Changed(const QString &)    \
0296     {                                                                          \
0297         Private::emitActivityUpdated(this, shownActivities, sender(), Role);   \
0298     }
0299 // clang-format on
0300 
0301 CREATE_SIGNAL_EMITTER(Name, Qt::DisplayRole)
0302 CREATE_SIGNAL_EMITTER(Description, ActivitiesModel::ActivityDescription)
0303 CREATE_SIGNAL_EMITTER(Icon, Qt::DecorationRole)
0304 
0305 #undef CREATE_SIGNAL_EMITTER
0306 
0307 void ActivitiesModelPrivate::onActivityStateChanged(Info::State state)
0308 {
0309     if (shownStates.empty()) {
0310         Private::emitActivityUpdated(this, shownActivities, sender(), ActivitiesModel::ActivityState);
0311 
0312     } else {
0313         auto info = findActivity(sender());
0314 
0315         if (!info) {
0316             return;
0317         }
0318 
0319         if (shownStates.contains(state)) {
0320             showActivity(info, true);
0321         } else {
0322             hideActivity(info->id());
0323         }
0324     }
0325 }
0326 
0327 void ActivitiesModel::setShownStates(const QList<Info::State> &states)
0328 {
0329     d->shownStates = states;
0330 
0331     d->replaceActivities(d->activities.activities());
0332 
0333     Q_EMIT shownStatesChanged(states);
0334 }
0335 
0336 QList<Info::State> ActivitiesModel::shownStates() const
0337 {
0338     return d->shownStates;
0339 }
0340 
0341 int ActivitiesModel::rowCount(const QModelIndex &parent) const
0342 {
0343     if (parent.isValid()) {
0344         return 0;
0345     }
0346 
0347     return d->shownActivities.size();
0348 }
0349 
0350 QVariant ActivitiesModel::data(const QModelIndex &index, int role) const
0351 {
0352     const int row = index.row();
0353     const auto &item = d->shownActivities.at(row);
0354 
0355     switch (role) {
0356     case Qt::DisplayRole:
0357     case ActivityName:
0358         return item->name();
0359 
0360     case ActivityId:
0361         return item->id();
0362 
0363     case ActivityState:
0364         return item->state();
0365 
0366     case Qt::DecorationRole:
0367     case ActivityIconSource: {
0368         const QString &icon = item->icon();
0369 
0370         // We need a default icon for activities
0371         return icon.isEmpty() ? QStringLiteral("activities") : icon;
0372     }
0373 
0374     case ActivityDescription:
0375         return item->description();
0376 
0377     case ActivityIsCurrent:
0378         return d->activities.currentActivity() == item->id();
0379 
0380     default:
0381         return QVariant();
0382     }
0383 }
0384 
0385 QVariant ActivitiesModel::headerData(int section, Qt::Orientation orientation, int role) const
0386 {
0387     Q_UNUSED(section);
0388     Q_UNUSED(orientation);
0389     Q_UNUSED(role);
0390 
0391     return QVariant();
0392 }
0393 
0394 ActivitiesModelPrivate::InfoPtr ActivitiesModelPrivate::findActivity(QObject *ptr) const
0395 {
0396     auto info = std::find_if(knownActivities.cbegin(), knownActivities.cend(), [ptr](const InfoPtr &info) {
0397         return ptr == info.get();
0398     });
0399 
0400     if (info == knownActivities.end()) {
0401         return nullptr;
0402     } else {
0403         return *info;
0404     }
0405 }
0406 
0407 } // namespace KActivities
0408 
0409 #include "moc_activitiesmodel.cpp"
0410 #include "moc_activitiesmodel_p.cpp"