File indexing completed on 2022-11-29 13:02:28

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 ? QVector<int>{role, ActivitiesModel::ActivityIconSource} : QVector<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(QVector<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()
0158 {
0159     delete d;
0160 }
0161 
0162 QHash<int, QByteArray> ActivitiesModel::roleNames() const
0163 {
0164     return {{ActivityName, "name"},
0165             {ActivityState, "state"},
0166             {ActivityId, "id"},
0167             {ActivityIconSource, "iconSource"},
0168             {ActivityDescription, "description"},
0169             {ActivityBackground, "background"},
0170             {ActivityIsCurrent, "isCurrent"}};
0171 }
0172 
0173 void ActivitiesModelPrivate::setServiceStatus(Consumer::ServiceStatus)
0174 {
0175     replaceActivities(activities.activities());
0176 }
0177 
0178 void ActivitiesModelPrivate::replaceActivities(const QStringList &activities)
0179 {
0180     q->beginResetModel();
0181 
0182     knownActivities.clear();
0183     shownActivities.clear();
0184 
0185     for (const QString &activity : activities) {
0186         onActivityAdded(activity, false);
0187     }
0188 
0189     q->endResetModel();
0190 }
0191 
0192 void ActivitiesModelPrivate::onActivityAdded(const QString &id, bool notifyClients)
0193 {
0194     auto info = registerActivity(id);
0195 
0196     showActivity(info, notifyClients);
0197 }
0198 
0199 void ActivitiesModelPrivate::onActivityRemoved(const QString &id)
0200 {
0201     hideActivity(id);
0202     unregisterActivity(id);
0203 }
0204 
0205 void ActivitiesModelPrivate::onCurrentActivityChanged(const QString &id)
0206 {
0207     Q_UNUSED(id);
0208 
0209     for (const auto &activity : shownActivities) {
0210         Private::emitActivityUpdated(this, shownActivities, activity->id(), ActivitiesModel::ActivityIsCurrent);
0211     }
0212 }
0213 
0214 ActivitiesModelPrivate::InfoPtr ActivitiesModelPrivate::registerActivity(const QString &id)
0215 {
0216     auto position = Private::activityPosition(knownActivities, id);
0217 
0218     if (position) {
0219         return *(position.iterator);
0220 
0221     } else {
0222         auto activityInfo = std::make_shared<Info>(id);
0223 
0224         auto ptr = activityInfo.get();
0225 
0226         connect(ptr, &Info::nameChanged, this, &ActivitiesModelPrivate::onActivityNameChanged);
0227         connect(ptr, &Info::descriptionChanged, this, &ActivitiesModelPrivate::onActivityDescriptionChanged);
0228         connect(ptr, &Info::iconChanged, this, &ActivitiesModelPrivate::onActivityIconChanged);
0229         connect(ptr, &Info::stateChanged, this, &ActivitiesModelPrivate::onActivityStateChanged);
0230 
0231         knownActivities.insert(InfoPtr(activityInfo));
0232 
0233         return activityInfo;
0234     }
0235 }
0236 
0237 void ActivitiesModelPrivate::unregisterActivity(const QString &id)
0238 {
0239     auto position = Private::activityPosition(knownActivities, id);
0240 
0241     if (position) {
0242         if (auto shown = Private::activityPosition(shownActivities, id)) {
0243             q->beginRemoveRows(QModelIndex(), shown.index, shown.index);
0244             shownActivities.removeAt(shown.index);
0245             q->endRemoveRows();
0246         }
0247 
0248         knownActivities.removeAt(position.index);
0249     }
0250 }
0251 
0252 void ActivitiesModelPrivate::showActivity(InfoPtr activityInfo, bool notifyClients)
0253 {
0254     // Should it really be shown?
0255     if (!Private::matchingState(activityInfo, shownStates)) {
0256         return;
0257     }
0258 
0259     // Is it already shown?
0260     if (std::binary_search(shownActivities.cbegin(), shownActivities.cend(), activityInfo, InfoPtrComparator())) {
0261         return;
0262     }
0263 
0264     auto registeredPosition = Private::activityPosition(knownActivities, activityInfo->id());
0265 
0266     if (!registeredPosition) {
0267         qDebug() << "Got a request to show an unknown activity, ignoring";
0268         return;
0269     }
0270 
0271     const auto activityInfoPtr = *(registeredPosition.iterator);
0272 
0273     // In C++17, this would be:
0274     // const auto [iterator, index, found] = shownActivities.insert(...);
0275     const auto _result = shownActivities.insert(activityInfoPtr);
0276     // const auto iterator = std::get<0>(_result);
0277     const auto index = std::get<1>(_result);
0278 
0279     if (notifyClients) {
0280         q->beginInsertRows(QModelIndex(), index, index);
0281         q->endInsertRows();
0282     }
0283 }
0284 
0285 void ActivitiesModelPrivate::hideActivity(const QString &id)
0286 {
0287     auto position = Private::activityPosition(shownActivities, id);
0288 
0289     if (position) {
0290         q->beginRemoveRows(QModelIndex(), position.index, position.index);
0291         shownActivities.removeAt(position.index);
0292         q->endRemoveRows();
0293     }
0294 }
0295 
0296 // clang-format off
0297 #define CREATE_SIGNAL_EMITTER(What,Role)                                      \
0298     void ActivitiesModelPrivate::onActivity##What##Changed(const QString &)    \
0299     {                                                                          \
0300         Private::emitActivityUpdated(this, shownActivities, sender(), Role);   \
0301     }
0302 // clang-format on
0303 
0304 CREATE_SIGNAL_EMITTER(Name, Qt::DisplayRole)
0305 CREATE_SIGNAL_EMITTER(Description, ActivitiesModel::ActivityDescription)
0306 CREATE_SIGNAL_EMITTER(Icon, Qt::DecorationRole)
0307 
0308 #undef CREATE_SIGNAL_EMITTER
0309 
0310 void ActivitiesModelPrivate::onActivityStateChanged(Info::State state)
0311 {
0312     if (shownStates.empty()) {
0313         Private::emitActivityUpdated(this, shownActivities, sender(), ActivitiesModel::ActivityState);
0314 
0315     } else {
0316         auto info = findActivity(sender());
0317 
0318         if (!info) {
0319             return;
0320         }
0321 
0322         if (shownStates.contains(state)) {
0323             showActivity(info, true);
0324         } else {
0325             hideActivity(info->id());
0326         }
0327     }
0328 }
0329 
0330 void ActivitiesModel::setShownStates(const QVector<Info::State> &states)
0331 {
0332     d->shownStates = states;
0333 
0334     d->replaceActivities(d->activities.activities());
0335 
0336     Q_EMIT shownStatesChanged(states);
0337 }
0338 
0339 QVector<Info::State> ActivitiesModel::shownStates() const
0340 {
0341     return d->shownStates;
0342 }
0343 
0344 int ActivitiesModel::rowCount(const QModelIndex &parent) const
0345 {
0346     if (parent.isValid()) {
0347         return 0;
0348     }
0349 
0350     return d->shownActivities.size();
0351 }
0352 
0353 QVariant ActivitiesModel::data(const QModelIndex &index, int role) const
0354 {
0355     const int row = index.row();
0356     const auto &item = d->shownActivities.at(row);
0357 
0358     switch (role) {
0359     case Qt::DisplayRole:
0360     case ActivityName:
0361         return item->name();
0362 
0363     case ActivityId:
0364         return item->id();
0365 
0366     case ActivityState:
0367         return item->state();
0368 
0369     case Qt::DecorationRole:
0370     case ActivityIconSource: {
0371         const QString &icon = item->icon();
0372 
0373         // We need a default icon for activities
0374         return icon.isEmpty() ? QStringLiteral("activities") : icon;
0375     }
0376 
0377     case ActivityDescription:
0378         return item->description();
0379 
0380     case ActivityIsCurrent:
0381         return d->activities.currentActivity() == item->id();
0382 
0383     default:
0384         return QVariant();
0385     }
0386 }
0387 
0388 QVariant ActivitiesModel::headerData(int section, Qt::Orientation orientation, int role) const
0389 {
0390     Q_UNUSED(section);
0391     Q_UNUSED(orientation);
0392     Q_UNUSED(role);
0393 
0394     return QVariant();
0395 }
0396 
0397 ActivitiesModelPrivate::InfoPtr ActivitiesModelPrivate::findActivity(QObject *ptr) const
0398 {
0399     auto info = std::find_if(knownActivities.cbegin(), knownActivities.cend(), [ptr](const InfoPtr &info) {
0400         return ptr == info.get();
0401     });
0402 
0403     if (info == knownActivities.end()) {
0404         return nullptr;
0405     } else {
0406         return *info;
0407     }
0408 }
0409 
0410 } // namespace KActivities
0411 
0412 // #include "activitiesmodel.moc"