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

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