mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge pull request #7480 from jherico/webchannel_redux
Webchannel redux
This commit is contained in:
commit
44b37c332a
15 changed files with 194 additions and 330 deletions
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -4,21 +4,17 @@
|
|||
<script type="text/javascript" src="jquery-2.1.4.min.js"></script>
|
||||
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||
<script type="text/javascript" src="eventBridgeLoader.js"></script>
|
||||
|
||||
<script>
|
||||
var myBridge;
|
||||
|
||||
window.onload = function() {
|
||||
openEventBridge(function(eventBridge) {
|
||||
myBridge = eventBridge;
|
||||
myBridge.scriptEventReceived.connect(function(message) {
|
||||
openEventBridge(function() {
|
||||
EventBridge.scriptEventReceived.connect(function(message) {
|
||||
console.log("HTML side received message: " + message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
testClick = function() {
|
||||
myBridge.emitWebEvent("HTML side sending message - button click");
|
||||
EventBridge.emitWebEvent(["Foo", "Bar", { "baz": 1} ]);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -26,7 +26,7 @@ ParticleExplorerTool = function() {
|
|||
});
|
||||
|
||||
that.webView.setVisible(true);
|
||||
that.webView.eventBridge.webEventReceived.connect(that.webEventReceived);
|
||||
that.webView.webEventReceived.connect(that.webEventReceived);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -8,35 +8,44 @@
|
|||
|
||||
#include "QmlWebWindowClass.h"
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QUrlQuery>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <QtQml/QQmlContext>
|
||||
|
||||
#include <QtScript/QScriptContext>
|
||||
#include <QtScript/QScriptEngine>
|
||||
|
||||
#include <QtQuick/QQuickItem>
|
||||
|
||||
#include <AbstractUriHandler.h>
|
||||
#include <AccountManager.h>
|
||||
#include <AddressManager.h>
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#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>();
|
||||
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<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -20,203 +20,113 @@
|
|||
|
||||
#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 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<QmlWindowClass*(QObject*)> 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<OffscreenUi>();
|
||||
|
||||
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<QQuickItem*>(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<void(QQmlContext*, QObject*)>, [&](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>();
|
||||
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<OffscreenUi>();
|
||||
_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<const QQuickItem*>(_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<OffscreenUi>()->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<OffscreenUi>();
|
||||
QMetaObject::invokeMethod(targetWindow, "showTabForUrl", Qt::QueuedConnection, Q_ARG(QVariant, _source), Q_ARG(QVariant, visible));
|
||||
} else {
|
||||
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
|
||||
|
@ -359,5 +260,3 @@ void QmlWindowClass::raise() {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
#include "QmlWindowClass.moc"
|
||||
|
|
|
@ -13,45 +13,22 @@
|
|||
#include <QtCore/QPointer>
|
||||
#include <QtScript/QScriptValue>
|
||||
#include <QtQuick/QQuickItem>
|
||||
#include <QtWebChannel/QWebChannelAbstractTransport>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
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<QmlWindowClass*(QObject*)> 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<QmlWindowClass*(QVariantMap)> 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<QObject> _qmlWindow;
|
||||
QString _source;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue