mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 04:24:07 +02:00
Support save file dialog
This commit is contained in:
parent
8f85abfec8
commit
60d97c45af
7 changed files with 186 additions and 34 deletions
|
@ -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);
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue