diff --git a/applets/kicker/plugin/abstractentry.cpp b/applets/kicker/plugin/abstractentry.cpp new file mode 100644 index 000000000..5ffab3464 --- /dev/null +++ b/applets/kicker/plugin/abstractentry.cpp @@ -0,0 +1,108 @@ +/*************************************************************************** + * Copyright (C) 2014-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 . * + ***************************************************************************/ + +#include "abstractentry.h" + +#include + +AbstractEntry::AbstractEntry(AbstractModel *owner) +: m_owner(owner) +{ +} + +AbstractEntry::~AbstractEntry() +{ +} + +AbstractModel *AbstractEntry::owner() const +{ + return m_owner; +} + +bool AbstractEntry::isValid() const +{ + return true; +} + +QIcon AbstractEntry::icon() const +{ + return QIcon(); +} + +QString AbstractEntry::name() const +{ + return QString(); +} + + +QString AbstractEntry::group() const +{ + return QString(); +} + +QString AbstractEntry::description() const +{ + return QString(); +} + +QString AbstractEntry::id() const +{ + return QString(); +} + +QUrl AbstractEntry::url() const +{ + return QUrl(); +} + +bool AbstractEntry::hasChildren() const +{ + return false; +} + +AbstractModel *AbstractEntry::childModel() const +{ + return nullptr; +} + +bool AbstractEntry::hasActions() const +{ + return false; +} + +QVariantList AbstractEntry::actions() const +{ + return QVariantList(); +} + +bool AbstractEntry::run(const QString& actionId, const QVariant &argument) +{ + Q_UNUSED(actionId) + Q_UNUSED(argument) + + return false; +} + +AbstractGroupEntry::AbstractGroupEntry(AbstractModel *owner) : AbstractEntry(owner) +{ +} + +SeparatorEntry::SeparatorEntry(AbstractModel *owner) : AbstractEntry(owner) +{ +} diff --git a/applets/kicker/plugin/abstractentry.h b/applets/kicker/plugin/abstractentry.h new file mode 100644 index 000000000..5297e3cee --- /dev/null +++ b/applets/kicker/plugin/abstractentry.h @@ -0,0 +1,79 @@ +/*************************************************************************** + * Copyright (C) 2012 Aurélien Gâteau * + * Copyright (C) 2014-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 ABSTRACTENTRY_H +#define ABSTRACTENTRY_H + +#include "abstractmodel.h" + +#include +#include + +class AbstractEntry +{ + public: + explicit AbstractEntry(AbstractModel *owner); + virtual ~AbstractEntry(); + + enum EntryType { RunnableType, GroupType, SeparatorType }; + + virtual EntryType type() const = 0; + + AbstractModel *owner() const; + + virtual bool isValid() const; + + virtual QIcon icon() const; + virtual QString name() const; + virtual QString group() const; + virtual QString description() const; + + virtual QString id() const; + virtual QUrl url() const; + + virtual bool hasChildren() const; + virtual AbstractModel *childModel() const; + + virtual bool hasActions() const; + virtual QVariantList actions() const; + + virtual bool run(const QString& actionId = QString(), const QVariant &argument = QVariant()); + + protected: + AbstractModel* m_owner; +}; + +class AbstractGroupEntry : public AbstractEntry +{ + public: + explicit AbstractGroupEntry(AbstractModel *owner); + + EntryType type() const override { return GroupType; } +}; + +class SeparatorEntry : public AbstractEntry +{ + public: + explicit SeparatorEntry(AbstractModel *owner); + + EntryType type() const override { return SeparatorType; } +}; + +#endif diff --git a/applets/kicker/plugin/abstractmodel.cpp b/applets/kicker/plugin/abstractmodel.cpp new file mode 100644 index 000000000..a4966d8d3 --- /dev/null +++ b/applets/kicker/plugin/abstractmodel.cpp @@ -0,0 +1,164 @@ +/*************************************************************************** + * Copyright (C) 2014-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 . * + ***************************************************************************/ + +#include "abstractmodel.h" +#include "actionlist.h" + +AbstractModel::AbstractModel(QObject *parent) : QAbstractListModel(parent) +, m_favoritesModel(nullptr) +, m_iconSize(32) +{ +} + +AbstractModel::~AbstractModel() +{ +} + +QHash AbstractModel::roleNames() const +{ + QHash roles; + roles.insert(Qt::DisplayRole, "display"); + roles.insert(Qt::DecorationRole, "decoration"); + roles.insert(Kicker::GroupRole, "group"); + roles.insert(Kicker::DescriptionRole, "description"); + roles.insert(Kicker::FavoriteIdRole, "favoriteId"); + roles.insert(Kicker::IsParentRole, "isParent"); + roles.insert(Kicker::IsSeparatorRole, "isSeparator"); + roles.insert(Kicker::HasChildrenRole, "hasChildren"); + roles.insert(Kicker::HasActionListRole, "hasActionList"); + roles.insert(Kicker::ActionListRole, "actionList"); + roles.insert(Kicker::UrlRole, "url"); + + return roles; +} + +int AbstractModel::count() const +{ + return rowCount(); +} + +int AbstractModel::separatorCount() const +{ + return 0; +} + +int AbstractModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return 1; +} + +int AbstractModel::iconSize() const +{ + return m_iconSize; +} + +void AbstractModel::setIconSize(int iconSize) { + if (m_iconSize != iconSize) { + m_iconSize = iconSize; + refresh(); + } +} + +void AbstractModel::refresh() +{ +} + +QString AbstractModel::labelForRow(int row) +{ + return data(index(row, 0), Qt::DisplayRole).toString(); +} + +AbstractModel *AbstractModel::modelForRow(int row) +{ + Q_UNUSED(row) + + return nullptr; +} + +int AbstractModel::rowForModel(AbstractModel *model) +{ + Q_UNUSED(model) + + return -1; +} + +bool AbstractModel::hasActions() const +{ + return false; +} + +QVariantList AbstractModel::actions() const +{ + return QVariantList(); +} + +AbstractModel* AbstractModel::favoritesModel() +{ + if (m_favoritesModel) { + return m_favoritesModel; + } else { + AbstractModel *model = rootModel(); + + if (model && model != this) { + return model->favoritesModel(); + } + } + + return nullptr; +} + +void AbstractModel::setFavoritesModel(AbstractModel *model) +{ + if (m_favoritesModel != model) { + m_favoritesModel = model; + + emit favoritesModelChanged(); + } +} + +AbstractModel* AbstractModel::rootModel() +{ + if (!parent()) { + return nullptr; + } + + QObject *p = this; + AbstractModel *rootModel = nullptr; + + while (p) { + if (qobject_cast(p)) { + rootModel = qobject_cast(p); + } else { + return rootModel; + } + + p = p->parent(); + } + + return rootModel; +} + +void AbstractModel::entryChanged(AbstractEntry *entry) +{ + Q_UNUSED(entry) +} diff --git a/applets/kicker/plugin/abstractmodel.h b/applets/kicker/plugin/abstractmodel.h new file mode 100644 index 000000000..20d2c1b4c --- /dev/null +++ b/applets/kicker/plugin/abstractmodel.h @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (C) 2014-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 ABSTRACTMODEL_H +#define ABSTRACTMODEL_H + +#include + +class AbstractEntry; + +class AbstractModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(QString description READ description NOTIFY descriptionChanged) + + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(int separatorCount READ separatorCount NOTIFY separatorCountChanged) + Q_PROPERTY(int iconSize READ iconSize WRITE setIconSize NOTIFY iconSizeChanged) + Q_PROPERTY(AbstractModel* favoritesModel READ favoritesModel WRITE setFavoritesModel NOTIFY favoritesModelChanged) + + public: + explicit AbstractModel(QObject *parent = nullptr); + ~AbstractModel() override; + + QHash roleNames() const override; + + virtual QString description() const = 0; + + int count() const; + virtual int separatorCount() const; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + int iconSize() const; + void setIconSize(int size); + + Q_INVOKABLE virtual bool trigger(int row, const QString &actionId, const QVariant &argument) = 0; + + Q_INVOKABLE virtual void refresh(); + + Q_INVOKABLE virtual QString labelForRow(int row); + + Q_INVOKABLE virtual AbstractModel *modelForRow(int row); + Q_INVOKABLE virtual int rowForModel(AbstractModel *model); + + virtual bool hasActions() const; + virtual QVariantList actions() const; + + virtual AbstractModel* favoritesModel(); + virtual void setFavoritesModel(AbstractModel *model); + AbstractModel* rootModel(); + + virtual void entryChanged(AbstractEntry *entry); + + Q_SIGNALS: + void descriptionChanged() const; + void countChanged() const; + void separatorCountChanged() const; + void iconSizeChanged() const; + void favoritesModelChanged() const; + + protected: + AbstractModel *m_favoritesModel; + + private: + int m_iconSize; +}; + +#endif diff --git a/applets/kicker/plugin/actionlist.cpp b/applets/kicker/plugin/actionlist.cpp new file mode 100644 index 000000000..f6ccccca4 --- /dev/null +++ b/applets/kicker/plugin/actionlist.cpp @@ -0,0 +1,420 @@ +/*************************************************************************** + * Copyright (C) 2013 by Aurélien Gâteau * + * Copyright (C) 2014 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 . * + ***************************************************************************/ + +#include "actionlist.h" +#include "menuentryeditor.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "containmentinterface.h" + +#ifdef HAVE_APPSTREAMQT +#include +#endif + +namespace KAStats = KActivities::Stats; + +using namespace KAStats; +using namespace KAStats::Terms; + +namespace Kicker +{ + +QVariantMap createActionItem(const QString &label, const QString &actionId, const QVariant &argument) +{ + QVariantMap map; + + map[QStringLiteral("text")] = label; + map[QStringLiteral("actionId")] = actionId; + + if (argument.isValid()) { + map[QStringLiteral("actionArgument")] = argument; + } + + return map; +} + +QVariantMap createTitleActionItem(const QString &label) +{ + QVariantMap map; + + map[QStringLiteral("text")] = label; + map[QStringLiteral("type")] = QStringLiteral("title"); + + return map; +} + +QVariantMap createSeparatorActionItem() +{ + QVariantMap map; + + map[QStringLiteral("type")] = QStringLiteral("separator"); + + return map; +} + +QVariantList createActionListForFileItem(const KFileItem &fileItem) +{ + QVariantList list; + + KService::List services = KMimeTypeTrader::self()->query(fileItem.mimetype(), QStringLiteral("Application")); + + if (!services.isEmpty()) { + list << createTitleActionItem(i18n("Open with:")); + + foreach (const KService::Ptr service, services) { + const QString text = service->name().replace(QLatin1Char('&'), QStringLiteral("&&")); + QVariantMap item = createActionItem(text, QStringLiteral("_kicker_fileItem_openWith"), service->entryPath()); + item[QStringLiteral("icon")] = service->icon(); + + list << item; + } + + list << createSeparatorActionItem(); + } + + QVariantMap propertiesItem = createActionItem(i18n("Properties"), QStringLiteral("_kicker_fileItem_properties")); + propertiesItem[QStringLiteral("icon")] = QStringLiteral("document-properties"); + list << propertiesItem; + + return list; +} + +bool handleFileItemAction(const KFileItem &fileItem, const QString &actionId, const QVariant &argument, bool *close) +{ + if (actionId == QLatin1String("_kicker_fileItem_properties")) { + KPropertiesDialog *dlg = new KPropertiesDialog(fileItem, QApplication::activeWindow()); + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->show(); + + *close = false; + + return true; + } + + if (actionId == QLatin1String("_kicker_fileItem_openWith")) { + const QString path = argument.toString(); + const KService::Ptr service = KService::serviceByDesktopPath(path); + + if (!service) { + return false; + } + + KRun::runService(*service, QList() << fileItem.url(), QApplication::activeWindow()); + + *close = true; + + return true; + } + + return false; +} + +QVariantList createAddLauncherActionList(QObject *appletInterface, const KService::Ptr &service) +{ + QVariantList actionList; + if (!service) { + return actionList; + } + + if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::Desktop)) { + QVariantMap addToDesktopAction = Kicker::createActionItem(i18n("Add to Desktop"), QStringLiteral("addToDesktop")); + addToDesktopAction[QStringLiteral("icon")] = QStringLiteral("list-add"); + actionList << addToDesktopAction; + } + + if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::Panel)) { + QVariantMap addToPanelAction = Kicker::createActionItem(i18n("Add to Panel (Widget)"), QStringLiteral("addToPanel")); + addToPanelAction[QStringLiteral("icon")] = QStringLiteral("list-add"); + actionList << addToPanelAction; + } + + if (service && ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::TaskManager, Kicker::resolvedServiceEntryPath(service))) { + QVariantMap addToTaskManagerAction = Kicker::createActionItem(i18n("Pin to Task Manager"), QStringLiteral("addToTaskManager")); + addToTaskManagerAction[QStringLiteral("icon")] = QStringLiteral("pin"); + actionList << addToTaskManagerAction; + } + + return actionList; +} + +bool handleAddLauncherAction(const QString &actionId, QObject *appletInterface, const KService::Ptr &service) +{ + if (!service) { + return false; + } + + if (actionId == QLatin1String("addToDesktop")) { + if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::Desktop)) { + ContainmentInterface::addLauncher(appletInterface, ContainmentInterface::Desktop, Kicker::resolvedServiceEntryPath(service)); + } + return true; + } else if (actionId == QLatin1String("addToPanel")) { + if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::Panel)) { + ContainmentInterface::addLauncher(appletInterface, ContainmentInterface::Panel, Kicker::resolvedServiceEntryPath(service)); + } + return true; + } else if (actionId == QLatin1String("addToTaskManager")) { + if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::TaskManager, Kicker::resolvedServiceEntryPath(service))) { + ContainmentInterface::addLauncher(appletInterface, ContainmentInterface::TaskManager, Kicker::resolvedServiceEntryPath(service)); + } + return true; + } + + return false; +} + +QString storageIdFromService(KService::Ptr service) +{ + QString storageId = service->storageId(); + + if (storageId.endsWith(QLatin1String(".desktop"))) { + storageId = storageId.left(storageId.length() - 8); + } + + return storageId; +} + +QVariantList jumpListActions(KService::Ptr service) +{ + QVariantList list; + + if (!service) { + return list; + } + + const auto &actions = service->actions(); + foreach (const KServiceAction &action, actions) { + if (action.text().isEmpty() || action.exec().isEmpty()) { + continue; + } + + QVariantMap item = createActionItem(action.text(), QStringLiteral("_kicker_jumpListAction"), action.exec()); + item[QStringLiteral("icon")] = action.icon(); + + list << item; + } + + return list; +} + +QVariantList recentDocumentActions(KService::Ptr service) +{ + QVariantList list; + + if (!service) { + return list; + } + + const QString storageId = storageIdFromService(service); + + if (storageId.isEmpty()) { + return list; + } + + auto query = UsedResources + | RecentlyUsedFirst + | Agent(storageId) + | Type::any() + | Activity::current() + | Url::file(); + + ResultSet results(query); + + ResultSet::const_iterator resultIt; + resultIt = results.begin(); + + while (list.count() < 6 && resultIt != results.end()) { + const QString resource = (*resultIt).resource(); + ++resultIt; + + const QUrl url(resource); + + if (!url.isValid()) { + continue; + } + + const KFileItem fileItem(url); + + if (!fileItem.isFile()) { + continue; + } + + if (list.isEmpty()) { + list << createTitleActionItem(i18n("Recent Documents")); + } + + QVariantMap item = createActionItem(url.fileName(), QStringLiteral("_kicker_recentDocument"), resource); + item[QStringLiteral("icon")] = fileItem.iconName(); + + list << item; + } + + if (!list.isEmpty()) { + QVariantMap forgetAction = createActionItem(i18n("Forget Recent Documents"), QStringLiteral("_kicker_forgetRecentDocuments")); + forgetAction[QStringLiteral("icon")] = QStringLiteral("edit-clear-history"); + list << forgetAction; + } + + return list; +} + +bool handleRecentDocumentAction(KService::Ptr service, const QString &actionId, const QVariant &_argument) +{ + if (!service) { + return false; + } + + if (actionId == QLatin1String("_kicker_forgetRecentDocuments")) { + const QString storageId = storageIdFromService(service); + + if (storageId.isEmpty()) { + return false; + } + + auto query = UsedResources + | Agent(storageId) + | Type::any() + | Activity::current() + | Url::file(); + + KAStats::forgetResources(query); + + return false; + } + + QString argument = _argument.toString(); + + if (argument.isEmpty()) { + return false; + } + + return (KRun::runService(*service, QList() << QUrl(argument), QApplication::activeWindow()) != 0); +} + +Q_GLOBAL_STATIC(MenuEntryEditor, menuEntryEditor) + +bool canEditApplication(const KService::Ptr &service) +{ + return (service->isApplication() && menuEntryEditor->canEdit(service->entryPath())); +} + +void editApplication(const QString &entryPath, const QString &menuId) +{ + menuEntryEditor->edit(entryPath, menuId); +} + +QVariantList editApplicationAction(const KService::Ptr &service) +{ + QVariantList actionList; + + if (canEditApplication(service)) { + QVariantMap editAction = Kicker::createActionItem(i18n("Edit Application..."), QStringLiteral("editApplication")); + editAction[QStringLiteral("icon")] = QStringLiteral("kmenuedit"); // TODO: Using the KMenuEdit icon might be misleading. + actionList << editAction; + } + + return actionList; +} + +bool handleEditApplicationAction(const QString &actionId, const KService::Ptr &service) +{ + + if (service && actionId ==QLatin1String("editApplication") && canEditApplication(service)) { + Kicker::editApplication(service->entryPath(), service->menuId()); + + return true; + } + + return false; +} + +#ifdef HAVE_APPSTREAMQT +Q_GLOBAL_STATIC(AppStream::Pool, appstreamPool) +#endif + +QVariantList appstreamActions(const KService::Ptr &service) +{ + QVariantList ret; + +#ifdef HAVE_APPSTREAMQT + const KService::Ptr appStreamHandler = KMimeTypeTrader::self()->preferredService(QStringLiteral("x-scheme-handler/appstream")); + + // Don't show action if we can't find any app to handle appstream:// URLs. + if (!appStreamHandler) { + if (!KProtocolInfo::isHelperProtocol(QStringLiteral("appstream")) + || KProtocolInfo::exec(QStringLiteral("appstream")).isEmpty()) { + return ret; + } + } + + if (!appstreamPool.exists()) { + appstreamPool->load(); + } + + const auto components = appstreamPool->componentsById(service->desktopEntryName()+QLatin1String(".desktop")); + for(const auto &component: components) { + const QString componentId = component.id(); + + QVariantMap appstreamAction = Kicker::createActionItem(i18nc("@action opens a software center with the application", "Uninstall or Manage Add-Ons..."), "manageApplication", QVariant(QLatin1String("appstream://") + componentId)); + appstreamAction[QStringLiteral("icon")] = appStreamHandler->icon(); + ret << appstreamAction; + } +#else + Q_UNUSED(service) +#endif + + return ret; +} + +bool handleAppstreamActions(const QString &actionId, const QVariant &argument) +{ + if (actionId == QLatin1String("manageApplication")) { + return QDesktopServices::openUrl(QUrl(argument.toString())); + } + + return false; +} + +QString resolvedServiceEntryPath(const KService::Ptr &service) +{ + QString path = service->entryPath(); + if (!QDir::isAbsolutePath(path)) { + path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kservices5/") + path); + } + return path; +} + +} diff --git a/applets/kicker/plugin/actionlist.h b/applets/kicker/plugin/actionlist.h new file mode 100644 index 000000000..db706d920 --- /dev/null +++ b/applets/kicker/plugin/actionlist.h @@ -0,0 +1,75 @@ +/*************************************************************************** + * Copyright (C) 2013 by Aurélien Gâteau * + * Copyright (C) 2014-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 ACTIONLIST_H +#define ACTIONLIST_H + +#include + +#include + +class KFileItem; + +namespace Kicker +{ + +enum { + DescriptionRole = Qt::UserRole + 1, + GroupRole, + FavoriteIdRole, + IsSeparatorRole, + IsDropPlaceholderRole, + IsParentRole, + HasChildrenRole, + HasActionListRole, + ActionListRole, + UrlRole +}; + +QVariantMap createActionItem(const QString &label, const QString &actionId, const QVariant &argument = QVariant()); + +QVariantMap createTitleActionItem(const QString &label); + +QVariantMap createSeparatorActionItem(); + +QVariantList createActionListForFileItem(const KFileItem &fileItem); +bool handleFileItemAction(const KFileItem &fileItem, const QString &actionId, const QVariant &argument, bool *close); + +QVariantList createAddLauncherActionList(QObject *appletInterface, const KService::Ptr &service); +bool handleAddLauncherAction(const QString &actionId, QObject *appletInterface, const KService::Ptr &service); + +QVariantList jumpListActions(KService::Ptr service); + +QVariantList recentDocumentActions(KService::Ptr service); +bool handleRecentDocumentAction(KService::Ptr service, const QString &actionId, const QVariant &argument); + +bool canEditApplication(const QString &entryPath); +void editApplication(const QString &entryPath, const QString &menuId); +QVariantList editApplicationAction(const KService::Ptr &service); +bool handleEditApplicationAction(const QString &actionId, const KService::Ptr &service); + +QVariantList appstreamActions(const KService::Ptr &service); +bool handleAppstreamActions(const QString &actionId, const QVariant &argument); + +QString resolvedServiceEntryPath(const KService::Ptr &service); + +} + +#endif diff --git a/applets/kicker/plugin/appentry.cpp b/applets/kicker/plugin/appentry.cpp new file mode 100644 index 000000000..820a94c12 --- /dev/null +++ b/applets/kicker/plugin/appentry.cpp @@ -0,0 +1,303 @@ +/*************************************************************************** + * Copyright (C) 201 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 . * + ***************************************************************************/ + +#include +#include "appentry.h" +#include "actionlist.h" +#include "appsmodel.h" +#include "containmentinterface.h" + +#include + +#include +#include +#include +#if HAVE_X11 +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +AppEntry::AppEntry(AbstractModel *owner, KService::Ptr service, NameFormat nameFormat) +: AbstractEntry(owner) +, m_service(service) +{ + if (m_service) { + init(nameFormat); + } +} + +AppEntry::AppEntry(AbstractModel *owner, const QString &id) : AbstractEntry(owner) +{ + const QUrl url(id); + + if (url.scheme() == QLatin1String("preferred")) { + m_service = defaultAppByName(url.host()); + m_id = id; + } else { + m_service = KService::serviceByStorageId(id); + } + + if (m_service) { + init((NameFormat)owner->rootModel()->property("appNameFormat").toInt()); + } +} + +void AppEntry::init(NameFormat nameFormat) +{ + m_name = nameFromService(m_service, nameFormat); + + if (nameFormat == GenericNameOnly) { + m_description = nameFromService(m_service, NameOnly); + } else { + m_description = nameFromService(m_service, GenericNameOnly); + } +} + +bool AppEntry::isValid() const +{ + return m_service; +} + +QIcon AppEntry::icon() const +{ + if (m_icon.isNull()) { + m_icon = QIcon::fromTheme(m_service->icon(), QIcon::fromTheme(QStringLiteral("unknown"))); + } + return m_icon; +} + +QString AppEntry::name() const +{ + return m_name; +} + +QString AppEntry::description() const +{ + return m_description; +} + +KService::Ptr AppEntry::service() const +{ + return m_service; +} + +QString AppEntry::id() const +{ + if (!m_id.isEmpty()) { + return m_id; + } + + return m_service->storageId(); +} + +QString AppEntry::menuId() const +{ + return m_service->menuId(); +} + +QUrl AppEntry::url() const +{ + return QUrl::fromLocalFile(Kicker::resolvedServiceEntryPath(m_service)); +} + +bool AppEntry::hasActions() const +{ + return true; +} + +QVariantList AppEntry::actions() const +{ + QVariantList actionList; + + actionList << Kicker::jumpListActions(m_service); + if (!actionList.isEmpty()) { + actionList << Kicker::createSeparatorActionItem(); + } + + QObject *appletInterface = m_owner->rootModel()->property("appletInterface").value(); + + const bool systemImmutable = appletInterface->property("immutability").toInt() == Plasma::Types::SystemImmutable; + + const QVariantList &addLauncherActions = Kicker::createAddLauncherActionList(appletInterface, m_service); + if (!systemImmutable && !addLauncherActions.isEmpty()) { + actionList << addLauncherActions + << Kicker::createSeparatorActionItem(); + } + + const QVariantList &recentDocuments = Kicker::recentDocumentActions(m_service); + if (!recentDocuments.isEmpty()) { + actionList << recentDocuments << Kicker::createSeparatorActionItem(); + } + + // Don't allow adding launchers, editing, hiding, or uninstalling applications + // when system is immutable. + if (systemImmutable) { + return actionList; + } + + if (m_service->isApplication()) { + actionList << Kicker::createSeparatorActionItem(); + actionList << Kicker::editApplicationAction(m_service); + actionList << Kicker::appstreamActions(m_service); + } + + QQmlPropertyMap *appletConfig = qobject_cast(appletInterface->property("configuration").value()); + + if (appletConfig && appletConfig->contains(QLatin1String("hiddenApplications")) && qobject_cast(m_owner)) { + const QStringList &hiddenApps = appletConfig->value(QLatin1String("hiddenApplications")).toStringList(); + + if (!hiddenApps.contains(m_service->menuId())) { + QVariantMap hideAction = Kicker::createActionItem(i18n("Hide Application"), QStringLiteral("hideApplication")); + hideAction[QStringLiteral("icon")] = QStringLiteral("hint"); + actionList << hideAction; + } + } + + return actionList; +} + +bool AppEntry::run(const QString& actionId, const QVariant &argument) +{ + if (!m_service->isValid()) { + return false; + } + + if (actionId.isEmpty()) { + quint32 timeStamp = 0; + +#if HAVE_X11 + if (QX11Info::isPlatformX11()) { + timeStamp = QX11Info::appUserTime(); + } +#endif + + KRun::runApplication(*m_service, {}, nullptr, KRun::DeleteTemporaryFiles, {}, KStartupInfo::createNewStartupIdForTimestamp(timeStamp)); + + KActivities::ResourceInstance::notifyAccessed(QUrl(QStringLiteral("applications:") + m_service->storageId()), + QStringLiteral("org.kde.plasma.kicker")); + + return true; + } + + QObject *appletInterface = m_owner->rootModel()->property("appletInterface").value(); + + if (Kicker::handleAddLauncherAction(actionId, appletInterface, m_service)) { + return true; + } else if (Kicker::handleEditApplicationAction(actionId, m_service)) { + return true; + } else if (Kicker::handleAppstreamActions(actionId, argument)) { + return true; + } else if (actionId == QLatin1String("_kicker_jumpListAction")) { + return KRun::run(argument.toString(), {}, nullptr, m_service->name(), m_service->icon()); + } + + return Kicker::handleRecentDocumentAction(m_service, actionId, argument); +} + +QString AppEntry::nameFromService(const KService::Ptr service, NameFormat nameFormat) +{ + const QString &name = service->name(); + QString genericName = service->genericName(); + + if (genericName.isEmpty()) { + genericName = service->comment(); + } + + if (nameFormat == NameOnly || genericName.isEmpty() || name == genericName) { + return name; + } else if (nameFormat == GenericNameOnly) { + return genericName; + } else if (nameFormat == NameAndGenericName) { + return i18nc("App name (Generic name)", "%1 (%2)", name, genericName); + } else { + return i18nc("Generic name (App name)", "%1 (%2)", genericName, name); + } +} + +KService::Ptr AppEntry::defaultAppByName(const QString& name) +{ + if (name == QLatin1String("browser")) { + KConfigGroup config(KSharedConfig::openConfig(), "General"); + QString browser = config.readPathEntry("BrowserApplication", QString()); + + if (browser.isEmpty()) { + return KMimeTypeTrader::self()->preferredService(QLatin1String("text/html")); + } else if (browser.startsWith(QLatin1Char('!'))) { + browser.remove(0, 1); + } + + return KService::serviceByStorageId(browser); + } + + return KService::Ptr(); +} + +AppGroupEntry::AppGroupEntry(AppsModel *parentModel, KServiceGroup::Ptr group, + bool paginate, int pageSize, bool flat, bool sorted, bool separators, int appNameFormat) : AbstractGroupEntry(parentModel), + m_group(group) +{ + AppsModel* model = new AppsModel(group->entryPath(), paginate, pageSize, flat, + sorted, separators, parentModel); + model->setAppNameFormat(appNameFormat); + m_childModel = model; + + QObject::connect(parentModel, &AppsModel::cleared, model, &AppsModel::deleteLater); + + QObject::connect(model, &AppsModel::countChanged, + [parentModel, this] { if (parentModel) { parentModel->entryChanged(this); } } + ); + + QObject::connect(model, &AppsModel::hiddenEntriesChanged, + [parentModel, this] { if (parentModel) { parentModel->entryChanged(this); } } + ); +} + +QIcon AppGroupEntry::icon() const +{ + if (m_icon.isNull()) { + m_icon = QIcon::fromTheme(m_group->icon(), QIcon::fromTheme(QStringLiteral("unknown"))); + } + return m_icon; +} + +QString AppGroupEntry::name() const +{ + return m_group->caption(); +} + +bool AppGroupEntry::hasChildren() const +{ + return m_childModel && m_childModel->count() > 0; +} + +AbstractModel *AppGroupEntry::childModel() const { + return m_childModel; +} diff --git a/applets/kicker/plugin/appentry.h b/applets/kicker/plugin/appentry.h new file mode 100644 index 000000000..48543d45f --- /dev/null +++ b/applets/kicker/plugin/appentry.h @@ -0,0 +1,95 @@ +/*************************************************************************** + * Copyright (C) 201 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 APPENTRY_H +#define APPENTRY_H + +#include "abstractentry.h" + +#include +#include + +class AppsModel; +class MenuEntryEditor; + +class AppEntry : public AbstractEntry +{ + public: + enum NameFormat { + NameOnly = 0, + GenericNameOnly, + NameAndGenericName, + GenericNameAndName + }; + + explicit AppEntry(AbstractModel *owner, KService::Ptr service, NameFormat nameFormat); + explicit AppEntry(AbstractModel *owner, const QString &id); + + EntryType type() const override { return RunnableType; } + + bool isValid() const override; + + QIcon icon() const override; + QString name() const override; + QString description() const override; + KService::Ptr service() const; + + QString id() const override; + QUrl url() const override; + + bool hasActions() const override; + QVariantList actions() const override; + + bool run(const QString& actionId = QString(), const QVariant &argument = QVariant()) override; + + QString menuId() const; + + static QString nameFromService(const KService::Ptr service, NameFormat nameFormat); + static KService::Ptr defaultAppByName(const QString &name); + + private: + void init(NameFormat nameFormat); + + QString m_id; + QString m_name; + QString m_description; + mutable QIcon m_icon; + KService::Ptr m_service; + static MenuEntryEditor *m_menuEntryEditor; +}; + +class AppGroupEntry : public AbstractGroupEntry +{ + public: + AppGroupEntry(AppsModel *parentModel, KServiceGroup::Ptr group, + bool paginate, int pageSize, bool flat, bool sorted, bool separators, int appNameFormat); + + QIcon icon() const override; + QString name() const override; + + bool hasChildren() const override; + AbstractModel *childModel() const override; + + private: + KServiceGroup::Ptr m_group; + mutable QIcon m_icon; + QPointer m_childModel; +}; + +#endif diff --git a/applets/kicker/plugin/appsmodel.cpp b/applets/kicker/plugin/appsmodel.cpp new file mode 100644 index 000000000..b66641a67 --- /dev/null +++ b/applets/kicker/plugin/appsmodel.cpp @@ -0,0 +1,748 @@ +/*************************************************************************** + * Copyright (C) 2012 Aurélien Gâteau * + * Copyright (C) 2013-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 . * + ***************************************************************************/ + +#include "appsmodel.h" +#include "actionlist.h" +#include "rootmodel.h" + +#include +#include +#include +#include + +#include +#include + +AppsModel::AppsModel(const QString &entryPath, bool paginate, int pageSize, bool flat, + bool sorted, bool separators, QObject *parent) +: AbstractModel(parent) +, m_complete(false) +, m_paginate(paginate) +, m_pageSize(pageSize) +, m_deleteEntriesOnDestruction(true) +, m_separatorCount(0) +, m_showSeparators(separators) +, m_showTopLevelItems(false) +, m_appletInterface(nullptr) +, m_autoPopulate(true) +, m_description(i18n("Applications")) +, m_entryPath(entryPath) +, m_staticEntryList(false) +, m_changeTimer(nullptr) +, m_flat(flat) +, m_sorted(sorted) +, m_appNameFormat(AppEntry::NameOnly) +{ + if (!m_entryPath.isEmpty()) { + componentComplete(); + } +} + +AppsModel::AppsModel(const QList entryList, bool deleteEntriesOnDestruction, QObject *parent) +: AbstractModel(parent) +, m_complete(false) +, m_paginate(false) +, m_pageSize(24) +, m_deleteEntriesOnDestruction(deleteEntriesOnDestruction) +, m_separatorCount(0) +, m_showSeparators(false) +, m_showTopLevelItems(false) +, m_appletInterface(nullptr) +, m_autoPopulate(true) +, m_description(i18n("Applications")) +, m_entryPath(QString()) +, m_staticEntryList(true) +, m_changeTimer(nullptr) +, m_flat(true) +, m_sorted(true) +, m_appNameFormat(AppEntry::NameOnly) +{ + foreach(AbstractEntry *suggestedEntry, entryList) { + bool found = false; + + foreach (const AbstractEntry *entry, m_entryList) { + if (entry->type() == AbstractEntry::RunnableType + && static_cast(entry)->service()->storageId() + == static_cast(suggestedEntry)->service()->storageId()) { + found = true; + break; + } + } + + if (!found) { + m_entryList << suggestedEntry; + } + } + + sortEntries(); +} + +AppsModel::~AppsModel() +{ + if (m_deleteEntriesOnDestruction) { + qDeleteAll(m_entryList); + } +} + +bool AppsModel::autoPopulate() const +{ + return m_autoPopulate; +} + +void AppsModel::setAutoPopulate(bool populate) +{ + if (m_autoPopulate != populate) { + m_autoPopulate = populate; + + emit autoPopulateChanged(); + } +} + +QString AppsModel::description() const +{ + return m_description; +} + +void AppsModel::setDescription(const QString &text) +{ + if (m_description != text) { + m_description = text; + + emit descriptionChanged(); + } +} + +QVariant AppsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_entryList.count()) { + return QVariant(); + } + + const AbstractEntry *entry = m_entryList.at(index.row()); + + if (role == Qt::DisplayRole) { + return entry->name(); + } else if (role == Qt::DecorationRole) { + return entry->icon(); + } else if (role == Kicker::DescriptionRole) { + return entry->description(); + } else if (role == Kicker::FavoriteIdRole && entry->type() == AbstractEntry::RunnableType) { + return entry->id(); + } else if (role == Kicker::UrlRole && entry->type() == AbstractEntry::RunnableType) { + return entry->url(); + } else if (role == Kicker::IsParentRole) { + return (entry->type() == AbstractEntry::GroupType); + } else if (role == Kicker::IsSeparatorRole) { + return (entry->type() == AbstractEntry::SeparatorType); + } else if (role == Kicker::HasChildrenRole) { + return entry->hasChildren(); + } else if (role == Kicker::HasActionListRole) { + const AppsModel *appsModel = qobject_cast(entry->childModel()); + + return entry->hasActions() || (appsModel && !appsModel->hiddenEntries().isEmpty()); + } else if (role == Kicker::ActionListRole) { + QVariantList actionList = entry->actions(); + + if (!m_hiddenEntries.isEmpty()) { + actionList << Kicker::createSeparatorActionItem(); + QVariantMap unhideSiblingApplicationsAction = Kicker::createActionItem(i18n("Unhide Applications in this Submenu"), QStringLiteral("unhideSiblingApplications")); + unhideSiblingApplicationsAction[QStringLiteral("icon")] = QStringLiteral("view-visible"); + actionList << unhideSiblingApplicationsAction; + } + + const AppsModel *appsModel = qobject_cast(entry->childModel()); + + if (appsModel && !appsModel->hiddenEntries().isEmpty()) { + QVariantMap unhideChildApplicationsAction = Kicker::createActionItem(i18n("Unhide Applications in '%1'", entry->name()), QStringLiteral("unhideChildApplications")); + unhideChildApplicationsAction[QStringLiteral("icon")] = QStringLiteral("view-visible"); + actionList << unhideChildApplicationsAction; + } + + return actionList; + } + + return QVariant(); +} + +QModelIndex AppsModel::index(int row, int column, const QModelIndex &parent) const +{ + return hasIndex(row, column, parent) ? createIndex(row, column, m_entryList.at(row)) : QModelIndex(); +} + + +int AppsModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : m_entryList.count(); +} + +bool AppsModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + if (row < 0 || row >= m_entryList.count()) { + return false; + } + + AbstractEntry *entry = m_entryList.at(row); + + if (actionId == QLatin1String("hideApplication") && entry->type() == AbstractEntry::RunnableType) { + QObject *appletInterface = rootModel()->property("appletInterface").value(); + QQmlPropertyMap *appletConfig = nullptr; + if (appletInterface) { + appletConfig = qobject_cast(appletInterface->property("configuration").value()); + } + + if (appletConfig && appletConfig->contains(QLatin1String("hiddenApplications"))) { + QStringList hiddenApps = appletConfig->value(QLatin1String("hiddenApplications")).toStringList(); + + KService::Ptr service = static_cast(entry)->service(); + + if (!hiddenApps.contains(service->menuId())) { + hiddenApps << service->menuId(); + + appletConfig->insert(QLatin1String("hiddenApplications"), hiddenApps); + QMetaObject::invokeMethod(appletConfig, "valueChanged", Qt::DirectConnection, + Q_ARG(QString, QStringLiteral("hiddenApplications")), + Q_ARG(QVariant, hiddenApps)); + + refresh(); + + emit hiddenEntriesChanged(); + } + } + + return false; + } else if (actionId == QLatin1String("unhideSiblingApplications")) { + QObject *appletInterface = rootModel()->property("appletInterface").value(); + QQmlPropertyMap *appletConfig = nullptr; + if (appletInterface) { + appletConfig = qobject_cast(appletInterface->property("configuration").value()); + } + + if (appletConfig && appletConfig->contains(QLatin1String("hiddenApplications"))) { + QStringList hiddenApps = appletConfig->value(QLatin1String("hiddenApplications")).toStringList(); + + foreach(const QString& app, m_hiddenEntries) { + hiddenApps.removeOne(app); + } + + appletConfig->insert(QStringLiteral("hiddenApplications"), hiddenApps); + QMetaObject::invokeMethod(appletConfig, "valueChanged", Qt::DirectConnection, + Q_ARG(QString, QStringLiteral("hiddenApplications")), + Q_ARG(QVariant, hiddenApps)); + + m_hiddenEntries.clear(); + + refresh(); + + emit hiddenEntriesChanged(); + } + + return false; + } else if (actionId == QLatin1String("unhideChildApplications")) { + QObject *appletInterface = rootModel()->property("appletInterface").value(); + QQmlPropertyMap *appletConfig = nullptr; + if (appletInterface) { + appletConfig = qobject_cast(appletInterface->property("configuration").value()); + } + + if (entry->type() == AbstractEntry::GroupType + && appletConfig && appletConfig->contains(QLatin1String("hiddenApplications"))) { + + const AppsModel *appsModel = qobject_cast(entry->childModel()); + + if (!appsModel) { + return false; + } + + QStringList hiddenApps = appletConfig->value(QLatin1String("hiddenApplications")).toStringList(); + + foreach(const QString& app, appsModel->hiddenEntries()) { + hiddenApps.removeOne(app); + } + + appletConfig->insert(QStringLiteral("hiddenApplications"), hiddenApps); + QMetaObject::invokeMethod(appletConfig, "valueChanged", Qt::DirectConnection, + Q_ARG(QString, QStringLiteral("hiddenApplications")), + Q_ARG(QVariant, hiddenApps)); + + refresh(); + + emit hiddenEntriesChanged(); + } + + return false; + } + + return entry->run(actionId, argument); +} + +AbstractModel *AppsModel::modelForRow(int row) +{ + if (row < 0 || row >= m_entryList.count()) { + return nullptr; + } + + return m_entryList.at(row)->childModel(); +} + +int AppsModel::rowForModel(AbstractModel *model) +{ + for (int i = 0; i < m_entryList.count(); ++i) { + if (m_entryList.at(i)->childModel() == model) { + return i; + } + } + + return -1; +} + +int AppsModel::separatorCount() const +{ + return m_separatorCount; +} + +bool AppsModel::paginate() const +{ + return m_paginate; +} + +void AppsModel::setPaginate(bool paginate) +{ + if (m_paginate != paginate) { + m_paginate = paginate; + + refresh(); + + emit paginateChanged(); + } +} + +int AppsModel::pageSize() const +{ + return m_pageSize; +} + +void AppsModel::setPageSize(int size) +{ + if (m_pageSize != size) { + m_pageSize = size; + + refresh(); + + emit pageSizeChanged(); + } +} + +bool AppsModel::flat() const +{ + return m_flat; +} + +void AppsModel::setFlat(bool flat) +{ + if (m_flat != flat) { + m_flat = flat; + + refresh(); + + emit flatChanged(); + } +} + +bool AppsModel::sorted() const +{ + return m_sorted; +} + +void AppsModel::setSorted(bool sorted) +{ + if (m_sorted != sorted) { + m_sorted = sorted; + + refresh(); + + emit sortedChanged(); + } +} + +bool AppsModel::showSeparators() const +{ + return m_showSeparators; +} + +void AppsModel::setShowSeparators(bool showSeparators) { + if (m_showSeparators != showSeparators) { + m_showSeparators = showSeparators; + + refresh(); + + emit showSeparatorsChanged(); + } +} + +bool AppsModel::showTopLevelItems() const +{ + return m_showTopLevelItems; +} + +void AppsModel::setShowTopLevelItems(bool showTopLevelItems) { + if (m_showTopLevelItems != showTopLevelItems) { + m_showTopLevelItems = showTopLevelItems; + + refresh(); + + emit showTopLevelItemsChanged(); + } +} + +int AppsModel::appNameFormat() const +{ + return m_appNameFormat; +} + +void AppsModel::setAppNameFormat(int format) +{ + if (m_appNameFormat != (AppEntry::NameFormat)format) { + m_appNameFormat = (AppEntry::NameFormat)format; + + refresh(); + + emit appNameFormatChanged(); + } +} + +QObject* AppsModel::appletInterface() const +{ + return m_appletInterface; +} + +void AppsModel::setAppletInterface(QObject* appletInterface) +{ + if (m_appletInterface != appletInterface) { + m_appletInterface = appletInterface; + + refresh(); + + emit appletInterfaceChanged(); + } +} + +QStringList AppsModel::hiddenEntries() const +{ + return m_hiddenEntries; +} + +void AppsModel::refresh() +{ + if (!m_complete) { + return; + } + + if (m_staticEntryList) { + return; + } + + if (rootModel() == this && !m_appletInterface) { + return; + } + + beginResetModel(); + + refreshInternal(); + + endResetModel(); + + if (favoritesModel()) { + favoritesModel()->refresh(); + } + + emit countChanged(); + emit separatorCountChanged(); +} + +void AppsModel::refreshInternal() +{ + if (m_staticEntryList) { + return; + } + + if (m_entryList.count()) { + qDeleteAll(m_entryList); + m_entryList.clear(); + emit cleared(); + } + + m_hiddenEntries.clear(); + m_separatorCount = 0; + + if (m_entryPath.isEmpty()) { + KServiceGroup::Ptr group = KServiceGroup::root(); + if (!group) { + return; + } + + bool sortByGenericName = (appNameFormat() == AppEntry::GenericNameOnly || appNameFormat() == AppEntry::GenericNameAndName); + + KServiceGroup::List list = group->entries(true /* sorted */, true /* excludeNoDisplay */, + true /* allowSeparators */, sortByGenericName /* sortByGenericName */); + + for (KServiceGroup::List::ConstIterator it = list.constBegin(); it != list.constEnd(); it++) { + const KSycocaEntry::Ptr p = (*it); + + if (p->isType(KST_KServiceGroup)) { + KServiceGroup::Ptr subGroup(static_cast(p.data())); + + if (!subGroup->noDisplay() && subGroup->childCount() > 0) { + AppGroupEntry *groupEntry = new AppGroupEntry(this, subGroup, m_paginate, m_pageSize, m_flat, + m_sorted, m_showSeparators, m_appNameFormat); + m_entryList << groupEntry; + } + } else if (p->isType(KST_KService) && m_showTopLevelItems) { + const KService::Ptr service(static_cast(p.data())); + + if (service->noDisplay()) { + continue; + } + + bool found = false; + + foreach (const AbstractEntry *entry, m_entryList) { + if (entry->type() == AbstractEntry::RunnableType + && static_cast(entry)->service()->storageId() == service->storageId()) { + found = true; + } + } + + if (!found) { + m_entryList << new AppEntry(this, service, m_appNameFormat); + } + } else if (p->isType(KST_KServiceSeparator) && m_showSeparators && m_showTopLevelItems) { + if (!m_entryList.count()) { + continue; + } + + if (m_entryList.last()->type() == AbstractEntry::SeparatorType) { + continue; + } + + m_entryList << new SeparatorEntry(this); + ++m_separatorCount; + } + } + + if (m_entryList.count()) { + while (m_entryList.last()->type() == AbstractEntry::SeparatorType) { + m_entryList.removeLast(); + --m_separatorCount; + } + } + + if (m_sorted) { + sortEntries(); + } + + m_changeTimer = new QTimer(this); + m_changeTimer->setSingleShot(true); + m_changeTimer->setInterval(100); + connect(m_changeTimer, SIGNAL(timeout()), this, SLOT(refresh())); + + connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), SLOT(checkSycocaChanges(QStringList))); + } else { + KServiceGroup::Ptr group = KServiceGroup::group(m_entryPath); + processServiceGroup(group); + + if (m_entryList.count()) { + while (m_entryList.last()->type() == AbstractEntry::SeparatorType) { + m_entryList.removeLast(); + --m_separatorCount; + } + } + + if (m_sorted) { + sortEntries(); + } + + if (m_paginate) { + QList groups; + + int at = 0; + QList page; + + foreach(AbstractEntry *app, m_entryList) { + page.append(app); + + if (at == (m_pageSize - 1)) { + at = 0; + AppsModel *model = new AppsModel(page, true, this); + groups.append(new GroupEntry(this, QString(), QString(), model)); + page.clear(); + } else { + ++at; + } + } + + if (page.count()) { + AppsModel *model = new AppsModel(page, true, this); + groups.append(new GroupEntry(this, QString(), QString(), model)); + } + + m_entryList = groups; + } + } +} + +void AppsModel::processServiceGroup(KServiceGroup::Ptr group) +{ + if (!group || !group->isValid()) { + return; + } + + bool hasSubGroups = false; + + foreach(KServiceGroup::Ptr subGroup, group->groupEntries(KServiceGroup::ExcludeNoDisplay)) { + if (subGroup->childCount() > 0) { + hasSubGroups = true; + + break; + } + } + + bool sortByGenericName = (appNameFormat() == AppEntry::GenericNameOnly || appNameFormat() == AppEntry::GenericNameAndName); + + KServiceGroup::List list = group->entries(true /* sorted */, + true /* excludeNoDisplay */, + (!m_flat || (m_flat && !hasSubGroups)) /* allowSeparators */, + sortByGenericName /* sortByGenericName */); + + QStringList hiddenApps; + + QObject *appletInterface = rootModel()->property("appletInterface").value(); + QQmlPropertyMap *appletConfig = nullptr; + if (appletInterface) { + appletConfig = qobject_cast(appletInterface->property("configuration").value()); + } + if (appletConfig && appletConfig->contains(QLatin1String("hiddenApplications"))) { + hiddenApps = appletConfig->value(QLatin1String("hiddenApplications")).toStringList(); + } + + for (KServiceGroup::List::ConstIterator it = list.constBegin(); + it != list.constEnd(); it++) { + const KSycocaEntry::Ptr p = (*it); + + if (p->isType(KST_KService)) { + const KService::Ptr service(static_cast(p.data())); + + if (service->noDisplay()) { + continue; + } + + if (hiddenApps.contains(service->menuId())) { + m_hiddenEntries << service->menuId(); + + continue; + } + + bool found = false; + + foreach (const AbstractEntry *entry, m_entryList) { + if (entry->type() == AbstractEntry::RunnableType + && static_cast(entry)->service()->storageId() == service->storageId()) { + found = true; + break; + } + } + + if (!found) { + m_entryList << new AppEntry(this, service, m_appNameFormat); + } + } else if (p->isType(KST_KServiceSeparator) && m_showSeparators) { + if (!m_entryList.count()) { + continue; + } + + if (m_entryList.last()->type() == AbstractEntry::SeparatorType) { + continue; + } + + m_entryList << new SeparatorEntry(this); + ++m_separatorCount; + } else if (p->isType(KST_KServiceGroup)) { + const KServiceGroup::Ptr subGroup(static_cast(p.data())); + + if (subGroup->childCount() == 0) { + continue; + } + + if (m_flat) { + m_sorted = true; + const KServiceGroup::Ptr serviceGroup(static_cast(p.data())); + processServiceGroup(serviceGroup); + } else { + AppGroupEntry *groupEntry = new AppGroupEntry(this, subGroup, m_paginate, m_pageSize, m_flat, + m_sorted, m_showSeparators, m_appNameFormat); + m_entryList << groupEntry; + } + } + } +} + +void AppsModel::sortEntries() +{ + QCollator c; + + std::sort(m_entryList.begin(), m_entryList.end(), + [&c](AbstractEntry* a, AbstractEntry* b) { + if (a->type() != b->type()) { + return a->type() > b->type(); + } else { + return c.compare(a->name(), b->name()) < 0; + } + }); +} + +void AppsModel::checkSycocaChanges(const QStringList &changes) +{ + if (changes.contains(QLatin1String("services")) || changes.contains(QLatin1String("apps")) || changes.contains(QLatin1String("xdgdata-apps"))) { + m_changeTimer->start(); + } +} + +void AppsModel::entryChanged(AbstractEntry *entry) +{ + int i = m_entryList.indexOf(entry); + + if (i != -1) { + QModelIndex idx = index(i, 0); + emit dataChanged(idx, idx); + } +} + +void AppsModel::classBegin() +{ + +} + +void AppsModel::componentComplete() +{ + m_complete = true; + + if (m_autoPopulate) { + refresh(); + } +} diff --git a/applets/kicker/plugin/appsmodel.h b/applets/kicker/plugin/appsmodel.h new file mode 100644 index 000000000..97823b475 --- /dev/null +++ b/applets/kicker/plugin/appsmodel.h @@ -0,0 +1,158 @@ +/*************************************************************************** + * Copyright (C) 2012 Aurélien Gâteau * + * Copyright (C) 2013-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 APPSMODEL_H +#define APPSMODEL_H + +#include "abstractmodel.h" +#include "appentry.h" + +#include + +#include + + +class QTimer; + +class AppsModel : public AbstractModel, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(bool autoPopulate READ autoPopulate WRITE setAutoPopulate NOTIFY autoPopulateChanged) + + Q_PROPERTY(bool paginate READ paginate WRITE setPaginate NOTIFY paginateChanged) + Q_PROPERTY(int pageSize READ pageSize WRITE setPageSize NOTIFY pageSizeChanged) + Q_PROPERTY(bool flat READ flat WRITE setFlat NOTIFY flatChanged) + Q_PROPERTY(bool sorted READ sorted WRITE setSorted NOTIFY sortedChanged) + Q_PROPERTY(bool showSeparators READ showSeparators WRITE setShowSeparators NOTIFY showSeparatorsChanged) + Q_PROPERTY(bool showTopLevelItems READ showTopLevelItems WRITE setShowTopLevelItems NOTIFY showTopLevelItemsChanged) + Q_PROPERTY(int appNameFormat READ appNameFormat WRITE setAppNameFormat NOTIFY appNameFormatChanged) + Q_PROPERTY(QObject* appletInterface READ appletInterface WRITE setAppletInterface NOTIFY appletInterfaceChanged) + + public: + explicit AppsModel(const QString &entryPath = QString(), bool paginate = false, int pageSize = 24, + bool flat = false, bool sorted = true, bool separators = true, QObject *parent = nullptr); + explicit AppsModel(const QList entryList, bool deleteEntriesOnDestruction, QObject *parent = nullptr); + ~AppsModel() override; + + QString description() const override; + void setDescription(const QString &text); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + bool autoPopulate() const; + void setAutoPopulate(bool populate); + + Q_INVOKABLE AbstractModel *modelForRow(int row) override; + Q_INVOKABLE int rowForModel(AbstractModel *model) override; + + int separatorCount() const override; + + bool paginate() const; + void setPaginate(bool paginate); + + int pageSize() const; + void setPageSize(int size); + + bool flat() const; + void setFlat(bool flat); + + bool sorted() const; + void setSorted(bool sorted); + + bool showSeparators() const; + void setShowSeparators(bool showSeparators); + + bool showTopLevelItems() const; + void setShowTopLevelItems(bool showTopLevelItems); + + int appNameFormat() const; + void setAppNameFormat(int format); + + QObject *appletInterface() const; + void setAppletInterface(QObject *appletInterface); + + QStringList hiddenEntries() const; + + void entryChanged(AbstractEntry *entry) override; + + void classBegin() override; + void componentComplete() override; + + Q_SIGNALS: + void cleared() const; + void autoPopulateChanged() const; + void paginateChanged() const; + void pageSizeChanged() const; + void flatChanged() const; + void sortedChanged() const; + void showSeparatorsChanged() const; + void showTopLevelItemsChanged() const; + void appNameFormatChanged() const; + void appletInterfaceChanged() const; + void hiddenEntriesChanged() const; + + protected Q_SLOTS: + void refresh() override; + + protected: + void refreshInternal(); + + bool m_complete; + + bool m_paginate; + int m_pageSize; + + QList m_entryList; + bool m_deleteEntriesOnDestruction; + int m_separatorCount; + bool m_showSeparators; + bool m_showTopLevelItems; + + QObject *m_appletInterface; + + private Q_SLOTS: + void checkSycocaChanges(const QStringList &changes); + + private: + void processServiceGroup(KServiceGroup::Ptr group); + void sortEntries(); + + bool m_autoPopulate; + + QString m_description; + QString m_entryPath; + bool m_staticEntryList; + QTimer *m_changeTimer; + bool m_flat; + bool m_sorted; + AppEntry::NameFormat m_appNameFormat; + QStringList m_hiddenEntries; + static MenuEntryEditor *m_menuEntryEditor; +}; + +#endif diff --git a/applets/kicker/plugin/computermodel.cpp b/applets/kicker/plugin/computermodel.cpp new file mode 100644 index 000000000..7227174d4 --- /dev/null +++ b/applets/kicker/plugin/computermodel.cpp @@ -0,0 +1,301 @@ +/*************************************************************************** + * Copyright (C) 2007 Kevin Ottens * + * 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 . * + ***************************************************************************/ + +#include "computermodel.h" +#include "actionlist.h" +#include "simplefavoritesmodel.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "krunner_interface.h" + +FilteredPlacesModel::FilteredPlacesModel(QObject *parent) : QSortFilterProxyModel(parent) +, m_placesModel(new KFilePlacesModel(this)) +{ + setSourceModel(m_placesModel); + sort(0); +} + +FilteredPlacesModel::~FilteredPlacesModel() +{ +} + +QUrl FilteredPlacesModel::url(const QModelIndex &index) const +{ + return KFilePlacesModel::convertedUrl(m_placesModel->url(mapToSource(index))); +} + +bool FilteredPlacesModel::isDevice(const QModelIndex &index) const +{ + return m_placesModel->isDevice(mapToSource(index)); +} + +Solid::Device FilteredPlacesModel::deviceForIndex(const QModelIndex &index) const +{ + return m_placesModel->deviceForIndex(mapToSource(index)); +} + +bool FilteredPlacesModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + const QModelIndex index = m_placesModel->index(sourceRow, 0, sourceParent); + + return !m_placesModel->isHidden(index) + && !m_placesModel->data(index, KFilePlacesModel::FixedDeviceRole).toBool(); +} + +bool FilteredPlacesModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + bool lDevice = m_placesModel->isDevice(left); + bool rDevice = m_placesModel->isDevice(right); + + if (lDevice && !rDevice) { + return false; + } else if (!lDevice && rDevice) { + return true; + } + + return (left.row() < right.row()); +} + +RunCommandModel::RunCommandModel(QObject *parent) : AbstractModel(parent) +{ +} + +RunCommandModel::~RunCommandModel() +{ +} + +QString RunCommandModel::description() const +{ + return QString(); +} + +QVariant RunCommandModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return i18n("Show KRunner"); + } else if (role == Qt::DecorationRole) { + return QIcon::fromTheme(QStringLiteral("plasma-search")); + } else if (role == Kicker::DescriptionRole) { + return i18n("Search, calculate, or run a command"); + } else if (role == Kicker::GroupRole) { + return i18n("Applications"); + } + + return QVariant(); +} + +int RunCommandModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : (KAuthorized::authorize(QStringLiteral("run_command")) ? 1 : 0); +} + +Q_INVOKABLE bool RunCommandModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + Q_UNUSED(actionId) + Q_UNUSED(argument) + + if (row == 0 && KAuthorized::authorize(QStringLiteral("run_command"))) { + org::kde::krunner::App krunner(QStringLiteral("org.kde.krunner"), + QStringLiteral("/App"), QDBusConnection::sessionBus()); + krunner.display(); + + return true; + } + + return false; +} + +ComputerModel::ComputerModel(QObject *parent) : ForwardingModel(parent) +, m_concatProxy(new KConcatenateRowsProxyModel(this)) +, m_runCommandModel(new RunCommandModel(this)) +, m_systemAppsModel(new SimpleFavoritesModel(this)) +, m_filteredPlacesModel(new FilteredPlacesModel(this)) +, m_appNameFormat(AppEntry::NameOnly) +, m_appletInterface(nullptr) +{ + connect(m_systemAppsModel, &SimpleFavoritesModel::favoritesChanged, this, &ComputerModel::systemApplicationsChanged); + m_systemAppsModel->setFavorites(QStringList() << QStringLiteral("systemsettings.desktop")); + + m_concatProxy->addSourceModel(m_runCommandModel); + m_concatProxy->addSourceModel(m_systemAppsModel); + m_concatProxy->addSourceModel(m_filteredPlacesModel); + + setSourceModel(m_concatProxy); +} + +ComputerModel::~ComputerModel() +{ +} + +QString ComputerModel::description() const +{ + return i18n("Computer"); +} + +int ComputerModel::appNameFormat() const +{ + return m_appNameFormat; +} + +void ComputerModel::setAppNameFormat(int format) +{ + if (m_appNameFormat != (AppEntry::NameFormat)format) { + m_appNameFormat = (AppEntry::NameFormat)format; + + m_systemAppsModel->refresh(); + + emit appNameFormatChanged(); + } +} + +QObject *ComputerModel::appletInterface() const +{ + return m_appletInterface; +} + +void ComputerModel::setAppletInterface(QObject *appletInterface) +{ + if (m_appletInterface != appletInterface) { + m_appletInterface = appletInterface; + + emit appletInterfaceChanged(); + } +} + +QStringList ComputerModel::systemApplications() const +{ + return m_systemAppsModel->favorites(); +} + +void ComputerModel::setSystemApplications(const QStringList &apps) +{ + m_systemAppsModel->setFavorites(apps); +} + +QVariant ComputerModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + const QModelIndex sourceIndex = m_concatProxy->mapToSource(m_concatProxy->index(index.row(), + index.column())); + + bool isPlace = (sourceIndex.model() == m_filteredPlacesModel); + + if (isPlace) { + if (role == Kicker::DescriptionRole) { + if (m_filteredPlacesModel->isDevice(sourceIndex)) { + Solid::Device device = m_filteredPlacesModel->deviceForIndex(sourceIndex); + Solid::StorageAccess *access = device.as(); + + if (access) { + return access->filePath(); + } else { + return QString(); + } + } else { + const QUrl &url = m_filteredPlacesModel->url(sourceIndex); + return url.toString(QUrl::PreferLocalFile); + } + } else if (role == Kicker::FavoriteIdRole) { + if (!m_filteredPlacesModel->isDevice(sourceIndex)) { + return m_filteredPlacesModel->url(sourceIndex); + } + } else if (role == Kicker::UrlRole) { + return m_filteredPlacesModel->url(sourceIndex); + } else if (role == Kicker::GroupRole) { + return sourceIndex.data(KFilePlacesModel::GroupRole).toString(); + } else if (role == Qt::DisplayRole || role == Qt::DecorationRole) { + return sourceIndex.data(role); + } + } else if (role == Kicker::GroupRole) { + return i18n("Applications"); + } else { + return sourceIndex.data(role); + } + + return QVariant(); +} + +bool ComputerModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + const QModelIndex sourceIndex = m_concatProxy->mapToSource(m_concatProxy->index(row, 0)); + + if (sourceIndex.model() == m_filteredPlacesModel) { + const QUrl &url = m_filteredPlacesModel->url(sourceIndex); + + if (url.isValid()) { + new KRun(url, nullptr); + + return true; + } + + Solid::Device device = m_filteredPlacesModel->deviceForIndex(sourceIndex); + Solid::StorageAccess *access = device.as(); + + if (access && !access->isAccessible()) { + connect(access, &Solid::StorageAccess::setupDone, this, &ComputerModel::onSetupDone); + access->setup(); + + return true; + } + } else { + AbstractModel *model = nullptr; + + if (sourceIndex.model() == m_systemAppsModel) { + model = m_systemAppsModel; + } else { + model = m_runCommandModel; + } + + return model->trigger(sourceIndex.row(), actionId, argument); + } + + return false; +} + +void ComputerModel::onSetupDone(Solid::ErrorType error, QVariant errorData, const QString &udi) +{ + Q_UNUSED(errorData); + + if (error != Solid::NoError) { + return; + } + + Solid::Device device(udi); + Solid::StorageAccess *access = device.as(); + + Q_ASSERT(access); + + new KRun(QUrl::fromLocalFile(access->filePath()), nullptr); +} diff --git a/applets/kicker/plugin/computermodel.h b/applets/kicker/plugin/computermodel.h new file mode 100644 index 000000000..e0af12290 --- /dev/null +++ b/applets/kicker/plugin/computermodel.h @@ -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 SimpleFavoritesModel; + +class KConcatenateRowsProxyModel; +class KFilePlacesModel; + +namespace Solid { + class Device; +} + +class FilteredPlacesModel : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + explicit FilteredPlacesModel(QObject *parent = nullptr); + ~FilteredPlacesModel() override; + + 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 override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + + private: + KFilePlacesModel *m_placesModel; +}; + +class RunCommandModel : public AbstractModel +{ + Q_OBJECT + + public: + RunCommandModel(QObject *parent = nullptr); + ~RunCommandModel() override; + + QString description() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) 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 = nullptr); + ~ComputerModel() override; + + QString description() const 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 override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) 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; + SimpleFavoritesModel *m_systemAppsModel; + FilteredPlacesModel *m_filteredPlacesModel; + AppEntry::NameFormat m_appNameFormat; + QObject *m_appletInterface; +}; + +#endif diff --git a/applets/kicker/plugin/contactentry.cpp b/applets/kicker/plugin/contactentry.cpp new file mode 100644 index 000000000..cca2def2b --- /dev/null +++ b/applets/kicker/plugin/contactentry.cpp @@ -0,0 +1,143 @@ +/*************************************************************************** + * Copyright (C) 201 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 . * + ***************************************************************************/ + +#include "contactentry.h" +#include "actionlist.h" + +#include +#include + +#include +#include + +#include +#include +#include + +ContactEntry::ContactEntry(AbstractModel *owner, const QString &id) : AbstractEntry(owner) +, m_personData(nullptr) +{ + if (!id.isEmpty()) { + m_personData = new KPeople::PersonData(id); + + QObject::connect(m_personData, &KPeople::PersonData::dataChanged, + [this] { if (m_owner) { m_owner->entryChanged(this); } } + ); + } +} + +bool ContactEntry::isValid() const { + return m_personData; +} + +QIcon ContactEntry::icon() const +{ + if (m_personData) { + QPixmap photo = m_personData->photo(); + QBitmap mask(photo.size()); + QPainter painter(&mask); + mask.fill(Qt::white); + painter.setBrush(Qt::black); + painter.drawEllipse(0, 0, mask.width(), mask.height()); + photo.setMask(mask); + + photo = photo.scaled(m_owner->iconSize(), m_owner->iconSize(), + Qt::KeepAspectRatio, Qt::SmoothTransformation); + + KIconLoader::global()->drawOverlays(QStringList() << m_personData->presenceIconName(), photo, KIconLoader::Panel); + + return QIcon(photo); + } + + return QIcon::fromTheme(QStringLiteral("unknown")); +} + +QString ContactEntry::name() const +{ + if (m_personData) { + return m_personData->name(); + } + + return QString(); +} + +QString ContactEntry::id() const +{ + if (m_personData) { + const QString &id = m_personData->personUri(); + + if (id.isEmpty()) { + const QStringList uris = m_personData->contactUris(); + + if (!uris.isEmpty()) { + return uris.at(0); + } + } else { + return id; + } + } + + return QString(); +} + +QUrl ContactEntry::url() const +{ + if (m_personData) { + return QUrl(m_personData->personUri()); + } + + return QUrl(); +} + +bool ContactEntry::hasActions() const +{ + return m_personData; +} + +QVariantList ContactEntry::actions() const +{ + QVariantList actionList; + + actionList << Kicker::createActionItem(i18n("Show Contact Information..."), QStringLiteral("showContactInfo")); + + return actionList; +} + +bool ContactEntry::run(const QString& actionId, const QVariant &argument) +{ + Q_UNUSED(argument) + + if (!m_personData) { + return false; + } + + if (actionId == QLatin1String("showContactInfo")) { + showPersonDetailsDialog(m_personData->personUri()); + } + + return false; +} + +void ContactEntry::showPersonDetailsDialog(const QString &id) { + KPeople::PersonDetailsDialog *view = new KPeople::PersonDetailsDialog(nullptr); + KPeople::PersonData *data = new KPeople::PersonData(id, view); + view->setPerson(data); + view->setAttribute(Qt::WA_DeleteOnClose); + view->show(); +} diff --git a/applets/kicker/plugin/contactentry.h b/applets/kicker/plugin/contactentry.h new file mode 100644 index 000000000..8e8eb76ad --- /dev/null +++ b/applets/kicker/plugin/contactentry.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * Copyright (C) 201 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 CONTACTENTRY_H +#define CONTACTENTRY_H + +#include "abstractentry.h" + +namespace KPeople { + class PersonData; +} + +class ContactEntry : public AbstractEntry +{ + public: + explicit ContactEntry(AbstractModel *owner, const QString &id); + + EntryType type() const override { return RunnableType; } + + bool isValid() const override; + + QIcon icon() const override; + QString name() const override; + + QString id() const override; + QUrl url() const override; + + bool hasActions() const override; + QVariantList actions() const override; + + bool run(const QString& actionId = QString(), const QVariant &argument = QVariant()) override; + + static void showPersonDetailsDialog(const QString &id); + + private: + KPeople::PersonData *m_personData; +}; + +#endif diff --git a/applets/kicker/plugin/containmentinterface.cpp b/applets/kicker/plugin/containmentinterface.cpp new file mode 100644 index 000000000..e01a97f8e --- /dev/null +++ b/applets/kicker/plugin/containmentinterface.cpp @@ -0,0 +1,252 @@ +/*************************************************************************** + * Copyright (C) 2014 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 . * + ***************************************************************************/ + +#include "containmentinterface.h" + +#include +#include +#include + +#include + +#include + +// FIXME HACK TODO: Unfortunately we have no choice but to hard-code a list of +// applets we know to expose the correct interface right now -- this is slated +// for replacement with some form of generic service. +QStringList ContainmentInterface::m_knownTaskManagers = QStringList() << QLatin1String("org.kde.plasma.taskmanager") + << QLatin1String("org.kde.plasma.icontasks") + << QLatin1String("org.kde.plasma.expandingiconstaskmanager"); + +ContainmentInterface::ContainmentInterface(QObject *parent) : QObject(parent) +{ +} + +ContainmentInterface::~ContainmentInterface() +{ +} + +bool ContainmentInterface::mayAddLauncher(QObject *appletInterface, ContainmentInterface::Target target, const QString &entryPath) +{ + if (!appletInterface) { + return false; + } + + Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); + Plasma::Containment *containment = applet->containment(); + + if (!containment) { + return false; + } + + Plasma::Corona *corona = containment->corona(); + + if (!corona) { + return false; + } + + switch (target) { + case Desktop: { + containment = corona->containmentForScreen(containment->screen()); + + if (containment) { + return (containment->immutability() == Plasma::Types::Mutable); + } + + break; + } + case Panel: { + if (containment->pluginMetaData().pluginId() == QLatin1String("org.kde.panel")) + { + return (containment->immutability() == Plasma::Types::Mutable); + } + + break; + } + case TaskManager: { + if (!entryPath.isEmpty() && containment->pluginMetaData().pluginId() == QLatin1String("org.kde.panel")) + { + const Plasma::Applet *taskManager = nullptr; + + foreach(const Plasma::Applet *applet, containment->applets()) { + if (m_knownTaskManagers.contains(applet->pluginMetaData().pluginId())) { + taskManager = applet; + + break; + } + } + + if (taskManager) { + QQuickItem* gObj = qobject_cast(taskManager->property("_plasma_graphicObject").value()); + + if (!gObj || !gObj->childItems().count()) { + return false; + } + + QQuickItem *rootItem = gObj->childItems().first(); + + QVariant ret; + + QMetaObject::invokeMethod(rootItem, "hasLauncher", Q_RETURN_ARG(QVariant, ret), + Q_ARG(QVariant, QUrl::fromLocalFile(entryPath))); + + return !ret.toBool(); + } + } + + break; + } + } + + return false; +} + +void ContainmentInterface::addLauncher(QObject *appletInterface, ContainmentInterface::Target target, const QString &entryPath) +{ + if (!appletInterface) { + return; + } + + Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); + Plasma::Containment *containment = applet->containment(); + + if (!containment) { + return; + } + + Plasma::Corona *corona = containment->corona(); + + if (!corona) { + return; + } + + switch (target) { + case Desktop: { + containment = corona->containmentForScreen(containment->screen()); + + if (!containment) { + return; + } + + const QStringList &containmentProvides = KPluginMetaData::readStringList(containment->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides")); + + if (containmentProvides.contains(QLatin1String("org.kde.plasma.filemanagement"))) { + QQuickItem* gObj = qobject_cast(containment->property("_plasma_graphicObject").value()); + + if (!gObj || !gObj->childItems().count()) { + return; + } + + QQuickItem *rootItem = nullptr; + + foreach(QQuickItem *item, gObj->childItems()) { + if (item->objectName() == QLatin1String("folder")) { + rootItem = item; + + break; + } + } + + if (rootItem) { + QMetaObject::invokeMethod(rootItem, "addLauncher", Q_ARG(QVariant, QUrl::fromLocalFile(entryPath))); + } + } else { + containment->createApplet(QStringLiteral("org.kde.plasma.icon"), QVariantList() << entryPath); + } + + break; + } + case Panel: { + if (containment->pluginMetaData().pluginId() == QLatin1String("org.kde.panel")) + { + containment->createApplet(QStringLiteral("org.kde.plasma.icon"), QVariantList() << entryPath); + } + + break; + } + case TaskManager: { + if (containment->pluginMetaData().pluginId() == QLatin1String("org.kde.panel")) + { + const Plasma::Applet *taskManager = nullptr; + + foreach(const Plasma::Applet *applet, containment->applets()) { + if (m_knownTaskManagers.contains(applet->pluginMetaData().pluginId())) { + taskManager = applet; + + break; + } + } + + if (taskManager) { + QQuickItem* gObj = qobject_cast(taskManager->property("_plasma_graphicObject").value()); + + if (!gObj || !gObj->childItems().count()) { + return; + } + + QQuickItem *rootItem = gObj->childItems().first(); + + QMetaObject::invokeMethod(rootItem, "addLauncher", Q_ARG(QVariant, QUrl::fromLocalFile(entryPath))); + } + } + + break; + } + } +} + +QObject* ContainmentInterface::screenContainment(QObject *appletInterface) +{ + if (!appletInterface) { + return nullptr; + } + + const Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); + Plasma::Containment *containment = applet->containment(); + + if (!containment) { + return nullptr; + } + + Plasma::Corona *corona = containment->corona(); + + if (!corona) { + return nullptr; + } + + return corona->containmentForScreen(containment->screen()); +} + +bool ContainmentInterface::screenContainmentMutable(QObject *appletInterface) +{ + const Plasma::Containment *containment = static_cast(screenContainment(appletInterface)); + + if (containment) { + return (containment->immutability() == Plasma::Types::Mutable); + } + + return false; +} + +void ContainmentInterface::ensureMutable(Plasma::Containment *containment) +{ + if (containment && containment->immutability() != Plasma::Types::Mutable) { + containment->actions()->action(QStringLiteral("lock widgets"))->trigger(); + } +} diff --git a/applets/kicker/plugin/containmentinterface.h b/applets/kicker/plugin/containmentinterface.h new file mode 100644 index 000000000..5a8c3375b --- /dev/null +++ b/applets/kicker/plugin/containmentinterface.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (C) 2014 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 CONTAINMENTINTERFACE_H +#define CONTAINMENTINTERFACE_H + +#include +#include + +namespace Plasma { + class Containment; +} + +class ContainmentInterface : public QObject +{ + Q_OBJECT + + public: + enum Target { + Desktop = 0, + Panel, + TaskManager + }; + + Q_ENUM(Target) + + explicit ContainmentInterface(QObject *parent = nullptr); + ~ContainmentInterface() override; + + static Q_INVOKABLE bool mayAddLauncher(QObject *appletInterface, Target target, const QString &entryPath = QString()); + + static Q_INVOKABLE void addLauncher(QObject *appletInterface, Target target, const QString &entryPath); + + static Q_INVOKABLE QObject* screenContainment(QObject *appletInterface); + static Q_INVOKABLE bool screenContainmentMutable(QObject *appletInterface); + static Q_INVOKABLE void ensureMutable(Plasma::Containment *containment); + + private: + static QStringList m_knownTaskManagers; +}; + +#endif diff --git a/applets/kicker/plugin/dashboardwindow.cpp b/applets/kicker/plugin/dashboardwindow.cpp new file mode 100644 index 000000000..6665e8df7 --- /dev/null +++ b/applets/kicker/plugin/dashboardwindow.cpp @@ -0,0 +1,235 @@ +/*************************************************************************** + * 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 . * + ***************************************************************************/ + +#include "dashboardwindow.h" + +#include +#include +#include + +#include +#include + +DashboardWindow::DashboardWindow(QQuickItem *parent) : QQuickWindow(parent ? parent->window() : nullptr) +, m_mainItem(nullptr) +, m_visualParentItem(nullptr) +, m_visualParentWindow(nullptr) +{ + setClearBeforeRendering(true); + setFlags(Qt::FramelessWindowHint); + + setIcon(QIcon::fromTheme(QStringLiteral("plasma"))); + + connect(&m_theme, &Plasma::Theme::themeChanged, this, &DashboardWindow::updateTheme); +} + +DashboardWindow::~DashboardWindow() +{ +} + +QQuickItem *DashboardWindow::mainItem() const +{ + return m_mainItem; +} + +void DashboardWindow::setMainItem(QQuickItem *item) +{ + if (m_mainItem != item) { + if (m_mainItem) { + m_mainItem->setVisible(false); + } + + m_mainItem = item; + + if (m_mainItem) { + m_mainItem->setVisible(isVisible()); + m_mainItem->setParentItem(contentItem()); + } + + emit mainItemChanged(); + } +} + +QQuickItem *DashboardWindow::visualParent() const +{ + return m_visualParentItem; +} + +void DashboardWindow::setVisualParent(QQuickItem *item) +{ + if (m_visualParentItem != item) { + if (m_visualParentItem) { + disconnect(m_visualParentItem.data(), &QQuickItem::windowChanged, this, &DashboardWindow::visualParentWindowChanged); + } + + m_visualParentItem = item; + + if (m_visualParentItem) { + if (m_visualParentItem->window()) { + visualParentWindowChanged(m_visualParentItem->window()); + } + + connect(m_visualParentItem.data(), &QQuickItem::windowChanged, this, &DashboardWindow::visualParentWindowChanged); + } + + emit visualParentChanged(); + } +} + +QColor DashboardWindow::backgroundColor() const +{ + return color(); +} + +void DashboardWindow::setBackgroundColor(const QColor &c) +{ + if (color() != c) { + setColor(c); + + emit backgroundColorChanged(); + } +} + +QQuickItem *DashboardWindow::keyEventProxy() const +{ + return m_keyEventProxy; +} + +void DashboardWindow::setKeyEventProxy(QQuickItem *item) +{ + if (m_keyEventProxy != item) { + m_keyEventProxy = item; + + emit keyEventProxyChanged(); + } +} + +void DashboardWindow::toggle() { + if (isVisible()) { + close(); + } else { + resize(screen()->size()); + showFullScreen(); + KWindowSystem::forceActiveWindow(winId()); + } +} + +bool DashboardWindow::event(QEvent *event) +{ + if (event->type() == QEvent::Expose) { + // FIXME TODO: We can remove this once we depend on Qt 5.6.1+. + // See: https://bugreports.qt.io/browse/QTBUG-26978 + KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager); + } else if (event->type() == QEvent::PlatformSurface) { + const QPlatformSurfaceEvent *pSEvent = static_cast(event); + + if (pSEvent->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) { + KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager); + } + } else if (event->type() == QEvent::Show) { + updateTheme(); + + if (m_mainItem) { + m_mainItem->setVisible(true); + } + } else if (event->type() == QEvent::Hide) { + if (m_mainItem) { + m_mainItem->setVisible(false); + } + } else if (event->type() == QEvent::FocusOut) { + if (isVisible()) { + KWindowSystem::raiseWindow(winId()); + KWindowSystem::forceActiveWindow(winId()); + } + } + + return QQuickWindow::event(event); +} + +void DashboardWindow::keyPressEvent(QKeyEvent *e) +{ + if (e->key() == Qt::Key_Escape) { + emit keyEscapePressed(); + + return; + } else if (m_keyEventProxy && !m_keyEventProxy->hasActiveFocus() + && !(e->key() == Qt::Key_Home) + && !(e->key() == Qt::Key_End) + && !(e->key() == Qt::Key_Left) + && !(e->key() == Qt::Key_Up) + && !(e->key() == Qt::Key_Right) + && !(e->key() == Qt::Key_Down) + && !(e->key() == Qt::Key_PageUp) + && !(e->key() == Qt::Key_PageDown) + && !(e->key() == Qt::Key_Enter) + && !(e->key() == Qt::Key_Return) + && !(e->key() == Qt::Key_Menu) + && !(e->key() == Qt::Key_Tab) + && !(e->key() == Qt::Key_Backtab)) { + + QPointer previousFocusItem = activeFocusItem(); + + m_keyEventProxy->forceActiveFocus(); + QEvent* eventCopy = new QKeyEvent(e->type(), e->key(), e->modifiers(), + e->nativeScanCode(), e->nativeVirtualKey(), e->nativeModifiers(), + e->text(), e->isAutoRepeat(), e->count()); + QCoreApplication::postEvent(this, eventCopy); + + // We _need_ to do it twice to make sure the event ping-pong needed + // for delivery happens before we sap focus again. + QCoreApplication::processEvents(); + QCoreApplication::processEvents(); + + if (previousFocusItem) { + previousFocusItem->forceActiveFocus(); + } + + return; + } + + QQuickWindow::keyPressEvent(e); +} + +void DashboardWindow::updateTheme() +{ + KWindowEffects::enableBlurBehind(winId(), true); +} + +void DashboardWindow::visualParentWindowChanged(QQuickWindow *window) +{ + if (m_visualParentWindow) { + disconnect(m_visualParentWindow.data(), &QQuickWindow::screenChanged, this, &DashboardWindow::visualParentScreenChanged); + } + + m_visualParentWindow = window; + + if (m_visualParentWindow) { + visualParentScreenChanged(m_visualParentWindow->screen()); + + connect(m_visualParentWindow.data(), &QQuickWindow::screenChanged, this, &DashboardWindow::visualParentScreenChanged); + } +} + +void DashboardWindow::visualParentScreenChanged(QScreen *screen) +{ + if (screen) { + setScreen(screen); + setGeometry(screen->geometry()); + } +} diff --git a/applets/kicker/plugin/dashboardwindow.h b/applets/kicker/plugin/dashboardwindow.h new file mode 100644 index 000000000..62de20b97 --- /dev/null +++ b/applets/kicker/plugin/dashboardwindow.h @@ -0,0 +1,81 @@ +/*************************************************************************** + * 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 DASHBOARDWINDOW_H +#define DASHBOARDWINDOW_H + +#include + +#include +#include + +class DashboardWindow : public QQuickWindow +{ + Q_OBJECT + + Q_PROPERTY(QQuickItem* mainItem READ mainItem WRITE setMainItem NOTIFY mainItemChanged) + Q_PROPERTY(QQuickItem* visualParent READ visualParent WRITE setVisualParent NOTIFY visualParentChanged) + Q_PROPERTY(QQuickItem* keyEventProxy READ keyEventProxy WRITE setKeyEventProxy NOTIFY keyEventProxyChanged) + Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged) + + Q_CLASSINFO("DefaultProperty", "mainItem") + + public: + explicit DashboardWindow(QQuickItem *parent = nullptr); + ~DashboardWindow() override; + + QQuickItem *mainItem() const; + void setMainItem(QQuickItem *item); + + QQuickItem *visualParent() const; + void setVisualParent(QQuickItem *item); + + QQuickItem *keyEventProxy() const; + void setKeyEventProxy(QQuickItem *item); + + QColor backgroundColor() const; + void setBackgroundColor(const QColor &color); + + Q_INVOKABLE void toggle(); + + Q_SIGNALS: + void mainItemChanged() const; + void visualParentChanged() const; + void keyEventProxyChanged() const; + void backgroundColorChanged() const; + void keyEscapePressed() const; + + private Q_SLOTS: + void updateTheme(); + void visualParentWindowChanged(QQuickWindow *window); + void visualParentScreenChanged(QScreen *screen); + + protected: + bool event(QEvent *event) override; + void keyPressEvent(QKeyEvent *e) override; + + private: + QQuickItem *m_mainItem; + QPointer m_visualParentItem; + QPointer m_visualParentWindow; + QPointer m_keyEventProxy; + Plasma::Theme m_theme; +}; + +#endif diff --git a/applets/kicker/plugin/draghelper.cpp b/applets/kicker/plugin/draghelper.cpp new file mode 100644 index 000000000..ddc397103 --- /dev/null +++ b/applets/kicker/plugin/draghelper.cpp @@ -0,0 +1,113 @@ +/*************************************************************************** + * Copyright (C) 2013 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 . * + ***************************************************************************/ + +#include "draghelper.h" + +#include +#include +#include +#include +#include +#include + +DragHelper::DragHelper(QObject* parent) : QObject(parent) +, m_dragIconSize(32) +, m_dragging(false) +{ +} + +DragHelper::~DragHelper() +{ +} + +int DragHelper::dragIconSize() const +{ + return m_dragIconSize; +} + +void DragHelper::setDragIconSize(int size) +{ + if (m_dragIconSize != size) { + m_dragIconSize = size; + + emit dragIconSizeChanged(); + } +} + +bool DragHelper::isDrag(int oldX, int oldY, int newX, int newY) const +{ + return ((QPoint(oldX, oldY) - QPoint(newX, newY)).manhattanLength() >= QApplication::startDragDistance()); +} + +void DragHelper::startDrag(QQuickItem *item, const QUrl &url, const QIcon &icon, + const QString &extraMimeType, const QString &extraMimeData) +{ + // This allows the caller to return, making sure we don't crash if + // the caller is destroyed mid-drag (as can happen due to a sycoca + // change). + + QMetaObject::invokeMethod(this, "doDrag", Qt::QueuedConnection, + Q_ARG(QQuickItem*, item), Q_ARG(QUrl, url), Q_ARG(QIcon, icon), + Q_ARG(QString, extraMimeType), Q_ARG(QString, extraMimeData)); +} + +void DragHelper::doDrag(QQuickItem *item, const QUrl &url, const QIcon &icon, + const QString &extraMimeType, const QString &extraMimeData) +{ + setDragging(true); + + if (item && item->window() && item->window()->mouseGrabberItem()) { + item->window()->mouseGrabberItem()->ungrabMouse(); + } + + QDrag *drag = new QDrag(item); + + QMimeData *mimeData = new QMimeData(); + + if (!url.isEmpty()) { + mimeData->setUrls(QList() << url); + } + + if (!extraMimeType.isEmpty() && !extraMimeData.isEmpty()) { + mimeData->setData(extraMimeType, extraMimeData.toLatin1()); + } + + drag->setMimeData(mimeData); + + if (!icon.isNull()) { + drag->setPixmap(icon.pixmap(m_dragIconSize, m_dragIconSize)); + } + + drag->exec(); + + emit dropped(); + + // Ensure dragging is still true when onRelease is called. + QTimer::singleShot(0, qApp, [this] { + setDragging(false); + }); +} + +void DragHelper::setDragging(bool dragging) +{ + if (m_dragging == dragging) + return; + m_dragging = dragging; + emit draggingChanged(); +} \ No newline at end of file diff --git a/applets/kicker/plugin/draghelper.h b/applets/kicker/plugin/draghelper.h new file mode 100644 index 000000000..ec5691f3e --- /dev/null +++ b/applets/kicker/plugin/draghelper.h @@ -0,0 +1,60 @@ +/*************************************************************************** + * Copyright (C) 2013 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 DRAGHELPER_H +#define DRAGHELPER_H + +#include +#include +#include + +class QQuickItem; + +class DragHelper : public QObject +{ + Q_OBJECT + Q_PROPERTY(int dragIconSize READ dragIconSize WRITE setDragIconSize NOTIFY dragIconSizeChanged) + Q_PROPERTY(bool dragging READ isDragging NOTIFY draggingChanged) + + public: + explicit DragHelper(QObject *parent = nullptr); + ~DragHelper() override; + + int dragIconSize() const; + void setDragIconSize(int size); + bool isDragging() const { return m_dragging; } + + Q_INVOKABLE bool isDrag(int oldX, int oldY, int newX, int newY) const; + Q_INVOKABLE void startDrag(QQuickItem* item, const QUrl &url = QUrl(), const QIcon &icon = QIcon(), + const QString &extraMimeType = QString(), const QString &extraMimeData = QString()); + + Q_SIGNALS: + void dragIconSizeChanged() const; + void dropped() const; + void draggingChanged() const; + + private: + int m_dragIconSize; + bool m_dragging; + Q_INVOKABLE void doDrag(QQuickItem* item, const QUrl &url = QUrl(), const QIcon &icon = QIcon(), + const QString &extraMimeType = QString(), const QString &extraMimeData = QString()); + void setDragging(bool dragging); +}; + +#endif diff --git a/applets/kicker/plugin/fileentry.cpp b/applets/kicker/plugin/fileentry.cpp new file mode 100644 index 000000000..c61dcf739 --- /dev/null +++ b/applets/kicker/plugin/fileentry.cpp @@ -0,0 +1,123 @@ +/*************************************************************************** + * 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 . * + ***************************************************************************/ + +#include "fileentry.h" +#include "actionlist.h" + +#include +#include + +FileEntry::FileEntry(AbstractModel *owner, const QUrl &url) : AbstractEntry(owner) +, m_fileItem(nullptr) +{ + if (url.isValid()) { + m_fileItem = new KFileItem(url); + m_fileItem->determineMimeType(); + } +} + +FileEntry::~FileEntry() +{ + delete m_fileItem; +} + +bool FileEntry::isValid() const +{ + return m_fileItem && (m_fileItem->isFile() || m_fileItem->isDir()); +} + +QIcon FileEntry::icon() const +{ + if (m_fileItem) { + return QIcon::fromTheme(m_fileItem->iconName(), QIcon::fromTheme(QStringLiteral("unknown"))); + } + + return QIcon::fromTheme(QStringLiteral("unknown")); +} + +QString FileEntry::name() const +{ + if (m_fileItem) { + return m_fileItem->text(); + } + + return QString(); +} + +QString FileEntry::description() const +{ + if (m_fileItem) { + return m_fileItem->url().toString(QUrl::PreferLocalFile); + } + + return QString(); +} + +QString FileEntry::id() const +{ + if (m_fileItem) { + return m_fileItem->url().toString(); + } + + return QString(); +} + +QUrl FileEntry::url() const +{ + if (m_fileItem) { + return m_fileItem->url(); + } + + return QUrl(); +} + +bool FileEntry::hasActions() const +{ + return m_fileItem && m_fileItem->isFile(); +} + +QVariantList FileEntry::actions() const +{ + if (m_fileItem) { + return Kicker::createActionListForFileItem(*m_fileItem); + } + + return QVariantList(); +} + +bool FileEntry::run(const QString& actionId, const QVariant &argument) +{ + if (!m_fileItem) { + return false; + } + + if (actionId.isEmpty()) { + new KRun(m_fileItem->url(), nullptr); + + return true; + } else { + bool close = false; + + if (Kicker::handleFileItemAction(*m_fileItem, actionId, argument, &close)) { + return close; + } + } + + return false; +} diff --git a/applets/kicker/plugin/fileentry.h b/applets/kicker/plugin/fileentry.h new file mode 100644 index 000000000..42ddc385f --- /dev/null +++ b/applets/kicker/plugin/fileentry.h @@ -0,0 +1,53 @@ +/*************************************************************************** + * 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 FILEENTRY_H +#define FILEENTRY_H + +#include "abstractentry.h" + +class KFileItem; + +class FileEntry : public AbstractEntry +{ + public: + explicit FileEntry(AbstractModel *owner, const QUrl &url); + ~FileEntry() override; + + EntryType type() const override { return RunnableType; } + + bool isValid() const override; + + QIcon icon() const override; + QString name() const override; + QString description() const override; + + QString id() const override; + QUrl url() const override; + + bool hasActions() const override; + QVariantList actions() const override; + + bool run(const QString& actionId = QString(), const QVariant &argument = QVariant()) override; + + private: + KFileItem *m_fileItem; +}; + +#endif diff --git a/applets/kicker/plugin/forwardingmodel.cpp b/applets/kicker/plugin/forwardingmodel.cpp new file mode 100644 index 000000000..a0cb86a8f --- /dev/null +++ b/applets/kicker/plugin/forwardingmodel.cpp @@ -0,0 +1,265 @@ +/*************************************************************************** + * 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 . * + ***************************************************************************/ + +#include "forwardingmodel.h" + +ForwardingModel::ForwardingModel(QObject *parent) : AbstractModel(parent) +{ +} + +ForwardingModel::~ForwardingModel() +{ +} + +QString ForwardingModel::description() const +{ + if (!m_sourceModel) { + return QString(); + } + + AbstractModel *abstractModel = qobject_cast(m_sourceModel); + + if (!abstractModel) { + return QString(); + } + + return abstractModel->description(); +} + +QAbstractItemModel *ForwardingModel::sourceModel() const +{ + return m_sourceModel; +} + +void ForwardingModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + disconnectSignals(); + + beginResetModel(); + + m_sourceModel = sourceModel; + + connectSignals(); + + endResetModel(); + + emit countChanged(); + emit sourceModelChanged(); + emit descriptionChanged(); +} + +bool ForwardingModel::canFetchMore(const QModelIndex &parent) const +{ + if (!m_sourceModel) { + return false; + } + + return m_sourceModel->canFetchMore(indexToSourceIndex(parent)); +} + +void ForwardingModel::fetchMore(const QModelIndex &parent) +{ + if (m_sourceModel) { + m_sourceModel->fetchMore(indexToSourceIndex(parent)); + } +} + +QModelIndex ForwardingModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + if (!m_sourceModel) { + return QModelIndex(); + } + + return createIndex(row, column); +} + +QModelIndex ForwardingModel::parent(const QModelIndex &index) const +{ + Q_UNUSED(index) + + return QModelIndex(); +} + +QVariant ForwardingModel::data(const QModelIndex &index, int role) const +{ + if (!m_sourceModel) { + return QVariant(); + } + + return m_sourceModel->data(indexToSourceIndex(index), role); +} + +int ForwardingModel::rowCount(const QModelIndex &parent) const +{ + if (!m_sourceModel) { + return 0; + } + + return m_sourceModel->rowCount(indexToSourceIndex(parent)); +} + +QModelIndex ForwardingModel::indexToSourceIndex(const QModelIndex& index) const +{ + if (!m_sourceModel || !index.isValid()) { + return QModelIndex(); + } + + return m_sourceModel->index(index.row(), index.column(), + index.parent().isValid() ? indexToSourceIndex(index.parent()) : QModelIndex()); +} + +bool ForwardingModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + if (!m_sourceModel) { + return false; + } + + AbstractModel *abstractModel = qobject_cast(m_sourceModel); + + if (!abstractModel) { + return false; + } + + return abstractModel->trigger(row, actionId, argument); +} + +QString ForwardingModel::labelForRow(int row) +{ + if (!m_sourceModel) { + return QString(); + } + + AbstractModel *abstractModel = qobject_cast(m_sourceModel); + + if (!abstractModel) { + return QString(); + } + + return abstractModel->labelForRow(row); +} + +AbstractModel* ForwardingModel::modelForRow(int row) +{ + if (!m_sourceModel) { + return nullptr; + } + + AbstractModel *abstractModel = qobject_cast(m_sourceModel); + + if (!abstractModel) { + return nullptr; + } + + return abstractModel->modelForRow(row); +} + +AbstractModel* ForwardingModel::favoritesModel() +{ + AbstractModel *sourceModel = qobject_cast(m_sourceModel); + + if (sourceModel) { + return sourceModel->favoritesModel(); + } + + return AbstractModel::favoritesModel(); +} + +int ForwardingModel::separatorCount() const +{ + if (!m_sourceModel) { + return 0; + } + + AbstractModel *abstractModel = qobject_cast(m_sourceModel); + + if (!abstractModel) { + return 0; + } + + return abstractModel->separatorCount(); +} + +void ForwardingModel::reset() +{ + beginResetModel(); + endResetModel(); + + emit countChanged(); + emit separatorCountChanged(); +} + +void ForwardingModel::connectSignals() +{ + if (!m_sourceModel) { + return; + } + + connect(m_sourceModel, SIGNAL(destroyed()), this, SLOT(reset())); + connect(m_sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), + this, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), + Qt::UniqueConnection); + connect(m_sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + Qt::UniqueConnection); + connect(m_sourceModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), + this, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), + Qt::UniqueConnection); + connect(m_sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + Qt::UniqueConnection); + connect(m_sourceModel, SIGNAL(layoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint)), + this, SIGNAL(layoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint)), + Qt::UniqueConnection); + connect(m_sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SIGNAL(rowsInserted(QModelIndex,int,int)), + Qt::UniqueConnection); + connect(m_sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SIGNAL(countChanged()), Qt::UniqueConnection); + connect(m_sourceModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + this, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + Qt::UniqueConnection); + connect(m_sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SIGNAL(rowsRemoved(QModelIndex,int,int)), + Qt::UniqueConnection); + connect(m_sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SIGNAL(countChanged()), Qt::UniqueConnection); + connect(m_sourceModel, SIGNAL(modelAboutToBeReset()), + this, SIGNAL(modelAboutToBeReset()), + Qt::UniqueConnection); + connect(m_sourceModel, SIGNAL(modelReset()), + this, SIGNAL(modelReset()), + Qt::UniqueConnection); + connect(m_sourceModel, SIGNAL(modelReset()), + this, SIGNAL(countChanged()), + Qt::UniqueConnection); + connect(m_sourceModel, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), + this, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), + Qt::UniqueConnection); +} + +void ForwardingModel::disconnectSignals() +{ + if (!m_sourceModel) { + return; + } + + disconnect(m_sourceModel, nullptr, this, nullptr); +} diff --git a/applets/kicker/plugin/forwardingmodel.h b/applets/kicker/plugin/forwardingmodel.h new file mode 100644 index 000000000..b059028b0 --- /dev/null +++ b/applets/kicker/plugin/forwardingmodel.h @@ -0,0 +1,77 @@ +/*************************************************************************** + * 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 FORWARDINGMODEL_H +#define FORWARDINGMODEL_H + +#include "abstractmodel.h" + +#include + +class ForwardingModel : public AbstractModel +{ + Q_OBJECT + + Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged) + + public: + explicit ForwardingModel(QObject *parent = nullptr); + ~ForwardingModel() override; + + QString description() const override; + + QAbstractItemModel *sourceModel() const; + virtual void setSourceModel(QAbstractItemModel *sourceModel); + + bool canFetchMore(const QModelIndex &parent) const override; + void fetchMore(const QModelIndex &parent) override; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + Q_INVOKABLE QString labelForRow(int row) override; + + Q_INVOKABLE AbstractModel *modelForRow(int row) override; + + AbstractModel* favoritesModel() override; + + int separatorCount() const override; + + public Q_SLOTS: + void reset(); + + Q_SIGNALS: + void sourceModelChanged() const; + + protected: + QModelIndex indexToSourceIndex(const QModelIndex &index) const; + + void connectSignals(); + void disconnectSignals(); + + QPointer m_sourceModel; +}; + +#endif diff --git a/applets/kicker/plugin/funnelmodel.cpp b/applets/kicker/plugin/funnelmodel.cpp new file mode 100644 index 000000000..165e43ded --- /dev/null +++ b/applets/kicker/plugin/funnelmodel.cpp @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2014 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 . * + ***************************************************************************/ + +#include "funnelmodel.h" + +FunnelModel::FunnelModel(QObject *parent) : ForwardingModel(parent) +{ +} + +FunnelModel::~FunnelModel() +{ +} + +void FunnelModel::setSourceModel(QAbstractItemModel *model) +{ + if (model && m_sourceModel == model) { + return; + } + + if (!model) { + reset(); + + return; + } + + connect(model, SIGNAL(destroyed(QObject*)), this, SLOT(reset())); + + if (!m_sourceModel) { + beginResetModel(); + + m_sourceModel = model; + + connectSignals(); + + endResetModel(); + + emit countChanged(); + + emit sourceModelChanged(); + emit descriptionChanged(); + + return; + } + + int oldCount = m_sourceModel->rowCount(); + int newCount = model->rowCount(); + + auto setNewModel = [this, model]() { + disconnectSignals(); + m_sourceModel = model; + connectSignals(); + }; + + if (newCount > oldCount) { + beginInsertRows(QModelIndex(), oldCount, newCount - 1); + setNewModel(); + endInsertRows(); + } else if (newCount < oldCount) { + if (newCount == 0) { + beginResetModel(); + setNewModel(); + endResetModel(); + } else { + beginRemoveRows(QModelIndex(), newCount, oldCount - 1); + setNewModel(); + endRemoveRows(); + } + } else { + setNewModel(); + } + + if (newCount > 0) { + emit dataChanged(index(0, 0), index(qMin(oldCount, newCount) - 1, 0)); + } + + if (oldCount != newCount) { + emit countChanged(); + } + + emit sourceModelChanged(); + emit descriptionChanged(); +} diff --git a/applets/kicker/plugin/funnelmodel.h b/applets/kicker/plugin/funnelmodel.h new file mode 100644 index 000000000..6077ff747 --- /dev/null +++ b/applets/kicker/plugin/funnelmodel.h @@ -0,0 +1,36 @@ +/*************************************************************************** + * Copyright (C) 2014 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 FUNNELMODEL_H +#define FUNNELMODEL_H + +#include "forwardingmodel.h" + +class FunnelModel : public ForwardingModel +{ + Q_OBJECT + + public: + explicit FunnelModel(QObject *parent = nullptr); + ~FunnelModel() override; + + void setSourceModel(QAbstractItemModel *model) override; +}; + +#endif diff --git a/applets/kicker/plugin/kastatsfavoritesmodel.cpp b/applets/kicker/plugin/kastatsfavoritesmodel.cpp new file mode 100644 index 000000000..3474faefa --- /dev/null +++ b/applets/kicker/plugin/kastatsfavoritesmodel.cpp @@ -0,0 +1,719 @@ +/*************************************************************************** + * 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 "debug.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace KAStats = KActivities::Stats; + +using namespace KAStats; +using namespace KAStats::Terms; + +#define AGENT_APPLICATIONS QStringLiteral("org.kde.plasma.favorites.applications") +#define AGENT_CONTACTS QStringLiteral("org.kde.plasma.favorites.contacts") +#define AGENT_DOCUMENTS QStringLiteral("org.kde.plasma.favorites.documents") + +QString agentForUrl(const QString &url) +{ + return url.startsWith(QLatin1String("ktp:")) + ? AGENT_CONTACTS + : url.startsWith(QLatin1String("preferred:")) + ? AGENT_APPLICATIONS + : url.startsWith(QLatin1String("applications:")) + ? AGENT_APPLICATIONS + : (url.startsWith(QLatin1Char('/')) && !url.endsWith(QLatin1String(".desktop"))) + ? AGENT_DOCUMENTS + : (url.startsWith(QLatin1String("file:/")) && !url.endsWith(QLatin1String(".desktop"))) + ? AGENT_DOCUMENTS + // use applications as the default + : AGENT_APPLICATIONS; +} + +class KAStatsFavoritesModel::Private: public QAbstractListModel { +public: + class NormalizedId { + public: + NormalizedId() + { + } + + NormalizedId(const Private *parent, const QString &id) + { + if (id.isEmpty()) return; + + QSharedPointer entry = nullptr; + + if (parent->m_itemEntries.contains(id)) { + entry = parent->m_itemEntries[id]; + } else { + // This entry is not cached - it is temporary, + // so let's clean up when we exit this function + entry = parent->entryForResource(id); + } + + if (!entry || !entry->isValid()) { + qWarning() << "Entry is not valid" << id << entry; + m_id = id; + return; + } + + const auto url = entry->url(); + + qCDebug(KICKER_DEBUG) << "Original id is: " << id << ", and the url is" << url; + + // Preferred applications need special handling + if (entry->id().startsWith(QLatin1String("preferred:"))) { + m_id = entry->id(); + return; + } + + // If this is an application, use the applications:-format url + auto appEntry = dynamic_cast(entry.data()); + if (appEntry && !appEntry->menuId().isEmpty()) { + m_id = QLatin1String("applications:") + appEntry->menuId(); + return; + } + + // 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()) { + m_id = QUrl::fromLocalFile(file.canonicalFilePath()).toString(); + return; + } + } + + // If this is a file, we should have already covered it + if (url.scheme() == QLatin1String("file")) { + return; + } + + m_id = url.toString(); + } + + const QString& value() const + { + return m_id; + } + + bool operator==(const NormalizedId &other) const + { + return m_id == other.m_id; + } + + private: + QString m_id; + }; + + NormalizedId normalizedId(const QString &id) const + { + return NormalizedId(this, id); + } + + QSharedPointer entryForResource(const QString &resource) const + { + using SP = QSharedPointer; + + const auto agent = + agentForUrl(resource); + + if (agent == AGENT_CONTACTS) { + return SP(new ContactEntry(q, resource)); + + } else if (agent == AGENT_DOCUMENTS) { + if (resource.startsWith(QLatin1String("/"))) { + return SP(new FileEntry(q, QUrl::fromLocalFile(resource))); + } else { + return SP(new FileEntry(q, QUrl(resource))); + } + + } else if (agent == AGENT_APPLICATIONS) { + if (resource.startsWith(QLatin1String("applications:"))) { + return SP(new AppEntry(q, resource.mid(13))); + } else { + return SP(new AppEntry(q, resource)); + } + + } else { + return {}; + } + } + + Private(KAStatsFavoritesModel *parent, QString clientId) + : q(parent) + , m_query( + LinkedResources + | Agent { + AGENT_APPLICATIONS, + AGENT_CONTACTS, + AGENT_DOCUMENTS + } + | Type::any() + | Activity::current() + | Activity::global() + | Limit::all() + ) + , m_watcher(m_query) + , m_clientId(clientId) + { + // Connecting the watcher + connect(&m_watcher, &ResultWatcher::resultLinked, + [this] (const QString &resource) { + addResult(resource, -1); + }); + + connect(&m_watcher, &ResultWatcher::resultUnlinked, + [this] (const QString &resource) { + removeResult(resource); + }); + + // Loading the items order + const auto cfg = KSharedConfig::openConfig(QStringLiteral("kactivitymanagerd-statsrc")); + + // We want first to check whether we have an ordering for this activity. + // If not, we will try to get a global one for this applet + + const QString thisGroupName = + QStringLiteral("Favorites-") + clientId + QStringLiteral("-") + m_activities.currentActivity(); + const QString globalGroupName = + QStringLiteral("Favorites-") + clientId + QStringLiteral("-global"); + + KConfigGroup thisCfgGroup(cfg, thisGroupName); + KConfigGroup globalCfgGroup(cfg, globalGroupName); + + QStringList ordering = + thisCfgGroup.readEntry("ordering", QStringList()) + + globalCfgGroup.readEntry("ordering", QStringList()); + + qCDebug(KICKER_DEBUG) << "Loading the ordering " << ordering; + + // Loading the results without emitting any model signals + qCDebug(KICKER_DEBUG) << "Query is" << m_query; + ResultSet results(m_query); + + for (const auto& result: results) { + qCDebug(KICKER_DEBUG) << "Got " << result.resource() << " -->"; + addResult(result.resource(), -1, false); + } + + // Normalizing all the ids + std::transform(ordering.begin(), ordering.end(), ordering.begin(), + [&] (const QString &item) { + return normalizedId(item).value(); + }); + + // Sorting the items in the cache + std::sort(m_items.begin(), m_items.end(), + [&] (const NormalizedId &left, const NormalizedId &right) { + auto leftIndex = ordering.indexOf(left.value()); + auto rightIndex = ordering.indexOf(right.value()); + + return (leftIndex == -1 && rightIndex == -1) ? + left.value() < right.value() : + + (leftIndex == -1) ? + false : + + (rightIndex == -1) ? + true : + + // otherwise + leftIndex < rightIndex; + }); + + // Debugging: + QVector itemStrings(m_items.size()); + std::transform(m_items.cbegin(), m_items.cend(), itemStrings.begin(), + [] (const NormalizedId &item) { + return item.value(); + }); + qCDebug(KICKER_DEBUG) << "After ordering: " << itemStrings; + } + + void addResult(const QString &_resource, int index, bool notifyModel = true) + { + // We want even files to have a proper URL + const auto resource = + _resource.startsWith(QLatin1Char('/')) ? QUrl::fromLocalFile(_resource).toString() : _resource; + + qCDebug(KICKER_DEBUG) << "Adding result" << resource << "already present?" << m_itemEntries.contains(resource); + + if (m_itemEntries.contains(resource)) return; + + auto entry = entryForResource(resource); + + if (!entry || !entry->isValid()) { + qCDebug(KICKER_DEBUG) << "Entry is not valid!"; + return; + } + + if (index == -1) { + index = m_items.count(); + } + + if (notifyModel) { + beginInsertRows(QModelIndex(), index, index); + } + + auto url = entry->url(); + + m_itemEntries[resource] + = m_itemEntries[entry->id()] + = m_itemEntries[url.toString()] + = m_itemEntries[url.toLocalFile()] + = entry; + + auto normalized = normalizedId(resource); + m_items.insert(index, normalized); + m_itemEntries[normalized.value()] = entry; + + if (notifyModel) { + endInsertRows(); + saveOrdering(); + } + } + + void removeResult(const QString &resource) + { + auto normalized = normalizedId(resource); + + // If we know this item will not really be removed, + // but only that activities it is on have changed, + // lets leave it + if (m_ignoredItems.contains(normalized.value())) { + m_ignoredItems.removeAll(normalized.value()); + return; + } + + qCDebug(KICKER_DEBUG) << "Removing result" << resource; + + auto index = m_items.indexOf(normalizedId(resource)); + + if (index == -1) return; + + beginRemoveRows(QModelIndex(), index, index); + auto entry = m_itemEntries[resource]; + m_items.removeAt(index); + + // Removing the entry from the cache + QMutableHashIterator> i(m_itemEntries); + while (i.hasNext()) { + i.next(); + if (i.value() == entry) { + i.remove(); + } + } + + endRemoveRows(); + } + + + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + if (parent.isValid()) return 0; + + return m_items.count(); + } + + QVariant data(const QModelIndex &item, + int role = Qt::DisplayRole) const override + { + if (item.parent().isValid()) return QVariant(); + + const auto index = item.row(); + + const auto entry = m_itemEntries[m_items[index].value()]; + + 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_itemEntries.contains(id) + ? m_itemEntries[id]->run(actionId, argument) + : false; + } + + void move(int from, int to) + { + if (from < 0) return; + if (from >= m_items.count()) return; + if (to < 0) return; + if (to >= m_items.count()) return; + + if (from == to) return; + + const int modelTo = to + (to > from ? 1 : 0); + + if (q->beginMoveRows(QModelIndex(), from, from, + QModelIndex(), modelTo)) { + m_items.move(from, to); + q->endMoveRows(); + + qCDebug(KICKER_DEBUG) << "Save ordering (from Private::move) -->"; + saveOrdering(); + } + } + + void saveOrdering() + { + QStringList ids; + + for (const auto& item: m_items) { + ids << item.value(); + } + + qCDebug(KICKER_DEBUG) << "Save ordering (from Private::saveOrdering) -->"; + saveOrdering(ids, m_clientId, m_activities.currentActivity()); + } + + static void saveOrdering(const QStringList &ids, const QString &clientId, const QString ¤tActivity) + { + const auto cfg = KSharedConfig::openConfig(QStringLiteral("kactivitymanagerd-statsrc")); + + QStringList activities { + currentActivity, + QStringLiteral("global") + }; + + qCDebug(KICKER_DEBUG) << "Saving ordering for" << currentActivity << "and global" << ids; + + for (const auto& activity: activities) { + const QString groupName = + QStringLiteral("Favorites-") + clientId + QStringLiteral("-") + activity; + + KConfigGroup cfgGroup(cfg, groupName); + + cfgGroup.writeEntry("ordering", ids); + } + + cfg->sync(); + } + + KAStatsFavoritesModel *const q; + KActivities::Consumer m_activities; + Query m_query; + ResultWatcher m_watcher; + QString m_clientId; + + QVector m_items; + QHash> m_itemEntries; + QStringList m_ignoredItems; +}; + +KAStatsFavoritesModel::KAStatsFavoritesModel(QObject *parent) +: PlaceholderModel(parent) +, d(nullptr) // we have no client id yet +, m_enabled(true) +, m_maxFavorites(-1) +, m_activities(new KActivities::Consumer(this)) +{ + connect(m_activities, &KActivities::Consumer::currentActivityChanged, + this, [&] (const QString ¤tActivity) { + qCDebug(KICKER_DEBUG) << "Activity just got changed to" << currentActivity; + Q_UNUSED(currentActivity); + if (d) { + auto clientId = d->m_clientId; + initForClient(clientId); + } + }); +} + +KAStatsFavoritesModel::~KAStatsFavoritesModel() +{ + delete d; +} + +void KAStatsFavoritesModel::initForClient(const QString &clientId) +{ + qCDebug(KICKER_DEBUG) << "initForClient" << clientId; + + setSourceModel(nullptr); + delete d; + d = new Private( + this, + clientId + ); + + setSourceModel(d); +} + +QString KAStatsFavoritesModel::description() const +{ + return i18n("Favorites"); +} + +bool KAStatsFavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + return d && 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 the API backwards-compatible"; + return QStringList(); +} + +void KAStatsFavoritesModel::setFavorites(const QStringList& favorites) +{ + Q_UNUSED(favorites); + qWarning() << "KAStatsFavoritesModel::setFavorites is ignored"; +} + +bool KAStatsFavoritesModel::isFavorite(const QString &id) const +{ + return d && d->m_itemEntries.contains(id); +} + +void KAStatsFavoritesModel::portOldFavorites(const QStringList &ids) +{ + if (!d) return; + qCDebug(KICKER_DEBUG) << "portOldFavorites" << ids; + + const QString activityId = QStringLiteral(":global"); + std::for_each(ids.begin(), ids.end(), [&] (const QString &id) { + addFavoriteTo(id, activityId); + }); + + // Resetting the model + auto clientId = d->m_clientId; + setSourceModel(nullptr); + delete d; + d = nullptr; + + qCDebug(KICKER_DEBUG) << "Save ordering (from portOldFavorites) -->"; + Private::saveOrdering(ids, clientId, m_activities->currentActivity()); + + QTimer::singleShot(500, + std::bind(&KAStatsFavoritesModel::initForClient, this, clientId)); +} + +void KAStatsFavoritesModel::addFavorite(const QString &id, int index) +{ + qCDebug(KICKER_DEBUG) << "addFavorite" << id << index << " -->"; + addFavoriteTo(id, QStringLiteral(":global"), index); +} + +void KAStatsFavoritesModel::removeFavorite(const QString &id) +{ + qCDebug(KICKER_DEBUG) << "removeFavorite" << id << " -->"; + removeFavoriteFrom(id, QStringLiteral(":any")); +} + +void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const QString &activityId, int index) +{ + qCDebug(KICKER_DEBUG) << "addFavoriteTo" << id << activityId << index << " -->"; + addFavoriteTo(id, Activity(activityId), index); +} + +void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const QString &activityId) +{ + qCDebug(KICKER_DEBUG) << "removeFavoriteFrom" << id << activityId << " -->"; + removeFavoriteFrom(id, Activity(activityId)); +} + +void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const Activity &activity, int index) +{ + if (!d || id.isEmpty()) return; + + Q_ASSERT(!activity.values.isEmpty()); + + setDropPlaceholderIndex(-1); + + QStringList matchers { d->m_activities.currentActivity(), QStringLiteral(":global"), QStringLiteral(":current") }; + if (std::find_first_of(activity.values.cbegin(), activity.values.cend(), + matchers.cbegin(), matchers.cend()) != activity.values.cend()) { + d->addResult(id, index); + } + + const auto url = d->normalizedId(id).value(); + + qCDebug(KICKER_DEBUG) << "addFavoriteTo" << id << activity << index << url << " (actual)"; + + if (url.isEmpty()) return; + + d->m_watcher.linkToActivity(QUrl(url), activity, + Agent(agentForUrl(url))); +} + +void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const Activity &activity) +{ + if (!d || id.isEmpty()) return; + + const auto url = d->normalizedId(id).value(); + + Q_ASSERT(!activity.values.isEmpty()); + + qCDebug(KICKER_DEBUG) << "addFavoriteTo" << id << activity << url << " (actual)"; + + if (url.isEmpty()) return; + + d->m_watcher.unlinkFromActivity(QUrl(url), activity, + Agent(agentForUrl(url))); +} + +void KAStatsFavoritesModel::setFavoriteOn(const QString &id, const QString &activityId) +{ + if (!d || id.isEmpty()) return; + + const auto url = d->normalizedId(id).value(); + + qCDebug(KICKER_DEBUG) << "setFavoriteOn" << id << activityId << url << " (actual)"; + + qCDebug(KICKER_DEBUG) << "%%%%%%%%%%% Activity is" << activityId; + if (activityId.isEmpty() || activityId == QLatin1String(":any") || + activityId == QLatin1String(":global") || + activityId == m_activities->currentActivity()) { + d->m_ignoredItems << url; + } + + d->m_watcher.unlinkFromActivity(QUrl(url), Activity::any(), + Agent(agentForUrl(url))); + d->m_watcher.linkToActivity(QUrl(url), activityId, + Agent(agentForUrl(url))); +} + +void KAStatsFavoritesModel::moveRow(int from, int to) +{ + if (!d) return; + + d->move(from, 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 +{ + if (!d) { + qCDebug(KICKER_DEBUG) << "Linked for" << id << "is empty, no Private instance"; + return {}; + } + + auto url = d->normalizedId(id).value(); + + if (url.startsWith(QLatin1String("file:"))) { + url = QUrl(url).toLocalFile(); + } + + if (url.isEmpty()) { + qCDebug(KICKER_DEBUG) << "The url for" << id << "is empty"; + return {}; + } + + auto query = LinkedResources + | Agent { + AGENT_APPLICATIONS, + AGENT_CONTACTS, + AGENT_DOCUMENTS + } + | Type::any() + | Activity::any() + | Url(url) + | Limit::all(); + + ResultSet results(query); + + for (const auto &result: results) { + qCDebug(KICKER_DEBUG) << "Returning" << result.linkedActivities() << "for" << id << url; + return result.linkedActivities(); + } + + qCDebug(KICKER_DEBUG) << "Returning empty list of activities for" << id << url; + return {}; +} + diff --git a/applets/kicker/plugin/kastatsfavoritesmodel.h b/applets/kicker/plugin/kastatsfavoritesmodel.h new file mode 100644 index 000000000..4322172dc --- /dev/null +++ b/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 { +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 = nullptr); + ~KAStatsFavoritesModel() override; + + QString description() const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + 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() override; + + public Q_SLOTS: + void refresh() override; + + 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 diff --git a/applets/kicker/plugin/kickerplugin.cpp b/applets/kicker/plugin/kickerplugin.cpp new file mode 100644 index 000000000..e9475fcac --- /dev/null +++ b/applets/kicker/plugin/kickerplugin.cpp @@ -0,0 +1,65 @@ +/*************************************************************************** + * Copyright (C) 2014 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 . * + ***************************************************************************/ + +#include "kickerplugin.h" +#include "abstractmodel.h" +#include "appsmodel.h" +#include "computermodel.h" +#include "containmentinterface.h" +#include "draghelper.h" +#include "simplefavoritesmodel.h" +#include "kastatsfavoritesmodel.h" +#include "dashboardwindow.h" +#include "funnelmodel.h" +#include "processrunner.h" +#include "recentusagemodel.h" +#include "rootmodel.h" +#include "runnermodel.h" +#include "submenu.h" +#include "systemmodel.h" +#include "systemsettings.h" +#include "wheelinterceptor.h" +#include "windowsystem.h" + +#include + +void KickerPlugin::registerTypes(const char *uri) +{ + Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.private.kicker")); + + qmlRegisterType(); + + qmlRegisterType(uri, 0, 1, "AppsModel"); + qmlRegisterType(uri, 0, 1, "ComputerModel"); + qmlRegisterType(uri, 0, 1, "ContainmentInterface"); + qmlRegisterType(uri, 0, 1, "DragHelper"); + 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"); + qmlRegisterType(uri, 0, 1, "RecentUsageModel"); + qmlRegisterType(uri, 0, 1, "RootModel"); + qmlRegisterType(uri, 0, 1, "RunnerModel"); + qmlRegisterType(uri, 0, 1, "SubMenu"); + qmlRegisterType(uri, 0, 1, "SystemModel"); + qmlRegisterType(uri, 0, 1, "SystemSettings"); + qmlRegisterType(uri, 0, 1, "WheelInterceptor"); + qmlRegisterType(uri, 0, 1, "WindowSystem"); +} diff --git a/applets/kicker/plugin/kickerplugin.h b/applets/kicker/plugin/kickerplugin.h new file mode 100644 index 000000000..df3d90e1b --- /dev/null +++ b/applets/kicker/plugin/kickerplugin.h @@ -0,0 +1,35 @@ +/*************************************************************************** + * Copyright (C) 2014 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 KICKERPLUGIN_H +#define KICKERPLUGIN_H + +#include +#include + +class KickerPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + + public: + void registerTypes(const char *uri) override; +}; + +#endif // KICKERPLUGIN_H diff --git a/applets/kicker/plugin/menuentryeditor.cpp b/applets/kicker/plugin/menuentryeditor.cpp new file mode 100644 index 000000000..4b4a759e5 --- /dev/null +++ b/applets/kicker/plugin/menuentryeditor.cpp @@ -0,0 +1,68 @@ +/*************************************************************************** + * Copyright (C) 2014 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 . * + ***************************************************************************/ + +#include "menuentryeditor.h" + +#include +#include +#include + +#include + +MenuEntryEditor::MenuEntryEditor(QObject* parent) : QObject(parent) +{ +} + +MenuEntryEditor::~MenuEntryEditor() +{ +} + +bool MenuEntryEditor::canEdit(const QString& entryPath) const +{ + KFileItemList itemList; + itemList << KFileItem(QUrl::fromLocalFile(entryPath)); + + return KPropertiesDialog::canDisplay(itemList); +} + +void MenuEntryEditor::edit(const QString& entryPath, const QString& menuId) +{ + const QString &appsPath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); + const QUrl &entryUrl = QUrl::fromLocalFile(entryPath); + + if (!appsPath.isEmpty() && entryUrl.isValid()) { + const QDir appsDir(appsPath); + const QString &fileName = entryUrl.fileName(); + + if (appsDir.exists(fileName)) { + KPropertiesDialog::showDialog(entryUrl, nullptr, false); + } else { + if (!appsDir.exists()) { + if (!QDir::root().mkpath(appsPath)) { + return; + } + } + + KPropertiesDialog *dialog = new KPropertiesDialog(entryUrl, + QUrl::fromLocalFile(appsPath), menuId); + //KPropertiesDialog deletes itself + dialog->show(); + } + } +} diff --git a/applets/kicker/plugin/menuentryeditor.h b/applets/kicker/plugin/menuentryeditor.h new file mode 100644 index 000000000..d61eb5794 --- /dev/null +++ b/applets/kicker/plugin/menuentryeditor.h @@ -0,0 +1,39 @@ +/*************************************************************************** + * Copyright (C) 2014 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 MENUENTRYEDITOR_H +#define MENUENTRYEDITOR_H + +#include + +class MenuEntryEditor : public QObject +{ + Q_OBJECT + + public: + explicit MenuEntryEditor(QObject *parent = nullptr); + ~MenuEntryEditor() override; + + bool canEdit(const QString &entryPath) const; + + public Q_SLOTS: + void edit(const QString &entryPath, const QString &menuId); +}; + +#endif diff --git a/applets/kicker/plugin/placeholdermodel.cpp b/applets/kicker/plugin/placeholdermodel.cpp new file mode 100644 index 000000000..4168e20eb --- /dev/null +++ b/applets/kicker/plugin/placeholdermodel.cpp @@ -0,0 +1,407 @@ +/*************************************************************************** + * 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 "debug.h" + +#include + +PlaceholderModel::PlaceholderModel(QObject *parent) + : AbstractModel(parent) + , m_dropPlaceholderIndex(-1) + , m_isTriggerInhibited(false) +{ + connect(&m_triggerInhibitor, &QTimer::timeout, + this, [&] { + qCDebug(KICKER_DEBUG) << "%%% Inhibit stopped"; + m_isTriggerInhibited = false; + }); + + m_triggerInhibitor.setInterval(500); + m_triggerInhibitor.setSingleShot(true); +} + +void PlaceholderModel::inhibitTriggering() +{ + qCDebug(KICKER_DEBUG) << "%%% Inhibit started"; + m_isTriggerInhibited = true; + m_triggerInhibitor.start(); +} + +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 (m_isTriggerInhibited) return false; + + 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 nullptr; + } +} + +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, nullptr, this, nullptr); +} + +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(); +} diff --git a/applets/kicker/plugin/placeholdermodel.h b/applets/kicker/plugin/placeholdermodel.h new file mode 100644 index 000000000..ed9e9346e --- /dev/null +++ b/applets/kicker/plugin/placeholdermodel.h @@ -0,0 +1,94 @@ +/*************************************************************************** + * 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 +#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 = nullptr); + ~PlaceholderModel() override; + + QString description() const override; + + QAbstractItemModel *sourceModel() const; + virtual void setSourceModel(QAbstractItemModel *sourceModel); + + bool canFetchMore(const QModelIndex &parent) const override; + void fetchMore(const QModelIndex &parent) override; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + Q_INVOKABLE QString labelForRow(int row) override; + + Q_INVOKABLE AbstractModel *modelForRow(int row) override; + + AbstractModel* favoritesModel() override; + + int separatorCount() const override; + + 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 m_sourceModel; + + int m_dropPlaceholderIndex; + bool m_isTriggerInhibited; + QTimer m_triggerInhibitor; +}; + +#endif diff --git a/applets/kicker/plugin/processrunner.cpp b/applets/kicker/plugin/processrunner.cpp new file mode 100644 index 000000000..b4854ea51 --- /dev/null +++ b/applets/kicker/plugin/processrunner.cpp @@ -0,0 +1,35 @@ +/*************************************************************************** + * Copyright (C) 2013 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 . * + ***************************************************************************/ + +#include "processrunner.h" + +#include + +ProcessRunner::ProcessRunner(QObject *parent) : QObject(parent) +{ +} + +ProcessRunner::~ProcessRunner() +{ +} + +void ProcessRunner::runMenuEditor() +{ + KProcess::startDetached(QStringLiteral("kmenuedit")); +} diff --git a/applets/kicker/plugin/processrunner.h b/applets/kicker/plugin/processrunner.h new file mode 100644 index 000000000..4dc8bf067 --- /dev/null +++ b/applets/kicker/plugin/processrunner.h @@ -0,0 +1,36 @@ +/*************************************************************************** + * Copyright (C) 2013 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 PROCESSRUNNER_H +#define PROCESSRUNNER_H + +#include + +class ProcessRunner : public QObject +{ + Q_OBJECT + + public: + explicit ProcessRunner(QObject *parent = nullptr); + ~ProcessRunner() override; + + Q_INVOKABLE void runMenuEditor(); +}; + +#endif diff --git a/applets/kicker/plugin/qmldir b/applets/kicker/plugin/qmldir new file mode 100644 index 000000000..c07aed3f8 --- /dev/null +++ b/applets/kicker/plugin/qmldir @@ -0,0 +1,2 @@ +module org.kde.plasma.private.kicker +plugin kickerplugin diff --git a/applets/kicker/plugin/recentcontactsmodel.cpp b/applets/kicker/plugin/recentcontactsmodel.cpp new file mode 100644 index 000000000..5b875b59e --- /dev/null +++ b/applets/kicker/plugin/recentcontactsmodel.cpp @@ -0,0 +1,246 @@ +/*************************************************************************** + * Copyright (C) 2012 by Aurélien Gâteau * + * Copyright (C) 2014-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 . * + ***************************************************************************/ + +#include "recentcontactsmodel.h" +#include "actionlist.h" +#include "contactentry.h" + +#include + +#include + +#include +#include + +#include //FIXME TODO: Pretty include in KPeople broken. +#include +#include + +namespace KAStats = KActivities::Stats; + +using namespace KAStats; +using namespace KAStats::Terms; + +RecentContactsModel::RecentContactsModel(QObject *parent) : ForwardingModel(parent) +{ + refresh(); +} + +RecentContactsModel::~RecentContactsModel() +{ +} + +QString RecentContactsModel::description() const +{ + return i18n("Contacts"); +} + +QVariant RecentContactsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + QString id = sourceModel()->data(index, ResultModel::ResourceRole).toString(); + + KPeople::PersonData *data = nullptr; + + if (m_idToData.contains(id)) { + data = m_idToData[id]; + } + + if (!data) { + const_cast(this)->insertPersonData(id, index.row()); + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return data->name(); + } else if (role == Qt::DecorationRole) { + return data->presenceIconName(); + } else if (role == Kicker::FavoriteIdRole) { + return id; + } else if (role == Kicker::HasActionListRole) { + return true; + } else if (role == Kicker::ActionListRole) { + QVariantList actionList ; + + const QVariantMap &forgetAction = Kicker::createActionItem(i18n("Forget Contact"), QStringLiteral("forget")); + actionList << forgetAction; + + const QVariantMap &forgetAllAction = Kicker::createActionItem(i18n("Forget All Contacts"), QStringLiteral("forgetAll")); + actionList << forgetAllAction; + + actionList << Kicker::createSeparatorActionItem(); + + actionList << Kicker::createActionItem(i18n("Show Contact Information..."), QStringLiteral("showContactInfo")); + + return actionList; + } else if (role == Kicker::DescriptionRole) { + return QString(); + } + + return QVariant(); +} + +bool RecentContactsModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + Q_UNUSED(argument) + + bool withinBounds = row >= 0 && row < rowCount(); + + if (actionId.isEmpty() && withinBounds) { + QString id = sourceModel()->data(sourceModel()->index(row, 0), ResultModel::ResourceRole).toString(); + + const QList actionList = KPeople::actionsForPerson(id, this); + + if (!actionList.isEmpty()) { + QAction *chat = nullptr; + + foreach (QAction *action, actionList) { + const QVariant &actionType = action->property("actionType"); + + if (!actionType.isNull() && actionType.toInt() == KPeople::ActionType::TextChatAction) { + chat = action; + } + } + + if (chat) { + chat->trigger(); + + return true; + } + } + + return false; + } else if (actionId == QLatin1String("showContactInfo") && withinBounds) { + ContactEntry::showPersonDetailsDialog(sourceModel()->data(sourceModel()->index(row, 0), + ResultModel::ResourceRole).toString()); + } else if (actionId == QLatin1String("forget") && withinBounds) { + if (sourceModel()) { + ResultModel *resultModel = static_cast(sourceModel()); + resultModel->forgetResource(row); + } + + return false; + } else if (actionId == QLatin1String("forgetAll")) { + if (sourceModel()) { + ResultModel *resultModel = static_cast(sourceModel()); + resultModel->forgetAllResources(); + } + + return false; + } + + return false; +} + +bool RecentContactsModel::hasActions() const +{ + return rowCount(); +} + +QVariantList RecentContactsModel::actions() const +{ + QVariantList actionList; + + if (rowCount()) { + actionList << Kicker::createActionItem(i18n("Forget All Contacts"), QStringLiteral("forgetAll")); + } + + return actionList; +} + +void RecentContactsModel::refresh() +{ + QObject *oldModel = sourceModel(); + + auto query = UsedResources + | RecentlyUsedFirst + | Agent(QStringLiteral("KTp")) + | Type::any() + | Activity::current() + | Url::startsWith(QStringLiteral("ktp")) + | Limit(15); + + ResultModel *model = new ResultModel(query); + + QModelIndex index; + + if (model->canFetchMore(index)) { + model->fetchMore(index); + } + + // FIXME TODO: Don't wipe entire cache on transactions. + connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(buildCache()), Qt::UniqueConnection); + connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(buildCache()), Qt::UniqueConnection); + connect(model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(buildCache()), Qt::UniqueConnection); + connect(model, SIGNAL(modelReset()), + this, SLOT(buildCache()), Qt::UniqueConnection); + + setSourceModel(model); + + buildCache(); + + delete oldModel; +} + +void RecentContactsModel::buildCache() +{ + qDeleteAll(m_idToData); + m_idToData.clear(); + m_dataToRow.clear(); + + QString id; + + for(int i = 0; i < sourceModel()->rowCount(); ++i) { + id = sourceModel()->data(sourceModel()->index(i, 0), ResultModel::ResourceRole).toString(); + + if (!m_idToData.contains(id)) { + insertPersonData(id, i); + } + } +} + +void RecentContactsModel::insertPersonData(const QString& id, int row) +{ + KPeople::PersonData *data = new KPeople::PersonData(id); + + m_idToData[id] = data; + m_dataToRow[data] = row; + + connect(data, SIGNAL(dataChanged()), this, SLOT(personDataChanged())); +} + +void RecentContactsModel::personDataChanged() +{ + KPeople::PersonData *data = static_cast(sender()); + + if (m_dataToRow.contains(data)) { + int row = m_dataToRow[data]; + + QModelIndex idx = sourceModel()->index(row, 0); + + emit dataChanged(idx, idx); + } +} diff --git a/applets/kicker/plugin/recentcontactsmodel.h b/applets/kicker/plugin/recentcontactsmodel.h new file mode 100644 index 000000000..7bc374856 --- /dev/null +++ b/applets/kicker/plugin/recentcontactsmodel.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * Copyright (C) 2012 by Aurélien Gâteau * + * Copyright (C) 2014-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 RECENTCONTACTSMODEL_H +#define RECENTCONTACTSMODEL_H + +#include "forwardingmodel.h" + +namespace KPeople { + class PersonData; +} + +class RecentContactsModel : public ForwardingModel +{ + Q_OBJECT + + public: + explicit RecentContactsModel(QObject *parent = nullptr); + ~RecentContactsModel() override; + + QString description() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + bool hasActions() const override; + QVariantList actions() const override; + + private Q_SLOTS: + void refresh() override; + void buildCache(); + void personDataChanged(); + + private: + void insertPersonData(const QString &id, int row); + + QHash m_idToData; + QHash m_dataToRow; +}; + +#endif diff --git a/applets/kicker/plugin/recentusagemodel.cpp b/applets/kicker/plugin/recentusagemodel.cpp new file mode 100644 index 000000000..6168b713d --- /dev/null +++ b/applets/kicker/plugin/recentusagemodel.cpp @@ -0,0 +1,491 @@ +/*************************************************************************** + * Copyright (C) 2014-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 . * + ***************************************************************************/ + +#include "recentusagemodel.h" +#include "actionlist.h" +#include "appsmodel.h" +#include "appentry.h" +#include "kastatsfavoritesmodel.h" +#include + +#include + +#include +#include +#include +#include +#if HAVE_X11 +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace KAStats = KActivities::Stats; + +using namespace KAStats; +using namespace KAStats::Terms; + +GroupSortProxy::GroupSortProxy(AbstractModel *parentModel, QAbstractItemModel *sourceModel) : QSortFilterProxyModel(parentModel) +{ + sourceModel->setParent(this); + setSourceModel(sourceModel); + sort(0); +} + +GroupSortProxy::~GroupSortProxy() +{ +} + +InvalidAppsFilterProxy::InvalidAppsFilterProxy(AbstractModel *parentModel, QAbstractItemModel *sourceModel) : QSortFilterProxyModel(parentModel) +, m_parentModel(parentModel) +{ + connect(parentModel, &AbstractModel::favoritesModelChanged, this, &InvalidAppsFilterProxy::connectNewFavoritesModel); + connectNewFavoritesModel(); + + sourceModel->setParent(this); + setSourceModel(sourceModel); +} + +InvalidAppsFilterProxy::~InvalidAppsFilterProxy() +{ +} + +void InvalidAppsFilterProxy::connectNewFavoritesModel() +{ + KAStatsFavoritesModel* favoritesModel = static_cast(m_parentModel->favoritesModel()); + connect(favoritesModel, &KAStatsFavoritesModel::favoritesChanged, this, &QSortFilterProxyModel::invalidate); + + invalidate(); +} + +bool InvalidAppsFilterProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_parent); + + const QString resource = sourceModel()->index(source_row, 0).data(ResultModel::ResourceRole).toString(); + + if (resource.startsWith(QLatin1String("applications:"))) { + KService::Ptr service = KService::serviceByStorageId(resource.section(QLatin1Char(':'), 1)); + + KAStatsFavoritesModel* favoritesModel = m_parentModel ? static_cast(m_parentModel->favoritesModel()) : nullptr; + + return (service && (!favoritesModel || !favoritesModel->isFavorite(service->storageId()))); + } + + return true; +} + +bool InvalidAppsFilterProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + return (left.row() < right.row()); +} + +bool GroupSortProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + const QString &lResource = sourceModel()->data(left, ResultModel::ResourceRole).toString(); + const QString &rResource = sourceModel()->data(right, ResultModel::ResourceRole).toString(); + + if (lResource.startsWith(QLatin1String("applications:")) + && !rResource.startsWith(QLatin1String("applications:"))) { + return true; + } else if (!lResource.startsWith(QLatin1String("applications:")) + && rResource.startsWith(QLatin1String("applications:"))) { + return false; + } + + return (left.row() < right.row()); +} + +RecentUsageModel::RecentUsageModel(QObject *parent, IncludeUsage usage, int ordering) +: ForwardingModel(parent) +, m_usage(usage) +, m_ordering((Ordering)ordering) +, m_complete(false) +{ + refresh(); +} + +RecentUsageModel::~RecentUsageModel() +{ +} + +RecentUsageModel::IncludeUsage RecentUsageModel::usage() const +{ + return m_usage; +} + +QString RecentUsageModel::description() const +{ + switch (m_usage) { + case AppsAndDocs: + return i18n("Recently Used"); + case OnlyApps: + return i18n("Applications"); + case OnlyDocs: + default: + return i18n("Documents"); + } +} + +QString RecentUsageModel::resourceAt(int row) const +{ + QSortFilterProxyModel *sourceProxy = qobject_cast(sourceModel()); + + if (sourceProxy) { + return sourceProxy->sourceModel()->data(sourceProxy->mapToSource(sourceProxy->index(row, 0)), + ResultModel::ResourceRole).toString(); + } + + return sourceModel()->data(index(row, 0), ResultModel::ResourceRole).toString(); +} + +QVariant RecentUsageModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + const QString &resource = resourceAt(index.row()); + + if (resource.startsWith(QLatin1String("applications:"))) { + return appData(resource, role); + } else { + return docData(resource, role); + } +} + +QVariant RecentUsageModel::appData(const QString &resource, int role) const +{ + const QString storageId = resource.section(QLatin1Char(':'), 1); + KService::Ptr service = KService::serviceByStorageId(storageId); + + QStringList allowedTypes({ QLatin1String("Service"), QLatin1String("Application") }); + + if (!service || !allowedTypes.contains(service->property(QLatin1String("Type")).toString()) + || service->exec().isEmpty()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + AppsModel *parentModel = qobject_cast(QObject::parent()); + + if (parentModel) { + return AppEntry::nameFromService(service, + (AppEntry::NameFormat)qobject_cast(QObject::parent())->appNameFormat()); + } else { + return AppEntry::nameFromService(service, AppEntry::NameOnly); + } + } else if (role == Qt::DecorationRole) { + return QIcon::fromTheme(service->icon(), QIcon::fromTheme(QStringLiteral("unknown"))); + } else if (role == Kicker::DescriptionRole) { + return service->comment(); + } else if (role == Kicker::GroupRole) { + return i18n("Applications"); + } else if (role == Kicker::FavoriteIdRole) { + return service->storageId(); + } else if (role == Kicker::HasActionListRole) { + return true; + } else if (role == Kicker::ActionListRole) { + QVariantList actionList; + + const QVariantList &jumpList = Kicker::jumpListActions(service); + if (!jumpList.isEmpty()) { + actionList << jumpList << Kicker::createSeparatorActionItem(); + } + + const QVariantList &recentDocuments = Kicker::recentDocumentActions(service); + if (!recentDocuments.isEmpty()) { + actionList << recentDocuments << Kicker::createSeparatorActionItem(); + } + + const QVariantMap &forgetAction = Kicker::createActionItem(i18n("Forget Application"), QStringLiteral("forget")); + actionList << forgetAction; + + const QVariantMap &forgetAllAction = Kicker::createActionItem(forgetAllActionName(), QStringLiteral("forgetAll")); + actionList << forgetAllAction; + + return actionList; + } + + return QVariant(); +} + +QVariant RecentUsageModel::docData(const QString &resource, int role) const +{ + QUrl url(resource); + + if (url.scheme().isEmpty()) { + url.setScheme(QStringLiteral("file")); + } + +#if KIO_VERSION >= QT_VERSION_CHECK(5,57,0) + // Avoid calling QT_LSTAT and accessing recent documents + const KFileItem fileItem(url, KFileItem::SkipMimeTypeFromContent); +#else + const KFileItem fileItem(url); +#endif + + if (!url.isValid()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return fileItem.text(); + } else if (role == Qt::DecorationRole) { + return QIcon::fromTheme(fileItem.iconName(), QIcon::fromTheme(QStringLiteral("unknown"))); + } else if (role == Kicker::GroupRole) { + return i18n("Documents"); + } else if (role == Kicker::FavoriteIdRole || role == Kicker::UrlRole) { + return url.toString(); + } else if (role == Kicker::UrlRole) { + return url; + } else if (role == Kicker::HasActionListRole) { + return true; + } else if (role == Kicker::ActionListRole) { + QVariantList actionList = Kicker::createActionListForFileItem(fileItem); + + actionList << Kicker::createSeparatorActionItem(); + + const QVariantMap &forgetAction = Kicker::createActionItem(i18n("Forget Document"), QStringLiteral("forget")); + actionList << forgetAction; + + const QVariantMap &forgetAllAction = Kicker::createActionItem(forgetAllActionName(), QStringLiteral("forgetAll")); + actionList << forgetAllAction; + + return actionList; + } + + return QVariant(); +} + +bool RecentUsageModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + Q_UNUSED(argument) + + bool withinBounds = row >= 0 && row < rowCount(); + + if (actionId.isEmpty() && withinBounds) { + const QString &resource = resourceAt(row); + + if (!resource.startsWith(QLatin1String("applications:"))) { + const QUrl resourceUrl = docData(resource, Kicker::UrlRole).toUrl(); + const QList urlsList{resourceUrl}; + + QMimeDatabase db; + QMimeType mime = db.mimeTypeForUrl(resourceUrl); + KService::Ptr service = KMimeTypeTrader::self()->preferredService(mime.name()); + if (service) { + KRun::runApplication(*service, urlsList, nullptr); + } else { + QTimer::singleShot(0, [urlsList] { + KRun::displayOpenWithDialog(urlsList, nullptr); + }); + } + + return true; + } + + const QString storageId = resource.section(QLatin1Char(':'), 1); + KService::Ptr service = KService::serviceByStorageId(storageId); + + if (!service) { + return false; + } + + quint32 timeStamp = 0; + +#if HAVE_X11 + if (QX11Info::isPlatformX11()) { + timeStamp = QX11Info::appUserTime(); + } +#endif + + // TODO Once we depend on KDE Frameworks 5.24 and D1902 is merged, use KRun::runApplication instead + KRun::runService(*service, {}, nullptr, true, {}, KStartupInfo::createNewStartupIdForTimestamp(timeStamp)); + + KActivities::ResourceInstance::notifyAccessed(QUrl(QStringLiteral("applications:") + storageId), + QStringLiteral("org.kde.plasma.kicker")); + + return true; + } else if (actionId == QLatin1String("forget") && withinBounds) { + if (m_activitiesModel) { + QModelIndex idx = sourceModel()->index(row, 0); + QSortFilterProxyModel *sourceProxy = qobject_cast(sourceModel()); + + while (sourceProxy) { + idx = sourceProxy->mapToSource(idx); + sourceProxy = qobject_cast(sourceProxy->sourceModel()); + } + + static_cast(m_activitiesModel.data())->forgetResource(idx.row()); + } + + return false; + } else if (actionId == QLatin1String("forgetAll")) { + if (m_activitiesModel) { + static_cast(m_activitiesModel.data())->forgetAllResources(); + } + + return false; + } else if (withinBounds) { + const QString &resource = resourceAt(row); + + if (resource.startsWith(QLatin1String("applications:"))) { + const QString storageId = sourceModel()->data(sourceModel()->index(row, 0), + ResultModel::ResourceRole).toString().section(QLatin1Char(':'), 1); + KService::Ptr service = KService::serviceByStorageId(storageId); + + if (service) { + return Kicker::handleRecentDocumentAction(service, actionId, argument); + } + } else { + bool close = false; + + QUrl url(sourceModel()->data(sourceModel()->index(row, 0), ResultModel::ResourceRole).toString()); + + KFileItem item(url); + + if (Kicker::handleFileItemAction(item, actionId, argument, &close)) { + return close; + } + } + } + + return false; +} + +bool RecentUsageModel::hasActions() const +{ + return rowCount(); +} + +QVariantList RecentUsageModel::actions() const +{ + QVariantList actionList; + + if (rowCount()) { + actionList << Kicker::createActionItem(forgetAllActionName(), QStringLiteral("forgetAll")); + } + + return actionList; +} + +QString RecentUsageModel::forgetAllActionName() const +{ + switch (m_usage) { + case AppsAndDocs: + return i18n("Forget All"); + case OnlyApps: + return i18n("Forget All Applications"); + case OnlyDocs: + default: + return i18n("Forget All Documents"); + } +} + +void RecentUsageModel::setOrdering(int ordering) +{ + if (ordering == m_ordering) return; + + m_ordering = (Ordering)ordering; + refresh(); + + emit orderingChanged(ordering); +} + +int RecentUsageModel::ordering() const +{ + return m_ordering; +} + +void RecentUsageModel::classBegin() +{ +} + +void RecentUsageModel::componentComplete() +{ + m_complete = true; + + refresh(); +} + +void RecentUsageModel::refresh() +{ + if (qmlEngine(this) && !m_complete) { + return; + } + + QAbstractItemModel *oldModel = sourceModel(); + disconnectSignals(); + setSourceModel(nullptr); + delete oldModel; + + auto query = UsedResources + | (m_ordering == Recent ? RecentlyUsedFirst : HighScoredFirst) + | Agent::any() + | Type::any() + | Activity::current(); + + switch (m_usage) { + case AppsAndDocs: + { + query = query | Url::startsWith(QStringLiteral("applications:")) | Url::file() | Limit(30); + break; + } + case OnlyApps: + { + query = query | Url::startsWith(QStringLiteral("applications:")) | Limit(15); + break; + } + case OnlyDocs: + default: + { + query = query | Url::file() | Limit(15); + } + } + + m_activitiesModel = new ResultModel(query); + QAbstractItemModel *model = m_activitiesModel; + + QModelIndex index; + + if (model->canFetchMore(index)) { + model->fetchMore(index); + } + + if (m_usage != OnlyDocs) { + model = new InvalidAppsFilterProxy(this, model); + } + + if (m_usage == AppsAndDocs) { + model = new GroupSortProxy(this, model); + } + + setSourceModel(model); +} diff --git a/applets/kicker/plugin/recentusagemodel.h b/applets/kicker/plugin/recentusagemodel.h new file mode 100644 index 000000000..04c5936a2 --- /dev/null +++ b/applets/kicker/plugin/recentusagemodel.h @@ -0,0 +1,115 @@ +/*************************************************************************** + * Copyright (C) 2014-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 RECENTUSAGEMODEL_H +#define RECENTUSAGEMODEL_H + +#include "forwardingmodel.h" + +#include +#include + +class GroupSortProxy : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + explicit GroupSortProxy(AbstractModel *parentModel, QAbstractItemModel *sourceModel); + ~GroupSortProxy() override; + + protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; +}; + +class InvalidAppsFilterProxy : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + explicit InvalidAppsFilterProxy(AbstractModel *parentModel, QAbstractItemModel *sourceModel); + ~InvalidAppsFilterProxy() override; + + protected: + bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + + private Q_SLOTS: + void connectNewFavoritesModel(); + + private: + QPointer m_parentModel; +}; + +class RecentUsageModel : public ForwardingModel, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(int ordering READ ordering WRITE setOrdering NOTIFY orderingChanged) + + public: + enum IncludeUsage { AppsAndDocs, OnlyApps, OnlyDocs }; + enum Ordering { Recent, Popular }; + + explicit RecentUsageModel( + QObject *parent = nullptr, + IncludeUsage usage = AppsAndDocs, + int ordering = Recent); + ~RecentUsageModel() override; + + QString description() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + bool hasActions() const override; + QVariantList actions() const override; + + IncludeUsage usage() const; + + void setOrdering(int ordering); + int ordering() const; + + void classBegin() override; + void componentComplete() override; + + Q_SIGNALS: + void orderingChanged(int ordering); + + private Q_SLOTS: + void refresh() override; + + private: + QVariant appData(const QString &resource, int role) const; + QVariant docData(const QString &resource, int role) const; + + QString resourceAt(int row) const; + + QString forgetAllActionName() const; + + IncludeUsage m_usage; + QPointer m_activitiesModel; + + Ordering m_ordering; + + bool m_complete; +}; + +#endif diff --git a/applets/kicker/plugin/rootmodel.cpp b/applets/kicker/plugin/rootmodel.cpp new file mode 100644 index 000000000..85d6b44be --- /dev/null +++ b/applets/kicker/plugin/rootmodel.cpp @@ -0,0 +1,453 @@ +/*************************************************************************** + * Copyright (C) 2014-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 . * + ***************************************************************************/ + +#include "rootmodel.h" +#include "actionlist.h" +#include "kastatsfavoritesmodel.h" +#include "recentcontactsmodel.h" +#include "recentusagemodel.h" +#include "systemmodel.h" + +#include + +#include + +GroupEntry::GroupEntry(AppsModel *parentModel, const QString &name, + const QString &iconName, AbstractModel *childModel) +: AbstractGroupEntry(parentModel) +, m_name(name) +, m_iconName(iconName) +, m_childModel(childModel) +{ + QObject::connect(parentModel, &RootModel::cleared, childModel, &AbstractModel::deleteLater); + + QObject::connect(childModel, &AbstractModel::countChanged, + [parentModel, this] { if (parentModel) { parentModel->entryChanged(this); } } + ); +} + +QString GroupEntry::name() const +{ + return m_name; +} + +QIcon GroupEntry::icon() const +{ + return QIcon::fromTheme(m_iconName, QIcon::fromTheme(QStringLiteral("unknown"))); +} + +bool GroupEntry::hasChildren() const +{ + return m_childModel && m_childModel->count() > 0; +} + +AbstractModel *GroupEntry::childModel() const +{ + return m_childModel; +} + +RootModel::RootModel(QObject *parent) : AppsModel(QString(), parent) +, m_favorites(new KAStatsFavoritesModel(this)) +, m_systemModel(nullptr) +, m_showAllApps(false) +, m_showAllAppsCategorized(false) +, m_showRecentApps(true) +, m_showRecentDocs(true) +, m_showRecentContacts(false) +, m_recentOrdering(RecentUsageModel::Recent) +, m_showPowerSession(true) +, m_recentAppsModel(nullptr) +, m_recentDocsModel(nullptr) +, m_recentContactsModel(nullptr) +{ +} + +RootModel::~RootModel() +{ +} + +QVariant RootModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || index.row() >= m_entryList.count()) { + return QVariant(); + } + + if (role == Kicker::HasActionListRole || role == Kicker::ActionListRole) { + const AbstractEntry *entry = m_entryList.at(index.row()); + + if (entry->type() == AbstractEntry::GroupType) { + const GroupEntry *group = static_cast(entry); + AbstractModel *model = group->childModel(); + + if (model == m_recentAppsModel + || model == m_recentDocsModel + || model == m_recentContactsModel) { + if (role == Kicker::HasActionListRole) { + return true; + } else if (role == Kicker::ActionListRole) { + QVariantList actionList; + actionList << model->actions(); + actionList << Kicker::createSeparatorActionItem(); + actionList << Kicker::createActionItem(i18n("Hide %1", + group->name()), QStringLiteral("hideCategory")); + return actionList; + } + } + } + } + + return AppsModel::data(index, role); +} + +bool RootModel::trigger(int row, const QString& actionId, const QVariant& argument) +{ + const AbstractEntry *entry = m_entryList.at(row); + + if (entry->type() == AbstractEntry::GroupType) { + if (actionId == QLatin1String("hideCategory")) { + AbstractModel *model = entry->childModel(); + + if (model == m_recentAppsModel) { + setShowRecentApps(false); + + return true; + } else if (model == m_recentDocsModel) { + setShowRecentDocs(false); + + return true; + } else if (model == m_recentContactsModel) { + setShowRecentContacts(false); + + return true; + } + } else if (entry->childModel()->hasActions()) { + return entry->childModel()->trigger(-1, actionId, QVariant()); + } + } + + return AppsModel::trigger(row, actionId, argument); +} + +bool RootModel::showAllApps() const +{ + return m_showAllApps; +} + +void RootModel::setShowAllApps(bool show) +{ + if (m_showAllApps != show) { + m_showAllApps = show; + + refresh(); + + emit showAllAppsChanged(); + } +} + +bool RootModel::showAllAppsCategorized() const +{ + return m_showAllAppsCategorized; +} + +void RootModel::setShowAllAppsCategorized(bool showCategorized) +{ + if (m_showAllAppsCategorized != showCategorized) { + m_showAllAppsCategorized = showCategorized; + + refresh(); + + emit showAllAppsCategorizedChanged(); + } +} + +bool RootModel::showRecentApps() const +{ + return m_showRecentApps; +} + +void RootModel::setShowRecentApps(bool show) +{ + if (show != m_showRecentApps) { + m_showRecentApps = show; + + refresh(); + + emit showRecentAppsChanged(); + } +} + +bool RootModel::showRecentDocs() const +{ + return m_showRecentDocs; +} + +void RootModel::setShowRecentDocs(bool show) +{ + if (show != m_showRecentDocs) { + m_showRecentDocs = show; + + refresh(); + + emit showRecentDocsChanged(); + } +} + +bool RootModel::showRecentContacts() const +{ + return m_showRecentContacts; +} + +void RootModel::setShowRecentContacts(bool show) +{ + if (show != m_showRecentContacts) { + m_showRecentContacts = show; + + refresh(); + + emit showRecentContactsChanged(); + } +} + +int RootModel::recentOrdering() const +{ + return m_recentOrdering; +} + +void RootModel::setRecentOrdering(int ordering) +{ + if (ordering != m_recentOrdering) { + m_recentOrdering = ordering; + + refresh(); + + emit recentOrderingChanged(); + } +} + +bool RootModel::showPowerSession() const +{ + return m_showPowerSession; +} + +void RootModel::setShowPowerSession(bool show) +{ + if (show != m_showPowerSession) { + m_showPowerSession = show; + + refresh(); + + emit showPowerSessionChanged(); + } +} + +AbstractModel* RootModel::favoritesModel() +{ + return m_favorites; +} + +AbstractModel* RootModel::systemFavoritesModel() +{ + if (m_systemModel) { + return m_systemModel->favoritesModel(); + } + + return nullptr; +} + +void RootModel::refresh() +{ + if (!m_complete) { + return; + } + + beginResetModel(); + + AppsModel::refreshInternal(); + + AppsModel *allModel = nullptr; + m_recentAppsModel = nullptr; + m_recentDocsModel = nullptr; + m_recentContactsModel = nullptr; + + if (m_showAllApps) { + QHash appsHash; + + std::function processEntry = [&](AbstractEntry *entry) { + if (entry->type() == AbstractEntry::RunnableType) { + AppEntry *appEntry = static_cast(entry); + appsHash.insert(appEntry->service()->menuId(), appEntry); + } else if (entry->type() == AbstractEntry::GroupType) { + GroupEntry *groupEntry = static_cast(entry); + AbstractModel *model = groupEntry->childModel(); + + if (!model) { + return; + } + + for (int i = 0; i < model->count(); ++i) { + processEntry(static_cast(model->index(i, 0).internalPointer())); + } + } + }; + + for (AbstractEntry *entry : m_entryList) { + processEntry(entry); + } + + QList apps(appsHash.values()); + QCollator c; + + std::sort(apps.begin(), apps.end(), + [&c](AbstractEntry* a, AbstractEntry* b) { + if (a->type() != b->type()) { + return a->type() > b->type(); + } else { + return c.compare(a->name(), b->name()) < 0; + } + }); + + if (!m_showAllAppsCategorized && !m_paginate) { // The app list built above goes into a model. + allModel = new AppsModel(apps, false, this); + } else if (m_paginate) { // We turn the apps list into a subtree of pages. + m_favorites = new KAStatsFavoritesModel(this); + emit favoritesModelChanged(); + + QList groups; + + int at = 0; + QList page; + page.reserve(m_pageSize); + + foreach(AbstractEntry *app, apps) { + page.append(app); + + if (at == (m_pageSize - 1)) { + at = 0; + AppsModel *model = new AppsModel(page, false, this); + groups.append(new GroupEntry(this, QString(), QString(), model)); + page.clear(); + } else { + ++at; + } + } + + if (!page.isEmpty()) { + AppsModel *model = new AppsModel(page, false, this); + groups.append(new GroupEntry(this, QString(), QString(), model)); + } + + groups.prepend(new GroupEntry(this, QString(), QString(), m_favorites)); + + allModel = new AppsModel(groups, true, this); + } else { // We turn the apps list into a subtree of apps by starting letter. + QList groups; + QHash> m_categoryHash; + + foreach (const AbstractEntry *groupEntry, m_entryList) { + AbstractModel *model = groupEntry->childModel(); + + if (!model) continue; + + for (int i = 0; i < model->count(); ++i) { + AbstractEntry *appEntry = static_cast(model->index(i, 0).internalPointer()); + + if (appEntry->name().isEmpty()) { + continue; + } + + const QChar &first = appEntry->name().at(0).toUpper(); + m_categoryHash[first.isDigit() ? QStringLiteral("0-9") : first].append(appEntry); + } + } + + QHashIterator> i(m_categoryHash); + + while (i.hasNext()) { + i.next(); + AppsModel *model = new AppsModel(i.value(), false, this); + model->setDescription(i.key()); + groups.append(new GroupEntry(this, i.key(), QString(), model)); + } + + allModel = new AppsModel(groups, true, this); + } + + allModel->setDescription(QStringLiteral("KICKER_ALL_MODEL")); // Intentionally no i18n. + } + + int separatorPosition = 0; + + if (allModel) { + m_entryList.prepend(new GroupEntry(this, i18n("All Applications"), QString(), allModel)); + ++separatorPosition; + } + + if (m_showRecentContacts) { + m_recentContactsModel = new RecentContactsModel(this); + m_entryList.prepend(new GroupEntry(this, i18n("Recent Contacts"), QString("view-history"), m_recentContactsModel)); + ++separatorPosition; + } + + if (m_showRecentDocs) { + m_recentDocsModel = new RecentUsageModel(this, RecentUsageModel::OnlyDocs, m_recentOrdering); + m_entryList.prepend(new GroupEntry(this, + m_recentOrdering == RecentUsageModel::Recent + ? i18n("Recent Documents") + : i18n("Often Used Documents"), + m_recentOrdering == RecentUsageModel::Recent + ? QString("view-history") + : QString("office-chart-pie"), + m_recentDocsModel)); + ++separatorPosition; + } + + if (m_showRecentApps) { + m_recentAppsModel = new RecentUsageModel(this, RecentUsageModel::OnlyApps, m_recentOrdering); + m_entryList.prepend(new GroupEntry(this, + m_recentOrdering == RecentUsageModel::Recent + ? i18n("Recent Applications") + : i18n("Often Used Applications"), + m_recentOrdering == RecentUsageModel::Recent + ? QString("view-history") + : QString("office-chart-pie"), + m_recentAppsModel)); + ++separatorPosition; + } + + if (m_showSeparators && separatorPosition > 0) { + m_entryList.insert(separatorPosition, new SeparatorEntry(this)); + ++m_separatorCount; + } + + m_systemModel = new SystemModel(this); + + if (m_showPowerSession) { + m_entryList << new GroupEntry(this, i18n("Power / Session"), QString("system-log-out"), m_systemModel); + } + + endResetModel(); + + m_favorites->refresh(); + + emit systemFavoritesModelChanged(); + emit countChanged(); + emit separatorCountChanged(); + + emit refreshed(); +} diff --git a/applets/kicker/plugin/rootmodel.h b/applets/kicker/plugin/rootmodel.h new file mode 100644 index 000000000..00766e2b5 --- /dev/null +++ b/applets/kicker/plugin/rootmodel.h @@ -0,0 +1,127 @@ +/*************************************************************************** + * Copyright (C) 2014-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 ROOTMODEL_H +#define ROOTMODEL_H + +#include "appsmodel.h" + +class KAStatsFavoritesModel; +class RecentContactsModel; +class RecentUsageModel; +class SystemModel; + +class RootModel; + +class GroupEntry : public AbstractGroupEntry +{ + public: + GroupEntry(AppsModel *parentModel, const QString &name, + const QString &iconName, AbstractModel *childModel); + + QIcon icon() const override; + QString name() const override; + + bool hasChildren() const override; + AbstractModel *childModel() const override; + + private: + QString m_name; + QString m_iconName; + QPointer m_childModel; +}; + +class RootModel : public AppsModel +{ + Q_OBJECT + + Q_PROPERTY(QObject* systemFavoritesModel READ systemFavoritesModel NOTIFY systemFavoritesModelChanged) + Q_PROPERTY(bool showAllApps READ showAllApps WRITE setShowAllApps NOTIFY showAllAppsChanged) + Q_PROPERTY(bool showAllAppsCategorized READ showAllAppsCategorized WRITE setShowAllAppsCategorized NOTIFY showAllAppsCategorizedChanged) + Q_PROPERTY(bool showRecentApps READ showRecentApps WRITE setShowRecentApps NOTIFY showRecentAppsChanged) + Q_PROPERTY(bool showRecentDocs READ showRecentDocs WRITE setShowRecentDocs NOTIFY showRecentDocsChanged) + Q_PROPERTY(bool showRecentContacts READ showRecentContacts WRITE setShowRecentContacts NOTIFY showRecentContactsChanged) + Q_PROPERTY(int recentOrdering READ recentOrdering WRITE setRecentOrdering NOTIFY recentOrderingChanged) + Q_PROPERTY(bool showPowerSession READ showPowerSession WRITE setShowPowerSession NOTIFY showPowerSessionChanged) + + public: + explicit RootModel(QObject *parent = nullptr); + ~RootModel() override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + bool showAllApps() const; + void setShowAllApps(bool show); + + bool showAllAppsCategorized() const; + void setShowAllAppsCategorized(bool showCategorized); + + bool showRecentApps() const; + void setShowRecentApps(bool show); + + bool showRecentDocs() const; + void setShowRecentDocs(bool show); + + bool showRecentContacts() const; + void setShowRecentContacts(bool show); + + int recentOrdering() const; + void setRecentOrdering(int ordering); + + bool showPowerSession() const; + void setShowPowerSession(bool show); + + AbstractModel* favoritesModel() override; + AbstractModel* systemFavoritesModel(); + + Q_SIGNALS: + void refreshed() const; + void systemFavoritesModelChanged() const; + void showAllAppsChanged() const; + void showAllAppsCategorizedChanged() const; + void showRecentAppsChanged() const; + void showRecentDocsChanged() const; + void showRecentContactsChanged() const; + void showPowerSessionChanged() const; + void recentOrderingChanged() const; + void recentAppsModelChanged() const; + + protected Q_SLOTS: + void refresh() override; + + private: + KAStatsFavoritesModel *m_favorites; + SystemModel *m_systemModel; + + bool m_showAllApps; + bool m_showAllAppsCategorized; + bool m_showRecentApps; + bool m_showRecentDocs; + bool m_showRecentContacts; + int m_recentOrdering; + bool m_showPowerSession; + + RecentUsageModel *m_recentAppsModel; + RecentUsageModel *m_recentDocsModel; + RecentContactsModel *m_recentContactsModel; +}; + +#endif diff --git a/applets/kicker/plugin/runnermatchesmodel.cpp b/applets/kicker/plugin/runnermatchesmodel.cpp new file mode 100644 index 000000000..0756e5f58 --- /dev/null +++ b/applets/kicker/plugin/runnermatchesmodel.cpp @@ -0,0 +1,255 @@ +/*************************************************************************** + * Copyright (C) 2012 by Aurélien Gâteau * + * Copyright (C) 2014-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 . * + ***************************************************************************/ + +#include "runnermatchesmodel.h" +#include "runnermodel.h" +#include "actionlist.h" + +#include +#include + +#include +#include +#include + +#include + +RunnerMatchesModel::RunnerMatchesModel(const QString &runnerId, const QString &name, + Plasma::RunnerManager *manager, QObject *parent) +: AbstractModel(parent) +, m_runnerId(runnerId) +, m_name(name) +, m_runnerManager(manager) +{ +} + +QString RunnerMatchesModel::description() const +{ + return m_name; +} + +QVariant RunnerMatchesModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_matches.count()) { + return QVariant(); + } + + Plasma::QueryMatch match = m_matches.at(index.row()); + + if (role == Qt::DisplayRole) { + return match.text(); + } else if (role == Qt::DecorationRole) { + if (!match.iconName().isEmpty()) { + return match.iconName(); + } + + return match.icon(); + } else if (role == Kicker::DescriptionRole) { + return match.subtext(); + } else if (role == Kicker::FavoriteIdRole) { + if (match.runner()->id() == QLatin1String("services")) { + return match.data().toString(); + } + } else if (role == Kicker::UrlRole) { + const QString &runnerId = match.runner()->id(); + if (runnerId == QLatin1String("baloosearch") || runnerId == QLatin1String("bookmarks")) { + return QUrl(match.data().toString()); + } else if (runnerId == QLatin1String("recentdocuments") + || runnerId == QLatin1String("services")) { + KService::Ptr service = KService::serviceByStorageId(match.data().toString()); + if (service) { + return QUrl::fromLocalFile(Kicker::resolvedServiceEntryPath(service)); + } + } + } else if (role == Kicker::HasActionListRole) { + // Hack to expose the protected Plasma::AbstractRunner::actions() method. + class MyRunner : public Plasma::AbstractRunner + { + public: + using Plasma::AbstractRunner::actions; + }; + + MyRunner *runner = static_cast(match.runner()); + + Q_ASSERT(runner); + + return match.runner()->id() == QLatin1String("services") || !runner->actions().isEmpty(); + } else if (role == Kicker::ActionListRole) { + QVariantList actionList; + + foreach (QAction *action, m_runnerManager->actionsForMatch(match)) { + QVariantMap item = Kicker::createActionItem(action->text(), QStringLiteral("runnerAction"), + QVariant::fromValue(action)); + item[QStringLiteral("icon")] = action->icon(); + + actionList << item; + } + + // Only try to get a KService for matches from the services runner. Assuming + // that any other runner returns something we want to turn into a KService is + // unsafe, e.g. files from the Baloo runner might match a storageId just by + // accident, creating a dangerous false positive. + if (match.runner()->id() != QLatin1String("services")) { + return actionList; + } + + const KService::Ptr service = KService::serviceByStorageId(match.data().toString()); + + if (service) { + if (!actionList.isEmpty()) { + actionList << Kicker::createSeparatorActionItem(); + } + + const QVariantList &jumpListActions = Kicker::jumpListActions(service); + if (!jumpListActions.isEmpty()) { + actionList << jumpListActions << Kicker::createSeparatorActionItem(); + } + + QObject *appletInterface = static_cast(parent())->appletInterface(); + + const bool systemImmutable = appletInterface->property("immutability").toInt() == Plasma::Types::SystemImmutable; + + const QVariantList &addLauncherActions = Kicker::createAddLauncherActionList(appletInterface, service); + if (!systemImmutable && !addLauncherActions.isEmpty()) { + actionList << addLauncherActions << Kicker::createSeparatorActionItem(); + } + + const QVariantList &recentDocuments = Kicker::recentDocumentActions(service); + if (!recentDocuments.isEmpty()) { + actionList << recentDocuments << Kicker::createSeparatorActionItem(); + } + + // Don't allow adding launchers, editing, hiding, or uninstalling applications + // when system is immutable. + if (systemImmutable) { + return actionList; + } + + if (service->isApplication()) { + actionList << Kicker::editApplicationAction(service); + actionList << Kicker::appstreamActions(service); + } + } + + return actionList; + } + + return QVariant(); +} + +int RunnerMatchesModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : m_matches.count(); +} + +bool RunnerMatchesModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + if (row < 0 || row >= m_matches.count()) { + return false; + } + + Plasma::QueryMatch match = m_matches.at(row); + + if (!match.isEnabled()) { + return false; + } + + QObject *appletInterface = static_cast(parent())->appletInterface(); + + const KService::Ptr service = KService::serviceByStorageId(match.data().toString()); + + if (Kicker::handleAddLauncherAction(actionId, appletInterface, service)) { + return true; + } else if (Kicker::handleEditApplicationAction(actionId, service)) { + return true; + } else if (Kicker::handleAppstreamActions(actionId, argument)) { + return true; + } else if (actionId == QLatin1String("_kicker_jumpListAction")) { + return KRun::run(argument.toString(), {}, nullptr, service ? service->name() : QString(), service ? service->icon() : QString()); + } else if (actionId == QLatin1String("_kicker_recentDocument") + || actionId == QLatin1String("_kicker_forgetRecentDocuments")) { + return Kicker::handleRecentDocumentAction(service, actionId, argument); + } + + if (!actionId.isEmpty()) { + QObject *obj = argument.value(); + + if (!obj) { + return false; + } + + QAction *action = qobject_cast(obj); + + if (!action) { + return false; + } + + match.setSelectedAction(action); + } + + m_runnerManager->run(match); + + return true; +} + +void RunnerMatchesModel::setMatches(const QList< Plasma::QueryMatch > &matches) +{ + int oldCount = m_matches.count(); + int newCount = matches.count(); + + bool emitCountChange = (oldCount != newCount); + + int ceiling = qMin(oldCount, newCount); + bool emitDataChange = false; + + for (int row = 0; row < ceiling; ++row) { + if (!(m_matches.at(row) == matches.at(row))) { + emitDataChange = true; + m_matches[row] = matches.at(row); + } + } + + if (emitDataChange) { + emit dataChanged(index(0, 0), index(ceiling - 1, 0)); + } + + if (newCount > oldCount) { + beginInsertRows(QModelIndex(), oldCount, newCount - 1); + + m_matches = matches; + + endInsertRows(); + } else if (newCount < oldCount) { + beginRemoveRows(QModelIndex(), newCount, oldCount - 1); + + m_matches = matches; + + endRemoveRows(); + } + + if (emitCountChange) { + emit countChanged(); + } +} + +AbstractModel *RunnerMatchesModel::favoritesModel() +{ + return static_cast(parent())->favoritesModel(); +} diff --git a/applets/kicker/plugin/runnermatchesmodel.h b/applets/kicker/plugin/runnermatchesmodel.h new file mode 100644 index 000000000..932fefe02 --- /dev/null +++ b/applets/kicker/plugin/runnermatchesmodel.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (C) 2012 by Aurélien Gâteau * + * Copyright (C) 2014 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 RUNNERMATCHESMODEL_H +#define RUNNERMATCHESMODEL_H + +#include "abstractmodel.h" + +#include + +namespace Plasma { + class RunnerManager; +} + +class RunnerMatchesModel : public AbstractModel +{ + Q_OBJECT + + Q_PROPERTY(QString name READ name CONSTANT) + + public: + explicit RunnerMatchesModel(const QString &runnerId, const QString &name, + Plasma::RunnerManager *manager, QObject *parent = nullptr); + + QString description() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + QString runnerId() const { return m_runnerId; } + QString name() const { return m_name; } + + void setMatches(const QList &matches); + + AbstractModel* favoritesModel() override; + + private: + QString m_runnerId; + QString m_name; + Plasma::RunnerManager *m_runnerManager; + QList m_matches; +}; + +#endif diff --git a/applets/kicker/plugin/runnermodel.cpp b/applets/kicker/plugin/runnermodel.cpp new file mode 100644 index 000000000..6a9b8188e --- /dev/null +++ b/applets/kicker/plugin/runnermodel.cpp @@ -0,0 +1,337 @@ +/*************************************************************************** + * Copyright (C) 2012 by Aurélien Gâteau * + * Copyright (C) 2014 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 . * + ***************************************************************************/ + +#include "runnermodel.h" +#include "runnermatchesmodel.h" + +#include + +#include +#include +#include + +RunnerModel::RunnerModel(QObject *parent) : QAbstractListModel(parent) +, m_favoritesModel(nullptr) +, m_appletInterface(nullptr) +, m_runnerManager(nullptr) +, m_mergeResults(false) +, m_deleteWhenEmpty(false) +{ + m_queryTimer.setSingleShot(true); + m_queryTimer.setInterval(10); + connect(&m_queryTimer, SIGNAL(timeout()), this, SLOT(startQuery())); +} + +RunnerModel::~RunnerModel() +{ +} + +QHash RunnerModel::roleNames() const +{ + return {{ Qt::DisplayRole, "display" }}; +} + +AbstractModel *RunnerModel::favoritesModel() const +{ + return m_favoritesModel; +} + +void RunnerModel::setFavoritesModel(AbstractModel *model) +{ + if (m_favoritesModel != model) { + m_favoritesModel = model; + + clear(); + + if (!m_query.isEmpty()) { + m_queryTimer.start(); + } + + emit favoritesModelChanged(); + } +} + +QObject *RunnerModel::appletInterface() const +{ + return m_appletInterface; +} + +void RunnerModel::setAppletInterface(QObject *appletInterface) +{ + if (m_appletInterface != appletInterface) { + m_appletInterface = appletInterface; + + clear(); + + if (!m_query.isEmpty()) { + m_queryTimer.start(); + } + + emit appletInterfaceChanged(); + } +} + +bool RunnerModel::deleteWhenEmpty() const +{ + return m_deleteWhenEmpty; +} + +void RunnerModel::setDeleteWhenEmpty(bool deleteWhenEmpty) +{ + if (m_deleteWhenEmpty != deleteWhenEmpty) { + m_deleteWhenEmpty = deleteWhenEmpty; + + clear(); + + if (!m_query.isEmpty()) { + m_queryTimer.start(); + } + + emit deleteWhenEmptyChanged(); + } +} + +bool RunnerModel::mergeResults() const +{ + return m_mergeResults; +} + +void RunnerModel::setMergeResults(bool merge) +{ + if (m_mergeResults != merge) { + m_mergeResults = merge; + + clear(); + + if (!m_query.isEmpty()) { + m_queryTimer.start(); + } + + emit mergeResultsChanged(); + } +} + +QVariant RunnerModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_models.count()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return m_models.at(index.row())->name(); + } + + return QVariant(); +} + +int RunnerModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : m_models.count(); +} + +int RunnerModel::count() const +{ + return rowCount(); +} + +QObject *RunnerModel::modelForRow(int row) +{ + if (row < 0 || row >= m_models.count()) { + return nullptr; + } + + return m_models.at(row); +} + +QStringList RunnerModel::runners() const +{ + return m_runners; +} + +void RunnerModel::setRunners(const QStringList &runners) +{ + if (m_runners.toSet() != runners.toSet()) { + m_runners = runners; + + if (m_runnerManager) { + m_runnerManager->setAllowedRunners(runners); + } + + emit runnersChanged(); + } +} + +QString RunnerModel::query() const +{ + return m_query; +} + +void RunnerModel::setQuery(const QString &query) +{ + if (m_query != query) { + m_query = query; + + m_queryTimer.start(); + + emit queryChanged(); + } +} + +void RunnerModel::startQuery() +{ + if (m_query.isEmpty()) { + clear(); + } + + if (m_query.isEmpty() && m_runnerManager) { + return; + } + + createManager(); + + m_runnerManager->launchQuery(m_query); +} + +void RunnerModel::matchesChanged(const QList &matches) +{ + // Group matches by runner. + // We do not use a QMultiHash here because it keeps values in LIFO order, while we want FIFO. + QHash > matchesForRunner; + + foreach (const Plasma::QueryMatch &match, matches) { + auto it = matchesForRunner.find(match.runner()->id()); + + if (it == matchesForRunner.end()) { + it = matchesForRunner.insert(match.runner()->id(), QList()); + } + + it.value().append(match); + } + + // Sort matches for all runners in descending order. This allows the best + // match to win whilest preserving order between runners. + for (auto &list : matchesForRunner) { + std::sort(list.begin(), list.end(), qGreater()); + } + + if (m_mergeResults) { + RunnerMatchesModel *matchesModel = nullptr; + + if (m_models.isEmpty()) { + matchesModel = new RunnerMatchesModel(QString(), i18n("Search results"), + m_runnerManager, this); + + beginInsertRows(QModelIndex(), 0, 0); + m_models.append(matchesModel); + endInsertRows(); + emit countChanged(); + } else { + matchesModel = m_models.at(0); + } + + QList matches; + + foreach (const QString &runnerId, m_runners) { + matches.append(matchesForRunner.take(runnerId)); + } + + matchesModel->setMatches(matches); + + return; + } + + // Assign matches to existing models. If there is no match for a model, delete it. + for (int row = m_models.count() - 1; row >= 0; --row) { + RunnerMatchesModel *matchesModel = m_models.at(row); + QList matches = matchesForRunner.take(matchesModel->runnerId()); + + if (m_deleteWhenEmpty && matches.isEmpty()) { + beginRemoveRows(QModelIndex(), row, row); + m_models.removeAt(row); + delete matchesModel; + endRemoveRows(); + emit countChanged(); + } else { + matchesModel->setMatches(matches); + } + } + + // At this point, matchesForRunner contains only matches for runners which + // do not have a model yet. Create new models for them. + if (!matchesForRunner.isEmpty()) { + auto it = matchesForRunner.constBegin(); + auto end = matchesForRunner.constEnd(); + int appendCount = 0; + + for (; it != end; ++it) { + QList matches = it.value(); + Q_ASSERT(!matches.isEmpty()); + RunnerMatchesModel *matchesModel = new RunnerMatchesModel(it.key(), + matches.first().runner()->name(), m_runnerManager, this); + matchesModel->setMatches(matches); + + if (it.key() == QLatin1String("services")) { + beginInsertRows(QModelIndex(), 0, 0); + m_models.prepend(matchesModel); + endInsertRows(); + emit countChanged(); + } else { + m_models.append(matchesModel); + ++appendCount; + } + } + + if (appendCount > 0) { + beginInsertRows(QModelIndex(), rowCount() - appendCount, rowCount() - 1); + endInsertRows(); + emit countChanged(); + } + } +} + +void RunnerModel::createManager() +{ + if (!m_runnerManager) { + m_runnerManager = new Plasma::RunnerManager(this); // FIXME: Which KConfigGroup is this using now? + m_runnerManager->setAllowedRunners(m_runners); + connect(m_runnerManager, SIGNAL(matchesChanged(QList)), + this, SLOT(matchesChanged(QList))); + } +} + +void RunnerModel::clear() +{ + if (m_runnerManager) { + m_runnerManager->reset(); + } + + if (m_models.isEmpty()) { + return; + } + + beginResetModel(); + + qDeleteAll(m_models); + m_models.clear(); + + endResetModel(); + + emit countChanged(); +} diff --git a/applets/kicker/plugin/runnermodel.h b/applets/kicker/plugin/runnermodel.h new file mode 100644 index 000000000..9320202e1 --- /dev/null +++ b/applets/kicker/plugin/runnermodel.h @@ -0,0 +1,108 @@ +/*************************************************************************** + * Copyright (C) 2012 by Aurélien Gâteau * + * Copyright (C) 2014 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 RUNNERMODEL_H +#define RUNNERMODEL_H + +#include "abstractmodel.h" + +#include +#include + +#include + +namespace Plasma { + class RunnerManager; +} + +class AbstractModel; +class RunnerMatchesModel; + +class RunnerModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(AbstractModel* favoritesModel READ favoritesModel WRITE setFavoritesModel NOTIFY favoritesModelChanged) + Q_PROPERTY(QObject* appletInterface READ appletInterface WRITE setAppletInterface NOTIFY appletInterfaceChanged) + Q_PROPERTY(QStringList runners READ runners WRITE setRunners NOTIFY runnersChanged) + Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged) + Q_PROPERTY(bool mergeResults READ mergeResults WRITE setMergeResults NOTIFY mergeResultsChanged) + Q_PROPERTY(bool deleteWhenEmpty READ deleteWhenEmpty WRITE setDeleteWhenEmpty NOTIFY deleteWhenEmptyChanged) + + public: + explicit RunnerModel(QObject *parent = nullptr); + ~RunnerModel() override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int count() const; + + Q_INVOKABLE QObject *modelForRow(int row); + + QStringList runners() const; + void setRunners(const QStringList &runners); + + QString query() const; + void setQuery(const QString &query); + + AbstractModel *favoritesModel() const; + void setFavoritesModel(AbstractModel *model); + + QObject *appletInterface() const; + void setAppletInterface(QObject *appletInterface); + + bool mergeResults() const; + void setMergeResults(bool merge); + + bool deleteWhenEmpty() const; + void setDeleteWhenEmpty(bool deleteWhenEmpty); + + Q_SIGNALS: + void countChanged() const; + void favoritesModelChanged() const; + void appletInterfaceChanged() const; + void runnersChanged() const; + void queryChanged() const; + void mergeResultsChanged() const; + void deleteWhenEmptyChanged(); + + private Q_SLOTS: + void startQuery(); + void matchesChanged(const QList &matches); + + private: + void createManager(); + void clear(); + + AbstractModel *m_favoritesModel; + QObject *m_appletInterface; + Plasma::RunnerManager *m_runnerManager; + QStringList m_runners; + QList m_models; + QString m_query; + QTimer m_queryTimer; + bool m_mergeResults; + bool m_deleteWhenEmpty; +}; + +#endif diff --git a/applets/kicker/plugin/simplefavoritesmodel.cpp b/applets/kicker/plugin/simplefavoritesmodel.cpp new file mode 100644 index 000000000..9bcbcdee1 --- /dev/null +++ b/applets/kicker/plugin/simplefavoritesmodel.cpp @@ -0,0 +1,333 @@ +/*************************************************************************** + * Copyright (C) 2014-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 . * + ***************************************************************************/ + +#include "simplefavoritesmodel.h" +#include "appentry.h" +#include "contactentry.h" +#include "fileentry.h" +#include "systementry.h" +#include "actionlist.h" + +#include + +SimpleFavoritesModel::SimpleFavoritesModel(QObject *parent) : AbstractModel(parent) +, m_enabled(true) +, m_maxFavorites(-1) +, m_dropPlaceholderIndex(-1) +{ +} + +SimpleFavoritesModel::~SimpleFavoritesModel() +{ + qDeleteAll(m_entryList); +} + +QString SimpleFavoritesModel::description() const +{ + return i18n("Favorites"); +} + +QVariant SimpleFavoritesModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || index.row() >= rowCount()) { + return QVariant(); + } + + if (index.row() == m_dropPlaceholderIndex) { + if (role == Kicker::IsDropPlaceholderRole) { + return true; + } else { + return QVariant(); + } + } + + int mappedIndex = index.row(); + + if (m_dropPlaceholderIndex != -1 && mappedIndex > m_dropPlaceholderIndex) { + --mappedIndex; + } + + const AbstractEntry *entry = m_entryList.at(mappedIndex); + + if (role == Qt::DisplayRole) { + return entry->name(); + } else if (role == Qt::DecorationRole) { + return entry->icon(); + } else if (role == Kicker::DescriptionRole) { + return entry->description(); + } else if (role == Kicker::FavoriteIdRole) { + return entry->id(); + } else if (role == Kicker::UrlRole) { + return entry->url(); + } else if (role == Kicker::HasActionListRole) { + return entry->hasActions(); + } else if (role == Kicker::ActionListRole) { + return entry->actions(); + } + + return QVariant(); +} + +int SimpleFavoritesModel::rowCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : m_entryList.count() + (m_dropPlaceholderIndex != -1 ? 1 : 0); +} + +bool SimpleFavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + if (row < 0 || row >= m_entryList.count()) { + return false; + } + + return m_entryList.at(row)->run(actionId, argument); +} + +bool SimpleFavoritesModel::enabled() const +{ + return m_enabled; +} + +void SimpleFavoritesModel::setEnabled(bool enable) +{ + if (m_enabled != enable) { + m_enabled = enable; + + emit enabledChanged(); + } +} + +QStringList SimpleFavoritesModel::favorites() const +{ + return m_favorites; +} + +void SimpleFavoritesModel::setFavorites(const QStringList& favorites) +{ + QStringList _favorites(favorites); + _favorites.removeDuplicates(); + + if (_favorites != m_favorites) { + m_favorites = _favorites; + refresh(); + } +} + +int SimpleFavoritesModel::maxFavorites() const +{ + return m_maxFavorites; +} + +void SimpleFavoritesModel::setMaxFavorites(int max) +{ + if (m_maxFavorites != max) + { + m_maxFavorites = max; + + if (m_maxFavorites != -1 && m_favorites.count() > m_maxFavorites) { + refresh(); + } + + emit maxFavoritesChanged(); + } +} + +bool SimpleFavoritesModel::isFavorite(const QString &id) const +{ + return m_favorites.contains(id); +} + +void SimpleFavoritesModel::addFavorite(const QString &id, int index) +{ + if (!m_enabled || id.isEmpty()) { + return; + } + + if (m_maxFavorites != -1 && m_favorites.count() == m_maxFavorites) { + return; + } + + AbstractEntry *entry = favoriteFromId(id); + + if (!entry || !entry->isValid()) { + delete entry; + return; + } + + setDropPlaceholderIndex(-1); + + int insertIndex = (index != -1) ? index : m_entryList.count(); + + beginInsertRows(QModelIndex(), insertIndex, insertIndex); + + m_entryList.insert(insertIndex, entry); + m_favorites.insert(insertIndex, entry->id()); + + endInsertRows(); + + emit countChanged(); + emit favoritesChanged(); +} + +void SimpleFavoritesModel::removeFavorite(const QString &id) +{ + if (!m_enabled || id.isEmpty()) { + return; + } + + int index = m_favorites.indexOf(id); + + if (index != -1) { + setDropPlaceholderIndex(-1); + + beginRemoveRows(QModelIndex(), index, index); + + delete m_entryList[index]; + m_entryList.removeAt(index); + m_favorites.removeAt(index); + + endRemoveRows(); + + emit countChanged(); + emit favoritesChanged(); + } +} + +void SimpleFavoritesModel::moveRow(int from, int to) +{ + if (from >= m_favorites.count() || to >= m_favorites.count()) { + return; + } + + if (from == to) { + return; + } + + setDropPlaceholderIndex(-1); + + int modelTo = to + (to > from ? 1 : 0); + + bool ok = beginMoveRows(QModelIndex(), from, from, QModelIndex(), modelTo); + + if (ok) { + m_entryList.move(from, to); + m_favorites.move(from, to); + + endMoveRows(); + + emit favoritesChanged(); + } +} + +int SimpleFavoritesModel::dropPlaceholderIndex() const +{ + return m_dropPlaceholderIndex; +} + +void SimpleFavoritesModel::setDropPlaceholderIndex(int index) +{ + if (index == -1 && m_dropPlaceholderIndex != -1) { + beginRemoveRows(QModelIndex(), m_dropPlaceholderIndex, m_dropPlaceholderIndex); + + m_dropPlaceholderIndex = index; + + endRemoveRows(); + + emit countChanged(); + } else if (index != -1 && m_dropPlaceholderIndex == -1) { + beginInsertRows(QModelIndex(), index, index); + + m_dropPlaceholderIndex = index; + + endInsertRows(); + + emit countChanged(); + } else if (m_dropPlaceholderIndex != index) { + int modelTo = index + (index > m_dropPlaceholderIndex ? 1 : 0); + + bool ok = beginMoveRows(QModelIndex(), m_dropPlaceholderIndex, m_dropPlaceholderIndex, QModelIndex(), modelTo); + + if (ok) { + m_dropPlaceholderIndex = index; + + endMoveRows(); + } + } +} + +AbstractModel *SimpleFavoritesModel::favoritesModel() +{ + return this; +} + +void SimpleFavoritesModel::refresh() +{ + beginResetModel(); + + setDropPlaceholderIndex(-1); + + int oldCount = m_entryList.count(); + + qDeleteAll(m_entryList); + m_entryList.clear(); + + QStringList newFavorites; + + foreach(const QString &id, m_favorites) { + AbstractEntry *entry = favoriteFromId(id); + + if (entry && entry->isValid()) { + m_entryList << entry; + newFavorites << entry->id(); + + if (m_maxFavorites != -1 && newFavorites.count() == m_maxFavorites) { + break; + } + } else if (entry) { + delete entry; + } + } + + m_favorites = newFavorites; + + endResetModel(); + + if (oldCount != m_entryList.count()) { + emit countChanged(); + } + + emit favoritesChanged(); +} + +AbstractEntry *SimpleFavoritesModel::favoriteFromId(const QString &id) +{ + const QUrl url(id); + const QString &s = url.scheme(); + + if ((s.isEmpty() && id.contains(QLatin1String(".desktop"))) || s == QLatin1String("preferred")) { + return new AppEntry(this, id); + } else if (s == QLatin1String("ktp")) { + return new ContactEntry(this, id); + } else if (url.isValid() && !url.scheme().isEmpty()) { + return new FileEntry(this, url); + } else { + return new SystemEntry(this, id); + } + + return nullptr; +} diff --git a/applets/kicker/plugin/simplefavoritesmodel.h b/applets/kicker/plugin/simplefavoritesmodel.h new file mode 100644 index 000000000..f01ca569a --- /dev/null +++ b/applets/kicker/plugin/simplefavoritesmodel.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2014-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 SIMPLEFAVORITESMODEL_H +#define SIMPLEFAVORITESMODEL_H + +#include "abstractmodel.h" + +#include + +#include + +class SimpleFavoritesModel : public AbstractModel +{ + 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(int dropPlaceholderIndex READ dropPlaceholderIndex WRITE setDropPlaceholderIndex NOTIFY dropPlaceholderIndexChanged) + + public: + explicit SimpleFavoritesModel(QObject *parent = nullptr); + ~SimpleFavoritesModel() override; + + QString description() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + 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 moveRow(int from, int to); + + int dropPlaceholderIndex() const; + void setDropPlaceholderIndex(int index); + + AbstractModel* favoritesModel() override; + + public Q_SLOTS: + void refresh() override; + + Q_SIGNALS: + void enabledChanged() const; + void favoritesChanged() const; + void maxFavoritesChanged() const; + void dropPlaceholderIndexChanged(); + + private: + AbstractEntry *favoriteFromId(const QString &id); + + bool m_enabled; + + QList m_entryList; + QStringList m_favorites; + int m_maxFavorites; + + int m_dropPlaceholderIndex; +}; + +#endif diff --git a/applets/kicker/plugin/submenu.cpp b/applets/kicker/plugin/submenu.cpp new file mode 100644 index 000000000..9c3a3675d --- /dev/null +++ b/applets/kicker/plugin/submenu.cpp @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2014 by David Edmundson * + * Copyright (C) 2014 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 . * + ***************************************************************************/ + +#include "submenu.h" + +#include + +#include + +#include + +SubMenu::SubMenu(QQuickItem *parent) : PlasmaQuick::Dialog(parent) +, m_offset(0) +, m_facingLeft(false) +{ + KWindowSystem::setType(winId(), NET::Menu); +} + +SubMenu::~SubMenu() +{ +} + +int SubMenu::offset() const +{ + return m_offset; +} + +void SubMenu::setOffset(int offset) +{ + if (m_offset != offset) { + m_offset = offset; + + emit offsetChanged(); + } +} + +QPoint SubMenu::popupPosition(QQuickItem* item, const QSize& size) +{ + if (!item || !item->window()) { + return QPoint(0, 0); + } + + QPointF pos = item->mapToScene(QPointF(0, 0)); + pos = item->window()->mapToGlobal(pos.toPoint()); + + pos.setX(pos.x() + m_offset + item->width()); + + QRect avail = availableScreenRectForItem(item); + + if (pos.x() + size.width() > avail.right()) { + pos.setX(pos.x() - m_offset - item->width() - size.width()); + + m_facingLeft = true; + emit facingLeftChanged(); + } + + pos.setY(pos.y() - margins()->property("top").toInt()); + + if (pos.y() + size.height() > avail.bottom()) { + int overshoot = std::ceil(((avail.bottom() - (pos.y() + size.height())) * -1) / item->height()) * item->height(); + + pos.setY(pos.y() - overshoot); + } + + return pos.toPoint(); +} + +QRect SubMenu::availableScreenRectForItem(QQuickItem *item) const +{ + QScreen *screen = QGuiApplication::primaryScreen(); + + const QPoint globalPosition = item->window()->mapToGlobal(item->position().toPoint()); + + foreach(QScreen *s, QGuiApplication::screens()) { + if (s->geometry().contains(globalPosition)) { + screen = s; + } + } + + return screen->availableGeometry(); +} diff --git a/applets/kicker/plugin/submenu.h b/applets/kicker/plugin/submenu.h new file mode 100644 index 000000000..699bafc03 --- /dev/null +++ b/applets/kicker/plugin/submenu.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2014 by David Edmundson * + * Copyright (C) 2014 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 SUBMENU_H +#define SUBMENU_H + +#include + + +class SubMenu : public PlasmaQuick::Dialog +{ + Q_OBJECT + + Q_PROPERTY(int offset READ offset WRITE setOffset NOTIFY offsetChanged) + Q_PROPERTY(bool facingLeft READ facingLeft NOTIFY facingLeftChanged) + + public: + explicit SubMenu(QQuickItem *parent = nullptr); + ~SubMenu() override; + + Q_INVOKABLE QRect availableScreenRectForItem(QQuickItem *item) const; + + QPoint popupPosition(QQuickItem *item, const QSize &size) override; + + int offset() const; + void setOffset(int offset); + + bool facingLeft() const { return m_facingLeft; } + + Q_SIGNALS: + void offsetChanged() const; + void facingLeftChanged() const; + + private: + int m_offset; + bool m_facingLeft; +}; + +#endif diff --git a/applets/kicker/plugin/systementry.cpp b/applets/kicker/plugin/systementry.cpp new file mode 100644 index 000000000..5cb8198f3 --- /dev/null +++ b/applets/kicker/plugin/systementry.cpp @@ -0,0 +1,350 @@ +/*************************************************************************** + * 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 . * + ***************************************************************************/ + +#include "systementry.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "ksmserver_interface.h" +#include + +SystemEntry::SystemEntry(AbstractModel *owner, Action action) : AbstractEntry(owner) +, m_action(action) +, m_valid(false) +{ + init(); +} + +SystemEntry::SystemEntry(AbstractModel *owner, const QString &id) : AbstractEntry(owner) +, m_action(NoAction) +, m_valid(false) +{ + if (id == QLatin1String("lock-screen")) { + m_action = LockSession; + } else if (id == QLatin1String("logout")) { + m_action = LogoutSession; + } else if (id == QLatin1String("save-session")) { + m_action = SaveSession; + } else if (id == QLatin1String("switch-user")) { + m_action = SwitchUser; + } else if (id == QLatin1String("suspend")) { + m_action = SuspendToRam; + } else if (id == QLatin1String("hibernate")) { + m_action = SuspendToDisk; + } else if (id == QLatin1String("reboot")) { + m_action = Reboot; + } else if (id == QLatin1String("shutdown")) { + m_action = Shutdown; + } + + init(); +} + +void SystemEntry::init() +{ + switch (m_action) { + case NoAction: + m_valid = false; + break; + case LockSession: + m_valid = KAuthorized::authorizeAction(QStringLiteral("lock_screen")); + break; + case LogoutSession: + case SaveSession: + { + bool authorize = KAuthorized::authorizeAction(QStringLiteral("logout")) && KAuthorized::authorize(QStringLiteral("logout")); + + if (m_action == SaveSession) { + const KConfigGroup c(KSharedConfig::openConfig(QStringLiteral("ksmserverrc"), KConfig::NoGlobals), "General"); + + m_valid = authorize && c.readEntry("loginMode") == QLatin1String("restoreSavedSession"); + } else { + m_valid = authorize; + } + + break; + } + case SwitchUser: + m_valid = (KAuthorized::authorizeAction(QStringLiteral("start_new_session")) || KAuthorized::authorizeAction(QStringLiteral("switch_user"))) + && KDisplayManager().isSwitchable(); + break; + case SuspendToRam: + m_valid = Solid::PowerManagement::supportedSleepStates().contains(Solid::PowerManagement::SuspendState); + break; + case SuspendToDisk: + m_valid = Solid::PowerManagement::supportedSleepStates().contains(Solid::PowerManagement::HibernateState); + break; + case Reboot: + m_valid = KWorkSpace::canShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeReboot); + break; + case Shutdown: + m_valid = KWorkSpace::canShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeHalt); + break; + default: + m_valid = true; + } +} + +bool SystemEntry::isValid() const +{ + return m_valid; +} + +QIcon SystemEntry::icon() const +{ + const QString &name = iconName(); + + if (!name.isEmpty()) { + return QIcon::fromTheme(name, QIcon::fromTheme(QStringLiteral("unknown"))); + } + + return QIcon::fromTheme(QStringLiteral("unknown")); +} + +QString SystemEntry::iconName() const +{ + switch (m_action) { + case LockSession: + return QStringLiteral("system-lock-screen"); + break; + case LogoutSession: + return QStringLiteral("system-log-out"); + break; + case SaveSession: + return QStringLiteral("system-save-session"); + break; + case SwitchUser: + return QStringLiteral("system-switch-user"); + break; + case SuspendToRam: + return QStringLiteral("system-suspend"); + break; + case SuspendToDisk: + return QStringLiteral("system-suspend-hibernate"); + break; + case Reboot: + return QStringLiteral("system-reboot"); + break; + case Shutdown: + return QStringLiteral("system-shutdown"); + break; + default: + break; + } + + return QString(); +} + +QString SystemEntry::name() const +{ + switch (m_action) { + case LockSession: + return i18n("Lock"); + break; + case LogoutSession: + return i18n("Log Out"); + break; + case SaveSession: + return i18n("Save Session"); + break; + case SwitchUser: + return i18n("Switch User"); + break; + case SuspendToRam: + return i18nc("Suspend to RAM", "Sleep"); + break; + case SuspendToDisk: + return i18n("Hibernate"); + break; + case Reboot: + return i18n("Restart"); + break; + case Shutdown: + return i18n("Shut Down"); + break; + default: + break; + } + + return QString(); +} + +QString SystemEntry::group() const +{ + switch (m_action) { + case LockSession: + return i18n("Session"); + break; + case LogoutSession: + return i18n("Session"); + break; + case SaveSession: + return i18n("Session"); + break; + case SwitchUser: + return i18n("Session"); + break; + case SuspendToRam: + return i18n("System"); + break; + case SuspendToDisk: + return i18n("System"); + break; + case Reboot: + return i18n("System"); + break; + case Shutdown: + return i18n("System"); + break; + default: + break; + } + + return QString(); +} + +QString SystemEntry::description() const +{ + switch (m_action) { + case LockSession: + return i18n("Lock screen"); + break; + case LogoutSession: + return i18n("End session"); + break; + case SaveSession: + return i18n("Save Session"); + break; + case SwitchUser: + return i18n("Start a parallel session as a different user"); + break; + case SuspendToRam: + return i18n("Suspend to RAM"); + break; + case SuspendToDisk: + return i18n("Suspend to disk"); + break; + case Reboot: + return i18n("Restart computer"); + break; + case Shutdown: + return i18n("Turn off computer"); + break; + default: + break; + } + + return QString(); +} + +QString SystemEntry::id() const +{ + switch (m_action) { + case LockSession: + return QStringLiteral("lock-screen"); + break; + case LogoutSession: + return QStringLiteral("logout"); + break; + case SaveSession: + return QStringLiteral("save-session"); + break; + case SwitchUser: + return QStringLiteral("switch-user"); + break; + case SuspendToRam: + return QStringLiteral("suspend"); + break; + case SuspendToDisk: + return QStringLiteral("hibernate"); + break; + case Reboot: + return QStringLiteral("reboot"); + break; + case Shutdown: + return QStringLiteral("shutdown"); + break; + + default: + break; + } + + return QString(); +} + +bool SystemEntry::run(const QString& actionId, const QVariant &argument) +{ + Q_UNUSED(actionId) + Q_UNUSED(argument) + + switch (m_action) { + case LockSession: + { + QDBusConnection bus = QDBusConnection::sessionBus(); + QDBusInterface interface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"), bus); + interface.asyncCall(QStringLiteral("Lock")); + break; + } + case LogoutSession: + KWorkSpace::requestShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeNone); + break; + case SaveSession: + { + org::kde::KSMServerInterface ksmserver(QStringLiteral("org.kde.ksmserver"), + QStringLiteral("/KSMServer"), QDBusConnection::sessionBus()); + + if (ksmserver.isValid()) { + ksmserver.saveCurrentSession(); + } + + break; + } + case SwitchUser: + { + QDBusConnection bus = QDBusConnection::sessionBus(); + QDBusInterface interface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QStringLiteral("org.kde.KSMServerInterface"), bus); + interface.asyncCall(QStringLiteral("openSwitchUserDialog")); + break; + }; + case SuspendToRam: + Solid::PowerManagement::requestSleep(Solid::PowerManagement::SuspendState, nullptr, nullptr); + break; + case SuspendToDisk: + Solid::PowerManagement::requestSleep(Solid::PowerManagement::HibernateState, nullptr, nullptr); + break; + case Reboot: + KWorkSpace::requestShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeReboot); + break; + case Shutdown: + KWorkSpace::requestShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeHalt); + break; + default: + return false; + } + + return true; +} diff --git a/applets/kicker/plugin/systementry.h b/applets/kicker/plugin/systementry.h new file mode 100644 index 000000000..da0f72f33 --- /dev/null +++ b/applets/kicker/plugin/systementry.h @@ -0,0 +1,66 @@ +/*************************************************************************** + * 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 SYSTEMENTRY_H +#define SYSTEMENTRY_H + +#include "abstractentry.h" + +class SystemEntry : public AbstractEntry +{ + public: + enum Action + { + NoAction = 0, + LockSession, + LogoutSession, + SaveSession, + SwitchUser, + SuspendToRam, + SuspendToDisk, + Reboot, + Shutdown + }; + + explicit SystemEntry(AbstractModel *owner, Action action); + explicit SystemEntry(AbstractModel *owner, const QString &id); + + EntryType type() const override { return RunnableType; } + + bool isValid() const override; + + QIcon icon() const override; + QString iconName() const; + QString name() const override; + QString group() const override; + QString description() const override; + + QString id() const override; + + bool run(const QString& actionId = QString(), const QVariant &argument = QVariant()) override; + + private: + void init(); + + Action m_action; + bool m_valid; + +}; + +#endif diff --git a/applets/kicker/plugin/systemmodel.cpp b/applets/kicker/plugin/systemmodel.cpp new file mode 100644 index 000000000..339bf0aa2 --- /dev/null +++ b/applets/kicker/plugin/systemmodel.cpp @@ -0,0 +1,135 @@ +/*************************************************************************** + * Copyright (C) 2014-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 . * + ***************************************************************************/ + +#include "systemmodel.h" +#include "actionlist.h" +#include "simplefavoritesmodel.h" +#include "systementry.h" + +#include + +#include +#include + +SystemModel::SystemModel(QObject *parent) : AbstractModel(parent) +{ + init(); + + m_favoritesModel = new SimpleFavoritesModel(this); + + const QString configFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/ksmserverrc"); + + KDirWatch *watch = new KDirWatch(this); + + watch->addFile(configFile); + + connect(watch, &KDirWatch::dirty, this, &SystemModel::refresh); + connect(watch, &KDirWatch::created, this, &SystemModel::refresh); +} + +SystemModel::~SystemModel() +{ + qDeleteAll(m_entryList); +} + +void SystemModel::init() +{ + QList actions; + + actions << new SystemEntry(this, SystemEntry::LockSession); + actions << new SystemEntry(this, SystemEntry::LogoutSession); + actions << new SystemEntry(this, SystemEntry::SaveSession); + actions << new SystemEntry(this, SystemEntry::SwitchUser); + actions << new SystemEntry(this, SystemEntry::SuspendToRam); + actions << new SystemEntry(this, SystemEntry::SuspendToDisk); + actions << new SystemEntry(this, SystemEntry::Reboot); + actions << new SystemEntry(this, SystemEntry::Shutdown); + + foreach(SystemEntry *entry, actions) { + if (entry->isValid()) { + m_entryList << entry; + } else { + delete entry; + } + } +} + +QString SystemModel::description() const +{ + return i18n("System actions"); +} + +QVariant SystemModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_entryList.count()) { + return QVariant(); + } + + const SystemEntry *entry = m_entryList.at(index.row()); + + if (role == Qt::DisplayRole) { + return entry->name(); + } else if (role == Qt::DecorationRole) { + return entry->iconName(); + } else if (role == Kicker::DescriptionRole) { + return entry->description(); + } else if (role == Kicker::GroupRole) { + return entry->group(); + } else if (role == Kicker::FavoriteIdRole) { + return entry->id(); + } else if (role == Kicker::HasActionListRole) { + return entry->hasActions(); + } else if (role == Kicker::ActionListRole) { + return entry->actions(); + } + + return QVariant(); +} + +int SystemModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : m_entryList.count(); +} + +bool SystemModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + if (row >= 0 && row < m_entryList.count()) { + m_entryList.at(row)->run(actionId, argument); + + return true; + } + + return false; +} + +void SystemModel::refresh() +{ + beginResetModel(); + + qDeleteAll(m_entryList); + m_entryList.clear(); + + init(); + + endResetModel(); + + emit countChanged(); + + m_favoritesModel->refresh(); +} diff --git a/applets/kicker/plugin/systemmodel.h b/applets/kicker/plugin/systemmodel.h new file mode 100644 index 000000000..56baef2dc --- /dev/null +++ b/applets/kicker/plugin/systemmodel.h @@ -0,0 +1,52 @@ +/*************************************************************************** + * Copyright (C) 2014-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 SYSTEMMODEL_H +#define SYSTEMMODEL_H + +#include "abstractmodel.h" + +class SystemEntry; + +class SystemModel : public AbstractModel +{ + Q_OBJECT + + public: + explicit SystemModel(QObject *parent = nullptr); + ~SystemModel() override; + + QString description() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + protected Q_SLOTS: + void refresh() override; + + private: + void init(); + + QList m_entryList; +}; + +#endif diff --git a/applets/kicker/plugin/systemsettings.cpp b/applets/kicker/plugin/systemsettings.cpp new file mode 100644 index 000000000..bca98a3fb --- /dev/null +++ b/applets/kicker/plugin/systemsettings.cpp @@ -0,0 +1,46 @@ +/*************************************************************************** + * 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 . * + ***************************************************************************/ + +#include "systemsettings.h" + +#include + +SystemSettings::SystemSettings(QObject *parent) : QObject(parent) +{ +} + +SystemSettings::~SystemSettings() +{ +} + +QString SystemSettings::picturesLocation() const +{ + QString path; + + const QStringList &locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); + + if (!locations.isEmpty()) { + path = locations.at(0); + } else { + // HomeLocation is guaranteed not to be empty. + path = QStandardPaths::standardLocations(QStandardPaths::HomeLocation).at(0); + } + + return path; +} diff --git a/applets/kicker/plugin/systemsettings.h b/applets/kicker/plugin/systemsettings.h new file mode 100644 index 000000000..d5aefafe2 --- /dev/null +++ b/applets/kicker/plugin/systemsettings.h @@ -0,0 +1,36 @@ +/*************************************************************************** + * 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 SYSTEMSETTINGS_H +#define SYSTEMSETTINGS_H + +#include + +class SystemSettings : public QObject +{ + Q_OBJECT + + public: + explicit SystemSettings(QObject *parent = nullptr); + ~SystemSettings() override; + + Q_INVOKABLE QString picturesLocation() const; +}; + +#endif diff --git a/applets/kicker/plugin/wheelinterceptor.cpp b/applets/kicker/plugin/wheelinterceptor.cpp new file mode 100644 index 000000000..804c2fe88 --- /dev/null +++ b/applets/kicker/plugin/wheelinterceptor.cpp @@ -0,0 +1,72 @@ +/*************************************************************************** + * Copyright (C) 2014-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 . * + ***************************************************************************/ + +#include "wheelinterceptor.h" + +#include + +WheelInterceptor::WheelInterceptor(QQuickItem *parent) : QQuickItem(parent) +{ +} + +WheelInterceptor::~WheelInterceptor() +{ +} + +QQuickItem* WheelInterceptor::destination() const +{ + return m_destination; +} + +void WheelInterceptor::setDestination(QQuickItem *destination) +{ + if (m_destination != destination) { + m_destination = destination; + + emit destinationChanged(); + } +} + +void WheelInterceptor::wheelEvent(QWheelEvent* event) +{ + if (m_destination) { + QCoreApplication::sendEvent(m_destination, event); + } + + emit wheelMoved(event->angleDelta()); +} + +QQuickItem *WheelInterceptor::findWheelArea(QQuickItem *parent) const +{ + if (!parent) { + return nullptr; + } + + foreach(QQuickItem *child, parent->childItems()) { + // HACK: ScrollView adds the WheelArea below its flickableItem with + // z==-1. This is reasonable non-risky considering we know about + // everything else in there, and worst case we break the mouse wheel. + if (child->z() == -1) { + return child; + } + } + + return nullptr; +} + diff --git a/applets/kicker/plugin/wheelinterceptor.h b/applets/kicker/plugin/wheelinterceptor.h new file mode 100644 index 000000000..2ad089e9c --- /dev/null +++ b/applets/kicker/plugin/wheelinterceptor.h @@ -0,0 +1,52 @@ +/************************************************************************** + * Copyright (C) 2014-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 WHEELINTERCEPTOR_H +#define WHEELINTERCEPTOR_H + +#include +#include + +class WheelInterceptor : public QQuickItem +{ + Q_OBJECT + + Q_PROPERTY(QQuickItem* destination READ destination WRITE setDestination NOTIFY destinationChanged) + + public: + explicit WheelInterceptor(QQuickItem *parent = nullptr); + ~WheelInterceptor() override; + + QQuickItem *destination() const; + void setDestination(QQuickItem *destination); + + Q_INVOKABLE QQuickItem *findWheelArea(QQuickItem *parent) const; + + Q_SIGNALS: + void destinationChanged() const; + void wheelMoved(QPoint delta) const; + + protected: + void wheelEvent(QWheelEvent *event) override; + + private: + QPointer m_destination; +}; + +#endif diff --git a/applets/kicker/plugin/windowsystem.cpp b/applets/kicker/plugin/windowsystem.cpp new file mode 100644 index 000000000..c3906af4c --- /dev/null +++ b/applets/kicker/plugin/windowsystem.cpp @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2014 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 . * + ***************************************************************************/ + +#include "windowsystem.h" + +#include +#include + +#include + +WindowSystem::WindowSystem(QObject *parent) : QObject(parent) +{ +} + +WindowSystem::~WindowSystem() +{ +} + +bool WindowSystem::eventFilter(QObject* watched, QEvent* event) +{ + if (event->type() == QEvent::FocusIn) { + removeEventFilter(watched); + emit focusIn(qobject_cast(watched)); + } + + return false; +} + +void WindowSystem::forceActive(QQuickItem *item) +{ + if (!item || !item->window()) { + return; + } + + KWindowSystem::forceActiveWindow(item->window()->winId()); + KWindowSystem::raiseWindow(item->window()->winId()); +} + +bool WindowSystem::isActive(QQuickItem *item) +{ + if (!item || !item->window()) { + return false; + } + + return item->window()->isActive(); +} + +void WindowSystem::monitorWindowFocus(QQuickItem* item) +{ + if (!item || !item->window()) { + return; + } + + item->window()->installEventFilter(this); +} + +void WindowSystem::monitorWindowVisibility(QQuickItem* item) +{ + if (!item || !item->window()) { + return; + } + + connect(item->window(), &QQuickWindow::visibilityChanged, this, + &WindowSystem::monitoredWindowVisibilityChanged, Qt::UniqueConnection); +} + +void WindowSystem::monitoredWindowVisibilityChanged(QWindow::Visibility visibility) const +{ + bool visible = (visibility != QWindow::Hidden); + QQuickWindow *w = static_cast(QObject::sender()); + + if (!visible) { + emit hidden(w); + } +} diff --git a/applets/kicker/plugin/windowsystem.h b/applets/kicker/plugin/windowsystem.h new file mode 100644 index 000000000..7d0fc216c --- /dev/null +++ b/applets/kicker/plugin/windowsystem.h @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2014 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 WINDOWSYSTEM_H +#define WINDOWSYSTEM_H + +#include +#include +class QQuickItem; + +class WindowSystem : public QObject +{ + Q_OBJECT + + public: + explicit WindowSystem(QObject *parent = nullptr); + ~WindowSystem() override; + + bool eventFilter(QObject *watched, QEvent *event) override; + + Q_INVOKABLE void forceActive(QQuickItem *item); + + Q_INVOKABLE bool isActive(QQuickItem *item); + + Q_INVOKABLE void monitorWindowFocus(QQuickItem *item); + + Q_INVOKABLE void monitorWindowVisibility(QQuickItem *item); + + Q_SIGNALS: + void focusIn(QQuickWindow *window) const; + void hidden(QQuickWindow *window) const; + + private Q_SLOTS: + void monitoredWindowVisibilityChanged(QWindow::Visibility visibility) const; +}; + +#endif