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 <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
Morten Johan Sørvig 2019-08-12 23:32:44 +02:00
parent 9f082b4e03
commit d46415c0af
5 changed files with 90 additions and 0 deletions

View File

@ -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<void>("set", fileContentView);
emscripten::val contentArray = emscripten::val::array();
contentArray.call<void>("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<emscripten::val>("createObjectURL", contentBlob);
emscripten::val contentLink = document.call<emscripten::val>("createElement", std::string("a"));
contentLink.set("href", contentUrl);
contentLink.set("download", fileNameHint);
contentLink.set("style", "display:none");
emscripten::val body = document["body"];
body.call<void>("appendChild", contentLink);
contentLink.call<void>("click");
body.call<void>("removeChild", contentLink);
window["URL"].call<emscripten::val>("revokeObjectURL", contentUrl);
}
} // namespace QWasmLocalFileAccess
QT_END_NAMESPACE

View File

@ -71,6 +71,8 @@ void openFile(const std::string &accept,
const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
const std::function<void()> &fileDataReady);
void saveFile(const char *content, size_t size, const std::string &fileNameHint);
} // namespace QWasmLocalFileAccess
QT_END_NAMESPACE

View File

@ -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.

View File

@ -284,6 +284,7 @@ public:
static void getOpenFileContent(const QString &nameFilter,
const std::function<void(const QString &, const QByteArray &)> &fileContentsReady);
static void saveFileContent(const QByteArray &fileContent, const QString &fileNameHint = QString());
protected:
QFileDialog(const QFileDialogArgs &args);

View File

@ -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]