// // AssetDialogContent.qml // // Created by David Rowe on 19 Apr 2017 // Copyright 2017 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.7 import QtQuick.Controls 1.5 import "../../controls-uit" import "../../styles-uit" import "../fileDialog" Item { // Set from OffscreenUi::assetDialog() property alias dir: assetTableModel.folder property alias filter: selectionType.filtersString // FIXME: Currently only supports simple filters, "*.xxx". property int options // Not used. property bool selectDirectory: false // Not implemented. //property bool saveDialog: false; //property bool multiSelect: false; property bool singleClickNavigate: false HifiConstants { id: hifi } Component.onCompleted: { homeButton.destination = dir; if (selectDirectory) { d.currentSelectionIsFolder = true; d.currentSelectionPath = assetTableModel.folder; } assetTableView.forceActiveFocus(); } Item { id: assetDialogItem anchors.fill: parent clip: true MouseArea { // Clear selection when click on internal unused area. anchors.fill: parent drag.target: root onClicked: { d.clearSelection(); frame.forceActiveFocus(); assetTableView.forceActiveFocus(); } } 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: assetTableModel.parentFolder !== "" onClicked: d.navigateUp(); } GlyphButton { id: homeButton property string destination: "" glyph: hifi.glyphs.home size: 28 width: height enabled: destination !== "" //onClicked: d.navigateHome(); onClicked: assetTableModel.folder = destination; } } ComboBox { id: pathSelector anchors { top: parent.top topMargin: hifi.dimensions.contentMargin.y left: navControls.right leftMargin: hifi.dimensions.contentSpacing.x right: parent.right } z: 10 property string lastValidFolder: assetTableModel.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] === "") { choices[0] = "/"; } choices.reverse(); if (choices.length > 0) { pathSelector.model = choices; } } onLastValidFolderChanged: { var folder = lastValidFolder; calculatePathChoices(folder); } onCurrentTextChanged: { var folder = currentText; if (folder !== "/") { folder += "/"; } if (folder !== assetTableModel.folder) { if (root.selectDirectory) { currentSelection.text = currentText; d.currentSelectionPath = currentText; } assetTableModel.folder = folder; assetTableView.forceActiveFocus(); } } } QtObject { id: d property string currentSelectionPath property bool currentSelectionIsFolder property var tableViewConnection: Connections { target: assetTableView; onCurrentRowChanged: d.update(); } function update() { var row = assetTableView.currentRow; if (row === -1) { if (!root.selectDirectory) { currentSelection.text = ""; currentSelectionIsFolder = false; } return; } var rowInfo = assetTableModel.get(row); currentSelectionPath = rowInfo.filePath; currentSelectionIsFolder = rowInfo.fileIsDir; if (root.selectDirectory || !currentSelectionIsFolder) { currentSelection.text = currentSelectionPath; } else { currentSelection.text = ""; } } function navigateUp() { if (assetTableModel.parentFolder !== "") { assetTableModel.folder = assetTableModel.parentFolder; return true; } return false; } function navigateHome() { assetTableModel.folder = homeButton.destination; return true; } function clearSelection() { assetTableView.selection.clear(); assetTableView.currentRow = -1; update(); } } ListModel { id: assetTableModel property string folder property string parentFolder: "" readonly property string rootFolder: "/" onFolderChanged: { parentFolder = calculateParentFolder(); update(); } function calculateParentFolder() { if (folder !== "/") { return folder.slice(0, folder.slice(0, -1).lastIndexOf("/") + 1); } return ""; } function isFolder(row) { if (row === -1) { return false; } return get(row).fileIsDir; } function onGetAllMappings(error, map) { var mappings, fileTypeFilter, index, path, fileName, fileType, fileIsDir, isValid, subDirectory, subDirectories = [], fileNameSort, rows = 0, lower, middle, upper, i, length; clear(); if (error === "") { mappings = Object.keys(map); fileTypeFilter = filter.replace("*", "").toLowerCase(); for (i = 0, length = mappings.length; i < length; i++) { index = mappings[i].lastIndexOf("/"); path = mappings[i].slice(0, mappings[i].lastIndexOf("/") + 1); fileName = mappings[i].slice(path.length); fileType = fileName.slice(fileName.lastIndexOf(".")); fileIsDir = false; isValid = false; if (fileType.toLowerCase() === fileTypeFilter) { if (path === folder) { isValid = !selectDirectory; } else if (path.length > folder.length) { subDirectory = path.slice(folder.length); index = subDirectory.indexOf("/"); if (index === subDirectory.lastIndexOf("/")) { fileName = subDirectory.slice(0, index); if (subDirectories.indexOf(fileName) === -1) { fileIsDir = true; isValid = true; subDirectories.push(fileName); } } } } if (isValid) { fileNameSort = (fileIsDir ? "*" : "") + fileName.toLowerCase(); lower = 0; upper = rows; while (lower < upper) { middle = Math.floor((lower + upper) / 2); var lessThan; if (fileNameSort < get(middle)["fileNameSort"]) { lessThan = true; upper = middle; } else { lessThan = false; lower = middle + 1; } } insert(lower, { fileName: fileName, filePath: path + (fileIsDir ? "" : fileName), fileIsDir: fileIsDir, fileNameSort: fileNameSort }); rows++; } } } else { console.log("Error getting mappings from Asset Server"); } } function update() { d.clearSelection(); clear(); Assets.getAllMappings(onGetAllMappings); } } Table { id: assetTableView 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 } model: assetTableModel focus: true onClicked: { if (singleClickNavigate) { navigateToRow(row); } } onDoubleClicked: navigateToRow(row); Keys.onReturnPressed: navigateToCurrentRow(); Keys.onEnterPressed: navigateToCurrentRow(); itemDelegate: Item { clip: true FiraSansSemiBold { text: styleData.value 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 && assetTableView.model.get(styleData.row).fileIsDir) ? "Fira Sans SemiBold" : "Fira Sans" } } TableViewColumn { id: fileNameColumn role: "fileName" title: "Name" width: assetTableView.width movable: false resizable: false } function navigateToRow(row) { currentRow = row; navigateToCurrentRow(); } function navigateToCurrentRow() { if (model.isFolder(currentRow)) { model.folder = model.get(currentRow).filePath; } else { okAction.trigger(); } } Timer { id: prefixClearTimer interval: 1000 repeat: false running: false onTriggered: assetTableView.prefix = ""; } 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) { assetTableView.selection.clear(); assetTableView.selection.select(matchedIndex); assetTableView.currentRow = matchedIndex; assetTableView.prefix = newPrefix; } prefixClearTimer.restart(); return true; } 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: buttonRow.top bottomMargin: hifi.dimensions.contentSpacing.y } readOnly: true 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: assetTableView KeyNavigation.right: openButton } Action { id: okAction text: currentSelection.text && root.selectDirectory && assetTableView.currentRow === -1 ? "Choose" : "Open" enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false onTriggered: { if (!root.selectDirectory && !d.currentSelectionIsFolder || root.selectDirectory && assetTableView.currentRow === -1) { selectedAsset(d.currentSelectionPath); root.destroy(); } else { assetTableView.navigateToCurrentRow(); } } } Action { id: cancelAction text: "Cancel" onTriggered: { canceled(); root.destroy(); } } 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: assetTableView.contentItem Keys.onReturnPressed: { canceled(); root.enabled = false } } } } Keys.onPressed: { switch (event.key) { case Qt.Key_Backspace: event.accepted = d.navigateUp(); break; case Qt.Key_Home: event.accepted = d.navigateHome(); break; } } }