();
+ updateFromOpenVrKeyboardInput();
// Simulate an enter press on the top level window to trigger the action
if (0 == (_currentHints & Qt::ImhMultiLine)) {
qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n")));
@@ -267,6 +271,11 @@ void handleOpenVrEvents() {
activeHmd->AcknowledgeQuit_Exiting();
break;
+ case vr::VREvent_KeyboardCharInput:
+ // Make the focused field match the keyboard results, inclusive of combining characters and such.
+ updateFromOpenVrKeyboardInput();
+ break;
+
case vr::VREvent_KeyboardDone:
finishOpenVrKeyboardInput();
diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js
index dc252afcf1..153d39f1f4 100644
--- a/scripts/defaultScripts.js
+++ b/scripts/defaultScripts.js
@@ -27,4 +27,6 @@ Script.load("system/controllers/grab.js");
Script.load("system/controllers/teleport.js");
Script.load("system/controllers/toggleAdvancedMovementForHandControllers.js")
Script.load("system/dialTone.js");
-Script.load("system/firstPersonHMD.js");
\ No newline at end of file
+Script.load("system/firstPersonHMD.js");
+Script.load("system/snapshot.js");
+
diff --git a/scripts/system/assets/images/tools/snap.svg b/scripts/system/assets/images/tools/snap.svg
new file mode 100755
index 0000000000..c540f307ae
--- /dev/null
+++ b/scripts/system/assets/images/tools/snap.svg
@@ -0,0 +1,109 @@
+
+
+
diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html
new file mode 100644
index 0000000000..db70a1910b
--- /dev/null
+++ b/scripts/system/html/SnapshotReview.html
@@ -0,0 +1,48 @@
+
+
+ Share
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Would you like to share your pic in the Snapshots feed?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css
new file mode 100644
index 0000000000..c2965f92e1
--- /dev/null
+++ b/scripts/system/html/css/SnapshotReview.css
@@ -0,0 +1,131 @@
+/*
+// SnapshotReview.css
+//
+// Created by Howard Stearns for David Rowe 8/22/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
+*/
+
+
+.snapshot-container {
+ width: 100%;
+ padding-top: 3px;
+}
+
+.snapshot-column-left {
+ width: 320px;
+ position: absolute;
+ padding-top: 8px;
+}
+
+.snapshot-column-right {
+ margin-left: 342px;
+}
+
+.snapshot-column-right > div > img {
+ width: 100%;
+}
+
+@media (max-width: 768px) {
+ .snapshot-column-left {
+ position: initial;
+ width: 100%;
+ }
+ .snapshot-column-right {
+ margin-left: 0;
+ width: 100%;
+ }
+ .snapshot-column-right > div > img {
+ margin-top: 18px !important;
+ }
+}
+
+.snapshot-column-right > div {
+ position: relative;
+ padding: 2px;
+}
+
+.snapshot-column-right > div > img {
+ border: 2px solid #575757;
+ margin: -2px;
+}
+
+hr {
+ padding-left: 0;
+ padding-right: 0;
+ margin: 21px 0;
+}
+
+.snapsection {
+ text-align: center;
+}
+
+.title {
+ text-transform: uppercase;
+ font-size: 12px;
+}
+
+.prompt {
+ font-family: Raleway-SemiBold;
+ font-size: 14px;
+}
+
+div.button {
+ padding-top: 21px;
+}
+
+.compound-button {
+ position: relative;
+ height: auto;
+}
+
+.compound-button input {
+ padding-left: 40px;
+}
+
+.compound-button .glyph {
+ display: inline-block;
+ position: absolute;
+ left: 12px;
+ top: 16px;
+ width: 23px;
+ height: 23px;
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgaGVpZ2h0PSI0MCIKICAgd2lkdGg9IjQwIgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgdmlld0JveD0iMCAwIDQwIDQwIgogICB5PSIwcHgiCiAgIHg9IjBweCIKICAgdmVyc2lvbj0iMS4xIj48bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGEzNCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48ZGM6dGl0bGU+PC9kYzp0aXRsZT48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczMyIiAvPjxzdHlsZQogICAgIGlkPSJzdHlsZTQiCiAgICAgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOiM0MTQwNDI7fQoJLnN0MXtmaWxsOiNDQ0NDQ0M7fQoJLnN0MntmaWxsOiMxMzk4QkI7fQoJLnN0M3tmaWxsOiMzMUQ4RkY7fQo8L3N0eWxlPjxnCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTEwKSIKICAgICBpZD0iTGF5ZXJfMSI+PGNpcmNsZQogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJjaXJjbGUxMyIKICAgICAgIHI9IjQuNDQwMDAwMSIKICAgICAgIGN5PSIxMjYuMTciCiAgICAgICBjeD0iMjAuNTQwMDAxIgogICAgICAgY2xhc3M9InN0MSIgLz48cGF0aAogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJwYXRoMTUiCiAgICAgICBkPSJtIDI4Ljg3LDEzOS4yNiBjIDAuMDEsLTAuMDEgMC4wMiwtMC4wMiAwLjAzLC0wLjAzIGwgMCwtMS44NiBjIDAsLTIuNjggLTIuMzMsLTQuNzcgLTUsLTQuNzcgbCAtNi40MiwwIGMgLTIuNjgsMCAtNC44NSwyLjA5IC00Ljg1LDQuNzcgbCAwLDEuODggMTYuMjQsMCB6IgogICAgICAgY2xhc3M9InN0MSIgLz48cGF0aAogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJwYXRoMTciCiAgICAgICBkPSJtIDM4LjE3LDEyMy40MiBjIDAsLTMuOTcgLTMuMjIsLTcuMTkgLTcuMTksLTcuMTkgbCAtMjAuMzEsMCBjIC0zLjk3LDAgLTcuMTksMy4yMiAtNy4xOSw3LjE5IGwgMCwxNC4xOCBjIDAsMy45NyAzLjIyLDcuMTkgNy4xOSw3LjE5IGwgMjAuMzEsMCBjIDMuOTcsMCA3LjE5LC0zLjIyIDcuMTksLTcuMTkgbCAwLC0xNC4xOCB6IG0gLTEuNzgsMTQuMjcgYyAwLDMuMDMgLTIuNDYsNS40OSAtNS40OSw1LjQ5IGwgLTIwLjMyLDAgYyAtMy4wMywwIC01LjQ5LC0yLjQ2IC01LjQ5LC01LjQ5IGwgMCwtMTQuMTkgYyAwLC0zLjAzIDIuNDYsLTUuNDkgNS40OSwtNS40OSBsIDIwLjMzLDAgYyAzLjAzLDAgNS40OSwyLjQ2IDUuNDksNS40OSBsIDAsMTQuMTkgeiIKICAgICAgIGNsYXNzPSJzdDEiIC8+PC9nPjxnCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTEwKSIKICAgICBpZD0iTGF5ZXJfMiIgLz48L3N2Zz4=);
+ background-repeat: no-repeat;
+ background-size: 23px 23px;
+}
+
+.setting {
+ display: inline-table;
+ height: 28px;
+}
+
+.setting label {
+ display: table-cell;
+ vertical-align: middle;
+ font-family: Raleway-SemiBold;
+ font-size: 14px;
+}
+
+.setting + .setting {
+ margin-left: 18px;
+}
+
+input[type=button].naked {
+ font-size: 40px;
+ line-height: 40px;
+ width: 30px;
+ padding: 0;
+ margin: 0 0 -6px 0;
+ position: relative;
+ top: -6px;
+ left: -8px;
+ background: none;
+}
+
+input[type=button].naked:hover {
+ color: #00b4ef;
+ background: none;
+}
diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js
new file mode 100644
index 0000000000..a6515df825
--- /dev/null
+++ b/scripts/system/html/js/SnapshotReview.js
@@ -0,0 +1,77 @@
+"use strict";
+//
+// SnapshotReview.js
+// scripts/system/html/js/
+//
+// Created by Howard Stearns 8/22/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
+//
+
+var paths = [], idCounter = 0, useCheckboxes;
+function addImage(data) {
+ var div = document.createElement("DIV"),
+ input = document.createElement("INPUT"),
+ label = document.createElement("LABEL"),
+ img = document.createElement("IMG"),
+ id = "p" + idCounter++;
+ function toggle() { data.share = input.checked; }
+ img.src = data.localPath;
+ div.appendChild(img);
+ data.share = true;
+ if (useCheckboxes) { // I'd rather use css, but the included stylesheet is quite particular.
+ // Our stylesheet(?) requires input.id to match label.for. Otherwise input doesn't display the check state.
+ label.setAttribute('for', id); // cannot do label.for =
+ input.id = id;
+ input.type = "checkbox";
+ input.checked = true;
+ input.addEventListener('change', toggle);
+ div.class = "property checkbox";
+ div.appendChild(input);
+ div.appendChild(label);
+ }
+ document.getElementById("snapshot-images").appendChild(div);
+ paths.push(data);
+
+}
+function handleShareButtons(shareMsg) {
+ var openFeed = document.getElementById('openFeed');
+ openFeed.checked = shareMsg.openFeedAfterShare;
+ openFeed.onchange = function () { EventBridge.emitWebEvent(openFeed.checked ? 'setOpenFeedTrue' : 'setOpenFeedFalse'); };
+ if (!shareMsg.canShare) {
+ // this means you may or may not be logged in, but can't share
+ // because you are not in a public place.
+ document.getElementById("sharing").innerHTML = "Snapshots can be shared when they're taken in shareable places.";
+ }
+}
+window.onload = function () {
+ // Something like the following will allow testing in a browser.
+ //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'});
+ //addImage({localPath: 'http://lorempixel.com/1512/1680'});
+ openEventBridge(function () {
+ // Set up a handler for receiving the data, and tell the .js we are ready to receive it.
+ EventBridge.scriptEventReceived.connect(function (message) {
+ // last element of list contains a bool for whether or not we can share stuff
+ var shareMsg = message.pop();
+ handleShareButtons(shareMsg);
+
+ // rest are image paths which we add
+ useCheckboxes = message.length > 1;
+ message.forEach(addImage);
+ });
+ EventBridge.emitWebEvent('ready');
+ });
+
+};
+// beware of bug: Cannot send objects at top level. (Nested in arrays is fine.)
+function shareSelected() {
+ EventBridge.emitWebEvent(paths);
+};
+function doNotShare() {
+ EventBridge.emitWebEvent([]);
+};
+function snapshotSettings() {
+ EventBridge.emitWebEvent("openSettings");
+};
diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js
index 98a31d708c..34f9814d8a 100644
--- a/scripts/system/notifications.js
+++ b/scripts/system/notifications.js
@@ -527,12 +527,14 @@ function onDomainConnectionRefused(reason) {
createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED);
}
-function onSnapshotTaken(path) {
- var imageProperties = {
- path: Script.resolvePath("file:///" + path),
- aspectRatio: Window.innerWidth / Window.innerHeight
+function onSnapshotTaken(path, notify) {
+ if (notify) {
+ var imageProperties = {
+ path: Script.resolvePath("file:///" + path),
+ aspectRatio: Window.innerWidth / Window.innerHeight
+ }
+ createNotification(wordWrap("Snapshot saved to " + path), NotificationType.SNAPSHOT, imageProperties);
}
- createNotification(wordWrap("Snapshot saved to " + path), NotificationType.SNAPSHOT, imageProperties);
}
// handles mouse clicks on buttons
diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js
new file mode 100644
index 0000000000..2f3b8862c2
--- /dev/null
+++ b/scripts/system/snapshot.js
@@ -0,0 +1,154 @@
+//
+// snapshot.js
+//
+// Created by David Kelly on 1 August 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
+//
+var SNAPSHOT_DELAY = 500; // 500ms
+var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
+var resetOverlays;
+var reticleVisible;
+var button = toolBar.addButton({
+ objectName: "snapshot",
+ imageURL: Script.resolvePath("assets/images/tools/snap.svg"),
+ visible: true,
+ buttonState: 1,
+ defaultState: 1,
+ hoverState: 2,
+ alpha: 0.9,
+});
+
+function shouldOpenFeedAfterShare() {
+ var persisted = Settings.getValue('openFeedAfterShare', true); // might answer true, false, "true", or "false"
+ return persisted && (persisted !== 'false');
+}
+function showFeedWindow() {
+ DialogsManager.showFeed();
+}
+
+var outstanding;
+function confirmShare(data) {
+ var dialog = new OverlayWebWindow('Snapshot Review', Script.resolvePath("html/SnapshotReview.html"), 800, 320);
+ function onMessage(message) {
+ // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following:
+ // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.)
+ // 2. Although we currently use a single image, we would like to take snapshot, a selfie, a 360 etc. all at the
+ // same time, show the user all of them, and have the user deselect any that they do not want to share.
+ // So we'll ultimately be receiving a set of objects, perhaps with different post processing for each.
+ var isLoggedIn;
+ var needsLogin = false;
+ switch (message) {
+ case 'ready':
+ dialog.emitScriptEvent(data); // Send it.
+ outstanding = 0;
+ break;
+ case 'openSettings':
+ Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog");
+ break;
+ case 'setOpenFeedFalse':
+ Settings.setValue('openFeedAfterShare', false)
+ break;
+ case 'setOpenFeedTrue':
+ Settings.setValue('openFeedAfterShare', true)
+ break;
+ default:
+ dialog.webEventReceived.disconnect(onMessage);
+ dialog.close();
+ isLoggedIn = Account.isLoggedIn();
+ message.forEach(function (submessage) {
+ if (submessage.share && !isLoggedIn) {
+ needsLogin = true;
+ submessage.share = false;
+ }
+ if (submessage.share) {
+ print('sharing', submessage.localPath);
+ outstanding++;
+ Window.shareSnapshot(submessage.localPath);
+ } else {
+ print('not sharing', submessage.localPath);
+ }
+ });
+ if (!outstanding && shouldOpenFeedAfterShare()) {
+ showFeedWindow();
+ }
+ if (needsLogin) { // after the possible feed, so that the login is on top
+ Account.checkAndSignalForAccessToken();
+ }
+ }
+ }
+ dialog.webEventReceived.connect(onMessage);
+ dialog.raise();
+}
+
+function snapshotShared(errorMessage) {
+ if (!errorMessage) {
+ print('snapshot uploaded and shared');
+ } else {
+ print(errorMessage);
+ }
+ if ((--outstanding <= 0) && shouldOpenFeedAfterShare()) {
+ showFeedWindow();
+ }
+}
+
+function onClicked() {
+ // update button states
+ resetOverlays = Menu.isOptionChecked("Overlays");
+ reticleVisible = Reticle.visible;
+ Reticle.visible = false;
+ Window.snapshotTaken.connect(resetButtons);
+
+ button.writeProperty("buttonState", 0);
+ button.writeProperty("defaultState", 0);
+ button.writeProperty("hoverState", 2);
+
+ // hide overlays if they are on
+ if (resetOverlays) {
+ Menu.setIsOptionChecked("Overlays", false);
+ }
+
+ // hide hud
+ toolBar.writeProperty("visible", false);
+
+ // take snapshot (with no notification)
+ Script.setTimeout(function () {
+ Window.takeSnapshot(false, 1.91);
+ }, SNAPSHOT_DELAY);
+}
+
+function resetButtons(path, notify) {
+ // show overlays if they were on
+ if (resetOverlays) {
+ Menu.setIsOptionChecked("Overlays", true);
+ }
+ // show hud
+ toolBar.writeProperty("visible", true);
+ Reticle.visible = reticleVisible;
+
+ // update button states
+ button.writeProperty("buttonState", 1);
+ button.writeProperty("defaultState", 1);
+ button.writeProperty("hoverState", 3);
+ Window.snapshotTaken.disconnect(resetButtons);
+
+ // last element in data array tells dialog whether we can share or not
+ confirmShare([
+ { localPath: path },
+ {
+ canShare: !!location.placename,
+ openFeedAfterShare: shouldOpenFeedAfterShare()
+ }
+ ]);
+ }
+
+button.clicked.connect(onClicked);
+Window.snapshotShared.connect(snapshotShared);
+
+Script.scriptEnding.connect(function () {
+ toolBar.removeButton("snapshot");
+ button.clicked.disconnect(onClicked);
+ Window.snapshotShared.disconnect(snapshotShared);
+});