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 
48 QT_BEGIN_NAMESPACE
49 
Uic(Driver * d)50 Uic::Uic(Driver *d)
51      : drv(d),
52        out(d->output()),
53        opt(d->option())
54 {
55 }
56 
57 Uic::~Uic() = default;
58 
printDependencies()59 bool 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 
writeCopyrightHeaderCpp(const DomUI * ui) const110 void 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 
isCppCommentChar(QChar c)128 static inline bool isCppCommentChar(QChar c)
129 {
130     return  c == QLatin1Char('/') || c == QLatin1Char('*');
131 }
132 
leadingCppCommentCharCount(const QStringRef & s)133 static int leadingCppCommentCharCount(const QStringRef &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 
writeCopyrightHeaderPython(const DomUI * ui) const141 void Uic::writeCopyrightHeaderPython(const DomUI *ui) const
142 {
143     QString comment = ui->elementComment();
144     if (!comment.isEmpty()) {
145         const auto lines = comment.splitRef(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 
versionFromUiAttribute(QXmlStreamReader & reader)169 static 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 QStringRef version = attributes.value(versionAttribute);
176     return version.toDouble();
177 }
178 
parseUiFile(QXmlStreamReader & reader)179 DomUI *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 
write(QIODevice * in)213 bool 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 
write(DomUI * ui)242 bool 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 
writeHeaderProtectionStart()302 void Uic::writeHeaderProtectionStart()
303 {
304     QString h = drv->headerFileName();
305     out << "#ifndef " << h << "\n"
306         << "#define " << h << "\n";
307 }
308 
writeHeaderProtectionEnd()309 void Uic::writeHeaderProtectionEnd()
310 {
311     QString h = drv->headerFileName();
312     out << "#endif // " << h << "\n";
313 }
314 
isButton(const QString & className) const315 bool 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 
isContainer(const QString & className) const325 bool 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 
isMenu(const QString & className) const337 bool 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 
345 QT_END_NAMESPACE
346