diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 3e93656d45..1a33088237 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -3,7 +3,7 @@ This is a stand-alone guide for creating your first High Fidelity build for Wind ## Building High Fidelity Note: We are now using Visual Studio 2017 and Qt 5.9.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide. -Note: The prerequisites will require about 10 GB of space on your drive. +Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory. ### Step 1. Visual Studio 2017 diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 6951a90261..620d593ebc 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -54,6 +54,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetSize() == 0) { return; } + QDataStream packetStream(message->getMessage()); // read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 163bd48f1b..5bd6020c92 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -458,7 +458,7 @@ const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled"; -bool DomainServer::packetVersionMatch(const udt::Packet& packet) { +bool DomainServer::isPacketVerified(const udt::Packet& packet) { PacketType headerType = NLPacket::typeInHeader(packet); PacketVersion headerVersion = NLPacket::versionInHeader(packet); @@ -471,7 +471,48 @@ bool DomainServer::packetVersionMatch(const udt::Packet& packet) { DomainGatekeeper::sendProtocolMismatchConnectionDenial(packet.getSenderSockAddr()); } - // let the normal nodeList implementation handle all other packets. + if (!PacketTypeEnum::getNonSourcedPackets().contains(headerType)) { + // this is a sourced packet - first check if we have a node that matches + QUuid sourceID = NLPacket::sourceIDInHeader(packet); + SharedNodePointer sourceNode = nodeList->nodeWithUUID(sourceID); + + if (sourceNode) { + // unverified DS packets (due to a lack of connection secret between DS + node) + // must come either from the same public IP address or a local IP address (set by RFC 1918) + + DomainServerNodeData* nodeData = static_cast(sourceNode->getLinkedData()); + + bool exactAddressMatch = nodeData->getSendingSockAddr() == packet.getSenderSockAddr(); + bool bothPrivateAddresses = nodeData->getSendingSockAddr().hasPrivateAddress() + && packet.getSenderSockAddr().hasPrivateAddress(); + + if (nodeData && (exactAddressMatch || bothPrivateAddresses)) { + // to the best of our ability we've verified that this packet comes from the right place + // let the NodeList do its checks now (but pass it the sourceNode so it doesn't need to look it up again) + return nodeList->isPacketVerifiedWithSource(packet, sourceNode.data()); + } else { + static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unmatched IP for UUID"; + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX); + + qDebug() << "Packet of type" << headerType + << "received from unmatched IP for UUID" << uuidStringWithoutCurlyBraces(sourceID); + + return false; + } + } else { + static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unknown node with UUID"; + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX); + + qDebug() << "Packet of type" << headerType + << "received from unknown node with UUID" << uuidStringWithoutCurlyBraces(sourceID); + + return false; + } + } + + // fallback to allow the normal NodeList implementation to verify packets return nodeList->isPacketVerified(packet); } @@ -570,7 +611,7 @@ void DomainServer::setupNodeListAndAssignments() { addStaticAssignmentsToQueue(); // set a custom packetVersionMatch as the verify packet operator for the udt::Socket - nodeList->setPacketFilterOperator(&DomainServer::packetVersionMatch); + nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified); } const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; @@ -853,7 +894,6 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet message, SharedNodePointer sendingNode) { - QDataStream packetStream(message->getMessage()); NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr(), false); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 4808297c89..03ad76d313 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -71,6 +71,7 @@ public slots: void restart(); +private slots: void processRequestAssignmentPacket(QSharedPointer packet); void processListRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); void processNodeJSONStatsPacket(QSharedPointer packetList, SharedNodePointer sendingNode); @@ -79,7 +80,6 @@ public slots: void processICEServerHeartbeatDenialPacket(QSharedPointer message); void processICEServerHeartbeatACK(QSharedPointer message); -private slots: void setupPendingAssignmentCredits(); void sendPendingTransactionsToServer(); @@ -129,7 +129,7 @@ private: void getTemporaryName(bool force = false); - static bool packetVersionMatch(const udt::Packet& packet); + static bool isPacketVerified(const udt::Packet& packet); bool resetAccountManagerAccessToken(); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 4ed95b59f7..43e50c6d33 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -18,6 +18,7 @@ find_package(Qt5LinguistTools REQUIRED) find_package(Qt5LinguistToolsMacros) if (WIN32) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -bigobj") add_definitions(-D_USE_MATH_DEFINES) # apparently needed to get M_PI and other defines from cmath/math.h add_definitions(-DWINDOWS_LEAN_AND_MEAN) # needed to make sure windows doesn't go to crazy with its defines endif() diff --git a/interface/resources/icons/tablet-icons/wallet-a.svg b/interface/resources/icons/tablet-icons/wallet-a.svg new file mode 100644 index 0000000000..4a0bab4b33 --- /dev/null +++ b/interface/resources/icons/tablet-icons/wallet-a.svg @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + HFC + diff --git a/interface/resources/icons/tablet-icons/wallet-i.svg b/interface/resources/icons/tablet-icons/wallet-i.svg new file mode 100644 index 0000000000..2a16ecf973 --- /dev/null +++ b/interface/resources/icons/tablet-icons/wallet-i.svg @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + HFC + diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 960b62c169..8576e26fcd 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -35,7 +35,6 @@ Column { property string metaverseServerUrl: ''; property string actions: 'snapshot'; // sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick. - Component.onCompleted: delay.start(); property string labelText: actions; property string filter: ''; onFilterChanged: filterChoicesByText(); @@ -125,11 +124,9 @@ Column { cb(); }); } - property var delay: Timer { // No setTimeout or nextTick in QML. - interval: 0; - onTriggered: fillDestinations(); - } function fillDestinations() { // Public + console.debug('Feed::fillDestinations()') + function report(label, error) { console.log(label, actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); } diff --git a/interface/resources/qml/hifi/commerce/Checkout.qml b/interface/resources/qml/hifi/commerce/Checkout.qml index 55bd6cb4c6..d9b0072917 100644 --- a/interface/resources/qml/hifi/commerce/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/Checkout.qml @@ -17,6 +17,7 @@ import QtQuick.Controls 1.4 import "../../styles-uit" import "../../controls-uit" as HifiControlsUit import "../../controls" as HifiControls +import "./wallet" as HifiWallet // references XXX from root context @@ -26,32 +27,33 @@ Rectangle { id: checkoutRoot; property bool inventoryReceived: false; property bool balanceReceived: false; - property string itemId: ""; + property string itemId: ""; property string itemHref: ""; property int balanceAfterPurchase: 0; property bool alreadyOwned: false; + property int itemPriceFull: 0; // Style color: hifi.colors.baseGray; Hifi.QmlCommerce { id: commerce; onBuyResult: { - if (result.status !== 'success') { - buyButton.text = result.message; - buyButton.enabled = false; - } else { - if (urlHandler.canHandleUrl(itemHref)) { - urlHandler.handleUrl(itemHref); - } - sendToScript({method: 'checkout_buySuccess', itemId: itemId}); + if (result.status !== 'success') { + buyButton.text = result.message; + buyButton.enabled = false; + } else { + if (urlHandler.canHandleUrl(itemHref)) { + urlHandler.handleUrl(itemHref); } + sendToScript({method: 'checkout_buySuccess', itemId: itemId}); + } } onBalanceResult: { if (result.status !== 'success') { console.log("Failed to get balance", result.message); } else { balanceReceived = true; - hfcBalanceText.text = result.data.balance; - balanceAfterPurchase = result.data.balance - parseInt(itemPriceText.text, 10); + hfcBalanceText.text = parseFloat(result.data.balance/100).toFixed(2); + balanceAfterPurchase = parseFloat(result.data.balance/100) - parseFloat(checkoutRoot.itemPriceFull/100).toFixed(2); } } onInventoryResult: { @@ -67,14 +69,6 @@ Rectangle { } } } - onSecurityImageResult: { - securityImage.source = securityImageSelection.getImagePathFromImageID(imageID); - } - } - - SecurityImageSelection { - id: securityImageSelection; - referrerURL: checkoutRoot.itemHref; } // @@ -89,20 +83,6 @@ Rectangle { anchors.left: parent.left; anchors.top: parent.top; - // Security Image - Image { - id: securityImage; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 16; - height: parent.height - 5; - width: height; - anchors.verticalCenter: parent.verticalCenter; - fillMode: Image.PreserveAspectFit; - mipmap: true; - } - // Title Bar text RalewaySemiBold { id: titleBarText; @@ -111,7 +91,7 @@ Rectangle { size: hifi.fontSizes.overlayTitle; // Anchors anchors.top: parent.top; - anchors.left: securityImage.right; + anchors.left: parent.left; anchors.leftMargin: 16; anchors.bottom: parent.bottom; width: paintedWidth; @@ -132,7 +112,7 @@ Rectangle { // // TITLE BAR END // - + // // ITEM DESCRIPTION START // @@ -147,7 +127,7 @@ Rectangle { // Item Name text Item { - id: itemNameContainer; + id: itemNameContainer; // Anchors anchors.top: parent.top; anchors.topMargin: 4; @@ -188,11 +168,11 @@ Rectangle { verticalAlignment: Text.AlignVCenter; } } - - + + // Item Author text Item { - id: itemAuthorContainer; + id: itemAuthorContainer; // Anchors anchors.top: itemNameContainer.bottom; anchors.topMargin: 4; @@ -233,10 +213,10 @@ Rectangle { verticalAlignment: Text.AlignVCenter; } } - + // HFC Balance text Item { - id: hfcBalanceContainer; + id: hfcBalanceContainer; // Anchors anchors.top: itemAuthorContainer.bottom; anchors.topMargin: 16; @@ -278,10 +258,10 @@ Rectangle { verticalAlignment: Text.AlignVCenter; } } - + // Item Price text Item { - id: itemPriceContainer; + id: itemPriceContainer; // Anchors anchors.top: hfcBalanceContainer.bottom; anchors.topMargin: 4; @@ -322,10 +302,10 @@ Rectangle { verticalAlignment: Text.AlignVCenter; } } - + // HFC "Balance After Purchase" text Item { - id: hfcBalanceAfterPurchaseContainer; + id: hfcBalanceAfterPurchaseContainer; // Anchors anchors.top: itemPriceContainer.bottom; anchors.topMargin: 4; @@ -372,7 +352,7 @@ Rectangle { // ITEM DESCRIPTION END // - + // // ACTION BUTTONS START // @@ -420,7 +400,7 @@ Rectangle { text: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned: Get Item" : "Buy") : "--"; onClicked: { if (!alreadyOwned) { - commerce.buy(itemId, parseInt(itemPriceText.text)); + commerce.buy(itemId, parseFloat(itemPriceText.text*100)); } else { if (urlHandler.canHandleUrl(itemHref)) { urlHandler.handleUrl(itemHref); @@ -456,11 +436,11 @@ Rectangle { itemId = message.params.itemId; itemNameText.text = message.params.itemName; itemAuthorText.text = message.params.itemAuthor; - itemPriceText.text = message.params.itemPrice; + checkoutRoot.itemPriceFull = message.params.itemPrice; + itemPriceText.text = parseFloat(checkoutRoot.itemPriceFull/100).toFixed(2); itemHref = message.params.itemHref; commerce.balance(); commerce.inventory(); - commerce.getSecurityImage(); break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); diff --git a/interface/resources/qml/hifi/commerce/Inventory.qml b/interface/resources/qml/hifi/commerce/Inventory.qml index 8f22e7de0f..20458f9f16 100644 --- a/interface/resources/qml/hifi/commerce/Inventory.qml +++ b/interface/resources/qml/hifi/commerce/Inventory.qml @@ -17,6 +17,7 @@ import QtQuick.Controls 1.4 import "../../styles-uit" import "../../controls-uit" as HifiControlsUit import "../../controls" as HifiControls +import "./wallet" as HifiWallet // references XXX from root context @@ -33,7 +34,7 @@ Rectangle { if (result.status !== 'success') { console.log("Failed to get balance", result.message); } else { - hfcBalanceText.text = result.data.balance; + hfcBalanceText.text = parseFloat(result.data.balance/100).toFixed(2); } } onInventoryResult: { @@ -43,14 +44,6 @@ Rectangle { inventoryContentsList.model = result.data.assets; } } - onSecurityImageResult: { - securityImage.source = securityImageSelection.getImagePathFromImageID(imageID); - } - } - - SecurityImageSelection { - id: securityImageSelection; - referrerURL: inventoryRoot.referrerURL; } // @@ -65,20 +58,6 @@ Rectangle { anchors.left: parent.left; anchors.top: parent.top; - // Security Image - Image { - id: securityImage; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 16; - height: parent.height - 5; - width: height; - anchors.verticalCenter: parent.verticalCenter; - fillMode: Image.PreserveAspectFit; - mipmap: true; - } - // Title Bar text RalewaySemiBold { id: titleBarText; @@ -87,7 +66,7 @@ Rectangle { size: hifi.fontSizes.overlayTitle; // Anchors anchors.top: parent.top; - anchors.left: securityImage.right; + anchors.left: parent.left; anchors.leftMargin: 16; anchors.bottom: parent.bottom; width: paintedWidth; @@ -98,25 +77,6 @@ Rectangle { verticalAlignment: Text.AlignVCenter; } - // "Change Security Image" button - HifiControlsUit.Button { - id: changeSecurityImageButton; - color: hifi.buttons.black; - colorScheme: hifi.colorSchemes.dark; - anchors.top: parent.top; - anchors.topMargin: 3; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 3; - anchors.right: parent.right; - anchors.rightMargin: 20; - width: 200; - text: "Change Security Image" - onClicked: { - securityImageSelection.isManuallyChangingSecurityImage = true; - securityImageSelection.visible = true; - } - } - // Separator HifiControlsUit.Separator { anchors.left: parent.left; @@ -132,7 +92,7 @@ Rectangle { // HFC BALANCE START // Item { - id: hfcBalanceContainer; + id: hfcBalanceContainer; // Size width: inventoryRoot.width; height: childrenRect.height + 20; @@ -177,7 +137,7 @@ Rectangle { // // HFC BALANCE END // - + // // INVENTORY CONTENTS START // @@ -192,7 +152,7 @@ Rectangle { anchors.topMargin: 8; anchors.bottom: actionButtonsContainer.top; anchors.bottomMargin: 8; - + RalewaySemiBold { id: inventoryContentsLabel; text: "Inventory:"; @@ -249,7 +209,7 @@ Rectangle { // // INVENTORY CONTENTS END // - + // // ACTION BUTTONS START // @@ -307,7 +267,6 @@ Rectangle { referrerURL = message.referrerURL; commerce.balance(); commerce.inventory(); - commerce.getSecurityImage(); break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); diff --git a/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml deleted file mode 100644 index 7775f1ff9c..0000000000 --- a/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml +++ /dev/null @@ -1,271 +0,0 @@ -// -// SecurityImageSelection.qml -// qml/hifi/commerce -// -// SecurityImageSelection -// -// Created by Zach Fox on 2017-08-15 -// Copyright 2017 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 -// - -import Hifi 1.0 as Hifi -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit -import "../../controls" as HifiControls - -// references XXX from root context - -Rectangle { - HifiConstants { id: hifi; } - - id: securityImageSelectionRoot; - property string referrerURL: ""; - property bool isManuallyChangingSecurityImage: false; - anchors.fill: parent; - // Style - color: hifi.colors.baseGray; - z:999; // On top of everything else - visible: false; - - Hifi.QmlCommerce { - id: commerce; - onSecurityImageResult: { - if (!isManuallyChangingSecurityImage) { - securityImageSelectionRoot.visible = (imageID == 0); - } - if (imageID > 0) { - for (var itr = 0; itr < gridModel.count; itr++) { - var thisValue = gridModel.get(itr).securityImageEnumValue; - if (thisValue === imageID) { - securityImageGrid.currentIndex = itr; - break; - } - } - } - } - } - - Component.onCompleted: { - commerce.getSecurityImage(); - } - - // - // TITLE BAR START - // - Item { - id: titleBarContainer; - // Size - width: securityImageSelectionRoot.width; - height: 30; - // Anchors - anchors.left: parent.left; - anchors.top: parent.top; - - // Title Bar text - RalewaySemiBold { - id: titleBarText; - text: "Select a Security Image"; - // Text size - size: hifi.fontSizes.overlayTitle; - // Anchors - anchors.fill: parent; - anchors.leftMargin: 16; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - - // Separator - HifiControlsUit.Separator { - anchors.left: parent.left; - anchors.right: parent.right; - anchors.bottom: parent.bottom; - } - } - // - // TITLE BAR END - // - - // - // EXPLANATION START - // - Item { - id: explanationContainer; - // Size - width: securityImageSelectionRoot.width; - height: 85; - // Anchors - anchors.top: titleBarContainer.bottom; - anchors.left: parent.left; - anchors.right: parent.right; - - RalewayRegular { - id: explanationText; - text: "This image will be displayed on secure Inventory and Marketplace Checkout dialogs.
If you don't see your selected image on these dialogs, do not use them!
"; - // Text size - size: 16; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 4; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - // Style - color: hifi.colors.lightGrayText; - wrapMode: Text.WordWrap; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - - // Separator - HifiControlsUit.Separator { - anchors.left: parent.left; - anchors.right: parent.right; - anchors.bottom: parent.bottom; - } - } - // - // EXPLANATION END - // - - // - // SECURITY IMAGE GRID START - // - Item { - id: securityImageGridContainer; - // Anchors - anchors.left: parent.left; - anchors.leftMargin: 8; - anchors.right: parent.right; - anchors.rightMargin: 8; - anchors.top: explanationContainer.bottom; - anchors.topMargin: 8; - anchors.bottom: actionButtonsContainer.top; - anchors.bottomMargin: 8; - - SecurityImageModel { - id: gridModel; - } - - GridView { - id: securityImageGrid; - clip: true; - // Anchors - anchors.fill: parent; - currentIndex: -1; - cellWidth: width / 2; - cellHeight: height / 3; - model: gridModel; - delegate: Item { - width: securityImageGrid.cellWidth; - height: securityImageGrid.cellHeight; - Item { - anchors.fill: parent; - Image { - width: parent.width - 8; - height: parent.height - 8; - source: sourcePath; - anchors.horizontalCenter: parent.horizontalCenter; - anchors.verticalCenter: parent.verticalCenter; - fillMode: Image.PreserveAspectFit; - mipmap: true; - } - } - MouseArea { - anchors.fill: parent; - onClicked: { - securityImageGrid.currentIndex = index; - } - } - } - highlight: Rectangle { - width: securityImageGrid.cellWidth; - height: securityImageGrid.cellHeight; - color: hifi.colors.blueHighlight; - } - } - } - // - // SECURITY IMAGE GRID END - // - - - // - // ACTION BUTTONS START - // - Item { - id: actionButtonsContainer; - // Size - width: securityImageSelectionRoot.width; - height: 40; - // Anchors - anchors.left: parent.left; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 8; - - // "Cancel" button - HifiControlsUit.Button { - id: cancelButton; - color: hifi.buttons.black; - colorScheme: hifi.colorSchemes.dark; - anchors.top: parent.top; - anchors.topMargin: 3; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 3; - anchors.left: parent.left; - anchors.leftMargin: 20; - width: parent.width/2 - anchors.leftMargin*2; - text: "Cancel" - onClicked: { - if (!securityImageSelectionRoot.isManuallyChangingSecurityImage) { - sendToScript({method: 'securityImageSelection_cancelClicked', referrerURL: securityImageSelectionRoot.referrerURL}); - } else { - securityImageSelectionRoot.visible = false; - } - } - } - - // "Confirm" button - HifiControlsUit.Button { - id: confirmButton; - color: hifi.buttons.black; - colorScheme: hifi.colorSchemes.dark; - anchors.top: parent.top; - anchors.topMargin: 3; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 3; - anchors.right: parent.right; - anchors.rightMargin: 20; - width: parent.width/2 - anchors.rightMargin*2; - text: "Confirm"; - onClicked: { - securityImageSelectionRoot.isManuallyChangingSecurityImage = false; - commerce.chooseSecurityImage(gridModel.get(securityImageGrid.currentIndex).securityImageEnumValue); - } - } - } - // - // ACTION BUTTONS END - // - - // - // FUNCTION DEFINITIONS START - // - signal sendToScript(var message); - - function getImagePathFromImageID(imageID) { - return (imageID ? gridModel.get(imageID - 1).sourcePath : ""); - } - // - // FUNCTION DEFINITIONS END - // -} diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml new file mode 100644 index 0000000000..2252cbfb59 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -0,0 +1,73 @@ +// +// SendMoney.qml +// qml/hifi/commerce/wallet +// +// SendMoney +// +// Created by Zach Fox on 2017-08-18 +// Copyright 2017 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls + +// references XXX from root context + +Item { + HifiConstants { id: hifi; } + + id: root; + + Hifi.QmlCommerce { + id: commerce; + } + + // "Unavailable" + RalewayRegular { + text: "Help me!"; + // Anchors + anchors.fill: parent; + // Text size + size: 24; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + // + // FUNCTION DEFINITIONS START + // + // + // Function Name: fromScript() + // + // Relevant Variables: + // None + // + // Arguments: + // message: The message sent from the JavaScript. + // Messages are in format "{method, params}", like json-rpc. + // + // Description: + // Called when a message is received from a script. + // + function fromScript(message) { + switch (message.method) { + default: + console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); + } + } + signal sendSignalToWallet(var msg); + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/wallet/NotSetUp.qml b/interface/resources/qml/hifi/commerce/wallet/NotSetUp.qml new file mode 100644 index 0000000000..3efb592ba1 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/NotSetUp.qml @@ -0,0 +1,127 @@ +// +// NotSetUp.qml +// qml/hifi/commerce/wallet +// +// NotSetUp +// +// Created by Zach Fox on 2017-08-18 +// Copyright 2017 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls + +// references XXX from root context + +Item { + HifiConstants { id: hifi; } + + id: root; + Hifi.QmlCommerce { + id: commerce; + } + + // + // TAB CONTENTS START + // + + // Text below title bar + RalewaySemiBold { + id: notSetUpText; + text: "Your Wallet Account Has Not Been Set Up"; + // Text size + size: 22; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 100; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: 50; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + // Explanitory text + RalewayRegular { + text: "To buy and sell items in High Fidelity Coin (HFC), you first need " + + "to set up your wallet.
You do not need to submit a credit card or personal information to set up your wallet."; + // Text size + size: 18; + // Anchors + anchors.top: notSetUpText.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 30; + anchors.right: parent.right; + anchors.rightMargin: 30; + height: 100; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + // "Set Up" button + HifiControlsUit.Button { + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 150; + anchors.horizontalCenter: parent.horizontalCenter; + width: parent.width/2; + height: 50; + text: "Set Up My Wallet"; + onClicked: { + sendSignalToWallet({method: 'setUpClicked'}); + } + } + + + // + // TAB CONTENTS END + // + + // + // FUNCTION DEFINITIONS START + // + // + // Function Name: fromScript() + // + // Relevant Variables: + // None + // + // Arguments: + // message: The message sent from the JavaScript. + // Messages are in format "{method, params}", like json-rpc. + // + // Description: + // Called when a message is received from a script. + // + function fromScript(message) { + switch (message.method) { + default: + console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); + } + } + signal sendSignalToWallet(var msg); + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml new file mode 100644 index 0000000000..89ef851b06 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml @@ -0,0 +1,232 @@ +// +// PassphraseSelection.qml +// qml/hifi/commerce/wallet +// +// PassphraseSelection +// +// Created by Zach Fox on 2017-08-18 +// Copyright 2017 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls + +// references XXX from root context + +Item { + HifiConstants { id: hifi; } + + id: root; + + // This object is always used in a popup. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + } + + Hifi.QmlCommerce { + id: commerce; + onSecurityImageResult: { + passphrasePageSecurityImage.source = ""; + passphrasePageSecurityImage.source = "image://security/securityImage"; + } + + onPassphraseSetupStatusResult: { + sendMessageToLightbox({method: 'statusResult', status: passphraseIsSetup}); + } + } + + onVisibleChanged: { + if (visible) { + passphraseField.focus = true; + } + } + + SecurityImageModel { + id: gridModel; + } + + HifiControlsUit.TextField { + id: passphraseField; + anchors.top: parent.top; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.leftMargin: 16; + width: 280; + height: 50; + echoMode: TextInput.Password; + placeholderText: "passphrase"; + + onVisibleChanged: { + if (visible) { + text = ""; + } + } + } + HifiControlsUit.TextField { + id: passphraseFieldAgain; + anchors.top: passphraseField.bottom; + anchors.topMargin: 10; + anchors.left: passphraseField.left; + anchors.right: passphraseField.right; + height: 50; + echoMode: TextInput.Password; + placeholderText: "re-enter passphrase"; + + onVisibleChanged: { + if (visible) { + text = ""; + } + } + } + + // Security Image + Item { + id: securityImageContainer; + // Anchors + anchors.top: passphraseField.top; + anchors.left: passphraseField.right; + anchors.leftMargin: 12; + anchors.right: parent.right; + Image { + id: passphrasePageSecurityImage; + anchors.top: parent.top; + anchors.horizontalCenter: parent.horizontalCenter; + height: 75; + width: height; + fillMode: Image.PreserveAspectFit; + mipmap: true; + source: "image://security/securityImage"; + cache: false; + onVisibleChanged: { + commerce.getSecurityImage(); + } + } + // "Security picture" text below pic + RalewayRegular { + text: "security picture"; + // Text size + size: 12; + // Anchors + anchors.top: passphrasePageSecurityImage.bottom; + anchors.topMargin: 4; + anchors.left: securityImageContainer.left; + anchors.right: securityImageContainer.right; + height: paintedHeight; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + } + + // Error text below TextFields + RalewaySemiBold { + id: errorText; + text: ""; + // Text size + size: 16; + // Anchors + anchors.top: passphraseFieldAgain.bottom; + anchors.topMargin: 0; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: 30; + // Style + color: hifi.colors.redHighlight; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Text below TextFields + RalewaySemiBold { + id: passwordReqs; + text: "Passphrase must be at least 4 characters"; + // Text size + size: 16; + // Anchors + anchors.top: passphraseFieldAgain.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: 30; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Show passphrase text + HifiControlsUit.CheckBox { + id: showPassphrase; + colorScheme: hifi.colorSchemes.dark; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.top: passwordReqs.bottom; + anchors.topMargin: 16; + height: 30; + text: "Show passphrase as plain text"; + boxSize: 24; + onClicked: { + passphraseField.echoMode = checked ? TextInput.Normal : TextInput.Password; + passphraseFieldAgain.echoMode = checked ? TextInput.Normal : TextInput.Password; + } + } + + // Text below checkbox + RalewayRegular { + text: "Your passphrase is used to encrypt your private keys. Please write it down. If it is lost, you will not be able to recover it."; + // Text size + size: 16; + // Anchors + anchors.top: showPassphrase.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + + function validateAndSubmitPassphrase() { + if (passphraseField.text.length < 4) { + setErrorText("Passphrase too short."); + return false; + } else if (passphraseField.text !== passphraseFieldAgain.text) { + setErrorText("Passphrases don't match."); + return false; + } else { + setErrorText(""); + commerce.setPassphrase(passphraseField.text); + return true; + } + } + + function setErrorText(text) { + errorText.text = text; + } + + signal sendMessageToLightbox(var msg); +} diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelectionLightbox.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelectionLightbox.qml new file mode 100644 index 0000000000..862d1894db --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelectionLightbox.qml @@ -0,0 +1,175 @@ +// +// PassphraseSelectionLightbox.qml +// qml/hifi/commerce/wallet +// +// PassphraseSelectionLightbox +// +// Created by Zach Fox on 2017-08-18 +// Copyright 2017 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: root; + // Style + color: hifi.colors.baseGray; + + onVisibleChanged: { + if (visible) { + root.resetSubmitButton(); + } + } + + Connections { + target: passphraseSelection; + onSendMessageToLightbox: { + if (msg.method === 'statusResult') { + if (msg.status) { + // Success submitting new passphrase + root.resetSubmitButton(); + root.visible = false; + } else { + // Error submitting new passphrase + root.resetSubmitButton(); + passphraseSelection.setErrorText("Backend error"); + } + } + } + } + + // + // SECURE PASSPHRASE SELECTION START + // + Item { + id: choosePassphraseContainer; + // Anchors + anchors.fill: parent; + + Item { + id: passphraseTitle; + // Size + width: parent.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + text: "CHANGE PASSPHRASE"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + } + + // Text below title bar + RalewaySemiBold { + id: passphraseTitleHelper; + text: "Choose a Secure Passphrase"; + // Text size + size: 24; + // Anchors + anchors.top: passphraseTitle.bottom; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: 50; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + PassphraseSelection { + id: passphraseSelection; + anchors.top: passphraseTitleHelper.bottom; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: passphraseNavBar.top; + } + + // Navigation Bar + Item { + id: passphraseNavBar; + // Size + width: parent.width; + height: 100; + // Anchors: + anchors.left: parent.left; + anchors.bottom: parent.bottom; + + // "Cancel" button + HifiControlsUit.Button { + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: 100; + text: "Cancel" + onClicked: { + root.visible = false; + } + } + + // "Submit" button + HifiControlsUit.Button { + id: passphraseSubmitButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: 100; + text: "Submit"; + onClicked: { + if (passphraseSelection.validateAndSubmitPassphrase()) { + passphraseSubmitButton.text = "Submitting..."; + passphraseSubmitButton.enabled = false; + } + } + } + } + } + // + // SECURE PASSPHRASE SELECTION END + // + + function resetSubmitButton() { + passphraseSubmitButton.enabled = true; + passphraseSubmitButton.text = "Submit"; + } +} diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml new file mode 100644 index 0000000000..3f3d00b401 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -0,0 +1,322 @@ +// +// Security.qml +// qml/hifi/commerce/wallet +// +// Security +// +// Created by Zach Fox on 2017-08-18 +// Copyright 2017 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls + +// references XXX from root context + +Item { + HifiConstants { id: hifi; } + + id: root; + + Hifi.QmlCommerce { + id: commerce; + + onSecurityImageResult: { + if (exists) { // "If security image is set up" + var path = "image://security/securityImage"; + topSecurityImage.source = ""; + topSecurityImage.source = path; + changeSecurityImageImage.source = ""; + changeSecurityImageImage.source = path; + changePassphraseImage.source = ""; + changePassphraseImage.source = path; + } + } + + onKeyFilePathResult: { + if (path !== "") { + keyFilePath.text = path; + } + } + } + + SecurityImageModel { + id: securityImageModel; + } + + // Username Text + RalewayRegular { + id: usernameText; + text: Account.username; + // Text size + size: 24; + // Style + color: hifi.colors.faintGray; + elide: Text.ElideRight; + // Anchors + anchors.top: securityImageContainer.top; + anchors.bottom: securityImageContainer.bottom; + anchors.left: parent.left; + anchors.right: securityImageContainer.left; + } + + // Security Image + Item { + id: securityImageContainer; + // Anchors + anchors.top: parent.top; + anchors.right: parent.right; + width: 75; + height: childrenRect.height; + + onVisibleChanged: { + if (visible) { + commerce.getSecurityImage(); + } + } + + Image { + id: topSecurityImage; + // Anchors + anchors.top: parent.top; + anchors.horizontalCenter: parent.horizontalCenter; + height: parent.width - 10; + width: height; + fillMode: Image.PreserveAspectFit; + mipmap: true; + source: "image://security/securityImage"; + cache: false; + } + // "Security picture" text below pic + RalewayRegular { + text: "security picture"; + // Text size + size: 12; + // Anchors + anchors.top: topSecurityImage.bottom; + anchors.topMargin: 4; + anchors.left: securityImageContainer.left; + anchors.right: securityImageContainer.right; + height: paintedHeight; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + } + + Item { + id: securityContainer; + anchors.top: securityImageContainer.bottom; + anchors.topMargin: 20; + anchors.left: parent.left; + anchors.right: parent.right; + height: childrenRect.height; + + RalewayRegular { + id: securityText; + text: "Security"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: 30; + // Text size + size: 22; + // Style + color: hifi.colors.faintGray; + } + + Item { + id: changePassphraseContainer; + anchors.top: securityText.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.right: parent.right; + height: 75; + + Image { + id: changePassphraseImage; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + height: parent.height; + width: height; + source: "image://security/securityImage"; + fillMode: Image.PreserveAspectFit; + mipmap: true; + cache: false; + } + // "Change Passphrase" button + HifiControlsUit.Button { + id: changePassphraseButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: changePassphraseImage.right; + anchors.leftMargin: 16; + width: 250; + height: 50; + text: "Change My Passphrase"; + onClicked: { + sendSignalToWallet({method: 'walletSecurity_changePassphrase'}); + } + } + } + + Item { + id: changeSecurityImageContainer; + anchors.top: changePassphraseContainer.bottom; + anchors.topMargin: 8; + anchors.left: parent.left; + anchors.right: parent.right; + height: 75; + + Image { + id: changeSecurityImageImage; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + height: parent.height; + width: height; + fillMode: Image.PreserveAspectFit; + mipmap: true; + cache: false; + source: "image://security/securityImage"; + } + // "Change Security Image" button + HifiControlsUit.Button { + id: changeSecurityImageButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: changeSecurityImageImage.right; + anchors.leftMargin: 16; + width: 250; + height: 50; + text: "Change My Security Image"; + onClicked: { + sendSignalToWallet({method: 'walletSecurity_changeSecurityImage'}); + } + } + } + } + + Item { + id: yourPrivateKeysContainer; + anchors.top: securityContainer.bottom; + anchors.topMargin: 20; + anchors.left: parent.left; + anchors.right: parent.right; + height: childrenRect.height; + + RalewaySemiBold { + id: yourPrivateKeysText; + text: "Your Private Keys"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: 30; + // Text size + size: 22; + // Style + color: hifi.colors.faintGray; + } + + // Text below "your private keys" + RalewayRegular { + id: explanitoryText; + text: "Your money and purchases are secured with private keys that only you " + + "have access to. If they are lost, you will not be able to access your money or purchases. " + + "To safeguard your private keys, back up this file regularly:"; + // Text size + size: 18; + // Anchors + anchors.top: yourPrivateKeysText.bottom; + anchors.topMargin: 10; + anchors.left: parent.left; + anchors.right: parent.right; + height: paintedHeight; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + HifiControlsUit.TextField { + id: keyFilePath; + anchors.top: explanitoryText.bottom; + anchors.topMargin: 10; + anchors.left: parent.left; + anchors.right: clipboardButton.left; + height: 40; + readOnly: true; + + onVisibleChanged: { + if (visible) { + commerce.getKeyFilePath(); + } + } + } + HifiControlsUit.Button { + id: clipboardButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.right: parent.right; + anchors.top: keyFilePath.top; + anchors.bottom: keyFilePath.bottom; + width: height; + HiFiGlyphs { + text: hifi.glyphs.question; + // Size + size: parent.height*1.3; + // Anchors + anchors.fill: parent; + // Style + horizontalAlignment: Text.AlignHCenter; + color: enabled ? hifi.colors.white : hifi.colors.faintGray; + } + + onClicked: { + Window.copyToClipboard(keyFilePath.text); + } + } + } + + // + // FUNCTION DEFINITIONS START + // + // + // Function Name: fromScript() + // + // Relevant Variables: + // None + // + // Arguments: + // message: The message sent from the JavaScript. + // Messages are in format "{method, params}", like json-rpc. + // + // Description: + // Called when a message is received from a script. + // + function fromScript(message) { + switch (message.method) { + default: + console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); + } + } + signal sendSignalToWallet(var msg); + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/SecurityImageModel.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml similarity index 85% rename from interface/resources/qml/hifi/commerce/SecurityImageModel.qml rename to interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml index 2fbf28683f..b49f16857b 100644 --- a/interface/resources/qml/hifi/commerce/SecurityImageModel.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml @@ -4,7 +4,7 @@ // // SecurityImageModel // -// Created by Zach Fox on 2017-08-15 +// Created by Zach Fox on 2017-08-17 // Copyright 2017 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -39,4 +39,8 @@ ListModel { sourcePath: "images/06gingerbread.jpg" securityImageEnumValue: 6; } + + function getImagePathFromImageID(imageID) { + return (imageID ? root.get(imageID - 1).sourcePath : ""); + } } diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml new file mode 100644 index 0000000000..7ab52b7551 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml @@ -0,0 +1,98 @@ +// +// SecurityImageSelection.qml +// qml/hifi/commerce/wallet +// +// SecurityImageSelection +// +// Created by Zach Fox on 2017-08-17 +// Copyright 2017 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls + +// references XXX from root context + +Item { + HifiConstants { id: hifi; } + + id: root; + + Hifi.QmlCommerce { + id: commerce; + onSecurityImageResult: { + } + } + + onVisibleChanged: { + if (visible) { + commerce.getSecurityImage(); + } + } + + SecurityImageModel { + id: gridModel; + } + + GridView { + id: securityImageGrid; + clip: true; + // Anchors + anchors.fill: parent; + currentIndex: -1; + cellWidth: width / 3; + cellHeight: height / 2; + model: gridModel; + delegate: Item { + width: securityImageGrid.cellWidth; + height: securityImageGrid.cellHeight; + Item { + anchors.fill: parent; + Image { + width: parent.width - 8; + height: parent.height - 8; + source: sourcePath; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.verticalCenter: parent.verticalCenter; + fillMode: Image.PreserveAspectFit; + mipmap: true; + } + } + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + onClicked: { + securityImageGrid.currentIndex = index; + } + } + } + highlight: Rectangle { + width: securityImageGrid.cellWidth; + height: securityImageGrid.cellHeight; + color: hifi.colors.blueHighlight; + } + } + + // + // FUNCTION DEFINITIONS START + // + signal sendToScript(var message); + + function getImagePathFromImageID(imageID) { + return (imageID ? gridModel.getImagePathFromImageID(imageID) : ""); + } + + function getSelectedImageIndex() { + return gridModel.get(securityImageGrid.currentIndex).securityImageEnumValue; + } + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelectionLightbox.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelectionLightbox.qml new file mode 100644 index 0000000000..d4b0b82ed3 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelectionLightbox.qml @@ -0,0 +1,200 @@ +// +// SecurityImageSelectionLightbox.qml +// qml/hifi/commerce/wallet +// +// SecurityImageSelectionLightbox +// +// Created by Zach Fox on 2017-08-18 +// Copyright 2017 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: root; + property bool justSubmitted: false; + // Style + color: hifi.colors.baseGray; + + onVisibleChanged: { + if (visible) { + root.resetSubmitButton(); + } + } + + Hifi.QmlCommerce { + id: commerce; + + onSecurityImageResult: { + if (exists) { // Success submitting new security image + if (root.justSubmitted) { + root.resetSubmitButton(); + root.visible = false; + root.justSubmitted = false; + } + } else if (root.justSubmitted) { + // Error submitting new security image. + root.resetSubmitButton(); + root.justSubmitted = false; + } + } + } + + // + // SECURITY IMAGE SELECTION START + // + Item { + id: securityImageContainer; + // Anchors + anchors.fill: parent; + + Item { + id: securityImageTitle; + // Size + width: parent.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + text: "CHANGE SECURITY IMAGE"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + } + + // Text below title bar + RalewaySemiBold { + id: securityImageTitleHelper; + text: "Choose a Security Picture:"; + // Text size + size: 24; + // Anchors + anchors.top: securityImageTitle.bottom; + anchors.left: parent.left; + anchors.leftMargin: 16; + height: 50; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + SecurityImageSelection { + id: securityImageSelection; + // Anchors + anchors.top: securityImageTitleHelper.bottom; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: 280; + } + + // Text below security images + RalewayRegular { + text: "Your security picture shows you that the service asking for your passphrase is authorized. You can change your secure picture at any time."; + // Text size + size: 18; + // Anchors + anchors.top: securityImageSelection.bottom; + anchors.topMargin: 40; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Navigation Bar + Item { + id: securityImageNavBar; + // Size + width: parent.width; + height: 100; + // Anchors: + anchors.left: parent.left; + anchors.bottom: parent.bottom; + + // "Cancel" button + HifiControlsUit.Button { + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: 100; + text: "Cancel" + onClicked: { + root.visible = false; + } + } + + // "Submit" button + HifiControlsUit.Button { + id: securityImageSubmitButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: 100; + text: "Submit"; + onClicked: { + root.justSubmitted = true; + securityImageSubmitButton.text = "Submitting..."; + securityImageSubmitButton.enabled = false; + var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex()) + commerce.chooseSecurityImage(securityImagePath); + } + } + } + } + // + // SECURITY IMAGE SELECTION END + // + + function resetSubmitButton() { + securityImageSubmitButton.enabled = true; + securityImageSubmitButton.text = "Submit"; + } +} diff --git a/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml new file mode 100644 index 0000000000..75334b1686 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml @@ -0,0 +1,73 @@ +// +// SendMoney.qml +// qml/hifi/commerce/wallet +// +// SendMoney +// +// Created by Zach Fox on 2017-08-18 +// Copyright 2017 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls + +// references XXX from root context + +Item { + HifiConstants { id: hifi; } + + id: root; + + Hifi.QmlCommerce { + id: commerce; + } + + // "Unavailable" + RalewayRegular { + text: "You currently cannot send money to other High Fidelity users."; + // Anchors + anchors.fill: parent; + // Text size + size: 24; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + // + // FUNCTION DEFINITIONS START + // + // + // Function Name: fromScript() + // + // Relevant Variables: + // None + // + // Arguments: + // message: The message sent from the JavaScript. + // Messages are in format "{method, params}", like json-rpc. + // + // Description: + // Called when a message is received from a script. + // + function fromScript(message) { + switch (message.method) { + default: + console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); + } + } + signal sendSignalToWallet(var msg); + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml new file mode 100644 index 0000000000..ac5d851bbc --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -0,0 +1,479 @@ +// +// Wallet.qml +// qml/hifi/commerce/wallet +// +// Wallet +// +// Created by Zach Fox on 2017-08-17 +// Copyright 2017 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: root; + + property string activeView: "walletHome"; + + // Style + color: hifi.colors.baseGray; + Hifi.QmlCommerce { + id: commerce; + + onSecurityImageResult: { + if (!exists) { // "If security image is not set up" + if (root.activeView !== "notSetUp") { + root.activeView = "notSetUp"; + } + } + } + + onKeyFilePathResult: { + if (path === "" && root.activeView !== "notSetUp") { + root.activeView = "notSetUp"; + } + } + } + + SecurityImageModel { + id: securityImageModel; + } + + Connections { + target: walletSetupLightbox; + onSendSignalToWallet: { + if (msg.method === 'walletSetup_cancelClicked') { + walletSetupLightbox.visible = false; + } else if (msg.method === 'walletSetup_finished') { + root.activeView = "walletHome"; + } else { + sendToScript(msg); + } + } + } + Connections { + target: notSetUp; + onSendSignalToWallet: { + if (msg.method === 'setUpClicked') { + walletSetupLightbox.visible = true; + } + } + } + + Rectangle { + id: walletSetupLightboxContainer; + visible: walletSetupLightbox.visible || passphraseSelectionLightbox.visible || securityImageSelectionLightbox.visible; + z: 998; + anchors.fill: parent; + color: "black"; + opacity: 0.5; + } + WalletSetupLightbox { + id: walletSetupLightbox; + visible: false; + z: 999; + anchors.centerIn: walletSetupLightboxContainer; + width: walletSetupLightboxContainer.width - 50; + height: walletSetupLightboxContainer.height - 50; + } + PassphraseSelectionLightbox { + id: passphraseSelectionLightbox; + visible: false; + z: 999; + anchors.centerIn: walletSetupLightboxContainer; + width: walletSetupLightboxContainer.width - 50; + height: walletSetupLightboxContainer.height - 50; + } + SecurityImageSelectionLightbox { + id: securityImageSelectionLightbox; + visible: false; + z: 999; + anchors.centerIn: walletSetupLightboxContainer; + width: walletSetupLightboxContainer.width - 50; + height: walletSetupLightboxContainer.height - 50; + } + + + // + // TITLE BAR START + // + Item { + id: titleBarContainer; + // Size + width: parent.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + id: titleBarText; + text: "WALLET"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // TITLE BAR END + // + + // + // TAB CONTENTS START + // + NotSetUp { + id: notSetUp; + visible: root.activeView === "notSetUp"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: tabButtonsContainer.top; + anchors.left: parent.left; + anchors.right: parent.right; + } + + WalletHome { + id: walletHome; + visible: root.activeView === "walletHome"; + anchors.top: titleBarContainer.bottom; + anchors.topMargin: 16; + anchors.bottom: tabButtonsContainer.top; + anchors.bottomMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + } + + SendMoney { + id: sendMoney; + visible: root.activeView === "sendMoney"; + anchors.top: titleBarContainer.bottom; + anchors.topMargin: 16; + anchors.bottom: tabButtonsContainer.top; + anchors.bottomMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + } + + Security { + id: security; + visible: root.activeView === "security"; + anchors.top: titleBarContainer.bottom; + anchors.topMargin: 16; + anchors.bottom: tabButtonsContainer.top; + anchors.bottomMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + } + Connections { + target: security; + onSendSignalToWallet: { + if (msg.method === 'walletSecurity_changePassphrase') { + passphraseSelectionLightbox.visible = true; + } else if (msg.method === 'walletSecurity_changeSecurityImage') { + securityImageSelectionLightbox.visible = true; + } + } + } + + Help { + id: help; + visible: root.activeView === "help"; + anchors.top: titleBarContainer.bottom; + anchors.topMargin: 16; + anchors.bottom: tabButtonsContainer.top; + anchors.bottomMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + } + + + // + // TAB CONTENTS END + // + + // + // TAB BUTTONS START + // + Item { + id: tabButtonsContainer; + property int numTabs: 5; + // Size + width: root.width; + height: 80; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + } + + // "WALLET HOME" tab button + Rectangle { + id: walletHomeButtonContainer; + visible: !notSetUp.visible; + color: root.activeView === "walletHome" ? hifi.colors.blueAccent : hifi.colors.black; + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: parent.width / tabButtonsContainer.numTabs; + + RalewaySemiBold { + text: "WALLET HOME"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.fill: parent; + anchors.leftMargin: 4; + anchors.rightMargin: 4; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + MouseArea { + enabled: !walletSetupLightboxContainer.visible; + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + root.activeView = "walletHome"; + tabButtonsContainer.resetTabButtonColors(); + } + onEntered: parent.color = hifi.colors.blueHighlight; + onExited: parent.color = root.activeView === "walletHome" ? hifi.colors.blueAccent : hifi.colors.black; + } + + onVisibleChanged: { + if (visible) { + commerce.getSecurityImage(); + commerce.balance(); + } + } + } + + // "SEND MONEY" tab button + Rectangle { + id: sendMoneyButtonContainer; + visible: !notSetUp.visible; + color: hifi.colors.black; + anchors.top: parent.top; + anchors.left: walletHomeButtonContainer.right; + anchors.bottom: parent.bottom; + width: parent.width / tabButtonsContainer.numTabs; + + RalewaySemiBold { + text: "SEND MONEY"; + // Text size + size: 14; + // Anchors + anchors.fill: parent; + anchors.leftMargin: 4; + anchors.rightMargin: 4; + // Style + color: hifi.colors.lightGray50; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + } + + // "EXCHANGE MONEY" tab button + Rectangle { + id: exchangeMoneyButtonContainer; + visible: !notSetUp.visible; + color: hifi.colors.black; + anchors.top: parent.top; + anchors.left: sendMoneyButtonContainer.right; + anchors.bottom: parent.bottom; + width: parent.width / tabButtonsContainer.numTabs; + + RalewaySemiBold { + text: "EXCHANGE MONEY"; + // Text size + size: 14; + // Anchors + anchors.fill: parent; + anchors.leftMargin: 4; + anchors.rightMargin: 4; + // Style + color: hifi.colors.lightGray50; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + } + + // "SECURITY" tab button + Rectangle { + id: securityButtonContainer; + visible: !notSetUp.visible; + color: root.activeView === "security" ? hifi.colors.blueAccent : hifi.colors.black; + anchors.top: parent.top; + anchors.left: exchangeMoneyButtonContainer.right; + anchors.bottom: parent.bottom; + width: parent.width / tabButtonsContainer.numTabs; + + RalewaySemiBold { + text: "SECURITY"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.fill: parent; + anchors.leftMargin: 4; + anchors.rightMargin: 4; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + MouseArea { + enabled: !walletSetupLightboxContainer.visible; + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + root.activeView = "security"; + tabButtonsContainer.resetTabButtonColors(); + } + onEntered: parent.color = hifi.colors.blueHighlight; + onExited: parent.color = root.activeView === "security" ? hifi.colors.blueAccent : hifi.colors.black; + } + + onVisibleChanged: { + if (visible) { + commerce.getSecurityImage(); + commerce.getKeyFilePath(); + } + } + } + + // "HELP" tab button + Rectangle { + id: helpButtonContainer; + visible: !notSetUp.visible; + color: root.activeView === "help" ? hifi.colors.blueAccent : hifi.colors.black; + anchors.top: parent.top; + anchors.left: securityButtonContainer.right; + anchors.bottom: parent.bottom; + width: parent.width / tabButtonsContainer.numTabs; + + RalewaySemiBold { + text: "HELP"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.fill: parent; + anchors.leftMargin: 4; + anchors.rightMargin: 4; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + MouseArea { + enabled: !walletSetupLightboxContainer.visible; + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + root.activeView = "help"; + tabButtonsContainer.resetTabButtonColors(); + } + onEntered: parent.color = hifi.colors.blueHighlight; + onExited: parent.color = root.activeView === "help" ? hifi.colors.blueAccent : hifi.colors.black; + } + } + + function resetTabButtonColors() { + walletHomeButtonContainer.color = hifi.colors.black; + sendMoneyButtonContainer.color = hifi.colors.black; + securityButtonContainer.color = hifi.colors.black; + helpButtonContainer.color = hifi.colors.black; + if (root.activeView === "walletHome") { + walletHomeButtonContainer.color = hifi.colors.blueAccent; + } else if (root.activeView === "sendMoney") { + sendMoneyButtonContainer.color = hifi.colors.blueAccent; + } else if (root.activeView === "security") { + securityButtonContainer.color = hifi.colors.blueAccent; + } else if (root.activeView === "help") { + helpButtonContainer.color = hifi.colors.blueAccent; + } + } + } + // + // TAB BUTTONS END + // + + // + // FUNCTION DEFINITIONS START + // + // + // Function Name: fromScript() + // + // Relevant Variables: + // None + // + // Arguments: + // message: The message sent from the JavaScript. + // Messages are in format "{method, params}", like json-rpc. + // + // Description: + // Called when a message is received from a script. + // + function fromScript(message) { + switch (message.method) { + default: + console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); + } + } + signal sendToScript(var message); + + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml new file mode 100644 index 0000000000..88f939d393 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -0,0 +1,357 @@ +// +// WalletHome.qml +// qml/hifi/commerce/wallet +// +// WalletHome +// +// Created by Zach Fox on 2017-08-18 +// Copyright 2017 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls + +// references XXX from root context + +Item { + HifiConstants { id: hifi; } + + id: root; + + Hifi.QmlCommerce { + id: commerce; + + onSecurityImageResult: { + if (exists) { + // just set the source again (to be sure the change was noticed) + securityImage.source = ""; + securityImage.source = "image://security/securityImage"; + } + } + + onBalanceResult : { + balanceText.text = parseFloat(result.data.balance/100).toFixed(2); + } + } + + SecurityImageModel { + id: securityImageModel; + } + + Connections { + target: GlobalServices + onMyUsernameChanged: { + usernameText.text = Account.username; + } + } + + // Username Text + RalewayRegular { + id: usernameText; + text: Account.username; + // Text size + size: 24; + // Style + color: hifi.colors.faintGray; + elide: Text.ElideRight; + // Anchors + anchors.top: securityImageContainer.top; + anchors.bottom: securityImageContainer.bottom; + anchors.left: parent.left; + anchors.right: hfcBalanceContainer.left; + } + + // HFC Balance Container + Item { + id: hfcBalanceContainer; + // Anchors + anchors.top: securityImageContainer.top; + anchors.right: securityImageContainer.left; + anchors.rightMargin: 16; + width: 175; + height: 60; + Rectangle { + id: hfcBalanceField; + anchors.right: parent.right; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + height: parent.height - 15; + + // "HFC" balance label + RalewayRegular { + id: balanceLabel; + text: "HFC"; + // Text size + size: 20; + // Anchors + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.right: hfcBalanceField.right; + anchors.rightMargin: 4; + width: paintedWidth; + // Style + color: hifi.colors.darkGray; + // Alignment + horizontalAlignment: Text.AlignRight; + verticalAlignment: Text.AlignVCenter; + + onVisibleChanged: { + if (visible) { + commerce.balance(); + } + } + } + + // Balance Text + FiraSansRegular { + id: balanceText; + text: "--"; + // Text size + size: 28; + // Anchors + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: balanceLabel.left; + anchors.rightMargin: 4; + // Style + color: hifi.colors.darkGray; + // Alignment + horizontalAlignment: Text.AlignRight; + verticalAlignment: Text.AlignVCenter; + } + } + // "balance" text above field + RalewayRegular { + text: "balance"; + // Text size + size: 12; + // Anchors + anchors.top: parent.top; + anchors.bottom: hfcBalanceField.top; + anchors.bottomMargin: 4; + anchors.left: hfcBalanceField.left; + anchors.right: hfcBalanceField.right; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + } + + // Security Image + Item { + id: securityImageContainer; + // Anchors + anchors.top: parent.top; + anchors.right: parent.right; + width: 75; + height: childrenRect.height; + + onVisibleChanged: { + if (visible) { + commerce.getSecurityImage(); + } + } + + Image { + id: securityImage; + // Anchors + anchors.top: parent.top; + anchors.horizontalCenter: parent.horizontalCenter; + height: parent.width - 10; + width: height; + fillMode: Image.PreserveAspectFit; + mipmap: true; + cache: false; + source: "image://security/securityImage"; + } + // "Security picture" text below pic + RalewayRegular { + text: "security picture"; + // Text size + size: 12; + // Anchors + anchors.top: securityImage.bottom; + anchors.topMargin: 4; + anchors.left: securityImageContainer.left; + anchors.right: securityImageContainer.right; + height: paintedHeight; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + } + + // Recent Activity + Item { + id: recentActivityContainer; + anchors.top: securityImageContainer.bottom; + anchors.topMargin: 8; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: homeMessage.visible ? homeMessage.top : root.bottom; + anchors.bottomMargin: 10; + + RalewayRegular { + id: recentActivityText; + text: "Recent Activity"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: 30; + // Text size + size: 22; + // Style + color: hifi.colors.faintGray; + } + + Rectangle { + id: transactionHistory; + anchors.top: recentActivityText.bottom; + anchors.topMargin: 4; + anchors.bottom: toggleFullHistoryButton.top; + anchors.bottomMargin: 8; + anchors.left: parent.left; + anchors.right: parent.right; + + // some placeholder stuff + RalewayRegular { + text: homeMessage.visible ? "you CANNOT scroll through this." : "you CAN scroll through this"; + // Text size + size: 16; + // Anchors + anchors.fill: parent; + // Style + color: hifi.colors.darkGray; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + } + + HifiControlsUit.Button { + id: toggleFullHistoryButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.bottom: parent.bottom; + anchors.right: parent.right; + width: 250; + height: 40; + text: homeMessage.visible ? "See Full Transaction History" : "Collapse Transaction History"; + onClicked: { + if (homeMessage.visible) { + homeMessage.visible = false; + } else { + homeMessage.visible = true; + } + } + } + } + + // Item for "messages" - like "Welcome" + Item { + id: homeMessage; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: childrenRect.height; + + RalewayRegular { + id: messageText; + text: "Welcome! Let's get you some spending money.

