mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-26 16:55:06 +02:00
504 lines
17 KiB
QML
504 lines
17 KiB
QML
//
|
|
// 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-uit"
|
|
|
|
import "fileDialog"
|
|
|
|
//FIXME implement shortcuts for favorite location
|
|
ModalWindow {
|
|
id: root
|
|
//resizable: true
|
|
implicitWidth: 640
|
|
implicitHeight: 480
|
|
|
|
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: model.folder;
|
|
// Set from OffscreenUi::getOpenFile()
|
|
property alias filter: selectionType.filtersString;
|
|
// Set from OffscreenUi::getOpenFile()
|
|
property int options; // <-- FIXME unused
|
|
|
|
|
|
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)
|
|
drivesSelector.onCurrentTextChanged.connect(function(){
|
|
root.dir = helper.pathToUrl(drivesSelector.currentText);
|
|
})
|
|
}
|
|
|
|
Item {
|
|
clip: true
|
|
width: pane.width
|
|
height: pane.height
|
|
anchors.margins: 0
|
|
|
|
Row {
|
|
id: navControls
|
|
anchors {
|
|
top: parent.top
|
|
topMargin: hifi.dimensions.contentMargin.y
|
|
left: parent.left
|
|
}
|
|
spacing: hifi.dimensions.contentSpacing.x
|
|
|
|
// FIXME implement back button
|
|
//VrControls.ButtonAwesome {
|
|
// id: backButton
|
|
// text: "\uf0a8"
|
|
// size: currentDirectory.height
|
|
// enabled: d.backStack.length != 0
|
|
// MouseArea { anchors.fill: parent; onClicked: d.navigateBack() }
|
|
//}
|
|
|
|
GlyphButton {
|
|
id: upButton
|
|
glyph: hifi.glyphs.levelUp
|
|
width: height
|
|
enabled: model.parentFolder && model.parentFolder !== ""
|
|
onClicked: d.navigateUp();
|
|
}
|
|
|
|
GlyphButton {
|
|
id: homeButton
|
|
property var destination: helper.home();
|
|
glyph: hifi.glyphs.home
|
|
width: height
|
|
enabled: d.homeDestination ? true : false
|
|
onClicked: d.navigateHome();
|
|
}
|
|
|
|
ComboBox {
|
|
id: drivesSelector
|
|
width: 62
|
|
model: drives
|
|
visible: drives.length > 1
|
|
currentIndex: 0
|
|
}
|
|
}
|
|
|
|
TextField {
|
|
id: currentDirectory
|
|
property var lastValidFolder: helper.urlToPath(model.folder)
|
|
height: homeButton.height
|
|
anchors {
|
|
top: parent.top
|
|
topMargin: hifi.dimensions.contentMargin.y
|
|
left: navControls.right
|
|
leftMargin: hifi.dimensions.contentSpacing.x
|
|
right: parent.right
|
|
}
|
|
onLastValidFolderChanged: text = lastValidFolder;
|
|
|
|
// FIXME add support auto-completion
|
|
onAccepted: {
|
|
if (!helper.validFolder(text)) {
|
|
text = lastValidFolder;
|
|
return
|
|
}
|
|
model.folder = helper.pathToUrl(text);
|
|
}
|
|
}
|
|
|
|
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: model; onFolderChanged: d.update(); }
|
|
property var homeDestination: helper.home();
|
|
Component.onCompleted: update();
|
|
|
|
function update() {
|
|
var row = fileTableView.currentRow;
|
|
if (row === -1 && root.selectDirectory) {
|
|
currentSelectionUrl = fileTableView.model.folder;
|
|
currentSelectionIsFolder = true;
|
|
return;
|
|
}
|
|
|
|
currentSelectionUrl = fileTableView.model.get(row, "fileURL");
|
|
currentSelectionIsFolder = fileTableView.model.isFolder(row);
|
|
if (root.selectDirectory || !currentSelectionIsFolder) {
|
|
currentSelection.text = helper.urlToPath(currentSelectionUrl);
|
|
} else {
|
|
currentSelection.text = ""
|
|
}
|
|
}
|
|
|
|
function navigateUp() {
|
|
if (model.parentFolder && model.parentFolder !== "") {
|
|
model.folder = model.parentFolder
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function navigateHome() {
|
|
model.folder = homeDestination;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
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: true
|
|
onDoubleClicked: navigateToRow(row);
|
|
focus: true
|
|
Keys.onReturnPressed: navigateToCurrentRow();
|
|
Keys.onEnterPressed: navigateToCurrentRow();
|
|
|
|
model: FolderListModel {
|
|
id: model
|
|
nameFilters: selectionType.currentFilter
|
|
showDirsFirst: true
|
|
showDotAndDotDot: false
|
|
showFiles: !root.selectDirectory
|
|
// For some reason, declaring these bindings directly in the targets doesn't
|
|
// work for setting the initial state
|
|
Component.onCompleted: {
|
|
currentDirectory.lastValidFolder = Qt.binding(function() { return helper.urlToPath(model.folder); });
|
|
upButton.enabled = Qt.binding(function() { return (model.parentFolder && model.parentFolder != "") ? true : false; });
|
|
showFiles = !root.selectDirectory
|
|
}
|
|
onFolderChanged: {
|
|
fileTableView.selection.clear();
|
|
fileTableView.selection.select(0);
|
|
fileTableView.currentRow = 0;
|
|
}
|
|
}
|
|
|
|
onActiveFocusChanged: {
|
|
if (activeFocus && currentRow == -1) {
|
|
fileTableView.selection.select(0)
|
|
}
|
|
}
|
|
|
|
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: fileTableView.model.get(styleData.row, "fileIsDir") ? firaSansSemiBold.name : firaSansRegular.name
|
|
|
|
function getText() {
|
|
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: 0.5 * fileTableView.width
|
|
resizable: true
|
|
}
|
|
TableViewColumn {
|
|
id: fileMofifiedColumn
|
|
role: "fileModified"
|
|
title: "Date"
|
|
width: 0.3 * fileTableView.width
|
|
resizable: true
|
|
}
|
|
TableViewColumn {
|
|
role: "fileSize"
|
|
title: "Size"
|
|
width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width
|
|
resizable: true
|
|
}
|
|
|
|
function navigateToRow(row) {
|
|
currentRow = row;
|
|
navigateToCurrentRow();
|
|
}
|
|
|
|
function navigateToCurrentRow() {
|
|
var row = fileTableView.currentRow
|
|
var isFolder = model.isFolder(row);
|
|
var file = model.get(row, "fileURL");
|
|
if (isFolder) {
|
|
fileTableView.model.folder = 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: "Path:"
|
|
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: !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
|
|
}
|
|
|
|
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: root.saveDialog ? "Save" : (root.selectDirectory ? "Choose" : "Open")
|
|
enabled: currentSelection.text ? true : false
|
|
onTriggered: okActionTimer.start();
|
|
}
|
|
|
|
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,
|
|
buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No,
|
|
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.visible = false; }
|
|
}
|
|
}
|
|
|
|
Keys.onPressed: {
|
|
switch (event.key) {
|
|
case Qt.Key_Backspace:
|
|
event.accepted = d.navigateUp();
|
|
break;
|
|
|
|
case Qt.Key_Home:
|
|
event.accepted = d.navigateHome();
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|