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"