howard-stearnsfix reticle bugsf8391f010 years agoHistory
634 lines · 20.7 KB · C++
RawDownload
////  OffscreenUi.cpp//  interface/src/render-utils////  Created by Bradley Austin Davis on 2015-04-04//  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 "OffscreenUi.h" #include <QtCore/QVariant>#include <QtGui/QGuiApplication>#include <QtQuick/QQuickWindow>#include <QtQml/QtQml> #include <gl/GLHelpers.h> #include <AbstractUriHandler.h>#include <AccountManager.h> #include "FileDialogHelper.h"#include "VrMenu.h"  // Needs to match the constants in resources/qml/Global.jsclass OffscreenFlags : public QObject {    Q_OBJECT    Q_PROPERTY(bool navigationFocused READ isNavigationFocused WRITE setNavigationFocused NOTIFY navigationFocusedChanged) public:     OffscreenFlags(QObject* parent = nullptr) : QObject(parent) {}    bool isNavigationFocused() const { return _navigationFocused; }    void setNavigationFocused(bool focused) {        if (_navigationFocused != focused) {            _navigationFocused = focused;            emit navigationFocusedChanged();        }    } signals:    void navigationFocusedChanged(); private:    bool _navigationFocused { false };}; QString fixupHifiUrl(const QString& urlString) {	static const QString ACCESS_TOKEN_PARAMETER = "access_token";	static const QString ALLOWED_HOST = "metaverse.highfidelity.com";    QUrl url(urlString);	QUrlQuery query(url);	if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) {	    auto accountManager = DependencyManager::get<AccountManager>();	    query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager->getAccountInfo().getAccessToken().token);	    url.setQuery(query.query());	    return url.toString();	}    return urlString;} class UrlHandler : public QObject {    Q_OBJECTpublic:    Q_INVOKABLE bool canHandleUrl(const QString& url) {        static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);        return handler->canAcceptURL(url);    }     Q_INVOKABLE bool handleUrl(const QString& url) {        static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);        return handler->acceptURL(url);    }        // FIXME hack for authentication, remove when we migrate to Qt 5.6    Q_INVOKABLE QString fixupUrl(const QString& originalUrl) {        return fixupHifiUrl(originalUrl);    }}; static OffscreenFlags* offscreenFlags { nullptr }; // This hack allows the QML UI to work with keys that are also bound as // shortcuts at the application level.  However, it seems as though the // bound actions are still getting triggered.  At least for backspace.  // Not sure why.//// However, the problem may go away once we switch to the new menu system,// so I think it's OK for the time being.bool OffscreenUi::shouldSwallowShortcut(QEvent* event) {    Q_ASSERT(event->type() == QEvent::ShortcutOverride);    QObject* focusObject = getWindow()->focusObject();    if (focusObject != getWindow() && focusObject != getRootItem()) {        //qDebug() << "Swallowed shortcut " << static_cast<QKeyEvent*>(event)->key();        event->accept();        return true;    }    return false;} OffscreenUi::OffscreenUi() {} void OffscreenUi::create(QOpenGLContext* context) {    OffscreenQmlSurface::create(context);    auto rootContext = getRootContext();     rootContext->setContextProperty("OffscreenUi", this);    rootContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());    rootContext->setContextProperty("urlHandler", new UrlHandler());    rootContext->setContextProperty("fileDialogHelper", new FileDialogHelper());} void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {    emit showDesktop();    QQuickItem* item = getRootItem()->findChild<QQuickItem*>(name);    // First load?    if (!item) {        load(url, f);        item = getRootItem()->findChild<QQuickItem*>(name);    }     if (item) {        QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).write(true);    }} void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {    QQuickItem* item = getRootItem()->findChild<QQuickItem*>(name);    if (!item) {        show(url, name, f);        return;    }     // Already loaded, so just flip the bit    QQmlProperty shownProperty(item, OFFSCREEN_VISIBILITY_PROPERTY);    shownProperty.write(!shownProperty.read().toBool());} void OffscreenUi::hide(const QString& name) {    QQuickItem* item = getRootItem()->findChild<QQuickItem*>(name);    if (item) {        QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).write(false);    }} class ModalDialogListener : public QObject {    Q_OBJECT    friend class OffscreenUi; protected:    ModalDialogListener(QQuickItem* dialog) : _dialog(dialog) {        if (!dialog) {            _finished = true;            return;        }        connect(_dialog, SIGNAL(destroyed()), this, SLOT(onDestroyed()));    }     ~ModalDialogListener() {        if (_dialog) {            disconnect(_dialog);        }    }     virtual QVariant waitForResult() {        while (!_finished) {            QCoreApplication::processEvents();        }        return _result;    } protected slots:    void onDestroyed() {        _finished = true;        disconnect(_dialog);        _dialog = nullptr;    } protected:    QQuickItem* _dialog;    bool _finished { false };    QVariant _result;}; class MessageBoxListener : public ModalDialogListener {    Q_OBJECT     friend class OffscreenUi;    MessageBoxListener(QQuickItem* messageBox) : ModalDialogListener(messageBox) {        if (_finished) {            return;        }        connect(_dialog, SIGNAL(selected(int)), this, SLOT(onSelected(int)));    }     virtual QMessageBox::StandardButton waitForButtonResult() {        ModalDialogListener::waitForResult();        return static_cast<QMessageBox::StandardButton>(_result.toInt());    } private slots:    void onSelected(int button) {        _result = button;        _finished = true;        disconnect(_dialog);    }}; QQuickItem* OffscreenUi::createMessageBox(Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {    QVariantMap map;    map.insert("title", title);    map.insert("text", text);    map.insert("icon", icon);    map.insert("buttons", buttons.operator int());    map.insert("defaultButton", defaultButton);    QVariant result;    bool invokeResult = QMetaObject::invokeMethod(_desktop, "messageBox",        Q_RETURN_ARG(QVariant, result),        Q_ARG(QVariant, QVariant::fromValue(map)));     if (!invokeResult) {        qWarning() << "Failed to create message box";        return nullptr;    }    return qvariant_cast<QQuickItem*>(result);} int OffscreenUi::waitForMessageBoxResult(QQuickItem* messageBox) {    if (!messageBox) {        return QMessageBox::NoButton;    }        return MessageBoxListener(messageBox).waitForButtonResult();}  QMessageBox::StandardButton OffscreenUi::messageBox(Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {    if (QThread::currentThread() != thread()) {        QMessageBox::StandardButton result = QMessageBox::StandardButton::NoButton;        QMetaObject::invokeMethod(this, "messageBox", Qt::BlockingQueuedConnection,            Q_RETURN_ARG(QMessageBox::StandardButton, result),            Q_ARG(Icon, icon),            Q_ARG(QString, title),            Q_ARG(QString, text),            Q_ARG(QMessageBox::StandardButtons, buttons),            Q_ARG(QMessageBox::StandardButton, defaultButton));        return result;    }     return static_cast<QMessageBox::StandardButton>(waitForMessageBoxResult(createMessageBox(icon, title, text, buttons, defaultButton)));} QMessageBox::StandardButton OffscreenUi::critical(const QString& title, const QString& text,    QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {    return DependencyManager::get<OffscreenUi>()->messageBox(OffscreenUi::Icon::ICON_CRITICAL, title, text, buttons, defaultButton);}QMessageBox::StandardButton OffscreenUi::information(const QString& title, const QString& text,    QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {    return DependencyManager::get<OffscreenUi>()->messageBox(OffscreenUi::Icon::ICON_INFORMATION, title, text, buttons, defaultButton);}QMessageBox::StandardButton OffscreenUi::question(const QString& title, const QString& text,    QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {    return DependencyManager::get<OffscreenUi>()->messageBox(OffscreenUi::Icon::ICON_QUESTION, title, text, buttons, defaultButton);}QMessageBox::StandardButton OffscreenUi::warning(const QString& title, const QString& text,    QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {    return DependencyManager::get<OffscreenUi>()->messageBox(OffscreenUi::Icon::ICON_WARNING, title, text, buttons, defaultButton);}   class InputDialogListener : public ModalDialogListener {    Q_OBJECT     friend class OffscreenUi;    InputDialogListener(QQuickItem* queryBox) : ModalDialogListener(queryBox) {        if (_finished) {            return;        }        connect(_dialog, SIGNAL(selected(QVariant)), this, SLOT(onSelected(const QVariant&)));    } private slots:    void onSelected(const QVariant& result) {        _result = result;        _finished = true;        disconnect(_dialog);    }}; QString OffscreenUi::getText(const Icon icon, const QString& title, const QString& label, const QString& text, bool* ok) {    if (ok) { *ok = false; }    QVariant result = DependencyManager::get<OffscreenUi>()->inputDialog(icon, title, label, text).toString();    if (ok && result.isValid()) {        *ok = true;    }    return result.toString();} QString OffscreenUi::getItem(const Icon icon, const QString& title, const QString& label, const QStringList& items,    int current, bool editable, bool* ok) {     if (ok) {         *ok = false;     }     auto offscreenUi = DependencyManager::get<OffscreenUi>();    auto inputDialog = offscreenUi->createInputDialog(icon, title, label, current);    if (!inputDialog) {        return QString();    }    inputDialog->setProperty("items", items);    inputDialog->setProperty("editable", editable);     QVariant result = offscreenUi->waitForInputDialogResult(inputDialog);    if (!result.isValid()) {        return QString();    }     if (ok) {        *ok = true;    }    return result.toString();} QVariant OffscreenUi::inputDialog(const Icon icon, const QString& title, const QString& label, const QVariant& current) {    if (QThread::currentThread() != thread()) {        QVariant result;        QMetaObject::invokeMethod(this, "inputDialog", Qt::BlockingQueuedConnection,            Q_RETURN_ARG(QVariant, result),            Q_ARG(Icon, icon),            Q_ARG(QString, title),            Q_ARG(QString, label),            Q_ARG(QVariant, current));        return result;    }     return waitForInputDialogResult(createInputDialog(icon, title, label, current));} void OffscreenUi::togglePinned() {    bool invokeResult = QMetaObject::invokeMethod(_desktop, "togglePinned");    if (!invokeResult) {        qWarning() << "Failed to toggle window visibility";    }} void OffscreenUi::setPinned(bool pinned) {    bool invokeResult = QMetaObject::invokeMethod(_desktop, "setPinned", Q_ARG(QVariant, pinned));    if (!invokeResult) {        qWarning() << "Failed to set window visibility";    }} void OffscreenUi::addMenuInitializer(std::function<void(VrMenu*)> f) {    if (!_vrMenu) {        _queuedMenuInitializers.push_back(f);        return;    }    f(_vrMenu);} QQuickItem* OffscreenUi::createInputDialog(const Icon icon, const QString& title, const QString& label,    const QVariant& current) {     QVariantMap map;    map.insert("title", title);    map.insert("icon", icon);    map.insert("label", label);    map.insert("current", current);    QVariant result;    bool invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog",        Q_RETURN_ARG(QVariant, result),        Q_ARG(QVariant, QVariant::fromValue(map)));     if (!invokeResult) {        qWarning() << "Failed to create message box";        return nullptr;    }     return qvariant_cast<QQuickItem*>(result);} QVariant OffscreenUi::waitForInputDialogResult(QQuickItem* inputDialog) {    if (!inputDialog) {        return QVariant();    }    return InputDialogListener(inputDialog).waitForResult();} bool OffscreenUi::navigationFocused() {    return offscreenFlags->isNavigationFocused();} void OffscreenUi::setNavigationFocused(bool focused) {    offscreenFlags->setNavigationFocused(focused);} // FIXME HACK....// This hack is an attempt to work around the 'offscreen UI can't gain keyboard focus' bug// https://app.asana.com/0/27650181942747/83176475832393// The problem seems related to https://bugreports.qt.io/browse/QTBUG-50309 //// The workaround seems to be to give some other window (same process or another process doesn't seem to matter)// focus and then put focus back on the interface main window.  //// If I could reliably reproduce this bug I could eventually track down what state change is occuring // during the process of the main window losing and then gaining focus, but failing that, here's a // brute force way of triggering that state change at application start in a way that should be nearly// imperceptible to the user.class KeyboardFocusHack : public QObject {    Q_OBJECTpublic:    KeyboardFocusHack() {        Q_ASSERT(_mainWindow);        QTimer::singleShot(200, [=] {            _hackWindow = new QWindow();            _hackWindow->setFlags(Qt::FramelessWindowHint);            _hackWindow->setGeometry(_mainWindow->x(), _mainWindow->y(), 10, 10);            _hackWindow->show();            _hackWindow->requestActivate();            QTimer::singleShot(200, [=] {                _hackWindow->hide();                _hackWindow->deleteLater();                _hackWindow = nullptr;                _mainWindow->requestActivate();                this->deleteLater();            });        });    } private:        static QWindow* findMainWindow() {        auto windows = qApp->topLevelWindows();        QWindow* result = nullptr;        for (auto window : windows) {            QVariant isMainWindow = window->property("MainWindow");            if (!qobject_cast<QQuickWindow*>(window)) {                result = window;                break;            }        }        return result;    }     QWindow* const _mainWindow { findMainWindow() };    QWindow* _hackWindow { nullptr };}; void OffscreenUi::createDesktop(const QUrl& url) {    if (_desktop) {        qDebug() << "Desktop already created";        return;    } #ifdef DEBUG    getRootContext()->setContextProperty("DebugQML", QVariant(true));#else     getRootContext()->setContextProperty("DebugQML", QVariant(false));#endif     _desktop = dynamic_cast<QQuickItem*>(load(url));    Q_ASSERT(_desktop);    getRootContext()->setContextProperty("desktop", _desktop);     _toolWindow = _desktop->findChild<QQuickItem*>("ToolWindow");     _vrMenu = new VrMenu(this);    for (const auto& menuInitializer : _queuedMenuInitializers) {        menuInitializer(_vrMenu);    }     new KeyboardFocusHack();     connect(_desktop, SIGNAL(showDesktop()), this, SIGNAL(showDesktop()));} QQuickItem* OffscreenUi::getDesktop() {    return _desktop;} QQuickItem* OffscreenUi::getToolWindow() {    return _toolWindow;} void OffscreenUi::unfocusWindows() {    bool invokeResult = QMetaObject::invokeMethod(_desktop, "unfocusWindows");    Q_ASSERT(invokeResult);} void OffscreenUi::toggleMenu(const QPoint& screenPosition) { // caller should already have mapped using getReticlePosition    emit showDesktop(); // we really only want to do this if you're showing the menu, but for now this works    QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, screenPosition));}  

Lines 1–500 of 634.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t