forked from quickshell/quickshell
service/tray!: redesign menus / dbusmenu and add native menu support
Reworks dbusmenu menus to be displayable with a system context menu. Breaks the entire DBusMenu api.
This commit is contained in:
parent
c31bbea837
commit
ec362637b8
18 changed files with 898 additions and 191 deletions
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
|
Loading…
Add table
Add a link
Reference in a new issue