Merge pull request #11815 from zfox23/commerce_QmlWhitelist

QML Whitelisting + Commerce Integration
This commit is contained in:
Zach Fox 2017-12-19 18:07:14 -08:00 committed by GitHub
commit 297a3df63c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
73 changed files with 907 additions and 910 deletions

View file

@ -12,8 +12,10 @@ function(JOIN VALUES GLUE OUTPUT)
endfunction()
set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc)
generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg)
if (NOT DEV_BUILD)
set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc)
generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg)
endif()
# set a default root dir for each of our optional externals if it was not passed
set(OPTIONAL_EXTERNALS "LeapMotion")
@ -80,7 +82,9 @@ qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}")
# add them to the interface source files
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}")
if (NOT DEV_BUILD)
list(APPEND INTERFACE_SRCS ${INTERFACE_QML_QRC})
endif()
if (UNIX)
install(

View file

@ -0,0 +1,18 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
Rectangle {
width: 100
height: 100
color: "white"
Rectangle {
width: 10
height: 10
color: "red"
}
Label {
text: OverlayWindowTestString
anchors.centerIn: parent
}
}

View file

@ -22,7 +22,6 @@ Windows.Window {
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false
property var source;
property var component;
property var dynamicContent;
// Keyboard control properties in case needed by QML content.
@ -35,28 +34,9 @@ Windows.Window {
dynamicContent.destroy();
dynamicContent = null;
}
component = Qt.createComponent(source);
console.log("Created component " + component + " from source " + source);
}
onComponentChanged: {
console.log("Component changed to " + component)
populate();
}
function populate() {
console.log("Populate called: dynamicContent " + dynamicContent + " component " + component);
if (!dynamicContent && component) {
if (component.status == Component.Error) {
console.log("Error loading component:", component.errorString());
} else if (component.status == Component.Ready) {
console.log("Building dynamic content");
dynamicContent = component.createObject(contentHolder);
} else {
console.log("Component not yet ready, connecting to status change");
component.statusChanged.connect(populate);
}
}
QmlSurface.load(source, contentHolder, function(newObject) {
dynamicContent = newObject;
});
}
// Handle message traffic from the script that launched us to the loaded QML

View file

@ -29,12 +29,12 @@ Original.Button {
onHoveredChanged: {
if (hovered) {
tabletInterface.playSound(TabletEnums.ButtonHover);
Tablet.playSound(TabletEnums.ButtonHover);
}
}
onClicked: {
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
}
style: ButtonStyle {

View file

@ -31,12 +31,12 @@ Original.CheckBox {
activeFocusOnPress: true
onClicked: {
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
}
// TODO: doesnt works for QQC1. check with QQC2
// onHovered: {
// tabletInterface.playSound(TabletEnums.ButtonHover);
// Tablet.playSound(TabletEnums.ButtonHover);
// }
style: CheckBoxStyle {

View file

@ -36,12 +36,12 @@ CheckBox {
hoverEnabled: true
onClicked: {
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
}
onHoveredChanged: {
if (hovered) {
tabletInterface.playSound(TabletEnums.ButtonHover);
Tablet.playSound(TabletEnums.ButtonHover);
}
}

View file

@ -27,12 +27,12 @@ Original.Button {
onHoveredChanged: {
if (hovered) {
tabletInterface.playSound(TabletEnums.ButtonHover);
Tablet.playSound(TabletEnums.ButtonHover);
}
}
onClicked: {
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
}
style: ButtonStyle {

View file

@ -41,13 +41,13 @@ Item {
onContainsMouseChanged: {
if (containsMouse) {
tabletInterface.playSound(TabletEnums.ButtonHover);
Tablet.playSound(TabletEnums.ButtonHover);
}
}
onClicked: {
mouse.accepted = true;
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
webEntity.synthesizeKeyPress(glyph);
webEntity.synthesizeKeyPress(glyph, mirrorText);

View file

@ -30,12 +30,12 @@ Original.RadioButton {
readonly property int checkRadius: 2
onClicked: {
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
}
// TODO: doesnt works for QQC1. check with QQC2
// onHovered: {
// tabletInterface.playSound(TabletEnums.ButtonHover);
// Tablet.playSound(TabletEnums.ButtonHover);
// }
style: RadioButtonStyle {

View file

@ -49,7 +49,7 @@ Item {
}
if (WebEngineView.LoadFailedStatus === loadRequest.status) {
console.log(" Tablet WebEngineView failed to load url: " + loadRequest.url.toString());
console.log("Tablet WebEngineView failed to load url: " + loadRequest.url.toString());
}
if (WebEngineView.LoadSucceededStatus === loadRequest.status) {

View file

@ -25,13 +25,13 @@ Preference {
id: button
onHoveredChanged: {
if (hovered) {
tabletInterface.playSound(TabletEnums.ButtonHover);
Tablet.playSound(TabletEnums.ButtonHover);
}
}
onClicked: {
preference.trigger();
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
}
width: 180
anchors.bottom: parent.bottom

View file

@ -41,12 +41,12 @@ Preference {
id: checkBox
onHoveredChanged: {
if (hovered) {
tabletInterface.playSound(TabletEnums.ButtonHover);
Tablet.playSound(TabletEnums.ButtonHover);
}
}
onClicked: {
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
}
anchors {

View file

@ -246,12 +246,12 @@ Item {
anchors.fill: parent;
acceptedButtons: Qt.LeftButton;
onClicked: {
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
goFunction("hifi://" + hifiUrl);
}
hoverEnabled: true;
onEntered: {
tabletInterface.playSound(TabletEnums.ButtonHover);
Tablet.playSound(TabletEnums.ButtonHover);
hoverThunk();
}
onExited: unhoverThunk();
@ -269,7 +269,7 @@ Item {
}
}
function go() {
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId));
}
MouseArea {

View file

@ -45,11 +45,13 @@ OriginalDesktop.Desktop {
Toolbar {
id: sysToolbar;
objectName: "com.highfidelity.interface.toolbar.system";
property var tablet: Tablet.getTablet("com.highfidelity.interface.tablet.system");
anchors.horizontalCenter: settings.constrainToolbarToCenterX ? desktop.horizontalCenter : undefined;
// Literal 50 is overwritten by settings from previous session, and sysToolbar.x comes from settings when not constrained.
x: sysToolbar.x
y: 50
shown: true
buttonModel: tablet.buttons;
shown: tablet.toolbarMode;
}
Settings {

View file

@ -61,12 +61,12 @@ Rectangle {
scrollGestureEnabled: false;
onClicked: {
Audio.muted = !Audio.muted;
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
}
drag.target: dragTarget;
onContainsMouseChanged: {
if (containsMouse) {
tabletInterface.playSound(TabletEnums.ButtonHover);
Tablet.playSound(TabletEnums.ButtonHover);
}
}
}

View file

@ -43,8 +43,8 @@ Rectangle {
property bool isWearable;
// Style
color: hifi.colors.white;
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
onWalletStatusResult: {
if (walletStatus === 0) {
@ -72,7 +72,7 @@ Rectangle {
if (!isLoggedIn && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
} else {
commerce.getWalletStatus();
Commerce.getWalletStatus();
}
}
@ -115,7 +115,7 @@ Rectangle {
}
onItemIdChanged: {
commerce.inventory();
Commerce.inventory();
itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg";
}
@ -124,7 +124,7 @@ Rectangle {
}
onItemPriceChanged: {
commerce.balance();
Commerce.balance();
}
Timer {
@ -203,7 +203,7 @@ Rectangle {
Component.onCompleted: {
purchasesReceived = false;
balanceReceived = false;
commerce.getWalletStatus();
Commerce.getWalletStatus();
}
}
@ -224,7 +224,7 @@ Rectangle {
Connections {
target: GlobalServices
onMyUsernameChanged: {
commerce.getLoginStatus();
Commerce.getLoginStatus();
}
}
@ -474,9 +474,9 @@ Rectangle {
if (itemIsJson) {
buyButton.enabled = false;
if (!root.shouldBuyWithControlledFailure) {
commerce.buy(itemId, itemPrice);
Commerce.buy(itemId, itemPrice);
} else {
commerce.buy(itemId, itemPrice, true);
Commerce.buy(itemId, itemPrice, true);
}
} else {
if (urlHandler.canHandleUrl(itemHref)) {
@ -940,8 +940,8 @@ Rectangle {
}
root.balanceReceived = false;
root.purchasesReceived = false;
commerce.inventory();
commerce.balance();
Commerce.inventory();
Commerce.balance();
}
//

View file

@ -31,14 +31,14 @@ Item {
height: mainContainer.height + additionalDropdownHeight;
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
onWalletStatusResult: {
if (walletStatus === 0) {
sendToParent({method: "needsLogIn"});
} else if (walletStatus === 3) {
commerce.getSecurityImage();
Commerce.getSecurityImage();
} else if (walletStatus > 3) {
console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus);
}
@ -48,7 +48,7 @@ Item {
if (!isLoggedIn) {
sendToParent({method: "needsLogIn"});
} else {
commerce.getWalletStatus();
Commerce.getWalletStatus();
}
}
@ -61,13 +61,13 @@ Item {
}
Component.onCompleted: {
commerce.getWalletStatus();
Commerce.getWalletStatus();
}
Connections {
target: GlobalServices
onMyUsernameChanged: {
commerce.getLoginStatus();
Commerce.getLoginStatus();
}
}

View file

@ -36,8 +36,8 @@ Rectangle {
property bool isCertificateInvalid: false;
// Style
color: hifi.colors.faintGray;
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
onCertificateInfoResult: {
if (result.status !== 'success') {
@ -109,7 +109,7 @@ Rectangle {
onCertificateIdChanged: {
if (certificateId !== "") {
commerce.certificateInfo(certificateId);
Commerce.certificateInfo(certificateId);
}
}

View file

@ -38,8 +38,8 @@ Rectangle {
property bool isDebuggingFirstUseTutorial: false;
// Style
color: hifi.colors.white;
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
onWalletStatusResult: {
if (walletStatus === 0) {
@ -61,7 +61,7 @@ Rectangle {
root.activeView = "firstUseTutorial";
} else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") {
root.activeView = "purchasesMain";
commerce.inventory();
Commerce.inventory();
}
} else {
console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus);
@ -72,7 +72,7 @@ Rectangle {
if (!isLoggedIn && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
} else {
commerce.getWalletStatus();
Commerce.getWalletStatus();
}
}
@ -198,7 +198,7 @@ Rectangle {
Component.onCompleted: {
securityImageResultReceived = false;
purchasesReceived = false;
commerce.getWalletStatus();
Commerce.getWalletStatus();
}
}
@ -219,7 +219,7 @@ Rectangle {
Connections {
target: GlobalServices
onMyUsernameChanged: {
commerce.getLoginStatus();
Commerce.getLoginStatus();
}
}
@ -234,7 +234,7 @@ Rectangle {
onSendSignalToParent: {
if (msg.method === "authSuccess") {
root.activeView = "initialize";
commerce.getWalletStatus();
Commerce.getWalletStatus();
} else {
sendToScript(msg);
}
@ -255,7 +255,7 @@ Rectangle {
case 'tutorial_finished':
Settings.setValue("isFirstUseOfPurchases", false);
root.activeView = "purchasesMain";
commerce.inventory();
Commerce.inventory();
break;
}
}
@ -595,7 +595,7 @@ Rectangle {
if (root.activeView === "purchasesMain" && !root.pendingInventoryReply) {
console.log("Refreshing Purchases...");
root.pendingInventoryReply = true;
commerce.inventory();
Commerce.inventory();
}
}
}

View file

@ -27,8 +27,8 @@ Item {
property string keyFilePath;
property bool showDebugButtons: true;
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
onKeyFilePathIfExistsResult: {
root.keyFilePath = path;
@ -37,7 +37,7 @@ Item {
onVisibleChanged: {
if (visible) {
commerce.getKeyFilePathIfExists();
Commerce.getKeyFilePathIfExists();
}
}
@ -55,6 +55,37 @@ Item {
// Style
color: hifi.colors.blueHighlight;
}
HifiControlsUit.Button {
id: clearCachedPassphraseButton;
visible: root.showDebugButtons;
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.top: parent.top;
anchors.left: helpTitleText.right;
anchors.leftMargin: 20;
height: 40;
width: 150;
text: "DBG: Clear Pass";
onClicked: {
Commerce.setPassphrase("");
sendSignalToWallet({method: 'passphraseReset'});
}
}
HifiControlsUit.Button {
id: resetButton;
visible: root.showDebugButtons;
color: hifi.buttons.red;
colorScheme: hifi.colorSchemes.dark;
anchors.top: clearCachedPassphraseButton.top;
anchors.left: clearCachedPassphraseButton.right;
height: 40;
width: 150;
text: "DBG: RST Wallet";
onClicked: {
Commerce.reset();
sendSignalToWallet({method: 'walletReset'});
}
}
ListModel {
id: helpModel;

View file

@ -30,8 +30,8 @@ Item {
source: "images/wallet-bg.jpg";
}
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
}
//

View file

@ -36,8 +36,8 @@ Item {
source: "images/wallet-bg.jpg";
}
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
onSecurityImageResult: {
titleBarSecurityImage.source = "";
@ -210,7 +210,7 @@ Item {
onAccepted: {
submitPassphraseInputButton.enabled = false;
commerce.setPassphrase(passphraseField.text);
Commerce.setPassphrase(passphraseField.text);
}
}
@ -250,7 +250,7 @@ Item {
source: "image://security/securityImage";
cache: false;
onVisibleChanged: {
commerce.getSecurityImage();
Commerce.getSecurityImage();
}
}
Item {
@ -318,7 +318,7 @@ Item {
text: "Submit"
onClicked: {
submitPassphraseInputButton.enabled = false;
commerce.setPassphrase(passphraseField.text);
Commerce.setPassphrase(passphraseField.text);
}
}

View file

@ -36,8 +36,8 @@ Item {
propagateComposedEvents: false;
}
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
onSecurityImageResult: {
passphrasePageSecurityImage.source = "";
passphrasePageSecurityImage.source = "image://security/securityImage";
@ -160,7 +160,7 @@ Item {
source: "image://security/securityImage";
cache: false;
onVisibleChanged: {
commerce.getSecurityImage();
Commerce.getSecurityImage();
}
}
Item {
@ -283,7 +283,7 @@ Item {
passphraseFieldAgain.error = false;
currentPassphraseField.error = false;
setErrorText("");
commerce.changePassphrase(currentPassphraseField.text, passphraseField.text);
Commerce.changePassphrase(currentPassphraseField.text, passphraseField.text);
return true;
}
}

View file

@ -27,8 +27,8 @@ Item {
id: root;
property string keyFilePath;
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
onKeyFilePathIfExistsResult: {
root.keyFilePath = path;
@ -234,7 +234,7 @@ Item {
onVisibleChanged: {
if (visible) {
commerce.getKeyFilePathIfExists();
Commerce.getKeyFilePathIfExists();
}
}

View file

@ -26,8 +26,8 @@ Item {
id: root;
property bool justSubmitted: false;
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
onSecurityImageResult: {
securityImageChangePageSecurityImage.source = "";

View file

@ -25,8 +25,8 @@ Item {
id: root;
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
}
// "Unavailable"

View file

@ -31,13 +31,15 @@ Rectangle {
property bool keyboardRaised: false;
property bool isPassword: false;
anchors.fill: (typeof parent === undefined) ? undefined : parent;
Image {
anchors.fill: parent;
source: "images/wallet-bg.jpg";
}
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
onWalletStatusResult: {
if (walletStatus === 0) {
@ -47,7 +49,7 @@ Rectangle {
} else if (walletStatus === 1) {
if (root.activeView !== "walletSetup") {
root.activeView = "walletSetup";
commerce.resetLocalWalletOnly();
Commerce.resetLocalWalletOnly();
var timestamp = new Date();
walletSetup.startingTimestamp = timestamp;
walletSetup.setupAttemptID = generateUUID();
@ -60,8 +62,10 @@ Rectangle {
UserActivityLogger.commercePassphraseEntry("wallet app");
}
} else if (walletStatus === 3) {
root.activeView = "walletHome";
commerce.getSecurityImage();
if (root.activeView !== "walletSetup") {
root.activeView = "walletHome";
Commerce.getSecurityImage();
}
} else {
console.log("ERROR in Wallet.qml: Unknown wallet status: " + walletStatus);
}
@ -71,7 +75,7 @@ Rectangle {
if (!isLoggedIn && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
} else if (isLoggedIn) {
commerce.getWalletStatus();
Commerce.getWalletStatus();
}
}
@ -177,7 +181,7 @@ Rectangle {
if (msg.method === 'walletSetup_finished') {
if (msg.referrer === '' || msg.referrer === 'marketplace cta') {
root.activeView = "initialize";
commerce.getWalletStatus();
Commerce.getWalletStatus();
} else if (msg.referrer === 'purchases') {
sendToScript({method: 'goToPurchases'});
} else {
@ -206,17 +210,19 @@ Rectangle {
Connections {
onSendSignalToWallet: {
if (msg.method === 'walletSetup_raiseKeyboard') {
root.keyboardRaised = true;
root.isPassword = msg.isPasswordField;
} else if (msg.method === 'walletSetup_lowerKeyboard') {
root.keyboardRaised = false;
} else if (msg.method === 'walletSecurity_changePassphraseCancelled') {
root.activeView = "security";
} else if (msg.method === 'walletSecurity_changePassphraseSuccess') {
root.activeView = "security";
} else {
sendToScript(msg);
if (passphraseChange.visible) {
if (msg.method === 'walletSetup_raiseKeyboard') {
root.keyboardRaised = true;
root.isPassword = msg.isPasswordField;
} else if (msg.method === 'walletSetup_lowerKeyboard') {
root.keyboardRaised = false;
} else if (msg.method === 'walletSecurity_changePassphraseCancelled') {
root.activeView = "security";
} else if (msg.method === 'walletSecurity_changePassphraseSuccess') {
root.activeView = "security";
} else {
sendToScript(msg);
}
}
}
}
@ -257,7 +263,7 @@ Rectangle {
color: hifi.colors.baseGray;
Component.onCompleted: {
commerce.getWalletStatus();
Commerce.getWalletStatus();
}
}
@ -278,7 +284,7 @@ Rectangle {
Connections {
target: GlobalServices
onMyUsernameChanged: {
commerce.getLoginStatus();
Commerce.getLoginStatus();
}
}
@ -292,7 +298,7 @@ Rectangle {
Connections {
onSendSignalToParent: {
if (msg.method === "authSuccess") {
commerce.getWalletStatus();
Commerce.getWalletStatus();
} else {
sendToScript(msg);
}

View file

@ -28,8 +28,8 @@ Item {
property bool historyReceived: false;
property int pendingCount: 0;
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
onBalanceResult : {
balanceText.text = result.data.balance;
@ -135,8 +135,8 @@ Item {
onVisibleChanged: {
if (visible) {
historyReceived = false;
commerce.balance();
commerce.history();
Commerce.balance();
Commerce.history();
} else {
refreshTimer.stop();
}
@ -165,8 +165,8 @@ Item {
interval: 4000;
onTriggered: {
console.log("Refreshing Wallet Home...");
commerce.balance();
commerce.history();
Commerce.balance();
Commerce.history();
}
}

View file

@ -41,8 +41,8 @@ Item {
source: "images/wallet-bg.jpg";
}
Hifi.QmlCommerce {
id: commerce;
Connections {
target: Commerce;
onSecurityImageResult: {
if (!exists && root.lastPage === "step_2") {
@ -366,7 +366,7 @@ Item {
onClicked: {
root.lastPage = "step_2";
var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex())
commerce.chooseSecurityImage(securityImagePath);
Commerce.chooseSecurityImage(securityImagePath);
root.activeView = "step_3";
passphraseSelection.clearPassphraseFields();
}
@ -449,7 +449,7 @@ Item {
onVisibleChanged: {
if (visible) {
commerce.getWalletAuthenticatedStatus();
Commerce.getWalletAuthenticatedStatus();
}
}
@ -535,7 +535,7 @@ Item {
onClicked: {
if (passphraseSelection.validateAndSubmitPassphrase()) {
root.lastPage = "step_3";
commerce.generateKeyPair();
Commerce.generateKeyPair();
root.activeView = "step_4";
}
}
@ -668,7 +668,7 @@ Item {
onVisibleChanged: {
if (visible) {
commerce.getKeyFilePathIfExists();
Commerce.getKeyFilePathIfExists();
}
}
}

View file

@ -123,11 +123,11 @@ Item {
hoverEnabled: true
enabled: true
onClicked: {
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
newEntityButton.clicked();
}
onEntered: {
tabletInterface.playSound(TabletEnums.ButtonHover);
Tablet.playSound(TabletEnums.ButtonHover);
newEntityButton.state = "hover state";
}
onExited: {

View file

@ -1,261 +0,0 @@
import QtQuick 2.5
import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.3
import "../../styles-uit"
import "../audio" as HifiAudio
Item {
id: tablet
objectName: "tablet"
property int rowIndex: 6 // by default
property int columnIndex: 1 // point to 'go to location'
property int count: (flowMain.children.length - 1)
// used to look up a button by its uuid
function findButtonIndex(uuid) {
if (!uuid) {
return -1;
}
for (var i in flowMain.children) {
var child = flowMain.children[i];
if (child.uuid === uuid) {
return i;
}
}
return -1;
}
function sortButtons() {
var children = [];
for (var i = 0; i < flowMain.children.length; i++) {
children[i] = flowMain.children[i];
}
children.sort(function (a, b) {
if (a.sortOrder === b.sortOrder) {
// subsort by stableOrder, because JS sort is not stable in qml.
return a.stableOrder - b.stableOrder;
} else {
return a.sortOrder - b.sortOrder;
}
});
flowMain.children = children;
}
// called by C++ code when a button should be added to the tablet
function addButtonProxy(properties) {
var component = Qt.createComponent("TabletButton.qml");
var button = component.createObject(flowMain);
// copy all properites to button
var keys = Object.keys(properties).forEach(function (key) {
button[key] = properties[key];
});
// pass a reference to the tabletRoot object to the button.
if (tabletRoot) {
button.tabletRoot = tabletRoot;
} else {
button.tabletRoot = parent.parent;
}
sortButtons();
return button;
}
// called by C++ code when a button should be removed from the tablet
function removeButtonProxy(properties) {
var index = findButtonIndex(properties.uuid);
if (index < 0) {
console.log("Warning: Tablet.qml could not find button with uuid = " + properties.uuid);
} else {
flowMain.children[index].destroy();
}
}
Rectangle {
id: bgTopBar
height: 90
anchors {
top: parent.top
topMargin: 0
left: parent.left
leftMargin: 0
right: parent.right
rightMargin: 0
}
gradient: Gradient {
GradientStop {
position: 0
color: "#2b2b2b"
}
GradientStop {
position: 1
color: "#1e1e1e"
}
}
HifiAudio.MicBar {
anchors {
left: parent.left
leftMargin: 30
verticalCenter: parent.verticalCenter
}
}
Item {
width: 150
height: 50
anchors.right: parent.right
anchors.rightMargin: 30
anchors.verticalCenter: parent.verticalCenter
ColumnLayout {
anchors.fill: parent
RalewaySemiBold {
text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in")
horizontalAlignment: Text.AlignRight
anchors.right: parent.right
font.pixelSize: 20
color: "#afafaf"
}
RalewaySemiBold {
visible: Account.loggedIn
height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0
text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : ""
horizontalAlignment: Text.AlignRight
anchors.right: parent.right
font.pixelSize: 20
color: "#afafaf"
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if (!Account.loggedIn) {
DialogsManager.showLoginDialog()
} else {
Account.logOut()
}
}
}
}
}
Rectangle {
id: bgMain
gradient: Gradient {
GradientStop {
position: 0
color: "#2b2b2b"
}
GradientStop {
position: 1
color: "#0f212e"
}
}
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
anchors.top: bgTopBar.bottom
anchors.topMargin: 0
Flickable {
id: flickable
width: parent.width
height: parent.height
contentWidth: parent.width
contentHeight: flowMain.childrenRect.height + flowMain.anchors.topMargin + flowMain.anchors.bottomMargin + flowMain.spacing
clip: true
Flow {
id: flowMain
spacing: 16
anchors.right: parent.right
anchors.rightMargin: 30
anchors.left: parent.left
anchors.leftMargin: 30
anchors.bottom: parent.bottom
anchors.bottomMargin: 30
anchors.top: parent.top
anchors.topMargin: 30
}
}
}
function setCurrentItemState(state) {
var index = rowIndex + columnIndex;
if (index >= 0 && index <= count ) {
flowMain.children[index].state = state;
}
}
function nextItem() {
setCurrentItemState("base state");
var nextColumnIndex = (columnIndex + 3 + 1) % 3;
var nextIndex = rowIndex + nextColumnIndex;
if(nextIndex <= count) {
columnIndex = nextColumnIndex;
};
setCurrentItemState("hover state");
}
function previousItem() {
setCurrentItemState("base state");
var prevIndex = (columnIndex + 3 - 1) % 3;
if((rowIndex + prevIndex) <= count){
columnIndex = prevIndex;
}
setCurrentItemState("hover state");
}
function upItem() {
setCurrentItemState("base state");
rowIndex = rowIndex - 3;
if (rowIndex < 0 ) {
rowIndex = (count - (count % 3));
var index = rowIndex + columnIndex;
if(index > count) {
rowIndex = rowIndex - 3;
}
}
setCurrentItemState("hover state");
}
function downItem() {
setCurrentItemState("base state");
rowIndex = rowIndex + 3;
var index = rowIndex + columnIndex;
if (index > count ) {
rowIndex = 0;
}
setCurrentItemState("hover state");
}
function selectItem() {
flowMain.children[rowIndex + columnIndex].clicked();
if (tabletRoot) {
tabletRoot.playButtonClickSound();
}
}
Keys.onRightPressed: nextItem();
Keys.onLeftPressed: previousItem();
Keys.onDownPressed: downItem();
Keys.onUpPressed: upItem();
Keys.onReturnPressed: selectItem();
}

View file

@ -123,7 +123,6 @@ Item {
enabled: true
preventStealing: true
onClicked: {
console.log("Tablet Button Clicked!");
if (tabletButton.inDebugMode) {
if (tabletButton.isActive) {
tabletButton.isActive = false;
@ -133,12 +132,12 @@ Item {
}
tabletButton.clicked();
if (tabletRoot) {
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
}
}
onEntered: {
tabletButton.isEntered = true;
tabletInterface.playSound(TabletEnums.ButtonHover);
Tablet.playSound(TabletEnums.ButtonHover);
if (tabletButton.isActive) {
tabletButton.state = "hover active state";

View file

@ -0,0 +1,159 @@
import QtQuick 2.5
import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.3
import "."
import "../../styles-uit"
import "../audio" as HifiAudio
Item {
id: tablet
objectName: "tablet"
property var tabletProxy: Tablet.getTablet("com.highfidelity.interface.tablet.system");
Rectangle {
id: bgTopBar
height: 90
anchors {
top: parent.top
left: parent.left
right: parent.right
}
gradient: Gradient {
GradientStop {
position: 0
color: "#2b2b2b"
}
GradientStop {
position: 1
color: "#1e1e1e"
}
}
HifiAudio.MicBar {
anchors {
left: parent.left
leftMargin: 30
verticalCenter: parent.verticalCenter
}
}
Item {
width: 150
height: 50
anchors.right: parent.right
anchors.rightMargin: 30
anchors.verticalCenter: parent.verticalCenter
ColumnLayout {
anchors.fill: parent
RalewaySemiBold {
text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in")
horizontalAlignment: Text.AlignRight
anchors.right: parent.right
font.pixelSize: 20
color: "#afafaf"
}
RalewaySemiBold {
visible: Account.loggedIn
height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0
text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : ""
horizontalAlignment: Text.AlignRight
anchors.right: parent.right
font.pixelSize: 20
color: "#afafaf"
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if (!Account.loggedIn) {
DialogsManager.showLoginDialog()
} else {
Account.logOut()
}
}
}
}
}
Rectangle {
id: bgMain
clip: true
gradient: Gradient {
GradientStop {
position: 0
color: "#2b2b2b"
}
GradientStop {
position: 1
color: "#0f212e"
}
}
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
anchors.top: bgTopBar.bottom
GridView {
id: flickable
anchors.top: parent.top
anchors.topMargin: 15
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
width: cellWidth * 3
cellHeight: 145
cellWidth: 145
model: tabletProxy.buttons
delegate: Item {
width: flickable.cellWidth
height: flickable.cellHeight
property var proxy: modelData
TabletButton {
id: tabletButton
anchors.centerIn: parent
onClicked: modelData.clicked()
state: wrapper.GridView.isCurrentItem ? "hover state" : "base state"
}
Connections {
target: modelData;
onPropertiesChanged: {
updateProperties();
}
}
Component.onCompleted: updateProperties()
function updateProperties() {
var keys = Object.keys(modelData.properties).forEach(function (key) {
if (tabletButton[key] !== modelData.properties[key]) {
tabletButton[key] = modelData.properties[key];
}
});
}
}
}
}
Keys.onRightPressed: flickable.moveCurrentIndexRight();
Keys.onLeftPressed: flickable.moveCurrentIndexLeft();
Keys.onDownPressed: flickable.moveCurrentIndexDown();
Keys.onUpPressed: flickable.moveCurrentIndexUp();
Keys.onReturnPressed: {
if (flickable.currentItem) {
flickable.currentItem.proxy.clicked();
if (tabletRoot) {
tabletRoot.playButtonClickSound();
}
}
}
}

View file

@ -77,12 +77,12 @@ FocusScope {
anchors.fill: parent
hoverEnabled: true
onEntered: {
tabletInterface.playSound(TabletEnums.ButtonHover);
Tablet.playSound(TabletEnums.ButtonHover);
listView.currentIndex = index
}
onClicked: {
tabletInterface.playSound(TabletEnums.ButtonClick);
Tablet.playSound(TabletEnums.ButtonClick);
root.selected(item);
}
}

View file

@ -68,37 +68,36 @@ Item {
function loadSource(url) {
tabletApps.clear();
loader.source = ""; // make sure we load the qml fresh each time.
loader.source = url;
tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
loader.load(url)
}
function loadQMLOnTop(url) {
tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
loader.source = "";
loader.source = tabletApps.get(currentApp).appUrl;
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
loader.item.gotoPreviousApp = true;
}
loader.load(tabletApps.get(currentApp).appUrl, function(){
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
loader.item.gotoPreviousApp = true;
}
})
}
function loadWebOnTop(url, injectJavaScriptUrl) {
tabletApps.append({"appUrl": loader.source, "isWebUrl": true, "scriptUrl": injectJavaScriptUrl, "appWebUrl": url});
loader.item.url = tabletApps.get(currentApp).appWebUrl;
loader.item.scriptUrl = tabletApps.get(currentApp).scriptUrl;
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
loader.item.gotoPreviousApp = true;
}
function loadWebContent(source, url, injectJavaScriptUrl) {
tabletApps.append({"appUrl": source, "isWebUrl": true, "scriptUrl": injectJavaScriptUrl, "appWebUrl": url});
loader.load(source, function() {
loader.item.scriptURL = injectJavaScriptUrl;
loader.item.url = url;
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
loader.item.gotoPreviousApp = true;
}
});
}
function loadWebBase() {
loader.source = "";
loader.source = "TabletWebView.qml";
function loadWebBase(url, injectJavaScriptUrl) {
loadWebContent("hifi/tablet/TabletWebView.qml", url, injectJavaScriptUrl);
}
function loadTabletWebBase() {
loader.source = "";
loader.source = "./BlocksWebView.qml";
function loadTabletWebBase(url, injectJavaScriptUrl) {
loadWebContent("hifi/tablet/BlocksWebView.qml", url, injectJavaScriptUrl);
}
function returnToPreviousApp() {
@ -110,7 +109,7 @@ Item {
loadSource("TabletWebView.qml");
loadWebUrl(webUrl, scriptUrl);
} else {
loader.source = tabletApps.get(currentApp).appUrl;
loader.load(tabletApps.get(currentApp).appUrl);
}
}
@ -173,47 +172,79 @@ Item {
}
}
Loader {
id: loader
objectName: "loader"
asynchronous: false
width: parent.width
height: parent.height
// Hook up callback for clara.io download from the marketplace.
Connections {
id: eventBridgeConnection
target: eventBridge
onWebEventReceived: {
if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
}
}
}
onLoaded: {
if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript);
}
if (loader.item.hasOwnProperty("setRootMenu")) {
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
}
loader.item.forceActiveFocus();
if (openModal) {
openModal.canceled();
openModal.destroy();
openModal = null;
}
if (openBrowser) {
openBrowser.destroy();
openBrowser = null;
// Hook up callback for clara.io download from the marketplace.
Connections {
id: eventBridgeConnection
target: eventBridge
onWebEventReceived: {
if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
}
}
}
Item {
id: loader
objectName: "loader";
anchors.fill: parent;
property string source: "";
property var item: null;
signal loaded;
onWidthChanged: {
if (loader.item) {
loader.item.width = loader.width;
}
}
onHeightChanged: {
if (loader.item) {
loader.item.height = loader.height;
}
}
function load(newSource, callback) {
if (loader.source == newSource) {
loader.loaded();
return;
}
if (loader.item) {
loader.item.destroy();
loader.item = null;
}
QmlSurface.load(newSource, loader, function(newItem) {
loader.item = newItem;
loader.item.width = loader.width;
loader.item.height = loader.height;
loader.loaded();
if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript);
}
if (loader.item.hasOwnProperty("setRootMenu")) {
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
}
loader.item.forceActiveFocus();
if (openModal) {
openModal.canceled();
openModal.destroy();
openModal = null;
}
if (openBrowser) {
openBrowser.destroy();
openBrowser = null;
}
if (callback) {
callback();
}
});
}
}
width: 480
height: 706

View file

@ -59,26 +59,25 @@ Windows.ScrollingWindow {
}
function loadSource(url) {
loader.source = ""; // make sure we load the qml fresh each time.
loader.source = url;
loader.load(url)
}
function loadWebBase() {
loader.source = "";
loader.source = "WindowWebView.qml";
function loadWebContent(source, url, injectJavaScriptUrl) {
loader.load(source, function() {
loader.item.scriptURL = injectJavaScriptUrl;
loader.item.url = url;
if (loader.item.hasOwnProperty("closeButtonVisible")) {
loader.item.closeButtonVisible = false;
}
});
}
function loadTabletWebBase() {
loader.source = "";
loader.source = "./BlocksWebView.qml";
function loadWebBase(url, injectJavaScriptUrl) {
loadWebContent("hifi/tablet/TabletWebView.qml", url, injectJavaScriptUrl);
}
function loadWebUrl(url, injectedJavaScriptUrl) {
loader.item.url = url;
loader.item.scriptURL = injectedJavaScriptUrl;
if (loader.item.hasOwnProperty("closeButtonVisible")) {
loader.item.closeButtonVisible = false;
}
function loadTabletWebBase(url, injectJavaScriptUrl) {
loadWebContent("hifi/tablet/BlocksWebView.qml", url, injectJavaScriptUrl);
}
// used to send a message from qml to interface script.
@ -111,38 +110,68 @@ Windows.ScrollingWindow {
username = newUsername;
}
Loader {
// Hook up callback for clara.io download from the marketplace.
Connections {
id: eventBridgeConnection
target: eventBridge
onWebEventReceived: {
if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
}
}
}
Item {
id: loader
objectName: "loader"
asynchronous: false
objectName: "loader";
property string source: "";
property var item: null;
height: pane.scrollHeight
width: pane.contentWidth
anchors.left: parent.left
anchors.top: parent.top
// Hook up callback for clara.io download from the marketplace.
Connections {
id: eventBridgeConnection
target: eventBridge
onWebEventReceived: {
if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
}
signal loaded;
onWidthChanged: {
if (loader.item) {
loader.item.width = loader.width;
}
}
onLoaded: {
if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript);
onHeightChanged: {
if (loader.item) {
loader.item.height = loader.height;
}
if (loader.item.hasOwnProperty("setRootMenu")) {
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
}
function load(newSource, callback) {
if (loader.item) {
loader.item.destroy();
loader.item = null;
}
loader.item.forceActiveFocus();
QmlSurface.load(newSource, loader, function(newItem) {
loader.item = newItem;
loader.item.width = loader.width;
loader.item.height = loader.height;
loader.loaded();
if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript);
}
if (loader.item.hasOwnProperty("setRootMenu")) {
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
}
loader.item.forceActiveFocus();
if (callback) {
callback();
}
});
}
}
implicitWidth: 480
implicitHeight: 706
}

View file

@ -11,6 +11,8 @@ Window {
horizontalSpacers: horizontal
verticalSpacers: !horizontal
}
property var tabletProxy;
property var buttonModel: ListModel {}
hideBackground: true
resizable: false
destroyOnCloseButton: false
@ -23,24 +25,32 @@ Window {
activator: Item {}
property bool horizontal: true
property real buttonSize: 50;
property var buttons: []
property var container: horizontal ? row : column
Settings {
category: "toolbar/" + window.objectName
property alias x: window.x
property alias y: window.y
}
Component {
id: buttonComponent
ToolbarButton {
id: toolbarButton
property var proxy: modelData;
onClicked: proxy.clicked()
Component.onCompleted: updateProperties()
onHorizontalChanged: {
var newParent = horizontal ? row : column;
for (var i in buttons) {
var child = buttons[i];
child.parent = newParent;
if (horizontal) {
child.y = 0
} else {
child.x = 0
Connections {
target: proxy;
onPropertiesChanged: updateProperties();
}
function updateProperties() {
Object.keys(proxy.properties).forEach(function (key) {
if (toolbarButton[key] !== proxy.properties[key]) {
toolbarButton[key] = proxy.properties[key];
}
});
}
}
}
@ -52,97 +62,22 @@ Window {
Row {
id: row
visible: window.horizontal
spacing: 6
Repeater {
model: buttonModel
delegate: buttonComponent
}
}
Column {
id: column
visible: !window.horizontal
spacing: 6
}
Component { id: toolbarButtonBuilder; ToolbarButton { } }
}
function findButtonIndex(name) {
if (!name) {
return -1;
}
for (var i in buttons) {
var child = buttons[i];
if (child.objectName === name) {
return i;
Repeater {
model: buttonModel
delegate: buttonComponent
}
}
return -1;
}
function findButton(name) {
var index = findButtonIndex(name);
if (index < 0) {
return;
}
return buttons[index];
}
function sortButtons() {
var children = [];
for (var i = 0; i < container.children.length; i++) {
children[i] = container.children[i];
}
children.sort(function (a, b) {
if (a.sortOrder === b.sortOrder) {
// subsort by stableOrder, because JS sort is not stable in qml.
return a.stableOrder - b.stableOrder;
} else {
return a.sortOrder - b.sortOrder;
}
});
container.children = children;
}
function addButton(properties) {
properties = properties || {}
// If a name is specified, then check if there's an existing button with that name
// and return it if so. This will allow multiple clients to listen to a single button,
// and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded
var result = findButton(properties.objectName);
if (result) {
for (var property in properties) {
result[property] = properties[property];
}
return result;
}
properties.toolbar = this;
properties.opacity = 0;
result = toolbarButtonBuilder.createObject(container, properties);
buttons.push(result);
result.opacity = 1;
sortButtons();
fadeIn(null);
return result;
}
function removeButton(name) {
var index = findButtonIndex(name);
if (index < -1) {
console.warn("Tried to remove non-existent button " + name);
return;
}
buttons[index].destroy();
buttons.splice(index, 1);
if (buttons.length === 0) {
fadeOut(null);
}
}
}

View file

@ -27,7 +27,8 @@ StateImage {
property string activeHoverIcon: button.activeIcon
property int sortOrder: 100
property int stableSortOrder: 0
property int stableOrder: 0
property var uuid;
signal clicked()

View file

@ -2248,27 +2248,65 @@ extern void setupPreferences();
void Application::initializeUi() {
// Make sure all QML surfaces share the main thread GL context
OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext());
OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "OverlayWindowTest.qml" },
[](QQmlContext* context) {
qDebug() << "Whitelist OverlayWindow worked";
context->setContextProperty("OverlayWindowTestString", "TestWorked");
});
OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "hifi/audio/Audio.qml" },
[](QQmlContext* context) {
qDebug() << "QQQ" << __FUNCTION__ << "Whitelist Audio worked";
});
AddressBarDialog::registerType();
ErrorDialog::registerType();
LoginDialog::registerType();
Tooltip::registerType();
UpdateDialog::registerType();
QmlCommerce::registerType();
QmlContextCallback callback = [](QQmlContext* context) {
context->setContextProperty("Commerce", new QmlCommerce());
};
OffscreenQmlSurface::addWhitelistContextHandler({
QUrl{ "hifi/commerce/checkout/Checkout.qml" },
QUrl{ "hifi/commerce/common/CommerceLightbox.qml" },
QUrl{ "hifi/commerce/common/EmulatedMarketplaceHeader.qml" },
QUrl{ "hifi/commerce/common/FirstUseTutorial.qml" },
QUrl{ "hifi/commerce/common/SortableListModel.qml" },
QUrl{ "hifi/commerce/inspectionCertificate/InspectionCertificate.qml" },
QUrl{ "hifi/commerce/purchases/PurchasedItem.qml" },
QUrl{ "hifi/commerce/purchases/Purchases.qml" },
QUrl{ "hifi/commerce/wallet/Help.qml" },
QUrl{ "hifi/commerce/wallet/NeedsLogIn.qml" },
QUrl{ "hifi/commerce/wallet/PassphraseChange.qml" },
QUrl{ "hifi/commerce/wallet/PassphraseModal.qml" },
QUrl{ "hifi/commerce/wallet/PassphraseSelection.qml" },
QUrl{ "hifi/commerce/wallet/Security.qml" },
QUrl{ "hifi/commerce/wallet/SecurityImageChange.qml" },
QUrl{ "hifi/commerce/wallet/SecurityImageModel.qml" },
QUrl{ "hifi/commerce/wallet/SecurityImageSelection.qml" },
QUrl{ "hifi/commerce/wallet/SendMoney.qml" },
QUrl{ "hifi/commerce/wallet/Wallet.qml" },
QUrl{ "hifi/commerce/wallet/WalletHome.qml" },
QUrl{ "hifi/commerce/wallet/WalletSetup.qml" },
}, callback);
qmlRegisterType<ResourceImageItem>("Hifi", 1, 0, "ResourceImageItem");
qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
qmlRegisterType<WebBrowserSuggestionsEngine>("HifiWeb", 1, 0, "WebBrowserSuggestionsEngine");
{
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
tabletScriptingInterface->getTablet(SYSTEM_TABLET);
}
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->create();
auto surfaceContext = offscreenUi->getSurfaceContext();
offscreenUi->setProxyWindow(_window->windowHandle());
offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
// OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to
// support the window management and scripting proxies for VR use
offscreenUi->createDesktop(QString("qrc:///qml/hifi/Desktop.qml"));
offscreenUi->createDesktop(QString("hifi/Desktop.qml"));
// FIXME either expose so that dialogs can set this themselves or
// do better detection in the offscreen UI of what has focus
@ -2336,9 +2374,6 @@ void Application::initializeUi() {
surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data());
surfaceContext->setContextProperty("Account", AccountScriptingInterface::getInstance());
surfaceContext->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
// Tablet inteference with Tablet.qml. Need to avoid this in QML space
surfaceContext->setContextProperty("tabletInterface", DependencyManager::get<TabletScriptingInterface>().data());
surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface);
surfaceContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance());
surfaceContext->setContextProperty("FaceTracker", DependencyManager::get<DdeFaceTracker>().data());
@ -5836,9 +5871,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue<TabletProxy>, wrapperFromScriptValue<TabletProxy>);
qScriptRegisterMetaType(scriptEngine.data(),
wrapperToScriptValue<TabletButtonProxy>, wrapperFromScriptValue<TabletButtonProxy>);
// Tablet inteference with Tablet.qml. Need to avoid this in QML space
scriptEngine->registerGlobalObject("tabletInterface", DependencyManager::get<TabletScriptingInterface>().data());
scriptEngine->registerGlobalObject("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
// FIXME remove these deprecated names for the tablet scripting interface
scriptEngine->registerGlobalObject("tabletInterface", DependencyManager::get<TabletScriptingInterface>().data());
auto toolbarScriptingInterface = DependencyManager::get<ToolbarScriptingInterface>().data();
DependencyManager::get<TabletScriptingInterface>().data()->setToolbarScriptingInterface(toolbarScriptingInterface);
@ -7245,13 +7280,17 @@ void Application::updateDisplayMode() {
}
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto desktop = offscreenUi->getDesktop();
// Make the switch atomic from the perspective of other threads
{
std::unique_lock<std::mutex> lock(_displayPluginLock);
// Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below.
bool wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool();
offscreenUi->getDesktop()->setProperty("repositionLocked", true);
bool wasRepositionLocked = false;
if (desktop) {
// Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below.
wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool();
offscreenUi->getDesktop()->setProperty("repositionLocked", true);
}
if (_displayPlugin) {
disconnect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent);
@ -7297,7 +7336,6 @@ void Application::updateDisplayMode() {
getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
_displayPlugin = newDisplayPlugin;
connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent, Qt::DirectConnection);
auto desktop = offscreenUi->getDesktop();
if (desktop) {
desktop->setProperty("repositionLocked", wasRepositionLocked);
}

View file

@ -101,7 +101,7 @@ Menu::Menu() {
auto action = addActionToQMenuAndActionHash(editMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J);
connect(action, &QAction::triggered, [] {
static const QUrl widgetUrl("hifi/dialogs/RunningScripts.qml");
static const QUrl tabletUrl("../../hifi/dialogs/TabletRunningScripts.qml");
static const QUrl tabletUrl("hifi/dialogs/TabletRunningScripts.qml");
static const QString name("RunningScripts");
qApp->showDialog(widgetUrl, tabletUrl, name);
});
@ -338,7 +338,7 @@ Menu::Menu() {
connect(action, &QAction::triggered, [] {
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
auto hmd = DependencyManager::get<HMDScriptingInterface>();
tablet->loadQMLSource("ControllerSettings.qml");
tablet->loadQMLSource("hifi/tablet/ControllerSettings.qml");
if (!hmd->getShouldShowTablet()) {
hmd->toggleShouldShowTablet();

View file

@ -16,9 +16,7 @@
#include "Wallet.h"
#include <AccountManager.h>
HIFI_QML_DEF(QmlCommerce)
QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
QmlCommerce::QmlCommerce() {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult);

View file

@ -16,16 +16,14 @@
#define hifi_QmlCommerce_h
#include <QJsonObject>
#include <OffscreenQmlDialog.h>
#include <QPixmap>
class QmlCommerce : public OffscreenQmlDialog {
class QmlCommerce : public QObject {
Q_OBJECT
HIFI_QML_DECL
public:
QmlCommerce(QQuickItem* parent = nullptr);
QmlCommerce();
signals:
void walletStatusResult(uint walletStatus);

View file

@ -31,7 +31,7 @@
#include "scripting/HMDScriptingInterface.h"
static const QVariant TABLET_ADDRESS_DIALOG = "TabletAddressDialog.qml";
static const QVariant TABLET_ADDRESS_DIALOG = "hifi/tablet/TabletAddressDialog.qml";
template<typename T>
void DialogsManager::maybeCreateDialog(QPointer<T>& member) {
if (!member) {
@ -91,7 +91,7 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) {
ConnectionFailureDialog::hide();
}
} else {
static const QUrl url("../../dialogs/TabletConnectionFailureDialog.qml");
static const QUrl url("dialogs/TabletConnectionFailureDialog.qml");
auto hmd = DependencyManager::get<HMDScriptingInterface>();
if (visible) {
tablet->initialScreen(url);

View file

@ -46,7 +46,7 @@ void LoginDialog::showWithSelection()
if (tablet->getToolbarMode()) {
LoginDialog::show();
} else {
static const QUrl url("../../dialogs/TabletLoginDialog.qml");
static const QUrl url("dialogs/TabletLoginDialog.qml");
tablet->initialScreen(url);
if (!hmd->getShouldShowTablet()) {
hmd->openTablet();

View file

@ -266,7 +266,7 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemI
}
}
static const QString INSPECTION_CERTIFICATE_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml";
static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml";
void ContextOverlayInterface::openInspectionCertificate() {
// lets open the tablet to the inspection certificate QML
if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) {

View file

@ -200,7 +200,6 @@ void Web3DOverlay::setupQmlSurface() {
_webSurface->getSurfaceContext()->setContextProperty("fileDialogHelper", new FileDialogHelper());
_webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
_webSurface->getSurfaceContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
_webSurface->getSurfaceContext()->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
_webSurface->getSurfaceContext()->setContextProperty("OctreeStats", DependencyManager::get<OctreeStatsProvider>().data());
@ -220,9 +219,6 @@ void Web3DOverlay::setupQmlSurface() {
_webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../");
// Tablet inteference with Tablet.qml. Need to avoid this in QML space
_webSurface->getSurfaceContext()->setContextProperty("tabletInterface", DependencyManager::get<TabletScriptingInterface>().data());
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data());
// mark the TabletProxy object as cpp ownership.
QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system");

View file

@ -30,6 +30,8 @@
using namespace render;
using namespace render::entities;
static const QString WEB_ENTITY_QML = "controls/WebEntityView.qml";
const float METERS_TO_INCHES = 39.3701f;
static uint32_t _currentWebCount{ 0 };
// Don't allow more than 100 concurrent web views
@ -218,6 +220,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
};
{
// FIXME use the surface cache instead of explicit creation
_webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), deleter);
_webSurface->create();
}
@ -291,7 +294,6 @@ void WebEntityRenderer::loadSourceURL() {
if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" ||
_lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) {
_contentType = htmlContent;
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/"));
// We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS.
if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) {
@ -300,12 +302,11 @@ void WebEntityRenderer::loadSourceURL() {
_webSurface->setMaxFps(DEFAULT_MAX_FPS);
}
_webSurface->load("WebEntityView.qml", [this](QQmlContext* context, QObject* item) {
_webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) {
item->setProperty("url", _lastSourceUrl);
});
} else {
_contentType = qmlContent;
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath()));
_webSurface->load(_lastSourceUrl);
if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();

View file

@ -217,8 +217,6 @@ gpu::TexturePointer TextureCache::cacheTextureByHash(const std::string& hash, co
if (!result) {
_texturesByHashes[hash] = texture;
result = texture;
} else {
qCWarning(modelnetworking) << "QQQ Swapping out texture with previous live texture in hash " << hash.c_str();
}
}
return result;

View file

@ -9,10 +9,6 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifdef _WIN32
#define _USE_MATH_DEFINES
#endif
#include <cstring>
#include <cstdio>
#include <cmath>

View file

@ -30,9 +30,30 @@ QString TEMP_DIR_FORMAT { "%1-%2-%3" };
const QString& PathUtils::resourcesPath() {
#ifdef Q_OS_MAC
static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/";
static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/";
#else
static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/";
static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/";
#endif
return staticResourcePath;
}
#ifdef DEV_BUILD
const QString& PathUtils::projectRootPath() {
static QString sourceFolder;
static std::once_flag once;
std::call_once(once, [&] {
QDir thisDir = QFileInfo(__FILE__).absoluteDir();
sourceFolder = QDir::cleanPath(thisDir.absoluteFilePath("../../../"));
});
return sourceFolder;
}
#endif
const QString& PathUtils::qmlBasePath() {
#ifdef DEV_BUILD
static const QString staticResourcePath = QUrl::fromLocalFile(projectRootPath() + "/interface/resources/qml/").toString();
#else
static const QString staticResourcePath = "qrc:///qml/";
#endif
return staticResourcePath;

View file

@ -34,6 +34,10 @@ class PathUtils : public QObject, public Dependency {
Q_PROPERTY(QUrl defaultScripts READ defaultScriptsLocation CONSTANT)
public:
static const QString& resourcesPath();
static const QString& qmlBasePath();
#ifdef DEV_BUILD
static const QString& projectRootPath();
#endif
static QString getAppDataPath();
static QString getAppLocalDataPath();

View file

@ -134,9 +134,6 @@ void OffscreenUi::create() {
myContext->setContextProperty("OffscreenUi", this);
myContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());
myContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
TabletProxy* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system");
myContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership);
}
void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {

View file

@ -61,8 +61,9 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) {
}
QUrl url { properties[SOURCE_PROPERTY].toString() };
// If the passed URL doesn't correspond to a known scheme, assume it's a local file path
if (url.scheme() != "http" && url.scheme() != "https" && url.scheme() != "file" && url.scheme() != "about" &&
url.scheme() != "atp") {
url.scheme() != "atp" && url.scheme() != "qrc") {
properties[SOURCE_PROPERTY] = QUrl::fromLocalFile(url.toString()).toString();
}

View file

@ -51,6 +51,8 @@
#include "types/HFWebEngineProfile.h"
#include "types/SoundEffect.h"
#include "TabletScriptingInterface.h"
#include "ToolbarScriptingInterface.h"
#include "Logging.h"
Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml")
@ -65,7 +67,10 @@ public:
void addWhitelistContextHandler(const std::initializer_list<QUrl>& urls, const QmlContextCallback& callback) {
withWriteLock([&] {
for (const auto& url : urls) {
for (auto url : urls) {
if (url.isRelative()) {
url = QUrl(PathUtils::qmlBasePath() + url.toString());
}
_callbacks[url].push_back(callback);
}
});
@ -102,7 +107,7 @@ void OffscreenQmlSurface::addWhitelistContextHandler(const std::initializer_list
}
QmlContextCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QObject*) {};
QmlContextObjectCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QQuickItem*) {};
struct TextureSet {
// The number of surfaces with this size
@ -443,6 +448,15 @@ void initializeQmlEngine(QQmlEngine* engine, QQuickWindow* window) {
rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext));
rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext));
rootContext->setContextProperty("Paths", DependencyManager::get<PathUtils>().data());
static std::once_flag once;
std::call_once(once, [&] {
qRegisterMetaType<TabletProxy*>();
qRegisterMetaType<TabletButtonProxy*>();
});
rootContext->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
rootContext->setContextProperty("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data());
TabletProxy* tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
engine->setObjectOwnership(tablet, QQmlEngine::CppOwnership);
}
QQmlEngine* acquireEngine(QQuickWindow* window) {
@ -659,10 +673,11 @@ void OffscreenQmlSurface::create() {
auto qmlEngine = acquireEngine(_quickWindow);
_qmlContext = new QQmlContext(qmlEngine->rootContext());
_qmlContext->setBaseUrl(QUrl{ PathUtils::qmlBasePath() });
_qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow()));
_qmlContext->setContextProperty("eventBridge", this);
_qmlContext->setContextProperty("webEntity", this);
_qmlContext->setContextProperty("QmlSurface", this);
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
@ -801,55 +816,70 @@ QQuickItem* OffscreenQmlSurface::getRootItem() {
return _rootItem;
}
void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) {
_qmlContext->setBaseUrl(baseUrl);
QQmlContext* OffscreenQmlSurface::contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext) {
// Get any whitelist functionality
QList<QmlContextCallback> callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource);
// If we have whitelisted content, we must load a new context
forceNewContext |= !callbacks.empty();
QQmlContext* targetContext = parent ? QQmlEngine::contextForObject(parent) : _qmlContext;
if (!targetContext) {
targetContext = _qmlContext;
}
if (_rootItem && forceNewContext) {
targetContext = new QQmlContext(targetContext);
}
for (const auto& callback : callbacks) {
callback(targetContext);
}
return targetContext;
}
void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback) {
void OffscreenQmlSurface::load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) {
loadInternal(qmlSource, false, parent, [callback](QQmlContext* context, QQuickItem* newItem) {
QJSValue(callback).call(QJSValueList() << context->engine()->newQObject(newItem));
});
}
void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback) {
loadInternal(qmlSource, createNewContext, nullptr, onQmlLoadedCallback);
}
void OffscreenQmlSurface::loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback) {
if (QThread::currentThread() != thread()) {
qCWarning(uiLogging) << "Called load on a non-surface thread";
}
// Synchronous loading may take a while; restart the deadlock timer
QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection);
// Get any whitelist functionality
QList<QmlContextCallback> callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource);
// If we have whitelisted content, we must load a new context
createNewContext |= !callbacks.empty();
callbacks.push_back(onQmlLoadedCallback);
QQmlContext* targetContext = _qmlContext;
if (_rootItem && createNewContext) {
targetContext = new QQmlContext(targetContext);
}
// FIXME eliminate loading of relative file paths for QML
QUrl finalQmlSource = qmlSource;
if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) {
finalQmlSource = _qmlContext->resolvedUrl(qmlSource);
}
auto targetContext = contextForUrl(finalQmlSource, parent, createNewContext);
auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous);
if (qmlComponent->isLoading()) {
connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) {
finishQmlLoad(qmlComponent, targetContext, callbacks);
finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback);
});
return;
}
finishQmlLoad(qmlComponent, targetContext, callbacks);
finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback);
}
void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) {
void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback) {
load(qmlSource, true, onQmlLoadedCallback);
}
void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) {
void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback) {
load(qmlSource, false, onQmlLoadedCallback);
}
void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback) {
void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextObjectCallback& onQmlLoadedCallback) {
return load(QUrl(qmlSourceFile), onQmlLoadedCallback);
}
@ -857,7 +887,8 @@ void OffscreenQmlSurface::clearCache() {
_qmlContext->engine()->clearComponentCache();
}
void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList<QmlContextCallback>& callbacks) {
void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& callback) {
disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (qmlComponent->isError()) {
for (const auto& error : qmlComponent->errors()) {
@ -879,6 +910,22 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
return;
}
if (!newObject) {
if (!_rootItem) {
qFatal("Could not load object as root item");
return;
}
qCWarning(uiLogging) << "Unable to load QML item";
return;
}
QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>();
if (qmlContext != _qmlContext && eventBridge && eventBridge != this) {
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext));
}
qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
// All quick items should be focusable
@ -889,41 +936,30 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
}
// Make sure we will call callback for this codepath
// Call this before qmlComponent->completeCreate() otherwise ghost window appears
if (newItem && _rootItem) {
for (const auto& callback : callbacks) {
callback(qmlContext, newObject);
}
}
// If we already have a root, just set a couple of flags and the ancestry
if (_rootItem) {
callback(qmlContext, newItem);
QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>();
if (qmlContext != _qmlContext && eventBridge && eventBridge != this) {
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext));
if (!parent) {
parent = _rootItem;
}
// Allow child windows to be destroyed from JS
QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
newObject->setParent(parent);
newItem->setParentItem(parent);
}
qmlComponent->completeCreate();
qmlComponent->deleteLater();
// If we already have a root, just set a couple of flags and the ancestry
if (newItem && _rootItem) {
// Allow child windows to be destroyed from JS
QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
newObject->setParent(_rootItem);
if (newItem) {
newItem->setParentItem(_rootItem);
}
if (_rootItem) {
QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection);
return;
}
if (!newItem) {
qFatal("Could not load object as root item");
return;
}
connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant)));
// The root item is ready. Associate it with the window.
@ -931,11 +967,16 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
_rootItem->setParentItem(_quickWindow->contentItem());
_rootItem->setSize(_quickWindow->renderTargetSize());
// Call this callback after rootitem is set, otherwise VrMenu wont work
for (const auto& callback : callbacks) {
callback(qmlContext, newObject);
if (_rootItem->objectName() == "tabletRoot") {
_qmlContext->setContextProperty("tabletRoot", QVariant::fromValue(_rootItem));
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", this);
QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system");
_qmlContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership);
}
QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection);
// Call this callback after rootitem is set, otherwise VrMenu wont work
callback(qmlContext, newItem);
}
void OffscreenQmlSurface::updateQuick() {

View file

@ -33,12 +33,14 @@ class QQmlContext;
class QQmlComponent;
class QQuickWindow;
class QQuickItem;
class QJSValue;
// GPU resources are typically buffered for one copy being used by the renderer,
// one copy in flight, and one copy being used by the receiver
#define GPU_RESOURCE_BUFFER_SIZE 3
using QmlContextCallback = std::function<void(QQmlContext*, QObject*)>;
using QmlContextCallback = std::function<void(QQmlContext*)>;
using QmlContextObjectCallback = std::function<void(QQmlContext*, QQuickItem*)>;
class OffscreenQmlSurface : public QObject, public QEnableSharedFromThis<OffscreenQmlSurface> {
Q_OBJECT
@ -46,7 +48,7 @@ class OffscreenQmlSurface : public QObject, public QEnableSharedFromThis<Offscre
public:
static void setSharedContext(QOpenGLContext* context);
static QmlContextCallback DEFAULT_CONTEXT_CALLBACK;
static QmlContextObjectCallback DEFAULT_CONTEXT_CALLBACK;
static void addWhitelistContextHandler(const std::initializer_list<QUrl>& urls, const QmlContextCallback& callback);
static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); };
@ -59,10 +61,15 @@ public:
void resize(const QSize& size, bool forceResize = false);
QSize size() const;
Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
// Usable from QML code as QmlSurface.load(url, parent, function(newItem){ ... })
Q_INVOKABLE void load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback);
// For C++ use
Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
void clearCache();
void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; }
// Optional values for event handling
@ -77,7 +84,6 @@ public:
bool isPaused() const;
bool getCleaned() { return _isCleaned; }
void setBaseUrl(const QUrl& baseUrl);
QQuickItem* getRootItem();
QQuickWindow* getWindow();
QObject* getEventHandler();
@ -142,13 +148,13 @@ protected:
private:
static QOpenGLContext* getSharedContext();
void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList<QmlContextCallback>& callbacks);
QQmlContext* contextForUrl(const QUrl& url, QQuickItem* parent, bool forceNewContext = false);
void loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback);
void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback);
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
void setupFbo();
bool allowNewFrame(uint8_t fps);
void render();
void cleanup();
QJsonObject getGLContextData();
void disconnectAudioOutputTimer();
private slots:

View file

@ -45,7 +45,6 @@ void OffscreenQmlSurfaceCache::release(const QString& rootSource, const QSharedP
QSharedPointer<OffscreenQmlSurface> OffscreenQmlSurfaceCache::buildSurface(const QString& rootSource) {
auto surface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface());
surface->create();
surface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
surface->load(rootSource);
surface->resize(QSize(100, 100));
return surface;

View file

@ -32,6 +32,14 @@ const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system";
const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
const QString TabletScriptingInterface::QML = "hifi/tablet/TabletRoot.qml";
static QString getUsername() {
QString username = "Unknown user";
auto accountManager = DependencyManager::get<AccountManager>();
if (accountManager->isLoggedIn()) {
username = accountManager->getAccountInfo().getUsername();
}
return username;
}
static Setting::Handle<QStringList> tabletSoundsButtonClick("TabletSounds", QStringList { "/sounds/Button06.wav",
"/sounds/Button04.wav",
@ -39,6 +47,51 @@ static Setting::Handle<QStringList> tabletSoundsButtonClick("TabletSounds", QStr
"/sounds/Tab01.wav",
"/sounds/Tab02.wav" });
TabletButtonListModel::TabletButtonListModel() {
}
TabletButtonListModel::~TabletButtonListModel() {
}
enum ButtonDeviceRole {
ButtonProxyRole = Qt::UserRole,
};
QHash<int, QByteArray> TabletButtonListModel::_roles{
{ ButtonProxyRole, "buttonProxy" },
};
Qt::ItemFlags TabletButtonListModel::_flags{ Qt::ItemIsSelectable | Qt::ItemIsEnabled };
QVariant TabletButtonListModel::data(const QModelIndex& index, int role) const {
if (!index.isValid() || index.row() >= rowCount() || role != ButtonProxyRole) {
return QVariant();
}
return QVariant::fromValue(_buttons.at(index.row()).data());
}
TabletButtonProxy* TabletButtonListModel::addButton(const QVariant& properties) {
auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap()));
beginResetModel();
_buttons.push_back(tabletButtonProxy);
endResetModel();
return tabletButtonProxy.data();
}
void TabletButtonListModel::removeButton(TabletButtonProxy* button) {
auto itr = std::find(_buttons.begin(), _buttons.end(), button);
if (itr == _buttons.end()) {
qCWarning(uiLogging) << "TabletProxy::removeButton() could not find button " << button;
return;
}
beginResetModel();
_buttons.erase(itr);
endResetModel();
}
TabletScriptingInterface::TabletScriptingInterface() {
qmlRegisterType<TabletScriptingInterface>("TabletScriptingInterface", 1, 0, "TabletEnums");
}
@ -210,9 +263,9 @@ QObject* TabletScriptingInterface::getFlags() {
// TabletProxy
//
static const char* TABLET_SOURCE_URL = "Tablet.qml";
static const char* WEB_VIEW_SOURCE_URL = "TabletWebView.qml";
static const char* VRMENU_SOURCE_URL = "TabletMenu.qml";
static const char* TABLET_HOME_SOURCE_URL = "hifi/tablet/TabletHome.qml";
static const char* WEB_VIEW_SOURCE_URL = "hifi/tablet/TabletWebView.qml";
static const char* VRMENU_SOURCE_URL = "hifi/tablet/TabletMenu.qml";
class TabletRootWindow : public QmlWindowClass {
virtual QString qmlSource() const override { return "hifi/tablet/WindowRoot.qml"; }
@ -247,9 +300,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
if (toolbarMode) {
removeButtonsFromHomeScreen();
addButtonsToToolbar();
// create new desktop window
auto tabletRootWindow = new TabletRootWindow();
tabletRootWindow->initQml(QVariantMap());
@ -264,11 +314,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
// forward qml surface events to interface js
connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml);
} else {
removeButtonsFromToolbar();
if (_currentPathLoaded == TABLET_SOURCE_URL) {
addButtonsToHomeScreen();
} else {
if (_currentPathLoaded != TABLET_HOME_SOURCE_URL) {
loadHomeScreen(true);
}
//check if running scripts window opened and save it for reopen in Tablet
@ -282,40 +328,8 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
_desktopWindow = nullptr;
}
}
}
static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
if (buttonProxy == NULL){
qCCritical(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet buttonProxy is NULL";
return;
}
QVariant resultVar;
bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", Qt::DirectConnection,
Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, buttonProxy->getProperties()));
if (!hasResult) {
qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet has no result";
return;
}
QObject* qmlButton = qvariant_cast<QObject *>(resultVar);
if (!qmlButton) {
qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet result not a QObject";
return;
}
QObject::connect(qmlButton, SIGNAL(clicked()), buttonProxy, SLOT(clickedSlot()));
buttonProxy->setQmlButton(qobject_cast<QQuickItem*>(qmlButton));
}
static QString getUsername() {
QString username = "Unknown user";
auto accountManager = DependencyManager::get<AccountManager>();
if (accountManager->isLoggedIn()) {
return accountManager->getAccountInfo().getUsername();
} else {
return "Unknown user";
}
emit toolbarModeChanged();
}
void TabletProxy::initialScreen(const QVariant& url) {
@ -363,7 +377,7 @@ void TabletProxy::onTabletShown() {
static_cast<TabletScriptingInterface*>(parent())->playSound(TabletScriptingInterface::TabletOpen);
if (_showRunningScripts) {
_showRunningScripts = false;
pushOntoStack("../../hifi/dialogs/TabletRunningScripts.qml");
pushOntoStack("hifi/dialogs/TabletRunningScripts.qml");
}
}
}
@ -397,10 +411,7 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) {
});
if (_toolbarMode) {
// if someone creates the tablet in toolbar mode, make sure to display the home screen on the tablet.
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()));
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_HOME_SOURCE_URL)));
}
// force to the tablet to go to the homescreen
@ -436,7 +447,6 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) {
QMetaObject::invokeMethod(_qmlTabletRoot, "setShown", Q_ARG(const QVariant&, QVariant(true)));
}
} else {
removeButtonsFromHomeScreen();
_state = State::Uninitialized;
emit screenChanged(QVariant("Closed"), QVariant(""));
_currentPathLoaded = "";
@ -465,7 +475,6 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) {
}
if (root) {
removeButtonsFromHomeScreen();
auto offscreenUi = DependencyManager::get<OffscreenUi>();
QObject* menu = offscreenUi->getRootMenu();
QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu)));
@ -539,7 +548,6 @@ void TabletProxy::loadQMLSource(const QVariant& path, bool resizable) {
}
if (root) {
removeButtonsFromHomeScreen(); //works only in Tablet
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path));
_state = State::QML;
if (path != _currentPathLoaded) {
@ -621,9 +629,7 @@ void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) {
if ((_state != State::Home && _state != State::Uninitialized) || forceOntoHomeScreen) {
if (!_toolbarMode && _qmlTabletRoot) {
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()));
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_HOME_SOURCE_URL)));
QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound");
} else if (_toolbarMode && _desktopWindow) {
// close desktop window
@ -632,8 +638,8 @@ void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) {
}
}
_state = State::Home;
emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL));
_currentPathLoaded = TABLET_SOURCE_URL;
emit screenChanged(QVariant("Home"), QVariant(TABLET_HOME_SOURCE_URL));
_currentPathLoaded = TABLET_HOME_SOURCE_URL;
}
}
@ -683,17 +689,18 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS
}
if (root) {
removeButtonsFromHomeScreen();
if (loadOtherBase) {
QMetaObject::invokeMethod(root, "loadTabletWebBase");
QMetaObject::invokeMethod(root, "loadTabletWebBase", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl)));
} else {
QMetaObject::invokeMethod(root, "loadWebBase");
QMetaObject::invokeMethod(root, "loadWebBase", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl)));
}
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
if (_toolbarMode && _desktopWindow) {
QMetaObject::invokeMethod(root, "setResizable", Q_ARG(const QVariant&, QVariant(false)));
}
QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl)));
_state = State::Web;
emit screenChanged(QVariant("Web"), QVariant(url));
_currentPathLoaded = QVariant(url);
} else {
// tablet is not initialized yet, save information and load when
// the tablet root is set
@ -702,10 +709,8 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS
_initialWebPathParams.first = injectedJavaScriptUrl;
_initialWebPathParams.second = loadOtherBase;
_initialScreen = true;
}
_state = State::Web;
emit screenChanged(QVariant("Web"), QVariant(url));
_currentPathLoaded = QVariant(url);
}
TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) {
@ -715,24 +720,7 @@ TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) {
return result;
}
auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap()));
_tabletButtonProxies.push_back(tabletButtonProxy);
if (!_toolbarMode && _qmlTabletRoot) {
auto tablet = getQmlTablet();
if (tablet) {
addButtonProxyToQmlTablet(tablet, tabletButtonProxy.data());
} else {
qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml";
}
} else if (_toolbarMode) {
auto toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
if (toolbarProxy) {
// copy properties from tablet button proxy to toolbar button proxy.
auto toolbarButtonProxy = toolbarProxy->addButton(tabletButtonProxy->getProperties());
tabletButtonProxy->setToolbarButtonProxy(toolbarButtonProxy);
}
}
return tabletButtonProxy.data();
return _buttons.addButton(properties);
}
bool TabletProxy::onHomeScreen() {
@ -751,35 +739,7 @@ void TabletProxy::removeButton(TabletButtonProxy* tabletButtonProxy) {
return;
}
auto tablet = getQmlTablet();
if (!tablet) {
qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml";
}
QSharedPointer<TabletButtonProxy> buttonProxy;
{
auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy);
if (iter == _tabletButtonProxies.end()) {
qCWarning(uiLogging) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy;
return;
}
buttonProxy = *iter;
_tabletButtonProxies.erase(iter);
}
if (!_toolbarMode && _qmlTabletRoot) {
buttonProxy->setQmlButton(nullptr);
if (tablet) {
QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties()));
}
} else if (_toolbarMode) {
auto toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
// remove button from toolbarProxy
if (toolbarProxy) {
toolbarProxy->removeButton(buttonProxy->getUuid().toString());
buttonProxy->setToolbarButtonProxy(nullptr);
}
}
_buttons.removeButton(tabletButtonProxy);
}
void TabletProxy::emitScriptEvent(const QVariant& msg) {
@ -808,57 +768,16 @@ void TabletProxy::sendToQml(const QVariant& msg) {
}
}
void TabletProxy::addButtonsToHomeScreen() {
auto tablet = getQmlTablet();
if (!tablet || _toolbarMode) {
return;
}
for (auto& buttonProxy : _tabletButtonProxies) {
addButtonProxyToQmlTablet(tablet, buttonProxy.data());
}
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::disconnect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()));
}
OffscreenQmlSurface* TabletProxy::getTabletSurface() {
return _qmlOffscreenSurface;
}
void TabletProxy::removeButtonsFromHomeScreen() {
auto tablet = getQmlTablet();
for (auto& buttonProxy : _tabletButtonProxies) {
if (tablet) {
QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties()));
}
buttonProxy->setQmlButton(nullptr);
}
}
void TabletProxy::desktopWindowClosed() {
gotoHomeScreen();
}
void TabletProxy::addButtonsToToolbar() {
Q_ASSERT(QThread::currentThread() == thread());
ToolbarProxy* toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
for (auto& buttonProxy : _tabletButtonProxies) {
// copy properties from tablet button proxy to toolbar button proxy.
buttonProxy->setToolbarButtonProxy(toolbarProxy->addButton(buttonProxy->getProperties()));
}
// make the toolbar visible
toolbarProxy->writeProperty("visible", QVariant(true));
}
void TabletProxy::removeButtonsFromToolbar() {
Q_ASSERT(QThread::currentThread() == thread());
ToolbarProxy* toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
for (auto& buttonProxy : _tabletButtonProxies) {
// remove button from toolbarProxy
toolbarProxy->removeButton(buttonProxy->getUuid().toString());
buttonProxy->setToolbarButtonProxy(nullptr);
}
}
QQuickItem* TabletProxy::getQmlTablet() const {
if (!_qmlTabletRoot) {
@ -928,25 +847,6 @@ TabletButtonProxy::~TabletButtonProxy() {
}
}
void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
if (_qmlButton) {
QObject::disconnect(_qmlButton, &QQuickItem::destroyed, this, nullptr);
}
_qmlButton = qmlButton;
if (_qmlButton) {
QObject::connect(_qmlButton, &QQuickItem::destroyed, this, [this] { _qmlButton = nullptr; });
}
}
void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) {
Q_ASSERT(QThread::currentThread() == thread());
_toolbarButtonProxy = toolbarButtonProxy;
if (_toolbarButtonProxy) {
QObject::connect(_toolbarButtonProxy, SIGNAL(clicked()), this, SLOT(clickedSlot()));
}
}
QVariantMap TabletButtonProxy::getProperties() {
if (QThread::currentThread() != thread()) {
QVariantMap result;
@ -963,20 +863,19 @@ void TabletButtonProxy::editProperties(const QVariantMap& properties) {
return;
}
bool changed = false;
QVariantMap::const_iterator iter = properties.constBegin();
while (iter != properties.constEnd()) {
const auto& key = iter.key();
const auto& value = iter.value();
if (!_properties.contains(key) || _properties[key] != value) {
_properties[iter.key()] = iter.value();
if (_qmlButton) {
QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection, Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value()));
}
_properties[key] = value;
changed = true;
}
++iter;
}
if (_toolbarButtonProxy) {
QMetaObject::invokeMethod(_toolbarButtonProxy, "editProperties", Qt::AutoConnection, Q_ARG(QVariantMap, properties));
if (changed) {
emit propertiesChanged();
}
}

View file

@ -12,13 +12,16 @@
#include <mutex>
#include <atomic>
#include <QObject>
#include <QVariant>
#include <QtCore/QObject>
#include <QtCore/QUuid>
#include <QtCore/QVariant>
#include <QtCore/QAbstractListModel>
#include <QtScript/QScriptValue>
#include <QScriptEngine>
#include <QScriptValueIterator>
#include <QQuickItem>
#include <QUuid>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValueIterator>
#include <QtQuick/QQuickItem>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
@ -90,6 +93,31 @@ protected:
bool _toolbarMode { false };
};
class TabletButtonListModel : public QAbstractListModel {
Q_OBJECT
public:
TabletButtonListModel();
~TabletButtonListModel();
int rowCount(const QModelIndex& parent = QModelIndex()) const override { Q_UNUSED(parent); return (int)_buttons.size(); }
QHash<int, QByteArray> roleNames() const override { return _roles; }
Qt::ItemFlags flags(const QModelIndex& index) const override { return _flags; }
QVariant data(const QModelIndex& index, int role) const override;
protected:
friend class TabletProxy;
TabletButtonProxy* addButton(const QVariant& properties);
void removeButton(TabletButtonProxy* button);
using List = std::list<QSharedPointer<TabletButtonProxy>>;
static QHash<int, QByteArray> _roles;
static Qt::ItemFlags _flags;
std::vector<QSharedPointer<TabletButtonProxy>> _buttons;
};
Q_DECLARE_METATYPE(TabletButtonListModel*);
/**jsdoc
* @class TabletProxy
* @property name {string} READ_ONLY: name of this tablet
@ -99,9 +127,10 @@ protected:
class TabletProxy : public QObject {
Q_OBJECT
Q_PROPERTY(QString name READ getName)
Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode)
Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode NOTIFY toolbarModeChanged)
Q_PROPERTY(bool landscape READ getLandscape WRITE setLandscape)
Q_PROPERTY(bool tabletShown MEMBER _tabletShown NOTIFY tabletShownChanged)
Q_PROPERTY(TabletButtonListModel* buttons READ getButtons CONSTANT)
public:
TabletProxy(QObject* parent, const QString& name);
~TabletProxy();
@ -204,6 +233,7 @@ public:
QQuickItem* getQmlMenu() const;
TabletButtonListModel* getButtons() { return &_buttons; }
signals:
/**jsdoc
* Signaled when this tablet receives an event from the html/js embedded in the tablet
@ -236,21 +266,19 @@ signals:
*/
void tabletShownChanged();
void toolbarModeChanged();
protected slots:
void addButtonsToHomeScreen();
void desktopWindowClosed();
void emitWebEvent(const QVariant& msg);
void onTabletShown();
protected:
void removeButtonsFromHomeScreen();
void loadHomeScreen(bool forceOntoHomeScreen);
void addButtonsToToolbar();
void removeButtonsFromToolbar();
bool _initialScreen { false };
QVariant _currentPathLoaded { "" };
QString _name;
std::vector<QSharedPointer<TabletButtonProxy>> _tabletButtonProxies;
QQuickItem* _qmlTabletRoot { nullptr };
OffscreenQmlSurface* _qmlOffscreenSurface { nullptr };
QmlWindowClass* _desktopWindow { nullptr };
@ -263,6 +291,8 @@ protected:
std::pair<QVariant, bool> _initialWebPathParams;
bool _landscape { false };
bool _showRunningScripts { false };
TabletButtonListModel _buttons;
};
Q_DECLARE_METATYPE(TabletProxy*);
@ -274,13 +304,11 @@ Q_DECLARE_METATYPE(TabletProxy*);
class TabletButtonProxy : public QObject {
Q_OBJECT
Q_PROPERTY(QUuid uuid READ getUuid)
Q_PROPERTY(QVariantMap properties READ getProperties NOTIFY propertiesChanged)
public:
TabletButtonProxy(const QVariantMap& properties);
~TabletButtonProxy();
void setQmlButton(QQuickItem* qmlButton);
void setToolbarButtonProxy(QObject* toolbarButtonProxy);
QUuid getUuid() const { return _uuid; }
/**jsdoc
@ -297,9 +325,6 @@ public:
*/
Q_INVOKABLE void editProperties(const QVariantMap& properties);
public slots:
void clickedSlot() { emit clicked(); }
signals:
/**jsdoc
* Signaled when this button has been clicked on by the user.
@ -307,12 +332,11 @@ signals:
* @returns {Signal}
*/
void clicked();
void propertiesChanged();
protected:
QUuid _uuid;
int _stableOrder;
QQuickItem* _qmlButton { nullptr };
QObject* _toolbarButtonProxy { nullptr };
QVariantMap _properties;
};

View file

@ -234,7 +234,7 @@
Recorder.setUp();
// Tablet/toolbar button.
// tablet/toolbar button.
button = tablet.addButton({
icon: APP_ICON_INACTIVE,
activeIcon: APP_ICON_ACTIVE,

View file

@ -19,7 +19,7 @@
tablet.gotoHomeScreen();
onRecordingScreen = false;
} else {
tablet.loadQMLSource("InputRecorder.qml");
tablet.loadQMLSource("hifi/tablet/InputRecorder.qml");
onRecordingScreen = true;
}
}

View file

@ -1,7 +1,7 @@
print("Launching web window");
qmlWindow = new OverlayWindow({
title: 'Test Qml',
source: "https://s3.amazonaws.com/DreamingContent/qml/content.qml",
source: "qrc:///qml/OverlayWindowTest.qml",
height: 240,
width: 320,
toolWindow: false,

View file

@ -15,7 +15,7 @@
var TABLET_BUTTON_NAME = "AUDIO";
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 AUDIO_QML_SOURCE = "../audio/Audio.qml";
var AUDIO_QML_SOURCE = "hifi/audio/Audio.qml";
var MUTE_ICONS = {
icon: "icons/tablet-icons/mic-mute-i.svg",

View file

@ -26,8 +26,8 @@
// 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 MARKETPLACE_PURCHASES_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/purchases/Purchases.qml";
var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml";
var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
var onWalletScreen = false;
function onButtonClicked() {
if (!tablet) {

View file

@ -427,7 +427,7 @@ var toolBar = (function () {
});
createButton = activeButton;
tablet.screenChanged.connect(function (type, url) {
if (isActive && (type !== "QML" || url !== "Edit.qml")) {
if (isActive && (type !== "QML" || url !== "hifi/tablet/Edit.qml")) {
that.setActive(false)
}
});
@ -656,7 +656,7 @@ var toolBar = (function () {
selectionDisplay.triggerMapping.disable();
tablet.landscape = false;
} else {
tablet.loadQMLSource("Edit.qml", true);
tablet.loadQMLSource("hifi/tablet/Edit.qml", true);
UserActivityLogger.enabledEdit();
entityListTool.setVisible(true);
gridTool.setVisible(true);

View file

@ -18,7 +18,7 @@
var buttonName = "Settings";
var toolBar = null;
var tablet = null;
var settings = "TabletGeneralPreferences.qml"
var settings = "hifi/tablet/TabletGeneralPreferences.qml"
function onClicked(){
if (tablet) {
tablet.loadQMLSource(settings);

View file

@ -10,7 +10,7 @@ openLoginWindow = function openLoginWindow() {
|| (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) {
Menu.triggerOption("Login / Sign Up");
} else {
tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml");
tablet.loadQMLOnTop("dialogs/TabletLoginDialog.qml");
HMD.openTablet();
}
};

View file

@ -23,10 +23,9 @@ var selectionDisplay = null; // for gridTool.js to ignore
var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page.
var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html");
var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
var MARKETPLACE_CHECKOUT_QML_PATH_BASE = "qml/hifi/commerce/checkout/Checkout.qml";
var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + MARKETPLACE_CHECKOUT_QML_PATH_BASE;
var MARKETPLACE_PURCHASES_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/purchases/Purchases.qml";
var MARKETPLACE_WALLET_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml";
var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml";
var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml";
var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.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";
@ -114,7 +113,7 @@ var selectionDisplay = null; // for gridTool.js to ignore
function onScreenChanged(type, url) {
onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1;
onWalletScreen = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1;
onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH_BASE) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH
onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH
|| url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1);
wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen);
@ -492,7 +491,7 @@ var selectionDisplay = null; // for gridTool.js to ignore
Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled);
break;
case 'purchases_openGoTo':
tablet.loadQMLSource("TabletAddressDialog.qml");
tablet.loadQMLSource("hifi/tablet/TabletAddressDialog.qml");
break;
case 'purchases_itemCertificateClicked':
setCertificateInfo("", message.itemCertificateId);

View file

@ -40,7 +40,7 @@ var HOVER_TEXTURES = {
var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6};
var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29};
var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now
var PAL_QML_SOURCE = "../Pal.qml";
var PAL_QML_SOURCE = "hifi/Pal.qml";
var conserveResources = true;
Script.include("/~/system/libraries/controllers.js");

View file

@ -24,7 +24,7 @@
print('tablet-goto.js:', [].map.call(arguments, JSON.stringify));
}
var gotoQmlSource = "TabletAddressDialog.qml";
var gotoQmlSource = "hifi/tablet/TabletAddressDialog.qml";
var buttonName = "GOTO";
var onGotoScreen = false;
var shouldActivateButton = false;

View file

@ -0,0 +1,27 @@
//
// Created by Bradley Austin Davis on 2017/11/08
// Copyright 2013-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 "PathUtilsTests.h"
#include <QtTest/QtTest>
#include <PathUtils.h>
QTEST_MAIN(PathUtilsTests)
void PathUtilsTests::testPathUtils() {
QString result = PathUtils::qmlBasePath();
#if DEV_BUILD
QVERIFY(result.startsWith("file:///"));
#else
QVERIFY(result.startsWith("qrc:///"));
#endif
QVERIFY(result.endsWith("/"));
}

View file

@ -0,0 +1,20 @@
//
// Created by Bradley Austin Davis on 2017/11/08
// Copyright 2013-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
//
#ifndef hifi_PathUtilsTests_h
#define hifi_PathUtilsTests_h
#include <QtCore/QObject>
class PathUtilsTests : public QObject {
Q_OBJECT
private slots:
void testPathUtils();
};
#endif // hifi_PathUtilsTests_h

View file

@ -24,7 +24,7 @@
if (onSkyboxChangerScreen) {
tablet.gotoHomeScreen();
} else {
tablet.loadQMLSource("../SkyboxChanger.qml");
tablet.loadQMLSource("hifi/SkyboxChanger.qml");
}
}

View file

@ -393,7 +393,7 @@
// Relevant Variables:
// -SPECTATOR_CAMERA_QML_SOURCE: The path to the SpectatorCamera QML
// -onSpectatorCameraScreen: true/false depending on whether we're looking at the spectator camera app.
var SPECTATOR_CAMERA_QML_SOURCE = Script.resourcesPath() + "qml/hifi/SpectatorCamera.qml";
var SPECTATOR_CAMERA_QML_SOURCE = "hifi/SpectatorCamera.qml";
var onSpectatorCameraScreen = false;
function onTabletButtonClicked() {
if (!tablet) {