Support QML and Web content in overlay windows

This commit is contained in:
Brad Davis 2015-12-22 20:39:33 -08:00
parent a5c19c04e5
commit 0901e3aae1
11 changed files with 498 additions and 273 deletions

View file

@ -0,0 +1 @@
set(GRAPHVIZ_EXTERNAL_LIBS FALSE)

View file

@ -140,8 +140,13 @@ var importingSVOTextOverlay = Overlays.addOverlay("text", {
});
var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace";
var marketplaceWindow = new OverlayWebWindow('Marketplace', "about:blank", 900, 700, false);
marketplaceWindow.setVisible(false);
var marketplaceWindow = new OverlayWebWindow({
title: 'Marketplace',
source: "about:blank",
width: 900,
height: 700,
visible: false
});
function showMarketplace(marketplaceID) {
var url = MARKETPLACE_URL;

40
examples/tests/qmlTest.js Normal file
View file

@ -0,0 +1,40 @@
print("Launching web window");
qmlWindow = new OverlayWindow({
title: 'Test Qml',
source: "https://s3.amazonaws.com/DreamingContent/qml/content.qml",
height: 240,
width: 320,
toolWindow: false,
visible: true
});
//qmlWindow.eventBridge.webEventReceived.connect(function(data) {
// print("JS Side event received: " + data);
//});
//
//var titles = ["A", "B", "C"];
//var titleIndex = 0;
//
//Script.setInterval(function() {
// qmlWindow.eventBridge.emitScriptEvent("JS Event sent");
// var size = qmlWindow.size;
// var position = qmlWindow.position;
// print("Window visible: " + qmlWindow.visible)
// if (qmlWindow.visible) {
// print("Window size: " + size.x + "x" + size.y)
// print("Window pos: " + position.x + "x" + position.y)
// qmlWindow.setVisible(false);
// } else {
// qmlWindow.setVisible(true);
// qmlWindow.setTitle(titles[titleIndex]);
// qmlWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100);
// titleIndex += 1;
// titleIndex %= titles.length;
// }
//}, 2 * 1000);
//
//Script.setTimeout(function() {
// print("Closing script");
// qmlWindow.close();
// Script.stop();
//}, 15 * 1000)

View file

@ -1,6 +1,7 @@
print("Launching web window");
webWindow = new OverlayWebWindow('Test Event Bridge', "file:///C:/Users/bdavis/Git/hifi/examples/html/qmlWebTest.html", 320, 240, false);
var htmlUrl = Script.resolvePath("..//html/qmlWebTest.html")
webWindow = new OverlayWebWindow('Test Event Bridge', htmlUrl, 320, 240, false);
print("JS Side window: " + webWindow);
print("JS Side bridge: " + webWindow.eventBridge);
webWindow.eventBridge.webEventReceived.connect(function(data) {

View file

@ -1,9 +1,6 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtWebEngine 1.1
import QtWebChannel 1.0
import QtWebSockets 1.0
import "controls"
import "styles"
@ -13,6 +10,7 @@ VrDialog {
HifiConstants { id: hifi }
title: "WebWindow"
resizable: true
enabled: false
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false
contentImplicitWidth: clientArea.implicitWidth
@ -24,18 +22,18 @@ VrDialog {
function stop() {
webview.stop();
}
Component.onCompleted: {
enabled = true
console.log("Web Window Created " + root);
// Ensure the JS from the web-engine makes it to our logging
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
});
// Required to support clicking on "hifi://" links
webview.loadingChanged.connect(handleWebviewLoading)
}
// Required to support clicking on "hifi://" links
function handleWebviewLoading(loadRequest) {
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
var newUrl = loadRequest.url.toString();
@ -56,6 +54,7 @@ VrDialog {
id: webview
url: root.source
anchors.fill: parent
onUrlChanged: {
var currentUrl = url.toString();
var newUrl = urlFixer.fixupUrl(currentUrl);
@ -63,6 +62,7 @@ VrDialog {
url = newUrl;
}
}
profile: WebEngineProfile {
id: webviewProfile
httpUserAgent: "Mozilla/5.0 (HighFidelityInterface)"

View file

@ -0,0 +1,44 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtWebChannel 1.0
import QtWebSockets 1.0
import "controls"
import "styles"
VrDialog {
id: root
HifiConstants { id: hifi }
title: "QmlWindow"
resizable: true
enabled: false
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false
contentImplicitWidth: clientArea.implicitWidth
contentImplicitHeight: clientArea.implicitHeight
property url source: ""
onEnabledChanged: {
if (enabled) {
clientArea.forceActiveFocus()
}
}
Item {
id: clientArea
implicitHeight: 600
implicitWidth: 800
x: root.clientX
y: root.clientY
width: root.clientWidth
height: root.clientHeight
Loader {
id: pageLoader
objectName: "Loader"
source: root.source
anchors.fill: parent
}
} // item
} // dialog

View file

@ -4111,6 +4111,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1);
scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor);
scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor);
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Stats", Stats::getInstance());

View file

@ -8,83 +8,27 @@
#include "QmlWebWindowClass.h"
#include <mutex>
#include <QtCore/QCoreApplication>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QUrl>
#include <QtCore/QUrlQuery>
#include <QtCore/QThread>
#include <QtQml/QQmlContext>
#include <QtScript/QScriptContext>
#include <QtScript/QScriptEngine>
#include <QtWebChannel/QWebChannel>
#include <QtWebSockets/QWebSocketServer>
#include <QtWebSockets/QWebSocket>
#include <QtQuick/QQuickItem>
#include <AbstractUriHandler.h>
#include <AccountManager.h>
#include <AddressManager.h>
#include <DependencyManager.h>
#include "OffscreenUi.h"
QWebSocketServer* QmlWebWindowClass::_webChannelServer { nullptr };
static QWebChannel webChannel;
static const uint16_t WEB_CHANNEL_PORT = 51016;
static std::atomic<int> nextWindowId;
static const char* const URL_PROPERTY = "source";
static const char* const TITLE_PROPERTY = "title";
static const QRegExp HIFI_URL_PATTERN { "^hifi://" };
void QmlScriptEventBridge::emitWebEvent(const QString& data) {
QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data));
}
void QmlScriptEventBridge::emitScriptEvent(const QString& data) {
QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection,
Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data));
}
class QmlWebTransport : public QWebChannelAbstractTransport {
Q_OBJECT
public:
QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) {
// Translate from the websocket layer to the webchannel layer
connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) {
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error);
if (error.error || !document.isObject()) {
qWarning() << "Unable to parse incoming JSON message" << message;
return;
}
emit messageReceived(document.object(), this);
});
}
virtual void sendMessage(const QJsonObject &message) override {
// Translate from the webchannel layer to the websocket layer
_webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
}
private:
QWebSocket* const _webSocket;
};
void QmlWebWindowClass::setupServer() {
if (!_webChannelServer) {
_webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode);
if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) {
qFatal("Failed to open web socket server.");
}
QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] {
webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection()));
});
}
}
class UrlFixer : public QObject {
Q_OBJECT
public:
@ -110,38 +54,14 @@ static UrlFixer URL_FIXER;
// Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
QmlWebWindowClass* retVal { nullptr };
const QString title = context->argument(0).toString();
QString url = context->argument(1).toString();
if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:")) {
url = QUrl::fromLocalFile(url).toString();
}
const int width = std::max(100, std::min(1280, context->argument(2).toInt32()));;
const int height = std::max(100, std::min(720, context->argument(3).toInt32()));;
// Build the event bridge and wrapper on the main thread
QMetaObject::invokeMethod(DependencyManager::get<OffscreenUi>().data(), "load", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, "QmlWebWindow.qml"),
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
setupServer();
retVal = new QmlWebWindowClass(object);
webChannel.registerObject(url.toLower(), retVal);
return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine,
[&](QQmlContext* context, QObject* object) {
context->setContextProperty("urlFixer", &URL_FIXER);
retVal->setTitle(title);
retVal->setURL(url);
retVal->setSize(width, height);
}));
connect(engine, &QScriptEngine::destroyed, retVal, &QmlWebWindowClass::deleteLater);
return engine->newQObject(retVal);
return new QmlWebWindowClass(object);
});
}
QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow)
: _windowId(++nextWindowId), _qmlWindow(qmlWindow)
{
qDebug() << "Created window with ID " << _windowId;
Q_ASSERT(_qmlWindow);
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow));
QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) {
QObject::connect(_qmlWindow, SIGNAL(navigating(QString)), this, SLOT(handleNavigation(QString)));
}
@ -165,80 +85,6 @@ void QmlWebWindowClass::handleNavigation(const QString& url) {
}
}
void QmlWebWindowClass::setVisible(bool visible) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
return;
}
auto qmlWindow = asQuickItem();
if (qmlWindow->isEnabled() != visible) {
qmlWindow->setEnabled(visible);
emit visibilityChanged(visible);
}
}
QQuickItem* QmlWebWindowClass::asQuickItem() const {
return dynamic_cast<QQuickItem*>(_qmlWindow);
}
bool QmlWebWindowClass::isVisible() const {
if (QThread::currentThread() != thread()) {
bool result;
QMetaObject::invokeMethod(const_cast<QmlWebWindowClass*>(this), "isVisible", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result));
return result;
}
return asQuickItem()->isEnabled();
}
glm::vec2 QmlWebWindowClass::getPosition() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
QMetaObject::invokeMethod(const_cast<QmlWebWindowClass*>(this), "getPosition", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result));
return result;
}
return glm::vec2(asQuickItem()->x(), asQuickItem()->y());
}
void QmlWebWindowClass::setPosition(const glm::vec2& position) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setPosition", Qt::QueuedConnection, Q_ARG(glm::vec2, position));
return;
}
asQuickItem()->setPosition(QPointF(position.x, position.y));
}
void QmlWebWindowClass::setPosition(int x, int y) {
setPosition(glm::vec2(x, y));
}
glm::vec2 QmlWebWindowClass::getSize() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
QMetaObject::invokeMethod(const_cast<QmlWebWindowClass*>(this), "getSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result));
return result;
}
return glm::vec2(asQuickItem()->width(), asQuickItem()->height());
}
void QmlWebWindowClass::setSize(const glm::vec2& size) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setSize", Qt::QueuedConnection, Q_ARG(glm::vec2, size));
}
asQuickItem()->setSize(QSizeF(size.x, size.y));
}
void QmlWebWindowClass::setSize(int width, int height) {
setSize(glm::vec2(width, height));
}
QString QmlWebWindowClass::getURL() const {
if (QThread::currentThread() != thread()) {
QString result;
@ -256,29 +102,4 @@ void QmlWebWindowClass::setURL(const QString& urlString) {
_qmlWindow->setProperty(URL_PROPERTY, urlString);
}
void QmlWebWindowClass::setTitle(const QString& title) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title));
}
_qmlWindow->setProperty(TITLE_PROPERTY, title);
}
void QmlWebWindowClass::close() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
}
_qmlWindow->setProperty("destroyOnInvisible", true);
_qmlWindow->setProperty("visible", false);
_qmlWindow->deleteLater();
}
void QmlWebWindowClass::hasClosed() {
}
void QmlWebWindowClass::raise() {
// FIXME
}
#include "QmlWebWindowClass.moc"
#include "QmlWebWindowClass.moc"

