/****************************************************************** * Copyright 2016 Kai Uwe Broulik * Copyright 2016 Chinmoy Ranjan Pradhan * * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * ******************************************************************/ #include "appmenumodel.h" #include #include #include #include #include #include #include #include class KDBusMenuImporter : public DBusMenuImporter { public: KDBusMenuImporter(const QString &service, const QString &path, QObject *parent) : DBusMenuImporter(service, path, parent) { } protected: QIcon iconForName(const QString &name) override { return QIcon::fromTheme(name); } }; AppMenuModel::AppMenuModel(QObject *parent) : QAbstractListModel(parent), m_serviceWatcher(new QDBusServiceWatcher(this)), m_tasksModel(new TaskManager::TasksModel(this)) { m_tasksModel->setFilterByScreen(true); connect(m_tasksModel, &TaskManager::TasksModel::activeTaskChanged, this, &AppMenuModel::onActiveWindowChanged); connect(m_tasksModel, &TaskManager::TasksModel::dataChanged, [=](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) { Q_UNUSED(topLeft) Q_UNUSED(bottomRight) if (roles.contains(TaskManager::AbstractTasksModel::ApplicationMenuObjectPath) || roles.contains(TaskManager::AbstractTasksModel::ApplicationMenuServiceName) || roles.isEmpty()) { onActiveWindowChanged(); } }); connect(m_tasksModel, &TaskManager::TasksModel::activityChanged, this, &AppMenuModel::onActiveWindowChanged); connect(m_tasksModel, &TaskManager::TasksModel::virtualDesktopChanged, this, &AppMenuModel::onActiveWindowChanged); connect(m_tasksModel, &TaskManager::TasksModel::countChanged, this, &AppMenuModel::onActiveWindowChanged); connect(m_tasksModel, &TaskManager::TasksModel::screenGeometryChanged, this, &AppMenuModel::screenGeometryChanged); connect(this, &AppMenuModel::modelNeedsUpdate, this, [this] { if (!m_updatePending) { m_updatePending = true; QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); } }); onActiveWindowChanged(); m_serviceWatcher->setConnection(QDBusConnection::sessionBus()); //if our current DBus connection gets lost, close the menu //we'll select the new menu when the focus changes connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &serviceName) { if (serviceName == m_serviceName) { setMenuAvailable(false); emit modelNeedsUpdate(); } }); } AppMenuModel::~AppMenuModel() = default; bool AppMenuModel::menuAvailable() const { return m_menuAvailable; } void AppMenuModel::setMenuAvailable(bool set) { if (m_menuAvailable != set) { m_menuAvailable = set; setVisible(true); emit menuAvailableChanged(); } } bool AppMenuModel::visible() const { return m_visible; } void AppMenuModel::setVisible(bool visible) { if (m_visible != visible) { m_visible = visible; emit visibleChanged(); } } QRect AppMenuModel::screenGeometry() const { return m_tasksModel->screenGeometry(); } void AppMenuModel::setScreenGeometry(QRect geometry) { m_tasksModel->setScreenGeometry(geometry); } int AppMenuModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if (!m_menuAvailable || !m_menu) { return 0; } return m_menu->actions().count(); } void AppMenuModel::update() { beginResetModel(); endResetModel(); m_updatePending = false; } void AppMenuModel::onActiveWindowChanged() { const QModelIndex activeTaskIndex = m_tasksModel->activeTask(); const QString objectPath = m_tasksModel->data(activeTaskIndex, TaskManager::AbstractTasksModel::ApplicationMenuObjectPath).toString(); const QString serviceName = m_tasksModel->data(activeTaskIndex, TaskManager::AbstractTasksModel::ApplicationMenuServiceName).toString(); if (!objectPath.isEmpty() && !serviceName.isEmpty()) { setMenuAvailable(true); updateApplicationMenu(serviceName, objectPath); setVisible(true); emit modelNeedsUpdate(); } else { setMenuAvailable(false); setVisible(false); } } QHash AppMenuModel::roleNames() const { QHash roleNames; roleNames[MenuRole] = QByteArrayLiteral("activeMenu"); roleNames[ActionRole] = QByteArrayLiteral("activeActions"); return roleNames; } QVariant AppMenuModel::data(const QModelIndex &index, int role) const { const int row = index.row(); if (row < 0 || !m_menuAvailable || !m_menu) { return QVariant(); } const auto actions = m_menu->actions(); if (row >= actions.count()) { return QVariant(); } if (role == MenuRole) { // TODO this should be Qt::DisplayRole return actions.at(row)->text(); } else if (role == ActionRole) { return QVariant::fromValue((void *) actions.at(row)); } return QVariant(); } void AppMenuModel::updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath) { if (m_serviceName == serviceName && m_menuObjectPath == menuObjectPath) { if (m_importer) { QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection); } return; } m_serviceName = serviceName; m_serviceWatcher->setWatchedServices(QStringList({m_serviceName})); m_menuObjectPath = menuObjectPath; if (m_importer) { m_importer->deleteLater(); } m_importer = new KDBusMenuImporter(serviceName, menuObjectPath, this); QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection); connect(m_importer.data(), &DBusMenuImporter::menuUpdated, this, [=](QMenu *menu) { m_menu = m_importer->menu(); if (m_menu.isNull() || menu != m_menu) { return; } //cache first layer of sub menus, which we'll be popping up const auto actions = m_menu->actions(); for(QAction *a: actions) { // signal dataChanged when the action changes connect(a, &QAction::changed, this, [this, a] { if (m_menuAvailable && m_menu) { const int actionIdx = m_menu->actions().indexOf(a); if (actionIdx > -1) { const QModelIndex modelIdx = index(actionIdx, 0); emit dataChanged(modelIdx, modelIdx); } } }); connect(a, &QAction::destroyed, this, &AppMenuModel::modelNeedsUpdate); if (a->menu()) { m_importer->updateMenu(a->menu()); } } setMenuAvailable(true); emit modelNeedsUpdate(); }); connect(m_importer.data(), &DBusMenuImporter::actionActivationRequested, this, [this](QAction *action) { // TODO submenus if (!m_menuAvailable || !m_menu) { return; } const auto actions = m_menu->actions(); auto it = std::find(actions.begin(), actions.end(), action); if (it != actions.end()) { Q_EMIT requestActivateIndex(it - actions.begin()); } }); }