aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/common/qqmlsignalnames.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qml/common/qqmlsignalnames.cpp')
-rw-r--r--src/qml/common/qqmlsignalnames.cpp257
1 files changed, 257 insertions, 0 deletions
diff --git a/src/qml/common/qqmlsignalnames.cpp b/src/qml/common/qqmlsignalnames.cpp
new file mode 100644
index 0000000000..d2a23205a6
--- /dev/null
+++ b/src/qml/common/qqmlsignalnames.cpp
@@ -0,0 +1,257 @@
+// 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 constexpr const QLatin1String On("on");
+static constexpr const QLatin1String Changed("Changed");
+
+static constexpr const qsizetype StrlenOn = On.length();
+static constexpr const qsizetype StrlenChanged = Changed.length();
+
+static std::optional<qsizetype> firstLetterIdx(QStringView name, qsizetype removePrefix = 0,
+ qsizetype removeSuffix = 0)
+{
+ auto end = std::prev(name.cend(), removeSuffix);
+ auto result = std::find_if(std::next(name.cbegin(), removePrefix), end,
+ [](const QChar &c) { return c.isLetter(); });
+ if (result != end)
+ 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(Changed);
+}
+
+QByteArray QQmlSignalNames::propertyNameToChangedSignalName(QUtf8StringView property)
+{
+ return toUtf8Data(property).append(QByteArrayView(Changed));
+}
+
+QString QQmlSignalNames::propertyNameToChangedHandlerName(QStringView property)
+{
+ return propertyNameToChangedSignalName(signalNameToHandlerName(property));
+}
+
+template<typename View>
+std::optional<View> changedSignalNameToPropertyNameTemplate(View changeSignal)
+{
+ const qsizetype changeSignalSize = changeSignal.size();
+ if (changeSignalSize < StrlenChanged || changeSignal.last(StrlenChanged).compare(Changed) != 0)
+ return std::nullopt;
+
+ const View result = changeSignal.sliced(0, changeSignalSize - StrlenChanged);
+ 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;
+ handlerName.reserve(StrlenOn + signal.length());
+ handlerName.append(On);
+
+ signal.visit([&handlerName](auto &&s) { handlerName.append(s); });
+
+ changeCaseOfFirstLetter(handlerName, ToUpper, StrlenOn);
+ return handlerName;
+}
+
+enum HandlerType { ChangedHandler, Handler };
+
+template<HandlerType type>
+static std::optional<QString> handlerNameToSignalNameHelper(QStringView handler)
+{
+ if (!QQmlSignalNames::isHandlerName(handler))
+ return {};
+
+ QString signalName = handler.sliced(StrlenOn).toString();
+ Q_ASSERT(!signalName.isEmpty());
+
+ changeCaseOfFirstLetter(signalName, ToLower, 0, type == ChangedHandler ? StrlenChanged : 0);
+ return signalName;
+}
+
+/*!
+\internal
+Returns a signal name from \a handlerName string. Do not use it on changed handlers, see
+changedHandlerNameToSignalName for that!
+*/
+std::optional<QString> QQmlSignalNames::handlerNameToSignalName(QStringView handler)
+{
+ return handlerNameToSignalNameHelper<Handler>(handler);
+}
+
+/*!
+\internal
+Returns a signal name from \a handlerName string. Do not use it on changed handlers, see
+changedHandlerNameToSignalName for that! Accepts improperly capitalized handler names and
+incorrectly resolves signal names that start with '_' or '$'.
+*/
+std::optional<QString> QQmlSignalNames::badHandlerNameToSignalName(QStringView handler)
+{
+ if (handler.size() <= StrlenOn || !handler.startsWith(On))
+ return {};
+
+ QString signalName = handler.sliced(StrlenOn).toString();
+
+ // This is quite wrong. But we need it for backwards compatibility.
+ signalName.front() = signalName.front().toLower();
+
+ return signalName;
+}
+
+/*!
+\internal
+Returns a signal name from \a changedHandlerName string. Makes sure not to lowercase the 'C' from
+Changed.
+*/
+std::optional<QString> QQmlSignalNames::changedHandlerNameToSignalName(QStringView handler)
+{
+ return handlerNameToSignalNameHelper<ChangedHandler>(handler);
+}
+
+bool QQmlSignalNames::isChangedSignalName(QStringView signalName)
+{
+ if (signalName.size() <= StrlenChanged || !signalName.endsWith(Changed))
+ return false;
+
+ if (auto letter = firstLetter(signalName, 0, StrlenChanged))
+ return letter->isLower();
+
+ return true;
+}
+
+bool QQmlSignalNames::isChangedHandlerName(QStringView signalName)
+{
+ if (signalName.size() <= (StrlenOn + StrlenChanged)
+ || !signalName.startsWith(On)
+ || !signalName.endsWith(Changed)) {
+ return false;
+ }
+
+ if (auto letter = firstLetter(signalName, StrlenOn, StrlenChanged))
+ return letter->isUpper();
+
+ return true;
+}
+
+bool QQmlSignalNames::isHandlerName(QStringView signalName)
+{
+ if (signalName.size() <= StrlenOn || !signalName.startsWith(On))
+ return false;
+
+ if (auto letter = firstLetter(signalName, StrlenOn))
+ return letter->isUpper();
+
+ return true;
+}
+
+QT_END_NAMESPACE