3
0
mirror of https://github.com/Qortal/Brooklyn.git synced 2025-02-12 10:15:54 +00:00
2022-04-02 18:24:21 +05:00

328 lines
10 KiB
C++

/*
SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-FileCopyrightText: 2016 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "appmenumodel.h"
#include <QAction>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusServiceWatcher>
#include <QGuiApplication>
#include <QMenu>
// Includes for the menu search.
#include <KLocalizedString>
#include <QLineEdit>
#include <QListView>
#include <QWidgetAction>
#include <abstracttasksmodel.h>
#include <dbusmenuimporter.h>
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<int> &roles = QVector<int>()) {
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);
Q_EMIT modelNeedsUpdate();
}
});
// X11 has funky menu behaviour that prevents this from working properly.
if (KWindowSystem::isPlatformWayland()) {
m_searchAction = new QAction(this);
m_searchAction->setText(i18n("Search"));
m_searchAction->setObjectName(QStringLiteral("appmenu"));
m_searchMenu.reset(new QMenu);
auto searchAction = new QWidgetAction(this);
auto searchBar = new QLineEdit;
searchBar->setClearButtonEnabled(true);
searchBar->setPlaceholderText(i18n("Search…"));
searchBar->setMinimumWidth(200);
searchBar->setContentsMargins(4, 4, 4, 4);
connect(m_tasksModel, &TaskManager::TasksModel::activeTaskChanged, [=]() {
searchBar->setText(QString());
});
connect(searchBar, &QLineEdit::textChanged, [=]() mutable {
insertSearchActionsIntoMenu(searchBar->text());
});
connect(searchBar, &QLineEdit::returnPressed, [=]() mutable {
if (m_currentSearchActions.first()) {
m_currentSearchActions.first()->trigger();
}
});
connect(this, &AppMenuModel::modelNeedsUpdate, this, [this, searchBar]() mutable {
insertSearchActionsIntoMenu(searchBar->text());
});
searchAction->setDefaultWidget(searchBar);
m_searchMenu->addAction(searchAction);
m_searchMenu->addSeparator();
m_searchAction->setMenu(m_searchMenu.get());
}
}
AppMenuModel::~AppMenuModel() = default;
bool AppMenuModel::menuAvailable() const
{
return m_menuAvailable;
}
void AppMenuModel::setMenuAvailable(bool set)
{
if (m_menuAvailable != set) {
m_menuAvailable = set;
setVisible(true);
Q_EMIT menuAvailableChanged();
}
}
bool AppMenuModel::visible() const
{
return m_visible;
}
void AppMenuModel::setVisible(bool visible)
{
if (m_visible != visible) {
m_visible = visible;
Q_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() + (KWindowSystem::isPlatformWayland() ? 1 : 0);
}
void AppMenuModel::removeSearchActionsFromMenu()
{
for (const auto &action : m_currentSearchActions) {
m_searchAction->menu()->removeAction(action);
}
m_currentSearchActions = QList<QAction *>();
}
void AppMenuModel::insertSearchActionsIntoMenu(const QString &filter)
{
removeSearchActionsFromMenu();
if (filter.isEmpty()) {
return;
}
const auto actions = flatActionList();
for (const auto &action : actions) {
if (action->text().contains(filter, Qt::CaseInsensitive)) {
m_searchAction->menu()->addAction(action);
m_currentSearchActions << action;
}
}
}
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);
Q_EMIT modelNeedsUpdate();
} else {
setMenuAvailable(false);
setVisible(false);
}
}
QHash<int, QByteArray> AppMenuModel::roleNames() const
{
QHash<int, QByteArray> roleNames;
roleNames[MenuRole] = QByteArrayLiteral("activeMenu");
roleNames[ActionRole] = QByteArrayLiteral("activeActions");
return roleNames;
}
QList<QAction *> AppMenuModel::flatActionList()
{
QList<QAction *> ret;
if (!m_menuAvailable || !m_menu) {
return ret;
}
const auto actions = m_menu->findChildren<QAction *>();
for (auto &action : actions) {
if (action->menu() == nullptr) {
ret << action;
}
}
return ret;
}
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() && KWindowSystem::isPlatformWayland()) {
if (role == MenuRole) {
return m_searchAction->text();
} else if (role == ActionRole) {
return QVariant::fromValue((void *)m_searchAction);
}
}
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);
Q_EMIT dataChanged(modelIdx, modelIdx);
}
}
});
connect(a, &QAction::destroyed, this, &AppMenuModel::modelNeedsUpdate);
if (a->menu()) {
m_importer->updateMenu(a->menu());
}
}
setMenuAvailable(true);
Q_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());
}
});
}