Commit 4ba9b01e authored by Ivan Čukić's avatar Ivan Čukić 👁

Per-activity favorites (Final, again?)

Summary:
The favourites are based on KAStats (already released version) **and kactivitymanagerd master (to be released with the next Plasma release)**. It allows favourites to be set to all activities, or the user can choose which activities to show a specific favourite application on.

This change covers applications, files and contacts, other favourites are still based on the old model (now named SimpleFavoritesModel).

{F1028047}

Test Plan:
Tested in Kicker, Dashboard and Kickoff the following:

Transitioning mechanism:

 - load default favorites for the blank user
 - load custom default favorites set in the plasmoidsetupscripts script for Kicker
 - transition old results - when transitioning, merge the favourites from all launchers. The ordering for each launcher is kept separate (newly added items due to the merge go to the end)

Favorite manipulation:

 - right-click add favorite to all activities
 - right-click remove favorite from all activities
 - right-click add favorite to specific activity (current)
 - right-click remove favorite from specific activity (current)
 - right-click add favorite to specific activity (not current)
 - right-click remove favorite from specific activity (not current)
 - right-click move from all to specific
 - right-click move from specific to all
 - right-click move from one activity to another
 - dnd reorder items in the model (up)
 - dnd reorder items in the model (down)
 - dnd add to favorites at a specific position

Other:
 - launch the application
 - ordering persists after restart
 - ordering from the previous is kept on the activity that has no ordering

Reviewers: mart, hein

Reviewed By: hein