" + + "Now that your account is all set up, click the button below to request your starter money. " + + "A robot will promptly review your request and put money into your account."; + // Text size + size: 16; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: 130; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + Item { + id: homeMessageButtons; + anchors.top: messageText.bottom; + anchors.topMargin: 4; + anchors.left: parent.left; + anchors.right: parent.right; + height: 40; + HifiControlsUit.Button { + id: noThanksButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + width: 100; + text: "No Thanks" + onClicked: { + messageText.text = "Okay...weird. Who doesn't like free money? If you change your mind, too bad. Sorry." + homeMessageButtons.visible = false; + } + } + HifiControlsUit.Button { + id: freeMoneyButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.right: parent.right; + width: 210; + text: "Free Money Please" + onClicked: { + messageText.text = "Go, MoneyRobots, Go!" + homeMessageButtons.visible = false; + } + } + } + } + + // + // FUNCTION DEFINITIONS START + // + // + // Function Name: fromScript() + // + // Relevant Variables: + // None + // + // Arguments: + // message: The message sent from the JavaScript. + // Messages are in format "{method, params}", like json-rpc. + // + // Description: + // Called when a message is received from a script. + // + function fromScript(message) { + switch (message.method) { + default: + console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); + } + } + signal sendSignalToWallet(var msg); + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml new file mode 100644 index 0000000000..474322062e --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml @@ -0,0 +1,633 @@ +// +// WalletSetupLightbox.qml +// qml/hifi/commerce/wallet +// +// WalletSetupLightbox +// +// Created by Zach Fox on 2017-08-17 +// Copyright 2017 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: root; + property string lastPage: "login"; + // Style + color: hifi.colors.baseGray; + + Hifi.QmlCommerce { + id: commerce; + + onLoginStatusResult: { + if (isLoggedIn) { + securityImageContainer.visible = true; + } else { + loginPageContainer.visible = true; + } + } + + onSecurityImageResult: { + if (!exists && root.lastPage === "securityImage") { + // ERROR! Invalid security image. + securityImageContainer.visible = true; + choosePassphraseContainer.visible = false; + } + } + + onPassphraseSetupStatusResult: { + securityImageContainer.visible = false; + if (passphraseIsSetup) { + privateKeysReadyContainer.visible = true; + } else { + choosePassphraseContainer.visible = true; + } + } + + onKeyFilePathResult: { + if (path !== "") { + keyFilePath.text = path; + } + } + } + + // + // LOGIN PAGE START + // + Item { + id: loginPageContainer; + visible: false; + // Anchors + anchors.fill: parent; + + Component.onCompleted: { + commerce.getLoginStatus(); + } + + Item { + id: loginTitle; + // Size + width: parent.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + text: "WALLET SETUP - LOGIN"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + } + + // Text below title bar + RalewaySemiBold { + id: loginTitleHelper; + text: "Please Log In to High Fidelity"; + // Text size + size: 24; + // Anchors + anchors.top: loginTitle.bottom; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: 50; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Text below helper text + RalewaySemiBold { + id: loginDetailText; + text: "To set up your wallet, you must first log in to High Fidelity."; + // Text size + size: 18; + // Anchors + anchors.top: loginTitleHelper.bottom; + anchors.topMargin: 25; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: 50; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // "Cancel" button + HifiControlsUit.Button { + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: loginDetailText.bottom; + anchors.topMargin: 25; + anchors.left: parent.left; + anchors.leftMargin: 16; + width: 150; + height: 50; + text: "Log In" + onClicked: { + sendSignalToWallet({method: 'walletSetup_loginClicked'}); + } + } + + // Navigation Bar + Item { + // Size + width: parent.width; + height: 100; + // Anchors: + anchors.left: parent.left; + anchors.bottom: parent.bottom; + + // "Cancel" button + HifiControlsUit.Button { + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: 100; + text: "Cancel" + onClicked: { + sendSignalToWallet({method: 'walletSetup_cancelClicked'}); + } + } + } + } + // + // LOGIN PAGE END + // + + // + // SECURITY IMAGE SELECTION START + // + Item { + id: securityImageContainer; + visible: false; + // Anchors + anchors.fill: parent; + + onVisibleChanged: { + if (visible) { + commerce.getSecurityImage(); + } + } + + Item { + id: securityImageTitle; + // Size + width: parent.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + text: "WALLET SETUP - STEP 1 OF 3"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + } + + // Text below title bar + RalewaySemiBold { + id: securityImageTitleHelper; + text: "Choose a Security Picture:"; + // Text size + size: 24; + // Anchors + anchors.top: securityImageTitle.bottom; + anchors.left: parent.left; + anchors.leftMargin: 16; + height: 50; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + SecurityImageSelection { + id: securityImageSelection; + // Anchors + anchors.top: securityImageTitleHelper.bottom; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: 280; + } + + // Text below security images + RalewayRegular { + text: "Your security picture shows you that the service asking for your passphrase is authorized. You can change your secure picture at any time."; + // Text size + size: 18; + // Anchors + anchors.top: securityImageSelection.bottom; + anchors.topMargin: 40; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Navigation Bar + Item { + // Size + width: parent.width; + height: 100; + // Anchors: + anchors.left: parent.left; + anchors.bottom: parent.bottom; + + // "Cancel" button + HifiControlsUit.Button { + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: 100; + text: "Cancel" + onClicked: { + sendSignalToWallet({method: 'walletSetup_cancelClicked'}); + } + } + + // "Next" button + HifiControlsUit.Button { + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: 100; + text: "Next"; + onClicked: { + root.lastPage = "securityImage"; + var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex()) + commerce.chooseSecurityImage(securityImagePath); + securityImageContainer.visible = false; + choosePassphraseContainer.visible = true; + } + } + } + } + // + // SECURITY IMAGE SELECTION END + // + + // + // SECURE PASSPHRASE SELECTION START + // + Item { + id: choosePassphraseContainer; + visible: false; + // Anchors + anchors.fill: parent; + + onVisibleChanged: { + if (visible) { + commerce.getPassphraseSetupStatus(); + } + } + + Item { + id: passphraseTitle; + // Size + width: parent.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + text: "WALLET SETUP - STEP 2 OF 3"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + } + + // Text below title bar + RalewaySemiBold { + id: passphraseTitleHelper; + text: "Choose a Secure Passphrase"; + // Text size + size: 24; + // Anchors + anchors.top: passphraseTitle.bottom; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: 50; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + PassphraseSelection { + id: passphraseSelection; + anchors.top: passphraseTitleHelper.bottom; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: passphraseNavBar.top; + } + + // Navigation Bar + Item { + id: passphraseNavBar; + // Size + width: parent.width; + height: 100; + // Anchors: + anchors.left: parent.left; + anchors.bottom: parent.bottom; + + // "Back" button + HifiControlsUit.Button { + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: 100; + text: "Back" + onClicked: { + root.lastPage = "choosePassphrase"; + choosePassphraseContainer.visible = false; + securityImageContainer.visible = true; + } + } + + // "Next" button + HifiControlsUit.Button { + id: passphrasePageNextButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: 100; + text: "Next"; + onClicked: { + if (passphraseSelection.validateAndSubmitPassphrase()) { + root.lastPage = "passphrase"; + choosePassphraseContainer.visible = false; + privateKeysReadyContainer.visible = true; + commerce.balance(); // Do this here so that keys are generated. Order might change as backend changes? + } + } + } + } + } + // + // SECURE PASSPHRASE SELECTION END + // + + // + // PRIVATE KEYS READY START + // + Item { + id: privateKeysReadyContainer; + visible: false; + // Anchors + anchors.fill: parent; + + Item { + id: keysReadyTitle; + // Size + width: parent.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + text: "WALLET SETUP - STEP 3 OF 3"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + } + + // Text below title bar + RalewaySemiBold { + id: keysReadyTitleHelper; + text: "Your Private Keys are Ready"; + // Text size + size: 24; + // Anchors + anchors.top: keysReadyTitle.bottom; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: 50; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Text below checkbox + RalewayRegular { + id: explanationText; + text: "Your money and purchases are secured with private keys that only you have access to. " + + "If they are lost, you will not be able to access your money or purchases.

