Merge branch 'master' of github.com:highfidelity/hifi into feat/geocaching

This commit is contained in:
Zach Pomerantz 2016-03-25 12:07:00 -07:00
commit c5e5195db1
18 changed files with 225 additions and 129 deletions

View file

@ -10,11 +10,50 @@
var EventBridge; var EventBridge;
openEventBridge = function(callback) { EventBridgeConnectionProxy = function(parent) {
new QWebChannel(qt.webChannelTransport, function(channel) { this.parent = parent;
console.log("uid " + EventBridgeUid); this.realSignal = this.parent.realBridge.scriptEventReceived
EventBridge = channel.objects[EventBridgeUid]; this.webWindowId = this.parent.webWindow.windowId;
callback(EventBridge); }
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);
}
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); }
});
}
}

View file

@ -4,17 +4,21 @@
<script type="text/javascript" src="jquery-2.1.4.min.js"></script> <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="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="eventBridgeLoader.js"></script> <script type="text/javascript" src="eventBridgeLoader.js"></script>
<script> <script>
var myBridge;
window.onload = function() { window.onload = function() {
openEventBridge(function() { openEventBridge(function(eventBridge) {
EventBridge.scriptEventReceived.connect(function(message) { myBridge = eventBridge;
myBridge.scriptEventReceived.connect(function(message) {
console.log("HTML side received message: " + message); console.log("HTML side received message: " + message);
}); });
}); });
} }
testClick = function() { testClick = function() {
EventBridge.emitWebEvent(["Foo", "Bar", { "baz": 1} ]); myBridge.emitWebEvent("HTML side sending message - button click");
} }
</script> </script>
</head> </head>

View file

@ -8,14 +8,26 @@ webWindow.eventBridge.webEventReceived.connect(function(data) {
print("JS Side event received: " + data); print("JS Side event received: " + data);
}); });
var titles = ["A", "B", "C"];
var titleIndex = 0;
Script.setInterval(function() { Script.setInterval(function() {
var message = [ Math.random(), Math.random() ]; webWindow.eventBridge.emitScriptEvent("JS Event sent");
print("JS Side sending: " + message); var size = webWindow.size;
webWindow.emitScriptEvent(message); var position = webWindow.position;
}, 5 * 1000); 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);
Script.scriptEnding.connect(function(){ Script.setTimeout(function() {
print("Closing script");
webWindow.close(); webWindow.close();
webWindow.deleteLater(); Script.stop();
}); }, 15 * 1000)

View file

@ -1,7 +1,6 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtWebEngine 1.1 import QtWebEngine 1.1
import QtWebChannel 1.0
import "windows" as Windows import "windows" as Windows
import "controls" as Controls import "controls" as Controls
@ -16,22 +15,11 @@ Windows.Window {
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false destroyOnCloseButton: false
property alias source: webview.url property alias source: webview.url
property alias webChannel: webview.webChannel
// A unique identifier to let the HTML JS find the event bridge
// object (our C++ wrapper)
property string uid;
// 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 { Controls.WebView {
id: webview id: webview
url: "about:blank" url: "about:blank"
anchors.fill: parent anchors.fill: parent
focus: true focus: true
onUrlChanged: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";");
Component.onCompleted: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";");
} }
} // dialog } // dialog

View file

@ -37,33 +37,14 @@ Windows.Window {
Repeater { Repeater {
model: 4 model: 4
Tab { Tab {
// Force loading of the content even if the tab is not visible
// (required for letting the C++ code access the webview)
active: true active: true
enabled: false enabled: false;
// we need to store the original url here for future identification
property string originalUrl: ""; property string originalUrl: "";
onEnabledChanged: toolWindow.updateVisiblity();
Controls.WebView { Controls.WebView {
id: webView; id: webView;
// we need to store the original url here for future identification
// A unique identifier to let the HTML JS find the event bridge
// object (our C++ wrapper)
property string uid;
anchors.fill: parent anchors.fill: parent
enabled: false
// 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);
onUrlChanged: webView.runJavaScript("EventBridgeUid = \"" + uid + "\";");
onEnabledChanged: toolWindow.updateVisiblity();
onLoadingChanged: {
if (loadRequest.status == WebEngineView.LoadSucceededStatus) {
webView.runJavaScript("EventBridgeUid = \"" + uid + "\";");
}
}
} }
} }
} }
@ -132,23 +113,20 @@ Windows.Window {
var tab = tabView.getTab(index); var tab = tabView.getTab(index);
tab.title = ""; tab.title = "";
tab.enabled = false;
tab.originalUrl = ""; tab.originalUrl = "";
tab.item.url = "about:blank"; tab.enabled = false;
tab.item.enabled = false;
} }
function addWebTab(properties) { function addWebTab(properties) {
if (!properties.source) { if (!properties.source) {
console.warn("Attempted to open Web Tool Pane without URL"); console.warn("Attempted to open Web Tool Pane without URL")
return; return;
} }
var existingTabIndex = findIndexForUrl(properties.source); var existingTabIndex = findIndexForUrl(properties.source);
if (existingTabIndex >= 0) { if (existingTabIndex >= 0) {
console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source); console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source)
var tab = tabView.getTab(existingTabIndex); return tabView.getTab(existingTabIndex);
return tab.item;
} }
var freeTabIndex = findFreeTab(); var freeTabIndex = findFreeTab();
@ -157,22 +135,25 @@ Windows.Window {
return; 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) { 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) { 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);
} }
var tab = tabView.getTab(freeTabIndex); console.log("Updating visibility based on child tab added");
tab.title = properties.title || "Unknown"; newTab.enabledChanged.connect(updateVisiblity)
tab.enabled = true; updateVisiblity();
tab.originalUrl = properties.source; return newTab
var result = tab.item;
result.enabled = true;
result.url = properties.source;
return result;
} }
} }

