diff --git a/libtaskmanager/autotests/launchertasksmodeltest.cpp b/libtaskmanager/autotests/launchertasksmodeltest.cpp --- a/libtaskmanager/autotests/launchertasksmodeltest.cpp +++ b/libtaskmanager/autotests/launchertasksmodeltest.cpp @@ -54,14 +54,14 @@ { LauncherTasksModel m; - QSignalSpy launcherListChangedSpy(&m, &LauncherTasksModel::launcherListChanged); + QSignalSpy launcherListChangedSpy(&m, &LauncherTasksModel::serializedLauncherListChanged); QVERIFY(launcherListChangedSpy.isValid()); - m.setLauncherList(m_urlStrings); + m.setSerializedLauncherList(m_urlStrings); QCOMPARE(launcherListChangedSpy.count(), 1); - QCOMPARE(m.launcherList(), m_urlStrings); + QCOMPARE(m.serializedLauncherList(), m_urlStrings); QCOMPARE(m.data(m.index(0, 0), AbstractTasksModel::LauncherUrl).toString(), m_urlStrings.at(0)); QCOMPARE(m.data(m.index(1, 0), AbstractTasksModel::LauncherUrl).toString(), m_urlStrings.at(1)); @@ -74,19 +74,19 @@ QStringList urlStrings; urlStrings << QLatin1String("GARBAGE URL"); - QSignalSpy launcherListChangedSpy(&m, &LauncherTasksModel::launcherListChanged); + QSignalSpy launcherListChangedSpy(&m, &LauncherTasksModel::serializedLauncherListChanged); QVERIFY(launcherListChangedSpy.isValid()); - m.setLauncherList(urlStrings); + m.setSerializedLauncherList(urlStrings); QCOMPARE(launcherListChangedSpy.count(), 0); bool added = m.requestAddLauncher(QUrl(urlStrings.at(0))); QVERIFY(!added); QCOMPARE(launcherListChangedSpy.count(), 0); - QCOMPARE(m.launcherList(), QStringList()); + QCOMPARE(m.serializedLauncherList(), QStringList()); } void LauncherTasksModelTest::shouldRejectDuplicates() @@ -97,34 +97,34 @@ urlStrings << QLatin1String("file:///usr/share/applications/org.kde.dolphin.desktop"); urlStrings << QLatin1String("file:///usr/share/applications/org.kde.dolphin.desktop"); - QSignalSpy launcherListChangedSpy(&m, &LauncherTasksModel::launcherListChanged); + QSignalSpy launcherListChangedSpy(&m, &LauncherTasksModel::serializedLauncherListChanged); QVERIFY(launcherListChangedSpy.isValid()); - m.setLauncherList(urlStrings); + m.setSerializedLauncherList(urlStrings); QCOMPARE(launcherListChangedSpy.count(), 1); bool added = m.requestAddLauncher(QUrl(urlStrings.at(0))); QVERIFY(!added); QCOMPARE(launcherListChangedSpy.count(), 1); - QCOMPARE(m.launcherList(), QStringList() << urlStrings.at(0)); + QCOMPARE(m.serializedLauncherList(), QStringList() << urlStrings.at(0)); } void LauncherTasksModelTest::shouldAddRemoveLauncher() { LauncherTasksModel m; - QSignalSpy launcherListChangedSpy(&m, &LauncherTasksModel::launcherListChanged); + QSignalSpy launcherListChangedSpy(&m, &LauncherTasksModel::serializedLauncherListChanged); QVERIFY(launcherListChangedSpy.isValid()); bool added = m.requestAddLauncher(QUrl(m_urlStrings.at(0))); QVERIFY(added); QCOMPARE(launcherListChangedSpy.count(), 1); - QCOMPARE(m.launcherList().at(0), m_urlStrings.at(0)); + QCOMPARE(m.serializedLauncherList().at(0), m_urlStrings.at(0)); bool removed = m.requestRemoveLauncher(QUrl(m_urlStrings.at(0))); @@ -135,17 +135,17 @@ QVERIFY(!removed); - QCOMPARE(m.launcherList(), QStringList()); + QCOMPARE(m.serializedLauncherList(), QStringList()); } void LauncherTasksModelTest::shouldReturnValidLauncherPositions() { LauncherTasksModel m; - QSignalSpy launcherListChangedSpy(&m, &LauncherTasksModel::launcherListChanged); + QSignalSpy launcherListChangedSpy(&m, &LauncherTasksModel::serializedLauncherListChanged); QVERIFY(launcherListChangedSpy.isValid()); - m.setLauncherList(m_urlStrings); + m.setSerializedLauncherList(m_urlStrings); QCOMPARE(launcherListChangedSpy.count(), 1); diff --git a/libtaskmanager/launchertasksmodel.h b/libtaskmanager/launchertasksmodel.h --- a/libtaskmanager/launchertasksmodel.h +++ b/libtaskmanager/launchertasksmodel.h @@ -52,7 +52,8 @@ { Q_OBJECT - Q_PROPERTY(QStringList launcherList READ launcherList WRITE setLauncherList NOTIFY launcherListChanged) + Q_PROPERTY(QStringList serializedLauncherList READ serializedLauncherList WRITE setSerializedLauncherList NOTIFY serializedLauncherListChanged) + Q_PROPERTY(QStringList shownLauncherList READ shownLauncherList NOTIFY shownLauncherListChanged) public: explicit LauncherTasksModel(QObject *parent = 0); @@ -62,23 +63,31 @@ int rowCount(const QModelIndex &parent = QModelIndex()) const override; /** - * The list of launcher URLs serialized to strings. + * The list of launcher URLs serialized to strings along with + * the activities they belong to. * - * @see setLauncherList + * @see setSerializedLauncherList * @returns the list of launcher URLs serialized to strings. **/ - QStringList launcherList() const; + QStringList serializedLauncherList() const; /** * Replace the list of launcher URL strings. * * Invalid or empty URLs will be rejected. Duplicate URLs will be * collapsed. * - * @see launcherList + * @see serializedLauncherList * @param launchers A list of launcher URL strings. **/ - void setLauncherList(const QStringList &launchers); + void setSerializedLauncherList(const QStringList &launchers); + + /** + * The list of currently shown launcher URLs serialized to strings. + * + * @returns the list of launcher URLs serialized to strings. + **/ + QStringList shownLauncherList() const; /** * Request adding a launcher with the given URL. @@ -141,7 +150,8 @@ void requestOpenUrls(const QModelIndex &index, const QList &urls) override; Q_SIGNALS: - void launcherListChanged() const; + void serializedLauncherListChanged() const; + void shownLauncherListChanged() const; private: class Private; diff --git a/libtaskmanager/launchertasksmodel.cpp b/libtaskmanager/launchertasksmodel.cpp --- a/libtaskmanager/launchertasksmodel.cpp +++ b/libtaskmanager/launchertasksmodel.cpp @@ -27,22 +27,81 @@ #include #include +#include + #include #include #include #if HAVE_X11 #include #endif +#include "launchertasksmodel_p.h" + namespace TaskManager { class LauncherTasksModel::Private { public: Private(LauncherTasksModel *q); - QList launchers; + + KActivities::Consumer activities; + + QList currentlyShownLaunchers; + QList launchersOrder; + QHash activitiesForLauncher; + + inline QList launchersForActivity(const QString &activity) const + { + QList result; + qDebug() << "GREPME: launchers" << launchersOrder; + for (const auto &launcher: launchersOrder) { + const auto activities = activitiesForLauncher[launcher]; + qDebug() << "GREPME: activities for launcher" << launcher << activities; + if (activities.contains(NULL_UUID) || activities.contains(activity)) { + result << launcher; + } + } + return result; + } + + inline void updateShownLaunchers() + { + const auto urls = launchersForActivity(activities.currentActivity()); + + qDebug() << "GREPME: Urls for this activity are: " << urls; + + if (currentlyShownLaunchers != urls) { + // Common case optimization: If the list changed but its size + // did not (e.g. due to reordering by a user of this model), + // just clear the caches and announce new data instead of + // resetting. + if (currentlyShownLaunchers.count() == urls.count()) { + currentlyShownLaunchers.clear(); + appDataCache.clear(); + + currentlyShownLaunchers = urls; + + emit q->dataChanged( + q->index(0, 0), + q->index(currentlyShownLaunchers.count() - 1, 0)); + } else { + q->beginResetModel(); + + currentlyShownLaunchers.clear(); + appDataCache.clear(); + + currentlyShownLaunchers = urls; + + q->endResetModel(); + + emit q->shownLauncherListChanged(); + } + } + } + QHash appDataCache; QTimer sycocaChangeTimer; @@ -65,14 +124,14 @@ QObject::connect(&sycocaChangeTimer, &QTimer::timeout, q, [this]() { - if (!launchers.count()) { + if (!currentlyShownLaunchers.count()) { return; } appDataCache.clear(); // Emit changes of all roles satisfied from app data cache. - q->dataChanged(q->index(0, 0), q->index(launchers.count() - 1, 0), + q->dataChanged(q->index(0, 0), q->index(currentlyShownLaunchers.count() - 1, 0), QVector{Qt::DisplayRole, Qt::DecorationRole, AbstractTasksModel::AppId, AbstractTasksModel::AppName, AbstractTasksModel::GenericName, AbstractTasksModel::LauncherUrl}); @@ -89,6 +148,12 @@ } } ); + + QObject::connect(&activities, &KActivities::Consumer::currentActivityChanged, q, + [this]() { + updateShownLaunchers(); + } + ); } AppData LauncherTasksModel::Private::appData(const QUrl &url) @@ -116,13 +181,13 @@ QVariant LauncherTasksModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= d->launchers.count()) { + if (!index.isValid() || index.row() >= d->currentlyShownLaunchers.count()) { + qDebug() << "GREPME NOT A VALID INDEX" << index.row(); return QVariant(); } - const QUrl &url = d->launchers.at(index.row()); + const QUrl &url = d->currentlyShownLaunchers.at(index.row()); const AppData &data = d->appData(url); - if (role == Qt::DisplayRole) { return data.name; } else if (role == Qt::DecorationRole) { @@ -158,59 +223,88 @@ int LauncherTasksModel::rowCount(const QModelIndex &parent) const { - return parent.isValid() ? 0 : d->launchers.count(); + return parent.isValid() ? 0 : d->currentlyShownLaunchers.count(); } -QStringList LauncherTasksModel::launcherList() const +QStringList LauncherTasksModel::shownLauncherList() const { - return QUrl::toStringList(d->launchers); + return QUrl::toStringList(d->currentlyShownLaunchers); } -void LauncherTasksModel::setLauncherList(const QStringList &launchers) +QStringList LauncherTasksModel::serializedLauncherList() const { - const QList &_urls = QUrl::fromStringList(launchers, QUrl::StrictMode); - QList urls; + // Serializing the launchers + QStringList result; - // Reject invalid urls and duplicates. - foreach(const QUrl &url, _urls) { - if (url.isValid()) { - bool dupe = false; + for (const auto &launcher: d->launchersOrder) { + const QString serializedLauncher = + "[" + d->activitiesForLauncher[launcher].join(",") + "]\n" + launcher.toString(); + result << serializedLauncher; + } - foreach(const QUrl &addedUrl, urls) { - dupe = launcherUrlsMatch(url, addedUrl, IgnoreQueryItems); - if (dupe) break; - } + return result; +} - if (!dupe) { - urls.append(url); - } - } - } +void LauncherTasksModel::setSerializedLauncherList(const QStringList &serializedLaunchers) +{ + // Clearing everything + d->launchersOrder.clear(); - if (d->launchers != urls) { - // Common case optimization: If the list changed but its size - // did not (e.g. due to reordering by a user of this model), - // just clear the caches and announce new data instead of - // resetting. - if (d->launchers.count() == urls.count()) { - d->launchers.clear(); - d->appDataCache.clear(); + QHash oldActivitiesForLauncher; + std::swap(oldActivitiesForLauncher, d->activitiesForLauncher); - d->launchers = urls; + qDebug() << "GREPME: We are asked to set these launchers:" + << serializedLaunchers + ; - emit dataChanged(index(0, 0), index(d->launchers.count() - 1, 0)); - } else { - beginResetModel(); + // Loading the activity to launchers map + QHash> launchersForActivitiesCandidates; + for (const auto& serializedLauncher: serializedLaunchers) { + QStringList activities; + QUrl url; + + std::tie(url, activities) = + deserializeLauncher(serializedLauncher); - d->launchers.clear(); - d->appDataCache.clear(); + qDebug() << "GREPME: Result: " << url << activities; - d->launchers = urls; + // Is url is not valid, ignore it + if (!url.isValid()) continue; - endResetModel(); + qDebug() << "GREPME: Url is valid"; + + // Is the url a duplicate? + const auto location = + std::find_if(d->launchersOrder.begin(), d->launchersOrder.end(), + [&url] (const QUrl &item) { + return launcherUrlsMatch(url, item, IgnoreQueryItems); + }); + + + if (location != d->launchersOrder.end()) { + // It is a duplicate + url = *location; + } else { + // It is not a duplicate, we need to add it + // to the list of registered launchers + d->launchersOrder << url; } - emit launcherListChanged(); + d->activitiesForLauncher[url].append(activities); + + // If this is shown on all activities, we do not need to remember + // each activity separately + if (d->activitiesForLauncher[url].contains(NULL_UUID)) { + d->activitiesForLauncher[url] = QStringList({ NULL_UUID }); + } + } + + qDebug() << "GREPME: We got:" << d->activitiesForLauncher; + qDebug() << "GREPME: We got order:" << d->launchersOrder; + + if (oldActivitiesForLauncher != d->activitiesForLauncher) { + d->updateShownLaunchers(); + emit serializedLauncherListChanged(); } } @@ -225,35 +319,43 @@ } // Reject duplicates. - foreach(const QUrl &launcher, d->launchers) { + foreach(const QUrl &launcher, d->launchersOrder) { if (launcherUrlsMatch(url, launcher, IgnoreQueryItems)) { return false; } } - const int count = d->launchers.count(); + // Adding the launcher to all activities + const int count = d->launchersOrder.count(); beginInsertRows(QModelIndex(), count, count); - d->launchers.append(url); + d->activitiesForLauncher[url] = QStringList({ NULL_UUID }); + d->launchersOrder.append(url); endInsertRows(); - emit launcherListChanged(); + emit serializedLauncherListChanged(); + emit shownLauncherListChanged(); return true; } bool LauncherTasksModel::requestRemoveLauncher(const QUrl &url) { - for (int i = 0; i < d->launchers.count(); ++i) { - const QUrl &launcher = d->launchers.at(i); + for (int i = 0; i < d->launchersOrder.count(); ++i) { + const QUrl &launcher = d->launchersOrder.at(i); if (launcherUrlsMatch(url, launcher, IgnoreQueryItems) || launcherUrlsMatch(url, d->appData(launcher).url, IgnoreQueryItems)) { + + // Removing the launcher from all activities + beginRemoveRows(QModelIndex(), i, i); - d->launchers.removeAt(i); + d->launchersOrder.removeAt(i); + d->activitiesForLauncher.remove(url); d->appDataCache.remove(launcher); endRemoveRows(); - emit launcherListChanged(); + emit serializedLauncherListChanged(); + emit shownLauncherListChanged(); return true; } @@ -264,8 +366,8 @@ int LauncherTasksModel::launcherPosition(const QUrl &url) const { - for (int i = 0; i < d->launchers.count(); ++i) { - if (launcherUrlsMatch(url, d->launchers.at(i), IgnoreQueryItems)) { + for (int i = 0; i < d->currentlyShownLaunchers.count(); ++i) { + if (launcherUrlsMatch(url, d->currentlyShownLaunchers.at(i), IgnoreQueryItems)) { return i; } } @@ -281,11 +383,11 @@ void LauncherTasksModel::requestNewInstance(const QModelIndex &index) { if (!index.isValid() || index.model() != this - || index.row() < 0 || index.row() >= d->launchers.count()) { + || index.row() < 0 || index.row() >= d->currentlyShownLaunchers.count()) { return; } - const QUrl &url = d->launchers.at(index.row()); + const QUrl &url = d->currentlyShownLaunchers.at(index.row()); quint32 timeStamp = 0; @@ -312,12 +414,12 @@ void LauncherTasksModel::requestOpenUrls(const QModelIndex &index, const QList &urls) { if (!index.isValid() || index.model() != this - || index.row() < 0 || index.row() >= d->launchers.count() + || index.row() < 0 || index.row() >= d->currentlyShownLaunchers.count() || urls.isEmpty()) { return; } - const QUrl &url = d->launchers.at(index.row()); + const QUrl &url = d->currentlyShownLaunchers.at(index.row()); quint32 timeStamp = 0; diff --git a/libtaskmanager/launchertasksmodel_p.h b/libtaskmanager/launchertasksmodel_p.h new file mode 100644 --- /dev/null +++ b/libtaskmanager/launchertasksmodel_p.h @@ -0,0 +1,66 @@ +/******************************************************************** +Copyright 2016 Ivan Cukic + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ + +#ifndef LAUNCHERTASKSMODEL_P_H +#define LAUNCHERTASKSMODEL_P_H + +#include + +namespace TaskManager { + +#define NULL_UUID "00000000-0000-0000-0000-000000000000" + +inline static std::pair deserializeLauncher(const QString &serializedLauncher) +{ + QStringList activities; + QUrl url(serializedLauncher, QUrl::StrictMode); + + // The storage format is: [list of activity ids]\nURL + // The activity IDs list can not be empty, it at least needs + // to contain the nulluuid. + // If parsing fails, we are considering the serialized launcher + // to not have the activities array -- to have the old format + if (serializedLauncher.startsWith('[')) { + // It seems we have the activity specifier in the launcher + qDebug() << "GREPME: It seems we have the activity specifier in the launcher"; + const auto activitiesBlockEnd = serializedLauncher.indexOf("]\n"); + + if (activitiesBlockEnd != -1) { + activities = serializedLauncher.mid(1, activitiesBlockEnd - 1).split(",", QString::SkipEmptyParts); + + if (!activities.isEmpty()) { + url = QUrl(serializedLauncher.mid(activitiesBlockEnd + 2), QUrl::StrictMode); + } + } + } + + // If the activities array is empty, this means that this launcher + // needs to be on all activities + if (activities.isEmpty()) { + qDebug() << "GREPME: We need to have this in all activities"; + activities = QStringList({ NULL_UUID }); + } + + return std::make_pair(url, activities); +} + +} // namespace TaskManager + +#endif // LAUNCHERTASKSMODEL_P_H diff --git a/libtaskmanager/tasksmodel.h b/libtaskmanager/tasksmodel.h --- a/libtaskmanager/tasksmodel.h +++ b/libtaskmanager/tasksmodel.h @@ -60,7 +60,8 @@ Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(int launcherCount READ launcherCount NOTIFY launcherCountChanged) - Q_PROPERTY(QStringList launcherList READ launcherList WRITE setLauncherList NOTIFY launcherListChanged) + Q_PROPERTY(QStringList serializedLauncherList READ serializedLauncherList WRITE setSerializedLauncherList NOTIFY serializedLauncherListChanged) + Q_PROPERTY(QStringList shownLauncherList READ shownLauncherList NOTIFY shownLauncherListChanged) Q_PROPERTY(bool anyTaskDemandsAttention READ anyTaskDemandsAttention NOTIFY anyTaskDemandsAttentionChanged) @@ -120,23 +121,31 @@ int launcherCount() const; /** - * The list of launcher URLs serialized to strings. + * The list of launcher URLs serialized to strings along with + * the activities they belong to. * - * @see setLauncherList + * @see setSerializedLauncherList * @returns the list of launcher URLs serialized to strings. **/ - QStringList launcherList() const; + QStringList serializedLauncherList() const; /** * Replace the list of launcher URL strings. * * Invalid or empty URLs will be rejected. Duplicate URLs will be * collapsed. * - * @see launcherList + * @see serializedLauncherList * @param launchers A list of launcher URL strings. **/ - void setLauncherList(const QStringList &launchers); + void setSerializedLauncherList(const QStringList &launchers); + + /** + * The list of currently shown launcher URLs serialized to strings. + * + * @returns the list of launcher URLs serialized to strings. + **/ + QStringList shownLauncherList() const; /** * Returns whether any task in the model currently demands attention @@ -773,7 +782,8 @@ Q_SIGNALS: void countChanged() const; void launcherCountChanged() const; - void launcherListChanged() const; + void serializedLauncherListChanged() const; + void shownLauncherListChanged() const; void anyTaskDemandsAttentionChanged() const; void virtualDesktopChanged() const; void screenGeometryChanged() const; diff --git a/libtaskmanager/tasksmodel.cpp b/libtaskmanager/tasksmodel.cpp --- a/libtaskmanager/tasksmodel.cpp +++ b/libtaskmanager/tasksmodel.cpp @@ -30,6 +30,8 @@ #include "startuptasksmodel.h" #include "windowtasksmodel.h" +#include "launchertasksmodel_p.h" + #include #include #include @@ -440,9 +442,11 @@ } launcherTasksModel = new LauncherTasksModel(q); - QObject::connect(launcherTasksModel, &LauncherTasksModel::launcherListChanged, - q, &TasksModel::launcherListChanged); - QObject::connect(launcherTasksModel, &LauncherTasksModel::launcherListChanged, + QObject::connect(launcherTasksModel, &LauncherTasksModel::serializedLauncherListChanged, + q, &TasksModel::serializedLauncherListChanged); + QObject::connect(launcherTasksModel, &LauncherTasksModel::shownLauncherListChanged, + q, &TasksModel::shownLauncherListChanged); + QObject::connect(launcherTasksModel, &LauncherTasksModel::shownLauncherListChanged, q, &TasksModel::updateLauncherCount); concatProxyModel->addSourceModel(launcherTasksModel); @@ -1100,19 +1104,28 @@ } } -QStringList TasksModel::launcherList() const +QStringList TasksModel::shownLauncherList() const { if (d->launcherTasksModel) { - return d->launcherTasksModel->launcherList(); + return d->launcherTasksModel->shownLauncherList(); } return QStringList(); } -void TasksModel::setLauncherList(const QStringList &launchers) +QStringList TasksModel::serializedLauncherList() const +{ + if (d->launcherTasksModel) { + return d->launcherTasksModel->serializedLauncherList(); + } + + return QStringList(); +} + +void TasksModel::setSerializedLauncherList(const QStringList &launchers) { d->initLauncherTasksModel(); - d->launcherTasksModel->setLauncherList(launchers); + d->launcherTasksModel->setSerializedLauncherList(launchers); d->launchersEverSet = true; } @@ -1435,11 +1448,14 @@ return; } - QMap sortedLaunchers; + QMap sortedShownLaunchers; - foreach(const QString &launcherUrlStr, launcherList()) { + foreach(const QString &launcherUrlStr, serializedLauncherList()) { int row = -1; - QUrl launcherUrl(launcherUrlStr); + QStringList activities; + QUrl launcherUrl; + + std::tie(launcherUrl, activities) = deserializeLauncher(launcherUrlStr); for (int i = 0; i < rowCount(); ++i) { const QUrl &rowLauncherUrl = index(i, 0).data(AbstractTasksModel::LauncherUrlWithoutIcon).toUrl(); @@ -1451,7 +1467,7 @@ } if (row != -1) { - sortedLaunchers.insert(row, launcherUrl); + sortedShownLaunchers.insert(row, launcherUrlStr); } } @@ -1477,7 +1493,7 @@ } } - setLauncherList(QUrl::toStringList(sortedLaunchers.values())); + setSerializedLauncherList(sortedShownLaunchers.values()); d->launcherSortingDirty = false; }