// 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 #include #include #include 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 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 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 toQStringData(std::optional view) { if (view) return view->toString(); return std::nullopt; } static QByteArray toUtf8Data(QUtf8StringView view) { return QByteArray(view.data(), view.size()); } static std::optional toUtf8Data(std::optional 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 std::optional 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 QQmlSignalNames::changedSignalNameToPropertyName(QStringView signalName) { return toQStringData(changedSignalNameToPropertyNameTemplate(signalName)); } std::optional 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 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 static std::optional 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 QQmlSignalNames::handlerNameToSignalName(QStringView handler) { return handlerNameToSignalNameHelper(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 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 QQmlSignalNames::changedHandlerNameToSignalName(QStringView handler) { return handlerNameToSignalNameHelper(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