View file

@ -59,7 +59,6 @@ WebEngineView {
request.openIn(newWindow.webView) request.openIn(newWindow.webView)
} }
// This breaks the webchannel used for passing messages. Fixed in Qt 5.6
// See https://bugreports.qt.io/browse/QTBUG-49521 profile: desktop.browserProfile
//profile: desktop.browserProfile
} }

View file

@ -231,6 +231,13 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
_leftEyePosition = _rightEyePosition = getPosition(); _leftEyePosition = _rightEyePosition = getPosition();
_eyePosition = calculateAverageEyePosition(); _eyePosition = calculateAverageEyePosition();
if (!billboard && _owningAvatar) {
auto skeletonModel = static_cast<Avatar*>(_owningAvatar)->getSkeletonModel();
if (skeletonModel) {
skeletonModel->getEyePositions(_leftEyePosition, _rightEyePosition);
}
}
} }
void Head::calculateMouthShapes() { void Head::calculateMouthShapes() {

View file

@ -897,7 +897,9 @@ void MyAvatar::updateLookAtTargetAvatar() {
// Scale by proportional differences between avatar and human. // Scale by proportional differences between avatar and human.
float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye) * ipdScale; float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye) * ipdScale;
float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye); float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye);
gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation; if (avatarEyeSeparation > 0.0f) {
gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation;
}
} }
// And now we can finally add that offset to the camera. // And now we can finally add that offset to the camera.

View file

@ -611,7 +611,7 @@ AnimNode::Pointer AnimNodeLoader::load(const QByteArray& contents, const QUrl& j
return loadNode(rootVal.toObject(), jsonUrl); return loadNode(rootVal.toObject(), jsonUrl);
} }
void AnimNodeLoader::onRequestDone(const QByteArray& data) { void AnimNodeLoader::onRequestDone(const QByteArray data) {
auto node = load(data, _url); auto node = load(data, _url);
if (node) { if (node) {
emit success(node); emit success(node);

View file

@ -36,7 +36,7 @@ protected:
static AnimNode::Pointer load(const QByteArray& contents, const QUrl& jsonUrl); static AnimNode::Pointer load(const QByteArray& contents, const QUrl& jsonUrl);
protected slots: protected slots:
void onRequestDone(const QByteArray& data); void onRequestDone(const QByteArray data);
void onRequestError(QNetworkReply::NetworkError error); void onRequestError(QNetworkReply::NetworkError error);
protected: protected:

View file

@ -101,12 +101,15 @@ void EntitySimulation::expireMortalEntities(const quint64& now) {
prepareEntityForDelete(entity); prepareEntityForDelete(entity);
} else { } else {
if (expiry < _nextExpiry) { if (expiry < _nextExpiry) {
// remeber the smallest _nextExpiry so we know when to start the next search // remember the smallest _nextExpiry so we know when to start the next search
_nextExpiry = expiry; _nextExpiry = expiry;
} }
++itemItr; ++itemItr;
} }
} }
if (_mortalEntities.size() < 1) {
_nextExpiry = -1;
}
} }
} }

View file

