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 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | using namespace Qt::Literals; |
13 | |
14 | static constexpr const QLatin1String On("on"); |
15 | static constexpr const QLatin1String Changed("Changed"); |
16 | |
17 | static constexpr const qsizetype StrlenOn = On.length(); |
18 | static constexpr const qsizetype StrlenChanged = Changed.length(); |
19 | |
20 | static 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 | |
32 | static 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 | |
40 | enum ChangeCase { ToUpper, ToLower }; |
41 | static 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 | |
52 | static std::optional<QString> toQStringData(std::optional<QStringView> view) |
53 | { |
54 | if (view) |
55 | return view->toString(); |
56 | return std::nullopt; |
57 | } |
58 | |
59 | static QByteArray toUtf8Data(QUtf8StringView view) |
60 | { |
61 | return QByteArray(view.data(), view.size()); |
62 | } |
63 | |
64 | static 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 | |
75 | QQmlSignalNames contains a list of helper methods to manipulate signal names. |
76 | Always try to use the most specific one, as combining them might lead to incorrect |
77 | results like wrong upper/lower case, for example. |
78 | */ |
79 | |
80 | /*! |
81 | \internal |
82 | Concatenate a prefix to a property name and uppercases the first letter of the property name. |
83 | */ |
84 | QString 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 | |
91 | QString QQmlSignalNames::propertyNameToChangedSignalName(QStringView property) |
92 | { |
93 | return property.toString().append(s: Changed); |
94 | } |
95 | |
96 | QByteArray QQmlSignalNames::propertyNameToChangedSignalName(QUtf8StringView property) |
97 | { |
98 | return toUtf8Data(view: property).append(a: QByteArrayView(Changed)); |
99 | } |
100 | |
101 | QString QQmlSignalNames::propertyNameToChangedHandlerName(QStringView property) |
102 | { |
103 | return propertyNameToChangedSignalName(property: signalNameToHandlerName(signal: property)); |
104 | } |
105 | |
106 | template<typename View> |
107 | std::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 |
122 | Obtain a propertyName from its changed signal handler. |
123 | Do not call this on a value obtained from handlerNameToSignalName! Instead use |
124 | changedHandlerNameToPropertyName() directly. Otherwise you might end up with a wrong |
125 | capitalization of _Changed for "on_Changed", for example. |
126 | */ |
127 | |
128 | std::optional<QString> QQmlSignalNames::changedSignalNameToPropertyName(QStringView signalName) |
129 | { |
130 | return toQStringData(view: changedSignalNameToPropertyNameTemplate(changeSignal: signalName)); |
131 | } |
132 | std::optional<QByteArray> |
133 | QQmlSignalNames::changedSignalNameToPropertyName(QUtf8StringView signalName) |
134 | { |
135 | return toUtf8Data(view: changedSignalNameToPropertyNameTemplate(changeSignal: signalName)); |
136 | } |
137 | |
138 | /*! |
139 | \internal |
140 | Returns a property name from \a changedHandler. |
141 | This fails for property names starting with an upper-case letter, as it will lower-case it in the |
142 | process. |
143 | */ |
144 | std::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 | |
155 | QString 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 | |
167 | enum HandlerType { ChangedHandler, Handler }; |
168 | |
169 | template<HandlerType type> |
170 | static 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 |
184 | Returns a signal name from \a handlerName string. Do not use it on changed handlers, see |
185 | changedHandlerNameToSignalName for that! |
186 | */ |
187 | std::optional<QString> QQmlSignalNames::handlerNameToSignalName(QStringView handler) |
188 | { |
189 | return handlerNameToSignalNameHelper<Handler>(handler); |
190 | } |
191 | |
192 | /*! |
193 | \internal |
194 | Returns a signal name from \a handlerName string. Do not use it on changed handlers, see |
195 | changedHandlerNameToSignalName for that! Accepts improperly capitalized handler names and |
196 | incorrectly resolves signal names that start with '_' or '$'. |
197 | */ |
198 | std::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 |
213 | Returns a signal name from \a changedHandlerName string. Makes sure not to lowercase the 'C' from |
214 | Changed. |
215 | */ |
216 | std::optional<QString> QQmlSignalNames::changedHandlerNameToSignalName(QStringView handler) |
217 | { |
218 | return handlerNameToSignalNameHelper<ChangedHandler>(handler); |
219 | } |
220 | |
221 | bool 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 | |
232 | bool 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 | |
246 | bool 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 | |
257 | QT_END_NAMESPACE |
258 |
Definitions
- On
- Changed
- StrlenOn
- StrlenChanged
- firstLetterIdx
- firstLetter
- ChangeCase
- changeCaseOfFirstLetter
- toQStringData
- toUtf8Data
- toUtf8Data
- addPrefixToPropertyName
- propertyNameToChangedSignalName
- propertyNameToChangedSignalName
- propertyNameToChangedHandlerName
- changedSignalNameToPropertyNameTemplate
- changedSignalNameToPropertyName
- changedSignalNameToPropertyName
- changedHandlerNameToPropertyName
- signalNameToHandlerName
- HandlerType
- handlerNameToSignalNameHelper
- handlerNameToSignalName
- badHandlerNameToSignalName
- changedHandlerNameToSignalName
- isChangedSignalName
- isChangedHandlerName
Learn Advanced QML with KDAB
Find out more