1 | // Copyright (C) 2018 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "language.h" |
5 | |
6 | #include <QtCore/qtextstream.h> |
7 | |
8 | namespace language { |
9 | |
10 | using namespace Qt::StringLiterals; |
11 | |
12 | static Encoding encoding = Encoding::Utf8; |
13 | static Language _language = Language::Cpp; |
14 | |
15 | Language language() { return _language; } |
16 | |
17 | void setLanguage(Language l) |
18 | { |
19 | _language = l; |
20 | switch (_language) { |
21 | case Language::Cpp: |
22 | derefPointer = u"->"_s ; |
23 | listStart = '{'; |
24 | listEnd = '}'; |
25 | nullPtr = u"nullptr"_s ; |
26 | operatorNew = u"new "_s ; |
27 | qtQualifier = u"Qt::"_s ; |
28 | qualifier = u"::"_s ; |
29 | self = u""_s ; // for testing: change to "this->"; |
30 | eol = u";\n"_s ; |
31 | emptyString = u"QString()"_s ; |
32 | encoding = Encoding::Utf8; |
33 | break; |
34 | case Language::Python: |
35 | derefPointer = u"."_s ; |
36 | listStart = '['; |
37 | listEnd = ']'; |
38 | nullPtr = u"None"_s ; |
39 | operatorNew = u""_s ; |
40 | qtQualifier = u"Qt."_s ; |
41 | qualifier = u"."_s ; |
42 | self = u"self."_s ; |
43 | eol = u"\n"_s ; |
44 | emptyString = u"\"\""_s ; |
45 | encoding = Encoding::Unicode; |
46 | break; |
47 | } |
48 | } |
49 | |
50 | QString derefPointer; |
51 | char listStart; |
52 | char listEnd; |
53 | QString nullPtr; |
54 | QString operatorNew; |
55 | QString qtQualifier; |
56 | QString qualifier; |
57 | QString self; |
58 | QString eol; |
59 | QString emptyString; |
60 | |
61 | QString cppQualifier = u"::"_s ; |
62 | QString cppTrue = u"true"_s ; |
63 | QString cppFalse = u"false"_s ; |
64 | |
65 | QTextStream &operator<<(QTextStream &str, const qtConfig &c) |
66 | { |
67 | str << "QT_CONFIG(" << c.parameter() << ')'; |
68 | return str; |
69 | } |
70 | |
71 | QTextStream &operator<<(QTextStream &str, const openQtConfig &c) |
72 | { |
73 | str << "#if " << qtConfig(c.parameter()) << '\n'; |
74 | return str; |
75 | } |
76 | |
77 | QTextStream &operator<<(QTextStream &str, const closeQtConfig &c) |
78 | { |
79 | str << "#endif // " << qtConfig(c.parameter()) << '\n'; |
80 | return str; |
81 | } |
82 | |
83 | struct EnumLookup |
84 | { |
85 | int value; |
86 | const char *valueString; |
87 | }; |
88 | |
89 | template <int N> |
90 | const char *lookupEnum(const EnumLookup(&array)[N], int value, int defaultIndex = 0) |
91 | { |
92 | for (int i = 0; i < N; ++i) { |
93 | if (value == array[i].value) |
94 | return array[i].valueString; |
95 | } |
96 | const char *defaultValue = array[defaultIndex].valueString; |
97 | qWarning(msg: "uic: Warning: Invalid enumeration value %d, defaulting to %s" , |
98 | value, defaultValue); |
99 | return defaultValue; |
100 | } |
101 | |
102 | QString fixClassName(QString className) |
103 | { |
104 | if (language() == Language::Python) |
105 | className.replace(before: cppQualifier, after: "_"_L1 ); |
106 | return className; |
107 | } |
108 | |
109 | const char *toolbarArea(int v) |
110 | { |
111 | static const EnumLookup toolBarAreas[] = |
112 | { |
113 | {.value: 0, .valueString: "NoToolBarArea" }, |
114 | {.value: 0x1, .valueString: "LeftToolBarArea" }, |
115 | {.value: 0x2, .valueString: "RightToolBarArea" }, |
116 | {.value: 0x4, .valueString: "TopToolBarArea" }, |
117 | {.value: 0x8, .valueString: "BottomToolBarArea" }, |
118 | {.value: 0xf, .valueString: "AllToolBarAreas" } |
119 | }; |
120 | return lookupEnum(array: toolBarAreas, value: v); |
121 | } |
122 | |
123 | const char *sizePolicy(int v) |
124 | { |
125 | static const EnumLookup sizePolicies[] = |
126 | { |
127 | {.value: 0, .valueString: "Fixed" }, |
128 | {.value: 0x1, .valueString: "Minimum" }, |
129 | {.value: 0x4, .valueString: "Maximum" }, |
130 | {.value: 0x5, .valueString: "Preferred" }, |
131 | {.value: 0x3, .valueString: "MinimumExpanding" }, |
132 | {.value: 0x7, .valueString: "Expanding" }, |
133 | {.value: 0xD, .valueString: "Ignored" } |
134 | }; |
135 | return lookupEnum(array: sizePolicies, value: v, defaultIndex: 3); |
136 | } |
137 | |
138 | const char *dockWidgetArea(int v) |
139 | { |
140 | static const EnumLookup dockWidgetAreas[] = |
141 | { |
142 | {.value: 0, .valueString: "NoDockWidgetArea" }, |
143 | {.value: 0x1, .valueString: "LeftDockWidgetArea" }, |
144 | {.value: 0x2, .valueString: "RightDockWidgetArea" }, |
145 | {.value: 0x4, .valueString: "TopDockWidgetArea" }, |
146 | {.value: 0x8, .valueString: "BottomDockWidgetArea" }, |
147 | {.value: 0xf, .valueString: "AllDockWidgetAreas" } |
148 | }; |
149 | return lookupEnum(array: dockWidgetAreas, value: v); |
150 | } |
151 | |
152 | const char *paletteColorRole(int v) |
153 | { |
154 | static const EnumLookup colorRoles[] = |
155 | { |
156 | {.value: 0, .valueString: "WindowText" }, |
157 | {.value: 1, .valueString: "Button" }, |
158 | {.value: 2, .valueString: "Light" }, |
159 | {.value: 3, .valueString: "Midlight" }, |
160 | {.value: 4, .valueString: "Dark" }, |
161 | {.value: 5, .valueString: "Mid" }, |
162 | {.value: 6, .valueString: "Text" }, |
163 | {.value: 7, .valueString: "BrightText" }, |
164 | {.value: 8, .valueString: "ButtonText" }, |
165 | {.value: 9, .valueString: "Base" }, |
166 | {.value: 10, .valueString: "Window" }, |
167 | {.value: 11, .valueString: "Shadow" }, |
168 | {.value: 12, .valueString: "Highlight" }, |
169 | {.value: 13, .valueString: "HighlightedText" }, |
170 | {.value: 14, .valueString: "Link" }, |
171 | {.value: 15, .valueString: "LinkVisited" }, |
172 | {.value: 16, .valueString: "AlternateBase" }, |
173 | {.value: 17, .valueString: "NoRole" }, |
174 | {.value: 18, .valueString: "ToolTipBase" }, |
175 | {.value: 19, .valueString: "ToolTipText" }, |
176 | {.value: 20, .valueString: "PlaceholderText" }, |
177 | }; |
178 | return lookupEnum(array: colorRoles, value: v); |
179 | } |
180 | |
181 | // Helpers for formatting a character sequences |
182 | |
183 | // Format a special character like '\x0a' |
184 | static int formatEscapedNumber(QTextStream &str, ushort value, int base, int width, |
185 | char prefix = 0) |
186 | { |
187 | int length = 1 + width; |
188 | str << '\\'; |
189 | if (prefix) { |
190 | str << prefix; |
191 | ++length; |
192 | } |
193 | const auto oldPadChar = str.padChar(); |
194 | const auto oldFieldWidth = str.fieldWidth(); |
195 | const auto oldFieldAlignment = str.fieldAlignment(); |
196 | const auto oldIntegerBase = str.integerBase(); |
197 | str.setPadChar(u'0'); |
198 | str.setFieldWidth(width); |
199 | str.setFieldAlignment(QTextStream::AlignRight); |
200 | str.setIntegerBase(base); |
201 | str << value; |
202 | str.setIntegerBase(oldIntegerBase); |
203 | str.setFieldAlignment(oldFieldAlignment); |
204 | str.setFieldWidth(oldFieldWidth); |
205 | str.setPadChar(oldPadChar); |
206 | return length; |
207 | } |
208 | |
209 | static int formatSpecialCharacter(QTextStream &str, ushort value) |
210 | { |
211 | int length = 0; |
212 | switch (value) { |
213 | case '\\': |
214 | str << "\\\\" ; |
215 | length += 2; |
216 | break; |
217 | case '\"': |
218 | str << "\\\"" ; |
219 | length += 2; |
220 | break; |
221 | case '\n': |
222 | str << "\\n\"\n\"" ; |
223 | length += 5; |
224 | break; |
225 | default: |
226 | break; |
227 | } |
228 | return length; |
229 | } |
230 | |
231 | // Format a sequence of characters for C++ with special characters numerically |
232 | // escaped (non-raw string literals), wrappped at maxSegmentSize. FormattingTraits |
233 | // are used to transform characters into (unsigned) codes, which can be used |
234 | // for either normal escapes or Unicode code points as used in Unicode literals. |
235 | |
236 | enum : int { maxSegmentSize = 1024 }; |
237 | |
238 | template <Encoding e> |
239 | struct FormattingTraits |
240 | { |
241 | }; |
242 | |
243 | template <> |
244 | struct FormattingTraits<Encoding::Utf8> |
245 | { |
246 | static ushort code(char c) { return uchar(c); } |
247 | }; |
248 | |
249 | template <> |
250 | struct FormattingTraits<Encoding::Unicode> |
251 | { |
252 | static ushort code(QChar c) { return c.unicode(); } |
253 | }; |
254 | |
255 | template <Encoding e, class Iterator> |
256 | static void formatStringSequence(QTextStream &str, Iterator it, Iterator end, |
257 | const QString &indent, |
258 | int escapeIntegerBase, int escapeWidth, |
259 | char escapePrefix = 0) |
260 | { |
261 | str << '"'; |
262 | int length = 0; |
263 | while (it != end) { |
264 | const auto code = FormattingTraits<e>::code(*it); |
265 | if (code >= 0x80) { |
266 | length += formatEscapedNumber(str, code, escapeIntegerBase, escapeWidth, escapePrefix); |
267 | } else if (const int l = formatSpecialCharacter(str, code)) { |
268 | length += l; |
269 | } else if (code != '\r') { |
270 | str << *it; |
271 | ++length; |
272 | } |
273 | ++it; |
274 | if (it != end && length > maxSegmentSize) { |
275 | str << "\"\n" << indent << indent << '"'; |
276 | length = 0; |
277 | } |
278 | } |
279 | str << '"'; |
280 | } |
281 | |
282 | void _formatString(QTextStream &str, const QString &value, const QString &indent, |
283 | bool qString) |
284 | { |
285 | switch (encoding) { |
286 | // Special characters as 3 digit octal escapes (u8"\303\234mlaut") |
287 | case Encoding::Utf8: { |
288 | if (qString && _language == Language::Cpp) |
289 | str << "QString::fromUtf8(" ; |
290 | const QByteArray utf8 = value.toUtf8(); |
291 | formatStringSequence<Encoding::Utf8>(str, it: utf8.cbegin(), end: utf8.cend(), indent, |
292 | escapeIntegerBase: 8, escapeWidth: 3); |
293 | if (qString && _language == Language::Cpp) |
294 | str << ')'; |
295 | } |
296 | break; |
297 | // Special characters as 4 digit hex Unicode points (u8"\u00dcmlaut") |
298 | case Encoding::Unicode: |
299 | str << 'u'; // Python Unicode literal (would be UTF-16 in C++) |
300 | formatStringSequence<Encoding::Unicode>(str, it: value.cbegin(), end: value.cend(), indent, |
301 | escapeIntegerBase: 16, escapeWidth: 4, escapePrefix: 'u'); |
302 | break; |
303 | } |
304 | } |
305 | |
306 | QTextStream &operator<<(QTextStream &str, const repeat &r) |
307 | { |
308 | for (int i = 0; i < r.m_count; ++i) |
309 | str << r.m_char; |
310 | return str; |
311 | } |
312 | |
313 | startFunctionDefinition1::startFunctionDefinition1(const char *name, const QString ¶meterType, |
314 | const QString ¶meterName, |
315 | const QString &indent, |
316 | const char *returnType) : |
317 | m_name(name), m_parameterType(parameterType), m_parameterName(parameterName), |
318 | m_indent(indent), m_return(returnType) |
319 | { |
320 | } |
321 | |
322 | QTextStream &operator<<(QTextStream &str, const startFunctionDefinition1 &f) |
323 | { |
324 | switch (language()) { |
325 | case Language::Cpp: |
326 | str << (f.m_return ? f.m_return : "void" ) << ' ' << f.m_name << '(' |
327 | << f.m_parameterType; |
328 | if (f.m_parameterType.cend()->isLetter()) |
329 | str << ' '; |
330 | str << f.m_parameterName << ')' << '\n' << f.m_indent << "{\n" ; |
331 | break; |
332 | case Language::Python: |
333 | str << "def " << f.m_name << "(self, " << f.m_parameterName << "):\n" ; |
334 | break; |
335 | } |
336 | return str; |
337 | } |
338 | |
339 | endFunctionDefinition::endFunctionDefinition(const char *name) : m_name(name) |
340 | { |
341 | } |
342 | |
343 | QTextStream &operator<<(QTextStream &str, const endFunctionDefinition &f) |
344 | { |
345 | switch (language()) { |
346 | case Language::Cpp: |
347 | str << "} // " << f.m_name << "\n\n" ; |
348 | break; |
349 | case Language::Python: |
350 | str << "# " << f.m_name << "\n\n" ; |
351 | break; |
352 | } |
353 | return str; |
354 | } |
355 | |
356 | void _formatStackVariable(QTextStream &str, const char *className, QStringView varName, |
357 | bool withInitParameters) |
358 | { |
359 | switch (language()) { |
360 | case Language::Cpp: |
361 | str << className << ' ' << varName; |
362 | if (withInitParameters) |
363 | str << '('; |
364 | break; |
365 | case Language::Python: |
366 | str << varName << " = " << className << '('; |
367 | if (!withInitParameters) |
368 | str << ')'; |
369 | break; |
370 | } |
371 | } |
372 | |
373 | enum OverloadUse { |
374 | UseOverload, |
375 | UseOverloadWhenNoArguments, // Use overload only when the argument list is empty, |
376 | // in this case there is no chance of connecting |
377 | // mismatching T against const T & |
378 | DontUseOverload |
379 | }; |
380 | |
381 | // Format a member function for a signal slot connection |
382 | static void formatMemberFnPtr(QTextStream &str, const SignalSlot &s, |
383 | OverloadUse useQOverload = DontUseOverload) |
384 | { |
385 | const qsizetype parenPos = s.signature.indexOf(c: u'('); |
386 | Q_ASSERT(parenPos >= 0); |
387 | const auto functionName = QStringView{s.signature}.left(n: parenPos); |
388 | |
389 | const auto parameters = QStringView{s.signature}.mid(pos: parenPos + 1, |
390 | n: s.signature.size() - parenPos - 2); |
391 | const bool withOverload = useQOverload == UseOverload || |
392 | (useQOverload == UseOverloadWhenNoArguments && parameters.isEmpty()); |
393 | |
394 | if (withOverload) |
395 | str << "qOverload<" << parameters << ">(" ; |
396 | |
397 | str << '&' << s.className << "::" << functionName; |
398 | |
399 | if (withOverload) |
400 | str << ')'; |
401 | } |
402 | |
403 | static void formatMemberFnPtrConnection(QTextStream &str, |
404 | const SignalSlot &sender, |
405 | const SignalSlot &receiver) |
406 | { |
407 | str << "QObject::connect(" << sender.name << ", " ; |
408 | formatMemberFnPtr(str, s: sender); |
409 | str << ", " << receiver.name << ", " ; |
410 | formatMemberFnPtr(str, s: receiver, useQOverload: UseOverloadWhenNoArguments); |
411 | str << ')'; |
412 | } |
413 | |
414 | static void formatStringBasedConnection(QTextStream &str, |
415 | const SignalSlot &sender, |
416 | const SignalSlot &receiver) |
417 | { |
418 | str << "QObject::connect(" << sender.name << ", SIGNAL(" << sender.signature |
419 | << "), " << receiver.name << ", SLOT(" << receiver.signature << "))" ; |
420 | } |
421 | |
422 | void formatConnection(QTextStream &str, const SignalSlot &sender, const SignalSlot &receiver, |
423 | ConnectionSyntax connectionSyntax) |
424 | { |
425 | switch (language()) { |
426 | case Language::Cpp: |
427 | switch (connectionSyntax) { |
428 | case ConnectionSyntax::MemberFunctionPtr: |
429 | formatMemberFnPtrConnection(str, sender, receiver); |
430 | break; |
431 | case ConnectionSyntax::StringBased: |
432 | formatStringBasedConnection(str, sender, receiver); |
433 | break; |
434 | } |
435 | break; |
436 | case Language::Python: { |
437 | const auto paren = sender.signature.indexOf(c: u'('); |
438 | auto senderSignature = QStringView{sender.signature}; |
439 | str << sender.name << '.' << senderSignature.left(n: paren); |
440 | // Signals like "QAbstractButton::clicked(checked=false)" require |
441 | // the parameter if it is used. |
442 | if (sender.options.testFlag(flag: SignalSlotOption::Ambiguous)) { |
443 | const QStringView parameters = |
444 | senderSignature.mid(pos: paren + 1, n: senderSignature.size() - paren - 2); |
445 | if (!parameters.isEmpty() && !parameters.contains(c: u',')) |
446 | str << "[\"" << parameters << "\"]" ; |
447 | } |
448 | str << ".connect(" << receiver.name << '.' |
449 | << QStringView{receiver.signature}.left(n: receiver.signature.indexOf(c: u'(')) |
450 | << ')'; |
451 | } |
452 | break; |
453 | } |
454 | } |
455 | |
456 | QString boolValue(bool v) |
457 | { |
458 | switch (language()) { |
459 | case Language::Cpp: |
460 | return v ? cppTrue : cppFalse; |
461 | case Language::Python: |
462 | return v ? QStringLiteral("True" ) : QStringLiteral("False" ); |
463 | } |
464 | Q_UNREACHABLE(); |
465 | } |
466 | |
467 | static inline QString dot() { return QStringLiteral("." ); } |
468 | |
469 | QString enumValue(const QString &value) |
470 | { |
471 | if (language() == Language::Cpp || !value.contains(s: cppQualifier)) |
472 | return value; |
473 | QString fixed = value; |
474 | fixed.replace(before: cppQualifier, after: dot()); |
475 | return fixed; |
476 | } |
477 | |
478 | } // namespace language |
479 | |