View file

@ -9,97 +9,26 @@
#ifndef hifi_ui_QmlWebWindowClass_h
#define hifi_ui_QmlWebWindowClass_h
#include <QtCore/QObject>
#include <QtScript/QScriptValue>
#include <QtQuick/QQuickItem>
#include <QtWebChannel/QWebChannelAbstractTransport>
#include <GLMHelpers.h>
class QScriptEngine;
class QScriptContext;
class QmlWebWindowClass;
class QWebSocketServer;
class QWebSocket;
class QmlScriptEventBridge : public QObject {
Q_OBJECT
public:
QmlScriptEventBridge(const QmlWebWindowClass* webWindow) : _webWindow(webWindow) {}
public slots :
void emitWebEvent(const QString& data);
void emitScriptEvent(const QString& data);
signals:
void webEventReceived(const QString& data);
void scriptEventReceived(int windowId, const QString& data);
private:
const QmlWebWindowClass* _webWindow { nullptr };
QWebSocket *_socket { nullptr };
};
#include "QmlWindowClass.h"
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
class QmlWebWindowClass : public QObject {
class QmlWebWindowClass : public QmlWindowClass {
Q_OBJECT
Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT)
Q_PROPERTY(int windowId READ getWindowId CONSTANT)
Q_PROPERTY(QString url READ getURL CONSTANT)
Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition)
Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize)
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged)
public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
QmlWebWindowClass(QObject* qmlWindow);
public slots:
bool isVisible() const;
void setVisible(bool visible);
glm::vec2 getPosition() const;
void setPosition(const glm::vec2& position);
void setPosition(int x, int y);
glm::vec2 getSize() const;
void setSize(const glm::vec2& size);
void setSize(int width, int height);
QString getURL() const;
void setURL(const QString& url);
void setTitle(const QString& title);
// Ugh.... do not want to do
Q_INVOKABLE void raise();
Q_INVOKABLE void close();
Q_INVOKABLE int getWindowId() const { return _windowId; };
Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; };
signals:
void visibilityChanged(bool visible); // Tool window
void urlChanged();
void moved(glm::vec2 position);
void resized(QSizeF size);
void closed();
private slots:
void hasClosed();
void handleNavigation(const QString& url);
private:
static void setupServer();
static QWebSocketServer* _webChannelServer;
QQuickItem* asQuickItem() const;
QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) };
// FIXME needs to be initialized in the ctor once we have support
// for tool window panes in QML
const bool _isToolWindow { false };
const int _windowId;
QObject* const _qmlWindow;
};
#endif

