Commit c311339e authored by Ivan Čukić's avatar Ivan Čukić 👁
Browse files

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 a6428b75
......@@ -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");
......
/***************************************************************************
* 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 . *
***************************************************************************/
#include "placeholdermodel.h"
#include "actionlist.h"
#include <QDebug>
#include <QTimer>
PlaceholderModel::PlaceholderModel(QObject *parent)
: AbstractModel(parent)
, m_dropPlaceholderIndex(-1)
, m_isTriggerInhibited(false)
{
connect(&m_triggerInhibitor, &QTimer::timeout,
this, [&] {
qDebug() << "%%% Inhibit stopped";
m_isTriggerInhibited = false;
});
m_triggerInhibitor.setInterval(500);
m_triggerInhibitor.setSingleShot(true);
}
void PlaceholderModel::inhibitTriggering()
{
qDebug() << "%%% Inhibit started";
m_isTriggerInhibited = true;
m_triggerInhibitor.start();
}
PlaceholderModel::~PlaceholderModel()
{
}
QString PlaceholderModel::description() const
{
if (auto abstractModel = qobject_cast<AbstractModel *>(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 (m_isTriggerInhibited) return false;
if (auto abstractModel = qobject_cast<AbstractModel *>(m_sourceModel)) {
return abstractModel->trigger(rowToSourceRow(row), actionId, argument);
} else {
return false;
}
}
QString PlaceholderModel::labelForRow(int row)
{
if (auto abstractModel = qobject_cast<AbstractModel *>(m_sourceModel)) {
return abstractModel->labelForRow(rowToSourceRow(row));
} else {
return QString();
}
}
AbstractModel* PlaceholderModel::modelForRow(int row)
{
if (auto abstractModel = qobject_cast<AbstractModel *>(m_sourceModel)) {
return abstractModel->modelForRow(rowToSourceRow(row));
} else {
return 0;
}
}
AbstractModel* PlaceholderModel::favoritesModel()
{
if (auto abstractModel = qobject_cast<AbstractModel *>(m_sourceModel)) {
return abstractModel->favoritesModel();
} else {
return AbstractModel::favoritesModel();
}
}
int PlaceholderModel::separatorCount() const
{
if (auto abstractModel = qobject_cast<AbstractModel *>(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<int> &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<QPersistentModelIndex>,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;
inhibitTriggering();
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();
}