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 
40 QT_BEGIN_NAMESPACE
41 
Driver()42 Driver::Driver()
43     : m_stdout(stdout, QFile::WriteOnly | QFile::Text)
44 {
45     m_output = &m_stdout;
46 }
47 
48 Driver::~Driver() = default;
49 
spacerItemClass()50 static inline QString spacerItemClass()  { return QStringLiteral("QSpacerItem"); }
actionGroupClass()51 static inline QString actionGroupClass() { return QStringLiteral("QActionGroup"); }
actionClass()52 static inline QString actionClass()      { return QStringLiteral("QAction"); }
buttonGroupClass()53 static inline QString buttonGroupClass() { return QStringLiteral("QButtonGroup"); }
54 
55 template <class DomClass>
56 Driver::DomObjectHashConstIt<DomClass>
findByAttributeNameIt(const DomObjectHash<DomClass> & domHash,const QString & name) const57     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 
68 template <class DomClass>
findByAttributeName(const DomObjectHash<DomClass> & domHash,const QString & name) const69 const 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 
76 template <class DomClass>
findOrInsert(DomObjectHash<DomClass> * domHash,const DomClass * dom,const QString & className,bool isMember)77 QString 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 
findOrInsertWidget(const DomWidget * ui_widget)88 QString 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 
findOrInsertSpacer(const DomSpacer * ui_spacer)95 QString Driver::findOrInsertSpacer(const DomSpacer *ui_spacer)
96 {
97     return findOrInsert(&m_spacers, ui_spacer, spacerItemClass());
98 }
99 
findOrInsertLayout(const DomLayout * ui_layout)100 QString Driver::findOrInsertLayout(const DomLayout *ui_layout)
101 {
102     return findOrInsert(&m_layouts, ui_layout, ui_layout->attributeClass());
103 }
104 
findOrInsertLayoutItem(const DomLayoutItem * ui_layoutItem)105 QString 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 
findOrInsertActionGroup(const DomActionGroup * ui_group)123 QString Driver::findOrInsertActionGroup(const DomActionGroup *ui_group)
124 {
125     return findOrInsert(&m_actionGroups, ui_group, actionGroupClass());
126 }
127 
findOrInsertAction(const DomAction * ui_action)128 QString Driver::findOrInsertAction(const DomAction *ui_action)
129 {
130     return findOrInsert(&m_actions, ui_action, actionClass());
131 }
132 
findOrInsertButtonGroup(const DomButtonGroup * ui_group)133 QString 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
findButtonGroup(const QString & attributeName) const139 const DomButtonGroup *Driver::findButtonGroup(const QString &attributeName) const
140 {
141     return findByAttributeName(m_buttonGroups, attributeName);
142 }
143 
144 
findOrInsertName(const QString & name)145 QString Driver::findOrInsertName(const QString &name)
146 {
147     return unique(name);
148 }
149 
normalizedName(const QString & name)150 QString 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 
unique(const QString & instanceName,const QString & className)159 QString 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 
qtify(const QString & name)189 QString 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 
isAnsiCCharacter(QChar c)202 static bool isAnsiCCharacter(QChar c)
203 {
204     return (c.toUpper() >= QLatin1Char('A') && c.toUpper() <= QLatin1Char('Z'))
205            || c.isDigit() || c == QLatin1Char('_');
206 }
207 
headerFileName() const208 QString 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 
headerFileName(const QString & fileName)220 QString 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 
printDependencies(const QString & fileName)242 bool 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 
uic(const QString & fileName,DomUI * ui,QTextStream * out)252 bool 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 
uic(const QString & fileName,QTextStream * out)269 bool 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 
widgetByName(const QString & attributeName) const312 const DomWidget *Driver::widgetByName(const QString &attributeName) const
313 {
314     return findByAttributeName(m_widgets, attributeName);
315 }
316 
widgetVariableName(const QString & attributeName) const317 QString 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 
actionGroupByName(const QString & attributeName) const323 const DomActionGroup *Driver::actionGroupByName(const QString &attributeName) const
324 {
325     return findByAttributeName(m_actionGroups, attributeName);
326 }
327 
actionByName(const QString & attributeName) const328 const DomAction *Driver::actionByName(const QString &attributeName) const
329 {
330     return findByAttributeName(m_actions, attributeName);
331 }
332 
333 QT_END_NAMESPACE
334