From a50590370f56723511f5b8104c2be8a3f5470a9d Mon Sep 17 00:00:00 2001 From: Mikolaj Boc Date: Wed, 22 Jun 2022 14:33:48 +0200 Subject: Create the Qt File Filter => showOpen/SaveFilePicker options mapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As a preparatory measure for using showXFilePicker, the Qt file filter has to be transformed to the format used by the showXFilePicker (sXFP) options. A class structure reflecting the options was created. Based on an input in the form of a qt file filter, it will parse the filter to the sXFP options format. Unit tests were added and the code is not yet used in non-test env, next change will use it. Task-number: QTBUG-99611 Change-Id: I277286467a7b5ce6f323c19bdd31740a41b6a6be Reviewed-by: Morten Johan Sørvig --- src/gui/platform/wasm/qlocalfileapi.cpp | 197 ++++++++++++++++++++++++++++++++ src/gui/platform/wasm/qlocalfileapi_p.h | 87 ++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 src/gui/platform/wasm/qlocalfileapi.cpp create mode 100644 src/gui/platform/wasm/qlocalfileapi_p.h (limited to 'src/gui/platform') diff --git a/src/gui/platform/wasm/qlocalfileapi.cpp b/src/gui/platform/wasm/qlocalfileapi.cpp new file mode 100644 index 0000000000..ec7ae40391 --- /dev/null +++ b/src/gui/platform/wasm/qlocalfileapi.cpp @@ -0,0 +1,197 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qlocalfileapi_p.h" +#include +#include + +QT_BEGIN_NAMESPACE +namespace LocalFileApi { +namespace { +std::optional qtFilterListToTypes(const QStringList &filterList) +{ + using namespace qstdweb; + using namespace emscripten; + + auto types = emscripten::val::array(); + + for (const auto &fileFilter : filterList) { + auto type = Type::fromQt(fileFilter); + if (type) + types.call("push", type->asVal()); + } + + return types["length"].as() == 0 ? std::optional() : types; +} +} + +Type::Type(QStringView description, std::optional accept) + : m_storage(emscripten::val::object()) +{ + m_storage.set("description", description.trimmed().toString().toStdString()); + if (accept) + m_storage.set("accept", accept->asVal()); +} + +Type::~Type() = default; + +std::optional Type::fromQt(QStringView type) +{ + using namespace emscripten; + + // 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("(?:(?:([^(]*)\\(([^()]+)\\)[^)]*)|([^()]+))"))); + const auto match = regex.match(type); + + if (!match.hasMatch()) + return std::nullopt; + + constexpr size_t DescriptionIndex = 1; + constexpr size_t FilterListFromParensIndex = 2; + constexpr size_t PlainFilterListIndex = 3; + + const auto description = match.hasCaptured(DescriptionIndex) + ? match.capturedView(DescriptionIndex) + : QStringView(); + const auto filterList = match.capturedView(match.hasCaptured(FilterListFromParensIndex) + ? FilterListFromParensIndex + : PlainFilterListIndex); + + auto accept = Type::Accept::fromQt(filterList); + if (!accept) + return std::nullopt; + + return Type(description, std::move(*accept)); +} + +emscripten::val Type::asVal() const +{ + return m_storage; +} + +Type::Accept::Accept() : m_storage(emscripten::val::object()) { } + +Type::Accept::~Accept() = default; + +std::optional Type::Accept::fromQt(QStringView qtRepresentation) +{ + Accept accept; + + // Used for accepting multiple extension specifications on a filter list. + // The next group of non-empty characters. + static QRegularExpression internalRegex(QString(QStringLiteral("([^\\s]+)\\s*"))); + int offset = 0; + auto internalMatch = internalRegex.match(qtRepresentation, offset); + MimeType mimeType; + + while (internalMatch.hasMatch()) { + auto webExtension = MimeType::Extension::fromQt(internalMatch.capturedView(1)); + + if (!webExtension) + return std::nullopt; + + mimeType.addExtension(*webExtension); + + internalMatch = internalRegex.match(qtRepresentation, internalMatch.capturedEnd()); + } + + accept.addMimeType(mimeType); + return accept; +} + +void Type::Accept::addMimeType(MimeType mimeType) +{ + // The mime type provided here does not seem to have any effect at the result at all. + m_storage.set("application/octet-stream", mimeType.asVal()); +} + +emscripten::val Type::Accept::asVal() const +{ + return m_storage; +} + +Type::Accept::MimeType::MimeType() : m_storage(emscripten::val::array()) { } + +Type::Accept::MimeType::~MimeType() = default; + +void Type::Accept::MimeType::addExtension(Extension extension) +{ + m_storage.call("push", extension.asVal()); +} + +emscripten::val Type::Accept::MimeType::asVal() const +{ + return m_storage; +} + +Type::Accept::MimeType::Extension::Extension(QStringView extension) + : m_storage(extension.toString().toStdString()) +{ +} + +Type::Accept::MimeType::Extension::~Extension() = default; + +std::optional +Type::Accept::MimeType::Extension::fromQt(QStringView qtRepresentation) +{ + // 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(qtRepresentation).hasMatch()) + return std::nullopt; + + // 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(qtRepresentation); + if (extensionMatch.hasMatch()) + return Extension(extensionMatch.capturedView(2)); + + // Mapping impossible. + return std::nullopt; +} + +emscripten::val Type::Accept::MimeType::Extension::asVal() const +{ + return m_storage; +} + +emscripten::val makeOpenFileOptions(const QStringList &filterList, bool acceptMultiple) +{ + auto options = emscripten::val::object(); + if (auto typeList = LocalFileApi::qtFilterListToTypes(filterList)) { + options.set("types", std::move(*typeList)); + options.set("excludeAcceptAllOption", true); + } + + options.set("multiple", acceptMultiple); + + return options; +} + +emscripten::val makeSaveFileOptions(const QStringList &filterList, const std::string& suggestedName) +{ + auto options = emscripten::val::object(); + + if (!suggestedName.empty()) + options.set("suggestedName", emscripten::val(suggestedName)); + + if (auto typeList = LocalFileApi::qtFilterListToTypes(filterList)) + options.set("types", emscripten::val(std::move(*typeList))); + + return options; +} + +} // namespace LocalFileApi + +QT_END_NAMESPACE diff --git a/src/gui/platform/wasm/qlocalfileapi_p.h b/src/gui/platform/wasm/qlocalfileapi_p.h new file mode 100644 index 0000000000..a8e7f666f9 --- /dev/null +++ b/src/gui/platform/wasm/qlocalfileapi_p.h @@ -0,0 +1,87 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QLOCALFILEAPI_P_H +#define QLOCALFILEAPI_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace LocalFileApi { +class Q_CORE_EXPORT Type { +public: + class Accept { + public: + class MimeType { + public: + class Extension { + public: + static std::optional fromQt(QStringView extension); + + ~Extension(); + + emscripten::val asVal() const; + + private: + explicit Extension(QStringView extension); + + emscripten::val m_storage; + }; + + MimeType(); + ~MimeType(); + + void addExtension(Extension type); + + emscripten::val asVal() const; + + private: + emscripten::val m_storage; + }; + + static std::optional fromQt(QStringView type); + + ~Accept(); + + void addMimeType(MimeType mimeType); + + emscripten::val asVal() const; + + private: + Accept(); + emscripten::val m_storage; + }; + + Type(QStringView description, std::optional accept); + ~Type(); + + static std::optional fromQt(QStringView type); + emscripten::val asVal() const; + +private: + emscripten::val m_storage; +}; + +Q_CORE_EXPORT emscripten::val makeOpenFileOptions(const QStringList &filterList, bool acceptMultiple); +Q_CORE_EXPORT emscripten::val makeSaveFileOptions(const QStringList &filterList, const std::string& suggestedName); + +} // namespace LocalFileApi +QT_END_NAMESPACE + +#endif // QLOCALFILEAPI_P_H -- cgit v1.2.3