From 2c5bdecca9aadb07bcaf63e272ae90d95d6684f1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 31 Aug 2016 11:11:49 -0700 Subject: [PATCH 01/22] Bug fix for touch event crash in qt. This was due to a missing touchDevice(). Also, touch events work for non-full screen qml widgets. --- .../src/RenderableWebEntityItem.cpp | 30 +++++++++++++++---- .../src/RenderableWebEntityItem.h | 1 + 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 4a42df561e..9cd8e4ca35 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -46,6 +46,11 @@ EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemID) : WebEntityItem(entityItemID) { qDebug() << "Created web entity " << getID(); + + _touchDevice.setCapabilities(QTouchDevice::Position); + _touchDevice.setType(QTouchDevice::TouchScreen); + _touchDevice.setName("RenderableWebEntityItemTouchDevice"); + _touchDevice.setMaximumTouchPoints(4); } RenderableWebEntityItem::~RenderableWebEntityItem() { @@ -93,10 +98,19 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) { point.setState(Qt::TouchPointReleased); glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); QPointF windowPoint(windowPos.x, windowPos.y); - point.setPos(windowPoint); + auto item = _webSurface->getRootItem()->childAt(windowPos.x, windowPos.y); + QPointF localPoint = windowPoint; + if (item) { + localPoint = item->mapFromScene(windowPoint); + } + point.setScenePos(windowPoint); + point.setPos(localPoint); QList touchPoints; touchPoints.push_back(point); QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr, Qt::NoModifier, Qt::TouchPointReleased, touchPoints); + touchEvent->setWindow(_webSurface->getWindow()); + touchEvent->setDevice(&_touchDevice); + touchEvent->setTarget(_webSurface->getRootItem()); QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent); } }); @@ -211,6 +225,12 @@ void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) { glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); QPointF windowPoint(windowPos.x, windowPos.y); + auto item = _webSurface->getRootItem()->childAt(windowPos.x, windowPos.y); + QPointF localPoint = windowPoint; + if (item) { + localPoint = item->mapFromScene(windowPoint); + } + if (event.getType() == PointerEvent::Move) { // Forward a mouse move event to webSurface QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, Qt::NoButton, Qt::NoButton, Qt::NoModifier); @@ -246,15 +266,15 @@ void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) { QTouchEvent::TouchPoint point; point.setId(event.getID()); point.setState(touchPointState); - point.setPos(windowPoint); + point.setPos(localPoint); point.setScreenPos(windowPoint); QList touchPoints; touchPoints.push_back(point); QTouchEvent* touchEvent = new QTouchEvent(type); - touchEvent->setWindow(nullptr); - touchEvent->setDevice(nullptr); - touchEvent->setTarget(nullptr); + touchEvent->setWindow(_webSurface->getWindow()); + touchEvent->setDevice(&_touchDevice); + touchEvent->setTarget(_webSurface->getRootItem()); touchEvent->setTouchPoints(touchPoints); touchEvent->setTouchPointStates(touchPointState); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 03234ce690..20c121a2c1 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -58,6 +58,7 @@ private: bool _pressed{ false }; QTouchEvent _lastTouchEvent { QEvent::TouchUpdate }; uint64_t _lastRenderTime{ 0 }; + QTouchDevice _touchDevice; QMetaObject::Connection _mousePressConnection; QMetaObject::Connection _mouseReleaseConnection; From 37d7e926d905d335f0a6262d222cd50e3102ffa1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 31 Aug 2016 11:14:01 -0700 Subject: [PATCH 02/22] first non-working prototype of web entity keyboard. --- interface/resources/qml/controls/Key.qml | 80 +++++++ interface/resources/qml/controls/Keyboard.qml | 208 ++++++++++++++++++ interface/resources/qml/controls/WebView.qml | 117 +++++----- 3 files changed, 355 insertions(+), 50 deletions(-) create mode 100644 interface/resources/qml/controls/Key.qml create mode 100644 interface/resources/qml/controls/Keyboard.qml diff --git a/interface/resources/qml/controls/Key.qml b/interface/resources/qml/controls/Key.qml new file mode 100644 index 0000000000..172d6cb507 --- /dev/null +++ b/interface/resources/qml/controls/Key.qml @@ -0,0 +1,80 @@ +import QtQuick 2.0 + +Item { + id: keyItem + width: 45 + height: 50 + property string glyph: "a" + + + MouseArea { + id: mouseArea1 + anchors.fill: parent + hoverEnabled: true + onEntered: { + keyItem.state = "mouseOver" + } + onExited: { + keyItem.state = "" + } + onPressed: { + keyItem.state = "mouseClicked" + } + onReleased: { + keyItem.state = "" + } + onClicked: { + } + onCanceled: { + keyItem.state = "" + } + } + + Rectangle { + id: roundedRect + color: "#e2e2e2" + radius: 10 + anchors.right: parent.right + anchors.rightMargin: 4 + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.bottom: parent.bottom + anchors.bottomMargin: 4 + anchors.top: parent.top + anchors.topMargin: 4 + } + + Text { + id: letter + width: 50 + text: glyph + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: parent.top + anchors.topMargin: 4 + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 32 + } + + states: [ + State { + name: "mouseOver" + + PropertyChanges { + target: roundedRect + color: "#ffff29" + } + }, + State { + name: "mouseClicked" + PropertyChanges { + target: roundedRect + color: "#e95a52" + } + } + ] +} diff --git a/interface/resources/qml/controls/Keyboard.qml b/interface/resources/qml/controls/Keyboard.qml new file mode 100644 index 0000000000..d895fcf0f1 --- /dev/null +++ b/interface/resources/qml/controls/Keyboard.qml @@ -0,0 +1,208 @@ +import QtQuick 2.0 + +Item { + id: keyboardBase + Rectangle { + id: leftRect + y: 0 + height: 200 + color: "#898989" + anchors.right: keyboardRect.left + anchors.rightMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + } + + Rectangle { + id: keyboardRect + x: 206 + y: 0 + width: 480 + height: 200 + color: "#b6b6b6" + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + + Column { + id: column1 + width: 480 + height: 200 + + Row { + id: row1 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 12 + + Key { + id: key1 + glyph: "q" + } + + Key { + id: key2 + glyph: "w" + } + + Key { + id: key3 + glyph: "e" + } + + Key { + id: key4 + glyph: "r" + } + + Key { + id: key5 + glyph: "t" + } + + Key { + id: key6 + glyph: "y" + } + + Key { + id: key7 + glyph: "u" + } + + Key { + id: key8 + glyph: "i" + } + + Key { + id: key9 + glyph: "o" + } + + Key { + id: key10 + glyph: "p" + } + } + + Row { + id: row2 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 34 + + Key { + id: key11 + } + + Key { + id: key12 + glyph: "s" + } + + Key { + id: key13 + glyph: "d" + } + + Key { + id: key14 + glyph: "f" + } + + Key { + id: key15 + glyph: "g" + } + + Key { + id: key16 + glyph: "h" + } + + Key { + id: key17 + glyph: "j" + } + + Key { + id: key18 + glyph: "k" + } + + Key { + id: key19 + glyph: "l" + } + } + + Row { + id: row3 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 79 + + Key { + id: key20 + glyph: "z" + } + + Key { + id: key21 + glyph: "x" + } + + Key { + id: key22 + glyph: "c" + } + + Key { + id: key23 + glyph: "v" + } + + Key { + id: key24 + glyph: "b" + } + + Key { + id: key25 + glyph: "n" + } + + Key { + id: key26 + glyph: "m" + } + } + + Row { + id: row4 + width: 480 + height: 50 + } + } + } + + Rectangle { + id: rightRect + y: 280 + height: 200 + color: "#8d8d8d" + anchors.left: keyboardRect.right + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + } + +} diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 7285db22d2..8de68d0d00 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -1,68 +1,85 @@ import QtQuick 2.5 import QtWebEngine 1.1 -WebEngineView { - id: root - property var newUrl; +Item { + property alias url: root.url - profile.httpUserAgent: "Mozilla/5.0 Chrome (HighFidelityInterface)" + WebEngineView { + id: root + anchors.fill: parent + property string newUrl: "" - Component.onCompleted: { - console.log("Connecting JS messaging to Hifi Logging") - // Ensure the JS from the web-engine makes it to our logging - root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { - console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); - }); + profile.httpUserAgent: "Mozilla/5.0 Chrome (HighFidelityInterface)" - } + Component.onCompleted: { + console.log("Connecting JS messaging to Hifi Logging") + // Ensure the JS from the web-engine makes it to our logging + root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { + console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); + }); - // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6 - Timer { - id: urlReplacementTimer - running: false - repeat: false - interval: 50 - onTriggered: url = newUrl; - } - - onUrlChanged: { - var originalUrl = url.toString(); - newUrl = urlHandler.fixupUrl(originalUrl).toString(); - if (newUrl !== originalUrl) { - root.stop(); - if (urlReplacementTimer.running) { - console.warn("Replacement timer already running"); - return; - } - urlReplacementTimer.start(); } - } - onFeaturePermissionRequested: { - grantFeaturePermission(securityOrigin, feature, true); - } + // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6 + Timer { + id: urlReplacementTimer + running: false + repeat: false + interval: 50 + onTriggered: url = root.newUrl; + } - onLoadingChanged: { - // Required to support clicking on "hifi://" links - if (WebEngineView.LoadStartedStatus == loadRequest.status) { - var url = loadRequest.url.toString(); - if (urlHandler.canHandleUrl(url)) { - if (urlHandler.handleUrl(url)) { - root.stop(); + onUrlChanged: { + var originalUrl = url.toString(); + root.newUrl = urlHandler.fixupUrl(originalUrl).toString(); + if (root.newUrl !== originalUrl) { + root.stop(); + if (urlReplacementTimer.running) { + console.warn("Replacement timer already running"); + return; + } + urlReplacementTimer.start(); + } + } + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + onLoadingChanged: { + // Required to support clicking on "hifi://" links + if (WebEngineView.LoadStartedStatus == loadRequest.status) { + var url = loadRequest.url.toString(); + if (urlHandler.canHandleUrl(url)) { + if (urlHandler.handleUrl(url)) { + root.stop(); + } } } } - } - onNewViewRequested:{ - if (desktop) { - var component = Qt.createComponent("../Browser.qml"); - var newWindow = component.createObject(desktop); - request.openIn(newWindow.webView); + onNewViewRequested:{ + if (desktop) { + var component = Qt.createComponent("../Browser.qml"); + var newWindow = component.createObject(desktop); + 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 } - // This breaks the webchannel used for passing messages. Fixed in Qt 5.6 - // See https://bugreports.qt.io/browse/QTBUG-49521 - //profile: desktop.browserProfile + Keyboard { + id: keyboard1 + x: 197 + y: 182 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + } } From 823420ae0aacd9585dfd7c0d808fe2e18f097066 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 31 Aug 2016 17:57:13 -0700 Subject: [PATCH 03/22] Working web entity keyboard * Missing many keys * Does not dynamically appear and disappear --- interface/resources/qml/controls/Key.qml | 30 ++++++++++++---- interface/resources/qml/controls/Keyboard.qml | 1 + interface/resources/qml/controls/WebView.qml | 7 +++- .../src/RenderableWebEntityItem.cpp | 34 +++++++++++-------- .../src/RenderableWebEntityItem.h | 18 +++++++++- .../system/controllers/handControllerGrab.js | 3 +- 6 files changed, 70 insertions(+), 23 deletions(-) diff --git a/interface/resources/qml/controls/Key.qml b/interface/resources/qml/controls/Key.qml index 172d6cb507..4d6fb79354 100644 --- a/interface/resources/qml/controls/Key.qml +++ b/interface/resources/qml/controls/Key.qml @@ -11,22 +11,40 @@ Item { id: mouseArea1 anchors.fill: parent hoverEnabled: true + + onCanceled: { + keyItem.state = "" + } + + onClicked: { + mouse.accepted = true + webEntity.synthesizeKeyPress(glyph) + } + + onDoubleClicked: { + mouse.accepted = true + } + onEntered: { keyItem.state = "mouseOver" } + onExited: { keyItem.state = "" } + onPressed: { keyItem.state = "mouseClicked" + mouse.accepted = true } + onReleased: { - keyItem.state = "" - } - onClicked: { - } - onCanceled: { - keyItem.state = "" + if (containsMouse) { + keyItem.state = "mouseOver" + } else { + keyItem.state = "" + } + mouse.accepted = true } } diff --git a/interface/resources/qml/controls/Keyboard.qml b/interface/resources/qml/controls/Keyboard.qml index d895fcf0f1..9b1cb0e277 100644 --- a/interface/resources/qml/controls/Keyboard.qml +++ b/interface/resources/qml/controls/Keyboard.qml @@ -2,6 +2,7 @@ import QtQuick 2.0 Item { id: keyboardBase + height: 200 Rectangle { id: leftRect y: 0 diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 8de68d0d00..99dc9c21c9 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -6,7 +6,12 @@ Item { WebEngineView { id: root - anchors.fill: parent + + x: 0 + y: 0 + width: parent.width + height: parent.height - keyboard1.height + property string newUrl: "" profile.httpUserAgent: "Mozilla/5.0 Chrome (HighFidelityInterface)" diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 9cd8e4ca35..426fd20e02 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -37,6 +37,12 @@ static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND; static int MAX_WINDOW_SIZE = 4096; static float OPAQUE_ALPHA_THRESHOLD = 0.99f; +void WebEntityQMLAPIHelper::synthesizeKeyPress(QString key) { + if (_ptr) { + _ptr->synthesizeKeyPress(key); + } +} + EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity{ new RenderableWebEntityItem(entityID) }; entity->setProperties(properties); @@ -51,9 +57,12 @@ RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemI _touchDevice.setType(QTouchDevice::TouchScreen); _touchDevice.setName("RenderableWebEntityItemTouchDevice"); _touchDevice.setMaximumTouchPoints(4); + + _webEntityQMLAPIHelper.setPtr(this); } RenderableWebEntityItem::~RenderableWebEntityItem() { + _webEntityQMLAPIHelper.setPtr(nullptr); destroyWebSurface(); qDebug() << "Destroyed web entity " << getID(); } @@ -76,6 +85,7 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) { _webSurface->resume(); _webSurface->getRootItem()->setProperty("url", _sourceUrl); _webSurface->getRootContext()->setContextProperty("desktop", QVariant()); + _webSurface->getRootContext()->setContextProperty("webEntity", &_webEntityQMLAPIHelper); _connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) { _texture = textureId; }); @@ -98,13 +108,8 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) { point.setState(Qt::TouchPointReleased); glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); QPointF windowPoint(windowPos.x, windowPos.y); - auto item = _webSurface->getRootItem()->childAt(windowPos.x, windowPos.y); - QPointF localPoint = windowPoint; - if (item) { - localPoint = item->mapFromScene(windowPoint); - } point.setScenePos(windowPoint); - point.setPos(localPoint); + point.setPos(windowPoint); QList touchPoints; touchPoints.push_back(point); QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr, Qt::NoModifier, Qt::TouchPointReleased, touchPoints); @@ -224,13 +229,6 @@ void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) { glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); QPointF windowPoint(windowPos.x, windowPos.y); - - auto item = _webSurface->getRootItem()->childAt(windowPos.x, windowPos.y); - QPointF localPoint = windowPoint; - if (item) { - localPoint = item->mapFromScene(windowPoint); - } - if (event.getType() == PointerEvent::Move) { // Forward a mouse move event to webSurface QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, Qt::NoButton, Qt::NoButton, Qt::NoModifier); @@ -266,7 +264,7 @@ void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) { QTouchEvent::TouchPoint point; point.setId(event.getID()); point.setState(touchPointState); - point.setPos(localPoint); + point.setPos(windowPoint); point.setScreenPos(windowPoint); QList touchPoints; touchPoints.push_back(point); @@ -323,3 +321,11 @@ bool RenderableWebEntityItem::isTransparent() { float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; return fadeRatio < OPAQUE_ALPHA_THRESHOLD; } + +void RenderableWebEntityItem::synthesizeKeyPress(QString key) { + auto utf8Key = key.toUtf8(); + QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, (int)utf8Key[0], Qt::NoModifier, key); + QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, (int)utf8Key[0], Qt::NoModifier, key); + QCoreApplication::postEvent(getEventHandler(), pressEvent); + QCoreApplication::postEvent(getEventHandler(), releaseEvent); +} diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 20c121a2c1..ec8223432c 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -22,6 +22,19 @@ class OffscreenQmlSurface; class QWindow; class QObject; class EntityTreeRenderer; +class RenderableWebEntityItem; + +class WebEntityQMLAPIHelper : public QObject { + Q_OBJECT +public: + void setPtr(RenderableWebEntityItem* ptr) { + _ptr = ptr; + } + Q_INVOKABLE void synthesizeKeyPress(QString key); + +protected: + RenderableWebEntityItem* _ptr{ nullptr }; +}; class RenderableWebEntityItem : public WebEntityItem { public: @@ -46,6 +59,9 @@ public: virtual bool isTransparent() override; +public: + void synthesizeKeyPress(QString key); + private: bool buildWebSurface(EntityTreeRenderer* renderer); void destroyWebSurface(); @@ -59,6 +75,7 @@ private: QTouchEvent _lastTouchEvent { QEvent::TouchUpdate }; uint64_t _lastRenderTime{ 0 }; QTouchDevice _touchDevice; + WebEntityQMLAPIHelper _webEntityQMLAPIHelper; QMetaObject::Connection _mousePressConnection; QMetaObject::Connection _mouseReleaseConnection; @@ -66,5 +83,4 @@ private: QMetaObject::Connection _hoverLeaveConnection; }; - #endif // hifi_RenderableWebEntityItem_h diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 32e0b047de..d285ddf5d8 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -21,7 +21,7 @@ Script.include("/~/system/libraries/Xform.js"); // add lines where the hand ray picking is happening // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = false; +var WANT_DEBUG_STATE = true; var WANT_DEBUG_SEARCH_NAME = null; // @@ -2159,6 +2159,7 @@ function MyController(hand) { }; } else { pointerEvent = this.touchingEnterPointerEvent; + pointerEvent.type = "Release"; pointerEvent.button = "Primary"; pointerEvent.isPrimaryButton = false; } From 21bf9315599d2889ed559e393a404db5a3cff11a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 1 Sep 2016 16:32:06 -0700 Subject: [PATCH 04/22] eventBridge for web entities. --- interface/resources/qml/controls/WebView.qml | 11 ++++++ .../src/RenderableWebEntityItem.cpp | 36 ++++++++++++++++--- .../src/RenderableWebEntityItem.h | 14 ++++++-- libraries/entities/src/EntityItem.h | 2 ++ .../entities/src/EntityScriptingInterface.cpp | 12 +++++++ .../entities/src/EntityScriptingInterface.h | 4 +++ 6 files changed, 73 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 99dc9c21c9..71353932cf 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -1,8 +1,16 @@ import QtQuick 2.5 import QtWebEngine 1.1 +import QtWebChannel 1.0 Item { property alias url: root.url + property alias eventBridge: eventBridgeWrapper.eventBridge + + QtObject { + id: eventBridgeWrapper + WebChannel.id: "eventBridgeWrapper" + property var eventBridge; + } WebEngineView { id: root @@ -16,6 +24,8 @@ Item { profile.httpUserAgent: "Mozilla/5.0 Chrome (HighFidelityInterface)" + webChannel.registeredObjects: [eventBridgeWrapper] + Component.onCompleted: { console.log("Connecting JS messaging to Hifi Logging") // Ensure the JS from the web-engine makes it to our logging @@ -76,6 +86,7 @@ Item { //profile: desktop.browserProfile } + // virtual keyboard Keyboard { id: keyboard1 x: 197 diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 426fd20e02..fb4f020a1b 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -37,12 +37,28 @@ static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND; static int MAX_WINDOW_SIZE = 4096; static float OPAQUE_ALPHA_THRESHOLD = 0.99f; -void WebEntityQMLAPIHelper::synthesizeKeyPress(QString key) { +void WebEntityAPIHelper::synthesizeKeyPress(QString key) { if (_ptr) { _ptr->synthesizeKeyPress(key); } } +void WebEntityAPIHelper::emitScriptEvent(const QVariant& message) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, message)); + } else { + emit scriptEventReceived(message); + } +} + +void WebEntityAPIHelper::emitWebEvent(const QVariant& message) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, message)); + } else { + emit webEventReceived(message); + } +} + EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity{ new RenderableWebEntityItem(entityID) }; entity->setProperties(properties); @@ -58,11 +74,18 @@ RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemI _touchDevice.setName("RenderableWebEntityItemTouchDevice"); _touchDevice.setMaximumTouchPoints(4); - _webEntityQMLAPIHelper.setPtr(this); + _webEntityAPIHelper.setPtr(this); + _webEntityAPIHelper.moveToThread(qApp->thread()); + + // forward web events to EntityScriptingInterface + auto entities = DependencyManager::get(); + QObject::connect(&_webEntityAPIHelper, &WebEntityAPIHelper::webEventReceived, [=](const QVariant& message) { + emit entities->webEventReceived(entityItemID, message); + }); } RenderableWebEntityItem::~RenderableWebEntityItem() { - _webEntityQMLAPIHelper.setPtr(nullptr); + _webEntityAPIHelper.setPtr(nullptr); destroyWebSurface(); qDebug() << "Destroyed web entity " << getID(); } @@ -83,9 +106,10 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) { _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/")); _webSurface->load("WebView.qml"); _webSurface->resume(); + _webSurface->getRootItem()->setProperty("eventBridge", QVariant::fromValue(&_webEntityAPIHelper)); _webSurface->getRootItem()->setProperty("url", _sourceUrl); _webSurface->getRootContext()->setContextProperty("desktop", QVariant()); - _webSurface->getRootContext()->setContextProperty("webEntity", &_webEntityQMLAPIHelper); + _webSurface->getRootContext()->setContextProperty("webEntity", &_webEntityAPIHelper); _connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) { _texture = textureId; }); @@ -329,3 +353,7 @@ void RenderableWebEntityItem::synthesizeKeyPress(QString key) { QCoreApplication::postEvent(getEventHandler(), pressEvent); QCoreApplication::postEvent(getEventHandler(), releaseEvent); } + +void RenderableWebEntityItem::emitScriptEvent(const QVariant& message) { + _webEntityAPIHelper.emitScriptEvent(message); +} diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index ec8223432c..6a19bd12cd 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -24,7 +24,7 @@ class QObject; class EntityTreeRenderer; class RenderableWebEntityItem; -class WebEntityQMLAPIHelper : public QObject { +class WebEntityAPIHelper : public QObject { Q_OBJECT public: void setPtr(RenderableWebEntityItem* ptr) { @@ -32,6 +32,14 @@ public: } Q_INVOKABLE void synthesizeKeyPress(QString key); + // event bridge +public slots: + void emitScriptEvent(const QVariant& scriptMessage); + void emitWebEvent(const QVariant& webMessage); +signals: + void scriptEventReceived(const QVariant& message); + void webEventReceived(const QVariant& message); + protected: RenderableWebEntityItem* _ptr{ nullptr }; }; @@ -55,6 +63,8 @@ public: void update(const quint64& now) override; bool needsToCallUpdate() const override { return _webSurface != nullptr; } + void emitScriptEvent(const QVariant& message); + SIMPLE_RENDERABLE(); virtual bool isTransparent() override; @@ -75,7 +85,7 @@ private: QTouchEvent _lastTouchEvent { QEvent::TouchUpdate }; uint64_t _lastRenderTime{ 0 }; QTouchDevice _touchDevice; - WebEntityQMLAPIHelper _webEntityQMLAPIHelper; + WebEntityAPIHelper _webEntityAPIHelper; QMetaObject::Connection _mousePressConnection; QMetaObject::Connection _mouseReleaseConnection; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index e572bf4de8..a751d76b2a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -446,6 +446,8 @@ public: virtual void setProxyWindow(QWindow* proxyWindow) {} virtual QObject* getEventHandler() { return nullptr; } + virtual void emitScriptEvent(const QVariant& message) {} + protected: void setSimulated(bool simulated) { _simulated = simulated; } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 2dca21ac73..12b9e2fa79 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1289,6 +1289,17 @@ bool EntityScriptingInterface::wantsHandControllerPointerEvents(QUuid id) { return result; } +void EntityScriptingInterface::emitScriptEvent(const EntityItemID& entityID, const QVariant& message) { + if (_entityTree) { + _entityTree->withReadLock([&] { + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); + if (entity) { + entity->emitScriptEvent(message); + } + }); + } +} + float EntityScriptingInterface::calculateCost(float mass, float oldVelocity, float newVelocity) { return std::abs(mass * (newVelocity - oldVelocity)); } @@ -1305,3 +1316,4 @@ float EntityScriptingInterface::getCostMultiplier() { void EntityScriptingInterface::setCostMultiplier(float value) { costMultiplier = value; } + diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index be9b1d27e7..cb69cebe6b 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -205,6 +205,8 @@ public slots: Q_INVOKABLE bool wantsHandControllerPointerEvents(QUuid id); + Q_INVOKABLE void emitScriptEvent(const EntityItemID& entityID, const QVariant& message); + signals: void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); @@ -232,6 +234,8 @@ signals: void clearingEntities(); void debitEnergySource(float value); + void webEventReceived(const EntityItemID& entityItemID, const QVariant& message); + private: bool actionWorker(const QUuid& entityID, std::function actor); bool setVoxels(QUuid entityID, std::function actor); From 442f6207fb6a83ccda0480b0ff02623edf904498 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 2 Sep 2016 11:17:08 -0700 Subject: [PATCH 05/22] Added raiseAndLowerKeyboard detector, streamlined EventBridge for web entities. --- .../resources/html/createGlobalEventBridge.js | 444 ++++++++++++++++++ .../resources/html/raiseAndLowerKeyboard.js | 28 ++ interface/resources/qml/controls/WebView.qml | 22 +- libraries/gl/src/gl/OffscreenQmlSurface.cpp | 1 + 4 files changed, 492 insertions(+), 3 deletions(-) create mode 100644 interface/resources/html/createGlobalEventBridge.js create mode 100644 interface/resources/html/raiseAndLowerKeyboard.js diff --git a/interface/resources/html/createGlobalEventBridge.js b/interface/resources/html/createGlobalEventBridge.js new file mode 100644 index 0000000000..834c8b3792 --- /dev/null +++ b/interface/resources/html/createGlobalEventBridge.js @@ -0,0 +1,444 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtWebChannel module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +"use strict"; + +var QWebChannelMessageTypes = { + signal: 1, + propertyUpdate: 2, + init: 3, + idle: 4, + debug: 5, + invokeMethod: 6, + connectToSignal: 7, + disconnectFromSignal: 8, + setProperty: 9, + response: 10, +}; + +var QWebChannel = function(transport, initCallback) +{ + if (typeof transport !== "object" || typeof transport.send !== "function") { + console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." + + " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send)); + return; + } + + var channel = this; + this.transport = transport; + + this.send = function(data) + { + if (typeof(data) !== "string") { + data = JSON.stringify(data); + } + channel.transport.send(data); + } + + this.transport.onmessage = function(message) + { + var data = message.data; + if (typeof data === "string") { + data = JSON.parse(data); + } + switch (data.type) { + case QWebChannelMessageTypes.signal: + channel.handleSignal(data); + break; + case QWebChannelMessageTypes.response: + channel.handleResponse(data); + break; + case QWebChannelMessageTypes.propertyUpdate: + channel.handlePropertyUpdate(data); + break; + default: + console.error("invalid message received:", message.data); + break; + } + } + + this.execCallbacks = {}; + this.execId = 0; + this.exec = function(data, callback) + { + if (!callback) { + // if no callback is given, send directly + channel.send(data); + return; + } + if (channel.execId === Number.MAX_VALUE) { + // wrap + channel.execId = Number.MIN_VALUE; + } + if (data.hasOwnProperty("id")) { + console.error("Cannot exec message with property id: " + JSON.stringify(data)); + return; + } + data.id = channel.execId++; + channel.execCallbacks[data.id] = callback; + channel.send(data); + }; + + this.objects = {}; + + this.handleSignal = function(message) + { + var object = channel.objects[message.object]; + if (object) { + object.signalEmitted(message.signal, message.args); + } else { + console.warn("Unhandled signal: " + message.object + "::" + message.signal); + } + } + + this.handleResponse = function(message) + { + if (!message.hasOwnProperty("id")) { + console.error("Invalid response message received: ", JSON.stringify(message)); + return; + } + channel.execCallbacks[message.id](message.data); + delete channel.execCallbacks[message.id]; + } + + this.handlePropertyUpdate = function(message) + { + for (var i in message.data) { + var data = message.data[i]; + var object = channel.objects[data.object]; + if (object) { + object.propertyUpdate(data.signals, data.properties); + } else { + console.warn("Unhandled property update: " + data.object + "::" + data.signal); + } + } + channel.exec({type: QWebChannelMessageTypes.idle}); + } + + this.debug = function(message) + { + channel.send({type: QWebChannelMessageTypes.debug, data: message}); + }; + + channel.exec({type: QWebChannelMessageTypes.init}, function(data) { + for (var objectName in data) { + var object = new QObject(objectName, data[objectName], channel); + } + // now unwrap properties, which might reference other registered objects + for (var objectName in channel.objects) { + channel.objects[objectName].unwrapProperties(); + } + if (initCallback) { + initCallback(channel); + } + channel.exec({type: QWebChannelMessageTypes.idle}); + }); +}; + +function QObject(name, data, webChannel) +{ + this.__id__ = name; + webChannel.objects[name] = this; + + // List of callbacks that get invoked upon signal emission + this.__objectSignals__ = {}; + + // Cache of all properties, updated when a notify signal is emitted + this.__propertyCache__ = {}; + + var object = this; + + // ---------------------------------------------------------------------- + + this.unwrapQObject = function(response) + { + if (response instanceof Array) { + // support list of objects + var ret = new Array(response.length); + for (var i = 0; i < response.length; ++i) { + ret[i] = object.unwrapQObject(response[i]); + } + return ret; + } + if (!response + || !response["__QObject*__"] + || response["id"] === undefined) { + return response; + } + + var objectId = response.id; + if (webChannel.objects[objectId]) + return webChannel.objects[objectId]; + + if (!response.data) { + console.error("Cannot unwrap unknown QObject " + objectId + " without data."); + return; + } + + var qObject = new QObject( objectId, response.data, webChannel ); + qObject.destroyed.connect(function() { + if (webChannel.objects[objectId] === qObject) { + delete webChannel.objects[objectId]; + // reset the now deleted QObject to an empty {} object + // just assigning {} though would not have the desired effect, but the + // below also ensures all external references will see the empty map + // NOTE: this detour is necessary to workaround QTBUG-40021 + var propertyNames = []; + for (var propertyName in qObject) { + propertyNames.push(propertyName); + } + for (var idx in propertyNames) { + delete qObject[propertyNames[idx]]; + } + } + }); + // here we are already initialized, and thus must directly unwrap the properties + qObject.unwrapProperties(); + return qObject; + } + + this.unwrapProperties = function() + { + for (var propertyIdx in object.__propertyCache__) { + object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]); + } + } + + function addSignal(signalData, isPropertyNotifySignal) + { + var signalName = signalData[0]; + var signalIndex = signalData[1]; + object[signalName] = { + connect: function(callback) { + if (typeof(callback) !== "function") { + console.error("Bad callback given to connect to signal " + signalName); + return; + } + + object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; + object.__objectSignals__[signalIndex].push(callback); + + if (!isPropertyNotifySignal && signalName !== "destroyed") { + // only required for "pure" signals, handled separately for properties in propertyUpdate + // also note that we always get notified about the destroyed signal + webChannel.exec({ + type: QWebChannelMessageTypes.connectToSignal, + object: object.__id__, + signal: signalIndex + }); + } + }, + disconnect: function(callback) { + if (typeof(callback) !== "function") { + console.error("Bad callback given to disconnect from signal " + signalName); + return; + } + object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; + var idx = object.__objectSignals__[signalIndex].indexOf(callback); + if (idx === -1) { + console.error("Cannot find connection of signal " + signalName + " to " + callback.name); + return; + } + object.__objectSignals__[signalIndex].splice(idx, 1); + if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) { + // only required for "pure" signals, handled separately for properties in propertyUpdate + webChannel.exec({ + type: QWebChannelMessageTypes.disconnectFromSignal, + object: object.__id__, + signal: signalIndex + }); + } + } + }; + } + + /** + * Invokes all callbacks for the given signalname. Also works for property notify callbacks. + */ + function invokeSignalCallbacks(signalName, signalArgs) + { + var connections = object.__objectSignals__[signalName]; + if (connections) { + connections.forEach(function(callback) { + callback.apply(callback, signalArgs); + }); + } + } + + this.propertyUpdate = function(signals, propertyMap) + { + // update property cache + for (var propertyIndex in propertyMap) { + var propertyValue = propertyMap[propertyIndex]; + object.__propertyCache__[propertyIndex] = propertyValue; + } + + for (var signalName in signals) { + // Invoke all callbacks, as signalEmitted() does not. This ensures the + // property cache is updated before the callbacks are invoked. + invokeSignalCallbacks(signalName, signals[signalName]); + } + } + + this.signalEmitted = function(signalName, signalArgs) + { + invokeSignalCallbacks(signalName, signalArgs); + } + + function addMethod(methodData) + { + var methodName = methodData[0]; + var methodIdx = methodData[1]; + object[methodName] = function() { + var args = []; + var callback; + for (var i = 0; i < arguments.length; ++i) { + if (typeof arguments[i] === "function") + callback = arguments[i]; + else + args.push(arguments[i]); + } + + webChannel.exec({ + "type": QWebChannelMessageTypes.invokeMethod, + "object": object.__id__, + "method": methodIdx, + "args": args + }, function(response) { + if (response !== undefined) { + var result = object.unwrapQObject(response); + if (callback) { + (callback)(result); + } + } + }); + }; + } + + function bindGetterSetter(propertyInfo) + { + var propertyIndex = propertyInfo[0]; + var propertyName = propertyInfo[1]; + var notifySignalData = propertyInfo[2]; + // initialize property cache with current value + // NOTE: if this is an object, it is not directly unwrapped as it might + // reference other QObject that we do not know yet + object.__propertyCache__[propertyIndex] = propertyInfo[3]; + + if (notifySignalData) { + if (notifySignalData[0] === 1) { + // signal name is optimized away, reconstruct the actual name + notifySignalData[0] = propertyName + "Changed"; + } + addSignal(notifySignalData, true); + } + + Object.defineProperty(object, propertyName, { + get: function () { + var propertyValue = object.__propertyCache__[propertyIndex]; + if (propertyValue === undefined) { + // This shouldn't happen + console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__); + } + + return propertyValue; + }, + set: function(value) { + if (value === undefined) { + console.warn("Property setter for " + propertyName + " called with undefined value!"); + return; + } + object.__propertyCache__[propertyIndex] = value; + webChannel.exec({ + "type": QWebChannelMessageTypes.setProperty, + "object": object.__id__, + "property": propertyIndex, + "value": value + }); + } + }); + + } + + // ---------------------------------------------------------------------- + + data.methods.forEach(addMethod); + + data.properties.forEach(bindGetterSetter); + + data.signals.forEach(function(signal) { addSignal(signal, false); }); + + for (var name in data.enums) { + object[name] = data.enums[name]; + } +} + +//required for use with nodejs +if (typeof module === 'object') { + module.exports = { + QWebChannel: QWebChannel + }; +} + +// Stick a EventBridge object in the global namespace. +var EventBridge; +(function () { + // the TempEventBridge class queues up emitWebEvent messages and executes them when the real EventBridge is ready. + // Similarly, it holds all scriptEventReceived callbacks, and hooks them up to the real EventBridge. + function TempEventBridge() { + var self = this; + this._callbacks = []; + this._messages = []; + this.scriptEventReceived = { + connect: function (cb) { self._callbacks.push(cb); } + }; + this.emitWebEvent = function (message) { + self._messages.push(message); + } + }; + + EventBridge = new TempEventBridge(); + + var webChannel = new QWebChannel(qt.webChannelTransport, function (channel) { + // replace the TempEventBridge with the real one. + var tempEventBridge = EventBridge; + EventBridge = channel.objects.eventBridgeWrapper.eventBridge; + tempEventBridge._callbacks.forEach(function (cb) { + EventBridge.scriptEventReceived.connect(cb); + }); + tempEventBridge._messages.forEach(function (msg) { + EventBridge.emitWebEvent(msg); + }); + }); +})(); diff --git a/interface/resources/html/raiseAndLowerKeyboard.js b/interface/resources/html/raiseAndLowerKeyboard.js new file mode 100644 index 0000000000..18b3c44453 --- /dev/null +++ b/interface/resources/html/raiseAndLowerKeyboard.js @@ -0,0 +1,28 @@ +// +// Created by Anthony Thibault on 2016-09-02 +// Copyright 2016 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 +// +// Sends messages over the EventBridge when text input is required. +// +(function () { + var POLL_FREQUENCY = 500; // ms + var lastRaiseKeyboard = false; + function shouldRaiseKeyboard() { + return document.activeElement.nodeName == "INPUT" || document.activeElement.nodeName == "TEXTAREA"; + }; + setInterval(function () { + var newRaiseKeyboard = shouldRaiseKeyboard(); + if (newRaiseKeyboard != lastRaiseKeyboard) { + var event = newRaiseKeyboard ? "_RAISE_KEYBOARD" : "_LOWER_KEYBOARD"; + if (typeof EventBridge != "undefined") { + EventBridge.emitWebEvent(event); + } else { + console.log("WARNING: no global EventBridge object found"); + } + lastRaiseKeyboard = newRaiseKeyboard; + } + }, POLL_FREQUENCY); +})(); diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 71353932cf..99b2bda237 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -14,12 +14,29 @@ Item { WebEngineView { id: root - x: 0 y: 0 width: parent.width height: parent.height - keyboard1.height + // creates a global EventBridge object. + WebEngineScript { + id: createGlobalEventBridge + sourceUrl: resourceDirectoryUrl + "/html/createGlobalEventBridge.js" + injectionPoint: WebEngineScript.DocumentCreation + worldId: WebEngineScript.MainWorld + } + + // detects when to raise and lower virtual keyboard + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } + + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] + property string newUrl: "" profile.httpUserAgent: "Mozilla/5.0 Chrome (HighFidelityInterface)" @@ -30,9 +47,8 @@ Item { console.log("Connecting JS messaging to Hifi Logging") // Ensure the JS from the web-engine makes it to our logging root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { - console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); + console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); }); - } // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6 diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 7973ed4b4f..5ab2678474 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -479,6 +479,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { auto rootContext = getRootContext(); rootContext->setContextProperty("urlHandler", new UrlHandler()); + rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath())); } void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) { From 3d5e8fc213da50f3d0760ee81476cccd87c70585 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 2 Sep 2016 12:15:30 -0700 Subject: [PATCH 06/22] First pass of keyboard raising and lowering --- .../resources/html/raiseAndLowerKeyboard.js | 18 ++++++++++-------- interface/resources/qml/controls/WebView.qml | 12 ++++++++---- .../src/RenderableWebEntityItem.cpp | 13 ++++++++++++- .../src/RenderableWebEntityItem.h | 1 + .../system/controllers/handControllerGrab.js | 2 +- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/interface/resources/html/raiseAndLowerKeyboard.js b/interface/resources/html/raiseAndLowerKeyboard.js index 18b3c44453..5190950332 100644 --- a/interface/resources/html/raiseAndLowerKeyboard.js +++ b/interface/resources/html/raiseAndLowerKeyboard.js @@ -9,20 +9,22 @@ // (function () { var POLL_FREQUENCY = 500; // ms - var lastRaiseKeyboard = false; + var MAX_WARNINGS = 3; + var numWarnings = 0; + function shouldRaiseKeyboard() { return document.activeElement.nodeName == "INPUT" || document.activeElement.nodeName == "TEXTAREA"; }; + setInterval(function () { - var newRaiseKeyboard = shouldRaiseKeyboard(); - if (newRaiseKeyboard != lastRaiseKeyboard) { - var event = newRaiseKeyboard ? "_RAISE_KEYBOARD" : "_LOWER_KEYBOARD"; - if (typeof EventBridge != "undefined") { - EventBridge.emitWebEvent(event); - } else { + var event = shouldRaiseKeyboard() ? "_RAISE_KEYBOARD" : "_LOWER_KEYBOARD"; + if (typeof EventBridge != "undefined") { + EventBridge.emitWebEvent(event); + } else { + if (numWarnings < MAX_WARNINGS) { console.log("WARNING: no global EventBridge object found"); + numWarnings++; } - lastRaiseKeyboard = newRaiseKeyboard; } }, POLL_FREQUENCY); })(); diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 99b2bda237..9eb079bfaa 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -5,6 +5,7 @@ import QtWebChannel 1.0 Item { property alias url: root.url property alias eventBridge: eventBridgeWrapper.eventBridge + property bool keyboardRaised: false QtObject { id: eventBridgeWrapper @@ -17,7 +18,7 @@ Item { x: 0 y: 0 width: parent.width - height: parent.height - keyboard1.height + height: keyboardRaised ? parent.height - keyboard.height : parent.height // creates a global EventBridge object. WebEngineScript { @@ -61,6 +62,7 @@ Item { } onUrlChanged: { + keyboardRaised = false; var originalUrl = url.toString(); root.newUrl = urlHandler.fixupUrl(originalUrl).toString(); if (root.newUrl !== originalUrl) { @@ -104,9 +106,11 @@ Item { // virtual keyboard Keyboard { - id: keyboard1 - x: 197 - y: 182 + id: keyboard + y: keyboardRaised ? parent.height : 0 + height: keyboardRaised ? 200 : 0 + visible: keyboardRaised + enabled: keyboardRaised anchors.right: parent.right anchors.rightMargin: 0 anchors.left: parent.left diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index fb4f020a1b..e3c5dac2c0 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -55,7 +55,14 @@ void WebEntityAPIHelper::emitWebEvent(const QVariant& message) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, message)); } else { - emit webEventReceived(message); + // special case to handle raising and lowering the virtual keyboard + if (message.type() == QVariant::String && message.toString() == "_RAISE_KEYBOARD" && _ptr) { + _ptr->setKeyboardRaised(true); + } else if (message.type() == QVariant::String && message.toString() == "_LOWER_KEYBOARD" && _ptr) { + _ptr->setKeyboardRaised(false); + } else { + emit webEventReceived(message); + } } } @@ -357,3 +364,7 @@ void RenderableWebEntityItem::synthesizeKeyPress(QString key) { void RenderableWebEntityItem::emitScriptEvent(const QVariant& message) { _webEntityAPIHelper.emitScriptEvent(message); } + +void RenderableWebEntityItem::setKeyboardRaised(bool raised) { + _webSurface->getRootItem()->setProperty("keyboardRaised", QVariant(raised)); +} diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 6a19bd12cd..9cf661aa70 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -64,6 +64,7 @@ public: bool needsToCallUpdate() const override { return _webSurface != nullptr; } void emitScriptEvent(const QVariant& message); + void setKeyboardRaised(bool raised); SIMPLE_RENDERABLE(); diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index d285ddf5d8..f99cec28cd 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -21,7 +21,7 @@ Script.include("/~/system/libraries/Xform.js"); // add lines where the hand ray picking is happening // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = true; +var WANT_DEBUG_STATE = false; var WANT_DEBUG_SEARCH_NAME = null; // From bcc0d27819b16fb44795ecfcb7cb2776f87a7de0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 2 Sep 2016 12:49:55 -0700 Subject: [PATCH 07/22] more reliable keyboard raise/lower --- interface/resources/qml/controls/WebView.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 9eb079bfaa..d958e1c774 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -62,7 +62,6 @@ Item { } onUrlChanged: { - keyboardRaised = false; var originalUrl = url.toString(); root.newUrl = urlHandler.fixupUrl(originalUrl).toString(); if (root.newUrl !== originalUrl) { @@ -80,6 +79,8 @@ Item { } onLoadingChanged: { + keyboardRaised = false; + // Required to support clicking on "hifi://" links if (WebEngineView.LoadStartedStatus == loadRequest.status) { var url = loadRequest.url.toString(); From a54c3bf8c046470057956354f0f3540d178515a2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 2 Sep 2016 16:23:46 -0700 Subject: [PATCH 08/22] support for backspace, carriage return, space and period. --- interface/resources/qml/controls/Keyboard.qml | 54 ++++++++++++++++++- .../src/RenderableWebEntityItem.cpp | 34 +++++++++++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/controls/Keyboard.qml b/interface/resources/qml/controls/Keyboard.qml index 9b1cb0e277..4bd5bd2ece 100644 --- a/interface/resources/qml/controls/Keyboard.qml +++ b/interface/resources/qml/controls/Keyboard.qml @@ -147,7 +147,13 @@ Item { width: 480 height: 50 anchors.left: parent.left - anchors.leftMargin: 79 + anchors.leftMargin: 0 + + Key { + id: key27 + width: 79 + glyph: "⇪" + } Key { id: key20 @@ -183,12 +189,45 @@ Item { id: key26 glyph: "m" } + + Key { + id: key28 + width: 85 + glyph: "←" + } + } Row { id: row4 width: 480 height: 50 + anchors.left: parent.left + anchors.leftMargin: 59 + + Key { + id: key30 + width: 45 + glyph: "⁂" + } + + Key { + id: key29 + width: 200 + glyph: " " + } + + Key { + id: key31 + glyph: "." + } + + Key { + id: key32 + width: 65 + glyph: "⏎" + } + } } } @@ -206,4 +245,17 @@ Item { anchors.bottomMargin: 0 } + Rectangle { + id: rectangle1 + color: "#ffffff" + anchors.bottom: keyboardRect.top + anchors.bottomMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + } + } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index e3c5dac2c0..568bf7c787 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -353,10 +353,40 @@ bool RenderableWebEntityItem::isTransparent() { return fadeRatio < OPAQUE_ALPHA_THRESHOLD; } +// UTF-8 encoded symbols +static const uint8_t UPWARDS_WHITE_ARROW_FROM_BAR[] = { 0xE2, 0x87, 0xAA, 0x00 }; // shift +static const uint8_t ERASE_TO_THE_LEFT[] = { 0xE2, 0x8C, 0xAB, 0x00 }; // backspace +static const uint8_t LONG_LEFTWARDS_ARROW[] = { 0xE2, 0x9F, 0xB5, 0x00 }; // backspace +static const uint8_t LEFT_ARROW[] = { 0xE2, 0x86, 0x90 }; // backspace +static const uint8_t ASTERISIM[] = { 0xE2, 0x81, 0x82, 0x00 }; // symbols +static const uint8_t RETURN_SYMBOL[] = { 0xE2, 0x8F, 0x8E, 0x00 }; // return + +static bool equals(const QByteArray& byteArray, const uint8_t* ptr) { + for (int i = 0; i < byteArray.size(); i++) { + if ((char)ptr[i] != byteArray[i]) { + return false; + } + } + return true; +} + void RenderableWebEntityItem::synthesizeKeyPress(QString key) { auto utf8Key = key.toUtf8(); - QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, (int)utf8Key[0], Qt::NoModifier, key); - QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, (int)utf8Key[0], Qt::NoModifier, key); + + int scanCode = (int)utf8Key[0]; + QString keyString = key; + if (equals(utf8Key, UPWARDS_WHITE_ARROW_FROM_BAR) || equals(utf8Key, ASTERISIM)) { + return; // ignore + } else if (equals(utf8Key, ERASE_TO_THE_LEFT) || equals(utf8Key, LONG_LEFTWARDS_ARROW) | equals(utf8Key, LEFT_ARROW)) { + scanCode = Qt::Key_Backspace; + keyString = "\x08"; + } else if (equals(utf8Key, RETURN_SYMBOL)) { + scanCode = Qt::Key_Return; + keyString = "\x0d"; + } + + QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, scanCode, Qt::NoModifier, keyString); + QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, scanCode, Qt::NoModifier, keyString); QCoreApplication::postEvent(getEventHandler(), pressEvent); QCoreApplication::postEvent(getEventHandler(), releaseEvent); } From 9122a33fffcc4ba8bd3d6aa312664dde400e7d1e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 2 Sep 2016 16:53:40 -0700 Subject: [PATCH 09/22] only raise the webEntity virtual keyboard in HMD mode --- interface/src/Application.h | 2 +- libraries/entities-renderer/src/RenderableWebEntityItem.cpp | 6 +++++- libraries/render-utils/src/AbstractViewStateInterface.h | 2 ++ tests/render-perf/src/main.cpp | 4 ++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.h b/interface/src/Application.h index a0c67a9e73..b8ba83afd7 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -223,7 +223,7 @@ public: // the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display // rendering of several elements depend on that // TODO: carry that information on the Camera as a setting - bool isHMDMode() const; + virtual bool isHMDMode() const override; glm::mat4 getHMDSensorPose() const; glm::mat4 getEyeOffset(int eye) const; glm::mat4 getEyeProjection(int eye) const; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 568bf7c787..f25ed55800 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -396,5 +396,9 @@ void RenderableWebEntityItem::emitScriptEvent(const QVariant& message) { } void RenderableWebEntityItem::setKeyboardRaised(bool raised) { - _webSurface->getRootItem()->setProperty("keyboardRaised", QVariant(raised)); + + // raise the keyboard only while in HMD mode and it's being requested. + bool value = AbstractViewStateInterface::instance()->isHMDMode() && raised; + + _webSurface->getRootItem()->setProperty("keyboardRaised", QVariant(value)); } diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index 65fa693914..362c0cc1bf 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -48,6 +48,8 @@ public: virtual void pushPostUpdateLambda(void* key, std::function func) = 0; + virtual bool isHMDMode() const = 0; + // FIXME - we shouldn't assume that there's a single instance of an AbstractViewStateInterface static AbstractViewStateInterface* instance(); static void setInstance(AbstractViewStateInterface* instance); diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index c6cca74c69..b5a702447a 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -497,6 +497,10 @@ protected: _postUpdateLambdas[key] = func; } + bool isHMDMode() const override { + return false; + } + public: //"/-17.2049,-8.08629,-19.4153/0,0.881994,0,-0.47126" static void setup() { From 787bb58c1a7a086f45fb910767a86196ef2862ab Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 2 Sep 2016 17:22:13 -0700 Subject: [PATCH 10/22] osx warning fix --- libraries/entities-renderer/src/RenderableWebEntityItem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 9cf661aa70..5e06440bcb 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -63,7 +63,7 @@ public: void update(const quint64& now) override; bool needsToCallUpdate() const override { return _webSurface != nullptr; } - void emitScriptEvent(const QVariant& message); + virtual void emitScriptEvent(const QVariant& message) override; void setKeyboardRaised(bool raised); SIMPLE_RENDERABLE(); From 71520a661a20a613cbeb4a2fa3d2edc14608e978 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 6 Sep 2016 13:39:07 -0700 Subject: [PATCH 11/22] WIP checkpoint of keyboard shift key support --- interface/resources/qml/controls/Key.qml | 31 +++++++++++++++++-- interface/resources/qml/controls/Keyboard.qml | 4 +++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/controls/Key.qml b/interface/resources/qml/controls/Key.qml index 4d6fb79354..d70a756445 100644 --- a/interface/resources/qml/controls/Key.qml +++ b/interface/resources/qml/controls/Key.qml @@ -5,7 +5,8 @@ Item { width: 45 height: 50 property string glyph: "a" - + property bool toggle: false // does this button have the toggle behaivor? + property bool toggled: false // is this button currently toggled? MouseArea { id: mouseArea1 @@ -19,6 +20,9 @@ Item { onClicked: { mouse.accepted = true webEntity.synthesizeKeyPress(glyph) + if (toggle) { + toggled = !toggled + } } onDoubleClicked: { @@ -30,7 +34,11 @@ Item { } onExited: { - keyItem.state = "" + if (toggled) { + keyItem.state = "mouseDepressed" + } else { + keyItem.state = "" + } } onPressed: { @@ -42,7 +50,11 @@ Item { if (containsMouse) { keyItem.state = "mouseOver" } else { - keyItem.state = "" + if (toggled) { + keyItem.state = "mouseDepressed" + } else { + keyItem.state = "" + } } mouse.accepted = true } @@ -93,6 +105,19 @@ Item { target: roundedRect color: "#e95a52" } + }, + State { + name: "mouseDepressed" + + PropertyChanges { + target: roundedRect + color: "#393939" + } + + PropertyChanges { + target: letter + color: "#fbfbfb" + } } ] } diff --git a/interface/resources/qml/controls/Keyboard.qml b/interface/resources/qml/controls/Keyboard.qml index 4bd5bd2ece..4a2a270769 100644 --- a/interface/resources/qml/controls/Keyboard.qml +++ b/interface/resources/qml/controls/Keyboard.qml @@ -153,6 +153,10 @@ Item { id: key27 width: 79 glyph: "⇪" + toggle: true + onToggledChanged: { + console.log("AJT: toggled" + toggled); + } } Key { From 28162915fd572163d310664a401c5ea1717d58d8 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 6 Sep 2016 15:21:58 -0700 Subject: [PATCH 12/22] Re-styled keyboard, now with working shift key --- interface/resources/qml/controls/Key.qml | 63 ++++++--- interface/resources/qml/controls/Keyboard.qml | 128 +++++++++++++++--- .../src/RenderableWebEntityItem.cpp | 12 +- 3 files changed, 166 insertions(+), 37 deletions(-) diff --git a/interface/resources/qml/controls/Key.qml b/interface/resources/qml/controls/Key.qml index d70a756445..bf018290f7 100644 --- a/interface/resources/qml/controls/Key.qml +++ b/interface/resources/qml/controls/Key.qml @@ -10,11 +10,16 @@ Item { MouseArea { id: mouseArea1 + width: 36 anchors.fill: parent hoverEnabled: true onCanceled: { - keyItem.state = "" + if (toggled) { + keyItem.state = "mouseDepressed" + } else { + keyItem.state = "" + } } onClicked: { @@ -62,8 +67,10 @@ Item { Rectangle { id: roundedRect - color: "#e2e2e2" - radius: 10 + width: 30 + color: "#121212" + radius: 2 + border.color: "#00000000" anchors.right: parent.right anchors.rightMargin: 4 anchors.left: parent.left @@ -76,8 +83,12 @@ Item { Text { id: letter + y: 6 width: 50 + color: "#ffffff" text: glyph + style: Text.Normal + font.family: "Tahoma" anchors.right: parent.right anchors.rightMargin: 0 anchors.left: parent.left @@ -85,9 +96,9 @@ Item { anchors.bottom: parent.bottom anchors.bottomMargin: 0 anchors.top: parent.top - anchors.topMargin: 4 + anchors.topMargin: 8 horizontalAlignment: Text.AlignHCenter - font.pixelSize: 32 + font.pixelSize: 28 } states: [ @@ -96,27 +107,47 @@ Item { PropertyChanges { target: roundedRect - color: "#ffff29" + color: "#121212" + radius: 3 + border.width: 2 + border.color: "#00b4ef" + } + + PropertyChanges { + target: letter + color: "#00b4ef" + style: Text.Normal } }, State { name: "mouseClicked" PropertyChanges { target: roundedRect - color: "#e95a52" - } - }, - State { - name: "mouseDepressed" - - PropertyChanges { - target: roundedRect - color: "#393939" + color: "#1080b8" + border.width: 2 + border.color: "#00b4ef" } PropertyChanges { target: letter - color: "#fbfbfb" + color: "#121212" + styleColor: "#00000000" + style: Text.Normal + } + }, + State { + name: "mouseDepressed" + PropertyChanges { + target: roundedRect + color: "#0578b1" + border.width: 0 + } + + PropertyChanges { + target: letter + color: "#121212" + styleColor: "#00000000" + style: Text.Normal } } ] diff --git a/interface/resources/qml/controls/Keyboard.qml b/interface/resources/qml/controls/Keyboard.qml index 4a2a270769..55310a815e 100644 --- a/interface/resources/qml/controls/Keyboard.qml +++ b/interface/resources/qml/controls/Keyboard.qml @@ -3,11 +3,36 @@ import QtQuick 2.0 Item { id: keyboardBase height: 200 + + function toUpper(str) { + if (str === ",") { + return "<"; + } else if (str === ".") { + return ">"; + } else if (str === "/") { + return "?"; + } else { + return str.toUpperCase(str); + } + } + + function toLower(str) { + if (str === "<") { + return ","; + } else if (str === ">") { + return "."; + } else if (str === "?") { + return "/"; + } else { + return str.toLowerCase(str); + } + } + Rectangle { id: leftRect y: 0 height: 200 - color: "#898989" + color: "#252525" anchors.right: keyboardRect.left anchors.rightMargin: 0 anchors.bottom: parent.bottom @@ -22,7 +47,7 @@ Item { y: 0 width: 480 height: 200 - color: "#b6b6b6" + color: "#252525" anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: 0 @@ -37,57 +62,73 @@ Item { width: 480 height: 50 anchors.left: parent.left - anchors.leftMargin: 12 + anchors.leftMargin: 0 Key { id: key1 + width: 44 glyph: "q" } Key { id: key2 + width: 44 glyph: "w" } Key { id: key3 + width: 44 glyph: "e" } Key { id: key4 + width: 43 glyph: "r" } Key { id: key5 + width: 43 glyph: "t" } Key { id: key6 + width: 44 glyph: "y" } Key { id: key7 + width: 44 glyph: "u" } Key { id: key8 + width: 43 glyph: "i" } Key { id: key9 + width: 42 glyph: "o" } Key { id: key10 + width: 44 glyph: "p" } + + Key { + id: key28 + width: 45 + glyph: "←" + } } Row { @@ -95,51 +136,66 @@ Item { width: 480 height: 50 anchors.left: parent.left - anchors.leftMargin: 34 + anchors.leftMargin: 18 Key { id: key11 + width: 43 } Key { id: key12 + width: 43 glyph: "s" } Key { id: key13 + width: 43 glyph: "d" } Key { id: key14 + width: 43 glyph: "f" } Key { id: key15 + width: 43 glyph: "g" } Key { id: key16 + width: 43 glyph: "h" } Key { id: key17 + width: 43 glyph: "j" } Key { id: key18 + width: 43 glyph: "k" } Key { id: key19 + width: 43 glyph: "l" } + + Key { + id: key32 + width: 75 + glyph: "⏎" + } } Row { @@ -151,53 +207,83 @@ Item { Key { id: key27 - width: 79 + width: 46 glyph: "⇪" toggle: true onToggledChanged: { - console.log("AJT: toggled" + toggled); + var i, j; + for (i = 0; i < column1.children.length; i++) { + var row = column1.children[i]; + for (j = 0; j < row.children.length; j++) { + var key = row.children[j]; + if (toggled) { + key.glyph = keyboardBase.toUpper(key.glyph); + } else { + key.glyph = keyboardBase.toLower(key.glyph); + } + } + } } } Key { id: key20 + width: 43 glyph: "z" } Key { id: key21 + width: 43 glyph: "x" } Key { id: key22 + width: 43 glyph: "c" } Key { id: key23 + width: 43 glyph: "v" } Key { id: key24 + width: 43 glyph: "b" } Key { id: key25 + width: 43 glyph: "n" } Key { id: key26 + width: 44 glyph: "m" } Key { - id: key28 - width: 85 - glyph: "←" + id: key31 + width: 43 + glyph: "," + } + + Key { + id: key33 + width: 43 + glyph: "." + } + + Key { + id: key36 + width: 46 + glyph: "/" } } @@ -207,29 +293,34 @@ Item { width: 480 height: 50 anchors.left: parent.left - anchors.leftMargin: 59 + anchors.leftMargin: 19 Key { id: key30 - width: 45 + width: 44 glyph: "⁂" } Key { id: key29 - width: 200 + width: 285 glyph: " " } Key { - id: key31 - glyph: "." + id: key34 + width: 43 + glyph: "⇦" } Key { - id: key32 - width: 65 - glyph: "⏎" + id: key35 + x: 343 + width: 43 + antialiasing: false + scale: 1 + transformOrigin: Item.Center + glyph: "⇨" } } @@ -240,7 +331,8 @@ Item { id: rightRect y: 280 height: 200 - color: "#8d8d8d" + color: "#252525" + border.width: 0 anchors.left: keyboardRect.right anchors.leftMargin: 0 anchors.right: parent.right diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f25ed55800..120949ad36 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -355,9 +355,9 @@ bool RenderableWebEntityItem::isTransparent() { // UTF-8 encoded symbols static const uint8_t UPWARDS_WHITE_ARROW_FROM_BAR[] = { 0xE2, 0x87, 0xAA, 0x00 }; // shift -static const uint8_t ERASE_TO_THE_LEFT[] = { 0xE2, 0x8C, 0xAB, 0x00 }; // backspace -static const uint8_t LONG_LEFTWARDS_ARROW[] = { 0xE2, 0x9F, 0xB5, 0x00 }; // backspace static const uint8_t LEFT_ARROW[] = { 0xE2, 0x86, 0x90 }; // backspace +static const uint8_t LEFTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA6 }; // left arrow +static const uint8_t RIGHTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA8 }; // right arrow static const uint8_t ASTERISIM[] = { 0xE2, 0x81, 0x82, 0x00 }; // symbols static const uint8_t RETURN_SYMBOL[] = { 0xE2, 0x8F, 0x8E, 0x00 }; // return @@ -377,12 +377,18 @@ void RenderableWebEntityItem::synthesizeKeyPress(QString key) { QString keyString = key; if (equals(utf8Key, UPWARDS_WHITE_ARROW_FROM_BAR) || equals(utf8Key, ASTERISIM)) { return; // ignore - } else if (equals(utf8Key, ERASE_TO_THE_LEFT) || equals(utf8Key, LONG_LEFTWARDS_ARROW) | equals(utf8Key, LEFT_ARROW)) { + } else if (equals(utf8Key, LEFT_ARROW)) { scanCode = Qt::Key_Backspace; keyString = "\x08"; } else if (equals(utf8Key, RETURN_SYMBOL)) { scanCode = Qt::Key_Return; keyString = "\x0d"; + } else if (equals(utf8Key, LEFTWARD_WHITE_ARROW)) { + scanCode = Qt::Key_Left; + keyString = ""; + } else if (equals(utf8Key, RIGHTWARD_WHITE_ARROW)) { + scanCode = Qt::Key_Right; + keyString = ""; } QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, scanCode, Qt::NoModifier, keyString); From 73315e3390c53c596822bbfeaeb69362d15c91fd Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 6 Sep 2016 17:07:41 -0700 Subject: [PATCH 13/22] Punctuation mode now works --- interface/resources/qml/controls/Key.qml | 10 + interface/resources/qml/controls/Keyboard.qml | 41 ++- .../qml/controls/KeyboardPunctuation.qml | 324 ++++++++++++++++++ interface/resources/qml/controls/WebView.qml | 27 +- 4 files changed, 382 insertions(+), 20 deletions(-) create mode 100644 interface/resources/qml/controls/KeyboardPunctuation.qml diff --git a/interface/resources/qml/controls/Key.qml b/interface/resources/qml/controls/Key.qml index bf018290f7..164b5c2f5b 100644 --- a/interface/resources/qml/controls/Key.qml +++ b/interface/resources/qml/controls/Key.qml @@ -7,6 +7,7 @@ Item { property string glyph: "a" property bool toggle: false // does this button have the toggle behaivor? property bool toggled: false // is this button currently toggled? + property alias mouseArea: mouseArea1 MouseArea { id: mouseArea1 @@ -14,6 +15,15 @@ Item { anchors.fill: parent hoverEnabled: true + function resetToggledMode(mode) { + toggled: mode + if (toggled) { + keyItem.state = "mouseDepressed" + } else { + keyItem.state = "" + } + } + onCanceled: { if (toggled) { keyItem.state = "mouseDepressed" diff --git a/interface/resources/qml/controls/Keyboard.qml b/interface/resources/qml/controls/Keyboard.qml index 55310a815e..cfa5e857a4 100644 --- a/interface/resources/qml/controls/Keyboard.qml +++ b/interface/resources/qml/controls/Keyboard.qml @@ -3,6 +3,13 @@ import QtQuick 2.0 Item { id: keyboardBase height: 200 + property alias shiftKey: key27 + property bool shiftMode: false + + function resetShiftMode(mode) { + shiftMode = mode + key23.resetToggledMode(mode) + } function toUpper(str) { if (str === ",") { @@ -28,6 +35,21 @@ Item { } } + onShiftModeChanged: { + var i, j; + for (i = 0; i < column1.children.length; i++) { + var row = column1.children[i]; + for (j = 0; j < row.children.length; j++) { + var key = row.children[j]; + if (shiftMode) { + key.glyph = keyboardBase.toUpper(key.glyph); + } else { + key.glyph = keyboardBase.toLower(key.glyph); + } + } + } + } + Rectangle { id: leftRect y: 0 @@ -211,18 +233,7 @@ Item { glyph: "⇪" toggle: true onToggledChanged: { - var i, j; - for (i = 0; i < column1.children.length; i++) { - var row = column1.children[i]; - for (j = 0; j < row.children.length; j++) { - var key = row.children[j]; - if (toggled) { - key.glyph = keyboardBase.toUpper(key.glyph); - } else { - key.glyph = keyboardBase.toLower(key.glyph); - } - } - } + shiftMode = toggled; } } @@ -299,6 +310,9 @@ Item { id: key30 width: 44 glyph: "⁂" + mouseArea.onClicked: { + keyboardBase.parent.punctuationMode = true + } } Key { @@ -317,9 +331,6 @@ Item { id: key35 x: 343 width: 43 - antialiasing: false - scale: 1 - transformOrigin: Item.Center glyph: "⇨" } diff --git a/interface/resources/qml/controls/KeyboardPunctuation.qml b/interface/resources/qml/controls/KeyboardPunctuation.qml new file mode 100644 index 0000000000..6fef366772 --- /dev/null +++ b/interface/resources/qml/controls/KeyboardPunctuation.qml @@ -0,0 +1,324 @@ +import QtQuick 2.0 + +Item { + id: keyboardBase + height: 200 + Rectangle { + id: leftRect + y: 0 + height: 200 + color: "#252525" + anchors.right: keyboardRect.left + anchors.rightMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + } + + Rectangle { + id: keyboardRect + x: 206 + y: 0 + width: 480 + height: 200 + color: "#252525" + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + + Column { + id: column1 + width: 480 + height: 200 + + Row { + id: row1 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 0 + + Key { + id: key1 + width: 43 + glyph: "1" + } + + Key { + id: key2 + width: 43 + glyph: "2" + } + + Key { + id: key3 + width: 43 + glyph: "3" + } + + Key { + id: key4 + width: 43 + glyph: "4" + } + + Key { + id: key5 + width: 43 + glyph: "5" + } + + Key { + id: key6 + width: 43 + glyph: "6" + } + + Key { + id: key7 + width: 43 + glyph: "7" + } + + Key { + id: key8 + width: 43 + glyph: "8" + } + + Key { + id: key9 + width: 43 + glyph: "9" + } + + Key { + id: key10 + width: 43 + glyph: "0" + } + + Key { + id: key28 + width: 50 + glyph: "←" + } + } + + Row { + id: row2 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 0 + + Key { + id: key11 + width: 43 + glyph: "!" + } + + Key { + id: key12 + width: 43 + glyph: "@" + } + + Key { + id: key13 + width: 43 + glyph: "#" + } + + Key { + id: key14 + width: 43 + glyph: "$" + } + + Key { + id: key15 + width: 43 + glyph: "%" + } + + Key { + id: key16 + width: 43 + glyph: "^" + } + + Key { + id: key17 + width: 43 + glyph: "&" + } + + Key { + id: key18 + width: 43 + glyph: "*" + } + + Key { + id: key19 + width: 43 + glyph: "(" + } + + Key { + id: key32 + width: 43 + glyph: ")" + } + + Key { + id: key37 + width: 50 + glyph: "⏎" + } + } + + Row { + id: row3 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 4 + + Key { + id: key27 + width: 43 + glyph: "=" + } + + Key { + id: key20 + width: 43 + glyph: "+" + } + + Key { + id: key21 + width: 43 + glyph: "-" + } + + Key { + id: key22 + width: 43 + glyph: "_" + } + + Key { + id: key23 + width: 43 + glyph: ";" + } + + Key { + id: key24 + width: 43 + glyph: ":" + } + + Key { + id: key25 + width: 43 + glyph: "'" + } + + Key { + id: key26 + width: 43 + glyph: "\"" + } + + Key { + id: key31 + width: 43 + glyph: "<" + } + + Key { + id: key33 + width: 43 + glyph: ">" + } + + Key { + id: key36 + width: 43 + glyph: "?" + } + + } + + Row { + id: row4 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 19 + + Key { + id: key30 + width: 65 + glyph: "abc" + mouseArea.onClicked: { + keyboardBase.parent.punctuationMode = false + } + } + + Key { + id: key29 + width: 285 + glyph: " " + } + + Key { + id: key34 + width: 43 + glyph: "⇦" + } + + Key { + id: key35 + x: 343 + width: 43 + glyph: "⇨" + } + + } + } + } + + Rectangle { + id: rightRect + y: 280 + height: 200 + color: "#252525" + border.width: 0 + anchors.left: keyboardRect.right + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + } + + Rectangle { + id: rectangle1 + color: "#ffffff" + anchors.bottom: keyboardRect.top + anchors.bottomMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + } + +} diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index d958e1c774..a49501bf83 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -6,6 +6,7 @@ Item { property alias url: root.url property alias eventBridge: eventBridgeWrapper.eventBridge property bool keyboardRaised: false + property bool punctuationMode: false QtObject { id: eventBridgeWrapper @@ -18,7 +19,7 @@ Item { x: 0 y: 0 width: parent.width - height: keyboardRaised ? parent.height - keyboard.height : parent.height + height: keyboardRaised ? parent.height - keyboard1.height : parent.height // creates a global EventBridge object. WebEngineScript { @@ -80,6 +81,8 @@ Item { onLoadingChanged: { keyboardRaised = false; + punctuationMode = false; + keyboard1.resetShiftMode(false); // Required to support clicking on "hifi://" links if (WebEngineView.LoadStartedStatus == loadRequest.status) { @@ -105,13 +108,27 @@ Item { //profile: desktop.browserProfile } - // virtual keyboard + // virtual keyboard, letters Keyboard { - id: keyboard + id: keyboard1 y: keyboardRaised ? parent.height : 0 height: keyboardRaised ? 200 : 0 - visible: keyboardRaised - enabled: keyboardRaised + visible: keyboardRaised && !punctuationMode + enabled: keyboardRaised && !punctuationMode + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + } + + KeyboardPunctuation { + id: keyboard2 + y: keyboardRaised ? parent.height : 0 + height: keyboardRaised ? 200 : 0 + visible: keyboardRaised && punctuationMode + enabled: keyboardRaised && punctuationMode anchors.right: parent.right anchors.rightMargin: 0 anchors.left: parent.left From 774509ba9e3ee552421a52ca53adb597e2c493ac Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 6 Sep 2016 17:41:56 -0700 Subject: [PATCH 14/22] Changed punctuation button to &123 --- interface/resources/qml/controls/Key.qml | 18 +++++++++--------- interface/resources/qml/controls/Keyboard.qml | 4 ++-- .../src/RenderableWebEntityItem.cpp | 16 ++++++++++------ 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/interface/resources/qml/controls/Key.qml b/interface/resources/qml/controls/Key.qml index 164b5c2f5b..d6e3f1cfe9 100644 --- a/interface/resources/qml/controls/Key.qml +++ b/interface/resources/qml/controls/Key.qml @@ -9,21 +9,21 @@ Item { property bool toggled: false // is this button currently toggled? property alias mouseArea: mouseArea1 + function resetToggledMode(mode) { + toggled: mode + if (toggled) { + state = "mouseDepressed" + } else { + state = "" + } + } + MouseArea { id: mouseArea1 width: 36 anchors.fill: parent hoverEnabled: true - function resetToggledMode(mode) { - toggled: mode - if (toggled) { - keyItem.state = "mouseDepressed" - } else { - keyItem.state = "" - } - } - onCanceled: { if (toggled) { keyItem.state = "mouseDepressed" diff --git a/interface/resources/qml/controls/Keyboard.qml b/interface/resources/qml/controls/Keyboard.qml index cfa5e857a4..ee60c8062c 100644 --- a/interface/resources/qml/controls/Keyboard.qml +++ b/interface/resources/qml/controls/Keyboard.qml @@ -308,8 +308,8 @@ Item { Key { id: key30 - width: 44 - glyph: "⁂" + width: 89 + glyph: "&123" mouseArea.onClicked: { keyboardBase.parent.punctuationMode = true } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 120949ad36..0ece9caca3 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -355,19 +355,22 @@ bool RenderableWebEntityItem::isTransparent() { // UTF-8 encoded symbols static const uint8_t UPWARDS_WHITE_ARROW_FROM_BAR[] = { 0xE2, 0x87, 0xAA, 0x00 }; // shift -static const uint8_t LEFT_ARROW[] = { 0xE2, 0x86, 0x90 }; // backspace -static const uint8_t LEFTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA6 }; // left arrow -static const uint8_t RIGHTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA8 }; // right arrow +static const uint8_t LEFT_ARROW[] = { 0xE2, 0x86, 0x90, 0x00 }; // backspace +static const uint8_t LEFTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA6, 0x00 }; // left arrow +static const uint8_t RIGHTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA8, 0x00 }; // right arrow static const uint8_t ASTERISIM[] = { 0xE2, 0x81, 0x82, 0x00 }; // symbols static const uint8_t RETURN_SYMBOL[] = { 0xE2, 0x8F, 0x8E, 0x00 }; // return +static const char PUNCTUATION_STRING[] = "&123"; +static const char ALPHABET_STRING[] = "abc"; static bool equals(const QByteArray& byteArray, const uint8_t* ptr) { - for (int i = 0; i < byteArray.size(); i++) { + int i; + for (i = 0; i < byteArray.size(); i++) { if ((char)ptr[i] != byteArray[i]) { return false; } } - return true; + return ptr[i] == 0x00; } void RenderableWebEntityItem::synthesizeKeyPress(QString key) { @@ -375,7 +378,8 @@ void RenderableWebEntityItem::synthesizeKeyPress(QString key) { int scanCode = (int)utf8Key[0]; QString keyString = key; - if (equals(utf8Key, UPWARDS_WHITE_ARROW_FROM_BAR) || equals(utf8Key, ASTERISIM)) { + if (equals(utf8Key, UPWARDS_WHITE_ARROW_FROM_BAR) || equals(utf8Key, ASTERISIM) || + equals(utf8Key, (uint8_t*)PUNCTUATION_STRING) || equals(utf8Key, (uint8_t*)ALPHABET_STRING)) { return; // ignore } else if (equals(utf8Key, LEFT_ARROW)) { scanCode = Qt::Key_Backspace; From 41e0b8732f63cf638308b6e3ddea5dbb5382f17b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 7 Sep 2016 10:59:16 -0700 Subject: [PATCH 15/22] fix for occasional crash when deleting web entity --- interface/resources/qml/controls/WebView.qml | 5 +++-- .../src/RenderableWebEntityItem.cpp | 16 +++++++++------- .../src/RenderableWebEntityItem.h | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index fa594574ae..ae1394f60f 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -41,8 +41,6 @@ Item { property string newUrl: "" - profile: desktop.browserProfile - webChannel.registeredObjects: [eventBridgeWrapper] Component.onCompleted: { @@ -51,6 +49,8 @@ Item { root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); }); + + root.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)" } // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6 @@ -96,6 +96,7 @@ Item { } onNewViewRequested:{ + // desktop is not defined for web-entities if (desktop) { var component = Qt.createComponent("../Browser.qml"); var newWindow = component.createObject(desktop); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 0ece9caca3..f1aaf54897 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -81,18 +81,20 @@ RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemI _touchDevice.setName("RenderableWebEntityItemTouchDevice"); _touchDevice.setMaximumTouchPoints(4); - _webEntityAPIHelper.setPtr(this); - _webEntityAPIHelper.moveToThread(qApp->thread()); + _webEntityAPIHelper = new WebEntityAPIHelper; + _webEntityAPIHelper->setPtr(this); + _webEntityAPIHelper->moveToThread(qApp->thread()); // forward web events to EntityScriptingInterface auto entities = DependencyManager::get(); - QObject::connect(&_webEntityAPIHelper, &WebEntityAPIHelper::webEventReceived, [=](const QVariant& message) { + QObject::connect(_webEntityAPIHelper, &WebEntityAPIHelper::webEventReceived, [=](const QVariant& message) { emit entities->webEventReceived(entityItemID, message); }); } RenderableWebEntityItem::~RenderableWebEntityItem() { - _webEntityAPIHelper.setPtr(nullptr); + _webEntityAPIHelper->setPtr(nullptr); + _webEntityAPIHelper->deleteLater(); destroyWebSurface(); qDebug() << "Destroyed web entity " << getID(); } @@ -113,10 +115,10 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) { _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/")); _webSurface->load("WebView.qml"); _webSurface->resume(); - _webSurface->getRootItem()->setProperty("eventBridge", QVariant::fromValue(&_webEntityAPIHelper)); + _webSurface->getRootItem()->setProperty("eventBridge", QVariant::fromValue(_webEntityAPIHelper)); _webSurface->getRootItem()->setProperty("url", _sourceUrl); _webSurface->getRootContext()->setContextProperty("desktop", QVariant()); - _webSurface->getRootContext()->setContextProperty("webEntity", &_webEntityAPIHelper); + _webSurface->getRootContext()->setContextProperty("webEntity", _webEntityAPIHelper); _connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) { _texture = textureId; }); @@ -402,7 +404,7 @@ void RenderableWebEntityItem::synthesizeKeyPress(QString key) { } void RenderableWebEntityItem::emitScriptEvent(const QVariant& message) { - _webEntityAPIHelper.emitScriptEvent(message); + _webEntityAPIHelper->emitScriptEvent(message); } void RenderableWebEntityItem::setKeyboardRaised(bool raised) { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 5e06440bcb..ec14d2d662 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -86,7 +86,7 @@ private: QTouchEvent _lastTouchEvent { QEvent::TouchUpdate }; uint64_t _lastRenderTime{ 0 }; QTouchDevice _touchDevice; - WebEntityAPIHelper _webEntityAPIHelper; + WebEntityAPIHelper* _webEntityAPIHelper; QMetaObject::Connection _mousePressConnection; QMetaObject::Connection _mouseReleaseConnection; From 9f0550ea7e070b673ae61e7c19e4bee6dd1f76a8 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 7 Sep 2016 10:59:54 -0700 Subject: [PATCH 16/22] Raised dpi of WebTablet to fit 480 width keyboard --- scripts/system/libraries/WebTablet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index adbcd78381..a255b7a494 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -11,7 +11,7 @@ var RAD_TO_DEG = 180 / Math.PI; var X_AXIS = {x: 1, y: 0, z: 0}; var Y_AXIS = {x: 0, y: 1, z: 0}; -var DEFAULT_DPI = 30; +var DEFAULT_DPI = 32; var DEFAULT_WIDTH = 0.5; var TABLET_URL = "https://s3.amazonaws.com/hifi-public/tony/tablet.fbx"; From 69bda0de83ecfde8a428396dd6afd1674a5e02c5 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 7 Sep 2016 13:49:47 -0700 Subject: [PATCH 17/22] Shift key resets after first alpha key. --- interface/resources/qml/controls/Key.qml | 2 +- interface/resources/qml/controls/Keyboard.qml | 36 +++++++++++++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/controls/Key.qml b/interface/resources/qml/controls/Key.qml index d6e3f1cfe9..1cd6105b66 100644 --- a/interface/resources/qml/controls/Key.qml +++ b/interface/resources/qml/controls/Key.qml @@ -10,7 +10,7 @@ Item { property alias mouseArea: mouseArea1 function resetToggledMode(mode) { - toggled: mode + toggled = mode if (toggled) { state = "mouseDepressed" } else { diff --git a/interface/resources/qml/controls/Keyboard.qml b/interface/resources/qml/controls/Keyboard.qml index ee60c8062c..a6d57393a6 100644 --- a/interface/resources/qml/controls/Keyboard.qml +++ b/interface/resources/qml/controls/Keyboard.qml @@ -8,7 +8,7 @@ Item { function resetShiftMode(mode) { shiftMode = mode - key23.resetToggledMode(mode) + shiftKey.resetToggledMode(mode) } function toUpper(str) { @@ -35,21 +35,43 @@ Item { } } - onShiftModeChanged: { + function forEachKey(func) { var i, j; for (i = 0; i < column1.children.length; i++) { var row = column1.children[i]; for (j = 0; j < row.children.length; j++) { var key = row.children[j]; - if (shiftMode) { - key.glyph = keyboardBase.toUpper(key.glyph); - } else { - key.glyph = keyboardBase.toLower(key.glyph); - } + func(key); } } } + onShiftModeChanged: { + forEachKey(function (key) { + if (shiftMode) { + key.glyph = keyboardBase.toUpper(key.glyph); + } else { + key.glyph = keyboardBase.toLower(key.glyph); + } + }); + } + + function alphaKeyClickedHandler(mouseArea) { + // reset shift mode to false after first keypress + if (shiftMode) { + resetShiftMode(false) + } + } + + Component.onCompleted: { + // hook up callbacks to every ascii key + forEachKey(function (key) { + if (/^[a-z]+$/i.test(key.glyph)) { + key.mouseArea.onClicked.connect(alphaKeyClickedHandler); + } + }); + } + Rectangle { id: leftRect y: 0 From ebb4c0990b223756183e2d671d0ae5c6e5ce5e5b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 7 Sep 2016 17:14:33 -0700 Subject: [PATCH 18/22] Removed local copy of qtwebchannel.js, use qt resource version instead --- .../resources/html/createGlobalEventBridge.js | 421 +----------------- interface/resources/qml/controls/WebView.qml | 2 +- .../src/RenderableWebEntityItem.cpp | 19 +- 3 files changed, 28 insertions(+), 414 deletions(-) diff --git a/interface/resources/html/createGlobalEventBridge.js b/interface/resources/html/createGlobalEventBridge.js index 834c8b3792..429e4b039e 100644 --- a/interface/resources/html/createGlobalEventBridge.js +++ b/interface/resources/html/createGlobalEventBridge.js @@ -1,415 +1,12 @@ -/**************************************************************************** -** -** Copyright (C) 2015 The Qt Company Ltd. -** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff -** Contact: http://www.qt.io/licensing/ -** -** This file is part of the QtWebChannel module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see http://www.qt.io/terms-conditions. For further -** information use the contact form at http://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** As a special exception, The Qt Company gives you certain additional -** rights. These rights are described in The Qt Company LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -"use strict"; - -var QWebChannelMessageTypes = { - signal: 1, - propertyUpdate: 2, - init: 3, - idle: 4, - debug: 5, - invokeMethod: 6, - connectToSignal: 7, - disconnectFromSignal: 8, - setProperty: 9, - response: 10, -}; - -var QWebChannel = function(transport, initCallback) -{ - if (typeof transport !== "object" || typeof transport.send !== "function") { - console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." + - " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send)); - return; - } - - var channel = this; - this.transport = transport; - - this.send = function(data) - { - if (typeof(data) !== "string") { - data = JSON.stringify(data); - } - channel.transport.send(data); - } - - this.transport.onmessage = function(message) - { - var data = message.data; - if (typeof data === "string") { - data = JSON.parse(data); - } - switch (data.type) { - case QWebChannelMessageTypes.signal: - channel.handleSignal(data); - break; - case QWebChannelMessageTypes.response: - channel.handleResponse(data); - break; - case QWebChannelMessageTypes.propertyUpdate: - channel.handlePropertyUpdate(data); - break; - default: - console.error("invalid message received:", message.data); - break; - } - } - - this.execCallbacks = {}; - this.execId = 0; - this.exec = function(data, callback) - { - if (!callback) { - // if no callback is given, send directly - channel.send(data); - return; - } - if (channel.execId === Number.MAX_VALUE) { - // wrap - channel.execId = Number.MIN_VALUE; - } - if (data.hasOwnProperty("id")) { - console.error("Cannot exec message with property id: " + JSON.stringify(data)); - return; - } - data.id = channel.execId++; - channel.execCallbacks[data.id] = callback; - channel.send(data); - }; - - this.objects = {}; - - this.handleSignal = function(message) - { - var object = channel.objects[message.object]; - if (object) { - object.signalEmitted(message.signal, message.args); - } else { - console.warn("Unhandled signal: " + message.object + "::" + message.signal); - } - } - - this.handleResponse = function(message) - { - if (!message.hasOwnProperty("id")) { - console.error("Invalid response message received: ", JSON.stringify(message)); - return; - } - channel.execCallbacks[message.id](message.data); - delete channel.execCallbacks[message.id]; - } - - this.handlePropertyUpdate = function(message) - { - for (var i in message.data) { - var data = message.data[i]; - var object = channel.objects[data.object]; - if (object) { - object.propertyUpdate(data.signals, data.properties); - } else { - console.warn("Unhandled property update: " + data.object + "::" + data.signal); - } - } - channel.exec({type: QWebChannelMessageTypes.idle}); - } - - this.debug = function(message) - { - channel.send({type: QWebChannelMessageTypes.debug, data: message}); - }; - - channel.exec({type: QWebChannelMessageTypes.init}, function(data) { - for (var objectName in data) { - var object = new QObject(objectName, data[objectName], channel); - } - // now unwrap properties, which might reference other registered objects - for (var objectName in channel.objects) { - channel.objects[objectName].unwrapProperties(); - } - if (initCallback) { - initCallback(channel); - } - channel.exec({type: QWebChannelMessageTypes.idle}); - }); -}; - -function QObject(name, data, webChannel) -{ - this.__id__ = name; - webChannel.objects[name] = this; - - // List of callbacks that get invoked upon signal emission - this.__objectSignals__ = {}; - - // Cache of all properties, updated when a notify signal is emitted - this.__propertyCache__ = {}; - - var object = this; - - // ---------------------------------------------------------------------- - - this.unwrapQObject = function(response) - { - if (response instanceof Array) { - // support list of objects - var ret = new Array(response.length); - for (var i = 0; i < response.length; ++i) { - ret[i] = object.unwrapQObject(response[i]); - } - return ret; - } - if (!response - || !response["__QObject*__"] - || response["id"] === undefined) { - return response; - } - - var objectId = response.id; - if (webChannel.objects[objectId]) - return webChannel.objects[objectId]; - - if (!response.data) { - console.error("Cannot unwrap unknown QObject " + objectId + " without data."); - return; - } - - var qObject = new QObject( objectId, response.data, webChannel ); - qObject.destroyed.connect(function() { - if (webChannel.objects[objectId] === qObject) { - delete webChannel.objects[objectId]; - // reset the now deleted QObject to an empty {} object - // just assigning {} though would not have the desired effect, but the - // below also ensures all external references will see the empty map - // NOTE: this detour is necessary to workaround QTBUG-40021 - var propertyNames = []; - for (var propertyName in qObject) { - propertyNames.push(propertyName); - } - for (var idx in propertyNames) { - delete qObject[propertyNames[idx]]; - } - } - }); - // here we are already initialized, and thus must directly unwrap the properties - qObject.unwrapProperties(); - return qObject; - } - - this.unwrapProperties = function() - { - for (var propertyIdx in object.__propertyCache__) { - object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]); - } - } - - function addSignal(signalData, isPropertyNotifySignal) - { - var signalName = signalData[0]; - var signalIndex = signalData[1]; - object[signalName] = { - connect: function(callback) { - if (typeof(callback) !== "function") { - console.error("Bad callback given to connect to signal " + signalName); - return; - } - - object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; - object.__objectSignals__[signalIndex].push(callback); - - if (!isPropertyNotifySignal && signalName !== "destroyed") { - // only required for "pure" signals, handled separately for properties in propertyUpdate - // also note that we always get notified about the destroyed signal - webChannel.exec({ - type: QWebChannelMessageTypes.connectToSignal, - object: object.__id__, - signal: signalIndex - }); - } - }, - disconnect: function(callback) { - if (typeof(callback) !== "function") { - console.error("Bad callback given to disconnect from signal " + signalName); - return; - } - object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; - var idx = object.__objectSignals__[signalIndex].indexOf(callback); - if (idx === -1) { - console.error("Cannot find connection of signal " + signalName + " to " + callback.name); - return; - } - object.__objectSignals__[signalIndex].splice(idx, 1); - if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) { - // only required for "pure" signals, handled separately for properties in propertyUpdate - webChannel.exec({ - type: QWebChannelMessageTypes.disconnectFromSignal, - object: object.__id__, - signal: signalIndex - }); - } - } - }; - } - - /** - * Invokes all callbacks for the given signalname. Also works for property notify callbacks. - */ - function invokeSignalCallbacks(signalName, signalArgs) - { - var connections = object.__objectSignals__[signalName]; - if (connections) { - connections.forEach(function(callback) { - callback.apply(callback, signalArgs); - }); - } - } - - this.propertyUpdate = function(signals, propertyMap) - { - // update property cache - for (var propertyIndex in propertyMap) { - var propertyValue = propertyMap[propertyIndex]; - object.__propertyCache__[propertyIndex] = propertyValue; - } - - for (var signalName in signals) { - // Invoke all callbacks, as signalEmitted() does not. This ensures the - // property cache is updated before the callbacks are invoked. - invokeSignalCallbacks(signalName, signals[signalName]); - } - } - - this.signalEmitted = function(signalName, signalArgs) - { - invokeSignalCallbacks(signalName, signalArgs); - } - - function addMethod(methodData) - { - var methodName = methodData[0]; - var methodIdx = methodData[1]; - object[methodName] = function() { - var args = []; - var callback; - for (var i = 0; i < arguments.length; ++i) { - if (typeof arguments[i] === "function") - callback = arguments[i]; - else - args.push(arguments[i]); - } - - webChannel.exec({ - "type": QWebChannelMessageTypes.invokeMethod, - "object": object.__id__, - "method": methodIdx, - "args": args - }, function(response) { - if (response !== undefined) { - var result = object.unwrapQObject(response); - if (callback) { - (callback)(result); - } - } - }); - }; - } - - function bindGetterSetter(propertyInfo) - { - var propertyIndex = propertyInfo[0]; - var propertyName = propertyInfo[1]; - var notifySignalData = propertyInfo[2]; - // initialize property cache with current value - // NOTE: if this is an object, it is not directly unwrapped as it might - // reference other QObject that we do not know yet - object.__propertyCache__[propertyIndex] = propertyInfo[3]; - - if (notifySignalData) { - if (notifySignalData[0] === 1) { - // signal name is optimized away, reconstruct the actual name - notifySignalData[0] = propertyName + "Changed"; - } - addSignal(notifySignalData, true); - } - - Object.defineProperty(object, propertyName, { - get: function () { - var propertyValue = object.__propertyCache__[propertyIndex]; - if (propertyValue === undefined) { - // This shouldn't happen - console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__); - } - - return propertyValue; - }, - set: function(value) { - if (value === undefined) { - console.warn("Property setter for " + propertyName + " called with undefined value!"); - return; - } - object.__propertyCache__[propertyIndex] = value; - webChannel.exec({ - "type": QWebChannelMessageTypes.setProperty, - "object": object.__id__, - "property": propertyIndex, - "value": value - }); - } - }); - - } - - // ---------------------------------------------------------------------- - - data.methods.forEach(addMethod); - - data.properties.forEach(bindGetterSetter); - - data.signals.forEach(function(signal) { addSignal(signal, false); }); - - for (var name in data.enums) { - object[name] = data.enums[name]; - } -} - -//required for use with nodejs -if (typeof module === 'object') { - module.exports = { - QWebChannel: QWebChannel - }; -} +// +// createGlobalEventBridge.js +// +// Created by Anthony J. Thibault on 9/7/2016 +// Copyright 2016 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 +// // Stick a EventBridge object in the global namespace. var EventBridge; diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index ae1394f60f..35a4e1f7e4 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -24,7 +24,7 @@ Item { // creates a global EventBridge object. WebEngineScript { id: createGlobalEventBridge - sourceUrl: resourceDirectoryUrl + "/html/createGlobalEventBridge.js" + sourceCode: eventBridgeJavaScriptToInject injectionPoint: WebEngineScript.DocumentCreation worldId: WebEngineScript.MainWorld } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f1aaf54897..cb6906534f 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -26,6 +26,7 @@ #include #include "EntityTreeRenderer.h" +#include "EntitiesRendererLogging.h" const float METERS_TO_INCHES = 39.3701f; static uint32_t _currentWebCount { 0 }; @@ -106,6 +107,20 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) { } qDebug() << "Building web surface"; + QString javaScriptToInject; + QFile webChannelFile(":qtwebchannel/qwebchannel.js"); + QFile createGlobalEventBridgeFile(PathUtils::resourcesPath() + "/html/createGlobalEventBridge.js"); + if (webChannelFile.open(QFile::ReadOnly | QFile::Text) && + createGlobalEventBridgeFile.open(QFile::ReadOnly | QFile::Text)) { + QString webChannelStr = QTextStream(&webChannelFile).readAll(); + QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll(); + + // concatenate these js files + javaScriptToInject = webChannelStr + createGlobalEventBridgeStr; + } else { + qCWarning(entitiesrenderer) << "unable to find qwebchannel.js or createGlobalEventBridge.js"; + } + ++_currentWebCount; // Save the original GL context, because creating a QML surface will create a new context QOpenGLContext * currentContext = QOpenGLContext::currentContext(); @@ -113,7 +128,9 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) { _webSurface = new OffscreenQmlSurface(); _webSurface->create(currentContext); _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/")); - _webSurface->load("WebView.qml"); + _webSurface->load("WebView.qml", [&](QQmlContext* context, QObject* obj) { + context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject)); + }); _webSurface->resume(); _webSurface->getRootItem()->setProperty("eventBridge", QVariant::fromValue(_webEntityAPIHelper)); _webSurface->getRootItem()->setProperty("url", _sourceUrl); From 594640f8b4c558ee41c852d0c7eeea7f0c344b1c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 8 Sep 2016 11:53:23 -0700 Subject: [PATCH 19/22] coding standard fixes --- .../src/RenderableWebEntityItem.cpp | 16 ++++++++-------- .../src/RenderableWebEntityItem.h | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index cb6906534f..cc022d9df2 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -39,8 +39,8 @@ static int MAX_WINDOW_SIZE = 4096; static float OPAQUE_ALPHA_THRESHOLD = 0.99f; void WebEntityAPIHelper::synthesizeKeyPress(QString key) { - if (_ptr) { - _ptr->synthesizeKeyPress(key); + if (_renderableWebEntityItem) { + _renderableWebEntityItem->synthesizeKeyPress(key); } } @@ -57,10 +57,10 @@ void WebEntityAPIHelper::emitWebEvent(const QVariant& message) { QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, message)); } else { // special case to handle raising and lowering the virtual keyboard - if (message.type() == QVariant::String && message.toString() == "_RAISE_KEYBOARD" && _ptr) { - _ptr->setKeyboardRaised(true); - } else if (message.type() == QVariant::String && message.toString() == "_LOWER_KEYBOARD" && _ptr) { - _ptr->setKeyboardRaised(false); + if (message.type() == QVariant::String && message.toString() == "_RAISE_KEYBOARD" && _renderableWebEntityItem) { + _renderableWebEntityItem->setKeyboardRaised(true); + } else if (message.type() == QVariant::String && message.toString() == "_LOWER_KEYBOARD" && _renderableWebEntityItem) { + _renderableWebEntityItem->setKeyboardRaised(false); } else { emit webEventReceived(message); } @@ -83,7 +83,7 @@ RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemI _touchDevice.setMaximumTouchPoints(4); _webEntityAPIHelper = new WebEntityAPIHelper; - _webEntityAPIHelper->setPtr(this); + _webEntityAPIHelper->setRenderableWebEntityItem(this); _webEntityAPIHelper->moveToThread(qApp->thread()); // forward web events to EntityScriptingInterface @@ -94,7 +94,7 @@ RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemI } RenderableWebEntityItem::~RenderableWebEntityItem() { - _webEntityAPIHelper->setPtr(nullptr); + _webEntityAPIHelper->setRenderableWebEntityItem(nullptr); _webEntityAPIHelper->deleteLater(); destroyWebSurface(); qDebug() << "Destroyed web entity " << getID(); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index ec14d2d662..47808c4262 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -27,8 +27,8 @@ class RenderableWebEntityItem; class WebEntityAPIHelper : public QObject { Q_OBJECT public: - void setPtr(RenderableWebEntityItem* ptr) { - _ptr = ptr; + void setRenderableWebEntityItem(RenderableWebEntityItem* renderableWebEntityItem) { + _renderableWebEntityItem = renderableWebEntityItem; } Q_INVOKABLE void synthesizeKeyPress(QString key); @@ -41,7 +41,7 @@ signals: void webEventReceived(const QVariant& message); protected: - RenderableWebEntityItem* _ptr{ nullptr }; + RenderableWebEntityItem* _renderableWebEntityItem{ nullptr }; }; class RenderableWebEntityItem : public WebEntityItem { From f7516c2b071f65329f882edfc288b9c9638331a0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 8 Sep 2016 13:52:19 -0700 Subject: [PATCH 20/22] code convention and eslint on createGlobalEventBridge.js --- .../resources/html/createGlobalEventBridge.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/interface/resources/html/createGlobalEventBridge.js b/interface/resources/html/createGlobalEventBridge.js index 429e4b039e..027d6fe8db 100644 --- a/interface/resources/html/createGlobalEventBridge.js +++ b/interface/resources/html/createGlobalEventBridge.js @@ -18,11 +18,13 @@ var EventBridge; this._callbacks = []; this._messages = []; this.scriptEventReceived = { - connect: function (cb) { self._callbacks.push(cb); } + connect: function (callback) { + self._callbacks.push(callback); + } }; this.emitWebEvent = function (message) { self._messages.push(message); - } + }; }; EventBridge = new TempEventBridge(); @@ -31,11 +33,11 @@ var EventBridge; // replace the TempEventBridge with the real one. var tempEventBridge = EventBridge; EventBridge = channel.objects.eventBridgeWrapper.eventBridge; - tempEventBridge._callbacks.forEach(function (cb) { - EventBridge.scriptEventReceived.connect(cb); + tempEventBridge._callbacks.forEach(function (callback) { + EventBridge.scriptEventReceived.connect(callback); }); - tempEventBridge._messages.forEach(function (msg) { - EventBridge.emitWebEvent(msg); + tempEventBridge._messages.forEach(function (message) { + EventBridge.emitWebEvent(message); }); }); })(); From 3574267382dd27976bd2b78d5b9ee9d86c532882 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 8 Sep 2016 18:00:45 -0700 Subject: [PATCH 21/22] Fix for keyboard on facebook share pop-up It was a div tag with "contenteditable"="true" attribute. --- interface/resources/html/raiseAndLowerKeyboard.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/interface/resources/html/raiseAndLowerKeyboard.js b/interface/resources/html/raiseAndLowerKeyboard.js index 5190950332..723767790a 100644 --- a/interface/resources/html/raiseAndLowerKeyboard.js +++ b/interface/resources/html/raiseAndLowerKeyboard.js @@ -13,7 +13,18 @@ var numWarnings = 0; function shouldRaiseKeyboard() { - return document.activeElement.nodeName == "INPUT" || document.activeElement.nodeName == "TEXTAREA"; + if (document.activeElement.nodeName == "INPUT" || document.activeElement.nodeName == "TEXTAREA") { + return true; + } else { + // check for contenteditable attribute + for (var i = 0; i < document.activeElement.attributes.length; i++) { + if (document.activeElement.attributes[i].name === "contenteditable" && + document.activeElement.attributes[i].value === "true") { + return true; + } + } + return false; + } }; setInterval(function () { From d38625ce2af28703e67eb9c806c75e45f50ef03b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 8 Sep 2016 18:16:32 -0700 Subject: [PATCH 22/22] Coding standard fix for JS portions of QML. Semicolons at end of line. --- interface/resources/qml/controls/Key.qml | 36 +++++++++---------- interface/resources/qml/controls/Keyboard.qml | 8 ++--- interface/resources/qml/controls/WebView.qml | 4 +-- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/interface/resources/qml/controls/Key.qml b/interface/resources/qml/controls/Key.qml index 1cd6105b66..2218474936 100644 --- a/interface/resources/qml/controls/Key.qml +++ b/interface/resources/qml/controls/Key.qml @@ -10,11 +10,11 @@ Item { property alias mouseArea: mouseArea1 function resetToggledMode(mode) { - toggled = mode + toggled = mode; if (toggled) { - state = "mouseDepressed" + state = "mouseDepressed"; } else { - state = "" + state = ""; } } @@ -26,52 +26,52 @@ Item { onCanceled: { if (toggled) { - keyItem.state = "mouseDepressed" + keyItem.state = "mouseDepressed"; } else { - keyItem.state = "" + keyItem.state = ""; } } onClicked: { - mouse.accepted = true - webEntity.synthesizeKeyPress(glyph) + mouse.accepted = true; + webEntity.synthesizeKeyPress(glyph); if (toggle) { - toggled = !toggled + toggled = !toggled; } } onDoubleClicked: { - mouse.accepted = true + mouse.accepted = true; } onEntered: { - keyItem.state = "mouseOver" + keyItem.state = "mouseOver"; } onExited: { if (toggled) { - keyItem.state = "mouseDepressed" + keyItem.state = "mouseDepressed"; } else { - keyItem.state = "" + keyItem.state = ""; } } onPressed: { - keyItem.state = "mouseClicked" - mouse.accepted = true + keyItem.state = "mouseClicked"; + mouse.accepted = true; } onReleased: { if (containsMouse) { - keyItem.state = "mouseOver" + keyItem.state = "mouseOver"; } else { if (toggled) { - keyItem.state = "mouseDepressed" + keyItem.state = "mouseDepressed"; } else { - keyItem.state = "" + keyItem.state = ""; } } - mouse.accepted = true + mouse.accepted = true; } } diff --git a/interface/resources/qml/controls/Keyboard.qml b/interface/resources/qml/controls/Keyboard.qml index a6d57393a6..eb34740402 100644 --- a/interface/resources/qml/controls/Keyboard.qml +++ b/interface/resources/qml/controls/Keyboard.qml @@ -7,8 +7,8 @@ Item { property bool shiftMode: false function resetShiftMode(mode) { - shiftMode = mode - shiftKey.resetToggledMode(mode) + shiftMode = mode; + shiftKey.resetToggledMode(mode); } function toUpper(str) { @@ -59,7 +59,7 @@ Item { function alphaKeyClickedHandler(mouseArea) { // reset shift mode to false after first keypress if (shiftMode) { - resetShiftMode(false) + resetShiftMode(false); } } @@ -333,7 +333,7 @@ Item { width: 89 glyph: "&123" mouseArea.onClicked: { - keyboardBase.parent.punctuationMode = true + keyboardBase.parent.punctuationMode = true; } } diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 35a4e1f7e4..2f7a668d65 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -44,13 +44,13 @@ Item { webChannel.registeredObjects: [eventBridgeWrapper] Component.onCompleted: { - console.log("Connecting JS messaging to Hifi Logging") + console.log("Connecting JS messaging to Hifi Logging"); // Ensure the JS from the web-engine makes it to our logging root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); }); - root.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)" + root.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; } // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6