From d46415c0af5c84b5924694a090a0cd70fdb18158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= Date: Mon, 12 Aug 2019 23:32:44 +0200 Subject: wasm: Add saveFileContent() Saves a file by file download, where the user can choose the file name and location using a file dialog. Change-Id: I4d2ecc76fc33bb65fdf3d7ca3fcd9566c62547dd Reviewed-by: Eskil Abrahamsen Blomfeldt --- src/gui/platform/wasm/qwasmlocalfileaccess.cpp | 37 ++++++++++++++++++ src/gui/platform/wasm/qwasmlocalfileaccess_p.h | 2 + src/widgets/dialogs/qfiledialog.cpp | 45 ++++++++++++++++++++++ src/widgets/dialogs/qfiledialog.h | 1 + .../snippets/code/src_gui_dialogs_qfiledialog.cpp | 5 +++ 5 files changed, 90 insertions(+) diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp index 83f9415c69..85c894a74a 100644 --- a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp +++ b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp @@ -164,6 +164,43 @@ void openFile(const std::string &accept, openFiles(accept, FileSelectMode::SingleFile, fileDialogClosedWithInt, acceptFile, fileDataReady); } +void saveFile(const char *content, size_t size, const std::string &fileNameHint) +{ + // Save a file by creating programatically clicking a download + // link to an object url to a Blob containing the file content. + // File content is copied once, so that the passed in content + // buffer can be released as soon as this function returns - we + // don't know for how long the browser will retain the TypedArray + // view used to create the Blob. + + emscripten::val document = emscripten::val::global("document"); + emscripten::val window = emscripten::val::global("window"); + + emscripten::val fileContentView = emscripten::val(emscripten::typed_memory_view(size, content)); + emscripten::val fileContentCopy = emscripten::val::global("ArrayBuffer").new_(size); + emscripten::val fileContentCopyView = emscripten::val::global("Uint8Array").new_(fileContentCopy); + fileContentCopyView.call("set", fileContentView); + + emscripten::val contentArray = emscripten::val::array(); + contentArray.call("push", fileContentCopyView); + emscripten::val type = emscripten::val::object(); + type.set("type","application/octet-stream"); + emscripten::val contentBlob = emscripten::val::global("Blob").new_(contentArray, type); + + emscripten::val contentUrl = window["URL"].call("createObjectURL", contentBlob); + emscripten::val contentLink = document.call("createElement", std::string("a")); + contentLink.set("href", contentUrl); + contentLink.set("download", fileNameHint); + contentLink.set("style", "display:none"); + + emscripten::val body = document["body"]; + body.call("appendChild", contentLink); + contentLink.call("click"); + body.call("removeChild", contentLink); + + window["URL"].call("revokeObjectURL", contentUrl); +} + } // namespace QWasmLocalFileAccess QT_END_NAMESPACE diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h index 794db8d9b2..ccd88570c8 100644 --- a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h +++ b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h @@ -71,6 +71,8 @@ void openFile(const std::string &accept, const std::function &acceptFile, const std::function &fileDataReady); +void saveFile(const char *content, size_t size, const std::string &fileNameHint); + } // namespace QWasmLocalFileAccess QT_END_NAMESPACE diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp index a89192e76f..a1b9003c1c 100644 --- a/src/widgets/dialogs/qfiledialog.cpp +++ b/src/widgets/dialogs/qfiledialog.cpp @@ -2447,6 +2447,51 @@ void QFileDialog::getOpenFileContent(const QString &nameFilter, const std::funct #endif } +/*! + This is a convenience static function that saves \a fileContent to a file, using + a file name and location chosen by the user. \a fileNameHint can be provided to + suggest a file name to the user. + + This function is used to save files to the local file system on Qt for WebAssembly, where + the web sandbox places restrictions on how such access may happen. Its implementation will + make the browser display a native file dialog, where the user makes the file selection. + + It can also be used on other platforms, where it will fall back to using QFileDialog. + + The function is asynchronous and returns immediately. + + \snippet code/src_gui_dialogs_qfiledialog.cpp 16 + \since 5.14 +*/ +void QFileDialog::saveFileContent(const QByteArray &fileContent, const QString &fileNameHint) +{ +#ifdef Q_OS_WASM + QWasmLocalFileAccess::saveFile(fileContent.constData(), fileContent.size(), fileNameHint.toStdString()); +#else + QFileDialog *dialog = new QFileDialog(); + dialog->setAcceptMode(QFileDialog::AcceptSave); + dialog->setFileMode(QFileDialog::AnyFile); + dialog->selectFile(fileNameHint); + + auto fileSelected = [=](const QString &fileName) { + if (!fileName.isNull()) { + QFile selectedFile(fileName); + if (selectedFile.open(QIODevice::WriteOnly)) + selectedFile.write(fileContent); + } + }; + + auto dialogClosed = [=](int code) { + Q_UNUSED(code); + delete dialog; + }; + + connect(dialog, &QFileDialog::fileSelected, fileSelected); + connect(dialog, &QFileDialog::finished, dialogClosed); + dialog->show(); +#endif +} + /*! This is a convenience static function that will return a file name selected by the user. The file does not have to exist. diff --git a/src/widgets/dialogs/qfiledialog.h b/src/widgets/dialogs/qfiledialog.h index 354a1f928b..790f52f2e7 100644 --- a/src/widgets/dialogs/qfiledialog.h +++ b/src/widgets/dialogs/qfiledialog.h @@ -284,6 +284,7 @@ public: static void getOpenFileContent(const QString &nameFilter, const std::function &fileContentsReady); + static void saveFileContent(const QByteArray &fileContent, const QString &fileNameHint = QString()); protected: QFileDialog(const QFileDialogArgs &args); diff --git a/src/widgets/doc/snippets/code/src_gui_dialogs_qfiledialog.cpp b/src/widgets/doc/snippets/code/src_gui_dialogs_qfiledialog.cpp index 39aca459db..7ccd827a04 100644 --- a/src/widgets/doc/snippets/code/src_gui_dialogs_qfiledialog.cpp +++ b/src/widgets/doc/snippets/code/src_gui_dialogs_qfiledialog.cpp @@ -155,3 +155,8 @@ auto fileOpenCompleted = [](const QString &fileName, const QByteArray &fileConte } QFileDialog::getOpenFileContent("Images (*.png *.xpm *.jpg)", fileContentReady); //! [15] + +//! [16] +QByteArray imageData; // obtained from e.g. QImage::save() +QFileDialog::saveFile("myimage.png", imageData); +//! [16] -- cgit v1.2.3