Compare commits
2 commits
d8b900ed0b
...
ec362637b8
Author | SHA1 | Date | |
---|---|---|---|
outfoxxed | ec362637b8 | ||
outfoxxed | c31bbea837 |
|
@ -1,7 +1,7 @@
|
||||||
# quickshell
|
# quickshell
|
||||||
<a href="https://matrix.to/#/#quickshell:outfoxxed.me"><img src="https://img.shields.io/badge/Join%20the%20matrix%20room-%23quickshell:outfoxxed.me-0dbd8b?logo=matrix&style=flat-square"></a>
|
<a href="https://matrix.to/#/#quickshell:outfoxxed.me"><img src="https://img.shields.io/badge/Join%20the%20matrix%20room-%23quickshell:outfoxxed.me-0dbd8b?logo=matrix&style=flat-square"></a>
|
||||||
|
|
||||||
Simple and flexbile QtQuick based desktop shell toolkit.
|
Flexbile QtQuick based desktop shell toolkit.
|
||||||
|
|
||||||
Hosted on: [outfoxxed's gitea], [github]
|
Hosted on: [outfoxxed's gitea], [github]
|
||||||
|
|
||||||
|
@ -14,6 +14,12 @@ can be built from the [quickshell-docs](https://git.outfoxxed.me/outfoxxed/quick
|
||||||
Some fully working examples can be found in the [quickshell-examples](https://git.outfoxxed.me/outfoxxed/quickshell-examples)
|
Some fully working examples can be found in the [quickshell-examples](https://git.outfoxxed.me/outfoxxed/quickshell-examples)
|
||||||
repo.
|
repo.
|
||||||
|
|
||||||
|
# Breaking Changes
|
||||||
|
Quickshell is still in alpha and there will be breaking changes.
|
||||||
|
|
||||||
|
Commits with breaking qml api changes will contain a `!` at the end of the scope
|
||||||
|
(`thing!: foo`) and the commit description will contain details about the broken api.
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
## Nix
|
## Nix
|
||||||
|
|
|
@ -30,6 +30,8 @@ qt_add_library(quickshell-core STATIC
|
||||||
elapsedtimer.cpp
|
elapsedtimer.cpp
|
||||||
desktopentry.cpp
|
desktopentry.cpp
|
||||||
objectrepeater.cpp
|
objectrepeater.cpp
|
||||||
|
platformmenu.cpp
|
||||||
|
qsmenu.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
|
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
|
||||||
|
|
|
@ -8,12 +8,17 @@
|
||||||
#include <qfileinfo.h>
|
#include <qfileinfo.h>
|
||||||
#include <qfilesystemwatcher.h>
|
#include <qfilesystemwatcher.h>
|
||||||
#include <qhash.h>
|
#include <qhash.h>
|
||||||
|
#include <qicon.h>
|
||||||
|
#include <qiconengine.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
|
#include <qpixmap.h>
|
||||||
#include <qqmlcontext.h>
|
#include <qqmlcontext.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qqmlincubator.h>
|
#include <qqmlincubator.h>
|
||||||
|
#include <qquickimageprovider.h>
|
||||||
|
#include <qsize.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include "iconimageprovider.hpp"
|
#include "iconimageprovider.hpp"
|
||||||
|
@ -308,6 +313,90 @@ EngineGeneration* EngineGeneration::currentGeneration() {
|
||||||
} else return nullptr;
|
} else return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QMenu re-calls pixmap() every time the mouse moves so its important to cache it.
|
||||||
|
class PixmapCacheIconEngine: public QIconEngine {
|
||||||
|
void paint(
|
||||||
|
QPainter* /*unused*/,
|
||||||
|
const QRect& /*unused*/,
|
||||||
|
QIcon::Mode /*unused*/,
|
||||||
|
QIcon::State /*unused*/
|
||||||
|
) override {
|
||||||
|
qFatal(
|
||||||
|
) << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug.";
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap pixmap(const QSize& size, QIcon::Mode /*unused*/, QIcon::State /*unused*/) override {
|
||||||
|
if (this->lastPixmap.isNull() || size != this->lastSize) {
|
||||||
|
this->lastPixmap = this->createPixmap(size);
|
||||||
|
this->lastSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->lastPixmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual QPixmap createPixmap(const QSize& size) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSize lastSize;
|
||||||
|
QPixmap lastPixmap;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageProviderIconEngine: public PixmapCacheIconEngine {
|
||||||
|
public:
|
||||||
|
explicit ImageProviderIconEngine(QQuickImageProvider* provider, QString id)
|
||||||
|
: provider(provider)
|
||||||
|
, id(std::move(id)) {}
|
||||||
|
|
||||||
|
QPixmap createPixmap(const QSize& size) override {
|
||||||
|
if (this->provider->imageType() == QQmlImageProviderBase::Pixmap) {
|
||||||
|
return this->provider->requestPixmap(this->id, nullptr, size);
|
||||||
|
} else if (this->provider->imageType() == QQmlImageProviderBase::Image) {
|
||||||
|
auto image = this->provider->requestImage(this->id, nullptr, size);
|
||||||
|
return QPixmap::fromImage(image);
|
||||||
|
} else {
|
||||||
|
qFatal() << "Unexpected ImageProviderIconEngine image type" << this->provider->imageType();
|
||||||
|
return QPixmap(); // never reached, satisfies lint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QIconEngine* clone() const override {
|
||||||
|
return new ImageProviderIconEngine(this->provider, this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QQuickImageProvider* provider;
|
||||||
|
QString id;
|
||||||
|
};
|
||||||
|
|
||||||
|
QIcon EngineGeneration::iconByUrl(const QUrl& url) const {
|
||||||
|
if (url.isEmpty()) return QIcon();
|
||||||
|
|
||||||
|
auto scheme = url.scheme();
|
||||||
|
if (scheme == "image") {
|
||||||
|
auto providerName = url.authority();
|
||||||
|
auto path = url.path();
|
||||||
|
if (!path.isEmpty()) path = path.sliced(1);
|
||||||
|
|
||||||
|
auto* provider = qobject_cast<QQuickImageProvider*>(this->engine->imageProvider(providerName));
|
||||||
|
|
||||||
|
if (provider == nullptr) {
|
||||||
|
qWarning() << "iconByUrl failed: no provider found for" << url;
|
||||||
|
return QIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider->imageType() == QQmlImageProviderBase::Pixmap
|
||||||
|
|| provider->imageType() == QQmlImageProviderBase::Image)
|
||||||
|
{
|
||||||
|
return QIcon(new ImageProviderIconEngine(provider, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
qWarning() << "iconByUrl failed: unsupported scheme" << scheme << "in path" << url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QIcon();
|
||||||
|
}
|
||||||
|
|
||||||
EngineGeneration* EngineGeneration::findEngineGeneration(QQmlEngine* engine) {
|
EngineGeneration* EngineGeneration::findEngineGeneration(QQmlEngine* engine) {
|
||||||
return g_generations.value(engine);
|
return g_generations.value(engine);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qfilesystemwatcher.h>
|
#include <qfilesystemwatcher.h>
|
||||||
|
#include <qicon.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qpair.h>
|
#include <qpair.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qqmlincubator.h>
|
#include <qqmlincubator.h>
|
||||||
#include <qtclasshelpermacros.h>
|
#include <qtclasshelpermacros.h>
|
||||||
|
#include <qurl.h>
|
||||||
|
|
||||||
#include "incubator.hpp"
|
#include "incubator.hpp"
|
||||||
#include "qsintercept.hpp"
|
#include "qsintercept.hpp"
|
||||||
|
@ -40,6 +42,8 @@ public:
|
||||||
// otherwise null.
|
// otherwise null.
|
||||||
static EngineGeneration* currentGeneration();
|
static EngineGeneration* currentGeneration();
|
||||||
|
|
||||||
|
[[nodiscard]] QIcon iconByUrl(const QUrl& url) const;
|
||||||
|
|
||||||
RootWrapper* wrapper = nullptr;
|
RootWrapper* wrapper = nullptr;
|
||||||
QDir rootPath;
|
QDir rootPath;
|
||||||
QmlScanner scanner;
|
QmlScanner scanner;
|
||||||
|
|
|
@ -22,5 +22,6 @@ headers = [
|
||||||
"elapsedtimer.hpp",
|
"elapsedtimer.hpp",
|
||||||
"desktopentry.hpp",
|
"desktopentry.hpp",
|
||||||
"objectrepeater.hpp",
|
"objectrepeater.hpp",
|
||||||
|
"qsmenu.hpp"
|
||||||
]
|
]
|
||||||
-----
|
-----
|
||||||
|
|
276
src/core/platformmenu.cpp
Normal file
276
src/core/platformmenu.cpp
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
#include "platformmenu.hpp"
|
||||||
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <qaction.h>
|
||||||
|
#include <qactiongroup.h>
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qicon.h>
|
||||||
|
#include <qlogging.h>
|
||||||
|
#include <qmenu.h>
|
||||||
|
#include <qnamespace.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qpoint.h>
|
||||||
|
#include <qqmllist.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
#include <qwindow.h>
|
||||||
|
|
||||||
|
#include "generation.hpp"
|
||||||
|
#include "proxywindow.hpp"
|
||||||
|
#include "qsmenu.hpp"
|
||||||
|
#include "windowinterface.hpp"
|
||||||
|
|
||||||
|
namespace qs::menu::platform {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QVector<std::function<void(PlatformMenuQMenu*)>> CREATION_HOOKS; // NOLINT
|
||||||
|
PlatformMenuQMenu* ACTIVE_MENU = nullptr; // NOLINT
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
PlatformMenuQMenu::~PlatformMenuQMenu() {
|
||||||
|
if (this == ACTIVE_MENU) {
|
||||||
|
ACTIVE_MENU = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformMenuQMenu::setVisible(bool visible) {
|
||||||
|
if (visible) {
|
||||||
|
for (auto& hook: CREATION_HOOKS) {
|
||||||
|
hook(this);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this == ACTIVE_MENU) {
|
||||||
|
ACTIVE_MENU = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->QMenu::setVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformMenuEntry::PlatformMenuEntry(QsMenuEntry* menu): QObject(menu), menu(menu) {
|
||||||
|
this->relayout();
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
QObject::connect(menu, &QsMenuEntry::enabledChanged, this, &PlatformMenuEntry::onEnabledChanged);
|
||||||
|
QObject::connect(menu, &QsMenuEntry::textChanged, this, &PlatformMenuEntry::onTextChanged);
|
||||||
|
QObject::connect(menu, &QsMenuEntry::iconChanged, this, &PlatformMenuEntry::onIconChanged);
|
||||||
|
QObject::connect(menu, &QsMenuEntry::buttonTypeChanged, this, &PlatformMenuEntry::onButtonTypeChanged);
|
||||||
|
QObject::connect(menu, &QsMenuEntry::checkStateChanged, this, &PlatformMenuEntry::onCheckStateChanged);
|
||||||
|
QObject::connect(menu, &QsMenuEntry::hasChildrenChanged, this, &PlatformMenuEntry::relayoutParent);
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformMenuEntry::~PlatformMenuEntry() {
|
||||||
|
this->clearChildren();
|
||||||
|
delete this->qaction;
|
||||||
|
delete this->qmenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformMenuEntry::registerCreationHook(std::function<void(PlatformMenuQMenu*)> hook) {
|
||||||
|
CREATION_HOOKS.push_back(std::move(hook));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlatformMenuEntry::display(QObject* parentWindow, int relativeX, int relativeY) {
|
||||||
|
QWindow* window = nullptr;
|
||||||
|
|
||||||
|
if (this->qmenu == nullptr) {
|
||||||
|
qCritical() << "Cannot display PlatformMenuEntry as it is not a menu.";
|
||||||
|
return false;
|
||||||
|
} else if (parentWindow == nullptr) {
|
||||||
|
qCritical() << "Cannot display PlatformMenuEntry with null parent window.";
|
||||||
|
return false;
|
||||||
|
} else if (auto* proxy = qobject_cast<ProxyWindowBase*>(parentWindow)) {
|
||||||
|
window = proxy->backingWindow();
|
||||||
|
} else if (auto* interface = qobject_cast<WindowInterface*>(parentWindow)) {
|
||||||
|
window = interface->proxyWindow()->backingWindow();
|
||||||
|
} else {
|
||||||
|
qCritical() << "PlatformMenuEntry.display() must be called with a window.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window == nullptr) {
|
||||||
|
qCritical() << "Cannot display PlatformMenuEntry from a parent window that is not visible.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ACTIVE_MENU && this->qmenu != ACTIVE_MENU) {
|
||||||
|
ACTIVE_MENU->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTIVE_MENU = this->qmenu;
|
||||||
|
|
||||||
|
auto point = window->mapToGlobal(QPoint(relativeX, relativeY));
|
||||||
|
|
||||||
|
this->qmenu->createWinId();
|
||||||
|
this->qmenu->windowHandle()->setTransientParent(window);
|
||||||
|
|
||||||
|
this->qmenu->popup(point);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformMenuEntry::relayout() {
|
||||||
|
if (this->menu->hasChildren()) {
|
||||||
|
delete this->qaction;
|
||||||
|
this->qaction = nullptr;
|
||||||
|
|
||||||
|
if (this->qmenu == nullptr) {
|
||||||
|
this->qmenu = new PlatformMenuQMenu();
|
||||||
|
QObject::connect(this->qmenu, &QMenu::aboutToShow, this, &PlatformMenuEntry::onAboutToShow);
|
||||||
|
QObject::connect(this->qmenu, &QMenu::aboutToHide, this, &PlatformMenuEntry::onAboutToHide);
|
||||||
|
} else {
|
||||||
|
this->clearChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->qmenu->setTitle(this->menu->text());
|
||||||
|
|
||||||
|
auto icon = this->menu->icon();
|
||||||
|
if (!icon.isEmpty()) {
|
||||||
|
auto* generation = EngineGeneration::currentGeneration();
|
||||||
|
this->qmenu->setIcon(generation->iconByUrl(this->menu->icon()));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto children = this->menu->children();
|
||||||
|
auto len = children.count(&children);
|
||||||
|
for (auto i = 0; i < len; i++) {
|
||||||
|
auto* child = children.at(&children, i);
|
||||||
|
|
||||||
|
auto* instance = new PlatformMenuEntry(child);
|
||||||
|
QObject::connect(instance, &QObject::destroyed, this, &PlatformMenuEntry::onChildDestroyed);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
instance,
|
||||||
|
&PlatformMenuEntry::relayoutParent,
|
||||||
|
this,
|
||||||
|
&PlatformMenuEntry::relayout
|
||||||
|
);
|
||||||
|
|
||||||
|
this->childEntries.push_back(instance);
|
||||||
|
instance->addToQMenu(this->qmenu);
|
||||||
|
}
|
||||||
|
} else if (!this->menu->isSeparator()) {
|
||||||
|
this->clearChildren();
|
||||||
|
delete this->qmenu;
|
||||||
|
this->qmenu = nullptr;
|
||||||
|
|
||||||
|
if (this->qaction == nullptr) {
|
||||||
|
this->qaction = new QAction(this);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
this->qaction,
|
||||||
|
&QAction::triggered,
|
||||||
|
this,
|
||||||
|
&PlatformMenuEntry::onActionTriggered
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->qaction->setText(this->menu->text());
|
||||||
|
|
||||||
|
auto icon = this->menu->icon();
|
||||||
|
if (!icon.isEmpty()) {
|
||||||
|
auto* generation = EngineGeneration::currentGeneration();
|
||||||
|
this->qaction->setIcon(generation->iconByUrl(this->menu->icon()));
|
||||||
|
}
|
||||||
|
|
||||||
|
this->qaction->setEnabled(this->menu->enabled());
|
||||||
|
this->qaction->setCheckable(this->menu->buttonType() != QsMenuButtonType::None);
|
||||||
|
|
||||||
|
if (this->menu->buttonType() == QsMenuButtonType::RadioButton) {
|
||||||
|
if (!this->qactiongroup) this->qactiongroup = new QActionGroup(this);
|
||||||
|
this->qaction->setActionGroup(this->qactiongroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->qaction->setChecked(this->menu->checkState() != Qt::Unchecked);
|
||||||
|
} else {
|
||||||
|
delete this->qmenu;
|
||||||
|
delete this->qaction;
|
||||||
|
this->qmenu = nullptr;
|
||||||
|
this->qaction = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformMenuEntry::onAboutToShow() { this->menu->ref(); }
|
||||||
|
|
||||||
|
void PlatformMenuEntry::onAboutToHide() {
|
||||||
|
this->menu->unref();
|
||||||
|
emit this->closed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformMenuEntry::onActionTriggered() {
|
||||||
|
auto* action = qobject_cast<PlatformMenuEntry*>(this->sender()->parent());
|
||||||
|
emit action->menu->triggered();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformMenuEntry::onChildDestroyed() { this->childEntries.removeOne(this->sender()); }
|
||||||
|
|
||||||
|
void PlatformMenuEntry::onEnabledChanged() {
|
||||||
|
if (this->qaction != nullptr) {
|
||||||
|
this->qaction->setEnabled(this->menu->enabled());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformMenuEntry::onTextChanged() {
|
||||||
|
if (this->qmenu != nullptr) {
|
||||||
|
this->qmenu->setTitle(this->menu->text());
|
||||||
|
} else if (this->qaction != nullptr) {
|
||||||
|
this->qaction->setText(this->menu->text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformMenuEntry::onIconChanged() {
|
||||||
|
if (this->qmenu == nullptr && this->qaction == nullptr) return;
|
||||||
|
|
||||||
|
auto iconName = this->menu->icon();
|
||||||
|
QIcon icon;
|
||||||
|
|
||||||
|
if (!iconName.isEmpty()) {
|
||||||
|
auto* generation = EngineGeneration::currentGeneration();
|
||||||
|
icon = generation->iconByUrl(iconName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->qmenu != nullptr) {
|
||||||
|
this->qmenu->setIcon(icon);
|
||||||
|
} else if (this->qaction != nullptr) {
|
||||||
|
this->qaction->setIcon(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformMenuEntry::onButtonTypeChanged() {
|
||||||
|
if (this->qaction != nullptr) {
|
||||||
|
QActionGroup* group = nullptr;
|
||||||
|
|
||||||
|
if (this->menu->buttonType() == QsMenuButtonType::RadioButton) {
|
||||||
|
if (!this->qactiongroup) this->qactiongroup = new QActionGroup(this);
|
||||||
|
group = this->qactiongroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->qaction->setActionGroup(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformMenuEntry::onCheckStateChanged() {
|
||||||
|
if (this->qaction != nullptr) {
|
||||||
|
this->qaction->setChecked(this->menu->checkState() != Qt::Unchecked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformMenuEntry::clearChildren() {
|
||||||
|
for (auto* child: this->childEntries) {
|
||||||
|
delete child;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->childEntries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformMenuEntry::addToQMenu(PlatformMenuQMenu* menu) {
|
||||||
|
if (this->qmenu != nullptr) {
|
||||||
|
menu->addMenu(this->qmenu);
|
||||||
|
this->qmenu->containingMenu = menu;
|
||||||
|
} else if (this->qaction != nullptr) {
|
||||||
|
menu->addAction(this->qaction);
|
||||||
|
} else {
|
||||||
|
menu->addSeparator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::menu::platform
|
72
src/core/platformmenu.hpp
Normal file
72
src/core/platformmenu.hpp
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <qaction.h>
|
||||||
|
#include <qactiongroup.h>
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qmenu.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qpoint.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
#include <qqmllist.h>
|
||||||
|
#include <qtclasshelpermacros.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
#include "qsmenu.hpp"
|
||||||
|
|
||||||
|
namespace qs::menu::platform {
|
||||||
|
|
||||||
|
class PlatformMenuQMenu: public QMenu {
|
||||||
|
public:
|
||||||
|
explicit PlatformMenuQMenu() = default;
|
||||||
|
~PlatformMenuQMenu() override;
|
||||||
|
Q_DISABLE_COPY_MOVE(PlatformMenuQMenu);
|
||||||
|
|
||||||
|
void setVisible(bool visible) override;
|
||||||
|
|
||||||
|
PlatformMenuQMenu* containingMenu = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PlatformMenuEntry: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PlatformMenuEntry(QsMenuEntry* menu);
|
||||||
|
~PlatformMenuEntry() override;
|
||||||
|
Q_DISABLE_COPY_MOVE(PlatformMenuEntry);
|
||||||
|
|
||||||
|
bool display(QObject* parentWindow, int relativeX, int relativeY);
|
||||||
|
|
||||||
|
static void registerCreationHook(std::function<void(PlatformMenuQMenu*)> hook);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void closed();
|
||||||
|
void relayoutParent();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void relayout();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onAboutToShow();
|
||||||
|
void onAboutToHide();
|
||||||
|
void onActionTriggered();
|
||||||
|
void onChildDestroyed();
|
||||||
|
void onEnabledChanged();
|
||||||
|
void onTextChanged();
|
||||||
|
void onIconChanged();
|
||||||
|
void onButtonTypeChanged();
|
||||||
|
void onCheckStateChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void clearChildren();
|
||||||
|
void addToQMenu(PlatformMenuQMenu* menu);
|
||||||
|
|
||||||
|
QsMenuEntry* menu;
|
||||||
|
PlatformMenuQMenu* qmenu = nullptr;
|
||||||
|
QAction* qaction = nullptr;
|
||||||
|
QActionGroup* qactiongroup = nullptr;
|
||||||
|
QVector<PlatformMenuEntry*> childEntries;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qs::menu::platform
|
95
src/core/qsmenu.cpp
Normal file
95
src/core/qsmenu.cpp
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
#include "qsmenu.hpp"
|
||||||
|
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qqmllist.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
#include "platformmenu.hpp"
|
||||||
|
|
||||||
|
using namespace qs::menu::platform;
|
||||||
|
|
||||||
|
namespace qs::menu {
|
||||||
|
|
||||||
|
QString QsMenuButtonType::toString(QsMenuButtonType::Enum value) {
|
||||||
|
switch (value) {
|
||||||
|
case QsMenuButtonType::None: return "None";
|
||||||
|
case QsMenuButtonType::CheckBox: return "CheckBox";
|
||||||
|
case QsMenuButtonType::RadioButton: return "RadioButton";
|
||||||
|
default: return "Invalid button type";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QsMenuEntry::display(QObject* parentWindow, int relativeX, int relativeY) {
|
||||||
|
auto* platform = new PlatformMenuEntry(this);
|
||||||
|
|
||||||
|
QObject::connect(platform, &PlatformMenuEntry::closed, platform, [=]() {
|
||||||
|
platform->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto success = platform->display(parentWindow, relativeX, relativeY);
|
||||||
|
if (!success) delete platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
QQmlListProperty<QsMenuEntry> QsMenuEntry::emptyChildren(QObject* parent) {
|
||||||
|
return QQmlListProperty<QsMenuEntry>(
|
||||||
|
parent,
|
||||||
|
nullptr,
|
||||||
|
&QsMenuEntry::childCount,
|
||||||
|
&QsMenuEntry::childAt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QsMenuEntry::ref() {
|
||||||
|
this->refcount++;
|
||||||
|
if (this->refcount == 1) emit this->opened();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QsMenuEntry::unref() {
|
||||||
|
this->refcount--;
|
||||||
|
if (this->refcount == 0) emit this->closed();
|
||||||
|
}
|
||||||
|
|
||||||
|
QQmlListProperty<QsMenuEntry> QsMenuEntry::children() { return QsMenuEntry::emptyChildren(this); }
|
||||||
|
|
||||||
|
QsMenuEntry* QsMenuOpener::menu() const { return this->mMenu; }
|
||||||
|
|
||||||
|
void QsMenuOpener::setMenu(QsMenuEntry* menu) {
|
||||||
|
if (menu == this->mMenu) return;
|
||||||
|
|
||||||
|
if (this->mMenu != nullptr) {
|
||||||
|
this->mMenu->unref();
|
||||||
|
QObject::disconnect(this->mMenu, nullptr, this, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->mMenu = menu;
|
||||||
|
|
||||||
|
if (menu != nullptr) {
|
||||||
|
QObject::connect(menu, &QObject::destroyed, this, &QsMenuOpener::onMenuDestroyed);
|
||||||
|
QObject::connect(menu, &QsMenuEntry::childrenChanged, this, &QsMenuOpener::childrenChanged);
|
||||||
|
menu->ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
emit this->menuChanged();
|
||||||
|
emit this->childrenChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QsMenuOpener::onMenuDestroyed() {
|
||||||
|
this->mMenu = nullptr;
|
||||||
|
emit this->menuChanged();
|
||||||
|
emit this->childrenChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QQmlListProperty<QsMenuEntry> QsMenuOpener::children() {
|
||||||
|
return this->mMenu ? this->mMenu->children() : QsMenuEntry::emptyChildren(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
qsizetype QsMenuEntry::childCount(QQmlListProperty<QsMenuEntry>* /*property*/) { return 0; }
|
||||||
|
|
||||||
|
QsMenuEntry*
|
||||||
|
QsMenuEntry::childAt(QQmlListProperty<QsMenuEntry>* /*property*/, qsizetype /*index*/) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::menu
|
140
src/core/qsmenu.hpp
Normal file
140
src/core/qsmenu.hpp
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qnamespace.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
#include <qqmllist.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
#include "doc.hpp"
|
||||||
|
|
||||||
|
namespace qs::menu {
|
||||||
|
|
||||||
|
class QsMenuButtonType: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_SINGLETON;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Enum {
|
||||||
|
/// This menu item does not have a checkbox or a radiobutton associated with it.
|
||||||
|
None = 0,
|
||||||
|
/// This menu item should draw a checkbox.
|
||||||
|
CheckBox = 1,
|
||||||
|
/// This menu item should draw a radiobutton.
|
||||||
|
RadioButton = 2,
|
||||||
|
};
|
||||||
|
Q_ENUM(Enum);
|
||||||
|
|
||||||
|
Q_INVOKABLE static QString toString(QsMenuButtonType::Enum value);
|
||||||
|
};
|
||||||
|
|
||||||
|
class QsMenuEntry: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
/// If this menu item should be rendered as a separator between other items.
|
||||||
|
///
|
||||||
|
/// No other properties have a meaningful value when `isSeparator` is true.
|
||||||
|
Q_PROPERTY(bool isSeparator READ isSeparator NOTIFY isSeparatorChanged);
|
||||||
|
Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged);
|
||||||
|
/// Text of the menu item.
|
||||||
|
Q_PROPERTY(QString text READ text NOTIFY textChanged);
|
||||||
|
/// Url of the menu item's icon or `""` if it doesn't have one.
|
||||||
|
///
|
||||||
|
/// This can be passed to [Image.source](https://doc.qt.io/qt-6/qml-qtquick-image.html#source-prop)
|
||||||
|
/// as shown below.
|
||||||
|
///
|
||||||
|
/// ```qml
|
||||||
|
/// Image {
|
||||||
|
/// source: menuItem.icon
|
||||||
|
/// // To get the best image quality, set the image source size to the same size
|
||||||
|
/// // as the rendered image.
|
||||||
|
/// sourceSize.width: width
|
||||||
|
/// sourceSize.height: height
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged);
|
||||||
|
/// If this menu item has an associated checkbox or radiobutton.
|
||||||
|
Q_PROPERTY(QsMenuButtonType::Enum buttonType READ buttonType NOTIFY buttonTypeChanged);
|
||||||
|
/// The check state of the checkbox or radiobutton if applicable, as a
|
||||||
|
/// [Qt.CheckState](https://doc.qt.io/qt-6/qt.html#CheckState-enum).
|
||||||
|
Q_PROPERTY(Qt::CheckState checkState READ checkState NOTIFY checkStateChanged);
|
||||||
|
/// If this menu item has children that can be accessed through a [QsMenuOpener](../qsmenuopener).
|
||||||
|
Q_PROPERTY(bool hasChildren READ hasChildren NOTIFY hasChildrenChanged);
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_UNCREATABLE("QsMenuEntry cannot be directly created");
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit QsMenuEntry(QObject* parent = nullptr): QObject(parent) {}
|
||||||
|
|
||||||
|
/// Display a platform menu at the given location relative to the parent window.
|
||||||
|
Q_INVOKABLE void display(QObject* parentWindow, qint32 relativeX, qint32 relativeY);
|
||||||
|
|
||||||
|
[[nodiscard]] virtual bool isSeparator() const { return false; }
|
||||||
|
[[nodiscard]] virtual bool enabled() const { return true; }
|
||||||
|
[[nodiscard]] virtual QString text() const { return ""; }
|
||||||
|
[[nodiscard]] virtual QString icon() const { return ""; }
|
||||||
|
[[nodiscard]] virtual QsMenuButtonType::Enum buttonType() const { return QsMenuButtonType::None; }
|
||||||
|
[[nodiscard]] virtual Qt::CheckState checkState() const { return Qt::Unchecked; }
|
||||||
|
[[nodiscard]] virtual bool hasChildren() const { return false; }
|
||||||
|
|
||||||
|
void ref();
|
||||||
|
void unref();
|
||||||
|
|
||||||
|
[[nodiscard]] virtual QQmlListProperty<QsMenuEntry> children();
|
||||||
|
|
||||||
|
static QQmlListProperty<QsMenuEntry> emptyChildren(QObject* parent);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/// Send a trigger/click signal to the menu entry.
|
||||||
|
void triggered();
|
||||||
|
|
||||||
|
QSDOC_HIDE void opened();
|
||||||
|
QSDOC_HIDE void closed();
|
||||||
|
|
||||||
|
void isSeparatorChanged();
|
||||||
|
void enabledChanged();
|
||||||
|
void textChanged();
|
||||||
|
void iconChanged();
|
||||||
|
void buttonTypeChanged();
|
||||||
|
void checkStateChanged();
|
||||||
|
void hasChildrenChanged();
|
||||||
|
QSDOC_HIDE void childrenChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static qsizetype childCount(QQmlListProperty<QsMenuEntry>* property);
|
||||||
|
static QsMenuEntry* childAt(QQmlListProperty<QsMenuEntry>* property, qsizetype index);
|
||||||
|
|
||||||
|
qsizetype refcount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
///! Provides access to children of a QsMenuEntry
|
||||||
|
class QsMenuOpener: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
/// The menu to retrieve children from.
|
||||||
|
Q_PROPERTY(QsMenuEntry* menu READ menu WRITE setMenu NOTIFY menuChanged);
|
||||||
|
/// The children of the given menu.
|
||||||
|
Q_PROPERTY(QQmlListProperty<QsMenuEntry> children READ children NOTIFY childrenChanged);
|
||||||
|
QML_ELEMENT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit QsMenuOpener(QObject* parent = nullptr): QObject(parent) {}
|
||||||
|
|
||||||
|
[[nodiscard]] QsMenuEntry* menu() const;
|
||||||
|
void setMenu(QsMenuEntry* menu);
|
||||||
|
|
||||||
|
[[nodiscard]] QQmlListProperty<QsMenuEntry> children();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void menuChanged();
|
||||||
|
void childrenChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onMenuDestroyed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QsMenuEntry* mMenu = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qs::menu
|
|
@ -21,19 +21,26 @@
|
||||||
#include <qvariant.h>
|
#include <qvariant.h>
|
||||||
|
|
||||||
#include "../../core/iconimageprovider.hpp"
|
#include "../../core/iconimageprovider.hpp"
|
||||||
|
#include "../../core/qsmenu.hpp"
|
||||||
#include "../../dbus/properties.hpp"
|
#include "../../dbus/properties.hpp"
|
||||||
#include "dbus_menu.h"
|
#include "dbus_menu.h"
|
||||||
#include "dbus_menu_types.hpp"
|
#include "dbus_menu_types.hpp"
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(logDbusMenu, "quickshell.dbus.dbusmenu", QtWarningMsg);
|
Q_LOGGING_CATEGORY(logDbusMenu, "quickshell.dbus.dbusmenu", QtWarningMsg);
|
||||||
|
|
||||||
|
using namespace qs::menu;
|
||||||
|
|
||||||
namespace qs::dbus::dbusmenu {
|
namespace qs::dbus::dbusmenu {
|
||||||
|
|
||||||
DBusMenuItem::DBusMenuItem(qint32 id, DBusMenu* menu, DBusMenuItem* parentMenu)
|
DBusMenuItem::DBusMenuItem(qint32 id, DBusMenu* menu, DBusMenuItem* parentMenu)
|
||||||
: QObject(menu)
|
: QsMenuEntry(menu)
|
||||||
, id(id)
|
, id(id)
|
||||||
, menu(menu)
|
, menu(menu)
|
||||||
, parentMenu(parentMenu) {
|
, parentMenu(parentMenu) {
|
||||||
|
QObject::connect(this, &QsMenuEntry::opened, this, &DBusMenuItem::sendOpened);
|
||||||
|
QObject::connect(this, &QsMenuEntry::closed, this, &DBusMenuItem::sendClosed);
|
||||||
|
QObject::connect(this, &QsMenuEntry::triggered, this, &DBusMenuItem::sendTriggered);
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
&this->menu->iconThemePath,
|
&this->menu->iconThemePath,
|
||||||
&AbstractDBusProperty::changed,
|
&AbstractDBusProperty::changed,
|
||||||
|
@ -42,20 +49,13 @@ DBusMenuItem::DBusMenuItem(qint32 id, DBusMenu* menu, DBusMenuItem* parentMenu)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DBusMenuItem::click() {
|
void DBusMenuItem::sendOpened() const { this->menu->sendEvent(this->id, "opened"); }
|
||||||
if (this->displayChildren) {
|
void DBusMenuItem::sendClosed() const { this->menu->sendEvent(this->id, "closed"); }
|
||||||
this->setShowChildren(!this->mShowChildren);
|
void DBusMenuItem::sendTriggered() const { this->menu->sendEvent(this->id, "clicked"); }
|
||||||
} else {
|
|
||||||
this->menu->sendEvent(this->id, "clicked");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DBusMenuItem::hover() const { this->menu->sendEvent(this->id, "hovered"); }
|
|
||||||
|
|
||||||
DBusMenu* DBusMenuItem::menuHandle() const { return this->menu; }
|
DBusMenu* DBusMenuItem::menuHandle() const { return this->menu; }
|
||||||
QString DBusMenuItem::label() const { return this->mLabel; }
|
|
||||||
QString DBusMenuItem::cleanLabel() const { return this->mCleanLabel; }
|
|
||||||
bool DBusMenuItem::enabled() const { return this->mEnabled; }
|
bool DBusMenuItem::enabled() const { return this->mEnabled; }
|
||||||
|
QString DBusMenuItem::text() const { return this->mCleanLabel; }
|
||||||
|
|
||||||
QString DBusMenuItem::icon() const {
|
QString DBusMenuItem::icon() const {
|
||||||
if (!this->iconName.isEmpty()) {
|
if (!this->iconName.isEmpty()) {
|
||||||
|
@ -68,23 +68,20 @@ QString DBusMenuItem::icon() const {
|
||||||
} else return nullptr;
|
} else return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
ToggleButtonType::Enum DBusMenuItem::toggleType() const { return this->mToggleType; };
|
QsMenuButtonType::Enum DBusMenuItem::buttonType() const { return this->mButtonType; };
|
||||||
Qt::CheckState DBusMenuItem::checkState() const { return this->mCheckState; }
|
Qt::CheckState DBusMenuItem::checkState() const { return this->mCheckState; }
|
||||||
bool DBusMenuItem::isSeparator() const { return this->mSeparator; }
|
bool DBusMenuItem::isSeparator() const { return this->mSeparator; }
|
||||||
|
|
||||||
bool DBusMenuItem::isShowingChildren() const { return this->mShowChildren && this->childrenLoaded; }
|
bool DBusMenuItem::isShowingChildren() const { return this->mShowChildren && this->childrenLoaded; }
|
||||||
|
|
||||||
void DBusMenuItem::setShowChildren(bool showChildren) {
|
void DBusMenuItem::setShowChildrenRecursive(bool showChildren) {
|
||||||
if (showChildren == this->mShowChildren) return;
|
if (showChildren == this->mShowChildren) return;
|
||||||
this->mShowChildren = showChildren;
|
this->mShowChildren = showChildren;
|
||||||
this->childrenLoaded = false;
|
this->childrenLoaded = false;
|
||||||
|
|
||||||
if (showChildren) {
|
if (showChildren) {
|
||||||
this->menu->prepareToShow(this->id, true);
|
this->menu->prepareToShow(this->id, -1);
|
||||||
} else {
|
} else {
|
||||||
this->menu->sendEvent(this->id, "closed");
|
|
||||||
emit this->showingChildrenChanged();
|
|
||||||
|
|
||||||
if (!this->mChildren.isEmpty()) {
|
if (!this->mChildren.isEmpty()) {
|
||||||
for (auto child: this->mChildren) {
|
for (auto child: this->mChildren) {
|
||||||
this->menu->removeRecursive(child);
|
this->menu->removeRecursive(child);
|
||||||
|
@ -96,10 +93,15 @@ void DBusMenuItem::setShowChildren(bool showChildren) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DBusMenuItem::updateLayout() const {
|
||||||
|
if (!this->isShowingChildren()) return;
|
||||||
|
this->menu->updateLayout(this->id, -1);
|
||||||
|
}
|
||||||
|
|
||||||
bool DBusMenuItem::hasChildren() const { return this->displayChildren; }
|
bool DBusMenuItem::hasChildren() const { return this->displayChildren; }
|
||||||
|
|
||||||
QQmlListProperty<DBusMenuItem> DBusMenuItem::children() {
|
QQmlListProperty<QsMenuEntry> DBusMenuItem::children() {
|
||||||
return QQmlListProperty<DBusMenuItem>(
|
return QQmlListProperty<QsMenuEntry>(
|
||||||
this,
|
this,
|
||||||
nullptr,
|
nullptr,
|
||||||
&DBusMenuItem::childrenCount,
|
&DBusMenuItem::childrenCount,
|
||||||
|
@ -107,11 +109,11 @@ QQmlListProperty<DBusMenuItem> DBusMenuItem::children() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
qsizetype DBusMenuItem::childrenCount(QQmlListProperty<DBusMenuItem>* property) {
|
qsizetype DBusMenuItem::childrenCount(QQmlListProperty<QsMenuEntry>* property) {
|
||||||
return reinterpret_cast<DBusMenuItem*>(property->object)->enabledChildren.count(); // NOLINT
|
return reinterpret_cast<DBusMenuItem*>(property->object)->enabledChildren.count(); // NOLINT
|
||||||
}
|
}
|
||||||
|
|
||||||
DBusMenuItem* DBusMenuItem::childAt(QQmlListProperty<DBusMenuItem>* property, qsizetype index) {
|
QsMenuEntry* DBusMenuItem::childAt(QQmlListProperty<QsMenuEntry>* property, qsizetype index) {
|
||||||
auto* item = reinterpret_cast<DBusMenuItem*>(property->object); // NOLINT
|
auto* item = reinterpret_cast<DBusMenuItem*>(property->object); // NOLINT
|
||||||
return item->menu->items.value(item->enabledChildren.at(index));
|
return item->menu->items.value(item->enabledChildren.at(index));
|
||||||
}
|
}
|
||||||
|
@ -124,30 +126,30 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto originalLabel = this->mLabel;
|
auto originalText = this->mText;
|
||||||
//auto originalMnemonic = this->mnemonic;
|
//auto originalMnemonic = this->mnemonic;
|
||||||
auto originalEnabled = this->mEnabled;
|
auto originalEnabled = this->mEnabled;
|
||||||
auto originalVisible = this->visible;
|
auto originalVisible = this->visible;
|
||||||
auto originalIconName = this->iconName;
|
auto originalIconName = this->iconName;
|
||||||
auto* originalImage = this->image;
|
auto* originalImage = this->image;
|
||||||
auto originalIsSeparator = this->mSeparator;
|
auto originalIsSeparator = this->mSeparator;
|
||||||
auto originalToggleType = this->mToggleType;
|
auto originalButtonType = this->mButtonType;
|
||||||
auto originalToggleState = this->mCheckState;
|
auto originalToggleState = this->mCheckState;
|
||||||
auto originalDisplayChildren = this->displayChildren;
|
auto originalDisplayChildren = this->displayChildren;
|
||||||
|
|
||||||
auto label = properties.value("label");
|
auto label = properties.value("label");
|
||||||
if (label.canConvert<QString>()) {
|
if (label.canConvert<QString>()) {
|
||||||
auto text = label.value<QString>();
|
auto text = label.value<QString>();
|
||||||
this->mLabel = text;
|
this->mText = text;
|
||||||
this->mCleanLabel = text;
|
this->mCleanLabel = text;
|
||||||
//this->mnemonic = QChar();
|
//this->mnemonic = QChar();
|
||||||
|
|
||||||
for (auto i = 0; i < this->mLabel.length() - 1;) {
|
for (auto i = 0; i < this->mText.length() - 1;) {
|
||||||
if (this->mLabel.at(i) == '_') {
|
if (this->mText.at(i) == '_') {
|
||||||
//if (this->mnemonic == QChar()) this->mnemonic = this->mLabel.at(i + 1);
|
//if (this->mnemonic == QChar()) this->mnemonic = this->mLabel.at(i + 1);
|
||||||
this->mLabel.remove(i, 1);
|
this->mText.remove(i, 1);
|
||||||
this->mLabel.insert(i + 1, "</u>");
|
this->mText.insert(i + 1, "</u>");
|
||||||
this->mLabel.insert(i, "<u>");
|
this->mText.insert(i, "<u>");
|
||||||
i += 8;
|
i += 8;
|
||||||
} else {
|
} else {
|
||||||
i++;
|
i++;
|
||||||
|
@ -160,7 +162,7 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (removed.isEmpty() || removed.contains("label")) {
|
} else if (removed.isEmpty() || removed.contains("label")) {
|
||||||
this->mLabel = "";
|
this->mText = "";
|
||||||
//this->mnemonic = QChar();
|
//this->mnemonic = QChar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,15 +210,15 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
|
||||||
if (toggleType.canConvert<QString>()) {
|
if (toggleType.canConvert<QString>()) {
|
||||||
auto toggleTypeStr = toggleType.value<QString>();
|
auto toggleTypeStr = toggleType.value<QString>();
|
||||||
|
|
||||||
if (toggleTypeStr == "") this->mToggleType = ToggleButtonType::None;
|
if (toggleTypeStr == "") this->mButtonType = QsMenuButtonType::None;
|
||||||
else if (toggleTypeStr == "checkmark") this->mToggleType = ToggleButtonType::CheckBox;
|
else if (toggleTypeStr == "checkmark") this->mButtonType = QsMenuButtonType::CheckBox;
|
||||||
else if (toggleTypeStr == "radio") this->mToggleType = ToggleButtonType::RadioButton;
|
else if (toggleTypeStr == "radio") this->mButtonType = QsMenuButtonType::RadioButton;
|
||||||
else {
|
else {
|
||||||
qCWarning(logDbusMenu) << "Unrecognized toggle type" << toggleTypeStr << "for" << this;
|
qCWarning(logDbusMenu) << "Unrecognized toggle type" << toggleTypeStr << "for" << this;
|
||||||
this->mToggleType = ToggleButtonType::None;
|
this->mButtonType = QsMenuButtonType::None;
|
||||||
}
|
}
|
||||||
} else if (removed.isEmpty() || removed.contains("toggle-type")) {
|
} else if (removed.isEmpty() || removed.contains("toggle-type")) {
|
||||||
this->mToggleType = ToggleButtonType::None;
|
this->mButtonType = QsMenuButtonType::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto toggleState = properties.value("toggle-state");
|
auto toggleState = properties.value("toggle-state");
|
||||||
|
@ -227,7 +229,7 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
|
||||||
else if (toggleStateInt == 1) this->mCheckState = Qt::Checked;
|
else if (toggleStateInt == 1) this->mCheckState = Qt::Checked;
|
||||||
else this->mCheckState = Qt::PartiallyChecked;
|
else this->mCheckState = Qt::PartiallyChecked;
|
||||||
} else if (removed.isEmpty() || removed.contains("toggle-state")) {
|
} else if (removed.isEmpty() || removed.contains("toggle-state")) {
|
||||||
this->mCheckState = Qt::PartiallyChecked;
|
this->mCheckState = Qt::Unchecked;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto childrenDisplay = properties.value("children-display");
|
auto childrenDisplay = properties.value("children-display");
|
||||||
|
@ -245,14 +247,14 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
|
||||||
this->displayChildren = false;
|
this->displayChildren = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->mLabel != originalLabel) emit this->labelChanged();
|
if (this->mText != originalText) emit this->textChanged();
|
||||||
//if (this->mnemonic != originalMnemonic) emit this->labelChanged();
|
//if (this->mnemonic != originalMnemonic) emit this->labelChanged();
|
||||||
if (this->mEnabled != originalEnabled) emit this->enabledChanged();
|
if (this->mEnabled != originalEnabled) emit this->enabledChanged();
|
||||||
if (this->visible != originalVisible && this->parentMenu != nullptr)
|
if (this->visible != originalVisible && this->parentMenu != nullptr)
|
||||||
this->parentMenu->onChildrenUpdated();
|
this->parentMenu->onChildrenUpdated();
|
||||||
if (this->mToggleType != originalToggleType) emit this->toggleTypeChanged();
|
if (this->mButtonType != originalButtonType) emit this->buttonTypeChanged();
|
||||||
if (this->mCheckState != originalToggleState) emit this->checkStateChanged();
|
if (this->mCheckState != originalToggleState) emit this->checkStateChanged();
|
||||||
if (this->mSeparator != originalIsSeparator) emit this->separatorChanged();
|
if (this->mSeparator != originalIsSeparator) emit this->isSeparatorChanged();
|
||||||
if (this->displayChildren != originalDisplayChildren) emit this->hasChildrenChanged();
|
if (this->displayChildren != originalDisplayChildren) emit this->hasChildrenChanged();
|
||||||
|
|
||||||
if (this->iconName != originalIconName || this->image != originalImage) {
|
if (this->iconName != originalIconName || this->image != originalImage) {
|
||||||
|
@ -263,11 +265,11 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
|
||||||
emit this->iconChanged();
|
emit this->iconChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(logDbusMenu).nospace() << "Updated properties of " << this << " { label=" << this->mLabel
|
qCDebug(logDbusMenu).nospace() << "Updated properties of " << this << " { label=" << this->mText
|
||||||
<< ", enabled=" << this->mEnabled << ", visible=" << this->visible
|
<< ", enabled=" << this->mEnabled << ", visible=" << this->visible
|
||||||
<< ", iconName=" << this->iconName << ", iconData=" << this->image
|
<< ", iconName=" << this->iconName << ", iconData=" << this->image
|
||||||
<< ", separator=" << this->mSeparator
|
<< ", separator=" << this->mSeparator
|
||||||
<< ", toggleType=" << this->mToggleType
|
<< ", toggleType=" << this->mButtonType
|
||||||
<< ", toggleState=" << this->mCheckState
|
<< ", toggleState=" << this->mCheckState
|
||||||
<< ", displayChildren=" << this->displayChildren << " }";
|
<< ", displayChildren=" << this->displayChildren << " }";
|
||||||
}
|
}
|
||||||
|
@ -291,20 +293,7 @@ QDebug operator<<(QDebug debug, DBusMenuItem* item) {
|
||||||
|
|
||||||
auto saver = QDebugStateSaver(debug);
|
auto saver = QDebugStateSaver(debug);
|
||||||
debug.nospace() << "DBusMenuItem(" << static_cast<void*>(item) << ", id=" << item->id
|
debug.nospace() << "DBusMenuItem(" << static_cast<void*>(item) << ", id=" << item->id
|
||||||
<< ", label=" << item->mLabel << ", menu=" << item->menu << ")";
|
<< ", label=" << item->mText << ", menu=" << item->menu << ")";
|
||||||
return debug;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDebug operator<<(QDebug debug, const ToggleButtonType::Enum& toggleType) {
|
|
||||||
auto saver = QDebugStateSaver(debug);
|
|
||||||
debug.nospace() << "ToggleType::";
|
|
||||||
|
|
||||||
switch (toggleType) {
|
|
||||||
case ToggleButtonType::None: debug << "None"; break;
|
|
||||||
case ToggleButtonType::CheckBox: debug << "Checkbox"; break;
|
|
||||||
case ToggleButtonType::RadioButton: debug << "Radiobutton"; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return debug;
|
return debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,19 +323,18 @@ DBusMenu::DBusMenu(const QString& service, const QString& path, QObject* parent)
|
||||||
this->properties.updateAllViaGetAll();
|
this->properties.updateAllViaGetAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DBusMenu::prepareToShow(qint32 item, bool sendOpened) {
|
void DBusMenu::prepareToShow(qint32 item, qint32 depth) {
|
||||||
auto pending = this->interface->AboutToShow(item);
|
auto pending = this->interface->AboutToShow(item);
|
||||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||||
|
|
||||||
auto responseCallback = [this, item, sendOpened](QDBusPendingCallWatcher* call) {
|
auto responseCallback = [this, item, depth](QDBusPendingCallWatcher* call) {
|
||||||
const QDBusPendingReply<bool> reply = *call;
|
const QDBusPendingReply<bool> reply = *call;
|
||||||
if (reply.isError()) {
|
if (reply.isError()) {
|
||||||
qCWarning(logDbusMenu) << "Error in AboutToShow, but showing anyway for menu" << item << "of"
|
qCWarning(logDbusMenu) << "Error in AboutToShow, but showing anyway for menu" << item << "of"
|
||||||
<< this << reply.error();
|
<< this << reply.error();
|
||||||
}
|
}
|
||||||
|
|
||||||
this->updateLayout(item, 1);
|
this->updateLayout(item, depth);
|
||||||
if (sendOpened) this->sendEvent(item, "opened");
|
|
||||||
|
|
||||||
delete call;
|
delete call;
|
||||||
};
|
};
|
||||||
|
@ -385,6 +373,7 @@ void DBusMenu::updateLayoutRecursive(
|
||||||
// there is an actual nullptr in the map and not no entry
|
// there is an actual nullptr in the map and not no entry
|
||||||
if (this->items.contains(layout.id)) {
|
if (this->items.contains(layout.id)) {
|
||||||
item = new DBusMenuItem(layout.id, this, parent);
|
item = new DBusMenuItem(layout.id, this, parent);
|
||||||
|
item->mShowChildren = parent != nullptr && parent->mShowChildren;
|
||||||
this->items.insert(layout.id, item);
|
this->items.insert(layout.id, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -431,8 +420,9 @@ void DBusMenu::updateLayoutRecursive(
|
||||||
|
|
||||||
if (item->mShowChildren && !item->childrenLoaded) {
|
if (item->mShowChildren && !item->childrenLoaded) {
|
||||||
item->childrenLoaded = true;
|
item->childrenLoaded = true;
|
||||||
emit item->showingChildrenChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit item->layoutUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DBusMenu::removeRecursive(qint32 id) {
|
void DBusMenu::removeRecursive(qint32 id) {
|
||||||
|
|
|
@ -14,32 +14,18 @@
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
#include "../../core/imageprovider.hpp"
|
#include "../../core/imageprovider.hpp"
|
||||||
|
#include "../../core/qsmenu.hpp"
|
||||||
#include "../properties.hpp"
|
#include "../properties.hpp"
|
||||||
#include "dbus_menu_types.hpp"
|
#include "dbus_menu_types.hpp"
|
||||||
|
|
||||||
Q_DECLARE_LOGGING_CATEGORY(logDbusMenu);
|
Q_DECLARE_LOGGING_CATEGORY(logDbusMenu);
|
||||||
|
|
||||||
namespace ToggleButtonType { // NOLINT
|
|
||||||
Q_NAMESPACE;
|
|
||||||
QML_ELEMENT;
|
|
||||||
|
|
||||||
enum Enum {
|
|
||||||
/// This menu item does not have a checkbox or a radiobutton associated with it.
|
|
||||||
None = 0,
|
|
||||||
/// This menu item should draw a checkbox.
|
|
||||||
CheckBox = 1,
|
|
||||||
/// This menu item should draw a radiobutton.
|
|
||||||
RadioButton = 2,
|
|
||||||
};
|
|
||||||
Q_ENUM_NS(Enum);
|
|
||||||
|
|
||||||
} // namespace ToggleButtonType
|
|
||||||
|
|
||||||
class DBusMenuInterface;
|
class DBusMenuInterface;
|
||||||
|
|
||||||
namespace qs::dbus::dbusmenu {
|
namespace qs::dbus::dbusmenu {
|
||||||
|
|
||||||
QDebug operator<<(QDebug debug, const ToggleButtonType::Enum& toggleType);
|
// hack because docgen can't take namespaces in superclasses
|
||||||
|
using menu::QsMenuEntry;
|
||||||
|
|
||||||
class DBusMenu;
|
class DBusMenu;
|
||||||
class DBusMenuPngImage;
|
class DBusMenuPngImage;
|
||||||
|
@ -47,113 +33,56 @@ class DBusMenuPngImage;
|
||||||
///! Menu item shared by an external program.
|
///! Menu item shared by an external program.
|
||||||
/// Menu item shared by an external program via the
|
/// Menu item shared by an external program via the
|
||||||
/// [DBusMenu specification](https://github.com/AyatanaIndicators/libdbusmenu/blob/master/libdbusmenu-glib/dbus-menu.xml).
|
/// [DBusMenu specification](https://github.com/AyatanaIndicators/libdbusmenu/blob/master/libdbusmenu-glib/dbus-menu.xml).
|
||||||
class DBusMenuItem: public QObject {
|
class DBusMenuItem: public QsMenuEntry {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
// clang-format off
|
|
||||||
/// Handle to the root of this menu.
|
/// Handle to the root of this menu.
|
||||||
Q_PROPERTY(DBusMenu* menuHandle READ menuHandle CONSTANT);
|
Q_PROPERTY(DBusMenu* menuHandle READ menuHandle CONSTANT);
|
||||||
/// Text of the menu item, including hotkey markup.
|
|
||||||
Q_PROPERTY(QString label READ label NOTIFY labelChanged);
|
|
||||||
/// Text of the menu item without hotkey markup.
|
|
||||||
Q_PROPERTY(QString cleanLabel READ cleanLabel NOTIFY labelChanged);
|
|
||||||
/// If the menu item should be shown as enabled.
|
|
||||||
///
|
|
||||||
/// > [!INFO] Disabled menu items are often used as headers in addition
|
|
||||||
/// > to actual disabled entries.
|
|
||||||
Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged);
|
|
||||||
/// Url of the menu item's icon or `""` if it doesn't have one.
|
|
||||||
///
|
|
||||||
/// This can be passed to [Image.source](https://doc.qt.io/qt-6/qml-qtquick-image.html#source-prop)
|
|
||||||
/// as shown below.
|
|
||||||
///
|
|
||||||
/// ```qml
|
|
||||||
/// Image {
|
|
||||||
/// source: menuItem.icon
|
|
||||||
/// // To get the best image quality, set the image source size to the same size
|
|
||||||
/// // as the rendered image.
|
|
||||||
/// sourceSize.width: width
|
|
||||||
/// sourceSize.height: height
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged);
|
|
||||||
/// If this menu item has an associated checkbox or radiobutton.
|
|
||||||
///
|
|
||||||
/// > [!INFO] It is the responsibility of the remote application to update the state of
|
|
||||||
/// > checkboxes and radiobuttons via [checkState](#prop.checkState).
|
|
||||||
Q_PROPERTY(ToggleButtonType::Enum toggleType READ toggleType NOTIFY toggleTypeChanged);
|
|
||||||
/// The check state of the checkbox or radiobutton if applicable, as a
|
|
||||||
/// [Qt.CheckState](https://doc.qt.io/qt-6/qt.html#CheckState-enum).
|
|
||||||
Q_PROPERTY(Qt::CheckState checkState READ checkState NOTIFY checkStateChanged);
|
|
||||||
/// If this menu item should be rendered as a separator between other items.
|
|
||||||
///
|
|
||||||
/// No other properties have a meaningful value when `isSeparator` is true.
|
|
||||||
Q_PROPERTY(bool isSeparator READ isSeparator NOTIFY separatorChanged);
|
|
||||||
/// If this menu item reveals a submenu containing more items.
|
|
||||||
///
|
|
||||||
/// Any submenu items must be requested by setting [showChildren](#prop.showChildren).
|
|
||||||
Q_PROPERTY(bool hasChildren READ hasChildren NOTIFY hasChildrenChanged);
|
|
||||||
/// If submenu entries of this item should be shown.
|
|
||||||
///
|
|
||||||
/// When true, children of this menu item will be exposed via [children](#prop.children).
|
|
||||||
/// Setting this property will additionally send the `opened` and `closed` events to the
|
|
||||||
/// process that provided the menu.
|
|
||||||
Q_PROPERTY(bool showChildren READ isShowingChildren WRITE setShowChildren NOTIFY showingChildrenChanged);
|
|
||||||
/// Children of this menu item. Only populated when [showChildren](#prop.showChildren) is true.
|
|
||||||
///
|
|
||||||
/// > [!INFO] Use [hasChildren](#prop.hasChildren) to check if this item should reveal a submenu
|
|
||||||
/// > instead of checking if `children` is empty.
|
|
||||||
Q_PROPERTY(QQmlListProperty<DBusMenuItem> children READ children NOTIFY childrenChanged);
|
|
||||||
// clang-format on
|
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
QML_UNCREATABLE("DBusMenus can only be acquired from a DBusMenuHandle");
|
QML_UNCREATABLE("DBusMenus can only be acquired from a DBusMenuHandle");
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DBusMenuItem(qint32 id, DBusMenu* menu, DBusMenuItem* parentMenu);
|
explicit DBusMenuItem(qint32 id, DBusMenu* menu, DBusMenuItem* parentMenu);
|
||||||
|
|
||||||
/// Send a `clicked` event to the remote application for this menu item.
|
/// Refreshes the menu contents.
|
||||||
Q_INVOKABLE void click();
|
|
||||||
|
|
||||||
/// Send a `hovered` event to the remote application for this menu item.
|
|
||||||
///
|
///
|
||||||
/// Note: we are not aware of any programs that use this in any meaningful way.
|
/// Usually you shouldn't need to call this manually but some applications providing
|
||||||
Q_INVOKABLE void hover() const;
|
/// menus do not update them correctly. Call this if menus don't update their state.
|
||||||
|
///
|
||||||
|
/// The `layoutUpdated` signal will be sent when a response is received.
|
||||||
|
Q_INVOKABLE void updateLayout() const;
|
||||||
|
|
||||||
[[nodiscard]] DBusMenu* menuHandle() const;
|
[[nodiscard]] DBusMenu* menuHandle() const;
|
||||||
[[nodiscard]] QString label() const;
|
|
||||||
[[nodiscard]] QString cleanLabel() const;
|
[[nodiscard]] bool isSeparator() const override;
|
||||||
[[nodiscard]] bool enabled() const;
|
[[nodiscard]] bool enabled() const override;
|
||||||
[[nodiscard]] QString icon() const;
|
[[nodiscard]] QString text() const override;
|
||||||
[[nodiscard]] ToggleButtonType::Enum toggleType() const;
|
[[nodiscard]] QString icon() const override;
|
||||||
[[nodiscard]] Qt::CheckState checkState() const;
|
[[nodiscard]] menu::QsMenuButtonType::Enum buttonType() const override;
|
||||||
[[nodiscard]] bool isSeparator() const;
|
[[nodiscard]] Qt::CheckState checkState() const override;
|
||||||
[[nodiscard]] bool hasChildren() const;
|
[[nodiscard]] bool hasChildren() const override;
|
||||||
|
|
||||||
[[nodiscard]] bool isShowingChildren() const;
|
[[nodiscard]] bool isShowingChildren() const;
|
||||||
void setShowChildren(bool showChildren);
|
void setShowChildrenRecursive(bool showChildren);
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<DBusMenuItem> children();
|
[[nodiscard]] QQmlListProperty<menu::QsMenuEntry> children() override;
|
||||||
|
|
||||||
void updateProperties(const QVariantMap& properties, const QStringList& removed = {});
|
void updateProperties(const QVariantMap& properties, const QStringList& removed = {});
|
||||||
void onChildrenUpdated();
|
void onChildrenUpdated();
|
||||||
|
|
||||||
qint32 id = 0;
|
qint32 id = 0;
|
||||||
QString mLabel;
|
QString mText;
|
||||||
QVector<qint32> mChildren;
|
QVector<qint32> mChildren;
|
||||||
bool mShowChildren = false;
|
bool mShowChildren = false;
|
||||||
bool childrenLoaded = false;
|
bool childrenLoaded = false;
|
||||||
DBusMenu* menu = nullptr;
|
DBusMenu* menu = nullptr;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void labelChanged();
|
void layoutUpdated();
|
||||||
//void mnemonicChanged();
|
|
||||||
void enabledChanged();
|
private slots:
|
||||||
void iconChanged();
|
void sendOpened() const;
|
||||||
void separatorChanged();
|
void sendClosed() const;
|
||||||
void toggleTypeChanged();
|
void sendTriggered() const;
|
||||||
void checkStateChanged();
|
|
||||||
void hasChildrenChanged();
|
|
||||||
void showingChildrenChanged();
|
|
||||||
void childrenChanged();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString mCleanLabel;
|
QString mCleanLabel;
|
||||||
|
@ -163,14 +92,14 @@ private:
|
||||||
bool mSeparator = false;
|
bool mSeparator = false;
|
||||||
QString iconName;
|
QString iconName;
|
||||||
DBusMenuPngImage* image = nullptr;
|
DBusMenuPngImage* image = nullptr;
|
||||||
ToggleButtonType::Enum mToggleType = ToggleButtonType::None;
|
menu::QsMenuButtonType::Enum mButtonType = menu::QsMenuButtonType::None;
|
||||||
Qt::CheckState mCheckState = Qt::Checked;
|
Qt::CheckState mCheckState = Qt::Unchecked;
|
||||||
bool displayChildren = false;
|
bool displayChildren = false;
|
||||||
QVector<qint32> enabledChildren;
|
QVector<qint32> enabledChildren;
|
||||||
DBusMenuItem* parentMenu = nullptr;
|
DBusMenuItem* parentMenu = nullptr;
|
||||||
|
|
||||||
static qsizetype childrenCount(QQmlListProperty<DBusMenuItem>* property);
|
static qsizetype childrenCount(QQmlListProperty<menu::QsMenuEntry>* property);
|
||||||
static DBusMenuItem* childAt(QQmlListProperty<DBusMenuItem>* property, qsizetype index);
|
static menu::QsMenuEntry* childAt(QQmlListProperty<menu::QsMenuEntry>* property, qsizetype index);
|
||||||
};
|
};
|
||||||
|
|
||||||
QDebug operator<<(QDebug debug, DBusMenuItem* item);
|
QDebug operator<<(QDebug debug, DBusMenuItem* item);
|
||||||
|
@ -192,7 +121,7 @@ public:
|
||||||
dbus::DBusProperty<QString> status {this->properties, "Status"};
|
dbus::DBusProperty<QString> status {this->properties, "Status"};
|
||||||
dbus::DBusProperty<QStringList> iconThemePath {this->properties, "IconThemePath", {}, false};
|
dbus::DBusProperty<QStringList> iconThemePath {this->properties, "IconThemePath", {}, false};
|
||||||
|
|
||||||
void prepareToShow(qint32 item, bool sendOpened);
|
void prepareToShow(qint32 item, qint32 depth);
|
||||||
void updateLayout(qint32 parent, qint32 depth);
|
void updateLayout(qint32 parent, qint32 depth);
|
||||||
void removeRecursive(qint32 id);
|
void removeRecursive(qint32 id);
|
||||||
void sendEvent(qint32 item, const QString& event);
|
void sendEvent(qint32 item, const QString& event);
|
||||||
|
|
|
@ -75,6 +75,7 @@ StatusNotifierItem::StatusNotifierItem(const QString& address, QObject* parent)
|
||||||
QObject::connect(&this->overlayIconPixmaps, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
|
QObject::connect(&this->overlayIconPixmaps, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
|
||||||
|
|
||||||
QObject::connect(&this->properties, &DBusPropertyGroup::getAllFinished, this, &StatusNotifierItem::onGetAllFinished);
|
QObject::connect(&this->properties, &DBusPropertyGroup::getAllFinished, this, &StatusNotifierItem::onGetAllFinished);
|
||||||
|
QObject::connect(&this->menuPath, &AbstractDBusProperty::changed, this, &StatusNotifierItem::onMenuPathChanged);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
QObject::connect(this->item, &DBusStatusNotifierItem::NewStatus, this, [this](QString value) {
|
QObject::connect(this->item, &DBusStatusNotifierItem::NewStatus, this, [this](QString value) {
|
||||||
|
@ -230,13 +231,41 @@ void StatusNotifierItem::updateIcon() {
|
||||||
emit this->iconChanged();
|
emit this->iconChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
DBusMenu* StatusNotifierItem::createMenu() const {
|
DBusMenu* StatusNotifierItem::menu() const { return this->mMenu; }
|
||||||
auto path = this->menuPath.get().path();
|
|
||||||
if (!path.isEmpty()) {
|
void StatusNotifierItem::refMenu() {
|
||||||
return new DBusMenu(this->item->service(), this->menuPath.get().path());
|
this->menuRefcount++;
|
||||||
|
|
||||||
|
if (this->menuRefcount == 1) {
|
||||||
|
this->onMenuPathChanged();
|
||||||
|
} else {
|
||||||
|
// Refresh the layout when opening a menu in case a bad client isn't updating it
|
||||||
|
// and another ref is open somewhere.
|
||||||
|
this->mMenu->rootItem.updateLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatusNotifierItem::unrefMenu() {
|
||||||
|
this->menuRefcount--;
|
||||||
|
|
||||||
|
if (this->menuRefcount == 0) {
|
||||||
|
this->onMenuPathChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatusNotifierItem::onMenuPathChanged() {
|
||||||
|
if (this->mMenu) {
|
||||||
|
this->mMenu->deleteLater();
|
||||||
|
this->mMenu = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
if (this->menuRefcount > 0 && !this->menuPath.get().path().isEmpty()) {
|
||||||
|
this->mMenu = new DBusMenu(this->item->service(), this->menuPath.get().path());
|
||||||
|
this->mMenu->setParent(this);
|
||||||
|
this->mMenu->rootItem.setShowChildrenRecursive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit this->menuChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatusNotifierItem::onGetAllFinished() {
|
void StatusNotifierItem::onGetAllFinished() {
|
||||||
|
|
|
@ -41,7 +41,10 @@ public:
|
||||||
[[nodiscard]] bool isReady() const;
|
[[nodiscard]] bool isReady() const;
|
||||||
[[nodiscard]] QString iconId() const;
|
[[nodiscard]] QString iconId() const;
|
||||||
[[nodiscard]] QPixmap createPixmap(const QSize& size) const;
|
[[nodiscard]] QPixmap createPixmap(const QSize& size) const;
|
||||||
[[nodiscard]] qs::dbus::dbusmenu::DBusMenu* createMenu() const;
|
|
||||||
|
[[nodiscard]] qs::dbus::dbusmenu::DBusMenu* menu() const;
|
||||||
|
void refMenu();
|
||||||
|
void unrefMenu();
|
||||||
|
|
||||||
void activate();
|
void activate();
|
||||||
void secondaryActivate();
|
void secondaryActivate();
|
||||||
|
@ -70,16 +73,22 @@ public:
|
||||||
signals:
|
signals:
|
||||||
void iconChanged();
|
void iconChanged();
|
||||||
void ready();
|
void ready();
|
||||||
|
void menuChanged();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateIcon();
|
void updateIcon();
|
||||||
void onGetAllFinished();
|
void onGetAllFinished();
|
||||||
|
void onMenuPathChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void updateMenuState();
|
||||||
|
|
||||||
DBusStatusNotifierItem* item = nullptr;
|
DBusStatusNotifierItem* item = nullptr;
|
||||||
TrayImageHandle imageHandle {this};
|
TrayImageHandle imageHandle {this};
|
||||||
bool mReady = false;
|
bool mReady = false;
|
||||||
|
|
||||||
dbus::dbusmenu::DBusMenu* mMenu = nullptr;
|
dbus::dbusmenu::DBusMenu* mMenu = nullptr;
|
||||||
|
quint32 menuRefcount = 0;
|
||||||
|
|
||||||
// bumped to inhibit caching
|
// bumped to inhibit caching
|
||||||
quint32 iconIndex = 0;
|
quint32 iconIndex = 0;
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
#include "../../core/model.hpp"
|
#include "../../core/model.hpp"
|
||||||
|
#include "../../core/platformmenu.hpp"
|
||||||
#include "../../dbus/dbusmenu/dbusmenu.hpp"
|
#include "../../dbus/dbusmenu/dbusmenu.hpp"
|
||||||
#include "../../dbus/properties.hpp"
|
#include "../../dbus/properties.hpp"
|
||||||
#include "host.hpp"
|
#include "host.hpp"
|
||||||
|
@ -18,6 +19,7 @@
|
||||||
using namespace qs::dbus;
|
using namespace qs::dbus;
|
||||||
using namespace qs::dbus::dbusmenu;
|
using namespace qs::dbus::dbusmenu;
|
||||||
using namespace qs::service::sni;
|
using namespace qs::service::sni;
|
||||||
|
using namespace qs::menu::platform;
|
||||||
|
|
||||||
SystemTrayItem::SystemTrayItem(qs::service::sni::StatusNotifierItem* item, QObject* parent)
|
SystemTrayItem::SystemTrayItem(qs::service::sni::StatusNotifierItem* item, QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
|
@ -30,6 +32,7 @@ SystemTrayItem::SystemTrayItem(qs::service::sni::StatusNotifierItem* item, QObje
|
||||||
QObject::connect(this->item, &StatusNotifierItem::iconChanged, this, &SystemTrayItem::iconChanged);
|
QObject::connect(this->item, &StatusNotifierItem::iconChanged, this, &SystemTrayItem::iconChanged);
|
||||||
QObject::connect(&this->item->tooltip, &AbstractDBusProperty::changed, this, &SystemTrayItem::tooltipTitleChanged);
|
QObject::connect(&this->item->tooltip, &AbstractDBusProperty::changed, this, &SystemTrayItem::tooltipTitleChanged);
|
||||||
QObject::connect(&this->item->tooltip, &AbstractDBusProperty::changed, this, &SystemTrayItem::tooltipDescriptionChanged);
|
QObject::connect(&this->item->tooltip, &AbstractDBusProperty::changed, this, &SystemTrayItem::tooltipDescriptionChanged);
|
||||||
|
QObject::connect(&this->item->menuPath, &AbstractDBusProperty::changed, this, &SystemTrayItem::hasMenuChanged);
|
||||||
QObject::connect(&this->item->isMenu, &AbstractDBusProperty::changed, this, &SystemTrayItem::onlyMenuChanged);
|
QObject::connect(&this->item->isMenu, &AbstractDBusProperty::changed, this, &SystemTrayItem::onlyMenuChanged);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
@ -87,6 +90,11 @@ QString SystemTrayItem::tooltipDescription() const {
|
||||||
return this->item->tooltip.get().description;
|
return this->item->tooltip.get().description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SystemTrayItem::hasMenu() const {
|
||||||
|
if (this->item == nullptr) return false;
|
||||||
|
return !this->item->menuPath.get().path().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
bool SystemTrayItem::onlyMenu() const {
|
bool SystemTrayItem::onlyMenu() const {
|
||||||
if (this->item == nullptr) return false;
|
if (this->item == nullptr) return false;
|
||||||
return this->item->isMenu.get();
|
return this->item->isMenu.get();
|
||||||
|
@ -94,10 +102,27 @@ bool SystemTrayItem::onlyMenu() const {
|
||||||
|
|
||||||
void SystemTrayItem::activate() const { this->item->activate(); }
|
void SystemTrayItem::activate() const { this->item->activate(); }
|
||||||
void SystemTrayItem::secondaryActivate() const { this->item->secondaryActivate(); }
|
void SystemTrayItem::secondaryActivate() const { this->item->secondaryActivate(); }
|
||||||
|
|
||||||
void SystemTrayItem::scroll(qint32 delta, bool horizontal) const {
|
void SystemTrayItem::scroll(qint32 delta, bool horizontal) const {
|
||||||
this->item->scroll(delta, horizontal);
|
this->item->scroll(delta, horizontal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SystemTrayItem::display(QObject* parentWindow, qint32 relativeX, qint32 relativeY) {
|
||||||
|
this->item->refMenu();
|
||||||
|
auto* platform = new PlatformMenuEntry(&this->item->menu()->rootItem);
|
||||||
|
|
||||||
|
QObject::connect(&this->item->menu()->rootItem, &DBusMenuItem::layoutUpdated, platform, [=]() {
|
||||||
|
platform->relayout();
|
||||||
|
auto success = platform->display(parentWindow, relativeX, relativeY);
|
||||||
|
|
||||||
|
// calls destroy which also unrefs
|
||||||
|
if (!success) delete platform;
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(platform, &PlatformMenuEntry::closed, this, [=]() { platform->deleteLater(); });
|
||||||
|
QObject::connect(platform, &QObject::destroyed, this, [this]() { this->item->unrefMenu(); });
|
||||||
|
}
|
||||||
|
|
||||||
SystemTray::SystemTray(QObject* parent): QObject(parent) {
|
SystemTray::SystemTray(QObject* parent): QObject(parent) {
|
||||||
auto* host = StatusNotifierHost::instance();
|
auto* host = StatusNotifierHost::instance();
|
||||||
|
|
||||||
|
@ -129,46 +154,45 @@ ObjectModel<SystemTrayItem>* SystemTray::items() { return &this->mItems; }
|
||||||
|
|
||||||
SystemTrayItem* SystemTrayMenuWatcher::trayItem() const { return this->item; }
|
SystemTrayItem* SystemTrayMenuWatcher::trayItem() const { return this->item; }
|
||||||
|
|
||||||
|
SystemTrayMenuWatcher::~SystemTrayMenuWatcher() {
|
||||||
|
if (this->item != nullptr) {
|
||||||
|
this->item->item->unrefMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SystemTrayMenuWatcher::setTrayItem(SystemTrayItem* item) {
|
void SystemTrayMenuWatcher::setTrayItem(SystemTrayItem* item) {
|
||||||
if (item == this->item) return;
|
if (item == this->item) return;
|
||||||
|
|
||||||
if (this->item != nullptr) {
|
if (this->item != nullptr) {
|
||||||
|
this->item->item->unrefMenu();
|
||||||
QObject::disconnect(this->item, nullptr, this, nullptr);
|
QObject::disconnect(this->item, nullptr, this, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->item = item;
|
this->item = item;
|
||||||
|
|
||||||
if (item != nullptr) {
|
if (item != nullptr) {
|
||||||
|
this->item->item->refMenu();
|
||||||
|
|
||||||
QObject::connect(item, &QObject::destroyed, this, &SystemTrayMenuWatcher::onItemDestroyed);
|
QObject::connect(item, &QObject::destroyed, this, &SystemTrayMenuWatcher::onItemDestroyed);
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
&item->item->menuPath,
|
item->item,
|
||||||
&AbstractDBusProperty::changed,
|
&StatusNotifierItem::menuChanged,
|
||||||
this,
|
this,
|
||||||
&SystemTrayMenuWatcher::onMenuPathChanged
|
&SystemTrayMenuWatcher::menuChanged
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->onMenuPathChanged();
|
|
||||||
emit this->trayItemChanged();
|
emit this->trayItemChanged();
|
||||||
|
emit this->menuChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
DBusMenuItem* SystemTrayMenuWatcher::menu() const {
|
DBusMenuItem* SystemTrayMenuWatcher::menu() const {
|
||||||
if (this->mMenu == nullptr) return nullptr;
|
return this->item ? &this->item->item->menu()->rootItem : nullptr;
|
||||||
return &this->mMenu->rootItem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SystemTrayMenuWatcher::onItemDestroyed() {
|
void SystemTrayMenuWatcher::onItemDestroyed() {
|
||||||
this->item = nullptr;
|
this->item = nullptr;
|
||||||
this->onMenuPathChanged();
|
|
||||||
emit this->trayItemChanged();
|
emit this->trayItemChanged();
|
||||||
}
|
|
||||||
|
|
||||||
void SystemTrayMenuWatcher::onMenuPathChanged() {
|
|
||||||
if (this->mMenu != nullptr) {
|
|
||||||
this->mMenu->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
this->mMenu = this->item == nullptr ? nullptr : this->item->item->createMenu();
|
|
||||||
emit this->menuChanged();
|
emit this->menuChanged();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
|
#include <qtclasshelpermacros.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include "../../core/model.hpp"
|
#include "../../core/model.hpp"
|
||||||
|
@ -44,8 +45,6 @@ Q_ENUM_NS(Enum);
|
||||||
/// A system tray item, roughly conforming to the [kde/freedesktop spec]
|
/// A system tray item, roughly conforming to the [kde/freedesktop spec]
|
||||||
/// (there is no real spec, we just implemented whatever seemed to actually be used).
|
/// (there is no real spec, we just implemented whatever seemed to actually be used).
|
||||||
///
|
///
|
||||||
/// The associated context menu can be retrieved using a [SystemTrayMenuWatcher](../systemtraymenuwatcher).
|
|
||||||
///
|
|
||||||
/// [kde/freedesktop spec]: https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierItem/
|
/// [kde/freedesktop spec]: https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierItem/
|
||||||
class SystemTrayItem: public QObject {
|
class SystemTrayItem: public QObject {
|
||||||
using DBusMenuItem = qs::dbus::dbusmenu::DBusMenuItem;
|
using DBusMenuItem = qs::dbus::dbusmenu::DBusMenuItem;
|
||||||
|
@ -61,6 +60,9 @@ class SystemTrayItem: public QObject {
|
||||||
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged);
|
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged);
|
||||||
Q_PROPERTY(QString tooltipTitle READ tooltipTitle NOTIFY tooltipTitleChanged);
|
Q_PROPERTY(QString tooltipTitle READ tooltipTitle NOTIFY tooltipTitleChanged);
|
||||||
Q_PROPERTY(QString tooltipDescription READ tooltipDescription NOTIFY tooltipDescriptionChanged);
|
Q_PROPERTY(QString tooltipDescription READ tooltipDescription NOTIFY tooltipDescriptionChanged);
|
||||||
|
/// If this tray item has an associated menu accessible via `display`
|
||||||
|
/// or a [SystemTrayMenuWatcher](../systemtraymenuwatcher).
|
||||||
|
Q_PROPERTY(bool hasMenu READ hasMenu NOTIFY hasMenuChanged);
|
||||||
/// If this tray item only offers a menu and activation will do nothing.
|
/// If this tray item only offers a menu and activation will do nothing.
|
||||||
Q_PROPERTY(bool onlyMenu READ onlyMenu NOTIFY onlyMenuChanged);
|
Q_PROPERTY(bool onlyMenu READ onlyMenu NOTIFY onlyMenuChanged);
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
|
@ -78,6 +80,9 @@ public:
|
||||||
/// Scroll action, such as changing volume on a mixer.
|
/// Scroll action, such as changing volume on a mixer.
|
||||||
Q_INVOKABLE void scroll(qint32 delta, bool horizontal) const;
|
Q_INVOKABLE void scroll(qint32 delta, bool horizontal) const;
|
||||||
|
|
||||||
|
/// Display a platform menu at the given location relative to the parent window.
|
||||||
|
Q_INVOKABLE void display(QObject* parentWindow, qint32 relativeX, qint32 relativeY);
|
||||||
|
|
||||||
[[nodiscard]] QString id() const;
|
[[nodiscard]] QString id() const;
|
||||||
[[nodiscard]] QString title() const;
|
[[nodiscard]] QString title() const;
|
||||||
[[nodiscard]] SystemTrayStatus::Enum status() const;
|
[[nodiscard]] SystemTrayStatus::Enum status() const;
|
||||||
|
@ -85,6 +90,7 @@ public:
|
||||||
[[nodiscard]] QString icon() const;
|
[[nodiscard]] QString icon() const;
|
||||||
[[nodiscard]] QString tooltipTitle() const;
|
[[nodiscard]] QString tooltipTitle() const;
|
||||||
[[nodiscard]] QString tooltipDescription() const;
|
[[nodiscard]] QString tooltipDescription() const;
|
||||||
|
[[nodiscard]] bool hasMenu() const;
|
||||||
[[nodiscard]] bool onlyMenu() const;
|
[[nodiscard]] bool onlyMenu() const;
|
||||||
|
|
||||||
qs::service::sni::StatusNotifierItem* item = nullptr;
|
qs::service::sni::StatusNotifierItem* item = nullptr;
|
||||||
|
@ -97,6 +103,7 @@ signals:
|
||||||
void iconChanged();
|
void iconChanged();
|
||||||
void tooltipTitleChanged();
|
void tooltipTitleChanged();
|
||||||
void tooltipDescriptionChanged();
|
void tooltipDescriptionChanged();
|
||||||
|
void hasMenuChanged();
|
||||||
void onlyMenuChanged();
|
void onlyMenuChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -141,6 +148,8 @@ class SystemTrayMenuWatcher: public QObject {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SystemTrayMenuWatcher(QObject* parent = nullptr): QObject(parent) {}
|
explicit SystemTrayMenuWatcher(QObject* parent = nullptr): QObject(parent) {}
|
||||||
|
~SystemTrayMenuWatcher() override;
|
||||||
|
Q_DISABLE_COPY_MOVE(SystemTrayMenuWatcher);
|
||||||
|
|
||||||
[[nodiscard]] SystemTrayItem* trayItem() const;
|
[[nodiscard]] SystemTrayItem* trayItem() const;
|
||||||
void setTrayItem(SystemTrayItem* item);
|
void setTrayItem(SystemTrayItem* item);
|
||||||
|
@ -148,14 +157,12 @@ public:
|
||||||
[[nodiscard]] DBusMenuItem* menu() const;
|
[[nodiscard]] DBusMenuItem* menu() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void menuChanged();
|
|
||||||
void trayItemChanged();
|
void trayItemChanged();
|
||||||
|
void menuChanged();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onItemDestroyed();
|
void onItemDestroyed();
|
||||||
void onMenuPathChanged();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SystemTrayItem* item = nullptr;
|
SystemTrayItem* item = nullptr;
|
||||||
DBusMenu* mMenu = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -50,7 +50,9 @@ endfunction()
|
||||||
|
|
||||||
# -----
|
# -----
|
||||||
|
|
||||||
qt_add_library(quickshell-wayland STATIC)
|
qt_add_library(quickshell-wayland STATIC
|
||||||
|
platformmenu.cpp
|
||||||
|
)
|
||||||
|
|
||||||
# required to make sure the constructor is linked
|
# required to make sure the constructor is linked
|
||||||
add_library(quickshell-wayland-init OBJECT init.cpp)
|
add_library(quickshell-wayland-init OBJECT init.cpp)
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <qtenvironmentvariables.h>
|
#include <qtenvironmentvariables.h>
|
||||||
|
|
||||||
#include "../core/plugin.hpp"
|
#include "../core/plugin.hpp"
|
||||||
|
#include "platformmenu.hpp"
|
||||||
|
|
||||||
#ifdef QS_WAYLAND_WLR_LAYERSHELL
|
#ifdef QS_WAYLAND_WLR_LAYERSHELL
|
||||||
#include "wlr_layershell.hpp"
|
#include "wlr_layershell.hpp"
|
||||||
|
@ -26,6 +27,8 @@ class WaylandPlugin: public QuickshellPlugin {
|
||||||
return isWayland;
|
return isWayland;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void init() override { installPlatformMenuHook(); }
|
||||||
|
|
||||||
void registerTypes() override {
|
void registerTypes() override {
|
||||||
#ifdef QS_WAYLAND_WLR_LAYERSHELL
|
#ifdef QS_WAYLAND_WLR_LAYERSHELL
|
||||||
qmlRegisterType<WaylandPanelInterface>("Quickshell._WaylandOverlay", 1, 0, "PanelWindow");
|
qmlRegisterType<WaylandPanelInterface>("Quickshell._WaylandOverlay", 1, 0, "PanelWindow");
|
||||||
|
|
32
src/wayland/platformmenu.cpp
Normal file
32
src/wayland/platformmenu.cpp
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#include "platformmenu.hpp"
|
||||||
|
|
||||||
|
#include <private/qwayland-xdg-shell.h>
|
||||||
|
#include <qmargins.h>
|
||||||
|
#include <qwindow.h>
|
||||||
|
|
||||||
|
#include "../core/platformmenu.hpp"
|
||||||
|
|
||||||
|
using namespace qs::menu::platform;
|
||||||
|
using namespace QtWayland;
|
||||||
|
|
||||||
|
// fixes positioning of submenus when hitting screen edges
|
||||||
|
void platformMenuHook(PlatformMenuQMenu* menu) {
|
||||||
|
auto* window = menu->windowHandle();
|
||||||
|
|
||||||
|
auto constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_flip_x
|
||||||
|
| QtWayland::xdg_positioner::constraint_adjustment_flip_y;
|
||||||
|
|
||||||
|
window->setProperty("_q_waylandPopupConstraintAdjustment", constraintAdjustment);
|
||||||
|
|
||||||
|
if (auto* containingMenu = menu->containingMenu) {
|
||||||
|
auto geom = containingMenu->actionGeometry(menu->menuAction());
|
||||||
|
|
||||||
|
// use the first action to find the offsets relative to the containing window
|
||||||
|
auto baseGeom = containingMenu->actionGeometry(containingMenu->actions().first());
|
||||||
|
geom += QMargins(0, baseGeom.top(), 0, baseGeom.top());
|
||||||
|
|
||||||
|
window->setProperty("_q_waylandPopupAnchorRect", geom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void installPlatformMenuHook() { PlatformMenuEntry::registerCreationHook(&platformMenuHook); }
|
3
src/wayland/platformmenu.hpp
Normal file
3
src/wayland/platformmenu.hpp
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void installPlatformMenuHook();
|
Loading…
Reference in a new issue