Support save file dialog

This commit is contained in:
Bradley Austin Davis 2016-02-03 14:49:50 -08:00
parent 8f85abfec8
commit 60d97c45af
7 changed files with 186 additions and 34 deletions

View file

@ -262,11 +262,10 @@ FocusScope {
}
Component { id: fileDialogBuilder; FileDialog { } }
function fileOpenDialog(properties) {
function fileDialog(properties) {
return fileDialogBuilder.createObject(desktop, properties);
}
MenuMouseHandler { id: menuPopperUpper }
function popupMenu(point) {
menuPopperUpper.popup(desktop, rootMenu.items, point);

View file

@ -3,6 +3,7 @@ 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 "../windows"
@ -39,7 +40,6 @@ ModalWindow {
property bool showHidden: false;
// FIXME implement
property bool multiSelect: false;
// FIXME implement
property bool saveDialog: false;
property var helper: fileDialogHelper
property alias model: fileTableView.model
@ -124,6 +124,8 @@ ModalWindow {
currentSelectionIsFolder = fileTableView.model.isFolder(row);
if (root.selectDirectory || !currentSelectionIsFolder) {
currentSelection.text = helper.urlToPath(currentSelectionUrl);
} else {
currentSelection.text = ""
}
}
@ -175,8 +177,7 @@ ModalWindow {
if (isFolder) {
fileTableView.model.folder = file
} else {
root.selectedFile(file);
root.destroy();
okAction.trigger();
}
}
}
@ -185,8 +186,10 @@ ModalWindow {
id: currentSelection
style: TextFieldStyle { renderType: Text.QtRendering }
anchors { right: root.selectDirectory ? parent.right : selectionType.left; rightMargin: 8; left: parent.left; leftMargin: 8; top: selectionType.top }
readOnly: true
activeFocusOnTab: false
readOnly: !root.saveDialog
activeFocusOnTab: !readOnly
onActiveFocusChanged: if (activeFocus) { selectAll(); }
onAccepted: okAction.trigger();
}
FileTypeSelection {
@ -206,25 +209,82 @@ ModalWindow {
spacing: 8
Button {
id: openButton
text: root.selectDirectory ? "Choose" : "Open"
enabled: currentSelection.text ? true : false
onClicked: { selectedFile(d.currentSelectionUrl); root.visible = false; }
Keys.onReturnPressed: { selectedFile(d.currentSelectionUrl); root.visible = false; }
action: okAction
Keys.onReturnPressed: okAction.trigger()
KeyNavigation.up: selectionType
KeyNavigation.left: selectionType
KeyNavigation.right: cancelButton
}
Button {
id: cancelButton
text: "Cancel"
action: cancelAction
KeyNavigation.up: selectionType
KeyNavigation.left: openButton
KeyNavigation.right: fileTableView.contentItem
Keys.onReturnPressed: { canceled(); root.enabled = false }
onClicked: { canceled(); root.visible = false; }
}
}
Action {
id: okAction
text: root.saveDialog ? "Save" : (root.selectDirectory ? "Choose" : "Open")
enabled: currentSelection.text ? true : false
onTriggered: {
if (root.saveDialog) {
// 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;
}
}
selectedFile(d.currentSelectionUrl);
root.destroy()
} else {
selectedFile(d.currentSelectionUrl);
root.destroy()
}
}
}
Action {
id: cancelAction
text: "Cancel"
onTriggered: { canceled(); root.visible = false; }
}
}
Keys.onPressed: {

View file

@ -25,6 +25,10 @@ ModalWindow {
destroy();
}
function exec() {
return OffscreenUi.waitForMessageBoxResult(root);
}
property alias detailedText: detailedText.text
property alias text: mainTextContainer.text
property alias informativeText: informativeTextContainer.text

View file

@ -12,6 +12,8 @@
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QDebug>
#include <QtCore/QRegularExpression>
QUrl FileDialogHelper::home() {
@ -26,7 +28,11 @@ QString FileDialogHelper::urlToPath(const QUrl& url) {
return url.toLocalFile();
}
bool FileDialogHelper::validPath(const QString& path) {
bool FileDialogHelper::fileExists(const QString& path) {
return QFile(path).exists();
}
bool FileDialogHelper::validPath(const QString& path) {
return QFile(path).exists();
}
@ -38,3 +44,47 @@ QUrl FileDialogHelper::pathToUrl(const QString& path) {
return QUrl::fromLocalFile(path);
}
QUrl FileDialogHelper::saveHelper(const QString& saveText, const QUrl& currentFolder, const QStringList& selectionFilters) {
qDebug() << "Calling save helper with " << saveText << " " << currentFolder << " " << selectionFilters;
QFileInfo fileInfo(saveText);
// Check if it's a relative path and if it is resolve to the absolute path
{
if (fileInfo.isRelative()) {
fileInfo = QFileInfo(currentFolder.toLocalFile() + "/" + fileInfo.filePath());
}
}
// Check if we need to append an extension, but only if the current resolved path isn't a directory
if (!fileInfo.isDir()) {
QString fileName = fileInfo.fileName();
if (!fileName.contains(".") && selectionFilters.size() == 1) {
const QRegularExpression extensionRe{ ".*(\\.[a-zA-Z0-9]+)$" };
QString filter = selectionFilters[0];
auto match = extensionRe.match(filter);
if (match.hasMatch()) {
fileInfo = QFileInfo(fileInfo.filePath() + match.captured(1));
}
}
}
return QUrl::fromLocalFile(fileInfo.absoluteFilePath());
}
bool FileDialogHelper::urlIsDir(const QUrl& url) {
return QFileInfo(url.toLocalFile()).isDir();
}
bool FileDialogHelper::urlIsFile(const QUrl& url) {
return QFileInfo(url.toLocalFile()).isFile();
}
bool FileDialogHelper::urlExists(const QUrl& url) {
return QFileInfo(url.toLocalFile()).exists();
}
bool FileDialogHelper::urlIsWritable(const QUrl& url) {
return QFileInfo(url.toLocalFile()).isWritable();
}

View file

@ -48,9 +48,15 @@ public:
Q_INVOKABLE QUrl home();
Q_INVOKABLE QStringList standardPath(StandardLocation location);
Q_INVOKABLE QString urlToPath(const QUrl& url);
Q_INVOKABLE bool urlIsDir(const QUrl& url);
Q_INVOKABLE bool urlIsFile(const QUrl& url);
Q_INVOKABLE bool urlExists(const QUrl& url);
Q_INVOKABLE bool urlIsWritable(const QUrl& url);
Q_INVOKABLE bool fileExists(const QString& path);
Q_INVOKABLE bool validPath(const QString& path);
Q_INVOKABLE bool validFolder(const QString& path);
Q_INVOKABLE QUrl pathToUrl(const QString& path);
Q_INVOKABLE QUrl saveHelper(const QString& saveText, const QUrl& currentFolder, const QStringList& selectionFilters);
};

View file

@ -105,6 +105,7 @@ void OffscreenUi::create(QOpenGLContext* context) {
OffscreenQmlSurface::create(context);
auto rootContext = getRootContext();
rootContext->setContextProperty("OffscreenUi", this);
rootContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());
rootContext->setContextProperty("urlHandler", new UrlHandler());
rootContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
@ -219,7 +220,7 @@ QQuickItem* OffscreenUi::createMessageBox(QMessageBox::Icon icon, const QString&
return qvariant_cast<QQuickItem*>(result);
}
QMessageBox::StandardButton OffscreenUi::waitForMessageBoxResult(QQuickItem* messageBox) {
int OffscreenUi::waitForMessageBoxResult(QQuickItem* messageBox) {
if (!messageBox) {
return QMessageBox::NoButton;
}
@ -241,7 +242,7 @@ QMessageBox::StandardButton OffscreenUi::messageBox(QMessageBox::Icon icon, cons
return result;
}
return waitForMessageBoxResult(createMessageBox(icon, title, text, buttons, defaultButton));
return static_cast<QMessageBox::StandardButton>(waitForMessageBoxResult(createMessageBox(icon, title, text, buttons, defaultButton)));
}
QMessageBox::StandardButton OffscreenUi::critical(const QString& title, const QString& text,
@ -478,6 +479,26 @@ private slots:
}
};
QString OffscreenUi::fileDialog(const QVariantMap& properties) {
QVariant buildDialogResult;
bool invokeResult = QMetaObject::invokeMethod(_desktop, "fileDialog",
Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties)));
if (!invokeResult) {
qWarning() << "Failed to create file open dialog";
return QString();
}
QVariant result = FileDialogListener(qvariant_cast<QQuickItem*>(buildDialogResult)).waitForResult();
if (!result.isValid()) {
return QString();
}
qDebug() << result.toString();
return result.toUrl().toLocalFile();
}
QString OffscreenUi::fileOpenDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
if (QThread::currentThread() != thread()) {
QString result;
@ -497,23 +518,31 @@ QString OffscreenUi::fileOpenDialog(const QString& caption, const QString& dir,
map.insert("dir", QUrl::fromLocalFile(dir));
map.insert("filter", filter);
map.insert("options", static_cast<int>(options));
return fileDialog(map);
}
QVariant buildDialogResult;
bool invokeResult = QMetaObject::invokeMethod(_desktop, "fileOpenDialog",
Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(map)));
if (!invokeResult) {
qWarning() << "Failed to create file open dialog";
return QString();
QString OffscreenUi::fileSaveDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
if (QThread::currentThread() != thread()) {
QString result;
QMetaObject::invokeMethod(this, "fileSaveDialog", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QString, result),
Q_ARG(QString, caption),
Q_ARG(QString, dir),
Q_ARG(QString, filter),
Q_ARG(QString*, selectedFilter),
Q_ARG(QFileDialog::Options, options));
return result;
}
QVariant result = FileDialogListener(qvariant_cast<QQuickItem*>(buildDialogResult)).waitForResult();
if (!result.isValid()) {
return QString();
}
qDebug() << result.toString();
return result.toUrl().toLocalFile();
// FIXME support returning the selected filter... somehow?
QVariantMap map;
map.insert("caption", caption);
map.insert("dir", QUrl::fromLocalFile(dir));
map.insert("filter", filter);
map.insert("options", static_cast<int>(options));
map.insert("saveDialog", true);
return fileDialog(map);
}
QString OffscreenUi::getOpenFileName(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
@ -521,7 +550,7 @@ QString OffscreenUi::getOpenFileName(void* ignored, const QString &caption, cons
}
QString OffscreenUi::getSaveFileName(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
return QFileDialog::getSaveFileName((QWidget*)ignored, caption, dir, filter, selectedFilter, options);
return DependencyManager::get<OffscreenUi>()->fileSaveDialog(caption, dir, filter, selectedFilter, options);
}

View file

@ -47,7 +47,7 @@ public:
// Must be called from the main thread
QQuickItem* createMessageBox(QMessageBox::Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton);
// Must be called from the main thread
QMessageBox::StandardButton waitForMessageBoxResult(QQuickItem* messageBox);
Q_INVOKABLE int waitForMessageBoxResult(QQuickItem* messageBox);
/// Same design as QMessageBox::critical(), will block, returns result
static QMessageBox::StandardButton critical(void* ignored, const QString& title, const QString& text,
@ -87,10 +87,12 @@ public:
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
// file dialog compatibility
Q_INVOKABLE QString fileOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QString fileSaveDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
// Compatibility with QFileDialog::getOpenFileName
static QString getOpenFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
// Compatibility with QFileDialog::getSaveFileName
static QString getSaveFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
@ -105,6 +107,8 @@ public:
static QString getItem(void *ignored, const QString & title, const QString & label, const QStringList & items, int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags flags = 0, Qt::InputMethodHints inputMethodHints = Qt::ImhNone);
private:
QString fileDialog(const QVariantMap& properties);
QQuickItem* _desktop { nullptr };
QQuickItem* _toolWindow { nullptr };
};