mirror of
https://github.com/overte-org/overte.git
synced 2025-06-21 16:41:34 +02:00
272 lines
9.3 KiB
C++
272 lines
9.3 KiB
C++
//
|
|
// VrMenu.cpp
|
|
//
|
|
// Created by Bradley Austin Davis on 2015/04/21
|
|
// Copyright 2015 High Fidelity, Inc.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
|
|
#include "VrMenu.h"
|
|
|
|
#include <QtQml>
|
|
#include <QMenuBar>
|
|
#include <QDebug>
|
|
|
|
#include "OffscreenUi.h"
|
|
#include "ui/Logging.h"
|
|
|
|
|
|
|
|
MenuUserData::MenuUserData(QAction* action, QObject* qmlObject, QObject* qmlParent) {
|
|
_action = action;
|
|
_qml = qmlObject;
|
|
_qmlParent = qmlParent;
|
|
|
|
action->setProperty(USER_DATA, QVariant::fromValue(this));
|
|
qmlObject->setProperty(USER_DATA, QVariant::fromValue(this));
|
|
qmlObject->setObjectName(uuid.toString());
|
|
// Make sure we can find it again in the future
|
|
updateQmlItemFromAction();
|
|
_changedConnection = QObject::connect(action, &QAction::changed, [=] {
|
|
updateQmlItemFromAction();
|
|
});
|
|
_shutdownConnection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [=] {
|
|
QObject::disconnect(_changedConnection);
|
|
});
|
|
|
|
class ExclusionGroupSetter : public QObject {
|
|
public:
|
|
ExclusionGroupSetter(QObject* from, QObject* to, QObject* qmlParent) : QObject(from), _from(from), _to(to), _qmlParent(qmlParent) {
|
|
_from->installEventFilter(this);
|
|
}
|
|
|
|
~ExclusionGroupSetter() {
|
|
_from->removeEventFilter(this);
|
|
}
|
|
protected:
|
|
virtual bool eventFilter(QObject* o, QEvent* e) override {
|
|
if (e->type() == QEvent::DynamicPropertyChange) {
|
|
QDynamicPropertyChangeEvent* dpc = static_cast<QDynamicPropertyChangeEvent*>(e);
|
|
if (dpc->propertyName() == "exclusionGroup") {
|
|
// unfortunately Qt doesn't support passing dynamic properties between C++ / QML, so we have to use this ugly helper function
|
|
QMetaObject::invokeMethod(_qmlParent,
|
|
"addExclusionGroup",
|
|
Qt::DirectConnection,
|
|
Q_ARG(QVariant, QVariant::fromValue(_to)),
|
|
Q_ARG(QVariant, _from->property(dpc->propertyName())));
|
|
}
|
|
}
|
|
|
|
return QObject::eventFilter(o, e);
|
|
}
|
|
|
|
private:
|
|
QObject* _from;
|
|
QObject* _to;
|
|
QObject* _qmlParent;
|
|
};
|
|
|
|
new ExclusionGroupSetter(action, qmlObject, qmlParent);
|
|
}
|
|
|
|
MenuUserData::~MenuUserData() {
|
|
QObject::disconnect(_changedConnection);
|
|
QObject::disconnect(_shutdownConnection);
|
|
_action->setProperty(USER_DATA, QVariant());
|
|
_qml->setProperty(USER_DATA, QVariant());
|
|
}
|
|
|
|
void MenuUserData::updateQmlItemFromAction() {
|
|
_qml->setProperty("checkable", _action->isCheckable());
|
|
_qml->setProperty("enabled", _action->isEnabled());
|
|
QString text = _action->text();
|
|
_qml->setProperty("text", text);
|
|
_qml->setProperty("shortcut", _action->shortcut().toString());
|
|
_qml->setProperty("checked", _action->isChecked());
|
|
_qml->setProperty("visible", _action->isVisible());
|
|
}
|
|
|
|
void MenuUserData::clear() {
|
|
_qml->setProperty("checkable", 0);
|
|
_qml->setProperty("enabled", 0);
|
|
_qml->setProperty("text", 0);
|
|
_qml->setProperty("shortcut", 0);
|
|
_qml->setProperty("checked", 0);
|
|
_qml->setProperty("visible", 0);
|
|
|
|
_action->setProperty(USER_DATA, QVariant());
|
|
_qml->setProperty(USER_DATA, QVariant());
|
|
}
|
|
|
|
|
|
|
|
bool MenuUserData::hasData(QAction* object) {
|
|
if (!object) {
|
|
qWarning() << "Attempted to fetch MenuUserData for null object";
|
|
return false;
|
|
}
|
|
return (nullptr != object->property(USER_DATA).value<MenuUserData*>());
|
|
}
|
|
|
|
MenuUserData* MenuUserData::forObject(QAction* object) {
|
|
if (!object) {
|
|
qWarning() << "Attempted to fetch MenuUserData for null object";
|
|
return nullptr;
|
|
}
|
|
auto result = object->property(USER_DATA).value<MenuUserData*>();
|
|
if (!result) {
|
|
qWarning() << "Unable to find MenuUserData for object " << object;
|
|
if (auto action = dynamic_cast<QAction*>(object)) {
|
|
qWarning() << action->text();
|
|
} else if (auto menu = dynamic_cast<QMenu*>(object)) {
|
|
qWarning() << menu->title();
|
|
}
|
|
return nullptr;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
VrMenu::VrMenu(OffscreenUi* parent) : QObject(parent) {
|
|
_rootMenu = parent->getRootItem()->findChild<QObject*>("rootMenu");
|
|
parent->getSurfaceContext()->setContextProperty("rootMenu", _rootMenu);
|
|
}
|
|
|
|
QObject* VrMenu::findMenuObject(const QString& menuOption) {
|
|
if (menuOption.isEmpty()) {
|
|
return _rootMenu;
|
|
}
|
|
QObject* result = _rootMenu->findChild<QObject*>(menuOption);
|
|
return result;
|
|
}
|
|
|
|
|
|
void VrMenu::addMenu(QMenu* menu) {
|
|
Q_ASSERT(!MenuUserData::hasData(menu->menuAction()));
|
|
QObject* parent = menu->parent();
|
|
QObject* qmlParent = nullptr;
|
|
QMenu* parentMenu = dynamic_cast<QMenu*>(parent);
|
|
if (parentMenu && menu->menuAction()) {
|
|
MenuUserData* userData = MenuUserData::forObject(parentMenu->menuAction());
|
|
if (!userData) {
|
|
return;
|
|
}
|
|
qmlParent = findMenuObject(userData->uuid.toString());
|
|
} else if (dynamic_cast<QMenuBar*>(parent)) {
|
|
qmlParent = _rootMenu;
|
|
} else {
|
|
Q_ASSERT(false);
|
|
}
|
|
QVariant returnedValue;
|
|
bool invokeResult = QMetaObject::invokeMethod(qmlParent, "addMenu", Qt::DirectConnection,
|
|
Q_RETURN_ARG(QVariant, returnedValue),
|
|
Q_ARG(QVariant, QVariant::fromValue(menu->title())));
|
|
Q_ASSERT(invokeResult);
|
|
Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x
|
|
QObject* result = returnedValue.value<QObject*>();
|
|
Q_ASSERT(result);
|
|
if (!result) {
|
|
qWarning() << "Unable to create QML menu for widget menu: " << menu->title();
|
|
return;
|
|
}
|
|
|
|
// Bind the QML and Widget together
|
|
new MenuUserData(menu->menuAction(), result, qmlParent);
|
|
}
|
|
|
|
void bindActionToQmlAction(QObject* qmlAction, QAction* action, QObject* qmlParent) {
|
|
auto text = action->text();
|
|
if (text == "Login") {
|
|
qDebug(uiLogging) << "Login action " << action;
|
|
}
|
|
|
|
new MenuUserData(action, qmlAction, qmlParent);
|
|
QObject::connect(action, &QAction::toggled, [=](bool checked) {
|
|
qmlAction->setProperty("checked", checked);
|
|
});
|
|
QObject::connect(qmlAction, SIGNAL(triggered()), action, SLOT(trigger()));
|
|
}
|
|
|
|
class QQuickMenuItem1;
|
|
|
|
void VrMenu::addAction(QMenu* menu, QAction* action) {
|
|
Q_ASSERT(!MenuUserData::hasData(action));
|
|
|
|
Q_ASSERT(MenuUserData::hasData(menu->menuAction()));
|
|
MenuUserData* userData = MenuUserData::forObject(menu->menuAction());
|
|
if (!userData) {
|
|
return;
|
|
}
|
|
QObject* menuQml = findMenuObject(userData->uuid.toString());
|
|
Q_ASSERT(menuQml);
|
|
QQuickMenuItem1* returnedValue { nullptr };
|
|
bool invokeResult = QMetaObject::invokeMethod(menuQml, "addItem", Qt::DirectConnection,
|
|
Q_RETURN_ARG(QQuickMenuItem1*, returnedValue),
|
|
Q_ARG(QString, action->text()));
|
|
|
|
Q_ASSERT(invokeResult);
|
|
Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x
|
|
QObject* result = reinterpret_cast<QObject*>(returnedValue); // returnedValue.value<QObject*>();
|
|
Q_ASSERT(result);
|
|
// Bind the QML and Widget together
|
|
bindActionToQmlAction(result, action, _rootMenu);
|
|
}
|
|
|
|
void VrMenu::addSeparator(QMenu* menu) {
|
|
Q_ASSERT(MenuUserData::hasData(menu->menuAction()));
|
|
MenuUserData* userData = MenuUserData::forObject(menu->menuAction());
|
|
if (!userData) {
|
|
return;
|
|
}
|
|
QObject* menuQml = findMenuObject(userData->uuid.toString());
|
|
Q_ASSERT(menuQml);
|
|
|
|
bool invokeResult = QMetaObject::invokeMethod(menuQml, "addSeparator", Qt::DirectConnection);
|
|
Q_ASSERT(invokeResult);
|
|
Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x
|
|
}
|
|
|
|
void VrMenu::insertAction(QAction* before, QAction* action) {
|
|
QObject* beforeQml{ nullptr };
|
|
{
|
|
MenuUserData* beforeUserData = MenuUserData::forObject(before);
|
|
Q_ASSERT(beforeUserData);
|
|
if (!beforeUserData) {
|
|
return;
|
|
}
|
|
beforeQml = findMenuObject(beforeUserData->uuid.toString());
|
|
}
|
|
QObject* menu = beforeQml->parent();
|
|
QQuickMenuItem1* returnedValue { nullptr };
|
|
// FIXME this needs to find the index of the beforeQml item and call insertItem(int, object)
|
|
bool invokeResult = QMetaObject::invokeMethod(menu, "addItem", Qt::DirectConnection,
|
|
Q_RETURN_ARG(QQuickMenuItem1*, returnedValue),
|
|
Q_ARG(QString, action->text()));
|
|
Q_ASSERT(invokeResult);
|
|
QObject* result = reinterpret_cast<QObject*>(returnedValue); // returnedValue.value<QObject*>();
|
|
Q_ASSERT(result);
|
|
if ( result ) {
|
|
bindActionToQmlAction(result, action, _rootMenu);
|
|
} else {
|
|
qWarning() << "Failed to find addItem() method in object " << menu << ". Not inserting action " << action;
|
|
}
|
|
}
|
|
|
|
class QQuickMenuBase;
|
|
class QQuickMenu1;
|
|
|
|
void VrMenu::removeAction(QAction* action) {
|
|
if (!action) {
|
|
qWarning("Attempted to remove invalid menu action");
|
|
return;
|
|
}
|
|
MenuUserData* userData = MenuUserData::forObject(action);
|
|
if (!userData) {
|
|
qWarning("Attempted to remove menu action with no found QML object");
|
|
return;
|
|
}
|
|
|
|
userData->clear();
|
|
delete userData;
|
|
}
|