diff --git a/examples/edit.js b/examples/edit.js
index eb853fd359..e22bb28cd7 100644
--- a/examples/edit.js
+++ b/examples/edit.js
@@ -1484,10 +1484,10 @@ PropertiesTool = function(opts) {
selections.push(entity);
}
data.selections = selections;
- webView.eventBridge.emitScriptEvent(JSON.stringify(data));
+ webView.emitScriptEvent(JSON.stringify(data));
});
- webView.eventBridge.webEventReceived.connect(function(data) {
+ webView.webEventReceived.connect(function(data) {
data = JSON.parse(data);
if (data.type == "print") {
if (data.message) {
@@ -1802,7 +1802,7 @@ var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace");
propertiesTool = PropertiesTool();
var particleExplorerTool = ParticleExplorerTool();
var selectedParticleEntity = 0;
-entityListTool.webView.eventBridge.webEventReceived.connect(function(data) {
+entityListTool.webView.webEventReceived.connect(function(data) {
var data = JSON.parse(data);
if (data.type == "selectionUpdate") {
var ids = data.entityIds;
@@ -1823,10 +1823,10 @@ entityListTool.webView.eventBridge.webEventReceived.connect(function(data) {
selectedParticleEntity = ids[0];
particleExplorerTool.setActiveParticleEntity(ids[0]);
- particleExplorerTool.webView.eventBridge.webEventReceived.connect(function(data) {
+ particleExplorerTool.webView.webEventReceived.connect(function(data) {
var data = JSON.parse(data);
if (data.messageType === "page_loaded") {
- particleExplorerTool.webView.eventBridge.emitScriptEvent(JSON.stringify(particleData));
+ particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
}
});
} else {
diff --git a/examples/html/eventBridgeLoader.js b/examples/html/eventBridgeLoader.js
index ebfb6dc740..0e95345b40 100644
--- a/examples/html/eventBridgeLoader.js
+++ b/examples/html/eventBridgeLoader.js
@@ -9,51 +9,11 @@
//
var EventBridge;
-
-EventBridgeConnectionProxy = function(parent) {
- this.parent = parent;
- this.realSignal = this.parent.realBridge.scriptEventReceived
- this.webWindowId = this.parent.webWindow.windowId;
-}
-
-EventBridgeConnectionProxy.prototype.connect = function(callback) {
- var that = this;
- this.realSignal.connect(function(id, message) {
- if (id === that.webWindowId) { callback(message); }
- });
-}
-
-EventBridgeProxy = function(webWindow) {
- this.webWindow = webWindow;
- this.realBridge = this.webWindow.eventBridge;
- this.scriptEventReceived = new EventBridgeConnectionProxy(this);
-}
-
-EventBridgeProxy.prototype.emitWebEvent = function(data) {
- this.realBridge.emitWebEvent(data);
-}
+var WebChannel;
openEventBridge = function(callback) {
- EVENT_BRIDGE_URI = "ws://localhost:51016";
- socket = new WebSocket(this.EVENT_BRIDGE_URI);
-
- socket.onclose = function() {
- console.error("web channel closed");
- };
-
- socket.onerror = function(error) {
- console.error("web channel error: " + error);
- };
-
- socket.onopen = function() {
- channel = new QWebChannel(socket, function(channel) {
- console.log("Document url is " + document.URL);
- var webWindow = channel.objects[document.URL.toLowerCase()];
- console.log("WebWindow is " + webWindow)
- eventBridgeProxy = new EventBridgeProxy(webWindow);
- EventBridge = eventBridgeProxy;
- if (callback) { callback(eventBridgeProxy); }
- });
- }
+ WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) {
+ EventBridge = WebChannel.objects.eventBridgeWrapper.eventBridge;
+ callback(EventBridge);
+ });
}
-
diff --git a/examples/html/qmlWebTest.html b/examples/html/qmlWebTest.html
index e59535701d..553ce83417 100644
--- a/examples/html/qmlWebTest.html
+++ b/examples/html/qmlWebTest.html
@@ -4,21 +4,17 @@
-
diff --git a/examples/libraries/entityList.js b/examples/libraries/entityList.js
index 6c6c0aaecb..06f6f0e88f 100644
--- a/examples/libraries/entityList.js
+++ b/examples/libraries/entityList.js
@@ -38,14 +38,14 @@ EntityListTool = function(opts) {
type: 'selectionUpdate',
selectedIDs: selectedIDs,
};
- webView.eventBridge.emitScriptEvent(JSON.stringify(data));
+ webView.emitScriptEvent(JSON.stringify(data));
});
that.clearEntityList = function () {
var data = {
type: 'clearEntityList'
}
- webView.eventBridge.emitScriptEvent(JSON.stringify(data));
+ webView.emitScriptEvent(JSON.stringify(data));
};
that.sendUpdate = function() {
@@ -72,11 +72,11 @@ EntityListTool = function(opts) {
entities: entities,
selectedIDs: selectedIDs,
};
- webView.eventBridge.emitScriptEvent(JSON.stringify(data));
+ webView.emitScriptEvent(JSON.stringify(data));
}
- webView.eventBridge.webEventReceived.connect(function(data) {
+ webView.webEventReceived.connect(function(data) {
data = JSON.parse(data);
if (data.type == "selectionUpdate") {
var ids = data.entityIds;
diff --git a/examples/libraries/gridTool.js b/examples/libraries/gridTool.js
index fbd2ec7ea0..c002aec3b1 100644
--- a/examples/libraries/gridTool.js
+++ b/examples/libraries/gridTool.js
@@ -234,11 +234,11 @@ GridTool = function(opts) {
});
horizontalGrid.addListener(function(data) {
- webView.eventBridge.emitScriptEvent(JSON.stringify(data));
+ webView.emitScriptEvent(JSON.stringify(data));
selectionDisplay.updateHandles();
});
- webView.eventBridge.webEventReceived.connect(function(data) {
+ webView.webEventReceived.connect(function(data) {
data = JSON.parse(data);
if (data.type == "init") {
horizontalGrid.emitUpdate();
diff --git a/examples/particle_explorer/particleExplorerTool.js b/examples/particle_explorer/particleExplorerTool.js
index 007eb717ec..8a28445c33 100644
--- a/examples/particle_explorer/particleExplorerTool.js
+++ b/examples/particle_explorer/particleExplorerTool.js
@@ -26,7 +26,7 @@ ParticleExplorerTool = function() {
});
that.webView.setVisible(true);
- that.webView.eventBridge.webEventReceived.connect(that.webEventReceived);
+ that.webView.webEventReceived.connect(that.webEventReceived);
}
diff --git a/examples/tests/qmlWebTest.js b/examples/tests/qmlWebTest.js
index 5faa68668d..ac40e54b85 100644
--- a/examples/tests/qmlWebTest.js
+++ b/examples/tests/qmlWebTest.js
@@ -2,32 +2,18 @@ print("Launching web window");
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) {
+webWindow.webEventReceived.connect(function(data) {
print("JS Side event received: " + data);
});
-var titles = ["A", "B", "C"];
-var titleIndex = 0;
-
Script.setInterval(function() {
- webWindow.eventBridge.emitScriptEvent("JS Event sent");
- var size = webWindow.size;
- var position = webWindow.position;
- print("Window url: " + webWindow.url)
- print("Window visible: " + webWindow.visible)
- print("Window size: " + size.x + "x" + size.y)
- print("Window pos: " + position.x + "x" + position.y)
- webWindow.setVisible(!webWindow.visible);
- webWindow.setTitle(titles[titleIndex]);
- webWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100);
- titleIndex += 1;
- titleIndex %= titles.length;
-}, 2 * 1000);
+ var message = [ Math.random(), Math.random() ];
+ print("JS Side sending: " + message);
+ webWindow.emitScriptEvent(message);
+}, 5 * 1000);
-Script.setTimeout(function() {
- print("Closing script");
+Script.scriptEnding.connect(function(){
webWindow.close();
- Script.stop();
-}, 15 * 1000)
+ webWindow.deleteLater();
+});
+
diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml
index fd4e629568..70c8afd298 100644
--- a/interface/resources/qml/QmlWebWindow.qml
+++ b/interface/resources/qml/QmlWebWindow.qml
@@ -1,6 +1,7 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtWebEngine 1.1
+import QtWebChannel 1.0
import "windows" as Windows
import "controls" as Controls
@@ -15,11 +16,24 @@ Windows.Window {
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false
property alias source: webview.url
+ property alias eventBridge: eventBridgeWrapper.eventBridge;
+
+ QtObject {
+ id: eventBridgeWrapper
+ WebChannel.id: "eventBridgeWrapper"
+ property var eventBridge;
+ }
+
+ // This is for JS/QML communication, which is unused in a WebWindow,
+ // but not having this here results in spurious warnings about a
+ // missing signal
+ signal sendToScript(var message);
Controls.WebView {
id: webview
url: "about:blank"
anchors.fill: parent
focus: true
+ webChannel.registeredObjects: [eventBridgeWrapper]
}
} // dialog
diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml
index ed8ff93f19..0420cd2e88 100644
--- a/interface/resources/qml/QmlWindow.qml
+++ b/interface/resources/qml/QmlWindow.qml
@@ -20,6 +20,7 @@ Windows.Window {
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false
property var source;
+ property var eventBridge;
property var component;
property var dynamicContent;
onSourceChanged: {
diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml
index 75aa50aa34..7398057722 100644
--- a/interface/resources/qml/ToolWindow.qml
+++ b/interface/resources/qml/ToolWindow.qml
@@ -1,7 +1,7 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtWebEngine 1.1
-
+import QtWebChannel 1.0
import Qt.labs.settings 1.0
import "windows" as Windows
@@ -37,14 +37,26 @@ Windows.Window {
Repeater {
model: 4
Tab {
+ // Force loading of the content even if the tab is not visible
+ // (required for letting the C++ code access the webview)
active: true
- enabled: false;
- // we need to store the original url here for future identification
+ enabled: false
property string originalUrl: "";
- onEnabledChanged: toolWindow.updateVisiblity();
+
Controls.WebView {
id: webView;
anchors.fill: parent
+ enabled: false
+ property alias eventBridgeWrapper: eventBridgeWrapper
+
+ QtObject {
+ id: eventBridgeWrapper
+ WebChannel.id: "eventBridgeWrapper"
+ property var eventBridge;
+ }
+
+ webChannel.registeredObjects: [eventBridgeWrapper]
+ onEnabledChanged: toolWindow.updateVisiblity();
}
}
}
@@ -113,20 +125,23 @@ Windows.Window {
var tab = tabView.getTab(index);
tab.title = "";
- tab.originalUrl = "";
tab.enabled = false;
+ tab.originalUrl = "";
+ tab.item.url = "about:blank";
+ tab.item.enabled = false;
}
function addWebTab(properties) {
if (!properties.source) {
- console.warn("Attempted to open Web Tool Pane without URL")
+ console.warn("Attempted to open Web Tool Pane without URL");
return;
}
var existingTabIndex = findIndexForUrl(properties.source);
if (existingTabIndex >= 0) {
- console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source)
- return tabView.getTab(existingTabIndex);
+ console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source);
+ var tab = tabView.getTab(existingTabIndex);
+ return tab.item;
}
var freeTabIndex = findFreeTab();
@@ -135,25 +150,28 @@ Windows.Window {
return;
}
- var newTab = tabView.getTab(freeTabIndex);
- newTab.title = properties.title || "Unknown";
- newTab.originalUrl = properties.source;
- newTab.item.url = properties.source;
- newTab.active = true;
-
if (properties.width) {
- tabView.width = Math.min(Math.max(tabView.width, properties.width),
- toolWindow.maxSize.x);
+ tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x);
}
if (properties.height) {
- tabView.height = Math.min(Math.max(tabView.height, properties.height),
- toolWindow.maxSize.y);
+ tabView.height = Math.min(Math.max(tabView.height, properties.height), toolWindow.maxSize.y);
}
- console.log("Updating visibility based on child tab added");
- newTab.enabledChanged.connect(updateVisiblity)
- updateVisiblity();
- return newTab
+ var tab = tabView.getTab(freeTabIndex);
+ tab.title = properties.title || "Unknown";
+ tab.enabled = true;
+ console.log("New tab URL: " + properties.source)
+ tab.originalUrl = properties.source;
+
+ var eventBridge = properties.eventBridge;
+ console.log("Event bridge: " + eventBridge);
+
+ var result = tab.item;
+ result.enabled = true;
+ console.log("Setting event bridge: " + eventBridge);
+ result.eventBridgeWrapper.eventBridge = eventBridge;
+ result.url = properties.source;
+ return result;
}
}
diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml
index 18080cd448..1361e6e322 100644
--- a/interface/resources/qml/controls/WebView.qml
+++ b/interface/resources/qml/controls/WebView.qml
@@ -59,6 +59,7 @@ WebEngineView {
request.openIn(newWindow.webView)
}
-
- profile: desktop.browserProfile
+ // This breaks the webchannel used for passing messages. Fixed in Qt 5.6
+ // See https://bugreports.qt.io/browse/QTBUG-49521
+ //profile: desktop.browserProfile
}
diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp
index 0228f77f4f..b964f305a4 100644
--- a/libraries/ui/src/QmlWebWindowClass.cpp
+++ b/libraries/ui/src/QmlWebWindowClass.cpp
@@ -8,35 +8,44 @@
#include "QmlWebWindowClass.h"
-#include
-#include
#include
-#include
-
#include
#include
-#include
-
-#include
-#include
-#include
-#include
-
#include "OffscreenUi.h"
static const char* const URL_PROPERTY = "source";
// Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
- return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine,
- [&](QObject* object) { return new QmlWebWindowClass(object); });
+ auto properties = parseArguments(context);
+ QmlWebWindowClass* retVal { nullptr };
+ auto offscreenUi = DependencyManager::get();
+ offscreenUi->executeOnUiThread([&] {
+ retVal = new QmlWebWindowClass();
+ retVal->initQml(properties);
+ }, true);
+ Q_ASSERT(retVal);
+ connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater);
+ return engine->newQObject(retVal);
}
-QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) {
+void QmlWebWindowClass::emitScriptEvent(const QVariant& scriptMessage) {
+ if (QThread::currentThread() != thread()) {
+ QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage));
+ } else {
+ emit scriptEventReceived(scriptMessage);
+ }
}
+void QmlWebWindowClass::emitWebEvent(const QVariant& webMessage) {
+ if (QThread::currentThread() != thread()) {
+ QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage));
+ } else {
+ emit webEventReceived(webMessage);
+ }
+}
QString QmlWebWindowClass::getURL() const {
QVariant result = DependencyManager::get()->returnFromUiThread([&]()->QVariant {
diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h
index 27c0e6996d..86d0e9b2c4 100644
--- a/libraries/ui/src/QmlWebWindowClass.h
+++ b/libraries/ui/src/QmlWebWindowClass.h
@@ -18,14 +18,21 @@ class QmlWebWindowClass : public QmlWindowClass {
public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
- QmlWebWindowClass(QObject* qmlWindow);
-public slots:
+ public slots:
QString getURL() const;
void setURL(const QString& url);
+ void emitScriptEvent(const QVariant& scriptMessage);
+ void emitWebEvent(const QVariant& webMessage);
+
signals:
void urlChanged();
+ void scriptEventReceived(const QVariant& message);
+ void webEventReceived(const QVariant& message);
+
+protected:
+ QString qmlSource() const override { return "QmlWebWindow.qml"; }
};
#endif
diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp
index b7fe330a4e..d18ada1f5a 100644
--- a/libraries/ui/src/QmlWindowClass.cpp
+++ b/libraries/ui/src/QmlWindowClass.cpp
@@ -20,203 +20,113 @@
#include
#include
-#include
#include
#include
#include "OffscreenUi.h"
-QWebSocketServer* QmlWindowClass::_webChannelServer { nullptr };
-static QWebChannel webChannel;
-static const uint16_t WEB_CHANNEL_PORT = 51016;
-static std::atomic nextWindowId;
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 char* const TOOLWINDOW_PROPERTY = "toolWindow";
+static const uvec2 MAX_QML_WINDOW_SIZE { 1280, 720 };
+static const uvec2 MIN_QML_WINDOW_SIZE { 120, 80 };
-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 builder)
-{
+QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) {
const auto argumentCount = context->argumentCount();
- QString url;
QString title;
- int width = -1, height = -1;
- bool visible = true;
- bool toolWindow = false;
+ QVariantMap properties;
if (argumentCount > 1) {
-
if (!context->argument(0).isUndefined()) {
- title = context->argument(0).toString();
+ properties[TITLE_PROPERTY] = context->argument(0).toString();
}
if (!context->argument(1).isUndefined()) {
- url = context->argument(1).toString();
+ properties[SOURCE_PROPERTY] = context->argument(1).toString();
}
if (context->argument(2).isNumber()) {
- width = context->argument(2).toInt32();
+ properties[WIDTH_PROPERTY] = context->argument(2).toInt32();
}
if (context->argument(3).isNumber()) {
- height = context->argument(3).toInt32();
+ properties[HEIGHT_PROPERTY] = context->argument(3).toInt32();
}
if (context->argument(4).isBool()) {
- toolWindow = context->argument(4).toBool();
+ properties[TOOLWINDOW_PROPERTY] = context->argument(4).toBool();
}
} else {
- auto argumentObject = context->argument(0);
- 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).toBool();
- }
- if (argumentObject.property(TOOLWINDOW_PROPERTY).isBool()) {
- toolWindow = argumentObject.property(TOOLWINDOW_PROPERTY).toBool();
- }
+ properties = context->argument(0).toVariant().toMap();
}
+ QString url = properties[SOURCE_PROPERTY].toString();
if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:")) {
- url = QUrl::fromLocalFile(url).toString();
+ properties[SOURCE_PROPERTY] = QUrl::fromLocalFile(url).toString();
}
- if (width != -1 || height != -1) {
- width = std::max(100, std::min(1280, width));
- height = std::max(100, std::min(720, height));
- }
-
- QmlWindowClass* retVal{ nullptr };
- auto offscreenUi = DependencyManager::get();
-
- if (toolWindow) {
- auto toolWindow = offscreenUi->getToolWindow();
- QVariantMap properties;
- properties.insert(TITLE_PROPERTY, title);
- properties.insert(SOURCE_PROPERTY, url);
- if (width != -1 && height != -1) {
- properties.insert(WIDTH_PROPERTY, width);
- properties.insert(HEIGHT_PROPERTY, height);
- }
-
- // Build the event bridge and wrapper on the main thread
- QVariant newTabVar;
- bool invokeResult = QMetaObject::invokeMethod(toolWindow, "addWebTab", Qt::BlockingQueuedConnection,
- Q_RETURN_ARG(QVariant, newTabVar),
- Q_ARG(QVariant, QVariant::fromValue(properties)));
-
- QQuickItem* newTab = qvariant_cast(newTabVar);
- if (!invokeResult || !newTab) {
- return QScriptValue();
- }
-
- offscreenUi->returnFromUiThread([&] {
- setupServer();
- retVal = builder(newTab);
- retVal->_toolWindow = true;
- registerObject(url.toLower(), retVal);
- return QVariant();
- });
- } else {
- // Build the event bridge and wrapper on the main thread
- QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection,
- Q_ARG(const QString&, qmlSource),
- Q_ARG(std::function, [&](QQmlContext* context, QObject* object) {
- setupServer();
- retVal = builder(object);
- context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership);
- registerObject(url.toLower(), retVal);
- if (!title.isEmpty()) {
- retVal->setTitle(title);
- }
- if (width != -1 && height != -1) {
- retVal->setSize(width, height);
- }
- object->setProperty(SOURCE_PROPERTY, url);
- if (visible) {
- object->setProperty("visible", true);
- }
- }));
- }
-
- retVal->_source = url;
- connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater);
- return engine->newQObject(retVal);
+ return properties;
}
+
// 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, [&](QObject* object){
- return new QmlWindowClass(object);
- });
+ auto properties = parseArguments(context);
+ QmlWindowClass* retVal { nullptr };
+ auto offscreenUi = DependencyManager::get();
+ offscreenUi->executeOnUiThread([&] {
+ retVal = new QmlWindowClass();
+ retVal->initQml(properties);
+ }, true);
+ Q_ASSERT(retVal);
+ connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater);
+ return engine->newQObject(retVal);
}
-QmlWindowClass::QmlWindowClass(QObject* qmlWindow)
- : _windowId(++nextWindowId), _qmlWindow(qmlWindow)
-{
- qDebug() << "Created window with ID " << _windowId;
+QmlWindowClass::QmlWindowClass() {
+
+}
+
+void QmlWindowClass::initQml(QVariantMap properties) {
+ auto offscreenUi = DependencyManager::get();
+ _toolWindow = properties.contains(TOOLWINDOW_PROPERTY) && properties[TOOLWINDOW_PROPERTY].toBool();
+ _source = properties[SOURCE_PROPERTY].toString();
+
+ if (_toolWindow) {
+ // Build the event bridge and wrapper on the main thread
+ _qmlWindow = offscreenUi->getToolWindow();
+ properties[EVENT_BRIDGE_PROPERTY] = QVariant::fromValue(this);
+ QVariant newTabVar;
+ bool invokeResult = QMetaObject::invokeMethod(_qmlWindow, "addWebTab", Qt::DirectConnection,
+ Q_RETURN_ARG(QVariant, newTabVar),
+ Q_ARG(QVariant, QVariant::fromValue(properties)));
+ Q_ASSERT(invokeResult);
+ } else {
+ // Build the event bridge and wrapper on the main thread
+ offscreenUi->load(qmlSource(), [&](QQmlContext* context, QObject* object) {
+ _qmlWindow = object;
+ _qmlWindow->setProperty("eventBridge", QVariant::fromValue(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(VISIBILE_PROPERTY, visible);
+ object->setProperty(SOURCE_PROPERTY, _source);
+
+ // Forward messages received from QML on to the script
+ connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(const QVariant&)), Qt::QueuedConnection);
+ });
+ }
Q_ASSERT(_qmlWindow);
Q_ASSERT(dynamic_cast(_qmlWindow.data()));
- // Forward messages received from QML on to the script
- connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(const QVariant&)), Qt::QueuedConnection);
}
void QmlWindowClass::sendToQml(const QVariant& message) {
@@ -228,14 +138,6 @@ QmlWindowClass::~QmlWindowClass() {
close();
}
-void QmlWindowClass::registerObject(const QString& name, QObject* object) {
- webChannel.registerObject(name, object);
-}
-
-void QmlWindowClass::deregisterObject(QObject* object) {
- webChannel.deregisterObject(object);
-}
-
QQuickItem* QmlWindowClass::asQuickItem() const {
if (_toolWindow) {
return DependencyManager::get()->getToolWindow();
@@ -248,7 +150,6 @@ void QmlWindowClass::setVisible(bool visible) {
if (_toolWindow) {
// For tool window tabs we special case visibility as a function call on the tab parent
// The tool window itself has special logic based on whether any tabs are visible
- auto offscreenUi = DependencyManager::get();
QMetaObject::invokeMethod(targetWindow, "showTabForUrl", Qt::QueuedConnection, Q_ARG(QVariant, _source), Q_ARG(QVariant, visible));
} else {
DependencyManager::get()->executeOnUiThread([=] {
@@ -359,5 +260,3 @@ void QmlWindowClass::raise() {
}
});
}
-
-#include "QmlWindowClass.moc"
diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h
index fb7dbf1253..242f9b0dd4 100644
--- a/libraries/ui/src/QmlWindowClass.h
+++ b/libraries/ui/src/QmlWindowClass.h
@@ -13,45 +13,22 @@
#include
#include
#include
-#include
#include
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 NOTIFY positionChanged)
Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged)
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged)
public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
- QmlWindowClass(QObject* qmlWindow);
+ QmlWindowClass();
~QmlWindowClass();
public slots:
@@ -69,8 +46,7 @@ public slots:
Q_INVOKABLE void raise();
Q_INVOKABLE void close();
- Q_INVOKABLE int getWindowId() const { return _windowId; };
- Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; };
+ Q_INVOKABLE QObject* getEventBridge() { return this; };
// Scripts can use this to send a message to the QML object
void sendToQml(const QVariant& message);
@@ -89,21 +65,18 @@ protected slots:
void hasClosed();
protected:
- static QScriptValue internalConstructor(const QString& qmlSource,
- QScriptContext* context, QScriptEngine* engine,
- std::function function);
- static void setupServer();
- static void registerObject(const QString& name, QObject* object);
- static void deregisterObject(QObject* object);
- static QWebSocketServer* _webChannelServer;
+ static QVariantMap parseArguments(QScriptContext* context);
+ static QScriptValue internalConstructor(QScriptContext* context, QScriptEngine* engine,
+ std::function function);
+ virtual QString qmlSource() const { return "QmlWindow.qml"; }
+
+ virtual void initQml(QVariantMap properties);
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
bool _toolWindow { false };
- const int _windowId;
QPointer _qmlWindow;
QString _source;
};