1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qqmlsignalnames_p.h"
5#include <iterator>
6#include <algorithm>
7#include <optional>
8#include <string>
9
10QT_BEGIN_NAMESPACE
11
12using namespace Qt::Literals;
13
14static constexpr const QLatin1String On("on");
15static constexpr const QLatin1String Changed("Changed");
16
17static constexpr const qsizetype StrlenOn = On.length();
18static constexpr const qsizetype StrlenChanged = Changed.length();
19
20static std::optional<qsizetype> firstLetterIdx(QStringView name, qsizetype removePrefix = 0,
21 qsizetype removeSuffix = 0)
22{
23 auto end = std::prev(x: name.cend(), n: removeSuffix);
24 auto result = std::find_if(first: std::next(x: name.cbegin(), n: removePrefix), last: end,
25 pred: [](const QChar &c) { return c.isLetter(); });
26 if (result != end)
27 return std::distance(first: name.begin(), last: result);
28
29 return {};
30}
31
32static std::optional<QChar> firstLetter(QStringView name, qsizetype removePrefix = 0,
33 qsizetype removeSuffix = 0)
34{
35 if (auto idx = firstLetterIdx(name, removePrefix, removeSuffix))
36 return name[*idx];
37 return {};
38}
39
40enum ChangeCase { ToUpper, ToLower };
41static void changeCaseOfFirstLetter(QString &name, ChangeCase option, qsizetype removePrefix = 0,
42 qsizetype removeSuffix = 0)
43{
44 auto idx = firstLetterIdx(name, removePrefix, removeSuffix);
45 if (!idx)
46 return;
47
48 QChar &changeMe = name[*idx];
49 changeMe = option == ToUpper ? changeMe.toUpper() : changeMe.toLower();
50};
51
52static std::optional<QString> toQStringData(std::optional<QStringView> view)
53{
54 if (view)
55 return view->toString();
56 return std::nullopt;
57}
58
59static QByteArray toUtf8Data(QUtf8StringView view)
60{
61 return QByteArray(view.data(), view.size());
62}
63
64static std::optional<QByteArray> toUtf8Data(std::optional<QUtf8StringView> view)
65{
66 if (view)
67 return toUtf8Data(view: *view);
68 return std::nullopt;
69}
70
71/*!
72\internal
73\class QQmlSignalNames
74
75QQmlSignalNames contains a list of helper methods to manipulate signal names.
76Always try to use the most specific one, as combining them might lead to incorrect
77results like wrong upper/lower case, for example.
78*/
79
80/*!
81\internal
82Concatenate a prefix to a property name and uppercases the first letter of the property name.
83*/
84QString QQmlSignalNames::addPrefixToPropertyName(QStringView prefix, QStringView propertyName)
85{
86 QString result = prefix.toString().append(v: propertyName);
87 changeCaseOfFirstLetter(name&: result, option: ToUpper, removePrefix: prefix.size());
88 return result;
89}
90
91QString QQmlSignalNames::propertyNameToChangedSignalName(QStringView property)
92{
93 return property.toString().append(s: Changed);
94}
95
96QByteArray QQmlSignalNames::propertyNameToChangedSignalName(QUtf8StringView property)
97{
98 return toUtf8Data(view: property).append(a: QByteArrayView(Changed));
99}
100
101QString QQmlSignalNames::propertyNameToChangedHandlerName(QStringView property)
102{
103 return propertyNameToChangedSignalName(property: signalNameToHandlerName(signal: property));
104}
105
106template<typename View>
107std::optional<View> changedSignalNameToPropertyNameTemplate(View changeSignal)
108{
109 const qsizetype changeSignalSize = changeSignal.size();
110 if (changeSignalSize < StrlenChanged || changeSignal.last(StrlenChanged).compare(Changed) != 0)
111 return std::nullopt;
112
113 const View result = changeSignal.sliced(0, changeSignalSize - StrlenChanged);
114 if (!result.isEmpty())
115 return result;
116
117 return {};
118}
119
120/*!
121\internal
122Obtain a propertyName from its changed signal handler.
123Do not call this on a value obtained from handlerNameToSignalName! Instead use
124changedHandlerNameToPropertyName() directly. Otherwise you might end up with a wrong
125capitalization of _Changed for "on_Changed", for example.
126*/
127
128std::optional<QString> QQmlSignalNames::changedSignalNameToPropertyName(QStringView signalName)
129{
130 return toQStringData(view: changedSignalNameToPropertyNameTemplate(changeSignal: signalName));
131}
132std::optional<QByteArray>
133QQmlSignalNames::changedSignalNameToPropertyName(QUtf8StringView signalName)
134{
135 return toUtf8Data(view: changedSignalNameToPropertyNameTemplate(changeSignal: signalName));
136}
137
138/*!
139\internal
140Returns a property name from \a changedHandler.
141This fails for property names starting with an upper-case letter, as it will lower-case it in the
142process.
143*/
144std::optional<QString> QQmlSignalNames::changedHandlerNameToPropertyName(QStringView handler)
145{
146 if (!isChangedHandlerName(signalName: handler))
147 return {};
148
149 if (auto withoutChangedSuffix = changedSignalNameToPropertyName(signalName: handler)) {
150 return handlerNameToSignalName(handler: *withoutChangedSuffix);
151 }
152 return {};
153}
154
155QString QQmlSignalNames::signalNameToHandlerName(QAnyStringView signal)
156{
157 QString handlerName;
158 handlerName.reserve(asize: StrlenOn + signal.length());
159 handlerName.append(s: On);
160
161 signal.visit(v: [&handlerName](auto &&s) { handlerName.append(s); });
162
163 changeCaseOfFirstLetter(name&: handlerName, option: ToUpper, removePrefix: StrlenOn);
164 return handlerName;
165}
166
167enum HandlerType { ChangedHandler, Handler };
168
169template<HandlerType type>
170static std::optional<QString> handlerNameToSignalNameHelper(QStringView handler)
171{
172 if (!QQmlSignalNames::isHandlerName(signalName: handler))
173 return {};
174
175 QString signalName = handler.sliced(pos: StrlenOn).toString();
176 Q_ASSERT(!signalName.isEmpty());
177
178 changeCaseOfFirstLetter(name&: signalName, option: ToLower, removePrefix: 0, removeSuffix: type == ChangedHandler ? StrlenChanged : 0);
179 return signalName;
180}
181
182/*!
183\internal
184Returns a signal name from \a handlerName string. Do not use it on changed handlers, see
185changedHandlerNameToSignalName for that!
186*/
187std::optional<QString> QQmlSignalNames::handlerNameToSignalName(QStringView handler)
188{
189 return handlerNameToSignalNameHelper<Handler>(handler);
190}
191
192/*!
193\internal
194Returns a signal name from \a handlerName string. Do not use it on changed handlers, see
195changedHandlerNameToSignalName for that! Accepts improperly capitalized handler names and
196incorrectly resolves signal names that start with '_' or '$'.
197*/
198std::optional<QString> QQmlSignalNames::badHandlerNameToSignalName(QStringView handler)
199{
200 if (handler.size() <= StrlenOn || !handler.startsWith(s: On))
201 return {};
202
203 QString signalName = handler.sliced(pos: StrlenOn).toString();
204
205 // This is quite wrong. But we need it for backwards compatibility.
206 signalName.front() = signalName.front().toLower();
207
208 return signalName;
209}
210
211/*!
212\internal
213Returns a signal name from \a changedHandlerName string. Makes sure not to lowercase the 'C' from
214Changed.
215*/
216std::optional<QString> QQmlSignalNames::changedHandlerNameToSignalName(QStringView handler)
217{
218 return handlerNameToSignalNameHelper<ChangedHandler>(handler);
219}
220
221bool QQmlSignalNames::isChangedSignalName(QStringView signalName)
222{
223 if (signalName.size() <= StrlenChanged || !signalName.endsWith(s: Changed))
224 return false;
225
226 if (auto letter = firstLetter(name: signalName, removePrefix: 0, removeSuffix: StrlenChanged))
227 return letter->isLower();
228
229 return true;
230}
231
232bool QQmlSignalNames::isChangedHandlerName(QStringView signalName)
233{
234 if (signalName.size() <= (StrlenOn + StrlenChanged)
235 || !signalName.startsWith(s: On)
236 || !signalName.endsWith(s: Changed)) {
237 return false;
238 }
239
240 if (auto letter = firstLetter(name: signalName, removePrefix: StrlenOn, removeSuffix: StrlenChanged))
241 return letter->isUpper();
242
243 return true;
244}
245
246bool QQmlSignalNames::isHandlerName(QStringView signalName)
247{
248 if (signalName.size() <= StrlenOn || !signalName.startsWith(s: On))
249 return false;
250
251 if (auto letter = firstLetter(name: signalName, removePrefix: StrlenOn))
252 return letter->isUpper();
253
254 return true;
255}
256
257QT_END_NAMESPACE
258

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtdeclarative/src/qml/common/qqmlsignalnames.cpp