272 lines · 9.3 KB · C++
RawDownload
////  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, [=, this] {        updateQmlItemFromAction();    });    _shutdownConnection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [=, this] {        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;}