Subscribers: Zren, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D3805
parent b20d5297
......@@ -22,9 +22,11 @@ set(kickerplugin_SRCS
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 @@ set(kickerplugin_SRCS
plugin/systemsettings.cpp
plugin/wheelinterceptor.cpp
plugin/windowsystem.cpp
plugin/funnelmodel.cpp
)
qt5_add_dbus_interface(kickerplugin_SRCS ${KRUNNERAPP_INTERFACE} krunner_interface)
......
/***************************************************************************
* Copyright (C) 2013 by Aurélien Gâteau <agateau@kde.org> *
* Copyright (C) 2013-2015 by Eike Hein <hein@kde.org> *
* Copyright (C) 2017 by Ivan Cukic <ivan.cukic@kde.org> *
* *
* 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 *
......@@ -22,42 +23,128 @@ function fillActionMenu(actionMenu, actionList, favoriteModel, favoriteId) {
// 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) {
......@@ -85,12 +172,31 @@ function handleFavoriteAction(actionId, actionArgument) {
var favoriteId = actionArgument.favoriteId;
var favoriteModel = actionArgument.favoriteModel;
console.log(actionId);
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.setFavoriteOn(favoriteId, actionArgument.favoriteActivity);
}
}
......@@ -44,6 +44,10 @@
<label>List of system action favorites.</label>
<default>logout,reboot,shutdown</default>
</entry>
<entry name="favoritesPortedToKAstats" type="Bool">
<label>Are the favorites ported to use KActivitiesStats to allow per-activity favorites</label>
<default>false</default>
</entry>
<entry name="hiddenApplications" type="StringList">
<label>List of menu id's (usually .desktop file names) of apps that should not be shown in the menu.</label>
<default></default>
......
......@@ -64,11 +64,28 @@ Item {
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 @@ Item {
}
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);
}
......
......@@ -109,7 +109,13 @@ Item {
}
Component.onCompleted: {
favoritesModel.favorites = plasmoid.configuration.favoriteApps;
favoritesModel.initForClient("org.kde.plasma.kicker.favorites.instance-" + plasmoid.id)
if (!plasmoid.configuration.favoritesPortedToKAstats) {
favoritesModel.portOldFavorites(plasmoid.configuration.favoriteApps);
plasmoid.configuration.favoritesPortedToKAstats = true;
}
rootModel.refresh();
}
}
......
......@@ -19,6 +19,8 @@
#include "abstractentry.h"
#include <QDebug>
AbstractEntry::AbstractEntry(AbstractModel *owner)
: m_owner(owner)
{
......
......@@ -118,6 +118,11 @@ QString AppEntry::id() const
return m_service->storageId();
}
QString AppEntry::menuId() const
{
return m_service->menuId();
}
QUrl AppEntry::url() const
{
return QUrl::fromLocalFile(m_service->entryPath());
......
......@@ -58,6 +58,8 @@ class AppEntry : public AbstractEntry
bool run(const QString& actionId = QString(), const QVariant &argument = QVariant()) Q_DECL_OVERRIDE;
QString menuId() const;
static QString nameFromService(const KService::Ptr service, NameFormat nameFormat);
static KService::Ptr defaultAppByName(const QString &name);
......
......@@ -20,7 +20,7 @@
#include "computermodel.h"
#include "actionlist.h"
#include "favoritesmodel.h"
#include "simplefavoritesmodel.h"
#include <QIcon>
......@@ -137,12 +137,13 @@ Q_INVOKABLE bool RunCommandModel::trigger(int row, const QString &actionId, cons
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);
......
......@@ -26,7 +26,7 @@
#include <QSortFilterProxyModel>
#include <Solid/StorageAccess>
class FavoritesModel;
class SimpleFavoritesModel;
class KConcatenateRowsProxyModel;
class KFilePlacesModel;
......@@ -110,7 +110,7 @@ class ComputerModel : public ForwardingModel
private:
KConcatenateRowsProxyModel *m_concatProxy;
RunCommandModel *m_runCommandModel;
FavoritesModel *m_systemAppsModel;
SimpleFavoritesModel *m_systemAppsModel;
FilteredPlacesModel *m_filteredPlacesModel;
AppEntry::NameFormat m_appNameFormat;
QObject *m_appletInterface;
......
This diff is collapsed.
/***************************************************************************
* Copyright (C) 2014-2015 by Eike Hein <hein@kde.org> *
* Copyright (C) 2016-2017 by Ivan Cukic <ivan.cukic@kde.org> *
* *
* 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 <QPointer>
#include <KService>
#include <KConfig>
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 setFavoriteOn(const QString &id, const QString &activityId);
Q_INVOKABLE void portOldFavorites(const QStringList &ids);
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
......@@ -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 @@ void KickerPlugin::registerTypes(const char *uri)
qmlRegisterType<ComputerModel>(uri, 0, 1, "ComputerModel");
qmlRegisterType<ContainmentInterface>(uri, 0, 1, "ContainmentInterface");
qmlRegisterType<DragHelper>(uri, 0, 1, "DragHelper");
qmlRegisterType<FavoritesModel>(uri, 0, 1, "FavoritesModel");
qmlRegisterType<SimpleFavoritesModel>(uri, 0, 1, "FavoritesModel");
qmlRegisterType<KAStatsFavoritesModel>(uri, 0, 1, "KAStatsFavoritesModel");
qmlRegisterType<DashboardWindow>(uri, 0, 1, "DashboardWindow");
qmlRegisterType<FunnelModel>(uri, 0, 1, "FunnelModel");
qmlRegisterType<ProcessRunner>(uri, 0, 1, "ProcessRunner");
......
This diff is collapsed.
/***************************************************************************
* Copyright (C) 2015 by Eike Hein <hein@kde.org> *
* Copyright (C) 2017 by Ivan Cukic <ivan.cukic@kde.org> *
* *
* 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 <QPointer>
#include <QTimer>
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:
void inhibitTriggering();
private:
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<QAbstractItemModel> m_sourceModel;
int m_dropPlaceholderIndex;
bool m_isTriggerInhibited;
QTimer m_triggerInhibitor;
};
#endif
......@@ -21,7 +21,7 @@
#include "actionlist.h"
#include "appsmodel.h"
#include "appentry.h"
#include "favoritesmodel.h"
#include "kastatsfavoritesmodel.h"
#include <config-X11.h>
......@@ -74,8 +74,8 @@ InvalidAppsFilterProxy::~InvalidAppsFilterProxy()
void InvalidAppsFilterProxy::connectNewFavoritesModel()
{
FavoritesModel* favoritesModel = static_cast<FavoritesModel *>(m_parentModel->favoritesModel());
connect(favoritesModel, &FavoritesModel::favoritesChanged, this, &QSortFilterProxyModel::invalidate);
KAStatsFavoritesModel* favoritesModel = static_cast<KAStatsFavoritesModel *>(m_parentModel->favoritesModel());
connect(favoritesModel, &KAStatsFavoritesModel::favoritesChanged, this, &QSortFilterProxyModel::invalidate);
invalidate();
}
......@@ -89,7 +89,7 @@ bool InvalidAppsFilterProxy::filterAcceptsRow(int source_row, const QModelIndex
if (resource.startsWith(QLatin1String("applications:"))) {
KService::Ptr service = KService::serviceByStorageId(resource.section(':', 1));
FavoritesModel* favoritesModel = m_parentModel ? static_cast<FavoritesModel *>(m_parentModel->favoritesModel()) : nullptr;
KAStatsFavoritesModel* favoritesModel = m_parentModel ? static_cast<KAStatsFavoritesModel *>(m_parentModel->favoritesModel()) : nullptr;
return (service && (!favoritesModel || !favoritesModel->isFavorite(service->storageId())));
}
......
......@@ -19,7 +19,7 @@
#include "rootmodel.h"
#include "actionlist.h"
#include "favoritesmodel.h"
#include "kastatsfavoritesmodel.h"