diff options
author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2021-05-12 13:20:40 +0200 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2021-06-01 16:29:15 +0200 |
commit | de15836dbf5007092c19bc9ab15ca3d1a36901ad (patch) | |
tree | 38e4b642d31bb633f2efa3b316cfcd1dfbaca7e3 | |
parent | b42e2d70fbda5afb462b869583b925ad5f1a5480 (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.cpp | 6 | ||||
-rw-r--r-- | src/tools/uic/option.h | 2 | ||||
-rw-r--r-- | src/tools/uic/python/pythonwriteimports.cpp | 206 | ||||
-rw-r--r-- | src/tools/uic/python/pythonwriteimports.h | 27 | ||||
-rw-r--r-- | tests/auto/tools/uic/baseline/config.ui.py | 15 |
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(): |