ksuprynowiczFix invalid referencecd8ad232 years agoHistory
404 lines · 14.0 KB · C++
RawDownload
////  Created by Bradley Austin Davis on 2015-12-15//  Copyright 2015 High Fidelity, Inc.//  Copyright 2023 Overte e.V.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html//  SPDX-License-Identifier: Apache-2.0// #include "QmlWindowClass.h" #include <mutex> #include <QtCore/QThread> #include <QtQuick/QQuickItem>#include <QtQml/QQmlContext>#include <QtQml/QQmlEngine> #include <QtWebSockets/QWebSocketServer>#include <QtWebSockets/QWebSocket>#include <QtCore/QJsonDocument>#include <QtCore/QJsonObject> #include <shared/QtHelpers.h>#include "OffscreenUi.h"#include "ui/types/HFWebEngineProfile.h"#include "ui/types/FileTypeProfile.h"#include <ScriptContext.h>#include <ScriptEngine.h>#include <ScriptManager.h>#include <ScriptValue.h> static const char* const SOURCE_PROPERTY = "source";static const char* const TITLE_PROPERTY = "title";static const char* const EVENT_BRIDGE_PROPERTY = "eventBridge";static const char* const WIDTH_PROPERTY = "width";static const char* const HEIGHT_PROPERTY = "height";static const char* const VISIBILE_PROPERTY = "visible";static const uvec2 MAX_QML_WINDOW_SIZE { 1280, 720 };static const uvec2 MIN_QML_WINDOW_SIZE { 120, 80 }; QVariantMap QmlWindowClass::parseArguments(ScriptContext* context) {    const auto argumentCount = context->argumentCount();    QVariantMap properties;    if (argumentCount > 1) {        if (!context->argument(0).isUndefined()) {            properties[TITLE_PROPERTY] = context->argument(0).toString();        }        if (!context->argument(1).isUndefined()) {            properties[SOURCE_PROPERTY] = context->argument(1).toString();        }        if (context->argument(2).isNumber()) {            properties[WIDTH_PROPERTY] = context->argument(2).toInt32();        }        if (context->argument(3).isNumber()) {            properties[HEIGHT_PROPERTY] = context->argument(3).toInt32();        }    } else {        properties = context->argument(0).toVariant().toMap();    }     QUrl url { properties[SOURCE_PROPERTY].toString() };    // If the passed URL doesn't correspond to a known scheme, assume it's a local file path    if (url.scheme() != "http" && url.scheme() != "https" && url.scheme() != "file" && url.scheme() != "about" &&            url.scheme() != "atp" && url.scheme() != "qrc") {        properties[SOURCE_PROPERTY] = QUrl::fromLocalFile(url.toString()).toString();    }     return properties;}   // Method called by Qt scripts to create a new web window in the overlayScriptValue QmlWindowClass::internal_constructor(ScriptContext* context, ScriptEngine* engine, bool restricted) {    auto properties = parseArguments(context);    QmlWindowClass* retVal = new QmlWindowClass(restricted);    Q_ASSERT(retVal);    if (QThread::currentThread() != qApp->thread()) {        retVal->moveToThread(qApp->thread());        BLOCKING_INVOKE_METHOD(retVal, "initQml", Q_ARG(QVariantMap, properties));    } else {        retVal->initQml(properties);    }    auto manager = engine->manager();    connect(manager, &ScriptManager::destroyed, retVal, &QmlWindowClass::deleteLater);    return engine->newQObject(retVal);} QmlWindowClass::QmlWindowClass(bool restricted) : _restricted(restricted) { } /*@jsdoc * Properties used to initialize an {@link OverlayWindow} or {@link OverlayWebWindow}. * @typedef {object} OverlayWindow.Properties * @property {string} [title="WebWindow] - The window title. * @property {string} [source] - The source of the QML or HTML to display. * @property {number} [width=0] - The width of the window interior, in pixels. * @property {number} [height=0] - The height of the window interior, in pixels. * @property {boolean} [visible=true] - <code>true</codE> if the window should be visible, <code>false</code> if it shouldn't. */void QmlWindowClass::initQml(QVariantMap properties) {#ifndef DISABLE_QML    auto offscreenUi = DependencyManager::get<OffscreenUi>();    _source = properties[SOURCE_PROPERTY].toString();     auto objectInitLambda = [&](QQmlContext* context, QObject* object) {        _qmlWindow = object;        context->setContextProperty(EVENT_BRIDGE_PROPERTY, this);        context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);        context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership);        if (properties.contains(TITLE_PROPERTY)) {            object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString());        }        if (properties.contains(HEIGHT_PROPERTY) && properties.contains(WIDTH_PROPERTY)) {            uvec2 requestedSize { properties[WIDTH_PROPERTY].toUInt(), properties[HEIGHT_PROPERTY].toUInt() };            requestedSize = glm::clamp(requestedSize, MIN_QML_WINDOW_SIZE, MAX_QML_WINDOW_SIZE);            asQuickItem()->setSize(QSize(requestedSize.x, requestedSize.y));        }         bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool();        object->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible);        object->setProperty(SOURCE_PROPERTY, _source);         const QMetaObject *metaObject = _qmlWindow->metaObject();        // Forward messages received from QML on to the script        connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);        connect(_qmlWindow, SIGNAL(visibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection);         if (metaObject->indexOfSignal("resized") >= 0)            connect(_qmlWindow, SIGNAL(resized(QSizeF)), this, SIGNAL(resized(QSizeF)), Qt::QueuedConnection);        if (metaObject->indexOfSignal("moved") >= 0)            connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection);        connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection);    };     auto contextInitLambda = [&](QQmlContext* context) {#if !defined(Q_OS_ANDROID)        // If the restricted flag is on, override the FileTypeProfile and HFWebEngineProfile objects in the         // QML surface root context with local ones        ContextAwareProfile::restrictContext(context, _restricted);        FileTypeProfile::registerWithContext(context);        HFWebEngineProfile::registerWithContext(context);#endif    };     // Build the event bridge and wrapper on the main thread    offscreenUi->loadInNewContext(qmlSource(), objectInitLambda, contextInitLambda);     Q_ASSERT(_qmlWindow);    Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow.data()));#endif} void QmlWindowClass::qmlToScript(const QVariant& message) {    if (message.canConvert<QJSValue>()) {        emit fromQml(qvariant_cast<QJSValue>(message).toVariant());    } else if (message.canConvert<QString>()) {        emit fromQml(message.toString());    } else {        qWarning() << "Unsupported message type " << message;    }} void QmlWindowClass::sendToQml(const QVariant& message) {    // Forward messages received from the script on to QML    QQuickItem *quickItem = asQuickItem();    if (quickItem) {        QMetaObject::invokeMethod(quickItem, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));    } else {        qDebug() << "QmlWindowClass::sendToQml: asQuickItem() returned NULL";    }} void QmlWindowClass::clearDebugWindow() {    QQuickItem *quickItem = asQuickItem();    if (quickItem) {        QMetaObject::invokeMethod(quickItem, "clearDebugWindow", Qt::QueuedConnection);    } else {        qDebug() << "QmlWindowClass::clearDebugWindow: asQuickItem() returned NULL";    }} void QmlWindowClass::emitScriptEvent(const QVariant& scriptMessage) {    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage));    } else {        emit scriptEventReceived(scriptMessage);    }} void QmlWindowClass::emitWebEvent(const QVariant& webMessage) {    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage));    } else {        // Special case to handle raising and lowering the virtual keyboard.        const QString RAISE_KEYBOARD = "_RAISE_KEYBOARD";        const QString RAISE_KEYBOARD_NUMERIC = "_RAISE_KEYBOARD_NUMERIC";        const QString LOWER_KEYBOARD = "_LOWER_KEYBOARD";        QString messageString = webMessage.type() == QVariant::String ? webMessage.toString() : "";        if (messageString.left(RAISE_KEYBOARD.length()) == RAISE_KEYBOARD) {            QQuickItem *quickItem = asQuickItem();            if (quickItem) {                setKeyboardRaised(quickItem, true, messageString == RAISE_KEYBOARD_NUMERIC);            } else {                qDebug() << "QmlWindowClass::emitWebEvent: asQuickItem() returned NULL";            }        } else if (messageString == LOWER_KEYBOARD) {            QQuickItem *quickItem = asQuickItem();            if (quickItem) {                setKeyboardRaised(quickItem, false);            } else {                qDebug() << "QmlWindowClass::emitWebEvent: asQuickItem() returned NULL";            }        } else {            emit webEventReceived(webMessage);        }    }} void QmlWindowClass::setKeyboardRaised(QObject* object, bool raised, bool numeric) {    if (!object) {        return;    }     QQuickItem* item = dynamic_cast<QQuickItem*>(object);    while (item) {        if (item->property("keyboardRaised").isValid()) {            if (item->property("punctuationMode").isValid()) {                item->setProperty("punctuationMode", QVariant(numeric));            }            item->setProperty("keyboardRaised", QVariant(raised));            return;        }        item = dynamic_cast<QQuickItem*>(item->parentItem());    }} QmlWindowClass::~QmlWindowClass() {    close();} QQuickItem* QmlWindowClass::asQuickItem() const {    return _qmlWindow.isNull() ? nullptr : dynamic_cast<QQuickItem*>(_qmlWindow.data());} void QmlWindowClass::setVisible(bool visible) {    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "setVisible", Q_ARG(bool, visible));        return;    }     QQuickItem* targetWindow = asQuickItem();    //TODO: all asQuickItem() need to be guarded like this    if (targetWindow) {        targetWindow->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible);    } else {        qDebug() << "QmlWindowClass::setVisible: asQuickItem() returned NULL";    }} bool QmlWindowClass::isVisible() {    if (QThread::currentThread() != thread()) {        bool result = false;        BLOCKING_INVOKE_METHOD(this, "isVisible", Q_RETURN_ARG(bool, result));        return result;    }     // The tool window itself has special logic based on whether any tabs are enabled    if (_qmlWindow.isNull()) {        return false;    }     QQuickItem *quickItem = asQuickItem();    if (quickItem) {        return quickItem->isVisible();    } else {        qDebug() << "QmlWindowClass::isVisible: asQuickItem() returned NULL";        return false;    }} glm::vec2 QmlWindowClass::getPosition() {    if (QThread::currentThread() != thread()) {        vec2 result;        BLOCKING_INVOKE_METHOD(this, "getPosition", Q_RETURN_ARG(glm::vec2, result));        return result;    }     if (_qmlWindow.isNull()) {        return {};    }     QQuickItem *quickItem = asQuickItem();    if (quickItem) {        return toGlm(quickItem->position());    } else {        qDebug() << "QmlWindowClass::getPosition: asQuickItem() returned NULL";        return glm::vec2(0.0f, 0.0f);    }} void QmlWindowClass::setPosition(const glm::vec2& position) {    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "setPosition", Q_ARG(const glm::vec2&, position));        return;    }     QQuickItem *quickItem = asQuickItem();    if (quickItem) {        quickItem->setPosition(QPointF(position.x, position.y));    }} void QmlWindowClass::setPosition(int x, int y) {    setPosition(glm::vec2(x, y));} // FIXME move to GLM helpersglm::vec2 toGlm(const QSizeF& size) {    return glm::vec2(size.width(), size.height());} glm::vec2 QmlWindowClass::getSize() {    if (QThread::currentThread() != thread()) {        vec2 result;        BLOCKING_INVOKE_METHOD(this, "getSize", Q_RETURN_ARG(glm::vec2, result));        return result;    }     if (_qmlWindow.isNull()) {        return {};    }    QQuickItem* targetWindow = asQuickItem();    if (targetWindow) {        return vec2(targetWindow->width(), targetWindow->height());    } else {        qDebug() << "QmlWindowClass::getSize: asQuickItem() returned NULL";        return vec2(0.0f, 0.0f);    }} void QmlWindowClass::setSize(const glm::vec2& size) {    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "setSize", Q_ARG(const glm::vec2&, size));        return;    }     QQuickItem *quickItem = asQuickItem();    if (quickItem) {        quickItem->setSize(QSizeF(size.x, size.y));    }} void QmlWindowClass::setSize(int width, int height) {    setSize(glm::vec2(width, height));} void QmlWindowClass::setTitle(const QString& title) {    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "setTitle", Q_ARG(QString, title));        return;    }     QQuickItem *quickItem = asQuickItem();    if (quickItem) {        quickItem->setProperty(TITLE_PROPERTY, title);    }} void QmlWindowClass::close() {    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "close");        return;    }     if (_qmlWindow) {        _qmlWindow->deleteLater();    }    _qmlWindow = nullptr;} void QmlWindowClass::hasMoved(QVector2D position) {    emit moved(glm::vec2(position.x(), position.y()));} void QmlWindowClass::hasClosed() {    emit closed();} void QmlWindowClass::raise() {    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "raise");        return;    }     QQuickItem *quickItem = asQuickItem();    if (quickItem) {        QMetaObject::invokeMethod(quickItem, "raise", Qt::DirectConnection);    }}