diff options
author | Sami Shalayel <sami.shalayel@qt.io> | 2023-07-05 16:53:09 +0200 |
---|---|---|
committer | Sami Shalayel <sami.shalayel@qt.io> | 2023-07-21 17:22:02 +0200 |
commit | 8d3e116068fc3432497f229e10f5b18d183c8f54 (patch) | |
tree | a40038245f45465dc7bdd6689d055dfb56f706d7 /src | |
parent | 6d64967f85efb8f5e79109dd8e60e92a74308e82 (diff) |
QQmlSignalNames: Add implementation for signal name manipulations
Add a set of static helper methods in
src/qml/common/qqmlsignalnames{_p.h,.cpp} to do signal name
manipulations (from signal to signal handler name and back, from
property to property changed signal to property changed handler and
back).
Add tests in tst_qml_common for the helper methods.
ToDo in following commit: replace all implementations of signal name
manipulations out there with the helpers introduced in this commit.
Change-Id: I8e606375839d9eda673da121a60484c5d211f4a0
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/qml/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/qml/common/qqmlsignalnames.cpp | 202 | ||||
-rw-r--r-- | src/qml/common/qqmlsignalnames_p.h | 54 |
3 files changed, 257 insertions, 0 deletions
diff --git a/src/qml/CMakeLists.txt b/src/qml/CMakeLists.txt index cd35596dbc..c907f2b17b 100644 --- a/src/qml/CMakeLists.txt +++ b/src/qml/CMakeLists.txt @@ -128,6 +128,7 @@ qt_internal_add_qml_module(Qml common/qv4staticvalue_p.h common/qv4stringtoarrayindex_p.h common/qqmltranslation.cpp common/qqmltranslation_p.h + common/qqmlsignalnames_p.h common/qqmlsignalnames.cpp compat/removed_api.cpp compiler/qqmlirbuilder.cpp compiler/qqmlirbuilder_p.h compiler/qv4bytecodegenerator.cpp compiler/qv4bytecodegenerator_p.h diff --git a/src/qml/common/qqmlsignalnames.cpp b/src/qml/common/qqmlsignalnames.cpp new file mode 100644 index 0000000000..c08277e7f1 --- /dev/null +++ b/src/qml/common/qqmlsignalnames.cpp @@ -0,0 +1,202 @@ +// Copyright (C) 2023 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 "qqmlsignalnames_p.h" +#include <iterator> +#include <algorithm> +#include <optional> +#include <string> + +QT_BEGIN_NAMESPACE + +using namespace Qt::Literals; + +static std::optional<qsizetype> firstLetterIdx(QStringView name, qsizetype removePrefix = 0, + qsizetype removeSuffix = 0) +{ + auto result = std::find_if(std::next(name.cbegin(), removePrefix), + std::prev(name.cend(), removeSuffix), + [](const QChar &c) { return c.isLetter(); }); + if (result != name.cend()) + return std::distance(name.begin(), result); + + return {}; +} + +static std::optional<QChar> firstLetter(QStringView name, qsizetype removePrefix = 0, + qsizetype removeSuffix = 0) +{ + if (auto idx = firstLetterIdx(name, removePrefix, removeSuffix)) + return name[*idx]; + return {}; +} + +enum ChangeCase { ToUpper, ToLower }; +static void changeCaseOfFirstLetter(QString &name, ChangeCase option, qsizetype removePrefix = 0, + qsizetype removeSuffix = 0) +{ + auto idx = firstLetterIdx(name, removePrefix, removeSuffix); + if (!idx) + return; + + QChar &changeMe = name[*idx]; + changeMe = option == ToUpper ? changeMe.toUpper() : changeMe.toLower(); +}; + +static std::optional<QString> toQStringData(std::optional<QStringView> view) +{ + if (view) + return view->toString(); + return std::nullopt; +} + +static QByteArray toUtf8Data(QUtf8StringView view) +{ + return QByteArray(view.data(), view.size()); +} + +static std::optional<QByteArray> toUtf8Data(std::optional<QUtf8StringView> view) +{ + if (view) + return toUtf8Data(*view); + return std::nullopt; +} + +/*! +\internal +\class QQmlSignalNames + +QQmlSignalNames contains a list of helper methods to manipulate signal names. +Always try to use the most specific one, as combining them might lead to incorrect +results like wrong upper/lower case, for example. +*/ + +/*! +\internal +Concatenate a prefix to a property name and uppercases the first letter of the property name. +*/ +QString QQmlSignalNames::addPrefixToPropertyName(QStringView prefix, QStringView propertyName) +{ + QString result = prefix.toString().append(propertyName); + changeCaseOfFirstLetter(result, ToUpper, prefix.size()); + return result; +} + +QString QQmlSignalNames::propertyNameToChangedSignalName(QStringView property) +{ + return property.toString().append(u"Changed"_s); +} + +QByteArray QQmlSignalNames::propertyNameToChangedSignalName(QUtf8StringView property) +{ + return toUtf8Data(property).append("Changed"_ba); +} + +QString QQmlSignalNames::propertyNameToChangedHandlerName(QStringView property) +{ + return propertyNameToChangedSignalName(signalNameToHandlerName(property)); +} + +template<typename View> +std::optional<View> changedSignalNameToPropertyNameTemplate(View changeSignal) +{ + constexpr qsizetype changedLen = + static_cast<qsizetype>(std::char_traits<char>::length("Changed")); + if (changeSignal.size() < changedLen + || changeSignal.last(changedLen).compare("Changed"_L1) != 0) + return std::nullopt; + + const View result = changeSignal.sliced(0, changeSignal.length() - changedLen); + if (!result.isEmpty()) + return result; + + return {}; +} + +/*! +\internal +Obtain a propertyName from its changed signal handler. +Do not call this on a value obtained from handlerNameToSignalName! Instead use +changedHandlerNameToPropertyName() directly. Otherwise you might end up with a wrong +capitalization of _Changed for "on_Changed", for example. +*/ + +std::optional<QString> QQmlSignalNames::changedSignalNameToPropertyName(QStringView signalName) +{ + return toQStringData(changedSignalNameToPropertyNameTemplate(signalName)); +} +std::optional<QByteArray> +QQmlSignalNames::changedSignalNameToPropertyName(QUtf8StringView signalName) +{ + return toUtf8Data(changedSignalNameToPropertyNameTemplate(signalName)); +} + +/*! +\internal +Returns a property name from \a changedHandler. +This fails for property names starting with an upper-case letter, as it will lower-case it in the +process. +*/ +std::optional<QString> QQmlSignalNames::changedHandlerNameToPropertyName(QStringView handler) +{ + if (!isChangedHandlerName(handler)) + return {}; + + if (auto withoutChangedSuffix = changedSignalNameToPropertyName(handler)) { + return handlerNameToSignalName(*withoutChangedSuffix); + } + return {}; +} + +QString QQmlSignalNames::signalNameToHandlerName(QAnyStringView signal) +{ + QString handlerName; + signal.visit([&handlerName](auto &&s) { handlerName = u"on"_s.append(s); }); + + changeCaseOfFirstLetter(handlerName, ToUpper, strlen("on")); + return handlerName; +} + +/*! +\internal +Returns a signal name from \a handlerName string. +*/ +std::optional<QString> QQmlSignalNames::handlerNameToSignalName(QStringView handler) +{ + if (!isHandlerName(handler)) + return {}; + + QString signalName = handler.sliced(strlen("on")).toString(); + if (signalName.isEmpty()) + return {}; + + changeCaseOfFirstLetter(signalName, ToLower); + return signalName; +} + +bool QQmlSignalNames::isChangedHandlerName(QStringView signalName) +{ + const qsizetype smallestAllowedSize = strlen("onXChanged"); + if (signalName.size() < smallestAllowedSize || !signalName.startsWith(u"on"_s) + || !signalName.endsWith(u"Changed"_s)) + return false; + + if (auto letter = firstLetter(signalName, strlen("on"), strlen("Changed"))) + return letter->isUpper(); + + return true; +} + +bool QQmlSignalNames::isHandlerName(QStringView signalName) +{ + const qsizetype smallestAllowedSize = strlen("onX"); + if (signalName.size() < smallestAllowedSize || !signalName.startsWith(u"on"_s)) + return false; + + if (auto letter = firstLetter(signalName, strlen("on"))) + return letter->isUpper(); + + return true; +} + +QT_END_NAMESPACE diff --git a/src/qml/common/qqmlsignalnames_p.h b/src/qml/common/qqmlsignalnames_p.h new file mode 100644 index 0000000000..7c46a71b3a --- /dev/null +++ b/src/qml/common/qqmlsignalnames_p.h @@ -0,0 +1,54 @@ +// Copyright (C) 2023 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 QQMLSIGNALANDPROPERTYNAMES_P_H +#define QQMLSIGNALANDPROPERTYNAMES_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 <cstddef> +#include <optional> + +#include <QtQml/private/qtqmlglobal_p.h> +#include <QtCore/qstringview.h> +#include <QtCore/qstring.h> +#include <type_traits> + +QT_BEGIN_NAMESPACE + +class Q_QML_EXPORT QQmlSignalNames +{ +public: + static QString propertyNameToChangedSignalName(QStringView property); + static QByteArray propertyNameToChangedSignalName(QUtf8StringView property); + + static QString propertyNameToChangedHandlerName(QStringView property); + + static QString signalNameToHandlerName(QAnyStringView signal); + + static std::optional<QString> changedSignalNameToPropertyName(QStringView changeSignal); + static std::optional<QByteArray> changedSignalNameToPropertyName(QUtf8StringView changeSignal); + + static std::optional<QString> changedHandlerNameToPropertyName(QStringView handler); + static std::optional<QByteArray> changedHandlerNameToPropertyName(QUtf8StringView handler); + + static std::optional<QString> handlerNameToSignalName(QStringView handler); + + static bool isChangedHandlerName(QStringView signalName); + static bool isHandlerName(QStringView signalName); + + static QString addPrefixToPropertyName(QStringView prefix, QStringView propertyName); +}; + +QT_END_NAMESPACE + +#endif // QQMLSIGNALANDPROPERTYNAMES_P_H |