Support for directory browsing

This commit is contained in:
Brad Davis 2016-01-22 00:31:05 -08:00
parent 2026821d9e
commit c7186590ea
11 changed files with 364 additions and 294 deletions

View file

@ -6,11 +6,19 @@ import Qt.labs.settings 1.0
import ".."
import "../windows"
import "../styles"
import "../controls" as VrControls
import "fileDialog"
// Work in progress....
//FIXME implement shortcuts for favorite location
ModalWindow {
id: root
HifiConstants { id: hifi }
property bool selectDirectory: false;
property bool showHidden: false;
// FIXME implement
property bool multiSelect: false;
// FIXME implement
property bool saveDialog: false;
signal selectedFile(var file);
signal canceled();
@ -18,149 +26,144 @@ ModalWindow {
width: 640
height: 480
property string settingsName: ""
property alias folder: folderModel.folder
property var helper: fileDialogHelper
property alias model: fileTableView.model
property alias filterModel: selectionType.model
property alias folder: model.folder
Rectangle {
anchors.fill: parent
color: "white"
Settings {
// fixme, combine with a property to allow different saved locations
category: "FileOpenLastFolder." + settingsName
property alias folder: folderModel.folder
Row {
id: navControls
anchors { left: parent.left; top: parent.top; margins: 8 }
spacing: 8
// FIXME implement back button
// VrControls.FontAwesome {
// id: backButton
// text: "\uf0a8"
// size: currentDirectory.height
// enabled: d.backStack.length != 0
// MouseArea { anchors.fill: parent; onClicked: d.navigateBack() }
// }
VrControls.FontAwesome {
id: upButton
text: "\uf0aa"
size: currentDirectory.height
color: enabled ? "black" : "gray"
MouseArea { anchors.fill: parent; onClicked: d.navigateUp() }
}
VrControls.FontAwesome {
id: homeButton
property var destination: helper.home();
visible: destination ? true : false
text: "\uf015"
size: currentDirectory.height
MouseArea { anchors.fill: parent; onClicked: model.folder = parent.destination }
}
}
TextField {
id: currentDirectory
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 8
readOnly: true
text: folderModel.folder
anchors { left: navControls.right; right: parent.right; top: parent.top; margins: 8 }
property var lastValidFolder: helper.urlToPath(model.folder)
onLastValidFolderChanged: text = lastValidFolder;
// FIXME add support auto-completion
onAccepted: {
if (!helper.validFolder(text)) {
text = lastValidFolder;
return
}
model.folder = helper.pathToUrl(text);
}
}
Component {
id: fileItemDelegate
Item {
clip: true
Text {
x: 3
id: columnText
anchors.verticalCenter: parent.verticalCenter
// font.pointSize: 12
color: tableView.activeFocus && styleData.row === tableView.currentRow ? "yellow" : styleData.textColor
elide: styleData.elideMode
text: getText();
font.italic: folderModel.get(styleData.row, "fileIsDir") ? true : false
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(); }
Component.onCompleted: update();
function update() {
var row = fileTableView.currentRow;
if (row === -1 && root.selectDirectory) {
currentSelectionUrl = fileTableView.model.folder;
currentSelectionIsFolder = true;
return;
}
Connections {
target: tableView
//onCurrentRowChanged: columnText.color = (tableView.activeFocus && styleData.row === tableView.currentRow ? "yellow" : styleData.textColor)
}
currentSelectionUrl = fileTableView.model.get(row, "fileURL");
currentSelectionIsFolder = fileTableView.model.isFolder(row);
if (root.selectDirectory || !currentSelectionIsFolder) {
currentSelection.text = helper.urlToPath(currentSelectionUrl);
}
}
function getText() {
switch (styleData.column) {
//case 1: return Date.fromLocaleString(locale, styleData.value, "yyyy-MM-dd hh:mm:ss");
case 2: return folderModel.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];
}
function navigateUp() {
if (model.parentFolder && model.parentFolder !== "") {
model.folder = model.parentFolder
return true;
}
}
}
TableView {
id: tableView
focus: true
FileTableView {
id: fileTableView
anchors { left: parent.left; right: parent.right; top: currentDirectory.bottom; bottom: currentSelection.top; margins: 8 }
onDoubleClicked: navigateToRow(row);
model: FolderListModel {
id: folderModel
showDotAndDotDot: true
id: model
showDirsFirst: true
onFolderChanged: {
tableView.currentRow = -1;
tableView.positionViewAtRow(0, ListView.Beginning)
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
}
}
anchors.top: currentDirectory.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: selectionType.top
anchors.margins: 8
itemDelegate: fileItemDelegate
// rowDelegate: Rectangle {
// id: rowDelegate
// color: styleData.selected ? "#7f0000ff" : "#00000000"
// Connections { target: folderModel; onFolderChanged: rowDelegate.visible = false }
// Connections { target: tableView; onCurrentRowChanged: rowDelegate.visible = true; }
// }
TableViewColumn {
role: "fileName"
title: "Name"
width: 400
}
TableViewColumn {
role: "fileModified"
title: "Date Modified"
width: 200
}
TableViewColumn {
role: "fileSize"
title: "Size"
width: 200
function navigateToRow(row) {
currentRow = row;
navigateToCurrentRow();
}
Keys.onReturnPressed: selectCurrentFile();
onDoubleClicked: { currentRow = row; selectCurrentFile(); }
onCurrentRowChanged: currentSelection.text = model.get(currentRow, "fileName");
KeyNavigation.left: cancelButton
KeyNavigation.right: selectionType
function navigateToCurrentRow() {
var row = fileTableView.currentRow
var isFolder = model.isFolder(row);
var file = model.get(row, "fileURL");
if (isFolder) {
fileTableView.model.folder = file
currentRow = -1;
} else {
root.selectedFile(file);
root.close();
}
}
}
TextField {
id: currentSelection
anchors.right: selectionType.left
anchors.rightMargin: 8
anchors.left: parent.left
anchors.leftMargin: 8
anchors.top: selectionType.top
anchors { right: root.selectDirectory ? parent.right : selectionType.left; rightMargin: 8; left: parent.left; leftMargin: 8; top: selectionType.top }
readOnly: true
}
ComboBox {
id: selectionType
anchors.bottom: buttonRow.top
anchors.bottomMargin: 8
anchors.right: parent.right
anchors.rightMargin: 8
anchors.left: buttonRow.left
model: ListModel {
ListElement { text: "All Files (*.*)"; filter: "*.*" }
}
onCurrentIndexChanged: {
folderModel.nameFilters = [ filterModel.get(currentIndex).filter ]
}
KeyNavigation.left: tableView
anchors { bottom: buttonRow.top; bottomMargin: 8; right: parent.right; rightMargin: 8; left: buttonRow.left }
visible: !selectDirectory
model: ListModel { ListElement { text: "All Files (*.*)"; filter: "*.*" } }
// onCurrentIndexChanged: model.nameFilters = [ filterModel.get(currentIndex).filter ]
KeyNavigation.left: fileTableView
KeyNavigation.right: openButton
}
@ -177,16 +180,16 @@ ModalWindow {
text: "Cancel"
KeyNavigation.up: selectionType
KeyNavigation.left: openButton
KeyNavigation.right: tableView.contentItem
KeyNavigation.right: fileTableView.contentItem
Keys.onReturnPressed: { canceled(); root.enabled = false }
onClicked: { canceled(); close() }
}
Button {
id: openButton
text: "Open"
enabled: tableView.currentRow != -1 && !folderModel.get(tableView.currentRow, "fileIsDir")
onClicked: selectCurrentFile();
Keys.onReturnPressed: selectCurrentFile();
text: root.selectDirectory ? "Choose" : "Open"
enabled: currentSelection.text ? true : false
onClicked: { selectedFile(d.currentSelectionUrl); close(); }
Keys.onReturnPressed: { selectedFile(d.currentSelectionUrl); close(); }
KeyNavigation.up: selectionType
KeyNavigation.left: selectionType
@ -195,26 +198,8 @@ ModalWindow {
}
}
function selectCurrentFile() {
var row = tableView.currentRow
console.log("Selecting row " + row)
var fileName = folderModel.get(row, "fileName");
if (fileName === "..") {
folderModel.folder = folderModel.parentFolder
} else if (folderModel.isFolder(row)) {
folderModel.folder = folderModel.get(row, "fileURL");
} else {
selectedFile(folderModel.get(row, "fileURL"));
close();
}
}
Keys.onPressed: {
if (event.key === Qt.Key_Backspace && folderModel.parentFolder && folderModel.parentFolder != "") {
console.log("Navigating to " + folderModel.parentFolder)
folderModel.folder = folderModel.parentFolder
if (event.key === Qt.Key_Backspace && d.navigateUp()) {
event.accepted = true
}
}

View file

@ -0,0 +1,59 @@
import QtQuick 2.0
import QtQuick.Controls 1.4
TableView {
id: root
itemDelegate: Component {
Item {
clip: true
Text {
x: 3
anchors.verticalCenter: parent.verticalCenter
color: root.activeFocus && styleData.row === root.currentRow ? "yellow" : styleData.textColor
elide: styleData.elideMode
text: getText();
font.italic: root.model.get(styleData.row, "fileIsDir") ? true : false
function getText() {
switch (styleData.column) {
//case 1: return Date.fromLocaleString(locale, styleData.value, "yyyy-MM-dd hh:mm:ss");
case 2: return root.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 {
role: "fileName"
title: "Name"
width: 400
}
TableViewColumn {
role: "fileModified"
title: "Date Modified"
width: 200
}
TableViewColumn {
role: "fileSize"
title: "Size"
width: 200
}
}

View file

@ -0,0 +1,33 @@
import QtQuick 2.5
import QtQuick.Controls 1.4 as Original
import "."
Preference {
id: root
property alias spinner: spinner
height: spinner.height
Component.onCompleted: {
spinner.value = preference.value;
}
function save() {
preference.value = spinner.value;
preference.save();
}
Text {
text: root.label
anchors.verticalCenter: spinner.verticalCenter
}
Original.SpinBox {
id: spinner
decimals: preference.decimals
minimumValue: preference.min
maximumValue: preference.max
width: 100
anchors { right: parent.right }
}
}

View file

@ -0,0 +1,40 @@
//
// OffscreenUi.cpp
// interface/src/render-utils
//
// Created by Bradley Austin Davis on 2015-04-04
// 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
//
#include "FileDialogHelper.h"
#include <QtCore/QDir>
#include <QtCore/QFile>
QUrl FileDialogHelper::home() {
return pathToUrl(QStandardPaths::standardLocations(QStandardPaths::HomeLocation)[0]);
}
QStringList FileDialogHelper::standardPath(StandardLocation location) {
return QStandardPaths::standardLocations(static_cast<QStandardPaths::StandardLocation>(location));
}
QString FileDialogHelper::urlToPath(const QUrl& url) {
return url.toLocalFile();
}
bool FileDialogHelper::validPath(const QString& path) {
return QFile(path).exists();
}
bool FileDialogHelper::validFolder(const QString& path) {
return QDir(path).exists();
}
QUrl FileDialogHelper::pathToUrl(const QString& path) {
return QUrl::fromLocalFile(path);
}

View file

@ -0,0 +1,57 @@
//
// Created by Bradley Austin Davis on 2016-01-21
// 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
//
#pragma once
#ifndef hifi_ui_FileDialogHelper_h
#define hifi_ui_FileDialogHelper_h
#include <QtCore/QObject>
#include <QtCore/QStandardPaths>
#include <QtCore/QUrl>
#include <QtCore/QString>
#include <QtCore/QStringList>
class FileDialogHelper : public QObject {
Q_OBJECT
Q_ENUMS(StandardLocation)
public:
// Do not re-order, must match QDesktopServices
enum StandardLocation {
DesktopLocation,
DocumentsLocation,
FontsLocation,
ApplicationsLocation,
MusicLocation,
MoviesLocation,
PicturesLocation,
TempLocation,
HomeLocation,
DataLocation,
CacheLocation,
GenericDataLocation,
RuntimeLocation,
ConfigLocation,
DownloadLocation,
GenericCacheLocation,
GenericConfigLocation,
AppDataLocation,
AppConfigLocation,
AppLocalDataLocation = DataLocation
};
Q_INVOKABLE QUrl home();
Q_INVOKABLE QStringList standardPath(StandardLocation location);
Q_INVOKABLE QString urlToPath(const QUrl& url);
Q_INVOKABLE bool validPath(const QString& path);
Q_INVOKABLE bool validFolder(const QString& path);
Q_INVOKABLE QUrl pathToUrl(const QString& path);
};
#endif

View file

@ -17,8 +17,10 @@
#include <AbstractUriHandler.h>
#include <AccountManager.h>
#include "FileDialogHelper.h"
#include "VrMenu.h"
// Needs to match the constants in resources/qml/Global.js
class OffscreenFlags : public QObject {
Q_OBJECT
@ -74,7 +76,6 @@ public:
}
};
static UrlHandler * urlHandler { nullptr };
static OffscreenFlags* offscreenFlags { nullptr };
// This hack allows the QML UI to work with keys that are also bound as
@ -102,10 +103,9 @@ void OffscreenUi::create(QOpenGLContext* context) {
OffscreenQmlSurface::create(context);
auto rootContext = getRootContext();
offscreenFlags = new OffscreenFlags();
rootContext->setContextProperty("offscreenFlags", offscreenFlags);
urlHandler = new UrlHandler();
rootContext->setContextProperty("urlHandler", urlHandler);
rootContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());
rootContext->setContextProperty("urlHandler", new UrlHandler());
rootContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
}
void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {

View file

@ -5,6 +5,11 @@ import QtQuick.Controls 1.4
// This is useful for testing inside Qt creator where these services don't actually exist.
Item {
Item {
objectName: "offscreenFlags"
property bool navigationFocused: false
}
Item {
objectName: "urlHandler"
function fixupUrl(url) { return url; }

View file

@ -16,34 +16,18 @@ ApplicationWindow {
height: 720
title: qsTr("Scratch App")
Component { id: listModelBuilder; ListModel{} }
function menuItemsToModel(menu) {
var items = menu.items
var newListModel = listModelBuilder.createObject(desktop);
for (var i = 0; i < items.length; ++i) {
var item = items[i];
switch (item.type) {
case 2:
newListModel.append({"type":item.type, "name": item.title, "item": item})
break;
case 1:
newListModel.append({"type":item.type, "name": item.text, "item": item})
break;
case 0:
newListModel.append({"type":item.type, "name": "-----", "item": item})
break;
}
}
return newListModel;
}
Desktop {
id: desktop
anchors.fill: parent
StubMenu { id: stubMenu }
rootMenu: StubMenu { id: rootMenu }
Component.onCompleted: offscreenWindow = appWindow
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: desktop.popupMenu(Qt.vector2d(mouseX, mouseY));
}
Row {
id: testButtons
anchors { margins: 8; left: parent.left; top: parent.top }
@ -52,6 +36,7 @@ ApplicationWindow {
property var tabs: [];
property var urls: [];
/*
Button {
text: "restore all"
onClicked: {
@ -72,6 +57,7 @@ ApplicationWindow {
blue.enabled = !blue.enabled
}
}
*/
Button {
text: "Show Long Error"
onClicked: {
@ -93,21 +79,13 @@ ApplicationWindow {
}
}
Button {
text: "Open File"
text: "Open Directory"
property var builder: Component {
FileDialog { }
FileDialog { selectDirectory: true }
}
ListModel {
id: jsFilters
ListElement { text: "Javascript Files (*.js)"; filter: "*.js" }
ListElement { text: "All Files (*.*)"; filter: "*.*" }
}
onClicked: {
var fileDialogProperties = {
filterModel: jsFilters
}
var fileDialog = builder.createObject(desktop, fileDialogProperties);
var fileDialog = builder.createObject(desktop);
fileDialog.canceled.connect(function(){
console.log("Cancelled")
})
@ -116,24 +94,31 @@ ApplicationWindow {
})
}
}
Button {
text: "Focus Test"
text: "Open File"
property var builder: Component {
FileDialog {
folder: "file:///C:/users/bdavis";
filterModel: ListModel {
ListElement { text: "Javascript Files (*.js)"; filter: "*.js" }
ListElement { text: "All Files (*.*)"; filter: "*.*" }
}
}
}
onClicked: {
var item = desktop;
while (item) {
console.log(item);
item = item.parent;
}
item = appWindow
while (item) {
console.log(item);
item = item.parent;
}
console.log(appWindow.activeFocusItem);
var fileDialog = builder.createObject(desktop);
fileDialog.canceled.connect(function(){
console.log("Cancelled")
})
fileDialog.selectedFile.connect(function(file){
console.log("Selected " + file)
})
}
}
}
/*
Window {
id: blue
closable: true
@ -189,109 +174,6 @@ ApplicationWindow {
color: "yellow"
}
}
*/
}
/*
Arcane.Test {
anchors.centerIn: parent
height: 600; width: 600
}
Item {
id: desktop
anchors.fill: parent
objectName: Desktop._OFFSCREEN_ROOT_OBJECT_NAME
property bool uiVisible: true
property variant toolbars: { "_root" : null }
focus: true
Rectangle {
id: root
Vr.Constants { id: vr }
implicitWidth: 384; implicitHeight: 640
anchors.centerIn: parent
color: vr.windows.colors.background
border.color: vr.controls.colors.background
border.width: vr.styles.borderWidth
radius: vr.styles.borderRadius
RunningScripts { }
}
FileDialog {
id: fileDialog
width: 800; height: 600
anchors.centerIn: parent
onSelectedFile: console.log("Chose file " + file)
}
Timer {
id: timer
running: false
interval: 100
onTriggered: wireFrameContainer.enabled = true
}
Item {
id: wireFrameContainer
objectName: Desktop._OFFSCREEN_DIALOG_OBJECT_NAME
anchors.fill: parent
onEnabledChanged: if (!enabled) timer.running = true
NewUi.Main {
id: wireFrame
anchors.fill: parent
property var offscreenFlags: Item {
property bool navigationFocused: false
}
property var urlHandler: Item {
function fixupUrl(url) {
var urlString = url.toString();
if (urlString.indexOf("https://metaverse.highfidelity.com/") !== -1 &&
urlString.indexOf("access_token") === -1) {
console.log("metaverse URL, fixing")
return urlString + "?access_token=875885020b1d5f1ea694ce971c8601fa33ffd77f61851be01ed1e3fde8cabbe9"
}
return url
}
function canHandleUrl(url) {
var urlString = url.toString();
if (urlString.indexOf("hifi://") === 0) {
console.log("Can handle hifi addresses: " + urlString)
return true;
}
if (urlString.indexOf(".svo.json?") !== -1) {
console.log("Can handle svo json addresses: " + urlString)
return true;
}
if (urlString.indexOf(".js?") !== -1) {
console.log("Can handle javascript addresses: " + urlString)
return true;
}
return false
}
function handleUrl(url) {
return true
}
}
property var addressManager: Item {
function navigate(url) {
console.log("Navigate to: " + url);
}
}
}
}
Keys.onMenuPressed: desktop.uiVisible = !desktop.uiVisible
Keys.onEscapePressed: desktop.uiVisible = !desktop.uiVisible
}
*/
}

View file

@ -4,7 +4,8 @@ QT += gui qml quick xml webengine widgets
CONFIG += c++11
SOURCES += src/main.cpp
SOURCES += src/main.cpp \
../../libraries/ui/src/FileDialogHelper.cpp
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
@ -96,4 +97,10 @@ DISTFILES += \
../../interface/resources/qml/hifi/dialogs/preferences/Slider.qml \
../../interface/resources/qml/hifi/dialogs/preferences/Preference.qml \
../../interface/resources/qml/hifi/dialogs/preferences/SpinBox.qml \
../../interface/resources/qml/hifi/dialogs/preferences/CheckBox.qml
../../interface/resources/qml/hifi/dialogs/preferences/CheckBox.qml \
../../interface/resources/qml/dialogs/fileDialog/FileTableView.qml \
../../interface/resources/qml/hifi/dialogs/preferences/Avatar.qml \
../../interface/resources/qml/hifi/dialogs/preferences/AvatarBrowser.qml
HEADERS += \
../../libraries/ui/src/FileDialogHelper.h

View file

@ -3,6 +3,7 @@
#include <QtWebEngine>
#include <QFileSystemModel>
#include "../../../libraries/ui/src/FileDialogHelper.h"
class Preference : public QObject {
@ -82,13 +83,14 @@ int main(int argc, char *argv[]) {
addImportPath(engine, "../../../interface/resources/qml");
engine.load(QUrl(QStringLiteral("qml/Stubs.qml")));
setChild(engine, "rootMenu");
setChild(engine, "offscreenFlags");
setChild(engine, "Account");
setChild(engine, "Desktop");
setChild(engine, "ScriptDiscoveryService");
setChild(engine, "MenuHelper");
setChild(engine, "urlHandler");
engine.rootContext()->setContextProperty("DebugQML", true);
engine.rootContext()->setContextProperty("fileDialogHelper", new FileDialogHelper());
//engine.load(QUrl(QStringLiteral("qrc:/qml/gallery/main.qml")));
engine.load(QUrl(QStringLiteral("qml/main.qml")));