@ -423,12 +423,12 @@ void Resource::handleReplyFinished() {
auto result = _request->getResult(); auto result = _request->getResult();
if (result == ResourceRequest::Success) { if (result == ResourceRequest::Success) {
_data = _request->getData();
auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString()); auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString());
qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo); qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo);
emit loaded(_data); auto data = _request->getData();
downloadFinished(_data); emit loaded(data);
downloadFinished(data);
} else { } else {
switch (result) { switch (result) {
case ResourceRequest::Result::Timeout: { case ResourceRequest::Result::Timeout: {

View file

@ -194,12 +194,11 @@ public:
Q_INVOKABLE void allReferencesCleared(); Q_INVOKABLE void allReferencesCleared();
const QUrl& getURL() const { return _url; } const QUrl& getURL() const { return _url; }
const QByteArray& getData() const { return _data; }
signals: signals:
/// Fired when the resource has been downloaded. /// Fired when the resource has been downloaded.
/// This can be used instead of downloadFinished to access data before it is processed. /// This can be used instead of downloadFinished to access data before it is processed.
void loaded(const QByteArray& request); void loaded(const QByteArray request);
/// Fired when the resource has finished loading. /// Fired when the resource has finished loading.
void finished(bool success); void finished(bool success);
@ -235,7 +234,6 @@ protected:
QHash<QPointer<QObject>, float> _loadPriorities; QHash<QPointer<QObject>, float> _loadPriorities;
QWeakPointer<Resource> _self; QWeakPointer<Resource> _self;
QPointer<ResourceCache> _cache; QPointer<ResourceCache> _cache;
QByteArray _data;
private slots: private slots:
void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal); void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal);

View file

@ -57,16 +57,18 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) {
} }
void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) { void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
EntitySimulation::removeEntityInternal(entity); if (entity->isSimulated()) {
QMutexLocker lock(&_mutex); EntitySimulation::removeEntityInternal(entity);
_entitiesToAddToPhysics.remove(entity); QMutexLocker lock(&_mutex);
_entitiesToAddToPhysics.remove(entity);
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) { if (motionState) {
_outgoingChanges.remove(motionState); _outgoingChanges.remove(motionState);
_entitiesToRemoveFromPhysics.insert(entity); _entitiesToRemoveFromPhysics.insert(entity);
} else { } else {
_entitiesToDelete.insert(entity); _entitiesToDelete.insert(entity);
}
} }
} }
@ -175,7 +177,7 @@ void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionState
_entitiesToRelease.insert(entity); _entitiesToRelease.insert(entity);
} }
if (entity->isSimulated() && entity->isDead()) { if (entity->isDead()) {
_entitiesToDelete.insert(entity); _entitiesToDelete.insert(entity);
} }
} }
@ -190,7 +192,7 @@ void PhysicalEntitySimulation::deleteObjectsRemovedFromPhysics() {
entity->setPhysicsInfo(nullptr); entity->setPhysicsInfo(nullptr);
delete motionState; delete motionState;
if (entity->isSimulated() && entity->isDead()) { if (entity->isDead()) {
_entitiesToDelete.insert(entity); _entitiesToDelete.insert(entity);
} }
} }

View file

