Warning, file /frameworks/kactivities/src/imports/activitymodel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2012, 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 // Self 0008 #include "activitymodel.h" 0009 0010 // Qt 0011 #include <QByteArray> 0012 #include <QDBusPendingCall> 0013 #include <QDBusPendingCallWatcher> 0014 #include <QDebug> 0015 #include <QFutureWatcher> 0016 #include <QHash> 0017 #include <QIcon> 0018 #include <QList> 0019 #include <QModelIndex> 0020 0021 // KDE 0022 #include <KConfig> 0023 #include <KConfigGroup> 0024 #include <KDirWatch> 0025 0026 // Boost 0027 #include <boost/optional.hpp> 0028 #include <boost/range/adaptor/filtered.hpp> 0029 #include <boost/range/algorithm/binary_search.hpp> 0030 #include <boost/range/algorithm/find_if.hpp> 0031 0032 // Local 0033 #include "utils/remove_if.h" 0034 #define ENABLE_QJSVALUE_CONTINUATION 0035 #include "utils/continue_with.h" 0036 #include "utils/model_updaters.h" 0037 0038 using kamd::utils::continue_with; 0039 0040 namespace KActivities 0041 { 0042 namespace Imports 0043 { 0044 class ActivityModel::Private 0045 { 0046 public: 0047 DECLARE_RAII_MODEL_UPDATERS(ActivityModel) 0048 0049 /** 0050 * Returns whether the activity has a desired state. 0051 * If the state is 0, returns true 0052 */ 0053 template<typename T> 0054 static inline bool matchingState(InfoPtr activity, T states) 0055 { 0056 // Are we filtering activities on their states? 0057 if (!states.empty() && !boost::binary_search(states, activity->state())) { 0058 return false; 0059 } 0060 0061 return true; 0062 } 0063 0064 /** 0065 * Searches for the activity. 0066 * Returns an option(index, iterator) for the found activity. 0067 */ 0068 template<typename _Container> 0069 static inline boost::optional<std::pair<unsigned int, typename _Container::const_iterator>> activityPosition(const _Container &container, 0070 const QString &activityId) 0071 { 0072 using ActivityPosition = decltype(activityPosition(container, activityId)); 0073 using ContainerElement = typename _Container::value_type; 0074 0075 auto position = boost::find_if(container, [&](const ContainerElement &activity) { 0076 return activity->id() == activityId; 0077 }); 0078 0079 return (position != container.end()) ? ActivityPosition(std::make_pair(position - container.begin(), position)) : ActivityPosition(); 0080 } 0081 0082 /** 0083 * Notifies the model that an activity was updated 0084 */ 0085 template<typename _Model, typename _Container> 0086 static inline void emitActivityUpdated(_Model *model, const _Container &container, QObject *activityInfo, int role) 0087 { 0088 const auto activity = static_cast<Info *>(activityInfo); 0089 emitActivityUpdated(model, container, activity->id(), role); 0090 } 0091 0092 /** 0093 * Notifies the model that an activity was updated 0094 */ 0095 template<typename _Model, typename _Container> 0096 static inline void emitActivityUpdated(_Model *model, const _Container &container, const QString &activity, int role) 0097 { 0098 auto position = Private::activityPosition(container, activity); 0099 0100 if (position) { 0101 Q_EMIT model->dataChanged(model->index(position->first), 0102 model->index(position->first), 0103 role == Qt::DecorationRole ? QVector<int>{role, ActivityModel::ActivityIcon} : QVector<int>{role}); 0104 } 0105 } 0106 0107 class BackgroundCache 0108 { 0109 public: 0110 BackgroundCache() 0111 : initialized(false) 0112 , plasmaConfig(QStringLiteral("plasma-org.kde.plasma.desktop-appletsrc")) 0113 { 0114 using namespace std::placeholders; 0115 0116 const QString configFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + plasmaConfig.name(); 0117 0118 KDirWatch::self()->addFile(configFile); 0119 0120 connect(KDirWatch::self(), &KDirWatch::dirty, std::bind(&BackgroundCache::settingsFileChanged, this, _1)); 0121 connect(KDirWatch::self(), &KDirWatch::created, std::bind(&BackgroundCache::settingsFileChanged, this, _1)); 0122 } 0123 0124 void settingsFileChanged(const QString &file) 0125 { 0126 if (!file.endsWith(plasmaConfig.name())) { 0127 return; 0128 } 0129 0130 plasmaConfig.reparseConfiguration(); 0131 0132 if (initialized) { 0133 reload(false); 0134 } 0135 } 0136 0137 void subscribe(ActivityModel *model) 0138 { 0139 if (!initialized) { 0140 reload(true); 0141 } 0142 0143 models << model; 0144 } 0145 0146 void unsubscribe(ActivityModel *model) 0147 { 0148 models.removeAll(model); 0149 0150 if (models.isEmpty()) { 0151 initialized = false; 0152 forActivity.clear(); 0153 } 0154 } 0155 0156 QString backgroundFromConfig(const KConfigGroup &config) const 0157 { 0158 auto wallpaperPlugin = config.readEntry("wallpaperplugin"); 0159 auto wallpaperConfig = config.group("Wallpaper").group(wallpaperPlugin).group("General"); 0160 0161 if (wallpaperConfig.hasKey("Image")) { 0162 // Trying for the wallpaper 0163 auto wallpaper = wallpaperConfig.readEntry("Image", QString()); 0164 if (!wallpaper.isEmpty()) { 0165 return wallpaper; 0166 } 0167 } 0168 if (wallpaperConfig.hasKey("Color")) { 0169 auto backgroundColor = wallpaperConfig.readEntry("Color", QColor(0, 0, 0)); 0170 return backgroundColor.name(); 0171 } 0172 0173 return QString(); 0174 } 0175 0176 void reload(bool fullReload) 0177 { 0178 QHash<QString, QString> newBackgrounds; 0179 0180 if (fullReload) { 0181 forActivity.clear(); 0182 } 0183 0184 QStringList changedBackgrounds; 0185 0186 for (const auto &cont : plasmaConfigContainments().groupList()) { 0187 auto config = plasmaConfigContainments().group(cont); 0188 auto activityId = config.readEntry("activityId", QString()); 0189 0190 // Ignore if it has no assigned activity 0191 if (activityId.isEmpty()) { 0192 continue; 0193 } 0194 0195 // Ignore if we have already found the background 0196 if (newBackgrounds.contains(activityId) && newBackgrounds[activityId][0] != QLatin1Char('#')) { 0197 continue; 0198 } 0199 0200 auto newBackground = backgroundFromConfig(config); 0201 0202 if (forActivity[activityId] != newBackground) { 0203 changedBackgrounds << activityId; 0204 if (!newBackground.isEmpty()) { 0205 newBackgrounds[activityId] = newBackground; 0206 } 0207 } 0208 } 0209 0210 initialized = true; 0211 0212 if (!changedBackgrounds.isEmpty()) { 0213 forActivity = newBackgrounds; 0214 0215 for (auto model : models) { 0216 model->backgroundsUpdated(changedBackgrounds); 0217 } 0218 } 0219 } 0220 0221 KConfigGroup plasmaConfigContainments() 0222 { 0223 return plasmaConfig.group("Containments"); 0224 } 0225 0226 QHash<QString, QString> forActivity; 0227 QList<ActivityModel *> models; 0228 0229 bool initialized; 0230 KConfig plasmaConfig; 0231 }; 0232 0233 static BackgroundCache &backgrounds() 0234 { 0235 // If you convert this to a shared pointer, 0236 // fix the connections to KDirWatcher 0237 static BackgroundCache cache; 0238 return cache; 0239 } 0240 }; 0241 0242 ActivityModel::ActivityModel(QObject *parent) 0243 : QAbstractListModel(parent) 0244 { 0245 // Initializing role names for qml 0246 connect(&m_service, &Consumer::serviceStatusChanged, this, &ActivityModel::setServiceStatus); 0247 0248 connect(&m_service, &KActivities::Consumer::activityAdded, this, [this](const QString &id) { 0249 onActivityAdded(id); 0250 }); 0251 connect(&m_service, &KActivities::Consumer::activityRemoved, this, &ActivityModel::onActivityRemoved); 0252 connect(&m_service, &KActivities::Consumer::currentActivityChanged, this, &ActivityModel::onCurrentActivityChanged); 0253 0254 setServiceStatus(m_service.serviceStatus()); 0255 0256 Private::backgrounds().subscribe(this); 0257 } 0258 0259 ActivityModel::~ActivityModel() 0260 { 0261 Private::backgrounds().unsubscribe(this); 0262 } 0263 0264 QHash<int, QByteArray> ActivityModel::roleNames() const 0265 { 0266 return {{Qt::DisplayRole, "name"}, 0267 {Qt::DecorationRole, "icon"}, 0268 0269 {ActivityState, "state"}, 0270 {ActivityId, "id"}, 0271 {ActivityIcon, "iconSource"}, 0272 {ActivityDescription, "description"}, 0273 {ActivityBackground, "background"}, 0274 {ActivityCurrent, "current"}}; 0275 } 0276 0277 void ActivityModel::setServiceStatus(Consumer::ServiceStatus) 0278 { 0279 replaceActivities(m_service.activities()); 0280 } 0281 0282 void ActivityModel::replaceActivities(const QStringList &activities) 0283 { 0284 // qDebug() << m_shownStatesString << "New list of activities: " 0285 // << activities; 0286 // qDebug() << m_shownStatesString << " -- RESET MODEL -- "; 0287 0288 Private::model_reset m(this); 0289 0290 m_knownActivities.clear(); 0291 m_shownActivities.clear(); 0292 0293 for (const QString &activity : activities) { 0294 onActivityAdded(activity, false); 0295 } 0296 } 0297 0298 void ActivityModel::onActivityAdded(const QString &id, bool notifyClients) 0299 { 0300 auto info = registerActivity(id); 0301 0302 // qDebug() << m_shownStatesString << "Added a new activity:" << info->id() 0303 // << " " << info->name(); 0304 0305 showActivity(info, notifyClients); 0306 } 0307 0308 void ActivityModel::onActivityRemoved(const QString &id) 0309 { 0310 // qDebug() << m_shownStatesString << "Removed an activity:" << id; 0311 0312 hideActivity(id); 0313 unregisterActivity(id); 0314 } 0315 0316 void ActivityModel::onCurrentActivityChanged(const QString &id) 0317 { 0318 Q_UNUSED(id); 0319 0320 for (const auto &activity : m_shownActivities) { 0321 Private::emitActivityUpdated(this, m_shownActivities, activity->id(), ActivityCurrent); 0322 } 0323 } 0324 0325 ActivityModel::InfoPtr ActivityModel::registerActivity(const QString &id) 0326 { 0327 auto position = Private::activityPosition(m_knownActivities, id); 0328 0329 // qDebug() << m_shownStatesString << "Registering activity: " << id 0330 // << " new? not " << (bool)position; 0331 0332 if (position) { 0333 return *(position->second); 0334 0335 } else { 0336 auto activityInfo = std::make_shared<Info>(id); 0337 0338 auto ptr = activityInfo.get(); 0339 0340 connect(ptr, &Info::nameChanged, this, &ActivityModel::onActivityNameChanged); 0341 connect(ptr, &Info::descriptionChanged, this, &ActivityModel::onActivityDescriptionChanged); 0342 connect(ptr, &Info::iconChanged, this, &ActivityModel::onActivityIconChanged); 0343 connect(ptr, &Info::stateChanged, this, &ActivityModel::onActivityStateChanged); 0344 0345 m_knownActivities.insert(InfoPtr(activityInfo)); 0346 0347 return activityInfo; 0348 } 0349 } 0350 0351 void ActivityModel::unregisterActivity(const QString &id) 0352 { 0353 // qDebug() << m_shownStatesString << "Deregistering activity: " << id; 0354 0355 auto position = Private::activityPosition(m_knownActivities, id); 0356 0357 if (position) { 0358 if (auto shown = Private::activityPosition(m_shownActivities, id)) { 0359 Private::model_remove(this, QModelIndex(), shown->first, shown->first); 0360 m_shownActivities.erase(shown->second); 0361 } 0362 0363 m_knownActivities.erase(position->second); 0364 } 0365 } 0366 0367 void ActivityModel::showActivity(InfoPtr activityInfo, bool notifyClients) 0368 { 0369 // Should it really be shown? 0370 if (!Private::matchingState(activityInfo, m_shownStates)) { 0371 return; 0372 } 0373 0374 // Is it already shown? 0375 if (boost::binary_search(m_shownActivities, activityInfo, InfoPtrComparator())) { 0376 return; 0377 } 0378 0379 auto registeredPosition = Private::activityPosition(m_knownActivities, activityInfo->id()); 0380 0381 if (!registeredPosition) { 0382 qDebug() << "Got a request to show an unknown activity, ignoring"; 0383 return; 0384 } 0385 0386 auto activityInfoPtr = *(registeredPosition->second); 0387 0388 // qDebug() << m_shownStatesString << "Setting activity visibility to true:" 0389 // << activityInfoPtr->id() << activityInfoPtr->name(); 0390 0391 auto position = m_shownActivities.insert(activityInfoPtr); 0392 0393 if (notifyClients) { 0394 unsigned int index = (position.second ? position.first : m_shownActivities.end()) - m_shownActivities.begin(); 0395 0396 // qDebug() << m_shownStatesString << " -- MODEL INSERT -- " << index; 0397 Private::model_insert(this, QModelIndex(), index, index); 0398 } 0399 } 0400 0401 void ActivityModel::hideActivity(const QString &id) 0402 { 0403 auto position = Private::activityPosition(m_shownActivities, id); 0404 0405 // qDebug() << m_shownStatesString 0406 // << "Setting activity visibility to false: " << id; 0407 0408 if (position) { 0409 // qDebug() << m_shownStatesString << " -- MODEL REMOVE -- " 0410 // << position->first; 0411 Private::model_remove(this, QModelIndex(), position->first, position->first); 0412 m_shownActivities.erase(position->second); 0413 } 0414 } 0415 // clang-format off 0416 #define CREATE_SIGNAL_EMITTER(What,Role) \ 0417 void ActivityModel::onActivity##What##Changed(const QString &) \ 0418 { \ 0419 Private::emitActivityUpdated(this, m_shownActivities, sender(), Role); \ 0420 } 0421 // clang-format on 0422 0423 CREATE_SIGNAL_EMITTER(Name, Qt::DisplayRole) 0424 CREATE_SIGNAL_EMITTER(Description, ActivityDescription) 0425 CREATE_SIGNAL_EMITTER(Icon, Qt::DecorationRole) 0426 0427 #undef CREATE_SIGNAL_EMITTER 0428 0429 void ActivityModel::onActivityStateChanged(Info::State state) 0430 { 0431 if (m_shownStates.empty()) { 0432 Private::emitActivityUpdated(this, m_shownActivities, sender(), ActivityState); 0433 0434 } else { 0435 auto info = findActivity(sender()); 0436 0437 if (!info) { 0438 return; 0439 } 0440 0441 if (boost::binary_search(m_shownStates, state)) { 0442 showActivity(info, true); 0443 } else { 0444 hideActivity(info->id()); 0445 } 0446 } 0447 } 0448 0449 void ActivityModel::backgroundsUpdated(const QStringList &activities) 0450 { 0451 for (const auto &activity : activities) { 0452 Private::emitActivityUpdated(this, m_shownActivities, activity, ActivityBackground); 0453 } 0454 } 0455 0456 void ActivityModel::setShownStates(const QString &states) 0457 { 0458 m_shownStates.clear(); 0459 m_shownStatesString = states; 0460 0461 for (const auto &state : states.split(QLatin1Char(','))) { 0462 if (state == QLatin1String("Running")) { 0463 m_shownStates.insert(Running); 0464 0465 } else if (state == QLatin1String("Starting")) { 0466 m_shownStates.insert(Starting); 0467 0468 } else if (state == QLatin1String("Stopped")) { 0469 m_shownStates.insert(Stopped); 0470 0471 } else if (state == QLatin1String("Stopping")) { 0472 m_shownStates.insert(Stopping); 0473 } 0474 } 0475 0476 replaceActivities(m_service.activities()); 0477 0478 Q_EMIT shownStatesChanged(states); 0479 } 0480 0481 QString ActivityModel::shownStates() const 0482 { 0483 return m_shownStatesString; 0484 } 0485 0486 int ActivityModel::rowCount(const QModelIndex &parent) const 0487 { 0488 Q_UNUSED(parent); 0489 0490 return m_shownActivities.size(); 0491 } 0492 0493 QVariant ActivityModel::data(const QModelIndex &index, int role) const 0494 { 0495 const int row = index.row(); 0496 const auto &item = *(m_shownActivities.cbegin() + row); 0497 0498 switch (role) { 0499 case Qt::DisplayRole: 0500 return item->name(); 0501 0502 case Qt::DecorationRole: 0503 return QIcon::fromTheme(data(index, ActivityIcon).toString()); 0504 0505 case ActivityId: 0506 return item->id(); 0507 0508 case ActivityState: 0509 return item->state(); 0510 0511 case ActivityIcon: { 0512 const QString &icon = item->icon(); 0513 0514 // We need a default icon for activities 0515 return icon.isEmpty() ? QStringLiteral("activities") : icon; 0516 } 0517 0518 case ActivityDescription: 0519 return item->description(); 0520 0521 case ActivityCurrent: 0522 return m_service.currentActivity() == item->id(); 0523 0524 case ActivityBackground: 0525 return Private::backgrounds().forActivity[item->id()]; 0526 0527 default: 0528 return QVariant(); 0529 } 0530 } 0531 0532 QVariant ActivityModel::headerData(int section, Qt::Orientation orientation, int role) const 0533 { 0534 Q_UNUSED(section); 0535 Q_UNUSED(orientation); 0536 Q_UNUSED(role); 0537 0538 return QVariant(); 0539 } 0540 0541 ActivityModel::InfoPtr ActivityModel::findActivity(QObject *ptr) const 0542 { 0543 auto info = boost::find_if(m_knownActivities, [ptr](const InfoPtr &info) { 0544 return ptr == info.get(); 0545 }); 0546 0547 if (info == m_knownActivities.end()) { 0548 return nullptr; 0549 } else { 0550 return *info; 0551 } 0552 } 0553 0554 // clang-format off 0555 // QFuture<void> Controller::setActivityWhat(id, value) 0556 #define CREATE_SETTER(What) \ 0557 void ActivityModel::setActivity##What( \ 0558 const QString &id, const QString &value, const QJSValue &callback) \ 0559 { \ 0560 continue_with(m_service.setActivity##What(id, value), callback); \ 0561 } 0562 // clang-format on 0563 0564 CREATE_SETTER(Name) 0565 CREATE_SETTER(Description) 0566 CREATE_SETTER(Icon) 0567 0568 #undef CREATE_SETTER 0569 0570 // QFuture<bool> Controller::setCurrentActivity(id) 0571 void ActivityModel::setCurrentActivity(const QString &id, const QJSValue &callback) 0572 { 0573 continue_with(m_service.setCurrentActivity(id), callback); 0574 } 0575 0576 // QFuture<QString> Controller::addActivity(name) 0577 void ActivityModel::addActivity(const QString &name, const QJSValue &callback) 0578 { 0579 continue_with(m_service.addActivity(name), callback); 0580 } 0581 0582 // QFuture<void> Controller::removeActivity(id) 0583 void ActivityModel::removeActivity(const QString &id, const QJSValue &callback) 0584 { 0585 continue_with(m_service.removeActivity(id), callback); 0586 } 0587 0588 // QFuture<void> Controller::stopActivity(id) 0589 void ActivityModel::stopActivity(const QString &id, const QJSValue &callback) 0590 { 0591 continue_with(m_service.stopActivity(id), callback); 0592 } 0593 0594 // QFuture<void> Controller::startActivity(id) 0595 void ActivityModel::startActivity(const QString &id, const QJSValue &callback) 0596 { 0597 continue_with(m_service.startActivity(id), callback); 0598 } 0599 0600 } // namespace Imports 0601 } // namespace KActivities 0602 0603 // #include "activitymodel.moc"