8#include "activitymodel.h"
12#include <QDBusPendingCall>
13#include <QDBusPendingCallWatcher>
15#include <QFutureWatcher>
23#include <KConfigGroup>
27#include <boost/range/adaptor/filtered.hpp>
28#include <boost/range/algorithm/binary_search.hpp>
29#include <boost/range/algorithm/find_if.hpp>
34#include "utils/remove_if.h"
35#define ENABLE_QJSVALUE_CONTINUATION
36#include "utils/continue_with.h"
37#include "utils/model_updaters.h"
39using kamd::utils::continue_with;
45class ActivityModel::Private
48 DECLARE_RAII_MODEL_UPDATERS(ActivityModel)
55 static inline bool matchingState(InfoPtr activity, T states)
58 if (!states.empty() && !boost::binary_search(states, activity->state())) {
69 template<
typename _Container>
70 static inline std::optional<std::pair<unsigned int, typename _Container::const_iterator>> activityPosition(
const _Container &container,
71 const QString &activityId)
73 using ActivityPosition =
decltype(activityPosition(container, activityId));
74 using ContainerElement =
typename _Container::value_type;
76 auto position = boost::find_if(container, [&](
const ContainerElement &activity) {
77 return activity->id() == activityId;
80 return (position != container.end()) ? ActivityPosition(std::make_pair(position - container.begin(), position)) : ActivityPosition();
86 template<
typename _Model,
typename _Container>
87 static inline void emitActivityUpdated(_Model *model,
const _Container &container,
QObject *activityInfo,
int role)
89 const auto activity =
static_cast<Info *
>(activityInfo);
90 emitActivityUpdated(model, container, activity->id(), role);
96 template<
typename _Model,
typename _Container>
97 static inline void emitActivityUpdated(_Model *model,
const _Container &container,
const QString &activity,
int role)
99 auto position = Private::activityPosition(container, activity);
102 Q_EMIT model->dataChanged(model->index(position->first),
103 model->index(position->first),
104 role ==
Qt::DecorationRole ? QList<int>{role, ActivityModel::ActivityIcon} : QList<int>{role});
108 class BackgroundCache
113 , plasmaConfig(QStringLiteral(
"plasma-org.kde.plasma.desktop-appletsrc"))
115 using namespace std::placeholders;
125 void settingsFileChanged(
const QString &file)
127 if (!file.
endsWith(plasmaConfig.name())) {
131 plasmaConfig.reparseConfiguration();
138 void subscribe(ActivityModel *model)
147 void unsubscribe(ActivityModel *model)
149 models.removeAll(model);
151 if (models.isEmpty()) {
157 QString backgroundFromConfig(
const KConfigGroup &config)
const
159 auto wallpaperPlugin = config.
readEntry(
"wallpaperplugin");
160 auto wallpaperConfig = config.
group(QStringLiteral(
"Wallpaper")).
group(wallpaperPlugin).
group(QStringLiteral(
"General"));
162 if (wallpaperConfig.hasKey(
"Image")) {
164 auto wallpaper = wallpaperConfig.readEntry(
"Image", QString());
165 if (!wallpaper.isEmpty()) {
169 if (wallpaperConfig.hasKey(
"Color")) {
170 auto backgroundColor = wallpaperConfig.readEntry(
"Color", QColor(0, 0, 0));
171 return backgroundColor.name();
177 void reload(
bool fullReload)
179 QHash<QString, QString> newBackgrounds;
185 QStringList changedBackgrounds;
187 for (
const auto &cont : plasmaConfigContainments().groupList()) {
188 auto config = plasmaConfigContainments().
group(cont);
189 auto activityId = config.
readEntry(
"activityId", QString());
197 if (newBackgrounds.
contains(activityId) && newBackgrounds[activityId][0] != QLatin1Char(
'#')) {
201 auto newBackground = backgroundFromConfig(config);
203 if (forActivity[activityId] != newBackground) {
204 changedBackgrounds << activityId;
205 if (!newBackground.isEmpty()) {
206 newBackgrounds[activityId] = newBackground;
213 if (!changedBackgrounds.
isEmpty()) {
214 forActivity = newBackgrounds;
216 for (
auto model : models) {
217 model->backgroundsUpdated(changedBackgrounds);
222 KConfigGroup plasmaConfigContainments()
224 return plasmaConfig.group(QStringLiteral(
"Containments"));
227 QHash<QString, QString> forActivity;
228 QList<ActivityModel *> models;
231 KConfig plasmaConfig;
234 static BackgroundCache &backgrounds()
238 static BackgroundCache cache;
243ActivityModel::ActivityModel(QObject *parent)
244 : QAbstractListModel(parent)
247 connect(&m_service, &Consumer::serviceStatusChanged,
this, &ActivityModel::setServiceStatus);
255 setServiceStatus(m_service.serviceStatus());
257 Private::backgrounds().subscribe(
this);
260ActivityModel::~ActivityModel()
262 Private::backgrounds().unsubscribe(
this);
270 {ActivityState,
"state"},
272 {ActivityIcon,
"iconSource"},
273 {ActivityDescription,
"description"},
274 {ActivityBackground,
"background"},
275 {ActivityCurrent,
"current"}};
278void ActivityModel::setServiceStatus(Consumer::ServiceStatus)
280 replaceActivities(m_service.activities());
283void ActivityModel::replaceActivities(
const QStringList &activities)
289 Private::model_reset m(
this);
291 m_knownActivities.clear();
292 m_shownActivities.clear();
294 for (
const QString &activity : activities) {
295 onActivityAdded(activity,
false);
299void ActivityModel::onActivityAdded(
const QString &
id,
bool notifyClients)
301 auto info = registerActivity(
id);
306 showActivity(info, notifyClients);
309void ActivityModel::onActivityRemoved(
const QString &
id)
314 unregisterActivity(
id);
317void ActivityModel::onCurrentActivityChanged(
const QString &
id)
321 for (
const auto &activity : m_shownActivities) {
322 Private::emitActivityUpdated(
this, m_shownActivities, activity->id(), ActivityCurrent);
326ActivityModel::InfoPtr ActivityModel::registerActivity(
const QString &
id)
328 auto position = Private::activityPosition(m_knownActivities,
id);
334 return *(position->second);
337 auto activityInfo = std::make_shared<Info>(
id);
339 auto ptr = activityInfo.get();
341 connect(ptr, &Info::nameChanged,
this, &ActivityModel::onActivityNameChanged);
342 connect(ptr, &Info::descriptionChanged,
this, &ActivityModel::onActivityDescriptionChanged);
343 connect(ptr, &Info::iconChanged,
this, &ActivityModel::onActivityIconChanged);
344 connect(ptr, &Info::stateChanged,
this, &ActivityModel::onActivityStateChanged);
346 m_knownActivities.insert(InfoPtr(activityInfo));
352void ActivityModel::unregisterActivity(
const QString &
id)
356 auto position = Private::activityPosition(m_knownActivities,
id);
359 if (
auto shown = Private::activityPosition(m_shownActivities,
id)) {
360 Private::model_remove(
this,
QModelIndex(), shown->first, shown->first);
361 m_shownActivities.erase(shown->second);
364 m_knownActivities.erase(position->second);
368void ActivityModel::showActivity(InfoPtr activityInfo,
bool notifyClients)
371 if (!Private::matchingState(activityInfo, m_shownStates)) {
376 if (boost::binary_search(m_shownActivities, activityInfo, InfoPtrComparator())) {
380 auto registeredPosition = Private::activityPosition(m_knownActivities, activityInfo->id());
382 if (!registeredPosition) {
383 qDebug() <<
"Got a request to show an unknown activity, ignoring";
387 auto activityInfoPtr = *(registeredPosition->second);
392 auto position = m_shownActivities.insert(activityInfoPtr);
395 unsigned int index = (position.second ? position.first : m_shownActivities.end()) - m_shownActivities.begin();
398 Private::model_insert(
this,
QModelIndex(), index, index);
402void ActivityModel::hideActivity(
const QString &
id)
404 auto position = Private::activityPosition(m_shownActivities,
id);
412 Private::model_remove(
this,
QModelIndex(), position->first, position->first);
413 m_shownActivities.erase(position->second);
417#define CREATE_SIGNAL_EMITTER(What,Role) \
418 void ActivityModel::onActivity##What##Changed(const QString &) \
420 Private::emitActivityUpdated(this, m_shownActivities, sender(), Role); \
425CREATE_SIGNAL_EMITTER(Description, ActivityDescription)
428#undef CREATE_SIGNAL_EMITTER
430void ActivityModel::onActivityStateChanged(Info::State state)
432 if (m_shownStates.empty()) {
433 Private::emitActivityUpdated(
this, m_shownActivities, sender(), ActivityState);
436 auto info = findActivity(sender());
442 if (boost::binary_search(m_shownStates, state)) {
443 showActivity(info,
true);
445 hideActivity(info->id());
450void ActivityModel::backgroundsUpdated(
const QStringList &activities)
452 for (
const auto &activity : activities) {
453 Private::emitActivityUpdated(
this, m_shownActivities, activity, ActivityBackground);
457void ActivityModel::setShownStates(
const QString &states)
459 m_shownStates.clear();
460 m_shownStatesString = states;
464 m_shownStates.insert(Running);
467 m_shownStates.insert(Starting);
470 m_shownStates.insert(Stopped);
473 m_shownStates.insert(Stopping);
477 replaceActivities(m_service.activities());
479 Q_EMIT shownStatesChanged(states);
482QString ActivityModel::shownStates()
const
484 return m_shownStatesString;
487int ActivityModel::rowCount(
const QModelIndex &parent)
const
491 return m_shownActivities.size();
496 const int row = index.
row();
497 const auto &item = *(m_shownActivities.cbegin() + row);
510 return item->state();
513 const QString &icon = item->icon();
516 return icon.
isEmpty() ? QStringLiteral(
"activities") : icon;
519 case ActivityDescription:
520 return item->description();
522 case ActivityCurrent:
523 return m_service.currentActivity() == item->id();
525 case ActivityBackground:
526 return Private::backgrounds().forActivity[item->id()];
536 Q_UNUSED(orientation);
542ActivityModel::InfoPtr ActivityModel::findActivity(
QObject *ptr)
const
544 auto info = boost::find_if(m_knownActivities, [ptr](
const InfoPtr &info) {
545 return ptr == info.get();
548 if (info == m_knownActivities.end()) {
557#define CREATE_SETTER(What) \
558 void ActivityModel::setActivity##What( \
559 const QString &id, const QString &value, const QJSValue &callback) \
561 continue_with(m_service.setActivity##What(id, value), callback); \
566CREATE_SETTER(Description)
572void ActivityModel::setCurrentActivity(
const QString &
id,
const QJSValue &callback)
574 continue_with(m_service.setCurrentActivity(
id), callback);
578void ActivityModel::addActivity(
const QString &name,
const QJSValue &callback)
580 continue_with(m_service.addActivity(name), callback);
584void ActivityModel::removeActivity(
const QString &
id,
const QJSValue &callback)
586 continue_with(m_service.removeActivity(
id), callback);
590void ActivityModel::stopActivity(
const QString &
id,
const QJSValue &callback)
592 continue_with(m_service.stopActivity(
id), callback);
596void ActivityModel::startActivity(
const QString &
id,
const QJSValue &callback)
598 continue_with(m_service.startActivity(
id), callback);
604#include "moc_activitymodel.cpp"
void activityAdded(const QString &id)
This signal is emitted when a new activity is added.
void currentActivityChanged(const QString &id)
This signal is emitted when the current activity is changed.
void activityRemoved(const QString &id)
This signal is emitted when an activity has been removed.
KConfigGroup group(const QString &group)
QString readEntry(const char *key, const char *aDefault=nullptr) const
void addFile(const QString &file)
static KDirWatch * self()
void dirty(const QString &path)
void created(const QString &path)
char * toString(const EngineQuery &query)
Namespace for everything in libkactivities.
bool contains(const Key &key) const const
QIcon fromTheme(const QString &name)
bool isEmpty() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString writableLocation(StandardLocation type)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)