View file

@ -0,0 +1,280 @@
//
// Created by Bradley Austin Davis on 2015-12-15
// 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 "QmlWindowClass.h"
#include <mutex>
#include <QtCore/QThread>
#include <QtScript/QScriptContext>
#include <QtScript/QScriptEngine>
#include <QtQuick/QQuickItem>
#include <QtWebSockets/QWebSocketServer>
#include <QtWebSockets/QWebSocket>
#include <QtWebChannel/QWebChannel>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include "OffscreenUi.h"
QWebSocketServer* QmlWindowClass::_webChannelServer { nullptr };
static QWebChannel webChannel;
static const uint16_t WEB_CHANNEL_PORT = 51016;
static std::atomic<int> nextWindowId;
static const char* const SOURCE_PROPERTY = "source";
static const char* const TITLE_PROPERTY = "title";
static const char* const WIDTH_PROPERTY = "width";
static const char* const HEIGHT_PROPERTY = "height";
static const char* const VISIBILE_PROPERTY = "visible";
void QmlScriptEventBridge::emitWebEvent(const QString& data) {
QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data));
}
void QmlScriptEventBridge::emitScriptEvent(const QString& data) {
QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection,
Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data));
}
class QmlWebTransport : public QWebChannelAbstractTransport {
Q_OBJECT
public:
QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) {
// Translate from the websocket layer to the webchannel layer
connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) {
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error);
if (error.error || !document.isObject()) {
qWarning() << "Unable to parse incoming JSON message" << message;
return;
}
emit messageReceived(document.object(), this);
});
}
virtual void sendMessage(const QJsonObject &message) override {
// Translate from the webchannel layer to the websocket layer
_webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
}
private:
QWebSocket* const _webSocket;
};
void QmlWindowClass::setupServer() {
if (!_webChannelServer) {
_webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode);
if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) {
qFatal("Failed to open web socket server.");
}
QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] {
webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection()));
});
}
}
QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
QScriptContext* context, QScriptEngine* engine,
std::function<QmlWindowClass*(QQmlContext*, QObject*)> function)
{
const auto argumentCount = context->argumentCount();
QString url;
QString title;
int width = 100, height = 100;
bool isToolWindow = false;
bool visible = true;
if (argumentCount > 1) {
if (!context->argument(0).isUndefined()) {
title = context->argument(0).toString();
}
if (!context->argument(1).isUndefined()) {
url = context->argument(1).toString();
}
if (context->argument(2).isNumber()) {
width = context->argument(2).toInt32();
}
if (context->argument(3).isNumber()) {
height = context->argument(3).toInt32();
}
} else {
auto argumentObject = context->argument(0);
qDebug() << argumentObject.toString();
if (!argumentObject.property(TITLE_PROPERTY).isUndefined()) {
title = argumentObject.property(TITLE_PROPERTY).toString();
}
if (!argumentObject.property(SOURCE_PROPERTY).isUndefined()) {
url = argumentObject.property(SOURCE_PROPERTY).toString();
}
if (argumentObject.property(WIDTH_PROPERTY).isNumber()) {
width = argumentObject.property(WIDTH_PROPERTY).toInt32();
}
if (argumentObject.property(HEIGHT_PROPERTY).isNumber()) {
height = argumentObject.property(HEIGHT_PROPERTY).toInt32();
}
if (argumentObject.property(VISIBILE_PROPERTY).isBool()) {
visible = argumentObject.property(VISIBILE_PROPERTY).isBool();
}
}
if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:")) {
url = QUrl::fromLocalFile(url).toString();
}
width = std::max(100, std::min(1280, width));
height = std::max(100, std::min(720, height));
QmlWindowClass* retVal{ nullptr };
// Build the event bridge and wrapper on the main thread
QMetaObject::invokeMethod(DependencyManager::get<OffscreenUi>().data(), "load", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, qmlSource),
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
setupServer();
retVal = function(context, object);
registerObject(url.toLower(), retVal);
if (!title.isEmpty()) {
retVal->setTitle(title);
}
retVal->setSize(width, height);
object->setProperty(SOURCE_PROPERTY, url);
if (visible) {
object->setProperty("enabled", true);
}
}));
connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater);
return engine->newQObject(retVal);
}
// Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
return internalConstructor("QmlWindow.qml", context, engine, [&](QQmlContext* context, QObject* object){
return new QmlWindowClass(object);
});
}
QmlWindowClass::QmlWindowClass(QObject* qmlWindow)
: _windowId(++nextWindowId), _qmlWindow(qmlWindow)
{
qDebug() << "Created window with ID " << _windowId;
Q_ASSERT(_qmlWindow);
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow));
}
void QmlWindowClass::registerObject(const QString& name, QObject* object) {
webChannel.registerObject(name, object);
}
void QmlWindowClass::deregisterObject(QObject* object) {
webChannel.deregisterObject(object);
}
void QmlWindowClass::setVisible(bool visible) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
return;
}
auto qmlWindow = asQuickItem();
if (qmlWindow->isEnabled() != visible) {
qmlWindow->setEnabled(visible);
emit visibilityChanged(visible);
}
}
QQuickItem* QmlWindowClass::asQuickItem() const {
return dynamic_cast<QQuickItem*>(_qmlWindow);
}
bool QmlWindowClass::isVisible() const {
if (QThread::currentThread() != thread()) {
bool result;
QMetaObject::invokeMethod(const_cast<QmlWindowClass*>(this), "isVisible", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result));
return result;
}
return asQuickItem()->isEnabled();
}
glm::vec2 QmlWindowClass::getPosition() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
QMetaObject::invokeMethod(const_cast<QmlWindowClass*>(this), "getPosition", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result));
return result;
}
return glm::vec2(asQuickItem()->x(), asQuickItem()->y());
}
void QmlWindowClass::setPosition(const glm::vec2& position) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setPosition", Qt::QueuedConnection, Q_ARG(glm::vec2, position));
return;
}
asQuickItem()->setPosition(QPointF(position.x, position.y));
}
void QmlWindowClass::setPosition(int x, int y) {
setPosition(glm::vec2(x, y));
}
glm::vec2 QmlWindowClass::getSize() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
QMetaObject::invokeMethod(const_cast<QmlWindowClass*>(this), "getSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result));
return result;
}
return glm::vec2(asQuickItem()->width(), asQuickItem()->height());
}
void QmlWindowClass::setSize(const glm::vec2& size) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setSize", Qt::QueuedConnection, Q_ARG(glm::vec2, size));
}
asQuickItem()->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", Qt::QueuedConnection, Q_ARG(QString, title));
}
_qmlWindow->setProperty(TITLE_PROPERTY, title);
}
void QmlWindowClass::close() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
}
_qmlWindow->setProperty("destroyOnInvisible", true);
_qmlWindow->setProperty("visible", false);
_qmlWindow->deleteLater();
}
void QmlWindowClass::hasClosed() {
}
void QmlWindowClass::raise() {
// FIXME
}
#include "QmlWindowClass.moc"

