aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/common/qqmlsignalnames.cpp
blob: d2a23205a6678ee60edcccab9b8bca8528b3b6e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
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