diff --git a/libtaskmanager/launchertasksmodel.h b/libtaskmanager/launchertasksmodel.h --- a/libtaskmanager/launchertasksmodel.h +++ b/libtaskmanager/launchertasksmodel.h @@ -62,7 +62,8 @@ 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 * @returns the list of launcher URLs serialized to strings. diff --git a/libtaskmanager/launchertasksmodel.cpp b/libtaskmanager/launchertasksmodel.cpp --- a/libtaskmanager/launchertasksmodel.cpp +++ b/libtaskmanager/launchertasksmodel.cpp @@ -27,22 +27,31 @@ #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 launchersOrder; + QHash activitiesForLauncher; + QHash appDataCache; QTimer sycocaChangeTimer; @@ -65,14 +74,14 @@ QObject::connect(&sycocaChangeTimer, &QTimer::timeout, q, [this]() { - if (!launchers.count()) { + if (!launchersOrder.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(launchersOrder.count() - 1, 0), QVector{Qt::DisplayRole, Qt::DecorationRole, AbstractTasksModel::AppId, AbstractTasksModel::AppName, AbstractTasksModel::GenericName, AbstractTasksModel::LauncherUrl}); @@ -116,13 +125,12 @@ QVariant LauncherTasksModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= d->launchers.count()) { + if (!index.isValid() || index.row() >= d->launchersOrder.count()) { return QVariant(); } - const QUrl &url = d->launchers.at(index.row()); + const QUrl &url = d->launchersOrder.at(index.row()); const AppData &data = d->appData(url); - if (role == Qt::DisplayRole) { return data.name; } else if (role == Qt::DecorationRole) { @@ -151,63 +159,142 @@ return true; } else if (role == IsOnAllVirtualDesktops) { return true; + } else if (role == Activities) { + return d->activitiesForLauncher[url]; } return QVariant(); } int LauncherTasksModel::rowCount(const QModelIndex &parent) const { - return parent.isValid() ? 0 : d->launchers.count(); + return parent.isValid() ? 0 : d->launchersOrder.count(); } QStringList LauncherTasksModel::launcherList() const { - return QUrl::toStringList(d->launchers); + // Serializing the launchers + QStringList result; + + for (const auto &launcher: d->launchersOrder) { + const auto &activities = d->activitiesForLauncher[launcher]; + + QString serializedLauncher; + if (activities.isEmpty()) { + serializedLauncher = launcher.toString(); + + } else { + serializedLauncher = + "[" + d->activitiesForLauncher[launcher].join(",") + "]\n" + + launcher.toString(); + } + + result << serializedLauncher; + } + + return result; } -void LauncherTasksModel::setLauncherList(const QStringList &launchers) +void LauncherTasksModel::setLauncherList(const QStringList &serializedLaunchers) { - const QList &_urls = QUrl::fromStringList(launchers, QUrl::StrictMode); - QList urls; + // Clearing everything + QList newLaunchersOrder; + QHash newActivitiesForLauncher; + + // Loading the activity to launchers map + QHash> launchersForActivitiesCandidates; + for (const auto& serializedLauncher: serializedLaunchers) { + QStringList activities; + QUrl url; - // Reject invalid urls and duplicates. - foreach(const QUrl &url, _urls) { - if (url.isValid()) { - bool dupe = false; + std::tie(url, activities) = + deserializeLauncher(serializedLauncher); - foreach(const QUrl &addedUrl, urls) { - dupe = launcherUrlsMatch(url, addedUrl, IgnoreQueryItems); - if (dupe) break; + // Is url is not valid, ignore it + if (!url.isValid()) continue; + + // If we have a null uuid, it means we are on all activities + if (activities.contains(NULL_UUID)) { + activities.clear(); + } + + // Filter invalid activities + if (!activities.isEmpty()) { + const auto allActivities = d->activities.activities(); + QStringList validActivities; + for (const auto& activity: activities) { + if (allActivities.contains(activity)) { + validActivities << activity; + } } - if (!dupe) { - urls.append(url); + if (validActivities.isEmpty()) { + // If all activities that had this launcher are + // removed, we are killing the launcher as well + continue; } + + activities = validActivities; + } + + // Is the url a duplicate? + const auto location = + std::find_if(newLaunchersOrder.begin(), newLaunchersOrder.end(), + [&url] (const QUrl &item) { + return launcherUrlsMatch(url, item, IgnoreQueryItems); + }); + + + if (location != newLaunchersOrder.end()) { + // It is a duplicate + url = *location; + + } else { + // It is not a duplicate, we need to add it + // to the list of registered launchers + newLaunchersOrder << url; + } + + + if (!newActivitiesForLauncher.contains(url)) { + // This is the first time we got this url + newActivitiesForLauncher[url] = activities; + + } else if (newActivitiesForLauncher[url].isEmpty()) { + // Do nothing, we are already on all activities + + } else { + // We are not on all activities, append the new ones + newActivitiesForLauncher[url].append(activities); + } } - if (d->launchers != urls) { + if (newActivitiesForLauncher != d->activitiesForLauncher) { // 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(); + if (newLaunchersOrder.count() == d->launchersOrder.count()) { d->appDataCache.clear(); - d->launchers = urls; + std::swap(newLaunchersOrder, d->launchersOrder); + std::swap(newActivitiesForLauncher, d->activitiesForLauncher); + + emit dataChanged( + index(0, 0), + index(d->launchersOrder.count() - 1, 0)); - emit dataChanged(index(0, 0), index(d->launchers.count() - 1, 0)); } else { beginResetModel(); - d->launchers.clear(); - d->appDataCache.clear(); + std::swap(newLaunchersOrder, d->launchersOrder); + std::swap(newActivitiesForLauncher, d->activitiesForLauncher); - d->launchers = urls; + d->appDataCache.clear(); endResetModel(); + } emit launcherListChanged(); @@ -225,15 +312,17 @@ } // 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(); + d->launchersOrder.append(url); endInsertRows(); emit launcherListChanged(); @@ -243,13 +332,17 @@ 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(); @@ -264,8 +357,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->launchersOrder.count(); ++i) { + if (launcherUrlsMatch(url, d->launchersOrder.at(i), IgnoreQueryItems)) { return i; } } @@ -281,11 +374,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->launchersOrder.count()) { return; } - const QUrl &url = d->launchers.at(index.row()); + const QUrl &url = d->launchersOrder.at(index.row()); quint32 timeStamp = 0; @@ -312,12 +405,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->launchersOrder.count() || urls.isEmpty()) { return; } - const QUrl &url = d->launchers.at(index.row()); + const QUrl &url = d->launchersOrder.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 +#include +#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 + 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()) { + activities = QStringList({ NULL_UUID }); + } + + return std::make_pair(url, activities); +} + +} // namespace TaskManager + +#endif // LAUNCHERTASKSMODEL_P_H diff --git a/libtaskmanager/taskfilterproxymodel.cpp b/libtaskmanager/taskfilterproxymodel.cpp --- a/libtaskmanager/taskfilterproxymodel.cpp +++ b/libtaskmanager/taskfilterproxymodel.cpp @@ -21,6 +21,8 @@ #include "taskfilterproxymodel.h" #include "abstracttasksmodel.h" +#include "launchertasksmodel_p.h" + namespace TaskManager { @@ -287,7 +289,7 @@ if (!activities.isNull()) { const QStringList l = activities.toStringList(); - if (!l.isEmpty() && !l.contains(d->activity)) { + if (!l.isEmpty() && !l.contains(NULL_UUID) && !l.contains(d->activity)) { return false; } } diff --git a/libtaskmanager/tasksmodel.h b/libtaskmanager/tasksmodel.h --- a/libtaskmanager/tasksmodel.h +++ b/libtaskmanager/tasksmodel.h @@ -120,7 +120,8 @@ 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 * @returns the list of launcher URLs serialized to strings. 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 @@ -1435,23 +1437,27 @@ return; } - QMap sortedLaunchers; + QMap sortedShownLaunchers; foreach(const QString &launcherUrlStr, launcherList()) { 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(); + for (int i = 0; i < d->launcherTasksModel->rowCount(); ++i) { + const QUrl &rowLauncherUrl = + d->launcherTasksModel->index(i, 0).data(AbstractTasksModel::LauncherUrlWithoutIcon).toUrl(); if (launcherUrlsMatch(launcherUrl, rowLauncherUrl, IgnoreQueryItems)) { row = i; break; } } if (row != -1) { - sortedLaunchers.insert(row, launcherUrl); + sortedShownLaunchers.insert(row, launcherUrlStr); } } @@ -1477,7 +1483,7 @@ } } - setLauncherList(QUrl::toStringList(sortedLaunchers.values())); + setLauncherList(sortedShownLaunchers.values()); d->launcherSortingDirty = false; }