aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/common
diff options
context:
space:
mode:
authorSami Shalayel <sami.shalayel@qt.io>2023-07-05 16:53:09 +0200
committerSami Shalayel <sami.shalayel@qt.io>2023-07-21 17:22:02 +0200
commit8d3e116068fc3432497f229e10f5b18d183c8f54 (patch)
treea40038245f45465dc7bdd6689d055dfb56f706d7 /src/qml/common
parent6d64967f85efb8f5e79109dd8e60e92a74308e82 (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/qml/common')
-rw-r--r--src/qml/common/qqmlsignalnames.cpp202
-rw-r--r--src/qml/common/qqmlsignalnames_p.h54
2 files changed, 256 insertions, 0 deletions
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