diff options
Diffstat (limited to 'src/tools/uic/python/pythonwriteimports.cpp')
-rw-r--r-- | src/tools/uic/python/pythonwriteimports.cpp | 305 |
1 files changed, 214 insertions, 91 deletions
diff --git a/src/tools/uic/python/pythonwriteimports.cpp b/src/tools/uic/python/pythonwriteimports.cpp index 6f4a4e6f4f..b122c0f895 100644 --- a/src/tools/uic/python/pythonwriteimports.cpp +++ b/src/tools/uic/python/pythonwriteimports.cpp @@ -1,142 +1,265 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "pythonwriteimports.h" #include <customwidgetsinfo.h> #include <option.h> #include <uic.h> +#include <driver.h> #include <ui4.h> +#include <QtCore/qdir.h> +#include <QtCore/qfileinfo.h> #include <QtCore/qtextstream.h> +#include <algorithm> + QT_BEGIN_NAMESPACE -static QString standardImports() +using namespace Qt::StringLiterals; + +// 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 { + {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")} + }, + // Add QWidget for QWidget.setTabOrder() + {QStringLiteral("QtWidgets"), + {QStringLiteral("QSizePolicy"), QStringLiteral("QWidget")} + } + }; +} + +// Helpers for WriteImports::ClassesPerModule maps +static void insertClass(const QString &module, const QString &className, + WriteImports::ClassesPerModule *c) { - return QString::fromLatin1(R"I(from PySide%1.QtCore import * -from PySide%1.QtGui import * -from PySide%1.QtWidgets import * -)I").arg(QT_VERSION_MAJOR); + auto usedIt = c->find(module); + if (usedIt == c->end()) + c->insert(module, {className}); + else if (!usedIt.value().contains(className)) + usedIt.value().append(className); } -// Change the name of a qrc file "dir/foo.qrc" file to the Python -// module name "foo_rc" according to project conventions. -static QString pythonResource(QString resource) +// Format a class list: "from A import (B, C)" +static void formatImportClasses(QTextStream &str, QStringList classList) { - const int lastSlash = resource.lastIndexOf(QLatin1Char('/')); - if (lastSlash != -1) - resource.remove(0, lastSlash + 1); - if (resource.endsWith(QLatin1String(".qrc"))) { - resource.chop(4); - resource.append(QLatin1String("_rc")); + 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); } - return resource; + if (size > 1) + str << ')'; } -namespace Python { +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(QLatin1StringView(e.klass), QLatin1StringView(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()) { const auto includes = resources->elementInclude(); for (auto include : includes) { if (include->hasAttributeLocation()) - writeImport(pythonResource(include->attributeLocation())); + writeResourceImport(include->attributeLocation()); } output << '\n'; } } -void WriteImports::writeImport(const QString &module) +QString WriteImports::resourceAbsolutePath(QString resource) const { + // If we know the project root, generate an absolute Python import + // to the resource. options. pythonRoot is the Python path component + // under which the UI file is. + const auto &options = uic()->option(); + if (!options.inputFile.isEmpty() && !options.pythonRoot.isEmpty()) { + resource = QDir::cleanPath(QFileInfo(options.inputFile).canonicalPath() + u'/' + resource); + if (resource.size() > options.pythonRoot.size()) + resource.remove(0, options.pythonRoot.size() + 1); + } + // If nothing is known, we assume the directory pointed by "../" is the root + while (resource.startsWith(u"../")) + resource.remove(0, 3); + resource.replace(u'/', u'.'); + return resource; +} - if (m_uic->option().fromImports) - m_uic->output() << "from . "; - m_uic->output() << "import " << module << '\n'; +void WriteImports::writeResourceImport(const QString &module) +{ + const auto &options = uic()->option(); + auto &str = uic()->output(); + + QString resource = QDir::cleanPath(module); + if (resource.endsWith(u".qrc")) + resource.chop(4); + const qsizetype basePos = resource.lastIndexOf(u'/') + 1; + // Change the name of a qrc file "dir/foo.qrc" file to the Python + // module name "foo_rc" according to project conventions. + if (options.rcPrefix) + resource.insert(basePos, u"rc_"); + else + resource.append(u"_rc"); + + switch (options.pythonResourceImport) { + case Option::PythonResourceImport::Default: + str << "import " << QStringView{resource}.sliced(basePos) << '\n'; + break; + case Option::PythonResourceImport::FromDot: + str << "from . import " << QStringView{resource}.sliced(basePos) << '\n'; + break; + case Option::PythonResourceImport::Absolute: + str << "import " << resourceAbsolutePath(resource) << '\n'; + break; + } } -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, "QListWidget")) + add(QStringLiteral("QListWidgetItem")); + else if (cwi->extends(className, "QTreeWidget")) + add(QStringLiteral("QTreeWidgetItem")); + else if (cwi->extends(className, "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("::"))) + if (className.contains("::"_L1)) 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(u'/', u'.'); + // '.h' is added by default on headers for <customwidget> + if (modulePath.endsWith(".h"_L1)) + modulePath.chop(2); + insertClass(modulePath, className, &m_customWidgets); + } +} + +void WriteImports::acceptProperty(DomProperty *node) +{ + switch (node->kind()) { + case DomProperty::Enum: + addEnumBaseClass(node->elementEnum()); + break; + case DomProperty::Set: + addEnumBaseClass(node->elementSet()); + break; + default: + break; + } + + WriteIncludesBase::acceptProperty(node); +} + +void WriteImports::addEnumBaseClass(const QString &v) +{ + // Add base classes like QFrame for QLabel::frameShape() + const auto colonPos = v.indexOf(u"::"); + if (colonPos > 0) { + const QString base = v.left(colonPos); + if (base.startsWith(u'Q') && base != u"Qt") + addQtClass(base); } } |