From 46cfa7cf1394a968703970bf33c25f887ab2b8cd Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 10 Feb 2017 16:50:52 +0000 Subject: [PATCH] saving work --- .../qml/controls-uit/ContentSection.qml | 8 +- .../qml/hifi/tablet/TabletGeneralSettings.qml | 23 + .../hifi/tablet/tabletWindows/FileDialog.qml | 783 ++++++++++++++++++ .../tabletWindows/TabletPreferencesDialog.qml | 85 ++ .../tabletWindows/TabletScrollingWindow.qml | 172 ++++ .../tabletWindows/preferences/Preference.qml | 28 + .../tabletWindows/preferences/Section.qml | 140 ++++ .../preferences/TabletBrowsablePreference.qml | 79 ++ interface/src/ui/overlays/Web3DOverlay.cpp | 2 + scripts/system/generalSettings.js | 2 +- 10 files changed, 1317 insertions(+), 5 deletions(-) create mode 100644 interface/resources/qml/hifi/tablet/TabletGeneralSettings.qml create mode 100644 interface/resources/qml/hifi/tablet/tabletWindows/FileDialog.qml create mode 100644 interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml create mode 100644 interface/resources/qml/hifi/tablet/tabletWindows/TabletScrollingWindow.qml create mode 100644 interface/resources/qml/hifi/tablet/tabletWindows/preferences/Preference.qml create mode 100644 interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml create mode 100644 interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml diff --git a/interface/resources/qml/controls-uit/ContentSection.qml b/interface/resources/qml/controls-uit/ContentSection.qml index 47a13e9262..012b803738 100644 --- a/interface/resources/qml/controls-uit/ContentSection.qml +++ b/interface/resources/qml/controls-uit/ContentSection.qml @@ -58,14 +58,14 @@ Column { Rectangle { id: shadow - width: frame.width + width: 480//frame ? frame.width: 480 height: 1 color: hifi.colors.baseGrayShadow x: -hifi.dimensions.contentMargin.x } Rectangle { - width: frame.width + width: 480 //frame ? frame.width : 480 height: 1 color: hifi.colors.baseGrayHighlight x: -hifi.dimensions.contentMargin.x @@ -121,8 +121,8 @@ Column { LinearGradient { id: bottomBar - visible: desktop.gradientsSupported && isCollapsible - width: frame.width + visible: false //(desktop ? desktop.gradientsSupported : false) && isCollapsible + width: 480 //(frame ? frame.width : 480) height: visible ? 4 : 0 x: -hifi.dimensions.contentMargin.x anchors.top: heading.bottom diff --git a/interface/resources/qml/hifi/tablet/TabletGeneralSettings.qml b/interface/resources/qml/hifi/tablet/TabletGeneralSettings.qml new file mode 100644 index 0000000000..88e3403e2d --- /dev/null +++ b/interface/resources/qml/hifi/tablet/TabletGeneralSettings.qml @@ -0,0 +1,23 @@ +// +// TabletGeneralSettings.qml +// scripts/system/ +// +// Created by Dante Ruiz on 9 Feb 2017 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "tabletWindows" +import "../../dialogs" + +TabletPreferencesDialog { + id: root + objectName: "GeneralPreferencesDialog" + width: parent.width + height: parent.height + showCategories: ["UI", "Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Sixense Controllers", "Perception Neuron", "Kinect"] + +} diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/FileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/FileDialog.qml new file mode 100644 index 0000000000..0886a25949 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/tabletWindows/FileDialog.qml @@ -0,0 +1,783 @@ +// +// FileDialog.qml +// +// Created by Bradley Austin Davis on 14 Jan 2016 +// Copyright 2015 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 Qt.labs.folderlistmodel 2.1 +import Qt.labs.settings 1.0 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Dialogs 1.2 as OriginalDialogs + +import ".." +import "../controls-uit" +import "../styles-uit" +import "../windows" + +import "fileDialog" + +//FIXME implement shortcuts for favorite location +ModalWindow { + id: root + resizable: true + implicitWidth: 480 + implicitHeight: 360 + (fileDialogItem.keyboardEnabled && fileDialogItem.keyboardRaised ? keyboard.raisedHeight + hifi.dimensions.contentSpacing.y : 0) + + minSize: Qt.vector2d(360, 240) + draggable: true + + HifiConstants { id: hifi } + + Settings { + category: "FileDialog" + property alias width: root.width + property alias height: root.height + property alias x: root.x + property alias y: root.y + } + + + // Set from OffscreenUi::getOpenFile() + property alias caption: root.title; + // Set from OffscreenUi::getOpenFile() + property alias dir: fileTableModel.folder; + // Set from OffscreenUi::getOpenFile() + property alias filter: selectionType.filtersString; + // Set from OffscreenUi::getOpenFile() + property int options; // <-- FIXME unused + + property string iconText: root.title !== "" ? hifi.glyphs.scriptUpload : "" + property int iconSize: 40 + + property bool selectDirectory: false; + property bool showHidden: false; + // FIXME implement + property bool multiSelect: false; + property bool saveDialog: false; + property var helper: fileDialogHelper + property alias model: fileTableView.model + property var drives: helper.drives() + + property int titleWidth: 0 + + signal selectedFile(var file); + signal canceled(); + + Component.onCompleted: { + console.log("Helper " + helper + " drives " + drives); + + fileDialogItem.keyboardEnabled = HMD.active; + + // HACK: The following lines force the model to initialize properly such that the go-up button + // works properly from the initial screen. + var initialFolder = folderListModel.folder; + fileTableModel.folder = helper.pathToUrl(drives[0]); + fileTableModel.folder = initialFolder; + + iconText = root.title !== "" ? hifi.glyphs.scriptUpload : ""; + + // Clear selection when click on external frame. + frameClicked.connect(function() { d.clearSelection(); }); + + if (selectDirectory) { + currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder)); + d.currentSelectionIsFolder = true; + d.currentSelectionUrl = initialFolder; + } + + helper.contentsChanged.connect(function() { + if (folderListModel) { + // Make folderListModel refresh. + var save = folderListModel.folder; + folderListModel.folder = ""; + folderListModel.folder = save; + } + }); + + fileTableView.forceActiveFocus(); + } + + Item { + id: fileDialogItem + clip: true + width: pane.width + height: pane.height + anchors.margins: 0 + + property bool keyboardEnabled: false + property bool keyboardRaised: false + property bool punctuationMode: false + + MouseArea { + // Clear selection when click on internal unused area. + anchors.fill: parent + drag.target: root + onClicked: { + d.clearSelection(); + frame.forceActiveFocus(); // Defocus text field so that the keyboard gets hidden. + } + } + + Row { + id: navControls + anchors { + top: parent.top + topMargin: hifi.dimensions.contentMargin.y + left: parent.left + } + spacing: hifi.dimensions.contentSpacing.x + + GlyphButton { + id: upButton + glyph: hifi.glyphs.levelUp + width: height + size: 30 + enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== "" + onClicked: d.navigateUp(); + } + + GlyphButton { + id: homeButton + property var destination: helper.home(); + glyph: hifi.glyphs.home + size: 28 + width: height + enabled: d.homeDestination ? true : false + onClicked: d.navigateHome(); + } + } + + ComboBox { + id: pathSelector + anchors { + top: parent.top + topMargin: hifi.dimensions.contentMargin.y + left: navControls.right + leftMargin: hifi.dimensions.contentSpacing.x + right: parent.right + } + + property var lastValidFolder: helper.urlToPath(fileTableModel.folder) + + function calculatePathChoices(folder) { + var folders = folder.split("/"), + choices = [], + i, length; + + if (folders[folders.length - 1] === "") { + folders.pop(); + } + + choices.push(folders[0]); + + for (i = 1, length = folders.length; i < length; i++) { + choices.push(choices[i - 1] + "/" + folders[i]); + } + + if (folders[0] === "") { + // Special handling for OSX root dir. + choices[0] = "/"; + } + + choices.reverse(); + + if (drives && drives.length > 1) { + choices.push("This PC"); + } + + if (choices.length > 0) { + pathSelector.model = choices; + } + } + + onLastValidFolderChanged: { + var folder = d.capitalizeDrive(lastValidFolder); + calculatePathChoices(folder); + } + + onCurrentTextChanged: { + var folder = currentText; + + if (/^[a-zA-z]:$/.test(folder)) { + folder = "file:///" + folder + "/"; + } else if (folder === "This PC") { + folder = "file:///"; + } else { + folder = helper.pathToUrl(folder); + } + + if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) { + if (root.selectDirectory) { + currentSelection.text = currentText !== "This PC" ? currentText : ""; + d.currentSelectionUrl = helper.pathToUrl(currentText); + } + fileTableModel.folder = folder; + fileTableView.forceActiveFocus(); + } + } + } + + QtObject { + id: d + property var currentSelectionUrl; + readonly property string currentSelectionPath: helper.urlToPath(currentSelectionUrl); + property bool currentSelectionIsFolder; + property var backStack: [] + property var tableViewConnection: Connections { target: fileTableView; onCurrentRowChanged: d.update(); } + property var modelConnection: Connections { target: fileTableModel; onFolderChanged: d.update(); } + property var homeDestination: helper.home(); + + function capitalizeDrive(path) { + // Consistently capitalize drive letter for Windows. + if (/[a-zA-Z]:/.test(path)) { + return path.charAt(0).toUpperCase() + path.slice(1); + } + return path; + } + + function update() { + var row = fileTableView.currentRow; + + if (row === -1) { + if (!root.selectDirectory) { + currentSelection.text = ""; + currentSelectionIsFolder = false; + } + return; + } + + currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath); + currentSelectionIsFolder = fileTableView.model.isFolder(row); + if (root.selectDirectory || !currentSelectionIsFolder) { + currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl)); + } else { + currentSelection.text = ""; + } + } + + function navigateUp() { + if (fileTableModel.parentFolder && fileTableModel.parentFolder !== "") { + fileTableModel.folder = fileTableModel.parentFolder; + return true; + } + } + + function navigateHome() { + fileTableModel.folder = homeDestination; + return true; + } + + function clearSelection() { + fileTableView.selection.clear(); + fileTableView.currentRow = -1; + update(); + } + } + + FolderListModel { + id: folderListModel + nameFilters: selectionType.currentFilter + showDirsFirst: true + showDotAndDotDot: false + showFiles: !root.selectDirectory + Component.onCompleted: { + showFiles = !root.selectDirectory + } + + onFolderChanged: { + fileTableModel.update(); // Update once the data from the folder change is available. + } + + function getItem(index, field) { + return get(index, field); + } + } + + ListModel { + // Emulates FolderListModel but contains drive data. + id: driveListModel + + property int count: 1 + + Component.onCompleted: initialize(); + + function initialize() { + var drive, + i; + + count = drives.length; + + for (i = 0; i < count; i++) { + drive = drives[i].slice(0, -1); // Remove trailing "/". + append({ + fileName: drive, + fileModified: new Date(0), + fileSize: 0, + filePath: drive + "/", + fileIsDir: true, + fileNameSort: drive.toLowerCase() + }); + } + } + + function getItem(index, field) { + return get(index)[field]; + } + } + + ListModel { + id: fileTableModel + + // FolderListModel has a couple of problems: + // 1) Files and directories sort case-sensitively: https://bugreports.qt.io/browse/QTBUG-48757 + // 2) Cannot browse up to the "computer" level to view Windows drives: https://bugreports.qt.io/browse/QTBUG-42901 + // + // To solve these problems an intermediary ListModel is used that implements proper sorting and can be populated with + // drive information when viewing at the computer level. + + property var folder + property int sortOrder: Qt.AscendingOrder + property int sortColumn: 0 + property var model: folderListModel + property string parentFolder: calculateParentFolder(); + + readonly property string rootFolder: "file:///" + + function calculateParentFolder() { + if (model === folderListModel) { + if (folderListModel.parentFolder.toString() === "" && driveListModel.count > 1) { + return rootFolder; + } else { + return folderListModel.parentFolder; + } + } else { + return ""; + } + } + + onFolderChanged: { + if (folder === rootFolder) { + model = driveListModel; + helper.monitorDirectory(""); + update(); + } else { + var needsUpdate = model === driveListModel && folder === folderListModel.folder; + + model = folderListModel; + folderListModel.folder = folder; + helper.monitorDirectory(helper.urlToPath(folder)); + + if (needsUpdate) { + update(); + } + } + } + + function isFolder(row) { + if (row === -1) { + return false; + } + return get(row).fileIsDir; + } + + function update() { + var dataFields = ["fileName", "fileModified", "fileSize"], + sortFields = ["fileNameSort", "fileModified", "fileSize"], + dataField = dataFields[sortColumn], + sortField = sortFields[sortColumn], + sortValue, + fileName, + fileIsDir, + comparisonFunction, + lower, + middle, + upper, + rows = 0, + i; + + clear(); + + comparisonFunction = sortOrder === Qt.AscendingOrder + ? function(a, b) { return a < b; } + : function(a, b) { return a > b; } + + for (i = 0; i < model.count; i++) { + fileName = model.getItem(i, "fileName"); + fileIsDir = model.getItem(i, "fileIsDir"); + + sortValue = model.getItem(i, dataField); + if (dataField === "fileName") { + // Directories first by prefixing a "*". + // Case-insensitive. + sortValue = (fileIsDir ? "*" : "") + sortValue.toLowerCase(); + } + + lower = 0; + upper = rows; + while (lower < upper) { + middle = Math.floor((lower + upper) / 2); + var lessThan; + if (comparisonFunction(sortValue, get(middle)[sortField])) { + lessThan = true; + upper = middle; + } else { + lessThan = false; + lower = middle + 1; + } + } + + insert(lower, { + fileName: fileName, + fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")), + fileSize: model.getItem(i, "fileSize"), + filePath: model.getItem(i, "filePath"), + fileIsDir: fileIsDir, + fileNameSort: (fileIsDir ? "*" : "") + fileName.toLowerCase() + }); + + rows++; + } + + d.clearSelection(); + } + } + + Table { + id: fileTableView + colorScheme: hifi.colorSchemes.light + anchors { + top: navControls.bottom + topMargin: hifi.dimensions.contentSpacing.y + left: parent.left + right: parent.right + bottom: currentSelection.top + bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height + } + headerVisible: !selectDirectory + onDoubleClicked: navigateToRow(row); + focus: true + Keys.onReturnPressed: navigateToCurrentRow(); + Keys.onEnterPressed: navigateToCurrentRow(); + + sortIndicatorColumn: 0 + sortIndicatorOrder: Qt.AscendingOrder + sortIndicatorVisible: true + + model: fileTableModel + + function updateSort() { + model.sortOrder = sortIndicatorOrder; + model.sortColumn = sortIndicatorColumn; + model.update(); + } + + onSortIndicatorColumnChanged: { updateSort(); } + + onSortIndicatorOrderChanged: { updateSort(); } + + itemDelegate: Item { + clip: true + + FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; } + FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; } + + FiraSansSemiBold { + text: getText(); + elide: styleData.elideMode + anchors { + left: parent.left + leftMargin: hifi.dimensions.tablePadding + right: parent.right + rightMargin: hifi.dimensions.tablePadding + verticalCenter: parent.verticalCenter + } + size: hifi.fontSizes.tableText + color: hifi.colors.baseGrayHighlight + font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir) + ? firaSansSemiBold.name : firaSansRegular.name + + function getText() { + if (styleData.row === -1) { + return styleData.value; + } + + switch (styleData.column) { + case 1: return fileTableView.model.get(styleData.row).fileIsDir ? "" : styleData.value; + case 2: return fileTableView.model.get(styleData.row).fileIsDir ? "" : formatSize(styleData.value); + default: return styleData.value; + } + } + function formatSize(size) { + var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ]; + var suffixIndex = 0 + while ((size / 1024.0) > 1.1) { + size /= 1024.0; + ++suffixIndex; + } + + size = Math.round(size*1000)/1000; + size = size.toLocaleString() + + return size + " " + suffixes[suffixIndex]; + } + } + } + + TableViewColumn { + id: fileNameColumn + role: "fileName" + title: "Name" + width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width + movable: false + resizable: true + } + TableViewColumn { + id: fileMofifiedColumn + role: "fileModified" + title: "Date" + width: 0.3 * fileTableView.width + movable: false + resizable: true + visible: !selectDirectory + } + TableViewColumn { + role: "fileSize" + title: "Size" + width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width + movable: false + resizable: true + visible: !selectDirectory + } + + function navigateToRow(row) { + currentRow = row; + navigateToCurrentRow(); + } + + function navigateToCurrentRow() { + var row = fileTableView.currentRow + var isFolder = model.isFolder(row); + var file = model.get(row).filePath; + if (isFolder) { + fileTableView.model.folder = helper.pathToUrl(file); + } else { + okAction.trigger(); + } + } + + property string prefix: "" + + function addToPrefix(event) { + if (!event.text || event.text === "") { + return false; + } + var newPrefix = prefix + event.text.toLowerCase(); + var matchedIndex = -1; + for (var i = 0; i < model.count; ++i) { + var name = model.get(i).fileName.toLowerCase(); + if (0 === name.indexOf(newPrefix)) { + matchedIndex = i; + break; + } + } + + if (matchedIndex !== -1) { + fileTableView.selection.clear(); + fileTableView.selection.select(matchedIndex); + fileTableView.currentRow = matchedIndex; + fileTableView.prefix = newPrefix; + } + prefixClearTimer.restart(); + return true; + } + + Timer { + id: prefixClearTimer + interval: 1000 + repeat: false + running: false + onTriggered: fileTableView.prefix = ""; + } + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Backspace: + case Qt.Key_Tab: + case Qt.Key_Backtab: + event.accepted = false; + break; + + default: + if (addToPrefix(event)) { + event.accepted = true + } else { + event.accepted = false; + } + break; + } + } + } + + TextField { + id: currentSelection + label: selectDirectory ? "Directory:" : "File name:" + anchors { + left: parent.left + right: selectionType.visible ? selectionType.left: parent.right + rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0 + bottom: keyboard.top + bottomMargin: hifi.dimensions.contentSpacing.y + } + readOnly: !root.saveDialog + activeFocusOnTab: !readOnly + onActiveFocusChanged: if (activeFocus) { selectAll(); } + onAccepted: okAction.trigger(); + } + + FileTypeSelection { + id: selectionType + anchors { + top: currentSelection.top + left: buttonRow.left + right: parent.right + } + visible: !selectDirectory && filtersCount > 1 + KeyNavigation.left: fileTableView + KeyNavigation.right: openButton + } + + Keyboard { + id: keyboard + raised: parent.keyboardEnabled && parent.keyboardRaised + numeric: parent.punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: buttonRow.top + bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0 + } + } + + Row { + id: buttonRow + anchors { + right: parent.right + bottom: parent.bottom + } + spacing: hifi.dimensions.contentSpacing.y + + Button { + id: openButton + color: hifi.buttons.blue + action: okAction + Keys.onReturnPressed: okAction.trigger() + KeyNavigation.up: selectionType + KeyNavigation.left: selectionType + KeyNavigation.right: cancelButton + } + + Button { + id: cancelButton + action: cancelAction + KeyNavigation.up: selectionType + KeyNavigation.left: openButton + KeyNavigation.right: fileTableView.contentItem + Keys.onReturnPressed: { canceled(); root.enabled = false } + } + } + + Action { + id: okAction + text: currentSelection.text ? (root.selectDirectory && fileTableView.currentRow === -1 ? "Choose" : (root.saveDialog ? "Save" : "Open")) : "Open" + enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false + onTriggered: { + if (!root.selectDirectory && !d.currentSelectionIsFolder + || root.selectDirectory && fileTableView.currentRow === -1) { + okActionTimer.start(); + } else { + fileTableView.navigateToCurrentRow(); + } + } + } + + Timer { + id: okActionTimer + interval: 50 + running: false + repeat: false + onTriggered: { + if (!root.saveDialog) { + selectedFile(d.currentSelectionUrl); + root.destroy() + return; + } + + // Handle the ambiguity between different cases + // * typed name (with or without extension) + // * full path vs relative vs filename only + var selection = helper.saveHelper(currentSelection.text, root.dir, selectionType.currentFilter); + + if (!selection) { + desktop.messageBox({ icon: OriginalDialogs.StandardIcon.Warning, text: "Unable to parse selection" }) + return; + } + + if (helper.urlIsDir(selection)) { + root.dir = selection; + currentSelection.text = ""; + return; + } + + // Check if the file is a valid target + if (!helper.urlIsWritable(selection)) { + desktop.messageBox({ + icon: OriginalDialogs.StandardIcon.Warning, + text: "Unable to write to location " + selection + }) + return; + } + + if (helper.urlExists(selection)) { + var messageBox = desktop.messageBox({ + icon: OriginalDialogs.StandardIcon.Question, + buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No, + text: "Do you wish to overwrite " + selection + "?", + }); + var result = messageBox.exec(); + if (OriginalDialogs.StandardButton.Yes !== result) { + return; + } + } + + console.log("Selecting " + selection) + selectedFile(selection); + root.destroy(); + } + } + + Action { + id: cancelAction + text: "Cancel" + onTriggered: { canceled(); root.shown = false; } + } + } + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Backspace: + event.accepted = d.navigateUp(); + break; + + case Qt.Key_Home: + event.accepted = d.navigateHome(); + break; + + } + } +} diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml new file mode 100644 index 0000000000..51441381fd --- /dev/null +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml @@ -0,0 +1,85 @@ +// +// TabletPreferencesDialog.qml +// +// Created by Dante Ruiz on 9 Feb 2017 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtGraphicalEffects 1.0 + +import "." +import "./preferences" +import "../../../styles-uit" +import "../../../controls-uit" as HifiControls + +Item { + id: root + width: 480 + height: 600 + + HifiConstants { id: hifi } + property var sections: [] + property var showCategories: [] + + function saveAll() { + + } + + function restoreAll() { + + } + + ListView { + + Component { + id: sectionBuilder + Section {} + } + + Component.onCompleted: { + var categories = Preferences.categories; + var i; + + // build a map of valid categories. + var categoryMap = {}; + for (i = 0; i < categories.length; i++) { + categoryMap[categories[i]] = true; + } + + // create a section for each valid category in showCategories + // NOTE: the sort order of items in the showCategories array is the same order in the dialog. + for (i = 0; i < showCategories.length; i++) { + if (categoryMap[showCategories[i]]) { + sections.push(sectionBuilder.createObject(prefControls, {name: showCategories[i]})); + } + } + + if (sections.length) { + // Default sections to expanded/collapsed as appropriate for dialog. + if (sections.length === 1) { + sections[0].collapsable = false + sections[0].expanded = true + } else { + for (i = 0; i < sections.length; i++) { + sections[i].collapsable = true; + sections[i].expanded = true; + } + } + sections[0].isFirst = true; + sections[sections.length - 1].isLast = true; + } + } + + Column { + id: prefControls + width: 320 + } + } + +} diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletScrollingWindow.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletScrollingWindow.qml new file mode 100644 index 0000000000..416842e204 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletScrollingWindow.qml @@ -0,0 +1,172 @@ +// +// TabletsScrollingWindow.qml +// +// Created by Dante Ruiz on 9 Feb 2017 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtGraphicalEffects 1.0 + +import "." +import "../../../styles-uit" +import "../../../controls-uit" as HiFiControls +FocusScope{ + id: window + HifiConstants { id: hifi } + + childern [ plane ] + property var footer: Item {} + readonly var footerContentHeight: 40 + property var pane: Item { + property bool isScrolling: true//scrollView.height < scrollView.contentItem.height + property int contentWidth: scrollView.width - (isScrolling ? 10 : 0) + property int scrollHeight: scrollView.height + anchors.fill: parent + anchors.rightMargin: 11 + Rectangle { + id: contentBackground + anchors.fill: parent + color: hifi.colors.baseGray + visible: true + } + + LinearGradient { + visible: true + anchors.top: contentBackground.bottom + anchors.left: contentBackground.left + width: contentBackground.width - 1 + height: 4 + start: Qt.point(0, 0) + end: Qt.point(0, 4) + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.darkGray } + GradientStop { position: 1.0; color: hifi.colors.darkGray0 } + } + cached: true + } + + ScrollView { + id: scrollView + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + verticalScrollBarPolicy: Qt.ScrollBarAsNeeded + anchors.fill: parent + anchors.rightMargin: parent.isScrolling ? 1 : 0 + anchors.bottomMargin: footerPane.height + + style: ScrollViewStyle { + + padding.right: -7 // Move to right away from content. + + handle: Item { + implicitWidth: 8 + Rectangle { + radius: 4 + color: hifi.colors.white30 + anchors { + fill: parent + leftMargin: 2 // Finesse size and position. + topMargin: 1 + bottomMargin: 1 + } + } + } + + scrollBarBackground: Item { + implicitWidth: 10 + Rectangle { + color: hifi.colors.darkGray30 + radius: 4 + anchors { + fill: parent + topMargin: -1 // Finesse size + bottomMargin: -2 + } + } + } + + incrementControl: Item { + visible: false + } + + decrementControl: Item { + visible: false + } + } + } + + function scrollBy(delta) { + scrollView.flickableItem.contentY += delta; + } + + Rectangle { + // Optional non-scrolling footer. + id: footerPane + + property alias keyboardOverride: window.keyboardOverride + property alias keyboardRaised: window.keyboardRaised + property alias punctuationMode: window.punctuationMode + + anchors { + left: parent.left + bottom: parent.bottom + } + width: parent.contentWidth + height: footerContentHeight + (keyboard.enabled && keyboard.raised ? keyboard.height : 0) + color: hifi.colors.baseGray + visible: footer.height > 0 || keyboard.enabled && keyboard.raised + + Item { + // Horizontal rule. + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + visible: footer.height > 0 + + Rectangle { + width: parent.width + height: 1 + y: 1 // Stop displaying content just above horizontal rule/=. + color: hifi.colors.baseGrayShadow + } + + Rectangle { + width: parent.width + height: 1 + y: 2 + color: hifi.colors.baseGrayHighlight + } + } + + Item { + anchors { + left: parent.left + right: parent.right + top: parent.top + topMargin: hifi.dimensions.contentSpacing.y + 3 + } + children: [ footer ] + } + + HiFiControls.Keyboard { + id: keyboard + enabled: !keyboardOverride + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + } + } + } +} diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Preference.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Preference.qml new file mode 100644 index 0000000000..1d72197382 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Preference.qml @@ -0,0 +1,28 @@ +// +// Preference.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Item { + id: root + anchors { left: parent.left; right: parent.right } + property var preference; + property string label: preference ? preference.name : ""; + property bool isFirstCheckBox; + Component.onCompleted: { + if (preference) { + preference.load(); + enabled = Qt.binding(function() { return preference.enabled; } ); + } + } + + function restore() { } +} diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml new file mode 100644 index 0000000000..eca4ea0c7b --- /dev/null +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml @@ -0,0 +1,140 @@ +// +// Section.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import Hifi 1.0 + +import "../../../../dialogs/preferences" +import "../../../../controls-uit" as HiFiControls +import "../../../../styles-uit" +import "." + +Preference { + id: root + property bool collapsable: true + property bool expanded: false + property bool isFirst: false + property bool isLast: false + property string name: "Header" + property real spacing: 8 + default property alias preferences: contentContainer.children + + HifiConstants { id: hifi } + + function saveAll() { + for (var i = 0; i < d.preferences.length; ++i) { + var preference = d.preferences[i]; + preference.save(); + } + } + + function restoreAll() { + for (var i = 0; i < d.preferences.length; ++i) { + var preference = d.preferences[i]; + preference.restore(); + } + } + + children: [ contentContainer ] + + height: contentContainer.height + (contentContainer.isCollapsed ? 0 : hifi.dimensions.controlInterlineHeight) + + Component.onCompleted: d.buildPreferences(); + + HiFiControls.ContentSection { + id: contentContainer + name: root.name + isFirst: root.isFirst + isCollapsible: root.collapsable + isCollapsed: !root.expanded + + anchors { + left: parent.left + right: parent.right + margins: 0 + } + } + + QtObject { + id: d + property var editableBuilder: Component { EditablePreference { } } + property var browsableBuilder: Component { TabletBrowsablePreference { } } + property var spinnerBuilder: Component { SpinBoxPreference { } } + property var checkboxBuilder: Component { CheckBoxPreference { } } + property var sliderBuilder: Component { SliderPreference { } } + property var avatarBuilder: Component { AvatarPreference { } } + property var buttonBuilder: Component { ButtonPreference { } } + property var comboBoxBuilder: Component { ComboBoxPreference { } } + property var preferences: [] + property int checkBoxCount: 0 + + function buildPreferences() { + var categoryPreferences = Preferences.preferencesByCategory[root.name]; + if (categoryPreferences) { + console.log("Category " + root.name + " with " + categoryPreferences.length + " preferences"); + for (var j = 0; j < categoryPreferences.length; ++j) { + buildPreference(categoryPreferences[j]); + } + } + } + + function buildPreference(preference) { + console.log("\tPreference type " + preference.type + " name " + preference.name) + var builder; + switch (preference.type) { + case Preference.Editable: + checkBoxCount = 0; + builder = editableBuilder; + break; + + case Preference.Browsable: + checkBoxCount = 0; + builder = browsableBuilder; + break; + + case Preference.Spinner: + checkBoxCount = 0; + builder = spinnerBuilder; + break; + + case Preference.Slider: + checkBoxCount = 0; + builder = sliderBuilder; + break; + + case Preference.Checkbox: + checkBoxCount++; + builder = checkboxBuilder; + break; + + case Preference.Avatar: + checkBoxCount = 0; + builder = avatarBuilder; + break; + + case Preference.Button: + checkBoxCount = 0; + builder = buttonBuilder; + break; + + case Preference.ComboBox: + checkBoxCount = 0; + builder = comboBoxBuilder; + break; + }; + + if (builder) { + preferences.push(builder.createObject(contentContainer, { preference: preference, isFirstCheckBox: (checkBoxCount === 1) })); + } + } + } +} + diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml new file mode 100644 index 0000000000..cbfc83c2bc --- /dev/null +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml @@ -0,0 +1,79 @@ +// +// BrowsablePreference.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +import "../../../../dialogs" +import "../../../../controls-uit" + +Preference { + id: root + property alias text: dataTextField.text + property alias placeholderText: dataTextField.placeholderText + height: control.height + hifi.dimensions.controlInterlineHeight + + Component.onCompleted: { + dataTextField.text = preference.value; + } + + function save() { + preference.value = dataTextField.text; + preference.save(); + } + + Item { + id: control + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: Math.max(dataTextField.controlHeight, button.height) + + TextField { + id: dataTextField + + anchors { + left: parent.left + right: button.left + rightMargin: hifi.dimensions.contentSpacing.x + bottom: parent.bottom + } + + label: root.label + placeholderText: root.placeholderText + colorScheme: hifi.colorSchemes.dark + } + + Component { + id: fileBrowserBuilder; + FileDialog { selectDirectory: true } + } + + Button { + id: button + text: preference.browseLabel + anchors { + right: parent.right + verticalCenter: dataTextField.verticalCenter + } + onClicked: { + var browser = fileBrowserBuilder.createObject(desktop, { + selectDirectory: true, + dir: fileDialogHelper.pathToUrl(preference.value) + }); + browser.selectedFile.connect(function(fileUrl){ + console.log(fileUrl); + dataTextField.text = fileDialogHelper.urlToPath(fileUrl); + }); + } + } + } +} diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index ddf380d0b2..c198ca2e4e 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -37,6 +37,7 @@ #include #include "scripting/AccountScriptingInterface.h" #include "scripting/HMDScriptingInterface.h" +#include static const float DPI = 30.47f; static const float INCHES_TO_METERS = 1.0f / 39.3701f; @@ -158,6 +159,7 @@ void Web3DOverlay::loadSourceURL() { _webSurface->getRootContext()->setContextProperty("Users", DependencyManager::get().data()); _webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get().data()); _webSurface->getRootContext()->setContextProperty("UserActivityLogger", DependencyManager::get().data()); + _webSurface->getRootContext()->setContextProperty("Preferences", DependencyManager::get().data()); if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); diff --git a/scripts/system/generalSettings.js b/scripts/system/generalSettings.js index 5c658951b9..0a9fc823ae 100644 --- a/scripts/system/generalSettings.js +++ b/scripts/system/generalSettings.js @@ -18,7 +18,7 @@ var buttonName = "Settings"; var toolBar = null; var tablet = null; - var settings = "../../windows/TabletScrollingWindow.qml" + var settings = "TabletGeneralSettings.qml" function onClicked(){ if (tablet) { tablet.loadQMLSource(settings);