//// FileDialog.qml//// Created by Dante Ruiz on 23 Feb 2017// 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.7import Qt.labs.folderlistmodel 2.2import Qt.labs.settings 1.0import QtQuick.Dialogs 1.2 as OriginalDialogsimport QtQuick.Controls 1.4 as QQC1import QtQuick.Controls 2.3 import ".."import controlsUit 1.0import stylesUit 1.0import "../windows" import "fileDialog" //FIXME implement shortcuts for favorite locationTabletModalWindow { id: root anchors.fill: parent width: parent.width height: parent.height HifiConstants { id: hifi } property var filesModel: ListModel { } 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: true; // 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: { 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(); } TabletModalFrame { id: fileDialogItem width: parent.width - 6 height: parent.height - 6 anchors { horizontalCenter: root.horizontalCenter verticalCenter: root.verticalCenter } property bool keyboardEnabled: false property bool keyboardRaised: false property bool punctuationMode: false MouseArea { // Clear selection when click on internal unused area. anchors.fill: parent onClicked: { d.clearSelection(); } } Row { id: navControls anchors { top: parent.top topMargin: (fileDialogItem.hasTitle ? (fileDialogItem.frameMarginTop + hifi.dimensions.modalDialogMargin.y) : hifi.dimension.modalDialogMargin.y) left: parent.left leftMargin: hifi.dimensions.contentSpacing.x } 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 z: 10 anchors { top: parent.top topMargin: (fileDialogItem.hasTitle ? (fileDialogItem.frameMarginTop + hifi.dimensions.modalDialogMargin.y) : hifi.dimension.modalDialogMargin.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; function onCurrentRowChanged() { d.update(); } } property var modelConnection: Connections { target: fileTableModel; function 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 !== filesModel ? fileTableView.model.isFolder(row) : fileTableModel.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 caseSensitive: false showDirsFirst: true showDotAndDotDot: false showFiles: !root.selectDirectory showHidden: root.showHidden Component.onCompleted: { showFiles = !root.selectDirectory showHidden = root.showHidden } onFolderChanged: { d.clearSelection(); fileTableModel.update(); } 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]; } } Component { id: filesModelBuilder ListModel { } } QtObject { 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 filesModel.get(row).fileIsDir; } function get(row) { return filesModel.get(row) } 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; filesModel = filesModelBuilder.createObject(root); 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, filesModel.get(middle)[sortField])) { lessThan = true; upper = middle; } else { lessThan = false; lower = middle + 1; } } filesModel.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++; } } } 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: filesModel function updateSort() { fileTableModel.sortOrder = sortIndicatorOrder; fileTableModel.sortColumn = sortIndicatorColumn; fileTableModel.update(); } onSortIndicatorColumnChanged: { updateSort(); } onSortIndicatorOrderChanged: { updateSort(); } itemDelegate: Item { clip: true FiraSansSemiBold { text: getText();Lines 1–500 of 794. Load more
// // FileDialog.qml // // Created by Dante Ruiz on 23 Feb 2017 // 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.7 import Qt.labs.folderlistmodel 2.2 import Qt.labs.settings 1.0 import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Controls 1.4 as QQC1 import QtQuick.Controls 2.3 import ".." import controlsUit 1.0 import stylesUit 1.0 import "../windows" import "fileDialog" //FIXME implement shortcuts for favorite location TabletModalWindow { id: root anchors.fill: parent width: parent.width height: parent.height HifiConstants { id: hifi } property var filesModel: ListModel { } 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: true; // 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: { 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(); } TabletModalFrame { id: fileDialogItem width: parent.width - 6 height: parent.height - 6 anchors { horizontalCenter: root.horizontalCenter verticalCenter: root.verticalCenter } property bool keyboardEnabled: false property bool keyboardRaised: false property bool punctuationMode: false MouseArea { // Clear selection when click on internal unused area. anchors.fill: parent onClicked: { d.clearSelection(); } } Row { id: navControls anchors { top: parent.top topMargin: (fileDialogItem.hasTitle ? (fileDialogItem.frameMarginTop + hifi.dimensions.modalDialogMargin.y) : hifi.dimension.modalDialogMargin.y) left: parent.left leftMargin: hifi.dimensions.contentSpacing.x } 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 z: 10 anchors { top: parent.top topMargin: (fileDialogItem.hasTitle ? (fileDialogItem.frameMarginTop + hifi.dimensions.modalDialogMargin.y) : hifi.dimension.modalDialogMargin.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; function onCurrentRowChanged() { d.update(); } } property var modelConnection: Connections { target: fileTableModel; function 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 !== filesModel ? fileTableView.model.isFolder(row) : fileTableModel.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 caseSensitive: false showDirsFirst: true showDotAndDotDot: false showFiles: !root.selectDirectory showHidden: root.showHidden Component.onCompleted: { showFiles = !root.selectDirectory showHidden = root.showHidden } onFolderChanged: { d.clearSelection(); fileTableModel.update(); } 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]; } } Component { id: filesModelBuilder ListModel { } } QtObject { 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 filesModel.get(row).fileIsDir; } function get(row) { return filesModel.get(row) } 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; filesModel = filesModelBuilder.createObject(root); 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, filesModel.get(middle)[sortField])) { lessThan = true; upper = middle; } else { lessThan = false; lower = middle + 1; } } filesModel.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++; } } } 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: filesModel function updateSort() { fileTableModel.sortOrder = sortIndicatorOrder; fileTableModel.sortColumn = sortIndicatorColumn; fileTableModel.update(); } onSortIndicatorColumnChanged: { updateSort(); } onSortIndicatorOrderChanged: { updateSort(); } itemDelegate: Item { clip: true FiraSansSemiBold { text: getText();