Index: applets/kicker/CMakeLists.txt =================================================================== --- applets/kicker/CMakeLists.txt +++ applets/kicker/CMakeLists.txt @@ -22,9 +22,11 @@ plugin/contactentry.cpp plugin/containmentinterface.cpp plugin/draghelper.cpp - plugin/favoritesmodel.cpp + plugin/simplefavoritesmodel.cpp + plugin/kastatsfavoritesmodel.cpp plugin/fileentry.cpp plugin/forwardingmodel.cpp + plugin/placeholdermodel.cpp plugin/funnelmodel.cpp plugin/dashboardwindow.cpp plugin/kickerplugin.cpp @@ -41,6 +43,7 @@ plugin/systemsettings.cpp plugin/wheelinterceptor.cpp plugin/windowsystem.cpp + plugin/funnelmodel.cpp ) qt5_add_dbus_interface(kickerplugin_SRCS ${KRUNNERAPP_INTERFACE} krunner_interface) Index: applets/kicker/package/contents/code/tools.js =================================================================== --- applets/kicker/package/contents/code/tools.js +++ applets/kicker/package/contents/code/tools.js @@ -22,42 +22,128 @@ // Accessing actionList can be a costly operation, so we don't // access it until we need the menu. - var action = createFavoriteAction(favoriteModel, favoriteId); + var actions = createFavoriteActions(favoriteModel, favoriteId); - if (action) { + if (actions) { if (actionList && actionList.length > 0) { var separator = { "type": "separator" }; - actionList.unshift(action, separator); + actionList.unshift(separator); + // actionList = actions.concat(actionList); // this crashes Qt O.o + actionList.unshift.apply(actionList, actions); } else { - actionList = [action]; + actionList = actions; } } actionMenu.actionList = actionList; } -function createFavoriteAction(favoriteModel, favoriteId) { +function createFavoriteActions(favoriteModel, favoriteId) { if (favoriteModel === null || !favoriteModel.enabled || favoriteId == null) { return null; } - var action = {}; + var activities = favoriteModel.activities.runningActivities; + + if (activities.length <= 1) { + var action = {}; + + if (favoriteModel.isFavorite(favoriteId)) { + action.text = i18n("Remove from Favorites"); + action.icon = "list-remove"; + action.actionId = "_kicker_favorite_remove"; + } else if (favoriteModel.maxFavorites == -1 || favoriteModel.count < favoriteModel.maxFavorites) { + action.text = i18n("Add to Favorites"); + action.icon = "bookmark-new"; + action.actionId = "_kicker_favorite_add"; + } else { + return null; + } + + action.actionArgument = { favoriteModel: favoriteModel, favoriteId: favoriteId }; + + return [action]; - if (favoriteModel.isFavorite(favoriteId)) { - action.text = i18n("Remove from Favorites"); - action.icon = "list-remove"; - action.actionId = "_kicker_favorite_remove"; - } else if (favoriteModel.maxFavorites == -1 || favoriteModel.count < favoriteModel.maxFavorites) { - action.text = i18n("Add to Favorites"); - action.icon = "bookmark-new"; - action.actionId = "_kicker_favorite_add"; } else { - return null; - } + var actions = []; + + var linkedActivities = favoriteModel.linkedActivitiesFor(favoriteId); + + // Adding the item to link/unlink to all activities + + var linkedToAllActivities = + !(linkedActivities.indexOf(":global") === -1); + + actions.push({ + text : i18n("On All Activities"), + checkable : true, + + actionId : linkedToAllActivities ? + "_kicker_favorite_remove_from_activity" : + "_kicker_favorite_set_to_activity", + checked : linkedToAllActivities, + + actionArgument : { + favoriteModel: favoriteModel, + favoriteId: favoriteId, + favoriteActivity: "" + } + }); + + + // Adding items for each activity separately + + var addActivityItem = function(activityId, activityName) { + var linkedToThisActivity = + !(linkedActivities.indexOf(activityId) === -1); - action.actionArgument = { favoriteModel: favoriteModel, favoriteId: favoriteId }; + actions.push({ + text : activityName, + checkable : true, + checked : linkedToThisActivity && !linkedToAllActivities, - return action; + actionId : + // If we are on all activities, and the user clicks just one + // specific activity, unlink from everything else + linkedToAllActivities ? "_kicker_favorite_set_to_activity" : + + // If we are linked to the current activity, just unlink from + // that single one + linkedToThisActivity ? "_kicker_favorite_remove_from_activity" : + + // Otherwise, link to this activity, but do not unlink from + // other ones + "_kicker_favorite_add_to_activity", + + actionArgument : { + favoriteModel : favoriteModel, + favoriteId : favoriteId, + favoriteActivity : activityId + } + }); + }; + + // Adding the item to link/unlink to the current activity + + addActivityItem(favoriteModel.activities.currentActivity, i18n("On The Current Activity")); + + actions.push({ + type: "separator", + actionId: "_kicker_favorite_separator" + }); + + // Adding the items for each activity + + activities.forEach(function(activityId) { + addActivityItem(activityId, favoriteModel.activityNameForId(activityId)); + }); + + return [{ + text : i18n("Show In Favorites"), + icon : "favorite", + subActions : actions + }]; + } } function triggerAction(model, index, actionId, actionArgument) { @@ -88,9 +174,27 @@ if (favoriteModel === null || favoriteId == null) { return null; } + if (actionId == "_kicker_favorite_remove") { - favoriteModel.removeFavorite(favoriteId); + console.log("Removing from all activities"); + favoriteModel.removeFavoriteFrom(favoriteId, ":any"); + } else if (actionId == "_kicker_favorite_add") { - favoriteModel.addFavorite(favoriteId); + console.log("Adding to global activity"); + favoriteModel.addFavoriteTo(favoriteId, ":global"); + + } else if (actionId == "_kicker_favorite_remove_from_activity") { + console.log("Removing from a specific activity"); + favoriteModel.removeFavoriteFrom(favoriteId, actionArgument.favoriteActivity); + + } else if (actionId == "_kicker_favorite_add_to_activity") { + console.log("Adding to another activity"); + favoriteModel.addFavoriteTo(favoriteId, actionArgument.favoriteActivity); + + } else if (actionId == "_kicker_favorite_set_to_activity") { + console.log("Removing the item from the favourites, and re-adding it just to be on a specific activity"); + favoriteModel.removeFavoriteFrom(favoriteId, ":any"); + favoriteModel.addFavoriteTo(favoriteId, actionArgument.favoriteActivity); + } } Index: applets/kicker/package/contents/config/main.xml =================================================================== --- applets/kicker/package/contents/config/main.xml +++ applets/kicker/package/contents/config/main.xml @@ -44,6 +44,10 @@ logout,reboot,shutdown + + + false + Index: applets/kicker/package/contents/ui/ActionMenu.qml =================================================================== --- applets/kicker/package/contents/ui/ActionMenu.qml +++ applets/kicker/package/contents/ui/ActionMenu.qml @@ -64,11 +64,28 @@ menu = contextMenuComponent.createObject(root); - actionList.forEach(function(actionItem) { - var item = contextMenuItemComponent.createObject(menu, { - "actionItem": actionItem, - }); + fillMenu(menu, actionList); + } + + function fillMenu(menu, items) { + items.forEach(function(actionItem) { + if (actionItem.subActions) { + // This is a menu + var submenuItem = contextSubmenuItemComponent.createObject( + menu, { "actionItem" : actionItem }); + + fillMenu(submenuItem.submenu, actionItem.subActions); + + } else { + var item = contextMenuItemComponent.createObject( + menu, + { + "actionItem": actionItem, + } + ); + } }); + } Component { @@ -80,17 +97,39 @@ } Component { - id: contextMenuItemComponent + id: contextSubmenuItemComponent PlasmaComponents.MenuItem { + id: submenuItem + property variant actionItem text: actionItem.text ? actionItem.text : "" - enabled: actionItem.type != "title" && ("enabled" in actionItem ? actionItem.enabled : true) - separator: actionItem.type == "separator" - section: actionItem.type == "title" icon: actionItem.icon ? actionItem.icon : null + property variant submenu : submenu_ + + PlasmaComponents.ContextMenu { + id: submenu_ + visualParent: submenuItem.action + } + } + } + + Component { + id: contextMenuItemComponent + + PlasmaComponents.MenuItem { + property variant actionItem + + text : actionItem.text ? actionItem.text : "" + enabled : actionItem.type != "title" && ("enabled" in actionItem ? actionItem.enabled : true) + separator : actionItem.type == "separator" + section : actionItem.type == "title" + icon : actionItem.icon ? actionItem.icon : null + checkable : actionItem.checkable ? actionItem.checkable : false + checked : actionItem.checked ? actionItem.checked : false + onClicked: { actionClicked(actionItem.actionId, actionItem.actionArgument); } Index: applets/kicker/package/contents/ui/main.qml =================================================================== --- applets/kicker/package/contents/ui/main.qml +++ applets/kicker/package/contents/ui/main.qml @@ -109,7 +109,13 @@ } Component.onCompleted: { - favoritesModel.favorites = plasmoid.configuration.favoriteApps; + favoritesModel.initForClient("org.kde.plasma.kicker.favorites.instance-" + plasmoid.id) + + if (!plasmoid.configuration.favoritesPortedToKAstats) { + favoritesModel.addFavoritesTo(plasmoid.configuration.favoriteApps, ":global"); + plasmoid.configuration.favoritesPortedToKAstats = true; + } + rootModel.refresh(); } } Index: applets/kicker/plugin/abstractentry.cpp =================================================================== --- applets/kicker/plugin/abstractentry.cpp +++ applets/kicker/plugin/abstractentry.cpp @@ -19,6 +19,8 @@ #include "abstractentry.h" +#include + AbstractEntry::AbstractEntry(AbstractModel *owner) : m_owner(owner) { Index: applets/kicker/plugin/computermodel.h =================================================================== --- applets/kicker/plugin/computermodel.h +++ applets/kicker/plugin/computermodel.h @@ -26,7 +26,7 @@ #include #include -class FavoritesModel; +class SimpleFavoritesModel; class KConcatenateRowsProxyModel; class KFilePlacesModel; @@ -110,7 +110,7 @@ private: KConcatenateRowsProxyModel *m_concatProxy; RunCommandModel *m_runCommandModel; - FavoritesModel *m_systemAppsModel; + SimpleFavoritesModel *m_systemAppsModel; FilteredPlacesModel *m_filteredPlacesModel; AppEntry::NameFormat m_appNameFormat; QObject *m_appletInterface; Index: applets/kicker/plugin/computermodel.h.orig =================================================================== --- /dev/null +++ applets/kicker/plugin/computermodel.h.orig @@ -0,0 +1,119 @@ +/*************************************************************************** + * Copyright (C) 2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * 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 . * + ***************************************************************************/ + +#ifndef COMPUTERMODEL_H +#define COMPUTERMODEL_H + +#include "forwardingmodel.h" +#include "appentry.h" + +#include +#include + +class FavoritesModel; + +class KConcatenateRowsProxyModel; +class KFilePlacesModel; + +namespace Solid { + class Device; +} + +class FilteredPlacesModel : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + FilteredPlacesModel(QObject *parent = 0); + ~FilteredPlacesModel(); + + QUrl url(const QModelIndex &index) const; + bool isDevice(const QModelIndex &index) const; + Solid::Device deviceForIndex(const QModelIndex &index) const; + + protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_OVERRIDE; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE; + + private: + KFilePlacesModel *m_placesModel; +}; + +class RunCommandModel : public AbstractModel +{ + Q_OBJECT + + public: + RunCommandModel(QObject *parent = 0); + ~RunCommandModel(); + + QString description() const Q_DECL_OVERRIDE; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + + int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) Q_DECL_OVERRIDE; +}; + +class ComputerModel : public ForwardingModel +{ + Q_OBJECT + + Q_PROPERTY(int appNameFormat READ appNameFormat WRITE setAppNameFormat NOTIFY appNameFormatChanged) + Q_PROPERTY(QObject* appletInterface READ appletInterface WRITE setAppletInterface NOTIFY appletInterfaceChanged) + Q_PROPERTY(QStringList systemApplications READ systemApplications WRITE setSystemApplications NOTIFY systemApplicationsChanged) + + public: + explicit ComputerModel(QObject *parent = 0); + ~ComputerModel(); + + QString description() const Q_DECL_OVERRIDE; + + int appNameFormat() const; + void setAppNameFormat(int format); + + QObject *appletInterface() const; + void setAppletInterface(QObject *appletInterface); + + QStringList systemApplications() const; + void setSystemApplications(const QStringList &apps); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) Q_DECL_OVERRIDE; + + Q_SIGNALS: + void appNameFormatChanged() const; + void appletInterfaceChanged() const; + void systemApplicationsChanged() const; + + private Q_SLOTS: + void onSetupDone(Solid::ErrorType error, QVariant errorData, const QString &udi); + + private: + KConcatenateRowsProxyModel *m_concatProxy; + RunCommandModel *m_runCommandModel; + FavoritesModel *m_systemAppsModel; + FilteredPlacesModel *m_filteredPlacesModel; + AppEntry::NameFormat m_appNameFormat; + QObject *m_appletInterface; +}; + +#endif Index: applets/kicker/plugin/computermodel.cpp =================================================================== --- applets/kicker/plugin/computermodel.cpp +++ applets/kicker/plugin/computermodel.cpp @@ -20,7 +20,7 @@ #include "computermodel.h" #include "actionlist.h" -#include "favoritesmodel.h" +#include "simplefavoritesmodel.h" #include @@ -137,12 +137,13 @@ ComputerModel::ComputerModel(QObject *parent) : ForwardingModel(parent) , m_concatProxy(new KConcatenateRowsProxyModel(this)) , m_runCommandModel(new RunCommandModel(this)) -, m_systemAppsModel(new FavoritesModel(this)) +, m_systemAppsModel(new SimpleFavoritesModel(this)) , m_filteredPlacesModel(new FilteredPlacesModel(this)) , m_appNameFormat(AppEntry::NameOnly) , m_appletInterface(nullptr) { - connect(m_systemAppsModel, &FavoritesModel::favoritesChanged, this, &ComputerModel::systemApplicationsChanged); + connect(m_systemAppsModel, &SimpleFavoritesModel::favoritesChanged, this, &ComputerModel::systemApplicationsChanged); + m_systemAppsModel->setFavorites(QStringList() << "systemsettings.desktop"); m_concatProxy->addSourceModel(m_runCommandModel); m_concatProxy->addSourceModel(m_systemAppsModel); Index: applets/kicker/plugin/kastatsfavoritesmodel.h =================================================================== --- /dev/null +++ applets/kicker/plugin/kastatsfavoritesmodel.h @@ -0,0 +1,116 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * Copyright (C) 2016-2017 by Ivan Cukic * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * 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 . * + ***************************************************************************/ + +#ifndef FAVORITESMODEL_H +#define FAVORITESMODEL_H + +#include "placeholdermodel.h" + +#include + +#include +#include + +class PlaceholderModel; + +namespace KActivities { + class Consumer; +namespace Stats { + class ResultModel; +namespace Terms { + class Activity; +} // namespace Terms +} // namespace Stats +} // namespace KActivities + +class KAStatsFavoritesModel : public PlaceholderModel +{ + Q_OBJECT + + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(QStringList favorites READ favorites WRITE setFavorites NOTIFY favoritesChanged) + Q_PROPERTY(int maxFavorites READ maxFavorites WRITE setMaxFavorites NOTIFY maxFavoritesChanged) + + Q_PROPERTY(QObject* activities READ activities CONSTANT) + + public: + explicit KAStatsFavoritesModel(QObject *parent = 0); + ~KAStatsFavoritesModel(); + + QString description() const; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument); + + bool enabled() const; + void setEnabled(bool enable); + + QStringList favorites() const; + void setFavorites(const QStringList &favorites); + + int maxFavorites() const; + void setMaxFavorites(int max); + + Q_INVOKABLE bool isFavorite(const QString &id) const; + + Q_INVOKABLE void addFavorite(const QString &id, int index = -1); + Q_INVOKABLE void removeFavorite(const QString &id); + + Q_INVOKABLE void addFavoriteTo(const QString &id, const QString &activityId, int index = -1); + Q_INVOKABLE void removeFavoriteFrom(const QString &id, const QString &activityId); + + Q_INVOKABLE void addFavorites(const QStringList &ids, int index = -1); + Q_INVOKABLE void addFavoritesTo(const QStringList &ids, const QString &activityId, int index = -1); + + Q_INVOKABLE QStringList linkedActivitiesFor(const QString &id) const; + + Q_INVOKABLE void moveRow(int from, int to); + + Q_INVOKABLE void initForClient(const QString &client); + + QObject *activities() const; + Q_INVOKABLE QString activityNameForId(const QString &activityId) const; + + AbstractModel* favoritesModel(); + + public Q_SLOTS: + virtual void refresh(); + + Q_SIGNALS: + void enabledChanged() const; + void favoritesChanged() const; + void maxFavoritesChanged() const; + + private: + class Private; + Private * d; + + AbstractEntry *favoriteFromId(const QString &id) const; + + void addFavoriteTo(const QString &id, const KActivities::Stats::Terms::Activity &activityId, int index = -1); + void removeFavoriteFrom(const QString &id, const KActivities::Stats::Terms::Activity &activityId); + + bool m_enabled; + + int m_maxFavorites; + + KActivities::Consumer *m_activities; +}; + +#endif Index: applets/kicker/plugin/kastatsfavoritesmodel.cpp =================================================================== --- /dev/null +++ applets/kicker/plugin/kastatsfavoritesmodel.cpp @@ -0,0 +1,483 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * Copyright (C) 2016-2017 by Ivan Cukic * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * 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 "kastatsfavoritesmodel.h" +#include "appentry.h" +#include "contactentry.h" +#include "fileentry.h" +#include "actionlist.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace KAStats = KActivities::Stats; + +using namespace KAStats; +using namespace KAStats::Terms; + +QString agentForPath(const QString &url) +{ + return url.startsWith("ktp:") + ? "org.kde.plasma.favorites.contacts" + : (url.startsWith("file:") && !url.endsWith(".desktop")) + ? "org.kde.plasma.favorites.documents" + : (url.startsWith("/") && !url.endsWith(".desktop")) + ? "org.kde.plasma.favorites.documents" + // use applications as the default + : "org.kde.plasma.favorites.applications"; +} + +class KAStatsFavoritesModel::Private: public ResultModel { +public: + Private(const KAStats::Query &query, const QString &clientId, KAStatsFavoritesModel *parent) + : ResultModel(query, clientId, parent) + , q(parent) + { + } + + QString pathForId(const QString &id) const + { + if (id.isEmpty()) return QString(); + + const auto entry = favoriteFromId(id); + + const auto url = entry && entry->isValid() ? entry->url() + : QUrl(id); + + // We want to resolve symbolic links not to have two paths + // refer to the same .desktop file + if (url.isLocalFile()) { + QFileInfo file(url.toLocalFile()); + + if (file.exists()) { + return file.canonicalFilePath(); + } + } + + if (url.scheme() == "file") { + return QString(); + } + + return url.toString(); + } + + AbstractEntry *favoriteFromId(const QString &id) const + { + // do not use pathForId in this function + int pathForId; Q_UNUSED(pathForId); + + // IDs can be plain appname.desktop + // Also, it can be a file:///... URL + // Or a file path + // Or it is an URL which is not a file + if (!m_entries.contains(id)) { + AbstractEntry *entry = nullptr; + + const auto agent = agentForPath(id); + + if (agent == "org.kde.plasma.favorites.contacts") { + entry = new ContactEntry(q, id); + } else if (agent == "org.kde.plasma.favorites.documents") { + entry = new FileEntry(q, QUrl(id)); + } else { + entry = new AppEntry(q, id); + } + + if (!entry->isValid()) return nullptr; + + const QUrl url = entry->url(); + + // Registering the entry for the ID we got + m_entries[id] = entry; + + // Registering the entry for the ID it thinks it has + m_entries[entry->id()] = entry; + + // Registering the entry for the URL it thinks it has + m_entries[url.toString()] = entry; + + // And if it is a local file, registering it for the path + if (url.isLocalFile()) { + m_entries[entry->url().toLocalFile()] = entry; + } + } + + return m_entries[id]; + } + + void removeOldCachedEntries() const + { + QSet knownIds; + for (int row = 0; row < rowCount(); ++row) { + const auto id = data(index(row, 0), Kicker::UrlRole).toString(); + // const auto id = data(index(row, 0), ResultModel::ResourceRole).toString(); + Q_ASSERT(!id.isEmpty()); + + const auto favorite = favoriteFromId(id); + + knownIds << id + << favorite->id() + << favorite->url().toString() + << favorite->url().toLocalFile(); + } + + QSet entriesToDelete; + + QHashIterator i(m_entries); + while (i.hasNext()) { + i.next(); + + if (!knownIds.contains(i.key())) { + entriesToDelete << i.value(); + } + } + + QMutableHashIterator mi(m_entries); + while (mi.hasNext()) { + mi.next(); + + if (entriesToDelete.contains(mi.value())) { + mi.remove(); + } + } + + qDeleteAll(entriesToDelete); + } + + QModelIndex cache_lastIndex; + AbstractEntry* cache_lastEntry = nullptr; + + QVariant data(const QModelIndex &index, int role) const override + { + auto entry = cache_lastEntry; + + if (cache_lastIndex != index) { + if (!index.isValid() || index.row() >= rowCount()) { + return QVariant(); + } + + const auto _this = const_cast(this); + const auto id = ResultModel::data(index, ResultModel::ResourceRole).toString(); + + if (id.isEmpty()) return QVariant(); + + entry = _this->favoriteFromId(id); + + if (!entry || !entry->isValid()) { + // If the result is not valid, we need to unlink it -- to + // remove it from the model + const auto path = pathForId(id); + + if (!path.isEmpty() && !m_invalidPaths.contains(path)) { + _this->unlinkFromActivity(QUrl(path), Activity::any(), + Agent(agentForPath(path))); + m_invalidPaths << path; + } + + return role == Qt::DecorationRole ? "unknown" + : QVariant(); + } + + _this->cache_lastIndex = index; + _this->cache_lastEntry = entry; + } + + return entry == nullptr ? QVariant() + : role == Qt::DisplayRole ? entry->name() + : role == Qt::DecorationRole ? entry->icon() + : role == Kicker::DescriptionRole ? entry->description() + : role == Kicker::FavoriteIdRole ? entry->id() + : role == Kicker::UrlRole ? entry->url() + : role == Kicker::HasActionListRole ? entry->hasActions() + : role == Kicker::ActionListRole ? entry->actions() + : QVariant(); + } + + bool trigger(int row, const QString &actionId, const QVariant &argument) + { + if (row < 0 || row >= rowCount()) { + return false; + } + + const QString id = data(index(row, 0), Kicker::UrlRole).toString(); + + return m_entries.contains(id) + ? m_entries[id]->run(actionId, argument) + : false; + } + + KAStatsFavoritesModel *const q; + mutable QList m_invalidPaths; + + // This can contain an entry multiple times - for the id, file and url + mutable QHash m_entries; +}; + + + + + +KAStatsFavoritesModel::KAStatsFavoritesModel(QObject *parent) +: PlaceholderModel(parent) +, d(new Private( + LinkedResources + | Agent { + "org.kde.plasma.favorites.applications", + "org.kde.plasma.favorites.documents", + "org.kde.plasma.favorites.contacts" + } + | Type::any() + | Activity::current() + | Activity::global() + | Limit(15), + QString(), + this) + ) +, m_enabled(true) +, m_maxFavorites(-1) +, m_activities(new KActivities::Consumer(this)) +{ + QModelIndex index; + + if (d->canFetchMore(index)) { + d->fetchMore(index); + } + + setSourceModel(d); +} + +KAStatsFavoritesModel::~KAStatsFavoritesModel() +{ +} + +void KAStatsFavoritesModel::initForClient(const QString &clientId) +{ + setSourceModel(nullptr); + delete d; + d = new Private( + LinkedResources + | Agent { + "org.kde.plasma.favorites.applications", + "org.kde.plasma.favorites.documents", + "org.kde.plasma.favorites.contacts" + } + | Type::any() + | Activity::current() + | Activity::global() + | Limit(15), + clientId, + this); + setSourceModel(d); +} + +QString KAStatsFavoritesModel::description() const +{ + return i18n("Favorites"); +} + +bool KAStatsFavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + // Do not allow triggering an item while another one is being dropped + if (m_dropPlaceholderIndex != -1) return false; + + return d->trigger(row, actionId, argument); +} + +bool KAStatsFavoritesModel::enabled() const +{ + return m_enabled; +} + +int KAStatsFavoritesModel::maxFavorites() const +{ + return m_maxFavorites; +} + +void KAStatsFavoritesModel::setMaxFavorites(int max) +{ + Q_UNUSED(max); +} + +void KAStatsFavoritesModel::setEnabled(bool enable) +{ + if (m_enabled != enable) { + m_enabled = enable; + + emit enabledChanged(); + } +} + +QStringList KAStatsFavoritesModel::favorites() const +{ + qWarning() << "KAStatsFavoritesModel::favorites returns nothing, it is here just to keep API backwards-compatible"; + return QStringList(); +} + +void KAStatsFavoritesModel::setFavorites(const QStringList& favorites) +{ + Q_UNUSED(favorites); + qWarning() << "KAStatsFavoritesModel::setFavorites is ignorred"; +} + +bool KAStatsFavoritesModel::isFavorite(const QString &id) const +{ + d->removeOldCachedEntries(); + return d->m_entries.contains(id); +} + +void KAStatsFavoritesModel::addFavorites(const QStringList &ids, int index) +{ + qDebug() << "Adding the favourites: " << ids; + std::for_each(ids.rbegin(), ids.rend(), [&] (const QString &id) { + addFavorite(id, index); + }); +} + +void KAStatsFavoritesModel::addFavoritesTo(const QStringList &ids, const QString &activityId, int index) +{ + qDebug() << "Adding the favourites: " << ids << "to" << activityId; + std::for_each(ids.rbegin(), ids.rend(), [&] (const QString &id) { + addFavoriteTo(id, activityId, index); + }); +} + +void KAStatsFavoritesModel::addFavorite(const QString &id, int index) +{ + addFavoriteTo(id, Activity::current(), index); +} + +void KAStatsFavoritesModel::removeFavorite(const QString &id) +{ + removeFavoriteFrom(id, Activity::current()); +} + +void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const QString &activityId, int index) +{ + addFavoriteTo(id, Activity(activityId), index); +} + +void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const QString &activityId) +{ + removeFavoriteFrom(id, Activity(activityId)); +} + +void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const Activity &activity, int index) +{ + if (id.isEmpty()) return; + + setDropPlaceholderIndex(-1); + + const auto path = d->pathForId(id); + + if (path.isEmpty()) return; + + // This is a file, we want to check that it exists + // if (url.isLocalFile() && !QFileInfo::exists(url.toLocalFile())) return; + + d->linkToActivity(QUrl(path), activity, + Agent(agentForPath(path))); + + if (index != -1) { + d->setResultPosition(path, index); + } +} + +void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const Activity &activity) +{ + const auto path = d->pathForId(id); + + if (path.isEmpty()) return; + + d->unlinkFromActivity( + QUrl(path), activity, + Agent(agentForPath(path)) + ); +} + +void KAStatsFavoritesModel::moveRow(int from, int to) +{ + const auto id = data(index(from, 0), Kicker::UrlRole).toString(); + const auto path = d->pathForId(id); + + if (path.isEmpty()) return; + + d->setResultPosition(path, to); +} + +AbstractModel *KAStatsFavoritesModel::favoritesModel() +{ + return this; +} + +void KAStatsFavoritesModel::refresh() +{ +} + +QObject *KAStatsFavoritesModel::activities() const +{ + return m_activities; +} + +QString KAStatsFavoritesModel::activityNameForId(const QString &activityId) const +{ + // It is safe to use a short-lived object here, + // we are always synced with KAMD in plasma + KActivities::Info info(activityId); + return info.name(); +} + +QStringList KAStatsFavoritesModel::linkedActivitiesFor(const QString &id) const +{ + const auto path = d->pathForId(id); + + if (path.isEmpty()) { + return {}; + } + + auto query = LinkedResources + | Agent { + "org.kde.plasma.favorites.applications", + "org.kde.plasma.favorites.documents", + "org.kde.plasma.favorites.contacts" + } + | Type::any() + | Activity::any() + | Url(path); + + ResultSet results(query); + + for (const auto &result: results) { + return result.linkedActivities(); + } + + return {}; +} + Index: applets/kicker/plugin/kickerplugin.cpp =================================================================== --- applets/kicker/plugin/kickerplugin.cpp +++ applets/kicker/plugin/kickerplugin.cpp @@ -23,7 +23,8 @@ #include "computermodel.h" #include "containmentinterface.h" #include "draghelper.h" -#include "favoritesmodel.h" +#include "simplefavoritesmodel.h" +#include "kastatsfavoritesmodel.h" #include "dashboardwindow.h" #include "funnelmodel.h" #include "processrunner.h" @@ -48,7 +49,8 @@ qmlRegisterType(uri, 0, 1, "ComputerModel"); qmlRegisterType(uri, 0, 1, "ContainmentInterface"); qmlRegisterType(uri, 0, 1, "DragHelper"); - qmlRegisterType(uri, 0, 1, "FavoritesModel"); + qmlRegisterType(uri, 0, 1, "FavoritesModel"); + qmlRegisterType(uri, 0, 1, "KAStatsFavoritesModel"); qmlRegisterType(uri, 0, 1, "DashboardWindow"); qmlRegisterType(uri, 0, 1, "FunnelModel"); qmlRegisterType(uri, 0, 1, "ProcessRunner"); Index: applets/kicker/plugin/placeholdermodel.h =================================================================== --- /dev/null +++ applets/kicker/plugin/placeholdermodel.h @@ -0,0 +1,88 @@ +/*************************************************************************** + * Copyright (C) 2015 by Eike Hein * + * Copyright (C) 2017 by Ivan Cukic * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * 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 . * + ***************************************************************************/ + +#ifndef PLACEHOLDERMODEL_H +#define PLACEHOLDERMODEL_H + +#include "abstractmodel.h" + +#include + +class PlaceholderModel : public AbstractModel +{ + Q_OBJECT + + Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged); + Q_PROPERTY(int dropPlaceholderIndex READ dropPlaceholderIndex WRITE setDropPlaceholderIndex NOTIFY dropPlaceholderIndexChanged) + + public: + explicit PlaceholderModel(QObject *parent = 0); + ~PlaceholderModel(); + + QString description() const; + + QAbstractItemModel *sourceModel() const; + virtual void setSourceModel(QAbstractItemModel *sourceModel); + + bool canFetchMore(const QModelIndex &parent) const; + void fetchMore(const QModelIndex &parent); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument); + + Q_INVOKABLE QString labelForRow(int row); + + Q_INVOKABLE AbstractModel *modelForRow(int row); + + AbstractModel* favoritesModel(); + + int separatorCount() const; + + int dropPlaceholderIndex() const; + void setDropPlaceholderIndex(int index); + + public Q_SLOTS: + void reset(); + + Q_SIGNALS: + void sourceModelChanged() const; + void dropPlaceholderIndexChanged(); + + protected: + QModelIndex indexToSourceIndex(const QModelIndex &index) const; + QModelIndex sourceIndexToIndex(const QModelIndex &index) const; + int sourceRowToRow(int sourceRow) const; + int rowToSourceRow(int row) const; + + void connectSignals(); + void disconnectSignals(); + + QPointer m_sourceModel; + + int m_dropPlaceholderIndex; +}; + +#endif Index: applets/kicker/plugin/placeholdermodel.cpp =================================================================== --- /dev/null +++ applets/kicker/plugin/placeholdermodel.cpp @@ -0,0 +1,386 @@ +/*************************************************************************** + * Copyright (C) 2015 by Eike Hein * + * Copyright (C) 2017 by Ivan Cukic * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * 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 "placeholdermodel.h" +#include "actionlist.h" + +#include + +PlaceholderModel::PlaceholderModel(QObject *parent) + : AbstractModel(parent) + , m_dropPlaceholderIndex(-1) +{ +} + +PlaceholderModel::~PlaceholderModel() +{ +} + +QString PlaceholderModel::description() const +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->description(); + + } else { + return QString(); + } +} + +QAbstractItemModel *PlaceholderModel::sourceModel() const +{ + return m_sourceModel; +} + +void PlaceholderModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + disconnectSignals(); + + beginResetModel(); + + m_sourceModel = sourceModel; + + connectSignals(); + + endResetModel(); + + emit countChanged(); + emit sourceModelChanged(); + emit descriptionChanged(); +} + +bool PlaceholderModel::canFetchMore(const QModelIndex &parent) const +{ + return m_sourceModel && m_sourceModel->canFetchMore(indexToSourceIndex(parent)); +} + +void PlaceholderModel::fetchMore(const QModelIndex &parent) +{ + if (m_sourceModel) { + m_sourceModel->fetchMore(indexToSourceIndex(parent)); + } +} + +QModelIndex PlaceholderModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return m_sourceModel ? createIndex(row, column) + : QModelIndex(); +} + +QModelIndex PlaceholderModel::parent(const QModelIndex &index) const +{ + Q_UNUSED(index) + + return QModelIndex(); +} + +QVariant PlaceholderModel::data(const QModelIndex &index, int role) const +{ + const auto row = index.row(); + + if (m_dropPlaceholderIndex == row) { + switch (role) { + case Kicker::IsDropPlaceholderRole: + return true; + + // TODO: Maybe it would be nice to show something here? + // case Qt::DisplayRole: + // return "placeholder"; + // + // case Qt::DecorationRole: + // return "select"; + + default: + return QVariant(); + + } + } + + return m_sourceModel ? m_sourceModel->data(indexToSourceIndex(index), role) + : QVariant(); +} + +int PlaceholderModel::rowCount(const QModelIndex &parent) const +{ + if (!m_sourceModel || parent.isValid()) { + return 0; + } + + return m_sourceModel->rowCount() + + (m_dropPlaceholderIndex != -1 ? 1 : 0); +} + +QModelIndex PlaceholderModel::indexToSourceIndex(const QModelIndex& index) const +{ + if (!m_sourceModel || !index.isValid()) { + return QModelIndex(); + } + + const auto row = index.row(); + const auto column = index.column(); + + return index.parent().isValid() ? + // We do not support tree models + QModelIndex() : + + // If we are on top-level, lets add a placeholder + m_sourceModel->index( + row - (m_dropPlaceholderIndex != -1 && row > m_dropPlaceholderIndex ? 1 : 0), + column, + QModelIndex() + ); +} + +int PlaceholderModel::sourceRowToRow(int sourceRow) const +{ + return sourceRow + + (m_dropPlaceholderIndex != -1 && sourceRow >= m_dropPlaceholderIndex ? 1 : 0); +} + +int PlaceholderModel::rowToSourceRow(int row) const +{ + return row == m_dropPlaceholderIndex ? -1 : + row - (m_dropPlaceholderIndex != -1 && row > m_dropPlaceholderIndex ? 1 : 0); +} + +QModelIndex PlaceholderModel::sourceIndexToIndex(const QModelIndex& sourceIndex) const +{ + if (!m_sourceModel || !sourceIndex.isValid()) { + return QModelIndex(); + } + + const auto sourceRow = sourceIndex.row(); + const auto sourceColumn = sourceIndex.column(); + + return sourceIndex.parent().isValid() ? + // We do not support tree-models + QModelIndex() : + + // If we are on top-level, lets add a placeholder + index( + sourceRowToRow(sourceRow), + sourceColumn, + QModelIndex() + ); +} + +bool PlaceholderModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->trigger(rowToSourceRow(row), actionId, argument); + + } else { + return false; + } +} + +QString PlaceholderModel::labelForRow(int row) +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->labelForRow(rowToSourceRow(row)); + + } else { + return QString(); + } + +} + +AbstractModel* PlaceholderModel::modelForRow(int row) +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->modelForRow(rowToSourceRow(row)); + + } else { + return 0; + } +} + +AbstractModel* PlaceholderModel::favoritesModel() +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->favoritesModel(); + + } else { + return AbstractModel::favoritesModel(); + } +} + +int PlaceholderModel::separatorCount() const +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->separatorCount(); + + } else { + return 0; + } +} + +void PlaceholderModel::reset() +{ + emit beginResetModel(); + emit endResetModel(); + emit countChanged(); + emit separatorCountChanged(); +} + +void PlaceholderModel::connectSignals() +{ + if (!m_sourceModel) { + return; + } + + const auto sourceModelPtr = m_sourceModel.data(); + + connect(sourceModelPtr, SIGNAL(destroyed()), this, SLOT(reset())); + + connect(sourceModelPtr, &QAbstractItemModel::dataChanged, + this, [this] (const QModelIndex &from, const QModelIndex &to, const QVector &roles) { + emit dataChanged(sourceIndexToIndex(from), + sourceIndexToIndex(to), + roles); + }); + + connect(sourceModelPtr, &QAbstractItemModel::rowsAboutToBeInserted, + this, [this] (const QModelIndex &parent, int from, int to) { + if (parent.isValid()) { + qWarning() << "We do not support tree models"; + + } else { + beginInsertRows(QModelIndex(), + sourceRowToRow(from), + sourceRowToRow(to)); + + } + }); + + connect(sourceModelPtr, &QAbstractItemModel::rowsInserted, + this, [this] { + endInsertRows(); + emit countChanged(); + }); + + + connect(sourceModelPtr, &QAbstractItemModel::rowsAboutToBeMoved, + this, [this] (const QModelIndex &source, int from, int to, const QModelIndex &dest, int destRow) { + if (source.isValid() || dest.isValid()) { + qWarning() << "We do not support tree models"; + + } else { + beginMoveRows(QModelIndex(), + sourceRowToRow(from), + sourceRowToRow(to), + QModelIndex(), + sourceRowToRow(destRow)); + } + }); + + connect(sourceModelPtr, &QAbstractItemModel::rowsMoved, + this, [this] { + endMoveRows(); + }); + + + connect(sourceModelPtr, &QAbstractItemModel::rowsAboutToBeRemoved, + this, [this] (const QModelIndex &parent, int from, int to) { + if (parent.isValid()) { + qWarning() << "We do not support tree models"; + + } else { + beginRemoveRows(QModelIndex(), + sourceRowToRow(from), + sourceRowToRow(to)); + } + }); + + connect(sourceModelPtr, &QAbstractItemModel::rowsRemoved, + this, [this] { + endRemoveRows(); + emit countChanged(); + }); + + + connect(sourceModelPtr, &QAbstractItemModel::modelAboutToBeReset, + this, [this] { + beginResetModel(); + }); + + connect(sourceModelPtr, &QAbstractItemModel::modelReset, + this, [this] { + endResetModel(); + emit countChanged(); + }); + + // We do not have persistant indices + // connect(sourceModelPtr, &QAbstractItemModel::layoutAboutToBeChanged), + // this, &PlaceholderModel::layoutAboutToBeChanged); + // connect(sourceModelPtr, &QAbstractItemModel::layoutChanged), + // this, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), + // Qt::UniqueConnection); +} + +void PlaceholderModel::disconnectSignals() +{ + if (!m_sourceModel) { + return; + } + + disconnect(m_sourceModel, 0, this, 0); +} + +int PlaceholderModel::dropPlaceholderIndex() const +{ + return m_dropPlaceholderIndex; +} + +void PlaceholderModel::setDropPlaceholderIndex(int index) +{ + if (index == m_dropPlaceholderIndex) return; + + if (index == -1 && m_dropPlaceholderIndex != -1) { + // Removing the placeholder + beginRemoveRows(QModelIndex(), m_dropPlaceholderIndex, m_dropPlaceholderIndex); + m_dropPlaceholderIndex = index; + endRemoveRows(); + + emit countChanged(); + + } else if (index != -1 && m_dropPlaceholderIndex == -1) { + // Creating the placeholder + beginInsertRows(QModelIndex(), index, index); + m_dropPlaceholderIndex = index; + endInsertRows(); + + emit countChanged(); + + } else if (m_dropPlaceholderIndex != index) { + // Moving the placeholder + int modelTo = index + (index > m_dropPlaceholderIndex ? 1 : 0); + + if (beginMoveRows( + QModelIndex(), m_dropPlaceholderIndex, m_dropPlaceholderIndex, + QModelIndex(), modelTo)) { + m_dropPlaceholderIndex = index; + endMoveRows(); + } + } + + emit dropPlaceholderIndexChanged(); +} Index: applets/kicker/plugin/recentusagemodel.cpp =================================================================== --- applets/kicker/plugin/recentusagemodel.cpp +++ applets/kicker/plugin/recentusagemodel.cpp @@ -21,7 +21,7 @@ #include "actionlist.h" #include "appsmodel.h" #include "appentry.h" -#include "favoritesmodel.h" +#include "kastatsfavoritesmodel.h" #include @@ -74,8 +74,8 @@ void InvalidAppsFilterProxy::connectNewFavoritesModel() { - FavoritesModel* favoritesModel = static_cast(m_parentModel->favoritesModel()); - connect(favoritesModel, &FavoritesModel::favoritesChanged, this, &QSortFilterProxyModel::invalidate); + KAStatsFavoritesModel* favoritesModel = static_cast(m_parentModel->favoritesModel()); + connect(favoritesModel, &KAStatsFavoritesModel::favoritesChanged, this, &QSortFilterProxyModel::invalidate); invalidate(); } @@ -89,7 +89,7 @@ if (resource.startsWith(QLatin1String("applications:"))) { KService::Ptr service = KService::serviceByStorageId(resource.section(':', 1)); - FavoritesModel* favoritesModel = m_parentModel ? static_cast(m_parentModel->favoritesModel()) : nullptr; + KAStatsFavoritesModel* favoritesModel = m_parentModel ? static_cast(m_parentModel->favoritesModel()) : nullptr; return (service && (!favoritesModel || !favoritesModel->isFavorite(service->storageId()))); } Index: applets/kicker/plugin/rootmodel.h =================================================================== --- applets/kicker/plugin/rootmodel.h +++ applets/kicker/plugin/rootmodel.h @@ -24,7 +24,7 @@ #include -class FavoritesModel; +class KAStatsFavoritesModel; class RecentContactsModel; class RecentUsageModel; class SystemModel; @@ -117,7 +117,7 @@ private: bool m_complete; - FavoritesModel *m_favorites; + KAStatsFavoritesModel *m_favorites; SystemModel *m_systemModel; bool m_autoPopulate; Index: applets/kicker/plugin/rootmodel.cpp =================================================================== --- applets/kicker/plugin/rootmodel.cpp +++ applets/kicker/plugin/rootmodel.cpp @@ -19,7 +19,7 @@ #include "rootmodel.h" #include "actionlist.h" -#include "favoritesmodel.h" +#include "kastatsfavoritesmodel.h" #include "recentcontactsmodel.h" #include "recentusagemodel.h" #include "systemmodel.h" @@ -64,7 +64,7 @@ RootModel::RootModel(QObject *parent) : AppsModel(QString(), parent) , m_complete(false) -, m_favorites(new FavoritesModel(this)) +, m_favorites(new KAStatsFavoritesModel(this)) , m_systemModel(nullptr) , m_autoPopulate(true) , m_showAllApps(false) @@ -301,7 +301,7 @@ QList groups; if (m_paginate) { - m_favorites = new FavoritesModel(this); + m_favorites = new KAStatsFavoritesModel(this); emit favoritesModelChanged(); QHash appsHash; Index: applets/kicker/plugin/simplefavoritesmodel.h =================================================================== --- applets/kicker/plugin/simplefavoritesmodel.h +++ applets/kicker/plugin/simplefavoritesmodel.h @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef FAVORITESMODEL_H -#define FAVORITESMODEL_H +#ifndef SIMPLEFAVORITESMODEL_H +#define SIMPLEFAVORITESMODEL_H #include "abstractmodel.h" @@ -26,7 +26,7 @@ #include -class FavoritesModel : public AbstractModel +class SimpleFavoritesModel : public AbstractModel { Q_OBJECT @@ -36,8 +36,8 @@ Q_PROPERTY(int dropPlaceholderIndex READ dropPlaceholderIndex WRITE setDropPlaceholderIndex NOTIFY dropPlaceholderIndexChanged) public: - explicit FavoritesModel(QObject *parent = 0); - ~FavoritesModel(); + explicit SimpleFavoritesModel(QObject *parent = 0); + ~SimpleFavoritesModel(); QString description() const Q_DECL_OVERRIDE; Index: applets/kicker/plugin/simplefavoritesmodel.cpp =================================================================== --- applets/kicker/plugin/simplefavoritesmodel.cpp +++ applets/kicker/plugin/simplefavoritesmodel.cpp @@ -17,7 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#include "favoritesmodel.h" +#include "simplefavoritesmodel.h" #include "appentry.h" #include "contactentry.h" #include "fileentry.h" @@ -26,24 +26,24 @@ #include -FavoritesModel::FavoritesModel(QObject *parent) : AbstractModel(parent) +SimpleFavoritesModel::SimpleFavoritesModel(QObject *parent) : AbstractModel(parent) , m_enabled(true) , m_maxFavorites(-1) , m_dropPlaceholderIndex(-1) { } -FavoritesModel::~FavoritesModel() +SimpleFavoritesModel::~SimpleFavoritesModel() { qDeleteAll(m_entryList); } -QString FavoritesModel::description() const +QString SimpleFavoritesModel::description() const { return i18n("Favorites"); } -QVariant FavoritesModel::data(const QModelIndex& index, int role) const +QVariant SimpleFavoritesModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() >= rowCount()) { return QVariant(); @@ -84,12 +84,12 @@ return QVariant(); } -int FavoritesModel::rowCount(const QModelIndex& parent) const +int SimpleFavoritesModel::rowCount(const QModelIndex& parent) const { return parent.isValid() ? 0 : m_entryList.count() + (m_dropPlaceholderIndex != -1 ? 1 : 0); } -bool FavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) +bool SimpleFavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) { if (row < 0 || row >= m_entryList.count()) { return false; @@ -98,12 +98,12 @@ return m_entryList.at(row)->run(actionId, argument); } -bool FavoritesModel::enabled() const +bool SimpleFavoritesModel::enabled() const { return m_enabled; } -void FavoritesModel::setEnabled(bool enable) +void SimpleFavoritesModel::setEnabled(bool enable) { if (m_enabled != enable) { m_enabled = enable; @@ -112,12 +112,12 @@ } } -QStringList FavoritesModel::favorites() const +QStringList SimpleFavoritesModel::favorites() const { return m_favorites; } -void FavoritesModel::setFavorites(const QStringList& favorites) +void SimpleFavoritesModel::setFavorites(const QStringList& favorites) { QStringList _favorites(favorites); _favorites.removeDuplicates(); @@ -128,12 +128,12 @@ } } -int FavoritesModel::maxFavorites() const +int SimpleFavoritesModel::maxFavorites() const { return m_maxFavorites; } -void FavoritesModel::setMaxFavorites(int max) +void SimpleFavoritesModel::setMaxFavorites(int max) { if (m_maxFavorites != max) { @@ -147,12 +147,12 @@ } } -bool FavoritesModel::isFavorite(const QString &id) const +bool SimpleFavoritesModel::isFavorite(const QString &id) const { return m_favorites.contains(id); } -void FavoritesModel::addFavorite(const QString &id, int index) +void SimpleFavoritesModel::addFavorite(const QString &id, int index) { if (!m_enabled || id.isEmpty()) { return; @@ -184,7 +184,7 @@ emit favoritesChanged(); } -void FavoritesModel::removeFavorite(const QString &id) +void SimpleFavoritesModel::removeFavorite(const QString &id) { if (!m_enabled || id.isEmpty()) { return; @@ -208,7 +208,7 @@ } } -void FavoritesModel::moveRow(int from, int to) +void SimpleFavoritesModel::moveRow(int from, int to) { if (from >= m_favorites.count() || to >= m_favorites.count()) { return; @@ -234,12 +234,12 @@ } } -int FavoritesModel::dropPlaceholderIndex() const +int SimpleFavoritesModel::dropPlaceholderIndex() const { return m_dropPlaceholderIndex; } -void FavoritesModel::setDropPlaceholderIndex(int index) +void SimpleFavoritesModel::setDropPlaceholderIndex(int index) { if (index == -1 && m_dropPlaceholderIndex != -1) { beginRemoveRows(QModelIndex(), m_dropPlaceholderIndex, m_dropPlaceholderIndex); @@ -270,12 +270,12 @@ } } -AbstractModel *FavoritesModel::favoritesModel() +AbstractModel *SimpleFavoritesModel::favoritesModel() { return this; } -void FavoritesModel::refresh() +void SimpleFavoritesModel::refresh() { beginResetModel(); @@ -314,7 +314,7 @@ emit favoritesChanged(); } -AbstractEntry *FavoritesModel::favoriteFromId(const QString &id) +AbstractEntry *SimpleFavoritesModel::favoriteFromId(const QString &id) { const QUrl url(id); const QString &s = url.scheme(); Index: applets/kicker/plugin/systemmodel.cpp =================================================================== --- applets/kicker/plugin/systemmodel.cpp +++ applets/kicker/plugin/systemmodel.cpp @@ -19,7 +19,7 @@ #include "systemmodel.h" #include "actionlist.h" -#include "favoritesmodel.h" +#include "simplefavoritesmodel.h" #include "systementry.h" #include @@ -31,7 +31,7 @@ { init(); - m_favoritesModel = new FavoritesModel(this); + m_favoritesModel = new SimpleFavoritesModel(this); const QString configFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/ksmserverrc"; Index: applets/kickoff/package/contents/config/main.xml =================================================================== --- applets/kickoff/package/contents/config/main.xml +++ applets/kickoff/package/contents/config/main.xml @@ -22,6 +22,10 @@ preferred://browser,kontact.desktop,systemsettings.desktop,org.kde.dolphin.desktop,ktp-contactlist.desktop,org.kde.kate.desktop,org.kde.discover.desktop + + + false + systemsettings.desktop,org.kde.kinfocenter.desktop,org.kde.discover.desktop Index: applets/kickoff/package/contents/ui/FullRepresentation.qml =================================================================== --- applets/kickoff/package/contents/ui/FullRepresentation.qml +++ applets/kickoff/package/contents/ui/FullRepresentation.qml @@ -65,7 +65,7 @@ sorted: plasmoid.configuration.alphaSort showSeparators: false - favoritesModel: Kicker.FavoritesModel { + favoritesModel: Kicker.KAStatsFavoritesModel { id: rootModelFavorites favorites: plasmoid.configuration.favorites @@ -73,6 +73,17 @@ plasmoid.configuration.favorites = favorites; } } + + Component.onCompleted: { + favoritesModel.initForClient("org.kde.plasma.kickoff.favorites.instance-" + plasmoid.id) + + if (!plasmoid.configuration.favoritesPortedToKAstats) { + favoritesModel.addFavoritesTo(plasmoid.configuration.favorites, ":global"); + plasmoid.configuration.favoritesPortedToKAstats = true; + } + + rootModel.refresh(); + } } Kicker.RunnerModel {