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 "driver.h"
30#include "uic.h"
31#include "ui4.h"
32
33#include <language.h>
34
35#include <qfileinfo.h>
36#include <qdebug.h>
37
38#include <algorithm>
39
40QT_BEGIN_NAMESPACE
41
42Driver::Driver()
43 : m_stdout(stdout, QFile::WriteOnly | QFile::Text)
44{
45 m_output = &m_stdout;
46}
47
48Driver::~Driver() = default;
49
50static inline QString spacerItemClass() { return QStringLiteral("QSpacerItem"); }
51static inline QString actionGroupClass() { return QStringLiteral("QActionGroup"); }
52static inline QString actionClass() { return QStringLiteral("QAction"); }
53static inline QString buttonGroupClass() { return QStringLiteral("QButtonGroup"); }
54
55template <class DomClass>
56Driver::DomObjectHashConstIt<DomClass>
57 Driver::findByAttributeNameIt(const DomObjectHash<DomClass> &domHash,
58 const QString &name) const
59{
60 const auto end = domHash.cend();
61 for (auto it = domHash.cbegin(); it != end; ++it) {
62 if (it.key()->attributeName() == name)
63 return it;
64 }
65 return end;
66}
67
68template <class DomClass>
69const DomClass *Driver::findByAttributeName(const DomObjectHash<DomClass> &domHash,
70 const QString &name) const
71{
72 auto it = findByAttributeNameIt(domHash, name);
73 return it != domHash.cend() ? it.key() : nullptr;
74}
75
76template <class DomClass>
77QString Driver::findOrInsert(DomObjectHash<DomClass> *domHash, const DomClass *dom,
78 const QString &className, bool isMember)
79{
80 auto it = domHash->find(dom);
81 if (it == domHash->end()) {
82 const QString name = this->unique(dom->attributeName(), className);
83 it = domHash->insert(dom, isMember ? language::self + name : name);
84 }
85 return it.value();
86}
87
88QString Driver::findOrInsertWidget(const DomWidget *ui_widget)
89{
90 // Top level is passed into setupUI(), everything else is a member variable
91 const bool isMember = !m_widgets.isEmpty();
92 return findOrInsert(&m_widgets, ui_widget, ui_widget->attributeClass(), isMember);
93}
94
95QString Driver::findOrInsertSpacer(const DomSpacer *ui_spacer)
96{
97 return findOrInsert(&m_spacers, ui_spacer, spacerItemClass());
98}
99
100QString Driver::findOrInsertLayout(const DomLayout *ui_layout)
101{
102 return findOrInsert(&m_layouts, ui_layout, ui_layout->attributeClass());
103}
104
105QString Driver::findOrInsertLayoutItem(const DomLayoutItem *ui_layoutItem)
106{
107 switch (ui_layoutItem->kind()) {
108 case DomLayoutItem::Widget:
109 return findOrInsertWidget(ui_layoutItem->elementWidget());
110 case DomLayoutItem::Spacer:
111 return findOrInsertSpacer(ui_layoutItem->elementSpacer());
112 case DomLayoutItem::Layout:
113 return findOrInsertLayout(ui_layoutItem->elementLayout());
114 case DomLayoutItem::Unknown:
115 break;
116 }
117
118 Q_ASSERT( 0 );
119
120 return QString();
121}
122
123QString Driver::findOrInsertActionGroup(const DomActionGroup *ui_group)
124{
125 return findOrInsert(&m_actionGroups, ui_group, actionGroupClass());
126}
127
128QString Driver::findOrInsertAction(const DomAction *ui_action)
129{
130 return findOrInsert(&m_actions, ui_action, actionClass());
131}
132
133QString Driver::findOrInsertButtonGroup(const DomButtonGroup *ui_group)
134{
135 return findOrInsert(&m_buttonGroups, ui_group, buttonGroupClass());
136}
137
138// Find a group by its non-uniqified name
139const DomButtonGroup *Driver::findButtonGroup(const QString &attributeName) const
140{
141 return findByAttributeName(m_buttonGroups, attributeName);
142}
143
144
145QString Driver::findOrInsertName(const QString &name)
146{
147 return unique(name);
148}
149
150QString Driver::normalizedName(const QString &name)
151{
152 QString result = name;
153 std::replace_if(result.begin(), result.end(),
154 [] (QChar c) { return !c.isLetterOrNumber(); },
155 QLatin1Char('_'));
156 return result;
157}
158
159QString Driver::unique(const QString &instanceName, const QString &className)
160{
161 QString name;
162 bool alreadyUsed = false;
163
164 if (!instanceName.isEmpty()) {
165 name = normalizedName(instanceName);
166 QString base = name;
167
168 for (int id = 1; m_nameRepository.contains(name); ++id) {
169 alreadyUsed = true;
170 name = base + QString::number(id);
171 }
172 } else if (!className.isEmpty()) {
173 name = unique(qtify(className));
174 } else {
175 name = unique(QLatin1String("var"));
176 }
177
178 if (alreadyUsed && !className.isEmpty()) {
179 fprintf(stderr, "%s: Warning: The name '%s' (%s) is already in use, defaulting to '%s'.\n",
180 qPrintable(m_option.messagePrefix()),
181 qPrintable(instanceName), qPrintable(className),
182 qPrintable(name));
183 }
184
185 m_nameRepository.insert(name, true);
186 return name;
187}
188
189QString Driver::qtify(const QString &name)
190{
191 QString qname = name;
192
193 if (qname.at(0) == QLatin1Char('Q') || qname.at(0) == QLatin1Char('K'))
194 qname.remove(0, 1);
195
196 for (int i = 0, size = qname.size(); i < size && qname.at(i).isUpper(); ++i)
197 qname[i] = qname.at(i).toLower();
198
199 return qname;
200}
201
202static bool isAnsiCCharacter(QChar c)
203{
204 return (c.toUpper() >= QLatin1Char('A') && c.toUpper() <= QLatin1Char('Z'))
205 || c.isDigit() || c == QLatin1Char('_');
206}
207
208QString Driver::headerFileName() const
209{
210 QString name = m_option.outputFile;
211
212 if (name.isEmpty()) {
213 name = QLatin1String("ui_"); // ### use ui_ as prefix.
214 name.append(m_option.inputFile);
215 }
216
217 return headerFileName(name);
218}
219
220QString Driver::headerFileName(const QString &fileName)
221{
222 if (fileName.isEmpty())
223 return headerFileName(QLatin1String("noname"));
224
225 QFileInfo info(fileName);
226 QString baseName = info.baseName();
227 // Transform into a valid C++ identifier
228 if (!baseName.isEmpty() && baseName.at(0).isDigit())
229 baseName.prepend(QLatin1Char('_'));
230 for (int i = 0; i < baseName.size(); ++i) {
231 QChar c = baseName.at(i);
232 if (!isAnsiCCharacter(c)) {
233 // Replace character by its unicode value
234 QString hex = QString::number(c.unicode(), 16);
235 baseName.replace(i, 1, QLatin1Char('_') + hex + QLatin1Char('_'));
236 i += hex.size() + 1;
237 }
238 }
239 return baseName.toUpper() + QLatin1String("_H");
240}
241
242bool Driver::printDependencies(const QString &fileName)
243{
244 Q_ASSERT(m_option.dependencies == true);
245
246 m_option.inputFile = fileName;
247
248 Uic tool(this);
249 return tool.printDependencies();
250}
251
252bool Driver::uic(const QString &fileName, DomUI *ui, QTextStream *out)
253{
254 m_option.inputFile = fileName;
255 setUseIdBasedTranslations(ui->attributeIdbasedtr());
256
257 QTextStream *oldOutput = m_output;
258
259 m_output = out != nullptr ? out : &m_stdout;
260
261 Uic tool(this);
262 const bool result = tool.write(ui);
263
264 m_output = oldOutput;
265
266 return result;
267}
268
269bool Driver::uic(const QString &fileName, QTextStream *out)
270{
271 QFile f;
272 if (fileName.isEmpty())
273 f.open(stdin, QIODevice::ReadOnly);
274 else {
275 f.setFileName(fileName);
276 if (!f.open(QIODevice::ReadOnly))
277 return false;
278 }
279
280 m_option.inputFile = fileName;
281
282 QTextStream *oldOutput = m_output;
283 bool deleteOutput = false;
284
285 if (out) {
286 m_output = out;
287 } else {
288#ifdef Q_OS_WIN
289 // As one might also redirect the output to a file on win,
290 // we should not create the textstream with QFile::Text flag.
291 // The redirected file is opened in TextMode and this will
292 // result in broken line endings as writing will replace \n again.
293 m_output = new QTextStream(stdout, QIODevice::WriteOnly);
294#else
295 m_output = new QTextStream(stdout, QIODevice::WriteOnly | QFile::Text);
296#endif
297 deleteOutput = true;
298 }
299
300 Uic tool(this);
301 bool rtn = tool.write(&f);
302 f.close();
303
304 if (deleteOutput)
305 delete m_output;
306
307 m_output = oldOutput;
308
309 return rtn;
310}
311
312const DomWidget *Driver::widgetByName(const QString &attributeName) const
313{
314 return findByAttributeName(m_widgets, attributeName);
315}
316
317QString Driver::widgetVariableName(const QString &attributeName) const
318{
319 auto it = findByAttributeNameIt(m_widgets, attributeName);
320 return it != m_widgets.cend() ? it.value() : QString();
321}
322
323const DomActionGroup *Driver::actionGroupByName(const QString &attributeName) const
324{
325 return findByAttributeName(m_actionGroups, attributeName);
326}
327
328const DomAction *Driver::actionByName(const QString &attributeName) const
329{
330 return findByAttributeName(m_actions, attributeName);
331}
332
333QT_END_NAMESPACE
334