View file

@ -0,0 +1,103 @@
//
// Created by Bradley Austin Davis on 2015-12-15
// 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
//
#ifndef hifi_ui_QmlWindowClass_h
#define hifi_ui_QmlWindowClass_h
#include <QtCore/QObject>
#include <GLMHelpers.h>
#include <QtScript/QScriptValue>
#include <QtQuick/QQuickItem>
#include <QtWebChannel/QWebChannelAbstractTransport>
class QScriptEngine;
class QScriptContext;
class QmlWindowClass;
class QWebSocketServer;
class QWebSocket;
class QmlScriptEventBridge : public QObject {
Q_OBJECT
public:
QmlScriptEventBridge(const QmlWindowClass* webWindow) : _webWindow(webWindow) {}
public slots :
void emitWebEvent(const QString& data);
void emitScriptEvent(const QString& data);
signals:
void webEventReceived(const QString& data);
void scriptEventReceived(int windowId, const QString& data);
private:
const QmlWindowClass* _webWindow { nullptr };
QWebSocket *_socket { nullptr };
};
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
class QmlWindowClass : public QObject {
Q_OBJECT
Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT)
Q_PROPERTY(int windowId READ getWindowId CONSTANT)
Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition)
Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize)
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged)
public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
QmlWindowClass(QObject* qmlWindow);
public slots:
bool isVisible() const;
void setVisible(bool visible);
glm::vec2 getPosition() const;
void setPosition(const glm::vec2& position);
void setPosition(int x, int y);
glm::vec2 getSize() const;
void setSize(const glm::vec2& size);
void setSize(int width, int height);
void setTitle(const QString& title);
// Ugh.... do not want to do
Q_INVOKABLE void raise();
Q_INVOKABLE void close();
Q_INVOKABLE int getWindowId() const { return _windowId; };
Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; };
signals:
void visibilityChanged(bool visible); // Tool window
void moved(glm::vec2 position);
void resized(QSizeF size);
void closed();
protected slots:
void hasClosed();
protected:
static QScriptValue internalConstructor(const QString& qmlSource,
QScriptContext* context, QScriptEngine* engine,
std::function<QmlWindowClass*(QQmlContext*, QObject*)> function);
static void setupServer();
static void registerObject(const QString& name, QObject* object);
static void deregisterObject(QObject* object);
static QWebSocketServer* _webChannelServer;
QQuickItem* asQuickItem() const;
QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) };
// FIXME needs to be initialized in the ctor once we have support
// for tool window panes in QML
const bool _isToolWindow { false };
const int _windowId;
QObject* const _qmlWindow;
};
#endif