" + + "To protect your privacy, High Fidelity has no access to your private keys and cannot " + + "recover them for any reason.

To safeguard your private keys, backup this file on a regular basis:
"; + // Text size + size: 16; + // Anchors + anchors.top: keysReadyTitleHelper.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + HifiControlsUit.TextField { + id: keyFilePath; + anchors.top: explanationText.bottom; + anchors.topMargin: 10; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: clipboardButton.left; + height: 40; + readOnly: true; + + onVisibleChanged: { + if (visible) { + commerce.getKeyFilePath(); + } + } + } + HifiControlsUit.Button { + id: clipboardButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.right: parent.right; + anchors.rightMargin: 16; + anchors.top: keyFilePath.top; + anchors.bottom: keyFilePath.bottom; + width: height; + HiFiGlyphs { + text: hifi.glyphs.question; + // Size + size: parent.height*1.3; + // Anchors + anchors.fill: parent; + // Style + horizontalAlignment: Text.AlignHCenter; + color: enabled ? hifi.colors.white : hifi.colors.faintGray; + } + + onClicked: { + Window.copyToClipboard(keyFilePath.text); + } + } + + // Navigation Bar + Item { + // Size + width: parent.width; + height: 100; + // Anchors: + anchors.left: parent.left; + anchors.bottom: parent.bottom; + // "Next" button + HifiControlsUit.Button { + id: keysReadyPageNextButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: 100; + text: "Finish"; + onClicked: { + root.visible = false; + sendSignalToWallet({method: 'walletSetup_finished'}); + } + } + } + } + // + // PRIVATE KEYS READY END + // + + // + // FUNCTION DEFINITIONS START + // + signal sendSignalToWallet(var msg); + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/wallet/images/01cat.jpg b/interface/resources/qml/hifi/commerce/wallet/images/01cat.jpg new file mode 100644 index 0000000000..6e7897cb82 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/01cat.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/02car.jpg b/interface/resources/qml/hifi/commerce/wallet/images/02car.jpg new file mode 100644 index 0000000000..5dd8091e57 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/02car.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/03dog.jpg b/interface/resources/qml/hifi/commerce/wallet/images/03dog.jpg new file mode 100644 index 0000000000..4a85b80c0c Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/03dog.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/04stars.jpg b/interface/resources/qml/hifi/commerce/wallet/images/04stars.jpg new file mode 100644 index 0000000000..8f2bf62f83 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/04stars.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/05plane.jpg b/interface/resources/qml/hifi/commerce/wallet/images/05plane.jpg new file mode 100644 index 0000000000..6504459d8b Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/05plane.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/06gingerbread.jpg b/interface/resources/qml/hifi/commerce/wallet/images/06gingerbread.jpg new file mode 100644 index 0000000000..54c37faa2f Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/06gingerbread.jpg differ diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 8bf13bad76..e8752b01e7 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -39,11 +39,24 @@ StackView { property var rpcCounter: 0; signal sendToScript(var message); function rpc(method, parameters, callback) { + console.debug('TabletAddressDialog: rpc: method = ', method, 'parameters = ', parameters, 'callback = ', callback) + rpcCalls[rpcCounter] = callback; var message = {method: method, params: parameters, id: rpcCounter++, jsonrpc: "2.0"}; sendToScript(message); } function fromScript(message) { + if (message.method === 'refreshFeeds') { + var feeds = [happeningNow, places, snapshots]; + console.debug('TabletAddressDialog::fromScript: refreshFeeds', 'feeds = ', feeds); + + feeds.forEach(function(feed) { + Qt.callLater(feed.fillDestinations); + }); + + return; + } + var callback = rpcCalls[message.id]; if (!callback) { console.log('No callback for message fromScript', JSON.stringify(message)); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 398b2dbdb4..affbf83081 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -183,6 +183,7 @@ #include "ui/UpdateDialog.h" #include "ui/overlays/Overlays.h" #include "ui/DomainConnectionModel.h" +#include "ui/ImageProvider.h" #include "Util.h" #include "InterfaceParentFinder.h" #include "ui/OctreeStatsProvider.h" @@ -1735,6 +1736,27 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged); + // Setup the mouse ray pick and related operators + DependencyManager::get()->setMouseRayPickID(_rayPickManager.createRayPick( + RayPickFilter(DependencyManager::get()->PICK_ENTITIES() | DependencyManager::get()->PICK_INCLUDE_NONCOLLIDABLE()), + 0.0f, true)); + DependencyManager::get()->setMouseRayPickResultOperator([&](QUuid rayPickID) { + RayToEntityIntersectionResult entityResult; + RayPickResult result = _rayPickManager.getPrevRayPickResult(rayPickID); + entityResult.intersects = result.type != DependencyManager::get()->INTERSECTED_NONE(); + if (entityResult.intersects) { + entityResult.intersection = result.intersection; + entityResult.distance = result.distance; + entityResult.surfaceNormal = result.surfaceNormal; + entityResult.entityID = result.objectID; + entityResult.entity = DependencyManager::get()->getTree()->findEntityByID(entityResult.entityID); + } + return entityResult; + }); + DependencyManager::get()->setSetPrecisionPickingOperator([&](QUuid rayPickID, bool value) { + _rayPickManager.setPrecisionPicking(rayPickID, value); + }); + qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID()); } @@ -2144,6 +2166,9 @@ void Application::initializeUi() { qApp->quit(); }); + // register the pixmap image provider (used only for security image, for now) + engine->addImageProvider(ImageProvider::PROVIDER_NAME, new ImageProvider()); + setupPreferences(); // For some reason there is already an "Application" object in the QML context, @@ -3736,7 +3761,7 @@ bool Application::shouldPaint() { (float)paintDelaySamples / paintDelayUsecs << "us"; } #endif - + // Throttle if requested if (displayPlugin->isThrottled() && (_lastTimeUpdated.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) { return false; @@ -6276,7 +6301,7 @@ bool Application::askToReplaceDomainContent(const QString& url) { OffscreenUi::warning("Unable to replace content", "You do not have permissions to replace domain content", QMessageBox::Ok, QMessageBox::Ok); } - QJsonObject messageProperties = { + QJsonObject messageProperties = { { "status", methodDetails }, { "content_set_url", url } }; @@ -6359,7 +6384,7 @@ void Application::showAssetServerWidget(QString filePath) { void Application::addAssetToWorldFromURL(QString url) { qInfo(interfaceapp) << "Download model and add to world from" << url; - + QString filename; if (url.contains("filename")) { filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL. diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index b3d6a5e67a..f1e9fa139e 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -14,6 +14,7 @@ #include "DependencyManager.h" #include "Ledger.h" #include "Wallet.h" +#include HIFI_QML_DEF(QmlCommerce) @@ -24,6 +25,7 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult); connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult); connect(wallet.data(), &Wallet::securityImageResult, this, &QmlCommerce::securityImageResult); + connect(wallet.data(), &Wallet::keyFilePathResult, this, &QmlCommerce::keyFilePathResult); } void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) { @@ -50,11 +52,24 @@ void QmlCommerce::inventory() { ledger->inventory(wallet->listPublicKeys()); } -void QmlCommerce::chooseSecurityImage(uint imageID) { +void QmlCommerce::chooseSecurityImage(const QString& imageFile) { auto wallet = DependencyManager::get(); - wallet->chooseSecurityImage(imageID); + wallet->chooseSecurityImage(imageFile); } void QmlCommerce::getSecurityImage() { auto wallet = DependencyManager::get(); wallet->getSecurityImage(); -} \ No newline at end of file +} +void QmlCommerce::getLoginStatus() { + emit loginStatusResult(DependencyManager::get()->isLoggedIn()); +} +void QmlCommerce::setPassphrase(const QString& passphrase) { + emit passphraseSetupStatusResult(true); +} +void QmlCommerce::getPassphraseSetupStatus() { + emit passphraseSetupStatusResult(false); +} +void QmlCommerce::getKeyFilePath() { + auto wallet = DependencyManager::get(); + wallet->getKeyFilePath(); +} diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 4112ab7177..d7a892cf1f 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -18,6 +18,8 @@ #include #include +#include + class QmlCommerce : public OffscreenQmlDialog { Q_OBJECT HIFI_QML_DECL @@ -27,18 +29,25 @@ public: signals: void buyResult(QJsonObject result); - // Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and + // Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and // because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain). void balanceResult(QJsonObject result); void inventoryResult(QJsonObject result); - void securityImageResult(uint imageID); + void securityImageResult(bool exists); + void loginStatusResult(bool isLoggedIn); + void passphraseSetupStatusResult(bool passphraseIsSetup); + void keyFilePathResult(const QString& path); protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = ""); Q_INVOKABLE void balance(); Q_INVOKABLE void inventory(); - Q_INVOKABLE void chooseSecurityImage(uint imageID); + Q_INVOKABLE void chooseSecurityImage(const QString& imageFile); Q_INVOKABLE void getSecurityImage(); + Q_INVOKABLE void getLoginStatus(); + Q_INVOKABLE void setPassphrase(const QString& passphrase); + Q_INVOKABLE void getPassphraseSetupStatus(); + Q_INVOKABLE void getKeyFilePath(); }; #endif // hifi_QmlCommerce_h diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 05ccea3a59..5fefd0c43b 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -12,10 +12,15 @@ #include "CommerceLogging.h" #include "Ledger.h" #include "Wallet.h" +#include "Application.h" +#include "ui/ImageProvider.h" #include +#include +#include #include +#include #include #include @@ -23,8 +28,10 @@ #include #include #include +#include static const char* KEY_FILE = "hifikey"; +static const char* IMAGE_FILE = "hifi_image"; // eventually this will live in keyfile void initialize() { static bool initialized = false; @@ -40,18 +47,30 @@ QString keyFilePath() { return PathUtils::getAppDataFilePath(KEY_FILE); } -// for now the callback function just returns the same string. Later we can hook -// this to the gui (some thought required) +QString imageFilePath() { + return PathUtils::getAppDataFilePath(IMAGE_FILE); +} + +// use the cached _passphrase if it exists, otherwise we need to prompt int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) { // just return a hardcoded pwd for now - static const char* pwd = "pwd"; - strcpy(password, pwd); - return static_cast(strlen(pwd)); + auto passphrase = DependencyManager::get()->getPassphrase(); + if (passphrase) { + strcpy(password, passphrase->toLocal8Bit().constData()); + return static_cast(passphrase->size()); + } else { + // ok gotta bring up modal dialog... But right now lets just + // just keep it empty + return 0; + } } // BEGIN copied code - this will be removed/changed at some point soon // copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp. // We will have a different implementation in practice, but this gives us a start for now +// +// NOTE: we don't really use the private keys returned - we can see how this evolves, but probably +// we should just return a list of public keys? QPair generateRSAKeypair() { RSA* keyPair = RSA_new(); @@ -106,9 +125,11 @@ QPair generateRSAKeypair() { // now lets persist them to files - // TODO: figure out a scheme for multiple keys, etc... + // FIXME: for now I'm appending to the file if it exists. As long as we always put + // the keys in the same order, this works fine. TODO: verify this will skip over + // anything else (like an embedded image) FILE* fp; - if ((fp = fopen(keyFilePath().toStdString().c_str(), "wt"))) { + if ((fp = fopen(keyFilePath().toStdString().c_str(), "at"))) { if (!PEM_write_RSAPublicKey(fp, keyPair)) { fclose(fp); qCDebug(commerce) << "failed to write public key"; @@ -125,7 +146,9 @@ QPair generateRSAKeypair() { RSA_free(keyPair); - // prepare the return values + // prepare the return values. TODO: Fix this - we probably don't really even want the + // private key at all (better to read it when we need it?). Or maybe we do, when we have + // multiple keys? retval.first = new QByteArray(reinterpret_cast(publicKeyDER), publicKeyLength ), retval.second = new QByteArray(reinterpret_cast(privateKeyDER), privateKeyLength ); @@ -192,6 +215,126 @@ RSA* readPrivateKey(const char* filename) { return key; } +static const unsigned char IVEC[16] = "IAmAnIVecYay123"; + +void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) { + // first ivec + memcpy(ivec, IVEC, 16); + auto hash = QCryptographicHash::hash(salt, QCryptographicHash::Md5); + memcpy(ckey, hash.data(), 16); +} + +void Wallet::setPassphrase(const QString& passphrase) { + if (_passphrase) { + delete _passphrase; + } + _passphrase = new QString(passphrase); +} + +// encrypt some stuff +bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFilePath) { + // aes requires a couple 128-bit keys (ckey and ivec). For now, I'll just + // use the md5 of the salt as the ckey (md5 is 128-bit), and ivec will be + // a constant. We can review this later - there are ways to generate keys + // from a password that may be better. + unsigned char ivec[16]; + unsigned char ckey[16]; + + initializeAESKeys(ivec, ckey, _salt); + + int tempSize, outSize; + + // read entire unencrypted file into memory + QFile inputFile(inputFilePath); + if (!inputFile.exists()) { + qCDebug(commerce) << "cannot encrypt" << inputFilePath << "file doesn't exist"; + return false; + } + inputFile.open(QIODevice::ReadOnly); + QByteArray inputFileBuffer = inputFile.readAll(); + inputFile.close(); + + // reserve enough capacity for encrypted bytes + unsigned char* outputFileBuffer = new unsigned char[inputFileBuffer.size() + AES_BLOCK_SIZE]; + + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + + // TODO: add error handling!!! + if (!EVP_EncryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, ckey, ivec)) { + qCDebug(commerce) << "encrypt init failure"; + delete[] outputFileBuffer; + return false; + } + if (!EVP_EncryptUpdate(ctx, outputFileBuffer, &tempSize, (unsigned char*)inputFileBuffer.data(), inputFileBuffer.size())) { + qCDebug(commerce) << "encrypt update failure"; + delete[] outputFileBuffer; + return false; + } + outSize = tempSize; + if (!EVP_EncryptFinal_ex(ctx, outputFileBuffer + outSize, &tempSize)) { + qCDebug(commerce) << "encrypt final failure"; + delete[] outputFileBuffer; + return false; + } + + outSize += tempSize; + EVP_CIPHER_CTX_free(ctx); + qCDebug(commerce) << "encrypted buffer size" << outSize; + QByteArray output((const char*)outputFileBuffer, outSize); + QFile outputFile(outputFilePath); + outputFile.open(QIODevice::WriteOnly); + outputFile.write(output); + outputFile.close(); + + delete[] outputFileBuffer; + return true; +} + +bool Wallet::decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) { + unsigned char ivec[16]; + unsigned char ckey[16]; + initializeAESKeys(ivec, ckey, _salt); + + // read encrypted file + QFile inputFile(inputFilePath); + if (!inputFile.exists()) { + qCDebug(commerce) << "cannot decrypt file" << inputFilePath << "it doesn't exist"; + return false; + } + inputFile.open(QIODevice::ReadOnly); + QByteArray encryptedBuffer = inputFile.readAll(); + inputFile.close(); + + // setup decrypted buffer + unsigned char* outputBuffer = new unsigned char[encryptedBuffer.size()]; + int tempSize; + + // TODO: add error handling + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if (!EVP_DecryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, ckey, ivec)) { + qCDebug(commerce) << "decrypt init failure"; + delete[] outputBuffer; + return false; + } + if (!EVP_DecryptUpdate(ctx, outputBuffer, &tempSize, (unsigned char*)encryptedBuffer.data(), encryptedBuffer.size())) { + qCDebug(commerce) << "decrypt update failure"; + delete[] outputBuffer; + return false; + } + *outputBufferSize = tempSize; + if (!EVP_DecryptFinal_ex(ctx, outputBuffer + tempSize, &tempSize)) { + qCDebug(commerce) << "decrypt final failure"; + delete[] outputBuffer; + return false; + } + EVP_CIPHER_CTX_free(ctx); + *outputBufferSize += tempSize; + *outputBufferPtr = outputBuffer; + qCDebug(commerce) << "decrypted buffer size" << *outputBufferSize; + delete[] outputBuffer; + return true; +} + bool Wallet::createIfNeeded() { if (_publicKeys.count() > 0) return false; @@ -205,7 +348,7 @@ bool Wallet::createIfNeeded() { qCDebug(commerce) << "read private key"; RSA_free(key); // K -- add the public key since we have a legit private key associated with it - _publicKeys.push_back(publicKey.toBase64()); + _publicKeys.push_back(QUrl::toPercentEncoding(publicKey.toBase64())); return false; } } @@ -228,6 +371,7 @@ bool Wallet::generateKeyPair() { auto ledger = DependencyManager::get(); return ledger->receiveAt(key, oldKey); } + QStringList Wallet::listPublicKeys() { qCInfo(commerce) << "Enumerating public keys."; createIfNeeded(); @@ -268,10 +412,65 @@ QString Wallet::signWithKey(const QByteArray& text, const QString& key) { } -void Wallet::chooseSecurityImage(uint imageID) { - _chosenSecurityImage = (SecurityImage)imageID; - emit securityImageResult(imageID); +void Wallet::chooseSecurityImage(const QString& filename) { + + if (_securityImage) { + delete _securityImage; + } + // temporary... + QString path = qApp->applicationDirPath(); + path.append("/resources/qml/hifi/commerce/"); + path.append(filename); + // now create a new security image pixmap + _securityImage = new QPixmap(); + + qCDebug(commerce) << "loading data for pixmap from" << path; + _securityImage->load(path); + + // encrypt it and save. + if (encryptFile(path, imageFilePath())) { + qCDebug(commerce) << "emitting pixmap"; + + // inform the image provider + auto engine = DependencyManager::get()->getSurfaceContext()->engine(); + auto imageProvider = reinterpret_cast(engine->imageProvider(ImageProvider::PROVIDER_NAME)); + imageProvider->setSecurityImage(_securityImage); + + emit securityImageResult(true); + } else { + qCDebug(commerce) << "failed to encrypt security image"; + emit securityImageResult(false); + } } + void Wallet::getSecurityImage() { - emit securityImageResult(_chosenSecurityImage); + unsigned char* data; + int dataLen; + + // if already decrypted, don't do it again + if (_securityImage) { + emit securityImageResult(true); + return; + } + + // decrypt and return + if (decryptFile(imageFilePath(), &data, &dataLen)) { + // create the pixmap + _securityImage = new QPixmap(); + _securityImage->loadFromData(data, dataLen, "jpg"); + qCDebug(commerce) << "created pixmap from encrypted file"; + + // inform the image provider + auto engine = DependencyManager::get()->getSurfaceContext()->engine(); + auto imageProvider = reinterpret_cast(engine->imageProvider(ImageProvider::PROVIDER_NAME)); + imageProvider->setSecurityImage(_securityImage); + + emit securityImageResult(true); + } else { + qCDebug(commerce) << "failed to decrypt security image (maybe none saved yet?)"; + emit securityImageResult(false); + } +} +void Wallet::getKeyFilePath() { + emit keyFilePathResult(keyFilePath()); } diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index a1c7c7752b..4c0d7190bb 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -16,6 +16,8 @@ #include +#include + class Wallet : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -26,16 +28,21 @@ public: bool generateKeyPair(); QStringList listPublicKeys(); QString signWithKey(const QByteArray& text, const QString& key); - void chooseSecurityImage(uint imageID); + void chooseSecurityImage(const QString& imageFile); void getSecurityImage(); + void getKeyFilePath(); + + void setSalt(const QByteArray& salt) { _salt = salt; } + QByteArray getSalt() { return _salt; } + + void setPassphrase(const QString& passphrase); + QString* getPassphrase() { return _passphrase; } signals: - void securityImageResult(uint imageID); + void securityImageResult(bool exists) ; + void keyFilePathResult(const QString& path); protected: - // ALWAYS add SecurityImage enum values to the END of the enum. - // They must be in the same order as the images are listed in - // SecurityImageSelection.qml enum SecurityImage { NONE = 0, Cat, @@ -48,7 +55,12 @@ protected: private: QStringList _publicKeys{}; - SecurityImage _chosenSecurityImage = SecurityImage::NONE; + QPixmap* _securityImage { nullptr }; + QByteArray _salt {"iamsalt!"}; + QString* _passphrase { new QString("pwd") }; + + bool encryptFile(const QString& inputFilePath, const QString& outputFilePath); + bool decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); }; #endif // hifi_Wallet_h diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index d901d12cf4..23f023cf7a 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -64,6 +64,7 @@ public: // You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays. void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps); + void setPrecisionPicking(const bool precisionPicking) { DependencyManager::get()->setPrecisionPicking(_rayPickUID, precisionPicking); } void setIgnoreEntities(const QScriptValue& ignoreEntities) { DependencyManager::get()->setIgnoreEntities(_rayPickUID, ignoreEntities); } void setIncludeEntities(const QScriptValue& includeEntities) { DependencyManager::get()->setIncludeEntities(_rayPickUID, includeEntities); } void setIgnoreOverlays(const QScriptValue& ignoreOverlays) { DependencyManager::get()->setIgnoreOverlays(_rayPickUID, ignoreOverlays); } diff --git a/interface/src/raypick/LaserPointerManager.cpp b/interface/src/raypick/LaserPointerManager.cpp index 908bcc39f1..1fd0fe9e88 100644 --- a/interface/src/raypick/LaserPointerManager.cpp +++ b/interface/src/raypick/LaserPointerManager.cpp @@ -97,6 +97,14 @@ void LaserPointerManager::update() { } } +void LaserPointerManager::setPrecisionPicking(QUuid uid, const bool precisionPicking) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setPrecisionPicking(precisionPicking); + } +} + void LaserPointerManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { QReadLocker lock(&_containsLock); if (_laserPointers.contains(uid)) { diff --git a/interface/src/raypick/LaserPointerManager.h b/interface/src/raypick/LaserPointerManager.h index 020b778983..b573410fe7 100644 --- a/interface/src/raypick/LaserPointerManager.h +++ b/interface/src/raypick/LaserPointerManager.h @@ -31,6 +31,7 @@ public: void editRenderState(QUuid uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps); const RayPickResult getPrevRayPickResult(const QUuid uid); + void setPrecisionPicking(QUuid uid, const bool precisionPicking); void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities); void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities); void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays); diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h index 8ae263b0ec..d65eb335b3 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.h +++ b/interface/src/raypick/LaserPointerScriptingInterface.h @@ -30,6 +30,7 @@ public slots: Q_INVOKABLE void setRenderState(QUuid uid, const QString& renderState) { qApp->getLaserPointerManager().setRenderState(uid, renderState.toStdString()); } Q_INVOKABLE RayPickResult getPrevRayPickResult(QUuid uid) { return qApp->getLaserPointerManager().getPrevRayPickResult(uid); } + Q_INVOKABLE void setPrecisionPicking(QUuid uid, const bool precisionPicking) { qApp->getLaserPointerManager().setPrecisionPicking(uid, precisionPicking); } Q_INVOKABLE void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { qApp->getLaserPointerManager().setIgnoreEntities(uid, ignoreEntities); } Q_INVOKABLE void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { qApp->getLaserPointerManager().setIncludeEntities(uid, includeEntities); } Q_INVOKABLE void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { qApp->getLaserPointerManager().setIgnoreOverlays(uid, ignoreOverlays); } diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index e108145d55..04045b2116 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -41,12 +41,14 @@ public: // The key is the Flags Flags _flags; - RayPickFilter() : _flags(PICK_NOTHING) {} + RayPickFilter() : _flags(getBitMask(PICK_NOTHING)) {} RayPickFilter(const Flags& flags) : _flags(flags) {} bool operator== (const RayPickFilter& rhs) const { return _flags == rhs._flags; } bool operator!= (const RayPickFilter& rhs) const { return _flags != rhs._flags; } + void setFlag(FlagBit flag, bool value) { _flags[flag] = value; } + bool doesPickNothing() const { return _flags[PICK_NOTHING]; } bool doesPickEntities() const { return _flags[PICK_ENTITIES]; } bool doesPickOverlays() const { return _flags[PICK_OVERLAYS]; } @@ -61,27 +63,27 @@ public: // Helpers for RayPickManager Flags getEntityFlags() const { - Flags toReturn(PICK_ENTITIES); + unsigned int toReturn = getBitMask(PICK_ENTITIES); if (doesPickInvisible()) { - toReturn |= Flags(PICK_INCLUDE_INVISIBLE); + toReturn |= getBitMask(PICK_INCLUDE_INVISIBLE); } if (doesPickNonCollidable()) { - toReturn |= Flags(PICK_INCLUDE_NONCOLLIDABLE); + toReturn |= getBitMask(PICK_INCLUDE_NONCOLLIDABLE); } - return toReturn; + return Flags(toReturn); } Flags getOverlayFlags() const { - Flags toReturn(PICK_OVERLAYS); + unsigned int toReturn = getBitMask(PICK_OVERLAYS); if (doesPickInvisible()) { - toReturn |= Flags(PICK_INCLUDE_INVISIBLE); + toReturn |= getBitMask(PICK_INCLUDE_INVISIBLE); } if (doesPickNonCollidable()) { - toReturn |= Flags(PICK_INCLUDE_NONCOLLIDABLE); + toReturn |= getBitMask(PICK_INCLUDE_NONCOLLIDABLE); } - return toReturn; + return Flags(toReturn); } - Flags getAvatarFlags() const { return Flags(PICK_AVATARS); } - Flags getHUDFlags() const { return Flags(PICK_HUD); } + Flags getAvatarFlags() const { return Flags(getBitMask(PICK_AVATARS)); } + Flags getHUDFlags() const { return Flags(getBitMask(PICK_HUD)); } static unsigned int getBitMask(FlagBit bit) { return 1 << bit; } @@ -98,10 +100,12 @@ public: void disable() { _enabled = false; } const RayPickFilter& getFilter() { return _filter; } - const float& getMaxDistance() { return _maxDistance; } - const bool& isEnabled() { return _enabled; } + float getMaxDistance() { return _maxDistance; } + bool isEnabled() { return _enabled; } const RayPickResult& getPrevRayPickResult() { return _prevResult; } + void setPrecisionPicking(bool precisionPicking) { _filter.setFlag(RayPickFilter::PICK_COURSE, !precisionPicking); } + void setRayPickResult(const RayPickResult& rayPickResult) { _prevResult = rayPickResult; } const QVector& getIgnoreEntites() { return _ignoreEntities; } diff --git a/interface/src/raypick/RayPickManager.cpp b/interface/src/raypick/RayPickManager.cpp index 8b639d4a81..1ec8efc331 100644 --- a/interface/src/raypick/RayPickManager.cpp +++ b/interface/src/raypick/RayPickManager.cpp @@ -64,10 +64,11 @@ void RayPickManager::update() { RayToEntityIntersectionResult entityRes; bool fromCache = true; bool invisible = rayPick->getFilter().doesPickInvisible(); - bool noncollidable = rayPick->getFilter().doesPickNonCollidable(); + bool nonCollidable = rayPick->getFilter().doesPickNonCollidable(); RayPickFilter::Flags entityMask = rayPick->getFilter().getEntityFlags(); if (!checkAndCompareCachedResults(rayKey, results, res, entityMask)) { - entityRes = DependencyManager::get()->findRayIntersectionVector(ray, true, rayPick->getIncludeEntites(), rayPick->getIgnoreEntites(), !invisible, !noncollidable); + entityRes = DependencyManager::get()->findRayIntersectionVector(ray, !rayPick->getFilter().doesPickCourse(), + rayPick->getIncludeEntites(), rayPick->getIgnoreEntites(), !invisible, !nonCollidable); fromCache = false; } @@ -81,10 +82,11 @@ void RayPickManager::update() { RayToOverlayIntersectionResult overlayRes; bool fromCache = true; bool invisible = rayPick->getFilter().doesPickInvisible(); - bool noncollidable = rayPick->getFilter().doesPickNonCollidable(); + bool nonCollidable = rayPick->getFilter().doesPickNonCollidable(); RayPickFilter::Flags overlayMask = rayPick->getFilter().getOverlayFlags(); if (!checkAndCompareCachedResults(rayKey, results, res, overlayMask)) { - overlayRes = qApp->getOverlays().findRayIntersectionVector(ray, true, rayPick->getIncludeOverlays(), rayPick->getIgnoreOverlays(), !invisible, !noncollidable); + overlayRes = qApp->getOverlays().findRayIntersectionVector(ray, !rayPick->getFilter().doesPickCourse(), + rayPick->getIncludeOverlays(), rayPick->getIgnoreOverlays(), !invisible, !nonCollidable); fromCache = false; } @@ -204,6 +206,14 @@ const RayPickResult RayPickManager::getPrevRayPickResult(const QUuid uid) { return RayPickResult(); } +void RayPickManager::setPrecisionPicking(QUuid uid, const bool precisionPicking) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker lock(_rayPickLocks[uid].get()); + _rayPicks[uid]->setPrecisionPicking(precisionPicking); + } +} + void RayPickManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { QReadLocker containsLock(&_containsLock); if (_rayPicks.contains(uid)) { diff --git a/interface/src/raypick/RayPickManager.h b/interface/src/raypick/RayPickManager.h index 8ef2ceebe0..f2b1ff4ae4 100644 --- a/interface/src/raypick/RayPickManager.h +++ b/interface/src/raypick/RayPickManager.h @@ -38,6 +38,7 @@ public: void disableRayPick(const QUuid uid); const RayPickResult getPrevRayPickResult(const QUuid uid); + void setPrecisionPicking(QUuid uid, const bool precisionPicking); void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities); void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities); void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays); diff --git a/interface/src/raypick/RayPickScriptingInterface.cpp b/interface/src/raypick/RayPickScriptingInterface.cpp index aa1613d696..015f8fd7d1 100644 --- a/interface/src/raypick/RayPickScriptingInterface.cpp +++ b/interface/src/raypick/RayPickScriptingInterface.cpp @@ -82,6 +82,10 @@ RayPickResult RayPickScriptingInterface::getPrevRayPickResult(QUuid uid) { return qApp->getRayPickManager().getPrevRayPickResult(uid); } +void RayPickScriptingInterface::setPrecisionPicking(QUuid uid, const bool precisionPicking) { + qApp->getRayPickManager().setPrecisionPicking(uid, precisionPicking); +} + void RayPickScriptingInterface::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { qApp->getRayPickManager().setIgnoreEntities(uid, ignoreEntities); } diff --git a/interface/src/raypick/RayPickScriptingInterface.h b/interface/src/raypick/RayPickScriptingInterface.h index e576b5d076..f7ed2e6fa6 100644 --- a/interface/src/raypick/RayPickScriptingInterface.h +++ b/interface/src/raypick/RayPickScriptingInterface.h @@ -43,6 +43,7 @@ public slots: Q_INVOKABLE void removeRayPick(QUuid uid); Q_INVOKABLE RayPickResult getPrevRayPickResult(QUuid uid); + Q_INVOKABLE void setPrecisionPicking(QUuid uid, const bool precisionPicking); Q_INVOKABLE void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities); Q_INVOKABLE void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities); Q_INVOKABLE void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays); @@ -50,7 +51,6 @@ public slots: Q_INVOKABLE void setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars); Q_INVOKABLE void setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars); -private: unsigned int PICK_NOTHING() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_NOTHING); } unsigned int PICK_ENTITIES() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_ENTITIES); } unsigned int PICK_OVERLAYS() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_OVERLAYS); } diff --git a/interface/src/ui/ImageProvider.cpp b/interface/src/ui/ImageProvider.cpp new file mode 100644 index 0000000000..4925cdf1e9 --- /dev/null +++ b/interface/src/ui/ImageProvider.cpp @@ -0,0 +1,29 @@ +// +// ImageProvider.cpp +// interface/src/ui +// +// Created by David Kelly on 8/23/2017. +// Copyright 2017 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 +// + +#include "ImageProvider.h" +#include + +const QString ImageProvider::PROVIDER_NAME = "security"; + +QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) { + + // adjust the internal pixmap to have the requested size + if (id == "securityImage" && _securityImage) { + *size = _securityImage->size(); + if (requestedSize.width() > 0 && requestedSize.height() > 0) { + return _securityImage->scaled(requestedSize.width(), requestedSize.height(), Qt::KeepAspectRatio); + } else { + return _securityImage->copy(); + } + } + return QPixmap(); +} diff --git a/interface/src/ui/ImageProvider.h b/interface/src/ui/ImageProvider.h new file mode 100644 index 0000000000..c0b620585a --- /dev/null +++ b/interface/src/ui/ImageProvider.h @@ -0,0 +1,33 @@ +// +// ImageProvider.h +// +// Created by David Kelly on 2017/08/23 +// Copyright 2017 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 +// + +#pragma once +#ifndef hifi_ImageProvider_h +#define hifi_ImageProvider_h + +#include + +class ImageProvider: public QQuickImageProvider { +public: + static const QString PROVIDER_NAME; + + ImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap) {} + + QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override; + + void setSecurityImage(QPixmap* pixmap) { _securityImage = pixmap; } + +protected: + QPixmap* _securityImage { nullptr }; + +}; + +#endif //hifi_ImageProvider_h + diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 52a3d7a929..242021d698 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -264,7 +264,7 @@ void Circle3DOverlay::render(RenderArgs* args) { const render::ShapeKey Circle3DOverlay::getShapeKey() { auto builder = render::ShapeKey::Builder().withoutCullFace().withUnlit(); - if (getAlpha() != 1.0f) { + if (isTransparent()) { builder.withTranslucent(); } if (!getIsSolid() || shouldDrawHUDLayer()) { diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index 31cbe5e822..5ab32d21fe 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -117,7 +117,7 @@ void Cube3DOverlay::render(RenderArgs* args) { const render::ShapeKey Cube3DOverlay::getShapeKey() { auto builder = render::ShapeKey::Builder(); - if (getAlpha() != 1.0f) { + if (isTransparent()) { builder.withTranslucent(); } if (!getIsSolid() || shouldDrawHUDLayer()) { diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index 82417db83a..76a82d0268 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -19,6 +19,7 @@ #include "GeometryUtil.h" +#include "AbstractViewStateInterface.h" QString const Image3DOverlay::TYPE = "image3d"; @@ -58,12 +59,29 @@ void Image3DOverlay::render(RenderArgs* args) { if (!_isLoaded) { _isLoaded = true; _texture = DependencyManager::get()->getTexture(_url); + _textureIsLoaded = false; } if (!_visible || !getParentVisible() || !_texture || !_texture->isLoaded()) { return; } + // Once the texture has loaded, check if we need to update the render item because of transparency + if (!_textureIsLoaded && _texture && _texture->getGPUTexture()) { + _textureIsLoaded = true; + bool prevAlphaTexture = _alphaTexture; + _alphaTexture = _texture->getGPUTexture()->getUsage().isAlpha(); + if (_alphaTexture != prevAlphaTexture) { + auto itemID = getRenderItemID(); + if (render::Item::isValidID(itemID)) { + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + render::Transaction transaction; + transaction.updateItem(itemID); + scene->enqueueTransaction(transaction); + } + } + } + Q_ASSERT(args->_batch); gpu::Batch* batch = args->_batch; @@ -92,9 +110,9 @@ void Image3DOverlay::render(RenderArgs* args) { glm::vec2 topLeft(-x, -y); glm::vec2 bottomRight(x, y); - glm::vec2 texCoordTopLeft(fromImage.x() / imageWidth, fromImage.y() / imageHeight); - glm::vec2 texCoordBottomRight((fromImage.x() + fromImage.width()) / imageWidth, - (fromImage.y() + fromImage.height()) / imageHeight); + glm::vec2 texCoordTopLeft((fromImage.x() + 0.5f) / imageWidth, (fromImage.y() + 0.5f) / imageHeight); + glm::vec2 texCoordBottomRight((fromImage.x() + fromImage.width() - 0.5f) / imageWidth, + (fromImage.y() + fromImage.height() - 0.5f) / imageHeight); const float MAX_COLOR = 255.0f; xColor color = getColor(); @@ -126,7 +144,7 @@ const render::ShapeKey Image3DOverlay::getShapeKey() { if (_emissive || shouldDrawHUDLayer()) { builder.withUnlit(); } - if (getAlpha() != 1.0f) { + if (isTransparent()) { builder.withTranslucent(); } return builder.build(); diff --git a/interface/src/ui/overlays/Image3DOverlay.h b/interface/src/ui/overlays/Image3DOverlay.h index 2e5f74749c..4f813e7368 100644 --- a/interface/src/ui/overlays/Image3DOverlay.h +++ b/interface/src/ui/overlays/Image3DOverlay.h @@ -39,6 +39,7 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; + bool isTransparent() override { return Base3DOverlay::isTransparent() || _alphaTexture; } virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) override; @@ -48,6 +49,8 @@ public: private: QString _url; NetworkTexturePointer _texture; + bool _textureIsLoaded { false }; + bool _alphaTexture { false }; bool _emissive { false }; QRect _fromImage; // where from in the image to sample diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index d8fe0e6bb8..cc8ed8e1a8 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -13,6 +13,7 @@ #include #include +#include "AbstractViewStateInterface.h" QString const Line3DOverlay::TYPE = "line3d"; @@ -149,7 +150,7 @@ void Line3DOverlay::render(RenderArgs* args) { const render::ShapeKey Line3DOverlay::getShapeKey() { auto builder = render::ShapeKey::Builder().withOwnPipeline(); - if (getAlpha() != 1.0f || _glow > 0.0f) { + if (isTransparent()) { builder.withTranslucent(); } return builder.build(); @@ -222,9 +223,17 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) { auto glow = properties["glow"]; if (glow.isValid()) { + float prevGlow = _glow; setGlow(glow.toFloat()); - if (_glow > 0.0f) { - _alpha = 0.5f; + // Update our payload key if necessary to handle transparency + if ((prevGlow <= 0.0f && _glow > 0.0f) || (prevGlow > 0.0f && _glow <= 0.0f)) { + auto itemID = getRenderItemID(); + if (render::Item::isValidID(itemID)) { + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + render::Transaction transaction; + transaction.updateItem(itemID); + scene->enqueueTransaction(transaction); + } } } diff --git a/interface/src/ui/overlays/Line3DOverlay.h b/interface/src/ui/overlays/Line3DOverlay.h index c9ceac55a9..9abc2f1a8d 100644 --- a/interface/src/ui/overlays/Line3DOverlay.h +++ b/interface/src/ui/overlays/Line3DOverlay.h @@ -45,6 +45,7 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; + bool isTransparent() override { return Base3DOverlay::isTransparent() || _glow > 0.0f; } virtual Line3DOverlay* createClone() const override; diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index a9774eea06..db2979b4d5 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -59,6 +59,7 @@ public: bool isLoaded() { return _isLoaded; } bool getVisible() const { return _visible; } bool shouldDrawHUDLayer() const { return _drawHUDLayer; } + virtual bool isTransparent() { return getAlphaPulse() != 0.0f || getAlpha() != 1.0f; }; xColor getColor(); float getAlpha(); Anchor getAnchor() const { return _anchor; } diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 0f7de8bd79..d6e0b61e7d 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -300,7 +300,12 @@ OverlayID Overlays::cloneOverlay(OverlayID id) { } bool Overlays::editOverlay(OverlayID id, const QVariant& properties) { - if (QThread::currentThread() != thread()) { + auto thisOverlay = getOverlay(id); + if (!thisOverlay) { + return false; + } + + if (!thisOverlay->is3D() && QThread::currentThread() != thread()) { // NOTE editOverlay can be called very frequently in scripts and can't afford to // block waiting on the main thread. Additionally, no script actually // examines the return value and does something useful with it, so use a non-blocking @@ -309,22 +314,15 @@ bool Overlays::editOverlay(OverlayID id, const QVariant& properties) { return true; } - Overlay::Pointer thisOverlay = getOverlay(id); - if (thisOverlay) { - thisOverlay->setProperties(properties.toMap()); - return true; - } - return false; + thisOverlay->setProperties(properties.toMap()); + return true; } bool Overlays::editOverlays(const QVariant& propertiesById) { - if (QThread::currentThread() != thread()) { - // NOTE see comment on editOverlay for why this is not a blocking call - QMetaObject::invokeMethod(this, "editOverlays", Q_ARG(QVariant, propertiesById)); - return true; - } + bool defer2DOverlays = QThread::currentThread() != thread(); - QVariantMap map = propertiesById.toMap(); + QVariantMap deferrred; + const QVariantMap map = propertiesById.toMap(); bool success = true; for (const auto& key : map.keys()) { OverlayID id = OverlayID(key); @@ -333,9 +331,21 @@ bool Overlays::editOverlays(const QVariant& propertiesById) { success = false; continue; } - QVariant properties = map[key]; + + const QVariant& properties = map[key]; + if (defer2DOverlays && !thisOverlay->is3D()) { + deferrred[key] = properties; + continue; + } thisOverlay->setProperties(properties.toMap()); } + + // For 2D/QML overlays, we need to perform the edit on the main thread + if (defer2DOverlays && !deferrred.empty()) { + // NOTE see comment on editOverlay for why this is not a blocking call + QMetaObject::invokeMethod(this, "editOverlays", Q_ARG(QVariant, deferrred)); + } + return success; } diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index 8b7100205a..f2684a4368 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -37,7 +37,7 @@ namespace render { if (std::static_pointer_cast(overlay)->getDrawInFront()) { builder.withLayered(); } - if (overlay->getAlphaPulse() != 0.0f || overlay->getAlpha() != 1.0f) { + if (overlay->isTransparent()) { builder.withTransparent(); } } else { diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp index f981fe1462..22124a0a97 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -110,7 +110,7 @@ void Rectangle3DOverlay::render(RenderArgs* args) { const render::ShapeKey Rectangle3DOverlay::getShapeKey() { auto builder = render::ShapeKey::Builder().withOwnPipeline(); - if (getAlpha() != 1.0f) { + if (isTransparent()) { builder.withTranslucent(); } return builder.build(); diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 7126f7bde4..df0ecba307 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -62,7 +62,7 @@ void Shape3DOverlay::render(RenderArgs* args) { const render::ShapeKey Shape3DOverlay::getShapeKey() { auto builder = render::ShapeKey::Builder(); - if (getAlpha() != 1.0f) { + if (isTransparent()) { builder.withTranslucent(); } if (!getIsSolid() || shouldDrawHUDLayer()) { diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index ee3f9b9784..9309316d6e 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -59,7 +59,7 @@ void Sphere3DOverlay::render(RenderArgs* args) { const render::ShapeKey Sphere3DOverlay::getShapeKey() { auto builder = render::ShapeKey::Builder(); - if (getAlpha() != 1.0f) { + if (isTransparent()) { builder.withTranslucent(); } if (!getIsSolid() || shouldDrawHUDLayer()) { diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index 4b110b8099..bb8c24aa11 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -143,7 +143,7 @@ void Text3DOverlay::render(RenderArgs* args) { const render::ShapeKey Text3DOverlay::getShapeKey() { auto builder = render::ShapeKey::Builder(); - if (getAlpha() != 1.0f) { + if (isTransparent()) { builder.withTranslucent(); } return builder.build(); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 40af679a13..5acb4d2ceb 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -318,8 +318,8 @@ void Web3DOverlay::render(RenderArgs* args) { } const render::ShapeKey Web3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias(); - if (getAlpha() != 1.0f) { + auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias().withOwnPipeline(); + if (isTransparent()) { builder.withTranslucent(); } return builder.build(); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 1b91737364..aa368b291b 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -32,6 +32,10 @@ #include "AnimUtil.h" #include "IKTarget.h" +static int nextRigId = 1; +static std::map rigRegistry; +static std::mutex rigRegistryMutex; + static bool isEqual(const glm::vec3& u, const glm::vec3& v) { const float EPSILON = 0.0001f; return glm::length(u - v) / glm::length(u) <= EPSILON; @@ -49,6 +53,26 @@ const glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 0.9f, 0.0f); const glm::vec3 DEFAULT_LEFT_EYE_POS(0.3f, 0.9f, 0.0f); const glm::vec3 DEFAULT_HEAD_POS(0.0f, 0.75f, 0.0f); +Rig::Rig() { + // Ensure thread-safe access to the rigRegistry. + std::lock_guard guard(rigRegistryMutex); + + // Insert this newly allocated rig into the rig registry + _rigId = nextRigId; + rigRegistry[_rigId] = this; + nextRigId++; +} + +Rig::~Rig() { + // Ensure thread-safe access to the rigRegstry, but also prevent the rig from being deleted + // while Rig::animationStateHandlerResult is being invoked on a script thread. + std::lock_guard guard(rigRegistryMutex); + auto iter = rigRegistry.find(_rigId); + if (iter != rigRegistry.end()) { + rigRegistry.erase(iter); + } +} + void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { UserAnimState::ClipNodeEnum clipNodeEnum; @@ -935,8 +959,19 @@ void Rig::updateAnimationStateHandlers() { // called on avatar update thread (wh int identifier = data.key(); StateHandler& value = data.value(); QScriptValue& function = value.function; - auto handleResult = [this, identifier](QScriptValue result) { // called in script thread to get the result back to us. - animationStateHandlerResult(identifier, result); + int rigId = _rigId; + auto handleResult = [rigId, identifier](QScriptValue result) { // called in script thread to get the result back to us. + // Hold the rigRegistryMutex to ensure thread-safe access to the rigRegistry, but + // also to prevent the rig from being deleted while this lambda is being executed. + std::lock_guard guard(rigRegistryMutex); + + // if the rig pointer is in the registry, it has not been deleted yet. + auto iter = rigRegistry.find(rigId); + if (iter != rigRegistry.end()) { + Rig* rig = iter->second; + assert(rig); + rig->animationStateHandlerResult(identifier, result); + } }; // invokeMethod makes a copy of the args, and copies of AnimVariantMap do copy the underlying map, so this will correctly capture // the state of _animVars and allow continued changes to _animVars in this thread without conflict. diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index ca55635250..eabc62ab75 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -97,8 +97,8 @@ public: Hover }; - Rig() {} - virtual ~Rig() {} + Rig(); + virtual ~Rig(); void destroyAnimGraph(); @@ -372,6 +372,8 @@ protected: glm::vec3 _prevLeftHandPoleVector { -Vectors::UNIT_Z }; bool _prevLeftHandPoleVectorValid { false }; + + int _rigId; }; #endif /* defined(__hifi__Rig__) */ diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index a8eca41077..a2d2d34837 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -53,7 +53,6 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf _viewState(viewState), _scriptingServices(scriptingServices), _displayModelBounds(false), - _dontDoPrecisionPicking(false), _layeredZones(this) { REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory) @@ -428,28 +427,6 @@ void EntityTreeRenderer::deleteReleasedModels() { } } -RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, - bool precisionPicking, const QVector& entityIdsToInclude, - const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { - RayToEntityIntersectionResult result; - if (_tree) { - EntityTreePointer entityTree = std::static_pointer_cast(_tree); - - OctreeElementPointer element; - EntityItemPointer intersectedEntity = NULL; - result.intersects = entityTree->findRayIntersection(ray.origin, ray.direction, - entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, precisionPicking, - element, result.distance, result.face, result.surfaceNormal, - (void**)&intersectedEntity, lockType, &result.accurate); - if (result.intersects && intersectedEntity) { - result.entityID = intersectedEntity->getEntityItemID(); - result.intersection = ray.origin + (ray.direction * result.distance); - result.entity = intersectedEntity; - } - } - return result; -} - void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface) { connect(this, &EntityTreeRenderer::mousePressOnEntity, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity); @@ -530,11 +507,10 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { if (!_tree || _shuttingDown) { return; } + PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent"); PickRay ray = _viewState->computePickRay(event->x(), event->y()); - - bool precisionPicking = !_dontDoPrecisionPicking; - RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock, precisionPicking); + RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); if (rayPickResult.intersects) { //qCDebug(entitiesrenderer) << "mousePressEvent over entity:" << rayPickResult.entityID; @@ -579,11 +555,10 @@ void EntityTreeRenderer::mouseDoublePressEvent(QMouseEvent* event) { if (!_tree || _shuttingDown) { return; } + PerformanceTimer perfTimer("EntityTreeRenderer::mouseDoublePressEvent"); PickRay ray = _viewState->computePickRay(event->x(), event->y()); - - bool precisionPicking = !_dontDoPrecisionPicking; - RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock, precisionPicking); + RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); if (rayPickResult.intersects) { //qCDebug(entitiesrenderer) << "mouseDoublePressEvent over entity:" << rayPickResult.entityID; @@ -622,8 +597,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) { PerformanceTimer perfTimer("EntityTreeRenderer::mouseReleaseEvent"); PickRay ray = _viewState->computePickRay(event->x(), event->y()); - bool precisionPicking = !_dontDoPrecisionPicking; - RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock, precisionPicking); + RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); if (rayPickResult.intersects) { //qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID; @@ -671,14 +645,11 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { if (!_tree || _shuttingDown) { return; } + PerformanceTimer perfTimer("EntityTreeRenderer::mouseMoveEvent"); - PickRay ray = _viewState->computePickRay(event->x(), event->y()); - - bool precisionPicking = false; // for mouse moves we do not do precision picking - RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking); + RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); if (rayPickResult.intersects) { - glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index f4909a2036..b70252d68c 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -59,6 +59,10 @@ public: float getEntityLoadingPriority(const EntityItem& item) const { return _calculateEntityLoadingPriorityFunc(item); } void setEntityLoadingPriorityFunction(CalculateEntityLoadingPriority fn) { this->_calculateEntityLoadingPriorityFunc = fn; } + void setMouseRayPickID(QUuid rayPickID) { _mouseRayPickID = rayPickID; } + void setMouseRayPickResultOperator(std::function getPrevRayPickResultOperator) { _getPrevRayPickResultOperator = getPrevRayPickResultOperator; } + void setSetPrecisionPickingOperator(std::function setPrecisionPickingOperator) { _setPrecisionPickingOperator = setPrecisionPickingOperator; } + void shutdown(); void update(); @@ -130,7 +134,7 @@ public slots: // optional slots that can be wired to menu items void setDisplayModelBounds(bool value) { _displayModelBounds = value; } - void setDontDoPrecisionPicking(bool value) { _dontDoPrecisionPicking = value; } + void setPrecisionPicking(bool value) { _setPrecisionPickingOperator(_mouseRayPickID, value); } protected: virtual OctreePointer createTree() override { @@ -150,10 +154,6 @@ private: void checkAndCallPreload(const EntityItemID& entityID, bool reload = false, bool unloadFirst = false); QList _releasedModels; - RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, - bool precisionPicking, const QVector& entityIdsToInclude = QVector(), - const QVector& entityIdsToDiscard = QVector(), bool visibleOnly=false, - bool collidableOnly = false); EntityItemID _currentHoverOverEntityID; EntityItemID _currentClickingOnEntityID; @@ -176,12 +176,15 @@ private: AbstractViewStateInterface* _viewState; AbstractScriptingServicesInterface* _scriptingServices; bool _displayModelBounds; - bool _dontDoPrecisionPicking; bool _shuttingDown { false }; QMultiMap _waitingOnPreload; + QUuid _mouseRayPickID; + std::function _getPrevRayPickResultOperator; + std::function _setPrecisionPickingOperator; + class LayeredZone { public: LayeredZone(std::shared_ptr zone, QUuid id, float volume) : zone(zone), id(id), volume(volume) {} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 36ac6ba1cc..0801c32cea 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -262,7 +262,7 @@ public: glm::vec3 getRegistrationPoint() const; /// registration point as ratio of entity /// registration point as ratio of entity - void setRegistrationPoint(const glm::vec3& value); + virtual void setRegistrationPoint(const glm::vec3& value); bool hasAngularVelocity() const { return getAngularVelocity() != ENTITY_ITEM_ZERO_VEC3; } bool hasLocalAngularVelocity() const { return getLocalAngularVelocity() != ENTITY_ITEM_ZERO_VEC3; } diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index fbbc1bde71..6a1c359b5a 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -280,24 +280,24 @@ void EntitySimulation::moveSimpleKinematics(const quint64& now) { } void EntitySimulation::addDynamic(EntityDynamicPointer dynamic) { - QMutexLocker lock(&_mutex); + QMutexLocker lock(&_dynamicsMutex); _dynamicsToAdd += dynamic; } void EntitySimulation::removeDynamic(const QUuid dynamicID) { - QMutexLocker lock(&_mutex); + QMutexLocker lock(&_dynamicsMutex); _dynamicsToRemove += dynamicID; } void EntitySimulation::removeDynamics(QList dynamicIDsToRemove) { - QMutexLocker lock(&_mutex); + QMutexLocker lock(&_dynamicsMutex); foreach(QUuid uuid, dynamicIDsToRemove) { _dynamicsToRemove.insert(uuid); } } void EntitySimulation::applyDynamicChanges() { - QMutexLocker lock(&_mutex); + QMutexLocker lock(&_dynamicsMutex); _dynamicsToAdd.clear(); _dynamicsToRemove.clear(); } diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 84d30c495d..1c633aa9dc 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -105,6 +105,7 @@ protected: SetOfEntities _simpleKinematicEntities; // entities undergoing non-colliding kinematic motion QList _dynamicsToAdd; QSet _dynamicsToRemove; + QMutex _dynamicsMutex { QMutex::Recursive }; protected: SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete) diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index c1f6508a76..aa31130c82 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -92,13 +92,12 @@ bool PolyLineEntityItem::appendPoint(const glm::vec3& point) { qCDebug(entities) << "MAX POINTS REACHED!"; return false; } - glm::vec3 halfBox = getDimensions() * 0.5f; - if ((point.x < -halfBox.x || point.x > halfBox.x) || (point.y < -halfBox.y || point.y > halfBox.y) || (point.z < -halfBox.z || point.z > halfBox.z)) { - qCDebug(entities) << "Point is outside entity's bounding box"; - return false; - } + _points << point; _pointsChanged = true; + + calculateScaleAndRegistrationPoint(); + return true; } @@ -141,23 +140,69 @@ bool PolyLineEntityItem::setLinePoints(const QVector& points) { return; } - for (int i = 0; i < points.size(); i++) { - glm::vec3 point = points.at(i); - glm::vec3 halfBox = getDimensions() * 0.5f; - if ((point.x < -halfBox.x || point.x > halfBox.x) || - (point.y < -halfBox.y || point.y > halfBox.y) || - (point.z < -halfBox.z || point.z > halfBox.z)) { - qCDebug(entities) << "Point is outside entity's bounding box"; - return; - } - } _points = points; + + calculateScaleAndRegistrationPoint(); + result = true; }); return result; } +void PolyLineEntityItem::calculateScaleAndRegistrationPoint() { + glm::vec3 high(0.0f, 0.0f, 0.0f); + glm::vec3 low(0.0f, 0.0f, 0.0f); + for (int i = 0; i < _points.size(); i++) { + glm::vec3 point = _points.at(i); + + if (point.x > high.x) { + high.x = point.x; + } else if (point.x < low.x) { + low.x = point.x; + } + + if (point.y > high.y) { + high.y = point.y; + } else if (point.y < low.y) { + low.y = point.y; + } + + if (point.z > high.z) { + high.z = point.z; + } else if (point.z < low.z) { + low.z = point.z; + } + } + const float EPSILON = 0.0001f; + if (_points.size() > 1) { + // if all the points in the Polyline are at the same place in space, use default dimension settings + if ((low - high).length() < EPSILON) { + SpatiallyNestable::setScale(glm::vec3(1.0f, 1.0f, 1.0f)); + EntityItem::setRegistrationPoint(glm::vec3(0.5f)); + return; + } + + glm::vec3 result; + const float halfLineWidth = 0.075f; // sadly _strokeWidths() don't seem to correspond to reality, so just use a flat assumption of the stroke width + result.x = fabsf(high.x) + fabsf(low.x) + halfLineWidth; + result.y = fabsf(high.y) + fabsf(low.y) + halfLineWidth; + result.z = fabsf(high.z) + fabsf(low.z) + halfLineWidth; + SpatiallyNestable::setScale(result); + + // Center the poly line in the bounding box + glm::vec3 point = _points.at(0); + glm::vec3 startPointInScaleSpace = point - low; + startPointInScaleSpace += glm::vec3(halfLineWidth * 0.5f); + glm::vec3 newRegistrationPoint = startPointInScaleSpace / result; + EntityItem::setRegistrationPoint(newRegistrationPoint); + } else { + // if Polyline has only one or fewer points, use default dimension settings + SpatiallyNestable::setScale(glm::vec3(1.0f, 1.0f, 1.0f)); + EntityItem::setRegistrationPoint(glm::vec3(0.5f)); + } +} + int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index ed161762fc..eca9a1ec79 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -81,10 +81,17 @@ class PolyLineEntityItem : public EntityItem { BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject, bool precisionPicking) const override { return false; } + // disable these external interfaces as PolyLineEntities caculate their own dimensions based on the points they contain + virtual void setRegistrationPoint(const glm::vec3& value) override {}; + virtual void setScale(const glm::vec3& scale) override {}; + virtual void setScale(float value) override {}; + virtual void debugDump() const override; static const float DEFAULT_LINE_WIDTH; static const int MAX_POINTS_PER_LINE; - +private: + void calculateScaleAndRegistrationPoint(); + protected: rgbColor _color; float _lineWidth; diff --git a/libraries/networking/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp index 217d3096f5..e2a3e79c79 100644 --- a/libraries/networking/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -113,6 +113,18 @@ QString HifiSockAddr::toString() const { return _address.toString() + ":" + QString::number(_port); } +bool HifiSockAddr::hasPrivateAddress() const { + // an address is private if it is loopback or falls in any of the RFC1918 address spaces + const QPair TWENTY_FOUR_BIT_BLOCK = { QHostAddress("10.0.0.0"), 8 }; + const QPair TWENTY_BIT_BLOCK = { QHostAddress("172.16.0.0") , 12 }; + const QPair SIXTEEN_BIT_BLOCK = { QHostAddress("192.168.0.0"), 16 }; + + return _address.isLoopback() + || _address.isInSubnet(TWENTY_FOUR_BIT_BLOCK) + || _address.isInSubnet(TWENTY_BIT_BLOCK) + || _address.isInSubnet(SIXTEEN_BIT_BLOCK); +} + QDebug operator<<(QDebug debug, const HifiSockAddr& sockAddr) { debug.nospace() << sockAddr._address.toString().toLocal8Bit().constData() << ":" << sockAddr._port; return debug.space(); diff --git a/libraries/networking/src/HifiSockAddr.h b/libraries/networking/src/HifiSockAddr.h index af939de736..b582198139 100644 --- a/libraries/networking/src/HifiSockAddr.h +++ b/libraries/networking/src/HifiSockAddr.h @@ -55,6 +55,8 @@ public: QString toString() const; + bool hasPrivateAddress() const; // checks if the address behind this sock addr is private per RFC 1918 + friend QDebug operator<<(QDebug debug, const HifiSockAddr& sockAddr); friend QDataStream& operator<<(QDataStream& dataStream, const HifiSockAddr& sockAddr); friend QDataStream& operator>>(QDataStream& dataStream, HifiSockAddr& sockAddr); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 1d094d923c..954b9685af 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -202,12 +202,12 @@ QUdpSocket& LimitedNodeList::getDTLSSocket() { return *_dtlsSocket; } -bool LimitedNodeList::isPacketVerified(const udt::Packet& packet) { +bool LimitedNodeList::isPacketVerifiedWithSource(const udt::Packet& packet, Node* sourceNode) { // We track bandwidth when doing packet verification to avoid needing to do a node lookup // later when we already do it in packetSourceAndHashMatchAndTrackBandwidth. A node lookup // incurs a lock, so it is ideal to avoid needing to do it 2+ times for each packet // received. - return packetVersionMatch(packet) && packetSourceAndHashMatchAndTrackBandwidth(packet); + return packetVersionMatch(packet) && packetSourceAndHashMatchAndTrackBandwidth(packet, sourceNode); } bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { @@ -256,7 +256,7 @@ bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { } } -bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packet& packet) { +bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packet& packet, Node* sourceNode) { PacketType headerType = NLPacket::typeInHeader(packet); @@ -298,14 +298,18 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe } else { QUuid sourceID = NLPacket::sourceIDInHeader(packet); - // figure out which node this is from - SharedNodePointer matchingNode = nodeWithUUID(sourceID); + // check if we were passed a sourceNode hint or if we need to look it up + if (!sourceNode) { + // figure out which node this is from + SharedNodePointer matchingNode = nodeWithUUID(sourceID); + sourceNode = matchingNode.data(); + } - if (matchingNode) { + if (sourceNode) { if (!PacketTypeEnum::getNonVerifiedPackets().contains(headerType)) { QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet); - QByteArray expectedHash = NLPacket::hashForPacketAndSecret(packet, matchingNode->getConnectionSecret()); + QByteArray expectedHash = NLPacket::hashForPacketAndSecret(packet, sourceNode->getConnectionSecret()); // check if the md5 hash in the header matches the hash we would expect if (packetHeaderHash != expectedHash) { @@ -323,9 +327,9 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe // No matter if this packet is handled or not, we update the timestamp for the last time we heard // from this sending node - matchingNode->setLastHeardMicrostamp(usecTimestampNow()); + sourceNode->setLastHeardMicrostamp(usecTimestampNow()); - emit dataReceived(matchingNode->getType(), packet.getPayloadSize()); + emit dataReceived(sourceNode->getType(), packet.getPayloadSize()); return true; diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index f4ec47636b..f730bcfa17 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -286,7 +286,9 @@ public: void setPacketFilterOperator(udt::PacketFilterOperator filterOperator) { _nodeSocket.setPacketFilterOperator(filterOperator); } bool packetVersionMatch(const udt::Packet& packet); - bool isPacketVerified(const udt::Packet& packet); + + bool isPacketVerifiedWithSource(const udt::Packet& packet, Node* sourceNode = nullptr); + bool isPacketVerified(const udt::Packet& packet) { return isPacketVerifiedWithSource(packet); } static void makeSTUNRequestPacket(char* stunRequestPacket); @@ -352,7 +354,7 @@ protected: void setLocalSocket(const HifiSockAddr& sockAddr); - bool packetSourceAndHashMatchAndTrackBandwidth(const udt::Packet& packet); + bool packetSourceAndHashMatchAndTrackBandwidth(const udt::Packet& packet, Node* sourceNode = nullptr); void processSTUNResponse(std::unique_ptr packet); void handleNodeKill(const SharedNodePointer& node); diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index cc5df515aa..e94c43b6fb 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -53,8 +53,12 @@ QVariant NodePermissions::toVariant(QHash groupRanks) { values["permissions_id"] = _id; if (_groupIDSet) { values["group_id"] = _groupID; - if (groupRanks.contains(_rankID)) { + + if (!_rankID.isNull()) { values["rank_id"] = _rankID; + } + + if (groupRanks.contains(_rankID)) { values["rank_name"] = groupRanks[_rankID].name; values["rank_order"] = groupRanks[_rankID].order; } diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index fe507ed1ee..b6f460cf39 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -348,8 +348,7 @@ void PhysicalEntitySimulation::addDynamic(EntityDynamicPointer dynamic) { void PhysicalEntitySimulation::applyDynamicChanges() { QList dynamicsFailedToAdd; if (_physicsEngine) { - // FIXME put fine grain locking into _physicsEngine - QMutexLocker lock(&_mutex); + QMutexLocker lock(&_dynamicsMutex); foreach(QUuid dynamicToRemove, _dynamicsToRemove) { _physicsEngine->removeDynamic(dynamicToRemove); } @@ -360,9 +359,10 @@ void PhysicalEntitySimulation::applyDynamicChanges() { } } } + // applyDynamicChanges will clear _dynamicsToRemove and _dynamicsToAdd + EntitySimulation::applyDynamicChanges(); } - // applyDynamicChanges will clear _dynamicsToRemove and _dynamicsToAdd - EntitySimulation::applyDynamicChanges(); + // put back the ones that couldn't yet be added foreach (EntityDynamicPointer dynamicFailedToAdd, dynamicsFailedToAdd) { addDynamic(dynamicFailedToAdd); diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 2270118861..2285337f41 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -28,7 +28,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/notifications.js", "system/dialTone.js", "system/firstPersonHMD.js", - "system/tablet-ui/tabletUI.js" + "system/tablet-ui/tabletUI.js", + "system/commerce/wallet.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js new file mode 100644 index 0000000000..14954e5df6 --- /dev/null +++ b/scripts/system/commerce/wallet.js @@ -0,0 +1,152 @@ +"use strict"; +/*jslint vars:true, plusplus:true, forin:true*/ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// wallet.js +// +// Created by Zach Fox on 2017-08-17 +// Copyright 2017 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 +// + +/*global XXX */ + +(function () { // BEGIN LOCAL_SCOPE + + + // Function Name: onButtonClicked() + // + // Description: + // -Fired when the app button is pressed. + // + // Relevant Variables: + // -WALLET_QML_SOURCE: The path to the Wallet QML + // -onWalletScreen: true/false depending on whether we're looking at the app. + var WALLET_QML_SOURCE = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml"; + var onWalletScreen = false; + function onButtonClicked() { + if (!tablet) { + print("Warning in buttonClicked(): 'tablet' undefined!"); + return; + } + if (onWalletScreen) { + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(WALLET_QML_SOURCE); + } + } + + // Function Name: sendToQml() + // + // Description: + // -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to + // the QML in the format "{method, params}", like json-rpc. See also fromQml(). + function sendToQml(message) { + tablet.sendToQml(message); + } + + // Function Name: fromQml() + // + // Description: + // -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML + // in the format "{method, params}", like json-rpc. See also sendToQml(). + function fromQml(message) { + switch (message.method) { + case 'walletSetup_cancelClicked': + tablet.gotoHomeScreen(); + break; + case 'walletSetup_loginClicked': + if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) + || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { + Menu.triggerOption("Login / Sign Up"); + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLOnTop("../../../dialogs/TabletLoginDialog.qml"); + } + break; + default: + print('Unrecognized message from QML:', JSON.stringify(message)); + } + } + + // Function Name: wireEventBridge() + // + // Description: + // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or + // disable to event bridge. + // + // Relevant Variables: + // -hasEventBridge: true/false depending on whether we've already connected the event bridge. + var hasEventBridge = false; + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + // Function Name: onTabletScreenChanged() + // + // Description: + // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string + // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. + function onTabletScreenChanged(type, url) { + onWalletScreen = (type === "QML" && url === WALLET_QML_SOURCE); + wireEventBridge(onWalletScreen); + // Change button to active when window is first openend, false otherwise. + if (button) { + button.editProperties({ isActive: onWalletScreen }); + } + } + + // + // Manage the connection between the button and the window. + // + var button; + var buttonName = "WALLET"; + var tablet = null; + var walletEnabled = Settings.getValue("inspectionMode", false); + function startup() { + if (walletEnabled) { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + text: buttonName, + icon: "icons/tablet-icons/wallet-i.svg", + activeIcon: "icons/tablet-icons/wallet-a.svg" + }); + button.clicked.connect(onButtonClicked); + tablet.screenChanged.connect(onTabletScreenChanged); + } + } + function shutdown() { + button.clicked.disconnect(onButtonClicked); + tablet.removeButton(button); + if (tablet) { + tablet.screenChanged.disconnect(onTabletScreenChanged); + if (onWalletScreen) { + tablet.gotoHomeScreen(); + } + } + } + + // + // Run the functions. + // + startup(); + Script.scriptEnding.connect(shutdown); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 95e3f9361e..d7f4ebb99d 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1182,7 +1182,7 @@ function MyController(hand) { this.fullEnd = fullEnd; this.laserPointer = LaserPointers.createLaserPointer({ joint: (hand == RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", - filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS | RayPick.PICK_INCLUDE_NONCOLLIDABLE, maxDistance: PICK_MAX_DISTANCE, posOffset: getGrabPointSphereOffset(this.handToController()), renderStates: renderStates, @@ -1191,7 +1191,7 @@ function MyController(hand) { }); this.headLaserPointer = LaserPointers.createLaserPointer({ joint: "Avatar", - filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS | RayPick.PICK_INCLUDE_NONCOLLIDABLE, maxDistance: PICK_MAX_DISTANCE, renderStates: headRenderStates, faceAvatar: true, diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 77be746bf4..2889a1514a 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -115,7 +115,7 @@ itemId: id, itemName: name, itemAuthor: author, - itemPrice: price ? parseInt(price, 10) : Math.round(Math.random() * 50), + itemPrice: price ? parseInt(price, 10) : 0, itemHref: href })); } diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index ca6a873b73..057bd1dd85 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -178,8 +178,8 @@ type: "Row" }, { - id: "emitShouldTrail", - name: "Emit Should Trail", + id: "emitterShouldTrail", + name: "Emitter Should Trail", type: "Boolean" }, { diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index fb842d1314..96ed3e3f59 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -42,6 +42,8 @@ }); function fromQml(message) { + console.debug('tablet-goto::fromQml: message = ', JSON.stringify(message)); + var response = {id: message.id, jsonrpc: "2.0"}; switch (message.method) { case 'request': @@ -98,6 +100,8 @@ button.editProperties({isActive: shouldActivateButton}); wireEventBridge(true); messagesWaiting(false); + tablet.sendToQml({ method: 'refreshFeeds' }) + } else { shouldActivateButton = false; onGotoScreen = false;