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 | 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 * = 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 | |
87 | void Uic::(const DomUI *ui) const |
88 | { |
89 | QString = 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 | |
105 | static inline bool (QChar c) |
106 | { |
107 | return c == u'/' || c == u'*'; |
108 | } |
109 | |
110 | static int (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 | |
118 | void Uic::(const DomUI *ui) const |
119 | { |
120 | QString = 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 = 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 | |
146 | static 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 | |
156 | DomUI *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 | |
190 | bool 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 | |
219 | bool 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 | |
278 | void Uic::() |
279 | { |
280 | QString h = drv->headerFileName(); |
281 | out << "#ifndef " << h << "\n" |
282 | << "#define " << h << "\n" ; |
283 | } |
284 | |
285 | void Uic::() |
286 | { |
287 | QString h = drv->headerFileName(); |
288 | out << "#endif // " << h << "\n" ; |
289 | } |
290 | |
291 | bool 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 | |
301 | bool 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 | |
313 | bool Uic::(const QString &className) const |
314 | { |
315 | static const QStringList = { |
316 | u"QMenu"_s , u"QPopupMenu"_s |
317 | }; |
318 | return customWidgetsInfo()->extendsOneOf(className, baseClassNames: menus); |
319 | } |
320 | |
321 | QT_END_NAMESPACE |
322 | |