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.
 
 
 
 
 
 

283 lines
8.2 KiB

/*
* Copyright 2016 Kai Uwe Broulik <kde@privat.broulik.de>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "appmenuapplet.h"
#include "../plugin/appmenumodel.h"
#include <QAction>
#include <QKeyEvent>
#include <QMenu>
#include <QMouseEvent>
#include <QQuickItem>
#include <QQuickWindow>
#include <QScreen>
#include <QDBusConnection>
#include <QTimer>
AppMenuApplet::AppMenuApplet(QObject *parent, const QVariantList &data)
: Plasma::Applet(parent, data)
{
}
AppMenuApplet::~AppMenuApplet() = default;
void AppMenuApplet::init()
{
// TODO Wayland PlasmaShellSurface stuff
QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.kappmenu"),
QStringLiteral("/KAppMenu"),
QStringLiteral("org.kde.kappmenu"),
QStringLiteral("reconfigured"),
this, SLOT(updateAppletEnabled()));
updateAppletEnabled();
}
AppMenuModel *AppMenuApplet::model() const
{
return m_model;
}
void AppMenuApplet::setModel(AppMenuModel *model)
{
if (m_model != model) {
m_model = model;
emit modelChanged();
}
}
int AppMenuApplet::view() const
{
return m_viewType;
}
void AppMenuApplet::setView(int type)
{
if (m_viewType != type) {
m_viewType = type;
emit viewChanged();
}
}
int AppMenuApplet::currentIndex() const
{
return m_currentIndex;
}
void AppMenuApplet::setCurrentIndex(int currentIndex)
{
if (m_currentIndex != currentIndex) {
m_currentIndex = currentIndex;
emit currentIndexChanged();
}
}
QQuickItem *AppMenuApplet::buttonGrid() const
{
return m_buttonGrid;
}
void AppMenuApplet::setButtonGrid(QQuickItem *buttonGrid)
{
if (m_buttonGrid != buttonGrid) {
m_buttonGrid = buttonGrid;
emit buttonGridChanged();
}
}
bool AppMenuApplet::appletEnabled() const
{
return m_appletEnabled;
}
void AppMenuApplet::updateAppletEnabled()
{
KConfigGroup config(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), QStringLiteral("Appmenu Style"));
const QString &menuStyle = config.readEntry(QStringLiteral("Style"));
const bool enabled = (menuStyle == QLatin1String("Widget"));
if (m_appletEnabled != enabled) {
m_appletEnabled = enabled;
emit appletEnabledChanged();
}
}
QMenu *AppMenuApplet::createMenu(int idx) const
{
QMenu *menu = nullptr;
QAction *action = nullptr;
if (view() == CompactView) {
menu = new QMenu();
for (int i=0; i<m_model->rowCount(); i++) {
const QModelIndex index = m_model->index(i, 0);
const QVariant data = m_model->data(index, AppMenuModel::ActionRole);
action = (QAction *)data.value<void *>();
menu->addAction(action);
}
menu->setAttribute(Qt::WA_DeleteOnClose);
} else if (view() == FullView) {
const QModelIndex index = m_model->index(idx, 0);
const QVariant data = m_model->data(index, AppMenuModel::ActionRole);
action = (QAction *)data.value<void *>();
if (action) {
menu = action->menu();
}
}
return menu;
}
void AppMenuApplet::onMenuAboutToHide()
{
setCurrentIndex(-1);
}
void AppMenuApplet::trigger(QQuickItem *ctx, int idx)
{
if (m_currentIndex == idx) {
return;
}
if (!ctx || !ctx->window() || !ctx->window()->screen()) {
return;
}
QMenu *actionMenu = createMenu(idx);
if (actionMenu) {
//this is a workaround where Qt will fail to realise a mouse has been released
// this happens if a window which does not accept focus spawns a new window that takes focus and X grab
// whilst the mouse is depressed
// https://bugreports.qt.io/browse/QTBUG-59044
// this causes the next click to go missing
//by releasing manually we avoid that situation
auto ungrabMouseHack = [ctx]() {
if (ctx && ctx->window() && ctx->window()->mouseGrabberItem()) {
// FIXME event forge thing enters press and hold move mode :/
ctx->window()->mouseGrabberItem()->ungrabMouse();
}
};
QTimer::singleShot(0, ctx, ungrabMouseHack);
//end workaround
const auto &geo = ctx->window()->screen()->availableVirtualGeometry();
QPoint pos = ctx->window()->mapToGlobal(ctx->mapToScene(QPointF()).toPoint());
if (location() == Plasma::Types::TopEdge) {
pos.setY(pos.y() + ctx->height());
}
actionMenu->adjustSize();
pos = QPoint(qBound(geo.x(), pos.x(), geo.x() + geo.width() - actionMenu->width()),
qBound(geo.y(), pos.y(), geo.y() + geo.height() - actionMenu->height()));
if (view() == FullView) {
actionMenu->installEventFilter(this);
}
setStatus(Plasma::Types::AcceptingInputStatus);
actionMenu->winId();//create window handle
actionMenu->windowHandle()->setTransientParent(ctx->window());
actionMenu->popup(pos);
//we can return to passive immediately, an autohide panel will stay open whilst
//any transient window is showing
setStatus(Plasma::Types::PassiveStatus);
if (view() == FullView) {
// hide the old menu only after showing the new one to avoid brief flickering
// in other windows as they briefly re-gain focus
QMenu *oldMenu = m_currentMenu;
m_currentMenu = actionMenu;
if (oldMenu && oldMenu != actionMenu) {
oldMenu->hide();
}
}
setCurrentIndex(idx);
// FIXME TODO connect only once
connect(actionMenu, &QMenu::aboutToHide, this, &AppMenuApplet::onMenuAboutToHide, Qt::UniqueConnection);
return;
}
}
// FIXME TODO doesn't work on submenu
bool AppMenuApplet::eventFilter(QObject *watched, QEvent *event)
{
auto *menu = qobject_cast<QMenu *>(watched);
if (!menu) {
return false;
}
if (event->type() == QEvent::KeyPress) {
auto *e = static_cast<QKeyEvent *>(event);
// TODO right to left languages
if (e->key() == Qt::Key_Left) {
int desiredIndex = m_currentIndex - 1;
emit requestActivateIndex(desiredIndex);
return true;
} else if (e->key() == Qt::Key_Right) {
if (menu->activeAction() && menu->activeAction()->menu()) {
return false;
}
int desiredIndex = m_currentIndex + 1;
emit requestActivateIndex(desiredIndex);
return true;
}
} else if (event->type() == QEvent::MouseMove) {
auto *e = static_cast<QMouseEvent *>(event);
if (!m_buttonGrid || !m_buttonGrid->window()) {
return false;
}
// FIXME the panel margin breaks Fitt's law :(
const QPointF &windowLocalPos = m_buttonGrid->window()->mapFromGlobal(e->globalPos());
const QPointF &buttonGridLocalPos = m_buttonGrid->mapFromScene(windowLocalPos);
auto *item = m_buttonGrid->childAt(buttonGridLocalPos.x(), buttonGridLocalPos.y());
if (!item) {
return false;
}
bool ok;
const int buttonIndex = item->property("buttonIndex").toInt(&ok);
if (!ok) {
return false;
}
emit requestActivateIndex(buttonIndex);
}
return false;
}
K_EXPORT_PLASMA_APPLET_WITH_JSON(appmenu, AppMenuApplet, "metadata.json")
#include "appmenuapplet.moc"