mirror of
https://github.com/overte-org/overte.git
synced 2025-06-18 20:40:38 +02:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
8ef8e974b6
18 changed files with 697 additions and 78 deletions
|
@ -183,6 +183,12 @@ if (WIN32)
|
||||||
add_dependency_external_projects(steamworks)
|
add_dependency_external_projects(steamworks)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# include OPENSSL
|
||||||
|
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
|
||||||
|
|
||||||
|
# append OpenSSL to our list of libraries to link
|
||||||
|
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES})
|
||||||
|
|
||||||
# disable /OPT:REF and /OPT:ICF for the Debug builds
|
# disable /OPT:REF and /OPT:ICF for the Debug builds
|
||||||
# This will prevent the following linker warnings
|
# This will prevent the following linker warnings
|
||||||
# LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification
|
# LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification
|
||||||
|
|
|
@ -66,6 +66,14 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onSecurityImageResult: {
|
||||||
|
securityImage.source = securityImageSelection.getImagePathFromImageID(imageID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SecurityImageSelection {
|
||||||
|
id: securityImageSelection;
|
||||||
|
referrerURL: checkoutRoot.itemHref;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -80,6 +88,20 @@ Rectangle {
|
||||||
anchors.left: parent.left;
|
anchors.left: parent.left;
|
||||||
anchors.top: parent.top;
|
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
|
// Title Bar text
|
||||||
RalewaySemiBold {
|
RalewaySemiBold {
|
||||||
id: titleBarText;
|
id: titleBarText;
|
||||||
|
@ -87,8 +109,11 @@ Rectangle {
|
||||||
// Text size
|
// Text size
|
||||||
size: hifi.fontSizes.overlayTitle;
|
size: hifi.fontSizes.overlayTitle;
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.fill: parent;
|
anchors.top: parent.top;
|
||||||
|
anchors.left: securityImage.right;
|
||||||
anchors.leftMargin: 16;
|
anchors.leftMargin: 16;
|
||||||
|
anchors.bottom: parent.bottom;
|
||||||
|
width: paintedWidth;
|
||||||
// Style
|
// Style
|
||||||
color: hifi.colors.lightGrayText;
|
color: hifi.colors.lightGrayText;
|
||||||
// Alignment
|
// Alignment
|
||||||
|
@ -381,7 +406,7 @@ Rectangle {
|
||||||
// "Buy" button
|
// "Buy" button
|
||||||
HifiControlsUit.Button {
|
HifiControlsUit.Button {
|
||||||
id: buyButton;
|
id: buyButton;
|
||||||
enabled: balanceAfterPurchase >= 0 && !alreadyOwned && inventoryReceived && balanceReceived;
|
enabled: balanceAfterPurchase >= 0 && inventoryReceived && balanceReceived;
|
||||||
color: hifi.buttons.black;
|
color: hifi.buttons.black;
|
||||||
colorScheme: hifi.colorSchemes.dark;
|
colorScheme: hifi.colorSchemes.dark;
|
||||||
anchors.top: parent.top;
|
anchors.top: parent.top;
|
||||||
|
@ -391,9 +416,16 @@ Rectangle {
|
||||||
anchors.right: parent.right;
|
anchors.right: parent.right;
|
||||||
anchors.rightMargin: 20;
|
anchors.rightMargin: 20;
|
||||||
width: parent.width/2 - anchors.rightMargin*2;
|
width: parent.width/2 - anchors.rightMargin*2;
|
||||||
text: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned" : "Buy") : "--";
|
text: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned: Get Item" : "Buy") : "--";
|
||||||
onClicked: {
|
onClicked: {
|
||||||
commerce.buy(itemId, parseInt(itemPriceText.text));
|
if (!alreadyOwned) {
|
||||||
|
commerce.buy(itemId, parseInt(itemPriceText.text));
|
||||||
|
} else {
|
||||||
|
if (urlHandler.canHandleUrl(itemHref)) {
|
||||||
|
urlHandler.handleUrl(itemHref);
|
||||||
|
}
|
||||||
|
sendToScript({method: 'checkout_buySuccess', itemId: itemId});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -427,6 +459,7 @@ Rectangle {
|
||||||
itemHref = message.params.itemHref;
|
itemHref = message.params.itemHref;
|
||||||
commerce.balance();
|
commerce.balance();
|
||||||
commerce.inventory();
|
commerce.inventory();
|
||||||
|
commerce.getSecurityImage();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
|
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
|
||||||
|
|
|
@ -43,6 +43,14 @@ Rectangle {
|
||||||
inventoryContentsList.model = inventory.assets;
|
inventoryContentsList.model = inventory.assets;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onSecurityImageResult: {
|
||||||
|
securityImage.source = securityImageSelection.getImagePathFromImageID(imageID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SecurityImageSelection {
|
||||||
|
id: securityImageSelection;
|
||||||
|
referrerURL: inventoryRoot.referrerURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -51,12 +59,26 @@ Rectangle {
|
||||||
Item {
|
Item {
|
||||||
id: titleBarContainer;
|
id: titleBarContainer;
|
||||||
// Size
|
// Size
|
||||||
width: inventoryRoot.width;
|
width: parent.width;
|
||||||
height: 50;
|
height: 50;
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.left: parent.left;
|
anchors.left: parent.left;
|
||||||
anchors.top: parent.top;
|
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
|
// Title Bar text
|
||||||
RalewaySemiBold {
|
RalewaySemiBold {
|
||||||
id: titleBarText;
|
id: titleBarText;
|
||||||
|
@ -64,8 +86,11 @@ Rectangle {
|
||||||
// Text size
|
// Text size
|
||||||
size: hifi.fontSizes.overlayTitle;
|
size: hifi.fontSizes.overlayTitle;
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.fill: parent;
|
anchors.top: parent.top;
|
||||||
|
anchors.left: securityImage.right;
|
||||||
anchors.leftMargin: 16;
|
anchors.leftMargin: 16;
|
||||||
|
anchors.bottom: parent.bottom;
|
||||||
|
width: paintedWidth;
|
||||||
// Style
|
// Style
|
||||||
color: hifi.colors.lightGrayText;
|
color: hifi.colors.lightGrayText;
|
||||||
// Alignment
|
// Alignment
|
||||||
|
@ -73,6 +98,25 @@ Rectangle {
|
||||||
verticalAlignment: Text.AlignVCenter;
|
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
|
// Separator
|
||||||
HifiControlsUit.Separator {
|
HifiControlsUit.Separator {
|
||||||
anchors.left: parent.left;
|
anchors.left: parent.left;
|
||||||
|
@ -166,6 +210,7 @@ Rectangle {
|
||||||
}
|
}
|
||||||
ListView {
|
ListView {
|
||||||
id: inventoryContentsList;
|
id: inventoryContentsList;
|
||||||
|
clip: true;
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.top: inventoryContentsLabel.bottom;
|
anchors.top: inventoryContentsLabel.bottom;
|
||||||
anchors.topMargin: 8;
|
anchors.topMargin: 8;
|
||||||
|
@ -262,6 +307,7 @@ Rectangle {
|
||||||
referrerURL = message.referrerURL;
|
referrerURL = message.referrerURL;
|
||||||
commerce.balance();
|
commerce.balance();
|
||||||
commerce.inventory();
|
commerce.inventory();
|
||||||
|
commerce.getSecurityImage();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
|
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
|
||||||
|
|
42
interface/resources/qml/hifi/commerce/SecurityImageModel.qml
Normal file
42
interface/resources/qml/hifi/commerce/SecurityImageModel.qml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// SecurityImageModel.qml
|
||||||
|
// qml/hifi/commerce
|
||||||
|
//
|
||||||
|
// SecurityImageModel
|
||||||
|
//
|
||||||
|
// 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 QtQuick 2.5
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: root;
|
||||||
|
ListElement{
|
||||||
|
sourcePath: "images/01cat.jpg"
|
||||||
|
securityImageEnumValue: 1;
|
||||||
|
}
|
||||||
|
ListElement{
|
||||||
|
sourcePath: "images/02car.jpg"
|
||||||
|
securityImageEnumValue: 2;
|
||||||
|
}
|
||||||
|
ListElement{
|
||||||
|
sourcePath: "images/03dog.jpg"
|
||||||
|
securityImageEnumValue: 3;
|
||||||
|
}
|
||||||
|
ListElement{
|
||||||
|
sourcePath: "images/04stars.jpg"
|
||||||
|
securityImageEnumValue: 4;
|
||||||
|
}
|
||||||
|
ListElement{
|
||||||
|
sourcePath: "images/05plane.jpg"
|
||||||
|
securityImageEnumValue: 5;
|
||||||
|
}
|
||||||
|
ListElement{
|
||||||
|
sourcePath: "images/06gingerbread.jpg"
|
||||||
|
securityImageEnumValue: 6;
|
||||||
|
}
|
||||||
|
}
|
271
interface/resources/qml/hifi/commerce/SecurityImageSelection.qml
Normal file
271
interface/resources/qml/hifi/commerce/SecurityImageSelection.qml
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
//
|
||||||
|
// 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.<b><br>If you don't see your selected image on these dialogs, do not use them!</b>";
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
}
|
BIN
interface/resources/qml/hifi/commerce/images/01cat.jpg
Normal file
BIN
interface/resources/qml/hifi/commerce/images/01cat.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
BIN
interface/resources/qml/hifi/commerce/images/02car.jpg
Normal file
BIN
interface/resources/qml/hifi/commerce/images/02car.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
BIN
interface/resources/qml/hifi/commerce/images/03dog.jpg
Normal file
BIN
interface/resources/qml/hifi/commerce/images/03dog.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
BIN
interface/resources/qml/hifi/commerce/images/04stars.jpg
Normal file
BIN
interface/resources/qml/hifi/commerce/images/04stars.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 86 KiB |
BIN
interface/resources/qml/hifi/commerce/images/05plane.jpg
Normal file
BIN
interface/resources/qml/hifi/commerce/images/05plane.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
BIN
interface/resources/qml/hifi/commerce/images/06gingerbread.jpg
Normal file
BIN
interface/resources/qml/hifi/commerce/images/06gingerbread.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
|
@ -24,12 +24,12 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons
|
||||||
transaction["inventory_key"] = inventory_key;
|
transaction["inventory_key"] = inventory_key;
|
||||||
transaction["inventory_buyer_username"] = buyerUsername;
|
transaction["inventory_buyer_username"] = buyerUsername;
|
||||||
QJsonDocument transactionDoc{ transaction };
|
QJsonDocument transactionDoc{ transaction };
|
||||||
QString transactionString = transactionDoc.toJson(QJsonDocument::Compact);
|
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
|
||||||
|
|
||||||
auto wallet = DependencyManager::get<Wallet>();
|
auto wallet = DependencyManager::get<Wallet>();
|
||||||
QString signature = wallet->signWithKey(transactionString, hfc_key);
|
QString signature = wallet->signWithKey(transactionString, hfc_key);
|
||||||
QJsonObject request;
|
QJsonObject request;
|
||||||
request["transaction"] = transactionString;
|
request["transaction"] = QString(transactionString);
|
||||||
request["signature"] = signature;
|
request["signature"] = signature;
|
||||||
|
|
||||||
qCInfo(commerce) << "Transaction:" << QJsonDocument(request).toJson(QJsonDocument::Compact);
|
qCInfo(commerce) << "Transaction:" << QJsonDocument(request).toJson(QJsonDocument::Compact);
|
||||||
|
@ -78,4 +78,4 @@ void Ledger::inventory(const QStringList& keys) {
|
||||||
inventoryObject.insert("assets", _inventory);
|
inventoryObject.insert("assets", _inventory);
|
||||||
qCInfo(commerce) << "Inventory:" << inventoryObject;
|
qCInfo(commerce) << "Inventory:" << inventoryObject;
|
||||||
emit inventoryResult(inventoryObject, "");
|
emit inventoryResult(inventoryObject, "");
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,11 @@ HIFI_QML_DEF(QmlCommerce)
|
||||||
|
|
||||||
QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
|
QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
|
||||||
auto ledger = DependencyManager::get<Ledger>();
|
auto ledger = DependencyManager::get<Ledger>();
|
||||||
|
auto wallet = DependencyManager::get<Wallet>();
|
||||||
connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult);
|
connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult);
|
||||||
connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult);
|
connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult);
|
||||||
connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult);
|
connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult);
|
||||||
|
connect(wallet.data(), &Wallet::securityImageResult, this, &QmlCommerce::securityImageResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) {
|
void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) {
|
||||||
|
@ -48,4 +50,13 @@ void QmlCommerce::inventory() {
|
||||||
auto ledger = DependencyManager::get<Ledger>();
|
auto ledger = DependencyManager::get<Ledger>();
|
||||||
auto wallet = DependencyManager::get<Wallet>();
|
auto wallet = DependencyManager::get<Wallet>();
|
||||||
ledger->inventory(wallet->listPublicKeys());
|
ledger->inventory(wallet->listPublicKeys());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QmlCommerce::chooseSecurityImage(uint imageID) {
|
||||||
|
auto wallet = DependencyManager::get<Wallet>();
|
||||||
|
wallet->chooseSecurityImage(imageID);
|
||||||
|
}
|
||||||
|
void QmlCommerce::getSecurityImage() {
|
||||||
|
auto wallet = DependencyManager::get<Wallet>();
|
||||||
|
wallet->getSecurityImage();
|
||||||
|
}
|
|
@ -30,11 +30,14 @@ signals:
|
||||||
// because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain).
|
// because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain).
|
||||||
void balanceResult(int balance, const QString& failureMessage);
|
void balanceResult(int balance, const QString& failureMessage);
|
||||||
void inventoryResult(QJsonObject inventory, const QString& failureMessage);
|
void inventoryResult(QJsonObject inventory, const QString& failureMessage);
|
||||||
|
void securityImageResult(uint imageID);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = "");
|
Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = "");
|
||||||
Q_INVOKABLE void balance();
|
Q_INVOKABLE void balance();
|
||||||
Q_INVOKABLE void inventory();
|
Q_INVOKABLE void inventory();
|
||||||
|
Q_INVOKABLE void chooseSecurityImage(uint imageID);
|
||||||
|
Q_INVOKABLE void getSecurityImage();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_QmlCommerce_h
|
#endif // hifi_QmlCommerce_h
|
||||||
|
|
|
@ -9,30 +9,223 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <quuid.h>
|
|
||||||
#include "CommerceLogging.h"
|
#include "CommerceLogging.h"
|
||||||
#include "Ledger.h"
|
#include "Ledger.h"
|
||||||
#include "Wallet.h"
|
#include "Wallet.h"
|
||||||
|
|
||||||
|
#include <PathUtils.h>
|
||||||
|
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
|
||||||
|
static const char* KEY_FILE = "hifikey";
|
||||||
|
|
||||||
|
void initialize() {
|
||||||
|
static bool initialized = false;
|
||||||
|
if (!initialized) {
|
||||||
|
SSL_load_error_strings();
|
||||||
|
SSL_library_init();
|
||||||
|
OpenSSL_add_all_algorithms();
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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<int>(strlen(pwd));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
QPair<QByteArray*, QByteArray*> generateRSAKeypair() {
|
||||||
|
|
||||||
|
RSA* keyPair = RSA_new();
|
||||||
|
BIGNUM* exponent = BN_new();
|
||||||
|
QPair<QByteArray*, QByteArray*> retval;
|
||||||
|
|
||||||
|
const unsigned long RSA_KEY_EXPONENT = 65537;
|
||||||
|
BN_set_word(exponent, RSA_KEY_EXPONENT);
|
||||||
|
|
||||||
|
// seed the random number generator before we call RSA_generate_key_ex
|
||||||
|
srand(time(NULL));
|
||||||
|
|
||||||
|
const int RSA_KEY_BITS = 2048;
|
||||||
|
|
||||||
|
if (!RSA_generate_key_ex(keyPair, RSA_KEY_BITS, exponent, NULL)) {
|
||||||
|
qCDebug(commerce) << "Error generating 2048-bit RSA Keypair -" << ERR_get_error();
|
||||||
|
|
||||||
|
// we're going to bust out of here but first we cleanup the BIGNUM
|
||||||
|
BN_free(exponent);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't need the BIGNUM anymore so clean that up
|
||||||
|
BN_free(exponent);
|
||||||
|
|
||||||
|
// grab the public key and private key from the file
|
||||||
|
unsigned char* publicKeyDER = NULL;
|
||||||
|
int publicKeyLength = i2d_RSAPublicKey(keyPair, &publicKeyDER);
|
||||||
|
|
||||||
|
unsigned char* privateKeyDER = NULL;
|
||||||
|
int privateKeyLength = i2d_RSAPrivateKey(keyPair, &privateKeyDER);
|
||||||
|
|
||||||
|
if (publicKeyLength <= 0 || privateKeyLength <= 0) {
|
||||||
|
qCDebug(commerce) << "Error getting DER public or private key from RSA struct -" << ERR_get_error();
|
||||||
|
|
||||||
|
|
||||||
|
// cleanup the RSA struct
|
||||||
|
RSA_free(keyPair);
|
||||||
|
|
||||||
|
// cleanup the public and private key DER data, if required
|
||||||
|
if (publicKeyLength > 0) {
|
||||||
|
OPENSSL_free(publicKeyDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (privateKeyLength > 0) {
|
||||||
|
OPENSSL_free(privateKeyDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// now lets persist them to files
|
||||||
|
// TODO: figure out a scheme for multiple keys, etc...
|
||||||
|
FILE* fp;
|
||||||
|
if ((fp = fopen(keyFilePath().toStdString().c_str(), "wt"))) {
|
||||||
|
if (!PEM_write_RSAPublicKey(fp, keyPair)) {
|
||||||
|
fclose(fp);
|
||||||
|
qCDebug(commerce) << "failed to write public key";
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PEM_write_RSAPrivateKey(fp, keyPair, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
|
||||||
|
fclose(fp);
|
||||||
|
qCDebug(commerce) << "failed to write private key";
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
RSA_free(keyPair);
|
||||||
|
|
||||||
|
// prepare the return values
|
||||||
|
retval.first = new QByteArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength ),
|
||||||
|
retval.second = new QByteArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength );
|
||||||
|
|
||||||
|
// cleanup the publicKeyDER and publicKeyDER data
|
||||||
|
OPENSSL_free(publicKeyDER);
|
||||||
|
OPENSSL_free(privateKeyDER);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
// END copied code (which will soon change)
|
||||||
|
|
||||||
|
// the public key can just go into a byte array
|
||||||
|
QByteArray readPublicKey(const char* filename) {
|
||||||
|
FILE* fp;
|
||||||
|
RSA* key = NULL;
|
||||||
|
if ((fp = fopen(filename, "r"))) {
|
||||||
|
// file opened successfully
|
||||||
|
qCDebug(commerce) << "opened key file" << filename;
|
||||||
|
if ((key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL))) {
|
||||||
|
// file read successfully
|
||||||
|
unsigned char* publicKeyDER = NULL;
|
||||||
|
int publicKeyLength = i2d_RSAPublicKey(key, &publicKeyDER);
|
||||||
|
// TODO: check for 0 length?
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
RSA_free(key);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
qCDebug(commerce) << "parsed public key file successfully";
|
||||||
|
|
||||||
|
QByteArray retval((char*)publicKeyDER, publicKeyLength);
|
||||||
|
OPENSSL_free(publicKeyDER);
|
||||||
|
return retval;
|
||||||
|
} else {
|
||||||
|
qCDebug(commerce) << "couldn't parse" << filename;
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
} else {
|
||||||
|
qCDebug(commerce) << "couldn't open" << filename;
|
||||||
|
}
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// the private key should be read/copied into heap memory. For now, we need the RSA struct
|
||||||
|
// so I'll return that. Note we need to RSA_free(key) later!!!
|
||||||
|
RSA* readPrivateKey(const char* filename) {
|
||||||
|
FILE* fp;
|
||||||
|
RSA* key = NULL;
|
||||||
|
if ((fp = fopen(filename, "r"))) {
|
||||||
|
// file opened successfully
|
||||||
|
qCDebug(commerce) << "opened key file" << filename;
|
||||||
|
if ((key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL))) {
|
||||||
|
// cleanup
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
qCDebug(commerce) << "parsed private key file successfully";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
qCDebug(commerce) << "couldn't parse" << filename;
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
} else {
|
||||||
|
qCDebug(commerce) << "couldn't open" << filename;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
bool Wallet::createIfNeeded() {
|
bool Wallet::createIfNeeded() {
|
||||||
// FIXME: persist in file between sessions.
|
|
||||||
if (_publicKeys.count() > 0) return false;
|
if (_publicKeys.count() > 0) return false;
|
||||||
|
|
||||||
|
// FIXME: initialize OpenSSL elsewhere soon
|
||||||
|
initialize();
|
||||||
|
|
||||||
|
// try to read existing keys if they exist...
|
||||||
|
auto publicKey = readPublicKey(keyFilePath().toStdString().c_str());
|
||||||
|
if (publicKey.size() > 0) {
|
||||||
|
if (auto key = readPrivateKey(keyFilePath().toStdString().c_str()) ) {
|
||||||
|
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(QUrl::toPercentEncoding(publicKey.toBase64()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
qCInfo(commerce) << "Creating wallet.";
|
qCInfo(commerce) << "Creating wallet.";
|
||||||
return generateKeyPair();
|
return generateKeyPair();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Wallet::generateKeyPair() {
|
bool Wallet::generateKeyPair() {
|
||||||
// FIXME: need private key, too, and persist in file.
|
|
||||||
qCInfo(commerce) << "Generating keypair.";
|
qCInfo(commerce) << "Generating keypair.";
|
||||||
QString key = QUuid::createUuid().toString();
|
auto keyPair = generateRSAKeypair();
|
||||||
|
|
||||||
|
_publicKeys.push_back(QUrl::toPercentEncoding(keyPair.first->toBase64()));
|
||||||
|
qCDebug(commerce) << "public key:" << _publicKeys.last();
|
||||||
|
|
||||||
_publicKeys.push_back(key); // Keep in memory for synchronous speed.
|
|
||||||
// It's arguable whether we want to change the receiveAt every time, but:
|
// It's arguable whether we want to change the receiveAt every time, but:
|
||||||
// 1. It's certainly needed the first time, when createIfNeeded answers true.
|
// 1. It's certainly needed the first time, when createIfNeeded answers true.
|
||||||
// 2. It is maximally private, and we can step back from that later if desired.
|
// 2. It is maximally private, and we can step back from that later if desired.
|
||||||
// 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
|
// 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
|
||||||
auto ledger = DependencyManager::get<Ledger>();
|
auto ledger = DependencyManager::get<Ledger>();
|
||||||
return ledger->receiveAt(key);
|
return ledger->receiveAt(_publicKeys.last());
|
||||||
}
|
}
|
||||||
QStringList Wallet::listPublicKeys() {
|
QStringList Wallet::listPublicKeys() {
|
||||||
qCInfo(commerce) << "Enumerating public keys.";
|
qCInfo(commerce) << "Enumerating public keys.";
|
||||||
|
@ -40,7 +233,44 @@ QStringList Wallet::listPublicKeys() {
|
||||||
return _publicKeys;
|
return _publicKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Wallet::signWithKey(const QString& text, const QString& key) {
|
// for now a copy of how we sign in libraries/networking/src/DataServerAccountInfo -
|
||||||
|
// we sha256 the text, read the private key from disk (for now!), and return the signed
|
||||||
|
// sha256. Note later with multiple keys, we may need the key parameter (or something
|
||||||
|
// similar) so I left it alone for now. Also this will probably change when we move
|
||||||
|
// away from RSA keys anyways. Note that since this returns a QString, we better avoid
|
||||||
|
// the horror of code pages and so on (changing the bytes) by just returning a base64
|
||||||
|
// encoded string representing the signature (suitable for http, etc...)
|
||||||
|
QString Wallet::signWithKey(const QByteArray& text, const QString& key) {
|
||||||
qCInfo(commerce) << "Signing text.";
|
qCInfo(commerce) << "Signing text.";
|
||||||
return "fixme signed";
|
RSA* rsaPrivateKey = NULL;
|
||||||
}
|
if ((rsaPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str()))) {
|
||||||
|
QByteArray signature(RSA_size(rsaPrivateKey), 0);
|
||||||
|
unsigned int signatureBytes = 0;
|
||||||
|
|
||||||
|
QByteArray hashedPlaintext = QCryptographicHash::hash(text, QCryptographicHash::Sha256);
|
||||||
|
|
||||||
|
int encryptReturn = RSA_sign(NID_sha256,
|
||||||
|
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
|
||||||
|
hashedPlaintext.size(),
|
||||||
|
reinterpret_cast<unsigned char*>(signature.data()),
|
||||||
|
&signatureBytes,
|
||||||
|
rsaPrivateKey);
|
||||||
|
|
||||||
|
// free the private key RSA struct now that we are done with it
|
||||||
|
RSA_free(rsaPrivateKey);
|
||||||
|
|
||||||
|
if (encryptReturn != -1) {
|
||||||
|
return QUrl::toPercentEncoding(signature.toBase64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Wallet::chooseSecurityImage(uint imageID) {
|
||||||
|
_chosenSecurityImage = (SecurityImage)imageID;
|
||||||
|
emit securityImageResult(imageID);
|
||||||
|
}
|
||||||
|
void Wallet::getSecurityImage() {
|
||||||
|
emit securityImageResult(_chosenSecurityImage);
|
||||||
|
}
|
||||||
|
|
|
@ -25,10 +25,30 @@ public:
|
||||||
bool createIfNeeded();
|
bool createIfNeeded();
|
||||||
bool generateKeyPair();
|
bool generateKeyPair();
|
||||||
QStringList listPublicKeys();
|
QStringList listPublicKeys();
|
||||||
QString signWithKey(const QString& text, const QString& key);
|
QString signWithKey(const QByteArray& text, const QString& key);
|
||||||
|
void chooseSecurityImage(uint imageID);
|
||||||
|
void getSecurityImage();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void securityImageResult(uint imageID);
|
||||||
|
|
||||||
|
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,
|
||||||
|
Car,
|
||||||
|
Dog,
|
||||||
|
Stars,
|
||||||
|
Plane,
|
||||||
|
Gingerbread
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QStringList _publicKeys{};
|
QStringList _publicKeys{};
|
||||||
|
SecurityImage _chosenSecurityImage = SecurityImage::NONE;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Wallet_h
|
#endif // hifi_Wallet_h
|
||||||
|
|
|
@ -168,6 +168,8 @@ void Rig::destroyAnimGraph() {
|
||||||
void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) {
|
void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) {
|
||||||
_geometryOffset = AnimPose(geometry.offset);
|
_geometryOffset = AnimPose(geometry.offset);
|
||||||
_invGeometryOffset = _geometryOffset.inverse();
|
_invGeometryOffset = _geometryOffset.inverse();
|
||||||
|
_geometryToRigTransform = modelOffset * geometry.offset;
|
||||||
|
_rigToGeometryTransform = glm::inverse(_geometryToRigTransform);
|
||||||
setModelOffset(modelOffset);
|
setModelOffset(modelOffset);
|
||||||
|
|
||||||
_animSkeleton = std::make_shared<AnimSkeleton>(geometry);
|
_animSkeleton = std::make_shared<AnimSkeleton>(geometry);
|
||||||
|
@ -1761,59 +1763,11 @@ void Rig::computeAvatarBoundingCapsule(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimInverseKinematics ikNode("boundingShape");
|
|
||||||
ikNode.setSkeleton(_animSkeleton);
|
|
||||||
|
|
||||||
ikNode.setTargetVars("LeftHand", "leftHandPosition", "leftHandRotation",
|
|
||||||
"leftHandType", "leftHandWeight", 1.0f, {},
|
|
||||||
QString(), QString(), QString());
|
|
||||||
ikNode.setTargetVars("RightHand", "rightHandPosition", "rightHandRotation",
|
|
||||||
"rightHandType", "rightHandWeight", 1.0f, {},
|
|
||||||
QString(), QString(), QString());
|
|
||||||
ikNode.setTargetVars("LeftFoot", "leftFootPosition", "leftFootRotation",
|
|
||||||
"leftFootType", "leftFootWeight", 1.0f, {},
|
|
||||||
QString(), QString(), QString());
|
|
||||||
ikNode.setTargetVars("RightFoot", "rightFootPosition", "rightFootRotation",
|
|
||||||
"rightFootType", "rightFootWeight", 1.0f, {},
|
|
||||||
QString(), QString(), QString());
|
|
||||||
glm::vec3 hipsPosition(0.0f);
|
glm::vec3 hipsPosition(0.0f);
|
||||||
int hipsIndex = indexOfJoint("Hips");
|
int hipsIndex = indexOfJoint("Hips");
|
||||||
if (hipsIndex >= 0) {
|
if (hipsIndex >= 0) {
|
||||||
hipsPosition = transformPoint(_geometryToRigTransform, _animSkeleton->getAbsoluteDefaultPose(hipsIndex).trans());
|
hipsPosition = transformPoint(_geometryToRigTransform, _animSkeleton->getAbsoluteDefaultPose(hipsIndex).trans());
|
||||||
}
|
}
|
||||||
AnimVariantMap animVars;
|
|
||||||
animVars.setRigToGeometryTransform(_rigToGeometryTransform);
|
|
||||||
glm::quat handRotation = glm::angleAxis(PI, Vectors::UNIT_X);
|
|
||||||
animVars.set("leftHandPosition", hipsPosition);
|
|
||||||
animVars.set("leftHandRotation", handRotation);
|
|
||||||
animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition);
|
|
||||||
animVars.set("rightHandPosition", hipsPosition);
|
|
||||||
animVars.set("rightHandRotation", handRotation);
|
|
||||||
animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition);
|
|
||||||
|
|
||||||
int rightFootIndex = indexOfJoint("RightFoot");
|
|
||||||
int leftFootIndex = indexOfJoint("LeftFoot");
|
|
||||||
if (rightFootIndex != -1 && leftFootIndex != -1) {
|
|
||||||
glm::vec3 geomFootPosition = glm::vec3(0.0f, _animSkeleton->getAbsoluteDefaultPose(rightFootIndex).trans().y, 0.0f);
|
|
||||||
glm::vec3 footPosition = transformPoint(_geometryToRigTransform, geomFootPosition);
|
|
||||||
glm::quat footRotation = glm::angleAxis(0.5f * PI, Vectors::UNIT_X);
|
|
||||||
animVars.set("leftFootPosition", footPosition);
|
|
||||||
animVars.set("leftFootRotation", footRotation);
|
|
||||||
animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition);
|
|
||||||
animVars.set("rightFootPosition", footPosition);
|
|
||||||
animVars.set("rightFootRotation", footRotation);
|
|
||||||
animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
// call overlay twice: once to verify AnimPoseVec joints and again to do the IK
|
|
||||||
AnimNode::Triggers triggersOut;
|
|
||||||
AnimContext context(false, false, false, _geometryToRigTransform, _rigToGeometryTransform);
|
|
||||||
float dt = 1.0f; // the value of this does not matter
|
|
||||||
ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
|
|
||||||
AnimPoseVec finalPoses = ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
|
|
||||||
|
|
||||||
// convert relative poses to absolute
|
|
||||||
_animSkeleton->convertRelativePosesToAbsolute(finalPoses);
|
|
||||||
|
|
||||||
// compute bounding box that encloses all points
|
// compute bounding box that encloses all points
|
||||||
Extents totalExtents;
|
Extents totalExtents;
|
||||||
|
@ -1824,15 +1778,15 @@ void Rig::computeAvatarBoundingCapsule(
|
||||||
// even if they do not have legs (default robot)
|
// even if they do not have legs (default robot)
|
||||||
totalExtents.addPoint(glm::vec3(0.0f));
|
totalExtents.addPoint(glm::vec3(0.0f));
|
||||||
|
|
||||||
// HACK to reduce the radius of the bounding capsule to be tight with the torso, we only consider joints
|
// To reduce the radius of the bounding capsule to be tight with the torso, we only consider joints
|
||||||
// from the head to the hips when computing the rest of the bounding capsule.
|
// from the head to the hips when computing the rest of the bounding capsule.
|
||||||
int index = indexOfJoint("Head");
|
int index = indexOfJoint("Head");
|
||||||
while (index != -1) {
|
while (index != -1) {
|
||||||
const FBXJointShapeInfo& shapeInfo = geometry.joints.at(index).shapeInfo;
|
const FBXJointShapeInfo& shapeInfo = geometry.joints.at(index).shapeInfo;
|
||||||
AnimPose pose = finalPoses[index];
|
AnimPose pose = _animSkeleton->getAbsoluteDefaultPose(index);
|
||||||
if (shapeInfo.points.size() > 0) {
|
if (shapeInfo.points.size() > 0) {
|
||||||
for (size_t j = 0; j < shapeInfo.points.size(); ++j) {
|
for (auto& point : shapeInfo.points) {
|
||||||
totalExtents.addPoint((pose * shapeInfo.points[j]));
|
totalExtents.addPoint((pose * point));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
index = _animSkeleton->getParentIndex(index);
|
index = _animSkeleton->getParentIndex(index);
|
||||||
|
@ -1846,7 +1800,6 @@ void Rig::computeAvatarBoundingCapsule(
|
||||||
radiusOut = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
|
radiusOut = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
|
||||||
heightOut = diagonal.y - 2.0f * radiusOut;
|
heightOut = diagonal.y - 2.0f * radiusOut;
|
||||||
|
|
||||||
glm::vec3 rootPosition = finalPoses[geometry.rootJointIndex].trans();
|
glm::vec3 capsuleCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum)));
|
||||||
glm::vec3 rigCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum)));
|
localOffsetOut = capsuleCenter - hipsPosition;
|
||||||
localOffsetOut = rigCenter - transformPoint(_geometryToRigTransform, rootPosition);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
|
var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
|
||||||
var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Checkout.qml";
|
var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Checkout.qml";
|
||||||
var MARKETPLACE_INVENTORY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Inventory.qml";
|
var MARKETPLACE_INVENTORY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Inventory.qml";
|
||||||
|
var MARKETPLACE_SECURITY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/SecurityImageSelection.qml";
|
||||||
|
|
||||||
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
|
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
|
||||||
// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
|
// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
|
||||||
|
@ -87,7 +88,7 @@
|
||||||
|
|
||||||
function onScreenChanged(type, url) {
|
function onScreenChanged(type, url) {
|
||||||
onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL;
|
onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL;
|
||||||
wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_INVENTORY_QML_PATH));
|
wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_INVENTORY_QML_PATH || url === MARKETPLACE_SECURITY_QML_PATH));
|
||||||
// for toolbar mode: change button to active when window is first openend, false otherwise.
|
// for toolbar mode: change button to active when window is first openend, false otherwise.
|
||||||
marketplaceButton.editProperties({ isActive: onMarketplaceScreen });
|
marketplaceButton.editProperties({ isActive: onMarketplaceScreen });
|
||||||
if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) {
|
if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) {
|
||||||
|
@ -217,8 +218,11 @@
|
||||||
case 'inventory_backClicked':
|
case 'inventory_backClicked':
|
||||||
tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL);
|
tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL);
|
||||||
break;
|
break;
|
||||||
|
case 'securityImageSelection_cancelClicked':
|
||||||
|
tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
print('Unrecognized message from Checkout.qml or Inventory.qml: ' + JSON.stringify(message));
|
print('Unrecognized message from Checkout.qml, Inventory.qml, or SecurityImageSelection.qml: ' + JSON.stringify(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue