Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
pythonwriteimports.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:insignificant reason:build-tool
4
6#include "language.h"
7
8#include <customwidgetsinfo.h>
9#include <option.h>
10#include <uic.h>
11#include <driver.h>
12
13#include <ui4.h>
14
15#include <QtCore/qdir.h>
16#include <QtCore/qfileinfo.h>
17#include <QtCore/qtextstream.h>
18
19#include <algorithm>
20
22
23using namespace Qt::StringLiterals;
24
25// Generate imports for Python. Note some things differ from C++:
26// - qItemView->header()->setFoo() does not require QHeaderView to be imported
27// - qLabel->setFrameShape(QFrame::Box) however requires QFrame to be imported
28// (see acceptProperty())
29
30namespace Python {
31
32// Classes required for properties
34{
35 return {
36 {QStringLiteral("QtCore"),
37 {QStringLiteral("QCoreApplication"), QStringLiteral("QDate"),
38 QStringLiteral("QDateTime"), QStringLiteral("QLocale"),
39 QStringLiteral("QMetaObject"), QStringLiteral("QObject"),
40 QStringLiteral("QPoint"), QStringLiteral("QRect"),
41 QStringLiteral("QSize"), QStringLiteral("QTime"),
42 QStringLiteral("QUrl"), QStringLiteral("Qt")},
43 },
44 {QStringLiteral("QtGui"),
45 {QStringLiteral("QBrush"), QStringLiteral("QColor"),
46 QStringLiteral("QConicalGradient"), QStringLiteral("QCursor"),
47 QStringLiteral("QGradient"), QStringLiteral("QFont"),
48 QStringLiteral("QFontDatabase"), QStringLiteral("QIcon"),
49 QStringLiteral("QImage"), QStringLiteral("QKeySequence"),
50 QStringLiteral("QLinearGradient"), QStringLiteral("QPalette"),
51 QStringLiteral("QPainter"), QStringLiteral("QPixmap"),
52 QStringLiteral("QTransform"), QStringLiteral("QRadialGradient")}
53 },
54 // Add QWidget for QWidget.setTabOrder()
55 {QStringLiteral("QtWidgets"),
56 {QStringLiteral("QSizePolicy"), QStringLiteral("QWidget")}
57 }
58 };
59}
60
61// Helpers for WriteImports::ClassesPerModule maps
62static void insertClass(const QString &module, const QString &className,
63 WriteImports::ClassesPerModule *c)
64{
65 auto usedIt = c->find(module);
66 if (usedIt == c->end())
67 c->insert(module, {className});
68 else if (!usedIt.value().contains(className))
69 usedIt.value().append(className);
70}
71
72// Format a class list: "from A import (B, C)"
73static void formatImportClasses(QTextStream &str, QStringList classList)
74{
75 std::sort(classList.begin(), classList.end());
76
77 const qsizetype size = classList.size();
78 if (size > 1)
79 str << '(';
80 for (qsizetype i = 0; i < size; ++i) {
81 if (i > 0)
82 str << (i % 4 == 0 ? ",\n " : ", ");
83 QString name = language::fixClassName(classList.at(i));
84 if (const auto dotPos = name.indexOf(u'.'); dotPos != -1)
85 name.truncate(dotPos); // Import outer class only in case of nested
86 str << name;
87 }
88 if (size > 1)
89 str << ')';
90}
91
92static void formatClasses(QTextStream &str, const WriteImports::ClassesPerModule &c,
93 bool useStarImports = false,
94 const QByteArray &modulePrefix = {})
95{
96 for (auto it = c.cbegin(), end = c.cend(); it != end; ++it) {
97 str << "from " << modulePrefix << it.key() << " import ";
98 if (useStarImports)
99 str << "* # type: ignore";
100 else
101 formatImportClasses(str, it.value());
102 str << '\n';
103 }
104}
105
108{
109 for (const auto &e : classInfoEntries())
110 m_classToModule.insert(QLatin1StringView(e.klass), QLatin1StringView(e.module));
111}
112
113void WriteImports::acceptUI(DomUI *node)
114{
116
117 auto &output = uic()->output();
118 const bool useStarImports = uic()->driver()->option().useStarImports;
119
120 const QByteArray qtPrefix = QByteArrayLiteral("PySide")
121 + QByteArray::number(QT_VERSION_MAJOR) + '.';
122
123 formatClasses(output, m_qtClasses, useStarImports, qtPrefix);
124
125 if (!m_customWidgets.isEmpty() || !m_plainCustomWidgets.isEmpty()) {
126 output << '\n';
127 formatClasses(output, m_customWidgets, useStarImports);
128 for (const auto &w : m_plainCustomWidgets)
129 output << "import " << w << '\n';
130 }
131
132 if (auto *resources = node->elementResources()) {
133 const auto &includes = resources->elementInclude();
134 for (auto *include : includes) {
135 if (include->hasAttributeLocation())
136 writeResourceImport(include->attributeLocation());
137 }
138 output << '\n';
139 }
140}
141
142QString WriteImports::resourceAbsolutePath(QString resource) const
143{
144 // If we know the project root, generate an absolute Python import
145 // to the resource. options. pythonRoot is the Python path component
146 // under which the UI file is.
147 const auto &options = uic()->option();
148 if (!options.inputFile.isEmpty() && !options.pythonRoot.isEmpty()) {
149 resource = QDir::cleanPath(QFileInfo(options.inputFile).canonicalPath() + u'/' + resource);
150 if (resource.size() > options.pythonRoot.size())
151 resource.remove(0, options.pythonRoot.size() + 1);
152 }
153 // If nothing is known, we assume the directory pointed by "../" is the root
154 while (resource.startsWith(u"../"))
155 resource.remove(0, 3);
156 resource.replace(u'/', u'.');
157 return resource;
158}
159
160void WriteImports::writeResourceImport(const QString &module)
161{
162 const auto &options = uic()->option();
163 auto &str = uic()->output();
164
165 QString resource = QDir::cleanPath(module);
166 if (resource.endsWith(u".qrc"))
167 resource.chop(4);
168 const qsizetype basePos = resource.lastIndexOf(u'/') + 1;
169 // Change the name of a qrc file "dir/foo.qrc" file to the Python
170 // module name "foo_rc" according to project conventions.
171 if (options.rcPrefix)
172 resource.insert(basePos, u"rc_");
173 else
174 resource.append(u"_rc");
175
176 switch (options.pythonResourceImport) {
177 case Option::PythonResourceImport::Default:
178 str << "import " << QStringView{resource}.sliced(basePos) << '\n';
179 break;
180 case Option::PythonResourceImport::FromDot:
181 str << "from . import " << QStringView{resource}.sliced(basePos) << '\n';
182 break;
183 case Option::PythonResourceImport::Absolute:
184 str << "import " << resourceAbsolutePath(resource) << '\n';
185 break;
186 }
187}
188
189void WriteImports::doAdd(const QString &className, const DomCustomWidget *dcw)
190{
191 const CustomWidgetsInfo *cwi = uic()->customWidgetsInfo();
192 if (cwi->extends(className, "QListWidget"))
193 add(QStringLiteral("QListWidgetItem"));
194 else if (cwi->extends(className, "QTreeWidget"))
195 add(QStringLiteral("QTreeWidgetItem"));
196 else if (cwi->extends(className, "QTableWidget"))
197 add(QStringLiteral("QTableWidgetItem"));
198
199 if (dcw != nullptr) {
200 addPythonCustomWidget(className, dcw);
201 return;
202 }
203
204 if (!addQtClass(className))
205 qWarning("WriteImports::add(): Unknown Qt class %s", qPrintable(className));
206}
207
208bool WriteImports::addQtClass(const QString &className)
209{
210 // QVariant is not exposed in PySide
211 if (className == u"QVariant" || className == u"Qt")
212 return true;
213
214 const auto moduleIt = m_classToModule.constFind(className);
215 const bool result = moduleIt != m_classToModule.cend();
216 if (result)
217 insertClass(moduleIt.value(), className, &m_qtClasses);
218 return result;
219}
220
221void WriteImports::addPythonCustomWidget(const QString &className, const DomCustomWidget *node)
222{
223 if (addQtClass(className)) // Qt custom widgets like QQuickWidget, QAxWidget, etc
224 return;
225
226 // When the elementHeader is not set, we know it's the continuation
227 // of a Qt for Python import or a normal import of another module.
228 if (!node->elementHeader() || node->elementHeader()->text().isEmpty()) {
229 m_plainCustomWidgets.append(className);
230 } else { // When we do have elementHeader, we know it's a relative import.
231 QString modulePath = node->elementHeader()->text();
232 // Replace the '/' by '.'
233 modulePath.replace(u'/', u'.');
234 // '.h' is added by default on headers for <customwidget>.
235 if (modulePath.endsWith(".h"_L1, Qt::CaseInsensitive))
236 modulePath.chop(2);
237 else if (modulePath.endsWith(".hh"_L1))
238 modulePath.chop(3);
239 else if (modulePath.endsWith(".hpp"_L1))
240 modulePath.chop(4);
241 insertClass(modulePath, className, &m_customWidgets);
242 }
243}
244
245void WriteImports::acceptProperty(DomProperty *node)
246{
247 switch (node->kind()) {
248 case DomProperty::Enum:
249 addEnumBaseClass(node->elementEnum());
250 break;
251 case DomProperty::Set:
252 addEnumBaseClass(node->elementSet());
253 break;
254 default:
255 break;
256 }
257
259}
260
261void WriteImports::addEnumBaseClass(const QString &v)
262{
263 // Add base classes like QFrame for QLabel::frameShape()
264 const auto colonPos = v.indexOf(u"::");
265 if (colonPos > 0) {
266 const QString base = v.left(colonPos);
267 if (base.startsWith(u'Q') && base != u"Qt")
268 addQtClass(base);
269 }
270}
271
272} // namespace Python
273
274QT_END_NAMESPACE
void acceptUI(DomUI *node) override
void acceptProperty(DomProperty *node) override
Definition uic.h:31
void acceptUI(DomUI *node) override
void acceptProperty(DomProperty *node) override
static void formatImportClasses(QTextStream &str, QStringList classList)
static void insertClass(const QString &module, const QString &className, WriteImports::ClassesPerModule *c)
static void formatClasses(QTextStream &str, const WriteImports::ClassesPerModule &c, bool useStarImports=false, const QByteArray &modulePrefix={})
static WriteImports::ClassesPerModule defaultClasses()
Combined button and popup list for selecting options.
const QString & asString(const QString &s)
Definition qstring.h:1700
#define qPrintable(string)
Definition qstring.h:1705
#define QStringLiteral(str)
Definition qstring.h:1847