You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

575 lines
20 KiB

/***************************************************************************
* Copyright (C) 2012 Aurélien Gâteau <agateau@kde.org> *
* Copyright (C) 2013-2014 by Eike Hein <hein@kde.org> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#include "appsmodel.h"
#include "actionlist.h"
#include "containmentinterface.h"
#include "menuentryeditor.h"
#include "config-workspace.h"
#ifdef PackageKitQt5_FOUND
#include "findpackagenamejob.h"
#endif
#include <config-X11.h>
#include <QCollator>
#include <QTimer>
#include <QProcess>
#include <QQmlPropertyMap>
#include <QStandardPaths>
#if HAVE_X11
#include <QX11Info>
#endif
#include <KActivities/ResourceInstance>
#include <KJob>
#include <KLocalizedString>
#include <KRun>
#include <KSycoca>
#include <KShell>
#include <KStartupInfo>
AppGroupEntry::AppGroupEntry(KServiceGroup::Ptr group, AppsModel *parentModel,
bool flat, int appNameFormat)
{
m_name = group->caption();
m_icon = QIcon::fromTheme(group->icon(), QIcon::fromTheme("unknown"));
AppsModel* model = new AppsModel(group->entryPath(), flat, parentModel);
model->setAppletInterface(parentModel->appletInterface());
model->setAppNameFormat(appNameFormat);
m_model = model;
QObject::connect(parentModel, SIGNAL(appletInterfaceChanged(QObject*)), model, SLOT(setAppletInterface(QObject*)));
QObject::connect(parentModel, SIGNAL(refreshing()), m_model, SLOT(deleteLater()));
}
AppEntry::AppEntry(KService::Ptr service, const QString &name)
: m_service(service)
{
m_name = name;
m_icon = QIcon::fromTheme(service->icon(), QIcon::fromTheme("unknown"));
m_service = service;
}
MenuEntryEditor *AppsModel::m_menuEntryEditor = 0;
AppsModel::AppsModel(const QString &entryPath, bool flat, QObject *parent)
: AbstractModel(parent)
, m_entryPath(entryPath)
, m_changeTimer(0)
, m_flat(flat)
, m_appNameFormat(NameOnly)
, m_appletInterface(0)
{
if (!m_menuEntryEditor) {
m_menuEntryEditor = new MenuEntryEditor();
}
}
AppsModel::~AppsModel()
{
qDeleteAll(m_entryList);
}
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::IsParentRole) {
return (entry->type() == AbstractEntry::GroupType);
} else if (role == Kicker::HasChildrenRole) {
if (entry->type() == AbstractEntry::GroupType) {
const AbstractGroupEntry *groupEntry = static_cast<const AbstractGroupEntry *>(entry);
if (groupEntry->model() && groupEntry->model()->rowCount()) {
return true;
}
}
} else if (role == Kicker::FavoriteIdRole) {
if (entry->type() == AbstractEntry::RunnableType) {
return QVariant("app:" + static_cast<AppEntry *>(m_entryList.at(index.row()))->service()->storageId());
}
} else if (role == Kicker::HasActionListRole) {
if (entry->type() == AbstractEntry::RunnableType || !m_hiddenEntries.isEmpty()) {
return true;
} else if (entry->type() == AbstractEntry::GroupType) {
const AbstractGroupEntry *groupEntry = static_cast<const AbstractGroupEntry *>(entry);
if (groupEntry->model()) {
const AppsModel *appsModel = qobject_cast<const AppsModel *>(groupEntry->model());
if (appsModel && !appsModel->hiddenEntries().isEmpty()) {
return true;
}
}
}
} else if (role == Kicker::ActionListRole) {
const KService::Ptr service = static_cast<const AppEntry *>(entry)->service();
QVariantList actionList;
actionList << Kicker::recentDocumentActions(service);
if (actionList.count()) {
actionList << Kicker::createSeparatorActionItem();
}
if (entry->type() == AbstractEntry::RunnableType) {
if (ContainmentInterface::mayAddLauncher(m_appletInterface, ContainmentInterface::Desktop)) {
actionList << Kicker::createActionItem(i18n("Add to Desktop"), "addToDesktop");
}
if (ContainmentInterface::mayAddLauncher(m_appletInterface, ContainmentInterface::Panel)) {
actionList << Kicker::createActionItem(i18n("Add to Panel"), "addToPanel");
}
if (ContainmentInterface::mayAddLauncher(m_appletInterface, ContainmentInterface::TaskManager, service->entryPath())) {
actionList << Kicker::createActionItem(i18n("Add as Launcher"), "addToTaskManager");
}
if (m_menuEntryEditor->canEdit(service->entryPath())) {
actionList << Kicker::createSeparatorActionItem();
QVariantMap editAction = Kicker::createActionItem(i18n("Edit Application..."), "editApplication");
editAction["icon"] = "kmenuedit"; // TODO: Using the KMenuEdit icon might be misleading.
actionList << editAction;
}
#ifdef PackageKitQt5_FOUND
QStringList files(service->entryPath());
if (service->isApplication()) {
files += QStandardPaths::findExecutable(KShell::splitArgs(service->exec()).first());
}
FindPackageJob* job = new FindPackageJob(files); // TODO: Would be great to make this async.
if (job->exec() && !job->packageNames().isEmpty()) {
QString packageName = job->packageNames().first();
QVariantMap removeAction = Kicker::createActionItem(i18n("Remove '%1'...", packageName), "removeApplication", packageName);
removeAction["icon"] = "applications-other";
actionList << removeAction;
}
#endif
if (appletConfig() && appletConfig()->contains("hiddenApplications")) {
const QStringList &hiddenApps = appletConfig()->value("hiddenApplications").toStringList();
if (!hiddenApps.contains(service->menuId())) {
actionList << Kicker::createActionItem(i18n("Hide Application"), "hideApplication");
}
}
}
if (!m_hiddenEntries.isEmpty()) {
actionList << Kicker::createSeparatorActionItem();
actionList << Kicker::createActionItem(i18n("Unhide Applications in this Submenu"), "unhideSiblingApplications");
}
if (entry->type() == AbstractEntry::GroupType) {
const AbstractGroupEntry *groupEntry = static_cast<const AbstractGroupEntry *>(entry);
if (groupEntry->model()) {
const AppsModel *appsModel = qobject_cast<const AppsModel *>(groupEntry->model());
if (appsModel && !appsModel->hiddenEntries().isEmpty()) {
actionList << Kicker::createActionItem(i18n("Unhide Applications in '%1'", entry->name()), "unhideChildApplications");
}
}
}
return actionList;
} else if (role == Kicker::UrlRole) {
if (entry->type() == AbstractEntry::RunnableType) {
return QUrl::fromLocalFile(static_cast<AppEntry *>(m_entryList.at(index.row()))->service()->entryPath());
}
}
return QVariant();
}
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)
{
Q_UNUSED(argument)
if (row < 0 || row >= m_entryList.count()) {
return false;
}
const AbstractEntry *entry = m_entryList.at(row);
KService::Ptr service;
if (entry->type() == AbstractEntry::RunnableType) {
service = static_cast<const AppEntry *>(entry)->service();
}
if (actionId.isEmpty()) {
if (!service) {
return false;
}
quint32 timeStamp = 0;
#if HAVE_X11
if (QX11Info::isPlatformX11()) {
timeStamp = QX11Info::appUserTime();
}
#endif
new KRun(QUrl::fromLocalFile(service->entryPath()), 0, true,
KStartupInfo::createNewStartupIdForTimestamp(timeStamp));
KActivities::ResourceInstance::notifyAccessed(QUrl("applications:" + service->storageId()),
"org.kde.plasma.kicker");
return true;
} else if (actionId == "addToDesktop" && ContainmentInterface::mayAddLauncher(m_appletInterface, ContainmentInterface::Desktop)) {
ContainmentInterface::addLauncher(m_appletInterface, ContainmentInterface::Desktop, service->entryPath());
} else if (actionId == "addToPanel" && ContainmentInterface::mayAddLauncher(m_appletInterface, ContainmentInterface::Panel)) {
ContainmentInterface::addLauncher(m_appletInterface, ContainmentInterface::Panel, service->entryPath());
} else if (actionId == "addToTaskManager" && ContainmentInterface::mayAddLauncher(m_appletInterface, ContainmentInterface::TaskManager, service->entryPath())) {
ContainmentInterface::addLauncher(m_appletInterface, ContainmentInterface::TaskManager, service->entryPath());
} else if (actionId == "editApplication" && m_menuEntryEditor->canEdit(service->entryPath())) {
QMetaObject::invokeMethod(m_menuEntryEditor, "edit", Qt::QueuedConnection,
Q_ARG(QString, service->entryPath()),
Q_ARG(QString, service->menuId()));
return true;
} else if (actionId == "removeApplication") {
if (appletConfig() && appletConfig()->contains("removeApplicationCommand")) {
const QStringList &removeAppCmd = KShell::splitArgs(appletConfig()->value("removeApplicationCommand").toString());
if (!removeAppCmd.isEmpty()) {
return QProcess::startDetached(removeAppCmd.first(), removeAppCmd.mid(1) << argument.toString());
}
}
} else if (actionId == "hideApplication") {
if (appletConfig() && appletConfig()->contains("hiddenApplications")) {
QStringList hiddenApps = appletConfig()->value("hiddenApplications").toStringList();
if (!hiddenApps.contains(service->menuId())) {
hiddenApps << service->menuId();
appletConfig()->insert("hiddenApplications", hiddenApps);
QMetaObject::invokeMethod(appletConfig(), "valueChanged", Qt::DirectConnection,
Q_ARG(QString, "hiddenApplications"),
Q_ARG(QVariant, hiddenApps));
refresh();
emit hiddenEntriesChanged();
}
}
} else if (actionId == "unhideSiblingApplications") {
if (appletConfig() && appletConfig()->contains("hiddenApplications")) {
QStringList hiddenApps = appletConfig()->value("hiddenApplications").toStringList();
foreach(const QString& app, m_hiddenEntries) {
hiddenApps.removeOne(app);
}
appletConfig()->insert("hiddenApplications", hiddenApps);
QMetaObject::invokeMethod(appletConfig(), "valueChanged", Qt::DirectConnection,
Q_ARG(QString, "hiddenApplications"),
Q_ARG(QVariant, hiddenApps));
m_hiddenEntries.clear();
refresh();
emit hiddenEntriesChanged();
}
} else if (actionId == "unhideChildApplications") {
if (entry->type() == AbstractEntry::GroupType
&& appletConfig() && appletConfig()->contains("hiddenApplications")) {
const AbstractGroupEntry *groupEntry = static_cast<const AbstractGroupEntry *>(entry);
if (groupEntry->model()) {
const AppsModel *appsModel = qobject_cast<const AppsModel *>(groupEntry->model());
QStringList hiddenApps = appletConfig()->value("hiddenApplications").toStringList();
foreach(const QString& app, appsModel->hiddenEntries()) {
hiddenApps.removeOne(app);
}
appletConfig()->insert("hiddenApplications", hiddenApps);
QMetaObject::invokeMethod(appletConfig(), "valueChanged", Qt::DirectConnection,
Q_ARG(QString, "hiddenApplications"),
Q_ARG(QVariant, hiddenApps));
refresh();
emit hiddenEntriesChanged();
}
}
} else if (service) {
return Kicker::handleRecentDocumentAction(service, actionId, argument);
}
return false;
}
AbstractModel *AppsModel::modelForRow(int row)
{
if (row < 0 || row >= m_entryList.count() || m_entryList.at(row)->type() != AbstractEntry::GroupType) {
return 0;
}
return static_cast<AbstractGroupEntry *>(m_entryList.at(row))->model();
}
bool AppsModel::flat() const
{
return m_flat;
}
void AppsModel::setFlat(bool flat)
{
if (m_flat != flat) {
m_flat = flat;
refresh();
emit flatChanged();
}
}
int AppsModel::appNameFormat() const
{
return m_appNameFormat;
}
void AppsModel::setAppNameFormat(int format)
{
if (m_appNameFormat != (NameFormat)format) {
m_appNameFormat = (NameFormat)format;
refresh();
emit appNameFormatChanged();
}
}
QStringList AppsModel::hiddenEntries() const
{
return m_hiddenEntries;
}
QObject* AppsModel::appletInterface() const
{
return m_appletInterface;
}
void AppsModel::setAppletInterface(QObject* appletInterface)
{
if (m_appletInterface != appletInterface) {
m_appletInterface = appletInterface;
refresh();
emit appletInterfaceChanged(m_appletInterface);
}
}
QString AppsModel::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);
}
}
void AppsModel::refresh()
{
beginResetModel();
emit refreshing();
qDeleteAll(m_entryList);
m_entryList.clear();
m_hiddenEntries.clear();
if (m_entryPath.isEmpty()) {
KServiceGroup::Ptr group = KServiceGroup::root();
KServiceGroup::List list = group->entries(true);
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<KServiceGroup*>(p.data()));
if (!subGroup->noDisplay() && subGroup->childCount() > 0) {
AppGroupEntry *groupEntry = new AppGroupEntry(subGroup, this, m_flat, m_appNameFormat);
connect(groupEntry->model(), SIGNAL(hiddenEntriesChanged()), this, SLOT(childHiddenEntriesChanged()));
m_entryList << groupEntry;
}
}
}
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);
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;
}
});
}
endResetModel();
emit countChanged();
}
void AppsModel::processServiceGroup(KServiceGroup::Ptr group)
{
if (!group || !group->isValid()) {
return;
}
KServiceGroup::List list = group->entries(true);
QStringList hiddenApps;
if (appletConfig() && appletConfig()->contains("hiddenApplications")) {
hiddenApps = appletConfig()->value("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<KService*>(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<const AppEntry *>(entry)->service()->storageId() == service->storageId()) {
found = true;
}
}
if (!found) {
m_entryList << new AppEntry(service, nameFromService(service, m_appNameFormat));
}
} else if (p->isType(KST_KServiceGroup)) {
if (m_flat) {
const KServiceGroup::Ptr serviceGroup(static_cast<KServiceGroup*>(p.data()));
processServiceGroup(serviceGroup);
} else {
const KServiceGroup::Ptr subGroup(static_cast<KServiceGroup*>(p.data()));
if (!subGroup->noDisplay() && subGroup->childCount() > 0) {
AppGroupEntry *groupEntry = new AppGroupEntry(subGroup, this, m_flat, m_appNameFormat);
connect(groupEntry->model(), SIGNAL(countChanged()), this, SLOT(refresh()));
connect(groupEntry->model(), SIGNAL(hiddenEntriesChanged()), this, SLOT(childHiddenEntriesChanged()));
m_entryList << groupEntry;
}
}
}
}
}
void AppsModel::checkSycocaChanges(const QStringList &changes)
{
if (changes.contains("services") || changes.contains("apps") || changes.contains("xdgdata-apps")) {
m_changeTimer->start();
}
}
void AppsModel::childHiddenEntriesChanged()
{
QObject *childModel = QObject::sender();
for (int i = 0; i < m_entryList.size(); ++i) {
const AbstractEntry *entry = m_entryList.at(i);
if (entry->type() == AbstractEntry::GroupType) {
const AbstractGroupEntry *groupEntry = static_cast<const AbstractGroupEntry *>(entry);
if (groupEntry->model() == childModel) {
const QModelIndex &idx = index(i, 0);
emit dataChanged(idx, idx);
}
}
}
}
QQmlPropertyMap *AppsModel::appletConfig() const
{
if (m_appletInterface) {
return qobject_cast<QQmlPropertyMap *>(m_appletInterface->property("configuration").value<QObject *>());
}
return 0;
}