// Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "language.h" #include #include #include namespace language { using namespace Qt::StringLiterals; static Encoding encoding = Encoding::Utf8; static Language _language = Language::Cpp; Language language() { return _language; } void setLanguage(Language l) { _language = l; switch (_language) { case Language::Cpp: derefPointer = u"->"_s; listStart = '{'; listEnd = '}'; nullPtr = u"nullptr"_s; operatorNew = u"new "_s; qtQualifier = u"Qt::"_s; qualifier = u"::"_s; self = u""_s; // for testing: change to "this->"; eol = u";\n"_s; emptyString = u"QString()"_s; encoding = Encoding::Utf8; break; case Language::Python: derefPointer = u"."_s; listStart = '['; listEnd = ']'; nullPtr = u"None"_s; operatorNew = u""_s; qtQualifier = u"Qt."_s; qualifier = u"."_s; self = u"self."_s; eol = u"\n"_s; emptyString = u"\"\""_s; encoding = Encoding::Unicode; break; } } QString derefPointer; char listStart; char listEnd; QString nullPtr; QString operatorNew; QString qtQualifier; QString qualifier; QString self; QString eol; QString emptyString; QString cppQualifier = u"::"_s; QString cppTrue = u"true"_s; QString cppFalse = u"false"_s; QTextStream &operator<<(QTextStream &str, const qtConfig &c) { str << "QT_CONFIG(" << c.parameter() << ')'; return str; } QTextStream &operator<<(QTextStream &str, const openQtConfig &c) { str << "#if " << qtConfig(c.parameter()) << '\n'; return str; } QTextStream &operator<<(QTextStream &str, const closeQtConfig &c) { str << "#endif // " << qtConfig(c.parameter()) << '\n'; return str; } struct EnumLookup { int value; QLatin1StringView valueString; }; template QLatin1StringView lookupEnum(const EnumLookup(&array)[N], int value, int defaultIndex = 0) { for (int i = 0; i < N; ++i) { if (value == array[i].value) return array[i].valueString; } auto defaultValue = array[defaultIndex].valueString; qWarning("uic: Warning: Invalid enumeration value %d, defaulting to %s", value, defaultValue.data()); return defaultValue; } QString fixClassName(QString className) { if (language() == Language::Python) className.replace(cppQualifier, "_"_L1); return className; } QLatin1StringView toolbarArea(int v) { static const EnumLookup toolBarAreas[] = { {0, "NoToolBarArea"_L1}, {0x1, "LeftToolBarArea"_L1}, {0x2, "RightToolBarArea"_L1}, {0x4, "TopToolBarArea"_L1}, {0x8, "BottomToolBarArea"_L1}, {0xf, "AllToolBarAreas"_L1} }; return lookupEnum(toolBarAreas, v); } QLatin1StringView sizePolicy(int v) { static const EnumLookup sizePolicies[] = { {0, "Fixed"_L1}, {0x1, "Minimum"_L1}, {0x4, "Maximum"_L1}, {0x5, "Preferred"_L1}, {0x3, "MinimumExpanding"_L1}, {0x7, "Expanding"_L1}, {0xD, "Ignored"_L1} }; return lookupEnum(sizePolicies, v, 3); } QLatin1StringView dockWidgetArea(int v) { static const EnumLookup dockWidgetAreas[] = { {0, "NoDockWidgetArea"_L1}, {0x1, "LeftDockWidgetArea"_L1}, {0x2, "RightDockWidgetArea"_L1}, {0x4, "TopDockWidgetArea"_L1}, {0x8, "BottomDockWidgetArea"_L1}, {0xf, "AllDockWidgetAreas"_L1} }; return lookupEnum(dockWidgetAreas, v); } QLatin1StringView paletteColorRole(int v) { static const EnumLookup colorRoles[] = { {0, "WindowText"_L1}, {1, "Button"_L1}, {2, "Light"_L1}, {3, "Midlight"_L1}, {4, "Dark"_L1}, {5, "Mid"_L1}, {6, "Text"_L1}, {7, "BrightText"_L1}, {8, "ButtonText"_L1}, {9, "Base"_L1}, {10, "Window"_L1}, {11, "Shadow"_L1}, {12, "Highlight"_L1}, {13, "HighlightedText"_L1}, {14, "Link"_L1}, {15, "LinkVisited"_L1}, {16, "AlternateBase"_L1}, {17, "NoRole"_L1}, {18, "ToolTipBase"_L1}, {19, "ToolTipText"_L1}, {20, "PlaceholderText"_L1}, }; return lookupEnum(colorRoles, v); } // Helpers for formatting a character sequences // Format a special character like '\x0a' static int formatEscapedNumber(QTextStream &str, uint value, int base, int width, char prefix = 0) { int length = 1 + width; str << '\\'; if (prefix) { str << prefix; ++length; } const auto oldPadChar = str.padChar(); const auto oldFieldWidth = str.fieldWidth(); const auto oldFieldAlignment = str.fieldAlignment(); const auto oldIntegerBase = str.integerBase(); str.setPadChar(u'0'); str.setFieldWidth(width); str.setFieldAlignment(QTextStream::AlignRight); str.setIntegerBase(base); str << value; str.setIntegerBase(oldIntegerBase); str.setFieldAlignment(oldFieldAlignment); str.setFieldWidth(oldFieldWidth); str.setPadChar(oldPadChar); return length; } static int formatSpecialCharacter(QTextStream &str, ushort value) { int length = 0; switch (value) { case '\\': str << "\\\\"; length += 2; break; case '\"': str << "\\\""; length += 2; break; case '\n': str << "\\n\"\n\""; length += 5; break; default: break; } return length; } // Format a sequence of characters for C++ with special characters numerically // escaped (non-raw string literals), wrappped at maxSegmentSize. FormattingTraits // are used to transform characters into (unsigned) codes, which can be used // for either normal escapes or Unicode code points as used in Unicode literals. enum : int { maxSegmentSize = 1024 }; static uint characterCode(char c) { return uchar(c); } static uint characterCode(QChar c) { return c.unicode(); } static uint characterCode(uint c) { return c; } template static void formatStringSequence(QTextStream &str, Iterator it, Iterator end, const QString &indent, int escapeIntegerBase, int escapeWidth, char escapePrefix = 0) { str << '"'; int length = 0; while (it != end) { const auto code = characterCode(*it); if (code >= 0x80) { length += formatEscapedNumber(str, code, escapeIntegerBase, escapeWidth, escapePrefix); } else if (const int l = formatSpecialCharacter(str, code)) { length += l; } else if (code != '\r') { str << char(code); ++length; } ++it; if (it != end && length > maxSegmentSize) { str << "\"\n" << indent << indent << '"'; length = 0; } } str << '"'; } static bool isSurrogate(QChar c) { return c.isSurrogate(); } void _formatString(QTextStream &str, const QString &value, const QString &indent, bool qString) { switch (encoding) { // Special characters as 3 digit octal escapes (u8"\303\234mlaut") case Encoding::Utf8: { if (qString && _language == Language::Cpp) str << "QString::fromUtf8("; const QByteArray utf8 = value.toUtf8(); formatStringSequence(str, utf8.cbegin(), utf8.cend(), indent, 8, 3); if (qString && _language == Language::Cpp) str << ')'; } break; // Special characters as 4 digit hex Unicode points (u8"\u00dcmlaut") case Encoding::Unicode: str << 'u'; // Python Unicode literal if (std::any_of(value.cbegin(), value.cend(), isSurrogate)) { const auto ucs4 = value.toUcs4(); formatStringSequence(str, ucs4.cbegin(), ucs4.cend(), indent, 16, 8, 'U'); } else { formatStringSequence(str, value.cbegin(), value.cend(), indent, 16, 4, 'u'); } break; } } QTextStream &operator<<(QTextStream &str, const repeat &r) { for (int i = 0; i < r.m_count; ++i) str << r.m_char; return str; } startFunctionDefinition1::startFunctionDefinition1(const char *name, const QString ¶meterType, const QString ¶meterName, const QString &indent, const char *returnType) : m_name(name), m_parameterType(parameterType), m_parameterName(parameterName), m_indent(indent), m_return(returnType) { } QTextStream &operator<<(QTextStream &str, const startFunctionDefinition1 &f) { switch (language()) { case Language::Cpp: str << (f.m_return ? f.m_return : "void") << ' ' << f.m_name << '(' << f.m_parameterType; if (f.m_parameterType.cend()->isLetter()) str << ' '; str << f.m_parameterName << ')' << '\n' << f.m_indent << "{\n"; break; case Language::Python: str << "def " << f.m_name << "(self, " << f.m_parameterName << "):\n"; break; } return str; } endFunctionDefinition::endFunctionDefinition(const char *name) : m_name(name) { } QTextStream &operator<<(QTextStream &str, const endFunctionDefinition &f) { switch (language()) { case Language::Cpp: str << "} // " << f.m_name << "\n\n"; break; case Language::Python: str << "# " << f.m_name << "\n\n"; break; } return str; } void _formatStackVariable(QTextStream &str, const char *className, QStringView varName, bool withInitParameters) { switch (language()) { case Language::Cpp: str << className << ' ' << varName; if (withInitParameters) str << '('; break; case Language::Python: str << varName << " = " << className << '('; if (!withInitParameters) str << ')'; break; } } enum class OverloadUse { Always, WhenAmbiguousOrEmpty, // Use overload if // - signal/slot is ambiguous // - argument list is empty (chance of connecting mismatching T against const T &) Never, }; // Format a member function for a signal slot connection static bool isConstRef(const QStringView &arg) { return arg.startsWith(u'Q') && arg != "QPoint"_L1 && arg != "QSize"_L1; } static QString formatOverload(const QStringView ¶meters) { QString result = "qOverload<"_L1; const auto args = QStringView{parameters}.split(u','); for (qsizetype i = 0, size = args.size(); i < size; ++i) { const auto &arg = args.at(i); if (i > 0) result += u','; const bool constRef = isConstRef(arg); if (constRef) result += "const "_L1; result += arg; if (constRef) result += u'&'; } result += u'>'; return result; } static void formatMemberFnPtr(QTextStream &str, const SignalSlot &s, OverloadUse useQOverload) { const qsizetype parenPos = s.signature.indexOf(u'('); Q_ASSERT(parenPos >= 0); const auto functionName = QStringView{s.signature}.left(parenPos); const auto parameters = QStringView{s.signature}.mid(parenPos + 1, s.signature.size() - parenPos - 2); const bool isAmbiguous = s.options.testFlag(SignalSlotOption::Ambiguous); bool withOverload = false; // just to silence the compiler switch (useQOverload) { case OverloadUse::Always: withOverload = true; break; case OverloadUse::Never: withOverload = false; break; case OverloadUse::WhenAmbiguousOrEmpty: withOverload = parameters.empty() || isAmbiguous; break; } if (withOverload) str << formatOverload(parameters) << '('; str << '&' << s.className << "::" << functionName; if (withOverload) str << ')'; } static void formatMemberFnPtrConnection(QTextStream &str, const SignalSlot &sender, const SignalSlot &receiver) { str << "QObject::connect(" << sender.name << ", "; formatMemberFnPtr(str, sender, OverloadUse::Never); str << ", " << receiver.name << ", "; formatMemberFnPtr(str, receiver, OverloadUse::WhenAmbiguousOrEmpty); str << ')'; } static void formatStringBasedConnection(QTextStream &str, const SignalSlot &sender, const SignalSlot &receiver) { str << "QObject::connect(" << sender.name << ", SIGNAL("<< sender.signature << "), " << receiver.name << ", SLOT(" << receiver.signature << "))"; } void formatConnection(QTextStream &str, const SignalSlot &sender, const SignalSlot &receiver, ConnectionSyntax connectionSyntax) { switch (language()) { case Language::Cpp: switch (connectionSyntax) { case ConnectionSyntax::MemberFunctionPtr: formatMemberFnPtrConnection(str, sender, receiver); break; case ConnectionSyntax::StringBased: formatStringBasedConnection(str, sender, receiver); break; } break; case Language::Python: { const auto paren = sender.signature.indexOf(u'('); auto senderSignature = QStringView{sender.signature}; str << sender.name << '.' << senderSignature.left(paren); // Signals like "QAbstractButton::clicked(checked=false)" require // the parameter if it is used. if (sender.options.testFlag(SignalSlotOption::Ambiguous)) { const QStringView parameters = senderSignature.mid(paren + 1, senderSignature.size() - paren - 2); if (!parameters.isEmpty() && !parameters.contains(u',')) str << "[\"" << parameters << "\"]"; } str << ".connect(" << receiver.name << '.' << QStringView{receiver.signature}.left(receiver.signature.indexOf(u'(')) << ')'; } break; } } QString boolValue(bool v) { switch (language()) { case Language::Cpp: return v ? cppTrue : cppFalse; case Language::Python: return v ? QStringLiteral("True") : QStringLiteral("False"); } Q_UNREACHABLE(); } static inline QString dot() { return QStringLiteral("."); } QString enumValue(const QString &value) { if (language() == Language::Cpp || !value.contains(cppQualifier)) return value; QString fixed = value; fixed.replace(cppQualifier, dot()); return fixed; } } // namespace language