diff --git a/interface/resources/html/raiseAndLowerKeyboard.js b/interface/resources/html/raiseAndLowerKeyboard.js
index 723767790a..aeca4dc112 100644
--- a/interface/resources/html/raiseAndLowerKeyboard.js
+++ b/interface/resources/html/raiseAndLowerKeyboard.js
@@ -11,9 +11,12 @@
var POLL_FREQUENCY = 500; // ms
var MAX_WARNINGS = 3;
var numWarnings = 0;
+ var isKeyboardRaised = false;
+ var isNumericKeyboard = false;
+ var KEYBOARD_HEIGHT = 200;
function shouldRaiseKeyboard() {
- if (document.activeElement.nodeName == "INPUT" || document.activeElement.nodeName == "TEXTAREA") {
+ if (document.activeElement.nodeName === "INPUT" || document.activeElement.nodeName === "TEXTAREA") {
return true;
} else {
// check for contenteditable attribute
@@ -27,15 +30,39 @@
}
};
+ function shouldSetNumeric() {
+ return document.activeElement.type === "number";
+ };
+
setInterval(function () {
- 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++;
+ var keyboardRaised = shouldRaiseKeyboard();
+ var numericKeyboard = shouldSetNumeric();
+
+ if (keyboardRaised !== isKeyboardRaised || numericKeyboard !== isNumericKeyboard) {
+
+ if (typeof EventBridge !== "undefined") {
+ EventBridge.emitWebEvent(
+ keyboardRaised ? ("_RAISE_KEYBOARD" + (numericKeyboard ? "_NUMERIC" : "")) : "_LOWER_KEYBOARD"
+ );
+ } else {
+ if (numWarnings < MAX_WARNINGS) {
+ console.log("WARNING: no global EventBridge object found");
+ numWarnings++;
+ }
}
+
+ if (!isKeyboardRaised) {
+ var delta = document.activeElement.getBoundingClientRect().bottom + 10
+ - (document.body.clientHeight - KEYBOARD_HEIGHT);
+ if (delta > 0) {
+ setTimeout(function () {
+ document.body.scrollTop += delta;
+ }, 500); // Allow time for keyboard to be raised in QML.
+ }
+ }
+
+ isKeyboardRaised = keyboardRaised;
+ isNumericKeyboard = numericKeyboard;
}
}, POLL_FREQUENCY);
})();
diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml
index efcf14fc89..bb44e2c56e 100644
--- a/interface/resources/qml/AddressBarDialog.qml
+++ b/interface/resources/qml/AddressBarDialog.qml
@@ -69,8 +69,12 @@ Window {
AddressBarDialog {
id: addressBarDialog
+
+ property bool keyboardRaised: false
+ property bool punctuationMode: false
+
implicitWidth: backgroundImage.width
- implicitHeight: backgroundImage.height
+ implicitHeight: backgroundImage.height + (keyboardRaised ? 200 : 0)
// The buttons have their button state changed on hover, so we have to manually fix them up here
onBackEnabledChanged: backArrow.buttonState = addressBarDialog.backEnabled ? 1 : 0;
@@ -252,8 +256,8 @@ Window {
}
Window {
- width: 938;
- height: 625;
+ width: 938
+ height: 625
scale: 0.8 // Reset scale of Window to 1.0 (counteract address bar's scale value of 1.25)
HifiControls.WebView {
anchors.fill: parent;
@@ -270,6 +274,35 @@ Window {
horizontalCenter: scroll.horizontalCenter;
}
}
+
+ // virtual keyboard, letters
+ HifiControls.Keyboard {
+ id: keyboard1
+ y: parent.keyboardRaised ? parent.height : 0
+ height: parent.keyboardRaised ? 200 : 0
+ visible: parent.keyboardRaised && !parent.punctuationMode
+ enabled: parent.keyboardRaised && !parent.punctuationMode
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+ anchors.left: parent.left
+ anchors.leftMargin: 0
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 0
+ }
+
+ HifiControls.KeyboardPunctuation {
+ id: keyboard2
+ y: parent.keyboardRaised ? parent.height : 0
+ height: parent.keyboardRaised ? 200 : 0
+ visible: parent.keyboardRaised && parent.punctuationMode
+ enabled: parent.keyboardRaised && parent.punctuationMode
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+ anchors.left: parent.left
+ anchors.leftMargin: 0
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 0
+ }
}
function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects.
diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml
index 631036580e..b258dadae4 100644
--- a/interface/resources/qml/Browser.qml
+++ b/interface/resources/qml/Browser.qml
@@ -1,5 +1,6 @@
import QtQuick 2.5
import QtQuick.Controls 1.2
+import QtWebChannel 1.0
import QtWebEngine 1.2
import "controls-uit"
@@ -19,6 +20,9 @@ ScrollingWindow {
property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
property alias url: webview.url
property alias webView: webview
+
+ property alias eventBridge: eventBridgeWrapper.eventBridge
+
x: 100
y: 100
@@ -130,10 +134,11 @@ ScrollingWindow {
case Qt.Key_Return:
event.accepted = true
if (text.indexOf("http") != 0) {
- text = "http://" + text
+ text = "http://" + text;
}
root.hidePermissionsBar();
- webview.url = text
+ root.keyboardRaised = false;
+ webview.url = text;
break;
}
}
@@ -197,32 +202,60 @@ ScrollingWindow {
}
}
- WebEngineView {
+ WebView {
id: webview
url: "https://highfidelity.com"
+
+ property alias eventBridgeWrapper: eventBridgeWrapper
+
+ QtObject {
+ id: eventBridgeWrapper
+ WebChannel.id: "eventBridgeWrapper"
+ property var eventBridge;
+ }
+
+ webChannel.registeredObjects: [eventBridgeWrapper]
+
+ // Create a global EventBridge object for raiseAndLowerKeyboard.
+ WebEngineScript {
+ id: createGlobalEventBridge
+ sourceCode: eventBridgeJavaScriptToInject
+ injectionPoint: WebEngineScript.DocumentCreation
+ worldId: WebEngineScript.MainWorld
+ }
+
+ // Detect when may want to raise and lower keyboard.
+ WebEngineScript {
+ id: raiseAndLowerKeyboard
+ injectionPoint: WebEngineScript.Deferred
+ sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
+ worldId: WebEngineScript.MainWorld
+ }
+
+ userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
+
anchors.top: buttons.bottom
anchors.topMargin: 8
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
+
onFeaturePermissionRequested: {
permissionsBar.securityOrigin = securityOrigin;
permissionsBar.feature = feature;
root.showPermissionsBar();
}
+
onLoadingChanged: {
if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
addressBar.text = loadRequest.url
}
}
+
onIconChanged: {
console.log("New icon: " + icon)
}
- onNewViewRequested: {
- var component = Qt.createComponent("Browser.qml");
- var newWindow = component.createObject(desktop);
- request.openIn(newWindow.webView)
- }
+
onWindowCloseRequested: {
root.destroy();
}
@@ -230,8 +263,6 @@ ScrollingWindow {
Component.onCompleted: {
desktop.initWebviewProfileHandlers(webview.profile)
}
-
- profile: desktop.browserProfile
}
} // item
diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml
index 153498e2f7..7ea45bff6b 100644
--- a/interface/resources/qml/QmlWebWindow.qml
+++ b/interface/resources/qml/QmlWebWindow.qml
@@ -66,6 +66,24 @@ Windows.ScrollingWindow {
anchors.fill: parent
focus: true
webChannel.registeredObjects: [eventBridgeWrapper]
+
+ // Create a global EventBridge object for raiseAndLowerKeyboard.
+ WebEngineScript {
+ id: createGlobalEventBridge
+ sourceCode: eventBridgeJavaScriptToInject
+ injectionPoint: WebEngineScript.DocumentCreation
+ worldId: WebEngineScript.MainWorld
+ }
+
+ // Detect when may want to raise and lower keyboard.
+ WebEngineScript {
+ id: raiseAndLowerKeyboard
+ injectionPoint: WebEngineScript.Deferred
+ sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
+ worldId: WebEngineScript.MainWorld
+ }
+
+ userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
}
}
}
diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml
index b4ae882330..68c8099970 100644
--- a/interface/resources/qml/ToolWindow.qml
+++ b/interface/resources/qml/ToolWindow.qml
@@ -19,6 +19,7 @@ import "windows"
import "controls-uit"
import "styles-uit"
+
ScrollingWindow {
id: toolWindow
resizable: true
@@ -47,92 +48,101 @@ ScrollingWindow {
property alias y: toolWindow.y
}
- TabView {
- id: tabView;
+ Item {
+ id: toolWindowTabViewItem
+ height: pane.scrollHeight
width: pane.contentWidth
- height: pane.scrollHeight // Pane height so that don't use Window's scrollbars otherwise tabs may be scrolled out of view.
- property int tabCount: 0
+ anchors.left: parent.left
+ anchors.top: parent.top
- Repeater {
- model: 4
- Tab {
- // Force loading of the content even if the tab is not visible
- // (required for letting the C++ code access the webview)
- active: true
- enabled: false
- property string originalUrl: "";
+ TabView {
+ id: tabView
+ width: pane.contentWidth
+ // Pane height so that don't use Window's scrollbars otherwise tabs may be scrolled out of view.
+ height: pane.scrollHeight
+ property int tabCount: 0
- WebView {
- id: webView;
- anchors.fill: parent
+ Repeater {
+ model: 4
+ Tab {
+ // Force loading of the content even if the tab is not visible
+ // (required for letting the C++ code access the webview)
+ active: true
enabled: false
- property alias eventBridgeWrapper: eventBridgeWrapper
-
- QtObject {
- id: eventBridgeWrapper
- WebChannel.id: "eventBridgeWrapper"
- property var eventBridge;
+ property string originalUrl: ""
+
+ WebView {
+ id: webView
+ anchors.fill: parent
+ enabled: false
+ property alias eventBridgeWrapper: eventBridgeWrapper
+
+ QtObject {
+ id: eventBridgeWrapper
+ WebChannel.id: "eventBridgeWrapper"
+ property var eventBridge
+ }
+
+ webChannel.registeredObjects: [eventBridgeWrapper]
+ onEnabledChanged: toolWindow.updateVisiblity()
+ }
+ }
+ }
+
+ style: TabViewStyle {
+
+ frame: Rectangle { // Background shown before content loads.
+ anchors.fill: parent
+ color: hifi.colors.baseGray
+ }
+
+ frameOverlap: 0
+
+ tab: Rectangle {
+ implicitWidth: text.width
+ implicitHeight: 3 * text.height
+ color: styleData.selected ? hifi.colors.black : hifi.colors.tabBackgroundDark
+
+ RalewayRegular {
+ id: text
+ text: styleData.title
+ font.capitalization: Font.AllUppercase
+ size: hifi.fontSizes.tabName
+ width: tabView.tabCount > 1 ? styleData.availableWidth / tabView.tabCount : implicitWidth + 2 * hifi.dimensions.contentSpacing.x
+ elide: Text.ElideRight
+ color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.lightGrayText
+ horizontalAlignment: Text.AlignHCenter
+ anchors.centerIn: parent
}
- webChannel.registeredObjects: [eventBridgeWrapper]
- onEnabledChanged: toolWindow.updateVisiblity();
- }
- }
- }
-
- style: TabViewStyle {
-
- frame: Rectangle { // Background shown before content loads.
- anchors.fill: parent
- color: hifi.colors.baseGray
- }
-
- frameOverlap: 0
-
- tab: Rectangle {
- implicitWidth: text.width
- implicitHeight: 3 * text.height
- color: styleData.selected ? hifi.colors.black : hifi.colors.tabBackgroundDark
-
- RalewayRegular {
- id: text
- text: styleData.title
- font.capitalization: Font.AllUppercase
- size: hifi.fontSizes.tabName
- width: tabView.tabCount > 1 ? styleData.availableWidth / tabView.tabCount : implicitWidth + 2 * hifi.dimensions.contentSpacing.x
- elide: Text.ElideRight
- color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.lightGrayText
- horizontalAlignment: Text.AlignHCenter
- anchors.centerIn: parent
- }
-
- Rectangle { // Separator.
- width: 1
- height: parent.height
- color: hifi.colors.black
- anchors.left: parent.left
- anchors.top: parent.top
- visible: styleData.index > 0
-
- Rectangle {
+ Rectangle { // Separator.
width: 1
- height: 1
- color: hifi.colors.baseGray
+ height: parent.height
+ color: hifi.colors.black
anchors.left: parent.left
+ anchors.top: parent.top
+ visible: styleData.index > 0
+
+ Rectangle {
+ width: 1
+ height: 1
+ color: hifi.colors.baseGray
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ }
+ }
+
+ Rectangle { // Active underline.
+ width: parent.width - (styleData.index > 0 ? 1 : 0)
+ height: 1
+ anchors.right: parent.right
anchors.bottom: parent.bottom
+ color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.baseGray
}
}
- Rectangle { // Active underline.
- width: parent.width - (styleData.index > 0 ? 1 : 0)
- height: 1
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.baseGray
- }
+ tabOverlap: 0
}
-
- tabOverlap: 0
}
}
@@ -224,7 +234,6 @@ ScrollingWindow {
return;
}
-
if (properties.width) {
tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x);
}
diff --git a/interface/resources/qml/controls-uit/BaseWebView.qml b/interface/resources/qml/controls-uit/BaseWebView.qml
index cefaf653fc..ef4764b08f 100644
--- a/interface/resources/qml/controls-uit/BaseWebView.qml
+++ b/interface/resources/qml/controls-uit/BaseWebView.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtWebEngine 1.1
+import QtWebEngine 1.2
WebEngineView {
id: root
diff --git a/interface/resources/qml/controls-uit/ContentSection.qml b/interface/resources/qml/controls-uit/ContentSection.qml
index 98350a9333..47a13e9262 100644
--- a/interface/resources/qml/controls-uit/ContentSection.qml
+++ b/interface/resources/qml/controls-uit/ContentSection.qml
@@ -109,8 +109,13 @@ Column {
}
MouseArea {
+ // Events are propogated so that any active control is defocused.
anchors.fill: parent
- onClicked: toggleCollapsed()
+ propagateComposedEvents: true
+ onPressed: {
+ toggleCollapsed();
+ mouse.accepted = false;
+ }
}
}
diff --git a/interface/resources/qml/controls/Key.qml b/interface/resources/qml/controls-uit/Key.qml
similarity index 100%
rename from interface/resources/qml/controls/Key.qml
rename to interface/resources/qml/controls-uit/Key.qml
diff --git a/interface/resources/qml/controls/Keyboard.qml b/interface/resources/qml/controls-uit/Keyboard.qml
similarity index 99%
rename from interface/resources/qml/controls/Keyboard.qml
rename to interface/resources/qml/controls-uit/Keyboard.qml
index eb34740402..1d957ce9cb 100644
--- a/interface/resources/qml/controls/Keyboard.qml
+++ b/interface/resources/qml/controls-uit/Keyboard.qml
@@ -304,13 +304,13 @@ Item {
Key {
id: key31
width: 43
- glyph: ","
+ glyph: "_"
}
Key {
id: key33
width: 43
- glyph: "."
+ glyph: "?"
}
Key {
diff --git a/interface/resources/qml/controls/KeyboardPunctuation.qml b/interface/resources/qml/controls-uit/KeyboardPunctuation.qml
similarity index 99%
rename from interface/resources/qml/controls/KeyboardPunctuation.qml
rename to interface/resources/qml/controls-uit/KeyboardPunctuation.qml
index 6fef366772..485468b46a 100644
--- a/interface/resources/qml/controls/KeyboardPunctuation.qml
+++ b/interface/resources/qml/controls-uit/KeyboardPunctuation.qml
@@ -208,49 +208,49 @@ Item {
Key {
id: key22
width: 43
- glyph: "_"
+ glyph: ","
}
Key {
id: key23
width: 43
- glyph: ";"
+ glyph: "."
}
Key {
id: key24
width: 43
- glyph: ":"
+ glyph: ";"
}
Key {
id: key25
width: 43
- glyph: "'"
+ glyph: ":"
}
Key {
id: key26
width: 43
- glyph: "\""
+ glyph: "'"
}
Key {
id: key31
width: 43
- glyph: "<"
+ glyph: "\""
}
Key {
id: key33
width: 43
- glyph: ">"
+ glyph: "<"
}
Key {
id: key36
width: 43
- glyph: "?"
+ glyph: ">"
}
}
diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml
index 22c751fb24..c3381ab824 100644
--- a/interface/resources/qml/controls/WebView.qml
+++ b/interface/resources/qml/controls/WebView.qml
@@ -1,6 +1,7 @@
import QtQuick 2.5
import QtWebEngine 1.1
import QtWebChannel 1.0
+import "../controls-uit" as HiFiControls
Item {
property alias url: root.url
@@ -105,7 +106,7 @@ Item {
}
// virtual keyboard, letters
- Keyboard {
+ HiFiControls.Keyboard {
id: keyboard1
y: keyboardRaised ? parent.height : 0
height: keyboardRaised ? 200 : 0
@@ -119,7 +120,7 @@ Item {
anchors.bottomMargin: 0
}
- KeyboardPunctuation {
+ HiFiControls.KeyboardPunctuation {
id: keyboard2
y: keyboardRaised ? parent.height : 0
height: keyboardRaised ? 200 : 0
diff --git a/interface/resources/qml/dialogs/CustomQueryDialog.qml b/interface/resources/qml/dialogs/CustomQueryDialog.qml
index d1fb885e0b..563dfc3099 100644
--- a/interface/resources/qml/dialogs/CustomQueryDialog.qml
+++ b/interface/resources/qml/dialogs/CustomQueryDialog.qml
@@ -22,6 +22,7 @@ ModalWindow {
implicitWidth: 640;
implicitHeight: 320;
visible: true;
+ keyboardEnabled: false // Disable ModalWindow's keyboard.
signal selected(var result);
signal canceled();
@@ -50,6 +51,10 @@ ModalWindow {
}
}
+ property bool keyboardRaised: false
+ property bool punctuationMode: false
+ onKeyboardRaisedChanged: d.resize();
+
property var warning: "";
property var result;
@@ -110,7 +115,9 @@ ModalWindow {
var targetWidth = Math.max(titleWidth, pane.width);
var targetHeight = (textField.visible ? textField.controlHeight + hifi.dimensions.contentSpacing.y : 0) +
(extraInputs.visible ? extraInputs.height + hifi.dimensions.contentSpacing.y : 0) +
- (buttons.height + 3 * hifi.dimensions.contentSpacing.y);
+ (buttons.height + 3 * hifi.dimensions.contentSpacing.y) +
+ (root.keyboardRaised ? (200 + hifi.dimensions.contentSpacing.y) : 0);
+
root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth);
root.height = (targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ?
d.maxHeight : targetHeight);
@@ -130,7 +137,6 @@ ModalWindow {
left: parent.left;
right: parent.right;
margins: 0;
- bottomMargin: hifi.dimensions.contentSpacing.y;
}
// FIXME make a text field type that can be bound to a history for autocompletion
@@ -142,7 +148,43 @@ ModalWindow {
anchors {
left: parent.left;
right: parent.right;
- bottom: parent.bottom;
+ bottom: keyboard.top;
+ bottomMargin: hifi.dimensions.contentSpacing.y;
+ }
+ }
+
+ Item {
+ id: keyboard
+
+ height: keyboardRaised ? 200 : 0
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ bottomMargin: keyboardRaised ? hifi.dimensions.contentSpacing.y : 0
+ }
+
+ Keyboard {
+ id: keyboard1
+ visible: keyboardRaised && !punctuationMode
+ enabled: keyboardRaised && !punctuationMode
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+ }
+
+ KeyboardPunctuation {
+ id: keyboard2
+ visible: keyboardRaised && punctuationMode
+ enabled: keyboardRaised && punctuationMode
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
}
}
}
diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml
index ff8be580db..0942e728f9 100644
--- a/interface/resources/qml/dialogs/FileDialog.qml
+++ b/interface/resources/qml/dialogs/FileDialog.qml
@@ -27,7 +27,7 @@ ModalWindow {
id: root
resizable: true
implicitWidth: 480
- implicitHeight: 360
+ implicitHeight: 360 + (fileDialogItem.keyboardRaised ? 200 + hifi.dimensions.contentSpacing.y : 0)
minSize: Qt.vector2d(360, 240)
draggable: true
@@ -100,16 +100,23 @@ ModalWindow {
}
Item {
+ id: fileDialogItem
clip: true
width: pane.width
height: pane.height
anchors.margins: 0
+ property bool keyboardRaised: false
+ property bool punctuationMode: false
+
MouseArea {
// Clear selection when click on internal unused area.
anchors.fill: parent
drag.target: root
- onClicked: d.clearSelection()
+ onClicked: {
+ d.clearSelection();
+ frame.forceActiveFocus(); // Defocus text field so that the keyboard gets hidden.
+ }
}
Row {
@@ -619,7 +626,7 @@ ModalWindow {
left: parent.left
right: selectionType.visible ? selectionType.left: parent.right
rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0
- bottom: buttonRow.top
+ bottom: keyboard1.top
bottomMargin: hifi.dimensions.contentSpacing.y
}
readOnly: !root.saveDialog
@@ -640,6 +647,28 @@ ModalWindow {
KeyNavigation.right: openButton
}
+ Keyboard {
+ id: keyboard1
+ height: parent.keyboardRaised ? 200 : 0
+ visible: parent.keyboardRaised && !parent.punctuationMode
+ enabled: visible
+ anchors.right: parent.right
+ anchors.left: parent.left
+ anchors.bottom: buttonRow.top
+ anchors.bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0
+ }
+
+ KeyboardPunctuation {
+ id: keyboard2
+ height: parent.keyboardRaised ? 200 : 0
+ visible: parent.keyboardRaised && parent.punctuationMode
+ enabled: visible
+ anchors.right: parent.right
+ anchors.left: parent.left
+ anchors.bottom: buttonRow.top
+ anchors.bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0
+ }
+
Row {
id: buttonRow
anchors {
diff --git a/interface/resources/qml/dialogs/PreferencesDialog.qml b/interface/resources/qml/dialogs/PreferencesDialog.qml
index 5278118a22..ac9aad0e4a 100644
--- a/interface/resources/qml/dialogs/PreferencesDialog.qml
+++ b/interface/resources/qml/dialogs/PreferencesDialog.qml
@@ -97,9 +97,9 @@ ScrollingWindow {
footer: Row {
anchors {
- right: parent.right;
+ top: parent.top
+ right: parent.right
rightMargin: hifi.dimensions.contentMargin.x
- verticalCenter: parent.verticalCenter
}
spacing: hifi.dimensions.contentSpacing.x
diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml
index 05cb347169..cf1b1e370a 100644
--- a/interface/resources/qml/dialogs/QueryDialog.qml
+++ b/interface/resources/qml/dialogs/QueryDialog.qml
@@ -53,11 +53,17 @@ ModalWindow {
}
Item {
+ id: modalWindowItem
clip: true
width: pane.width
height: pane.height
anchors.margins: 0
+ property bool keyboardRaised: false
+ property bool punctuationMode: false
+
+ onKeyboardRaisedChanged: d.resize();
+
QtObject {
id: d
readonly property int minWidth: 480
@@ -69,14 +75,14 @@ ModalWindow {
var targetWidth = Math.max(titleWidth, pane.width)
var targetHeight = (items ? comboBox.controlHeight : textResult.controlHeight) + 5 * hifi.dimensions.contentSpacing.y + buttons.height
root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth)
- root.height = (targetHeight < d.minHeight) ? d.minHeight: ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)
+ root.height = ((targetHeight < d.minHeight) ? d.minHeight: ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)) + (modalWindowItem.keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : 0)
}
}
Item {
anchors {
top: parent.top
- bottom: buttons.top;
+ bottom: keyboard1.top;
left: parent.left;
right: parent.right;
margins: 0
@@ -110,6 +116,35 @@ ModalWindow {
}
}
+ // virtual keyboard, letters
+ Keyboard {
+ id: keyboard1
+ y: parent.keyboardRaised ? parent.height : 0
+ height: parent.keyboardRaised ? 200 : 0
+ visible: parent.keyboardRaised && !parent.punctuationMode
+ enabled: parent.keyboardRaised && !parent.punctuationMode
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+ anchors.left: parent.left
+ anchors.leftMargin: 0
+ anchors.bottom: buttons.top
+ anchors.bottomMargin: parent.keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0
+ }
+
+ KeyboardPunctuation {
+ id: keyboard2
+ y: parent.keyboardRaised ? parent.height : 0
+ height: parent.keyboardRaised ? 200 : 0
+ visible: parent.keyboardRaised && parent.punctuationMode
+ enabled: parent.keyboardRaised && parent.punctuationMode
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+ anchors.left: parent.left
+ anchors.leftMargin: 0
+ anchors.bottom: buttons.top
+ anchors.bottomMargin: parent.keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0
+ }
+
Flow {
id: buttons
focus: true
diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml
index 15467f8021..cc9a570d47 100644
--- a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml
+++ b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml
@@ -45,7 +45,7 @@ ScrollingWindow {
Rectangle {
width: parent.width
- height: root.height
+ height: root.height - (keyboardRaised ? 200 : 0)
radius: 4
color: hifi.colors.baseGray
@@ -128,6 +128,10 @@ ScrollingWindow {
}
onCountChanged: MyAvatar.setAttachmentsVariant(attachments);
}
+
+ function scrollBy(delta) {
+ flickableItem.contentY += delta;
+ }
}
}
@@ -204,5 +208,22 @@ ScrollingWindow {
}
}
}
+
+ onKeyboardRaisedChanged: {
+ if (keyboardRaised) {
+ // Scroll to item with focus if necessary.
+ var footerHeight = newAttachmentButton.height + buttonRow.height + 3 * hifi.dimensions.contentSpacing.y;
+ var delta = activator.mouseY
+ - (activator.height + activator.y - 200 - footerHeight - hifi.dimensions.controlLineHeight);
+
+ if (delta > 0) {
+ scrollView.scrollBy(delta);
+ } else {
+ // HACK: Work around for case where are 100% scrolled; stops window from erroneously scrolling to 100% when show keyboard.
+ scrollView.scrollBy(-1);
+ scrollView.scrollBy(1);
+ }
+ }
+ }
}
diff --git a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml
index aeffb8e4bf..a5a254f605 100644
--- a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml
+++ b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml
@@ -30,7 +30,7 @@ ScrollingWindow {
Rectangle {
width: parent.width
- height: root.height
+ height: root.height - (keyboardRaised ? 200 : 0)
radius: 4
color: hifi.colors.baseGray
diff --git a/interface/resources/qml/windows/ScrollingWindow.qml b/interface/resources/qml/windows/ScrollingWindow.qml
index f1dc744344..ce4bd45cff 100644
--- a/interface/resources/qml/windows/ScrollingWindow.qml
+++ b/interface/resources/qml/windows/ScrollingWindow.qml
@@ -1,3 +1,4 @@
+
//
// Window.qml
//
@@ -15,6 +16,7 @@ import QtGraphicalEffects 1.0
import "."
import "../styles-uit"
+import "../controls-uit" as HiFiControls
// FIXME how do I set the initial position of a window without
// overriding places where the a individual client of the window
@@ -23,12 +25,18 @@ import "../styles-uit"
// FIXME how to I enable dragging without allowing the window to lay outside
// of the desktop? How do I ensure when the desktop resizes all the windows
// are still at least partially visible?
+
Window {
id: window
HifiConstants { id: hifi }
- children: [ swallower, frame, pane, activator ]
+ children: [ swallower, frame, defocuser, pane, activator ]
property var footer: Item { } // Optional static footer at the bottom of the dialog.
+ readonly property var footerContentHeight: footer.height > 0 ? (footer.height + 2 * hifi.dimensions.contentSpacing.y + 3) : 0
+
+ property bool keyboardEnabled: true // Set false if derived control implements its own keyboard.
+ property bool keyboardRaised: false
+ property bool punctuationMode: false
// Scrollable window content.
// FIXME this should not define any visual content in this type. The base window
@@ -73,7 +81,7 @@ Window {
verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
anchors.fill: parent
anchors.rightMargin: parent.isScrolling ? 1 : 0
- anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0
+ anchors.bottomMargin: footerPane.height
style: ScrollViewStyle {
@@ -116,21 +124,36 @@ Window {
}
}
+ function scrollBy(delta) {
+ scrollView.flickableItem.contentY += delta;
+ }
+
Rectangle {
// Optional non-scrolling footer.
id: footerPane
+
+ property alias keyboardEnabled: window.keyboardEnabled
+ property alias keyboardRaised: window.keyboardRaised
+ property alias punctuationMode: window.punctuationMode
+
anchors {
left: parent.left
bottom: parent.bottom
}
width: parent.contentWidth
- height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3
+ height: footerContentHeight + (keyboardEnabled && keyboardRaised ? 200 : 0)
color: hifi.colors.baseGray
- visible: footer.height > 0
+ visible: footer.height > 0 || keyboardEnabled && keyboardRaised
Item {
// Horizontal rule.
- anchors.fill: parent
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ }
+
+ visible: footer.height > 0
Rectangle {
width: parent.width
@@ -148,10 +171,53 @@ Window {
}
Item {
- anchors.fill: parent
- anchors.topMargin: 3 // Horizontal rule.
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: parent.top
+ topMargin: hifi.dimensions.contentSpacing.y + 3
+ }
children: [ footer ]
}
+
+ HiFiControls.Keyboard {
+ id: keyboard1
+ height: parent.keyboardEnabled && parent.keyboardRaised ? 200 : 0
+ visible: parent.keyboardEnabled && parent.keyboardRaised && !parent.punctuationMode
+ enabled: parent.keyboardEnabled && parent.keyboardRaised && !parent.punctuationMode
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+ }
+
+ HiFiControls.KeyboardPunctuation {
+ id: keyboard2
+ height: parent.keyboardEnabled && parent.keyboardRaised ? 200 : 0
+ visible: parent.keyboardEnabled && parent.keyboardRaised && parent.punctuationMode
+ enabled: parent.keyboardEnabled && parent.keyboardRaised && parent.punctuationMode
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+ }
+ }
+ }
+
+ onKeyboardRaisedChanged: {
+ if (keyboardEnabled && keyboardRaised) {
+ var delta = activator.mouseY
+ - (activator.height + activator.y - 200 - footerContentHeight - hifi.dimensions.controlLineHeight);
+
+ if (delta > 0) {
+ pane.scrollBy(delta);
+ } else {
+ // HACK: Work around for case where are 100% scrolled; stops window from erroneously scrolling to 100% when show keyboard.
+ pane.scrollBy(-1);
+ pane.scrollBy(1);
+ }
}
}
}
diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml
index 40ef74c59b..35e0fb961c 100644
--- a/interface/resources/qml/windows/Window.qml
+++ b/interface/resources/qml/windows/Window.qml
@@ -44,7 +44,7 @@ Fadable {
implicitHeight: content ? content.height : 0
implicitWidth: content ? content.width : 0
x: desktop.invalid_position; y: desktop.invalid_position;
- children: [ swallower, frame, content, activator ]
+ children: [ swallower, frame, defocuser, content, activator ]
//
// Custom properties
@@ -122,6 +122,21 @@ Fadable {
}
}
+ // This mouse area defocuses the current control so that the HMD keyboard gets hidden.
+ property var defocuser: MouseArea {
+ width: frame.decoration ? frame.decoration.width : window.width
+ height: frame.decoration ? frame.decoration.height : window.height
+ x: frame.decoration ? frame.decoration.anchors.leftMargin : 0
+ y: frame.decoration ? frame.decoration.anchors.topMargin : 0
+ propagateComposedEvents: true
+ acceptedButtons: Qt.AllButtons
+ enabled: window.visible
+ onPressed: {
+ frame.forceActiveFocus();
+ mouse.accepted = false;
+ }
+ }
+
// This mouse area serves to swallow mouse events while the mouse is over the window
// to prevent things like mouse wheel events from reaching the application and changing
// the camera if the user is scrolling through a list and gets to the end.
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 610144b2cc..ffce2f489a 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -486,7 +486,7 @@ bool setupEssentials(int& argc, char** argv) {
// FIXME move to header, or better yet, design some kind of UI manager
// to take care of highlighting keyboard focused items, rather than
// continuing to overburden Application.cpp
-Cube3DOverlay* _keyboardFocusHighlight{ nullptr };
+std::shared_ptr _keyboardFocusHighlight{ nullptr };
int _keyboardFocusHighlightID{ -1 };
@@ -3625,7 +3625,7 @@ void Application::setKeyboardFocusEntity(EntityItemID entityItemID) {
_keyboardFocusedItem.set(entityItemID);
_lastAcceptedKeyPress = usecTimestampNow();
if (_keyboardFocusHighlightID < 0 || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) {
- _keyboardFocusHighlight = new Cube3DOverlay();
+ _keyboardFocusHighlight = std::make_shared();
_keyboardFocusHighlight->setAlpha(1.0f);
_keyboardFocusHighlight->setBorderSize(1.0f);
_keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 });
diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
index e415062e5c..0b36e9db8b 100644
--- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
@@ -38,39 +38,6 @@ 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 WebEntityAPIHelper::synthesizeKeyPress(QString key) {
- if (_renderableWebEntityItem) {
- _renderableWebEntityItem->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 {
- // special case to handle raising and lowering the virtual keyboard
- if (message.type() == QVariant::String && message.toString() == "_RAISE_KEYBOARD" && _renderableWebEntityItem) {
- if (_renderableWebEntityItem) {
- _renderableWebEntityItem->setKeyboardRaised(true);
- }
- } else if (message.type() == QVariant::String && message.toString() == "_LOWER_KEYBOARD" && _renderableWebEntityItem) {
- if (_renderableWebEntityItem) {
- _renderableWebEntityItem->setKeyboardRaised(false);
- }
- } else {
- emit webEventReceived(message);
- }
- }
-}
-
EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer entity{ new RenderableWebEntityItem(entityID) };
entity->setProperties(properties);
@@ -85,21 +52,9 @@ RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemI
_touchDevice.setType(QTouchDevice::TouchScreen);
_touchDevice.setName("RenderableWebEntityItemTouchDevice");
_touchDevice.setMaximumTouchPoints(4);
-
- _webEntityAPIHelper = new WebEntityAPIHelper;
- _webEntityAPIHelper->setRenderableWebEntityItem(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() {
- _webEntityAPIHelper->setRenderableWebEntityItem(nullptr);
- _webEntityAPIHelper->deleteLater();
destroyWebSurface();
qDebug() << "Destroyed web entity " << getID();
}
@@ -148,10 +103,16 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject));
});
_webSurface->resume();
- _webSurface->getRootItem()->setProperty("eventBridge", QVariant::fromValue(_webEntityAPIHelper));
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
- _webSurface->getRootContext()->setContextProperty("webEntity", _webEntityAPIHelper);
+
+ // forward web events to EntityScriptingInterface
+ auto entities = DependencyManager::get();
+ const EntityItemID entityItemID = getID();
+ QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, [=](const QVariant& message) {
+ emit entities->webEventReceived(entityItemID, message);
+ });
+
// Restore the original GL context
currentContext->makeCurrent(currentSurface);
@@ -370,7 +331,6 @@ void RenderableWebEntityItem::destroyWebSurface() {
}
}
-
void RenderableWebEntityItem::update(const quint64& now) {
auto interval = now - _lastRenderTime;
if (interval > MAX_NO_RENDER_INTERVAL) {
@@ -378,78 +338,13 @@ void RenderableWebEntityItem::update(const quint64& now) {
}
}
-
bool RenderableWebEntityItem::isTransparent() {
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
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 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) {
- int i;
- for (i = 0; i < byteArray.size(); i++) {
- if ((char)ptr[i] != byteArray[i]) {
- return false;
- }
- }
- return ptr[i] == 0x00;
-}
-
-void RenderableWebEntityItem::synthesizeKeyPress(QString key) {
- auto eventHandler = getEventHandler();
- if (eventHandler) {
- auto utf8Key = key.toUtf8();
-
- int scanCode = (int)utf8Key[0];
- QString keyString = key;
- 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;
- 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);
- QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, scanCode, Qt::NoModifier, keyString);
- QCoreApplication::postEvent(eventHandler, pressEvent);
- QCoreApplication::postEvent(eventHandler, releaseEvent);
- }
-}
-
void RenderableWebEntityItem::emitScriptEvent(const QVariant& message) {
- if (_webEntityAPIHelper) {
- _webEntityAPIHelper->emitScriptEvent(message);
- }
-}
-
-void RenderableWebEntityItem::setKeyboardRaised(bool raised) {
-
- // raise the keyboard only while in HMD mode and it's being requested.
- bool value = AbstractViewStateInterface::instance()->isHMDMode() && raised;
-
if (_webSurface) {
- auto rootItem = _webSurface->getRootItem();
- if (rootItem) {
- rootItem->setProperty("keyboardRaised", QVariant(value));
- }
+ _webSurface->emitScriptEvent(message);
}
}
diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h
index b7caaae68c..5414f43dc8 100644
--- a/libraries/entities-renderer/src/RenderableWebEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h
@@ -13,36 +13,18 @@
#include
#include
#include
+#include
#include
#include "RenderableEntityItem.h"
-class OffscreenQmlSurface;
+
class QWindow;
class QObject;
class EntityTreeRenderer;
class RenderableWebEntityItem;
-class WebEntityAPIHelper : public QObject {
- Q_OBJECT
-public:
- void setRenderableWebEntityItem(RenderableWebEntityItem* renderableWebEntityItem) {
- _renderableWebEntityItem = renderableWebEntityItem;
- }
- 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* _renderableWebEntityItem{ nullptr };
-};
class RenderableWebEntityItem : public WebEntityItem {
public:
@@ -64,15 +46,11 @@ public:
bool needsToCallUpdate() const override { return _webSurface != nullptr; }
virtual void emitScriptEvent(const QVariant& message) override;
- void setKeyboardRaised(bool raised);
SIMPLE_RENDERABLE();
virtual bool isTransparent() override;
-public:
- void synthesizeKeyPress(QString key);
-
private:
bool buildWebSurface(EntityTreeRenderer* renderer);
void destroyWebSurface();
@@ -86,7 +64,6 @@ private:
QTouchEvent _lastTouchEvent { QEvent::TouchUpdate };
uint64_t _lastRenderTime{ 0 };
QTouchDevice _touchDevice;
- WebEntityAPIHelper* _webEntityAPIHelper;
QMetaObject::Connection _mousePressConnection;
QMetaObject::Connection _mouseReleaseConnection;
diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp
index 29296425e7..f48e2e6092 100644
--- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp
+++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp
@@ -41,16 +41,16 @@
#include "Context.h"
QString fixupHifiUrl(const QString& urlString) {
- static const QString ACCESS_TOKEN_PARAMETER = "access_token";
- static const QString ALLOWED_HOST = "metaverse.highfidelity.com";
+ static const QString ACCESS_TOKEN_PARAMETER = "access_token";
+ static const QString ALLOWED_HOST = "metaverse.highfidelity.com";
QUrl url(urlString);
- QUrlQuery query(url);
- if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) {
- auto accountManager = DependencyManager::get();
- query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager->getAccountInfo().getAccessToken().token);
- url.setQuery(query.query());
- return url.toString();
- }
+ QUrlQuery query(url);
+ if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) {
+ auto accountManager = DependencyManager::get();
+ query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager->getAccountInfo().getAccessToken().token);
+ url.setQuery(query.query());
+ return url.toString();
+ }
return urlString;
}
@@ -403,13 +403,13 @@ QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::functionloadUrl(qmlSource, QQmlComponent::PreferSynchronous);
if (_qmlComponent->isLoading()) {
- connect(_qmlComponent, &QQmlComponent::statusChanged, this,
+ connect(_qmlComponent, &QQmlComponent::statusChanged, this,
[this, f](QQmlComponent::Status){
finishQmlLoad(f);
});
return nullptr;
}
-
+
return finishQmlLoad(f);
}
@@ -427,6 +427,19 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::functionbeginCreate(newContext);
if (_qmlComponent->isError()) {
@@ -439,6 +452,9 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::functionsetProperty("eventBridge", QVariant::fromValue(this));
+ newContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject));
+
f(newContext, newObject);
_qmlComponent->completeCreate();
@@ -446,7 +462,7 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function(newObject);
if (newItem) {
- // Make sure we make items focusable (critical for
+ // Make sure we make items focusable (critical for
// supporting keyboard shortcuts)
newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
}
@@ -474,11 +490,11 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::functiontype()) {
case QEvent::Resize: {
QResizeEvent* resizeEvent = static_cast(event);
@@ -610,6 +625,9 @@ void OffscreenQmlSurface::pause() {
void OffscreenQmlSurface::resume() {
_paused = false;
_render = true;
+
+ getRootItem()->setProperty("eventBridge", QVariant::fromValue(this));
+ getRootContext()->setContextProperty("webEntity", this);
}
bool OffscreenQmlSurface::isPaused() const {
@@ -667,15 +685,37 @@ QVariant OffscreenQmlSurface::returnFromUiThread(std::function funct
return function();
}
+void OffscreenQmlSurface::focusDestroyed(QObject *obj) {
+ _currentFocusItem = nullptr;
+}
+
void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) {
- if (!object) {
+ QQuickItem* item = dynamic_cast(object);
+ if (!item) {
setFocusText(false);
+ _currentFocusItem = nullptr;
return;
}
QInputMethodQueryEvent query(Qt::ImEnabled);
qApp->sendEvent(object, &query);
setFocusText(query.value(Qt::ImEnabled).toBool());
+
+ if (_currentFocusItem) {
+ disconnect(_currentFocusItem, &QObject::destroyed, this, 0);
+ }
+
+ // Raise and lower keyboard for QML text fields.
+ // HTML text fields are handled in emitWebEvent() methods - testing READ_ONLY_PROPERTY prevents action for HTML files.
+ const char* READ_ONLY_PROPERTY = "readOnly";
+ bool raiseKeyboard = item->hasActiveFocus() && item->property(READ_ONLY_PROPERTY) == false;
+ if (_currentFocusItem && !raiseKeyboard) {
+ setKeyboardRaised(_currentFocusItem, false);
+ }
+ setKeyboardRaised(item, raiseKeyboard); // Always set focus so that alphabetic / numeric setting is updated.
+
+ _currentFocusItem = item;
+ connect(_currentFocusItem, &QObject::destroyed, this, &OffscreenQmlSurface::focusDestroyed);
}
void OffscreenQmlSurface::setFocusText(bool newFocusText) {
@@ -685,4 +725,103 @@ void OffscreenQmlSurface::setFocusText(bool newFocusText) {
}
}
+// 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, 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) {
+ int i;
+ for (i = 0; i < byteArray.size(); i++) {
+ if ((char)ptr[i] != byteArray[i]) {
+ return false;
+ }
+ }
+ return ptr[i] == 0x00;
+}
+
+void OffscreenQmlSurface::synthesizeKeyPress(QString key) {
+ auto eventHandler = getEventHandler();
+ if (eventHandler) {
+ auto utf8Key = key.toUtf8();
+
+ int scanCode = (int)utf8Key[0];
+ QString keyString = key;
+ 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;
+ 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);
+ QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, scanCode, Qt::NoModifier, keyString);
+ QCoreApplication::postEvent(eventHandler, pressEvent);
+ QCoreApplication::postEvent(eventHandler, releaseEvent);
+ }
+}
+
+void OffscreenQmlSurface::setKeyboardRaised(QObject* object, bool raised, bool numeric) {
+ if (!object) {
+ return;
+ }
+
+ QQuickItem* item = dynamic_cast(object);
+ while (item) {
+ // Numeric value may be set in parameter from HTML UI; for QML UI, detect numeric fields here.
+ numeric = numeric || QString(item->metaObject()->className()).left(7) == "SpinBox";
+
+ if (item->property("keyboardRaised").isValid()) {
+ if (item->property("punctuationMode").isValid()) {
+ item->setProperty("punctuationMode", QVariant(numeric));
+ }
+ item->setProperty("keyboardRaised", QVariant(raised));
+ return;
+ }
+ item = dynamic_cast(item->parentItem());
+ }
+}
+
+void OffscreenQmlSurface::emitScriptEvent(const QVariant& message) {
+ if (QThread::currentThread() != thread()) {
+ QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, message));
+ } else {
+ emit scriptEventReceived(message);
+ }
+}
+
+void OffscreenQmlSurface::emitWebEvent(const QVariant& message) {
+ if (QThread::currentThread() != thread()) {
+ QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, message));
+ } else {
+ // Special case to handle raising and lowering the virtual keyboard.
+ const QString RAISE_KEYBOARD = "_RAISE_KEYBOARD";
+ const QString RAISE_KEYBOARD_NUMERIC = "_RAISE_KEYBOARD_NUMERIC";
+ const QString LOWER_KEYBOARD = "_LOWER_KEYBOARD";
+ QString messageString = message.type() == QVariant::String ? message.toString() : "";
+ if (messageString.left(RAISE_KEYBOARD.length()) == RAISE_KEYBOARD) {
+ setKeyboardRaised(_currentFocusItem, true, messageString == RAISE_KEYBOARD_NUMERIC);
+ } else if (messageString == LOWER_KEYBOARD) {
+ setKeyboardRaised(_currentFocusItem, false);
+ } else {
+ emit webEventReceived(message);
+ }
+ }
+}
+
#include "OffscreenQmlSurface.moc"
diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h
index 30b9b2a58a..639213868c 100644
--- a/libraries/gl/src/gl/OffscreenQmlSurface.h
+++ b/libraries/gl/src/gl/OffscreenQmlSurface.h
@@ -30,7 +30,6 @@ class QQmlContext;
class QQmlComponent;
class QQuickWindow;
class QQuickItem;
-
class OffscreenQmlSurface : public QObject {
Q_OBJECT
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
@@ -74,6 +73,9 @@ public:
QPointF mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget);
bool eventFilter(QObject* originalDestination, QEvent* event) override;
+ void setKeyboardRaised(QObject* object, bool raised, bool numeric = false);
+ Q_INVOKABLE void synthesizeKeyPress(QString key);
+
using TextureAndFence = std::pair;
// Checks to see if a new texture is available. If one is, the function returns true and
// textureAndFence will be populated with the texture ID and a fence which will be signalled
@@ -90,6 +92,16 @@ signals:
public slots:
void onAboutToQuit();
+ void focusDestroyed(QObject *obj);
+
+ // 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:
bool filterEnabled(QObject* originalDestination, QEvent* event) const;
@@ -137,6 +149,8 @@ private:
uint8_t _maxFps { 60 };
MouseTranslator _mouseTranslator { [](const QPointF& p) { return p.toPoint(); } };
QWindow* _proxyWindow { nullptr };
+
+ QQuickItem* _currentFocusItem { nullptr };
};
#endif
diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp
index b964f305a4..84d0aa0489 100644
--- a/libraries/ui/src/QmlWebWindowClass.cpp
+++ b/libraries/ui/src/QmlWebWindowClass.cpp
@@ -43,7 +43,36 @@ void QmlWebWindowClass::emitWebEvent(const QVariant& webMessage) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage));
} else {
- emit webEventReceived(webMessage);
+ // Special case to handle raising and lowering the virtual keyboard.
+ const QString RAISE_KEYBOARD = "_RAISE_KEYBOARD";
+ const QString RAISE_KEYBOARD_NUMERIC = "_RAISE_KEYBOARD_NUMERIC";
+ const QString LOWER_KEYBOARD = "_LOWER_KEYBOARD";
+ QString messageString = webMessage.type() == QVariant::String ? webMessage.toString() : "";
+ if (messageString.left(RAISE_KEYBOARD.length()) == RAISE_KEYBOARD) {
+ setKeyboardRaised(asQuickItem(), true, messageString == RAISE_KEYBOARD_NUMERIC);
+ } else if (messageString == LOWER_KEYBOARD) {
+ setKeyboardRaised(asQuickItem(), false);
+ } else {
+ emit webEventReceived(webMessage);
+ }
+ }
+}
+
+void QmlWebWindowClass::setKeyboardRaised(QObject* object, bool raised, bool numeric) {
+ if (!object) {
+ return;
+ }
+
+ QQuickItem* item = dynamic_cast(object);
+ while (item) {
+ if (item->property("keyboardRaised").isValid()) {
+ if (item->property("punctuationMode").isValid()) {
+ item->setProperty("punctuationMode", QVariant(numeric));
+ }
+ item->setProperty("keyboardRaised", QVariant(raised));
+ return;
+ }
+ item = dynamic_cast(item->parentItem());
}
}
diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h
index 86d0e9b2c4..e32c6d5a04 100644
--- a/libraries/ui/src/QmlWebWindowClass.h
+++ b/libraries/ui/src/QmlWebWindowClass.h
@@ -33,6 +33,9 @@ signals:
protected:
QString qmlSource() const override { return "QmlWebWindow.qml"; }
+
+private:
+ void setKeyboardRaised(QObject* object, bool raised, bool numeric = false);
};
#endif
diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp
index 820476191a..f5e36492bd 100644
--- a/plugins/openvr/src/OpenVrHelpers.cpp
+++ b/plugins/openvr/src/OpenVrHelpers.cpp
@@ -44,7 +44,7 @@ static const uint32_t RELEASE_OPENVR_HMD_DELAY_MS = 5000;
bool isOculusPresent() {
bool result = false;
-#if defined(Q_OS_WIN32)
+#if defined(Q_OS_WIN32)
HANDLE oculusServiceEvent = ::OpenEventW(SYNCHRONIZE, FALSE, L"OculusHMDConnected");
// The existence of the service indicates a running Oculus runtime
if (oculusServiceEvent) {
@@ -54,7 +54,7 @@ bool isOculusPresent() {
}
::CloseHandle(oculusServiceEvent);
}
-#endif
+#endif
return result;
}
@@ -123,65 +123,12 @@ static bool _keyboardShown { false };
static bool _overlayRevealed { false };
static const uint32_t SHOW_KEYBOARD_DELAY_MS = 400;
-void showOpenVrKeyboard(bool show = true) {
- if (!_overlay) {
- return;
- }
-
- if (show) {
- // To avoid flickering the keyboard when a text element is only briefly selected,
- // show the keyboard asynchrnously after a very short delay, but only after we check
- // that the current focus object is still one that is text enabled
- QTimer::singleShot(SHOW_KEYBOARD_DELAY_MS, [] {
- auto offscreenUi = DependencyManager::get();
- auto currentFocus = offscreenUi->getWindow()->focusObject();
- QInputMethodQueryEvent query(Qt::ImEnabled | Qt::ImQueryInput | Qt::ImHints);
- qApp->sendEvent(currentFocus, &query);
- // Current focus isn't text enabled, bail early.
- if (!query.value(Qt::ImEnabled).toBool()) {
- return;
- }
- // We're going to show the keyboard now...
- _keyboardFocusObject = currentFocus;
- _currentHints = Qt::InputMethodHints(query.value(Qt::ImHints).toUInt());
- vr::EGamepadTextInputMode inputMode = vr::k_EGamepadTextInputModeNormal;
- if (_currentHints & Qt::ImhHiddenText) {
- inputMode = vr::k_EGamepadTextInputModePassword;
- }
- vr::EGamepadTextInputLineMode lineMode = vr::k_EGamepadTextInputLineModeSingleLine;
- if (_currentHints & Qt::ImhMultiLine) {
- lineMode = vr::k_EGamepadTextInputLineModeMultipleLines;
- }
- _existingText = query.value(Qt::ImSurroundingText).toString();
-
- auto showKeyboardResult = _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024,
- _existingText.toLocal8Bit().toStdString().c_str(), false, 0);
-
- if (vr::VROverlayError_None == showKeyboardResult) {
- _keyboardShown = true;
- // Try to position the keyboard slightly below where the user is looking.
- mat4 headPose = cancelOutRollAndPitch(toGlm(_nextSimPoseData.vrPoses[0].mDeviceToAbsoluteTracking));
- mat4 keyboardTransform = glm::translate(headPose, vec3(0, -0.5, -1));
- keyboardTransform = keyboardTransform * glm::rotate(mat4(), 3.14159f / 4.0f, vec3(-1, 0, 0));
- auto keyboardTransformVr = toOpenVr(keyboardTransform);
- _overlay->SetKeyboardTransformAbsolute(vr::ETrackingUniverseOrigin::TrackingUniverseStanding, &keyboardTransformVr);
- }
- });
- } else {
- _keyboardFocusObject = nullptr;
- if (_keyboardShown) {
- _overlay->HideKeyboard();
- _keyboardShown = false;
- }
- }
-}
-
void updateFromOpenVrKeyboardInput() {
auto chars = _overlay->GetKeyboardText(textArray, 8192);
auto newText = QString(QByteArray(textArray, chars));
_keyboardFocusObject->setProperty("text", newText);
//// TODO modify the new text to match the possible input hints:
- //// ImhDigitsOnly ImhFormattedNumbersOnly ImhUppercaseOnly ImhLowercaseOnly
+ //// ImhDigitsOnly ImhFormattedNumbersOnly ImhUppercaseOnly ImhLowercaseOnly
//// ImhDialableCharactersOnly ImhEmailCharactersOnly ImhUrlCharactersOnly ImhLatinOnly
//QInputMethodEvent event(_existingText, QList());
//event.setCommitString(newText, 0, _existingText.size());
@@ -208,11 +155,11 @@ void enableOpenVrKeyboard(PluginContainer* container) {
auto offscreenUi = DependencyManager::get();
_overlay = vr::VROverlay();
-
+
auto menu = container->getPrimaryMenu();
auto action = menu->getActionForOption(MenuOption::Overlays);
- // When the overlays are revealed, suppress the keyboard from appearing on text focus for a tenth of a second.
+ // When the overlays are revealed, suppress the keyboard from appearing on text focus for a tenth of a second.
_overlayMenuConnection = QObject::connect(action, &QAction::triggered, [action] {
if (action->isChecked()) {
_overlayRevealed = true;
@@ -220,23 +167,6 @@ void enableOpenVrKeyboard(PluginContainer* container) {
QTimer::singleShot(KEYBOARD_DELAY_MS, [&] { _overlayRevealed = false; });
}
});
-
- _focusConnection = QObject::connect(offscreenUi->getWindow(), &QQuickWindow::focusObjectChanged, [](QObject* object) {
- if (object != _keyboardFocusObject) {
- showOpenVrKeyboard(false);
- }
- });
-
- _focusTextConnection = QObject::connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [](bool focusText) {
- if (_openVrDisplayActive) {
- if (_overlayRevealed) {
- // suppress at most one text focus event
- _overlayRevealed = false;
- return;
- }
- showOpenVrKeyboard(focusText);
- }
- });
}
@@ -276,7 +206,7 @@ void handleOpenVrEvents() {
updateFromOpenVrKeyboardInput();
break;
- case vr::VREvent_KeyboardDone:
+ case vr::VREvent_KeyboardDone:
finishOpenVrKeyboardInput();
// FALL THROUGH
diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html
index 58dca4567f..6ea281e467 100644
--- a/scripts/system/html/entityList.html
+++ b/scripts/system/html/entityList.html
@@ -14,6 +14,7 @@
+
diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html
index 6de1eec7d0..5cc8b67b44 100644
--- a/scripts/system/html/entityProperties.html
+++ b/scripts/system/html/entityProperties.html
@@ -19,6 +19,7 @@
+
diff --git a/scripts/system/html/gridControls.html b/scripts/system/html/gridControls.html
index cd646fed51..c0bd87988d 100644
--- a/scripts/system/html/gridControls.html
+++ b/scripts/system/html/gridControls.html
@@ -16,6 +16,7 @@
+
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js
index e9075da3eb..60aa2ebe25 100644
--- a/scripts/system/html/js/entityList.js
+++ b/scripts/system/html/js/entityList.js
@@ -122,6 +122,8 @@ function loaded() {
focus: false,
entityIds: selection,
}));
+
+ refreshFooter();
}
function onRowDoubleClicked() {
@@ -184,6 +186,7 @@ function loaded() {
function clearEntities() {
entities = {};
entityList.clear();
+ refreshFooter();
}
var elSortOrder = {
@@ -236,13 +239,16 @@ function loaded() {
refreshFooter();
}
- function updateSelectedEntities(selectedEntities) {
+ function updateSelectedEntities(selectedIDs) {
var notFound = false;
for (var id in entities) {
entities[id].el.className = '';
}
- for (var i = 0; i < selectedEntities.length; i++) {
- var id = selectedEntities[i];
+
+ selectedEntities = [];
+ for (var i = 0; i < selectedIDs.length; i++) {
+ var id = selectedIDs[i];
+ selectedEntities.push(id);
if (id in entities) {
var entity = entities[id];
entity.el.className = 'selected';
@@ -251,10 +257,7 @@ function loaded() {
}
}
- // HACK: Fixes the footer and header text sometimes not displaying after adding or deleting entities.
- // The problem appears to be a bug in the Qt HTML/CSS rendering (Qt 5.5).
- document.getElementById("radius").focus();
- document.getElementById("radius").blur();
+ refreshFooter();
return notFound;
}
@@ -412,6 +415,8 @@ function loaded() {
augmentSpinButtons();
+ setUpKeyboardControl();
+
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
document.addEventListener("contextmenu", function (event) {
event.preventDefault();
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js
index 8ce3fbbe00..67aa8bdb13 100644
--- a/scripts/system/html/js/entityProperties.js
+++ b/scripts/system/html/js/entityProperties.js
@@ -1590,6 +1590,8 @@ function loaded() {
augmentSpinButtons();
+ setUpKeyboardControl();
+
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
document.addEventListener("contextmenu", function(event) {
event.preventDefault();
diff --git a/scripts/system/html/js/gridControls.js b/scripts/system/html/js/gridControls.js
index cc268bcbff..a245ed4cda 100644
--- a/scripts/system/html/js/gridControls.js
+++ b/scripts/system/html/js/gridControls.js
@@ -127,6 +127,8 @@ function loaded() {
augmentSpinButtons();
+ setUpKeyboardControl();
+
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' }));
});
diff --git a/scripts/system/html/js/keyboardControl.js b/scripts/system/html/js/keyboardControl.js
new file mode 100644
index 0000000000..964f5f5786
--- /dev/null
+++ b/scripts/system/html/js/keyboardControl.js
@@ -0,0 +1,67 @@
+//
+// keyboardControl.js
+//
+// Created by David Rowe on 28 Sep 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
+//
+
+function setUpKeyboardControl() {
+
+ var lowerTimer = null;
+ var isRaised = false;
+ var KEYBOARD_HEIGHT = 200;
+
+ function raiseKeyboard() {
+ if (lowerTimer !== null) {
+ clearTimeout(lowerTimer);
+ lowerTimer = null;
+ }
+
+ EventBridge.emitWebEvent("_RAISE_KEYBOARD" + (this.type === "number" ? "_NUMERIC" : ""));
+
+ if (!isRaised) {
+ var delta = this.getBoundingClientRect().bottom + 10 - (document.body.clientHeight - KEYBOARD_HEIGHT);
+ if (delta > 0) {
+ setTimeout(function () {
+ document.body.scrollTop += delta;
+ }, 500); // Allow time for keyboard to be raised in QML.
+ }
+ }
+
+ isRaised = true;
+ }
+
+ function doLowerKeyboard() {
+ EventBridge.emitWebEvent("_LOWER_KEYBOARD");
+ lowerTimer = null;
+ isRaised = false;
+ }
+
+ function lowerKeyboard() {
+ // Delay lowering keyboard a little in case immediately raise it again.
+ if (lowerTimer === null) {
+ lowerTimer = setTimeout(doLowerKeyboard, 20);
+ }
+ }
+
+ function documentBlur() {
+ // Action any pending Lower keyboard event immediately upon leaving document window so that they don't interfere with
+ // other Entities Editor tab.
+ if (lowerTimer !== null) {
+ clearTimeout(lowerTimer);
+ doLowerKeyboard();
+ }
+ }
+
+ var inputs = document.querySelectorAll("input[type=text], input[type=number], textarea");
+ for (var i = 0, length = inputs.length; i < length; i++) {
+ inputs[i].addEventListener("focus", raiseKeyboard);
+ inputs[i].addEventListener("blur", lowerKeyboard);
+ }
+
+ window.addEventListener("blur", documentBlur);
+}
+