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