diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/corelib/platform/wasm/qstdweb.cpp | 3 | ||||
-rw-r--r-- | src/corelib/platform/wasm/qstdweb_p.h | 8 | ||||
-rw-r--r-- | src/gui/platform/wasm/qwasmlocalfileaccess.cpp | 216 | ||||
-rw-r--r-- | src/gui/platform/wasm/qwasmlocalfileaccess_p.h | 12 | ||||
-rw-r--r-- | src/widgets/dialogs/qfiledialog.cpp | 10 |
5 files changed, 184 insertions, 65 deletions
diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp index 43cd079e9d..486bfaf485 100644 --- a/src/corelib/platform/wasm/qstdweb.cpp +++ b/src/corelib/platform/wasm/qstdweb.cpp @@ -451,7 +451,8 @@ File FileList::operator[](int index) const return item(index); } -emscripten::val FileList::val() { +emscripten::val FileList::val() const +{ return m_fileList; } diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h index b4b8948b3a..70f58cb85c 100644 --- a/src/corelib/platform/wasm/qstdweb_p.h +++ b/src/corelib/platform/wasm/qstdweb_p.h @@ -91,7 +91,7 @@ namespace qstdweb { int length() const; File item(int index) const; File operator[](int index) const; - emscripten::val val(); + emscripten::val val() const; private: emscripten::val m_fileList = emscripten::val::undefined(); @@ -183,6 +183,12 @@ namespace qstdweb { void all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks); }; + + inline emscripten::val window() + { + static emscripten::val savedWindow = emscripten::val::global("window"); + return savedWindow; + } } QT_END_NAMESPACE diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp index 172c8f6814..1b797be9fe 100644 --- a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp +++ b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qwasmlocalfileaccess_p.h" +#include "qlocalfileapi_p.h" #include <private/qstdweb_p.h> #include <emscripten.h> #include <emscripten/bind.h> @@ -11,10 +12,98 @@ QT_BEGIN_NAMESPACE namespace QWasmLocalFileAccess { +namespace FileDialog { +namespace { +bool hasLocalFilesApi() +{ + return !qstdweb::window()["showOpenFilePicker"].isUndefined(); +} + +void showOpenViaHTMLPolyfill(const QStringList &accept, FileSelectMode fileSelectMode, + qstdweb::PromiseCallbacks onFilesSelected) +{ + // Create file input html element which will display a native file dialog + // and call back to our onchange handler once the user has selected + // one or more files. + emscripten::val document = emscripten::val::global("document"); + emscripten::val input = document.call<emscripten::val>("createElement", std::string("input")); + input.set("type", "file"); + input.set("style", "display:none"); + // input.set("accept", emscripten::val(accept)); + Q_UNUSED(accept); + input.set("multiple", emscripten::val(fileSelectMode == FileSelectMode::MultipleFiles)); + + // Note: there is no event in case the user cancels the file dialog. + static std::unique_ptr<qstdweb::EventCallback> changeEvent; + auto callback = [=](emscripten::val) { onFilesSelected.thenFunc(input["files"]); }; + changeEvent = std::make_unique<qstdweb::EventCallback>(input, "change", callback); + + // Activate file input + emscripten::val body = document["body"]; + body.call<void>("appendChild", input); + input.call<void>("click"); + body.call<void>("removeChild", input); +} + +void showOpenViaLocalFileApi(const QStringList &accept, FileSelectMode fileSelectMode, + qstdweb::PromiseCallbacks callbacks) +{ + using namespace qstdweb; + + auto options = LocalFileApi::makeOpenFileOptions(accept, fileSelectMode == FileSelectMode::MultipleFiles); + + Promise::make( + window(), QStringLiteral("showOpenFilePicker"), + { + .thenFunc = [=](emscripten::val fileHandles) mutable { + std::vector<emscripten::val> filePromises; + filePromises.reserve(fileHandles["length"].as<int>()); + for (int i = 0; i < fileHandles["length"].as<int>(); ++i) + filePromises.push_back(fileHandles[i].call<emscripten::val>("getFile")); + Promise::all(std::move(filePromises), callbacks); + }, + .catchFunc = callbacks.catchFunc, + .finallyFunc = callbacks.finallyFunc, + }, std::move(options)); +} + +void showSaveViaLocalFileApi(const std::string &fileNameHint, qstdweb::PromiseCallbacks callbacks) +{ + using namespace qstdweb; + using namespace emscripten; + + auto options = LocalFileApi::makeSaveFileOptions(QStringList(), fileNameHint); + + Promise::make( + window(), QStringLiteral("showSaveFilePicker"), + std::move(callbacks), std::move(options)); +} +} // namespace + +void showOpen(const QStringList &accept, FileSelectMode fileSelectMode, + qstdweb::PromiseCallbacks callbacks) +{ + hasLocalFilesApi() ? + showOpenViaLocalFileApi(accept, fileSelectMode, std::move(callbacks)) : + showOpenViaHTMLPolyfill(accept, fileSelectMode, std::move(callbacks)); +} + +bool canShowSave() +{ + return hasLocalFilesApi(); +} + +void showSave(const std::string &fileNameHint, qstdweb::PromiseCallbacks callbacks) +{ + Q_ASSERT(canShowSave()); + showSaveViaLocalFileApi(fileNameHint, std::move(callbacks)); +} +} // namespace FileDialog +namespace { void readFiles(const qstdweb::FileList &fileList, - const std::function<char *(uint64_t size, const std::string name)> &acceptFile, - const std::function<void ()> &fileDataReady) + const std::function<char *(uint64_t size, const std::string name)> &acceptFile, + const std::function<void ()> &fileDataReady) { auto readFile = std::make_shared<std::function<void(int)>>(); @@ -25,7 +114,7 @@ void readFiles(const qstdweb::FileList &fileList, return; } - const qstdweb::File file = fileList[fileIndex]; + const qstdweb::File file = qstdweb::File(fileList[fileIndex]); // Ask caller if the file should be accepted char *buffer = acceptFile(file.size(), file.name()); @@ -43,74 +132,103 @@ void readFiles(const qstdweb::FileList &fileList, (*readFile)(0); } +} -typedef std::function<void (const qstdweb::FileList &fileList)> OpenFileDialogCallback; -void openFileDialog(const std::string &accept, FileSelectMode fileSelectMode, - const OpenFileDialogCallback &filesSelected) +void downloadDataAsFile(const QByteArray &data, const std::string &fileNameHint) { - // Create file input html element which will display a native file dialog - // and call back to our onchange handler once the user has selected - // one or more files. + // Save a file by creating programmatically clicking a download + // link to an object url to a Blob containing a copy of the file + // content. The copy is made so that the passed in content buffer + // can be released as soon as this function returns. + qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(data.constData(), data.size()); emscripten::val document = emscripten::val::global("document"); - emscripten::val input = document.call<emscripten::val>("createElement", std::string("input")); - input.set("type", "file"); - input.set("style", "display:none"); - input.set("accept", emscripten::val(accept)); - input.set("multiple", emscripten::val(fileSelectMode == MultipleFiles)); - - // Note: there is no event in case the user cancels the file dialog. - static std::unique_ptr<qstdweb::EventCallback> changeEvent; - auto callback = [=](emscripten::val) { filesSelected(qstdweb::FileList(input["files"])); }; - changeEvent.reset(new qstdweb::EventCallback(input, "change", callback)); + emscripten::val window = qstdweb::window(); + emscripten::val contentUrl = window["URL"].call<emscripten::val>("createObjectURL", contentBlob.val()); + emscripten::val contentLink = document.call<emscripten::val>("createElement", std::string("a")); + contentLink.set("href", contentUrl); + contentLink.set("download", fileNameHint); + contentLink.set("style", "display:none"); - // Activate file input emscripten::val body = document["body"]; - body.call<void>("appendChild", input); - input.call<void>("click"); - body.call<void>("removeChild", input); + body.call<void>("appendChild", contentLink); + contentLink.call<void>("click"); + body.call<void>("removeChild", contentLink); + + window["URL"].call<emscripten::val>("revokeObjectURL", contentUrl); } -void openFiles(const std::string &accept, FileSelectMode fileSelectMode, +void openFiles(const QStringList &accept, FileSelectMode fileSelectMode, const std::function<void (int fileCount)> &fileDialogClosed, - const std::function<char *(uint64_t size, const std::string name)> &acceptFile, + const std::function<char *(uint64_t size, const std::string& name)> &acceptFile, const std::function<void()> &fileDataReady) { - openFileDialog(accept, fileSelectMode, [=](const qstdweb::FileList &files) { - fileDialogClosed(files.length()); - readFiles(files, acceptFile, fileDataReady); + FileDialog::showOpen(accept, fileSelectMode, { + .thenFunc = [=](emscripten::val result) { + auto files = qstdweb::FileList(result); + fileDialogClosed(files.length()); + readFiles(files, acceptFile, fileDataReady); + }, + .catchFunc = [=](emscripten::val) { + fileDialogClosed(0); + } }); } -void openFile(const std::string &accept, +void openFile(const QStringList &accept, const std::function<void (bool fileSelected)> &fileDialogClosed, - const std::function<char *(uint64_t size, const std::string name)> &acceptFile, + const std::function<char *(uint64_t size, const std::string& name)> &acceptFile, const std::function<void()> &fileDataReady) { auto fileDialogClosedWithInt = [=](int fileCount) { fileDialogClosed(fileCount != 0); }; openFiles(accept, FileSelectMode::SingleFile, fileDialogClosedWithInt, acceptFile, fileDataReady); } -void saveFile(const char *content, size_t size, const std::string &fileNameHint) +void saveDataToFileInChunks(emscripten::val fileHandle, const QByteArray &data) { - // Save a file by creating programmatically clicking a download - // link to an object url to a Blob containing a copy of the file - // content. The copy is made so that the passed in content buffer - // can be released as soon as this function returns. - qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(content, size); - emscripten::val document = emscripten::val::global("document"); - emscripten::val window = emscripten::val::global("window"); - emscripten::val contentUrl = window["URL"].call<emscripten::val>("createObjectURL", contentBlob.val()); - 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); + using namespace emscripten; + using namespace qstdweb; + + Promise::make(fileHandle, QStringLiteral("createWritable"), { + .thenFunc = [=](val writable) { + struct State { + size_t written; + std::function<void(val result)> continuation; + }; + + auto state = std::make_shared<State>(); + state->written = 0u; + state->continuation = [=](val) mutable { + const size_t remaining = data.size() - state->written; + if (remaining == 0) { + Promise::make(writable, QStringLiteral("close"), { .thenFunc = [=](val) {} }); + state.reset(); + return; + } + static constexpr size_t desiredChunkSize = 1024u; + const auto currentChunkSize = std::min(remaining, desiredChunkSize); + Promise::make(writable, QStringLiteral("write"), { + .thenFunc = state->continuation, + }, val(typed_memory_view(currentChunkSize, data.constData() + state->written))); + state->written += currentChunkSize; + }; + + state->continuation(val::undefined()); + }, + }); +} - window["URL"].call<emscripten::val>("revokeObjectURL", contentUrl); +void saveFile(const QByteArray &data, const std::string &fileNameHint) +{ + if (!FileDialog::canShowSave()) { + downloadDataAsFile(data, fileNameHint); + return; + } + + FileDialog::showSave(fileNameHint, { + .thenFunc = [=](emscripten::val result) { + saveDataToFileInChunks(result, data); + }, + }); } } // namespace QWasmLocalFileAccess diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h index eb73463759..040fe3c47a 100644 --- a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h +++ b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h @@ -23,19 +23,19 @@ QT_BEGIN_NAMESPACE namespace QWasmLocalFileAccess { -enum FileSelectMode { SingleFile, MultipleFiles }; +enum class FileSelectMode { SingleFile, MultipleFiles }; -Q_CORE_EXPORT void openFiles(const std::string &accept, FileSelectMode fileSelectMode, +Q_CORE_EXPORT void openFiles(const QStringList &accept, FileSelectMode fileSelectMode, const std::function<void (int fileCount)> &fileDialogClosed, - const std::function<char *(uint64_t size, const std::string name)> &acceptFile, + const std::function<char *(uint64_t size, const std::string& name)> &acceptFile, const std::function<void()> &fileDataReady); -Q_CORE_EXPORT void openFile(const std::string &accept, +Q_CORE_EXPORT void openFile(const QStringList &accept, const std::function<void (bool fileSelected)> &fileDialogClosed, - const std::function<char *(uint64_t size, const std::string name)> &acceptFile, + const std::function<char *(uint64_t size, const std::string& name)> &acceptFile, const std::function<void()> &fileDataReady); -Q_CORE_EXPORT void saveFile(const char *content, size_t size, const std::string &fileNameHint); +Q_CORE_EXPORT void saveFile(const QByteArray &data, const std::string &fileNameHint); } // namespace QWasmLocalFileAccess diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp index 966d86b089..885d56c5bb 100644 --- a/src/widgets/dialogs/qfiledialog.cpp +++ b/src/widgets/dialogs/qfiledialog.cpp @@ -2304,13 +2304,7 @@ void QFileDialog::getOpenFileContent(const QString &nameFilter, const std::funct openFileImpl.reset(); }; - auto qtFilterStringToWebAcceptString = [](const QString &qtString) { - // The Qt and Web name filter string formats are similar, but - // not identical. - return qtString.toStdString(); // ### TODO - }; - - QWasmLocalFileAccess::openFile(qtFilterStringToWebAcceptString(nameFilter), fileDialogClosed, acceptFile, fileContentReady); + QWasmLocalFileAccess::openFile(qt_make_filter_list(nameFilter), fileDialogClosed, acceptFile, fileContentReady); }; (*openFileImpl)(); @@ -2359,7 +2353,7 @@ void QFileDialog::getOpenFileContent(const QString &nameFilter, const std::funct void QFileDialog::saveFileContent(const QByteArray &fileContent, const QString &fileNameHint) { #ifdef Q_OS_WASM - QWasmLocalFileAccess::saveFile(fileContent.constData(), fileContent.size(), fileNameHint.toStdString()); + QWasmLocalFileAccess::saveFile(fileContent, fileNameHint.toStdString()); #else QFileDialog *dialog = new QFileDialog(); dialog->setAcceptMode(QFileDialog::AcceptSave); |