mirror of https://github.com/qt/qtactiveqt.git
288 lines
8.8 KiB
C++
288 lines
8.8 KiB
C++
// Copyright (C) 2020 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include "moc.h"
|
|
|
|
#include <QDir>
|
|
#include <QMetaObject>
|
|
#include <QMetaProperty>
|
|
#include <QProcess>
|
|
#include <QTextStream>
|
|
|
|
#include <private/qtools_p.h>
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
extern QByteArrayList qax_qualified_usertypes;
|
|
|
|
QByteArray setterName(const QByteArray &propertyName)
|
|
{
|
|
QByteArray setter(propertyName);
|
|
if (isupper(setter.at(0))) {
|
|
setter = "Set" + setter;
|
|
} else {
|
|
setter[0] = QtMiscUtils::toAsciiUpper(setter[0]);
|
|
setter = "set" + setter;
|
|
}
|
|
return setter;
|
|
}
|
|
|
|
void formatCppEnum(QTextStream &str, const QMetaEnum &metaEnum)
|
|
{
|
|
str << " enum " << metaEnum.name() << " {" << Qt::endl;
|
|
for (int k = 0, last = metaEnum.keyCount() - 1; k <= last; ++k) {
|
|
QByteArray key(metaEnum.key(k));
|
|
str << " " << key.leftJustified(24) << "= " << metaEnum.value(k);
|
|
if (k < last)
|
|
str << ',';
|
|
str << Qt::endl;
|
|
}
|
|
str << " };" << Qt::endl;
|
|
}
|
|
|
|
void formatCppEnums(QTextStream &str, const QMetaObject *mo,
|
|
const char *qEnumDecl = nullptr /* Q_ENUM, Q_ENUM_NS */)
|
|
{
|
|
const int offset = mo->enumeratorOffset();
|
|
const int count = mo->enumeratorCount();
|
|
for (int e = offset; e < count; ++e) {
|
|
const auto me = mo->enumerator(e);
|
|
formatCppEnum(str, me);
|
|
if (qEnumDecl)
|
|
str << " " << qEnumDecl << '(' << me.name() << ")\n";
|
|
str << '\n';
|
|
}
|
|
if (offset < count)
|
|
str << '\n';
|
|
}
|
|
|
|
// Create a set of type names that have been declared in the type library. Remove any "struct" or
|
|
// "enum" prefixes. Also remove Qt-types that we hard-code in qaxbase.cpp.
|
|
static QSet<QByteArray> collectKnownTypes()
|
|
{
|
|
QSet<QByteArray> result;
|
|
for (const QByteArray &ba : qax_qualified_usertypes) {
|
|
if (ba == "struct QRect" || ba == "struct QSize" || ba == "struct QPoint")
|
|
continue;
|
|
if (ba.startsWith("enum "))
|
|
result.insert(ba.right(ba.length() - 5));
|
|
else if (ba.startsWith("struct "))
|
|
result.insert(ba.right(ba.length() - 7));
|
|
else
|
|
result.insert(ba);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static bool isKnownUserType(const QByteArray &name)
|
|
{
|
|
static qsizetype nrOfQualifiedUserTypes = 0;
|
|
static QSet<QByteArray> knownTypes;
|
|
if (nrOfQualifiedUserTypes != qax_qualified_usertypes.size()) {
|
|
nrOfQualifiedUserTypes = qax_qualified_usertypes.size();
|
|
knownTypes = collectKnownTypes();
|
|
}
|
|
return knownTypes.contains(name);
|
|
}
|
|
|
|
static QByteArray cleanedType(QByteArray name)
|
|
{
|
|
if (name.endsWith("*"))
|
|
name.chop(1);
|
|
return name;
|
|
}
|
|
|
|
// If 'name' is a type that's declared within the TLB then fully qualify it with the TLB namespace.
|
|
static QByteArray maybeQualifyType(QByteArray name, const QByteArray &tlbNamespace)
|
|
{
|
|
if (!name.contains("::") && isKnownUserType(cleanedType(name))) {
|
|
name.prepend("::");
|
|
name.prepend(tlbNamespace);
|
|
}
|
|
return name;
|
|
}
|
|
|
|
static void formatCppMethod(QTextStream &str, const QMetaMethod &mt, const QByteArray &tlbNamespace)
|
|
{
|
|
str << " " << maybeQualifyType(mt.typeName(), tlbNamespace) << ' ' << mt.name() << '(';
|
|
const auto paramTypes = mt.parameterTypes();
|
|
auto ptit = paramTypes.begin();
|
|
if (ptit != paramTypes.end()) {
|
|
while (true) {
|
|
str << maybeQualifyType(*ptit, tlbNamespace);
|
|
if (++ptit == paramTypes.end())
|
|
break;
|
|
str << ", ";
|
|
}
|
|
}
|
|
str << ");\n";
|
|
}
|
|
|
|
static void formatCppMethods(QTextStream &str, const QMetaObject *mo,
|
|
const QByteArray &tlbNamespace, QMetaMethod::MethodType filter)
|
|
{
|
|
for (int m = mo->methodOffset(), count = mo->methodCount(); m < count; ++m) {
|
|
const auto &mt = mo->method(m);
|
|
if (mt.methodType() == filter)
|
|
formatCppMethod(str, mt, tlbNamespace);
|
|
}
|
|
}
|
|
|
|
static void formatCppProperty(QTextStream &str, const QMetaProperty &p,
|
|
const QByteArray &tlbNamespace)
|
|
{
|
|
str << " Q_PROPERTY(" << maybeQualifyType(p.typeName(), tlbNamespace) << ' ' << p.name()
|
|
<< " READ " << p.name();
|
|
if (p.isWritable())
|
|
str << " WRITE " << setterName(p.name());
|
|
if (p.hasNotifySignal())
|
|
str << " NOTIFY " << p.notifySignal().name();
|
|
if (p.isUser())
|
|
str << " USER true";
|
|
if (!p.isDesignable())
|
|
str << " DESIGNABLE false";
|
|
if (!p.isStored())
|
|
str << " STORED false";
|
|
if (p.isFinal())
|
|
str << " FINAL";
|
|
str << ")\n";
|
|
}
|
|
|
|
static void formatCppQuotedString(QTextStream &str, const char *s)
|
|
{
|
|
str << '"';
|
|
for ( ; *s ; ++s) {
|
|
const char c = *s;
|
|
if (c == '\\' || c == '\"')
|
|
str << '\\';
|
|
str << c;
|
|
}
|
|
str << '"';
|
|
}
|
|
|
|
static QByteArray extractNamespaceFromTypeName(const QStringList &name)
|
|
{
|
|
QByteArray result;
|
|
if (name.size() < 2)
|
|
return result;
|
|
|
|
result = name.first().toUtf8();
|
|
for (int i = 1, count = name.size() - 1; i < count; ++i) {
|
|
result.append("::");
|
|
result.append(name.at(i).toUtf8());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Generate C++ code from an ActiveQt QMetaObject to be parsed by moc
|
|
static QString mocHeader(const QMetaObject *mo, const QStringList &name,
|
|
const QString &baseClass)
|
|
{
|
|
QString result;
|
|
QTextStream str(&result);
|
|
|
|
str << "#pragma once\n\n";
|
|
if (!baseClass.isEmpty())
|
|
str << "#include <" << baseClass << ">\n";
|
|
str << "#include <qt_windows.h>\n\n";
|
|
|
|
for (int n = 0, count = name.size() - 1; n < count; ++n)
|
|
str << "namespace " << name.at(n) << " {\n";
|
|
|
|
str << "\nclass " << name.constLast();
|
|
if (!baseClass.isEmpty())
|
|
str << " : public " << baseClass;
|
|
str<< "\n{\n Q_OBJECT\n";
|
|
|
|
for (int i = mo->classInfoOffset(), count = mo->classInfoCount(); i < count; ++i) {
|
|
const auto &info = mo->classInfo(i);
|
|
str << " Q_CLASSINFO(";
|
|
formatCppQuotedString(str, info.name());
|
|
str << ", ";
|
|
formatCppQuotedString(str, info.value());
|
|
str << ")\n";
|
|
}
|
|
|
|
const QByteArray tlbNamespace = extractNamespaceFromTypeName(name);
|
|
for (int p = mo->propertyOffset(), count = mo-> propertyCount(); p < count; ++p)
|
|
formatCppProperty(str, mo->property(p), tlbNamespace);
|
|
|
|
str << "public:\n";
|
|
|
|
formatCppEnums(str, mo, "Q_ENUM");
|
|
|
|
formatCppMethods(str, mo, tlbNamespace, QMetaMethod::Constructor);
|
|
str << "\nQ_SIGNALS:\n";
|
|
formatCppMethods(str, mo, tlbNamespace, QMetaMethod::Signal);
|
|
str << "\npublic Q_SLOTS:\n";
|
|
formatCppMethods(str, mo, tlbNamespace, QMetaMethod::Slot);
|
|
str << "};\n";
|
|
|
|
// The final part of "name" is the class name. It shouldn't be closed as a namespace
|
|
for (int n = name.size() - 2; n >= 0; --n)
|
|
str << "} // namespace " << name.at(n) << '\n';
|
|
|
|
return result;
|
|
}
|
|
|
|
static QString processOutput(QByteArray output)
|
|
{
|
|
for (int c = output.size() - 1; c >= 0; --c) {
|
|
if (output.at(c) == '\r')
|
|
output.remove(c, 1);
|
|
}
|
|
return QString::fromUtf8(output);
|
|
}
|
|
|
|
static QString runProcess(const QString &binary, const QStringList &args, const QByteArray &input,
|
|
QString *errorString)
|
|
{
|
|
QProcess process;
|
|
process.start(binary, args);
|
|
if (!process.waitForStarted()) {
|
|
*errorString = QLatin1String("Cannot start ") + binary + QLatin1String(": ") + process.errorString();
|
|
return QString();
|
|
}
|
|
process.write(input);
|
|
process.closeWriteChannel();
|
|
if (!process.waitForFinished()) {
|
|
*errorString = binary + QLatin1String(" timed out: ") + process.errorString();
|
|
return QString();
|
|
}
|
|
if (process.exitStatus() != QProcess::NormalExit) {
|
|
*errorString = binary + QLatin1String(" crashed: ") + process.errorString();
|
|
return QString();
|
|
}
|
|
if (process.exitCode() != 0) {
|
|
*errorString = binary + QLatin1String(" failed: ") + processOutput(process.readAllStandardError());
|
|
return QString();
|
|
}
|
|
return processOutput(process.readAllStandardOutput());
|
|
}
|
|
|
|
QString mocCode(const QMetaObject *mo, const QString &qualifiedClassName,
|
|
QString *errorString)
|
|
{
|
|
QStringList name = qualifiedClassName.split(QLatin1String("::"));
|
|
if (name.isEmpty())
|
|
name.append(QLatin1String(mo->className()));
|
|
|
|
const QString baseClass = QLatin1String(mo->superClass()->className());
|
|
|
|
const QString headerCode = mocHeader(mo, name, baseClass);
|
|
|
|
const QString binary = QLatin1String("moc.exe");
|
|
|
|
QString result = runProcess(binary, { u"--active-qt"_s }, headerCode.toUtf8(), errorString);
|
|
if (result.isEmpty()) {
|
|
errorString->append(QLatin1String("\n\nOffending code:\n"));
|
|
errorString->append(headerCode);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
QT_END_NAMESPACE
|