diff --git a/autotests/common/test.cpp b/autotests/common/test.cpp index d0c68652..a247bfe0 100644 --- a/autotests/common/test.cpp +++ b/autotests/common/test.cpp @@ -1,56 +1,56 @@ /* * Copyright (C) 2013, 2014, 2015 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test.h" #include #include #include "common/dbus/common.h" Test::Test(QObject *parent) : QObject(parent) { } bool Test::inEmptySession() { const QStringList services = QDBusConnection::sessionBus().interface()->registeredServiceNames(); for (const QString & service : services) { bool kdeServiceAndNotKAMD = - service.startsWith(QStringLiteral("org.kde")) && + service.startsWith(QLatin1String("org.kde")) && service != KAMD_DBUS_SERVICE; if (kdeServiceAndNotKAMD) { return false; } } return true; } bool Test::isActivityManagerRunning() { return QDBusConnection::sessionBus().interface()->isServiceRegistered( KAMD_DBUS_SERVICE); } diff --git a/autotests/core/CleanOnlineTest.cpp b/autotests/core/CleanOnlineTest.cpp index 6e5a9d05..c0e30e15 100644 --- a/autotests/core/CleanOnlineTest.cpp +++ b/autotests/core/CleanOnlineTest.cpp @@ -1,126 +1,126 @@ /* * Copyright (C) 2013 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "CleanOnlineTest.h" #include #include #include #include #include QString CleanOnlineSetup::id1; QString CleanOnlineSetup::id2; CleanOnlineTest::CleanOnlineTest(QObject *parent) : Test(parent) { activities.reset(new KActivities::Controller()); } CleanOnlineSetup::CleanOnlineSetup(QObject *parent) : Test(parent) { activities.reset(new KActivities::Controller()); } OnlineTest::OnlineTest(QObject *parent) : Test(parent) { activities.reset(new KActivities::Controller()); } void CleanOnlineTest::testCleanOnlineActivityListing() { // Waiting for the service to start, and for us to sync TEST_WAIT_UNTIL(activities->serviceStatus() == KActivities::Consumer::Running); QCOMPARE(activities->activities(), QStringList()); QCOMPARE(activities->serviceStatus(), KActivities::Consumer::Running); QCOMPARE(activities->currentActivity(), QString()); QCOMPARE(activities->activities(), QStringList()); QCOMPARE(activities->activities(KActivities::Info::Running), QStringList()); QCOMPARE(activities->activities(KActivities::Info::Stopped), QStringList()); } void CleanOnlineSetup::testCleanOnlineActivityControl() { // Waiting for the service to start, and for us to sync TEST_WAIT_UNTIL(activities->serviceStatus() == KActivities::Consumer::Running); auto activity1 = activities->addActivity(QStringLiteral("The first one")); TEST_WAIT_UNTIL(activity1.isFinished()); id1 = activity1.result(); auto activity2 = activities->addActivity(QStringLiteral("The second one")); TEST_WAIT_UNTIL(activity2.isFinished()); id2 = activity2.result(); TEST_WAIT_UNTIL(activities->currentActivity() == id1); auto f1 = activities->setCurrentActivity(activity2.result()); TEST_WAIT_UNTIL(f1.isFinished()); TEST_WAIT_UNTIL(activities->currentActivity() == id2); auto f2 = activities->setCurrentActivity(id1); TEST_WAIT_UNTIL(f2.isFinished()); TEST_WAIT_UNTIL(activities->currentActivity() == id1); auto f3 = activities->setActivityName(id1, QStringLiteral("Renamed activity")); TEST_WAIT_UNTIL(f3.isFinished()); KActivities::Info ac(id1); - TEST_WAIT_UNTIL(ac.name() == QStringLiteral("Renamed activity")); + TEST_WAIT_UNTIL(ac.name() == QLatin1String("Renamed activity")); TEST_WAIT_UNTIL(activities->activities().size() == 2); } void OnlineTest::testOnlineActivityListing() { // Waiting for the service to start, and for us to sync TEST_WAIT_UNTIL(activities->serviceStatus() == KActivities::Consumer::Running); KActivities::Info i1(CleanOnlineSetup::id1); KActivities::Info i2(CleanOnlineSetup::id2); - TEST_WAIT_UNTIL(i1.name() == QStringLiteral("Renamed activity")); - TEST_WAIT_UNTIL(i2.name() == QStringLiteral("The second one")); + TEST_WAIT_UNTIL(i1.name() == QLatin1String("Renamed activity")); + TEST_WAIT_UNTIL(i2.name() == QLatin1String("The second one")); qDebug() << CleanOnlineSetup::id1 << i1.name(); qDebug() << CleanOnlineSetup::id2 << i2.name(); } void CleanOnlineTest::cleanupTestCase() { emit testFinished(); } void CleanOnlineSetup::cleanupTestCase() { emit testFinished(); } void OnlineTest::cleanupTestCase() { emit testFinished(); } diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 0de80a67..25f13f41 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -1,288 +1,288 @@ /* * Copyright (C) 2016 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "utils.h" // Output modifiers DEFINE_COMMAND(bare, 0) { flags.bare = true; return 0; } DEFINE_COMMAND(noBare, 0) { flags.bare = false; return 0; } DEFINE_COMMAND(color, 0) { flags.color = true; return 0; } DEFINE_COMMAND(noColor, 0) { flags.color = false; return 0; } // Activity management DEFINE_COMMAND(createActivity, 1) { auto result = awaitFuture(controller->addActivity(args(1))); qDebug().noquote() << result; return 1; } DEFINE_COMMAND(removeActivity, 1) { awaitFuture(controller->removeActivity(args(1))); return 1; } DEFINE_COMMAND(startActivity, 1) { awaitFuture(controller->startActivity(args(1))); return 1; } DEFINE_COMMAND(stopActivity, 1) { awaitFuture(controller->stopActivity(args(1))); return 1; } DEFINE_COMMAND(listActivities, 0) { for (const auto& activity: controller->activities()) { printActivity(activity); } return 0; } DEFINE_COMMAND(currentActivity, 0) { printActivity(controller->currentActivity()); return 0; } DEFINE_COMMAND(setActivityProperty, 3) { const auto what = args(1); const auto id = args(2); const auto value = args(3); awaitFuture( what == QLatin1String("name") ? controller->setActivityName(id, value) : what == QLatin1String("description") ? controller->setActivityDescription(id, value) : what == QLatin1String("icon") ? controller->setActivityIcon(id, value) : QFuture() ); return 3; } DEFINE_COMMAND(activityProperty, 2) { const auto what = args(1); const auto id = args(2); KActivities::Info info(id); out << ( what == QLatin1String("name") ? info.name() : what == QLatin1String("description") ? info.description() : what == QLatin1String("icon") ? info.icon() : QString() ) << "\n"; return 2; } // Activity switching DEFINE_COMMAND(setCurrentActivity, 1) { switchToActivity(args(1)); return 1; } DEFINE_COMMAND(nextActivity, 0) { const auto activities = controller->activities(); const auto currentActivity = controller->currentActivity(); for (int i = 0; i < activities.count() - 1; ++i) { if (activities[i] == currentActivity) { switchToActivity(activities[i + 1]); return 0; } } switchToActivity(activities[0]); return 0; } DEFINE_COMMAND(previousActivity, 0) { const auto activities = controller->activities(); const auto currentActivity = controller->currentActivity(); for (int i = 1; i < activities.count(); ++i) { if (activities[i] == currentActivity) { switchToActivity(activities[i - 1]); return 0; } } switchToActivity(activities.last()); return 0; } void printHelp() { if (!flags.bare) { qDebug() << "\nModifiers (applied only to trailing commands):" << "\n --bare, --no-bare - show minimal info vs show everything" << "\n --color, --no-color - make the output pretty" << "\n\nCommands:" << "\n --list-activities - lists all activities" << "\n --create-activity Name - creates a new activity with the specified name" << "\n --remove-activity ID - removes the activity with the specified id" << "\n --start-activity ID - starts the specified activity" << "\n --stop-activity ID - stops the specified activity" << "\n --current-activity - show the current activity" << "\n --set-current-activity - sets the current activity" << "\n --next-activity - switches to the next activity (in list-activities order)" << "\n --previous-activity - switches to the previous activity (in list-activities order)" << "\n --activity-property What ID" << "\n - gets activity name, icon or description" << "\n --set-activity-property What ID Value" << "\n - changes activity name, icon or description" ; } else { qDebug() << "\n--bare" << "\n--no-bare" << "\n--color" << "\n--no-color" << "\n--list-activities" << "\n--create-activitya NAME" << "\n--remove-activity ID" << "\n--current-activity" << "\n--set-current-activity" << "\n--next-activity" << "\n--previous-activity" ; } } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QTimer::singleShot(0, &app, [] { const auto args = QCoreApplication::arguments(); controller = new KActivities::Controller(); while (controller->serviceStatus() != KActivities::Controller::Running) { QCoreApplication::processEvents(); } #define MATCH_COMMAND(Command) \ - else if (args[argId] == QStringLiteral("--") + toDashes(QStringLiteral(#Command))) \ + else if (args[argId] == QLatin1String("--") + toDashes(QStringLiteral(#Command))) \ { \ argId += 1 + Command##_command({ args, argId })(); \ } if (args.count() <= 1) { printHelp(); } else for (int argId = 1; argId < args.count(); ) { if (args[argId] == QLatin1String("--help")) { printHelp(); argId++; } MATCH_COMMAND(bare) MATCH_COMMAND(noBare) MATCH_COMMAND(color) MATCH_COMMAND(noColor) MATCH_COMMAND(listActivities) MATCH_COMMAND(currentActivity) MATCH_COMMAND(setCurrentActivity) MATCH_COMMAND(activityProperty) MATCH_COMMAND(setActivityProperty) MATCH_COMMAND(nextActivity) MATCH_COMMAND(previousActivity) MATCH_COMMAND(createActivity) MATCH_COMMAND(removeActivity) MATCH_COMMAND(startActivity) MATCH_COMMAND(stopActivity) else { qDebug() << "Skipping unknown argument" << args[argId]; argId++; } } delete controller; QCoreApplication::quit(); }); return app.exec(); } diff --git a/src/imports/activityinfo.cpp b/src/imports/activityinfo.cpp index a170c5cb..097377be 100644 --- a/src/imports/activityinfo.cpp +++ b/src/imports/activityinfo.cpp @@ -1,109 +1,109 @@ /* * Copyright (C) 2012, 2013, 2014, 2015 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Self #include "activityinfo.h" namespace KActivities { namespace Imports { ActivityInfo::ActivityInfo(QObject *parent) : QObject(parent) , m_showCurrentActivity(false) { connect(&m_service, &KActivities::Controller::currentActivityChanged, this, &ActivityInfo::setCurrentActivity); } ActivityInfo::~ActivityInfo() { } void ActivityInfo::setCurrentActivity(const QString &id) { if (!m_showCurrentActivity) return; setIdInternal(id); emit nameChanged(m_info->name()); emit descriptionChanged(m_info->description()); emit iconChanged(m_info->icon()); } void ActivityInfo::setActivityId(const QString &id) { - m_showCurrentActivity = (id == QStringLiteral(":current")); + m_showCurrentActivity = (id == QLatin1String(":current")); setIdInternal(m_showCurrentActivity ? m_service.currentActivity() : id); } void ActivityInfo::setIdInternal(const QString &id) { using namespace KActivities; // We are killing the old info object, if any m_info.reset(new KActivities::Info(id)); auto ptr = m_info.get(); connect(ptr, &Info::nameChanged, this, &ActivityInfo::nameChanged); connect(ptr, &Info::descriptionChanged, this, &ActivityInfo::descriptionChanged); connect(ptr, &Info::iconChanged, this, &ActivityInfo::iconChanged); } #define CREATE_GETTER_AND_SETTER(WHAT, What) \ QString ActivityInfo::What() const \ { \ return m_info ? m_info->What() : QString(); \ } \ \ void ActivityInfo::set##WHAT(const QString &value) \ { \ if (!m_info) \ return; \ \ m_service.setActivity##WHAT(m_info->id(), value); \ } CREATE_GETTER_AND_SETTER(Name, name) CREATE_GETTER_AND_SETTER(Description, description) CREATE_GETTER_AND_SETTER(Icon, icon) #undef CREATE_GETTER_AND_SETTER QString ActivityInfo::activityId() const { return m_info ? m_info->id() : QString(); } bool ActivityInfo::valid() const { return true; } } // namespace Imports } // namespace KActivities // #include "activityinfo.moc" diff --git a/src/imports/activitymodel.cpp b/src/imports/activitymodel.cpp index 6391eb58..ee59e015 100644 --- a/src/imports/activitymodel.cpp +++ b/src/imports/activitymodel.cpp @@ -1,654 +1,654 @@ /* * Copyright (C) 2012, 2013, 2014, 2015 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Self #include "activitymodel.h" // Qt #include #include #include #include #include #include #include #include #include // KDE #include #include #include // Boost #include #include #include #include // Local #include "utils/remove_if.h" #define ENABLE_QJSVALUE_CONTINUATION #include "utils/continue_with.h" #include "utils/model_updaters.h" using kamd::utils::continue_with; namespace KActivities { namespace Imports { class ActivityModel::Private { public: DECLARE_RAII_MODEL_UPDATERS(ActivityModel) /** * Returns whether the activity has a desired state. * If the state is 0, returns true */ template static inline bool matchingState(InfoPtr activity, T states) { // Are we filtering activities on their states? if (!states.empty() && !boost::binary_search(states, activity->state())) { return false; } return true; } /** * Searches for the activity. * Returns an option(index, iterator) for the found activity. */ template static inline boost::optional< std::pair > activityPosition(const _Container &container, const QString &activityId) { using ActivityPosition = decltype(activityPosition(container, activityId)); using ContainerElement = typename _Container::value_type; auto position = boost::find_if(container, [&] (const ContainerElement &activity) { return activity->id() == activityId; } ); return (position != container.end()) ? ActivityPosition( std::make_pair(position - container.begin(), position) ) : ActivityPosition(); } /** * Notifies the model that an activity was updated */ template static inline void emitActivityUpdated(_Model *model, const _Container &container, QObject *activityInfo, int role) { const auto activity = static_cast (activityInfo); emitActivityUpdated(model, container, activity->id(), role); } /** * Notifies the model that an activity was updated */ template static inline void emitActivityUpdated(_Model *model, const _Container &container, const QString &activity, int role) { auto position = Private::activityPosition(container, activity); if (position) { emit model->dataChanged( model->index(position->first), model->index(position->first), role == Qt::DecorationRole ? QVector {role, ActivityModel::ActivityIcon} : QVector {role} ); } } class BackgroundCache { public: BackgroundCache() : initialized(false) , plasmaConfig(QStringLiteral("plasma-org.kde.plasma.desktop-appletsrc")) { using namespace std::placeholders; const QString configFile = QStandardPaths::writableLocation( QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + plasmaConfig.name(); KDirWatch::self()->addFile(configFile); connect(KDirWatch::self(), &KDirWatch::dirty, std::bind(&BackgroundCache::settingsFileChanged, this, _1)); connect(KDirWatch::self(), &KDirWatch::created, std::bind(&BackgroundCache::settingsFileChanged, this, _1)); } void settingsFileChanged(const QString &file) { if (!file.endsWith(plasmaConfig.name())) return; plasmaConfig.reparseConfiguration(); if (initialized) { reload(false); } } void subscribe(ActivityModel *model) { if (!initialized) { reload(true); } models << model; } void unsubscribe(ActivityModel *model) { models.removeAll(model); if (models.isEmpty()) { initialized = false; forActivity.clear(); } } QString backgroundFromConfig(const KConfigGroup &config) const { auto wallpaperPlugin = config.readEntry("wallpaperplugin"); auto wallpaperConfig = config.group("Wallpaper").group(wallpaperPlugin).group("General"); if (wallpaperConfig.hasKey("Image")) { // Trying for the wallpaper auto wallpaper = wallpaperConfig.readEntry("Image", QString()); if (!wallpaper.isEmpty()) { return wallpaper; } } if (wallpaperConfig.hasKey("Color")) { auto backgroundColor = wallpaperConfig.readEntry("Color", QColor(0, 0, 0)); return backgroundColor.name(); } return QString(); } void reload(bool fullReload) { QHash newBackgrounds; if (fullReload) { forActivity.clear(); } QStringList changedBackgrounds; for (const auto &cont: plasmaConfigContainments().groupList()) { auto config = plasmaConfigContainments().group(cont); auto activityId = config.readEntry("activityId", QString()); // Ignore if it has no assigned activity if (activityId.isEmpty()) continue; // Ignore if we have already found the background if (newBackgrounds.contains(activityId) && newBackgrounds[activityId][0] != QLatin1Char('#')) continue; auto newBackground = backgroundFromConfig(config); if (forActivity[activityId] != newBackground) { changedBackgrounds << activityId; if (!newBackground.isEmpty()) { newBackgrounds[activityId] = newBackground; } } } initialized = true; if (!changedBackgrounds.isEmpty()) { forActivity = newBackgrounds; for (auto model: models) { model->backgroundsUpdated(changedBackgrounds); } } } KConfigGroup plasmaConfigContainments() { return plasmaConfig.group("Containments"); } QHash forActivity; QList models; bool initialized; KConfig plasmaConfig; }; static BackgroundCache &backgrounds() { // If you convert this to a shared pointer, // fix the connections to KDirWatcher static BackgroundCache cache; return cache; } }; ActivityModel::ActivityModel(QObject *parent) : QAbstractListModel(parent) { // Initializing role names for qml connect(&m_service, &Consumer::serviceStatusChanged, this, &ActivityModel::setServiceStatus); connect(&m_service, SIGNAL(activityAdded(QString)), this, SLOT(onActivityAdded(QString))); connect(&m_service, SIGNAL(activityRemoved(QString)), this, SLOT(onActivityRemoved(QString))); connect(&m_service, SIGNAL(currentActivityChanged(QString)), this, SLOT(onCurrentActivityChanged(QString))); setServiceStatus(m_service.serviceStatus()); Private::backgrounds().subscribe(this); } ActivityModel::~ActivityModel() { Private::backgrounds().unsubscribe(this); } QHash ActivityModel::roleNames() const { return { {Qt::DisplayRole, "name"}, {Qt::DecorationRole, "icon"}, {ActivityState, "state"}, {ActivityId, "id"}, {ActivityIcon, "iconSource"}, {ActivityDescription, "description"}, {ActivityBackground, "background"}, {ActivityCurrent, "current"} }; } void ActivityModel::setServiceStatus(Consumer::ServiceStatus) { replaceActivities(m_service.activities()); } void ActivityModel::replaceActivities(const QStringList &activities) { // qDebug() << m_shownStatesString << "New list of activities: " // << activities; // qDebug() << m_shownStatesString << " -- RESET MODEL -- "; Private::model_reset m(this); m_knownActivities.clear(); m_shownActivities.clear(); for (const QString &activity: activities) { onActivityAdded(activity, false); } } void ActivityModel::onActivityAdded(const QString &id, bool notifyClients) { auto info = registerActivity(id); // qDebug() << m_shownStatesString << "Added a new activity:" << info->id() // << " " << info->name(); showActivity(info, notifyClients); } void ActivityModel::onActivityRemoved(const QString &id) { // qDebug() << m_shownStatesString << "Removed an activity:" << id; hideActivity(id); unregisterActivity(id); } void ActivityModel::onCurrentActivityChanged(const QString &id) { Q_UNUSED(id); for (const auto &activity: m_shownActivities) { Private::emitActivityUpdated(this, m_shownActivities, activity->id(), ActivityCurrent); } } ActivityModel::InfoPtr ActivityModel::registerActivity(const QString &id) { auto position = Private::activityPosition(m_knownActivities, id); // qDebug() << m_shownStatesString << "Registering activity: " << id // << " new? not " << (bool)position; if (position) { return *(position->second); } else { auto activityInfo = std::make_shared(id); auto ptr = activityInfo.get(); connect(ptr, &Info::nameChanged, this, &ActivityModel::onActivityNameChanged); connect(ptr, &Info::descriptionChanged, this, &ActivityModel::onActivityDescriptionChanged); connect(ptr, &Info::iconChanged, this, &ActivityModel::onActivityIconChanged); connect(ptr, &Info::stateChanged, this, &ActivityModel::onActivityStateChanged); m_knownActivities.insert(InfoPtr(activityInfo)); return activityInfo; } } void ActivityModel::unregisterActivity(const QString &id) { // qDebug() << m_shownStatesString << "Deregistering activity: " << id; auto position = Private::activityPosition(m_knownActivities, id); if (position) { if (auto shown = Private::activityPosition(m_shownActivities, id)) { Private::model_remove(this, QModelIndex(), shown->first, shown->first); m_shownActivities.erase(shown->second); } m_knownActivities.erase(position->second); } } void ActivityModel::showActivity(InfoPtr activityInfo, bool notifyClients) { // Should it really be shown? if (!Private::matchingState(activityInfo, m_shownStates)) return; // Is it already shown? if (boost::binary_search(m_shownActivities, activityInfo, InfoPtrComparator())) return; auto registeredPosition = Private::activityPosition(m_knownActivities, activityInfo->id()); if (!registeredPosition) { qDebug() << "Got a request to show an unknown activity, ignoring"; return; } auto activityInfoPtr = *(registeredPosition->second); // qDebug() << m_shownStatesString << "Setting activity visibility to true:" // << activityInfoPtr->id() << activityInfoPtr->name(); auto position = m_shownActivities.insert(activityInfoPtr); if (notifyClients) { unsigned int index = (position.second ? position.first : m_shownActivities.end()) - m_shownActivities.begin(); // qDebug() << m_shownStatesString << " -- MODEL INSERT -- " << index; Private::model_insert(this, QModelIndex(), index, index); } } void ActivityModel::hideActivity(const QString &id) { auto position = Private::activityPosition(m_shownActivities, id); // qDebug() << m_shownStatesString // << "Setting activity visibility to false: " << id; if (position) { // qDebug() << m_shownStatesString << " -- MODEL REMOVE -- " // << position->first; Private::model_remove(this, QModelIndex(), position->first, position->first); m_shownActivities.erase(position->second); } } #define CREATE_SIGNAL_EMITTER(What,Role) \ void ActivityModel::onActivity##What##Changed(const QString &) \ { \ Private::emitActivityUpdated(this, m_shownActivities, sender(), Role); \ } CREATE_SIGNAL_EMITTER(Name,Qt::DisplayRole) CREATE_SIGNAL_EMITTER(Description,ActivityDescription) CREATE_SIGNAL_EMITTER(Icon,Qt::DecorationRole) #undef CREATE_SIGNAL_EMITTER void ActivityModel::onActivityStateChanged(Info::State state) { if (m_shownStates.empty()) { Private::emitActivityUpdated(this, m_shownActivities, sender(), ActivityState); } else { auto info = findActivity(sender()); if (!info) { return; } if (boost::binary_search(m_shownStates, state)) { showActivity(info, true); } else { hideActivity(info->id()); } } } void ActivityModel::backgroundsUpdated(const QStringList &activities) { for (const auto &activity: activities) { Private::emitActivityUpdated(this, m_shownActivities, activity, ActivityBackground); } } void ActivityModel::setShownStates(const QString &states) { m_shownStates.clear(); m_shownStatesString = states; for (const auto &state: states.split(QLatin1Char(','))) { - if (state == QStringLiteral("Running")) { + if (state == QLatin1String("Running")) { m_shownStates.insert(Running); - } else if (state == QStringLiteral("Starting")) { + } else if (state == QLatin1String("Starting")) { m_shownStates.insert(Starting); - } else if (state == QStringLiteral("Stopped")) { + } else if (state == QLatin1String("Stopped")) { m_shownStates.insert(Stopped); - } else if (state == QStringLiteral("Stopping")) { + } else if (state == QLatin1String("Stopping")) { m_shownStates.insert(Stopping); } } replaceActivities(m_service.activities()); emit shownStatesChanged(states); } QString ActivityModel::shownStates() const { return m_shownStatesString; } int ActivityModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_shownActivities.size(); } QVariant ActivityModel::data(const QModelIndex &index, int role) const { const int row = index.row(); const auto &item = *(m_shownActivities.cbegin() + row); switch (role) { case Qt::DisplayRole: return item->name(); case Qt::DecorationRole: return QIcon::fromTheme(data(index, ActivityIcon).toString()); case ActivityId: return item->id(); case ActivityState: return item->state(); case ActivityIcon: { const QString &icon = item->icon(); // We need a default icon for activities return icon.isEmpty() ? QStringLiteral("preferences-activities") : icon; } case ActivityDescription: return item->description(); case ActivityCurrent: return m_service.currentActivity() == item->id(); case ActivityBackground: return Private::backgrounds().forActivity[item->id()]; default: return QVariant(); } } QVariant ActivityModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section); Q_UNUSED(orientation); Q_UNUSED(role); return QVariant(); } ActivityModel::InfoPtr ActivityModel::findActivity(QObject *ptr) const { auto info = boost::find_if(m_knownActivities, [ptr] (const InfoPtr &info) { return ptr == info.get(); }); if (info == m_knownActivities.end()) { return nullptr; } else { return *info; } } // QFuture Controller::setActivityWhat(id, value) #define CREATE_SETTER(What) \ void ActivityModel::setActivity##What( \ const QString &id, const QString &value, const QJSValue &callback) \ { \ continue_with(m_service.setActivity##What(id, value), callback); \ } CREATE_SETTER(Name) CREATE_SETTER(Description) CREATE_SETTER(Icon) #undef CREATE_SETTER // QFuture Controller::setCurrentActivity(id) void ActivityModel::setCurrentActivity(const QString &id, const QJSValue &callback) { continue_with(m_service.setCurrentActivity(id), callback); } // QFuture Controller::addActivity(name) void ActivityModel::addActivity(const QString &name, const QJSValue &callback) { continue_with(m_service.addActivity(name), callback); } // QFuture Controller::removeActivity(id) void ActivityModel::removeActivity(const QString &id, const QJSValue &callback) { continue_with(m_service.removeActivity(id), callback); } // QFuture Controller::stopActivity(id) void ActivityModel::stopActivity(const QString &id, const QJSValue &callback) { continue_with(m_service.stopActivity(id), callback); } // QFuture Controller::startActivity(id) void ActivityModel::startActivity(const QString &id, const QJSValue &callback) { continue_with(m_service.startActivity(id), callback); } } // namespace Imports } // namespace KActivities // #include "activitymodel.moc" diff --git a/src/imports/resourcemodel.cpp b/src/imports/resourcemodel.cpp index fb4f63ce..54b7d594 100644 --- a/src/imports/resourcemodel.cpp +++ b/src/imports/resourcemodel.cpp @@ -1,696 +1,696 @@ /* * Copyright (C) 2012, 2013, 2014 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Self #include "resourcemodel.h" // Qt #include #include #include #include #include #include // KDE #include #include #include #include #include // STL and Boost #include #include #include #include #include #include // Local #include "utils/range.h" #include "utils/dbusfuture_p.h" #include "common/dbus/common.h" #define ENABLE_QJSVALUE_CONTINUATION #include "utils/continue_with.h" #define ACTIVITY_COLUMN 0 #define AGENT_COLUMN 1 #define RESOURCE_COLUMN 2 #define UNKNOWN_COLUMN 3 using kamd::utils::continue_with; namespace KActivities { namespace Imports { class ResourceModel::LinkerService: public QDBusInterface { private: LinkerService() : KAMD_DBUS_INTERFACE(Resources/Linking, ResourcesLinking, nullptr) { } public: static std::shared_ptr self() { static std::weak_ptr s_instance; static std::mutex singleton; std::lock_guard singleton_lock(singleton); auto result = s_instance.lock(); if (s_instance.expired()) { result.reset(new LinkerService()); s_instance = result; } return result; } }; ResourceModel::ResourceModel(QObject *parent) : QSortFilterProxyModel(parent) , m_shownActivities(QStringLiteral(":current")) , m_shownAgents(QStringLiteral(":current")) , m_defaultItemsLoaded(false) , m_linker(LinkerService::self()) , m_config(KSharedConfig::openConfig("kactivitymanagerd-resourcelinkingrc") ->group("Order")) { // NOTE: What to do if the file does not exist? // Ignoring that case since the daemon creates it on startup. // Is it plausible that somebody will instantiate the ResourceModel // before the daemon is started? const QString databaseDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kactivitymanagerd/resources/"); m_databaseFile = databaseDir + QStringLiteral("database"); loadDatabase(); connect(&m_service, &KActivities::Consumer::currentActivityChanged, this, &ResourceModel::onCurrentActivityChanged); connect(m_linker.get(), SIGNAL(ResourceLinkedToActivity(QString,QString,QString)), this, SLOT(onResourceLinkedToActivity(QString,QString,QString))); connect(m_linker.get(), SIGNAL(ResourceUnlinkedFromActivity(QString,QString,QString)), this, SLOT(onResourceUnlinkedFromActivity(QString,QString,QString))); setDynamicSortFilter(true); sort(0); } bool ResourceModel::loadDatabase() { if (m_database.isValid()) return true; if (!QFile(m_databaseFile).exists()) return false; // TODO: Database connection naming could be smarter (thread-id-based, // reusing connections...?) m_database = QSqlDatabase::addDatabase( QStringLiteral("QSQLITE"), QStringLiteral("kactivities_db_resources_") + QString::number((quintptr)this)); // qDebug() << "Database file is: " << m_databaseFile; m_database.setDatabaseName(m_databaseFile); m_database.open(); m_databaseModel = new QSqlTableModel(this, m_database); m_databaseModel->setTable("ResourceLink"); m_databaseModel->select(); setSourceModel(m_databaseModel); reloadData(); return true; } ResourceModel::~ResourceModel() { } QVariant ResourceModel::dataForColumn(const QModelIndex &index, int column) const { if (!m_database.isValid()) return QVariant(); return m_databaseModel->data(index.sibling(index.row(), column), Qt::DisplayRole); } bool ResourceModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { const auto leftResource = dataForColumn(left, RESOURCE_COLUMN).toString(); const auto rightResource = dataForColumn(right, RESOURCE_COLUMN).toString(); const bool hasLeft = m_sorting.contains(leftResource); const bool hasRight = m_sorting.contains(rightResource); return ( hasLeft && !hasRight) ? true : (!hasLeft && hasRight) ? false : ( hasLeft && hasRight) ? m_sorting.indexOf(leftResource) < m_sorting.indexOf(rightResource) : QString::compare(leftResource, rightResource, Qt::CaseInsensitive) < 0; } QHash ResourceModel::roleNames() const { return { { Qt::DisplayRole, "display" }, { Qt::DecorationRole, "decoration" }, { ResourceRole, "uri" }, { AgentRole, "agent" }, { ActivityRole, "activity" }, { DescriptionRole, "subtitle" } }; } template inline QStringList validateList( const QString &values, Validator validator) { using boost::adaptors::filtered; using kamd::utils::as_collection; auto result = as_collection(values.split(',') | filtered(validator)); if (result.isEmpty()) { result.append(QStringLiteral(":current")); } return result; } void ResourceModel::setShownActivities(const QString &activities) { m_shownActivities = validateList(activities, [&] (const QString &activity) { return activity == ":current" || activity == ":any" || activity == ":global" || !QUuid(activity).isNull(); }); reloadData(); emit shownActivitiesChanged(); } void ResourceModel::setShownAgents(const QString &agents) { m_shownAgents = validateList(agents, [&] (const QString &agent) { return agent == ":current" || agent == ":any" || agent == ":global" || (!agent.isEmpty() && !agent.contains('\'') && !agent.contains('"')); }); loadDefaultsIfNeeded(); reloadData(); emit shownAgentsChanged(); } QString ResourceModel::shownActivities() const { return m_shownActivities.join(','); } QString ResourceModel::shownAgents() const { return m_shownAgents.join(','); } QString ResourceModel::defaultItemsConfig() const { return m_defaultItemsConfig; } void ResourceModel::setDefaultItemsConfig(const QString &defaultItemsConfig) { m_defaultItemsConfig = defaultItemsConfig; loadDefaultsIfNeeded(); } QString ResourceModel::activityToWhereClause(const QString &shownActivity) const { return QStringLiteral(" OR usedActivity=") + ( shownActivity == ":current" ? "'" + m_service.currentActivity() + "'" : shownActivity == ":any" ? "usedActivity" : shownActivity == ":global" ? "''" : "'" + shownActivity + "'" ); } QString ResourceModel::agentToWhereClause(const QString &shownAgent) const { return QStringLiteral(" OR initiatingAgent=") + ( shownAgent == ":current" ? "'" + QCoreApplication::applicationName() + "'" : shownAgent == ":any" ? "initiatingAgent" : shownAgent == ":global" ? "''" : "'" + shownAgent + "'" ); } QString ResourceModel::whereClause(const QStringList &activities, const QStringList &agents) const { using boost::accumulate; using namespace kamd::utils; // qDebug() << "Getting the where clause for: " << activities << " " << agents; // Defining the transformation functions for generating the SQL WHERE clause // from the specified activity/agent. They also resolve the special values // like :current, :any and :global. auto activityToWhereClause = transformed(&ResourceModel::activityToWhereClause, this); auto agentToWhereClause = transformed(&ResourceModel::agentToWhereClause, this); // Generating the SQL WHERE part by concatenating the generated clauses. // The generated query will be in the form of '0 OR clause1 OR clause2 ...' const QString whereActivity = accumulate(activities | activityToWhereClause, QStringLiteral("0")); const QString whereAgent = accumulate(agents | agentToWhereClause, QStringLiteral("0")); // qDebug() << "This is the filter: " << '(' + whereActivity + ") AND (" + whereAgent + ')'; return '(' + whereActivity + ") AND (" + whereAgent + ')'; } void ResourceModel::reloadData() { m_sorting = m_config.readEntry(m_shownAgents.first(), QStringList()); if (!m_database.isValid()) return; m_databaseModel->setFilter(whereClause(m_shownActivities, m_shownAgents)); } void ResourceModel::onCurrentActivityChanged(const QString &activity) { Q_UNUSED(activity); if (m_shownActivities.contains(":current")) { reloadData(); } } QVariant ResourceModel::data(const QModelIndex &proxyIndex, int role) const { auto index = mapToSource(proxyIndex); if (role == Qt::DisplayRole || role == DescriptionRole || role == Qt::DecorationRole) { auto uri = dataForColumn(index, RESOURCE_COLUMN).toString(); // TODO: Will probably need some more special handling - // for application:/ and a few more if (uri.startsWith('/')) { - uri = QStringLiteral("file://") + uri; + uri = QLatin1String("file://") + uri; } KFileItem file(uri); if (file.mimetype() == "application/x-desktop") { KDesktopFile desktop(file.localPath()); return role == Qt::DisplayRole ? desktop.readGenericName() : role == DescriptionRole ? desktop.readName() : role == Qt::DecorationRole ? desktop.readIcon() : QVariant(); } return role == Qt::DisplayRole ? file.name() : role == Qt::DecorationRole ? file.iconName() : QVariant(); } return dataForColumn(index, role == ResourceRole ? RESOURCE_COLUMN : role == AgentRole ? AGENT_COLUMN : role == ActivityRole ? ACTIVITY_COLUMN : UNKNOWN_COLUMN ); } void ResourceModel::linkResourceToActivity(const QString &resource, const QJSValue &callback) const { linkResourceToActivity(resource, m_shownActivities.first(), callback); } void ResourceModel::linkResourceToActivity(const QString &resource, const QString &activity, const QJSValue &callback) const { linkResourceToActivity(m_shownAgents.first(), resource, activity, callback); } void ResourceModel::linkResourceToActivity(const QString &agent, const QString &_resource, const QString &activity, const QJSValue &callback) const { if (activity == ":any") { qWarning() << ":any is not a valid activity specification for linking"; return; } auto resource = validateResource(_resource); // qDebug() << "ResourceModel: Linking resource to activity: --------------------------------------------------\n" // << "ResourceModel: Resource: " << resource << "\n" // << "ResourceModel: Agents: " << agent << "\n" // << "ResourceModel: Activities: " << activity << "\n"; kamd::utils::continue_with( DBusFuture::asyncCall(m_linker.get(), QStringLiteral("LinkResourceToActivity"), agent, resource, activity == ":current" ? m_service.currentActivity() : activity == ":global" ? "" : activity), callback); } void ResourceModel::unlinkResourceFromActivity(const QString &resource, const QJSValue &callback) { unlinkResourceFromActivity(m_shownAgents, resource, m_shownActivities, callback); } void ResourceModel::unlinkResourceFromActivity(const QString &resource, const QString &activity, const QJSValue &callback) { unlinkResourceFromActivity(m_shownAgents, resource, QStringList() << activity, callback); } void ResourceModel::unlinkResourceFromActivity(const QString &agent, const QString &resource, const QString &activity, const QJSValue &callback) { unlinkResourceFromActivity(QStringList() << agent, resource, QStringList() << activity, callback); } void ResourceModel::unlinkResourceFromActivity(const QStringList &agents, const QString &_resource, const QStringList &activities, const QJSValue &callback) { auto resource = validateResource(_resource); // qDebug() << "ResourceModel: Unlinking resource from activity: ----------------------------------------------\n" // << "ResourceModel: Resource: " << resource << "\n" // << "ResourceModel: Agents: " << agents << "\n" // << "ResourceModel: Activities: " << activities << "\n"; for (const auto& agent: agents) { for (const auto& activity: activities) { if (activity == ":any") { qWarning() << ":any is not a valid activity specification for linking"; return; } // We might want to compose the continuations into one // so that the callback gets called only once, // but we don't care about that at the moment kamd::utils::continue_with( DBusFuture::asyncCall(m_linker.get(), QStringLiteral("UnlinkResourceFromActivity"), agent, resource, activity == ":current" ? m_service.currentActivity() : activity == ":global" ? "" : activity), callback); } } } bool ResourceModel::isResourceLinkedToActivity(const QString &resource) { return isResourceLinkedToActivity(m_shownAgents, resource, m_shownActivities); } bool ResourceModel::isResourceLinkedToActivity(const QString &resource, const QString &activity) { return isResourceLinkedToActivity(m_shownAgents, resource, QStringList() << activity); } bool ResourceModel::isResourceLinkedToActivity(const QString &agent, const QString &resource, const QString &activity) { return isResourceLinkedToActivity(QStringList() << agent, resource, QStringList() << activity); } bool ResourceModel::isResourceLinkedToActivity(const QStringList &agents, const QString &_resource, const QStringList &activities) { if (!m_database.isValid()) return false; auto resource = validateResource(_resource); // qDebug() << "ResourceModel: Testing whether the resource is linked to activity: ----------------------------\n" // << "ResourceModel: Resource: " << resource << "\n" // << "ResourceModel: Agents: " << agents << "\n" // << "ResourceModel: Activities: " << activities << "\n"; QSqlQuery query(m_database); query.prepare("SELECT targettedResource " "FROM ResourceLink " "WHERE targettedResource=:resource AND " + whereClause(activities, agents)); query.bindValue(":resource", resource); query.exec(); auto result = query.next(); // qDebug() << "Query: " << query.lastQuery(); // // if (query.lastError().isValid()) { // qDebug() << "Error: " << query.lastError(); // } // // qDebug() << "Result: " << result; return result; } void ResourceModel::onResourceLinkedToActivity(const QString &initiatingAgent, const QString &targettedResource, const QString &usedActivity) { Q_UNUSED(targettedResource); if (!loadDatabase()) return; auto matchingActivity = boost::find_if(m_shownActivities, [&] (const QString &shownActivity) { return // If the activity is not important shownActivity == ":any" || // or we are listening for the changes for the current activity (shownActivity == ":current" && usedActivity == m_service.currentActivity()) || // or we want the globally linked resources (shownActivity == ":global" && usedActivity == "") || // or we have a specific activity in mind shownActivity == usedActivity; }); auto matchingAgent = boost::find_if(m_shownAgents, [&](const QString &shownAgent) { return // If the agent is not important shownAgent == ":any" || // or we are listening for the changes for the current agent (shownAgent == ":current" && initiatingAgent == QCoreApplication::applicationName()) || // or for links that are global, and not related to a specific agent (shownAgent == ":global" && initiatingAgent == "") || // or we have a specific agent to listen for shownAgent == initiatingAgent; }); if (matchingActivity != m_shownActivities.end() && matchingAgent != m_shownAgents.end()) { // TODO: This might be smarter possibly, but might collide // with the SQL model. Implement a custom model with internal // cache instead of basing it on QSqlModel. reloadData(); } } void ResourceModel::onResourceUnlinkedFromActivity(const QString &initiatingAgent, const QString &targettedResource, const QString &usedActivity) { // These are the same at the moment onResourceLinkedToActivity(initiatingAgent, targettedResource, usedActivity); } void ResourceModel::setOrder(const QStringList &resources) { m_sorting = resources; m_config.writeEntry(m_shownAgents.first(), m_sorting); m_config.sync(); invalidate(); } void ResourceModel::move(int sourceItem, int destinationItem) { QStringList resources; const int rows = rowCount(); for (int row = 0; row < rows; row++) { resources << resourceAt(row); } if (sourceItem < 0 || sourceItem >= rows || destinationItem < 0 || destinationItem >= rows) { return; } // Moving one item from the source item's location to the location // after the destination item std::rotate( resources.begin() + sourceItem, resources.begin() + sourceItem + 1, resources.begin() + destinationItem + 1 ); setOrder(resources); } void ResourceModel::sortItems(Qt::SortOrder sortOrder) { typedef QPair Resource; QList resources; const int rows = rowCount(); for (int row = 0; row < rows; ++row) { resources << qMakePair(resourceAt(row), displayAt(row)); } std::sort(resources.begin(), resources.end(), [sortOrder] (const Resource &left, const Resource &right) { return sortOrder == Qt::AscendingOrder ? left.second < right.second : right.second < left.second; } ); QStringList result; for (const auto &resource : qAsConst(resources)) { result << resource.first; } setOrder(result); } KConfigGroup ResourceModel::config() const { return KSharedConfig::openConfig("kactivitymanagerd-resourcelinkingrc") ->group("Order"); } int ResourceModel::count() const { return QSortFilterProxyModel::rowCount(); } QString ResourceModel::displayAt(int row) const { return data(index(row, 0), Qt::DisplayRole).toString(); } QString ResourceModel::resourceAt(int row) const { return validateResource(data(index(row, 0), ResourceRole).toString()); } void ResourceModel::loadDefaultsIfNeeded() const { // Did we get a request to actually do anything? if (m_defaultItemsConfig.isEmpty()) return; if (m_shownAgents.size() == 0) return; // If we have already loaded the items, just exit if (m_defaultItemsLoaded) return; m_defaultItemsLoaded = true; // If there are items in the model, no need to load the defaults if (count() != 0) return; // Did we already load the defaults for this agent? QStringList alreadyInitialized = m_config.readEntry("defaultItemsProcessedFor", QStringList()); if (alreadyInitialized.contains(m_shownAgents.first())) return; alreadyInitialized << m_shownAgents.first(); m_config.writeEntry("defaultItemsProcessedFor", alreadyInitialized); m_config.sync(); QStringList args = m_defaultItemsConfig.split("/"); QString configField = args.takeLast(); QString configGroup = args.takeLast(); QString configFile = args.join("/"); // qDebug() << "Config" // << configFile << " " // << configGroup << " " // << configField << " "; QStringList items = KSharedConfig::openConfig(configFile) ->group(configGroup) .readEntry(configField, QStringList()); for (const auto& item: items) { // qDebug() << "Adding: " << item; linkResourceToActivity(item, ":global", QJSValue()); } } QString ResourceModel::validateResource(const QString &resource) const { - return resource.startsWith(QStringLiteral("file://")) ? + return resource.startsWith(QLatin1String("file://")) ? QUrl(resource).toLocalFile() : resource; } } // namespace Imports } // namespace KActivities // #include "resourcemodel.moc"