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