1// Copyright (C) 2016 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 "uic.h"
5#include "ui4.h"
6#include "driver.h"
7#include "option.h"
8#include "treewalker.h"
9#include "validator.h"
10
11#include "cppwriteincludes.h"
12#include "cppwritedeclaration.h"
13#include <pythonwritedeclaration.h>
14#include <pythonwriteimports.h>
15
16#include <language.h>
17
18#include <qxmlstream.h>
19#include <qfileinfo.h>
20#include <qscopedpointer.h>
21#include <qtextstream.h>
22
23QT_BEGIN_NAMESPACE
24
25using namespace Qt::StringLiterals;
26
27Uic::Uic(Driver *d)
28 : drv(d),
29 out(d->output()),
30 opt(d->option())
31{
32}
33
34Uic::~Uic() = default;
35
36bool Uic::printDependencies()
37{
38 QString fileName = opt.inputFile;
39
40 QFile f;
41 if (fileName.isEmpty()) {
42 if (!f.open(stdin, ioFlags: QIODevice::ReadOnly))
43 return false;
44 } else {
45 f.setFileName(fileName);
46 if (!f.open(flags: QIODevice::ReadOnly))
47 return false;
48 }
49
50 DomUI *ui = nullptr;
51 {
52 QXmlStreamReader reader;
53 reader.setDevice(&f);
54 ui = parseUiFile(reader);
55 if (!ui)
56 return false;
57 }
58
59 if (DomIncludes *includes = ui->elementIncludes()) {
60 const auto incls = includes->elementInclude();
61 for (DomInclude *incl : incls) {
62 QString file = incl->text();
63 if (file.isEmpty())
64 continue;
65
66 fprintf(stdout, format: "%s\n", file.toLocal8Bit().constData());
67 }
68 }
69
70 if (DomCustomWidgets *customWidgets = ui->elementCustomWidgets()) {
71 const auto elementCustomWidget = customWidgets->elementCustomWidget();
72 for (DomCustomWidget *customWidget : elementCustomWidget) {
73 if (DomHeader *header = customWidget->elementHeader()) {
74 QString file = header->text();
75 if (file.isEmpty())
76 continue;
77
78 fprintf(stdout, format: "%s\n", file.toLocal8Bit().constData());
79 }
80 }
81 }
82
83 delete ui;
84
85 return true;
86}
87
88void Uic::writeCopyrightHeaderCpp(const DomUI *ui) const
89{
90 QString comment = ui->elementComment();
91 if (!comment.isEmpty())
92 out << "/*\n" << comment << "\n*/\n\n";
93
94 out << "/********************************************************************************\n";
95 out << "** Form generated from reading UI file '" << QFileInfo(opt.inputFile).fileName() << "'\n";
96 out << "**\n";
97 out << "** Created by: Qt User Interface Compiler version " << QT_VERSION_STR << "\n";
98 out << "**\n";
99 out << "** WARNING! All changes made in this file will be lost when recompiling UI file!\n";
100 out << "********************************************************************************/\n\n";
101}
102
103// Format existing UI file comments for Python with some smartness : Replace all
104// leading C++ comment characters by '#' or prepend '#' if needed.
105
106static inline bool isCppCommentChar(QChar c)
107{
108 return c == u'/' || c == u'*';
109}
110
111static qsizetype leadingCppCommentCharCount(QStringView s)
112{
113 qsizetype i = 0;
114 for (const qsizetype size = s.size(); i < size && isCppCommentChar(c: s.at(n: i)); ++i) {
115 }
116 return i;
117}
118
119void Uic::writeCopyrightHeaderPython(const DomUI *ui) const
120{
121 QString comment = ui->elementComment();
122 if (!comment.isEmpty()) {
123 const auto lines = QStringView{comment}.split(sep: u'\n');
124 for (const auto &line : lines) {
125 if (const auto leadingCommentChars = leadingCppCommentCharCount(s: line)) {
126 out << language::repeat(leadingCommentChars, '#')
127 << line.right(n: line.size() - leadingCommentChars);
128 } else {
129 if (!line.startsWith(c: u'#'))
130 out << "# ";
131 out << line;
132 }
133 out << '\n';
134 }
135 out << '\n';
136 }
137
138 out << language::repeat(80, '#') << "\n## Form generated from reading UI file '"
139 << QFileInfo(opt.inputFile).fileName()
140 << "'\n##\n## Created by: Qt User Interface Compiler version " << QT_VERSION_STR
141 << "\n##\n## WARNING! All changes made in this file will be lost when recompiling UI file!\n"
142 << language::repeat(80, '#') << "\n\n";
143}
144
145// Check the version with a stream reader at the <ui> element.
146
147static double versionFromUiAttribute(QXmlStreamReader &reader)
148{
149 const QXmlStreamAttributes attributes = reader.attributes();
150 const auto versionAttribute = "version"_L1;
151 if (!attributes.hasAttribute(qualifiedName: versionAttribute))
152 return 4.0;
153 const QStringView version = attributes.value(qualifiedName: versionAttribute);
154 return version.toDouble();
155}
156
157DomUI *Uic::parseUiFile(QXmlStreamReader &reader)
158{
159 DomUI *ui = nullptr;
160
161 const auto uiElement = "ui"_L1;
162 while (!reader.atEnd()) {
163 if (reader.readNext() == QXmlStreamReader::StartElement) {
164 if (reader.name().compare(s: uiElement, cs: Qt::CaseInsensitive) == 0
165 && !ui) {
166 const double version = versionFromUiAttribute(reader);
167 if (version < 4.0) {
168 const QString msg = QString::fromLatin1(ba: "uic: File generated with too old version of Qt Widgets Designer (%1)").arg(a: version);
169 fprintf(stderr, format: "%s\n", qPrintable(msg));
170 return nullptr;
171 }
172
173 ui = new DomUI();
174 ui->read(reader);
175 } else {
176 reader.raiseError(message: "Unexpected element "_L1 + reader.name().toString());
177 }
178 }
179 }
180 if (reader.hasError()) {
181 delete ui;
182 ui = nullptr;
183 fprintf(stderr, format: "%s\n", qPrintable(QString::fromLatin1("uic: Error in line %1, column %2 : %3")
184 .arg(reader.lineNumber()).arg(reader.columnNumber())
185 .arg(reader.errorString())));
186 }
187
188 return ui;
189}
190
191bool Uic::write(QIODevice *in)
192{
193 QScopedPointer<DomUI> ui;
194 {
195 QXmlStreamReader reader;
196 reader.setDevice(in);
197 ui.reset(other: parseUiFile(reader));
198 }
199
200 if (ui.isNull())
201 return false;
202
203 double version = ui->attributeVersion().toDouble();
204 if (version < 4.0) {
205 fprintf(stderr, format: "uic: File generated with too old version of Qt Widgets Designer\n");
206 return false;
207 }
208
209 const QString &language = ui->attributeLanguage();
210 driver()->setUseIdBasedTranslations(ui->attributeIdbasedtr());
211
212 if (!language.isEmpty() && language.compare(other: "c++"_L1, cs: Qt::CaseInsensitive) != 0) {
213 fprintf(stderr, format: "uic: File is not a \"c++\" ui file, language=%s\n", qPrintable(language));
214 return false;
215 }
216
217 return write(ui: ui.data());
218}
219
220bool Uic::write(DomUI *ui)
221{
222 if (!ui || !ui->elementWidget())
223 return false;
224
225 const auto lang = language::language();
226
227 if (lang == Language::Python)
228 out << "# -*- coding: utf-8 -*-\n\n";
229
230 if (opt.copyrightHeader) {
231 switch (language::language()) {
232 case Language::Cpp:
233 writeCopyrightHeaderCpp(ui);
234 break;
235 case Language::Python:
236 writeCopyrightHeaderPython(ui);
237 break;
238 }
239 }
240
241 if (opt.headerProtection && lang == Language::Cpp) {
242 writeHeaderProtectionStart();
243 out << "\n";
244 }
245
246 pixFunction = ui->elementPixmapFunction();
247 if (pixFunction == "QPixmap::fromMimeSource"_L1 || pixFunction == "qPixmapFromMimeSource"_L1) {
248 fprintf(stderr, format: "%s: Warning: Obsolete pixmap function '%s' specified in the UI file.\n",
249 qPrintable(opt.messagePrefix()), qPrintable(pixFunction));
250 pixFunction.clear();
251 }
252
253 info.acceptUI(node: ui);
254 cWidgetsInfo.acceptUI(node: ui);
255
256 switch (language::language()) {
257 case Language::Cpp: {
258 CPP::WriteIncludes writeIncludes(this);
259 writeIncludes.acceptUI(node: ui);
260 Validator(this).acceptUI(node: ui);
261 CPP::WriteDeclaration(this).acceptUI(node: ui);
262 }
263 break;
264 case Language::Python: {
265 Python::WriteImports writeImports(this);
266 writeImports.acceptUI(node: ui);
267 Validator(this).acceptUI(node: ui);
268 Python::WriteDeclaration(this).acceptUI(node: ui);
269 }
270 break;
271 }
272
273 if (opt.headerProtection && lang == Language::Cpp)
274 writeHeaderProtectionEnd();
275
276 return true;
277}
278
279void Uic::writeHeaderProtectionStart()
280{
281 QString h = drv->headerFileName();
282 out << "#ifndef " << h << "\n"
283 << "#define " << h << "\n";
284}
285
286void Uic::writeHeaderProtectionEnd()
287{
288 QString h = drv->headerFileName();
289 out << "#endif // " << h << "\n";
290}
291
292bool Uic::isButton(const QString &className) const
293{
294 static const QStringList buttons = {
295 u"QRadioButton"_s, u"QToolButton"_s,
296 u"QCheckBox"_s, u"QPushButton"_s,
297 u"QCommandLinkButton"_s
298 };
299 return customWidgetsInfo()->extendsOneOf(className, baseClassNames: buttons);
300}
301
302bool Uic::isContainer(const QString &className) const
303{
304 static const QStringList containers = {
305 u"QStackedWidget"_s, u"QToolBox"_s,
306 u"QTabWidget"_s, u"QScrollArea"_s,
307 u"QMdiArea"_s, u"QWizard"_s,
308 u"QDockWidget"_s
309 };
310
311 return customWidgetsInfo()->extendsOneOf(className, baseClassNames: containers);
312}
313
314bool Uic::isMenu(const QString &className) const
315{
316 static const QStringList menus = {
317 u"QMenu"_s, u"QPopupMenu"_s
318 };
319 return customWidgetsInfo()->extendsOneOf(className, baseClassNames: menus);
320}
321
322QT_END_NAMESPACE
323

source code of qtbase/src/tools/uic/uic.cpp