From 5b11b631914ff399d72f0a9b58a7b06e96fc7a6a Mon Sep 17 00:00:00 2001 From: Topi Reinio Date: Tue, 11 Aug 2020 11:18:26 +0200 Subject: qdoc: Add support for bindable properties Add support for the BINDABLE attribute in the Q_PROPERTY macro. The new properties are marked with 'bindable' tag, and the list of access functions/notifier signal is replaced with a descriptive note and a link to QProperty. Read-only properties are also properly marked as such. [ChangeLog][qdoc] The \property command now supports bindable C++ properties that use the new system based on QProperty. Task-number: QTBUG-85565 Change-Id: Ie352b3ce962b6b05a022d444da0991b8a849e474 Reviewed-by: Lars Knoll Reviewed-by: Paul Wicking --- src/qdoc/clangcodeparser.cpp | 52 ++++++++++++++++----------- src/qdoc/codemarker.cpp | 8 +++++ src/qdoc/docbookgenerator.cpp | 83 ++++++++++++++++++++++++++----------------- src/qdoc/generator.cpp | 13 +++++++ src/qdoc/generator.h | 10 +++++- src/qdoc/htmlgenerator.cpp | 28 ++++++++------- src/qdoc/propertynode.cpp | 3 ++ src/qdoc/propertynode.h | 4 +++ src/qdoc/qdocindexfiles.cpp | 10 ++++-- 9 files changed, 141 insertions(+), 70 deletions(-) diff --git a/src/qdoc/clangcodeparser.cpp b/src/qdoc/clangcodeparser.cpp index 9f9dba7e0..36a65c1f1 100644 --- a/src/qdoc/clangcodeparser.cpp +++ b/src/qdoc/clangcodeparser.cpp @@ -468,6 +468,9 @@ private: { if (symbolName == QLatin1String("QPrivateSignal")) return true; + // Ignore functions generated by property macros + if (symbolName.startsWith("_qt_property_")) + return true; return false; } @@ -500,7 +503,7 @@ private: CXChildVisitResult visitHeader(CXCursor cursor, CXSourceLocation loc); CXChildVisitResult visitFnSignature(CXCursor cursor, CXSourceLocation loc, Node **fnNode, bool &ignoreSignature); - void parseProperty(const QString &spelling, const Location &loc); + bool parseProperty(const QString &spelling, const Location &loc); void readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor); Aggregate *getSemanticParent(CXCursor cursor); }; @@ -609,8 +612,7 @@ CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation l if (!clang_isCursorDefinition(cursor)) return CXChildVisit_Continue; - if (findNodeForCursor(qdb_, - cursor)) // Was already parsed, propably in another translation unit + if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU return CXChildVisit_Continue; QString className = fromCXString(clang_getCursorSpelling(cursor)); @@ -679,8 +681,7 @@ CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation l case CXCursor_Constructor: case CXCursor_Destructor: case CXCursor_ConversionFunction: { - if (findNodeForCursor(qdb_, - cursor)) // Was already parsed, propably in another translation unit + if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU return CXChildVisit_Continue; QString name = functionName(cursor); if (ignoredSymbol(name)) @@ -838,9 +839,9 @@ CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation l } case CXCursor_FieldDecl: case CXCursor_VarDecl: { - if (findNodeForCursor(qdb_, - cursor)) // Was already parsed, propably in another translation unit + if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU return CXChildVisit_Continue; + auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)); auto var = new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor))); var->setAccess(access); @@ -850,8 +851,7 @@ CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation l return CXChildVisit_Continue; } case CXCursor_TypedefDecl: { - if (findNodeForCursor(qdb_, - cursor)) // Was already parsed, propably in another translation unit + if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU return CXChildVisit_Continue; TypedefNode *td = new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor))); td->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor))); @@ -877,13 +877,10 @@ CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation l } default: if (clang_isDeclaration(kind) && parent_->isClassNode()) { - // maybe a static_assert (which is not exposed from the clang API) - QString spelling = getSpelling(clang_getCursorExtent(cursor)); - if (spelling.startsWith(QLatin1String("Q_PROPERTY")) - || spelling.startsWith(QLatin1String("QDOC_PROPERTY")) - || spelling.startsWith(QLatin1String("Q_OVERRIDE"))) { - parseProperty(spelling, fromCXSourceLocation(loc)); - } + // may be a property macro or a static_assert + // which is not exposed from the clang API + parseProperty(getSpelling(clang_getCursorExtent(cursor)), + fromCXSourceLocation(loc)); } return CXChildVisit_Continue; } @@ -933,22 +930,31 @@ void ClangVisitor::readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cu }); } -void ClangVisitor::parseProperty(const QString &spelling, const Location &loc) +bool ClangVisitor::parseProperty(const QString &spelling, const Location &loc) { + if (!spelling.startsWith(QLatin1String("Q_PROPERTY")) + && !spelling.startsWith(QLatin1String("QDOC_PROPERTY")) + && !spelling.startsWith(QLatin1String("Q_OVERRIDE"))) + return false; + int lpIdx = spelling.indexOf(QChar('(')); int rpIdx = spelling.lastIndexOf(QChar(')')); if (lpIdx <= 0 || rpIdx <= lpIdx) - return; + return false; + QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1); signature = signature.simplified(); - QStringList part = signature.split(QChar(' ')); + + QStringList part; + part = signature.split(QChar(' ')); if (part.first() == QLatin1String("enum")) part.takeFirst(); // QTBUG-80027 if (part.size() < 2) - return; + return false; QString type = part.at(0); QString name = part.at(1); - if (name.at(0) == QChar('*')) { + + if (name.front() == QChar('*')) { type.append(QChar('*')); name.remove(0, 1); } @@ -956,6 +962,7 @@ void ClangVisitor::parseProperty(const QString &spelling, const Location &loc) property->setAccess(Access::Public); property->setLocation(loc); property->setDataType(type); + int i = 2; while (i < part.size()) { QString key = part.at(i++); @@ -986,6 +993,8 @@ void ClangVisitor::parseProperty(const QString &spelling, const Location &loc) property->setDesignable(false); property->setRuntimeDesFunc(value); } + } else if (key == "BINDABLE") { + property->setPropertyType(PropertyNode::Bindable); } else if (key == "RESET") { qdb_->addPropertyFunction(property, value, PropertyNode::Resetter); } else if (key == "NOTIFY") { @@ -1011,6 +1020,7 @@ void ClangVisitor::parseProperty(const QString &spelling, const Location &loc) } } } + return true; } /*! diff --git a/src/qdoc/codemarker.cpp b/src/qdoc/codemarker.cpp index f22dd66bc..546ab3296 100644 --- a/src/qdoc/codemarker.cpp +++ b/src/qdoc/codemarker.cpp @@ -188,6 +188,14 @@ QString CodeMarker::extraSynopsis(const Node *node, Section::Style style) case Node::TypeAlias: extra << "alias"; break; + case Node::Property: { + auto propertyNode = static_cast(node); + if (propertyNode->propertyType() == PropertyNode::Bindable) + extra << "bindable"; + if (!propertyNode->isWritable()) + extra << "read-only"; + } + break; default: break; } diff --git a/src/qdoc/docbookgenerator.cpp b/src/qdoc/docbookgenerator.cpp index 0fa640f51..a902db3fc 100644 --- a/src/qdoc/docbookgenerator.cpp +++ b/src/qdoc/docbookgenerator.cpp @@ -2150,7 +2150,10 @@ void DocBookGenerator::generateBody(const Node *node) generateReimplementsClause(fn); else if (node->isTypeAlias()) generateAddendum(node, TypeAlias, nullptr, false); - + else if (node->isProperty()) { + if (static_cast(node)->propertyType() != PropertyNode::Standard) + generateAddendum(node, BindableProperty, nullptr, false); + } if (!generateText(node->doc().body(), node)) { if (node->isMarkedReimp()) return; @@ -3512,7 +3515,19 @@ void DocBookGenerator::generateAddendum(const Node *node, Addendum type, CodeMar newLine(); break; } - + case BindableProperty: + { + const Node *linkNode; + Atom linkAtom = Atom(Atom::Link, "QProperty"); + QString link = getAutoLink(&linkAtom, node, &linkNode); + writer->writeStartElement(dbNamespace, "para"); + writer->writeCharacters("This property supports "); + generateSimpleLink(link, "QProperty"); + writer->writeCharacters(" bindings."); + writer->writeEndElement(); // para + newLine(); + break; + } default: break; } @@ -3589,41 +3604,43 @@ void DocBookGenerator::generateDetailedMember(const Node *node, const PageNode * if (node->isProperty()) { const auto property = static_cast(node); - Section section(Section::Accessors, Section::Active); + if (property->propertyType() == PropertyNode::Standard) { + Section section(Section::Accessors, Section::Active); - section.appendMembers(property->getters().toVector()); - section.appendMembers(property->setters().toVector()); - section.appendMembers(property->resetters().toVector()); + section.appendMembers(property->getters().toVector()); + section.appendMembers(property->setters().toVector()); + section.appendMembers(property->resetters().toVector()); - if (!section.members().isEmpty()) { - writer->writeStartElement(dbNamespace, "para"); - newLine(); - writer->writeStartElement(dbNamespace, "emphasis"); - writer->writeAttribute("role", "bold"); - writer->writeCharacters("Access functions:"); - newLine(); - writer->writeEndElement(); // emphasis - newLine(); - writer->writeEndElement(); // para - newLine(); - generateSectionList(section, node); - } + if (!section.members().isEmpty()) { + writer->writeStartElement(dbNamespace, "para"); + newLine(); + writer->writeStartElement(dbNamespace, "emphasis"); + writer->writeAttribute("role", "bold"); + writer->writeCharacters("Access functions:"); + newLine(); + writer->writeEndElement(); // emphasis + newLine(); + writer->writeEndElement(); // para + newLine(); + generateSectionList(section, node); + } - Section notifiers(Section::Accessors, Section::Active); - notifiers.appendMembers(property->notifiers().toVector()); + Section notifiers(Section::Accessors, Section::Active); + notifiers.appendMembers(property->notifiers().toVector()); - if (!notifiers.members().isEmpty()) { - writer->writeStartElement(dbNamespace, "para"); - newLine(); - writer->writeStartElement(dbNamespace, "emphasis"); - writer->writeAttribute("role", "bold"); - writer->writeCharacters("Notifier signal:"); - newLine(); - writer->writeEndElement(); // emphasis - newLine(); - writer->writeEndElement(); // para - newLine(); - generateSectionList(notifiers, node); + if (!notifiers.members().isEmpty()) { + writer->writeStartElement(dbNamespace, "para"); + newLine(); + writer->writeStartElement(dbNamespace, "emphasis"); + writer->writeAttribute("role", "bold"); + writer->writeCharacters("Notifier signal:"); + newLine(); + writer->writeEndElement(); // emphasis + newLine(); + writer->writeEndElement(); // para + newLine(); + generateSectionList(notifiers, node); + } } } else if (node->isEnumType()) { const auto en = static_cast(node); diff --git a/src/qdoc/generator.cpp b/src/qdoc/generator.cpp index 409171fd8..8e9a8698c 100644 --- a/src/qdoc/generator.cpp +++ b/src/qdoc/generator.cpp @@ -808,6 +808,10 @@ void Generator::generateBody(const Node *node, CodeMarker *marker) generateReimplementsClause(fn, marker); else if (node->isTypeAlias()) generateAddendum(node, TypeAlias, marker, false); + else if (node->isProperty()) { + if (static_cast(node)->propertyType() != PropertyNode::Standard) + generateAddendum(node, BindableProperty, marker); + } if (!generateText(node->doc().body(), node, marker)) { if (node->isMarkedReimp()) @@ -1419,6 +1423,15 @@ void Generator::generateAddendum(const Node *node, Addendum type, CodeMarker *ma } break; } + case BindableProperty: + { + text << "This property supports " + << Atom(Atom::Link, "QProperty") + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << "QProperty" + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + text << " bindings."; + break; + } default: return; } diff --git a/src/qdoc/generator.h b/src/qdoc/generator.h index 39aaf10f3..746ba424a 100644 --- a/src/qdoc/generator.h +++ b/src/qdoc/generator.h @@ -55,7 +55,15 @@ class Generator { public: enum ListType { Generic, Obsolete }; - enum Addendum { Invokable, PrivateSignal, QmlSignalHandler, AssociatedProperties, TypeAlias }; + + enum Addendum { + Invokable, + PrivateSignal, + QmlSignalHandler, + AssociatedProperties, + TypeAlias, + BindableProperty + }; Generator(); virtual ~Generator(); diff --git a/src/qdoc/htmlgenerator.cpp b/src/qdoc/htmlgenerator.cpp index 3cc08c84b..dcf5a6d3c 100644 --- a/src/qdoc/htmlgenerator.cpp +++ b/src/qdoc/htmlgenerator.cpp @@ -3376,23 +3376,25 @@ void HtmlGenerator::generateDetailedMember(const Node *node, const PageNode *rel if (node->isProperty()) { const auto property = static_cast(node); - Section section(Section::Accessors, Section::Active); + if (property->propertyType() == PropertyNode::Standard) { + Section section(Section::Accessors, Section::Active); - section.appendMembers(property->getters().toVector()); - section.appendMembers(property->setters().toVector()); - section.appendMembers(property->resetters().toVector()); + section.appendMembers(property->getters().toVector()); + section.appendMembers(property->setters().toVector()); + section.appendMembers(property->resetters().toVector()); - if (!section.members().isEmpty()) { - out() << "

Access functions:

\n"; - generateSectionList(section, node, marker); - } + if (!section.members().isEmpty()) { + out() << "

Access functions:

\n"; + generateSectionList(section, node, marker); + } - Section notifiers(Section::Accessors, Section::Active); - notifiers.appendMembers(property->notifiers().toVector()); + Section notifiers(Section::Accessors, Section::Active); + notifiers.appendMembers(property->notifiers().toVector()); - if (!notifiers.members().isEmpty()) { - out() << "

Notifier signal:

\n"; - generateSectionList(notifiers, node, marker); + if (!notifiers.members().isEmpty()) { + out() << "

Notifier signal:

\n"; + generateSectionList(notifiers, node, marker); + } } } else if (node->isEnumType()) { const auto *enumTypeNode = static_cast(node); diff --git a/src/qdoc/propertynode.cpp b/src/qdoc/propertynode.cpp index 824ad851a..77a7e249d 100644 --- a/src/qdoc/propertynode.cpp +++ b/src/qdoc/propertynode.cpp @@ -83,6 +83,9 @@ void PropertyNode::setOverriddenFrom(const PropertyNode *baseProperty) */ QString PropertyNode::qualifiedDataType() const { + if (m_propertyType != Standard) + return m_type; + if (setters().isEmpty() && resetters().isEmpty()) { if (m_type.contains(QLatin1Char('*')) || m_type.contains(QLatin1Char('&'))) { // 'QWidget *' becomes 'QWidget *' const diff --git a/src/qdoc/propertynode.h b/src/qdoc/propertynode.h index 5598ab703..be1ccfa09 100644 --- a/src/qdoc/propertynode.h +++ b/src/qdoc/propertynode.h @@ -42,6 +42,7 @@ class Aggregate; class PropertyNode : public Node { public: + enum PropertyType { Standard, Bindable }; enum FunctionRole { Getter, Setter, Resetter, Notifier }; enum { NumFunctionRoles = Notifier + 1 }; @@ -61,6 +62,7 @@ public: void setConstant() { m_const = true; } void setFinal() { m_final = true; } void setRequired() { m_required = true; } + void setPropertyType(PropertyType type) { m_propertyType = type; } void setRevision(int revision) { m_revision = revision; } const QString &dataType() const { return m_type; } @@ -83,6 +85,7 @@ public: bool isConstant() const { return m_const; } bool isFinal() const { return m_final; } bool isRequired() const { return m_required; } + PropertyType propertyType() const { return m_propertyType; } const PropertyNode *overriddenFrom() const { return m_overrides; } bool storedDefault() const { return true; } @@ -93,6 +96,7 @@ public: private: QString m_type {}; + PropertyType m_propertyType { Standard }; QString m_runtimeDesFunc {}; QString m_runtimeScrFunc {}; NodeList m_functions[NumFunctionRoles] {}; diff --git a/src/qdoc/qdocindexfiles.cpp b/src/qdoc/qdocindexfiles.cpp index 22cbef688..2b680b586 100644 --- a/src/qdoc/qdocindexfiles.cpp +++ b/src/qdoc/qdocindexfiles.cpp @@ -471,7 +471,10 @@ void QDocIndexFiles::readIndexSection(QXmlStreamReader &reader, Node *current, location = Location(parent->name().toLower() + ".html"); } else if (elementName == QLatin1String("property")) { - node = new PropertyNode(parent, name); + PropertyNode *propNode = new PropertyNode(parent, name); + node = propNode; + if (attributes.value(QLatin1String("bindable")) == QLatin1String("true")) + propNode->setPropertyType(PropertyNode::Bindable); if (!indexUrl.isEmpty()) location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html"); @@ -1145,7 +1148,10 @@ bool QDocIndexFiles::generateIndexSection(QXmlStreamWriter &writer, Node *node, } break; case Node::Property: { const auto *propertyNode = static_cast(node); - writer.writeAttribute("type", propertyNode->dataType()); + + if (propertyNode->propertyType() == PropertyNode::Bindable) + writer.writeAttribute("bindable", "true"); + if (!brief.isEmpty()) writer.writeAttribute("brief", brief); const auto &getters = propertyNode->getters(); -- cgit v1.2.3