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>bb10
parent
b42e2d70fb
commit
de15836dbf
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
WriteImports::WriteImports(Uic *uic) : m_uic(uic)
|
||||
// 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) : 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,59 +170,78 @@ 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));
|
||||
}
|
||||
|
||||
void WriteImports::acceptCustomWidget(DomCustomWidget *node)
|
||||
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::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
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
|||
|
|
@ -29,27 +29,40 @@
|
|||
#ifndef PYTHONWRITEIMPORTS_H
|
||||
#define PYTHONWRITEIMPORTS_H
|
||||
|
||||
#include <treewalker.h>
|
||||
#include <writeincludesbase.h>
|
||||
|
||||
#include <QtCore/qhash.h>
|
||||
#include <QtCore/qmap.h>
|
||||
#include <QtCore/qstringlist.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class Uic;
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
Loading…
Reference in New Issue