summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2021-05-12 13:20:40 +0200
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2021-06-01 16:29:15 +0200
commitde15836dbf5007092c19bc9ab15ca3d1a36901ad (patch)
tree38e4b642d31bb633f2efa3b316cfcd1dfbaca7e3
parentb42e2d70fbda5afb462b869583b925ad5f1a5480 (diff)
uic: No longer generate star imports in Python
Use class WriteIncludesBase and store classes encountered in a per-module hash (Qt/custom widgets). Write out only the required classes. Add --star-import as a fallback should the change cause issues. Task-number: PYSIDE-1404 Change-Id: Ic50e26758ddd0f2f8aebbce470d32a36fb09a2c4 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
-rw-r--r--src/tools/uic/main.cpp6
-rw-r--r--src/tools/uic/option.h2
-rw-r--r--src/tools/uic/python/pythonwriteimports.cpp206
-rw-r--r--src/tools/uic/python/pythonwriteimports.h27
-rw-r--r--tests/auto/tools/uic/baseline/config.ui.py15
5 files changed, 193 insertions, 63 deletions
diff --git a/src/tools/uic/main.cpp b/src/tools/uic/main.cpp
index cb21277086..4a5f0ea309 100644
--- a/src/tools/uic/main.cpp
+++ b/src/tools/uic/main.cpp
@@ -112,6 +112,11 @@ int runUic(int argc, char *argv[])
fromImportsOption.setDescription(QStringLiteral("Python: generate imports relative to '.'"));
parser.addOption(fromImportsOption);
+ // FIXME Qt 7: Remove?
+ QCommandLineOption useStarImportsOption(QStringLiteral("star-imports"));
+ useStarImportsOption.setDescription(QStringLiteral("Python: Use * imports"));
+ parser.addOption(useStarImportsOption);
+
parser.addPositionalArgument(QStringLiteral("[uifile]"), QStringLiteral("Input file (*.ui), otherwise stdin."));
parser.process(app);
@@ -123,6 +128,7 @@ int runUic(int argc, char *argv[])
driver.option().implicitIncludes = !parser.isSet(noImplicitIncludesOption);
driver.option().idBased = parser.isSet(idBasedOption);
driver.option().fromImports = parser.isSet(fromImportsOption);
+ driver.option().useStarImports = parser.isSet(useStarImportsOption);
driver.option().postfix = parser.value(postfixOption);
driver.option().translateFunction = parser.value(translateOption);
driver.option().includeFile = parser.value(includeOption);
diff --git a/src/tools/uic/option.h b/src/tools/uic/option.h
index c869fec9a6..b08aab2d3e 100644
--- a/src/tools/uic/option.h
+++ b/src/tools/uic/option.h
@@ -48,6 +48,7 @@ struct Option
unsigned int fromImports: 1;
unsigned int forceMemberFnPtrConnectionSyntax: 1;
unsigned int forceStringConnectionSyntax: 1;
+ unsigned int useStarImports: 1;
QString inputFile;
QString outputFile;
@@ -71,6 +72,7 @@ struct Option
fromImports(0),
forceMemberFnPtrConnectionSyntax(0),
forceStringConnectionSyntax(0),
+ useStarImports(0),
prefix(QLatin1String("Ui_"))
{ indent.fill(QLatin1Char(' '), 4); }
diff --git a/src/tools/uic/python/pythonwriteimports.cpp b/src/tools/uic/python/pythonwriteimports.cpp
index 6dab354a60..f9dfeb28da 100644
--- a/src/tools/uic/python/pythonwriteimports.cpp
+++ b/src/tools/uic/python/pythonwriteimports.cpp
@@ -31,19 +31,49 @@
#include <customwidgetsinfo.h>
#include <option.h>
#include <uic.h>
+#include <driver.h>
#include <ui4.h>
#include <QtCore/qtextstream.h>
+#include <algorithm>
+
QT_BEGIN_NAMESPACE
-static QString standardImports()
+// Generate imports for Python. Note some things differ from C++:
+// - qItemView->header()->setFoo() does not require QHeaderView to be imported
+// - qLabel->setFrameShape(QFrame::Box) however requires QFrame to be imported
+// (see acceptProperty())
+
+namespace Python {
+
+// Classes required for properties
+static WriteImports::ClassesPerModule defaultClasses()
{
- return QString::fromLatin1(R"I(from PySide%1.QtCore import * # type: ignore
-from PySide%1.QtGui import * # type: ignore
-from PySide%1.QtWidgets import * # type: ignore
-)I").arg(QT_VERSION_MAJOR);
+ return {
+ {QStringLiteral("QtCore"),
+ {QStringLiteral("QCoreApplication"), QStringLiteral("QDate"),
+ QStringLiteral("QDateTime"), QStringLiteral("QLocale"),
+ QStringLiteral("QMetaObject"), QStringLiteral("QObject"),
+ QStringLiteral("QPoint"), QStringLiteral("QRect"),
+ QStringLiteral("QSize"), QStringLiteral("QTime"),
+ QStringLiteral("QUrl"), QStringLiteral("Qt")},
+ },
+ {QStringLiteral("QtGui"),
+ {QStringLiteral("QBrush"), QStringLiteral("QColor"),
+ QStringLiteral("QConicalGradient"), QStringLiteral("QCursor"),
+ QStringLiteral("QGradient"), QStringLiteral("QFont"),
+ QStringLiteral("QFontDatabase"), QStringLiteral("QIcon"),
+ QStringLiteral("QImage"), QStringLiteral("QKeySequence"),
+ QStringLiteral("QLinearGradient"), QStringLiteral("QPalette"),
+ QStringLiteral("QPainter"), QStringLiteral("QPixmap"),
+ QStringLiteral("QTransform"), QStringLiteral("QRadialGradient")}
+ },
+ {QStringLiteral("QtWidgets"),
+ {QStringLiteral("QSizePolicy")}
+ }
+ };
}
// Change the name of a qrc file "dir/foo.qrc" file to the Python
@@ -60,19 +90,72 @@ static QString pythonResource(QString resource)
return resource;
}
-namespace Python {
+// Helpers for WriteImports::ClassesPerModule maps
+static void insertClass(const QString &module, const QString &className,
+ WriteImports::ClassesPerModule *c)
+{
+ auto usedIt = c->find(module);
+ if (usedIt == c->end())
+ c->insert(module, {className});
+ else if (!usedIt.value().contains(className))
+ usedIt.value().append(className);
+}
+
+// Format a class list: "from A import (B, C)"
+static void formatImportClasses(QTextStream &str, QStringList classList)
+{
+ std::sort(classList.begin(), classList.end());
+
+ const qsizetype size = classList.size();
+ if (size > 1)
+ str << '(';
+ for (qsizetype i = 0; i < size; ++i) {
+ if (i > 0)
+ str << (i % 4 == 0 ? ",\n " : ", ");
+ str << classList.at(i);
+ }
+ if (size > 1)
+ str << ')';
+}
+
+static void formatClasses(QTextStream &str, const WriteImports::ClassesPerModule &c,
+ bool useStarImports = false,
+ const QByteArray &modulePrefix = {})
+{
+ for (auto it = c.cbegin(), end = c.cend(); it != end; ++it) {
+ str << "from " << modulePrefix << it.key() << " import ";
+ if (useStarImports)
+ str << "* # type: ignore";
+ else
+ formatImportClasses(str, it.value());
+ str << '\n';
+ }
+}
-WriteImports::WriteImports(Uic *uic) : m_uic(uic)
+WriteImports::WriteImports(Uic *uic) : WriteIncludesBase(uic),
+ m_qtClasses(defaultClasses())
{
+ for (const auto &e : classInfoEntries())
+ m_classToModule.insert(QLatin1String(e.klass), QLatin1String(e.module));
}
void WriteImports::acceptUI(DomUI *node)
{
- auto &output = m_uic->output();
- output << standardImports() << '\n';
- if (auto customWidgets = node->elementCustomWidgets()) {
- TreeWalker::acceptCustomWidgets(customWidgets);
+ WriteIncludesBase::acceptUI(node);
+
+ auto &output = uic()->output();
+ const bool useStarImports = uic()->driver()->option().useStarImports;
+
+ const QByteArray qtPrefix = QByteArrayLiteral("PySide")
+ + QByteArray::number(QT_VERSION_MAJOR) + '.';
+
+ formatClasses(output, m_qtClasses, useStarImports, qtPrefix);
+
+ if (!m_customWidgets.isEmpty() || !m_plainCustomWidgets.isEmpty()) {
output << '\n';
+ formatClasses(output, m_customWidgets, useStarImports);
+ for (const auto &w : m_plainCustomWidgets)
+ output << "import " << w << '\n';
}
if (auto resources = node->elementResources()) {
@@ -87,57 +170,76 @@ void WriteImports::acceptUI(DomUI *node)
void WriteImports::writeImport(const QString &module)
{
-
- if (m_uic->option().fromImports)
- m_uic->output() << "from . ";
- m_uic->output() << "import " << module << '\n';
+ if (uic()->option().fromImports)
+ uic()->output() << "from . ";
+ uic()->output() << "import " << module << '\n';
}
-QString WriteImports::qtModuleOf(const DomCustomWidget *node) const
+void WriteImports::doAdd(const QString &className, const DomCustomWidget *dcw)
{
- if (m_uic->customWidgetsInfo()->extends(node->elementClass(), QLatin1String("QAxWidget")))
- return QStringLiteral("QtAxContainer");
- if (const auto headerElement = node->elementHeader()) {
- const auto &header = headerElement->text();
- if (header.startsWith(QLatin1String("Qt"))) {
- const int slash = header.indexOf(QLatin1Char('/'));
- if (slash != -1)
- return header.left(slash);
- }
+ const CustomWidgetsInfo *cwi = uic()->customWidgetsInfo();
+ if (cwi->extends(className, QLatin1String("QListWidget")))
+ add(QStringLiteral("QListWidgetItem"));
+ else if (cwi->extends(className, QLatin1String("QTreeWidget")))
+ add(QStringLiteral("QTreeWidgetItem"));
+ else if (cwi->extends(className, QLatin1String("QTableWidget")))
+ add(QStringLiteral("QTableWidgetItem"));
+
+ if (dcw != nullptr) {
+ addPythonCustomWidget(className, dcw);
+ return;
}
- return QString();
+
+ if (!addQtClass(className))
+ qWarning("WriteImports::add(): Unknown Qt class %s", qPrintable(className));
+}
+
+bool WriteImports::addQtClass(const QString &className)
+{
+ // QVariant is not exposed in PySide
+ if (className == u"QVariant" || className == u"Qt")
+ return true;
+
+ const auto moduleIt = m_classToModule.constFind(className);
+ const bool result = moduleIt != m_classToModule.cend();
+ if (result)
+ insertClass(moduleIt.value(), className, &m_qtClasses);
+ return result;
}
-void WriteImports::acceptCustomWidget(DomCustomWidget *node)
+void WriteImports::addPythonCustomWidget(const QString &className, const DomCustomWidget *node)
{
- const auto &className = node->elementClass();
if (className.contains(QLatin1String("::")))
return; // Exclude namespaced names (just to make tests pass).
- const QString &importModule = qtModuleOf(node);
- auto &output = m_uic->output();
- // For starting importing Qt for Python modules
- if (!importModule.isEmpty()) {
- output << "from ";
- if (importModule.startsWith(QLatin1String("Qt")))
- output << "PySide" << QT_VERSION_MAJOR << '.';
- output << importModule;
- if (!className.isEmpty())
- output << " import " << className << "\n\n";
- } else {
- // When the elementHeader is not set, we know it's the continuation
- // of a Qt for Python import or a normal import of another module.
- if (!node->elementHeader() || node->elementHeader()->text().isEmpty()) {
- output << "import " << className << '\n';
- } else { // When we do have elementHeader, we know it's a relative import.
- QString modulePath = node->elementHeader()->text();
- // Replace the '/' by '.'
- modulePath.replace(QLatin1Char('/'), QLatin1Char('.'));
- // '.h' is added by default on headers for <customwidget>
- if (modulePath.endsWith(QLatin1String(".h")))
- modulePath.chop(2);
- output << "from " << modulePath << " import " << className << '\n';
- }
+
+ if (addQtClass(className)) // Qt custom widgets like QQuickWidget, QAxWidget, etc
+ return;
+
+ // When the elementHeader is not set, we know it's the continuation
+ // of a Qt for Python import or a normal import of another module.
+ if (!node->elementHeader() || node->elementHeader()->text().isEmpty()) {
+ m_plainCustomWidgets.append(className);
+ } else { // When we do have elementHeader, we know it's a relative import.
+ QString modulePath = node->elementHeader()->text();
+ // Replace the '/' by '.'
+ modulePath.replace(QLatin1Char('/'), QLatin1Char('.'));
+ // '.h' is added by default on headers for <customwidget>
+ if (modulePath.endsWith(QLatin1String(".h")))
+ modulePath.chop(2);
+ insertClass(modulePath, className, &m_customWidgets);
+ }
+}
+
+void WriteImports::acceptProperty(DomProperty *node)
+{
+ if (node->kind() == DomProperty::Enum) {
+ // Add base classes like QFrame for QLabel::frameShape()
+ const QString &enumV = node->elementEnum();
+ const auto colonPos = enumV.indexOf(u"::");
+ if (colonPos > 0)
+ addQtClass(enumV.left(colonPos));
}
+ WriteIncludesBase::acceptProperty(node);
}
} // namespace Python
diff --git a/src/tools/uic/python/pythonwriteimports.h b/src/tools/uic/python/pythonwriteimports.h
index 5462453962..c833e216a6 100644
--- a/src/tools/uic/python/pythonwriteimports.h
+++ b/src/tools/uic/python/pythonwriteimports.h
@@ -29,27 +29,40 @@
#ifndef PYTHONWRITEIMPORTS_H
#define PYTHONWRITEIMPORTS_H
-#include <treewalker.h>
+#include <writeincludesbase.h>
-QT_BEGIN_NAMESPACE
+#include <QtCore/qhash.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qstringlist.h>
-class Uic;
+QT_BEGIN_NAMESPACE
namespace Python {
-struct WriteImports : public TreeWalker
+class WriteImports : public WriteIncludesBase
{
public:
+ using ClassesPerModule = QMap<QString, QStringList>;
+
explicit WriteImports(Uic *uic);
void acceptUI(DomUI *node) override;
- void acceptCustomWidget(DomCustomWidget *node) override;
+ void acceptProperty(DomProperty *node) override;
+
+protected:
+ void doAdd(const QString &className, const DomCustomWidget *dcw = nullptr) override;
private:
+ void addPythonCustomWidget(const QString &className, const DomCustomWidget *dcw);
+ bool addQtClass(const QString &className);
void writeImport(const QString &module);
- QString qtModuleOf(const DomCustomWidget *node) const;
- Uic *const m_uic;
+ QHash<QString, QString> m_classToModule;
+ // Module->class (modules sorted)
+
+ ClassesPerModule m_qtClasses;
+ ClassesPerModule m_customWidgets;
+ QStringList m_plainCustomWidgets; // Custom widgets without any module
};
} // namespace Python
diff --git a/tests/auto/tools/uic/baseline/config.ui.py b/tests/auto/tools/uic/baseline/config.ui.py
index d697ee622b..f6b7eb92aa 100644
--- a/tests/auto/tools/uic/baseline/config.ui.py
+++ b/tests/auto/tools/uic/baseline/config.ui.py
@@ -36,13 +36,20 @@
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
-from PySide6.QtCore import * # type: ignore
-from PySide6.QtGui import * # type: ignore
-from PySide6.QtWidgets import * # type: ignore
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QDialog,
+ QGridLayout, QGroupBox, QHBoxLayout, QLabel,
+ QPushButton, QRadioButton, QSizePolicy, QSlider,
+ QSpacerItem, QSpinBox, QVBoxLayout)
from gammaview import GammaView
-
class Ui_Config(object):
def setupUi(self, Config):
if not Config.objectName():