Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qdbusxml2cpp.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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 <qbytearray.h>
5#include <qcommandlineparser.h>
6#include <qcoreapplication.h>
7#include <qdebug.h>
8#include <qfile.h>
9#include <qfileinfo.h>
10#include <qloggingcategory.h>
11#include <qstring.h>
12#include <qstringlist.h>
13#include <qtextstream.h>
14#include <qset.h>
15
16#include <qdbusmetatype.h>
17#include <private/qdbusintrospection_p.h>
18
19#include <stdio.h>
20#include <stdlib.h>
21
22#define PROGRAMNAME "qdbusxml2cpp"
23#define PROGRAMVERSION "0.8"
24#define PROGRAMCOPYRIGHT QT_COPYRIGHT
25
26#define ANNOTATION_NO_WAIT "org.freedesktop.DBus.Method.NoReply"
27
28using namespace Qt::StringLiterals;
29
30class QDBusXmlToCpp final
31{
32public:
33 int run(const QCoreApplication &app);
34
35private:
36 class DiagnosticsReporter final : public QDBusIntrospection::DiagnosticsReporter
37 {
38 public:
39 void setFileName(const QString &fileName) { m_fileName = fileName; }
40 bool hadErrors() const { return m_hadErrors; }
41
42 void warning(const QDBusIntrospection::SourceLocation &location, const char *msg,
43 ...) override;
44 void error(const QDBusIntrospection::SourceLocation &location, const char *msg,
45 ...) override;
46 void note(const QDBusIntrospection::SourceLocation &location, const char *msg, ...)
47 Q_ATTRIBUTE_FORMAT_PRINTF(3, 4);
48
49 private:
50 QString m_fileName;
51 bool m_hadErrors = false;
52
53 void report(const QDBusIntrospection::SourceLocation &location, const char *msg, va_list ap,
54 const char *severity);
55 };
56
57 enum ClassType { Proxy, Adaptor };
58
59 void writeAdaptor(const QString &filename, const QDBusIntrospection::Interfaces &interfaces);
60 void writeProxy(const QString &filename, const QDBusIntrospection::Interfaces &interfaces);
61
62 QDBusIntrospection::Interfaces readInput();
63 void cleanInterfaces(QDBusIntrospection::Interfaces &interfaces);
64 QTextStream &writeHeader(QTextStream &ts, bool changesWillBeLost);
65 QString classNameForInterface(const QString &interface, ClassType classType);
66 QByteArray qtTypeName(const QDBusIntrospection::SourceLocation &location,
67 const QString &signature,
68 const QDBusIntrospection::Annotations &annotations,
69 qsizetype paramId = -1, const char *direction = "Out");
70 void
71 writeArgList(QTextStream &ts, const QStringList &argNames,
72 const QDBusIntrospection::Annotations &annotations,
73 const QDBusIntrospection::Arguments &inputArgs,
74 const QDBusIntrospection::Arguments &outputArgs = QDBusIntrospection::Arguments());
75 void writeSignalArgList(QTextStream &ts, const QStringList &argNames,
76 const QDBusIntrospection::Annotations &annotations,
77 const QDBusIntrospection::Arguments &outputArgs);
78 QString propertyGetter(const QDBusIntrospection::Property &property);
79 QString propertySetter(const QDBusIntrospection::Property &property);
80
81 QString globalClassName;
82 QString parentClassName;
83 QString customNamespace;
84 QString inputFile;
85 bool skipNamespaces = false;
86 bool includeMocs = false;
87 QString commandLine;
88 QStringList includes;
89 QStringList globalIncludes;
90 QStringList wantedInterfaces;
91
92 DiagnosticsReporter reporter;
93};
94
95static const char includeList[] =
96 "#include <QtCore/QByteArray>\n"
97 "#include <QtCore/QList>\n"
98 "#include <QtCore/QMap>\n"
99 "#include <QtCore/QString>\n"
100 "#include <QtCore/QStringList>\n"
101 "#include <QtCore/QVariant>\n";
102
103static const char forwardDeclarations[] =
104 "#include <QtCore/qcontainerfwd.h>\n";
105
106void QDBusXmlToCpp::DiagnosticsReporter::warning(const QDBusIntrospection::SourceLocation &location,
107 const char *msg, ...)
108{
109 va_list ap;
110 va_start(ap, msg);
111 report(location, msg, ap, "warning");
112 va_end(ap);
113}
114
115void QDBusXmlToCpp::DiagnosticsReporter::error(const QDBusIntrospection::SourceLocation &location,
116 const char *msg, ...)
117{
118 va_list ap;
119 va_start(ap, msg);
120 report(location, msg, ap, "error");
121 va_end(ap);
122 m_hadErrors = true;
123}
124
125void QDBusXmlToCpp::DiagnosticsReporter::note(const QDBusIntrospection::SourceLocation &location,
126 const char *msg, ...)
127{
128 va_list ap;
129 va_start(ap, msg);
130 report(location, msg, ap, "note");
131 va_end(ap);
132 m_hadErrors = true;
133}
134
135void QDBusXmlToCpp::DiagnosticsReporter::report(const QDBusIntrospection::SourceLocation &location,
136 const char *msg, va_list ap, const char *severity)
137{
138 fprintf(stderr, "%s:%lld:%lld: %s: ", qPrintable(m_fileName),
139 (long long int)location.lineNumber, (long long int)location.columnNumber + 1, severity);
140 vfprintf(stderr, msg, ap);
141}
142
143QDBusIntrospection::Interfaces QDBusXmlToCpp::readInput()
144{
145 QFile input(inputFile);
146 if (inputFile.isEmpty() || inputFile == "-"_L1) {
147 reporter.setFileName("<standard input>"_L1);
148 if (!input.open(stdin, QIODevice::ReadOnly)) {
149 fprintf(stderr, PROGRAMNAME ": could not open standard input: %s\n",
150 qPrintable(input.errorString()));
151 exit(1);
152 }
153 } else {
154 reporter.setFileName(inputFile);
155 if (!input.open(QIODevice::ReadOnly)) {
156 fprintf(stderr, PROGRAMNAME ": could not open input file '%s': %s\n",
157 qPrintable(inputFile), qPrintable(input.errorString()));
158 exit(1);
159 }
160 }
161
162 QByteArray data = input.readAll();
163 auto interfaces = QDBusIntrospection::parseInterfaces(QString::fromUtf8(data), &reporter);
164 if (reporter.hadErrors())
165 exit(1);
166
167 return interfaces;
168}
169
170void QDBusXmlToCpp::cleanInterfaces(QDBusIntrospection::Interfaces &interfaces)
171{
172 if (!wantedInterfaces.isEmpty()) {
173 QDBusIntrospection::Interfaces::Iterator it = interfaces.begin();
174 while (it != interfaces.end())
175 if (!wantedInterfaces.contains(it.key()))
176 it = interfaces.erase(it);
177 else
178 ++it;
179 }
180}
181
182static bool isSupportedSuffix(QStringView suffix)
183{
184 const QLatin1StringView candidates[] = {
185 "h"_L1,
186 "cpp"_L1,
187 "cc"_L1
188 };
189
190 for (auto candidate : candidates)
191 if (suffix == candidate)
192 return true;
193
194 return false;
195}
196
197// produce a header name from the file name
198static QString header(const QString &name)
199{
200 QStringList parts = name.split(u':');
201 QString retval = parts.front();
202
203 if (retval.isEmpty() || retval == "-"_L1)
204 return retval;
205
206 QFileInfo header{retval};
207 if (!isSupportedSuffix(header.suffix()))
208 retval.append(".h"_L1);
209
210 return retval;
211}
212
213// produce a cpp name from the file name
214static QString cpp(const QString &name)
215{
216 QStringList parts = name.split(u':');
217 QString retval = parts.back();
218
219 if (retval.isEmpty() || retval == "-"_L1)
220 return retval;
221
222 QFileInfo source{retval};
223 if (!isSupportedSuffix(source.suffix()))
224 retval.append(".cpp"_L1);
225
226 return retval;
227}
228
229// produce a moc name from the file name
230static QString moc(const QString &name)
231{
232 QString retval;
233 const QStringList fileNames = name.split(u':');
234
235 if (fileNames.size() == 1) {
236 QFileInfo fi{fileNames.front()};
237 if (isSupportedSuffix(fi.suffix())) {
238 // Generates a file that contains the header and the implementation: include "filename.moc"
239 retval += fi.completeBaseName();
240 retval += ".moc"_L1;
241 } else {
242 // Separate source and header files are generated: include "moc_filename.cpp"
243 retval += "moc_"_L1;
244 retval += fi.fileName();
245 retval += ".cpp"_L1;
246 }
247 } else {
248 QString headerName = fileNames.front();
249 QString sourceName = fileNames.back();
250
251 if (sourceName.isEmpty() || sourceName == "-"_L1) {
252 // If only a header is generated, don't include anything
253 } else if (headerName.isEmpty() || headerName == "-"_L1) {
254 // If only source file is generated: include "moc_sourcename.cpp"
255 QFileInfo source{sourceName};
256
257 retval += "moc_"_L1;
258 retval += source.completeBaseName();
259 retval += ".cpp"_L1;
260
261 fprintf(stderr, "warning: no header name is provided, assuming it to be \"%s\"\n",
262 qPrintable(source.completeBaseName() + ".h"_L1));
263 } else {
264 // Both source and header generated: include "moc_headername.cpp"
265 QFileInfo header{headerName};
266
267 retval += "moc_"_L1;
268 retval += header.completeBaseName();
269 retval += ".cpp"_L1;
270 }
271 }
272
273 return retval;
274}
275
276QTextStream &QDBusXmlToCpp::writeHeader(QTextStream &ts, bool changesWillBeLost)
277{
278 ts << "/*\n"
279 " * This file was generated by " PROGRAMNAME " version " PROGRAMVERSION "\n"
280 " * Source file was " << QFileInfo(inputFile).fileName() << "\n"
281 " *\n"
282 " * " PROGRAMNAME " is " PROGRAMCOPYRIGHT "\n"
283 " *\n"
284 " * This is an auto-generated file.\n";
285
286 if (changesWillBeLost)
287 ts << " * Do not edit! All changes made to it will be lost.\n";
288 else
289 ts << " * This file may have been hand-edited. Look for HAND-EDIT comments\n"
290 " * before re-generating it.\n";
291
292 ts << " */\n\n";
293
294 return ts;
295}
296
297QString QDBusXmlToCpp::classNameForInterface(const QString &interface,
298 QDBusXmlToCpp::ClassType classType)
299{
300 if (!globalClassName.isEmpty())
301 return globalClassName;
302
303 const auto parts = QStringView{interface}.split(u'.');
304
305 QString retval;
306 if (classType == Proxy) {
307 for (const auto &part : parts) {
308 retval += part[0].toUpper();
309 retval += part.mid(1);
310 }
311 } else {
312 retval += parts.last()[0].toUpper() + parts.last().mid(1);
313 }
314
315 if (classType == Proxy)
316 retval += "Interface"_L1;
317 else
318 retval += "Adaptor"_L1;
319
320 return retval;
321}
322
323QByteArray QDBusXmlToCpp::qtTypeName(const QDBusIntrospection::SourceLocation &location,
324 const QString &signature,
325 const QDBusIntrospection::Annotations &annotations,
326 qsizetype paramId, const char *direction)
327{
328 int type = QDBusMetaType::signatureToMetaType(signature.toLatin1()).id();
329 if (type == QMetaType::UnknownType) {
330 QString annotationName = u"org.qtproject.QtDBus.QtTypeName"_s;
331 if (paramId >= 0)
332 annotationName += ".%1%2"_L1.arg(QLatin1StringView(direction)).arg(paramId);
333 auto annotation = annotations.value(annotationName);
334 QString qttype = annotation.value;
335 if (!qttype.isEmpty())
336 return std::move(qttype).toLatin1();
337
338 QString oldAnnotationName = u"com.trolltech.QtDBus.QtTypeName"_s;
339 if (paramId >= 0)
340 oldAnnotationName += ".%1%2"_L1.arg(QLatin1StringView(direction)).arg(paramId);
341 annotation = annotations.value(oldAnnotationName);
342 qttype = annotation.value;
343
344 if (qttype.isEmpty()) {
345 reporter.error(location, "unknown type `%s'\n", qPrintable(signature));
346 reporter.note(location, "you should add <annotation name=\"%s\" value=\"<type>\"/>\n",
347 qPrintable(annotationName));
348
349 exit(1);
350 }
351
352 reporter.warning(annotation.location, "deprecated annotation '%s' found\n",
353 qPrintable(oldAnnotationName));
354 reporter.note(annotation.location, "suggest updating to '%s'\n",
355 qPrintable(annotationName));
356 return std::move(qttype).toLatin1();
357 }
358
359 return QMetaType(type).name();
360}
361
362static QString nonConstRefArg(const QByteArray &arg)
363{
364 return QLatin1StringView(arg) + " &"_L1;
365}
366
367static QString templateArg(const QByteArray &arg)
368{
369 if (!arg.endsWith('>'))
370 return QLatin1StringView(arg);
371
372 return QLatin1StringView(arg) + " "_L1;
373}
374
375static QString constRefArg(const QByteArray &arg)
376{
377 if (!arg.startsWith('Q'))
378 return QLatin1StringView(arg) + " "_L1;
379 else
380 return "const %1 &"_L1.arg(QLatin1StringView(arg));
381}
382
383static QStringList makeArgNames(const QDBusIntrospection::Arguments &inputArgs,
384 const QDBusIntrospection::Arguments &outputArgs =
386{
387 QStringList retval;
388 const qsizetype numInputArgs = inputArgs.size();
389 const qsizetype numOutputArgs = outputArgs.size();
390 retval.reserve(numInputArgs + numOutputArgs);
391 for (qsizetype i = 0; i < numInputArgs; ++i) {
392 const QDBusIntrospection::Argument &arg = inputArgs.at(i);
393 QString name = arg.name;
394 if (name.isEmpty())
395 name = u"in%1"_s.arg(i);
396 else
397 name.replace(u'-', u'_');
398 while (retval.contains(name))
399 name += "_"_L1;
400 retval << name;
401 }
402 for (qsizetype i = 0; i < numOutputArgs; ++i) {
403 const QDBusIntrospection::Argument &arg = outputArgs.at(i);
404 QString name = arg.name;
405 if (name.isEmpty())
406 name = u"out%1"_s.arg(i);
407 else
408 name.replace(u'-', u'_');
409 while (retval.contains(name))
410 name += "_"_L1;
411 retval << name;
412 }
413 return retval;
414}
415
416void QDBusXmlToCpp::writeArgList(QTextStream &ts, const QStringList &argNames,
417 const QDBusIntrospection::Annotations &annotations,
418 const QDBusIntrospection::Arguments &inputArgs,
419 const QDBusIntrospection::Arguments &outputArgs)
420{
421 // input args:
422 bool first = true;
423 qsizetype argPos = 0;
424 for (qsizetype i = 0; i < inputArgs.size(); ++i) {
425 const QDBusIntrospection::Argument &arg = inputArgs.at(i);
426 QString type = constRefArg(qtTypeName(arg.location, arg.type, annotations, i, "In"));
427
428 if (!first)
429 ts << ", ";
430 ts << type << argNames.at(argPos++);
431 first = false;
432 }
433
434 argPos++;
435
436 // output args
437 // yes, starting from 1
438 for (qsizetype i = 1; i < outputArgs.size(); ++i) {
439 const QDBusIntrospection::Argument &arg = outputArgs.at(i);
440
441 if (!first)
442 ts << ", ";
443 ts << nonConstRefArg(qtTypeName(arg.location, arg.type, annotations, i, "Out"))
444 << argNames.at(argPos++);
445 first = false;
446 }
447}
448
449void QDBusXmlToCpp::writeSignalArgList(QTextStream &ts, const QStringList &argNames,
450 const QDBusIntrospection::Annotations &annotations,
451 const QDBusIntrospection::Arguments &outputArgs)
452{
453 bool first = true;
454 qsizetype argPos = 0;
455 for (qsizetype i = 0; i < outputArgs.size(); ++i) {
456 const QDBusIntrospection::Argument &arg = outputArgs.at(i);
457 QString type = constRefArg(qtTypeName(arg.location, arg.type, annotations, i, "Out"));
458
459 if (!first)
460 ts << ", ";
461 ts << type << argNames.at(argPos++);
462 first = false;
463 }
464}
465
466QString QDBusXmlToCpp::propertyGetter(const QDBusIntrospection::Property &property)
467{
468 auto annotation = property.annotations.value("org.qtproject.QtDBus.PropertyGetter"_L1);
469 if (!annotation.value.isEmpty())
470 return annotation.value;
471
472 annotation = property.annotations.value("com.trolltech.QtDBus.propertyGetter"_L1);
473 if (!annotation.value.isEmpty()) {
474 reporter.warning(annotation.location,
475 "deprecated annotation 'com.trolltech.QtDBus.propertyGetter' found\n");
476 reporter.note(annotation.location,
477 "suggest updating to 'org.qtproject.QtDBus.PropertyGetter'\n");
478 return annotation.value;
479 }
480
481 QString getter = property.name;
482 getter[0] = getter[0].toLower();
483 return getter;
484}
485
486QString QDBusXmlToCpp::propertySetter(const QDBusIntrospection::Property &property)
487{
488 auto annotation = property.annotations.value("org.qtproject.QtDBus.PropertySetter"_L1);
489 if (!annotation.value.isEmpty())
490 return annotation.value;
491
492 annotation = property.annotations.value("com.trolltech.QtDBus.propertySetter"_L1);
493 if (!annotation.value.isEmpty()) {
494 reporter.warning(annotation.location,
495 "deprecated annotation 'com.trolltech.QtDBus.propertySetter' found\n");
496 reporter.note(annotation.location,
497 "suggest updating to 'org.qtproject.QtDBus.PropertySetter'\n");
498 return annotation.value;
499 }
500
501 QString setter = "set"_L1 + property.name;
502 setter[3] = setter[3].toUpper();
503 return setter;
504}
505
506static QString methodName(const QDBusIntrospection::Method &method)
507{
508 QString name = method.annotations.value(u"org.qtproject.QtDBus.MethodName"_s).value;
509 if (!name.isEmpty())
510 return name;
511
512 return method.name;
513}
514
515static QString stringify(const QString &data)
516{
517 QString retval;
518 qsizetype i;
519 for (i = 0; i < data.size(); ++i) {
520 retval += u'\"';
521 for ( ; i < data.size() && data[i] != u'\n' && data[i] != u'\r'; ++i)
522 if (data[i] == u'\"')
523 retval += "\\\""_L1;
524 else
525 retval += data[i];
526 if (i+1 < data.size() && data[i] == u'\r' && data[i+1] == u'\n')
527 i++;
528 retval += "\\n\"\n"_L1;
529 }
530 return retval;
531}
532
533static bool openFile(const QString &fileName, QFile &file)
534{
535 if (fileName.isEmpty())
536 return false;
537
538 bool isOk = false;
539 if (fileName == "-"_L1) {
540 isOk = file.open(stdout, QIODevice::WriteOnly | QIODevice::Text);
541 } else {
542 file.setFileName(fileName);
543 isOk = file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text);
544 }
545
546 if (!isOk)
547 fprintf(stderr, "%s: Unable to open '%s': %s\n",
548 PROGRAMNAME, qPrintable(fileName), qPrintable(file.errorString()));
549 return isOk;
550}
551
552void QDBusXmlToCpp::writeProxy(const QString &filename,
553 const QDBusIntrospection::Interfaces &interfaces)
554{
555 // open the file
556 QString headerName = header(filename);
557 QByteArray headerData;
558 QTextStream hs(&headerData);
559
560 QString cppName = cpp(filename);
561 QByteArray cppData;
562 QTextStream cs(&cppData);
563
564 // write the header:
565 writeHeader(hs, true);
566 if (cppName != headerName)
567 writeHeader(cs, false);
568
569 // include guards:
570 QString includeGuard;
571 if (!headerName.isEmpty() && headerName != "-"_L1) {
572 includeGuard = headerName.toUpper().replace(u'.', u'_');
573 qsizetype pos = includeGuard.lastIndexOf(u'/');
574 if (pos != -1)
575 includeGuard = includeGuard.mid(pos + 1);
576 } else {
577 includeGuard = u"QDBUSXML2CPP_PROXY"_s;
578 }
579
580 hs << "#ifndef " << includeGuard << "\n"
581 "#define " << includeGuard << "\n\n";
582
583 // include our stuff:
584 hs << "#include <QtCore/QObject>\n"
585 << includeList;
586#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
587 hs << "#include <QtDBus/QtDBus>\n";
588#else
589 hs << "#include <QtDBus/QDBusAbstractInterface>\n"
590 "#include <QtDBus/QDBusPendingReply>\n";
591#endif
592
593 for (const QString &include : std::as_const(includes)) {
594 hs << "#include \"" << include << "\"\n";
595 if (headerName.isEmpty())
596 cs << "#include \"" << include << "\"\n";
597 }
598
599 for (const QString &include : std::as_const(globalIncludes)) {
600 hs << "#include <" << include << ">\n";
601 if (headerName.isEmpty())
602 cs << "#include <" << include << ">\n";
603 }
604
605 hs << "\n";
606
607 if (cppName != headerName) {
608 if (!headerName.isEmpty() && headerName != "-"_L1)
609 cs << "#include \"" << headerName << "\"\n\n";
610 }
611
612 if (!customNamespace.isEmpty()) {
613 hs << "namespace " << customNamespace << " { \n"
614 "\n";
615 cs << "namespace " << customNamespace << " { \n"
616 "\n";
617 }
618
619 for (const QDBusIntrospection::Interface *interface : interfaces) {
620 QString className = classNameForInterface(interface->name, Proxy);
621
622 // comment:
623 hs << "/*\n"
624 " * Proxy class for interface " << interface->name << "\n"
625 " */\n";
626 cs << "/*\n"
627 " * Implementation of interface class " << className << "\n"
628 " */\n\n";
629
630 // class header:
631 hs << "class " << className << ": public QDBusAbstractInterface\n"
632 "{\n"
633 " Q_OBJECT\n";
634
635 // the interface name
636 hs << "public:\n"
637 " static inline const char *staticInterfaceName()\n"
638 " { return \"" << interface->name << "\"; }\n\n";
639
640 // constructors/destructors:
641 hs << "public:\n"
642 " " << className << "(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr);\n\n"
643 " ~" << className << "();\n\n";
644 cs << className << "::" << className << "(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent)\n"
645 " : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)\n"
646 "{\n"
647 "}\n\n"
648 << className << "::~" << className << "()\n"
649 "{\n"
650 "}\n\n";
651
652 // properties:
653 for (const QDBusIntrospection::Property &property : interface->properties) {
654 QByteArray type = qtTypeName(property.location, property.type, property.annotations);
655 QString getter = propertyGetter(property);
656 QString setter = propertySetter(property);
657
658 hs << " Q_PROPERTY(" << type << " " << property.name;
659
660 // getter:
661 if (property.access != QDBusIntrospection::Property::Write)
662 // it's readable
663 hs << " READ " << getter;
664
665 // setter
666 if (property.access != QDBusIntrospection::Property::Read)
667 // it's writeable
668 hs << " WRITE " << setter;
669
670 hs << ")\n";
671
672 // getter:
673 if (property.access != QDBusIntrospection::Property::Write) {
674 hs << " inline " << type << " " << getter << "() const\n"
675 " { return qvariant_cast< " << type << " >(property(\""
676 << property.name << "\")); }\n";
677 }
678
679 // setter:
680 if (property.access != QDBusIntrospection::Property::Read) {
681 hs << " inline void " << setter << "(" << constRefArg(type) << "value)\n"
682 " { setProperty(\"" << property.name
683 << "\", QVariant::fromValue(value)); }\n";
684 }
685
686 hs << "\n";
687 }
688
689 // methods:
690 hs << "public Q_SLOTS: // METHODS\n";
691 for (const QDBusIntrospection::Method &method : interface->methods) {
692 bool isDeprecated = method.annotations.value("org.freedesktop.DBus.Deprecated"_L1).value
693 == "true"_L1;
694 bool isNoReply = method.annotations.value(ANNOTATION_NO_WAIT ""_L1).value == "true"_L1;
695 if (isNoReply && !method.outputArgs.isEmpty()) {
696 reporter.warning(method.location,
697 "method %s in interface %s is marked 'no-reply' but has output "
698 "arguments.\n",
699 qPrintable(method.name), qPrintable(interface->name));
700 continue;
701 }
702
703 if (isDeprecated)
704 hs << " Q_DECL_DEPRECATED ";
705 else
706 hs << " ";
707
708 if (isNoReply) {
709 hs << "Q_NOREPLY inline void ";
710 } else {
711 hs << "inline QDBusPendingReply<";
712 for (qsizetype i = 0; i < method.outputArgs.size(); ++i)
713 hs << (i > 0 ? ", " : "")
714 << templateArg(qtTypeName(method.outputArgs.at(i).location,
715 method.outputArgs.at(i).type, method.annotations,
716 i, "Out"));
717 hs << "> ";
718 }
719
720 hs << methodName(method) << "(";
721
722 QStringList argNames = makeArgNames(method.inputArgs);
723 writeArgList(hs, argNames, method.annotations, method.inputArgs);
724
725 hs << ")\n"
726 " {\n"
727 " QList<QVariant> argumentList;\n";
728
729 if (!method.inputArgs.isEmpty()) {
730 hs << " argumentList";
731 for (qsizetype argPos = 0; argPos < method.inputArgs.size(); ++argPos)
732 hs << " << QVariant::fromValue(" << argNames.at(argPos) << ')';
733 hs << ";\n";
734 }
735
736 if (isNoReply)
737 hs << " callWithArgumentList(QDBus::NoBlock, "
738 "QStringLiteral(\"" << method.name << "\"), argumentList);\n";
739 else
740 hs << " return asyncCallWithArgumentList(QStringLiteral(\""
741 << method.name << "\"), argumentList);\n";
742
743 // close the function:
744 hs << " }\n";
745
746 if (method.outputArgs.size() > 1) {
747 // generate the old-form QDBusReply methods with multiple incoming parameters
748 hs << (isDeprecated ? " Q_DECL_DEPRECATED " : " ") << "inline QDBusReply<"
749 << templateArg(qtTypeName(method.outputArgs.first().location,
750 method.outputArgs.first().type, method.annotations, 0,
751 "Out"))
752 << "> ";
753 hs << method.name << "(";
754
755 QStringList argNames = makeArgNames(method.inputArgs, method.outputArgs);
756 writeArgList(hs, argNames, method.annotations, method.inputArgs, method.outputArgs);
757
758 hs << ")\n"
759 " {\n"
760 " QList<QVariant> argumentList;\n";
761
762 qsizetype argPos = 0;
763 if (!method.inputArgs.isEmpty()) {
764 hs << " argumentList";
765 for (argPos = 0; argPos < method.inputArgs.size(); ++argPos)
766 hs << " << QVariant::fromValue(" << argNames.at(argPos) << ')';
767 hs << ";\n";
768 }
769
770 hs << " QDBusMessage reply = callWithArgumentList(QDBus::Block, "
771 "QStringLiteral(\"" << method.name << "\"), argumentList);\n";
772
773 argPos++;
774 hs << " if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().size() == "
775 << method.outputArgs.size() << ") {\n";
776
777 // yes, starting from 1
778 for (qsizetype i = 1; i < method.outputArgs.size(); ++i)
779 hs << " " << argNames.at(argPos++) << " = qdbus_cast<"
780 << templateArg(qtTypeName(method.outputArgs.at(i).location,
781 method.outputArgs.at(i).type, method.annotations,
782 i, "Out"))
783 << ">(reply.arguments().at(" << i << "));\n";
784 hs << " }\n"
785 " return reply;\n"
786 " }\n";
787 }
788
789 hs << "\n";
790 }
791
792 hs << "Q_SIGNALS: // SIGNALS\n";
793 for (const QDBusIntrospection::Signal &signal : interface->signals_) {
794 hs << " ";
795 if (signal.annotations.value("org.freedesktop.DBus.Deprecated"_L1).value == "true"_L1)
796 hs << "Q_DECL_DEPRECATED ";
797
798 hs << "void " << signal.name << "(";
799
800 QStringList argNames = makeArgNames(signal.outputArgs);
801 writeSignalArgList(hs, argNames, signal.annotations, signal.outputArgs);
802
803 hs << ");\n"; // finished for header
804 }
805
806 // close the class:
807 hs << "};\n\n";
808 }
809
810 if (!customNamespace.isEmpty()) {
811 hs << "} // end of namespace " << customNamespace << "\n"
812 << "\n";
813 cs << "} // end of namespace " << customNamespace << "\n"
814 << "\n";
815 }
816
817 if (!skipNamespaces) {
818 QStringList last;
819 QDBusIntrospection::Interfaces::ConstIterator it = interfaces.constBegin();
820 do
821 {
822 QStringList current;
823 QString name;
824 if (it != interfaces.constEnd()) {
825 current = it->constData()->name.split(u'.');
826 name = current.takeLast();
827 }
828
829 qsizetype i = 0;
830 while (i < current.size() && i < last.size() && current.at(i) == last.at(i))
831 ++i;
832
833 // i parts matched
834 // close last.arguments().size() - i namespaces:
835 for (qsizetype j = i; j < last.size(); ++j)
836 hs << QString((last.size() - j - 1 + i) * 2, u' ') << "}\n";
837
838 // open current.arguments().size() - i namespaces
839 for (qsizetype j = i; j < current.size(); ++j)
840 hs << QString(j * 2, u' ') << "namespace " << current.at(j) << " {\n";
841
842 // add this class:
843 if (!name.isEmpty()) {
844 hs << QString(current.size() * 2, u' ')
845 << "using " << name << " = " << (customNamespace.isEmpty() ? "" : "::")
846 << customNamespace << "::" << classNameForInterface(it->constData()->name, Proxy)
847 << ";\n";
848 }
849
850 if (it == interfaces.constEnd())
851 break;
852 ++it;
853 last = current;
854 } while (true);
855
856 hs << "\n";
857 }
858
859 // close the include guard
860 hs << "#endif\n";
861
862 QString mocName = moc(filename);
863 if (includeMocs && !mocName.isEmpty())
864 cs << "\n"
865 "#include \"" << mocName << "\"\n";
866
867 cs.flush();
868 hs.flush();
869
870 QFile file;
871 const bool headerOpen = openFile(headerName, file);
872 if (headerOpen)
873 file.write(headerData);
874
875 if (headerName == cppName) {
876 if (headerOpen)
877 file.write(cppData);
878 } else {
879 QFile cppFile;
880 if (openFile(cppName, cppFile))
881 cppFile.write(cppData);
882 }
883}
884
885void QDBusXmlToCpp::writeAdaptor(const QString &filename,
886 const QDBusIntrospection::Interfaces &interfaces)
887{
888 // open the file
889 QString headerName = header(filename);
890 QByteArray headerData;
891 QTextStream hs(&headerData);
892
893 QString cppName = cpp(filename);
894 QByteArray cppData;
895 QTextStream cs(&cppData);
896
897 // write the headers
898 writeHeader(hs, false);
899 if (cppName != headerName)
900 writeHeader(cs, true);
901
902 // include guards:
903 QString includeGuard;
904 if (!headerName.isEmpty() && headerName != "-"_L1) {
905 includeGuard = headerName.toUpper().replace(u'.', u'_');
906 qsizetype pos = includeGuard.lastIndexOf(u'/');
907 if (pos != -1)
908 includeGuard = includeGuard.mid(pos + 1);
909 } else {
910 includeGuard = u"QDBUSXML2CPP_ADAPTOR"_s;
911 }
912
913 hs << "#ifndef " << includeGuard << "\n"
914 "#define " << includeGuard << "\n\n";
915
916 // include our stuff:
917 hs << "#include <QtCore/QObject>\n";
918 if (cppName == headerName)
919 hs << "#include <QtCore/QMetaObject>\n"
920 "#include <QtCore/QVariant>\n";
921#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
922 hs << "#include <QtDBus/QtDBus>\n";
923#else
924 hs << "#include <QtDBus/QDBusAbstractAdaptor>\n"
925 "#include <QtDBus/QDBusObjectPath>\n";
926#endif
927
928 for (const QString &include : std::as_const(includes)) {
929 hs << "#include \"" << include << "\"\n";
930 if (headerName.isEmpty())
931 cs << "#include \"" << include << "\"\n";
932 }
933
934 for (const QString &include : std::as_const(globalIncludes)) {
935 hs << "#include <" << include << ">\n";
936 if (headerName.isEmpty())
937 cs << "#include <" << include << ">\n";
938 }
939
940 if (cppName != headerName) {
941 if (!headerName.isEmpty() && headerName != "-"_L1)
942 cs << "#include \"" << headerName << "\"\n";
943
944 cs << "#include <QtCore/QMetaObject>\n"
945 << includeList
946 << "\n";
947 hs << forwardDeclarations;
948 } else {
949 hs << includeList;
950 }
951
952 hs << "\n";
953
954 QString parent = parentClassName;
955 if (parentClassName.isEmpty())
956 parent = u"QObject"_s;
957
958 if (!customNamespace.isEmpty()) {
959 hs << "namespace " << customNamespace << " { \n"
960 "\n";
961 cs << "namespace " << customNamespace << " { \n"
962 "\n";
963 }
964
965 for (const QDBusIntrospection::Interface *interface : interfaces) {
966 QString className = classNameForInterface(interface->name, Adaptor);
967
968 // comment:
969 hs << "/*\n"
970 " * Adaptor class for interface " << interface->name << "\n"
971 " */\n";
972 cs << "/*\n"
973 " * Implementation of adaptor class " << className << "\n"
974 " */\n\n";
975
976 // class header:
977 hs << "class " << className << ": public QDBusAbstractAdaptor\n"
978 "{\n"
979 " Q_OBJECT\n"
980 " Q_CLASSINFO(\"D-Bus Interface\", \"" << interface->name << "\")\n"
981 " Q_CLASSINFO(\"D-Bus Introspection\", \"\"\n"
982 << stringify(interface->introspection)
983 << " \"\")\n"
984 "public:\n"
985 " " << className << "(" << parent << " *parent);\n"
986 " ~" << className << "() override;\n\n";
987
988 if (!parentClassName.isEmpty())
989 hs << " inline " << parent << " *parent() const\n"
990 " { return static_cast<" << parent << " *>(QObject::parent()); }\n\n";
991
992 // constructor/destructor
993 cs << className << "::" << className << "(" << parent << " *parent)\n"
994 " : QDBusAbstractAdaptor(parent)\n"
995 "{\n"
996 " // constructor\n"
997 " setAutoRelaySignals(true);\n"
998 "}\n\n"
999 << className << "::~" << className << "()\n"
1000 "{\n"
1001 " // destructor\n"
1002 "}\n\n";
1003
1004 hs << "public: // PROPERTIES\n";
1005 for (const QDBusIntrospection::Property &property : interface->properties) {
1006 QByteArray type = qtTypeName(property.location, property.type, property.annotations);
1007 QString constRefType = constRefArg(type);
1008 QString getter = propertyGetter(property);
1009 QString setter = propertySetter(property);
1010
1011 hs << " Q_PROPERTY(" << type << " " << property.name;
1012 if (property.access != QDBusIntrospection::Property::Write)
1013 hs << " READ " << getter;
1014 if (property.access != QDBusIntrospection::Property::Read)
1015 hs << " WRITE " << setter;
1016 hs << ")\n";
1017
1018 // getter:
1019 if (property.access != QDBusIntrospection::Property::Write) {
1020 hs << " " << type << " " << getter << "() const;\n";
1021 cs << type << " "
1022 << className << "::" << getter << "() const\n"
1023 "{\n"
1024 " // get the value of property " << property.name << "\n"
1025 " return qvariant_cast< " << type <<" >(parent()->property(\"" << property.name << "\"));\n"
1026 "}\n\n";
1027 }
1028
1029 // setter
1030 if (property.access != QDBusIntrospection::Property::Read) {
1031 hs << " void " << setter << "(" << constRefType << "value);\n";
1032 cs << "void " << className << "::" << setter << "(" << constRefType << "value)\n"
1033 "{\n"
1034 " // set the value of property " << property.name << "\n"
1035 " parent()->setProperty(\"" << property.name << "\", QVariant::fromValue(value";
1036 if (constRefType.contains("QDBusVariant"_L1))
1037 cs << ".variant()";
1038 cs << "));\n"
1039 "}\n\n";
1040 }
1041
1042 hs << "\n";
1043 }
1044
1045 hs << "public Q_SLOTS: // METHODS\n";
1046 for (const QDBusIntrospection::Method &method : interface->methods) {
1047 bool isNoReply = method.annotations.value(ANNOTATION_NO_WAIT ""_L1).value == "true"_L1;
1048 if (isNoReply && !method.outputArgs.isEmpty()) {
1049 reporter.warning(method.location,
1050 "method %s in interface %s is marked 'no-reply' but has output "
1051 "arguments.\n",
1052 qPrintable(method.name), qPrintable(interface->name));
1053 continue;
1054 }
1055
1056 hs << " ";
1057 QByteArray returnType;
1058 if (isNoReply) {
1059 hs << "Q_NOREPLY void ";
1060 cs << "void ";
1061 } else if (method.outputArgs.isEmpty()) {
1062 hs << "void ";
1063 cs << "void ";
1064 } else {
1065 returnType =
1066 qtTypeName(method.outputArgs.first().location,
1067 method.outputArgs.first().type, method.annotations, 0, "Out");
1068 hs << returnType << " ";
1069 cs << returnType << " ";
1070 }
1071
1072 QString name = methodName(method);
1073 hs << name << "(";
1074 cs << className << "::" << name << "(";
1075
1076 QStringList argNames = makeArgNames(method.inputArgs, method.outputArgs);
1077 writeArgList(hs, argNames, method.annotations, method.inputArgs, method.outputArgs);
1078 writeArgList(cs, argNames, method.annotations, method.inputArgs, method.outputArgs);
1079
1080 hs << ");\n"; // finished for header
1081 cs << ")\n"
1082 "{\n"
1083 " // handle method call " << interface->name << "." << methodName(method) << "\n";
1084
1085 // make the call
1086 bool usingInvokeMethod = false;
1087 if (parentClassName.isEmpty() && method.inputArgs.size() <= 10
1088 && method.outputArgs.size() <= 1)
1089 usingInvokeMethod = true;
1090
1091 if (usingInvokeMethod) {
1092 // we are using QMetaObject::invokeMethod
1093 if (!returnType.isEmpty())
1094 cs << " " << returnType << " " << argNames.at(method.inputArgs.size())
1095 << ";\n";
1096
1097 static const char invoke[] = " QMetaObject::invokeMethod(parent(), \"";
1098 cs << invoke << name << "\"";
1099
1100 if (!method.outputArgs.isEmpty())
1101 cs << ", Q_RETURN_ARG("
1102 << qtTypeName(method.outputArgs.at(0).location, method.outputArgs.at(0).type,
1103 method.annotations, 0, "Out")
1104 << ", " << argNames.at(method.inputArgs.size()) << ")";
1105
1106 for (qsizetype i = 0; i < method.inputArgs.size(); ++i)
1107 cs << ", Q_ARG("
1108 << qtTypeName(method.inputArgs.at(i).location, method.inputArgs.at(i).type,
1109 method.annotations, i, "In")
1110 << ", " << argNames.at(i) << ")";
1111
1112 cs << ");\n";
1113
1114 if (!returnType.isEmpty())
1115 cs << " return " << argNames.at(method.inputArgs.size()) << ";\n";
1116 } else {
1117 if (parentClassName.isEmpty())
1118 cs << " //";
1119 else
1120 cs << " ";
1121
1122 if (!method.outputArgs.isEmpty())
1123 cs << "return ";
1124
1125 if (parentClassName.isEmpty())
1126 cs << "static_cast<YourObjectType *>(parent())->";
1127 else
1128 cs << "parent()->";
1129 cs << name << "(";
1130
1131 qsizetype argPos = 0;
1132 bool first = true;
1133 for (qsizetype i = 0; i < method.inputArgs.size(); ++i) {
1134 cs << (first ? "" : ", ") << argNames.at(argPos++);
1135 first = false;
1136 }
1137 ++argPos; // skip retval, if any
1138 for (qsizetype i = 1; i < method.outputArgs.size(); ++i) {
1139 cs << (first ? "" : ", ") << argNames.at(argPos++);
1140 first = false;
1141 }
1142
1143 cs << ");\n";
1144 }
1145 cs << "}\n\n";
1146 }
1147
1148 hs << "Q_SIGNALS: // SIGNALS\n";
1149 for (const QDBusIntrospection::Signal &signal : interface->signals_) {
1150 hs << " void " << signal.name << "(";
1151
1152 QStringList argNames = makeArgNames(signal.outputArgs);
1153 writeSignalArgList(hs, argNames, signal.annotations, signal.outputArgs);
1154
1155 hs << ");\n"; // finished for header
1156 }
1157
1158 // close the class:
1159 hs << "};\n\n";
1160 }
1161
1162 if (!customNamespace.isEmpty()) {
1163 hs << "} // end of namespace " << customNamespace << "\n"
1164 << "\n";
1165 cs << "} // end of namespace " << customNamespace << "\n"
1166 << "\n";
1167 }
1168
1169 // close the include guard
1170 hs << "#endif\n";
1171
1172 QString mocName = moc(filename);
1173 if (includeMocs && !mocName.isEmpty())
1174 cs << "\n"
1175 "#include \"" << mocName << "\"\n";
1176
1177 cs.flush();
1178 hs.flush();
1179
1180 QFile file;
1181 const bool headerOpen = openFile(headerName, file);
1182 if (headerOpen)
1183 file.write(headerData);
1184
1185 if (headerName == cppName) {
1186 if (headerOpen)
1187 file.write(cppData);
1188 } else {
1189 QFile cppFile;
1190 if (openFile(cppName, cppFile))
1191 cppFile.write(cppData);
1192 }
1193}
1194
1195int QDBusXmlToCpp::run(const QCoreApplication &app)
1196{
1197 QCommandLineParser parser;
1198 parser.setApplicationDescription(
1199 "Produces the C++ code to implement the interfaces defined in the input file.\n\n"
1200 "If the file name given to the options -a and -p does not end in .cpp or .h, the\n"
1201 "program will automatically append the suffixes and produce both files.\n"
1202 "You can also use a colon (:) to separate the header name from the source file\n"
1203 "name, as in '-a filename_p.h:filename.cpp'.\n\n"
1204 "If you pass a dash (-) as the argument to either -p or -a, the output is written\n"
1205 "to the standard output."_L1);
1206
1207 parser.addHelpOption();
1208 parser.addVersionOption();
1209 parser.addPositionalArgument(u"xml-or-xml-file"_s, u"XML file to use."_s);
1210 parser.addPositionalArgument(u"interfaces"_s, u"List of interfaces to use."_s,
1211 u"[interfaces ...]"_s);
1212
1213 QCommandLineOption adapterCodeOption(QStringList{u"a"_s, u"adaptor"_s},
1214 u"Write the adaptor code to <filename>"_s, u"filename"_s);
1215 parser.addOption(adapterCodeOption);
1216
1217 QCommandLineOption classNameOption(QStringList{u"c"_s, u"classname"_s},
1218 u"Use <classname> as the class name for the generated classes. "
1219 u"This option can only be used when processing a single interface."_s,
1220 u"classname"_s);
1221 parser.addOption(classNameOption);
1222
1223 QCommandLineOption namespaceOption(QStringList{u"namespace"_s},
1224 u"Put all generated classes into the namespace <namespace>. "_s, u"namespace"_s);
1225 parser.addOption(namespaceOption);
1226
1227 QCommandLineOption addIncludeOption(QStringList{u"i"_s, u"include"_s},
1228 u"Add #include \"filename\" to the output"_s, u"filename"_s);
1229 parser.addOption(addIncludeOption);
1230
1231 QCommandLineOption addGlobalIncludeOption(QStringList{u"I"_s, u"global-include"_s},
1232 u"Add #include <filename> to the output"_s, u"filename"_s);
1233 parser.addOption(addGlobalIncludeOption);
1234
1235 QCommandLineOption adapterParentOption(u"l"_s,
1236 u"When generating an adaptor, use <classname> as the parent class"_s, u"classname"_s);
1237 parser.addOption(adapterParentOption);
1238
1239 QCommandLineOption mocIncludeOption(QStringList{u"m"_s, u"moc"_s},
1240 u"Generate #include \"filename.moc\" statements in the .cpp files"_s);
1241 parser.addOption(mocIncludeOption);
1242
1243 QCommandLineOption noNamespaceOption(QStringList{u"N"_s, u"no-namespaces"_s},
1244 u"Do not export the generated class into the D-Bus specific namespace"_s);
1245 parser.addOption(noNamespaceOption);
1246
1247 QCommandLineOption proxyCodeOption(QStringList{u"p"_s, u"proxy"_s},
1248 u"Write the proxy code to <filename>"_s, u"filename"_s);
1249 parser.addOption(proxyCodeOption);
1250
1251 QCommandLineOption verboseOption(QStringList{u"V"_s, u"verbose"_s},
1252 u"Be verbose."_s);
1253 parser.addOption(verboseOption);
1254
1255 parser.process(app);
1256
1257 QString adaptorFile = parser.value(adapterCodeOption);
1258 globalClassName = parser.value(classNameOption);
1259 includes = parser.values(addIncludeOption);
1260 globalIncludes = parser.values(addGlobalIncludeOption);
1261 parentClassName = parser.value(adapterParentOption);
1262 customNamespace = parser.value(namespaceOption);
1263 includeMocs = parser.isSet(mocIncludeOption);
1264 skipNamespaces = parser.isSet(noNamespaceOption);
1265 QString proxyFile = parser.value(proxyCodeOption);
1266 bool verbose = parser.isSet(verboseOption);
1267
1268 wantedInterfaces = parser.positionalArguments();
1269 if (!wantedInterfaces.isEmpty()) {
1270 inputFile = wantedInterfaces.takeFirst();
1271
1272 QFileInfo inputInfo(inputFile);
1273 if (!inputInfo.exists() || !inputInfo.isFile() || !inputInfo.isReadable()) {
1274 qCritical("Error: Input %s is not a file or cannot be accessed\n", qPrintable(inputFile));
1275 return 1;
1276 }
1277 }
1278
1279 if (verbose)
1280 QLoggingCategory::setFilterRules(u"dbus.parser.debug=true"_s);
1281
1282 QDBusIntrospection::Interfaces interfaces = readInput();
1283 cleanInterfaces(interfaces);
1284
1285 if (!globalClassName.isEmpty() && interfaces.count() != 1) {
1286 qCritical("Option -c/--classname can only be used with a single interface.\n");
1287 return 1;
1288 }
1289
1290 QStringList args = app.arguments();
1291 args.removeFirst();
1292 commandLine = PROGRAMNAME " "_L1 + args.join(u' ');
1293
1294 if (!proxyFile.isEmpty() || adaptorFile.isEmpty())
1295 writeProxy(proxyFile, interfaces);
1296
1297 if (!adaptorFile.isEmpty())
1298 writeAdaptor(adaptorFile, interfaces);
1299
1300 return 0;
1301}
1302
1303int main(int argc, char **argv)
1304{
1305 QCoreApplication app(argc, argv);
1306 QCoreApplication::setApplicationName(QStringLiteral(PROGRAMNAME));
1307 QCoreApplication::setApplicationVersion(QStringLiteral(PROGRAMVERSION));
1308
1309 return QDBusXmlToCpp().run(app);
1310}
int main(int argc, char *argv[])
[2]
Definition buffer.cpp:77
The QCommandLineParser class provides a means for handling the command line options.
int run(const QCoreApplication &app)
\inmodule QtCore
Definition qfile.h:95
QT_FORWARD_DECLARE_CLASS(QTextStream)
const QString & asString(const QString &s)
Definition qstring.h:1661
#define ANNOTATION_NO_WAIT
#define PROGRAMNAME
#define PROGRAMCOPYRIGHT
#define PROGRAMVERSION
static const char forwardDeclarations[]
static bool isSupportedSuffix(QStringView suffix)
static QString stringify(const QString &data)
static QString moc(const QString &name)
static const char includeList[]
static QString cpp(const QString &name)
static QString templateArg(const QByteArray &arg)
static QString methodName(const QDBusIntrospection::Method &method)
static QString header(const QString &name)
static QStringList makeArgNames(const QDBusIntrospection::Arguments &inputArgs, const QDBusIntrospection::Arguments &outputArgs=QDBusIntrospection::Arguments())
static QString constRefArg(const QByteArray &arg)
static bool openFile(const QString &fileName, QFile &file)
static QString nonConstRefArg(const QByteArray &arg)
#define qPrintable(string)
Definition qstring.h:1666