diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ab7e55343..d7ea297637 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -201,6 +201,17 @@ set(HF_CMAKE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake") set(MACRO_DIR "${HF_CMAKE_DIR}/macros") set(EXTERNAL_PROJECT_DIR "${HF_CMAKE_DIR}/externals") +file(GLOB_RECURSE JS_SRC scripts/*.js) +add_custom_target(js SOURCES ${JS_SRC}) + +if (UNIX) + install( + DIRECTORY "${CMAKE_SOURCE_DIR}/scripts" + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/interface + COMPONENT ${CLIENT_COMPONENT} + ) +endif() + file(GLOB HIFI_CUSTOM_MACROS "cmake/macros/*.cmake") foreach(CUSTOM_MACRO ${HIFI_CUSTOM_MACROS}) include(${CUSTOM_MACRO}) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 868a2cf933..0ae7539448 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -54,6 +54,17 @@ find_package( WebChannel WebSockets ) +file(GLOB_RECURSE QML_SRC resources/qml/*.qml resources/qml/*.js) +add_custom_target(qml SOURCES ${QML_SRC}) + +if (UNIX) + install( + DIRECTORY "${CMAKE_SOURCE_DIR}/interface/resources/qml" + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/resources + COMPONENT ${CLIENT_COMPONENT} + ) +endif() + # grab the ui files in resources/ui file (GLOB_RECURSE QT_UI_FILES ui/*.ui) source_group("UI Files" FILES ${QT_UI_FILES}) diff --git a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml new file mode 100644 index 0000000000..6e5bc50d32 --- /dev/null +++ b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml @@ -0,0 +1,339 @@ +// +// TabletCustomQueryDialog.qml +// +// Created by Vlad Stelmahovsky on 3/27/17 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Dialogs 1.2 as OriginalDialogs + +import "../controls-uit" as ControlsUIT +import "../styles-uit" +import "../windows" + +TabletModalWindow { + id: root; + HifiConstants { id: hifi; } + + y: hifi.dimensions.tabletMenuHeader + + title: "" + visible: true; + property bool keyboardOverride: true + + signal selected(var result); + signal canceled(); + + property int icon: hifi.icons.none; + property string iconText: ""; + property int iconSize: 35; + onIconChanged: updateIcon(); + + property var textInput; + property var comboBox; + property var checkBox; + onTextInputChanged: { + if (textInput && textInput.text !== undefined) { + textField.text = textInput.text; + } + } + onComboBoxChanged: { + if (comboBox && comboBox.index !== undefined) { + comboBoxField.currentIndex = comboBox.index; + } + } + onCheckBoxChanged: { + if (checkBox && checkBox.checked !== undefined) { + checkBoxField.checked = checkBox.checked; + } + } + + property bool keyboardEnabled: false + property bool keyboardRaised: false + property bool punctuationMode: false + onKeyboardRaisedChanged: d.resize(); + + property var warning: ""; + property var result; + + property var implicitCheckState: null; + + property int titleWidth: 0; + onTitleWidthChanged: d.resize(); + + MouseArea { + width: parent.width + height: parent.height + } + + function updateIcon() { + if (!root) { + return; + } + iconText = hifi.glyphForIcon(root.icon); + } + + function updateCheckbox() { + if (checkBox.disableForItems) { + var currentItemInDisableList = false; + for (var i in checkBox.disableForItems) { + if (comboBoxField.currentIndex === checkBox.disableForItems[i]) { + currentItemInDisableList = true; + break; + } + } + + if (currentItemInDisableList) { + checkBoxField.enabled = false; + if (checkBox.checkStateOnDisable !== null && checkBox.checkStateOnDisable !== undefined) { + root.implicitCheckState = checkBoxField.checked; + checkBoxField.checked = checkBox.checkStateOnDisable; + } + root.warning = checkBox.warningOnDisable; + } else { + checkBoxField.enabled = true; + if (root.implicitCheckState !== null) { + checkBoxField.checked = root.implicitCheckState; + root.implicitCheckState = null; + } + root.warning = ""; + } + } + } + + TabletModalFrame { + id: modalWindowItem + width: parent.width - 12 + height: 240 + anchors { + verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter + } + + QtObject { + id: d; + readonly property int minWidth: 470 + readonly property int maxWidth: 470 + readonly property int minHeight: 240 + readonly property int maxHeight: 720 + + function resize() { + var targetWidth = Math.max(titleWidth, 470); + var targetHeight = (textField.visible ? textField.controlHeight + hifi.dimensions.contentSpacing.y : 0) + + (extraInputs.visible ? extraInputs.height + hifi.dimensions.contentSpacing.y : 0) + + (buttons.height + 3 * hifi.dimensions.contentSpacing.y) + + ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + hifi.dimensions.contentSpacing.y) : 0); + + root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth); + root.height = (targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? + d.maxHeight : targetHeight); + if (checkBoxField.visible && comboBoxField.visible) { + checkBoxField.width = extraInputs.width / 2; + comboBoxField.width = extraInputs.width / 2; + } else if (!checkBoxField.visible && comboBoxField.visible) { + comboBoxField.width = extraInputs.width; + } + } + } +// Item { +// anchors { +// // top: parent.top; +// // bottom: extraInputs.visible ? extraInputs.top : buttons.top; +// left: parent.left; +// right: parent.right; +// topMargin: 20 +// } + + // FIXME make a text field type that can be bound to a history for autocompletion + ControlsUIT.TextField { + id: textField; + label: root.textInput.label; + focus: root.textInput ? true : false; + visible: root.textInput ? true : false; + anchors { + top: parent.top + left: parent.left; + right: parent.right; + leftMargin: 5; rightMargin: 5; + topMargin: textField.controlHeight - textField.height + 5 + } + } + + ControlsUIT.Keyboard { + id: keyboard + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode + anchors { + left: parent.left + right: parent.right + leftMargin: 5; rightMargin: 5; + top: textField.bottom + topMargin: raised ? hifi.dimensions.contentSpacing.y : 0 + } + } + // } + + Row { + id: extraInputs; + visible: Boolean(root.checkBox || root.comboBox); + anchors { + left: parent.left; + right: parent.right; + leftMargin: 5; rightMargin: 5; + top: keyboard.bottom; + topMargin: hifi.dimensions.contentSpacing.y + 20; + } + height: comboBoxField.controlHeight; + onHeightChanged: d.resize(); + onWidthChanged: d.resize(); + + ControlsUIT.CheckBox { + id: checkBoxField; + text: root.checkBox.label; + focus: Boolean(root.checkBox); + visible: Boolean(root.checkBox); + // anchors { + // left: parent.left; + // bottom: parent.bottom; + // leftMargin: 6; // Magic number to align with warning icon + // bottomMargin: 6; + // } + } + + ControlsUIT.TabletComboBox { + id: comboBoxField; + label: root.comboBox.label; + focus: Boolean(root.comboBox); + visible: Boolean(root.comboBox); + // anchors { + // right: parent.right; + // bottom: parent.bottom; + // } + model: root.comboBox ? root.comboBox.items : []; + onAccepted: { + updateCheckbox(); + focus = true; + } + } + } + + Row { + id: buttons; + focus: true; + spacing: hifi.dimensions.contentSpacing.x; + layoutDirection: Qt.RightToLeft; + onHeightChanged: d.resize(); + onWidthChanged: { + d.resize(); + resizeWarningText(); + } + + anchors { + bottom: parent.bottom; + left: parent.left; + right: parent.right; + bottomMargin: hifi.dimensions.contentSpacing.y; + leftMargin: 5; rightMargin: 5; + } + + function resizeWarningText() { + var rowWidth = buttons.width; + var buttonsWidth = acceptButton.width + cancelButton.width + hifi.dimensions.contentSpacing.x * 2; + var warningIconWidth = warningIcon.width + hifi.dimensions.contentSpacing.x; + warningText.width = rowWidth - buttonsWidth - warningIconWidth; + } + + ControlsUIT.Button { + id: cancelButton; + action: cancelAction; + } + + ControlsUIT.Button { + id: acceptButton; + action: acceptAction; + } + + Text { + id: warningText; + visible: Boolean(root.warning); + text: root.warning; + wrapMode: Text.WordWrap; + font.italic: true; + maximumLineCount: 2; + } + + HiFiGlyphs { + id: warningIcon; + visible: Boolean(root.warning); + text: hifi.glyphs.alert; + size: hifi.dimensions.controlLineHeight; + } + } +// }//column + Action { + id: cancelAction; + text: qsTr("Cancel"); + shortcut: Qt.Key_Escape; + onTriggered: { + root.result = null; + root.canceled(); + root.destroy(); + } + } + + Action { + id: acceptAction; + text: qsTr("Add"); + shortcut: Qt.Key_Return; + onTriggered: { + var result = {}; + if (textInput) { + result.textInput = textField.text; + } + if (comboBox) { + result.comboBox = comboBoxField.currentIndex; + result.comboBoxText = comboBoxField.currentText; + } + if (checkBox) { + result.checkBox = checkBoxField.enabled ? checkBoxField.checked : null; + } + root.result = JSON.stringify(result); + root.selected(root.result); + root.destroy(); + } + } + } + + Keys.onPressed: { + if (!visible) { + return; + } + + switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + cancelAction.trigger(); + event.accepted = true; + break; + + case Qt.Key_Return: + case Qt.Key_Enter: + acceptAction.trigger(); + event.accepted = true; + break; + } + } + + Component.onCompleted: { + keyboardEnabled = HMD.active; + updateIcon(); + updateCheckbox(); + d.resize(); + textField.forceActiveFocus(); + } +} diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml new file mode 100644 index 0000000000..93e18c7aba --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -0,0 +1,614 @@ +// +// TabletAssetServer.qml +// +// Created by Vlad Stelmahovsky on 3/3/17 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Dialogs 1.2 as OriginalDialogs +import Qt.labs.settings 1.0 + +import "../../styles-uit" +import "../../controls-uit" as HifiControls +import "../../windows" + +Rectangle { + id: root + objectName: "AssetServer" + + property var eventBridge; + signal sendToScript(var message); + property bool isHMD: false + + color: hifi.colors.baseGray + + property int colorScheme: hifi.colorSchemes.dark + + HifiConstants { id: hifi } + + property var scripts: ScriptDiscoveryService; + property var assetProxyModel: Assets.proxyModel; + property var assetMappingsModel: Assets.mappingModel; + property var currentDirectory; + + Settings { + category: "Overlay.AssetServer" + property alias directory: root.currentDirectory + } + + Component.onCompleted: { + isHMD = HMD.active; + ApplicationInterface.uploadRequest.connect(uploadClicked); + assetMappingsModel.errorGettingMappings.connect(handleGetMappingsError); + reload(); + } + + function doDeleteFile(path) { + console.log("Deleting " + path); + + Assets.deleteMappings(path, function(err) { + if (err) { + console.log("Asset browser - error deleting path: ", path, err); + + box = errorMessageBox("There was an error deleting:\n" + path + "\n" + err); + box.selected.connect(reload); + } else { + console.log("Asset browser - finished deleting path: ", path); + reload(); + } + }); + + } + + function doRenameFile(oldPath, newPath) { + + if (newPath[0] !== "/") { + newPath = "/" + newPath; + } + + if (oldPath[oldPath.length - 1] === '/' && newPath[newPath.length - 1] != '/') { + // this is a folder rename but the user neglected to add a trailing slash when providing a new path + newPath = newPath + "/"; + } + + if (Assets.isKnownFolder(newPath)) { + box = errorMessageBox("Cannot overwrite existing directory."); + box.selected.connect(reload); + } + + console.log("Asset browser - renaming " + oldPath + " to " + newPath); + + Assets.renameMapping(oldPath, newPath, function(err) { + if (err) { + console.log("Asset browser - error renaming: ", oldPath, "=>", newPath, " - error ", err); + box = errorMessageBox("There was an error renaming:\n" + oldPath + " to " + newPath + "\n" + err); + box.selected.connect(reload); + } else { + console.log("Asset browser - finished rename: ", oldPath, "=>", newPath); + } + + reload(); + }); + } + + function fileExists(path) { + return Assets.isKnownMapping(path); + } + + function askForOverwrite(path, callback) { + var object = tabletRoot.messageBox({ + icon: hifi.icons.question, + buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No, + defaultButton: OriginalDialogs.StandardButton.No, + title: "Overwrite File", + text: path + "\n" + "This file already exists. Do you want to overwrite it?" + }); + object.selected.connect(function(button) { + if (button === OriginalDialogs.StandardButton.Yes) { + callback(); + } + }); + } + + function canAddToWorld(path) { + var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i]; + + return supportedExtensions.reduce(function(total, current) { + return total | new RegExp(current).test(path); + }, false); + } + + function clear() { + Assets.mappingModel.clear(); + } + + function reload() { + Assets.mappingModel.refresh(); + treeView.selection.clear(); + } + + function handleGetMappingsError(errorString) { + errorMessageBox( + "There was a problem retreiving the list of assets from your Asset Server.\n" + + errorString + ); + } + + function addToWorld() { + var defaultURL = assetProxyModel.data(treeView.selection.currentIndex, 0x103); + + if (!defaultURL || !canAddToWorld(defaultURL)) { + return; + } + + var SHAPE_TYPE_NONE = 0; + var SHAPE_TYPE_SIMPLE_HULL = 1; + var SHAPE_TYPE_SIMPLE_COMPOUND = 2; + var SHAPE_TYPE_STATIC_MESH = 3; + + var SHAPE_TYPES = []; + SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; + SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; + SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes"; + SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; + + var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; + var DYNAMIC_DEFAULT = false; + var prompt = tabletRoot.customInputDialog({ + textInput: { + label: "Model URL", + text: defaultURL + }, + comboBox: { + label: "Automatic Collisions", + index: SHAPE_TYPE_DEFAULT, + items: SHAPE_TYPES + }, + checkBox: { + label: "Dynamic", + checked: DYNAMIC_DEFAULT, + disableForItems: [ + SHAPE_TYPE_STATIC_MESH + ], + checkStateOnDisable: false, + warningOnDisable: "Models with automatic collisions set to 'Exact' cannot be dynamic" + } + }); + + prompt.selected.connect(function (jsonResult) { + if (jsonResult) { + var result = JSON.parse(jsonResult); + var url = result.textInput.trim(); + var shapeType; + switch (result.comboBox) { + case SHAPE_TYPE_SIMPLE_HULL: + shapeType = "simple-hull"; + break; + case SHAPE_TYPE_SIMPLE_COMPOUND: + shapeType = "simple-compound"; + break; + case SHAPE_TYPE_STATIC_MESH: + shapeType = "static-mesh"; + break; + default: + shapeType = "none"; + } + + var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT; + if (shapeType === "static-mesh" && dynamic) { + // The prompt should prevent this case + print("Error: model cannot be both static mesh and dynamic. This should never happen."); + } else if (url) { + var name = assetProxyModel.data(treeView.selection.currentIndex); + var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getFront(MyAvatar.orientation))); + var gravity; + if (dynamic) { + // Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a + // different scripting engine from QTScript. + gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10); + } else { + gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); + } + + print("Asset browser - adding asset " + url + " (" + name + ") to world."); + + // Entities.addEntity doesn't work from QML, so we use this. + Entities.addModelEntity(name, url, shapeType, dynamic, addPosition, gravity); + } + } + }); + } + + function copyURLToClipboard(index) { + if (!index) { + index = treeView.selection.currentIndex; + } + + var url = assetProxyModel.data(treeView.selection.currentIndex, 0x103); + if (!url) { + return; + } + Window.copyToClipboard(url); + } + + function renameEl(index, data) { + if (!index) { + return false; + } + + var path = assetProxyModel.data(index, 0x100); + if (!path) { + return false; + } + + var destinationPath = path.split('/'); + destinationPath[destinationPath.length - (path[path.length - 1] === '/' ? 2 : 1)] = data; + destinationPath = destinationPath.join('/').trim(); + + if (path === destinationPath) { + return; + } + if (!fileExists(destinationPath)) { + doRenameFile(path, destinationPath); + } + } + function renameFile(index) { + if (!index) { + index = treeView.selection.currentIndex; + } + + var path = assetProxyModel.data(index, 0x100); + if (!path) { + return; + } + + var object = tabletRoot.inputDialog({ + label: "Enter new path:", + current: path, + placeholderText: "Enter path here" + }); + object.selected.connect(function(destinationPath) { + destinationPath = destinationPath.trim(); + + if (path === destinationPath) { + return; + } + if (fileExists(destinationPath)) { + askForOverwrite(destinationPath, function() { + doRenameFile(path, destinationPath); + }); + } else { + doRenameFile(path, destinationPath); + } + }); + } + function deleteFile(index) { + if (!index) { + index = treeView.selection.currentIndex; + } + var path = assetProxyModel.data(index, 0x100); + if (!path) { + return; + } + + var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101); + var typeString = isFolder ? 'folder' : 'file'; + + var object = tabletRoot.messageBox({ + icon: hifi.icons.question, + buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No, + defaultButton: OriginalDialogs.StandardButton.Yes, + title: "Delete", + text: "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?" + }); + object.selected.connect(function(button) { + if (button === OriginalDialogs.StandardButton.Yes) { + doDeleteFile(path); + } + }); + } + + Timer { + id: doUploadTimer + property var url + property bool isConnected: false + interval: 5 + repeat: false + running: false + } + + property bool uploadOpen: false; + Timer { + id: timer + } + function uploadClicked(fileUrl) { + if (uploadOpen) { + return; + } + uploadOpen = true; + + function doUpload(url, dropping) { + var fileUrl = fileDialogHelper.urlToPath(url); + + var path = assetProxyModel.data(treeView.selection.currentIndex, 0x100); + var directory = path ? path.slice(0, path.lastIndexOf('/') + 1) : "/"; + var filename = fileUrl.slice(fileUrl.lastIndexOf('/') + 1); + + Assets.uploadFile(fileUrl, directory + filename, + function() { + // Upload started + uploadSpinner.visible = true; + uploadButton.enabled = false; + uploadProgressLabel.text = "In progress..."; + }, + function(err, path) { + print(err, path); + if (err === "") { + uploadProgressLabel.text = "Upload Complete"; + timer.interval = 1000; + timer.repeat = false; + timer.triggered.connect(function() { + uploadSpinner.visible = false; + uploadButton.enabled = true; + uploadOpen = false; + }); + timer.start(); + console.log("Asset Browser - finished uploading: ", fileUrl); + reload(); + } else { + uploadSpinner.visible = false; + uploadButton.enabled = true; + uploadOpen = false; + + if (err !== -1) { + console.log("Asset Browser - error uploading: ", fileUrl, " - error ", err); + var box = errorMessageBox("There was an error uploading:\n" + fileUrl + "\n" + err); + box.selected.connect(reload); + } + } + }, dropping); + } + + function initiateUpload(url) { + doUpload(doUploadTimer.url, false); + } + + if (fileUrl) { + doUpload(fileUrl, true); + } else { + var browser = tabletRoot.fileDialog({ + selectDirectory: false, + dir: currentDirectory + }); + + browser.canceled.connect(function() { + uploadOpen = false; + }); + + browser.selectedFile.connect(function(url) { + currentDirectory = browser.dir; + + // Initiate upload from a timer so that file browser dialog can close beforehand. + doUploadTimer.url = url; + if (!doUploadTimer.isConnected) { + doUploadTimer.triggered.connect(function() { initiateUpload(); }); + doUploadTimer.isConnected = true; + } + doUploadTimer.start(); + }); + } + } + + function errorMessageBox(message) { + return tabletRoot.messageBox({ + icon: hifi.icons.warning, + defaultButton: OriginalDialogs.StandardButton.Ok, + title: "Error", + text: message + }); + } + + Column { + width: parent.width + y: hifi.dimensions.tabletMenuHeader //-bgNavBar + spacing: 10 + + HifiControls.TabletContentSection { + id: assetDirectory + name: "Asset Directory" + isFirst: true + + HifiControls.VerticalSpacer {} + + Row { + id: buttonRow + width: parent.width + height: 30 + spacing: hifi.dimensions.contentSpacing.x + + HifiControls.GlyphButton { + glyph: hifi.glyphs.reload + color: hifi.buttons.black + colorScheme: root.colorScheme + width: hifi.dimensions.controlLineHeight + + onClicked: root.reload() + } + + HifiControls.Button { + text: "Add To World" + color: hifi.buttons.black + colorScheme: root.colorScheme + width: 120 + + enabled: canAddToWorld(assetProxyModel.data(treeView.selection.currentIndex, 0x100)) + + onClicked: root.addToWorld() + } + + HifiControls.Button { + text: "Rename" + color: hifi.buttons.black + colorScheme: root.colorScheme + width: 80 + + onClicked: root.renameFile() + enabled: treeView.selection.hasSelection + } + + HifiControls.Button { + id: deleteButton + + text: "Delete" + color: hifi.buttons.red + colorScheme: root.colorScheme + width: 80 + + onClicked: root.deleteFile() + enabled: treeView.selection.hasSelection + } + } + + Menu { + id: contextMenu + title: "Edit" + property var url: "" + property var currentIndex: null + + MenuItem { + text: "Copy URL" + onTriggered: { + copyURLToClipboard(contextMenu.currentIndex); + } + } + + MenuItem { + text: "Rename" + onTriggered: { + renameFile(contextMenu.currentIndex); + } + } + + MenuItem { + text: "Delete" + onTriggered: { + deleteFile(contextMenu.currentIndex); + } + } + } + + } + HifiControls.Tree { + id: treeView + height: 430 + anchors.leftMargin: hifi.dimensions.contentMargin.x + 2 // Extra for border + anchors.rightMargin: hifi.dimensions.contentMargin.x + 2 // Extra for border + anchors.left: parent.left + anchors.right: parent.right + + treeModel: assetProxyModel + canEdit: true + colorScheme: root.colorScheme + + modifyEl: renameEl + + MouseArea { + propagateComposedEvents: true + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: { + if (!HMD.active) { // Popup only displays properly on desktop + var index = treeView.indexAt(mouse.x, mouse.y); + treeView.selection.setCurrentIndex(index, 0x0002); + contextMenu.currentIndex = index; + contextMenu.popup(); + } + } + } + } + + + HifiControls.TabletContentSection { + id: uploadSection + name: "Upload A File" + spacing: hifi.dimensions.contentSpacing.y + //anchors.bottom: parent.bottom + height: 65 + anchors.left: parent.left + anchors.right: parent.right + + Item { + height: parent.height + width: parent.width + HifiControls.Button { + id: uploadButton + anchors.right: parent.right + + text: "Choose File" + color: hifi.buttons.blue + colorScheme: root.colorScheme + height: 30 + width: 155 + + onClicked: uploadClickedTimer.running = true + + // For some reason trigginer an API that enters + // an internal event loop directly from the button clicked + // trigger below causes the appliction to behave oddly. + // Most likely because the button onClicked handling is never + // completed until the function returns. + // FIXME find a better way of handling the input dialogs that + // doesn't trigger this. + Timer { + id: uploadClickedTimer + interval: 5 + repeat: false + running: false + onTriggered: uploadClicked(); + } + } + + Item { + id: uploadSpinner + visible: false + anchors.top: parent.top + anchors.left: parent.left + width: 40 + height: 32 + + Image { + id: image + width: 24 + height: 24 + source: "../../../images/Loading-Outer-Ring.png" + RotationAnimation on rotation { + loops: Animation.Infinite + from: 0 + to: 360 + duration: 2000 + } + } + Image { + width: 24 + height: 24 + source: "../../../images/Loading-Inner-H.png" + } + HifiControls.Label { + id: uploadProgressLabel + anchors.left: image.right + anchors.leftMargin: 10 + anchors.verticalCenter: image.verticalCenter + text: "In progress..." + colorScheme: root.colorScheme + } + } + } + } + } +} + diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 8eaddfb601..99c9351993 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -2,6 +2,7 @@ import QtQuick 2.0 import Hifi 1.0 import QtQuick.Controls 1.4 import "../../dialogs" + Item { id: tabletRoot objectName: "tabletRoot" @@ -29,7 +30,9 @@ Item { return openMessage; } + Component { id: customInputDialogBuilder; TabletCustomQueryDialog { } } function customInputDialog(properties) { + return customInputDialogBuilder.createObject(tabletRoot, properties); } Component { id: fileDialogBuilder; TabletFileDialog { } } diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 031e80283e..38534d4243 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -159,6 +159,7 @@ Item { readonly property vector2d menuPadding: Qt.vector2d(14, 102) readonly property real scrollbarBackgroundWidth: 18 readonly property real scrollbarHandleWidth: scrollbarBackgroundWidth - 2 + readonly property real tabletMenuHeader: 90 } Item { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index be385a0cac..af5a93a905 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -499,6 +499,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) DependencyManager::set(); @@ -1972,7 +1973,7 @@ void Application::initializeUi() { rootContext->setContextProperty("Quat", new Quat()); rootContext->setContextProperty("Vec3", new Vec3()); rootContext->setContextProperty("Uuid", new ScriptUUID()); - rootContext->setContextProperty("Assets", new AssetMappingsScriptingInterface()); + rootContext->setContextProperty("Assets", DependencyManager::get().data()); rootContext->setContextProperty("AvatarList", DependencyManager::get().data()); rootContext->setContextProperty("Users", DependencyManager::get().data()); @@ -5838,7 +5839,21 @@ void Application::showAssetServerWidget(QString filePath) { emit uploadRequest(filePath); } }; - DependencyManager::get()->show(url, "AssetServer", startUpload); + auto tabletScriptingInterface = DependencyManager::get(); + auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); + + if (tablet->getToolbarMode()) { + DependencyManager::get()->show(url, "AssetServer", startUpload); + } else { + QQuickItem* tabletRoot = tablet->getTabletRoot(); + if (!tabletRoot && !isHMDMode()) { + DependencyManager::get()->show(url, "AssetServer", startUpload); + } else { + static const QUrl url("../../hifi/dialogs/TabletAssetServer.qml"); + tablet->pushOntoStack(url); + } + } + startUpload(nullptr, nullptr); } diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 39b37e3d19..b64541a9f4 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -29,6 +29,7 @@ #include "UserActivityLogger.h" #include "MainWindow.h" #include +#include #ifdef HAS_BUGSPLAT #include @@ -194,6 +195,7 @@ int main(int argc, const char* argv[]) { { Application app(argc, const_cast(argv), startupTime, runServer, serverContentPathOptionValue); + QNetworkProxyFactory::setUseSystemConfiguration(true); // If we failed the OpenGLVersion check, log it. if (override) { auto accountManager = DependencyManager::get(); diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.h b/interface/src/scripting/AssetMappingsScriptingInterface.h index 459f01b512..b7fcea2491 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.h +++ b/interface/src/scripting/AssetMappingsScriptingInterface.h @@ -20,6 +20,8 @@ #include #include +#include "DependencyManager.h" + class AssetMappingModel : public QStandardItemModel { Q_OBJECT @@ -39,10 +41,12 @@ private: QHash _pathToItemMap; }; -Q_DECLARE_METATYPE(AssetMappingModel*); +Q_DECLARE_METATYPE(AssetMappingModel*) -class AssetMappingsScriptingInterface : public QObject { +class AssetMappingsScriptingInterface : public QObject, public Dependency { Q_OBJECT + SINGLETON_DEPENDENCY + Q_PROPERTY(AssetMappingModel* mappingModel READ getAssetMappingModel CONSTANT) Q_PROPERTY(QAbstractProxyModel* proxyModel READ getProxyModel CONSTANT) public: diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index b817960f0d..996615d234 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -37,9 +37,11 @@ #include #include "scripting/AccountScriptingInterface.h" #include "scripting/HMDScriptingInterface.h" +#include "scripting/AssetMappingsScriptingInterface.h" #include #include #include "FileDialogHelper.h" +#include "avatar/AvatarManager.h" static const float DPI = 30.47f; @@ -163,6 +165,10 @@ void Web3DOverlay::loadSourceURL() { _webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get().data()); _webSurface->getRootContext()->setContextProperty("UserActivityLogger", DependencyManager::get().data()); _webSurface->getRootContext()->setContextProperty("Preferences", DependencyManager::get().data()); + _webSurface->getRootContext()->setContextProperty("Vec3", new Vec3()); + _webSurface->getRootContext()->setContextProperty("Quat", new Quat()); + _webSurface->getRootContext()->setContextProperty("MyAvatar", DependencyManager::get()->getMyAvatar().get()); + _webSurface->getRootContext()->setContextProperty("Entities", DependencyManager::get().data()); if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); @@ -173,6 +179,7 @@ void Web3DOverlay::loadSourceURL() { _webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get().data()); _webSurface->getRootContext()->setContextProperty("fileDialogHelper", new FileDialogHelper()); _webSurface->getRootContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); + _webSurface->getRootContext()->setContextProperty("Assets", DependencyManager::get().data()); _webSurface->getRootContext()->setContextProperty("pathToFonts", "../../../"); tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data());