@ -14,8 +14,6 @@
#include <QtQml/QQmlContext> #include <QtQml/QQmlContext>
#include <QtWebChannel/QWebChannel>
#include <QtScript/QScriptContext> #include <QtScript/QScriptContext>
#include <QtScript/QScriptEngine> #include <QtScript/QScriptEngine>
@ -37,29 +35,8 @@ QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngi
} }
QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) { QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) {
_uid = QUuid::createUuid().toString();
asQuickItem()->setProperty("uid", _uid);
auto webchannelVar = qmlWindow->property("webChannel");
_webchannel = qvariant_cast<QWebChannel*>(webchannelVar);
Q_ASSERT(_webchannel);
_webchannel->registerObject(_uid, this);
} }
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 { QString QmlWebWindowClass::getURL() const {
QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant { QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {

View file

@ -11,13 +11,10 @@
#include "QmlWindowClass.h" #include "QmlWindowClass.h"
class QWebChannel;
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
class QmlWebWindowClass : public QmlWindowClass { class QmlWebWindowClass : public QmlWindowClass {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString url READ getURL CONSTANT) Q_PROPERTY(QString url READ getURL CONSTANT)
Q_PROPERTY(QString uid READ getUid CONSTANT)
public: public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
@ -26,18 +23,9 @@ public:
public slots: public slots:
QString getURL() const; QString getURL() const;
void setURL(const QString& url); void setURL(const QString& url);
const QString& getUid() const { return _uid; }
void emitScriptEvent(const QVariant& scriptMessage);
void emitWebEvent(const QVariant& webMessage);
signals: signals:
void urlChanged(); void urlChanged();
void scriptEventReceived(const QVariant& message);
void webEventReceived(const QVariant& message);
private:
QString _uid;
QWebChannel* _webchannel { nullptr };
}; };
#endif #endif

View file

@ -26,6 +26,10 @@
#include "OffscreenUi.h" #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 SOURCE_PROPERTY = "source";
static const char* const TITLE_PROPERTY = "title"; static const char* const TITLE_PROPERTY = "title";
static const char* const WIDTH_PROPERTY = "width"; static const char* const WIDTH_PROPERTY = "width";
@ -33,6 +37,54 @@ static const char* const HEIGHT_PROPERTY = "height";
static const char* const VISIBILE_PROPERTY = "visible"; static const char* const VISIBILE_PROPERTY = "visible";
static const char* const TOOLWINDOW_PROPERTY = "toolWindow"; static const char* const TOOLWINDOW_PROPERTY = "toolWindow";
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, QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
QScriptContext* context, QScriptEngine* engine, QScriptContext* context, QScriptEngine* engine,
std::function<QmlWindowClass*(QObject*)> builder) std::function<QmlWindowClass*(QObject*)> builder)
@ -116,8 +168,10 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
} }
offscreenUi->returnFromUiThread([&] { offscreenUi->returnFromUiThread([&] {
setupServer();
retVal = builder(newTab); retVal = builder(newTab);
retVal->_toolWindow = true; retVal->_toolWindow = true;
registerObject(url.toLower(), retVal);
return QVariant(); return QVariant();
}); });
} else { } else {
@ -125,8 +179,10 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection, QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, qmlSource), Q_ARG(const QString&, qmlSource),
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) { Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
setupServer();
retVal = builder(object); retVal = builder(object);
context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership); context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership);
registerObject(url.toLower(), retVal);
if (!title.isEmpty()) { if (!title.isEmpty()) {
retVal->setTitle(title); retVal->setTitle(title);
} }
@ -153,7 +209,10 @@ QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine*
}); });
} }
QmlWindowClass::QmlWindowClass(QObject* qmlWindow) : _qmlWindow(qmlWindow) { QmlWindowClass::QmlWindowClass(QObject* qmlWindow)
: _windowId(++nextWindowId), _qmlWindow(qmlWindow)
{
qDebug() << "Created window with ID " << _windowId;
Q_ASSERT(_qmlWindow); Q_ASSERT(_qmlWindow);
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow.data())); Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow.data()));
// Forward messages received from QML on to the script // Forward messages received from QML on to the script
@ -169,6 +228,14 @@ QmlWindowClass::~QmlWindowClass() {
close(); 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 { QQuickItem* QmlWindowClass::asQuickItem() const {
if (_toolWindow) { if (_toolWindow) {
return DependencyManager::get<OffscreenUi>()->getToolWindow(); return DependencyManager::get<OffscreenUi>()->getToolWindow();

View file

@ -13,16 +13,38 @@
#include <QtCore/QPointer> #include <QtCore/QPointer>
#include <QtScript/QScriptValue> #include <QtScript/QScriptValue>
#include <QtQuick/QQuickItem> #include <QtQuick/QQuickItem>
#include <QtWebChannel/QWebChannelAbstractTransport>
#include <GLMHelpers.h> #include <GLMHelpers.h>
class QScriptEngine; class QScriptEngine;
class QScriptContext; 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 // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
class QmlWindowClass : public QObject { class QmlWindowClass : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT) 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 position READ getPosition WRITE setPosition NOTIFY positionChanged)
Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged) Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged)
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged)
@ -47,7 +69,8 @@ public slots:
Q_INVOKABLE void raise(); Q_INVOKABLE void raise();
Q_INVOKABLE void close(); Q_INVOKABLE void close();
Q_INVOKABLE QObject* getEventBridge() { return this; }; Q_INVOKABLE int getWindowId() const { return _windowId; };
Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; };
// Scripts can use this to send a message to the QML object // Scripts can use this to send a message to the QML object
void sendToQml(const QVariant& message); void sendToQml(const QVariant& message);
@ -69,12 +92,18 @@ protected:
static QScriptValue internalConstructor(const QString& qmlSource, static QScriptValue internalConstructor(const QString& qmlSource,
QScriptContext* context, QScriptEngine* engine, QScriptContext* context, QScriptEngine* engine,
std::function<QmlWindowClass*(QObject*)> function); std::function<QmlWindowClass*(QObject*)> function);
static void setupServer();
static void registerObject(const QString& name, QObject* object);
static void deregisterObject(QObject* object);
static QWebSocketServer* _webChannelServer;
QQuickItem* asQuickItem() const; QQuickItem* asQuickItem() const;
QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) };
// FIXME needs to be initialized in the ctor once we have support // FIXME needs to be initialized in the ctor once we have support
// for tool window panes in QML // for tool window panes in QML
bool _toolWindow { false }; bool _toolWindow { false };
const int _windowId;
QPointer<QObject> _qmlWindow; QPointer<QObject> _qmlWindow;
QString _source; QString _source;
}; };