From fbf2a318dd3f1e0f9fc8dadb68d47923dff3d672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20S=C3=B8rvig?= Date: Fri, 16 Dec 2022 15:22:32 +0100 Subject: wasm: align QWasmLocalFileAccess API with 6.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This API is undocumented, but by keeping unchanged makes it easier to use from external projects. openFile(): Copy the accept/filter list parsing function from 6.4. It makes sense to implement this behind the API, so that user code doesn't have to reimplement it. saveFile(): Slightly more complicated; the new variant which takes a QByteArray is better since the implementation can then keep a reference to the data for as long as it needs, without copying the data. Add the const char * variant to keep existing code going for now. Adjust the calling code in widgets. Pick-to: 6.5 Change-Id: I1899ebffdb90e40429dcb10313ccc5334f20c34f Reviewed-by: MikoĊ‚aj Boc Reviewed-by: Lorn Potter --- src/gui/platform/wasm/qwasmlocalfileaccess.cpp | 89 ++++++++++++++++++++++++-- src/gui/platform/wasm/qwasmlocalfileaccess_p.h | 5 +- 2 files changed, 86 insertions(+), 8 deletions(-) (limited to 'src/gui/platform') diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp index 47deb0d553..72f1dcc339 100644 --- a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp +++ b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp @@ -9,6 +9,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE namespace QWasmLocalFileAccess { @@ -132,15 +134,76 @@ void readFiles(const qstdweb::FileList &fileList, (*readFile)(0); } + +QStringList acceptListFromQtFormat(const std::string &qtAcceptList) +{ + // copy of qt_make_filter_list() from qfiledialog.cpp + auto make_filter_list = [](const QString &filter) -> QStringList + { + if (filter.isEmpty()) + return QStringList(); + + QString sep(";;"); + if (!filter.contains(sep) && filter.contains(u'\n')) + sep = u'\n'; + + return filter.split(sep); + }; + + const QStringList fileFilter = make_filter_list(QString::fromStdString(qtAcceptList)); + QStringList transformed; + for (const auto &element : fileFilter) { + // Accepts either a string in format: + // GROUP3 + // or in this format: + // GROUP1 (GROUP2) + // Group 1 is treated as the description, whereas group 2 or 3 are treated as the filter + // list. + static QRegularExpression regex( + QString(QStringLiteral("(?:([^(]*)\\(([^()]+)\\)[^)]*)|([^()]+)"))); + static QRegularExpression wordCharacterRegex(QString(QStringLiteral("\\w"))); + const auto match = regex.match(element); + + if (!match.hasMatch()) + continue; + + constexpr size_t FilterListFromParensIndex = 2; + constexpr size_t PlainFilterListIndex = 3; + QString filterList = match.captured(match.hasCaptured(FilterListFromParensIndex) + ? FilterListFromParensIndex + : PlainFilterListIndex); + for (auto singleExtension : filterList.split(QStringLiteral(" "), Qt::SkipEmptyParts)) { + // Checks for a filter that matches everything: + // Any number of asterisks or any number of asterisks with a '.' between them. + // The web filter does not support wildcards. + static QRegularExpression qtAcceptAllRegex(QRegularExpression::anchoredPattern( + QString(QStringLiteral("[*]+|[*]+\\.[*]+")))); + if (qtAcceptAllRegex.match(singleExtension).hasMatch()) + continue; + + // Checks for correctness. The web filter only allows filename extensions and does not + // filter the actual filenames, therefore we check whether the filter provided only + // filters for the extension. + static QRegularExpression qtFilenameMatcherRegex(QRegularExpression::anchoredPattern( + QString(QStringLiteral("(\\*?)(\\.[^*]+)")))); + + auto extensionMatch = qtFilenameMatcherRegex.match(singleExtension); + if (extensionMatch.hasMatch()) + transformed.append(extensionMatch.captured(2)); + } + } + return transformed; +} + } -void downloadDataAsFile(const QByteArray &data, const std::string &fileNameHint) +void downloadDataAsFile(const char *content, size_t size, const std::string &fileNameHint) { // 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()); + qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(content, size); emscripten::val document = emscripten::val::global("document"); emscripten::val window = qstdweb::window(); emscripten::val contentUrl = window["URL"].call("createObjectURL", contentBlob.val()); @@ -157,12 +220,12 @@ void downloadDataAsFile(const QByteArray &data, const std::string &fileNameHint) window["URL"].call("revokeObjectURL", contentUrl); } -void openFiles(const QStringList &accept, FileSelectMode fileSelectMode, +void openFiles(const std::string &accept, FileSelectMode fileSelectMode, const std::function &fileDialogClosed, const std::function &acceptFile, const std::function &fileDataReady) { - FileDialog::showOpen(accept, fileSelectMode, { + FileDialog::showOpen(acceptListFromQtFormat(accept), fileSelectMode, { .thenFunc = [=](emscripten::val result) { auto files = qstdweb::FileList(result); fileDialogClosed(files.length()); @@ -174,7 +237,7 @@ void openFiles(const QStringList &accept, FileSelectMode fileSelectMode, }); } -void openFile(const QStringList &accept, +void openFile(const std::string &accept, const std::function &fileDialogClosed, const std::function &acceptFile, const std::function &fileDataReady) @@ -220,7 +283,7 @@ void saveDataToFileInChunks(emscripten::val fileHandle, const QByteArray &data) void saveFile(const QByteArray &data, const std::string &fileNameHint) { if (!FileDialog::canShowSave()) { - downloadDataAsFile(data, fileNameHint); + downloadDataAsFile(data.constData(), data.size(), fileNameHint); return; } @@ -231,6 +294,20 @@ void saveFile(const QByteArray &data, const std::string &fileNameHint) }); } +void saveFile(const char *content, size_t size, const std::string &fileNameHint) +{ + if (!FileDialog::canShowSave()) { + downloadDataAsFile(content, size, fileNameHint); + return; + } + + FileDialog::showSave(fileNameHint, { + .thenFunc = [=](emscripten::val result) { + saveDataToFileInChunks(result, QByteArray(content, size)); + }, + }); +} + } // namespace QWasmLocalFileAccess QT_END_NAMESPACE diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h index 040fe3c47a..77b14577f7 100644 --- a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h +++ b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h @@ -25,17 +25,18 @@ namespace QWasmLocalFileAccess { enum class FileSelectMode { SingleFile, MultipleFiles }; -Q_CORE_EXPORT void openFiles(const QStringList &accept, FileSelectMode fileSelectMode, +Q_CORE_EXPORT void openFiles(const std::string &accept, FileSelectMode fileSelectMode, const std::function &fileDialogClosed, const std::function &acceptFile, const std::function &fileDataReady); -Q_CORE_EXPORT void openFile(const QStringList &accept, +Q_CORE_EXPORT void openFile(const std::string &accept, const std::function &fileDialogClosed, const std::function &acceptFile, const std::function &fileDataReady); Q_CORE_EXPORT void saveFile(const QByteArray &data, const std::string &fileNameHint); +Q_CORE_EXPORT void saveFile(const char *content, size_t size, const std::string &fileNameHint); } // namespace QWasmLocalFileAccess -- cgit v1.2.3