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 | |
23 | QT_BEGIN_NAMESPACE |
24 | |
25 | using namespace Qt::StringLiterals; |
26 | |
27 | Uic::Uic(Driver *d) |
28 | : drv(d), |
29 | out(d->output()), |
30 | opt(d->option()) |
31 | { |
32 | } |
33 | |
34 | Uic::~Uic() = default; |
35 | |
36 | bool 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 * = 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 | |
88 | void Uic::(const DomUI *ui) const |
89 | { |
90 | QString = 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 | |
106 | static inline bool (QChar c) |
107 | { |
108 | return c == u'/' || c == u'*'; |
109 | } |
110 | |
111 | static qsizetype (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 | |
119 | void Uic::(const DomUI *ui) const |
120 | { |
121 | QString = 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 = 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 | |
147 | static 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 | |
157 | DomUI *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 | |
191 | bool 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 | |
220 | bool 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 | |
279 | void Uic::() |
280 | { |
281 | QString h = drv->headerFileName(); |
282 | out << "#ifndef " << h << "\n" |
283 | << "#define " << h << "\n" ; |
284 | } |
285 | |
286 | void Uic::() |
287 | { |
288 | QString h = drv->headerFileName(); |
289 | out << "#endif // " << h << "\n" ; |
290 | } |
291 | |
292 | bool 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 | |
302 | bool 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 | |
314 | bool Uic::(const QString &className) const |
315 | { |
316 | static const QStringList = { |
317 | u"QMenu"_s , u"QPopupMenu"_s |
318 | }; |
319 | return customWidgetsInfo()->extendsOneOf(className, baseClassNames: menus); |
320 | } |
321 | |
322 | QT_END_NAMESPACE |
323 | |