diff options
author | Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com> | 2015-10-21 15:51:54 +0200 |
---|---|---|
committer | Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com> | 2015-10-23 10:47:25 +0000 |
commit | e15ca59111fe05f5ecc0f7fb48c34b5b22115a12 (patch) | |
tree | 1f66c3191f4745dea9bf126b8c22a4d8c5a90c58 /src | |
parent | 4e2d1893a89ce3dcfaacea091acae1f673c14a9e (diff) | |
download | qttools-e15ca59111fe05f5ecc0f7fb48c34b5b22115a12.tar.gz |
move qdoc back to qttools
we can do that now, as the bootstrap lib is now a properly exported
module, and qmldevtools is now bootstrapped as well.
this removes the abomination of a copy of the qml parser in qtbase.
unfortunately qtbase/2422251ee5025a067b14b989153764ab36e43f10 is
reverted, as qtdeclarative is still missing the respective change.
this introduces no regression in discoverability or usability, as a full
doc build already needed qttools - for qhelpgenerator.
Change-Id: Ic9c4c9732ddf5998637b9e42e27939ba50b31479
Reviewed-by: Jędrzej Nowacki <jedrzej.nowacki@theqtcompany.com>
Reviewed-by: Martin Smith <martin.smith@digia.com>
Reviewed-by: Lars Knoll <lars.knoll@theqtcompany.com>
Reviewed-by: Topi Reiniö <topi.reinio@digia.com>
Diffstat (limited to 'src')
107 files changed, 51487 insertions, 0 deletions
diff --git a/src/qdoc/TODO.txt b/src/qdoc/TODO.txt new file mode 100644 index 000000000..9bf4a2864 --- /dev/null +++ b/src/qdoc/TODO.txt @@ -0,0 +1,87 @@ + * fix QWSPointerCalibrationData::devPoints and qwsServer + * Fix QMenu::addAction(QAction *) overload, "using" etc. + * fix space between two tables using <p></p> + * qpixmap-qt3.html; remove 8 public functions inherited from QPaintDevice + * \variable array + * Added support for parameterless macros (e.g. \macro Q_OBJECT). + * Made qdoc stricter regarding the data types (e.g. can't use \enum to document a typedef). + * Parse QT_MODULE() macro and generate proper warnings for the various editions. + * Fix parsing of \image following \value (e.g. qt.html). + * Don't turn X11 and similar names into links. + * Added automatic links from getters to setters and vice versa. + * Support \module and show which module each class is from. + * Fix occasional crash caused by misuse of const_cast(). + * Provide clearer error messages when resolves fail. + + + + +CHECK: + + * Identify editions + * Automatic \sa getter setter + * \macro Q_OBJECT + +MUST HAVES: + + * resolve [gs]etters for \sa using base class + + * fix \overload when one is a signal and the other a normal function + * use "project" for .dcf files + * functions.html: include the types from QtGlobal as well as the functions (whatever that means) + + * nice template function/class syntax + * spellchecker: built-in vs. builtin + + * verbose mode for functions that don't exist + * No links to Porting Guide sections (e.g. QStringList) + * link toggled(bool) + * autolink foo(1) + * handle using correctly + * QObject "reentrant" list: duplicates + * operator<< \overload + * \compat \overload + * qWarning() link + * operator<<() autolink + + * get rid of spurious 'global' functions + * Make automatic links in code work + + * Fix encoding bug (see Important email from Simon Hausmann) + * Make links to QFoo::bar().baz() work + * Fix automatic links in \sectionX (e.g. qt4-getting-started.html) + * Provide a "List of all properties" page. + + * expand QObjectList -> QList<QObject *> + * warning for unnamed parameters in property access functions + * \center...\endcenter + +LINKS: + + * explanation following nonstandard wording warning + + * omit \overload in operator<< and operator>> + * make operator-() unary and binary independent functions (no \overload) + * fix \overload + * fix \legalese + * remove warning for undocumented enum item like QLocale::LastLanguage + * improve the \a warnings for overloads; if one overload documents a para, fine + + * implement \sidebar + + * implement \legalesefile + + * show in which module each class is + * list namespaces, list header files + + +NICE FEATURES: + * implement inheritance tree for each class (as a PNG) + * avoid <p>...</p> in table/item cells without relying on horrible kludge + * prevent macros from having same name as commands + * be smart about enum types Foo::Bar vs. Bar when comparing functions + * be smart about const & non-const when comparing functions + +OTHER: + * make qdoc run faster + * make sure \headerfile works even if specified after \relates diff --git a/src/qdoc/atom.cpp b/src/qdoc/atom.cpp new file mode 100644 index 000000000..f50f401c5 --- /dev/null +++ b/src/qdoc/atom.cpp @@ -0,0 +1,460 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qregexp.h> +#include "atom.h" +#include "location.h" +#include "qdocdatabase.h" +#include <stdio.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +/*! \class Atom + \brief The Atom class is the fundamental unit for representing + documents internally. + + Atoms have a \i type and are completed by a \i string whose + meaning depends on the \i type. For example, the string + \quotation + \i italic text looks nicer than \bold bold text + \endquotation + is represented by the following atoms: + \quotation + (FormattingLeft, ATOM_FORMATTING_ITALIC) + (String, "italic") + (FormattingRight, ATOM_FORMATTING_ITALIC) + (String, " text is more attractive than ") + (FormattingLeft, ATOM_FORMATTING_BOLD) + (String, "bold") + (FormattingRight, ATOM_FORMATTING_BOLD) + (String, " text") + \endquotation + + \also Text +*/ + +/*! \enum Atom::AtomType + + \value AnnotatedList + \value AutoLink + \value BaseName + \value BriefLeft + \value BriefRight + \value C + \value CaptionLeft + \value CaptionRight + \value Code + \value CodeBad + \value CodeNew + \value CodeOld + \value CodeQuoteArgument + \value CodeQuoteCommand + \value DivLeft + \value DivRight + \value EndQmlText + \value FormatElse + \value FormatEndif + \value FormatIf + \value FootnoteLeft + \value FootnoteRight + \value FormattingLeft + \value FormattingRight + \value GeneratedList + \value Image + \value ImageText + \value ImportantNote + \value InlineImage + \value JavaScript + \value EndJavaScript + \value Keyword + \value LineBreak + \value Link + \value LinkNode + \value ListLeft + \value ListItemNumber + \value ListTagLeft + \value ListTagRight + \value ListItemLeft + \value ListItemRight + \value ListRight + \value NavAutoLink + \value NavLink + \value Nop + \value Note + \value ParaLeft + \value ParaRight + \value Qml + \value QmlText + \value QuotationLeft + \value QuotationRight + \value RawString + \value SectionLeft + \value SectionRight + \value SectionHeadingLeft + \value SectionHeadingRight + \value SidebarLeft + \value SidebarRight + \value SinceList + \value String + \value TableLeft + \value TableRight + \value TableHeaderLeft + \value TableHeaderRight + \value TableRowLeft + \value TableRowRight + \value TableItemLeft + \value TableItemRight + \value TableOfContents + \value Target + \value UnhandledFormat + \value UnknownCommand +*/ + +QString Atom::noError_ = QString(); + +static const struct { + const char *english; + int no; +} atms[] = { + { "AnnotatedList", Atom::AnnotatedList }, + { "AutoLink", Atom::AutoLink }, + { "BaseName", Atom::BaseName }, + { "br", Atom::BR}, + { "BriefLeft", Atom::BriefLeft }, + { "BriefRight", Atom::BriefRight }, + { "C", Atom::C }, + { "CaptionLeft", Atom::CaptionLeft }, + { "CaptionRight", Atom::CaptionRight }, + { "Code", Atom::Code }, + { "CodeBad", Atom::CodeBad }, + { "CodeNew", Atom::CodeNew }, + { "CodeOld", Atom::CodeOld }, + { "CodeQuoteArgument", Atom::CodeQuoteArgument }, + { "CodeQuoteCommand", Atom::CodeQuoteCommand }, + { "DivLeft", Atom::DivLeft }, + { "DivRight", Atom::DivRight }, + { "EndQmlText", Atom::EndQmlText }, + { "FootnoteLeft", Atom::FootnoteLeft }, + { "FootnoteRight", Atom::FootnoteRight }, + { "FormatElse", Atom::FormatElse }, + { "FormatEndif", Atom::FormatEndif }, + { "FormatIf", Atom::FormatIf }, + { "FormattingLeft", Atom::FormattingLeft }, + { "FormattingRight", Atom::FormattingRight }, + { "GeneratedList", Atom::GeneratedList }, + { "GuidLink", Atom::GuidLink}, + { "hr", Atom::HR}, + { "Image", Atom::Image }, + { "ImageText", Atom::ImageText }, + { "ImportantLeft", Atom::ImportantLeft }, + { "ImportantRight", Atom::ImportantRight }, + { "InlineImage", Atom::InlineImage }, + { "JavaScript", Atom::JavaScript }, + { "EndJavaScript", Atom::EndJavaScript }, + { "Keyword", Atom::Keyword }, + { "LegaleseLeft", Atom::LegaleseLeft }, + { "LegaleseRight", Atom::LegaleseRight }, + { "LineBreak", Atom::LineBreak }, + { "Link", Atom::Link }, + { "LinkNode", Atom::LinkNode }, + { "ListLeft", Atom::ListLeft }, + { "ListItemNumber", Atom::ListItemNumber }, + { "ListTagLeft", Atom::ListTagLeft }, + { "ListTagRight", Atom::ListTagRight }, + { "ListItemLeft", Atom::ListItemLeft }, + { "ListItemRight", Atom::ListItemRight }, + { "ListRight", Atom::ListRight }, + { "NavAutoLink", Atom::NavAutoLink }, + { "NavLink", Atom::NavLink }, + { "Nop", Atom::Nop }, + { "NoteLeft", Atom::NoteLeft }, + { "NoteRight", Atom::NoteRight }, + { "ParaLeft", Atom::ParaLeft }, + { "ParaRight", Atom::ParaRight }, + { "Qml", Atom::Qml}, + { "QmlText", Atom::QmlText }, + { "QuotationLeft", Atom::QuotationLeft }, + { "QuotationRight", Atom::QuotationRight }, + { "RawString", Atom::RawString }, + { "SectionLeft", Atom::SectionLeft }, + { "SectionRight", Atom::SectionRight }, + { "SectionHeadingLeft", Atom::SectionHeadingLeft }, + { "SectionHeadingRight", Atom::SectionHeadingRight }, + { "SidebarLeft", Atom::SidebarLeft }, + { "SidebarRight", Atom::SidebarRight }, + { "SinceList", Atom::SinceList }, + { "SnippetCommand", Atom::SnippetCommand }, + { "SnippetIdentifier", Atom::SnippetIdentifier }, + { "SnippetLocation", Atom::SnippetLocation }, + { "String", Atom::String }, + { "TableLeft", Atom::TableLeft }, + { "TableRight", Atom::TableRight }, + { "TableHeaderLeft", Atom::TableHeaderLeft }, + { "TableHeaderRight", Atom::TableHeaderRight }, + { "TableRowLeft", Atom::TableRowLeft }, + { "TableRowRight", Atom::TableRowRight }, + { "TableItemLeft", Atom::TableItemLeft }, + { "TableItemRight", Atom::TableItemRight }, + { "TableOfContents", Atom::TableOfContents }, + { "Target", Atom::Target }, + { "UnhandledFormat", Atom::UnhandledFormat }, + { "UnknownCommand", Atom::UnknownCommand }, + { 0, 0 } +}; + +/*! \fn Atom::Atom(AtomType type, const QString& string) + + Constructs an atom of the specified \a type with the single + parameter \a string and does not put the new atom in a list. +*/ + +/*! \fn Atom::Atom(AtomType type, const QString& p1, const QString& p2) + + Constructs an atom of the specified \a type with the two + parameters \a p1 and \a p2 and does not put the new atom + in a list. +*/ + +/*! \fn Atom(Atom *previous, AtomType type, const QString& string) + + Constructs an atom of the specified \a type with the single + parameter \a string and inserts the new atom into the list + after the \a previous atom. +*/ + +/*! \fn Atom::Atom(Atom* previous, AtomType type, const QString& p1, const QString& p2) + + Constructs an atom of the specified \a type with the two + parameters \a p1 and \a p2 and inserts the new atom into + the list after the \a previous atom. +*/ + +/*! \fn void Atom::appendChar(QChar ch) + + Appends \a ch to the string parameter of this atom. + + \also string() +*/ + +/*! \fn void Atom::appendString(const QString& string) + + Appends \a string to the string parameter of this atom. + + \also string() +*/ + +/*! \fn void Atom::chopString() + + \also string() +*/ + +/*! \fn Atom *Atom::next() + Return the next atom in the atom list. + \also type(), string() +*/ + +/*! + Return the next Atom in the list if it is of AtomType \a t. + Otherwise return 0. + */ +const Atom* Atom::next(AtomType t) const +{ + return (next_ && (next_->type() == t)) ? next_ : 0; +} + +/*! + Return the next Atom in the list if it is of AtomType \a t + and its string part is \a s. Otherwise return 0. + */ +const Atom* Atom::next(AtomType t, const QString& s) const +{ + return (next_ && (next_->type() == t) && (next_->string() == s)) ? next_ : 0; +} + +/*! \fn const Atom *Atom::next() const + Return the next atom in the atom list. + \also type(), string() +*/ + +/*! \fn AtomType Atom::type() const + Return the type of this atom. + \also string(), next() +*/ + +/*! + Return the type of this atom as a string. Return "Invalid" if + type() returns an impossible value. + + This is only useful for debugging. + + \also type() +*/ +QString Atom::typeString() const +{ + static bool deja = false; + + if (!deja) { + int i = 0; + while (atms[i].english != 0) { + if (atms[i].no != i) + Location::internalError(QCoreApplication::translate("QDoc::Atom", "atom %1 missing").arg(i)); + i++; + } + deja = true; + } + + int i = (int) type(); + if (i < 0 || i > (int) Last) + return QLatin1String("Invalid"); + return QLatin1String(atms[i].english); +} + +/*! \fn const QString& Atom::string() const + + Returns the string parameter that together with the type + characterizes this atom. + + \also type(), next() +*/ + +/*! + Dumps this Atom to stderr in printer friendly form. + */ +void Atom::dump() const +{ + QString str = string(); + str.replace(QLatin1String("\\"), QLatin1String("\\\\")); + str.replace(QLatin1String("\""), QLatin1String("\\\"")); + str.replace(QLatin1String("\n"), QLatin1String("\\n")); + str.replace(QRegExp(QLatin1String("[^\x20-\x7e]")), QLatin1String("?")); + if (!str.isEmpty()) + str = QLatin1String(" \"") + str + QLatin1Char('"'); + fprintf(stderr, + " %-15s%s\n", + typeString().toLatin1().data(), + str.toLatin1().data()); +} + +/*! + The only constructor for LinkAtom. It creates an Atom of + type Atom::Link. \a p1 being the link target. \a p2 is the + parameters in square brackets. Normally there is just one + word in the square brackets, but there can be up to three + words separated by spaces. The constructor splits \a p2 on + the space character. + */ +LinkAtom::LinkAtom(const QString& p1, const QString& p2) + : Atom(p1), + resolved_(false), + genus_(Node::DontCare), + goal_(Node::NoType), + domain_(0), + squareBracketParams_(p2) +{ + // nada. +} + +/*! + This function resolves the parameters that were enclosed in + square brackets. If the parameters have already been resolved, + it does nothing and returns immediately. + */ +void LinkAtom::resolveSquareBracketParams() +{ + if (resolved_) + return; + QStringList params = squareBracketParams_.toLower().split(QLatin1Char(' ')); + foreach (const QString& p, params) { + if (!domain_) { + domain_ = QDocDatabase::qdocDB()->findTree(p); + if (domain_) { + continue; + } + } + if (goal_ == Node::NoType) { + goal_ = Node::goal(p); + if (goal_ != Node::NoType) + continue; + } + if (p == "qml") { + genus_ = Node::QML; + continue; + } + if (p == "cpp") { + genus_ = Node::CPP; + continue; + } + if (p == "doc") { + genus_ = Node::DOC; + continue; + } + error_ = squareBracketParams_; + break; + } + resolved_ = true; +} + +/*! + Standard copy constructor of LinkAtom \a t. + */ +LinkAtom::LinkAtom(const LinkAtom& t) + : Atom(Link, t.string()), + resolved_(t.resolved_), + genus_(t.genus_), + goal_(t.goal_), + domain_(t.domain_), + error_(t.error_), + squareBracketParams_(t.squareBracketParams_) +{ + // nothing +} + +/*! + Special copy constructor of LinkAtom \a t, where + where the new LinkAtom will not be the first one + in the list. + */ +LinkAtom::LinkAtom(Atom* previous, const LinkAtom& t) + : Atom(previous, Link, t.string()), + resolved_(t.resolved_), + genus_(t.genus_), + goal_(t.goal_), + domain_(t.domain_), + error_(t.error_), + squareBracketParams_(t.squareBracketParams_) +{ + previous->next_ = this; +} + +QT_END_NAMESPACE diff --git a/src/qdoc/atom.h b/src/qdoc/atom.h new file mode 100644 index 000000000..86b8ba7b3 --- /dev/null +++ b/src/qdoc/atom.h @@ -0,0 +1,257 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef ATOM_H +#define ATOM_H + +#include <qstringlist.h> +#include "node.h" +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +class Tree; +class LinkAtom; + +class Atom +{ +public: + enum AtomType { + AnnotatedList, + AutoLink, + BaseName, + BR, + BriefLeft, + BriefRight, + C, + CaptionLeft, + CaptionRight, + Code, + CodeBad, + CodeNew, + CodeOld, + CodeQuoteArgument, + CodeQuoteCommand, + DivLeft, + DivRight, + EndQmlText, + FootnoteLeft, + FootnoteRight, + FormatElse, + FormatEndif, + FormatIf, + FormattingLeft, + FormattingRight, + GeneratedList, + GuidLink, + HR, + Image, + ImageText, + ImportantLeft, + ImportantRight, + InlineImage, + JavaScript, + EndJavaScript, + Keyword, + LegaleseLeft, + LegaleseRight, + LineBreak, + Link, + LinkNode, + ListLeft, + ListItemNumber, + ListTagLeft, + ListTagRight, + ListItemLeft, + ListItemRight, + ListRight, + NavAutoLink, + NavLink, + Nop, + NoteLeft, + NoteRight, + ParaLeft, + ParaRight, + Qml, + QmlText, + QuotationLeft, + QuotationRight, + RawString, + SectionLeft, + SectionRight, + SectionHeadingLeft, + SectionHeadingRight, + SidebarLeft, + SidebarRight, + SinceList, + SnippetCommand, + SnippetIdentifier, + SnippetLocation, + String, + TableLeft, + TableRight, + TableHeaderLeft, + TableHeaderRight, + TableRowLeft, + TableRowRight, + TableItemLeft, + TableItemRight, + TableOfContents, + Target, + UnhandledFormat, + UnknownCommand, + Last = UnknownCommand + }; + + friend class LinkAtom; + + Atom(const QString& string) + : next_(0), type_(Link) + { + strs << string; + } + + Atom(AtomType type, const QString& string = "") + : next_(0), type_(type) + { + strs << string; + } + + Atom(AtomType type, const QString& p1, const QString& p2) + : next_(0), type_(type) + { + strs << p1; + if (!p2.isEmpty()) + strs << p2; + } + + Atom(Atom* previous, AtomType type, const QString& string = "") + : next_(previous->next_), type_(type) + { + strs << string; + previous->next_ = this; + } + + Atom(Atom* previous, AtomType type, const QString& p1, const QString& p2) + : next_(previous->next_), type_(type) + { + strs << p1; + if (!p2.isEmpty()) + strs << p2; + previous->next_ = this; + } + + virtual ~Atom() { } + + void appendChar(QChar ch) { strs[0] += ch; } + void appendString(const QString& string) { strs[0] += string; } + void chopString() { strs[0].chop(1); } + void setString(const QString& string) { strs[0] = string; } + Atom* next() { return next_; } + void setNext(Atom* newNext) { next_ = newNext; } + + const Atom* next() const { return next_; } + const Atom* next(AtomType t) const; + const Atom* next(AtomType t, const QString& s) const; + AtomType type() const { return type_; } + QString typeString() const; + const QString& string() const { return strs[0]; } + const QString& string(int i) const { return strs[i]; } + int count() const { return strs.size(); } + void dump() const; + const QStringList& strings() const { return strs; } + + virtual bool isLinkAtom() const { return false; } + virtual Node::Genus genus() { return Node::DontCare; } + virtual bool specifiesDomain() { return false; } + virtual Tree* domain() { return 0; } + virtual Node::NodeType goal() { return Node::NoType; } + virtual const QString& error() { return noError_; } + virtual void resolveSquareBracketParams() { } + + protected: + static QString noError_; + Atom* next_; + AtomType type_; + QStringList strs; +}; + +class LinkAtom : public Atom +{ + public: + LinkAtom(const QString& p1, const QString& p2); + LinkAtom(const LinkAtom& t); + LinkAtom(Atom* previous, const LinkAtom& t); + virtual ~LinkAtom() { } + + virtual bool isLinkAtom() const Q_DECL_OVERRIDE { return true; } + virtual Node::Genus genus() Q_DECL_OVERRIDE { resolveSquareBracketParams(); return genus_; } + virtual bool specifiesDomain() Q_DECL_OVERRIDE { resolveSquareBracketParams(); return (domain_ != 0); } + virtual Tree* domain() Q_DECL_OVERRIDE { resolveSquareBracketParams(); return domain_; } + virtual Node::NodeType goal() Q_DECL_OVERRIDE { resolveSquareBracketParams(); return goal_; } + virtual const QString& error() Q_DECL_OVERRIDE { return error_; } + virtual void resolveSquareBracketParams() Q_DECL_OVERRIDE; + + protected: + bool resolved_; + Node::Genus genus_; + Node::NodeType goal_; + Tree* domain_; + QString error_; + QString squareBracketParams_; +}; + +#define ATOM_FORMATTING_BOLD "bold" +#define ATOM_FORMATTING_INDEX "index" +#define ATOM_FORMATTING_ITALIC "italic" +#define ATOM_FORMATTING_LINK "link" +#define ATOM_FORMATTING_PARAMETER "parameter" +#define ATOM_FORMATTING_SPAN "span " +#define ATOM_FORMATTING_SUBSCRIPT "subscript" +#define ATOM_FORMATTING_SUPERSCRIPT "superscript" +#define ATOM_FORMATTING_TELETYPE "teletype" +#define ATOM_FORMATTING_UICONTROL "uicontrol" +#define ATOM_FORMATTING_UNDERLINE "underline" + +#define ATOM_LIST_BULLET "bullet" +#define ATOM_LIST_TAG "tag" +#define ATOM_LIST_VALUE "value" +#define ATOM_LIST_LOWERALPHA "loweralpha" +#define ATOM_LIST_LOWERROMAN "lowerroman" +#define ATOM_LIST_NUMERIC "numeric" +#define ATOM_LIST_UPPERALPHA "upperalpha" +#define ATOM_LIST_UPPERROMAN "upperroman" + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/codechunk.cpp b/src/qdoc/codechunk.cpp new file mode 100644 index 000000000..5799e0bac --- /dev/null +++ b/src/qdoc/codechunk.cpp @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + codechunk.cpp +*/ + +#include <qregexp.h> +#include <qstringlist.h> + +#include "codechunk.h" + +QT_BEGIN_NAMESPACE + +enum { Other, Alnum, Gizmo, Comma, LParen, RParen, RAngle, Colon }; + +// entries 128 and above are Other +static const int charCategory[256] = { + Other, Other, Other, Other, Other, Other, Other, Other, + Other, Other, Other, Other, Other, Other, Other, Other, + Other, Other, Other, Other, Other, Other, Other, Other, + Other, Other, Other, Other, Other, Other, Other, Other, + // ! " # $ % & ' + Other, Other, Other, Other, Other, Gizmo, Gizmo, Other, + // ( ) * + , - . / + LParen, RParen, Gizmo, Gizmo, Comma, Other, Other, Gizmo, + // 0 1 2 3 4 5 6 7 + Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, + // 8 9 : ; < = > ? + Alnum, Alnum, Colon, Other, Other, Gizmo, RAngle, Gizmo, + // @ A B C D E F G + Other, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, + // H I J K L M N O + Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, + // P Q R S T U V W + Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, + // X Y Z [ \ ] ^ _ + Alnum, Alnum, Alnum, Other, Other, Other, Gizmo, Alnum, + // ` a b c d e f g + Other, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, + // h i j k l m n o + Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, + // p q r s t u v w + Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, + // x y z { | } ~ + Alnum, Alnum, Alnum, LParen, Gizmo, RParen, Other, Other +}; + +static const bool needSpace[8][8] = { + /* [ a + , ( ) > : */ + /* [ */ { false, false, false, false, false, true, false, false }, + /* a */ { false, true, true, false, false, true, false, false }, + /* + */ { false, true, false, false, false, true, false, true }, + /* , */ { true, true, true, true, true, true, true, true }, + /* ( */ { true, true, true, false, true, false, true, true }, + /* ) */ { true, true, true, false, true, true, true, true }, + /* > */ { true, true, true, false, true, true, true, false }, + /* : */ { false, false, true, true, true, true, true, false } +}; + +static int category( QChar ch ) +{ + return charCategory[(int)ch.toLatin1()]; +} + +CodeChunk::CodeChunk() + : hotspot( -1 ) +{ +} + +CodeChunk::CodeChunk( const QString& str ) + : s( str ), hotspot( -1 ) +{ +} + +void CodeChunk::append( const QString& lexeme ) +{ + if ( !s.isEmpty() && !lexeme.isEmpty() ) { + /* + Should there be a space or not between the code chunk so far and the + new lexeme? + */ + int cat1 = category(s.at(s.size() - 1)); + int cat2 = category(lexeme[0]); + if ( needSpace[cat1][cat2] ) + s += QLatin1Char( ' ' ); + } + s += lexeme; +} + +void CodeChunk::appendHotspot() +{ + /* + The first hotspot is the right one. + */ + if ( hotspot == -1 ) + hotspot = s.length(); +} + +QString CodeChunk::toString() const +{ + return s; +} + +QStringList CodeChunk::toPath() const +{ + QString t = s; + t.remove(QRegExp(QLatin1String("<([^<>]|<([^<>]|<[^<>]*>)*>)*>"))); + return t.split(QLatin1String("::")); +} + +QT_END_NAMESPACE diff --git a/src/qdoc/codechunk.h b/src/qdoc/codechunk.h new file mode 100644 index 000000000..259012df9 --- /dev/null +++ b/src/qdoc/codechunk.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + codechunk.h +*/ + +#ifndef CODECHUNK_H +#define CODECHUNK_H + +#include <qstring.h> + +QT_BEGIN_NAMESPACE + +// ### get rid of that class + +/* + The CodeChunk class represents a tiny piece of C++ code. + + The class provides conversion between a list of lexemes and a string. It adds + spaces at the right place for consistent style. The tiny pieces of code it + represents are data types, enum values, and default parameter values. + + Apart from the piece of code itself, there are two bits of metainformation + stored in CodeChunk: the base and the hotspot. The base is the part of the + piece that may be a hypertext link. The base of + + QMap<QString, QString> + + is QMap. + + The hotspot is the place the variable name should be inserted in the case of a + variable (or parameter) declaration. The base of + + char * [] + + is between '*' and '[]'. +*/ +class CodeChunk +{ +public: + CodeChunk(); + CodeChunk( const QString& str ); + + void append( const QString& lexeme ); + void appendHotspot(); + + bool isEmpty() const { return s.isEmpty(); } + void clear() { s.clear(); } + QString toString() const; + QStringList toPath() const; + QString left() const { return s.left(hotspot == -1 ? s.length() : hotspot); } + QString right() const { return s.mid(hotspot == -1 ? s.length() : hotspot); } + +private: + QString s; + int hotspot; +}; + +inline bool operator==( const CodeChunk& c, const CodeChunk& d ) { + return c.toString() == d.toString(); +} + +inline bool operator!=( const CodeChunk& c, const CodeChunk& d ) { + return !( c == d ); +} + +inline bool operator<( const CodeChunk& c, const CodeChunk& d ) { + return c.toString() < d.toString(); +} + +inline bool operator>( const CodeChunk& c, const CodeChunk& d ) { + return d < c; +} + +inline bool operator<=( const CodeChunk& c, const CodeChunk& d ) { + return !( c > d ); +} + +inline bool operator>=( const CodeChunk& c, const CodeChunk& d ) { + return !( c < d ); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/codemarker.cpp b/src/qdoc/codemarker.cpp new file mode 100644 index 000000000..a668205a6 --- /dev/null +++ b/src/qdoc/codemarker.cpp @@ -0,0 +1,668 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qobjectdefs.h> +#include "codemarker.h" +#include "config.h" +#include "node.h" +#include <qdebug.h> +#include <stdio.h> + +QT_BEGIN_NAMESPACE + +QString CodeMarker::defaultLang; +QList<CodeMarker *> CodeMarker::markers; + +/*! + When a code marker constructs itself, it puts itself into + the static list of code markers. All the code markers in + the static list get initialized in initialize(), which is + not called until after the qdoc configuration file has + been read. + */ +CodeMarker::CodeMarker() +{ + markers.prepend(this); +} + +/*! + When a code marker destroys itself, it removes itself from + the static list of code markers. + */ +CodeMarker::~CodeMarker() +{ + markers.removeAll(this); +} + +/*! + A code market performs no initialization by default. Marker-specific + initialization is performed in subclasses. + */ +void CodeMarker::initializeMarker(const Config& ) // config +{ +} + +/*! + Terminating a code marker is trivial. + */ +void CodeMarker::terminateMarker() +{ + // nothing. +} + +/*! + All the code markers in the static list are initialized + here, after the qdoc configuration file has been loaded. + */ +void CodeMarker::initialize(const Config& config) +{ + defaultLang = config.getString(CONFIG_LANGUAGE); + QList<CodeMarker *>::ConstIterator m = markers.constBegin(); + while (m != markers.constEnd()) { + (*m)->initializeMarker(config); + ++m; + } +} + +/*! + All the code markers in the static list are terminated here. + */ +void CodeMarker::terminate() +{ + QList<CodeMarker *>::ConstIterator m = markers.constBegin(); + while (m != markers.constEnd()) { + (*m)->terminateMarker(); + ++m; + } +} + +CodeMarker *CodeMarker::markerForCode(const QString& code) +{ + CodeMarker *defaultMarker = markerForLanguage(defaultLang); + if (defaultMarker != 0 && defaultMarker->recognizeCode(code)) + return defaultMarker; + + QList<CodeMarker *>::ConstIterator m = markers.constBegin(); + while (m != markers.constEnd()) { + if ((*m)->recognizeCode(code)) + return *m; + ++m; + } + return defaultMarker; +} + +CodeMarker *CodeMarker::markerForFileName(const QString& fileName) +{ + CodeMarker *defaultMarker = markerForLanguage(defaultLang); + int dot = -1; + while ((dot = fileName.lastIndexOf(QLatin1Char('.'), dot)) != -1) { + QString ext = fileName.mid(dot + 1); + if (defaultMarker != 0 && defaultMarker->recognizeExtension(ext)) + return defaultMarker; + QList<CodeMarker *>::ConstIterator m = markers.constBegin(); + while (m != markers.constEnd()) { + if ((*m)->recognizeExtension(ext)) + return *m; + ++m; + } + --dot; + } + return defaultMarker; +} + +CodeMarker *CodeMarker::markerForLanguage(const QString& lang) +{ + QList<CodeMarker *>::ConstIterator m = markers.constBegin(); + while (m != markers.constEnd()) { + if ((*m)->recognizeLanguage(lang)) + return *m; + ++m; + } + return 0; +} + +const Node *CodeMarker::nodeForString(const QString& string) +{ + if (sizeof(const Node *) == sizeof(uint)) { + return reinterpret_cast<const Node *>(string.toUInt()); + } + else { + return reinterpret_cast<const Node *>(string.toULongLong()); + } +} + +QString CodeMarker::stringForNode(const Node *node) +{ + if (sizeof(const Node *) == sizeof(ulong)) { + return QString::number(reinterpret_cast<quintptr>(node)); + } + else { + return QString::number(reinterpret_cast<qulonglong>(node)); + } +} + +static const QString samp = QLatin1String("&"); +static const QString slt = QLatin1String("<"); +static const QString sgt = QLatin1String(">"); +static const QString squot = QLatin1String("""); + +QString CodeMarker::protect(const QString& str) +{ + int n = str.length(); + QString marked; + marked.reserve(n * 2 + 30); + const QChar *data = str.constData(); + for (int i = 0; i != n; ++i) { + switch (data[i].unicode()) { + case '&': marked += samp; break; + case '<': marked += slt; break; + case '>': marked += sgt; break; + case '"': marked += squot; break; + default : marked += data[i]; + } + } + return marked; +} + +void CodeMarker::appendProtectedString(QString *output, const QStringRef &str) +{ + int n = str.length(); + output->reserve(output->size() + n * 2 + 30); + const QChar *data = str.constData(); + for (int i = 0; i != n; ++i) { + switch (data[i].unicode()) { + case '&': *output += samp; break; + case '<': *output += slt; break; + case '>': *output += sgt; break; + case '"': *output += squot; break; + default : *output += data[i]; + } + } +} + +QString CodeMarker::typified(const QString &string, bool trailingSpace) +{ + QString result; + QString pendingWord; + + for (int i = 0; i <= string.size(); ++i) { + QChar ch; + if (i != string.size()) + ch = string.at(i); + + QChar lower = ch.toLower(); + if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) + || ch.digitValue() >= 0 || ch == QLatin1Char('_') + || ch == QLatin1Char(':')) { + pendingWord += ch; + } + else { + if (!pendingWord.isEmpty()) { + bool isProbablyType = (pendingWord != QLatin1String("const")); + if (isProbablyType) + result += QLatin1String("<@type>"); + result += pendingWord; + if (isProbablyType) + result += QLatin1String("</@type>"); + } + pendingWord.clear(); + + switch (ch.unicode()) { + case '\0': + break; + case '&': + result += QLatin1String("&"); + break; + case '<': + result += QLatin1String("<"); + break; + case '>': + result += QLatin1String(">"); + break; + default: + result += ch; + } + } + } + if (trailingSpace && string.size()) { + if (!string.endsWith(QLatin1Char('*')) + && !string.endsWith(QLatin1Char('&'))) + result += QLatin1Char(' '); + } + return result; +} + +QString CodeMarker::taggedNode(const Node* node) +{ + QString tag; + QString name = node->name(); + + switch (node->type()) { + case Node::Namespace: + tag = QLatin1String("@namespace"); + break; + case Node::Class: + tag = QLatin1String("@class"); + break; + case Node::Enum: + tag = QLatin1String("@enum"); + break; + case Node::Typedef: + tag = QLatin1String("@typedef"); + break; + case Node::Function: + tag = QLatin1String("@function"); + break; + case Node::Property: + tag = QLatin1String("@property"); + break; + case Node::QmlType: + /* + Remove the "QML:" prefix, if present. + There shouldn't be any of these "QML:" + prefixes in the documentation sources + after the switch to using QML module + qualifiers, but this code is kept to + be backward compatible. + */ + if (node->name().startsWith(QLatin1String("QML:"))) + name = name.mid(4); + tag = QLatin1String("@property"); + break; + case Node::Document: + tag = QLatin1String("@property"); + break; + case Node::QmlMethod: + case Node::QmlSignal: + case Node::QmlSignalHandler: + tag = QLatin1String("@function"); + break; + default: + tag = QLatin1String("@unknown"); + break; + } + return (QLatin1Char('<') + tag + QLatin1Char('>') + protect(name) + + QLatin1String("</") + tag + QLatin1Char('>')); +} + +QString CodeMarker::taggedQmlNode(const Node* node) +{ + QString tag; + switch (node->type()) { + case Node::QmlProperty: + tag = QLatin1String("@property"); + break; + case Node::QmlSignal: + tag = QLatin1String("@signal"); + break; + case Node::QmlSignalHandler: + tag = QLatin1String("@signalhandler"); + break; + case Node::QmlMethod: + tag = QLatin1String("@method"); + break; + default: + tag = QLatin1String("@unknown"); + break; + } + return QLatin1Char('<') + tag + QLatin1Char('>') + protect(node->name()) + + QLatin1String("</") + tag + QLatin1Char('>'); +} + +QString CodeMarker::linkTag(const Node *node, const QString& body) +{ + return QLatin1String("<@link node=\"") + stringForNode(node) + + QLatin1String("\">") + body + QLatin1String("</@link>"); +} + +QString CodeMarker::sortName(const Node *node, const QString* name) +{ + QString nodeName; + if (name != 0) + nodeName = *name; + else + nodeName = node->name(); + int numDigits = 0; + for (int i = nodeName.size() - 1; i > 0; --i) { + if (nodeName.at(i).digitValue() == -1) + break; + ++numDigits; + } + + // we want 'qint8' to appear before 'qint16' + if (numDigits > 0) { + for (int i = 0; i < 4 - numDigits; ++i) + nodeName.insert(nodeName.size()-numDigits-1, QLatin1Char('0')); + } + + if (node->type() == Node::Function) { + const FunctionNode *func = static_cast<const FunctionNode *>(node); + QString sortNo; + if (func->metaness() == FunctionNode::Ctor) { + sortNo = QLatin1String("C"); + } + else if (func->metaness() == FunctionNode::Dtor) { + sortNo = QLatin1String("D"); + } + else { + if (nodeName.startsWith(QLatin1String("operator")) + && nodeName.length() > 8 + && !nodeName[8].isLetterOrNumber()) + sortNo = QLatin1String("F"); + else + sortNo = QLatin1String("E"); + } + return sortNo + nodeName + QLatin1Char(' ') + QString::number(func->overloadNumber(), 36); + } + + if (node->type() == Node::Class) + return QLatin1Char('A') + nodeName; + + if (node->type() == Node::Property || node->type() == Node::Variable) + return QLatin1Char('E') + nodeName; + + if ((node->type() == Node::QmlMethod) || + (node->type() == Node::QmlSignal) || + (node->type() == Node::QmlSignalHandler)) { + //const FunctionNode* func = static_cast<const FunctionNode *>(node); + //return QLatin1Char('E') + func->name(); + return QLatin1Char('E') + nodeName; + } + + return QLatin1Char('B') + nodeName; +} + +void CodeMarker::insert(FastSection &fastSection, + Node *node, + SynopsisStyle style, + Status status) +{ + bool irrelevant = false; + bool inheritedMember = false; + if (!node->relates()) { + Aggregate* p = node->parent(); + if (p->isQmlPropertyGroup()) + p = p->parent(); + if (p != fastSection.parent_) { + if ((!p->isQmlType() && !p->isJsType()) || !p->isAbstract()) + inheritedMember = true; + } + } + + if (node->access() == Node::Private) { + irrelevant = true; + } + else if (node->type() == Node::Function) { + FunctionNode *func = (FunctionNode *) node; + irrelevant = (inheritedMember + && (func->metaness() == FunctionNode::Ctor || + func->metaness() == FunctionNode::Dtor)); + } + else if (node->type() == Node::Class || node->type() == Node::Enum + || node->type() == Node::Typedef) { + irrelevant = (inheritedMember && style != Subpage); + if (!irrelevant && style == Detailed && node->type() == Node::Typedef) { + const TypedefNode* typedeffe = static_cast<const TypedefNode*>(node); + if (typedeffe->associatedEnum()) + irrelevant = true; + } + } + + if (!irrelevant) { + if (status == Compat) { + irrelevant = (node->status() != Node::Compat); + } + else if (status == Obsolete) { + irrelevant = (node->status() != Node::Obsolete); + } + else { + irrelevant = (node->status() == Node::Compat || + node->status() == Node::Obsolete); + } + } + + if (!irrelevant) { + if (!inheritedMember || style == Subpage) { + QString key = sortName(node); + fastSection.memberMap.insertMulti(key, node); + } + else { + if (node->parent()->isClass() || node->parent()->isNamespace()) { + if (fastSection.inherited.isEmpty() + || fastSection.inherited.last().first != node->parent()) { + QPair<Aggregate *, int> p(node->parent(), 0); + fastSection.inherited.append(p); + } + fastSection.inherited.last().second++; + } + } + } +} + +/*! + Returns \c true if \a node represents a reimplemented member + function in the class of the FastSection \a fs. If it is + a reimplemented function, then it is inserted into the + reimplemented member map in \a fs. The test is performed + only if \a status is \e OK. True is returned if \a node + is inserted into the map. Otherwise, false is returned. + */ +bool CodeMarker::insertReimpFunc(FastSection& fs, Node* node, Status status) +{ + if ((node->access() != Node::Private) && (node->relates() == 0)) { + const FunctionNode* fn = static_cast<const FunctionNode*>(node); + if ((fn->reimplementedFrom() != 0) && (status == Okay)) { + if (fn->parent() == fs.parent_) { + QString key = sortName(fn); + if (!fs.reimpMemberMap.contains(key)) { + fs.reimpMemberMap.insert(key,node); + return true; + } + } + } + } + return false; +} + +/*! + If \a fs is not empty, convert it to a Section and append + the new Section to \a sectionList. + */ +void CodeMarker::append(QList<Section>& sectionList, const FastSection& fs, bool includeKeys) +{ + if (!fs.isEmpty()) { + if (fs.classMapList_.isEmpty()) { + Section section(fs.name,fs.divClass,fs.singularMember,fs.pluralMember); + if (includeKeys) { + section.keys = fs.memberMap.keys(); + } + section.members = fs.memberMap.values(); + section.reimpMembers = fs.reimpMemberMap.values(); + section.inherited = fs.inherited; + sectionList.append(section); + } + else { + Section section(fs.name,fs.divClass,fs.singularMember,fs.pluralMember); + sectionList.append(section); + Section* s = §ionList[sectionList.size()-1]; + for (int i=0; i<fs.classMapList_.size(); i++) { + ClassMap* classMap = fs.classMapList_[i]; + ClassKeysNodes* ckn = new ClassKeysNodes; + ckn->first = classMap->first; + ckn->second.second = classMap->second.values(); + ckn->second.first = classMap->second.keys(); + s->classKeysNodesList_.append(ckn); + } + } + } +} + +/*! + The destructor must delete each member of the + list of QML class lists, if it is not empty; + */ +Section::~Section() +{ + if (!classKeysNodesList_.isEmpty()) { + for (int i=0; i<classKeysNodesList_.size(); i++) { + ClassKeysNodes* classKeysNodes = classKeysNodesList_[i]; + classKeysNodesList_[i] = 0; + delete classKeysNodes; + } + } +} + +/*! + The destructor must delete the QML class maps in the class + map list, if the class map list is not empty. + */ +FastSection::~FastSection() +{ + if (!classMapList_.isEmpty()) { + for (int i=0; i<classMapList_.size(); i++) { + ClassMap* classMap = classMapList_[i]; + classMapList_[i] = 0; + delete classMap; + } + } +} + +static QString encode(const QString &string) +{ + return string; +} + +QStringList CodeMarker::macRefsForNode(Node *node) +{ + QString result = QLatin1String("cpp/"); + switch (node->type()) { + case Node::Class: + { + const ClassNode *classe = static_cast<const ClassNode *>(node); + { + result += QLatin1String("cl/"); + } + result += macName(classe); // ### Maybe plainName? + } + break; + case Node::Enum: + { + QStringList stringList; + stringList << encode(result + QLatin1String("tag/") + + macName(node)); + foreach (const QString &enumName, node->doc().enumItemNames()) { + // ### Write a plainEnumValue() and use it here + stringList << encode(result + QLatin1String("econst/") + + macName(node->parent(), enumName)); + } + return stringList; + } + case Node::Typedef: + result += QLatin1String("tdef/") + macName(node); + break; + case Node::Function: + { + bool isMacro = false; + Q_UNUSED(isMacro) + const FunctionNode *func = static_cast<const FunctionNode *>(node); + + // overloads are too clever for the Xcode documentation browser + if (func->isOverload()) + return QStringList(); + + if (func->metaness() == FunctionNode::MacroWithParams + || func->metaness() == FunctionNode::MacroWithoutParams) { + result += QLatin1String("macro/"); + } + else if (func->isStatic()) { + result += QLatin1String("clm/"); + } + else if (!func->parent()->name().isEmpty()) { + result += QLatin1String("instm/"); + } + else { + result += QLatin1String("func/"); + } + + result += macName(func); + if (result.endsWith(QLatin1String("()"))) + result.chop(2); + } + break; + case Node::Variable: + result += QLatin1String("data/") + macName(node); + break; + case Node::Property: + { + NodeList list = static_cast<const PropertyNode*>(node)->functions(); + QStringList stringList; + foreach (Node* node, list) { + stringList += macRefsForNode(node); + } + return stringList; + } + case Node::Namespace: + case Node::Document: + case Node::QmlType: + default: + return QStringList(); + } + + return QStringList(encode(result)); +} + +QString CodeMarker::macName(const Node *node, const QString &name) +{ + QString myName = name; + if (myName.isEmpty()) { + myName = node->name(); + node = node->parent(); + } + + if (node->name().isEmpty()) { + return QLatin1Char('/') + protect(myName); + } + else { + return node->plainFullName() + QLatin1Char('/') + protect(myName); + } +} + +/*! + Returns an empty list of documentation sections. + */ +QList<Section> CodeMarker::qmlSections(QmlTypeNode* , SynopsisStyle , Status ) +{ + return QList<Section>(); +} + +QT_END_NAMESPACE diff --git a/src/qdoc/codemarker.h b/src/qdoc/codemarker.h new file mode 100644 index 000000000..011c8623d --- /dev/null +++ b/src/qdoc/codemarker.h @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + codemarker.h +*/ + +#ifndef CODEMARKER_H +#define CODEMARKER_H + +#include <qpair.h> + +#include "atom.h" +#include "node.h" + +QT_BEGIN_NAMESPACE + +class Config; + +typedef QMultiMap<QString, Node*> MemberMap; // the string is the member signature +typedef QPair<const QmlTypeNode*, MemberMap> ClassMap; // the node is the QML type +typedef QList<ClassMap*> ClassMapList; + +typedef QPair<QStringList, NodeList> KeysAndNodes; +typedef QPair<const QmlTypeNode*, KeysAndNodes> ClassKeysNodes; +typedef QList<ClassKeysNodes*> ClassKeysNodesList; + +struct Section +{ + QString name; + QString divClass; + QString singularMember; + QString pluralMember; + QStringList keys; + NodeList members; + NodeList reimpMembers; + QList<QPair<Aggregate *, int> > inherited; + ClassKeysNodesList classKeysNodesList_; + + Section() { } + Section(const QString& name0, + const QString& divClass0, + const QString& singularMember0, + const QString& pluralMember0) + : name(name0), + divClass(divClass0), + singularMember(singularMember0), + pluralMember(pluralMember0) { } + ~Section(); + void appendMember(Node* node) { members.append(node); } + void appendReimpMember(Node* node) { reimpMembers.append(node); } +}; + +struct FastSection +{ + const Aggregate *parent_; + QString name; + QString divClass; + QString singularMember; + QString pluralMember; + QMultiMap<QString, Node *> memberMap; + QMultiMap<QString, Node *> reimpMemberMap; + ClassMapList classMapList_; + QList<QPair<Aggregate *, int> > inherited; + + FastSection(const Aggregate *parent, + const QString& name0, + const QString& divClass0, + const QString& singularMember0, + const QString& pluralMember0) + : parent_(parent), + name(name0), + divClass(divClass0), + singularMember(singularMember0), + pluralMember(pluralMember0) { } + ~FastSection(); + bool isEmpty() const { + return (memberMap.isEmpty() && + inherited.isEmpty() && + reimpMemberMap.isEmpty() && + classMapList_.isEmpty()); + } + +}; + +class CodeMarker +{ +public: + enum SynopsisStyle { Summary, Detailed, Subpage, Accessors }; + enum Status { Compat, Obsolete, Okay }; + + CodeMarker(); + virtual ~CodeMarker(); + + virtual void initializeMarker(const Config& config); + virtual void terminateMarker(); + virtual bool recognizeCode(const QString& code) = 0; + virtual bool recognizeExtension(const QString& ext) = 0; + virtual bool recognizeLanguage(const QString& lang) = 0; + virtual Atom::AtomType atomType() const = 0; + virtual QString markedUpCode(const QString& code, + const Node *relative, + const Location &location) = 0; + virtual QString markedUpSynopsis(const Node *node, + const Node *relative, + SynopsisStyle style) = 0; + virtual QString markedUpQmlItem(const Node* , bool) { return QString(); } + virtual QString markedUpName(const Node *node) = 0; + virtual QString markedUpFullName(const Node *node, + const Node *relative = 0) = 0; + virtual QString markedUpEnumValue(const QString &enumValue, + const Node *relative) = 0; + virtual QString markedUpIncludes(const QStringList& includes) = 0; + virtual QString functionBeginRegExp(const QString& funcName) = 0; + virtual QString functionEndRegExp(const QString& funcName) = 0; + virtual QList<Section> sections(const Aggregate *inner, + SynopsisStyle style, + Status status) = 0; + virtual QList<Section> qmlSections(QmlTypeNode* qmlTypeNode, + SynopsisStyle style, + Status status = Okay); + virtual QStringList macRefsForNode(Node* node); + + static void initialize(const Config& config); + static void terminate(); + static CodeMarker *markerForCode(const QString& code); + static CodeMarker *markerForFileName(const QString& fileName); + static CodeMarker *markerForLanguage(const QString& lang); + static const Node *nodeForString(const QString& string); + static QString stringForNode(const Node *node); + + QString typified(const QString &string, bool trailingSpace = false); + +protected: + virtual QString sortName(const Node *node, const QString* name = 0); + static QString protect(const QString &string); + static void appendProtectedString(QString *output, const QStringRef &str); + QString taggedNode(const Node* node); + QString taggedQmlNode(const Node* node); + QString linkTag(const Node *node, const QString& body); + void insert(FastSection &fastSection, + Node *node, + SynopsisStyle style, + Status status); + bool insertReimpFunc(FastSection& fs, Node* node, Status status); + void append(QList<Section>& sectionList, const FastSection& fastSection, bool includeKeys = false); + +private: + QString macName(const Node *parent, const QString &name = QString()); + + static QString defaultLang; + static QList<CodeMarker *> markers; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/codeparser.cpp b/src/qdoc/codeparser.cpp new file mode 100644 index 000000000..92a0d5212 --- /dev/null +++ b/src/qdoc/codeparser.cpp @@ -0,0 +1,437 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + codeparser.cpp +*/ + +#include "codeparser.h" +#include "node.h" +#include "tree.h" +#include "config.h" +#include "generator.h" +#include "qdocdatabase.h" +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +#define COMMAND_COMPAT Doc::alias(QLatin1String("compat")) +#define COMMAND_DEPRECATED Doc::alias(QLatin1String("deprecated")) // ### don't document +#define COMMAND_INGROUP Doc::alias(QLatin1String("ingroup")) +#define COMMAND_INMODULE Doc::alias(QLatin1String("inmodule")) // ### don't document +#define COMMAND_INQMLMODULE Doc::alias(QLatin1String("inqmlmodule")) +#define COMMAND_INJSMODULE Doc::alias(QLatin1String("injsmodule")) +#define COMMAND_INTERNAL Doc::alias(QLatin1String("internal")) +#define COMMAND_MAINCLASS Doc::alias(QLatin1String("mainclass")) +#define COMMAND_NONREENTRANT Doc::alias(QLatin1String("nonreentrant")) +#define COMMAND_OBSOLETE Doc::alias(QLatin1String("obsolete")) +#define COMMAND_PAGEKEYWORDS Doc::alias(QLatin1String("pagekeywords")) +#define COMMAND_PRELIMINARY Doc::alias(QLatin1String("preliminary")) +#define COMMAND_INPUBLICGROUP Doc::alias(QLatin1String("inpublicgroup")) +#define COMMAND_QTVARIABLE Doc::alias(QLatin1String("qtvariable")) +#define COMMAND_REENTRANT Doc::alias(QLatin1String("reentrant")) +#define COMMAND_SINCE Doc::alias(QLatin1String("since")) +#define COMMAND_SUBTITLE Doc::alias(QLatin1String("subtitle")) +#define COMMAND_THREADSAFE Doc::alias(QLatin1String("threadsafe")) +#define COMMAND_TITLE Doc::alias(QLatin1String("title")) +#define COMMAND_WRAPPER Doc::alias(QLatin1String("wrapper")) +#define COMMAND_NOAUTOLIST Doc::alias(QLatin1String("noautolist")) + +QList<CodeParser *> CodeParser::parsers; +bool CodeParser::showInternal_ = false; +bool CodeParser::singleExec_ = false; + +/*! + The constructor adds this code parser to the static + list of code parsers. + */ +CodeParser::CodeParser() +{ + qdb_ = QDocDatabase::qdocDB(); + parsers.prepend(this); +} + +/*! + The destructor removes this code parser from the static + list of code parsers. + */ +CodeParser::~CodeParser() +{ + parsers.removeAll(this); +} + +/*! + Initialize the code parser base class. + */ +void CodeParser::initializeParser(const Config& config) +{ + showInternal_ = config.getBool(CONFIG_SHOWINTERNAL); + singleExec_ = config.getBool(CONFIG_SINGLEEXEC); +} + +/*! + Terminating a code parser is trivial. + */ +void CodeParser::terminateParser() +{ + // nothing. +} + +QStringList CodeParser::headerFileNameFilter() +{ + return sourceFileNameFilter(); +} + +void CodeParser::parseHeaderFile(const Location& location, const QString& filePath) +{ + parseSourceFile(location, filePath); +} + +void CodeParser::doneParsingHeaderFiles() +{ + doneParsingSourceFiles(); +} + +/*! + All the code parsers in the static list are initialized here, + after the qdoc configuration variables have been set. + */ +void CodeParser::initialize(const Config& config) +{ + QList<CodeParser *>::ConstIterator p = parsers.constBegin(); + while (p != parsers.constEnd()) { + (*p)->initializeParser(config); + ++p; + } +} + +/*! + All the code parsers in the static list are terminated here. + */ +void CodeParser::terminate() +{ + QList<CodeParser *>::ConstIterator p = parsers.constBegin(); + while (p != parsers.constEnd()) { + (*p)->terminateParser(); + ++p; + } +} + +CodeParser *CodeParser::parserForLanguage(const QString& language) +{ + QList<CodeParser *>::ConstIterator p = parsers.constBegin(); + while (p != parsers.constEnd()) { + if ((*p)->language() == language) + return *p; + ++p; + } + return 0; +} + +CodeParser *CodeParser::parserForHeaderFile(const QString &filePath) +{ + QString fileName = QFileInfo(filePath).fileName(); + + QList<CodeParser *>::ConstIterator p = parsers.constBegin(); + while (p != parsers.constEnd()) { + + QStringList headerPatterns = (*p)->headerFileNameFilter(); + foreach (const QString &pattern, headerPatterns) { + QRegExp re(pattern, Qt::CaseInsensitive, QRegExp::Wildcard); + if (re.exactMatch(fileName)) + return *p; + } + ++p; + } + return 0; +} + +CodeParser *CodeParser::parserForSourceFile(const QString &filePath) +{ + QString fileName = QFileInfo(filePath).fileName(); + + QList<CodeParser *>::ConstIterator p = parsers.constBegin(); + while (p != parsers.constEnd()) { + + QStringList sourcePatterns = (*p)->sourceFileNameFilter(); + foreach (const QString &pattern, sourcePatterns) { + QRegExp re(pattern, Qt::CaseInsensitive, QRegExp::Wildcard); + if (re.exactMatch(fileName)) + return *p; + } + ++p; + } + return 0; +} + +static QSet<QString> commonMetaCommands_; +/*! + Returns the set of strings representing the common metacommands. + */ +const QSet<QString>& CodeParser::commonMetaCommands() +{ + if (commonMetaCommands_.isEmpty()) { + commonMetaCommands_ << COMMAND_COMPAT + << COMMAND_DEPRECATED + << COMMAND_INGROUP + << COMMAND_INMODULE + << COMMAND_INQMLMODULE + << COMMAND_INTERNAL + << COMMAND_MAINCLASS + << COMMAND_NONREENTRANT + << COMMAND_OBSOLETE + << COMMAND_PAGEKEYWORDS + << COMMAND_PRELIMINARY + << COMMAND_INPUBLICGROUP + << COMMAND_QTVARIABLE + << COMMAND_REENTRANT + << COMMAND_SINCE + << COMMAND_SUBTITLE + << COMMAND_THREADSAFE + << COMMAND_TITLE + << COMMAND_WRAPPER + << COMMAND_INJSMODULE + << COMMAND_NOAUTOLIST; + } + return commonMetaCommands_; +} + +/*! + The topic command has been processed. Now process the other + metacommands that were found. These are not the text markup + commands. + */ +void CodeParser::processCommonMetaCommand(const Location& location, + const QString& command, + const ArgLocPair& arg, + Node* node) +{ + if (command == COMMAND_COMPAT) { + location.warning(tr("\\compat command used, but Qt3 compatibility is no longer supported")); + node->setStatus(Node::Compat); + } + else if (command == COMMAND_DEPRECATED) { + node->setStatus(Node::Obsolete); + } + else if ((command == COMMAND_INGROUP) || (command == COMMAND_INPUBLICGROUP)) { + // Note: \ingroup and \inpublicgroup are now the same. + // Not that they were ever different. + qdb_->addToGroup(arg.first, node); + } + else if (command == COMMAND_INMODULE) { + qdb_->addToModule(arg.first,node); + } + else if (command == COMMAND_INQMLMODULE) { + qdb_->addToQmlModule(arg.first,node); + } + else if (command == COMMAND_INJSMODULE) { + qdb_->addToJsModule(arg.first, node); + } + else if (command == COMMAND_MAINCLASS) { + node->doc().location().warning(tr("'\\mainclass' is deprecated. Consider '\\ingroup mainclasses'")); + } + else if (command == COMMAND_OBSOLETE) { + node->setStatus(Node::Obsolete); + } + else if (command == COMMAND_NONREENTRANT) { + node->setThreadSafeness(Node::NonReentrant); + } + else if (command == COMMAND_PRELIMINARY) { + node->setStatus(Node::Preliminary); + } + else if (command == COMMAND_INTERNAL) { + if (!showInternal_) { + node->setAccess(Node::Private); + node->setStatus(Node::Internal); + if (node->type() == Node::QmlPropertyGroup) { + const QmlPropertyGroupNode* qpgn = static_cast<const QmlPropertyGroupNode*>(node); + NodeList::ConstIterator p = qpgn->childNodes().constBegin(); + while (p != qpgn->childNodes().constEnd()) { + if ((*p)->type() == Node::QmlProperty) { + (*p)->setAccess(Node::Private); + (*p)->setStatus(Node::Internal); + } + ++p; + } + } + } + } + else if (command == COMMAND_REENTRANT) { + node->setThreadSafeness(Node::Reentrant); + } + else if (command == COMMAND_SINCE) { + node->setSince(arg.first); + } + else if (command == COMMAND_WRAPPER) { + node->setWrapper(); + } + else if (command == COMMAND_PAGEKEYWORDS) { + node->addPageKeywords(arg.first); + } + else if (command == COMMAND_THREADSAFE) { + node->setThreadSafeness(Node::ThreadSafe); + } + else if (command == COMMAND_TITLE) { + node->setTitle(arg.first); + if (!node->isDocumentNode() && !node->isCollectionNode()) + location.warning(tr("Ignored '\\%1'").arg(COMMAND_SUBTITLE)); + else if (node->isExample()) + qdb_->addExampleNode(static_cast<ExampleNode*>(node)); + } + else if (command == COMMAND_SUBTITLE) { + node->setSubTitle(arg.first); + if (!node->isDocumentNode() && !node->isCollectionNode()) + location.warning(tr("Ignored '\\%1'").arg(COMMAND_SUBTITLE)); + } + else if (command == COMMAND_QTVARIABLE) { + node->setQtVariable(arg.first); + if (!node->isModule() && !node->isQmlModule()) + location.warning(tr("Command '\\%1' is only meanigfule in '\\module' and '\\qmlmodule'.") + .arg(COMMAND_QTVARIABLE)); + } + else if (command == COMMAND_NOAUTOLIST) { + node->setNoAutoList(true); + } +} + +/*! + \internal + */ +void CodeParser::extractPageLinkAndDesc(const QString& arg, + QString* link, + QString* desc) +{ + QRegExp bracedRegExp(QLatin1String("\\{([^{}]*)\\}(?:\\{([^{}]*)\\})?")); + + if (bracedRegExp.exactMatch(arg)) { + *link = bracedRegExp.cap(1); + *desc = bracedRegExp.cap(2); + if (desc->isEmpty()) + *desc = *link; + } + else { + int spaceAt = arg.indexOf(QLatin1Char(' ')); + if (arg.contains(QLatin1String(".html")) && spaceAt != -1) { + *link = arg.leftRef(spaceAt).trimmed().toString(); + *desc = arg.midRef(spaceAt).trimmed().toString(); + } + else { + *link = arg; + *desc = arg; + } + } +} + +/*! + \internal + */ +void CodeParser::setLink(Node* node, Node::LinkType linkType, const QString& arg) +{ + QString link; + QString desc; + extractPageLinkAndDesc(arg, &link, &desc); + node->setLink(linkType, link, desc); +} + +/*! + Returns \c true if the file being parsed is a .h file. + */ +bool CodeParser::isParsingH() const +{ + return currentFile_.endsWith(".h"); +} + +/*! + Returns \c true if the file being parsed is a .cpp file. + */ +bool CodeParser::isParsingCpp() const +{ + return currentFile_.endsWith(".cpp"); +} + +/*! + Returns \c true if the file being parsed is a .qdoc file. + */ +bool CodeParser::isParsingQdoc() const +{ + return currentFile_.endsWith(".qdoc"); +} + +/*! + For each node that will produce a documentation page, this function + ensures that the node belongs to a module. Normally, the qdoc comment + for an entity that will produce a documentation page will contain an + \inmodule command to tell qdoc which module the entity belongs to. + + But now we normally run qdoc on each module in two passes. The first + produces an index file; the second pass generates the docs after + reading all the index files it needs. + + This means that all the pages generated during each pass 2 run of + qdoc almost certainly belong to a single module, and the name of + that module is, as a rule, used as the project name in the qdocconf + file used when running qdoc on the module. + + So this function first asks if the node \a n has a non-empty module + name. If it it does not have a non-empty module name, it sets the + module name to be the project name. + + In some cases it prints a qdoc warning that it has done this. Namely, + for C++ classes and namespaces. + */ +void CodeParser::checkModuleInclusion(Node* n) +{ + if (n->physicalModuleName().isEmpty()) { + n->setPhysicalModuleName(Generator::defaultModuleName()); + switch (n->type()) { + case Node::Class: + if (n->access() != Node::Private && !n->doc().isEmpty()) { + n->doc().location().warning(tr("Class %1 has no \\inmodule command; " + "using project name by default: %2") + .arg(n->name()).arg(Generator::defaultModuleName())); + } + break; + case Node::Namespace: + if (n->access() != Node::Private && !n->name().isEmpty() && !n->doc().isEmpty()) { + n->doc().location().warning(tr("Namespace %1 has no \\inmodule command; " + "using project name by default: %2") + .arg(n->name()).arg(Generator::defaultModuleName())); + } + break; + default: + break; + } + } +} + +QT_END_NAMESPACE diff --git a/src/qdoc/codeparser.h b/src/qdoc/codeparser.h new file mode 100644 index 000000000..9d9e9286e --- /dev/null +++ b/src/qdoc/codeparser.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CODEPARSER_H +#define CODEPARSER_H + +#include <qset.h> +#include "node.h" + +QT_BEGIN_NAMESPACE + +class Config; +class QString; +class QDocDatabase; + +class CodeParser +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::CodeParser) + +public: + CodeParser(); + virtual ~CodeParser(); + + virtual void initializeParser(const Config& config); + virtual void terminateParser(); + virtual QString language() = 0; + virtual QStringList headerFileNameFilter(); + virtual QStringList sourceFileNameFilter() = 0; + virtual void parseHeaderFile(const Location& location, const QString& filePath); + virtual void parseSourceFile(const Location& location, const QString& filePath) = 0; + virtual void doneParsingHeaderFiles(); + virtual void doneParsingSourceFiles() = 0; + + bool isParsingH() const; + bool isParsingCpp() const; + bool isParsingQdoc() const; + const QString& currentFile() const { return currentFile_; } + void checkModuleInclusion(Node* n); + + static void initialize(const Config& config); + static void terminate(); + static CodeParser *parserForLanguage(const QString& language); + static CodeParser *parserForHeaderFile(const QString &filePath); + static CodeParser *parserForSourceFile(const QString &filePath); + static void setLink(Node* node, Node::LinkType linkType, const QString& arg); + +protected: + const QSet<QString>& commonMetaCommands(); + void processCommonMetaCommand(const Location& location, + const QString& command, + const ArgLocPair& arg, + Node *node); + static void extractPageLinkAndDesc(const QString& arg, + QString* link, + QString* desc); + QString currentFile_; + QDocDatabase* qdb_; + +private: + static QList<CodeParser *> parsers; + static bool showInternal_; + static bool singleExec_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/config.cpp b/src/qdoc/config.cpp new file mode 100644 index 000000000..6c47b86d2 --- /dev/null +++ b/src/qdoc/config.cpp @@ -0,0 +1,1221 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + config.cpp +*/ + +#include <qdir.h> +#include <qvariant.h> +#include <qfile.h> +#include <qtemporaryfile.h> +#include <qtextstream.h> +#include <qdebug.h> +#include "config.h" +#include "generator.h" +#include <stdlib.h> + +QT_BEGIN_NAMESPACE + +QString ConfigStrings::ALIAS = QStringLiteral("alias"); +QString ConfigStrings::AUTOLINKERRORS = QStringLiteral("autolinkerrors"); +QString ConfigStrings::BASE = QStringLiteral("base"); +QString ConfigStrings::BASEDIR = QStringLiteral("basedir"); +QString ConfigStrings::BUILDVERSION = QStringLiteral("buildversion"); +QString ConfigStrings::CODEINDENT = QStringLiteral("codeindent"); +QString ConfigStrings::CODEPREFIX = QStringLiteral("codeprefix"); +QString ConfigStrings::CODESUFFIX = QStringLiteral("codesuffix"); +QString ConfigStrings::CPPCLASSESPAGE = QStringLiteral("cppclassespage"); +QString ConfigStrings::DEFINES = QStringLiteral("defines"); +QString ConfigStrings::DEPENDS = QStringLiteral("depends"); +QString ConfigStrings::DESCRIPTION = QStringLiteral("description"); +QString ConfigStrings::EDITION = QStringLiteral("edition"); +QString ConfigStrings::ENDHEADER = QStringLiteral("endheader"); +QString ConfigStrings::EXAMPLEDIRS = QStringLiteral("exampledirs"); +QString ConfigStrings::EXAMPLES = QStringLiteral("examples"); +QString ConfigStrings::EXAMPLESINSTALLPATH = QStringLiteral("examplesinstallpath"); +QString ConfigStrings::EXCLUDEDIRS = QStringLiteral("excludedirs"); +QString ConfigStrings::EXCLUDEFILES = QStringLiteral("excludefiles"); +QString ConfigStrings::EXTRAIMAGES = QStringLiteral("extraimages"); +QString ConfigStrings::FALSEHOODS = QStringLiteral("falsehoods"); +QString ConfigStrings::FORMATTING = QStringLiteral("formatting"); +QString ConfigStrings::GENERATEINDEX = QStringLiteral("generateindex"); +QString ConfigStrings::HEADERDIRS = QStringLiteral("headerdirs"); +QString ConfigStrings::HEADERS = QStringLiteral("headers"); +QString ConfigStrings::HEADERSCRIPTS = QStringLiteral("headerscripts"); +QString ConfigStrings::HEADERSTYLES = QStringLiteral("headerstyles"); +QString ConfigStrings::HOMEPAGE = QStringLiteral("homepage"); +QString ConfigStrings::IGNOREDIRECTIVES = QStringLiteral("ignoredirectives"); +QString ConfigStrings::IGNORETOKENS = QStringLiteral("ignoretokens"); +QString ConfigStrings::IMAGEDIRS = QStringLiteral("imagedirs"); +QString ConfigStrings::IMAGES = QStringLiteral("images"); +QString ConfigStrings::INDEXES = QStringLiteral("indexes"); +QString ConfigStrings::LANDINGPAGE = QStringLiteral("landingpage"); +QString ConfigStrings::LANGUAGE = QStringLiteral("language"); +QString ConfigStrings::MACRO = QStringLiteral("macro"); +QString ConfigStrings::MANIFESTMETA = QStringLiteral("manifestmeta"); +QString ConfigStrings::NATURALLANGUAGE = QStringLiteral("naturallanguage"); +QString ConfigStrings::NAVIGATION = QStringLiteral("navigation"); +QString ConfigStrings::NOLINKERRORS = QStringLiteral("nolinkerrors"); +QString ConfigStrings::OBSOLETELINKS = QStringLiteral("obsoletelinks"); +QString ConfigStrings::OUTPUTDIR = QStringLiteral("outputdir"); +QString ConfigStrings::OUTPUTENCODING = QStringLiteral("outputencoding"); +QString ConfigStrings::OUTPUTLANGUAGE = QStringLiteral("outputlanguage"); +QString ConfigStrings::OUTPUTFORMATS = QStringLiteral("outputformats"); +QString ConfigStrings::OUTPUTPREFIXES = QStringLiteral("outputprefixes"); +QString ConfigStrings::OUTPUTSUFFIXES = QStringLiteral("outputsuffixes"); +QString ConfigStrings::PROJECT = QStringLiteral("project"); +QString ConfigStrings::REDIRECTDOCUMENTATIONTODEVNULL = QStringLiteral("redirectdocumentationtodevnull"); +QString ConfigStrings::QHP = QStringLiteral("qhp"); +QString ConfigStrings::QUOTINGINFORMATION = QStringLiteral("quotinginformation"); +QString ConfigStrings::SCRIPTDIRS = QStringLiteral("scriptdirs"); +QString ConfigStrings::SCRIPTS = QStringLiteral("scripts"); +QString ConfigStrings::SHOWINTERNAL = QStringLiteral("showinternal"); +QString ConfigStrings::SINGLEEXEC = QStringLiteral("singleexec"); +QString ConfigStrings::SOURCEDIRS = QStringLiteral("sourcedirs"); +QString ConfigStrings::SOURCEENCODING = QStringLiteral("sourceencoding"); +QString ConfigStrings::SOURCES = QStringLiteral("sources"); +QString ConfigStrings::SPURIOUS = QStringLiteral("spurious"); +QString ConfigStrings::STYLEDIRS = QStringLiteral("styledirs"); +QString ConfigStrings::STYLE = QStringLiteral("style"); +QString ConfigStrings::STYLES = QStringLiteral("styles"); +QString ConfigStrings::STYLESHEETS = QStringLiteral("stylesheets"); +QString ConfigStrings::SYNTAXHIGHLIGHTING = QStringLiteral("syntaxhighlighting"); +QString ConfigStrings::TEMPLATEDIR = QStringLiteral("templatedir"); +QString ConfigStrings::TABSIZE = QStringLiteral("tabsize"); +QString ConfigStrings::TAGFILE = QStringLiteral("tagfile"); +QString ConfigStrings::TRANSLATORS = QStringLiteral("translators"); +QString ConfigStrings::URL = QStringLiteral("url"); +QString ConfigStrings::VERSION = QStringLiteral("version"); +QString ConfigStrings::VERSIONSYM = QStringLiteral("versionsym"); +QString ConfigStrings::FILEEXTENSIONS = QStringLiteral("fileextensions"); +QString ConfigStrings::IMAGEEXTENSIONS = QStringLiteral("imageextensions"); +QString ConfigStrings::QMLONLY = QStringLiteral("qmlonly"); +QString ConfigStrings::QMLTYPESPAGE = QStringLiteral("qmltypespage"); +QString ConfigStrings::WRITEQAPAGES = QStringLiteral("writeqapages"); + +/*! + An entry in a stack, where each entry is a list + of string values. + */ +class MetaStackEntry +{ +public: + void open(); + void close(); + + QStringList accum; + QStringList next; +}; +Q_DECLARE_TYPEINFO(MetaStackEntry, Q_MOVABLE_TYPE); + +/*! + Start accumulating values in a list by appending an empty + string to the list. + */ +void MetaStackEntry::open() +{ + next.append(QString()); +} + +/*! + Stop accumulating values and append the list of accumulated + values to the complete list of accumulated values. + + */ +void MetaStackEntry::close() +{ + accum += next; + next.clear(); +} + +/*! + \class MetaStack + + This class maintains a stack of values of config file variables. +*/ +class MetaStack : private QStack<MetaStackEntry> +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::MetaStack) + +public: + MetaStack(); + + void process(QChar ch, const Location& location); + QStringList getExpanded(const Location& location); +}; + +/*! + The default constructor pushes a new stack entry and + opens it. + */ +MetaStack::MetaStack() +{ + push(MetaStackEntry()); + top().open(); +} + +/*! + Processes the character \a ch using the \a location. + It really just builds up a name by appending \a ch to + it. + */ +void MetaStack::process(QChar ch, const Location& location) +{ + if (ch == QLatin1Char('{')) { + push(MetaStackEntry()); + top().open(); + } else if (ch == QLatin1Char('}')) { + if (count() == 1) + location.fatal(tr("Unexpected '}'")); + + top().close(); + QStringList suffixes = pop().accum; + QStringList prefixes = top().next; + + top().next.clear(); + QStringList::ConstIterator pre = prefixes.constBegin(); + while (pre != prefixes.constEnd()) { + QStringList::ConstIterator suf = suffixes.constBegin(); + while (suf != suffixes.constEnd()) { + top().next << (*pre + *suf); + ++suf; + } + ++pre; + } + } else if (ch == QLatin1Char(',') && count() > 1) { + top().close(); + top().open(); + } else { + /* + This is where all the processing is done. + */ + QStringList::Iterator pre = top().next.begin(); + while (pre != top().next.end()) { + *pre += ch; + ++pre; + } + } +} + +/*! + Returns the accumulated string values. + */ +QStringList MetaStack::getExpanded(const Location& location) +{ + if (count() > 1) + location.fatal(tr("Missing '}'")); + + top().close(); + return top().accum; +} + +const QString Config::dot = QLatin1String("."); +bool Config::debug_ = false; +bool Config::generateExamples = true; +QString Config::overrideOutputDir; +QString Config::installDir; +QSet<QString> Config::overrideOutputFormats; +QMap<QString, QString> Config::extractedDirs; +int Config::numInstances; +QStack<QString> Config::workingDirs_; +QMap<QString, QStringList> Config::includeFilesMap_; + +/*! + \class Config + \brief The Config class contains the configuration variables + for controlling how qdoc produces documentation. + + Its load() function, reads, parses, and processes a qdocconf file. + */ + +/*! + The constructor sets the \a programName and initializes all + internal state variables to empty values. + */ +Config::Config(const QString& programName) + : prog(programName) +{ + loc = Location::null; + lastLocation_ = Location::null; + configVars_.clear(); + numInstances++; + includeFilesMap_.clear(); +} + +/*! + The destructor has nothing special to do. + */ +Config::~Config() +{ + includeFilesMap_.clear(); +} + +/*! + Loads and parses the qdoc configuration file \a fileName. + This function calls the other load() function, which does + the loading, parsing, and processing of the configuration + file. + + Intializes the location variables returned by location() + and lastLocation(). + */ +void Config::load(const QString& fileName) +{ + load(Location::null, fileName); + if (loc.isEmpty()) + loc = Location(fileName); + else + loc.setEtc(true); + lastLocation_ = Location::null; +} + +/*! + Joins all the strings in \a values into a single string with the + individual \a values separated by ' '. Then it inserts the result + into the string list map with \a var as the key. + + It also inserts the \a values string list into a separate map, + also with \a var as the key. + */ +void Config::setStringList(const QString& var, const QStringList& values) +{ + configVars_.insert(var,ConfigVar(var, values, QDir::currentPath())); +} + +/*! + Looks up the configuarion variable \a var in the string + map and returns the boolean value. + */ +bool Config::getBool(const QString& var) const +{ + return QVariant(getString(var)).toBool(); +} + +/*! + Looks up the configuration variable \a var in the string list + map. Iterates through the string list found, interpreting each + string in the list as an integer and adding it to a total sum. + Returns the sum or \c -1 if \a var is not set. + */ +int Config::getInt(const QString& var) const +{ + QStringList strs = getStringList(var); + if (strs.isEmpty()) + return -1; + + QStringList::ConstIterator s = strs.constBegin(); + int sum = 0; + + while (s != strs.constEnd()) { + sum += (*s).toInt(); + ++s; + } + return sum; +} + +/*! + Function to return the correct outputdir. + outputdir can be set using the qdocconf or the command-line + variable -outputdir. + */ +QString Config::getOutputDir() const +{ + QString t; + if (overrideOutputDir.isNull()) + t = getString(CONFIG_OUTPUTDIR); + else + t = overrideOutputDir; + if (Generator::singleExec()) { + QString project = getString(CONFIG_PROJECT); + t += QLatin1Char('/') + project.toLower(); + } + if (!Generator::useOutputSubdirs()) { + t = t.left(t.lastIndexOf('/')); + QString singleOutputSubdir = getString("HTML.outputsubdir"); + if (singleOutputSubdir.isEmpty()) + singleOutputSubdir = "html"; + t += QLatin1Char('/') + singleOutputSubdir; + } + return t; +} + +/*! + Function to return the correct outputformats. + outputformats can be set using the qdocconf or the command-line + variable -outputformat. + */ +QSet<QString> Config::getOutputFormats() const +{ + if (overrideOutputFormats.isEmpty()) + return getStringSet(CONFIG_OUTPUTFORMATS); + else + return overrideOutputFormats; +} + +/*! + First, this function looks up the configuration variable \a var + in the location map and, if found, sets the internal variable + \c{lastLocation_} to the Location that \a var maps to. + + Then it looks up the configuration variable \a var in the string + map and returns the string that \a var maps to. + */ +QString Config::getString(const QString& var) const +{ + QList<ConfigVar> configVars = configVars_.values(var); + QString value; + if (!configVars.empty()) { + int i = configVars.size() - 1; + while (i >= 0) { + const ConfigVar& cv = configVars[i]; + if (!cv.location_.isEmpty()) + const_cast<Config *>(this)->lastLocation_ = cv.location_; + if (!cv.values_.isEmpty()) { + if (!cv.plus_) + value.clear(); + for (int j=0; j<cv.values_.size(); ++j) { + if (!value.isEmpty() && !value.endsWith(QChar('\n'))) + value.append(QChar(' ')); + value.append(cv.values_[j]); + } + } + --i; + } + } + return value; +} + +/*! + Looks up the configuration variable \a var in the string + list map, converts the string list it maps to into a set + of strings, and returns the set. + */ +QSet<QString> Config::getStringSet(const QString& var) const +{ + return QSet<QString>::fromList(getStringList(var)); +} + +/*! + First, this function looks up the configuration variable \a var + in the location map. If found, it sets the internal variable + \c{lastLocation_} to the Location that \a var maps to. + + Then it looks up the configuration variable \a var in the map of + configuration variable records. If found, it gets a list of all + the records for \a var. Then it appends all the values for \a var + to a list and returns the list. As it appends the values from each + record, if the \a var used '=' instead of '+=' the list is cleared + before the values are appended. \note '+=' should always be used. + The final list is returned. + */ +QStringList Config::getStringList(const QString& var) const +{ + QList<ConfigVar> configVars = configVars_.values(var); + QStringList values; + if (!configVars.empty()) { + int i = configVars.size() - 1; + while (i >= 0) { + if (!configVars[i].location_.isEmpty()) + const_cast<Config *>(this)->lastLocation_ = configVars[i].location_; + if (configVars[i].plus_) + values.append(configVars[i].values_); + else + values = configVars[i].values_; + --i; + } + } + return values; +} + +/*! + Returns the a path list where all paths from the config variable \a var + are canonicalized. If \a validate is true, a warning for invalid paths is + generated. + + First, this function looks up the configuration variable \a var + in the location map and, if found, sets the internal variable + \c{lastLocation_} the Location that \a var maps to. + + Then it looks up the configuration variable \a var in the string + list map, which maps to one or more records that each contains a + list of file paths. + + \sa Location::canonicalRelativePath() + */ +QStringList Config::getCanonicalPathList(const QString& var, bool validate) const +{ + QStringList t; + QList<ConfigVar> configVars = configVars_.values(var); + if (!configVars.empty()) { + int i = configVars.size() - 1; + while (i >= 0) { + const ConfigVar& cv = configVars[i]; + if (!cv.location_.isEmpty()) + const_cast<Config *>(this)->lastLocation_ = cv.location_; + if (!cv.plus_) + t.clear(); + const QString d = cv.currentPath_; + const QStringList& sl = cv.values_; + if (!sl.isEmpty()) { + t.reserve(t.size() + sl.size()); + for (int i=0; i<sl.size(); ++i) { + QDir dir(sl[i].simplified()); + QString path = dir.path(); + if (dir.isRelative()) + dir.setPath(d + QLatin1Char('/') + path); + if (validate && !QFileInfo::exists(dir.path())) + lastLocation_.warning(tr("Cannot find file or directory: %1").arg(path)); + else + t.append(dir.canonicalPath()); + } + } + --i; + } + } + return t; +} + +/*! + Calls getRegExpList() with the control variable \a var and + iterates through the resulting list of regular expressions, + concatening them with some extras characters to form a single + QRegExp, which is returned/ + + \sa getRegExpList() + */ +QRegExp Config::getRegExp(const QString& var) const +{ + QString pattern; + QList<QRegExp> subRegExps = getRegExpList(var); + QList<QRegExp>::ConstIterator s = subRegExps.constBegin(); + + while (s != subRegExps.constEnd()) { + if (!(*s).isValid()) + return *s; + if (!pattern.isEmpty()) + pattern += QLatin1Char('|'); + pattern += QLatin1String("(?:") + (*s).pattern() + QLatin1Char(')'); + ++s; + } + if (pattern.isEmpty()) + pattern = QLatin1String("$x"); // cannot match + return QRegExp(pattern); +} + +/*! + Looks up the configuration variable \a var in the string list + map, converts the string list to a list of regular expressions, + and returns it. + */ +QList<QRegExp> Config::getRegExpList(const QString& var) const +{ + QStringList strs = getStringList(var); + QStringList::ConstIterator s = strs.constBegin(); + QList<QRegExp> regExps; + + while (s != strs.constEnd()) { + regExps += QRegExp(*s); + ++s; + } + return regExps; +} + +/*! + This function is slower than it could be. What it does is + find all the keys that begin with \a var + dot and return + the matching keys in a set, stripped of the matching prefix + and dot. + */ +QSet<QString> Config::subVars(const QString& var) const +{ + QSet<QString> result; + QString varDot = var + QLatin1Char('.'); + ConfigVarMultimap::ConstIterator i = configVars_.constBegin(); + while (i != configVars_.constEnd()) { + if (i.key().startsWith(varDot)) { + QString subVar = i.key().mid(varDot.length()); + int dot = subVar.indexOf(QLatin1Char('.')); + if (dot != -1) + subVar.truncate(dot); + if (!result.contains(subVar)) + result.insert(subVar); + } + ++i; + } + return result; +} + +/*! + Same as subVars(), but in this case we return a config var + multimap with the matching keys (stripped of the prefix \a var + and mapped to their values. The pairs are inserted into \a t + */ +void Config::subVarsAndValues(const QString& var, ConfigVarMultimap& t) const +{ + QString varDot = var + QLatin1Char('.'); + ConfigVarMultimap::ConstIterator v = configVars_.constBegin(); + while (v != configVars_.constEnd()) { + if (v.key().startsWith(varDot)) { + QString subVar = v.key().mid(varDot.length()); + int dot = subVar.indexOf(QLatin1Char('.')); + if (dot != -1) + subVar.truncate(dot); + t.insert(subVar,v.value()); + } + ++v; + } +} + +/*! + Get all .qdocinc files. + */ +QString Config::getIncludeFilePath(const QString& fileName) const +{ + QString ext = fileName.mid(fileName.lastIndexOf('.')); + ext.prepend('*'); + + if (!includeFilesMap_.contains(ext)) { + QSet<QString> t; + QStringList result; + QStringList dirs = getCanonicalPathList(CONFIG_SOURCEDIRS); + QStringList::ConstIterator d = dirs.constBegin(); + while (d != dirs.constEnd()) { + result += getFilesHere(*d, ext, location(), t, t); + ++d; + } + includeFilesMap_.insert(ext, result); + } + const QStringList& paths = (*includeFilesMap_.find(ext)); + for (int i=0; i<paths.size(); ++i) { + if (paths[i].endsWith(fileName)) + return paths[i]; + } + return QString(); +} + +/*! + Builds and returns a list of file pathnames for the file + type specified by \a filesVar (e.g. "headers" or "sources"). + The files are found in the directories specified by + \a dirsVar, and they are filtered by \a defaultNameFilter + if a better filter can't be constructed from \a filesVar. + The directories in \a excludedDirs are avoided. The files + in \a excludedFiles are not included in the return list. + */ +QStringList Config::getAllFiles(const QString &filesVar, + const QString &dirsVar, + const QSet<QString> &excludedDirs, + const QSet<QString> &excludedFiles) +{ + QStringList result = getCanonicalPathList(filesVar); + QStringList dirs = getCanonicalPathList(dirsVar); + + QString nameFilter = getString(filesVar + dot + CONFIG_FILEEXTENSIONS); + + QStringList::ConstIterator d = dirs.constBegin(); + while (d != dirs.constEnd()) { + result += getFilesHere(*d, nameFilter, location(), excludedDirs, excludedFiles); + ++d; + } + return result; +} + +QStringList Config::getExampleQdocFiles(const QSet<QString> &excludedDirs, + const QSet<QString> &excludedFiles) +{ + QStringList result; + QStringList dirs = getCanonicalPathList("exampledirs"); + QString nameFilter = " *.qdoc"; + + QStringList::ConstIterator d = dirs.constBegin(); + while (d != dirs.constEnd()) { + result += getFilesHere(*d, nameFilter, location(), excludedDirs, excludedFiles); + ++d; + } + return result; +} + +QStringList Config::getExampleImageFiles(const QSet<QString> &excludedDirs, + const QSet<QString> &excludedFiles) +{ + QStringList result; + QStringList dirs = getCanonicalPathList("exampledirs"); + QString nameFilter = getString(CONFIG_EXAMPLES + dot + CONFIG_IMAGEEXTENSIONS); + + QStringList::ConstIterator d = dirs.constBegin(); + while (d != dirs.constEnd()) { + result += getFilesHere(*d, nameFilter, location(), excludedDirs, excludedFiles); + ++d; + } + return result; +} + +/*! + \a fileName is the path of the file to find. + + \a files and \a dirs are the lists where we must find the + components of \a fileName. + + \a location is used for obtaining the file and line numbers + for report qdoc errors. + */ +QString Config::findFile(const Location& location, + const QStringList& files, + const QStringList& dirs, + const QString& fileName, + QString& userFriendlyFilePath) +{ + if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) { + userFriendlyFilePath = fileName; + return fileName; + } + + QFileInfo fileInfo; + QStringList components = fileName.split(QLatin1Char('?')); + QString firstComponent = components.first(); + + QStringList::ConstIterator f = files.constBegin(); + while (f != files.constEnd()) { + if (*f == firstComponent || + (*f).endsWith(QLatin1Char('/') + firstComponent)) { + fileInfo.setFile(*f); + if (!fileInfo.exists()) + location.fatal(tr("File '%1' does not exist").arg(*f)); + break; + } + ++f; + } + + if (fileInfo.fileName().isEmpty()) { + QStringList::ConstIterator d = dirs.constBegin(); + while (d != dirs.constEnd()) { + fileInfo.setFile(QDir(*d), firstComponent); + if (fileInfo.exists()) + break; + ++d; + } + } + + userFriendlyFilePath = QString(); + if (!fileInfo.exists()) + return QString(); + + QStringList::ConstIterator c = components.constBegin(); + for (;;) { + bool isArchive = (c != components.constEnd() - 1); + QString userFriendly = *c; + + userFriendlyFilePath += userFriendly; + + if (isArchive) { + QString extracted = extractedDirs[fileInfo.filePath()]; + ++c; + fileInfo.setFile(QDir(extracted), *c); + } else { + break; + } + + userFriendlyFilePath += QLatin1Char('?'); + } + return fileInfo.filePath(); +} + +/*! + */ +QString Config::findFile(const Location& location, + const QStringList& files, + const QStringList& dirs, + const QString& fileBase, + const QStringList& fileExtensions, + QString& userFriendlyFilePath) +{ + QStringList::ConstIterator e = fileExtensions.constBegin(); + while (e != fileExtensions.constEnd()) { + QString filePath = findFile(location, + files, + dirs, + fileBase + QLatin1Char('.') + *e, + userFriendlyFilePath); + if (!filePath.isEmpty()) + return filePath; + ++e; + } + return findFile(location, files, dirs, fileBase, userFriendlyFilePath); +} + +/*! + Copies the \a sourceFilePath to the file name constructed by + concatenating \a targetDirPath and the file name from the + \a userFriendlySourceFilePath. \a location is for identifying + the file and line number where a qdoc error occurred. The + constructed output file name is returned. + */ +QString Config::copyFile(const Location& location, + const QString& sourceFilePath, + const QString& userFriendlySourceFilePath, + const QString& targetDirPath) +{ + QFile inFile(sourceFilePath); + if (!inFile.open(QFile::ReadOnly)) { + location.warning(tr("Cannot open input file for copy: '%1': %2") + .arg(sourceFilePath).arg(inFile.errorString())); + return QString(); + } + + QString outFileName = userFriendlySourceFilePath; + int slash = outFileName.lastIndexOf(QLatin1Char('/')); + if (slash != -1) + outFileName = outFileName.mid(slash); + if ((outFileName.size()) > 0 && (outFileName[0] != '/')) + outFileName = targetDirPath + QLatin1Char('/') + outFileName; + else + outFileName = targetDirPath + outFileName; + QFile outFile(outFileName); + if (!outFile.open(QFile::WriteOnly)) { + location.warning(tr("Cannot open output file for copy: '%1': %2") + .arg(outFileName).arg(outFile.errorString())); + return QString(); + } + + char buffer[1024]; + int len; + while ((len = inFile.read(buffer, sizeof(buffer))) > 0) + outFile.write(buffer, len); + return outFileName; +} + +/*! + Finds the largest unicode digit in \a value in the range + 1..7 and returns it. + */ +int Config::numParams(const QString& value) +{ + int max = 0; + for (int i = 0; i != value.length(); i++) { + uint c = value[i].unicode(); + if (c > 0 && c < 8) + max = qMax(max, (int)c); + } + return max; +} + +/*! + Removes everything from \a dir. This function is recursive. + It doesn't remove \a dir itself, but if it was called + recursively, then the caller will remove \a dir. + */ +bool Config::removeDirContents(const QString& dir) +{ + QDir dirInfo(dir); + QFileInfoList entries = dirInfo.entryInfoList(); + + bool ok = true; + + QFileInfoList::Iterator it = entries.begin(); + while (it != entries.end()) { + if ((*it).isFile()) { + if (!dirInfo.remove((*it).fileName())) + ok = false; + } + else if ((*it).isDir()) { + if ((*it).fileName() != QLatin1String(".") && (*it).fileName() != QLatin1String("..")) { + if (removeDirContents((*it).absoluteFilePath())) { + if (!dirInfo.rmdir((*it).fileName())) + ok = false; + } + else { + ok = false; + } + } + } + ++it; + } + return ok; +} + +/*! + Returns \c true if \a ch is a letter, number, '_', '.', + '{', '}', or ','. + */ +bool Config::isMetaKeyChar(QChar ch) +{ + return ch.isLetterOrNumber() + || ch == QLatin1Char('_') + || ch == QLatin1Char('.') + || ch == QLatin1Char('{') + || ch == QLatin1Char('}') + || ch == QLatin1Char(','); +} + +/*! + \a fileName is a master qdocconf file. It contains a list of + qdocconf files and nothing else. Read the list and return it. + */ +QStringList Config::loadMaster(const QString& fileName) +{ + Location location = Location::null; + QFile fin(fileName); + if (!fin.open(QFile::ReadOnly | QFile::Text)) { + if (!Config::installDir.isEmpty()) { + int prefix = location.filePath().length() - location.fileName().length(); + fin.setFileName(Config::installDir + QLatin1Char('/') + fileName.right(fileName.length() - prefix)); + } + if (!fin.open(QFile::ReadOnly | QFile::Text)) + location.fatal(tr("Cannot open master qdocconf file '%1': %2").arg(fileName).arg(fin.errorString())); + } + QTextStream stream(&fin); +#ifndef QT_NO_TEXTCODEC + stream.setCodec("UTF-8"); +#endif + QStringList qdocFiles; + QString line = stream.readLine(); + while (!line.isNull()) { + qdocFiles.append(line); + line = stream.readLine(); + } + fin.close(); + return qdocFiles; +} + +/*! + Load, parse, and process a qdoc configuration file. This + function is only called by the other load() function, but + this one is recursive, i.e., it calls itself when it sees + an \c{include} statement in the qdoc configuration file. + */ +void Config::load(Location location, const QString& fileName) +{ + QFileInfo fileInfo(fileName); + QString path = fileInfo.canonicalPath(); + pushWorkingDir(path); + QDir::setCurrent(path); + QRegExp keySyntax(QLatin1String("\\w+(?:\\.\\w+)*")); + +#define SKIP_CHAR() \ + do { \ + location.advance(c); \ + ++i; \ + c = text.at(i); \ + cc = c.unicode(); \ +} while (0) + +#define SKIP_SPACES() \ + while (c.isSpace() && cc != '\n') \ + SKIP_CHAR() + +#define PUT_CHAR() \ + word += c; \ + SKIP_CHAR(); + + if (location.depth() > 16) + location.fatal(tr("Too many nested includes")); + + QFile fin(fileInfo.fileName()); + if (!fin.open(QFile::ReadOnly | QFile::Text)) { + if (!Config::installDir.isEmpty()) { + int prefix = location.filePath().length() - location.fileName().length(); + fin.setFileName(Config::installDir + QLatin1Char('/') + fileName.right(fileName.length() - prefix)); + } + if (!fin.open(QFile::ReadOnly | QFile::Text)) + location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString())); + } + + QTextStream stream(&fin); +#ifndef QT_NO_TEXTCODEC + stream.setCodec("UTF-8"); +#endif + QString text = stream.readAll(); + text += QLatin1String("\n\n"); + text += QLatin1Char('\0'); + fin.close(); + + location.push(fileName); + location.start(); + + int i = 0; + QChar c = text.at(0); + uint cc = c.unicode(); + while (i < (int) text.length()) { + if (cc == 0) { + ++i; + } else if (c.isSpace()) { + SKIP_CHAR(); + } else if (cc == '#') { + do { + SKIP_CHAR(); + } while (cc != '\n'); + } else if (isMetaKeyChar(c)) { + Location keyLoc = location; + bool plus = false; + QString stringValue; + QStringList rhsValues; + QString word; + bool inQuote = false; + bool prevWordQuoted = true; + bool metWord = false; + + MetaStack stack; + do { + stack.process(c, location); + SKIP_CHAR(); + } while (isMetaKeyChar(c)); + + QStringList keys = stack.getExpanded(location); + SKIP_SPACES(); + + if (keys.count() == 1 && keys.first() == QLatin1String("include")) { + QString includeFile; + + if (cc != '(') + location.fatal(tr("Bad include syntax")); + SKIP_CHAR(); + SKIP_SPACES(); + + while (!c.isSpace() && cc != '#' && cc != ')') { + + if (cc == '$') { + QString var; + SKIP_CHAR(); + while (c.isLetterOrNumber() || cc == '_') { + var += c; + SKIP_CHAR(); + } + if (!var.isEmpty()) { + const QByteArray val = qgetenv(var.toLatin1().data()); + if (val.isNull()) { + location.fatal(tr("Environment variable '%1' undefined").arg(var)); + } + else { + includeFile += QString::fromLatin1(val); + } + } + } else { + includeFile += c; + SKIP_CHAR(); + } + } + SKIP_SPACES(); + if (cc != ')') + location.fatal(tr("Bad include syntax")); + SKIP_CHAR(); + SKIP_SPACES(); + if (cc != '#' && cc != '\n') + location.fatal(tr("Trailing garbage")); + + /* + Here is the recursive call. + */ + load(location, QFileInfo(QDir(path), includeFile).filePath()); + } + else { + /* + It wasn't an include statement, so it's something else. + We must see either '=' or '+=' next. If not, fatal error. + */ + if (cc == '+') { + plus = true; + SKIP_CHAR(); + } + if (cc != '=') + location.fatal(tr("Expected '=' or '+=' after key")); + SKIP_CHAR(); + SKIP_SPACES(); + + for (;;) { + if (cc == '\\') { + int metaCharPos; + + SKIP_CHAR(); + if (cc == '\n') { + SKIP_CHAR(); + } + else if (cc > '0' && cc < '8') { + word += QChar(c.digitValue()); + SKIP_CHAR(); + } + else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) { + word += QLatin1Char("\a\b\f\n\r\t\v"[metaCharPos]); + SKIP_CHAR(); + } + else { + PUT_CHAR(); + } + } + else if (c.isSpace() || cc == '#') { + if (inQuote) { + if (cc == '\n') + location.fatal(tr("Unterminated string")); + PUT_CHAR(); + } + else { + if (!word.isEmpty()) { + if (metWord) + stringValue += QLatin1Char(' '); + stringValue += word; +#if 0 + if (metWord) + rhsValues << QString(" " + word); + else +#endif + rhsValues << word; + metWord = true; + word.clear(); + prevWordQuoted = false; + } + if (cc == '\n' || cc == '#') + break; + SKIP_SPACES(); + } + } + else if (cc == '"') { + if (inQuote) { + if (!prevWordQuoted) + stringValue += QLatin1Char(' '); + stringValue += word; + if (!word.isEmpty()) + rhsValues << word; + metWord = true; + word.clear(); + prevWordQuoted = true; + } + inQuote = !inQuote; + SKIP_CHAR(); + } + else if (cc == '$') { + QString var; + SKIP_CHAR(); + while (c.isLetterOrNumber() || cc == '_') { + var += c; + SKIP_CHAR(); + } + if (!var.isEmpty()) { + const QByteArray val = qgetenv(var.toLatin1().constData()); + if (val.isNull()) { + location.fatal(tr("Environment variable '%1' undefined").arg(var)); + } + else { + word += QString::fromLatin1(val); + } + } + } + else { + if (!inQuote && cc == '=') + location.fatal(tr("Unexpected '='")); + PUT_CHAR(); + } + } + + QStringList::ConstIterator key = keys.constBegin(); + while (key != keys.constEnd()) { + if (!keySyntax.exactMatch(*key)) + keyLoc.fatal(tr("Invalid key '%1'").arg(*key)); + + ConfigVarMultimap::Iterator i; + i = configVars_.insert(*key, ConfigVar(*key, rhsValues, QDir::currentPath(), keyLoc)); + i.value().plus_ = plus; + ++key; + } + } + } else { + location.fatal(tr("Unexpected character '%1' at beginning of line").arg(c)); + } + } + popWorkingDir(); + if (!workingDirs_.isEmpty()) + QDir::setCurrent(workingDirs_.top()); +} + +QStringList Config::getFilesHere(const QString& uncleanDir, + const QString& nameFilter, + const Location &location, + const QSet<QString> &excludedDirs, + const QSet<QString> &excludedFiles) +{ + QString dir = location.isEmpty() ? QDir::cleanPath(uncleanDir) : QDir(uncleanDir).canonicalPath(); + QStringList result; + if (excludedDirs.contains(dir)) + return result; + + QDir dirInfo(dir); + QStringList fileNames; + QStringList::const_iterator fn; + + dirInfo.setNameFilters(nameFilter.split(QLatin1Char(' '))); + dirInfo.setSorting(QDir::Name); + dirInfo.setFilter(QDir::Files); + fileNames = dirInfo.entryList(); + fn = fileNames.constBegin(); + while (fn != fileNames.constEnd()) { + if (!fn->startsWith(QLatin1Char('~'))) { + QString s = dirInfo.filePath(*fn); + QString c = QDir::cleanPath(s); + if (!excludedFiles.contains(c)) + result.append(c); + } + ++fn; + } + + dirInfo.setNameFilters(QStringList(QLatin1String("*"))); + dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot); + fileNames = dirInfo.entryList(); + fn = fileNames.constBegin(); + while (fn != fileNames.constEnd()) { + result += getFilesHere(dirInfo.filePath(*fn), nameFilter, location, excludedDirs, excludedFiles); + ++fn; + } + return result; +} + +/*! + Push \a dir onto the stack of working directories. + */ +void Config::pushWorkingDir(const QString& dir) +{ + workingDirs_.push(dir); +} + +/*! + If the stack of working directories is not empty, pop the + top entry and return it. Otherwise return an empty string. + */ +QString Config::popWorkingDir() +{ + if (!workingDirs_.isEmpty()) + return workingDirs_.pop(); + + qDebug() << "RETURNED EMPTY WORKING DIR"; + return QString(); +} + +QT_END_NAMESPACE diff --git a/src/qdoc/config.h b/src/qdoc/config.h new file mode 100644 index 000000000..740568ca0 --- /dev/null +++ b/src/qdoc/config.h @@ -0,0 +1,319 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + config.h +*/ + +#ifndef CONFIG_H +#define CONFIG_H + +#include <qmap.h> +#include <qset.h> +#include <qstringlist.h> +#include <qstack.h> +#include <qpair.h> +#include "location.h" + +QT_BEGIN_NAMESPACE + +/* + This struct contains all the information for + one config variable found in a qdocconf file. + */ +struct ConfigVar { + bool plus_; + QString name_; + QStringList values_; + QString currentPath_; + Location location_; + + ConfigVar() : plus_(false) { } + + ConfigVar(const QString& name, const QStringList& values, const QString& dir) + : plus_(true), name_(name), values_(values), currentPath_(dir) { } + + ConfigVar(const QString& name, const QStringList& values, const QString& dir, const Location& loc) + : plus_(false), name_(name), values_(values), currentPath_(dir), location_(loc) { } +}; + +/* + In this multimap, the key is a config variable name. + */ +typedef QMultiMap<QString, ConfigVar> ConfigVarMultimap; + +class Config +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::Config) + +public: + Config(const QString& programName); + ~Config(); + + static bool debug_; + + void load(const QString& fileName); + void setStringList(const QString& var, const QStringList& values); + + const QString& programName() const { return prog; } + const Location& location() const { return loc; } + const Location& lastLocation() const { return lastLocation_; } + bool getBool(const QString& var) const; + int getInt(const QString& var) const; + QString getOutputDir() const; + QSet<QString> getOutputFormats() const; + QString getString(const QString& var) const; + QSet<QString> getStringSet(const QString& var) const; + QStringList getStringList(const QString& var) const; + QStringList getCanonicalPathList(const QString& var, bool validate = false) const; + QRegExp getRegExp(const QString& var) const; + QList<QRegExp> getRegExpList(const QString& var) const; + QSet<QString> subVars(const QString& var) const; + void subVarsAndValues(const QString& var, ConfigVarMultimap& t) const; + QStringList getAllFiles(const QString& filesVar, + const QString& dirsVar, + const QSet<QString> &excludedDirs = QSet<QString>(), + const QSet<QString> &excludedFiles = QSet<QString>()); + QString getIncludeFilePath(const QString& fileName) const; + QStringList getExampleQdocFiles(const QSet<QString> &excludedDirs, const QSet<QString> &excludedFiles); + QStringList getExampleImageFiles(const QSet<QString> &excludedDirs, const QSet<QString> &excludedFiles); + + static QStringList loadMaster(const QString& fileName); + static QStringList getFilesHere(const QString& dir, + const QString& nameFilter, + const Location &location = Location(), + const QSet<QString> &excludedDirs = QSet<QString>(), + const QSet<QString> &excludedFiles = QSet<QString>()); + static QString findFile(const Location& location, + const QStringList &files, + const QStringList& dirs, + const QString& fileName, + QString& userFriendlyFilePath); + static QString findFile(const Location &location, + const QStringList &files, + const QStringList &dirs, + const QString &fileBase, + const QStringList &fileExtensions, + QString &userFriendlyFilePath); + static QString copyFile(const Location& location, + const QString& sourceFilePath, + const QString& userFriendlySourceFilePath, + const QString& targetDirPath); + static int numParams(const QString& value); + static bool removeDirContents(const QString& dir); + static void pushWorkingDir(const QString& dir); + static QString popWorkingDir(); + + static const QString dot; + + static bool generateExamples; + static QString installDir; + static QString overrideOutputDir; + static QSet<QString> overrideOutputFormats; + +private: + static bool isMetaKeyChar(QChar ch); + void load(Location location, const QString& fileName); + + QString prog; + Location loc; + Location lastLocation_; + ConfigVarMultimap configVars_; + + static QMap<QString, QString> uncompressedFiles; + static QMap<QString, QString> extractedDirs; + static int numInstances; + static QStack<QString> workingDirs_; + static QMap<QString, QStringList> includeFilesMap_; +}; + +struct ConfigStrings +{ + static QString ALIAS; + static QString AUTOLINKERRORS; + static QString BASE; + static QString BASEDIR; + static QString BUILDVERSION; + static QString CODEINDENT; + static QString CODEPREFIX; + static QString CODESUFFIX; + static QString CPPCLASSESPAGE; + static QString DEFINES; + static QString DEPENDS; + static QString DESCRIPTION; + static QString EDITION; + static QString ENDHEADER; + static QString EXAMPLEDIRS; + static QString EXAMPLES; + static QString EXAMPLESINSTALLPATH; + static QString EXCLUDEDIRS; + static QString EXCLUDEFILES; + static QString EXTRAIMAGES; + static QString FALSEHOODS; + static QString FORMATTING; + static QString GENERATEINDEX; + static QString HEADERDIRS; + static QString HEADERS; + static QString HEADERSCRIPTS; + static QString HEADERSTYLES; + static QString HOMEPAGE; + static QString IGNOREDIRECTIVES; + static QString IGNORETOKENS; + static QString IMAGEDIRS; + static QString IMAGES; + static QString INDEXES; + static QString LANDINGPAGE; + static QString LANGUAGE; + static QString MACRO; + static QString MANIFESTMETA; + static QString NATURALLANGUAGE; + static QString NAVIGATION; + static QString NOLINKERRORS; + static QString OBSOLETELINKS; + static QString OUTPUTDIR; + static QString OUTPUTENCODING; + static QString OUTPUTLANGUAGE; + static QString OUTPUTFORMATS; + static QString OUTPUTPREFIXES; + static QString OUTPUTSUFFIXES; + static QString PROJECT; + static QString REDIRECTDOCUMENTATIONTODEVNULL; + static QString QHP; + static QString QUOTINGINFORMATION; + static QString SCRIPTDIRS; + static QString SCRIPTS; + static QString SHOWINTERNAL; + static QString SINGLEEXEC; + static QString SOURCEDIRS; + static QString SOURCEENCODING; + static QString SOURCES; + static QString SPURIOUS; + static QString STYLEDIRS; + static QString STYLE; + static QString STYLES; + static QString STYLESHEETS; + static QString SYNTAXHIGHLIGHTING; + static QString TEMPLATEDIR; + static QString TABSIZE; + static QString TAGFILE; + static QString TRANSLATORS; + static QString URL; + static QString VERSION; + static QString VERSIONSYM; + static QString FILEEXTENSIONS; + static QString IMAGEEXTENSIONS; + static QString QMLONLY; + static QString QMLTYPESPAGE; + static QString WRITEQAPAGES; +}; + +#define CONFIG_ALIAS ConfigStrings::ALIAS +#define CONFIG_AUTOLINKERRORS ConfigStrings::AUTOLINKERRORS +#define CONFIG_BASE ConfigStrings::BASE +#define CONFIG_BASEDIR ConfigStrings::BASEDIR +#define CONFIG_BUILDVERSION ConfigStrings::BUILDVERSION +#define CONFIG_CODEINDENT ConfigStrings::CODEINDENT +#define CONFIG_CODEPREFIX ConfigStrings::CODEPREFIX +#define CONFIG_CODESUFFIX ConfigStrings::CODESUFFIX +#define CONFIG_CPPCLASSESPAGE ConfigStrings::CPPCLASSESPAGE +#define CONFIG_DEFINES ConfigStrings::DEFINES +#define CONFIG_DEPENDS ConfigStrings::DEPENDS +#define CONFIG_DESCRIPTION ConfigStrings::DESCRIPTION +#define CONFIG_EDITION ConfigStrings::EDITION +#define CONFIG_ENDHEADER ConfigStrings::ENDHEADER +#define CONFIG_EXAMPLEDIRS ConfigStrings::EXAMPLEDIRS +#define CONFIG_EXAMPLES ConfigStrings::EXAMPLES +#define CONFIG_EXAMPLESINSTALLPATH ConfigStrings::EXAMPLESINSTALLPATH +#define CONFIG_EXCLUDEDIRS ConfigStrings::EXCLUDEDIRS +#define CONFIG_EXCLUDEFILES ConfigStrings::EXCLUDEFILES +#define CONFIG_EXTRAIMAGES ConfigStrings::EXTRAIMAGES +#define CONFIG_FALSEHOODS ConfigStrings::FALSEHOODS +#define CONFIG_FORMATTING ConfigStrings::FORMATTING +#define CONFIG_GENERATEINDEX ConfigStrings::GENERATEINDEX +#define CONFIG_HEADERDIRS ConfigStrings::HEADERDIRS +#define CONFIG_HEADERS ConfigStrings::HEADERS +#define CONFIG_HEADERSCRIPTS ConfigStrings::HEADERSCRIPTS +#define CONFIG_HEADERSTYLES ConfigStrings::HEADERSTYLES +#define CONFIG_HOMEPAGE ConfigStrings::HOMEPAGE +#define CONFIG_IGNOREDIRECTIVES ConfigStrings::IGNOREDIRECTIVES +#define CONFIG_IGNORETOKENS ConfigStrings::IGNORETOKENS +#define CONFIG_IMAGEDIRS ConfigStrings::IMAGEDIRS +#define CONFIG_IMAGES ConfigStrings::IMAGES +#define CONFIG_INDEXES ConfigStrings::INDEXES +#define CONFIG_LANDINGPAGE ConfigStrings::LANDINGPAGE +#define CONFIG_LANGUAGE ConfigStrings::LANGUAGE +#define CONFIG_MACRO ConfigStrings::MACRO +#define CONFIG_MANIFESTMETA ConfigStrings::MANIFESTMETA +#define CONFIG_NATURALLANGUAGE ConfigStrings::NATURALLANGUAGE +#define CONFIG_NAVIGATION ConfigStrings::NAVIGATION +#define CONFIG_NOLINKERRORS ConfigStrings::NOLINKERRORS +#define CONFIG_OBSOLETELINKS ConfigStrings::OBSOLETELINKS +#define CONFIG_OUTPUTDIR ConfigStrings::OUTPUTDIR +#define CONFIG_OUTPUTENCODING ConfigStrings::OUTPUTENCODING +#define CONFIG_OUTPUTLANGUAGE ConfigStrings::OUTPUTLANGUAGE +#define CONFIG_OUTPUTFORMATS ConfigStrings::OUTPUTFORMATS +#define CONFIG_OUTPUTPREFIXES ConfigStrings::OUTPUTPREFIXES +#define CONFIG_OUTPUTSUFFIXES ConfigStrings::OUTPUTSUFFIXES +#define CONFIG_PROJECT ConfigStrings::PROJECT +#define CONFIG_REDIRECTDOCUMENTATIONTODEVNULL ConfigStrings::REDIRECTDOCUMENTATIONTODEVNULL +#define CONFIG_QHP ConfigStrings::QHP +#define CONFIG_QUOTINGINFORMATION ConfigStrings::QUOTINGINFORMATION +#define CONFIG_SCRIPTDIRS ConfigStrings::SCRIPTDIRS +#define CONFIG_SCRIPTS ConfigStrings::SCRIPTS +#define CONFIG_SHOWINTERNAL ConfigStrings::SHOWINTERNAL +#define CONFIG_SINGLEEXEC ConfigStrings::SINGLEEXEC +#define CONFIG_SOURCEDIRS ConfigStrings::SOURCEDIRS +#define CONFIG_SOURCEENCODING ConfigStrings::SOURCEENCODING +#define CONFIG_SOURCES ConfigStrings::SOURCES +#define CONFIG_SPURIOUS ConfigStrings::SPURIOUS +#define CONFIG_STYLEDIRS ConfigStrings::STYLEDIRS +#define CONFIG_STYLE ConfigStrings::STYLE +#define CONFIG_STYLES ConfigStrings::STYLES +#define CONFIG_STYLESHEETS ConfigStrings::STYLESHEETS +#define CONFIG_SYNTAXHIGHLIGHTING ConfigStrings::SYNTAXHIGHLIGHTING +#define CONFIG_TEMPLATEDIR ConfigStrings::TEMPLATEDIR +#define CONFIG_TABSIZE ConfigStrings::TABSIZE +#define CONFIG_TAGFILE ConfigStrings::TAGFILE +#define CONFIG_TRANSLATORS ConfigStrings::TRANSLATORS +#define CONFIG_URL ConfigStrings::URL +#define CONFIG_VERSION ConfigStrings::VERSION +#define CONFIG_VERSIONSYM ConfigStrings::VERSIONSYM +#define CONFIG_FILEEXTENSIONS ConfigStrings::FILEEXTENSIONS +#define CONFIG_IMAGEEXTENSIONS ConfigStrings::IMAGEEXTENSIONS +#define CONFIG_QMLONLY ConfigStrings::QMLONLY +#define CONFIG_QMLTYPESPAGE ConfigStrings::QMLTYPESPAGE +#define CONFIG_WRITEQAPAGES ConfigStrings::WRITEQAPAGES + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/cppcodemarker.cpp b/src/qdoc/cppcodemarker.cpp new file mode 100644 index 000000000..01ff827d5 --- /dev/null +++ b/src/qdoc/cppcodemarker.cpp @@ -0,0 +1,1326 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + cppcodemarker.cpp +*/ + +#include "atom.h" +#include "cppcodemarker.h" +#include "node.h" +#include "text.h" +#include "tree.h" +#include <qdebug.h> +#include <ctype.h> + +QT_BEGIN_NAMESPACE + +/*! + The constructor does nothing. + */ +CppCodeMarker::CppCodeMarker() +{ + // nothing. +} + +/*! + The destructor does nothing. + */ +CppCodeMarker::~CppCodeMarker() +{ + // nothing. +} + +/*! + Returns \c true. + */ +bool CppCodeMarker::recognizeCode(const QString & /* code */) +{ + return true; +} + +/*! + Returns \c true if \a ext is any of a list of file extensions + for the C++ language. + */ +bool CppCodeMarker::recognizeExtension(const QString& extension) +{ + QByteArray ext = extension.toLatin1(); + return ext == "c" || + ext == "c++" || + ext == "qdoc" || + ext == "qtt" || + ext == "qtx" || + ext == "cc" || + ext == "cpp" || + ext == "cxx" || + ext == "ch" || + ext == "h" || + ext == "h++" || + ext == "hh" || + ext == "hpp" || + ext == "hxx"; +} + +/*! + Returns \c true if \a lang is either "C" or "Cpp". + */ +bool CppCodeMarker::recognizeLanguage(const QString &lang) +{ + return lang == QLatin1String("C") || lang == QLatin1String("Cpp"); +} + +/*! + Returns the type of atom used to represent C++ code in the documentation. +*/ +Atom::AtomType CppCodeMarker::atomType() const +{ + return Atom::Code; +} + +QString CppCodeMarker::markedUpCode(const QString &code, + const Node *relative, + const Location &location) +{ + return addMarkUp(code, relative, location); +} + +QString CppCodeMarker::markedUpSynopsis(const Node *node, + const Node * /* relative */, + SynopsisStyle style) +{ + const int MaxEnumValues = 6; + const FunctionNode *func; + const PropertyNode *property; + const VariableNode *variable; + const EnumNode *enume; + const TypedefNode *typedeff; + QString synopsis; + QString extra; + QString name; + + name = taggedNode(node); + if (style != Detailed) + name = linkTag(node, name); + name = "<@name>" + name + "</@name>"; + + if ((style == Detailed) && !node->parent()->name().isEmpty() && + (node->type() != Node::Property) && !node->isQmlNode() && !node->isJsNode()) + name.prepend(taggedNode(node->parent()) + "::"); + + switch (node->type()) { + case Node::Namespace: + synopsis = "namespace " + name; + break; + case Node::Class: + synopsis = "class " + name; + break; + case Node::Function: + case Node::QmlSignal: + case Node::QmlSignalHandler: + case Node::QmlMethod: + func = (const FunctionNode *) node; + + if (style != Subpage && !func->returnType().isEmpty()) + synopsis = typified(func->returnType(), true); + synopsis += name; + if (func->metaness() != FunctionNode::MacroWithoutParams) { + synopsis += QLatin1Char('('); + if (!func->parameters().isEmpty()) { + QVector<Parameter>::ConstIterator p = func->parameters().constBegin(); + while (p != func->parameters().constEnd()) { + if (p != func->parameters().constBegin()) + synopsis += ", "; + synopsis += typified((*p).dataType(), true); + if (style != Subpage && !(*p).name().isEmpty()) + synopsis += + "<@param>" + protect((*p).name()) + "</@param>"; + synopsis += protect((*p).rightType()); + if (style != Subpage && !(*p).defaultValue().isEmpty()) + synopsis += " = " + protect((*p).defaultValue()); + ++p; + } + } + synopsis += QLatin1Char(')'); + } + if (func->isConst()) + synopsis += " const"; + + if (style == Summary || style == Accessors) { + if (func->virtualness() != FunctionNode::NonVirtual) + synopsis.prepend("virtual "); + if (func->virtualness() == FunctionNode::PureVirtual) + synopsis.append(" = 0"); + } + else if (style == Subpage) { + if (!func->returnType().isEmpty() && func->returnType() != "void") + synopsis += " : " + typified(func->returnType()); + } + else { + QStringList bracketed; + if (func->isStatic()) { + bracketed += "static"; + } + else if (func->virtualness() != FunctionNode::NonVirtual) { + if (func->virtualness() == FunctionNode::PureVirtual) + bracketed += "pure"; + bracketed += "virtual"; + } + + if (func->access() == Node::Protected) { + bracketed += "protected"; + } + else if (func->access() == Node::Private) { + bracketed += "private"; + } + + if (func->metaness() == FunctionNode::Signal) { + bracketed += "signal"; + } + else if (func->metaness() == FunctionNode::Slot) { + bracketed += "slot"; + } + if (!bracketed.isEmpty()) + extra += QLatin1Char('[') + bracketed.join(' ') + QStringLiteral("] "); + } + break; + case Node::Enum: + enume = static_cast<const EnumNode *>(node); + synopsis = "enum " + name; + if (style == Summary) { + synopsis += " { "; + + QStringList documentedItems = enume->doc().enumItemNames(); + if (documentedItems.isEmpty()) { + foreach (const EnumItem &item, enume->items()) + documentedItems << item.name(); + } + QStringList omitItems = enume->doc().omitEnumItemNames(); + foreach (const QString &item, omitItems) + documentedItems.removeAll(item); + + if (documentedItems.size() <= MaxEnumValues) { + for (int i = 0; i < documentedItems.size(); ++i) { + if (i != 0) + synopsis += ", "; + synopsis += documentedItems.at(i); + } + } + else { + for (int i = 0; i < documentedItems.size(); ++i) { + if (i < MaxEnumValues-2 || i == documentedItems.size()-1) { + if (i != 0) + synopsis += ", "; + synopsis += documentedItems.at(i); + } + else if (i == MaxEnumValues - 1) { + synopsis += ", ..."; + } + } + } + if (!documentedItems.isEmpty()) + synopsis += QLatin1Char(' '); + synopsis += QLatin1Char('}'); + } + break; + case Node::Typedef: + typedeff = static_cast<const TypedefNode *>(node); + if (typedeff->associatedEnum()) { + synopsis = "flags " + name; + } + else { + synopsis = "typedef " + name; + } + break; + case Node::Property: + property = static_cast<const PropertyNode *>(node); + synopsis = name + " : " + typified(property->qualifiedDataType()); + break; + case Node::Variable: + variable = static_cast<const VariableNode *>(node); + if (style == Subpage) { + synopsis = name + " : " + typified(variable->dataType()); + } + else { + synopsis = typified(variable->leftType(), true) + + name + protect(variable->rightType()); + } + break; + default: + synopsis = name; + } + + if (style == Summary) { + if (node->status() == Node::Preliminary) { + extra += "(preliminary) "; + } + else if (node->status() == Node::Deprecated) { + extra += "(deprecated) "; + } + else if (node->status() == Node::Obsolete) { + extra += "(obsolete) "; + } + } + + if (!extra.isEmpty()) { + extra.prepend("<@extra>"); + extra.append("</@extra>"); + } + return extra + synopsis; +} + +/*! + */ +QString CppCodeMarker::markedUpQmlItem(const Node* node, bool summary) +{ + QString name = taggedQmlNode(node); + if (summary) + name = linkTag(node,name); + else if (node->isQmlProperty() || node->isJsProperty()) { + const QmlPropertyNode* pn = static_cast<const QmlPropertyNode*>(node); + if (pn->isAttached()) + name.prepend(pn->element() + QLatin1Char('.')); + } + name = "<@name>" + name + "</@name>"; + QString synopsis; + if (node->isQmlProperty() || node->isJsProperty()) { + const QmlPropertyNode* pn = static_cast<const QmlPropertyNode*>(node); + synopsis = name + " : " + typified(pn->dataType()); + } + else if ((node->type() == Node::QmlMethod) || + (node->type() == Node::QmlSignal) || + (node->type() == Node::QmlSignalHandler)) { + const FunctionNode* func = static_cast<const FunctionNode*>(node); + if (!func->returnType().isEmpty()) + synopsis = typified(func->returnType(), true) + name; + else + synopsis = name; + synopsis += QLatin1Char('('); + if (!func->parameters().isEmpty()) { + QVector<Parameter>::ConstIterator p = func->parameters().constBegin(); + while (p != func->parameters().constEnd()) { + if (p != func->parameters().constBegin()) + synopsis += ", "; + synopsis += typified((*p).dataType(), true); + if (!(*p).name().isEmpty()) + synopsis += "<@param>" + protect((*p).name()) + "</@param>"; + synopsis += protect((*p).rightType()); + ++p; + } + } + synopsis += QLatin1Char(')'); + } + else + synopsis = name; + + QString extra; + if (summary) { + if (node->status() == Node::Preliminary) { + extra += " (preliminary)"; + } + else if (node->status() == Node::Deprecated) { + extra += " (deprecated)"; + } + else if (node->status() == Node::Obsolete) { + extra += " (obsolete)"; + } + } + + if (!extra.isEmpty()) { + extra.prepend("<@extra>"); + extra.append("</@extra>"); + } + return synopsis + extra; +} + +QString CppCodeMarker::markedUpName(const Node *node) +{ + QString name = linkTag(node, taggedNode(node)); + if (node->type() == Node::Function) + name += "()"; + return name; +} + +QString CppCodeMarker::markedUpFullName(const Node *node, const Node *relative) +{ + if (node->name().isEmpty()) { + return "global"; + } + else { + QString fullName; + for (;;) { + fullName.prepend(markedUpName(node)); + if (node->parent() == relative || node->parent()->name().isEmpty()) + break; + fullName.prepend("<@op>::</@op>"); + node = node->parent(); + } + return fullName; + } +} + +QString CppCodeMarker::markedUpEnumValue(const QString &enumValue, const Node *relative) +{ + if (relative->type() != Node::Enum) + return enumValue; + + const Node *node = relative->parent(); + QString fullName; + while (node->parent()) { + fullName.prepend(markedUpName(node)); + if (node->parent() == relative || node->parent()->name().isEmpty()) + break; + fullName.prepend("<@op>::</@op>"); + node = node->parent(); + } + if (!fullName.isEmpty()) + fullName.append("<@op>::</@op>"); + fullName.append(enumValue); + return fullName; +} + +QString CppCodeMarker::markedUpIncludes(const QStringList& includes) +{ + QString code; + + QStringList::ConstIterator inc = includes.constBegin(); + while (inc != includes.constEnd()) { + code += "<@preprocessor>#include <<@headerfile>" + *inc + "</@headerfile>></@preprocessor>\n"; + ++inc; + } + return code; +} + +QString CppCodeMarker::functionBeginRegExp(const QString& funcName) +{ + return QLatin1Char('^') + QRegExp::escape(funcName) + QLatin1Char('$'); + +} + +QString CppCodeMarker::functionEndRegExp(const QString& /* funcName */) +{ + return "^\\}$"; +} + +QList<Section> CppCodeMarker::sections(const Aggregate *inner, + SynopsisStyle style, + Status status) +{ + QList<Section> sections; + + if (inner->isClass()) { + if (style == Summary) { + FastSection privateFunctions(inner, + "Private Functions", + QString(), + "private function", + "private functions"); + FastSection privateSlots(inner, "Private Slots", QString(), "private slot", "private slots"); + FastSection privateTypes(inner, "Private Types", QString(), "private type", "private types"); + FastSection protectedFunctions(inner, + "Protected Functions", + QString(), + "protected function", + "protected functions"); + FastSection protectedSlots(inner, + "Protected Slots", + QString(), + "protected slot", + "protected slots"); + FastSection protectedTypes(inner, + "Protected Types", + QString(), + "protected type", + "protected types"); + FastSection protectedVariables(inner, + "Protected Variables", + QString(), + "protected type", + "protected variables"); + FastSection publicFunctions(inner, + "Public Functions", + QString(), + "public function", + "public functions"); + FastSection publicSignals(inner, "Signals", QString(), "signal", "signals"); + FastSection publicSlots(inner, "Public Slots", QString(), "public slot", "public slots"); + FastSection publicTypes(inner, "Public Types", QString(), "public type", "public types"); + FastSection publicVariables(inner, + "Public Variables", + QString(), + "public variable", + "public variables"); + FastSection properties(inner, "Properties", QString(), "property", "properties"); + FastSection relatedNonMembers(inner, + "Related Non-Members", + QString(), + "related non-member", + "related non-members"); + FastSection staticPrivateMembers(inner, + "Static Private Members", + QString(), + "static private member", + "static private members"); + FastSection staticProtectedMembers(inner, + "Static Protected Members", + QString(), + "static protected member", + "static protected members"); + FastSection staticPublicMembers(inner, + "Static Public Members", + QString(), + "static public member", + "static public members"); + FastSection macros(inner, "Macros", QString(), "macro", "macros"); + + NodeList::ConstIterator r = inner->relatedNodes().constBegin(); + while (r != inner->relatedNodes().constEnd()) { + if ((*r)->type() == Node::Function) { + FunctionNode *func = static_cast<FunctionNode *>(*r); + if (func->isMacro()) + insert(macros, *r, style, status); + else + insert(relatedNonMembers, *r, style, status); + } + else { + insert(relatedNonMembers, *r, style, status); + } + ++r; + } + + QStack<const Aggregate *> stack; + stack.push(inner); + while (!stack.isEmpty()) { + const Aggregate* ancestor = stack.pop(); + + NodeList::ConstIterator c = ancestor->childNodes().constBegin(); + while (c != ancestor->childNodes().constEnd()) { + bool isSlot = false; + bool isSignal = false; + bool isStatic = false; + if ((*c)->type() == Node::Function) { + const FunctionNode *func = (const FunctionNode *) *c; + isSlot = (func->metaness() == FunctionNode::Slot); + isSignal = (func->metaness() == FunctionNode::Signal); + isStatic = func->isStatic(); + if (func->hasAssociatedProperties() && !func->hasActiveAssociatedProperty()) { + ++c; + continue; + } + } + else if ((*c)->type() == Node::Variable) { + const VariableNode *var = static_cast<const VariableNode *>(*c); + isStatic = var->isStatic(); + } + + switch ((*c)->access()) { + case Node::Public: + if (isSlot) { + insert(publicSlots, *c, style, status); + } + else if (isSignal) { + insert(publicSignals, *c, style, status); + } + else if (isStatic) { + if ((*c)->type() != Node::Variable || !(*c)->doc().isEmpty()) + insert(staticPublicMembers,*c,style,status); + } + else if ((*c)->type() == Node::Property) { + insert(properties, *c, style, status); + } + else if ((*c)->type() == Node::Variable) { + if (!(*c)->doc().isEmpty()) + insert(publicVariables, *c, style, status); + } + else if ((*c)->type() == Node::Function) { + if (!insertReimpFunc(publicFunctions,*c,status)) { + insert(publicFunctions, *c, style, status); + } + } + else { + insert(publicTypes, *c, style, status); + } + break; + case Node::Protected: + if (isSlot) { + insert(protectedSlots, *c, style, status); + } + else if (isStatic) { + if ((*c)->type() != Node::Variable || !(*c)->doc().isEmpty()) + insert(staticProtectedMembers,*c,style,status); + } + else if ((*c)->type() == Node::Variable) { + if (!(*c)->doc().isEmpty()) + insert(protectedVariables,*c,style,status); + } + else if ((*c)->type() == Node::Function) { + if (!insertReimpFunc(protectedFunctions,*c,status)) { + insert(protectedFunctions, *c, style, status); + } + } + else { + insert(protectedTypes, *c, style, status); + } + break; + case Node::Private: + if (isSlot) { + insert(privateSlots, *c, style, status); + } + else if (isStatic) { + if ((*c)->type() != Node::Variable || !(*c)->doc().isEmpty()) + insert(staticPrivateMembers,*c,style,status); + } + else if ((*c)->type() == Node::Function) { + if (!insertReimpFunc(privateFunctions,*c,status)) { + insert(privateFunctions, *c, style, status); + } + } + else { + insert(privateTypes,*c,style,status); + } + } + ++c; + } + + if (ancestor->isClass()) { + const ClassNode* cn = static_cast<const ClassNode*>(ancestor); + QList<RelatedClass>::ConstIterator r = cn->baseClasses().constBegin(); + while (r != cn->baseClasses().constEnd()) { + if ((*r).node_) + stack.prepend((*r).node_); + ++r; + } + } + } + append(sections, publicTypes); + append(sections, properties); + append(sections, publicFunctions); + append(sections, publicSlots); + append(sections, publicSignals); + append(sections, publicVariables); + append(sections, staticPublicMembers); + append(sections, protectedTypes); + append(sections, protectedFunctions); + append(sections, protectedSlots); + append(sections, protectedVariables); + append(sections, staticProtectedMembers); + append(sections, privateTypes); + append(sections, privateFunctions); + append(sections, privateSlots); + append(sections, staticPrivateMembers); + append(sections, relatedNonMembers); + append(sections, macros); + } + else if (style == Detailed) { + FastSection memberFunctions(inner,"Member Function Documentation","func","member","members"); + FastSection memberTypes(inner,"Member Type Documentation","types","member","members"); + FastSection memberVariables(inner,"Member Variable Documentation","vars","member","members"); + FastSection properties(inner,"Property Documentation","prop","member","members"); + FastSection relatedNonMembers(inner,"Related Non-Members","relnonmem","member","members"); + FastSection macros(inner,"Macro Documentation","macros","member","members"); + + NodeList::ConstIterator r = inner->relatedNodes().constBegin(); + while (r != inner->relatedNodes().constEnd()) { + if ((*r)->type() == Node::Function) { + FunctionNode *func = static_cast<FunctionNode *>(*r); + if (func->isMacro()) + insert(macros, *r, style, status); + else + insert(relatedNonMembers, *r, style, status); + } + else { + insert(relatedNonMembers, *r, style, status); + } + ++r; + } + + NodeList::ConstIterator c = inner->childNodes().constBegin(); + while (c != inner->childNodes().constEnd()) { + if ((*c)->type() == Node::Enum || + (*c)->type() == Node::Typedef) { + insert(memberTypes, *c, style, status); + } + else if ((*c)->type() == Node::Property) { + insert(properties, *c, style, status); + } + else if ((*c)->type() == Node::Variable) { + if (!(*c)->doc().isEmpty()) + insert(memberVariables, *c, style, status); + } + else if ((*c)->type() == Node::Function) { + FunctionNode *function = static_cast<FunctionNode *>(*c); + if (!function->hasAssociatedProperties() || !function->doc().isEmpty()) + insert(memberFunctions, function, style, status); + } + ++c; + } + + append(sections, memberTypes); + append(sections, properties); + append(sections, memberFunctions); + append(sections, memberVariables); + append(sections, relatedNonMembers); + append(sections, macros); + } + else { + FastSection all(inner,QString(),QString(),"member","members"); + + QStack<const Aggregate*> stack; + stack.push(inner); + + while (!stack.isEmpty()) { + const Aggregate* ancestor = stack.pop(); + NodeList::ConstIterator c = ancestor->childNodes().constBegin(); + while (c != ancestor->childNodes().constEnd()) { + if ((*c)->access() != Node::Private && (*c)->type() != Node::Property) + insert(all, *c, style, status); + ++c; + } + + if (ancestor->isClass()) { + const ClassNode* cn = static_cast<const ClassNode*>(ancestor); + QList<RelatedClass>::ConstIterator r = cn->baseClasses().constBegin(); + while (r != cn->baseClasses().constEnd()) { + if ((*r).node_) + stack.prepend((*r).node_); + ++r; + } + } + } + append(sections, all); + } + } + else { + if (style == Summary || style == Detailed) { + FastSection namespaces(inner, + "Namespaces", + style == Detailed ? "nmspace" : QString(), + "namespace", + "namespaces"); + FastSection classes(inner, + "Classes", + style == Detailed ? "classes" : QString(), + "class", + "classes"); + FastSection types(inner, + style == Summary ? "Types" : "Type Documentation", + style == Detailed ? "types" : QString(), + "type", + "types"); + FastSection variables(inner, + style == Summary ? "Variables" : "Variable Documentation", + style == Detailed ? "vars" : QString(), + "variable", + "variables"); + FastSection staticVariables(inner, + "Static Variables", + QString(), + "static variable", + "static variables"); + FastSection functions(inner, + style == Summary ? + "Functions" : "Function Documentation", + style == Detailed ? "func" : QString(), + "function", + "functions"); + FastSection macros(inner, + style == Summary ? + "Macros" : "Macro Documentation", + style == Detailed ? "macros" : QString(), + "macro", + "macros"); + + NodeList nodeList = inner->childNodes(); + nodeList += inner->relatedNodes(); + + NodeList::ConstIterator n = nodeList.constBegin(); + while (n != nodeList.constEnd()) { + switch ((*n)->type()) { + case Node::Namespace: + insert(namespaces, *n, style, status); + break; + case Node::Class: + insert(classes, *n, style, status); + break; + case Node::Enum: + case Node::Typedef: + insert(types, *n, style, status); + break; + case Node::Function: + { + FunctionNode *func = static_cast<FunctionNode *>(*n); + if (func->isMacro()) + insert(macros, *n, style, status); + else + insert(functions, *n, style, status); + } + break; + case Node::Variable: + { + const VariableNode* var = static_cast<const VariableNode*>(*n); + if (!var->doc().isEmpty()) { + if (var->isStatic()) + insert(staticVariables,*n,style,status); + else + insert(variables, *n, style, status); + } + } + break; + default: + break; + } + ++n; + } + if (inner->isNamespace()) { + const NamespaceNode* ns = static_cast<const NamespaceNode*>(inner); + if (!ns->orphans().isEmpty()) { + foreach (Node* n, ns->orphans()) { + // Use inner as a temporary parent when inserting orphans + Aggregate* p = n->parent(); + n->setParent(const_cast<Aggregate*>(inner)); + if (n->isClass()) + insert(classes, n, style, status); + else if (n->isNamespace()) + insert(namespaces, n, style, status); + n->setParent(p); + } + } + } + append(sections, namespaces); + append(sections, classes); + append(sections, types); + append(sections, variables); + append(sections, staticVariables); + append(sections, functions); + append(sections, macros); + } + } + + return sections; +} + +/* + @char + @class + @comment + @function + @keyword + @number + @op + @preprocessor + @string + @type +*/ + +QString CppCodeMarker::addMarkUp(const QString &in, + const Node * /* relative */, + const Location & /* location */) +{ + static QSet<QString> types; + static QSet<QString> keywords; + + if (types.isEmpty()) { + // initialize statics + Q_ASSERT(keywords.isEmpty()); + static const QString typeTable[] = { + QLatin1String("bool"), QLatin1String("char"), QLatin1String("double"), QLatin1String("float"), QLatin1String("int"), QLatin1String("long"), QLatin1String("short"), + QLatin1String("signed"), QLatin1String("unsigned"), QLatin1String("uint"), QLatin1String("ulong"), QLatin1String("ushort"), QLatin1String("uchar"), QLatin1String("void"), + QLatin1String("qlonglong"), QLatin1String("qulonglong"), + QLatin1String("qint"), QLatin1String("qint8"), QLatin1String("qint16"), QLatin1String("qint32"), QLatin1String("qint64"), + QLatin1String("quint"), QLatin1String("quint8"), QLatin1String("quint16"), QLatin1String("quint32"), QLatin1String("quint64"), + QLatin1String("qreal"), QLatin1String("cond") + }; + + static const QString keywordTable[] = { + QLatin1String("and"), QLatin1String("and_eq"), QLatin1String("asm"), QLatin1String("auto"), QLatin1String("bitand"), QLatin1String("bitor"), QLatin1String("break"), + QLatin1String("case"), QLatin1String("catch"), QLatin1String("class"), QLatin1String("compl"), QLatin1String("const"), QLatin1String("const_cast"), + QLatin1String("continue"), QLatin1String("default"), QLatin1String("delete"), QLatin1String("do"), QLatin1String("dynamic_cast"), QLatin1String("else"), + QLatin1String("enum"), QLatin1String("explicit"), QLatin1String("export"), QLatin1String("extern"), QLatin1String("false"), QLatin1String("for"), QLatin1String("friend"), + QLatin1String("goto"), QLatin1String("if"), QLatin1String("include"), QLatin1String("inline"), QLatin1String("monitor"), QLatin1String("mutable"), QLatin1String("namespace"), + QLatin1String("new"), QLatin1String("not"), QLatin1String("not_eq"), QLatin1String("operator"), QLatin1String("or"), QLatin1String("or_eq"), QLatin1String("private"), QLatin1String("protected"), + QLatin1String("public"), QLatin1String("register"), QLatin1String("reinterpret_cast"), QLatin1String("return"), QLatin1String("sizeof"), + QLatin1String("static"), QLatin1String("static_cast"), QLatin1String("struct"), QLatin1String("switch"), QLatin1String("template"), QLatin1String("this"), + QLatin1String("throw"), QLatin1String("true"), QLatin1String("try"), QLatin1String("typedef"), QLatin1String("typeid"), QLatin1String("typename"), QLatin1String("union"), + QLatin1String("using"), QLatin1String("virtual"), QLatin1String("volatile"), QLatin1String("wchar_t"), QLatin1String("while"), QLatin1String("xor"), + QLatin1String("xor_eq"), QLatin1String("synchronized"), + // Qt specific + QLatin1String("signals"), QLatin1String("slots"), QLatin1String("emit") + }; + + types.reserve(sizeof(typeTable) / sizeof(QString)); + for (int j = sizeof(typeTable) / sizeof(QString) - 1; j; --j) + types.insert(typeTable[j]); + + keywords.reserve(sizeof(keywordTable) / sizeof(QString)); + for (int j = sizeof(keywordTable) / sizeof(QString) - 1; j; --j) + keywords.insert(keywordTable[j]); + } +#define readChar() \ + ch = (i < (int)code.length()) ? code[i++].cell() : EOF + + QString code = in; + QString out; + QStringRef text; + int braceDepth = 0; + int parenDepth = 0; + int i = 0; + int start = 0; + int finish = 0; + QChar ch; + QRegExp classRegExp("Qt?(?:[A-Z3]+[a-z][A-Za-z]*|t)"); + QRegExp functionRegExp("q([A-Z][a-z]+)+"); + QRegExp findFunctionRegExp(QStringLiteral("^\\s*\\(")); + + readChar(); + + while (ch != EOF) { + QString tag; + bool target = false; + + if (ch.isLetter() || ch == '_') { + QString ident; + do { + ident += ch; + finish = i; + readChar(); + } while (ch.isLetterOrNumber() || ch == '_'); + + if (classRegExp.exactMatch(ident)) { + tag = QStringLiteral("type"); + } else if (functionRegExp.exactMatch(ident)) { + tag = QStringLiteral("func"); + target = true; + } else if (types.contains(ident)) { + tag = QStringLiteral("type"); + } else if (keywords.contains(ident)) { + tag = QStringLiteral("keyword"); + } else if (braceDepth == 0 && parenDepth == 0) { + if (code.indexOf(findFunctionRegExp, i - 1) == i - 1) + tag = QStringLiteral("func"); + target = true; + } + } else if (ch.isDigit()) { + do { + finish = i; + readChar(); + } while (ch.isLetterOrNumber() || ch == '.'); + tag = QStringLiteral("number"); + } else { + switch (ch.unicode()) { + case '+': + case '-': + case '!': + case '%': + case '^': + case '&': + case '*': + case ',': + case '.': + case '<': + case '=': + case '>': + case '?': + case '[': + case ']': + case '|': + case '~': + finish = i; + readChar(); + tag = QStringLiteral("op"); + break; + case '"': + finish = i; + readChar(); + + while (ch != EOF && ch != '"') { + if (ch == '\\') + readChar(); + readChar(); + } + finish = i; + readChar(); + tag = QStringLiteral("string"); + break; + case '#': + finish = i; + readChar(); + while (ch != EOF && ch != '\n') { + if (ch == '\\') + readChar(); + finish = i; + readChar(); + } + tag = QStringLiteral("preprocessor"); + break; + case '\'': + finish = i; + readChar(); + + while (ch != EOF && ch != '\'') { + if (ch == '\\') + readChar(); + readChar(); + } + finish = i; + readChar(); + tag = QStringLiteral("char"); + break; + case '(': + finish = i; + readChar(); + parenDepth++; + break; + case ')': + finish = i; + readChar(); + parenDepth--; + break; + case ':': + finish = i; + readChar(); + if (ch == ':') { + finish = i; + readChar(); + tag = QStringLiteral("op"); + } + break; + case '/': + finish = i; + readChar(); + if (ch == '/') { + do { + finish = i; + readChar(); + } while (ch != EOF && ch != '\n'); + tag = QStringLiteral("comment"); + } else if (ch == '*') { + bool metAster = false; + bool metAsterSlash = false; + + finish = i; + readChar(); + + while (!metAsterSlash) { + if (ch == EOF) + break; + + if (ch == '*') + metAster = true; + else if (metAster && ch == '/') + metAsterSlash = true; + else + metAster = false; + finish = i; + readChar(); + } + tag = QStringLiteral("comment"); + } else { + tag = QStringLiteral("op"); + } + break; + case '{': + finish = i; + readChar(); + braceDepth++; + break; + case '}': + finish = i; + readChar(); + braceDepth--; + break; + default: + finish = i; + readChar(); + } + } + + text = code.midRef(start, finish - start); + start = finish; + + if (!tag.isEmpty()) { + out += QStringLiteral("<@"); + out += tag; + if (target) { + out += QStringLiteral(" target=\""); + out += text; + out += QStringLiteral("()\""); + } + out += QStringLiteral(">"); + } + + appendProtectedString(&out, text); + + if (!tag.isEmpty()) { + out += QStringLiteral("</@"); + out += tag; + out += QStringLiteral(">"); + } + } + + if (start < code.length()) { + appendProtectedString(&out, code.midRef(start)); + } + + return out; +} + +/*! + This function is for documenting QML properties. It returns + the list of documentation sections for the children of the + \a qmlTypeNode. + */ +QList<Section> CppCodeMarker::qmlSections(QmlTypeNode* qmlTypeNode, SynopsisStyle style, Status status) +{ + QList<Section> sections; + if (qmlTypeNode) { + if (style == Summary) { + FastSection qmlproperties(qmlTypeNode, + "Properties", + QString(), + "property", + "properties"); + FastSection qmlattachedproperties(qmlTypeNode, + "Attached Properties", + QString(), + "property", + "properties"); + FastSection qmlsignals(qmlTypeNode, + "Signals", + QString(), + "signal", + "signals"); + FastSection qmlsignalhandlers(qmlTypeNode, + "Signal Handlers", + QString(), + "signal handler", + "signal handlers"); + FastSection qmlattachedsignals(qmlTypeNode, + "Attached Signals", + QString(), + "signal", + "signals"); + FastSection qmlmethods(qmlTypeNode, + "Methods", + QString(), + "method", + "methods"); + FastSection qmlattachedmethods(qmlTypeNode, + "Attached Methods", + QString(), + "method", + "methods"); + + QmlTypeNode* qcn = qmlTypeNode; + while (qcn != 0) { + NodeList::ConstIterator c = qcn->childNodes().constBegin(); + while (c != qcn->childNodes().constEnd()) { + if ((*c)->status() == Node::Internal) { + ++c; + continue; + } + if ((*c)->isQmlPropertyGroup() || (*c)->isJsPropertyGroup()) { + insert(qmlproperties, *c, style, status); + } + else if ((*c)->isQmlProperty() || (*c)->isJsProperty()) { + const QmlPropertyNode* pn = static_cast<const QmlPropertyNode*>(*c); + if (pn->isAttached()) + insert(qmlattachedproperties,*c,style, status); + else { + insert(qmlproperties,*c,style, status); + } + } + else if ((*c)->isQmlSignal() || (*c)->isJsSignal()) { + const FunctionNode* sn = static_cast<const FunctionNode*>(*c); + if (sn->isAttached()) + insert(qmlattachedsignals,*c,style, status); + else + insert(qmlsignals,*c,style, status); + } + else if ((*c)->isQmlSignalHandler() || (*c)->isJsSignalHandler()) { + insert(qmlsignalhandlers,*c,style, status); + } + else if ((*c)->isQmlMethod() || (*c)->isJsMethod()) { + const FunctionNode* mn = static_cast<const FunctionNode*>(*c); + if (mn->isAttached()) + insert(qmlattachedmethods,*c,style, status); + else + insert(qmlmethods,*c,style, status); + } + ++c; + } + if (qcn->qmlBaseNode() != 0) { + qcn = static_cast<QmlTypeNode*>(qcn->qmlBaseNode()); + if (!qcn->isAbstract()) + qcn = 0; + } + else + qcn = 0; + } + append(sections,qmlproperties); + append(sections,qmlattachedproperties); + append(sections,qmlsignals); + append(sections,qmlsignalhandlers); + append(sections,qmlattachedsignals); + append(sections,qmlmethods); + append(sections,qmlattachedmethods); + } + else if (style == Detailed) { + FastSection qmlproperties(qmlTypeNode, "Property Documentation","qmlprop","member","members"); + FastSection qmlattachedproperties(qmlTypeNode,"Attached Property Documentation","qmlattprop", + "member","members"); + FastSection qmlsignals(qmlTypeNode,"Signal Documentation","qmlsig","signal","signals"); + FastSection qmlsignalhandlers(qmlTypeNode,"Signal Handler Documentation","qmlsighan","signal handler","signal handlers"); + FastSection qmlattachedsignals(qmlTypeNode,"Attached Signal Documentation","qmlattsig", + "signal","signals"); + FastSection qmlmethods(qmlTypeNode,"Method Documentation","qmlmeth","member","members"); + FastSection qmlattachedmethods(qmlTypeNode,"Attached Method Documentation","qmlattmeth", + "member","members"); + QmlTypeNode* qcn = qmlTypeNode; + while (qcn != 0) { + NodeList::ConstIterator c = qcn->childNodes().constBegin(); + while (c != qcn->childNodes().constEnd()) { + if ((*c)->status() == Node::Internal) { + ++c; + continue; + } + if ((*c)->isQmlPropertyGroup() || (*c)->isJsPropertyGroup()) { + insert(qmlproperties,*c,style, status); + } + else if ((*c)->isQmlProperty() || (*c)->isJsProperty()) { + const QmlPropertyNode* pn = static_cast<const QmlPropertyNode*>(*c); + if (pn->isAttached()) + insert(qmlattachedproperties,*c,style, status); + else + insert(qmlproperties,*c,style, status); + } + else if ((*c)->isQmlSignal() || (*c)->isJsSignal()) { + const FunctionNode* sn = static_cast<const FunctionNode*>(*c); + if (sn->isAttached()) + insert(qmlattachedsignals,*c,style, status); + else + insert(qmlsignals,*c,style, status); + } + else if ((*c)->isQmlSignalHandler() || (*c)->isJsSignalHandler()) { + insert(qmlsignalhandlers,*c,style, status); + } + else if ((*c)->isQmlMethod() || (*c)->isJsMethod()) { + const FunctionNode* mn = static_cast<const FunctionNode*>(*c); + if (mn->isAttached()) + insert(qmlattachedmethods,*c,style, status); + else + insert(qmlmethods,*c,style, status); + } + ++c; + } + if (qcn->qmlBaseNode() != 0) { + qcn = static_cast<QmlTypeNode*>(qcn->qmlBaseNode()); + if (!qcn->isAbstract()) + qcn = 0; + } + else + qcn = 0; + } + append(sections,qmlproperties); + append(sections,qmlattachedproperties); + append(sections,qmlsignals); + append(sections,qmlsignalhandlers); + append(sections,qmlattachedsignals); + append(sections,qmlmethods); + append(sections,qmlattachedmethods); + } + else { + /* + This is where the list of all members including inherited + members is prepared. + */ + ClassMap* classMap = 0; + FastSection all(qmlTypeNode,QString(),QString(),"member","members"); + QmlTypeNode* current = qmlTypeNode; + while (current != 0) { + /* + If the QML type is abstract, do not create + a new entry in the list for it. Instead, + add its members to the current entry. + + However, if the first class is abstract, + there is no current entry. In that case, + create a new entry in the list anyway. + I'm not sure that is correct, but it at + least can prevent a crash. + */ + if (!current->isAbstract() || !classMap) { + classMap = new ClassMap; + classMap->first = current; + all.classMapList_.append(classMap); + } + NodeList::ConstIterator c = current->childNodes().constBegin(); + while (c != current->childNodes().constEnd()) { + if ((*c)->isQmlPropertyGroup() || (*c)->isJsPropertyGroup()) { + const QmlPropertyGroupNode* qpgn = static_cast<const QmlPropertyGroupNode*>(*c); + NodeList::ConstIterator p = qpgn->childNodes().constBegin(); + while (p != qpgn->childNodes().constEnd()) { + if ((*p)->isQmlProperty() || (*c)->isJsProperty()) { + QString key = (*p)->name(); + key = sortName(*p, &key); + all.memberMap.insert(key,*p); + classMap->second.insert(key,*p); + } + ++p; + } + } + else { + QString key = (*c)->name(); + key = sortName(*c, &key); + all.memberMap.insert(key,*c); + classMap->second.insert(key,*c); + } + ++c; + } + current = current->qmlBaseNode(); + while (current) { + if (current->isAbstract()) + break; + if (current->isInternal()) + current = current->qmlBaseNode(); + else + break; + } + } + append(sections, all, true); + } + } + + return sections; +} + +QT_END_NAMESPACE diff --git a/src/qdoc/cppcodemarker.h b/src/qdoc/cppcodemarker.h new file mode 100644 index 000000000..aa759f299 --- /dev/null +++ b/src/qdoc/cppcodemarker.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + cppcodemarker.h +*/ + +#ifndef CPPCODEMARKER_H +#define CPPCODEMARKER_H + +#include "codemarker.h" + +QT_BEGIN_NAMESPACE + +class CppCodeMarker : public CodeMarker +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::CppCodeMarker) + +public: + CppCodeMarker(); + ~CppCodeMarker(); + + virtual bool recognizeCode(const QString& code) Q_DECL_OVERRIDE; + virtual bool recognizeExtension(const QString& ext) Q_DECL_OVERRIDE; + virtual bool recognizeLanguage(const QString& lang) Q_DECL_OVERRIDE; + virtual Atom::AtomType atomType() const Q_DECL_OVERRIDE; + virtual QString markedUpCode(const QString& code, + const Node *relative, + const Location &location) Q_DECL_OVERRIDE; + virtual QString markedUpSynopsis(const Node *node, + const Node *relative, + SynopsisStyle style) Q_DECL_OVERRIDE; + virtual QString markedUpQmlItem(const Node *node, bool summary) Q_DECL_OVERRIDE; + virtual QString markedUpName(const Node *node) Q_DECL_OVERRIDE; + virtual QString markedUpFullName(const Node *node, const Node *relative) Q_DECL_OVERRIDE; + virtual QString markedUpEnumValue(const QString &enumValue, const Node *relative) Q_DECL_OVERRIDE; + virtual QString markedUpIncludes(const QStringList& includes) Q_DECL_OVERRIDE; + virtual QString functionBeginRegExp(const QString& funcName) Q_DECL_OVERRIDE; + virtual QString functionEndRegExp(const QString& funcName) Q_DECL_OVERRIDE; + virtual QList<Section> sections(const Aggregate *innerNode, + SynopsisStyle style, + Status status) Q_DECL_OVERRIDE; + virtual QList<Section> qmlSections(QmlTypeNode* qmlTypeNode, + SynopsisStyle style, + Status status = Okay) Q_DECL_OVERRIDE; + +private: + QString addMarkUp(const QString& protectedCode, + const Node *relative, + const Location &location); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/cppcodeparser.cpp b/src/qdoc/cppcodeparser.cpp new file mode 100644 index 000000000..0405cc2c2 --- /dev/null +++ b/src/qdoc/cppcodeparser.cpp @@ -0,0 +1,2611 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + cppcodeparser.cpp +*/ + +#include <qfile.h> +#include <stdio.h> +#include <errno.h> +#include "codechunk.h" +#include "config.h" +#include "cppcodeparser.h" +#include "tokenizer.h" +#include "qdocdatabase.h" +#include <qdebug.h> +#include "generator.h" + +QT_BEGIN_NAMESPACE + +/* qmake ignore Q_OBJECT */ + +static bool inMacroCommand_ = false; +static bool parsingHeaderFile_ = false; +QStringList CppCodeParser::exampleFiles; +QStringList CppCodeParser::exampleDirs; +CppCodeParser* CppCodeParser::cppParser_ = 0; + +/*! + The constructor initializes some regular expressions + and calls reset(). + */ +CppCodeParser::CppCodeParser() + : varComment("/\\*\\s*([a-zA-Z_0-9]+)\\s*\\*/"), sep("(?:<[^>]+>)?::") +{ + reset(); + cppParser_ = this; +} + +/*! + The destructor is trivial. + */ +CppCodeParser::~CppCodeParser() +{ + // nothing. +} + +/*! + The constructor initializes a map of special node types + for identifying important nodes. And it initializes + some filters for identifying certain kinds of files. + */ +void CppCodeParser::initializeParser(const Config &config) +{ + CodeParser::initializeParser(config); + + /* + All these can appear in a C++ namespace. Don't add + anything that can't be in a C++ namespace. + */ + nodeTypeMap.insert(COMMAND_NAMESPACE, Node::Namespace); + nodeTypeMap.insert(COMMAND_CLASS, Node::Class); + nodeTypeMap.insert(COMMAND_ENUM, Node::Enum); + nodeTypeMap.insert(COMMAND_TYPEDEF, Node::Typedef); + nodeTypeMap.insert(COMMAND_PROPERTY, Node::Property); + nodeTypeMap.insert(COMMAND_VARIABLE, Node::Variable); + + exampleFiles = config.getCanonicalPathList(CONFIG_EXAMPLES); + exampleDirs = config.getCanonicalPathList(CONFIG_EXAMPLEDIRS); + QStringList exampleFilePatterns = config.getStringList( + CONFIG_EXAMPLES + Config::dot + CONFIG_FILEEXTENSIONS); + + if (!exampleFilePatterns.isEmpty()) + exampleNameFilter = exampleFilePatterns.join(' '); + else + exampleNameFilter = "*.cpp *.h *.js *.xq *.svg *.xml *.dita *.ui"; + + QStringList exampleImagePatterns = config.getStringList( + CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS); + + if (!exampleImagePatterns.isEmpty()) + exampleImageFilter = exampleImagePatterns.join(' '); + else + exampleImageFilter = "*.png"; +} + +/*! + Clear the map of common node types and call + the same function in the base class. + */ +void CppCodeParser::terminateParser() +{ + nodeTypeMap.clear(); + CodeParser::terminateParser(); +} + +/*! + Returns "Cpp". + */ +QString CppCodeParser::language() +{ + return "Cpp"; +} + +/*! + Returns a list of extensions for header files. + */ +QStringList CppCodeParser::headerFileNameFilter() +{ + return QStringList() << "*.ch" << "*.h" << "*.h++" << "*.hh" << "*.hpp" << "*.hxx"; +} + +/*! + Returns a list of extensions for source files, i.e. not + header files. + */ +QStringList CppCodeParser::sourceFileNameFilter() +{ + return QStringList() << "*.c++" << "*.cc" << "*.cpp" << "*.cxx" << "*.mm"; +} + +/*! + Parse the C++ header file identified by \a filePath and add + the parsed contents to the database. The \a location is used + for reporting errors. + */ +void CppCodeParser::parseHeaderFile(const Location& location, const QString& filePath) +{ + QFile in(filePath); + currentFile_ = filePath; + if (!in.open(QIODevice::ReadOnly)) { + location.error(tr("Cannot open C++ header file '%1'").arg(filePath)); + currentFile_.clear(); + return; + } + + reset(); + Location fileLocation(filePath); + Tokenizer fileTokenizer(fileLocation, in); + tokenizer = &fileTokenizer; + readToken(); + parsingHeaderFile_ = true; + matchDeclList(qdb_->primaryTreeRoot()); + parsingHeaderFile_ = false; + if (!fileTokenizer.version().isEmpty()) + qdb_->setVersion(fileTokenizer.version()); + in.close(); + + if (fileLocation.fileName() == "qiterator.h") + parseQiteratorDotH(location, filePath); + currentFile_.clear(); +} + +/*! + Get ready to parse the C++ cpp file identified by \a filePath + and add its parsed contents to the database. \a location is + used for reporting errors. + + Call matchDocsAndStuff() to do all the parsing and tree building. + */ +void CppCodeParser::parseSourceFile(const Location& location, const QString& filePath) +{ + QFile in(filePath); + currentFile_ = filePath; + if (!in.open(QIODevice::ReadOnly)) { + location.error(tr("Cannot open C++ source file '%1' (%2)").arg(filePath).arg(strerror(errno))); + currentFile_.clear(); + return; + } + + reset(); + Location fileLocation(filePath); + Tokenizer fileTokenizer(fileLocation, in); + tokenizer = &fileTokenizer; + readToken(); + + /* + The set of open namespaces is cleared before parsing + each source file. The word "source" here means cpp file. + */ + qdb_->clearOpenNamespaces(); + + matchDocsAndStuff(); + in.close(); + currentFile_.clear(); +} + +/*! + This is called after all the C++ header files have been + parsed. The most important thing it does is resolve C++ + class inheritance links in the tree. It also initializes + a bunch of other collections. + */ +void CppCodeParser::doneParsingHeaderFiles() +{ + QMapIterator<QString, QString> i(sequentialIteratorClasses); + while (i.hasNext()) { + i.next(); + instantiateIteratorMacro(i.key(), i.value(), sequentialIteratorDefinition); + } + i = mutableSequentialIteratorClasses; + while (i.hasNext()) { + i.next(); + instantiateIteratorMacro(i.key(), i.value(), mutableSequentialIteratorDefinition); + } + i = associativeIteratorClasses; + while (i.hasNext()) { + i.next(); + instantiateIteratorMacro(i.key(), i.value(), associativeIteratorDefinition); + } + i = mutableAssociativeIteratorClasses; + while (i.hasNext()) { + i.next(); + instantiateIteratorMacro(i.key(), i.value(), mutableAssociativeIteratorDefinition); + } + sequentialIteratorDefinition.clear(); + mutableSequentialIteratorDefinition.clear(); + associativeIteratorDefinition.clear(); + mutableAssociativeIteratorDefinition.clear(); + sequentialIteratorClasses.clear(); + mutableSequentialIteratorClasses.clear(); + associativeIteratorClasses.clear(); + mutableAssociativeIteratorClasses.clear(); +} + +/*! + This is called after all the source files (i.e., not the + header files) have been parsed. Currently nothing to do. + */ +void CppCodeParser::doneParsingSourceFiles() +{ + // contents moved to QdocDatabase::resolveIssues() +} + +static QSet<QString> topicCommands_; +/*! + Returns the set of strings reopresenting the topic commands. + */ +const QSet<QString>& CppCodeParser::topicCommands() +{ + if (topicCommands_.isEmpty()) { + topicCommands_ << COMMAND_CLASS + << COMMAND_DITAMAP + << COMMAND_ENUM + << COMMAND_EXAMPLE + << COMMAND_EXTERNALPAGE + << COMMAND_FILE + << COMMAND_FN + << COMMAND_GROUP + << COMMAND_HEADERFILE + << COMMAND_MACRO + << COMMAND_MODULE + << COMMAND_NAMESPACE + << COMMAND_PAGE + << COMMAND_PROPERTY + << COMMAND_TYPEDEF + << COMMAND_VARIABLE + << COMMAND_QMLTYPE + << COMMAND_QMLPROPERTY + << COMMAND_QMLPROPERTYGROUP + << COMMAND_QMLATTACHEDPROPERTY + << COMMAND_QMLSIGNAL + << COMMAND_QMLATTACHEDSIGNAL + << COMMAND_QMLMETHOD + << COMMAND_QMLATTACHEDMETHOD + << COMMAND_QMLBASICTYPE + << COMMAND_QMLMODULE + << COMMAND_JSTYPE + << COMMAND_JSPROPERTY + << COMMAND_JSPROPERTYGROUP + << COMMAND_JSATTACHEDPROPERTY + << COMMAND_JSSIGNAL + << COMMAND_JSATTACHEDSIGNAL + << COMMAND_JSMETHOD + << COMMAND_JSATTACHEDMETHOD + << COMMAND_JSBASICTYPE + << COMMAND_JSMODULE; + } + return topicCommands_; +} + +/*! + Process the topic \a command found in the \a doc with argument \a arg. + */ +Node* CppCodeParser::processTopicCommand(const Doc& doc, + const QString& command, + const ArgLocPair& arg) +{ + ExtraFuncData extra; + if (command == COMMAND_FN) { + QStringList parentPath; + FunctionNode *func = 0; + FunctionNode *clone = 0; + + if (!makeFunctionNode(arg.first, &parentPath, &clone, extra) && + !makeFunctionNode("void " + arg.first, &parentPath, &clone, extra)) { + doc.startLocation().warning(tr("Invalid syntax in '\\%1'").arg(COMMAND_FN)); + } + else { + func = qdb_->findFunctionNode(parentPath, clone); + if (func == 0) { + if (parentPath.isEmpty() && !lastPath_.isEmpty()) + func = qdb_->findFunctionNode(lastPath_, clone); + } + + /* + If the node was not found, then search for it in the + open C++ namespaces. We don't expect this search to + be necessary often. Nor do we expect it to succeed + very often. + */ + if (func == 0) + func = qdb_->findNodeInOpenNamespace(parentPath, clone); + + if (func == 0) { + doc.location().warning(tr("Cannot find '%1' in '\\%2' %3") + .arg(clone->name() + "(...)") + .arg(COMMAND_FN) + .arg(arg.first), + tr("I cannot find any function of that name with the " + "specified signature. Make sure that the signature " + "is identical to the declaration, including 'const' " + "qualifiers.")); + } + else + lastPath_ = parentPath; + if (func) { + func->borrowParameterNames(clone); + func->setParentPath(clone->parentPath()); + } + delete clone; + } + return func; + } + else if (command == COMMAND_MACRO) { + QStringList parentPath; + FunctionNode *func = 0; + + extra.root = qdb_->primaryTreeRoot(); + extra.isMacro = true; + if (makeFunctionNode(arg.first, &parentPath, &func, extra)) { + if (!parentPath.isEmpty()) { + doc.startLocation().warning(tr("Invalid syntax in '\\%1'").arg(COMMAND_MACRO)); + delete func; + func = 0; + } + else { + func->setMetaness(FunctionNode::MacroWithParams); + QVector<Parameter> params = func->parameters(); + for (int i = 0; i < params.size(); ++i) { + Parameter ¶m = params[i]; + if (param.name().isEmpty() && !param.dataType().isEmpty() + && param.dataType() != "...") + param = Parameter("", "", param.dataType()); + } + func->setParameters(params); + } + return func; + } + else if (QRegExp("[A-Za-z_][A-Za-z0-9_]+").exactMatch(arg.first)) { + func = new FunctionNode(qdb_->primaryTreeRoot(), arg.first); + func->setAccess(Node::Public); + func->setLocation(doc.startLocation()); + func->setMetaness(FunctionNode::MacroWithoutParams); + } + else { + doc.location().warning(tr("Invalid syntax in '\\%1'").arg(COMMAND_MACRO)); + + } + return func; + } + else if (nodeTypeMap.contains(command)) { + /* + We should only get in here if the command refers to + something that can appear in a C++ namespace, + i.e. a class, another namespace, an enum, a typedef, + a property or a variable. I think these are handled + this way to allow the writer to refer to the entity + without including the namespace qualifier. + */ + Node::NodeType type = nodeTypeMap[command]; + QStringList paths = arg.first.split(QLatin1Char(' ')); + QStringList path = paths[0].split("::"); + Node *node = 0; + + node = qdb_->findNodeInOpenNamespace(path, type); + if (node == 0) + node = qdb_->findNodeByNameAndType(path, type); + if (node == 0) { + doc.location().warning(tr("Cannot find '%1' specified with '\\%2' in any header file") + .arg(arg.first).arg(command)); + lastPath_ = path; + + } + else if (node->isAggregate()) { + if (type == Node::Namespace) { + NamespaceNode* ns = static_cast<NamespaceNode*>(node); + ns->markSeen(); + } + /* + This treats a class as a namespace. + */ + if ((type == Node::Class) || (type == Node::Namespace)) { + if (path.size() > 1) { + path.pop_back(); + QString ns = path.join("::"); + qdb_->insertOpenNamespace(ns); + } + } + } + return node; + } + else if (command == COMMAND_EXAMPLE) { + if (Config::generateExamples) { + ExampleNode* en = new ExampleNode(qdb_->primaryTreeRoot(), arg.first); + en->setLocation(doc.startLocation()); + createExampleFileNodes(en); + return en; + } + } + else if (command == COMMAND_EXTERNALPAGE) { + DocumentNode* dn = new DocumentNode(qdb_->primaryTreeRoot(), + arg.first, + Node::ExternalPage, + Node::ArticlePage); + dn->setLocation(doc.startLocation()); + return dn; + } + else if (command == COMMAND_FILE) { + DocumentNode* dn = new DocumentNode(qdb_->primaryTreeRoot(), + arg.first, + Node::File, + Node::NoPageType); + dn->setLocation(doc.startLocation()); + return dn; + } + else if (command == COMMAND_HEADERFILE) { + DocumentNode* dn = new DocumentNode(qdb_->primaryTreeRoot(), + arg.first, + Node::HeaderFile, + Node::ApiPage); + dn->setLocation(doc.startLocation()); + return dn; + } + else if (command == COMMAND_GROUP) { + CollectionNode* cn = qdb_->addGroup(arg.first); + cn->setLocation(doc.startLocation()); + cn->markSeen(); + return cn; + } + else if (command == COMMAND_MODULE) { + CollectionNode* cn = qdb_->addModule(arg.first); + cn->setLocation(doc.startLocation()); + cn->markSeen(); + return cn; + } + else if (command == COMMAND_QMLMODULE) { + QStringList blankSplit = arg.first.split(QLatin1Char(' ')); + CollectionNode* cn = qdb_->addQmlModule(blankSplit[0]); + cn->setLogicalModuleInfo(blankSplit); + cn->setLocation(doc.startLocation()); + cn->markSeen(); + return cn; + } + else if (command == COMMAND_JSMODULE) { + QStringList blankSplit = arg.first.split(QLatin1Char(' ')); + CollectionNode* cn = qdb_->addJsModule(blankSplit[0]); + cn->setLogicalModuleInfo(blankSplit); + cn->setLocation(doc.startLocation()); + cn->markSeen(); + return cn; + } + else if (command == COMMAND_PAGE) { + Node::PageType ptype = Node::ArticlePage; + QStringList args = arg.first.split(QLatin1Char(' ')); + if (args.size() > 1) { + QString t = args[1].toLower(); + if (t == "howto") + ptype = Node::HowToPage; + else if (t == "api") + ptype = Node::ApiPage; + else if (t == "example") + ptype = Node::ExamplePage; + else if (t == "overview") + ptype = Node::OverviewPage; + else if (t == "tutorial") + ptype = Node::TutorialPage; + else if (t == "faq") + ptype = Node::FAQPage; + else if (t == "ditamap") + ptype = Node::DitaMapPage; + } + DocumentNode* dn = 0; + if (ptype == Node::DitaMapPage) + dn = new DitaMapNode(qdb_->primaryTreeRoot(), args[0]); + else + dn = new DocumentNode(qdb_->primaryTreeRoot(), args[0], Node::Page, ptype); + dn->setLocation(doc.startLocation()); + return dn; + } + else if (command == COMMAND_DITAMAP) { + DocumentNode* dn = new DitaMapNode(qdb_->primaryTreeRoot(), arg.first); + dn->setLocation(doc.startLocation()); + return dn; + } + else if ((command == COMMAND_QMLTYPE) || (command == COMMAND_JSTYPE)) { + QmlTypeNode* qcn = new QmlTypeNode(qdb_->primaryTreeRoot(), arg.first); + if (command == COMMAND_JSTYPE) + qcn->setGenus(Node::JS); + qcn->setLocation(doc.startLocation()); + return qcn; + } + else if ((command == COMMAND_QMLBASICTYPE) || (command == COMMAND_JSBASICTYPE)) { + QmlBasicTypeNode* n = new QmlBasicTypeNode(qdb_->primaryTreeRoot(), arg.first); + if (command == COMMAND_JSBASICTYPE) + n->setGenus(Node::JS); + n->setLocation(doc.startLocation()); + return n; + } + else if ((command == COMMAND_QMLSIGNAL) || + (command == COMMAND_QMLMETHOD) || + (command == COMMAND_QMLATTACHEDSIGNAL) || + (command == COMMAND_QMLATTACHEDMETHOD) || + (command == COMMAND_JSSIGNAL) || + (command == COMMAND_JSMETHOD) || + (command == COMMAND_JSATTACHEDSIGNAL) || + (command == COMMAND_JSATTACHEDMETHOD)) { + QString module; + QString qmlTypeName; + QString type; + if (splitQmlMethodArg(arg.first, type, module, qmlTypeName)) { + QmlTypeNode* qmlType = qdb_->findQmlType(module, qmlTypeName); + if (qmlType) { + bool attached = false; + Node::NodeType nodeType = Node::QmlMethod; + if ((command == COMMAND_QMLSIGNAL) || + (command == COMMAND_JSSIGNAL)) + nodeType = Node::QmlSignal; + else if ((command == COMMAND_QMLATTACHEDSIGNAL) || + (command == COMMAND_JSATTACHEDSIGNAL)) { + nodeType = Node::QmlSignal; + attached = true; + } + else if ((command == COMMAND_QMLMETHOD) || + (command == COMMAND_JSMETHOD)) { + // do nothing + } + else if ((command == COMMAND_QMLATTACHEDMETHOD) || + (command == COMMAND_JSATTACHEDMETHOD)) + attached = true; + else + return 0; // never get here. + FunctionNode* fn = makeFunctionNode(doc, + arg.first, + qmlType, + nodeType, + attached, + command); + if (fn) { + fn->setLocation(doc.startLocation()); + if ((command == COMMAND_JSSIGNAL) || + (command == COMMAND_JSMETHOD) || + (command == COMMAND_JSATTACHEDSIGNAL) || + (command == COMMAND_JSATTACHEDMETHOD)) + fn->setGenus(Node::JS); + } + return fn; + } + } + } + return 0; +} + +/*! + A QML property group argument has the form... + + <QML-module>::<QML-type>::<name> + + This function splits the argument into those parts. + A <QML-module> is the QML equivalent of a C++ namespace. + So this function splits \a arg on "::" and stores the + parts in \a module, \a qmlTypeName, and \a name, and returns + true. If any part is not found, a qdoc warning is emitted + and false is returned. + */ +bool CppCodeParser::splitQmlPropertyGroupArg(const QString& arg, + QString& module, + QString& qmlTypeName, + QString& name) +{ + QStringList colonSplit = arg.split("::"); + if (colonSplit.size() == 3) { + module = colonSplit[0]; + qmlTypeName = colonSplit[1]; + name = colonSplit[2]; + return true; + } + QString msg = "Unrecognizable QML module/component qualifier for " + arg; + location().warning(tr(msg.toLatin1().data())); + return false; +} + +/*! + A QML property argument has the form... + + <type> <QML-type>::<name> + <type> <QML-module>::<QML-type>::<name> + + This function splits the argument into one of those + two forms. The three part form is the old form, which + was used before the creation of Qt Quick 2 and Qt + Components. A <QML-module> is the QML equivalent of a + C++ namespace. So this function splits \a arg on "::" + and stores the parts in \a type, \a module, \a qmlTypeName, + and \a name, and returns \c true. If any part other than + \a module is not found, a qdoc warning is emitted and + false is returned. + + \note The two QML types \e{Component} and \e{QtObject} + never have a module qualifier. + */ +bool CppCodeParser::splitQmlPropertyArg(const QString& arg, + QString& type, + QString& module, + QString& qmlTypeName, + QString& name) +{ + QStringList blankSplit = arg.split(QLatin1Char(' ')); + if (blankSplit.size() > 1) { + type = blankSplit[0]; + QStringList colonSplit(blankSplit[1].split("::")); + if (colonSplit.size() == 3) { + module = colonSplit[0]; + qmlTypeName = colonSplit[1]; + name = colonSplit[2]; + return true; + } + if (colonSplit.size() == 2) { + module.clear(); + qmlTypeName = colonSplit[0]; + name = colonSplit[1]; + return true; + } + QString msg = "Unrecognizable QML module/component qualifier for " + arg; + location().warning(tr(msg.toLatin1().data())); + } + else { + QString msg = "Missing property type for " + arg; + location().warning(tr(msg.toLatin1().data())); + } + return false; +} + +/*! + A QML signal or method argument has the form... + + <type> <QML-type>::<name>(<param>, <param>, ...) + <type> <QML-module>::<QML-type>::<name>(<param>, <param>, ...) + + This function splits the \a{arg}ument into one of those + two forms, sets \a type, \a module, and \a qmlTypeName, + and returns true. If the argument doesn't match either + form, an error message is emitted and false is returned. + + \note The two QML types \e{Component} and \e{QtObject} never + have a module qualifier. + */ +bool CppCodeParser::splitQmlMethodArg(const QString& arg, + QString& type, + QString& module, + QString& qmlTypeName) +{ + QString name; + int leftParen = arg.indexOf(QChar('(')); + if (leftParen > 0) + name = arg.left(leftParen); + else + name = arg; + int firstBlank = name.indexOf(QChar(' ')); + if (firstBlank > 0) { + type = name.left(firstBlank); + name = name.right(name.length() - firstBlank - 1); + } + else + type.clear(); + + QStringList colonSplit(name.split("::")); + if (colonSplit.size() > 1) { + if (colonSplit.size() > 2) { + module = colonSplit[0]; + qmlTypeName = colonSplit[1]; + } + else { + module.clear(); + qmlTypeName = colonSplit[0]; + } + return true; + } + QString msg = "Unrecognizable QML module/component qualifier for " + arg; + location().warning(tr(msg.toLatin1().data())); + return false; +} + +/*! + Process the topic \a command group found in the \a doc with arguments \a args. + + Currently, this function is called only for \e{qmlproperty} + and \e{qmlattachedproperty}. + */ +void CppCodeParser::processQmlProperties(const Doc& doc, + NodeList& nodes, + DocList& docs, + bool jsProps) +{ + QString arg; + QString type; + QString topic; + QString module; + QString qmlTypeName; + QString property; + QmlPropertyNode* qpn = 0; + QmlTypeNode* qmlType = 0; + QmlPropertyGroupNode* qpgn = 0; + + Topic qmlPropertyGroupTopic; + const TopicList& topics = doc.topicsUsed(); + for (int i=0; i<topics.size(); ++i) { + if ((topics.at(i).topic == COMMAND_QMLPROPERTYGROUP) || + (topics.at(i).topic == COMMAND_JSPROPERTYGROUP)) { + qmlPropertyGroupTopic = topics.at(i); + break; + } + } + if (qmlPropertyGroupTopic.isEmpty() && topics.size() > 1) { + qmlPropertyGroupTopic = topics.at(0); + if (jsProps) + qmlPropertyGroupTopic.topic = COMMAND_JSPROPERTYGROUP; + else + qmlPropertyGroupTopic.topic = COMMAND_QMLPROPERTYGROUP; + arg = qmlPropertyGroupTopic.args; + if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property)) { + int i = property.indexOf('.'); + if (i != -1) { + property = property.left(i); + qmlPropertyGroupTopic.args = module + "::" + qmlTypeName + "::" + property; + doc.location().warning(tr("No QML property group command found; using \\%1 %2") + .arg(COMMAND_QMLPROPERTYGROUP).arg(qmlPropertyGroupTopic.args)); + } + else { + /* + Assumption: No '.' in the property name + means there is no property group. + */ + qmlPropertyGroupTopic.clear(); + } + } + } + + if (!qmlPropertyGroupTopic.isEmpty()) { + arg = qmlPropertyGroupTopic.args; + if (splitQmlPropertyGroupArg(arg, module, qmlTypeName, property)) { + qmlType = qdb_->findQmlType(module, qmlTypeName); + if (qmlType) { + qpgn = new QmlPropertyGroupNode(qmlType, property); + qpgn->setLocation(doc.startLocation()); + if (jsProps) + qpgn->setGenus(Node::JS); + nodes.append(qpgn); + docs.append(doc); + } + } + } + for (int i=0; i<topics.size(); ++i) { + if (topics.at(i).topic == COMMAND_QMLPROPERTYGROUP) { + continue; + } + topic = topics.at(i).topic; + arg = topics.at(i).args; + if ((topic == COMMAND_QMLPROPERTY) || (topic == COMMAND_QMLATTACHEDPROPERTY) || + (topic == COMMAND_JSPROPERTY) || (topic == COMMAND_JSATTACHEDPROPERTY)) { + bool attached = ((topic == COMMAND_QMLATTACHEDPROPERTY) || + (topic == COMMAND_JSATTACHEDPROPERTY)); + if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property)) { + qmlType = qdb_->findQmlType(module, qmlTypeName); + if (qmlType) { + if (qmlType->hasQmlProperty(property, attached) != 0) { + QString msg = tr("QML property documented multiple times: '%1'").arg(arg); + doc.startLocation().warning(msg); + } + else if (qpgn) { + qpn = new QmlPropertyNode(qpgn, property, type, attached); + qpn->setLocation(doc.startLocation()); + if (jsProps) + qpn->setGenus(Node::JS); + } + else { + qpn = new QmlPropertyNode(qmlType, property, type, attached); + qpn->setLocation(doc.startLocation()); + if (jsProps) + qpn->setGenus(Node::JS); + nodes.append(qpn); + docs.append(doc); + } + } + } + } else if (qpgn) { + doc.startLocation().warning( + tr("Invalid use of '\\%1'; not allowed in a '\\%2'").arg( + topic, qmlPropertyGroupTopic.topic)); + } + } +} + +static QSet<QString> otherMetaCommands_; +/*! + Returns the set of strings representing the common metacommands + plus some other metacommands. + */ +const QSet<QString>& CppCodeParser::otherMetaCommands() +{ + if (otherMetaCommands_.isEmpty()) { + otherMetaCommands_ = commonMetaCommands(); + otherMetaCommands_ << COMMAND_INHEADERFILE + << COMMAND_OVERLOAD + << COMMAND_REIMP + << COMMAND_RELATES + << COMMAND_CONTENTSPAGE + << COMMAND_NEXTPAGE + << COMMAND_PREVIOUSPAGE + << COMMAND_INDEXPAGE + << COMMAND_STARTPAGE + << COMMAND_QMLINHERITS + << COMMAND_QMLINSTANTIATES + << COMMAND_QMLDEFAULT + << COMMAND_QMLREADONLY + << COMMAND_QMLABSTRACT + << COMMAND_ABSTRACT; + } + return otherMetaCommands_; +} + +/*! + Process the metacommand \a command in the context of the + \a node associated with the topic command and the \a doc. + \a arg is the argument to the metacommand. + */ +void CppCodeParser::processOtherMetaCommand(const Doc& doc, + const QString& command, + const ArgLocPair& argLocPair, + Node *node) +{ + QString arg = argLocPair.first; + if (command == COMMAND_INHEADERFILE) { + if (node != 0 && node->isAggregate()) { + ((Aggregate *) node)->addInclude(arg); + } + else { + doc.location().warning(tr("Ignored '\\%1'").arg(COMMAND_INHEADERFILE)); + } + } + else if (command == COMMAND_OVERLOAD) { + if (node && node->isFunction()) + ((FunctionNode *) node)->setOverloadFlag(true); + else + doc.location().warning(tr("Ignored '\\%1'").arg(COMMAND_OVERLOAD)); + } + else if (command == COMMAND_REIMP) { + if (node != 0 && node->parent() && !node->parent()->isInternal()) { + if (node->type() == Node::Function) { + FunctionNode *func = (FunctionNode *) node; + const FunctionNode *from = func->reimplementedFrom(); + if (from == 0) { + doc.location().warning(tr("Cannot find base function for '\\%1' in %2()") + .arg(COMMAND_REIMP).arg(node->name()), + tr("The function either doesn't exist in any " + "base class with the same signature or it " + "exists but isn't virtual.")); + } + /* + Ideally, we would enable this check to warn whenever + \reimp is used incorrectly, and only make the node + internal if the function is a reimplementation of + another function in a base class. + */ + else if (from->access() == Node::Private + || from->parent()->access() == Node::Private) { + doc.location().warning(tr("'\\%1' in %2() should be '\\internal' " + "because its base function is private " + "or internal").arg(COMMAND_REIMP).arg(node->name())); + } + func->setReimplemented(true); + } + else { + doc.location().warning(tr("Ignored '\\%1' in %2").arg(COMMAND_REIMP).arg(node->name())); + } + } + } + else if (command == COMMAND_RELATES) { + QStringList path = arg.split("::"); + Node* n = qdb_->findRelatesNode(path); + if (!n) { + // Store just a string to write to the index file + if (Generator::preparing()) + node->setRelates(arg); + else + doc.location().warning(tr("Cannot find '%1' in '\\%2'").arg(arg).arg(COMMAND_RELATES)); + + } + else if (node->parent() != n) + node->setRelates(static_cast<Aggregate*>(n)); + else + doc.location().warning(tr("Invalid use of '\\%1' (already a member of '%2')") + .arg(COMMAND_RELATES, arg)); + } + else if (command == COMMAND_CONTENTSPAGE) { + setLink(node, Node::ContentsLink, arg); + } + else if (command == COMMAND_NEXTPAGE) { + setLink(node, Node::NextLink, arg); + } + else if (command == COMMAND_PREVIOUSPAGE) { + setLink(node, Node::PreviousLink, arg); + } + else if (command == COMMAND_INDEXPAGE) { + setLink(node, Node::IndexLink, arg); + } + else if (command == COMMAND_STARTPAGE) { + setLink(node, Node::StartLink, arg); + } + else if (command == COMMAND_QMLINHERITS) { + if (node->name() == arg) + doc.location().warning(tr("%1 tries to inherit itself").arg(arg)); + else if (node->isQmlType() || node->isJsType()) { + QmlTypeNode* qmlType = static_cast<QmlTypeNode*>(node); + qmlType->setQmlBaseName(arg); + QmlTypeNode::addInheritedBy(arg,node); + } + } + else if (command == COMMAND_QMLINSTANTIATES) { + if (node->isQmlType() || node->isJsType()) { + ClassNode* classNode = qdb_->findClassNode(arg.split("::")); + if (classNode) + node->setClassNode(classNode); + else + doc.location().warning(tr("C++ class %1 not found: \\instantiates %1").arg(arg)); + } + else + doc.location().warning(tr("\\instantiates is only allowed in \\qmltype")); + } + else if (command == COMMAND_QMLDEFAULT) { + if (node->type() == Node::QmlProperty) { + QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node); + qpn->setDefault(); + } + else if (node->type() == Node::QmlPropertyGroup) { + QmlPropertyGroupNode* qpgn = static_cast<QmlPropertyGroupNode*>(node); + NodeList::ConstIterator p = qpgn->childNodes().constBegin(); + while (p != qpgn->childNodes().constEnd()) { + if ((*p)->type() == Node::QmlProperty) { + QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(*p); + qpn->setDefault(); + } + ++p; + } + } + } + else if (command == COMMAND_QMLREADONLY) { + if (node->type() == Node::QmlProperty) { + QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node); + qpn->setReadOnly(1); + } + else if (node->type() == Node::QmlPropertyGroup) { + QmlPropertyGroupNode* qpgn = static_cast<QmlPropertyGroupNode*>(node); + NodeList::ConstIterator p = qpgn->childNodes().constBegin(); + while (p != qpgn->childNodes().constEnd()) { + if ((*p)->type() == Node::QmlProperty) { + QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(*p); + qpn->setReadOnly(1); + } + ++p; + } + } + } + else if ((command == COMMAND_QMLABSTRACT) || (command == COMMAND_ABSTRACT)) { + if (node->isQmlType() || node->isJsType()) + node->setAbstract(true); + } + else { + processCommonMetaCommand(doc.location(),command,argLocPair,node); + } +} + +/*! + The topic command has been processed resulting in the \a doc + and \a node passed in here. Process the other meta commands, + which are found in \a doc, in the context of the topic \a node. + */ +void CppCodeParser::processOtherMetaCommands(const Doc& doc, Node *node) +{ + const QSet<QString> metaCommands = doc.metaCommandsUsed(); + QSet<QString>::ConstIterator cmd = metaCommands.constBegin(); + while (cmd != metaCommands.constEnd()) { + ArgList args = doc.metaCommandArgs(*cmd); + ArgList::ConstIterator arg = args.constBegin(); + while (arg != args.constEnd()) { + processOtherMetaCommand(doc, *cmd, *arg, node); + ++arg; + } + ++cmd; + } +} + +/*! + Resets the C++ code parser to its default initialized state. + */ +void CppCodeParser::reset() +{ + tokenizer = 0; + tok = 0; + access = Node::Public; + metaness_ = FunctionNode::Plain; + lastPath_.clear(); + physicalModuleName.clear(); +} + +/*! + Get the next token from the file being parsed and store it + in the token variable. + */ +void CppCodeParser::readToken() +{ + tok = tokenizer->getToken(); +} + +/*! + Return the current location in the file being parsed, + i.e. the file name, line number, and column number. + */ +const Location& CppCodeParser::location() +{ + return tokenizer->location(); +} + +/*! + Return the previous string read from the file being parsed. + */ +QString CppCodeParser::previousLexeme() +{ + return tokenizer->previousLexeme(); +} + +/*! + Return the current string string from the file being parsed. + */ +QString CppCodeParser::lexeme() +{ + return tokenizer->lexeme(); +} + +bool CppCodeParser::match(int target) +{ + if (tok == target) { + readToken(); + return true; + } + return false; +} + +/*! + Skip to \a target. If \a target is found before the end + of input, return true. Otherwise return false. + */ +bool CppCodeParser::skipTo(int target) +{ + while ((tok != Tok_Eoi) && (tok != target)) + readToken(); + return tok == target; +} + +/*! + If the current token is one of the keyword thingees that + are used in Qt, skip over it to the next token and return + true. Otherwise just return false without reading the + next token. + */ +bool CppCodeParser::matchCompat() +{ + switch (tok) { + case Tok_QT_COMPAT: + case Tok_QT_COMPAT_CONSTRUCTOR: + case Tok_QT_DEPRECATED: + case Tok_QT_MOC_COMPAT: + case Tok_QT3_SUPPORT: + case Tok_QT3_SUPPORT_CONSTRUCTOR: + case Tok_QT3_MOC_SUPPORT: + readToken(); + return true; + default: + return false; + } +} + +bool CppCodeParser::matchModuleQualifier(QString& name) +{ + bool matches = (lexeme() == QString('.')); + if (matches) { + do { + name += lexeme(); + readToken(); + } while ((tok == Tok_Ident) || (lexeme() == QString('.'))); + } + return matches; +} + +bool CppCodeParser::matchTemplateAngles(CodeChunk *dataType) +{ + bool matches = (tok == Tok_LeftAngle); + if (matches) { + int leftAngleDepth = 0; + int parenAndBraceDepth = 0; + do { + if (tok == Tok_LeftAngle) { + leftAngleDepth++; + } + else if (tok == Tok_RightAngle) { + leftAngleDepth--; + } + else if (tok == Tok_LeftParen || tok == Tok_LeftBrace) { + ++parenAndBraceDepth; + } + else if (tok == Tok_RightParen || tok == Tok_RightBrace) { + if (--parenAndBraceDepth < 0) + return false; + } + if (dataType != 0) + dataType->append(lexeme()); + readToken(); + } while (leftAngleDepth > 0 && tok != Tok_Eoi); + } + return matches; +} + +/* + This function is no longer used. + */ +bool CppCodeParser::matchTemplateHeader() +{ + readToken(); + return matchTemplateAngles(); +} + +bool CppCodeParser::matchDataType(CodeChunk *dataType, QString *var) +{ + /* + This code is really hard to follow... sorry. The loop is there to match + Alpha::Beta::Gamma::...::Omega. + */ + for (;;) { + bool virgin = true; + + if (tok != Tok_Ident) { + /* + There is special processing for 'Foo::operator int()' + and such elsewhere. This is the only case where we + return something with a trailing gulbrandsen ('Foo::'). + */ + if (tok == Tok_operator) + return true; + + /* + People may write 'const unsigned short' or + 'short unsigned const' or any other permutation. + */ + while (match(Tok_const) || match(Tok_volatile)) + dataType->append(previousLexeme()); + while (match(Tok_signed) || match(Tok_unsigned) || + match(Tok_short) || match(Tok_long) || match(Tok_int64)) { + dataType->append(previousLexeme()); + virgin = false; + } + while (match(Tok_const) || match(Tok_volatile)) + dataType->append(previousLexeme()); + + if (match(Tok_Tilde)) + dataType->append(previousLexeme()); + } + + if (virgin) { + if (match(Tok_Ident)) { + /* + This is a hack until we replace this "parser" + with the real one used in Qt Creator. + */ + if (!inMacroCommand_ && lexeme() == "(" && + ((previousLexeme() == "QT_PREPEND_NAMESPACE") || (previousLexeme() == "NS"))) { + readToken(); + readToken(); + dataType->append(previousLexeme()); + readToken(); + } + else + dataType->append(previousLexeme()); + } + else if (match(Tok_void) || match(Tok_int) || match(Tok_char) || + match(Tok_double) || match(Tok_Ellipsis)) { + dataType->append(previousLexeme()); + } + else { + return false; + } + } + else if (match(Tok_int) || match(Tok_char) || match(Tok_double)) { + dataType->append(previousLexeme()); + } + + matchTemplateAngles(dataType); + + while (match(Tok_const) || match(Tok_volatile)) + dataType->append(previousLexeme()); + + if (match(Tok_Gulbrandsen)) + dataType->append(previousLexeme()); + else + break; + } + + while (match(Tok_Ampersand) || match(Tok_Aster) || match(Tok_const) || + match(Tok_Caret)) + dataType->append(previousLexeme()); + + if (match(Tok_LeftParenAster)) { + /* + A function pointer. This would be rather hard to handle without a + tokenizer hack, because a type can be followed with a left parenthesis + in some cases (e.g., 'operator int()'). The tokenizer recognizes '(*' + as a single token. + */ + dataType->append(previousLexeme()); + dataType->appendHotspot(); + if (var != 0 && match(Tok_Ident)) + *var = previousLexeme(); + if (!match(Tok_RightParen) || tok != Tok_LeftParen) { + return false; + } + dataType->append(previousLexeme()); + + int parenDepth0 = tokenizer->parenDepth(); + while (tokenizer->parenDepth() >= parenDepth0 && tok != Tok_Eoi) { + dataType->append(lexeme()); + readToken(); + } + if (match(Tok_RightParen)) + dataType->append(previousLexeme()); + } + else { + /* + The common case: Look for an optional identifier, then for + some array brackets. + */ + dataType->appendHotspot(); + + if (var != 0) { + if (match(Tok_Ident)) { + *var = previousLexeme(); + } + else if (match(Tok_Comment)) { + /* + A neat hack: Commented-out parameter names are + recognized by qdoc. It's impossible to illustrate + here inside a C-style comment, because it requires + an asterslash. It's also impossible to illustrate + inside a C++-style comment, because the explanation + does not fit on one line. + */ + if (varComment.exactMatch(previousLexeme())) + *var = varComment.cap(1); + } + } + + if (tok == Tok_LeftBracket) { + int bracketDepth0 = tokenizer->bracketDepth(); + while ((tokenizer->bracketDepth() >= bracketDepth0 && + tok != Tok_Eoi) || + tok == Tok_RightBracket) { + dataType->append(lexeme()); + readToken(); + } + } + } + return true; +} + +/*! + Parse the next function parameter, if there is one, and + append it to parameter vector \a pvect. Return true if + a parameter is parsed and appended to \a pvect. + Otherwise return false. + */ +bool CppCodeParser::matchParameter(QVector<Parameter>& pvect, bool& isQPrivateSignal) +{ + if (match(Tok_QPrivateSignal)) { + isQPrivateSignal = true; + return true; + } + + Parameter p; + CodeChunk chunk; + if (!matchDataType(&chunk, &p.name_)) { + return false; + } + p.dataType_ = chunk.toString(); + chunk.clear(); + match(Tok_Comment); + if (match(Tok_Equal)) { + int pdepth = tokenizer->parenDepth(); + while (tokenizer->parenDepth() >= pdepth && + (tok != Tok_Comma || (tokenizer->parenDepth() > pdepth)) && + tok != Tok_Eoi) { + chunk.append(lexeme()); + readToken(); + } + } + p.defaultValue_ = chunk.toString(); + pvect.append(p); + return true; +} + +/*! + If the current token is any of several function modifiers, + return that token value after reading the next token. If it + is not one of the function modieifer tokens, return -1 but + don\t read the next token. + */ +int CppCodeParser::matchFunctionModifier() +{ + switch (tok) { + case Tok_friend: + case Tok_inline: + case Tok_explicit: + case Tok_static: + case Tok_QT_DEPRECATED: + readToken(); + return tok; + case Tok_QT_COMPAT: + case Tok_QT_COMPAT_CONSTRUCTOR: + case Tok_QT_MOC_COMPAT: + case Tok_QT3_SUPPORT: + case Tok_QT3_SUPPORT_CONSTRUCTOR: + case Tok_QT3_MOC_SUPPORT: + readToken(); + return Tok_QT_COMPAT; + default: + break; + } + return -1; +} + +bool CppCodeParser::matchFunctionDecl(Aggregate *parent, + QStringList *parentPathPtr, + FunctionNode **funcPtr, + const QString &templateStuff, + ExtraFuncData& extra) +{ + CodeChunk returnType; + QStringList parentPath; + QString name; + + bool matched_QT_DEPRECATED = false; + bool matched_friend = false; + bool matched_static = false; + bool matched_inline = false; + bool matched_explicit = false; + bool matched_compat = false; + + int token = tok; + while (token != -1) { + switch (token) { + case Tok_friend: + matched_friend = true; + break; + case Tok_inline: + matched_inline = true; + break; + case Tok_explicit: + matched_explicit = true; + break; + case Tok_static: + matched_static = true; + break; + case Tok_QT_DEPRECATED: + // no break here. + matched_QT_DEPRECATED = true; + case Tok_QT_COMPAT: + matched_compat = true; + break; + } + token = matchFunctionModifier(); + } + + FunctionNode::Virtualness virtuality = FunctionNode::NonVirtual; + if (match(Tok_virtual)) { + virtuality = FunctionNode::NormalVirtual; + if (!matched_compat) + matched_compat = matchCompat(); + } + + if (!matchDataType(&returnType)) { + if (tokenizer->parsingFnOrMacro() + && (match(Tok_Q_DECLARE_FLAGS) || + match(Tok_Q_PROPERTY) || + match(Tok_Q_PRIVATE_PROPERTY))) + returnType = CodeChunk(previousLexeme()); + else { + return false; + } + } + + if (returnType.toString() == "QBool") + returnType = CodeChunk("bool"); + + if (!matched_compat) + matched_compat = matchCompat(); + + if (tok == Tok_operator && + (returnType.toString().isEmpty() || + returnType.toString().endsWith("::"))) { + // 'QString::operator const char *()' + parentPath = returnType.toString().split(sep); + parentPath.removeAll(QString()); + returnType = CodeChunk(); + readToken(); + + CodeChunk restOfName; + if (tok != Tok_Tilde && matchDataType(&restOfName)) { + name = "operator " + restOfName.toString(); + } + else { + name = previousLexeme() + lexeme(); + readToken(); + while (tok != Tok_LeftParen && tok != Tok_Eoi) { + name += lexeme(); + readToken(); + } + } + if (tok != Tok_LeftParen) { + return false; + } + } + else if (tok == Tok_LeftParen) { + // constructor or destructor + parentPath = returnType.toString().split(sep); + if (!parentPath.isEmpty()) { + name = parentPath.last(); + parentPath.erase(parentPath.end() - 1); + } + returnType = CodeChunk(); + } + else { + while (match(Tok_Ident)) { + name = previousLexeme(); + /* + This is a hack to let QML module identifiers through. + */ + matchModuleQualifier(name); + matchTemplateAngles(); + + if (match(Tok_Gulbrandsen)) + parentPath.append(name); + else + break; + } + + if (tok == Tok_operator) { + name = lexeme(); + readToken(); + while (tok != Tok_Eoi) { + name += lexeme(); + readToken(); + if (tok == Tok_LeftParen) + break; + } + } + if (parent && (tok == Tok_Semicolon || + tok == Tok_LeftBracket || + tok == Tok_Colon) + && access != Node::Private) { + if (tok == Tok_LeftBracket) { + returnType.appendHotspot(); + + int bracketDepth0 = tokenizer->bracketDepth(); + while ((tokenizer->bracketDepth() >= bracketDepth0 && + tok != Tok_Eoi) || + tok == Tok_RightBracket) { + returnType.append(lexeme()); + readToken(); + } + if (tok != Tok_Semicolon) { + return false; + } + } + else if (tok == Tok_Colon) { + returnType.appendHotspot(); + + while (tok != Tok_Semicolon && tok != Tok_Eoi) { + returnType.append(lexeme()); + readToken(); + } + if (tok != Tok_Semicolon) { + return false; + } + } + + VariableNode *var = new VariableNode(parent, name); + var->setAccess(access); + var->setLocation(location()); + var->setLeftType(returnType.left()); + var->setRightType(returnType.right()); + if (matched_compat) + var->setStatus(Node::Compat); + var->setStatic(matched_static); + return false; + } + if (tok != Tok_LeftParen) + return false; + } + readToken(); + + // A left paren was seen. Parse the parameters + bool isQPrivateSignal = false; + QVector<Parameter> pvect; + if (tok != Tok_RightParen) { + do { + if (!matchParameter(pvect, isQPrivateSignal)) + return false; + } while (match(Tok_Comma)); + } + // The parameters must end with a right paren + if (!match(Tok_RightParen)) + return false; + + // look for const + bool matchedConst = match(Tok_const); + + // look for 0 indicating pure virtual + if (match(Tok_Equal) && match(Tok_Number)) + virtuality = FunctionNode::PureVirtual; + + // look for colon indicating ctors which must be skipped + if (match(Tok_Colon)) { + while (tok != Tok_LeftBrace && tok != Tok_Eoi) + readToken(); + } + + // If no ';' expect a body, which must be skipped. + bool body_expected = false; + bool body_present = false; + if (!match(Tok_Semicolon) && tok != Tok_Eoi) { + body_expected = true; + int nesting = tokenizer->braceDepth(); + if (!match(Tok_LeftBrace)) + return false; + // skip the body + while (tokenizer->braceDepth() >= nesting && tok != Tok_Eoi) + readToken(); + body_present = true; + match(Tok_RightBrace); + } + + FunctionNode *func = 0; + bool createFunctionNode = false; + if (parsingHeaderFile_) { + if (matched_friend) { + if (matched_inline) { + // nothing yet + } + if (body_present) { + if (body_expected) { + // nothing yet + } + createFunctionNode = true; + if (parent && parent->parent()) + parent = parent->parent(); + else + return false; + } + } + else + createFunctionNode = true; + } + else + createFunctionNode = true; + + if (createFunctionNode) { + func = new FunctionNode(extra.type, parent, name, extra.isAttached); + if (matched_friend) + access = Node::Public; + func->setAccess(access); + func->setLocation(location()); + func->setReturnType(returnType.toString()); + func->setParentPath(parentPath); + func->setTemplateStuff(templateStuff); + if (matched_compat) + func->setStatus(Node::Compat); + if (matched_QT_DEPRECATED) + func->setStatus(Node::Deprecated); + if (matched_explicit) { /* What can be done? */ } + func->setMetaness(metaness_); + if (parent) { + if (name == parent->name()) + func->setMetaness(FunctionNode::Ctor); + else if (name.startsWith(QLatin1Char('~'))) + func->setMetaness(FunctionNode::Dtor); + } + func->setStatic(matched_static); + func->setConst(matchedConst); + func->setVirtualness(virtuality); + if (isQPrivateSignal) + func->setPrivateSignal(); + if (!pvect.isEmpty()) { + func->setParameters(pvect); + } + } + if (parentPathPtr != 0) + *parentPathPtr = parentPath; + if (funcPtr != 0) + *funcPtr = func; + return true; +} + +bool CppCodeParser::matchBaseSpecifier(ClassNode *classe, bool isClass) +{ + Node::Access access; + + switch (tok) { + case Tok_public: + access = Node::Public; + readToken(); + break; + case Tok_protected: + access = Node::Protected; + readToken(); + break; + case Tok_private: + access = Node::Private; + readToken(); + break; + default: + access = isClass ? Node::Private : Node::Public; + } + + if (tok == Tok_virtual) + readToken(); + + CodeChunk baseClass; + if (!matchDataType(&baseClass)) + return false; + + classe->addUnresolvedBaseClass(access, baseClass.toPath(), baseClass.toString()); + return true; +} + +bool CppCodeParser::matchBaseList(ClassNode *classe, bool isClass) +{ + for (;;) { + if (!matchBaseSpecifier(classe, isClass)) + return false; + if (tok == Tok_LeftBrace) + return true; + if (!match(Tok_Comma)) + return false; + } +} + +/*! + Parse a C++ class, union, or struct declaration. + + This function only handles one level of class nesting, but that is + sufficient for Qt because there are no cases of class nesting more + than one level deep. + */ +bool CppCodeParser::matchClassDecl(Aggregate *parent, + const QString &templateStuff) +{ + bool isClass = (tok == Tok_class); + readToken(); + + bool compat = matchCompat(); + + if (tok != Tok_Ident) + return false; + while (tok == Tok_Ident) + readToken(); + if (tok == Tok_Gulbrandsen) { + Node* n = parent->findChildNode(previousLexeme(),Node::Class); + if (n) { + parent = static_cast<Aggregate*>(n); + if (parent) { + readToken(); + if (tok != Tok_Ident) + return false; + readToken(); + } + } + } + if (tok != Tok_Colon && tok != Tok_LeftBrace) + return false; + + /* + So far, so good. We have 'class Foo {' or 'class Foo :'. + This is enough to recognize a class definition. + */ + ClassNode *classe = new ClassNode(parent, previousLexeme()); + classe->setAccess(access); + classe->setLocation(location()); + if (compat) + classe->setStatus(Node::Compat); + if (!physicalModuleName.isEmpty()) + classe->setPhysicalModuleName(physicalModuleName); + classe->setTemplateStuff(templateStuff); + + if (match(Tok_Colon) && !matchBaseList(classe, isClass)) + return false; + if (!match(Tok_LeftBrace)) + return false; + + Node::Access outerAccess = access; + access = isClass ? Node::Private : Node::Public; + FunctionNode::Metaness outerMetaness = metaness_; + metaness_ = FunctionNode::Plain; + + bool matches = (matchDeclList(classe) && match(Tok_RightBrace) && + match(Tok_Semicolon)); + access = outerAccess; + metaness_ = outerMetaness; + return matches; +} + +bool CppCodeParser::matchNamespaceDecl(Aggregate *parent) +{ + readToken(); // skip 'namespace' + if (tok != Tok_Ident) + return false; + while (tok == Tok_Ident) + readToken(); + if (tok != Tok_LeftBrace) + return false; + + /* + So far, so good. We have 'namespace Foo {'. + */ + QString namespaceName = previousLexeme(); + NamespaceNode* ns = 0; + if (parent) + ns = static_cast<NamespaceNode*>(parent->findChildNode(namespaceName, Node::Namespace)); + if (!ns) { + ns = new NamespaceNode(parent, namespaceName); + ns->setAccess(access); + ns->setLocation(location()); + } + + readToken(); // skip '{' + bool matched = matchDeclList(ns); + return matched && match(Tok_RightBrace); +} + +/*! + Match a C++ \c using clause. Return \c true if the match + is successful. Otherwise false. + + If the \c using clause is for a namespace, an open namespace + <is inserted for qdoc to look in to find things. + + If the \c using clause is a base class member function, the + member function is added to \a parent as an unresolved + \c using clause. + */ +bool CppCodeParser::matchUsingDecl(Aggregate* parent) +{ + bool usingNamespace = false; + readToken(); // skip 'using' + + if (tok == Tok_namespace) { + usingNamespace = true; + readToken(); + } + + int openLeftAngles = 0; + int openLeftParens = 0; + bool usingOperator = false; + QString name; + while (tok != Tok_Semicolon) { + if ((tok != Tok_Ident) && (tok != Tok_Gulbrandsen)) { + if (tok == Tok_LeftAngle) { + ++openLeftAngles; + } + else if (tok == Tok_RightAngle) { + if (openLeftAngles <= 0) + return false; + --openLeftAngles; + } + else if (tok == Tok_Comma) { + if (openLeftAngles <= 0) + return false; + } + else if (tok == Tok_operator) { + usingOperator = true; + } + else if (tok == Tok_SomeOperator) { + if (!usingOperator) + return false; + } + else if (tok == Tok_LeftParen) { + ++openLeftParens; + } + else if (tok == Tok_RightParen) { + if (openLeftParens <= 0) + return false; + --openLeftParens; + } + else { + return false; + } + } + name += lexeme(); + readToken(); + } + + if (usingNamespace) { + // 'using namespace Foo;'. + qdb_->insertOpenNamespace(name); + } + else if (parent && parent->isClass()) { + ClassNode* cn = static_cast<ClassNode*>(parent); + cn->addUnresolvedUsingClause(name); + } + return true; +} + +bool CppCodeParser::matchEnumItem(Aggregate *parent, EnumNode *enume) +{ + if (!match(Tok_Ident)) + return false; + + QString name = previousLexeme(); + CodeChunk val; + int parenLevel = 0; + + if (match(Tok_Equal)) { + while (tok != Tok_RightBrace && tok != Tok_Eoi) { + if (tok == Tok_LeftParen) + parenLevel++; + else if (tok == Tok_RightParen) + parenLevel--; + else if (tok == Tok_Comma) { + if (parenLevel <= 0) + break; + } + val.append(lexeme()); + readToken(); + } + } + + if (enume) { + QString strVal = val.toString(); + if (strVal.isEmpty()) { + if (enume->items().isEmpty()) { + strVal = "0"; + } + else { + QString last = enume->items().last().value(); + bool ok; + int n = last.toInt(&ok); + if (ok) { + if (last.startsWith(QLatin1Char('0')) && last.size() > 1) { + if (last.startsWith("0x") || last.startsWith("0X")) + strVal = last.left(2) + QString::number(n + 1, 16); + else + strVal = QLatin1Char('0') + QString::number(n + 1, 8); + } + else + strVal = QString::number(n + 1); + } + } + } + + enume->addItem(EnumItem(name, strVal)); + } + else { + VariableNode *var = new VariableNode(parent, name); + var->setAccess(access); + var->setLocation(location()); + var->setLeftType("const int"); + var->setStatic(true); + } + return true; +} + +bool CppCodeParser::matchEnumDecl(Aggregate *parent) +{ + QString name; + + if (!match(Tok_enum)) + return false; + if (match(Tok_Ident)) + name = previousLexeme(); + if (tok != Tok_LeftBrace) + return false; + + EnumNode *enume = 0; + + if (!name.isEmpty()) { + enume = new EnumNode(parent, name); + enume->setAccess(access); + enume->setLocation(location()); + } + + readToken(); + + if (!matchEnumItem(parent, enume)) + return false; + + while (match(Tok_Comma)) { + if (!matchEnumItem(parent, enume)) + return false; + } + return match(Tok_RightBrace) && match(Tok_Semicolon); +} + +bool CppCodeParser::matchTypedefDecl(Aggregate *parent) +{ + CodeChunk dataType; + QString name; + + if (!match(Tok_typedef)) + return false; + if (!matchDataType(&dataType, &name)) + return false; + if (!match(Tok_Semicolon)) + return false; + + if (parent && !parent->findChildNode(name, Node::Typedef)) { + TypedefNode* td = new TypedefNode(parent, name); + td->setAccess(access); + td->setLocation(location()); + } + return true; +} + +bool CppCodeParser::matchProperty(Aggregate *parent) +{ + int expected_tok = Tok_LeftParen; + if (match(Tok_Q_PRIVATE_PROPERTY)) { + expected_tok = Tok_Comma; + if (!skipTo(Tok_Comma)) + return false; + } + else if (!match(Tok_Q_PROPERTY) && + !match(Tok_Q_OVERRIDE) && + !match(Tok_QDOC_PROPERTY)) { + return false; + } + + if (!match(expected_tok)) + return false; + + QString name; + CodeChunk dataType; + if (!matchDataType(&dataType, &name)) + return false; + + PropertyNode *property = new PropertyNode(parent, name); + property->setAccess(Node::Public); + property->setLocation(location()); + property->setDataType(dataType.toString()); + + while (tok != Tok_RightParen && tok != Tok_Eoi) { + if (!match(Tok_Ident)) + return false; + QString key = previousLexeme(); + QString value; + + // Keywords with no associated values + if (key == "CONSTANT") { + property->setConstant(); + continue; + } + else if (key == "FINAL") { + property->setFinal(); + continue; + } + + if (match(Tok_Ident) || match(Tok_Number)) { + value = previousLexeme(); + } + else if (match(Tok_LeftParen)) { + int depth = 1; + while (tok != Tok_Eoi) { + if (tok == Tok_LeftParen) { + readToken(); + ++depth; + } else if (tok == Tok_RightParen) { + readToken(); + if (--depth == 0) + break; + } else { + readToken(); + } + } + value = "?"; + } + + if (key == "READ") + qdb_->addPropertyFunction(property, value, PropertyNode::Getter); + else if (key == "WRITE") { + qdb_->addPropertyFunction(property, value, PropertyNode::Setter); + property->setWritable(true); + } + else if (key == "STORED") + property->setStored(value.toLower() == "true"); + else if (key == "DESIGNABLE") { + QString v = value.toLower(); + if (v == "true") + property->setDesignable(true); + else if (v == "false") + property->setDesignable(false); + else { + property->setDesignable(false); + property->setRuntimeDesFunc(value); + } + } + else if (key == "RESET") + qdb_->addPropertyFunction(property, value, PropertyNode::Resetter); + else if (key == "NOTIFY") { + qdb_->addPropertyFunction(property, value, PropertyNode::Notifier); + } else if (key == "REVISION") { + int revision; + bool ok; + revision = value.toInt(&ok); + if (ok) + property->setRevision(revision); + else + location().warning(tr("Invalid revision number: %1").arg(value)); + } else if (key == "SCRIPTABLE") { + QString v = value.toLower(); + if (v == "true") + property->setScriptable(true); + else if (v == "false") + property->setScriptable(false); + else { + property->setScriptable(false); + property->setRuntimeScrFunc(value); + } + } + } + match(Tok_RightParen); + return true; +} + +/*! + Parse a C++ declaration. + */ +bool CppCodeParser::matchDeclList(Aggregate *parent) +{ + ExtraFuncData extra; + QString templateStuff; + int braceDepth0 = tokenizer->braceDepth(); + if (tok == Tok_RightBrace) // prevents failure on empty body + braceDepth0++; + + while (tokenizer->braceDepth() >= braceDepth0 && tok != Tok_Eoi) { + switch (tok) { + case Tok_Colon: + readToken(); + break; + case Tok_class: + case Tok_struct: + case Tok_union: + matchClassDecl(parent, templateStuff); + break; + case Tok_namespace: + matchNamespaceDecl(parent); + break; + case Tok_using: + matchUsingDecl(parent); + break; + case Tok_template: + { + CodeChunk dataType; + readToken(); + matchTemplateAngles(&dataType); + templateStuff = dataType.toString(); + } + continue; + case Tok_enum: + matchEnumDecl(parent); + break; + case Tok_typedef: + matchTypedefDecl(parent); + break; + case Tok_private: + readToken(); + access = Node::Private; + metaness_ = FunctionNode::Plain; + break; + case Tok_protected: + readToken(); + access = Node::Protected; + metaness_ = FunctionNode::Plain; + break; + case Tok_public: + readToken(); + access = Node::Public; + metaness_ = FunctionNode::Plain; + break; + case Tok_signals: + case Tok_Q_SIGNALS: + readToken(); + access = Node::Public; + metaness_ = FunctionNode::Signal; + break; + case Tok_slots: + case Tok_Q_SLOTS: + readToken(); + metaness_ = FunctionNode::Slot; + break; + case Tok_Q_OBJECT: + readToken(); + break; + case Tok_Q_OVERRIDE: + case Tok_Q_PROPERTY: + case Tok_Q_PRIVATE_PROPERTY: + case Tok_QDOC_PROPERTY: + if (!matchProperty(parent)) { + location().warning(tr("Failed to parse token %1 in property declaration").arg(lexeme())); + skipTo(Tok_RightParen); + match(Tok_RightParen); + } + break; + case Tok_Q_DECLARE_SEQUENTIAL_ITERATOR: + readToken(); + if (match(Tok_LeftParen) && match(Tok_Ident)) + sequentialIteratorClasses.insert(previousLexeme(), location().fileName()); + match(Tok_RightParen); + break; + case Tok_Q_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR: + readToken(); + if (match(Tok_LeftParen) && match(Tok_Ident)) + mutableSequentialIteratorClasses.insert(previousLexeme(), location().fileName()); + match(Tok_RightParen); + break; + case Tok_Q_DECLARE_ASSOCIATIVE_ITERATOR: + readToken(); + if (match(Tok_LeftParen) && match(Tok_Ident)) + associativeIteratorClasses.insert(previousLexeme(), location().fileName()); + match(Tok_RightParen); + break; + case Tok_Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR: + readToken(); + if (match(Tok_LeftParen) && match(Tok_Ident)) + mutableAssociativeIteratorClasses.insert(previousLexeme(), location().fileName()); + match(Tok_RightParen); + break; + case Tok_Q_DECLARE_FLAGS: + readToken(); + if (match(Tok_LeftParen) && match(Tok_Ident)) { + QString flagsType = previousLexeme(); + if (match(Tok_Comma) && match(Tok_Ident)) { + QString name = previousLexeme(); + TypedefNode *flagsNode = new TypedefNode(parent, flagsType); + flagsNode->setAccess(access); + flagsNode->setLocation(location()); + EnumNode* en = static_cast<EnumNode*>(parent->findChildNode(name, Node::Enum)); + if (en) + en->setFlagsType(flagsNode); + } + } + match(Tok_RightParen); + break; + case Tok_QT_MODULE: + readToken(); + if (match(Tok_LeftParen) && match(Tok_Ident)) + physicalModuleName = previousLexeme(); + if (!physicalModuleName.startsWith("Qt")) + physicalModuleName.prepend("Qt"); + match(Tok_RightParen); + break; + default: + if (!matchFunctionDecl(parent, 0, 0, templateStuff, extra)) { + while (tok != Tok_Eoi && + (tokenizer->braceDepth() > braceDepth0 || + (!match(Tok_Semicolon) && + tok != Tok_public && tok != Tok_protected && + tok != Tok_private))) { + readToken(); + } + } + } + templateStuff.clear(); + } + return true; +} + +/*! + This is called by parseSourceFile() to do the actual parsing + and tree building. + */ +bool CppCodeParser::matchDocsAndStuff() +{ + ExtraFuncData extra; + const QSet<QString>& topicCommandsAllowed = topicCommands(); + const QSet<QString>& otherMetacommandsAllowed = otherMetaCommands(); + const QSet<QString>& metacommandsAllowed = topicCommandsAllowed + otherMetacommandsAllowed; + + while (tok != Tok_Eoi) { + if (tok == Tok_Doc) { + /* + lexeme() returns an entire qdoc comment. + */ + QString comment = lexeme(); + Location start_loc(location()); + readToken(); + + Doc::trimCStyleComment(start_loc,comment); + Location end_loc(location()); + + /* + Doc parses the comment. + */ + Doc doc(start_loc,end_loc,comment,metacommandsAllowed, topicCommandsAllowed); + QString topic; + bool isQmlPropertyTopic = false; + bool isJsPropertyTopic = false; + + const TopicList& topics = doc.topicsUsed(); + if (!topics.isEmpty()) { + topic = topics[0].topic; + if ((topic == COMMAND_QMLPROPERTY) || + (topic == COMMAND_QMLPROPERTYGROUP) || + (topic == COMMAND_QMLATTACHEDPROPERTY)) { + isQmlPropertyTopic = true; + } + else if ((topic == COMMAND_JSPROPERTY) || + (topic == COMMAND_JSPROPERTYGROUP) || + (topic == COMMAND_JSATTACHEDPROPERTY)) { + isJsPropertyTopic = true; + } + } + NodeList nodes; + DocList docs; + + if (topic.isEmpty()) { + QStringList parentPath; + FunctionNode *clone; + FunctionNode *func = 0; + + if (matchFunctionDecl(0, &parentPath, &clone, QString(), extra)) { + func = qdb_->findFunctionNode(parentPath, clone); + /* + If the node was not found, then search for it in the + open C++ namespaces. We don't expect this search to + be necessary often. Nor do we expect it to succeed + very often. + */ + if (func == 0) + func = qdb_->findNodeInOpenNamespace(parentPath, clone); + + if (func) { + func->borrowParameterNames(clone); + nodes.append(func); + docs.append(doc); + } + delete clone; + } + else { + doc.location().warning(tr("Cannot tie this documentation to anything"), + tr("I found a /*! ... */ comment, but there was no " + "topic command (e.g., '\\%1', '\\%2') in the " + "comment and no function definition following " + "the comment.") + .arg(COMMAND_FN).arg(COMMAND_PAGE)); + } + } + else if (isQmlPropertyTopic || isJsPropertyTopic) { + Doc nodeDoc = doc; + processQmlProperties(nodeDoc, nodes, docs, isJsPropertyTopic); + } + else { + ArgList args; + const QSet<QString>& topicCommandsUsed = topicCommandsAllowed & doc.metaCommandsUsed(); + if (topicCommandsUsed.count() > 0) { + topic = *topicCommandsUsed.constBegin(); + args = doc.metaCommandArgs(topic); + } + if (topicCommandsUsed.count() > 1) { + QString topics; + QSet<QString>::ConstIterator t = topicCommandsUsed.constBegin(); + while (t != topicCommandsUsed.constEnd()) { + topics += " \\" + *t + QLatin1Char(','); + ++t; + } + topics[topics.lastIndexOf(',')] = '.'; + int i = topics.lastIndexOf(','); + topics[i] = ' '; + topics.insert(i+1,"and"); + doc.location().warning(tr("Multiple topic commands found in comment: %1").arg(topics)); + } + ArgList::ConstIterator a = args.constBegin(); + while (a != args.constEnd()) { + Doc nodeDoc = doc; + Node *node = processTopicCommand(nodeDoc,topic,*a); + if (node != 0) { + nodes.append(node); + docs.append(nodeDoc); + } + ++a; + } + } + + NodeList::Iterator n = nodes.begin(); + QList<Doc>::Iterator d = docs.begin(); + while (n != nodes.end()) { + processOtherMetaCommands(*d, *n); + (*n)->setDoc(*d); + checkModuleInclusion(*n); + if ((*n)->isAggregate() && ((Aggregate *)*n)->includes().isEmpty()) { + Aggregate *m = static_cast<Aggregate *>(*n); + while (m->parent() && m->physicalModuleName().isEmpty()) { + m = m->parent(); + } + if (m == *n) + ((Aggregate *)*n)->addInclude((*n)->name()); + else + ((Aggregate *)*n)->setIncludes(m->includes()); + } + ++d; + ++n; + } + } + else if (tok == Tok_using) { + matchUsingDecl(0); + } + else { + QStringList parentPath; + FunctionNode *clone; + FunctionNode *node = 0; + + if (matchFunctionDecl(0, &parentPath, &clone, QString(), extra)) { + /* + The location of the definition is more interesting + than that of the declaration. People equipped with + a sophisticated text editor can respond to warnings + concerning undocumented functions very quickly. + + Signals are implemented in uninteresting files + generated by moc. + */ + node = qdb_->findFunctionNode(parentPath, clone); + if (node != 0 && node->metaness() != FunctionNode::Signal) + node->setLocation(clone->location()); + delete clone; + } + else { + if (tok != Tok_Doc) + readToken(); + } + } + } + return true; +} + +/*! + This function uses a Tokenizer to parse the function \a signature + in an attempt to match it to the signature of a child node of \a root. + If a match is found, \a funcPtr is set to point to the matching node + and true is returned. + */ +bool CppCodeParser::makeFunctionNode(const QString& signature, + QStringList* parentPathPtr, + FunctionNode** funcPtr, + ExtraFuncData& extra) +{ + Tokenizer* outerTokenizer = tokenizer; + int outerTok = tok; + + QByteArray latin1 = signature.toLatin1(); + Tokenizer stringTokenizer(location(), latin1); + stringTokenizer.setParsingFnOrMacro(true); + tokenizer = &stringTokenizer; + readToken(); + + inMacroCommand_ = extra.isMacro; + bool ok = matchFunctionDecl(extra.root, parentPathPtr, funcPtr, QString(), extra); + inMacroCommand_ = false; + // potential memory leak with funcPtr + + tokenizer = outerTokenizer; + tok = outerTok; + return ok; +} + +/*! + This function uses a Tokenizer to parse the \a parameters of a + function into the parameter vector \a {pvect}. + */ +bool CppCodeParser::parseParameters(const QString& parameters, + QVector<Parameter>& pvect, + bool& isQPrivateSignal) +{ + Tokenizer* outerTokenizer = tokenizer; + int outerTok = tok; + + QByteArray latin1 = parameters.toLatin1(); + Tokenizer stringTokenizer(Location(), latin1); + stringTokenizer.setParsingFnOrMacro(true); + tokenizer = &stringTokenizer; + readToken(); + + inMacroCommand_ = false; + do { + if (!matchParameter(pvect, isQPrivateSignal)) + return false; + } while (match(Tok_Comma)); + + tokenizer = outerTokenizer; + tok = outerTok; + return true; +} + +/*! + Create a new FunctionNode for a QML method or signal, as + specified by \a type, as a child of \a parent. \a sig is + the complete signature, and if \a attached is true, the + method or signal is "attached". \a qdoctag is the text of + the \a type. + + \a parent is the QML class node. The QML module and QML + type names have already been consumed to find \a parent. + What remains in \a sig is the method signature. The method + must be a child of \a parent. + */ +FunctionNode* CppCodeParser::makeFunctionNode(const Doc& doc, + const QString& sig, + Aggregate* parent, + Node::NodeType type, + bool attached, + QString qdoctag) +{ + QStringList pp; + FunctionNode* fn = 0; + ExtraFuncData extra(parent, type, attached); + if (!makeFunctionNode(sig, &pp, &fn, extra) && !makeFunctionNode("void " + sig, &pp, &fn, extra)) { + doc.location().warning(tr("Invalid syntax in '\\%1'").arg(qdoctag)); + } + return fn; +} + +void CppCodeParser::parseQiteratorDotH(const Location &location, const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QFile::ReadOnly)) + return; + + QString text = file.readAll(); + text.remove("\r"); + text.remove("\\\n"); + QStringList lines = text.split(QLatin1Char('\n')); + lines = lines.filter("Q_DECLARE"); + lines.replaceInStrings(QRegExp("#define Q[A-Z_]*\\(C\\)"), QString()); + + if (lines.size() == 4) { + sequentialIteratorDefinition = lines[0]; + mutableSequentialIteratorDefinition = lines[1]; + associativeIteratorDefinition = lines[2]; + mutableAssociativeIteratorDefinition = lines[3]; + } + else { + location.warning(tr("The qiterator.h hack failed")); + } +} + +void CppCodeParser::instantiateIteratorMacro(const QString &container, + const QString &includeFile, + const QString ¯oDef) +{ + QString resultingCode = macroDef; + resultingCode.replace(QRegExp("\\bC\\b"), container); + resultingCode.remove(QRegExp("\\s*##\\s*")); + + Location loc(includeFile); // hack to get the include file for free + QByteArray latin1 = resultingCode.toLatin1(); + Tokenizer stringTokenizer(loc, latin1); + tokenizer = &stringTokenizer; + readToken(); + matchDeclList(QDocDatabase::qdocDB()->primaryTreeRoot()); +} + +void CppCodeParser::createExampleFileNodes(DocumentNode *dn) +{ + QString examplePath = dn->name(); + QString proFileName = examplePath + QLatin1Char('/') + examplePath.split(QLatin1Char('/')).last() + ".pro"; + QString userFriendlyFilePath; + + QString fullPath = Config::findFile(dn->doc().location(), + exampleFiles, + exampleDirs, + proFileName, + userFriendlyFilePath); + + if (fullPath.isEmpty()) { + QString tmp = proFileName; + proFileName = examplePath + QLatin1Char('/') + "qbuild.pro"; + userFriendlyFilePath.clear(); + fullPath = Config::findFile(dn->doc().location(), + exampleFiles, + exampleDirs, + proFileName, + userFriendlyFilePath); + if (fullPath.isEmpty()) { + proFileName = examplePath + QLatin1Char('/') + examplePath.split(QLatin1Char('/')).last() + ".qmlproject"; + userFriendlyFilePath.clear(); + fullPath = Config::findFile(dn->doc().location(), + exampleFiles, + exampleDirs, + proFileName, + userFriendlyFilePath); + if (fullPath.isEmpty()) { + QString details = QLatin1String("Example directories: ") + exampleDirs.join(QLatin1Char(' ')); + if (!exampleFiles.isEmpty()) + details += QLatin1String(", example files: ") + exampleFiles.join(QLatin1Char(' ')); + dn->location().warning(tr("Cannot find file '%1' or '%2'").arg(tmp).arg(proFileName), details); + dn->location().warning(tr(" EXAMPLE PATH DOES NOT EXIST: %1").arg(examplePath), details); + return; + } + } + } + + int sizeOfBoringPartOfName = fullPath.size() - proFileName.size(); + if (fullPath.startsWith("./")) + sizeOfBoringPartOfName = sizeOfBoringPartOfName - 2; + fullPath.truncate(fullPath.lastIndexOf('/')); + + QStringList exampleFiles = Config::getFilesHere(fullPath,exampleNameFilter); + QString imagesPath = fullPath + "/images"; + QStringList imageFiles = Config::getFilesHere(imagesPath,exampleImageFilter); + if (!exampleFiles.isEmpty()) { + // move main.cpp and to the end, if it exists + QString mainCpp; + QMutableStringListIterator i(exampleFiles); + i.toBack(); + while (i.hasPrevious()) { + QString fileName = i.previous(); + if (fileName.endsWith("/main.cpp")) { + mainCpp = fileName; + i.remove(); + } + else if (fileName.contains("/qrc_") || fileName.contains("/moc_") + || fileName.contains("/ui_")) + i.remove(); + } + if (!mainCpp.isEmpty()) + exampleFiles.append(mainCpp); + + // add any qmake Qt resource files and qmake project files + exampleFiles += Config::getFilesHere(fullPath, "*.qrc *.pro *.qmlproject qmldir"); + } + + foreach (const QString &exampleFile, exampleFiles) { + new DocumentNode(dn, + exampleFile.mid(sizeOfBoringPartOfName), + Node::File, + Node::NoPageType); + } + foreach (const QString &imageFile, imageFiles) { + new DocumentNode(dn, + imageFile.mid(sizeOfBoringPartOfName), + Node::Image, + Node::NoPageType); + } +} + +QT_END_NAMESPACE diff --git a/src/qdoc/cppcodeparser.h b/src/qdoc/cppcodeparser.h new file mode 100644 index 000000000..ec0448232 --- /dev/null +++ b/src/qdoc/cppcodeparser.h @@ -0,0 +1,255 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CPPCODEPARSER_H +#define CPPCODEPARSER_H + +#include <qregexp.h> + +#include "codeparser.h" + +QT_BEGIN_NAMESPACE + +class ClassNode; +class CodeChunk; +class CppCodeParserPrivate; +class FunctionNode; +class Aggregate; +class Tokenizer; + +class CppCodeParser : public CodeParser +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::CppCodeParser) + + struct ExtraFuncData { + Aggregate* root; // Used as the parent. + Node::NodeType type; // The node type: Function, etc. + bool isAttached; // If true, the method is attached. + bool isMacro; // If true, we are parsing a macro signature. + ExtraFuncData() : root(0), type(Node::Function), isAttached(false), isMacro(false) { } + ExtraFuncData(Aggregate* r, Node::NodeType t, bool a) + : root(r), type(t), isAttached(a), isMacro(false) { } + }; + +public: + CppCodeParser(); + ~CppCodeParser(); + static CppCodeParser* cppParser() { return cppParser_; } + + virtual void initializeParser(const Config& config) Q_DECL_OVERRIDE; + virtual void terminateParser() Q_DECL_OVERRIDE; + virtual QString language() Q_DECL_OVERRIDE; + virtual QStringList headerFileNameFilter() Q_DECL_OVERRIDE; + virtual QStringList sourceFileNameFilter() Q_DECL_OVERRIDE; + virtual void parseHeaderFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; + virtual void parseSourceFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; + virtual void doneParsingHeaderFiles() Q_DECL_OVERRIDE; + virtual void doneParsingSourceFiles() Q_DECL_OVERRIDE; + bool parseParameters(const QString& parameters, QVector<Parameter>& pvect, bool& isQPrivateSignal); + +protected: + const QSet<QString>& topicCommands(); + const QSet<QString>& otherMetaCommands(); + virtual Node* processTopicCommand(const Doc& doc, + const QString& command, + const ArgLocPair& arg); + void processQmlProperties(const Doc& doc, NodeList& nodes, DocList& docs, bool jsProps); + bool splitQmlPropertyGroupArg(const QString& arg, + QString& module, + QString& element, + QString& name); + bool splitQmlPropertyArg(const QString& arg, + QString& type, + QString& module, + QString& element, + QString& name); + bool splitQmlMethodArg(const QString& arg, + QString& type, + QString& module, + QString& element); + virtual void processOtherMetaCommand(const Doc& doc, + const QString& command, + const ArgLocPair& argLocPair, + Node *node); + void processOtherMetaCommands(const Doc& doc, Node *node); + + protected: + void reset(); + void readToken(); + const Location& location(); + QString previousLexeme(); + QString lexeme(); + + private: + bool match(int target); + bool skipTo(int target); + bool matchCompat(); + bool matchModuleQualifier(QString& name); + bool matchTemplateAngles(CodeChunk *type = 0); + bool matchTemplateHeader(); + bool matchDataType(CodeChunk *type, QString *var = 0); + bool matchParameter(QVector<Parameter>& pvect, bool& isQPrivateSignal); + bool matchFunctionDecl(Aggregate *parent, + QStringList *parentPathPtr, + FunctionNode **funcPtr, + const QString &templateStuff, + ExtraFuncData& extra); + bool matchBaseSpecifier(ClassNode *classe, bool isClass); + bool matchBaseList(ClassNode *classe, bool isClass); + bool matchClassDecl(Aggregate *parent, + const QString &templateStuff = QString()); + bool matchNamespaceDecl(Aggregate *parent); + bool matchUsingDecl(Aggregate* parent); + bool matchEnumItem(Aggregate *parent, EnumNode *enume); + bool matchEnumDecl(Aggregate *parent); + bool matchTypedefDecl(Aggregate *parent); + bool matchProperty(Aggregate *parent); + bool matchDeclList(Aggregate *parent); + bool matchDocsAndStuff(); + bool makeFunctionNode(const QString &synopsis, + QStringList *parentPathPtr, + FunctionNode **funcPtr, + ExtraFuncData& params); + FunctionNode* makeFunctionNode(const Doc& doc, + const QString& sig, + Aggregate* parent, + Node::NodeType type, + bool attached, + QString qdoctag); + void parseQiteratorDotH(const Location &location, const QString &filePath); + void instantiateIteratorMacro(const QString &container, + const QString &includeFile, + const QString ¯oDef); + void createExampleFileNodes(DocumentNode *dn); + int matchFunctionModifier(); + + protected: + QMap<QString, Node::NodeType> nodeTypeMap; + Tokenizer *tokenizer; + int tok; + Node::Access access; + FunctionNode::Metaness metaness_; + QString physicalModuleName; + QStringList lastPath_; + QRegExp varComment; + QRegExp sep; + + private: + QString sequentialIteratorDefinition; + QString mutableSequentialIteratorDefinition; + QString associativeIteratorDefinition; + QString mutableAssociativeIteratorDefinition; + QMap<QString, QString> sequentialIteratorClasses; + QMap<QString, QString> mutableSequentialIteratorClasses; + QMap<QString, QString> associativeIteratorClasses; + QMap<QString, QString> mutableAssociativeIteratorClasses; + + static QStringList exampleFiles; + static QStringList exampleDirs; + static CppCodeParser* cppParser_; + QString exampleNameFilter; + QString exampleImageFilter; +}; + +#define COMMAND_ABSTRACT Doc::alias("abstract") +#define COMMAND_CLASS Doc::alias("class") +#define COMMAND_CONTENTSPAGE Doc::alias("contentspage") +#define COMMAND_DITAMAP Doc::alias("ditamap") +#define COMMAND_ENUM Doc::alias("enum") +#define COMMAND_EXAMPLE Doc::alias("example") +#define COMMAND_EXTERNALPAGE Doc::alias("externalpage") +#define COMMAND_FILE Doc::alias("file") +#define COMMAND_FN Doc::alias("fn") +#define COMMAND_GROUP Doc::alias("group") +#define COMMAND_HEADERFILE Doc::alias("headerfile") +#define COMMAND_INDEXPAGE Doc::alias("indexpage") +#define COMMAND_INHEADERFILE Doc::alias("inheaderfile") +#define COMMAND_MACRO Doc::alias("macro") +#define COMMAND_MODULE Doc::alias("module") +#define COMMAND_NAMESPACE Doc::alias("namespace") +#define COMMAND_OVERLOAD Doc::alias("overload") +#define COMMAND_NEXTPAGE Doc::alias("nextpage") +#define COMMAND_PAGE Doc::alias("page") +#define COMMAND_PREVIOUSPAGE Doc::alias("previouspage") +#define COMMAND_PROPERTY Doc::alias("property") +#define COMMAND_REIMP Doc::alias("reimp") +#define COMMAND_RELATES Doc::alias("relates") +#define COMMAND_STARTPAGE Doc::alias("startpage") +#define COMMAND_TYPEDEF Doc::alias("typedef") +#define COMMAND_VARIABLE Doc::alias("variable") +#define COMMAND_QMLABSTRACT Doc::alias("qmlabstract") +#define COMMAND_QMLTYPE Doc::alias("qmltype") +#define COMMAND_QMLPROPERTY Doc::alias("qmlproperty") +#define COMMAND_QMLPROPERTYGROUP Doc::alias("qmlpropertygroup") +#define COMMAND_QMLATTACHEDPROPERTY Doc::alias("qmlattachedproperty") +#define COMMAND_QMLINHERITS Doc::alias("inherits") +#define COMMAND_QMLINSTANTIATES Doc::alias("instantiates") +#define COMMAND_QMLSIGNAL Doc::alias("qmlsignal") +#define COMMAND_QMLATTACHEDSIGNAL Doc::alias("qmlattachedsignal") +#define COMMAND_QMLMETHOD Doc::alias("qmlmethod") +#define COMMAND_QMLATTACHEDMETHOD Doc::alias("qmlattachedmethod") +#define COMMAND_QMLDEFAULT Doc::alias("default") +#define COMMAND_QMLREADONLY Doc::alias("readonly") +#define COMMAND_QMLBASICTYPE Doc::alias("qmlbasictype") +#define COMMAND_QMLMODULE Doc::alias("qmlmodule") +#define COMMAND_AUDIENCE Doc::alias("audience") +#define COMMAND_CATEGORY Doc::alias("category") +#define COMMAND_PRODNAME Doc::alias("prodname") +#define COMMAND_COMPONENT Doc::alias("component") +#define COMMAND_AUTHOR Doc::alias("author") +#define COMMAND_PUBLISHER Doc::alias("publisher") +#define COMMAND_COPYRYEAR Doc::alias("copyryear") +#define COMMAND_COPYRHOLDER Doc::alias("copyrholder") +#define COMMAND_PERMISSIONS Doc::alias("permissions") +#define COMMAND_LIFECYCLEVERSION Doc::alias("lifecycleversion") +#define COMMAND_LIFECYCLEWSTATUS Doc::alias("lifecyclestatus") +#define COMMAND_LICENSEYEAR Doc::alias("licenseyear") +#define COMMAND_LICENSENAME Doc::alias("licensename") +#define COMMAND_LICENSEDESCRIPTION Doc::alias("licensedescription") +#define COMMAND_RELEASEDATE Doc::alias("releasedate") +#define COMMAND_QTVARIABLE Doc::alias("qtvariable") +// Some of these are not used currenmtly, but they are included now for completeness. +#define COMMAND_JSTYPE Doc::alias("jstype") +#define COMMAND_JSPROPERTY Doc::alias("jsproperty") +#define COMMAND_JSPROPERTYGROUP Doc::alias("jspropertygroup") +#define COMMAND_JSATTACHEDPROPERTY Doc::alias("jsattachedproperty") +#define COMMAND_JSSIGNAL Doc::alias("jssignal") +#define COMMAND_JSATTACHEDSIGNAL Doc::alias("jsattachedsignal") +#define COMMAND_JSMETHOD Doc::alias("jsmethod") +#define COMMAND_JSATTACHEDMETHOD Doc::alias("jsattachedmethod") +#define COMMAND_JSBASICTYPE Doc::alias("jsbasictype") +#define COMMAND_JSMODULE Doc::alias("jsmodule") + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/doc.cpp b/src/qdoc/doc.cpp new file mode 100644 index 000000000..4ed589454 --- /dev/null +++ b/src/qdoc/doc.cpp @@ -0,0 +1,3380 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "config.h" +#include "doc.h" +#include "codemarker.h" +#include "editdistance.h" +#include "openedlist.h" +#include "quoter.h" +#include "text.h" +#include "atom.h" +#include "tokenizer.h" +#include <qdatetime.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <qhash.h> +#include <qtextstream.h> +#include <qregexp.h> +#include <ctype.h> +#include <limits.h> +#include <qdebug.h> +#include "generator.h" + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QSet<QString>, null_Set_QString) +Q_GLOBAL_STATIC(TopicList, nullTopicList) +Q_GLOBAL_STATIC(QStringList, null_QStringList) +Q_GLOBAL_STATIC(QList<Text>, null_QList_Text) +Q_GLOBAL_STATIC(QStringMultiMap, null_QStringMultiMap) + +struct Macro +{ + QString defaultDef; + Location defaultDefLocation; + QStringMap otherDefs; + int numParams; +}; + +enum { + CMD_A, + CMD_ANNOTATEDLIST, + CMD_B, + CMD_BADCODE, + CMD_BOLD, + CMD_BR, + CMD_BRIEF, + CMD_C, + CMD_CAPTION, + CMD_CHAPTER, + CMD_CODE, + CMD_CODELINE, + CMD_DIV, + CMD_DOTS, + CMD_E, + CMD_ELSE, + CMD_ENDCHAPTER, + CMD_ENDCODE, + CMD_ENDDIV, + CMD_ENDFOOTNOTE, + CMD_ENDIF, + CMD_ENDLEGALESE, + CMD_ENDLINK, + CMD_ENDLIST, + CMD_ENDMAPREF, + CMD_ENDOMIT, + CMD_ENDPART, + CMD_ENDQUOTATION, + CMD_ENDRAW, + CMD_ENDSECTION1, + CMD_ENDSECTION2, + CMD_ENDSECTION3, + CMD_ENDSECTION4, + CMD_ENDSIDEBAR, + CMD_ENDTABLE, + CMD_ENDTOPICREF, + CMD_FOOTNOTE, + CMD_GENERATELIST, + CMD_GRANULARITY, + CMD_HEADER, + CMD_HR, + CMD_I, + CMD_IF, + CMD_IMAGE, + CMD_IMPORTANT, + CMD_INCLUDE, + CMD_INLINEIMAGE, + CMD_INDEX, + CMD_INPUT, + CMD_KEYWORD, + CMD_L, + CMD_LEGALESE, + CMD_LI, + CMD_LINK, + CMD_LIST, + CMD_MAPREF, + CMD_META, + CMD_NEWCODE, + CMD_NOTE, + CMD_O, + CMD_OLDCODE, + CMD_OMIT, + CMD_OMITVALUE, + CMD_OVERLOAD, + CMD_PART, + CMD_PRINTLINE, + CMD_PRINTTO, + CMD_PRINTUNTIL, + CMD_QUOTATION, + CMD_QUOTEFILE, + CMD_QUOTEFROMFILE, + CMD_QUOTEFUNCTION, + CMD_RAW, + CMD_ROW, + CMD_SA, + CMD_SECTION1, + CMD_SECTION2, + CMD_SECTION3, + CMD_SECTION4, + CMD_SIDEBAR, + CMD_SINCELIST, + CMD_SKIPLINE, + CMD_SKIPTO, + CMD_SKIPUNTIL, + CMD_SNIPPET, + CMD_SPAN, + CMD_SUB, + CMD_SUP, + CMD_TABLE, + CMD_TABLEOFCONTENTS, + CMD_TARGET, + CMD_TOPICREF, + CMD_TT, + CMD_UICONTROL, + CMD_UNDERLINE, + CMD_UNICODE, + CMD_VALUE, + CMD_WARNING, + CMD_QML, + CMD_ENDQML, + CMD_CPP, + CMD_ENDCPP, + CMD_QMLTEXT, + CMD_ENDQMLTEXT, + CMD_CPPTEXT, + CMD_ENDCPPTEXT, + CMD_JS, + CMD_ENDJS, + NOT_A_CMD +}; + +static struct { + const char *english; + int no; + QString *alias; +} cmds[] = { + { "a", CMD_A, 0 }, + { "annotatedlist", CMD_ANNOTATEDLIST, 0 }, + { "b", CMD_B, 0 }, + { "badcode", CMD_BADCODE, 0 }, + { "bold", CMD_BOLD, 0 }, + { "br", CMD_BR, 0 }, + { "brief", CMD_BRIEF, 0 }, + { "c", CMD_C, 0 }, + { "caption", CMD_CAPTION, 0 }, + { "chapter", CMD_CHAPTER, 0 }, + { "code", CMD_CODE, 0 }, + { "codeline", CMD_CODELINE, 0}, + { "div", CMD_DIV, 0 }, + { "dots", CMD_DOTS, 0 }, + { "e", CMD_E, 0 }, + { "else", CMD_ELSE, 0 }, + { "endchapter", CMD_ENDCHAPTER, 0 }, + { "endcode", CMD_ENDCODE, 0 }, + { "enddiv", CMD_ENDDIV, 0 }, + { "endfootnote", CMD_ENDFOOTNOTE, 0 }, + { "endif", CMD_ENDIF, 0 }, + { "endlegalese", CMD_ENDLEGALESE, 0 }, + { "endlink", CMD_ENDLINK, 0 }, + { "endlist", CMD_ENDLIST, 0 }, + { "endmapref", CMD_ENDMAPREF, 0 }, + { "endomit", CMD_ENDOMIT, 0 }, + { "endpart", CMD_ENDPART, 0 }, + { "endquotation", CMD_ENDQUOTATION, 0 }, + { "endraw", CMD_ENDRAW, 0 }, + { "endsection1", CMD_ENDSECTION1, 0 }, // ### don't document for now + { "endsection2", CMD_ENDSECTION2, 0 }, // ### don't document for now + { "endsection3", CMD_ENDSECTION3, 0 }, // ### don't document for now + { "endsection4", CMD_ENDSECTION4, 0 }, // ### don't document for now + { "endsidebar", CMD_ENDSIDEBAR, 0 }, + { "endtable", CMD_ENDTABLE, 0 }, + { "endtopicref", CMD_ENDTOPICREF, 0 }, + { "footnote", CMD_FOOTNOTE, 0 }, + { "generatelist", CMD_GENERATELIST, 0 }, + { "granularity", CMD_GRANULARITY, 0 }, // ### don't document for now + { "header", CMD_HEADER, 0 }, + { "hr", CMD_HR, 0 }, + { "i", CMD_I, 0 }, + { "if", CMD_IF, 0 }, + { "image", CMD_IMAGE, 0 }, + { "important", CMD_IMPORTANT, 0 }, + { "include", CMD_INCLUDE, 0 }, + { "inlineimage", CMD_INLINEIMAGE, 0 }, + { "index", CMD_INDEX, 0 }, // ### don't document for now + { "input", CMD_INPUT, 0 }, + { "keyword", CMD_KEYWORD, 0 }, + { "l", CMD_L, 0 }, + { "legalese", CMD_LEGALESE, 0 }, + { "li", CMD_LI, 0 }, + { "link", CMD_LINK, 0 }, + { "list", CMD_LIST, 0 }, + { "mapref", CMD_MAPREF, 0 }, + { "meta", CMD_META, 0 }, + { "newcode", CMD_NEWCODE, 0 }, + { "note", CMD_NOTE, 0 }, + { "o", CMD_O, 0 }, + { "oldcode", CMD_OLDCODE, 0 }, + { "omit", CMD_OMIT, 0 }, + { "omitvalue", CMD_OMITVALUE, 0 }, + { "overload", CMD_OVERLOAD, 0 }, + { "part", CMD_PART, 0 }, + { "printline", CMD_PRINTLINE, 0 }, + { "printto", CMD_PRINTTO, 0 }, + { "printuntil", CMD_PRINTUNTIL, 0 }, + { "quotation", CMD_QUOTATION, 0 }, + { "quotefile", CMD_QUOTEFILE, 0 }, + { "quotefromfile", CMD_QUOTEFROMFILE, 0 }, + { "quotefunction", CMD_QUOTEFUNCTION, 0 }, + { "raw", CMD_RAW, 0 }, + { "row", CMD_ROW, 0 }, + { "sa", CMD_SA, 0 }, + { "section1", CMD_SECTION1, 0 }, + { "section2", CMD_SECTION2, 0 }, + { "section3", CMD_SECTION3, 0 }, + { "section4", CMD_SECTION4, 0 }, + { "sidebar", CMD_SIDEBAR, 0 }, + { "sincelist", CMD_SINCELIST, 0 }, + { "skipline", CMD_SKIPLINE, 0 }, + { "skipto", CMD_SKIPTO, 0 }, + { "skipuntil", CMD_SKIPUNTIL, 0 }, + { "snippet", CMD_SNIPPET, 0 }, + { "span", CMD_SPAN, 0 }, + { "sub", CMD_SUB, 0 }, + { "sup", CMD_SUP, 0 }, + { "table", CMD_TABLE, 0 }, + { "tableofcontents", CMD_TABLEOFCONTENTS, 0 }, + { "target", CMD_TARGET, 0 }, + { "topicref", CMD_TOPICREF, 0 }, + { "tt", CMD_TT, 0 }, + { "uicontrol", CMD_UICONTROL, 0 }, + { "underline", CMD_UNDERLINE, 0 }, + { "unicode", CMD_UNICODE, 0 }, + { "value", CMD_VALUE, 0 }, + { "warning", CMD_WARNING, 0 }, + { "qml", CMD_QML, 0 }, + { "endqml", CMD_ENDQML, 0 }, + { "cpp", CMD_CPP, 0 }, + { "endcpp", CMD_ENDCPP, 0 }, + { "qmltext", CMD_QMLTEXT, 0 }, + { "endqmltext", CMD_ENDQMLTEXT, 0 }, + { "cpptext", CMD_CPPTEXT, 0 }, + { "endcpptext", CMD_ENDCPPTEXT, 0 }, + { "js", CMD_JS, 0 }, + { "endjs", CMD_ENDJS, 0 }, + { 0, 0, 0 } +}; + +typedef QHash<QString, int> QHash_QString_int; +typedef QHash<QString, Macro> QHash_QString_Macro; + +Q_GLOBAL_STATIC(QStringMap, aliasMap) +Q_GLOBAL_STATIC(QHash_QString_int, cmdHash) +Q_GLOBAL_STATIC(QHash_QString_Macro, macroHash) + +class DocPrivateExtra +{ +public: + Doc::Sections granularity_; + Doc::Sections section_; // ### + QList<Atom*> tableOfContents_; + QVector<int> tableOfContentsLevels_; + QList<Atom*> keywords_; + QList<Atom*> targets_; + QStringMultiMap metaMap_; + + DocPrivateExtra() + : granularity_(Doc::Part) + , section_(Doc::NoSection) + { } +}; + +struct Shared // ### get rid of +{ + Shared() + : count(1) { } + void ref() { ++count; } + bool deref() { return (--count == 0); } + + int count; +}; + +static QString cleanLink(const QString &link) +{ + int colonPos = link.indexOf(':'); + if ((colonPos == -1) || + (!link.startsWith("file:") && !link.startsWith("mailto:"))) + return link; + return link.mid(colonPos + 1).simplified(); +} + +typedef QMap<QString, ArgList> CommandMap; + +class DocPrivate : public Shared +{ +public: + DocPrivate(const Location& start = Location::null, + const Location& end = Location::null, + const QString& source = QString()); + ~DocPrivate(); + + void addAlso(const Text& also); + void constructExtra(); + bool isEnumDocSimplifiable() const; + + // ### move some of this in DocPrivateExtra + Location start_loc; + Location end_loc; + QString src; + Text text; + QSet<QString> params; + QList<Text> alsoList; + QStringList enumItemList; + QStringList omitEnumItemList; + QSet<QString> metacommandsUsed; + CommandMap metaCommandMap; + bool hasLegalese : 1; + bool hasSectioningUnits : 1; + DocPrivateExtra *extra; + TopicList topics_; + DitaRefList ditamap_; +}; + +DocPrivate::DocPrivate(const Location& start, + const Location& end, + const QString& source) + : start_loc(start), + end_loc(end), + src(source), + hasLegalese(false), + hasSectioningUnits(false), + extra(0) +{ + // nothing. +} + +/*! + If the doc is a ditamap, the destructor deletes each element + in the ditamap structure. These were allocated as needed. + */ +DocPrivate::~DocPrivate() +{ + delete extra; + foreach (DitaRef* t, ditamap_) { + delete t; + } +} + +void DocPrivate::addAlso(const Text& also) +{ + alsoList.append(also); +} + +void DocPrivate::constructExtra() +{ + if (extra == 0) + extra = new DocPrivateExtra; +} + +bool DocPrivate::isEnumDocSimplifiable() const +{ + bool justMetColon = false; + int numValueTables = 0; + + const Atom *atom = text.firstAtom(); + while (atom) { + if (atom->type() == Atom::AutoLink || atom->type() == Atom::String) { + justMetColon = atom->string().endsWith(QLatin1Char(':')); + } + else if ((atom->type() == Atom::ListLeft) && + (atom->string() == ATOM_LIST_VALUE)) { + if (justMetColon || numValueTables > 0) + return false; + ++numValueTables; + } + atom = atom->next(); + } + return true; +} + +class DocParser +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::DocParser) + +public: + void parse(const QString &source, + DocPrivate *docPrivate, + const QSet<QString> &metaCommandSet, + const QSet<QString>& possibleTopics); + + static int endCmdFor(int cmd); + static QString cmdName(int cmd); + static QString endCmdName(int cmd); + static QString untabifyEtc(const QString& str); + static int indentLevel(const QString& str); + static QString unindent(int level, const QString& str); + static QString slashed(const QString& str); + + static int tabSize; + static QStringList exampleFiles; + static QStringList exampleDirs; + static QStringList sourceFiles; + static QStringList sourceDirs; + static bool quoting; + +private: + Location& location(); + QString detailsUnknownCommand(const QSet<QString>& metaCommandSet, + const QString& str); + void insertTarget(const QString& target, bool keyword); + void include(const QString& fileName, const QString& identifier); + void startFormat(const QString& format, int cmd); + bool openCommand(int cmd); + bool closeCommand(int endCmd); + void startSection(Doc::Sections unit, int cmd); + void endSection(int unit, int endCmd); + void parseAlso(); + void append(const QString &string); + void append(Atom::AtomType type, const QString& string = QString()); + void append(Atom::AtomType type, const QString& p1, const QString& p2); + void append(const QString& p1, const QString& p2); + void appendChar(QChar ch); + void appendWord(const QString &word); + void appendToCode(const QString &code); + void appendToCode(const QString &code, Atom::AtomType defaultType); + void startNewPara(); + void enterPara(Atom::AtomType leftType = Atom::ParaLeft, + Atom::AtomType rightType = Atom::ParaRight, + const QString& string = QString()); + void leavePara(); + void leaveValue(); + void leaveValueList(); + void leaveTableRow(); + CodeMarker *quoteFromFile(); + void expandMacro(const QString& name, const QString& def, int numParams); + QString expandMacroToString(const QString &name, const QString &def, int numParams); + Doc::Sections getSectioningUnit(); + QString getArgument(bool verbatim = false); + QString getBracedArgument(bool verbatim); + QString getBracketedArgument(); + QString getOptionalArgument(); + QString getRestOfLine(); + QString getMetaCommandArgument(const QString &cmdStr); + QString getUntilEnd(int cmd); + QString getCode(int cmd, CodeMarker *marker); + QString getUnmarkedCode(int cmd); + + bool isBlankLine(); + bool isLeftBraceAhead(); + bool isLeftBracketAhead(); + void skipSpacesOnLine(); + void skipSpacesOrOneEndl(); + void skipAllSpaces(); + void skipToNextPreprocessorCommand(); + + QStack<int> openedInputs; + + QString in; + int pos; + int len; + Location cachedLoc; + int cachedPos; + + DocPrivate* priv; + enum ParagraphState { + OutsideParagraph, + InSingleLineParagraph, + InMultiLineParagraph + }; + ParagraphState paraState; + bool inTableHeader; + bool inTableRow; + bool inTableItem; + bool indexStartedPara; // ### rename + Atom::AtomType pendingParaLeftType; + Atom::AtomType pendingParaRightType; + QString pendingParaString; + + int braceDepth; + int minIndent; + Doc::Sections currentSection; + QMap<QString, Location> targetMap_; + QMap<int, QString> pendingFormats; + QStack<int> openedCommands; + QStack<OpenedList> openedLists; + Quoter quoter; + QStack<DitaRef*> ditarefs_; +}; + +int DocParser::tabSize; +QStringList DocParser::exampleFiles; +QStringList DocParser::exampleDirs; +QStringList DocParser::sourceFiles; +QStringList DocParser::sourceDirs; +bool DocParser::quoting; + +/*! + Parse the \a source string to build a Text data structure + in \a docPrivate. The Text data structure is a linked list + of Atoms. + + \a metaCommandSet is the set of metacommands that may be + found in \a source. These metacommands are not markup text + commands. They are topic commands and related metacommands. + */ +void DocParser::parse(const QString& source, + DocPrivate *docPrivate, + const QSet<QString>& metaCommandSet, + const QSet<QString>& possibleTopics) +{ + in = source; + pos = 0; + len = in.length(); + cachedLoc = docPrivate->start_loc; + cachedPos = 0; + priv = docPrivate; + priv->text << Atom::Nop; + priv->topics_.clear(); + + paraState = OutsideParagraph; + inTableHeader = false; + inTableRow = false; + inTableItem = false; + indexStartedPara = false; + pendingParaLeftType = Atom::Nop; + pendingParaRightType = Atom::Nop; + + braceDepth = 0; + minIndent = INT_MAX; + currentSection = Doc::NoSection; + openedCommands.push(CMD_OMIT); + quoter.reset(); + + CodeMarker *marker = 0; + Atom *currentLinkAtom = 0; + QString p1, p2; + QStack<bool> preprocessorSkipping; + int numPreprocessorSkipping = 0; + + while (pos < len) { + QChar ch = in.at(pos); + + switch (ch.unicode()) { + case '\\': + { + QString cmdStr; + pos++; + while (pos < len) { + ch = in.at(pos); + if (ch.isLetterOrNumber()) { + cmdStr += ch; + pos++; + } + else { + break; + } + } + if (cmdStr.isEmpty()) { + if (pos < len) { + enterPara(); + if (in.at(pos).isSpace()) { + skipAllSpaces(); + appendChar(QLatin1Char(' ')); + } + else { + appendChar(in.at(pos++)); + } + } + } + else { + int cmd = cmdHash()->value(cmdStr,NOT_A_CMD); + switch (cmd) { + case CMD_A: + enterPara(); + p1 = getArgument(); + append(Atom::FormattingLeft,ATOM_FORMATTING_PARAMETER); + append(Atom::String, p1); + append(Atom::FormattingRight,ATOM_FORMATTING_PARAMETER); + priv->params.insert(p1); + break; + case CMD_BADCODE: + leavePara(); + append(Atom::CodeBad,getCode(CMD_BADCODE, marker)); + break; + case CMD_BR: + enterPara(); + append(Atom::BR); + break; + case CMD_BOLD: + location().warning(tr("'\\bold' is deprecated. Use '\\b'")); + case CMD_B: + startFormat(ATOM_FORMATTING_BOLD, cmd); + break; + case CMD_BRIEF: + leavePara(); + enterPara(Atom::BriefLeft, Atom::BriefRight); + break; + case CMD_C: + enterPara(); + p1 = untabifyEtc(getArgument(true)); + marker = CodeMarker::markerForCode(p1); + append(Atom::C, marker->markedUpCode(p1, 0, location())); + break; + case CMD_CAPTION: + leavePara(); + enterPara(Atom::CaptionLeft, Atom::CaptionRight); + break; + case CMD_CHAPTER: + startSection(Doc::Chapter, cmd); + break; + case CMD_CODE: + leavePara(); + append(Atom::Code, getCode(CMD_CODE, 0)); + break; + case CMD_QML: + leavePara(); + append(Atom::Qml, getCode(CMD_QML, CodeMarker::markerForLanguage(QLatin1String("QML")))); + break; + case CMD_QMLTEXT: + append(Atom::QmlText); + break; + case CMD_JS: + leavePara(); + append(Atom::JavaScript, getCode(CMD_JS, CodeMarker::markerForLanguage(QLatin1String("JavaScript")))); + break; + case CMD_DIV: + leavePara(); + p1 = getArgument(true); + append(Atom::DivLeft, p1); + openedCommands.push(cmd); + break; + case CMD_ENDDIV: + leavePara(); + append(Atom::DivRight); + closeCommand(cmd); + break; + case CMD_CODELINE: + { + if (!quoting) { + if (priv->text.lastAtom()->type() == Atom::Code + && priv->text.lastAtom()->string().endsWith("\n\n")) + priv->text.lastAtom()->chopString(); + appendToCode("\n"); + } + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, " "); + } + } + break; + case CMD_DOTS: + { + if (!quoting) { + if (priv->text.lastAtom()->type() == Atom::Code + && priv->text.lastAtom()->string().endsWith("\n\n")) + priv->text.lastAtom()->chopString(); + + QString arg = getOptionalArgument(); + int indent = 4; + if (!arg.isEmpty()) + indent = arg.toInt(); + for (int i = 0; i < indent; ++i) + appendToCode(" "); + appendToCode("...\n"); + } + else { + append(Atom::CodeQuoteCommand, cmdStr); + QString arg = getOptionalArgument(); + if (arg.isEmpty()) + arg = "4"; + append(Atom::CodeQuoteArgument, arg); + } + } + break; + case CMD_ELSE: + if (preprocessorSkipping.size() > 0) { + if (preprocessorSkipping.top()) { + --numPreprocessorSkipping; + } + else { + ++numPreprocessorSkipping; + } + preprocessorSkipping.top() = !preprocessorSkipping.top(); + (void)getRestOfLine(); // ### should ensure that it's empty + if (numPreprocessorSkipping) + skipToNextPreprocessorCommand(); + } + else { + location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ELSE))); + } + break; + case CMD_ENDCHAPTER: + endSection(Doc::Chapter, cmd); + break; + case CMD_ENDCODE: + closeCommand(cmd); + break; + case CMD_ENDQML: + closeCommand(cmd); + break; + case CMD_ENDQMLTEXT: + append(Atom::EndQmlText); + break; + case CMD_ENDJS: + closeCommand(cmd); + break; + case CMD_ENDFOOTNOTE: + if (closeCommand(cmd)) { + leavePara(); + append(Atom::FootnoteRight); + paraState = InMultiLineParagraph; // ### + } + break; + case CMD_ENDIF: + if (preprocessorSkipping.count() > 0) { + if (preprocessorSkipping.pop()) + --numPreprocessorSkipping; + (void)getRestOfLine(); // ### should ensure that it's empty + if (numPreprocessorSkipping) + skipToNextPreprocessorCommand(); + } + else { + location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ENDIF))); + } + break; + case CMD_ENDLEGALESE: + if (closeCommand(cmd)) { + leavePara(); + append(Atom::LegaleseRight); + } + break; + case CMD_ENDLINK: + if (closeCommand(cmd)) { + if (priv->text.lastAtom()->type() == Atom::String + && priv->text.lastAtom()->string().endsWith(QLatin1Char(' '))) + priv->text.lastAtom()->chopString(); + append(Atom::FormattingRight, ATOM_FORMATTING_LINK); + } + break; + case CMD_ENDLIST: + if (closeCommand(cmd)) { + leavePara(); + if (openedLists.top().isStarted()) { + append(Atom::ListItemRight, + openedLists.top().styleString()); + append(Atom::ListRight, + openedLists.top().styleString()); + } + openedLists.pop(); + } + break; + case CMD_ENDMAPREF: + case CMD_ENDTOPICREF: + if (closeCommand(cmd)) { + ditarefs_.pop(); // zzz + } + break; + case CMD_ENDOMIT: + closeCommand(cmd); + break; + case CMD_ENDPART: + endSection(Doc::Part, cmd); + break; + case CMD_ENDQUOTATION: + if (closeCommand(cmd)) { + leavePara(); + append(Atom::QuotationRight); + } + break; + case CMD_ENDRAW: + location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ENDRAW))); + break; + case CMD_ENDSECTION1: + endSection(Doc::Section1, cmd); + break; + case CMD_ENDSECTION2: + endSection(Doc::Section2, cmd); + break; + case CMD_ENDSECTION3: + endSection(Doc::Section3, cmd); + break; + case CMD_ENDSECTION4: + endSection(Doc::Section4, cmd); + break; + case CMD_ENDSIDEBAR: + if (closeCommand(cmd)) { + leavePara(); + append(Atom::SidebarRight); + } + break; + case CMD_ENDTABLE: + if (closeCommand(cmd)) { + leaveTableRow(); + append(Atom::TableRight); + } + break; + case CMD_FOOTNOTE: + if (openCommand(cmd)) { + enterPara(); + append(Atom::FootnoteLeft); + paraState = OutsideParagraph; // ### + } + break; + case CMD_ANNOTATEDLIST: + append(Atom::AnnotatedList, getArgument()); + break; + case CMD_SINCELIST: + append(Atom::SinceList, getRestOfLine().simplified()); + break; + case CMD_GENERATELIST: + { + QString arg1 = getArgument(); + QString arg2 = getOptionalArgument(); + if (!arg2.isEmpty()) + arg1 += " " + arg2; + append(Atom::GeneratedList, arg1); + } + break; + case CMD_GRANULARITY: + priv->constructExtra(); + priv->extra->granularity_ = getSectioningUnit(); + break; + case CMD_HEADER: + if (openedCommands.top() == CMD_TABLE) { + leaveTableRow(); + append(Atom::TableHeaderLeft); + inTableHeader = true; + } + else { + if (openedCommands.contains(CMD_TABLE)) { + location().warning(tr("Cannot use '\\%1' within '\\%2'") + .arg(cmdName(CMD_HEADER)) + .arg(cmdName(openedCommands.top()))); + } + else { + location().warning(tr("Cannot use '\\%1' outside of '\\%2'") + .arg(cmdName(CMD_HEADER)) + .arg(cmdName(CMD_TABLE))); + } + } + break; + case CMD_I: + location().warning(tr("'\\i' is deprecated. Use '\\e' for italic or '\\li' for list item")); + case CMD_E: + startFormat(ATOM_FORMATTING_ITALIC, cmd); + break; + case CMD_HR: + leavePara(); + append(Atom::HR); + break; + case CMD_IF: + preprocessorSkipping.push(!Tokenizer::isTrue(getRestOfLine())); + if (preprocessorSkipping.top()) + ++numPreprocessorSkipping; + if (numPreprocessorSkipping) + skipToNextPreprocessorCommand(); + break; + case CMD_IMAGE: + leaveValueList(); + append(Atom::Image, getArgument()); + append(Atom::ImageText, getRestOfLine()); + break; + case CMD_IMPORTANT: + leavePara(); + enterPara(Atom::ImportantLeft, Atom::ImportantRight); + break; + case CMD_INCLUDE: + case CMD_INPUT: + { + QString fileName = getArgument(); + QString identifier = getRestOfLine(); + include(fileName, identifier); + } + break; + case CMD_INLINEIMAGE: + enterPara(); + append(Atom::InlineImage, getArgument()); + append(Atom::ImageText, getRestOfLine()); + append(Atom::String, " "); + break; + case CMD_INDEX: + if (paraState == OutsideParagraph) { + enterPara(); + indexStartedPara = true; + } + else { + const Atom *last = priv->text.lastAtom(); + if (indexStartedPara && + (last->type() != Atom::FormattingRight || + last->string() != ATOM_FORMATTING_INDEX)) + indexStartedPara = false; + } + startFormat(ATOM_FORMATTING_INDEX, cmd); + break; + case CMD_KEYWORD: + insertTarget(getRestOfLine(),true); + break; + case CMD_L: + enterPara(); + if (isLeftBracketAhead()) { + p2 = getBracketedArgument(); + } + if (isLeftBraceAhead()) { + p1 = getArgument(); + append(p1, p2); + if (!p2.isEmpty() && !(priv->text.lastAtom()->error().isEmpty())) { + location().warning(tr("Check parameter in '[ ]' of '\\l' command: '%1', " + "possible misspelling, or unrecognized module name") + .arg(priv->text.lastAtom()->error())); + } + if (isLeftBraceAhead()) { + currentLinkAtom = priv->text.lastAtom(); + startFormat(ATOM_FORMATTING_LINK, cmd); + } + else { + append(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + append(Atom::String, cleanLink(p1)); + append(Atom::FormattingRight, ATOM_FORMATTING_LINK); + } + } + else { + p1 = getArgument(); + append(p1, p2); + if (!p2.isEmpty() && !(priv->text.lastAtom()->error().isEmpty())) { + location().warning(tr("Check parameter in '[ ]' of '\\l' command: '%1', " + "possible misspelling, or unrecognized module name") + .arg(priv->text.lastAtom()->error())); + } + append(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + append(Atom::String, cleanLink(p1)); + append(Atom::FormattingRight, ATOM_FORMATTING_LINK); + } + p2.clear(); + break; + case CMD_LEGALESE: + leavePara(); + if (openCommand(cmd)) + append(Atom::LegaleseLeft); + docPrivate->hasLegalese = true; + break; + case CMD_LINK: + if (openCommand(cmd)) { + enterPara(); + p1 = getArgument(); + append(p1); + append(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + skipSpacesOrOneEndl(); + } + break; + case CMD_LIST: + if (openCommand(cmd)) { + leavePara(); + openedLists.push(OpenedList(location(), + getOptionalArgument())); + } + break; + case CMD_TOPICREF: + case CMD_MAPREF: + if (openCommand(cmd)) { + DitaRef* t = 0; + if (cmd == CMD_MAPREF) + t = new MapRef(); + else + t = new TopicRef(); + t->setNavtitle(getArgument(true)); + if (cmd == CMD_MAPREF) + t->setHref(getArgument()); + else + t->setHref(getOptionalArgument()); + if (ditarefs_.isEmpty()) + priv->ditamap_.append(t); + else + ditarefs_.top()->appendSubref(t); + ditarefs_.push(t); + } + break; + case CMD_META: + priv->constructExtra(); + p1 = getArgument(); + priv->extra->metaMap_.insert(p1, getArgument()); + break; + case CMD_NEWCODE: + location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_NEWCODE))); + break; + case CMD_NOTE: + leavePara(); + enterPara(Atom::NoteLeft, Atom::NoteRight); + break; + case CMD_O: + location().warning(tr("'\\o' is deprecated. Use '\\li'")); + case CMD_LI: + leavePara(); + if (openedCommands.top() == CMD_LIST) { + if (openedLists.top().isStarted()) { + append(Atom::ListItemRight, + openedLists.top().styleString()); + } + else { + append(Atom::ListLeft, + openedLists.top().styleString()); + } + openedLists.top().next(); + append(Atom::ListItemNumber, + openedLists.top().numberString()); + append(Atom::ListItemLeft, + openedLists.top().styleString()); + enterPara(); + } + else if (openedCommands.top() == CMD_TABLE) { + p1 = "1,1"; + p2.clear(); + if (isLeftBraceAhead()) { + p1 = getArgument(); + if (isLeftBraceAhead()) { + p2 = getArgument(); + } + } + + if (!inTableHeader && !inTableRow) { + location().warning(tr("Missing '\\%1' or '\\%2' before '\\%3'") + .arg(cmdName(CMD_HEADER)) + .arg(cmdName(CMD_ROW)) + .arg(cmdName(CMD_LI))); + append(Atom::TableRowLeft); + inTableRow = true; + } + else if (inTableItem) { + append(Atom::TableItemRight); + inTableItem = false; + } + + append(Atom::TableItemLeft, p1, p2); + inTableItem = true; + } + else { + location().warning(tr("Command '\\%1' outside of '\\%2' and '\\%3'") + .arg(cmdName(cmd)) + .arg(cmdName(CMD_LIST)) + .arg(cmdName(CMD_TABLE))); + } + break; + case CMD_OLDCODE: + leavePara(); + append(Atom::CodeOld, getCode(CMD_OLDCODE, marker)); + append(Atom::CodeNew, getCode(CMD_NEWCODE, marker)); + break; + case CMD_OMIT: + getUntilEnd(cmd); + break; + case CMD_OMITVALUE: + p1 = getArgument(); + if (!priv->enumItemList.contains(p1)) + priv->enumItemList.append(p1); + if (!priv->omitEnumItemList.contains(p1)) + priv->omitEnumItemList.append(p1); + break; + case CMD_PART: + startSection(Doc::Part, cmd); + break; + case CMD_PRINTLINE: + leavePara(); + if (!quoting) + appendToCode(quoter.quoteLine(location(), cmdStr, + getRestOfLine())); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getRestOfLine()); + } + break; + case CMD_PRINTTO: + leavePara(); + if (!quoting) + appendToCode(quoter.quoteTo(location(), cmdStr, + getRestOfLine())); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getRestOfLine()); + } + break; + case CMD_PRINTUNTIL: + leavePara(); + if (!quoting) + appendToCode(quoter.quoteUntil(location(), cmdStr, + getRestOfLine())); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getRestOfLine()); + } + break; + case CMD_QUOTATION: + if (openCommand(cmd)) { + leavePara(); + append(Atom::QuotationLeft); + } + break; + case CMD_QUOTEFILE: + { + leavePara(); + QString fileName = getArgument(); + Doc::quoteFromFile(location(), quoter, fileName); + if (!quoting) { + append(Atom::Code, + quoter.quoteTo(location(), cmdStr, QString())); + quoter.reset(); + } + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, fileName); + } + break; + } + case CMD_QUOTEFROMFILE: + leavePara(); + if (!quoting) + quoteFromFile(); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getArgument()); + } + break; + case CMD_QUOTEFUNCTION: + leavePara(); + marker = quoteFromFile(); + p1 = getRestOfLine(); + if (!quoting) { + quoter.quoteTo(location(), cmdStr, + slashed(marker->functionBeginRegExp(p1))); + append(Atom::Code, + quoter.quoteUntil(location(), cmdStr, + slashed(marker->functionEndRegExp(p1)))); + quoter.reset(); + } + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, slashed(marker->functionEndRegExp(p1))); + } + break; + case CMD_RAW: + leavePara(); + p1 = getRestOfLine(); + if (p1.isEmpty()) + location().warning(tr("Missing format name after '\\%1'") + .arg(cmdName(CMD_RAW))); + append(Atom::FormatIf, p1); + append(Atom::RawString, untabifyEtc(getUntilEnd(cmd))); + append(Atom::FormatElse); + append(Atom::FormatEndif); + break; + case CMD_ROW: + if (openedCommands.top() == CMD_TABLE) { + p1.clear(); + if (isLeftBraceAhead()) + p1 = getArgument(true); + leaveTableRow(); + append(Atom::TableRowLeft,p1); + inTableRow = true; + } + else { + if (openedCommands.contains(CMD_TABLE)) { + location().warning(tr("Cannot use '\\%1' within '\\%2'") + .arg(cmdName(CMD_ROW)) + .arg(cmdName(openedCommands.top()))); + } + else { + location().warning(tr("Cannot use '\\%1' outside of '\\%2'") + .arg(cmdName(CMD_ROW)) + .arg(cmdName(CMD_TABLE))); + } + } + break; + case CMD_SA: + parseAlso(); + break; + case CMD_SECTION1: + startSection(Doc::Section1, cmd); + break; + case CMD_SECTION2: + startSection(Doc::Section2, cmd); + break; + case CMD_SECTION3: + startSection(Doc::Section3, cmd); + break; + case CMD_SECTION4: + startSection(Doc::Section4, cmd); + break; + case CMD_SIDEBAR: + if (openCommand(cmd)) { + leavePara(); + append(Atom::SidebarLeft); + } + break; + case CMD_SKIPLINE: + leavePara(); + if (!quoting) + quoter.quoteLine(location(), + cmdStr, + getRestOfLine()); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getRestOfLine()); + } + break; + case CMD_SKIPTO: + leavePara(); + if (!quoting) + quoter.quoteTo(location(), + cmdStr, + getRestOfLine()); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getRestOfLine()); + } + break; + case CMD_SKIPUNTIL: + leavePara(); + if (!quoting) + quoter.quoteUntil(location(), + cmdStr, + getRestOfLine()); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getRestOfLine()); + } + break; + case CMD_SPAN: + p1 = ATOM_FORMATTING_SPAN + getArgument(true); + startFormat(p1, cmd); + break; + case CMD_SNIPPET: + leavePara(); + { + QString snippet = getArgument(); + QString identifier = getRestOfLine(); + if (quoting) { + append(Atom::SnippetCommand, cmdStr); + append(Atom::SnippetLocation, snippet); + append(Atom::SnippetIdentifier, identifier); + } + else { + marker = Doc::quoteFromFile(location(),quoter,snippet); + appendToCode(quoter.quoteSnippet(location(), identifier), marker->atomType()); + } + } + break; + case CMD_SUB: + startFormat(ATOM_FORMATTING_SUBSCRIPT, cmd); + break; + case CMD_SUP: + startFormat(ATOM_FORMATTING_SUPERSCRIPT, cmd); + break; + case CMD_TABLE: + //p1 = getRestOfLine(); + p1 = getOptionalArgument(); + p2 = getOptionalArgument(); + if (openCommand(cmd)) { + leavePara(); + append(Atom::TableLeft, p1, p2); + inTableHeader = false; + inTableRow = false; + inTableItem = false; + } + break; + case CMD_TABLEOFCONTENTS: + p1 = "1"; + if (isLeftBraceAhead()) + p1 = getArgument(); + p1 += QLatin1Char(','); + p1 += QString::number((int)getSectioningUnit()); + append(Atom::TableOfContents, p1); + break; + case CMD_TARGET: + insertTarget(getRestOfLine(),false); + break; + case CMD_TT: + startFormat(ATOM_FORMATTING_TELETYPE, cmd); + break; + case CMD_UICONTROL: + startFormat(ATOM_FORMATTING_UICONTROL, cmd); + break; + case CMD_UNDERLINE: + startFormat(ATOM_FORMATTING_UNDERLINE, cmd); + break; + case CMD_UNICODE: + enterPara(); + p1 = getArgument(); + { + bool ok; + uint unicodeChar = p1.toUInt(&ok, 0); + if (!ok || + (unicodeChar == 0x0000) || + (unicodeChar > 0xFFFE)) { + location().warning(tr("Invalid Unicode character '%1' specified " + "with '%2'") + .arg(p1, cmdName(CMD_UNICODE))); + } + else { + append(Atom::String, QChar(unicodeChar)); + } + } + break; + case CMD_VALUE: + leaveValue(); + if (openedLists.top().style() == OpenedList::Value) { + p1 = getArgument(); + if (!priv->enumItemList.contains(p1)) + priv->enumItemList.append(p1); + + openedLists.top().next(); + append(Atom::ListTagLeft, ATOM_LIST_VALUE); + append(Atom::String, p1); + append(Atom::ListTagRight, ATOM_LIST_VALUE); + append(Atom::ListItemLeft, ATOM_LIST_VALUE); + + skipSpacesOrOneEndl(); + if (isBlankLine()) + append(Atom::Nop); + } + else { + // ### problems + } + break; + case CMD_WARNING: + leavePara(); + enterPara(); + append(Atom::FormattingLeft, ATOM_FORMATTING_BOLD); + append(Atom::String, "Warning:"); + append(Atom::FormattingRight, ATOM_FORMATTING_BOLD); + append(Atom::String, " "); + break; + case CMD_OVERLOAD: + priv->metacommandsUsed.insert(cmdStr); + p1.clear(); + if (!isBlankLine()) + p1 = getRestOfLine(); + if (!p1.isEmpty()) { + append(Atom::ParaLeft); + append(Atom::String, "This function overloads "); + append(Atom::AutoLink,p1); + append(Atom::String, "."); + append(Atom::ParaRight); + } + else { + append(Atom::ParaLeft); + append(Atom::String,"This is an overloaded function."); + append(Atom::ParaRight); + p1 = getMetaCommandArgument(cmdStr); + } + priv->metaCommandMap[cmdStr].append(ArgLocPair(p1,location())); + break; + case NOT_A_CMD: + if (metaCommandSet.contains(cmdStr)) { + priv->metacommandsUsed.insert(cmdStr); + QString arg = getMetaCommandArgument(cmdStr); + priv->metaCommandMap[cmdStr].append(ArgLocPair(arg,location())); + if (possibleTopics.contains(cmdStr)) { + priv->topics_.append(Topic(cmdStr,arg)); + } + } + else if (macroHash()->contains(cmdStr)) { + const Macro ¯o = macroHash()->value(cmdStr); + int numPendingFi = 0; + QStringMap::ConstIterator d; + d = macro.otherDefs.constBegin(); + while (d != macro.otherDefs.constEnd()) { + append(Atom::FormatIf, d.key()); + expandMacro(cmdStr, *d, macro.numParams); + ++d; + + if (d == macro.otherDefs.constEnd()) { + append(Atom::FormatEndif); + } + else { + append(Atom::FormatElse); + numPendingFi++; + } + } + while (numPendingFi-- > 0) + append(Atom::FormatEndif); + + if (!macro.defaultDef.isEmpty()) { + if (!macro.otherDefs.isEmpty()) { + macro.defaultDefLocation.warning( + tr("Macro cannot have both " + "format-specific and qdoc- " + "syntax definitions")); + } + else { + location().push(macro.defaultDefLocation.filePath()); + in.insert(pos, expandMacroToString(cmdStr, macro.defaultDef, macro.numParams)); + len = in.length(); + openedInputs.push(pos + macro.defaultDef.length()); + } + } + } + else { + location().warning( + tr("Unknown command '\\%1'").arg(cmdStr), + detailsUnknownCommand(metaCommandSet,cmdStr)); + enterPara(); + append(Atom::UnknownCommand, cmdStr); + } + } + } + } + break; + case '{': + enterPara(); + appendChar('{'); + braceDepth++; + pos++; + break; + case '}': + { + braceDepth--; + pos++; + + QMap<int, QString>::Iterator f = pendingFormats.find(braceDepth); + if (f == pendingFormats.end()) { + enterPara(); + appendChar('}'); + } + else { + append(Atom::FormattingRight, *f); + if (*f == ATOM_FORMATTING_INDEX) { + if (indexStartedPara) + skipAllSpaces(); + } + else if (*f == ATOM_FORMATTING_LINK) { + // hack for C++ to support links like + // \l{QString::}{count()} + if (currentLinkAtom && + currentLinkAtom->string().endsWith("::")) { + QString suffix = Text::subText(currentLinkAtom, + priv->text.lastAtom()).toString(); + currentLinkAtom->appendString(suffix); + } + currentLinkAtom = 0; + } + pendingFormats.erase(f); + } + } + break; + default: + { + bool newWord; + switch (priv->text.lastAtom()->type()) { + case Atom::ParaLeft: + newWord = true; + break; + default: + newWord = false; + } + + if (paraState == OutsideParagraph) { + if (ch.isSpace()) { + ++pos; + newWord = false; + } + else { + enterPara(); + newWord = true; + } + } + else { + if (ch.isSpace()) { + ++pos; + if ((ch == '\n') && + (paraState == InSingleLineParagraph || + isBlankLine())) { + leavePara(); + newWord = false; + } + else { + appendChar(' '); + newWord = true; + } + } + else { + newWord = true; + } + } + + if (newWord) { + int startPos = pos; + int numInternalUppercase = 0; + int numLowercase = 0; + int numStrangeSymbols = 0; + + while (pos < len) { + unsigned char latin1Ch = in.at(pos).toLatin1(); + if (islower(latin1Ch)) { + ++numLowercase; + ++pos; + } + else if (isupper(latin1Ch)) { + if (pos > startPos) + ++numInternalUppercase; + ++pos; + } + else if (isdigit(latin1Ch)) { + if (pos > startPos) { + ++pos; + } + else { + break; + } + } + else if (latin1Ch == '_' || latin1Ch == '@') { + ++numStrangeSymbols; + ++pos; + } + else if (latin1Ch == ':' && pos < len - 1 + && in.at(pos + 1) == QLatin1Char(':')) { + ++numStrangeSymbols; + pos += 2; + } + else if (latin1Ch == '(') { + if (pos > startPos) { + if (pos < len - 1 && + in.at(pos + 1) == QLatin1Char(')')) { + ++numStrangeSymbols; + pos += 2; + break; + } + else { + // ### handle functions with signatures + // and function calls + break; + } + } + else { + break; + } + } + else { + break; + } + } + + if (pos == startPos) { + if (!ch.isSpace()) { + appendChar(ch); + ++pos; + } + } + else { + QString word = in.mid(startPos, pos - startPos); + // is word a C++ symbol or an English word? + if ((numInternalUppercase >= 1 && numLowercase >= 2) + || numStrangeSymbols > 0) { + if (word.startsWith(QString("__"))) + appendWord(word); + else + append(Atom::AutoLink, word); + } + else + appendWord(word); + } + } + } + } + } + leaveValueList(); + + // for compatibility + if (openedCommands.top() == CMD_LEGALESE) { + append(Atom::LegaleseRight); + openedCommands.pop(); + } + + if (openedCommands.top() != CMD_OMIT) { + location().warning(tr("Missing '\\%1'").arg(endCmdName(openedCommands.top()))); + } + else if (preprocessorSkipping.count() > 0) { + location().warning(tr("Missing '\\%1'").arg(cmdName(CMD_ENDIF))); + } + + if (currentSection > Doc::NoSection) { + append(Atom::SectionRight, QString::number(currentSection)); + currentSection = Doc::NoSection; + } + + if (priv->extra && priv->extra->granularity_ < priv->extra->section_) + priv->extra->granularity_ = priv->extra->section_; + priv->text.stripFirstAtom(); +} + +/*! + Returns the current location. + */ +Location &DocParser::location() +{ + while (!openedInputs.isEmpty() && openedInputs.top() <= pos) { + cachedLoc.pop(); + cachedPos = openedInputs.pop(); + } + while (cachedPos < pos) + cachedLoc.advance(in.at(cachedPos++)); + return cachedLoc; +} + +QString DocParser::detailsUnknownCommand(const QSet<QString> &metaCommandSet, + const QString &str) +{ + QSet<QString> commandSet = metaCommandSet; + int i = 0; + while (cmds[i].english != 0) { + commandSet.insert(*cmds[i].alias); + i++; + } + + if (aliasMap()->contains(str)) + return tr("The command '\\%1' was renamed '\\%2' by the configuration" + " file. Use the new name.") + .arg(str).arg((*aliasMap())[str]); + + QString best = nearestName(str, commandSet); + if (best.isEmpty()) + return QString(); + return tr("Maybe you meant '\\%1'?").arg(best); +} + +void DocParser::insertTarget(const QString &target, bool keyword) +{ + if (targetMap_.contains(target)) { + location().warning(tr("Duplicate target name '%1'").arg(target)); + targetMap_[target].warning(tr("(The previous occurrence is here)")); + } + else { + targetMap_.insert(target, location()); + priv->constructExtra(); + if (keyword) { + append(Atom::Keyword, target); + priv->extra->keywords_.append(priv->text.lastAtom()); + } + else { + append(Atom::Target, target); + priv->extra->targets_.append(priv->text.lastAtom()); + } + } +} + +void DocParser::include(const QString& fileName, const QString& identifier) +{ + if (location().depth() > 16) + location().fatal(tr("Too many nested '\\%1's").arg(cmdName(CMD_INCLUDE))); + + QString userFriendlyFilePath; + QString filePath = Doc::config()->getIncludeFilePath(fileName); +#if 0 + QString filePath = Config::findFile(location(), + sourceFiles, + sourceDirs, + fileName, + userFriendlyFilePath); +#endif + if (filePath.isEmpty()) { + location().warning(tr("Cannot find qdoc include file '%1'").arg(fileName)); + } + else { + QFile inFile(filePath); + if (!inFile.open(QFile::ReadOnly)) { + location().warning(tr("Cannot open qdoc include file '%1'") + .arg(userFriendlyFilePath)); + } + else { + location().push(userFriendlyFilePath); + + QTextStream inStream(&inFile); + QString includedStuff = inStream.readAll(); + inFile.close(); + + if (identifier.isEmpty()) { + in.insert(pos, includedStuff); + len = in.length(); + openedInputs.push(pos + includedStuff.length()); + } + else { + QStringList lineBuffer = includedStuff.split(QLatin1Char('\n')); + int i = 0; + int startLine = -1; + while (i < lineBuffer.size()) { + if (lineBuffer[i].startsWith("//!")) { + if (lineBuffer[i].contains(identifier)) { + startLine = i+1; + break; + } + } + ++i; + } + if (startLine < 0) { + location().warning(tr("Cannot find '%1' in '%2'") + .arg(identifier) + .arg(userFriendlyFilePath)); + return; + + } + QString result; + i = startLine; + do { + if (lineBuffer[i].startsWith("//!")) { + if (i<lineBuffer.size()) { + if (lineBuffer[i].contains(identifier)) { + break; + } + } + } + else + result += lineBuffer[i] + QLatin1Char('\n'); + ++i; + } while (i < lineBuffer.size()); + if (result.isEmpty()) { + location().warning(tr("Empty qdoc snippet '%1' in '%2'") + .arg(identifier) + .arg(userFriendlyFilePath)); + } + else { + in.insert(pos, result); + len = in.length(); + openedInputs.push(pos + result.length()); + } + } + } + } +} + +void DocParser::startFormat(const QString& format, int cmd) +{ + enterPara(); + + QMap<int, QString>::ConstIterator f = pendingFormats.constBegin(); + while (f != pendingFormats.constEnd()) { + if (*f == format) { + location().warning(tr("Cannot nest '\\%1' commands") + .arg(cmdName(cmd))); + return; + } + ++f; + } + + append(Atom::FormattingLeft, format); + + if (isLeftBraceAhead()) { + skipSpacesOrOneEndl(); + pendingFormats.insert(braceDepth, format); + ++braceDepth; + ++pos; + } + else { + append(Atom::String, getArgument()); + append(Atom::FormattingRight, format); + if (format == ATOM_FORMATTING_INDEX && indexStartedPara) { + skipAllSpaces(); + indexStartedPara = false; + } + } +} + +bool DocParser::openCommand(int cmd) +{ + int outer = openedCommands.top(); + bool ok = true; + + if (cmd != CMD_LINK) { + if (outer == CMD_LIST) { + ok = (cmd == CMD_FOOTNOTE || cmd == CMD_LIST); + } + else if (outer == CMD_SIDEBAR) { + ok = (cmd == CMD_LIST || + cmd == CMD_QUOTATION || + cmd == CMD_SIDEBAR); + } + else if (outer == CMD_QUOTATION) { + ok = (cmd == CMD_LIST); + } + else if (outer == CMD_TABLE) { + ok = (cmd == CMD_LIST || + cmd == CMD_FOOTNOTE || + cmd == CMD_QUOTATION); + } + else if (outer == CMD_FOOTNOTE || outer == CMD_LINK) { + ok = false; + } + else if (outer == CMD_TOPICREF) + ok = (cmd == CMD_TOPICREF || cmd == CMD_MAPREF); + else if (outer == CMD_MAPREF) + ok = false; + } + + if (ok) { + openedCommands.push(cmd); + } + else { + location().warning(tr("Can't use '\\%1' in '\\%2'").arg(cmdName(cmd)).arg(cmdName(outer))); + } + return ok; +} + +bool DocParser::closeCommand(int endCmd) +{ + if (endCmdFor(openedCommands.top()) == endCmd && openedCommands.size() > 1) { + openedCommands.pop(); + return true; + } + else { + bool contains = false; + QStack<int> opened2 = openedCommands; + while (opened2.size() > 1) { + if (endCmdFor(opened2.top()) == endCmd) { + contains = true; + break; + } + opened2.pop(); + } + + if (contains) { + while (endCmdFor(openedCommands.top()) != endCmd && openedCommands.size() > 1) { + location().warning(tr("Missing '\\%1' before '\\%2'") + .arg(endCmdName(openedCommands.top())) + .arg(cmdName(endCmd))); + openedCommands.pop(); + } + } + else { + location().warning(tr("Unexpected '\\%1'").arg(cmdName(endCmd))); + } + return false; + } +} + +void DocParser::startSection(Doc::Sections unit, int cmd) +{ + leaveValueList(); + + if (currentSection == Doc::NoSection) { + currentSection = (Doc::Sections) (unit); + priv->constructExtra(); + priv->extra->section_ = currentSection; + } + else + endSection(unit,cmd); + + append(Atom::SectionLeft, QString::number(unit)); + priv->constructExtra(); + priv->extra->tableOfContents_.append(priv->text.lastAtom()); + priv->extra->tableOfContentsLevels_.append(unit); + enterPara(Atom::SectionHeadingLeft, + Atom::SectionHeadingRight, + QString::number(unit)); + currentSection = unit; + +} + +void DocParser::endSection(int , int) // (int unit, int endCmd) +{ + leavePara(); + append(Atom::SectionRight, QString::number(currentSection)); + currentSection = (Doc::NoSection); +} + +void DocParser::parseAlso() +{ + leavePara(); + skipSpacesOnLine(); + while (pos < len && in[pos] != '\n') { + QString target; + QString str; + + if (in[pos] == '{') { + target = getArgument(); + skipSpacesOnLine(); + if (in[pos] == '{') { + str = getArgument(); + + // hack for C++ to support links like \l{QString::}{count()} + if (target.endsWith("::")) + target += str; + } + else { + str = target; + } +#ifdef QDOC2_COMPAT + } + else if (in[pos] == '\\' && in.mid(pos, 5) == "\\link") { + pos += 6; + target = getArgument(); + int endPos = in.indexOf("\\endlink", pos); + if (endPos != -1) { + str = in.mid(pos, endPos - pos).trimmed(); + pos = endPos + 8; + } +#endif + } + else { + target = getArgument(); + str = cleanLink(target); + } + + Text also; + also << Atom(Atom::Link, target) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << str + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + priv->addAlso(also); + + skipSpacesOnLine(); + if (pos < len && in[pos] == ',') { + pos++; + skipSpacesOrOneEndl(); + } + else if (in[pos] != '\n') { + location().warning(tr("Missing comma in '\\%1'").arg(cmdName(CMD_SA))); + } + } +} + +void DocParser::append(Atom::AtomType type, const QString &string) +{ + Atom::AtomType lastType = priv->text.lastAtom()->type(); + if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n"))) + priv->text.lastAtom()->chopString(); + priv->text << Atom(type, string); +} + +void DocParser::append(const QString &string) +{ + Atom::AtomType lastType = priv->text.lastAtom()->type(); + if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n"))) + priv->text.lastAtom()->chopString(); + priv->text << Atom(string); // The Atom type is Link. +} + +void DocParser::append(Atom::AtomType type, const QString& p1, const QString& p2) +{ + Atom::AtomType lastType = priv->text.lastAtom()->type(); + if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n"))) + priv->text.lastAtom()->chopString(); + priv->text << Atom(type, p1, p2); +} + +void DocParser::append(const QString& p1, const QString& p2) +{ + Atom::AtomType lastType = priv->text.lastAtom()->type(); + if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n"))) + priv->text.lastAtom()->chopString(); + if (p2.isEmpty()) + priv->text << Atom(p1); // The Atom type is Link. + else + priv->text << LinkAtom(p1, p2); +} + +void DocParser::appendChar(QChar ch) +{ + if (priv->text.lastAtom()->type() != Atom::String) + append(Atom::String); + Atom *atom = priv->text.lastAtom(); + if (ch == QLatin1Char(' ')) { + if (!atom->string().endsWith(QLatin1Char(' '))) + atom->appendChar(QLatin1Char(' ')); + } + else + atom->appendChar(ch); +} + +void DocParser::appendWord(const QString &word) +{ + if (priv->text.lastAtom()->type() != Atom::String) { + append(Atom::String, word); + } + else + priv->text.lastAtom()->appendString(word); +} + +void DocParser::appendToCode(const QString& markedCode) +{ + Atom::AtomType lastType = priv->text.lastAtom()->type(); + if (lastType != Atom::Qml && lastType != Atom::Code && lastType != Atom::JavaScript) + append(Atom::Qml); + priv->text.lastAtom()->appendString(markedCode); +} + +void DocParser::appendToCode(const QString &markedCode, Atom::AtomType defaultType) +{ + Atom::AtomType lastType = priv->text.lastAtom()->type(); + if (lastType != Atom::Qml && lastType != Atom::Code && lastType != Atom::JavaScript) + append(defaultType, markedCode); + else + priv->text.lastAtom()->appendString(markedCode); +} + +void DocParser::startNewPara() +{ + leavePara(); + enterPara(); +} + +void DocParser::enterPara(Atom::AtomType leftType, + Atom::AtomType rightType, + const QString& string) +{ + if (paraState == OutsideParagraph) { + + if ((priv->text.lastAtom()->type() != Atom::ListItemLeft) && + (priv->text.lastAtom()->type() != Atom::DivLeft)) { + leaveValueList(); + } + + append(leftType, string); + indexStartedPara = false; + pendingParaLeftType = leftType; + pendingParaRightType = rightType; + pendingParaString = string; + if (leftType == Atom::SectionHeadingLeft) { + paraState = InSingleLineParagraph; + } + else { + paraState = InMultiLineParagraph; + } + skipSpacesOrOneEndl(); + } +} + +void DocParser::leavePara() +{ + if (paraState != OutsideParagraph) { + if (!pendingFormats.isEmpty()) { + location().warning(tr("Missing '}'")); + pendingFormats.clear(); + } + + if (priv->text.lastAtom()->type() == pendingParaLeftType) { + priv->text.stripLastAtom(); + } + else { + if (priv->text.lastAtom()->type() == Atom::String && + priv->text.lastAtom()->string().endsWith(QLatin1Char(' '))) { + priv->text.lastAtom()->chopString(); + } + append(pendingParaRightType, pendingParaString); + } + paraState = OutsideParagraph; + indexStartedPara = false; + pendingParaRightType = Atom::Nop; + pendingParaString.clear(); + } +} + +void DocParser::leaveValue() +{ + leavePara(); + if (openedLists.isEmpty()) { + openedLists.push(OpenedList(OpenedList::Value)); + append(Atom::ListLeft, ATOM_LIST_VALUE); + } + else { + if (priv->text.lastAtom()->type() == Atom::Nop) + priv->text.stripLastAtom(); + append(Atom::ListItemRight, ATOM_LIST_VALUE); + } +} + +void DocParser::leaveValueList() +{ + leavePara(); + if (!openedLists.isEmpty() && + (openedLists.top().style() == OpenedList::Value)) { + if (priv->text.lastAtom()->type() == Atom::Nop) + priv->text.stripLastAtom(); + append(Atom::ListItemRight, ATOM_LIST_VALUE); + append(Atom::ListRight, ATOM_LIST_VALUE); + openedLists.pop(); + } +} + +void DocParser::leaveTableRow() +{ + if (inTableItem) { + leavePara(); + append(Atom::TableItemRight); + inTableItem = false; + } + if (inTableHeader) { + append(Atom::TableHeaderRight); + inTableHeader = false; + } + if (inTableRow) { + append(Atom::TableRowRight); + inTableRow = false; + } +} + +CodeMarker *DocParser::quoteFromFile() +{ + return Doc::quoteFromFile(location(), quoter, getArgument()); +} + +void DocParser::expandMacro(const QString &name, + const QString &def, + int numParams) +{ + if (numParams == 0) { + append(Atom::RawString, def); + } + else { + QStringList args; + QString rawString; + + for (int i = 0; i < numParams; i++) { + if (numParams == 1 || isLeftBraceAhead()) { + args << getArgument(true); + } + else { + location().warning(tr("Macro '\\%1' invoked with too few" + " arguments (expected %2, got %3)") + .arg(name).arg(numParams).arg(i)); + break; + } + } + + int j = 0; + while (j < def.size()) { + int paramNo; + if (((paramNo = def[j].unicode()) >= 1) && + (paramNo <= numParams)) { + if (!rawString.isEmpty()) { + append(Atom::RawString, rawString); + rawString.clear(); + } + append(Atom::String, args[paramNo - 1]); + j += 1; + } + else { + rawString += def[j++]; + } + } + if (!rawString.isEmpty()) + append(Atom::RawString, rawString); + } +} + +QString DocParser::expandMacroToString(const QString &name, const QString &def, int numParams) +{ + if (numParams == 0) { + return def; + } + else { + QStringList args; + QString rawString; + + for (int i = 0; i < numParams; i++) { + if (numParams == 1 || isLeftBraceAhead()) { + args << getArgument(true); + } + else { + location().warning(tr("Macro '\\%1' invoked with too few" + " arguments (expected %2, got %3)") + .arg(name).arg(numParams).arg(i)); + break; + } + } + + int j = 0; + while (j < def.size()) { + int paramNo; + if (((paramNo = def[j].unicode()) >= 1) && + (paramNo <= numParams)) { + rawString += args[paramNo - 1]; + j += 1; + } + else { + rawString += def[j++]; + } + } + return rawString; + } +} + +Doc::Sections DocParser::getSectioningUnit() +{ + QString name = getOptionalArgument(); + + if (name == "part") { + return Doc::Part; + } + else if (name == "chapter") { + return Doc::Chapter; + } + else if (name == "section1") { + return Doc::Section1; + } + else if (name == "section2") { + return Doc::Section2; + } + else if (name == "section3") { + return Doc::Section3; + } + else if (name == "section4") { + return Doc::Section4; + } + else if (name.isEmpty()) { + return Doc::NoSection; + } + else { + location().warning(tr("Invalid section '%1'").arg(name)); + return Doc::NoSection; + } +} + +/*! + Gets an argument that is enclosed in braces and returns it + without the enclosing braces. On entry, the current character + is the left brace. On exit, the current character is the one + that comes after the right brace. + + If \a verbatim is true, extra whitespace is retained in the + returned string. Otherwise, extra whitespace is removed. + */ +QString DocParser::getBracedArgument(bool verbatim) +{ + QString arg; + int delimDepth = 0; + if (pos < (int) in.length() && in[pos] == '{') { + pos++; + while (pos < (int) in.length() && delimDepth >= 0) { + switch (in[pos].unicode()) { + case '{': + delimDepth++; + arg += QLatin1Char('{'); + pos++; + break; + case '}': + delimDepth--; + if (delimDepth >= 0) + arg += QLatin1Char('}'); + pos++; + break; + case '\\': + if (verbatim) { + arg += in[pos]; + pos++; + } + else { + pos++; + if (pos < (int) in.length()) { + if (in[pos].isLetterOrNumber()) + break; + arg += in[pos]; + if (in[pos].isSpace()) { + skipAllSpaces(); + } + else { + pos++; + } + } + } + break; + default: + if (in[pos].isSpace() && !verbatim) + arg += QChar(' '); + else + arg += in[pos]; + pos++; + } + } + if (delimDepth > 0) + location().warning(tr("Missing '}'")); + } + return arg; +} + +/*! + Typically, an argument ends at the next white-space. However, + braces can be used to group words: + + {a few words} + + Also, opening and closing parentheses have to match. Thus, + + printf("%d\n", x) + + is an argument too, although it contains spaces. Finally, + trailing punctuation is not included in an argument, nor is 's. +*/ +QString DocParser::getArgument(bool verbatim) +{ + skipSpacesOrOneEndl(); + + int delimDepth = 0; + int startPos = pos; + QString arg = getBracedArgument(verbatim); + if (arg.isEmpty()) { + while ((pos < in.length()) && + ((delimDepth > 0) || ((delimDepth == 0) && !in[pos].isSpace()))) { + switch (in[pos].unicode()) { + case '(': + case '[': + case '{': + delimDepth++; + arg += in[pos]; + pos++; + break; + case ')': + case ']': + case '}': + delimDepth--; + if (pos == startPos || delimDepth >= 0) { + arg += in[pos]; + pos++; + } + break; + case '\\': + if (verbatim) { + arg += in[pos]; + pos++; + } + else { + pos++; + if (pos < (int) in.length()) { + if (in[pos].isLetterOrNumber()) + break; + arg += in[pos]; + if (in[pos].isSpace()) { + skipAllSpaces(); + } + else { + pos++; + } + } + } + break; + default: + arg += in[pos]; + pos++; + } + } + if ((arg.length() > 1) && + (QString(".,:;!?").indexOf(in[pos - 1]) != -1) && + !arg.endsWith("...")) { + arg.truncate(arg.length() - 1); + pos--; + } + if (arg.length() > 2 && in.mid(pos - 2, 2) == "'s") { + arg.truncate(arg.length() - 2); + pos -= 2; + } + } + return arg.simplified(); +} + +/*! + Gets an argument that is enclosed in brackets and returns it + without the enclosing brackets. On entry, the current character + is the left bracket. On exit, the current character is the one + that comes after the right bracket. + */ +QString DocParser::getBracketedArgument() +{ + QString arg; + int delimDepth = 0; + skipSpacesOrOneEndl(); + if (pos < in.length() && in[pos] == '[') { + pos++; + while (pos < in.length() && delimDepth >= 0) { + switch (in[pos].unicode()) { + case '[': + delimDepth++; + arg += QLatin1Char('['); + pos++; + break; + case ']': + delimDepth--; + if (delimDepth >= 0) + arg += QLatin1Char(']'); + pos++; + break; + case '\\': + arg += in[pos]; + pos++; + break; + default: + arg += in[pos]; + pos++; + } + } + if (delimDepth > 0) + location().warning(tr("Missing ']'")); + } + return arg; +} + +QString DocParser::getOptionalArgument() +{ + skipSpacesOrOneEndl(); + if (pos + 1 < (int) in.length() && in[pos] == '\\' && + in[pos + 1].isLetterOrNumber()) { + return QString(); + } + else { + return getArgument(); + } +} + +QString DocParser::getRestOfLine() +{ + QString t; + + skipSpacesOnLine(); + + bool trailingSlash = false; + + do { + int begin = pos; + + while (pos < in.size() && in[pos] != '\n') { + if (in[pos] == '\\' && !trailingSlash) { + trailingSlash = true; + ++pos; + while ((pos < in.size()) && + in[pos].isSpace() && + (in[pos] != '\n')) + ++pos; + } + else { + trailingSlash = false; + ++pos; + } + } + + if (!t.isEmpty()) + t += QLatin1Char(' '); + t += in.mid(begin, pos - begin).simplified(); + + if (trailingSlash) { + t.chop(1); + t = t.simplified(); + } + if (pos < in.size()) + ++pos; + } while (pos < in.size() && trailingSlash); + + return t; +} + +/*! + The metacommand argument is normally the remaining text to + the right of the metacommand itself. The extra blanks are + stripped and the argument string is returned. + */ +QString DocParser::getMetaCommandArgument(const QString &cmdStr) +{ + skipSpacesOnLine(); + + int begin = pos; + int parenDepth = 0; + + while (pos < in.size() && (in[pos] != '\n' || parenDepth > 0)) { + if (in.at(pos) == '(') + ++parenDepth; + else if (in.at(pos) == ')') + --parenDepth; + + ++pos; + } + if (pos == in.size() && parenDepth > 0) { + pos = begin; + location().warning(tr("Unbalanced parentheses in '%1'").arg(cmdStr)); + } + + QString t = in.mid(begin, pos - begin).simplified(); + skipSpacesOnLine(); + return t; +} + +QString DocParser::getUntilEnd(int cmd) +{ + int endCmd = endCmdFor(cmd); + QRegExp rx("\\\\" + cmdName(endCmd) + "\\b"); + QString t; + int end = rx.indexIn(in, pos); + + if (end == -1) { + location().warning(tr("Missing '\\%1'").arg(cmdName(endCmd))); + pos = in.length(); + } + else { + t = in.mid(pos, end - pos); + pos = end + rx.matchedLength(); + } + return t; +} + +QString DocParser::getCode(int cmd, CodeMarker *marker) +{ + QString code = untabifyEtc(getUntilEnd(cmd)); + int indent = indentLevel(code); + if (indent < minIndent) + minIndent = indent; + code = unindent(minIndent, code); + if (!marker) + marker = CodeMarker::markerForCode(code); + return marker->markedUpCode(code, 0, location()); +} + +/*! + Was used only for generating doxygen output. + */ +QString DocParser::getUnmarkedCode(int cmd) +{ + QString code = getUntilEnd(cmd); + return code; +} + +bool DocParser::isBlankLine() +{ + int i = pos; + + while (i < len && in[i].isSpace()) { + if (in[i] == '\n') + return true; + i++; + } + return false; +} + +bool DocParser::isLeftBraceAhead() +{ + int numEndl = 0; + int i = pos; + + while (i < len && in[i].isSpace() && numEndl < 2) { + // ### bug with '\\' + if (in[i] == '\n') + numEndl++; + i++; + } + return numEndl < 2 && i < len && in[i] == '{'; +} + +bool DocParser::isLeftBracketAhead() +{ + int numEndl = 0; + int i = pos; + + while (i < len && in[i].isSpace() && numEndl < 2) { + // ### bug with '\\' + if (in[i] == '\n') + numEndl++; + i++; + } + return numEndl < 2 && i < len && in[i] == '['; +} + +/*! + Skips to the next non-space character or EOL. + */ +void DocParser::skipSpacesOnLine() +{ + while ((pos < in.length()) && + in[pos].isSpace() && + (in[pos].unicode() != '\n')) + ++pos; +} + +/*! + Skips spaces and on EOL. + */ +void DocParser::skipSpacesOrOneEndl() +{ + int firstEndl = -1; + while (pos < (int) in.length() && in[pos].isSpace()) { + QChar ch = in[pos]; + if (ch == '\n') { + if (firstEndl == -1) { + firstEndl = pos; + } + else { + pos = firstEndl; + break; + } + } + pos++; + } +} + +void DocParser::skipAllSpaces() +{ + while (pos < len && in[pos].isSpace()) + pos++; +} + +void DocParser::skipToNextPreprocessorCommand() +{ + QRegExp rx("\\\\(?:" + cmdName(CMD_IF) + QLatin1Char('|') + + cmdName(CMD_ELSE) + QLatin1Char('|') + + cmdName(CMD_ENDIF) + ")\\b"); + int end = rx.indexIn(in, pos + 1); // ### + 1 necessary? + + if (end == -1) + pos = in.length(); + else + pos = end; +} + +int DocParser::endCmdFor(int cmd) +{ + switch (cmd) { + case CMD_BADCODE: + return CMD_ENDCODE; + case CMD_CHAPTER: + return CMD_ENDCHAPTER; + case CMD_CODE: + return CMD_ENDCODE; + case CMD_DIV: + return CMD_ENDDIV; + case CMD_QML: + return CMD_ENDQML; + case CMD_QMLTEXT: + return CMD_ENDQMLTEXT; + case CMD_JS: + return CMD_ENDJS; + case CMD_FOOTNOTE: + return CMD_ENDFOOTNOTE; + case CMD_LEGALESE: + return CMD_ENDLEGALESE; + case CMD_LINK: + return CMD_ENDLINK; + case CMD_LIST: + return CMD_ENDLIST; + case CMD_NEWCODE: + return CMD_ENDCODE; + case CMD_OLDCODE: + return CMD_NEWCODE; + case CMD_OMIT: + return CMD_ENDOMIT; + case CMD_PART: + return CMD_ENDPART; + case CMD_QUOTATION: + return CMD_ENDQUOTATION; + case CMD_RAW: + return CMD_ENDRAW; + case CMD_SECTION1: + return CMD_ENDSECTION1; + case CMD_SECTION2: + return CMD_ENDSECTION2; + case CMD_SECTION3: + return CMD_ENDSECTION3; + case CMD_SECTION4: + return CMD_ENDSECTION4; + case CMD_SIDEBAR: + return CMD_ENDSIDEBAR; + case CMD_TABLE: + return CMD_ENDTABLE; + case CMD_TOPICREF: + return CMD_ENDTOPICREF; + case CMD_MAPREF: + return CMD_ENDMAPREF; + default: + return cmd; + } +} + +QString DocParser::cmdName(int cmd) +{ + return *cmds[cmd].alias; +} + +QString DocParser::endCmdName(int cmd) +{ + return cmdName(endCmdFor(cmd)); +} + +QString DocParser::untabifyEtc(const QString& str) +{ + QString result; + result.reserve(str.length()); + int column = 0; + + for (int i = 0; i < str.length(); i++) { + const QChar c = str.at(i); + if (c == QLatin1Char('\r')) + continue; + if (c == QLatin1Char('\t')) { + result += &" "[column % tabSize]; + column = ((column / tabSize) + 1) * tabSize; + continue; + } + if (c == QLatin1Char('\n')) { + while (result.endsWith(QLatin1Char(' '))) + result.chop(1); + result += c; + column = 0; + continue; + } + result += c; + column++; + } + + while (result.endsWith("\n\n")) + result.truncate(result.length() - 1); + while (result.startsWith(QLatin1Char('\n'))) + result = result.mid(1); + + return result; +} + +int DocParser::indentLevel(const QString& str) +{ + int minIndent = INT_MAX; + int column = 0; + + for (int i = 0; i < (int) str.length(); i++) { + if (str[i] == '\n') { + column = 0; + } + else { + if (str[i] != ' ' && column < minIndent) + minIndent = column; + column++; + } + } + return minIndent; +} + +QString DocParser::unindent(int level, const QString& str) +{ + if (level == 0) + return str; + + QString t; + int column = 0; + + for (int i = 0; i < (int) str.length(); i++) { + if (str[i] == QLatin1Char('\n')) { + t += '\n'; + column = 0; + } + else { + if (column >= level) + t += str[i]; + column++; + } + } + return t; +} + +QString DocParser::slashed(const QString& str) +{ + QString result = str; + result.replace(QLatin1Char('/'), "\\/"); + return QLatin1Char('/') + result + QLatin1Char('/'); +} + +/*! + Parse the qdoc comment \a source. Build up a list of all the topic + commands found including their arguments. This constructor is used + when there can be more than one topic command in theqdoc comment. + Normally, there is only one topic command in a qdoc comment, but in + QML documentation, there is the case where the qdoc \e{qmlproperty} + command can appear multiple times in a qdoc comment. + */ +Doc::Doc(const Location& start_loc, + const Location& end_loc, + const QString& source, + const QSet<QString>& metaCommandSet, + const QSet<QString>& topics) +{ + priv = new DocPrivate(start_loc,end_loc,source); + DocParser parser; + parser.parse(source,priv,metaCommandSet,topics); +} + +Doc::Doc(const Doc& doc) + : priv(0) +{ + operator=(doc); +} + +Doc::~Doc() +{ + if (priv && priv->deref()) + delete priv; +} + +Doc &Doc::operator=(const Doc& doc) +{ + if (doc.priv) + doc.priv->ref(); + if (priv && priv->deref()) + delete priv; + priv = doc.priv; + return *this; +} + +void Doc::renameParameters(const QStringList &oldNames, + const QStringList &newNames) +{ + if (priv && oldNames != newNames) { + detach(); + + priv->params = newNames.toSet(); + + Atom *atom = priv->text.firstAtom(); + while (atom) { + if (atom->type() == Atom::FormattingLeft + && atom->string() == ATOM_FORMATTING_PARAMETER) { + atom = atom->next(); + if (!atom) + return; + int index = oldNames.indexOf(atom->string()); + if (index != -1 && index < newNames.count()) + atom->setString(newNames.at(index)); + } + atom = atom->next(); + } + } +} + +void Doc::simplifyEnumDoc() +{ + if (priv) { + if (priv->isEnumDocSimplifiable()) { + detach(); + + Text newText; + + Atom *atom = priv->text.firstAtom(); + while (atom) { + if ((atom->type() == Atom::ListLeft) && + (atom->string() == ATOM_LIST_VALUE)) { + while (atom && ((atom->type() != Atom::ListRight) || + (atom->string() != ATOM_LIST_VALUE))) + atom = atom->next(); + if (atom) + atom = atom->next(); + } + else { + newText << *atom; + atom = atom->next(); + } + } + priv->text = newText; + } + } +} + +void Doc::setBody(const Text &text) +{ + detach(); + priv->text = text; +} + +/*! + Returns the starting location of a qdoc comment. + */ +const Location &Doc::location() const +{ + static const Location dummy; + return priv == 0 ? dummy : priv->start_loc; +} + +/*! + Returns the starting location of a qdoc comment. + */ +const Location& Doc::startLocation() const +{ + return location(); +} + +/*! + Returns the ending location of a qdoc comment. + */ +const Location& Doc::endLocation() const +{ + static const Location dummy; + return priv == 0 ? dummy : priv->end_loc; +} + +const QString &Doc::source() const +{ + static QString null; + return priv == 0 ? null : priv->src; +} + +bool Doc::isEmpty() const +{ + return priv == 0 || priv->src.isEmpty(); +} + +const Text& Doc::body() const +{ + static const Text dummy; + return priv == 0 ? dummy : priv->text; +} + +Text Doc::briefText(bool inclusive) const +{ + return body().subText(Atom::BriefLeft, Atom::BriefRight, 0, inclusive); +} + +Text Doc::trimmedBriefText(const QString &className) const +{ + QString classNameOnly = className; + if (className.contains("::")) + classNameOnly = className.split("::").last(); + + Text originalText = briefText(); + Text resultText; + const Atom *atom = originalText.firstAtom(); + if (atom) { + QString briefStr; + QString whats; + /* + This code is really ugly. The entire \brief business + should be rethought. + */ + while (atom) { + if (atom->type() == Atom::AutoLink || atom->type() == Atom::String) { + briefStr += atom->string(); + } + atom = atom->next(); + } + + QStringList w = briefStr.split(QLatin1Char(' ')); + if (!w.isEmpty() && w.first() == "Returns") { + } + else { + if (!w.isEmpty() && w.first() == "The") + w.removeFirst(); + + if (!w.isEmpty() && (w.first() == className || w.first() == classNameOnly)) + w.removeFirst(); + + if (!w.isEmpty() && ((w.first() == "class") || + (w.first() == "function") || + (w.first() == "macro") || + (w.first() == "widget") || + (w.first() == "namespace") || + (w.first() == "header"))) + w.removeFirst(); + + if (!w.isEmpty() && (w.first() == "is" || w.first() == "provides")) + w.removeFirst(); + + if (!w.isEmpty() && (w.first() == "a" || w.first() == "an")) + w.removeFirst(); + } + + whats = w.join(' '); + + if (whats.endsWith(QLatin1Char('.'))) + whats.truncate(whats.length() - 1); + + if (!whats.isEmpty()) + whats[0] = whats[0].toUpper(); + + // ### move this once \brief is abolished for properties + resultText << whats; + } + return resultText; +} + +Text Doc::legaleseText() const +{ + if (priv == 0 || !priv->hasLegalese) + return Text(); + else + return body().subText(Atom::LegaleseLeft, Atom::LegaleseRight); +} + +Doc::Sections Doc::granularity() const +{ + if (priv == 0 || priv->extra == 0) { + return DocPrivateExtra().granularity_; + } + else { + return priv->extra->granularity_; + } +} + +const QSet<QString> &Doc::parameterNames() const +{ + return priv == 0 ? *null_Set_QString() : priv->params; +} + +const QStringList &Doc::enumItemNames() const +{ + return priv == 0 ? *null_QStringList() : priv->enumItemList; +} + +const QStringList &Doc::omitEnumItemNames() const +{ + return priv == 0 ? *null_QStringList() : priv->omitEnumItemList; +} + +const QSet<QString> &Doc::metaCommandsUsed() const +{ + return priv == 0 ? *null_Set_QString() : priv->metacommandsUsed; +} + +/*! + Returns a reference to the list of topic commands used in the + current qdoc comment. Normally there is only one, but there + can be multiple \e{qmlproperty} commands, for example. + */ +const TopicList& Doc::topicsUsed() const +{ + return priv == 0 ? *nullTopicList() : priv->topics_; +} + +ArgList Doc::metaCommandArgs(const QString& metacommand) const +{ + return priv == 0 ? ArgList() : priv->metaCommandMap.value(metacommand); +} + +const QList<Text> &Doc::alsoList() const +{ + return priv == 0 ? *null_QList_Text() : priv->alsoList; +} + +bool Doc::hasTableOfContents() const +{ + return priv && priv->extra && !priv->extra->tableOfContents_.isEmpty(); +} + +bool Doc::hasKeywords() const +{ + return priv && priv->extra && !priv->extra->keywords_.isEmpty(); +} + +bool Doc::hasTargets() const +{ + return priv && priv->extra && !priv->extra->targets_.isEmpty(); +} + +const QList<Atom *> &Doc::tableOfContents() const +{ + priv->constructExtra(); + return priv->extra->tableOfContents_; +} + +const QVector<int> &Doc::tableOfContentsLevels() const +{ + priv->constructExtra(); + return priv->extra->tableOfContentsLevels_; +} + +const QList<Atom *> &Doc::keywords() const +{ + priv->constructExtra(); + return priv->extra->keywords_; +} + +const QList<Atom *> &Doc::targets() const +{ + priv->constructExtra(); + return priv->extra->targets_; +} + +const QStringMultiMap &Doc::metaTagMap() const +{ + return priv && priv->extra ? priv->extra->metaMap_ : *null_QStringMultiMap(); +} + +const Config* Doc::config_ = 0; + +void Doc::initialize(const Config& config) +{ + DocParser::tabSize = config.getInt(CONFIG_TABSIZE); + DocParser::exampleFiles = config.getCanonicalPathList(CONFIG_EXAMPLES); + DocParser::exampleDirs = config.getCanonicalPathList(CONFIG_EXAMPLEDIRS); + DocParser::sourceFiles = config.getCanonicalPathList(CONFIG_SOURCES); + DocParser::sourceDirs = config.getCanonicalPathList(CONFIG_SOURCEDIRS); + DocParser::quoting = config.getBool(CONFIG_QUOTINGINFORMATION); + + QmlTypeNode::qmlOnly = config.getBool(CONFIG_QMLONLY); + QStringMap reverseAliasMap; + config_ = &config; + + QSet<QString> commands = config.subVars(CONFIG_ALIAS); + QSet<QString>::ConstIterator c = commands.constBegin(); + while (c != commands.constEnd()) { + QString alias = config.getString(CONFIG_ALIAS + Config::dot + *c); + if (reverseAliasMap.contains(alias)) { + config.lastLocation().warning(tr("Command name '\\%1' cannot stand" + " for both '\\%2' and '\\%3'") + .arg(alias) + .arg(reverseAliasMap[alias]) + .arg(*c)); + } + else { + reverseAliasMap.insert(alias, *c); + } + aliasMap()->insert(*c, alias); + ++c; + } + + int i = 0; + while (cmds[i].english) { + cmds[i].alias = new QString(alias(cmds[i].english)); + cmdHash()->insert(*cmds[i].alias, cmds[i].no); + + if (cmds[i].no != i) + Location::internalError(tr("command %1 missing").arg(i)); + i++; + } + + QSet<QString> macroNames = config.subVars(CONFIG_MACRO); + QSet<QString>::ConstIterator n = macroNames.constBegin(); + while (n != macroNames.constEnd()) { + QString macroDotName = CONFIG_MACRO + Config::dot + *n; + Macro macro; + macro.numParams = -1; + macro.defaultDef = config.getString(macroDotName); + if (!macro.defaultDef.isEmpty()) { + macro.defaultDefLocation = config.lastLocation(); + macro.numParams = Config::numParams(macro.defaultDef); + } + bool silent = false; + + QSet<QString> formats = config.subVars(macroDotName); + QSet<QString>::ConstIterator f = formats.constBegin(); + while (f != formats.constEnd()) { + QString def = config.getString(macroDotName + Config::dot + *f); + if (!def.isEmpty()) { + macro.otherDefs.insert(*f, def); + int m = Config::numParams(def); + if (macro.numParams == -1) { + macro.numParams = m; + } + else if (macro.numParams != m) { + if (!silent) { + QString other = tr("default"); + if (macro.defaultDef.isEmpty()) + other = macro.otherDefs.constBegin().key(); + config.lastLocation().warning(tr("Macro '\\%1' takes" + " inconsistent number" + " of arguments (%2" + " %3, %4 %5)") + .arg(*n) + .arg(*f) + .arg(m) + .arg(other) + .arg(macro.numParams)); + silent = true; + } + if (macro.numParams < m) + macro.numParams = m; + } + } + ++f; + } + + if (macro.numParams != -1) + macroHash()->insert(*n, macro); + ++n; + } +} + +/*! + All the heap allocated variables are deleted. + */ +void Doc::terminate() +{ + DocParser::exampleFiles.clear(); + DocParser::exampleDirs.clear(); + DocParser::sourceFiles.clear(); + DocParser::sourceDirs.clear(); + aliasMap()->clear(); + cmdHash()->clear(); + macroHash()->clear(); + + int i = 0; + while (cmds[i].english) { + delete cmds[i].alias; + cmds[i].alias = 0; + ++i; + } +} + +QString Doc::alias(const QString &english) +{ + return aliasMap()->value(english, english); +} + +/*! + Trims the deadwood out of \a str. i.e., this function + cleans up \a str. + */ +void Doc::trimCStyleComment(Location& location, QString& str) +{ + QString cleaned; + Location m = location; + bool metAsterColumn = true; + int asterColumn = location.columnNo() + 1; + int i; + + for (i = 0; i < (int) str.length(); i++) { + if (m.columnNo() == asterColumn) { + if (str[i] != '*') + break; + cleaned += ' '; + metAsterColumn = true; + } + else { + if (str[i] == '\n') { + if (!metAsterColumn) + break; + metAsterColumn = false; + } + cleaned += str[i]; + } + m.advance(str[i]); + } + if (cleaned.length() == str.length()) + str = cleaned; + + for (int i = 0; i < 3; i++) + location.advance(str[i]); + str = str.mid(3, str.length() - 5); +} + +CodeMarker *Doc::quoteFromFile(const Location &location, + Quoter "er, + const QString &fileName) +{ + quoter.reset(); + + QString code; + + QString userFriendlyFilePath; + QString filePath = Config::findFile(location, + DocParser::exampleFiles, + DocParser::exampleDirs, + fileName, userFriendlyFilePath); + if (filePath.isEmpty()) { + QString details = QLatin1String("Example directories: ") + DocParser::exampleDirs.join(QLatin1Char(' ')); + if (!DocParser::exampleFiles.isEmpty()) + details += QLatin1String(", example files: ") + DocParser::exampleFiles.join(QLatin1Char(' ')); + location.warning(tr("Cannot find file to quote from: '%1'").arg(fileName), details); + } + else { + QFile inFile(filePath); + if (!inFile.open(QFile::ReadOnly)) { + location.warning(tr("Cannot open file to quote from: '%1'").arg(userFriendlyFilePath)); + } + else { + QTextStream inStream(&inFile); + code = DocParser::untabifyEtc(inStream.readAll()); + } + } + + QString dirPath = QFileInfo(filePath).path(); + CodeMarker *marker = CodeMarker::markerForFileName(fileName); + quoter.quoteFromFile(userFriendlyFilePath, code, marker->markedUpCode(code, 0, location)); + return marker; +} + +QString Doc::canonicalTitle(const QString &title) +{ + // The code below is equivalent to the following chunk, but _much_ + // faster (accounts for ~10% of total running time) + // + // QRegExp attributeExpr("[^A-Za-z0-9]+"); + // QString result = title.toLower(); + // result.replace(attributeExpr, " "); + // result = result.simplified(); + // result.replace(QLatin1Char(' '), QLatin1Char('-')); + + QString result; + result.reserve(title.size()); + + bool dashAppended = false; + bool begun = false; + int lastAlnum = 0; + for (int i = 0; i != title.size(); ++i) { + uint c = title.at(i).unicode(); + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + bool alnum = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); + if (alnum) { + result += QLatin1Char(c); + begun = true; + dashAppended = false; + lastAlnum = result.size(); + } + else if (!dashAppended) { + if (begun) + result += QLatin1Char('-'); + dashAppended = true; + } + } + result.truncate(lastAlnum); + return result; +} + +void Doc::detach() +{ + if (!priv) { + priv = new DocPrivate; + return; + } + if (priv->count == 1) + return; + + --priv->count; + + DocPrivate *newPriv = new DocPrivate(*priv); + newPriv->count = 1; + if (priv->extra) + newPriv->extra = new DocPrivateExtra(*priv->extra); + + priv = newPriv; +} + +/*! + The destructor deletes all the sub-TopicRefs. + */ +TopicRef::~TopicRef() +{ + foreach (DitaRef* t, subrefs_) { + delete t; + } +} + +/*! + Returns a reference to the structure that will be used + for generating a DITA mao. + */ +const DitaRefList& Doc::ditamap() const { return priv->ditamap_; } + +QT_END_NAMESPACE diff --git a/src/qdoc/doc.h b/src/qdoc/doc.h new file mode 100644 index 000000000..b064b5878 --- /dev/null +++ b/src/qdoc/doc.h @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + doc.h +*/ + +#ifndef DOC_H +#define DOC_H + +#include <qset.h> +#include <qstring.h> +#include <qmap.h> + +#include "location.h" + +QT_BEGIN_NAMESPACE + +class Atom; +class CodeMarker; +class Config; +class DocPrivate; +class Quoter; +class Text; +class DitaRef; + +typedef QPair<QString, Location> ArgLocPair; +typedef QList<ArgLocPair> ArgList; +typedef QMap<QString, QString> QStringMap; +typedef QMultiMap<QString, QString> QStringMultiMap; + +struct Topic +{ + QString topic; + QString args; + Topic() { } + Topic(QString& t, const QString &a) : topic(t), args(a) { } + bool isEmpty() const { return topic.isEmpty(); } + void clear() { topic.clear(); args.clear(); } +}; +typedef QList<Topic> TopicList; + +typedef QList<DitaRef*> DitaRefList; + +class DitaRef +{ +public: + DitaRef() { } + virtual ~DitaRef() { } + + const QString& navtitle() const { return navtitle_; } + const QString& href() const { return href_; } + void setNavtitle(const QString& t) { navtitle_ = t; } + void setHref(const QString& t) { href_ = t; } + virtual bool isMapRef() const = 0; + virtual const DitaRefList* subrefs() const { return 0; } + virtual void appendSubref(DitaRef* ) { } + +private: + QString navtitle_; + QString href_; +}; + +class TopicRef : public DitaRef +{ +public: + TopicRef() { } + ~TopicRef(); + + virtual bool isMapRef() const Q_DECL_OVERRIDE { return false; } + virtual const DitaRefList* subrefs() const Q_DECL_OVERRIDE { return &subrefs_; } + virtual void appendSubref(DitaRef* t) Q_DECL_OVERRIDE { subrefs_.append(t); } + +private: + DitaRefList subrefs_; +}; + +class MapRef : public DitaRef +{ +public: + MapRef() { } + ~MapRef() { } + + virtual bool isMapRef() const Q_DECL_OVERRIDE { return true; } +}; + +class Doc +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::Doc) + +public: + // the order is important + enum Sections { + NoSection = -2, + Part = -1, + Chapter = 1, + Section1 = 1, + Section2 = 2, + Section3 = 3, + Section4 = 4 + }; + + Doc() : priv(0) {} + Doc(const Location& start_loc, + const Location& end_loc, + const QString& source, + const QSet<QString>& metaCommandSet, + const QSet<QString>& topics); + Doc(const Doc &doc); + ~Doc(); + + Doc& operator=( const Doc& doc ); + + void renameParameters(const QStringList &oldNames, + const QStringList &newNames); + void simplifyEnumDoc(); + void setBody(const Text &body); + const DitaRefList& ditamap() const; + + const Location &location() const; + const Location& startLocation() const; + const Location& endLocation() const; + bool isEmpty() const; + const QString& source() const; + const Text& body() const; + Text briefText(bool inclusive = false) const; + Text trimmedBriefText(const QString &className) const; + Text legaleseText() const; + Sections granularity() const; + const QSet<QString> ¶meterNames() const; + const QStringList &enumItemNames() const; + const QStringList &omitEnumItemNames() const; + const QSet<QString> &metaCommandsUsed() const; + const TopicList& topicsUsed() const; + ArgList metaCommandArgs(const QString& metaCommand) const; + const QList<Text> &alsoList() const; + bool hasTableOfContents() const; + bool hasKeywords() const; + bool hasTargets() const; + const QList<Atom *> &tableOfContents() const; + const QVector<int> &tableOfContentsLevels() const; + const QList<Atom *> &keywords() const; + const QList<Atom *> &targets() const; + const QStringMultiMap &metaTagMap() const; + + static void initialize( const Config &config ); + static void terminate(); + static QString alias( const QString &english ); + static void trimCStyleComment( Location& location, QString& str ); + static CodeMarker *quoteFromFile(const Location &location, + Quoter "er, + const QString &fileName); + static QString canonicalTitle(const QString &title); + static const Config* config() { return config_; } + +private: + void detach(); + DocPrivate *priv; + static const Config* config_; +}; +Q_DECLARE_TYPEINFO(Doc, Q_MOVABLE_TYPE); +typedef QList<Doc> DocList; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/doc/config/qdoc.qdocconf b/src/qdoc/doc/config/qdoc.qdocconf new file mode 100644 index 000000000..9d841e9b6 --- /dev/null +++ b/src/qdoc/doc/config/qdoc.qdocconf @@ -0,0 +1,72 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) + +project = QDoc +description = QDoc Manual +version = $QT_VERSION + +sourcedirs = .. + +exampledirs = .. \ + ../examples + +imagedirs = ../images \ + ../../../../widgets/doc/images +# ../../../doc/src/templates/images + +tagfile = ../html/qdoc.tags + +examples.fileextensions = "*.cpp *.h *.js *.xq *.svg *.xml *.ui *.qhp *.qhcp *.qml *.css *.qdoc *.qdocinc *.sample" + +qhp.projects = QDoc + +qhp.QDoc.file = qdoc.qhp +qhp.QDoc.namespace = org.qt-project.qdoc.$QT_VERSION_TAG +qhp.QDoc.virtualFolder = qdoc +qhp.QDoc.indexTitle = QDoc Manual +qhp.QDoc.indexRoot = + +qhp.QDoc.filterAttributes = qdoc qtrefdoc +qhp.QDoc.customFilters.QDoc.name = QDoc +qhp.QDoc.customFilters.QDoc.filterAttributes = qdoc +qhp.QDoc.subprojects = overviews +qhp.QDoc.subprojects.overviews.title = Overviews +qhp.QDoc.subprojects.overviews.indexTitle = QDoc Manual +qhp.QDoc.subprojects.overviews.selectors = fake:page,group,module + +depends += \ + activeqt \ + qtassistant \ + qtbluetooth \ + qtconcurrent \ + qtcontacts \ + qtcore \ + qtdbus \ + qtdesigner \ + qtdoc \ + qthelp \ + qtimageformats \ + qtgui \ + qtlocation \ + qtlinguist \ + qtmultimedia \ + qtnetwork \ + qtopengl \ + qtorganizer \ + qtprintsupport \ + qtqml \ + qtquick \ + qtscript \ + qtscripttools \ + qtsensors \ + qtsql \ + qtsvg \ + qttestlib \ + qtuitools \ + qtversit \ + qtwidgets \ + qtwebkit \ + qtwebkitexamples \ + qtxml \ + qtxmlpatterns + +navigation.landingpage = "QDoc Manual" diff --git a/src/qdoc/doc/corefeatures.qdoc b/src/qdoc/doc/corefeatures.qdoc new file mode 100644 index 000000000..bbee7410f --- /dev/null +++ b/src/qdoc/doc/corefeatures.qdoc @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page corefeatures.html + \title Core Features + + \input examples/signalandslots.qdocinc + \input examples/objectmodel.qdocinc + \input examples/layoutmanagement.qdocinc +*/ diff --git a/src/qdoc/doc/examples/componentset/ProgressBar.qml b/src/qdoc/doc/examples/componentset/ProgressBar.qml new file mode 100644 index 000000000..c4e8c103e --- /dev/null +++ b/src/qdoc/doc/examples/componentset/ProgressBar.qml @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 1.0 + +/*! + \qmltype ProgressBar + \inqmlmodule UIComponents + \brief A component that shows the progress of an event + + A ProgressBar shows the linear progress of an event as its \l value. + The range is specified using the \l {minimum} and the \l{maximum} values. + + The ProgressBar component is part of the \l {UI Components} module. + + This documentation is part of the \l{componentset}{UIComponents} example. +*/ +Item { + id: progressbar + + /*! + The minimum value of the ProgressBar range. + The \l value must not be less than this value. + */ + property int minimum: 0 + + /*! + The maximum value of the ProgressBar range. + The \l value must not be more than this value. + */ + property int maximum: 100 + + /*! + The value of the progress. + */ + property int value: 0 + + /*! + \qmlproperty color ProgressBar::color + The color of the ProgressBar's gradient. Must bind to a color type. + + \omit + The "\qmlproperty <type> <property name>" is needed because + property alias need to have their types manually entered. + + QDoc will not publish the documentation within omit and endomit. + \endomit + + \sa secondColor + */ + property alias color: gradient1.color + + /*! + \qmlproperty color ProgressBar::secondColor + The second color of the ProgressBar's gradient. + Must bind to a color type. + + \omit + The "\qmlproperty <type> <property name>" is needed because + property alias need to have their types manually entered. + + QDoc will not publish the documentation within omit and endomit. + \endomit + + \sa color + */ + property alias secondColor: gradient2.color + + width: 250; height: 23 + clip: true + + Rectangle { + id: highlight + + /*! + An internal documentation comment. The widthDest property is not + a public API and therefore will not be exposed. + */ + property int widthDest: ((progressbar.width * (value - minimum)) / (maximum - minimum) - 6) + + width: highlight.widthDest + Behavior on width { SmoothedAnimation { velocity: 1200 } } + + anchors { left: parent.left; top: parent.top; bottom: parent.bottom; margins: 3 } + radius: 1 + gradient: Gradient { + GradientStop { id: gradient1; position: 0.0 } + GradientStop { id: gradient2; position: 1.0 } + } + + } + Text { + anchors { right: highlight.right; rightMargin: 6; verticalCenter: parent.verticalCenter } + color: "white" + font.bold: true + text: Math.floor((value - minimum) / (maximum - minimum) * 100) + '%' + } +} diff --git a/src/qdoc/doc/examples/componentset/Switch.qml b/src/qdoc/doc/examples/componentset/Switch.qml new file mode 100644 index 000000000..7b7e7af31 --- /dev/null +++ b/src/qdoc/doc/examples/componentset/Switch.qml @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 1.0 + +/*! + \qmltype ToggleSwitch + \inqmlmodule UIComponents + \brief A component that can be turned on or off + + A toggle switch has two states: an \c on and an \c off state. The \c off + state is when the \l on property is set to \c false. + + The ToggleSwitch component is part of the \l {UI Components} module. + + This documentation is part of the \l{componentset}{UIComponents} example. + +*/ +Item { + id: toggleswitch + width: background.width; height: background.height + + /*! + Indicates the state of the switch. If \c false, then the switch is in + the \c off state. + + \omit + The \qmlproperty <type> <propertyname> is not necessary as QDoc + will associate this property to the ToggleSwitch + + QDoc will not publish the documentation within omit and endomit. + \endomit + */ + property bool on: false + + + /*! + A method to toggle the switch. If the switch is \c on, the toggling it + will turn it \c off. Toggling a switch in the \c off position will + turn it \c on. + */ + function toggle() { + if (toggleswitch.state == "on") + toggleswitch.state = "off"; + else + toggleswitch.state = "on"; + } + + + /*! + \internal + + An internal function to synchronize the switch's internals. This + function is not for public access. The \internal command will + prevent QDoc from publishing this comment in the public API. + */ + function releaseSwitch() { + if (knob.x == 1) { + if (toggleswitch.state == "off") return; + } + if (knob.x == 78) { + if (toggleswitch.state == "on") return; + } + toggle(); + } + + Rectangle { + id: background + width: 130; height: 48 + radius: 48 + color: "lightsteelblue" + MouseArea { anchors.fill: parent; onClicked: toggle() } + } + + Rectangle { + id: knob + width: 48; height: 48 + radius: width + color: "lightblue" + + MouseArea { + anchors.fill: parent + drag.target: knob; drag.axis: Drag.XAxis; drag.minimumX: 1; drag.maximumX: 78 + onClicked: toggle() + onReleased: releaseSwitch() + } + } + + states: [ + State { + name: "on" + PropertyChanges { target: knob; x: 78 } + PropertyChanges { target: toggleswitch; on: true } + }, + State { + name: "off" + PropertyChanges { target: knob; x: 1 } + PropertyChanges { target: toggleswitch; on: false } + } + ] + + transitions: Transition { + NumberAnimation { properties: "x"; easing.type: Easing.InOutQuad; duration: 200 } + } +} diff --git a/src/qdoc/doc/examples/componentset/TabWidget.qml b/src/qdoc/doc/examples/componentset/TabWidget.qml new file mode 100644 index 000000000..008c5e14e --- /dev/null +++ b/src/qdoc/doc/examples/componentset/TabWidget.qml @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 1.0 + +/*! + \qmltype TabWidget + \inqmlmodule UIComponents + \brief A widget that places its children as tabs + + A TabWidget places its children as tabs in a view. Selecting + a tab involves selecting the tab at the top. + + The TabWidget component is part of the \l {UI Components} module. + + This documentation is part of the \l{componentset}{UIComponents} example. + + \section1 Adding Tabs + + To add a tab, declare the tab as a child of the TabWidget. + + \code + TabWidget { + id: tabwidget + + Rectangle { + id: tab1 + color: "red" + //... omitted + } + Rectangle { + id: tab2 + color: "blue" + //... omitted + } + + } + \endcode + +*/ +Item { + id: tabWidget + + /*! + \internal + + Setting the default property to stack.children means any child items + of the TabWidget are actually added to the 'stack' item's children. + + See the \l{"Property Binding in QML"} + documentation for details on default properties. + + This is an implementation detail, not meant for public knowledge. Putting + the \internal command at the beginning will cause QDoc to not publish this + documentation in the public API page. + + Normally, a property alias needs to have a + "\qmlproperty <type> <propertyname>" to assign the alias a type. + + */ + default property alias content: stack.children + + + /*! + The currently active tab in the TabWidget. + */ + property int current: 0 + + /*! + A sample \c{read-only} property. + A contrived property to demonstrate QDoc's ability to detect + read-only properties. + + The signature is: + \code + readonly property int sampleReadOnlyProperty: 0 + \endcode + + Note that the property must be initialized to a value. + + */ + readonly property int sampleReadOnlyProperty: 0 + + /*! + \internal + + This handler is an implementation + detail. The \c{\internal} command will prevent QDoc from publishing this + documentation on the public API. + */ + onCurrentChanged: setOpacities() + Component.onCompleted: setOpacities() + + /*! + \internal + + An internal function to set the opacity. + The \internal command will prevent QDoc from publishing this + documentation on the public API. + */ + function setOpacities() { + for (var i = 0; i < stack.children.length; ++i) { + stack.children[i].opacity = (i == current ? 1 : 0) + } + } + + Row { + id: header + + Repeater { + model: stack.children.length + delegate: Rectangle { + width: tabWidget.width / stack.children.length; height: 36 + + Rectangle { + width: parent.width; height: 1 + anchors { bottom: parent.bottom; bottomMargin: 1 } + color: "#acb2c2" + } + BorderImage { + anchors { fill: parent; leftMargin: 2; topMargin: 5; rightMargin: 1 } + border { left: 7; right: 7 } + source: "tab.png" + visible: tabWidget.current == index + } + Text { + horizontalAlignment: Qt.AlignHCenter; verticalAlignment: Qt.AlignVCenter + anchors.fill: parent + text: stack.children[index].title + elide: Text.ElideRight + font.bold: tabWidget.current == index + } + MouseArea { + anchors.fill: parent + onClicked: tabWidget.current = index + } + } + } + } + + Item { + id: stack + width: tabWidget.width + anchors.top: header.bottom; anchors.bottom: tabWidget.bottom + } +} diff --git a/src/qdoc/doc/examples/componentset/componentset.pro b/src/qdoc/doc/examples/componentset/componentset.pro new file mode 100644 index 000000000..5b44737c2 --- /dev/null +++ b/src/qdoc/doc/examples/componentset/componentset.pro @@ -0,0 +1,5 @@ +SOURCES = componentset.pro \ + ProgressBar.qml \ + Switch.qml \ + TabWidget.qml \ + uicomponents.qdoc diff --git a/src/qdoc/doc/examples/componentset/uicomponents.qdoc.sample b/src/qdoc/doc/examples/componentset/uicomponents.qdoc.sample new file mode 100644 index 000000000..7a14f88f4 --- /dev/null +++ b/src/qdoc/doc/examples/componentset/uicomponents.qdoc.sample @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \qmlmodule UIComponents 1.0 + \title UI Components + \brief Basic set of UI components + + This is a listing of a list of UI components implemented by QML types. These + files are available for general import and they are based off the \l{Qt + Quick Code Samples}. + + This module is part of the \l{componentset}{UIComponents} example. +*/ diff --git a/src/qdoc/doc/examples/cpp.qdoc.sample b/src/qdoc/doc/examples/cpp.qdoc.sample new file mode 100644 index 000000000..38a131783 --- /dev/null +++ b/src/qdoc/doc/examples/cpp.qdoc.sample @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//![class] +/*! + \class QCache + \brief The QCache class is a template class that provides a cache. + + \ingroup tools + \ingroup shared + + \reentrant + + QCache\<Key, T\> defines a cache that stores objects of type T + associated with keys of type Key. For example, here's the + definition of a cache that stores objects of type Employee + associated with an integer key: + + \snippet code/doc_src_qcache.cpp 0 + + Here's how to insert an object in the cache: + + \snippet code/doc_src_qcache.cpp 1 + + ... detailed description ommitted + + \sa QPixmapCache, QHash, QMap +*/ +//![class] + +//![function] +/*! + \fn QString &QString::remove(int position, int n) + + Removes \a n characters from the string, starting at the given \a + position index, and returns a reference to the string. + + If the specified \a position index is within the string, but \a + position + \a n is beyond the end of the string, the string is + truncated at the specified \a position. + + \snippet qstring/main.cpp 37 + + \sa insert(), replace() +*/ +QString &QString::remove(int pos, int len) +//! [function] + +//! [return] +/*! + Returns \c true if a QScroller object was already created for \a target; \c false otherwise. + + \sa scroller() +*/ +bool QScroller::hasScroller(QObject *target) +//! [return] + +//! [property] +/*! + \property QVariantAnimation::duration + \brief the duration of the animation + + This property describes the duration in milliseconds of the + animation. The default duration is 250 milliseconds. + + \sa QAbstractAnimation::duration() + */ +int QVariantAnimation::duration() const +//! [property] + +//! [signals] +/*! + \fn QAbstractTransition::triggered() + + This signal is emitted when the transition has been triggered (after + onTransition() has been called). +*/ +//! [signals] + +//! [enums] +/*! + \enum QSql::TableType + + This enum type describes types of SQL tables. + + \value Tables All the tables visible to the user. + \value SystemTables Internal tables used by the database. + \value Views All the views visible to the user. + \value AllTables All of the above. +*/ +//! [enums] + +//! [overloaded notifier] +/*! +\property QSpinBox::value +\brief the value of the spin box + +setValue() will emit valueChanged() if the new value is different +from the old one. The \l{QSpinBox::}{value} property has a second notifier +signal which includes the spin box's prefix and suffix. +*/ +//! [overloaded notifier] diff --git a/src/qdoc/doc/examples/examples.qdoc b/src/qdoc/doc/examples/examples.qdoc new file mode 100644 index 000000000..28810e30d --- /dev/null +++ b/src/qdoc/doc/examples/examples.qdoc @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example componentset + \title QML Documentation Example + + This example demonstrates one of the ways to document QML types. + + In particular, there are sample types that are documented with QDoc + commands comments. There are documentation comments for the QML types + and their public interfaces. The types are grouped into a module, the + \l{UI Components} module. + + The \l{componentset/uicomponents.qdoc.sample}{uicomponents.qdoc} file generates + the overview page for the \l{UI Components} module page. + + The generated documentation is available in the \l{UI Components} module. + + \section1 QML Class + + The QML types use the \l{qmltype-command}{\\qmltype} to document the + type. In addition, they have the \l{inmodule-command}{\\inmodule} + command in order for QDoc to associate them to the \c UIComponents module. + + QDoc uses the \l{brief-command}{\\brief} command to place a basic + description when listing the types. + + \section1 Properties, Signals, Handlers, and Methods + + The types have their properties, signals, handlers, and methods + defined in their respective QML files. QDoc associates the properties and + methods to the types, therefore, you only need to place the + documentation above the property, method, or signal. + + To document the type of a \e {property alias}, you must use the + \l{qmlproperty-command}{\\qmlproperty} command to specify the data type. + + \code + \qmlproperty int anAliasedProperty + An aliased property of type int. + \endcode + + \section2 Internal Documentation + + You may declare that a documentation is for internal use by placing the + \l{internal-command}{\\internal} command after the beginning QDoc comment + \begincomment. QDoc will prevent the internal documentation from appearing + in the public API. + + If you wish to omit certain parts of the documentation, you may use the + \l{omit-command}{\\omit} and \l{omit-command}{\\endomit} command. + + \section1 QML Types with C++ Implementation + + This example only demonstrates the documentation for types in QML + files, but the regular \l{qml-documentation}{QML commands} may be placed + inside C++ classes to define the public API of the QML type. + +*/ + + +/*! + \qmlmodule UIComponents 1.0 + \title UI Components + \brief Basic set of UI components + + This is a listing of a list of UI components implemented by QML types. These + files are available for general import and they are based on the + \l{Qt Quick Examples and Tutorials}{Qt Quick Code Samples}. + + This module is part of the \l{componentset}{UIComponents} example. +*/ diff --git a/src/qdoc/doc/examples/layoutmanagement.qdocinc b/src/qdoc/doc/examples/layoutmanagement.qdocinc new file mode 100644 index 000000000..780b03c8f --- /dev/null +++ b/src/qdoc/doc/examples/layoutmanagement.qdocinc @@ -0,0 +1,13 @@ +\section1 Layout Classes + +The Qt layout system provides a simple and powerful way of specifying +the layout of child widgets. + +By specifying the logical layout once, you get the following benefits: + +\list + \li Positioning of child widgets. + \li Sensible default sizes for windows. + \li Sensible minimum sizes for windows. + \li ... +\endlist diff --git a/src/qdoc/doc/examples/main.cpp b/src/qdoc/doc/examples/main.cpp new file mode 100644 index 000000000..849405e0a --- /dev/null +++ b/src/qdoc/doc/examples/main.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QApplication> +#include <QPushButton> + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + QPushButton hello("Hello world!"); + hello.resize(100, 30); + + hello.show(); + return app.exec(); +} diff --git a/src/qdoc/doc/examples/mainwindow.cpp b/src/qdoc/doc/examples/mainwindow.cpp new file mode 100644 index 000000000..68b878c07 --- /dev/null +++ b/src/qdoc/doc/examples/mainwindow.cpp @@ -0,0 +1,243 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtWidgets> + +#include "mainwindow.h" +#include "scribblearea.h" + +//! [0] +MainWindow::MainWindow() +{ + scribbleArea = new ScribbleArea; + setCentralWidget(scribbleArea); + + createActions(); + createMenus(); + + setWindowTitle(tr("Scribble")); + resize(500, 500); +} +//! [0] + +//! [1] +void MainWindow::closeEvent(QCloseEvent *event) +//! [1] //! [2] +{ + if (maybeSave()) { + event->accept(); + } else { + event->ignore(); + } +} +//! [2] + +//! [3] +void MainWindow::open() +//! [3] //! [4] +{ + if (maybeSave()) { + QString fileName = QFileDialog::getOpenFileName(this, + tr("Open File"), QDir::currentPath()); + if (!fileName.isEmpty()) + scribbleArea->openImage(fileName); + } +} +//! [4] + +//! [5] +void MainWindow::save() +//! [5] //! [6] +{ + QAction *action = qobject_cast<QAction *>(sender()); + QByteArray fileFormat = action->data().toByteArray(); + saveFile(fileFormat); +} +//! [6] + +//! [7] +void MainWindow::penColor() +//! [7] //! [8] +{ + QColor newColor = QColorDialog::getColor(scribbleArea->penColor()); + if (newColor.isValid()) + scribbleArea->setPenColor(newColor); +} +//! [8] + +//! [9] +void MainWindow::penWidth() +//! [9] //! [10] +{ + bool ok; + int newWidth = QInputDialog::getInteger(this, tr("Scribble"), + tr("Select pen width:"), + scribbleArea->penWidth(), + 1, 50, 1, &ok); + if (ok) + scribbleArea->setPenWidth(newWidth); +} +//! [10] + +//! [11] +void MainWindow::about() +//! [11] //! [12] +{ + QMessageBox::about(this, tr("About Scribble"), + tr("<p>The <b>Scribble</b> example shows how to use QMainWindow as the " + "base widget for an application, and how to reimplement some of " + "QWidget's event handlers to receive the events generated for " + "the application's widgets:</p><p> We reimplement the mouse event " + "handlers to facilitate drawing, the paint event handler to " + "update the application and the resize event handler to optimize " + "the application's appearance. In addition we reimplement the " + "close event handler to intercept the close events before " + "terminating the application.</p><p> The example also demonstrates " + "how to use QPainter to draw an image in real time, as well as " + "to repaint widgets.</p>")); +} +//! [12] + +//! [13] +void MainWindow::createActions() +//! [13] //! [14] +{ + openAct = new QAction(tr("&Open..."), this); + openAct->setShortcuts(QKeySequence::Open); + connect(openAct, SIGNAL(triggered()), this, SLOT(open())); + + foreach (const QByteArray &format, QImageWriter::supportedImageFormats()) { + QString text = tr("%1...").arg(QString(format).toUpper()); + + QAction *action = new QAction(text, this); + action->setData(format); + connect(action, SIGNAL(triggered()), this, SLOT(save())); + saveAsActs.append(action); + } + + printAct = new QAction(tr("&Print..."), this); + connect(printAct, SIGNAL(triggered()), scribbleArea, SLOT(print())); + + exitAct = new QAction(tr("E&xit"), this); + exitAct->setShortcuts(QKeySequence::Quit); + connect(exitAct, SIGNAL(triggered()), this, SLOT(close())); + + penColorAct = new QAction(tr("&Pen Color..."), this); + connect(penColorAct, SIGNAL(triggered()), this, SLOT(penColor())); + + penWidthAct = new QAction(tr("Pen &Width..."), this); + connect(penWidthAct, SIGNAL(triggered()), this, SLOT(penWidth())); + + clearScreenAct = new QAction(tr("&Clear Screen"), this); + clearScreenAct->setShortcut(tr("Ctrl+L")); + connect(clearScreenAct, SIGNAL(triggered()), + scribbleArea, SLOT(clearImage())); + + aboutAct = new QAction(tr("&About"), this); + connect(aboutAct, SIGNAL(triggered()), this, SLOT(about())); + + aboutQtAct = new QAction(tr("About &Qt"), this); + connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt())); +} +//! [14] + +//! [15] +void MainWindow::createMenus() +//! [15] //! [16] +{ + saveAsMenu = new QMenu(tr("&Save As"), this); + foreach (QAction *action, saveAsActs) + saveAsMenu->addAction(action); + + fileMenu = new QMenu(tr("&File"), this); + fileMenu->addAction(openAct); + fileMenu->addMenu(saveAsMenu); + fileMenu->addAction(printAct); + fileMenu->addSeparator(); + fileMenu->addAction(exitAct); + + optionMenu = new QMenu(tr("&Options"), this); + optionMenu->addAction(penColorAct); + optionMenu->addAction(penWidthAct); + optionMenu->addSeparator(); + optionMenu->addAction(clearScreenAct); + + helpMenu = new QMenu(tr("&Help"), this); + helpMenu->addAction(aboutAct); + helpMenu->addAction(aboutQtAct); + + menuBar()->addMenu(fileMenu); + menuBar()->addMenu(optionMenu); + menuBar()->addMenu(helpMenu); +} +//! [16] + +//! [17] +bool MainWindow::maybeSave() +//! [17] //! [18] +{ + if (scribbleArea->isModified()) { + QMessageBox::StandardButton ret; + ret = QMessageBox::warning(this, tr("Scribble"), + tr("The image has been modified.\n" + "Do you want to save your changes?"), + QMessageBox::Save | QMessageBox::Discard + | QMessageBox::Cancel); + if (ret == QMessageBox::Save) { + return saveFile("png"); + } else if (ret == QMessageBox::Cancel) { + return false; + } + } + return true; +} +//! [18] + +//! [19] +bool MainWindow::saveFile(const QByteArray &fileFormat) +//! [19] //! [20] +{ + QString initialPath = QDir::currentPath() + "/untitled." + fileFormat; + + QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"), + initialPath, + tr("%1 Files (*.%2);;All Files (*)") + .arg(QString(fileFormat.toUpper())) + .arg(QString(fileFormat))); + if (fileName.isEmpty()) { + return false; + } else { + return scribbleArea->saveImage(fileName, fileFormat); + } +} +//! [20] diff --git a/src/qdoc/doc/examples/minimum.qdocconf b/src/qdoc/doc/examples/minimum.qdocconf new file mode 100644 index 000000000..e360685f1 --- /dev/null +++ b/src/qdoc/doc/examples/minimum.qdocconf @@ -0,0 +1,38 @@ +# QDoc is a tool that constantly evolves to suit our needs, +# and there are some compatibility issues between old and new +# practices. For that reason, any QDoc configuration file needs to +# include compat.qdocconf. + +#include(compat.qdocconf) + + +# The outputdir variable specifies the directory +# where QDoc will put the generated documentation. + +outputdir = html + + +# The headerdirs variable specifies the directories +# containing the header files associated +# with the .cpp source files used in the documentation. + +headerdirs = . + + +# The sourcedirs variable specifies the +# directories containing the .cpp or .qdoc +# files used in the documentation. + +#sourcedirs = . + + +# The exampledirs variable specifies the directories containing +# the source code of the example files. + +exampledirs = . + + +# The imagedirs variable specifies the +# directories containing the images used in the documentation. + +imagedirs = ./images diff --git a/src/qdoc/doc/examples/objectmodel.qdocinc b/src/qdoc/doc/examples/objectmodel.qdocinc new file mode 100644 index 000000000..02b5991c4 --- /dev/null +++ b/src/qdoc/doc/examples/objectmodel.qdocinc @@ -0,0 +1,11 @@ +\section1 Qt Object Model + +The standard C++ object model provides very efficient runtime support +for the object paradigm. But its static nature is inflexibile in +certain problem domains. Graphical user interface programming is a +domain that requires both runtime efficiency and a high level of +flexibility. Qt provides this, by combining the speed of C++ with the +flexibility of the Qt Object Model. + +... + diff --git a/src/qdoc/doc/examples/qml.qdoc.sample b/src/qdoc/doc/examples/qml.qdoc.sample new file mode 100644 index 000000000..cacd91224 --- /dev/null +++ b/src/qdoc/doc/examples/qml.qdoc.sample @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//![qmltype] + \qmltype TextEdit + \instantiates QQuickTextEdit + \inqmlmodule QtQuick + \ingroup qtquick-visual + \ingroup qtquick-input + \inherits Item + \brief Displays multiple lines of editable formatted text + + The TextEdit item displays a block of editable, formatted text. + + It can display both plain and rich text. For example: + + \qml + TextEdit { + width: 240 + text: "<b>Hello</b> <i>World!</i>" + font.family: "Helvetica" + font.pointSize: 20 + color: "blue" + focus: true + } + \endqml + + \image declarative-textedit.gif + + ... omitted detailed description + + \sa Text, TextInput, {examples/quick/text/textselection}{Text Selection example} +//![qmltype] + +//![function] +/* + \qmlmethod QtQuick2::ListModel::remove(int index, int count = 1) + + Deletes the content at \a index from the model. + + \sa clear() +*/ +void QQuickListModel::remove(QQmlV8Function *args) +//! [function] + +//! [return] +/* + Returns \c true if a QScroller object was already created for \a target; \c false otherwise. + + \sa scroller() +*/ +bool QScroller::hasScroller(QObject *target) +//! [return] + +//! [property] +/* + \property QVariantAnimation::duration + \brief the duration of the animation + + This property describes the duration in milliseconds of the + animation. The default duration is 250 milliseconds. + + \sa QAbstractAnimation::duration() + */ +int QVariantAnimation::duration() const +//! [property] + +//! [signals] +/* + This signal is emitted when the user clicks the button. A click is defined + as a press followed by a release. The corresponding handler is + \c onClicked. +*/ +signal clicked() +//! [signals] + +//! [enums] +/*! +\qmlproperty enumeration QtQuick2::Text::font.weight + +Sets the font's weight. + +The weight can be one of: +\list +\li Font.Light +\li Font.Normal - the default +\li Font.DemiBold +\li Font.Bold +\li Font.Black +\endlist +*/ +//! [enums] diff --git a/src/qdoc/doc/examples/samples.qdocinc b/src/qdoc/doc/examples/samples.qdocinc new file mode 100644 index 000000000..d5679fdcd --- /dev/null +++ b/src/qdoc/doc/examples/samples.qdocinc @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//! [qvector3d-class] +/*! + \class QVector3D + \brief The QVector3D class represents a vector or vertex in 3D space. + \since 4.6 + \ingroup painting-3D + + Vectors are one of the main building blocks of 3D representation and + drawing. They consist of three coordinates, traditionally called + x, y, and z. + + The QVector3D class can also be used to represent vertices in 3D space. + We therefore do not need to provide a separate vertex class. + + \b{Note:} By design values in the QVector3D instance are stored as \c float. + This means that on platforms where the \c qreal arguments to QVector3D + functions are represented by \c double values, it is possible to + lose precision. + + \sa QVector2D, QVector4D, QQuaternion +*/ +//! [qvector3d-class] + +//! [qvector3d-function] +/*! + \fn QVector3D::QVector3D(const QPoint& point) + + Constructs a vector with x and y coordinates from a 2D \a point, and a + z coordinate of 0. +*/ +//! [qvector3d-function] + +//! [sample-page] +/*! + \page generic-guide.html + \title Generic QDoc Guide + \nextpage Creating QDoc Configuration Files + There are three essential materials for generating documentation with qdoc: + + \list + \li \c qdoc binary + \li \c qdocconf configuration files + \li \c Documentation in \c C++, \c QML, and \c .qdoc files + \endlist +*/ +//! [sample-page] + +//! [sample-faq] +/*! + \page altruism-faq.html faq + \title Altruism Frequently Asked Questions + + \brief All the questions about altruism, answered. + + ... +*/ +//! [sample-faq] + +//! [sample-example] +/*! + \title UI Components: Tab Widget Example + \example declarative/ui-components/tabwidget + + This example shows how to create a tab widget. It also demonstrates how + \l {Property aliases}{property aliases} and + \l {Introduction to the QML Language#Default Properties}{default properties} can be used to collect and + assemble the child items declared within an \l Item. + + \image qml-tabwidget-example.png +*/ +//! [sample-example] + +//! [sample-overview] +/*! + \page overview-qt-technology.html overview + \title Overview of a Qt Technology + + \brief provides a technology never seen before. + +*/ +//! [sample-overview] + diff --git a/src/qdoc/doc/examples/signalandslots.qdocinc b/src/qdoc/doc/examples/signalandslots.qdocinc new file mode 100644 index 000000000..e14ede144 --- /dev/null +++ b/src/qdoc/doc/examples/signalandslots.qdocinc @@ -0,0 +1,9 @@ +\section1 Signals and Slots + +Signals and slots are used for communication between objects. The signals and +slots mechanism is a central feature of Qt and probably the part that differs +most from the features provided by other frameworks. + +\section2 Introduction + +In GUI programming, when we ... diff --git a/src/qdoc/doc/files/basicqt.qdoc.sample b/src/qdoc/doc/files/basicqt.qdoc.sample new file mode 100644 index 000000000..ce8df096f --- /dev/null +++ b/src/qdoc/doc/files/basicqt.qdoc.sample @@ -0,0 +1,67 @@ + /*! + \page basicqt.html + \contentspage {Basic Qt} {Contents} + \nextpage Getting Started + + \indexpage Index + \startpage Basic Qt + + \title Basic Qt + + The Qt toolkit is a C++ class library and a set of tools for + building multiplatform GUI programs using a "write once, + compile anywhere approach". + + Table of contents: + + \list + \li \l {Getting Started} + \li \l {Creating Dialogs} + \li \l {Creating Main Windows} + \endlist + */ + + /*! + \page gettingstarted.html + \previouspage Basic Qt + \contentspage {Basic Qt} {Contents} + \nextpage Creating Dialogs + + \indexpage Index + \startpage Basic Qt + + \title Getting Started + + This chapter shows how to combine basic C++ with the + functionality provided by Qt to create a few small graphical + interface (GUI) applications. +*/ + +/ *! + \page creatingdialogs.html + \previouspage Getting Started + \contentspage {Basic Qt} {Contents} + + \indexpage Index + \startpage Basic Qt + + \title Creating Dialogs + + This chapter will teach you how to create dialog boxes using Qt. +*/ + +/*! + \page index.html + + \indexpage Index + \startpage Basic Qt + + \title Index + + \list + \li \l {Basic Qt} + \li \l {Creating Dialogs} + \li \l {Getting Started} + \endlist +*/ + diff --git a/src/qdoc/doc/files/compat.qdocconf b/src/qdoc/doc/files/compat.qdocconf new file mode 100644 index 000000000..3e7ea6c89 --- /dev/null +++ b/src/qdoc/doc/files/compat.qdocconf @@ -0,0 +1,12 @@ +alias.include = input + +macro.0 = "\\\\0" +macro.b = "\\\\b" +macro.n = "\\\\n" +macro.r = "\\\\r" +macro.img = "\\image" +macro.endquote = "\\endquotation" +macro.relatesto = "\\relates" + +spurious = "Missing comma in .*" \ + "Missing pattern .*" diff --git a/src/qdoc/doc/files/qtgui.qdocconf b/src/qdoc/doc/files/qtgui.qdocconf new file mode 100644 index 000000000..0b2d28108 --- /dev/null +++ b/src/qdoc/doc/files/qtgui.qdocconf @@ -0,0 +1,49 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) + +project = QtGui +description = Qt GUI Reference Documentation +version = $QT_VERSION + +examplesinstallpath = qtbase/gui + +qhp.projects = QtGui + +qhp.QtGui.file = qtgui.qhp +qhp.QtGui.namespace = org.qt-project.qtgui.$QT_VERSION_TAG +qhp.QtGui.virtualFolder = qtgui +qhp.QtGui.indexTitle = Qt GUI +qhp.QtGui.indexRoot = + +qhp.QtGui.filterAttributes = qtgui $QT_VERSION qtrefdoc +qhp.QtGui.customFilters.Qt.name = Qtgui $QT_VERSION +qhp.QtGui.customFilters.Qt.filterAttributes = qtgui $QT_VERSION + +qhp.QtGui.subprojects = classes +qhp.QtGui.subprojects.classes.title = C++ Classes +qhp.QtGui.subprojects.classes.indexTitle = Qt GUI C++ Classes +qhp.QtGui.subprojects.classes.selectors = class fake:headerfile +qhp.QtGui.subprojects.classes.sortPages = true + +tagfile = ../../../doc/qtgui/qtgui.tags + +depends += \ + qtcore \ + qtnetwork \ + qtopengl \ + qtsvg \ + qtqml \ + qtquick \ + qtwidgets \ + qtdoc + +headerdirs += .. + +sourcedirs += .. \ + ../../../examples/gui/doc/src + +exampledirs += ../../../examples/gui \ + snippets + +imagedirs += images \ + ../../../examples/gui/doc/images \ + ../../../doc/src/images \ diff --git a/src/qdoc/doc/images/happy.gif b/src/qdoc/doc/images/happy.gif Binary files differnew file mode 100644 index 000000000..a4597f6fa --- /dev/null +++ b/src/qdoc/doc/images/happy.gif diff --git a/src/qdoc/doc/images/happyguy.jpg b/src/qdoc/doc/images/happyguy.jpg Binary files differnew file mode 100644 index 000000000..e8604793c --- /dev/null +++ b/src/qdoc/doc/images/happyguy.jpg diff --git a/src/qdoc/doc/images/link-to-qquickitem.png b/src/qdoc/doc/images/link-to-qquickitem.png Binary files differnew file mode 100644 index 000000000..00e03c371 --- /dev/null +++ b/src/qdoc/doc/images/link-to-qquickitem.png diff --git a/src/qdoc/doc/images/links-to-broken-links.png b/src/qdoc/doc/images/links-to-broken-links.png Binary files differnew file mode 100644 index 000000000..775143bd4 --- /dev/null +++ b/src/qdoc/doc/images/links-to-broken-links.png diff --git a/src/qdoc/doc/images/links-to-links.png b/src/qdoc/doc/images/links-to-links.png Binary files differnew file mode 100644 index 000000000..9d2cc2fae --- /dev/null +++ b/src/qdoc/doc/images/links-to-links.png diff --git a/src/qdoc/doc/images/qa-table.png b/src/qdoc/doc/images/qa-table.png Binary files differnew file mode 100644 index 000000000..5818739fa --- /dev/null +++ b/src/qdoc/doc/images/qa-table.png diff --git a/src/qdoc/doc/images/qt-logo.png b/src/qdoc/doc/images/qt-logo.png Binary files differnew file mode 100644 index 000000000..6b72d5fb7 --- /dev/null +++ b/src/qdoc/doc/images/qt-logo.png diff --git a/src/qdoc/doc/images/training.jpg b/src/qdoc/doc/images/training.jpg Binary files differnew file mode 100644 index 000000000..c2ce5c3b2 --- /dev/null +++ b/src/qdoc/doc/images/training.jpg diff --git a/src/qdoc/doc/qa-pages.qdoc b/src/qdoc/doc/qa-pages.qdoc new file mode 100644 index 000000000..a96673901 --- /dev/null +++ b/src/qdoc/doc/qa-pages.qdoc @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page 28-qdoc-qa-pages.html + \previouspage Generating DITA XML Output + \contentspage QDoc Manual + \nextpage QDoc Manual + + \title QA Pages + + qdoc can generate some extra HTML pages that can be useful for + debugging qdoc documentation. These \e QA pages make it easier for + those who write documentation to find links that either go to the + wrong targets or don't go anywhere at all. + + \section2 Generating the QA Pages + + Add \c {-write-qa-pages} to the command line to tell qdoc to + generate the QA pages. If this option is not provided, the QA + pages will not be generated, and previolusly generated QA pages + will be deleted. + + \section2 Finding the Module's Main QA Page + + The main QA page for a module is not linked into the module's + generated documentation, but it is located in the same output + directory. To find the top-level QA page for module \e {xxx}, set + your browser to the qdoc output directory for module \e {xxx}. + Several files whose names begin with \e {aaa} appear at the top of + the list. These are the QA pages for module \e{xxx}. The file + names begin with \e {aaa} to ensure that they are easy to find at + the top of the directory. + + For module \e{xxx}, find the file \e{aaa-xxx-qa-page.html}. This + is the top-level QA page for module \e{xxx}. Load that file into + the browser. The top-level QA page shows a table that contains + links to several QA sub-pages. + + For example, the main QA page for QtCore is \c{aaa-qtcore-qa-page.html}. + This was the table for QtCore at one point: + + \image qa-table.png + + Each table entry shows the number of links from QtCore to some + other module, except for the last entry, which shows the number of + broken links in QtCore. Click the \b qtquick entry to load the QA + subpage showing the links from QtCore to QtQuick. + + \section2 Links To Links Page + + Clicking the \b qtquick table entry on the main QA page for QtCore + loads the QA subpage showing a table containing all the links from + QtCore to QtQuick. The table contains all the links constructed + with the \l {l-command} {\\l command}, as well as the autolinks. + + \image links-to-links.png + + At the time this table was generated, there were six links from + QtCore to QtQuick. The first column of each table entry contains + a link to some link in QtCore. The link text as it appears in + QtCore is shown. The second and third columns contain the source + file name and line number for where qdoc saw the link in a qdoc + comment. + + \note The line number will normally refer to the first line of the + comment where qdoc saw the link. + + Clicking on a link in the table takes you to that link in the + documentation. There the link will be marked with three red + asterisks. For example, clicking on the link in the fifth table + entry takes you here: + + \image link-to-qquickitem.png + + The link is marked with three red asterisks. Now you can click on + the actual link to check that it goes to the correct place. In + this case, the link should go to the reference page for the + QQuickItem class. You can check each link in the table this + way. If you find a link that goes to the wrong place, use the + source file name and line number to find the link, and fix the + problem using the square bracket notation for the \l {l-command} + {\\l command}. + + */ diff --git a/src/qdoc/doc/qdoc-guide/qdoc-guide.qdoc b/src/qdoc/doc/qdoc-guide/qdoc-guide.qdoc new file mode 100644 index 000000000..af1fa1ba1 --- /dev/null +++ b/src/qdoc/doc/qdoc-guide/qdoc-guide.qdoc @@ -0,0 +1,632 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ +/*! + \page qdoc-guide.html + \title Getting Started with QDoc + \nextpage Creating QDoc Configuration Files + + Qt uses QDoc to generate its documentation set into HTML and DITA XML + formats. QDoc uses a set of configuration files to generate documentation + from QDoc comments. The comments have types called + \l{writing-topic-commands}{topics} that determine whether a comment is a + class documentation or a property documentation. A comment may also have + \l{writing-markup}{mark up} to enhance the layout and formatting of the + final output. + + There are three essential materials for generating documentation with qdoc: + \list + \li \c QDoc binary + \li \c qdocconf configuration files + \li \c Documentation in \c C++, \c QML, and \c .qdoc files + \endlist + + This section intends to cover the basic necessities for creating a + documentation set. Additionally, the guide presents special considerations + and options to documenting non-C++ API documentation as well as QML + documentation. Finally, the guide will provide a sample project + documentation and an example of a QML type documentation. + + For specific QDoc information, consult the + \l{QDoc Manual}. + \section1 Chapters + + \list 1 + \li \l{Creating QDoc Configuration Files} + \li \l{Writing Documentation} + \li \l{Categories of Documentation} + \list + \li \l{C++ Documentation Style} + \li \l{QML Documentation Style} + \endlist + \li \l{QML Documentation Example} + \endlist + +*/ + +/*! + \page qdoc-guide-conf.html + \title Creating QDoc Configuration Files + \previouspage Getting Started with QDoc + \nextpage Writing Documentation + To generate documentation, QDoc uses configuration files, with the + \c qdocconf extension, to store configuration settings. + + The \l{The QDoc Configuration File} article covers the various configuration + variables in greater detail. + + \section1 QDoc Configuration Files + QDoc's configuration settings can reside in a single \e qdocconf file, but + can also be in other qdocconf files. The \c {include(<filepath>)} command + allows configuration files to include other configuration files. + + QDoc has two outputs, HTML documentation and documentation in DITA XML + format. The main distinction between the two outputs is that HTML + documentation needs to have its HTML styling information in the + configuration files. DITA XML documentation does not, and a separate process + can style the documentation in DITA at a later time. DITA XML is therefore + more flexible in allowing different styles to apply to the same information. + + To run qdoc, the project configuration file is supplied as an argument. + \code + qdoc project.qdocconf + \endcode + + The project configuration contains information that qdoc uses to create the + documentation. + + \section2 Project Information + + QDoc uses the \c project information to generate the documentation. + \code + project = QDoc Project + description = Sample QDoc project + \endcode + + \target qdoc-input-output-dir + \section2 Input and Output Directories + + Specifying the path to the source directories allow QDoc to find sources and + generate documentation. + + \badcode + sourcedirs = <path to source code> + exampledirs = <path to examples directory> + imagedirs = <path to image directory> + + sources.fileextensions = "*.cpp *.qdoc *.mm *.qml" + headers.fileextensions = "*.h *.ch *.h++ *.hh *.hpp *.hxx" + examples.fileextensions = "*.cpp *.h *.js *.xq *.svg *.xml *.ui *.qhp *.qhcp *.qml" + examples.imageextensions = "*.png *.jpeg *.jpg *.gif *.mng" + \endcode + + QDoc will process headers and sources from the ones specified in the + \c fileextensions variable. + + Likewise, QDoc needs the path to the output directory. The \c outputformats + variable determines the type of documentation. These variables should be + in separate configuration files to modularize the documentation build. + \badcode + outputdir = $SAMPLE_PROJECT/doc/html + outputformats = HTML + \endcode + + QDoc can resolve the paths relative to the qdocconf file as well as + environment variables. + + \note During each QDoc run, the output directory is deleted. + \section2 Extra Files + + QDoc will output generated documentation into the directory specified in + the \l{Input and Output Directories}{output} directory. It is also possible + to specify extra files that QDoc should export. + + \badcode + HTML.extraimages = extraImage.png \ + extraImage2.png + \endcode + + The \c extraImage.png and the \c extraImage2.png files will be copied to the + HTML output directory. + + \section2 Qt Help Framework Configuration + + QDoc will also export a \e {Qt Help Project} file, in a \c qhp file. + The qhp file is then used by the \c qhelpgenerator to package the + documentation into a \c qch file. Qt Creator and Qt Assistant reads the qch + file to display the documentation. + + The \l {Creating Help Project Files} article covers the configuration + options. + + \section2 HTML Configuration + + QDoc has an HTML generator that will export a set of documentation into + HTML files using various configuration settings. QDoc will place the + generated documentation into the directory specified by the \c outputdir + variable. + + \badcode + outputformats = HTML + outputdir = <path to output directory> + \endcode + + QDoc needs to know where the styles and templates for generating HTML + are located. Typically, the templates directory contains a \c scripts, + \c images, and a \c style directory, containing scripts and CSS files. + + \badcode + HTML.templatedir = <path to templates> + \endcode + + The main configuration variables are: + \badcode + HTML.postheader + HTML.postpostheader + HTML.postheader + HTML.footer + + HTML.headerstyles + HTML.stylesheets = style.css \ + style1.css + + HTML.scripts = script.js + \endcode + + The \c{HTML.headerstyles} variable inserts the style information into the + HTML file and the \c{HTML.stylesheets} specifies which files QDoc should + copy into the output directory. As well, QDoc will embed the string + in the \c postheader, \c footer, and related variables into each HTML file. + + The \l {HTML Specific Configuration Variables} article outlines the usage + of each variable. + + \section2 DITA XML Configuration + + DITA XML output is enabled using the \c outputformats variable. Unlike HTML + documentation, QDoc does not need HTML style templates for generating + documentation in DITA XML format. + + \badcode + outputformats = DITAXML + outputdir + \endcode + + \section2 Qt Index Reference + Documentation projects can link to Qt APIs and other articles by specifying + the path to the \c qt.index file. When qdoc generates the Qt Reference + Documentation, it will also generate an index file, containing the URLs to + the articles. Other projects can use the links in the index file so that + they can link to other articles and API documentation within Qt. + + \badcode + indexes = $QT_INSTALL_DOCS/html/qt.index $OTHER_PROJECT/html/qt.index + \endcode + It is possible to specify multiple index files from several projects. + + \section1 Macros and Other Configurations + + Macros for substituting HTML characters exist and are helpful for generating + specific HTML-valid characters. + + \badcode + macro.pi.HTML = "Π" + \endcode + The snippet code will replace any instances of \c{\\pi} with \c Π in the + HTML file, which will appear as the Greek \pi symbol when viewed in + browsers. + + \section2 QML Additions + + QDoc is able to parse QML files for QDoc comments. QDoc will parse files + with the QML extension, \c{.qml}, if the extension type is included in the + \l{Input and Output Directories}{fileextensions} variable. + + Also, the generated HTML files can have a prefix and a suffix following the + QML module name, specified in the QDoc configuration file. + \badcode + outputprefixes = QML + outputprefixes.QML = uicomponents- + outputsuffixes = QML + outputsuffixes.QML = -tp + \endcode + + \b {See also}: \l {outputprefixes-variable}{outputprefixes}, + \l {outputsuffixes-variable}{outputsuffixes}. + +*/ + +/*! + \page qdoc-guide-writing.html + \title Writing Documentation + \previouspage Creating QDoc Configuration Files + \nextpage Categories of Documentation + + \section1 QDoc Comments + + Documentation is contained within qdoc \e comments, delimited by + \beginqdoc and \endqdoc comments. Note that these are valid comments + in C++, QML, and JavaScript. + + QDoc will parse C++ and QML files to look for qdoc comments. To explicitly + omit a certain file type, omit it from the + \l{Input and Output Directories}{configuration} file. + + \section1 QDoc Commands + + QDoc uses \e commands to retrieve information about the documentation. \c + Topic commands determine the type of documentation element, the \c context + commands provide hints and information about a topic, and \c markup commands + provide information on how QDoc should format a piece of documentation. + + \target writing-topic-commands + \section2 QDoc Topics + Each qdoc comment must have a \e topic type. A topic distinguishes it from + other topics. To specify a topic type, use one of the several + \l{Topic Commands}{topic commands}. + + QDoc will collect similar topics and create a page for each one. For + example, all the enumerations, properties, functions, and class description + of a particular C++ class will reside in one page. A generic page is + specified using the \l{page-command}{\\page} command and the filename is the + argument. + + Example of topic commands: + \list + \li \l{enum-command}{\\enum} - for enumeration documentation + \li \l{class-command}{\\class} - for C++ class documentation + \li \l{qmltype-command}{\\qmltype} - for QML type documentation + \li \l{page-command}{\\page} - for creating a page. + \endlist + + The \l{page-command}{\\page} command is for creating articles that are not + part of source documentation. The command can also accept two arguments: the + file name of the article and the documentation type. The possible types are: + \list + \li \c howto + \li \c overview + \li \c tutorial + \li \c faq + \li \c article - \e default when there is no type + \endlist + + \snippet examples/samples.qdocinc sample-faq + + The \l{Topic Commands} page has information on all of the available topic + commands. + + \target writing-context + \section2 Topic Contexts + + Context commands give QDoc a hint about the \e context of the topic. For + example, if a C++ function is obsolete, then it should be marked obsolete + with the \l{obsolete-command}{\\obsolete} command. Likewise, + \l{nextpage-command}{page navigation} and \l{title-command}{page title} + give extra page information to QDoc. + + QDoc will create additional links or pages for these contexts. For example, + a group is created using the \l{group-command}{\\group} command and the + members have the \l{ingroup-command}{\\ingroup} command. The group name is + supplied as an argument. + + The \l{Context Commands} page has a listing of all the available context + commands. + + \target writing-markup + \section2 Documentation Markup + + QDoc can do \e markup of text similar to other markup or + documentation tools. QDoc can mark a section of text in \b{bold}, + when the text is marked up with the \l{b-command}{\\b} command. + + \code + \b{This} text will be in \b{bold}. + \endcode + + The \l{Markup Commands} page has a full listing of the available markup + commands. + + \section1 Anatomy of Documentation + + Essentially, for QDoc to create a page, there must be some essential + ingredients present. + + \list + \li Assign a topic to a QDoc comment - A comment could be a page, a + property documentation, a class documentation, or any of the available + \l{Topic Commands}{topic commands}. + + \li Give the topic a context - QDoc can associate certain topics to other + pages such as associating obsolete functions when the documentation is + marked with \l{obsolete-command}{\\obsolete}. + + \li Mark sections of the document with + \l{Markup Commands}{markup commands} - QDoc can create layouts and + format the documentation for the documentation. + \endlist + + In Qt, the \l{QVector3D} class was documented with the following QDoc + comment: + \snippet examples/samples.qdocinc qvector3d-class + + It has a constructor, \l{QVector3D::QVector3D()}, which was documented with + the following QDoc comment: + \snippet examples/samples.qdocinc qvector3d-function + + The different comments may reside in different files and QDoc will collect + them depending on their topic and their context. The resulting documentation + from the snippets are generated into the \l{QVector3D} class documentation. + + Note that if the documentation immediately precedes the function or class + in the source code, then it does not need to have a topic. QDoc will assume + that the documentation above the code is the documentation for that code. + + An article is created using \l{page-command}{\\page} command. The first + argument is the HTML file that QDoc will create. The topic is supplemented + with context commands, the \l{title-command}{\\title} and + \l{nextpage-command}{\\nextpage} commands. There are several other + QDoc commands such as the \l{list-command}{\\list} command. + \snippet examples/samples.qdocinc sample-page + + The section on \l{QDoc Topics}{topic commands} gives an overview on several + other topic types. + + +*/ + +/*! + \page qdoc-categories.html + \title Categories of Documentation + \previouspage Writing Documentation + \nextpage QML Documentation Example + \brief Describes the different types such as How-To's, Tutorials, Overviews, + Examples, and Class Documentation. + + There are several types of predefined documentation \e categories or + \e types: + \list + \li How-To's + \li Tutorial + \li Overview + \li Article + \li FAQ (Frequently Asked Questions) + \li C++ API Documentation + \li QML Type Documentation + \li Code Example + \endlist + + QDoc has the ability to format a page depending on the type. Further, + stylesheets can provide additional control on the display of each category. + + \section1 API Documentation + QDoc excels in the creation of API documentation given a set of source code + and documentation in QDoc comments. Specifically, QDoc is aware of Qt's + architecture and can validate the existence of Qt C++ class, function, or + property documentation. QDoc gives warnings and errors if it cannot + associate a documentation with a code entity or if a code entity does not + have documentation. + + In general, every Qt code entity such as properties, classes, methods, + signals, and enumerations have a corresponding + \l{qdoc-topics}{topic command}. QDoc will associate the documentation to the + source using C++ naming rules. + + QDoc will parse the header files (typically \c .h files) to build a tree of + the class structures. Then QDoc will parse the source files and + documentation files to attach documentation to the class structure. + Afterwards, QDoc will generate a page for the class. + + \note QDoc uses the header files to inform itself about the class and will + not properly process QDoc comments in header files. + + \section2 Language Styles + + To produce quality API documentation, the Qt API references follow a + particular language guidelines. While the contents of this page demonstrates + how to create API documentation, the style guidelines demonstrate how + the reference materials follow a consistent use of language. + + \list + \li \l{C++ Documentation Style} + \li \l{QML Documentation Style} + \endlist + + \keyword qml-documentation + \section2 Documenting QML Types + + In the world of \l{Qt Quick}{QML}, there are additional entities we need to + document such as QML signals, attached properties, and QML methods. + Internally, they use Qt technologies, however, QML API documentation + requires different layout and naming conventions from the Qt C++ API + documentation. + + A list of QML related QDoc commands: + \list + \li \l{qmlattachedproperty-command}{\\qmlattachedproperty} + \li \l{qmlattachedsignal-command}{\\qmlattachedsignal} + \li \l{qmlbasictype-command}{\\qmlbasictype} + \li \l{qmltype-command}{\\qmltype} - creates a QML type documentation + \li \l{qmlmethod-command}{\\qmlmethod} + \li \l{qmlproperty-command}{\\qmlproperty} + \li \l{qmlsignal-command}{\\qmlsignal} + \li \l{inherits-command}{\\inherits} + \li \l{qmlmodule-command}{\\qmlmodule} + \li \l{inqmlmodule-command}{\\inqmlmodule} + \li \l{instantiates-command}{\\instantiates} + + \endlist + + \note Remember to enable QML parsing by including the \c{*.qml} filetype in + the \l{qdoc-input-output-dir}{fileextension} variable. + + To document a QML type, start by creating a QDoc comment that uses the + \l{qmltype-command} {\\qmltype} command as its topic command. + + \section3 QML Parser + + If your QML type is defined in a \e qml file, document it there. + If your QML type is represented by a C++ class, document it in the + \e cpp file for that C++ class and include an + \l{instantiates-command}{\\instantiates} command to specify the + name of the C++ class. Don't document a QML type in a \e{cpp} file + if the QML type is defined in a \e{qml} file. + + When documenting a QML type in a \e{qml} file, place each QDoc + comment directly above the entity to which the comment applies. + For example, place the QDoc comment containing the \e{\\qmltype} + command (the topic comment) directly above the outer QML type in + the \e{qml} file. Place the comment for documenting a QML property + directly above the property declaration, and so on for QML signal + handlers and QML methods. Note that when documenting QML + properties in a \e{qml} file, you don't normally include the + \e{\\qmlproperty} command as a topic command (which you must do + when documenting QML types in \e{cpp} files), because the QML + parser automatically associates each QDoc comment with the next + QML declaration it parses. The same is true for QML signal handler + and QML method comments. But it is sometimes useful to include one + or more \e{\\qmlproperty} commands in the comment, e.g. when the + property type is another QML type and you want the user to only + use certain properties within that other QML type, but not all of + them. But when documenting a property that has an alias, place the + QDoc comment for it directly above the alias declaration. In these + cases, the QDoc comment \e must contain a \e{\\qmlproperty} + command, because that is the only way QDoc can know the type of + the aliased property. + + When documenting a QML type in the \e cpp file of its + corresponding C++ class (if it has one), you normally place each + QDoc comment directly above the entity it documents. However, QDoc + does not use the QML parser to parse these files (the C++ parser + is used), so these QML QDoc comments can appear anywhere in the + \e{cpp} file. Note that QML QDoc comments in \e cpp files \e must + use the QML topic commands. i.e., the \l{qmltype-command} + {\\qmltype} command \e must appear in the QDoc comment for the + QML type, and a \l{qmlproperty-command} {\\qmlproperty} command \e + must appear in each QML property QDoc comment. + + \section3 QML Modules + + A QML type belongs to a \e module. The module + may include all the related types for a platform or contain a certain + version of \l{Qt Quick}. For example, the Qt Quick 2 QML types belong + to the Qt Quick 2 module while there is also a Qt Quick 1 module for the older + types introduced in Qt 4. + + QML modules allow grouping QML types. The \l{qmltype-command} + {\\qmltype} topic command must have an \l{inqmlmodule-command} + {\\inqmlmodule} context command to relate the type to a QML + module. Similarly, a \l{qmlmodule-command}{\\qmlmodule} topic + command must exist in a separate \c{.qdoc} file to create the + overview page for the module. The overview page will list the + QML types of the QML module. + + The links to the QML types must therefore also contain the module name. + For example, if a type called \c TabWidget is in the \c UIComponents + module, it must be linked as \c {UIComponents::TabWidget}. + + The \l{componentset}{UIComponents} example demonstrates proper usage of + QDoc commands to document QML types and QML modules. + + \section3 Read-only and Internal QML Properties + + QDoc detects QML properties that are marked as \c readonly. Note that the + property must be initialized with a value. + + \code + readonly property int sampleReadOnlyProperty: 0 + \endcode + For example, the example \l{TabWidget} type has a fictitious read-only + property \c sampleReadOnlyProperty. Its declaration has the \c readonly + identifier and it has an initial value. + + Properties and signals that are not meant for the public interface may + be marked with the \l{internal-command}{\\internal} command. QDoc will not + publish the documentation in the generated outputs. + + \section1 Articles & Overviews + Articles and overviews are a style of writing best used for providing + summary detail on a topic or concept. It may introduce a technology or + discuss how a concept may be applied, but without discussing exact steps + in too much detail. However, this type of content could provide the entry + point for readers to find instructional and reference materials that do, + such as tutorials, examples and class documentation. An example of an + overview might be a product page, such as a top level discussion of + Qt Quick, individual modules, design principles, or tools. + + To signify that a document is an article, you append the article keyword + to the \\page command: + + \snippet examples/samples.qdocinc sample-overview + + The \l{writing-topic-commands}{writing topic commands} section has a listing + of the available \\page command arguments. + + \section1 Tutorials, How-To's, FAQ's + + Tutorials, How-To's, and FAQ's are all instructional material, in that they + instruct or prescribe to the reader. Tutorials are content designed to guide + the reader along a progressive learning path for a concept or technology. + How-To's and FAQ's (\e{Frequently Asked Questions}) provide guidance by + presenting material in the form of answers to commonly asked topics. + How-To's and FAQ's are designed for easy reference and are not necessarily + presented in a linear progression. + + To create these types, mark the pages by providing a \c type argument to the + \l{page-command}{\\page} command. The \c type argument is the second + argument, with the file name being the first. + \snippet examples/samples.qdocinc sample-faq + + The \l{writing-topic-commands}{writing topic commands} section has a listing + of the available \\page command arguments. + + \section1 Code Examples + Examples are an effective way to demonstrate practical usage of a given + technology or concept. When it comes to middleware this is usually in the + form of an application using simple code and clear explanations of what the + code is doing. Any module, API, project, pattern etc. should have at least + one good example. + + An example may have an accompanying tutorial. The tutorial instructs and + describes the code, while the code example is the code content that users + may study. Code examples may have accompanying text that are not in the + tutorial. + + QDoc will create a page containing the example code with a description + using the \l{example-command}{\\example} command. + + \snippet examples/samples.qdocinc sample-example + + QDoc will use the directory specified in the input + \l{Input and Output Directories}{exampledirs} variable to find the Qt + Project (\c .pro) file to generate the example files. The generated HTML + will have the filename, \c {declarative-ui-components-tabwidget.html}. QDoc + will also list all of the example code. + + \note The example's project file must be the same as the + directory name. +*/ + + diff --git a/src/qdoc/doc/qdoc-guide/qtwritingstyle-cpp.qdoc b/src/qdoc/doc/qdoc-guide/qtwritingstyle-cpp.qdoc new file mode 100644 index 000000000..8cbf74cd6 --- /dev/null +++ b/src/qdoc/doc/qdoc-guide/qtwritingstyle-cpp.qdoc @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page qtwritingstyle-cpp.html +\title C++ Documentation Style +\brief Style guidelines for C++ documentation + +To generate the documentation, QDoc goes through the source code and generates +documentation for C++ types such as classes. QDoc then associates member +functions, properties, and other types to the appropriate class. + +Note that the documentation must be in the implementation files such as \c .cpp. + +\section1 Class Documentation + +Class documentation is generated using the \l{class-command}{\\class} command and +the name of the class as the first argument. + +\snippet examples/cpp.qdoc.sample class + +\l{Context commands} add information about the class, such as its module or +which version the class was added. + +Some common context commands are: +\list +\li \l{brief-command}{\\brief} - the class' brief description \b (mandatory) +\li \l{since-command}{\\since} - the version to which the class was added \b (mandatory) +\li \l{internal-command}{\\internal} - marks the class as internal. Internal +classes do not appear in the public API documentation. +\endlist + + +\section2 The Brief and Detailed Description + +The \e{brief description} is marked with the \l{brief-command}{\\brief} command +and it is for summarizing the purpose or functionality of the class. For C++ +classes, QDoc will take the class and create annotated information for the +class. The annotated information appears in lists and tables which display the +class. + +The C++ brief should start with: +\code +"The <C++ class name> class" +\endcode + +The \e{detailed description} section starts after the brief description. It +provides more information about the class. The detailed description may contain +images, snippet code, or links to other relevant documents. There +must be an empty line which separates the brief and detailed description. + +\section1 Member Functions + +Typically, function documentation immediately precedes the implementation of the +function in the \c .cpp file. For function documentation that is not immediately +above the implementation, the \l{fn-command}{\\fn} is needed. + +\snippet examples/cpp.qdoc.sample function + +The function documentation starts with a verb, indicating the operation the +function performs. This also applies to constructors and destructors. + +Some common verbs for function documentation: +\list +\li "Constructs..." - for constructors +\li "Destroys..." - for destructors +\li "Returns..." - for accessor functions +\endlist + +The function documentation must document: +\list +\li the return type +\li the parameters +\li the actions of the functions +\endlist + +The \l{a-command}{\\a} command marks the parameter in the documentation. +The return type documentation should link to the type documentation or be +marked with the \l{c-command}{\\c} command in the case of boolean values. + +\snippet examples/cpp.qdoc.sample return + +\section1 Properties + +The property documentation resides immediately above the read function's +implementation. The \l{writing-topic-commands}{topic command} for properties is +\l{property-command}{\\property}. + +\snippet examples/cpp.qdoc.sample property + +Property documentation usually starts with "This property...", but these are +alternate expressions: +\list +\li "This property holds..." +\li "This property describes..." +\li "This property represents..." +\li "Returns \c true when... and \c false when..." - for properties that +are read. +\li "Sets the..." - for properties that configure a type. +\endlist + +Property documentation must include: +\list +\li description and behavior of the property +\li accepted values for the property +\li the default value of the property +\endlist +Similar to \l{Member Functions}{functions}, the default type may be linked +or marked with the \c{\c} command. + +An example of a value range style is: +\quotation +The values range from 0.0 (no blur) to maximumRadius (maximum blur). By default, the property is set to 0.0 (no blur). +\endquotation + +\section1 Signals, Notifiers, and Slots +The \l{writing-topic-commands}{topic command} for signals, notifiers, and slots +is \l{fn-command}{\\fn}. Signal documentation state when they are triggered +or emitted. + +\snippet examples/cpp.qdoc.sample signals + +Signal documentation typically begin with "This signal is triggered when...". +Here are alternate styles: +\list +\li "This signal is triggered when..." +\li "Triggered when..." +\li "Emitted when..." +\endlist + +For slots or notifiers, the condition when they are executed or triggered by +a signal should be documented. +\list +\li "Executed when..." +\li "This slot is executed when..." +\endlist + +For properties that have overloaded signals, QDoc groups the overloaded +notifiers together. To refer to a specific version of a notifier or signal, +simply refer to the property and mention that there are different versions of +the notifier. + +\snippet examples/cpp.qdoc.sample overloaded notifier + +\section1 Enums, Namespaces, and Other Types + +Enums, namespaces, and macros have a \l{writing-topic-commands}{topic command} for their documentation: +\list +\li \l{enum-command}{\\enum} +\li \l{typedef-command}{\\typedef} +\li \l{macro-command}{\\macro} +\endlist + +The language style for these types mention that they are an enum or a macro and +continues with the type description. + +For enumerations, the \l{value-command}{\\value} command is for listing the +values. QDoc creates a table of values for the enum. + +\snippet examples/cpp.qdoc.sample enums + +*/ + diff --git a/src/qdoc/doc/qdoc-guide/qtwritingstyle-qml.qdoc b/src/qdoc/doc/qdoc-guide/qtwritingstyle-qml.qdoc new file mode 100644 index 000000000..6955a042c --- /dev/null +++ b/src/qdoc/doc/qdoc-guide/qtwritingstyle-qml.qdoc @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page qtwritingstyle-qml.html +\title QML Documentation Style +\brief Style guidelines for QML documentation + +QDoc can process QML types defined as C++ classes and QML types defined in +\c .qml files. For C++ classes documented as QML types, the QDoc comments are +in the \c .cpp file while QML types defined in QML are in the \c .qml +file. The C++ classes must also be documented +documented with the QML \l{topic-commands}{topic commands}: + +\list +\li \l{qmlattachedproperty-command}{\\qmlattachedproperty} +\li \l{qmlattachedsignal-command}{\\qmlattachedsignal} +\li \l{qmlbasictype-command}{\\qmlbasictype} +\li \l{qmltype-command}{\\qmltype} +\li \l{qmlmethod-command}{\\qmlmethod} +\li \l{qmlproperty-command}{\\qmlproperty} +\li \l{qmlsignal-command}{\\qmlsignal} +\li \l{qmlmodule-command}{\\qmlmodule} +\li \l{inqmlmodule-command}{\\inqmlmodule} +\li \l{instantiates-command}{\\instantiates} +\endlist + +For QML types defined in \c .qml files, QDoc will parse the QML and determine +the properties, signals, and the type within the QML definition. The QDoc +block then needs to be immediately above the declaration. For QML types +implemented in C++, QDoc will output warnings if the C++ class documentation +does not exist. The class documentation may be marked as +\l{internal-command}{internal} if it is not a public API. + +\section1 QML Types + +The \l{qmltype-command}{\\qmltype} command is for QML type documentation. + +\snippet examples/qml.qdoc.sample qmltype + +The \l{instantiates-command}{\\instantiates} accepts the C++ class which +implements the QML type as the argument. For types implemented in QML, this +is not needed. + +The \e{brief description} provides a summary for the QML type. The brief does +not need to be a complete sentence and may start with a verb. QDoc will append +the brief description onto the QML type in tables and generated lists. + +\code +\qmltype ColorAnimation +\brief Animates changes in color values +\endcode + +Here are some alternate verbs for the brief statement: +\list +\li "Provides..." +\li "Specifies..." +\li "Describes..." +\endlist + +The \e{detailed description} follows the brief and may contain images, snippet, +and link to other documentation. + +\section1 Properties + +The property description focuses on what the property \e does and may use the +following style: + +Property documentation usually starts with "This property..." but for certain +properties, these are the common expressions: +\list +\li "This property holds..." +\li "This property describes..." +\li "This property represents..." +\li "Returns \c true when... and \c false when..." - for properties that +are marked \c{read-only}. +\li "Sets the..." - for properties that configure a type. +\endlist + +\section1 Signals and Handlers Documentation + +QML signals are documented either in the QML file or in the C++ implementation +with the \l{qmlsignal-command}{\\qmlsignal} command. Signal documentation +must include the condition for emitting the signal, mention the corresponding +signal handler, and document whether the signal accepts a parameter. + +\snippet examples/qml.qdoc.sample signals + +These are the possible documentation styles for signals: +\list +\li "This signal is triggered when..." +\li "Triggered when..." +\li "Emitted when..." +\endlist + +\section1 Methods and JavaScript Functions + +Typically, function documentation immediately precedes the implementation of the +function in the \c .cpp file. The \l{topic-commands}{topic command} for +functions is \l{fn-command}{\\fn}. For functions in QML or JavaScript, the +documentation must reside immediately above the function declaration. + +The function documentation starts with a verb, indicating the operation the +function performs. + +\snippet examples/qml.qdoc.sample function + +Some common verbs for function documentation: +\list +\li "Copies..." - for constructors +\li "Destroys..." - for destructors +\li "Returns..." - for accessor functions +\endlist + +The function documentation must document: +\list +\li the return type +\li the parameters +\li the actions of the functions +\endlist + +The \l{a-command}{\\a} command marks the parameter in the documentation. +The return type documentation should link to the type documentation or be +marked with the \l{c-command}{\\c} command in the case of boolean values. + +\section1 Enumerations + +QML enumerations are documented as QML properties with the +\l{qmlproperty-command}{\\qmlproperty} command. The type of the property +is \c enumeration. + +\snippet examples/qml.qdoc.sample enums + +The QDoc comment lists the values of the enumeration. If the enumeration is +implemented in C++, the documentation may link to the corresponding C++ +enumeration. However, the QDoc comment should advise that the enumeration +is a C++ enumeration. + +*/ + diff --git a/src/qdoc/doc/qdoc-manual-DITA.qdoc b/src/qdoc/doc/qdoc-manual-DITA.qdoc new file mode 100644 index 000000000..72882c8eb --- /dev/null +++ b/src/qdoc/doc/qdoc-manual-DITA.qdoc @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page 21-0-qdoc-creating-dita-maps.html + \previouspage Miscellaneous + \contentspage QDoc Manual + \nextpage The QDoc Configuration File + + \title Creating DITA Maps + + You can create DITA map files using three new qdoc commands, the \l{ditamap-command} + {ditamap} command, the \l{topicref-command} {topicref} command, and the \l{mapref-command} + {mapref} command. How these DITA maps will be used automatically or manually by the + documentation build process is still under consideration. This section will be updated + as the decisions are made. + + \section1 What is a DITA Map? + + A complete description of DITA can be found at the + \l{http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=dita} + {OASIS Darwin Information Typing Architecture} site. + + An explanation of the DITA map is found at that site + \l{http://docs.oasis-open.org/dita/v1.2/os/spec/langref/map.html}{here}. + + \target ditamap-command + \section1 \\ditamap + + The \\ditamap command is for creating a DITA map using qdoc commands. + The \\ditamap command is a kind of \\page command that produces a + \e{.ditamap} instead of a \e{.html} or \e{.xml} file. The file that + is created actually contains XML text, but the \e{.ditamap} suffix is + used to identify the file as containing a DITA MAP. + + The argument is the name of the file to be created. In the following + example, the file \e{creator.ditamap} is output: + \code + \ditamap creator.ditamap + \endcode + + \target topicref-command + \section1 \\topicref \\endtopicref + + The \\topicref \\endtopicref commands are for creating a topicref + in the ditamap. The \\endtopicref command is required because + \\topicref commands can be nested. + + \\topicref has two arguments. The first argument becomes the value + of the \e navtitle attribute. Normally, you use the title of the + topic being referenced. This title is often what will appear in a + table of contents constructed from the ditamap. + + The second argument is the name of the page being referenced. The + second argument is actually optional, for example if you are using + a topicref as a container for other topicrefs and maprefs. It is + also optional if you want qdoc to find the page name for you by + looking up the title in its internal data structure. It is recommended + that you provide the second parameter if you know the page name. + + \code + \topicref {QML Module QtQuick 2} {qtquick-2.xml} + \mapref {Creator Manual} {creator-manual.ditamap} \endmapref + \topicref {QML Mouse Events} {qtquick2-mouseevents.xml} \endtopicref + \topicref {Property Binding} {qtquick2-propertybinding.xml} \endtopicref + \endtopicref + \endcode + + \target mapref-command + \section1 \\mapref + + The \\mapref command is for creating a mapref in the ditamap. A + mapref refers to another ditamap, which you want to include in + your ditamap. Like the \\topicref command, the \\mapref command + has two arguments, but for the \\mapref command, both arguments + are required. The arguments are essentially the same as described + for \\topicref, but for \\mapref, the second command must be the + name of another ditamap, i.e. it must have the \e{.ditamap} + suffix. You must provide the file name. qdoc can't look up the + file name for you. + + \code + \mapref {Creator Manual} {creator-manual.ditamap} \endmapref + \endcode + + \section1 An Example Ditamap Page + + The following example uses the three qdoc ditamap commands described above. + + \code + \ditamap creator.ditamap + \title The DITA Map for Creator + + \topicref {QML Module QtQuick 1} + \topicref {QML Mouse Events} \endtopicref + \topicref {Property Binding} \endtopicref + \endtopicref + + \topicref {QML Module QtQuick 2} {qtquick-2.xml} + \mapref {Creator Manual} {creator-manual.ditamap} \endmapref + \topicref {QML Mouse Events} {qtquick2-mouseevents.xml} \endtopicref + \topicref {Property Binding} {qtquick2-propertybinding.xml} \endtopicref + \endtopicref + + \topicref {QML Module QtQuick.Particles 2} {qtquick-particles-2.xml} + \topicref {Age} {qml-qtquick-particles2-age.xml} \endtopicref + \endtopicref + \endcode + + \section1 The Resulting Ditamap File + + This is the \e{.ditamap} file you get when you input the qdoc + ditamap page shown above. Note that you can write ditamap files + directly in XML just as easily as you can write them using the + qdoc commands. The choice is yours. + + \code + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE map PUBLIC "-//OASIS//DTD DITA Map//EN" "map.dtd"> + <map> + <topicmeta> + <shortdesc>The DITA Map for Creator</shortdesc> + </topicmeta> + <topicref navtitle="QML Module QtQuick 1" href="qtquick-1.xml"> + <topicref navtitle="QML Mouse Events" href="qtquick2-mouseevents.xml"/> + <topicref navtitle="Property Binding" href="qtquick2-propertybinding.xml"/> + </topicref> + <topicref navtitle="QML Module QtQuick 2" href="qtquick-2.xml"> + <mapref navtitle="Creator Manual" href="creator-manual.ditamap"/> + <topicref navtitle="QML Mouse Events" href="qtquick2-mouseevents.xml"/> + <topicref navtitle="Property Binding" href="qtquick2-propertybinding.xml"/> + </topicref> + <topicref navtitle="QML Module QtQuick.Particles 2" href="qtquick-particles-2.xml"> + <topicref navtitle="Age" href="qml-qtquick-particles2-age.xml"/> + </topicref> + </map> + \endcode + +*/ + diff --git a/src/qdoc/doc/qdoc-manual-cmdindex.qdoc b/src/qdoc/doc/qdoc-manual-cmdindex.qdoc new file mode 100644 index 000000000..d3f188c26 --- /dev/null +++ b/src/qdoc/doc/qdoc-manual-cmdindex.qdoc @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page 27-qdoc-commands-alphabetical.html + \previouspage Introduction to QDoc + \contentspage QDoc Manual + \nextpage Topic Commands + + \title Command Index + + This is a complete, alphabetized list of the QDoc commands. + + \list + + \li \l {a-command} {\\a} + \li \l {abstract-command} {\\abstract} + \li \l {annotatedlist-command} {\\annotatedlist} + \li \l {b-command} {\\b} + \li \l {b-command} {\\bold} \span {class="newStuff"} {(deprecated, use \\b)} + \li \l {brief-command} {\\brief} + \li \l {c-command} {\\c} + \li \l {caption-command} {\\caption} + \li \l {chapter-command} {\\chapter} + \li \l {class-command} {\\class} + \li \l {code-command} {\\code} + \li \l {codeline-command} {\\codeline}, + \li \l {compat-command} {\\compat} + \li \l {contentspage-command} {\\contentspage} + \li \l {default-command} {\\default} + \li \l {div-command} {\\div} + \li \l {dots-command} {\\dots} + \li \l {e-command} {\\e} + \li \l {else-command} {\\else} + \li \l {endif-command} {\\endif} + \li \l {enum-command} {\\enum} + \li \l {example-command} {\\example} + \li \l {externalpage-command} {\\externalpage} + \li \l {fn-command} {\\fn} + \li \l {footnote-command} {\\footnote} + \li \l {generatelist-command} {\\generatelist} + \li \l {group-command} {\\group} + \li \l {header-command} {\\header} + \li \l {headerfile-command} {\\headerfile} + \li \l {e-command} {\\i} \span {class="newStuff"} {(deprecated, use \\e)} + \li \l {if-command} {\\if} + \li \l {image-command} {\\image} + \li \l {include-command} {\\include} + \li \l {indexpage-command} {\\indexpage} + \li \l {ingroup-command} {\\ingroup} + \li \l {inherits-command}{\\inherits} + \li \l {inlineimage-command} {\\inlineimage} + \li \l {inmodule-command} {\\inmodule} + \li \l {inqmlmodule-command} {\\inqmlmodule} + \li \l {instantiates-command} {\\instantiates} + \li \l {internal-command} {\\internal} + \li \l {keyword-command} {\\keyword} + \li \l {l-command} {\\l} + \li \l {legalese-command} {\\legalese} + \li \l {li-command} {\\li} + \li \l {list-command} {\\list} + \li \l {macro-command} {\\macro} + \li \l {meta-command} {\\meta} + \li \l {module-command} {\\module} + \li \l {namespace-command} {\\namespace} + \li \l {nextpage-command} {\\nextpage} + \li \l {newcode-command} {\\newcode} + \li \l {noautolist-command} {\\noautolist} + \li \l {nonreentrant-command} {\\nonreentrant} + \li \l {note-command} {\\note} + \li \l {li-command} {\\o} \span {class="newStuff"} {(deprecated, use \\li)} + + \li \l {obsolete-command} {\\obsolete} + \li \l {oldcode-command} {\\oldcode} + \li \l {omit-command} {\\omit} + \li \l {omitvalue-command} {\\omitvalue} + \li \l {overload-command} {\\overload} + \li \l {page-command} {\\page} + \li \l {part-command} {\\part} + \li \l {preliminary-command} {\\preliminary} + \li \l {previouspage-command} {\\previouspage} + \li \l {printline-command} {\\printline} + \li \l {printto-command} {\\printto} + \li \l {printuntil-command} {\\printuntil} + \li \l {property-command} {\\property} + \li \l {qmlabstract-command} {\\qmlabstract} + \li \l {qmlattachedproperty-command} {\\qmlattachedproperty} + \li \l {qmlattachedsignal-command} {\\qmlattachedsignal} + \li \l {qmlbasictype-command} {\\qmlbasictype} + \li \l {qmlclass-command} {\\qmlclass} \span {class="newStuff"} {(deprecated, use \\qmltype)} + \li \l {qmltype-command} {\\qmltype} + \li \l {qmlmethod-command} {\\qmlmethod} + \li \l {qmlproperty-command} {\\qmlproperty} + \li \l {qmlsignal-command} {\\qmlsignal} + \li \l {qmlmodule-command} {\\qmlmodule} + \li \l {quotation-command} {\\quotation} + \li \l {quotefile-command} {\\quotefile} + \li \l {quotefromfile-command} {\\quotefromfile} + \li \l {raw-command} {\\raw} + \li \l {reentrant-command} {\\reentrant} + \li \l {reimp-command} {\\reimp} + \li \l {relates-command} {\\relates} + \li \l {row-command} {\\row} + \li \l {sa-command} {\\sa} + \li \l {sectionOne-command} {\\section1} + \li \l {sectionTwo-command} {\\section2} + \li \l {sectionThree-command} {\\section3} + \li \l {sectionFour-command} {\\section4} + \li \l {since-command} {\\since} + \li \l {skipline-command} {\\skipline} + \li \l {skipto-command} {\\skipto} + \li \l {skipuntil-command} {\\skipuntil} + \li \l {snippet-command} {\\snippet}, + \li \l {span-command} {\\span} + \li \l {startpage-command} {\\startpage} + \li \l {sub-command} {\\sub} + \li \l {subtitle-command} {\\subtitle} + \li \l {sup-command} {\\sup} + \li \l {table-command} {\\table} + \li \l {tableofcontents-command} {\\tableofcontents} + \li \l {target-command} {\\target} + \li \l {threadsafe-command} {\\threadsafe} + \li \l {title-command} {\\title} + \li \l {tt-command} {\\tt} + \li \l {typedef-command} {\\typedef} + \li \l {uicontrol-command} {\\uicontrol} + \li \l {underline-command} {\\underline} + \li \l {variable-command} {\\variable} + \li \l {value-command} {\\value} + \li \l {warning-command} {\\warning} + \endlist +*/ diff --git a/src/qdoc/doc/qdoc-manual-contextcmds.qdoc b/src/qdoc/doc/qdoc-manual-contextcmds.qdoc new file mode 100644 index 000000000..d707c77cf --- /dev/null +++ b/src/qdoc/doc/qdoc-manual-contextcmds.qdoc @@ -0,0 +1,1058 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page 14-qdoc-commands-contextcommands.html + \previouspage Topic Commands + \contentspage QDoc Manual + \nextpage Document Navigation + + \title Context Commands + + The context commands provide information about the element being + documented that QDoc can't deduce on its own. For example: + \list + \li Is this class thread-safe? + \li Is this function reentrant? + \li Of which module is this class a member ? + \endlist + + Context commands can appear anywhere in a QDoc comment, + but they are normally placed near the top of the comment, just + below the \l {Topic Commands} {topic} command. + + \list + \li \l {abstract-command} {\\abstract} + \li \l {compat-command}{\\compat}, + \li \l {contentspage-command}{\\contentspage}, + \li \l {indexpage-command}{\\indexpage}, + \li \l {ingroup-command}{\\ingroup}, + \li \l {inherits-command}{\\inherits}, + \li \l {inmodule-command}{\\inmodule}, + \li \l {internal-command}{\\internal}, + \li \l {nextpage-command}{\\nextpage}, + \li \l {nonreentrant-command}{\\nonreentrant}, + \li \l {obsolete-command}{\\obsolete}, + \li \l {overload-command}{\\overload}, + \li \l {preliminary-command}{\\preliminary}, + \li \l {previouspage-command}{\\previouspage}, + \li \l {qmlabstract-command} {\\qmlabstract} + \li \l {reentrant-command}{\\reentrant}, + \li \l {reimp-command}{\\reimp}, + \li \l {relates-command}{\\relates}, + \li \l {since-command}{\\since}, + \li \l {startpage-command}{\\startpage}, + \li \l {subtitle-command}{\\subtitle} + \li \l {threadsafe-command}{\\threadsafe}, + \li \l {title-command}{\\title} + \endlist + +*/ + +/*! + \page 15-qdoc-commands-navigation.html + \previouspage Context Commands + \contentspage QDoc Manual + \nextpage Status + + \title Document Navigation + + The navigation commands are for linking the pages of a document in + a meaningful sequence. Below is a sequence of QDoc comments that + shows a typical use of the navigation commands. + + \section1 Example + \quotefile files/basicqt.qdoc.sample + + QDoc renders the "Getting Started" page in \c{creatingdialogs.html}: + + \quotation + \raw HTML + <table border="0" cellpadding="0" cellspacing="5" width="100%"> + + <tr> + <p> + [Previous: <a href="15-qdoc-commands-navigation.html#deadlink"> + Basic Qt</a>] + [<a href="15-qdoc-commands-navigation.html#deadlink">Contents</a>] + [Next: <a href="15-qdoc-commands-navigation.html#deadlink"> + Creating Dialogs</a>] + </p> + + <h1 align="center">Getting Started<br /></h1> + + <p> + This chapter shows how to combine basic C++ with the + functionality provided by Qt to create a few small graphical + interface (GUI) applications. + </p> + + <p> + [Previous: <a href="15-qdoc-commands-navigation.html#deadlink"> + Basic Qt</a>] + [<a href="15-qdoc-commands-navigation.html#deadlink">Contents</a>] + [Next: <a href="15-qdoc-commands-navigation.html#deadlink"> + Creating Dialogs</a>] + </p> + + </table> + \endraw + \endquotation + + The \l {indexpage-command} {\\indexpage} and \l + {startpage-command} {\\startpage} commands create links to the + page's index page and start page. These links can be used by + browsers and search engines. + + The index page is typically an alphabetical list of the document's + titles and topics, while the start page is the page considered by + the author to be the starting point of a multipage document. + + The links are included in the generated HTML source code, but have + no visual effect on the documentation: + + \code + <head> + ... + <link rel="index" href="index.html" /> + <link rel="start" href="basicqt.html" /> + ... + </head> + \endcode + + \section1 Commands + + \target previouspage-command + \section2 \\previouspage + + The \\previouspage command links the current page to the previous + page in a sequence.a The command has two arguments, each enclosed + by curly braces: the first is the link target (the title of + the previous page), the second is the link text. If the page's + title is equivalent to the link text, the second argument can be + omitted. + + The command must stand alone on its own line. + + \target nextpage-command + \section2 \\nextpage + + The \\nextpage command links the current page to the next page in + a sequence. The command follows the same syntax and argument + convention as the \l {previouspage-command} {\\previouspage} + command. + + \target startpage-command + \section2 \\startpage + + The \\startpage command specifies the first page of a sequence of + pages. The command must stand alone on its own line, and its + unique argument is the title of the first document. + + QDoc will generate a link to the start page and include it in the + generated HTML file, but this has no visual effect on the + documentation. The generated link type tells browsers and search + engines which document is considered by the author to be the + starting point of the collection. + + \target contentspage-command + \section2 \\contentspage + + The \\contentspage command links the current page to a table of + contents page. The command follows the same syntax and argument + convention as the \l {previouspage-command} {\\previouspage} + command. + + \target indexpage-command + \section2 \\indexpage + + The \\indexpage command specifies an index page for the current + document. The command must stand alone on its own line, and its + unique argument is the title of the index document. + + QDoc will generate a link to the index page and include it in the + generated HTML file, but this has no visual effect on the + documentation. The generated link type tells browsers and search + engines which document is considered by the author to be the + index page of the collection. +*/ + +/*! + \page 16-qdoc-commands-status.html + \previouspage Document Navigation + \contentspage QDoc Manual + \nextpage Thread Support + + \title Status + + These commands are for indicating that a documented element has + some special status. The element could be marked as about to be + made obsolete, or that it is provided for compatibility with an + earlier version, or is simply not to be included in the public + interface. The \l {since-command}{\\since} command is for + specifying the version number in which a function or class first + appeared. The \l {qmlabstract-command} {\\qmlabstract} command is + for marking a QML type as an abstract base class. + + \target abstract-command + \target qmlabstract-command + \section1 \\abstract and \\qmlabstract + + \\abstract is a synonym for the \\qmlabstract command. Add this + command to the \l {qmltype-command} {\\qmltype} comment for a QML + type when that type is meant to be used \e {only} as an abstract + base type. When a QML type is abstract, it means that the QML type + that can't be instantiated. Instead, the properties in its public + API are included in the public properties list on the reference + page for each QML type that inherits the abstract QML type. The + properties are documented as if they are properties of the + inheriting QML type. + + Normally, when a QML type is marked with \e{\\qmlabstract}, it is + also marked with \e{\\internal} so that its reference page is not + generated. It the abstract QML type is not marked internal, it + will have a reference page in the documentation. + + \target compat-command + \section1 \\compat + + The \\compat command is for indicating that a class or function is + part of the support library provided to keep old source code + working. + + The command must stand on its own line. + + Usually an equivalent function or class is provided as an + alternative. + + If the command is used in the documentation of a class, the + command expands to a warning that the referenced class is part of + the support library. The warning is located at the top of the + documentation page. + + \code + \beginqdoc + \class MyQt3SupportClass + \compat + \endqdoc + \endcode + + QDoc renders this at the top of the MyQt3SupportClass class + reference page. + + \quotation + \b {This class is part of the Qt 3 support + library.} It is provided to keep old source code + working. We strongly advise against using it in new + code. See the \l + {http://doc.qt.io/qt-4.8/porting4.html} {Porting + Guide} for more information. + \endquotation + + If the command is used when documenting a function, QDoc will + create and link to a separate page documenting Qt 3 support + members when generating the reference documentation for the + associated class. + + \code + \beginqdoc + \fn MyClass::MyQt3SupportMemberFunction + \compat + + Use MyNewFunction() instead. + \endqdoc + \endcode + + QDoc renders this in \c{myclass-qt3.html} as: + + \quotation + \raw HTML + <h1>Qt 3 Support Members for MyClass</h1> + \endraw + + \b {The following class members are part of the Qt 3 + support layer.} They are provided to help you port old code to + Qt 4. We advise against using them in new code. + + ... + + \list + \li void MyQt3SupportMemberFunction() + \li ... + \endlist + + \raw HTML + <hr /> + <h2>Member Function Documentation</h2> + <h3>void MyQt3SupportMemberFunction ()</h3> + <p>Use MyNewFunction() instead.</p> + \endraw + ... + \endquotation + + \target default-command + \section1 \\default + + The \\default command is for marking a QML property as the + \l {default-properties} + {default property}. The word \span {class="newStuff"} {default} is shown in red in + the documentation of the property. + + \code + / *! + \qmlproperty list<Change> State::changes + This property holds the changes to apply for this state. + \default + + By default these changes are applied against the default state. If the state + extends another state, then the changes are applied against the state being + extended. + * / + \endcode + + See how QDoc renders this property on the reference page for the + \l {State::changes}{State} type. + + \target obsolete-command + \section1 \\obsolete + + The \\obsolete command is for indicating that a function is being + deprecated, and it should no longer be used in new code. There is + no guarantee for how long it will remain in the library. + + The command must stand on its own line. + + When generating the reference documentation for a class, QDoc will + create and link to a separate page documenting its obsolete + functions. Usually an equivalent function is provided as an + alternative. + + \code + / *! + \fn MyClass::MyObsoleteFunction + \obsolete + + Use MyNewFunction() instead. + * / + \endcode + + QDoc renders this in \c{myclass-obsolete.html} as: + + \quotation + \raw HTML + <h1>Obsolete Members for MyClass</h1> + \endraw + + \b {The following class members are obsolete.} They are + provided to keep old source code working. We strongly advise + against using them in new code. + + ... + + \list + \li void MyObsoleteFunction() \c (obsolete) + \li ... + \endlist + + \raw HTML + <hr /> + <h2>Member Function Documentation</h2> + <h3>void MyObsoleteFunction ()</h3> + <p>Use MyNewFunction() instead.</p> + \endraw + ... + \endquotation + + \target internal-command + \section1 \\internal + + The \\internal command indicates that the referenced + function is not part of the public interface. + + The command must stand on its own line. + + QDoc ignores the documentation as well as the documented item, + when generating the associated class reference documentation. + + \code + / *! + \internal + + Tries to find the decimal separator. If it can't find + it and the thousand delimiter is != '.' it will try to + find a '.'; + * / + int QDoubleSpinBoxPrivate::findDelimiter + (const QString &str, int index) const + { + int dotindex = str.indexOf(delimiter, index); + if (dotindex == -1 && thousand != dot && delimiter != dot) + dotindex = str.indexOf(dot, index); + return dotindex; + } + \endcode + + This function will not be included in the documentation. + + \target preliminary-command + \section1 \\preliminary + + The \\preliminary command is for indicating that a referenced + function is still under development. + + The command must stand on its own line. + + The \\preliminary command expands to a notification in the + function documentation, and marks the function as preliminary when + it appears in lists. + + \code + / *! + \preliminary + + Returns information about the joining type attributes of the + character (needed for certain languages such as Arabic or + Syriac). + + * / + QChar::JoiningType QChar::joiningType() const + { + return QChar::joiningType(ucs); + } + \endcode + + QDoc renders this as: + + \quotation + \raw HTML + <h3> + <a href="http://doc.qt.io/qt-5/qchar.html#JoiningType-enum">JoiningType</a> + QChar::joiningType() const</h3> + \endraw + + \b {This function is under development and + subject to change.} + + Returns information about the joining type attributes of the + character (needed for certain languages such as Arabic or + Syriac). + \endquotation + + And the function's entry in QChar's list of public functions will be + rendered as: + + \quotation + \list + \li ... + \li JoiningType \l {QChar::joiningType()} {joiningType}() const \c (preliminary) + \li ... + \endlist + \endquotation + + \target since-command + \section1 \\since + + The \\since command tells in which minor release + the associated functionality was added. + + \code + / *! + \since 4.1 + + Returns an icon for \a standardIcon. + + ... + + \sa standardPixmap() + * / + QIcon QStyle::standardIcon(StandardPixmap standardIcon, const QStyleOption *option, const QWidget *widget) const + { + } + \endcode + + QDoc renders this as: + + \quotation + \raw HTML + <h3>QIcon QStyle::standardIcon(StandardPixmap standardIcon, const QStyleOption *option, const QWidget *widget) const</h3> + \endraw + + This function was introduced in Qt version 4.1 + + Returns an icon for \a standardIcon. + + ... + + See also \l {QStyle::standardPixmap()} {standardPixmap()}. + \endquotation + + QDoc generates the "Qt" reference from the \l + {25-qdoc-configuration-derivedprojects.html#project} {\c project} + configuration variable. For that reason this reference will change + according to the current documentation project. + + See also \l {project} + {\c project}. +*/ + + +/*! + \page 17-qdoc-commands-thread.html + \previouspage Status + \contentspage QDoc Manual + \nextpage Relating Things + + \title Thread Support + + The thread support commands are for specifying the level of + support for multithreaded programming in a class or function. + There are three levels of support: \c threadsafe, \c reentrant and + \c nonreentrant. + + The default is \c nonreentrant which means that the associated + class or function cannot be called by multiple threads. \c + Reentrant and \c threadsafe are levels primarily used for classes. + + \c Reentrant means that all the functions in the referenced class + can be called simultaneously by multiple threads, provided that + each invocation of the functions reference unique data. While \c + threadsafe means that all the functions in the referenced class + can be called simultaneously by multiple threads even when each + invocation references shared data. + + When a class is marked \l {reentrant-command} {\\reentrant} or \l + {threadsafe-command} {\\threadsafe}, functions in that class can + be marked \c nonreentrant using the \l {nonreentrant-command} + {\\nonreentrant} command. + + \section1 Example + + \target reentrant-example + \code + \beginqdoc + \class QLocale + \brief The QLocale class converts between numbers and their + string representations in various languages. + + \reentrant + \ingroup i18n + \ingroup text + + QLocale is initialized with a language/country pair in its + constructor and offers number-to-string and string-to-number + conversion functions similar to those in QString. + + ... + + \nonreentrant + + Sets the global default locale to \a locale. These values are + used when a QLocale object is constructed with no + arguments. If this function is not called, the system's locale + is used. + + \warning In a multithreaded application, the default locale + should be set at application startup, before any non-GUI + threads are created. + + \sa system(), c() + \endqdoc + void QLocale::setDefault(const QLocale &locale) + { + default_d = locale.d; + } + \endcode + + QDoc renders this as: + + \quotation + \raw HTML + <h1><center>QLocale Class Reference</center></h1> + \endraw + + The QLocale class converts between numbers and their string + representations in various languages. More... + + \code + #include <QLocale> + \endcode + + \b {Note:} All the functions in this class are \l + {17-qdoc-commands-thread.html#reentrant} {reentrant}, except \l + {QLocale::setDefault()} {setDefault()}. + + ... + + \raw HTML + <hr /> + <h2>Member Type Documentation</h2> + \endraw + + ... + + \raw HTML + <h3>void QLocale::setDefault ( const QLocale & locale ) </h3> + \endraw + + Sets the global default locale to locale. These values are + used when a QLocale object is constructed with no + arguments. If this function is not called, the system's locale + is used. + + \warning In a multithreaded application, the default locale + should be set at application startup, before any non-GUI + threads are created. + + \warning This function is not reentrant. + + See also \l {QLocale::system()} {system()} and \l + {QLocale::c()} {c()}. + + ... + \endquotation + + As shown above, QDoc generates a notification when a class is + declared reentrant, and lists the exceptions (the declared + nonreentrant functions). A link to the general documentation on \l + {17-qdoc-commands-thread.html#reentrant} {reentrancy and thread-safety} is + included. In addition a warning, "\b Warning: This function is + not reentrant.", is generated in the nonreentrant functions' + documentation. + + QDoc will generate the same notification and warnings when a class + is declared threadsafe. + + For more information see the general documentation on \l + {17-qdoc-commands-thread.html#reentrant} {reentrancy and thread-safety}. + + \section1 Commands + + \target threadsafe-command + \section2 \\threadsafe + + The \\threadsafe command includes a line in the documentation to + indicate that the associated class or function is \e threadsafe + and can be called simultaneously by multiple threads, even when + separate invocations reference shared data. + + The command must stand on its own line. + + The documentation generated from this command will be similar to + the what is generated for the \l {reentrant-command} {\\reentrant} + command. See the example above in the \l {reentrant-example} + {introduction}. + + See also \l{reentrant-command} {\\reentrant} and + \l{nonreentrant-command} {\\nonreentrant}. + + \target reentrant-command + \section2 \\reentrant + + The \\reentrant command indicates that the associated class or + function can be called simultaneously by multiple threads, + provided that each invocation references its own data. See the \l + {reentrant-example} {example} above. + + The command must stand on its own line. + + See also \l{nonreentrant-command} {\\nonreentrant} and + \l{threadsafe-command} {\\threadsafe}. + + \target nonreentrant-command + \section2 \\nonreentrant + + The \\nonreentrant command indicates that the associated class or + function cannot be called by multiple threads. Nonreentrant is the + default case. + + The command must stand on its own line. + + When a class is marked \l {reentrant-command} {\\reentrant} or \l + {threadsafe-command} {\\threadsafe}, functions in that class can + be marked \c nonreentrant using this command in the \l{fn-command} + {\\fn} comment of the functions to be excluded. + + See also \l{reentrant-command} {\\reentrant} and + \l{threadsafe-command} {\\threadsafe}. +*/ + +/*! + \page 18-qdoc-commands-relating.html + \previouspage Thread Support + \contentspage QDoc Manual + \nextpage Grouping Things + + \title Relating Things + + The relating commands are for specifying how one documented + element relates to another documented element. Some examples: + \list + \li This function is an overload of another function. + \li This function is a reimplementation of another function. + \li This typedef is \e related to some class or header file. + \endlist + + There is also a command for documenting that a QML type inherits + some other QML type. + + \section1 Commands + + \target inherits-command + \section2 \\inherits + + The \\inherits command is for documenting that one QML type + inherits some other QML type. It must be included in the + inheriting element's \l{qmltype-command}{\\qmltype} comment. + The argument is the name of the inherited QML type. + + \code + / *! + \qmltype PauseAnimation + \instantiates QDeclarativePauseAnimation + \ingroup qml-animation-transition + \since 4.7 + \inherits Animation + \brief The PauseAnimation element provides a pause for an animation. + + When used in a SequentialAnimation, PauseAnimation is a step + when nothing happens, for a specified duration. + + A 500ms animation sequence, with a 100ms pause between two animations: + + SequentialAnimation { + NumberAnimation { ... duration: 200 } + PauseAnimation { duration: 100 } + NumberAnimation { ... duration: 200 } + } + + \sa {QML Animation and Transitions}, {declarative/animation/basics}{Animation basics example} + * / + \endcode + + QDoc includes this line on the reference page for the + \l{http://qt-project.org/doc/qt-4.7/qml-pauseanimation.html} {PauseAnimation} + element: + + \quotation + Inherits \l{http://qt-project.org/doc/qt-4.7/qml-animation.html} {Animation} + \endquotation + + \target overload-command + \section2 \\overload + + The \\overload command is for indicating that a function is a + secondary overload of its name. + + The command must stand on its own line. + + For a function name that is overloaded (except constructors), QDoc + expects one primary version of the function, and all the others + marked with the \b {\\overload command}. The primary version + should be fully documented. Each overload can have whatever extra + documentation you want to add for just that overloaded version. + + From Qt 4.5, you can include the function name plus '()' as a + parameter to the \b{\\overload} command, which will include a + standard \e{This function overloads...} line of text with a link + to the documentation for the primary version of the function. + + \code + / *! + \overload addAction() + + This convenience function creates a new action with an + \a icon and some \a text. The function adds the newly + created action to the menu's list of actions, and + returns it. + + \sa QWidget::addAction() + * / + QAction *QMenu::addAction(const QIcon &icon, const QString &text) + { + QAction *ret = new QAction(icon, text, this); + addAction(ret); + return ret; + } + \endcode + + QDoc renders this as: + + \quotation + \raw HTML + <h3><a href="http://doc.qt.io/qt-5/qaction.html">QAction</a> + * QMenu::addAction ( const QIcon & <i>icon</i>, + const QString & <i>text</i> ) + </h3> + \endraw + + This function overloads \l {QMenu::addAction()} {addAction()} + + This convenience function creates a new action with an + \e icon and some \e text. The function adds the newly + created action to the menu's list of actions, and + returns it. + + See also + \l {QWidget::addAction()} {QWidget::addAction}(). + \endquotation + + If you don't include the function name with the \b{\\overload} + command, then instead of the "This function overloads..." line + with the link to the documentation for the primary version, you + get the old standard line: + + \quotation + This is an overloaded member function, provided for + convenience. + \endquotation. + + \target reimp-command + \section2 \\reimp + + The \\reimp command is for indicating that a function is a + reimplementation of a virtual function. + + The command must stand on its own line. + + QDoc will omit the reimplemented function from the class + reference. + + \code + / *! + \reimp + * / + void QToolButton::nextCheckState() + { + Q_D(QToolButton); + if (!d->defaultAction) + QAbstractButton::nextCheckState(); + else + d->defaultAction->trigger(); + } + \endcode + + This function will not be included in the documentation. Instead, + a link to the base function QAbstractButton::nextCheckState() will + appear in the documentation. + + \target relates-command + \section2 \\relates + + The \\relates command is for including the documentation of a + global element to some class or header file. The argument is a + class name or header file. + + \code + / *! + \relates QChar + + Reads a char from the stream \a in into char \a chr. + + \sa {Format of the QDataStream operators} + * / + QDataStream &operator>>(QDataStream &in, QChar &chr) + { + quint16 u; + in >> u; + chr.unicode() = ushort(u); + return in; + } + \endcode + + The documentation for this function will be included on the reference page + for class QChar. +*/ + +/*! + \page 19-qdoc-commands-grouping.html + \previouspage Relating Things + \contentspage QDoc Manual + \nextpage Naming Things + + \title Grouping Things + + The grouping commands relate classes to defined groups and + modules. The groups are used when generating lists of related + classes in the documentation, while the modules are elements of + Qt's structure. + + \section1 Commands + + \target ingroup-command + \section2 \\ingroup + + The \\ingroup command indicates that the given + overview or documented class belongs to a certain group of + related docmentation. + + A class or overview may belong to many groups. + + The \\ingroup command's argument is a group name, but note + that the command considers the rest of the line as part of + its argument. Make sure that the group name is followed by + a linebreak. + + \code + / *! + \class QDir + \brief The QDir class provides access to directory + structures and their contents. + + \ingroup io + ... + * / + \endcode + + This will include the QDir class in the \c io group, which means, + for example, that QDir will appear on the list created by calling + the \l {group-command} {\\group} command with the \c io argument. + + To list overviews that are related to a certain group, you must + generate the list explicitly using the \l {generatelist-command} + {\\generatelist} command with the \c related argument. + + See also \l {group-command} {\\group}. + + \target inmodule-command + \section2 \\inmodule + + The \\inmodule command relates a class to the module specified by + the command's argument. + + For the basic classes in Qt, a class's module is determined by its + location, namely its directory. However, for extensions like + ActiveQt and Qt Designer, a class must be related to a module + explicitly. + + The command's argument is a module name, but note that the command + considers the rest of the line as part of its argument. Make sure + that the module name is followed by a linebreak. + + \code + /*! + \class QDesignerTaskMenuExtension + \inmodule QtDesigner + * / + \endcode + + This ensures that the QDesignerTaskMenuExtension class is included + in the Qt Designer module, which means, for example, that the + class will appear on the list created by calling the \l + {generatelist-command} {\\generatelist} command with the \c + {{classesbymodule QtDesigner}} argument. + + See also \l {module-command} {\\module} and \l + {generatelist-command} {\\generatelist}. +*/ + +/*! + \page 20-qdoc-commands-namingthings.html + \previouspage Grouping Things + \contentspage QDoc Manual + \nextpage Markup Commands + + \title Naming Things + + In general, a title command considers everything that follows it + until the first line break as its argument. If the title is so + long it must span multiple lines, end each line (except the last + one) with a backslash. + + \section1 Commands + + \target title-command + \section2 \\title + + The \\title command sets the title for a documentation page, or + allows you to override it. + + \code + / *! + \page signalandslots.html + + \title Signals & Slots + + Signals and slots are used for communication between + objects. The signals and slots mechanism is a central + feature of Qt, and probably the part that differs most + from the features provided by other frameworks. + + ... + * / + \endcode + + QDoc renders this as: + + \quotation + \raw HTML + <h1><center>Signal and Slots</center></h1> + \endraw + + Signals and slots are used for communication between + objects. The signals and slots mechanism is a central + feature of Qt and probably the part that differs most + from the features provided by other frameworks. + ... + \endquotation + See also \l {subtitle-command} {\\subtitle}. + + \target subtitle-command + \section2 \\subtitle + + The \\subtitle command sets a subtitle for a documentation page. + + \code + \beginqdoc + \page qtopiacore-overview.html + + \title Qtopia Core + \subtitle Qt for Embedded Linux + + Qt/Embedded, the embedded Linux port of Qt, is a + complete and self-contained C++ GUI and platform + development tool for Linux-based embedded development. + ... + \endqdoc + \endcode + + QDoc renders this as: + + \quotation + \raw HTML + <h1><center>Qtopia Core</center></h1> + <h2><center>Qt for Embedded Linux</center></h2> + \endraw + + Qt/Embedded, the embedded Linux port of Qt, is a + complete and self-contained C++ GUI and platform + development tool for Linux-based embedded development. + ... + \endquotation + + See also \l {title-command} {\\title}. + +*/ diff --git a/src/qdoc/doc/qdoc-manual-intro.qdoc b/src/qdoc/doc/qdoc-manual-intro.qdoc new file mode 100644 index 000000000..84f941684 --- /dev/null +++ b/src/qdoc/doc/qdoc-manual-intro.qdoc @@ -0,0 +1,325 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page 01-qdoc-manual.html + \contentspage QDoc Manual + \previouspage QDoc Manual + \nextpage Command Index + + \title Introduction to QDoc + + QDoc is a tool used by Qt Developers to generate documentation for + software projects. It works by extracting \e {QDoc comments} from + project source files and then formatting these comments as HTML + pages or DITA XML documents. QDoc finds QDoc comments in \c + {.cpp} files and in \c {.qdoc} files. QDoc does not look for QDoc + comments in \c {.h} files. A QDoc comment always begins with an + exclamation mark (\b{!})). For example: + + \code + / *! + \class QObject + \brief The QObject class is the base class of all Qt objects. + + \ingroup objectmodel + + \reentrant + + QObject is the heart of the Qt \l{Object Model}. The + central feature in this model is a very powerful mechanism + for seamless object communication called \l{signals and + slots}. You can connect a signal to a slot with connect() + and destroy the connection with disconnect(). To avoid + never ending notification loops you can temporarily block + signals with blockSignals(). The protected functions + connectNotify() and disconnectNotify() make it possible to + track connections. + + QObjects organize themselves in \l {Object Trees & + Ownership} {object trees}. When you create a QObject with + another object as parent, the object will automatically + add itself to the parent's \c children() list. The parent + takes ownership of the object. It will automatically + delete its children in its destructor. You can look for an + object by name and optionally type using findChild() or + findChildren(). + + Every object has an objectName() and its class name can be + found via the corresponding metaObject() (see + QMetaObject::className()). You can determine whether the + object's class inherits another class in the QObject + inheritance hierarchy by using the \c inherits() function. + + .... + * / + \endcode + + From the QDoc comment above, QDoc generates the HTML \l {QObject} + {QObject class reference} page. + + This manual explains how to use the QDoc commands in QDoc comments + to embed good documentation in your source files. It also explains + how to make a \l {The QDoc Configuration File} {QDoc configuration + file}, which you will pass to QDoc on the command line. + + \section1 Running QDoc + + The name of the QDoc program is \c {qdoc}. To run qdoc from the + command line, give it the name of a configuration file: + + \quotation + \c {$ ../../bin/qdoc ./config.qdocconf} + \endquotation + + QDoc recognizes the \c {.qdocconf} suffix as a \l{The QDoc + Configuration File} {QDoc configuration file}. The configuration + file is where you tell QDoc where to find the project source + files, header files, and \c {.qdoc} files. It is also where you + tell QDoc what kind of output to generate (HTML, DITA XML,...), + and where to put the generated documentation. The configuration + file also contains other information for QDoc. + + See \l{The QDoc Configuration File} for instructions on how to + set up a QDoc configuration file. + + \section2 Running QDoc in Single Execution Mode + + Beginning with Qt 5.5, a new way to run QDoc is available that + reduces the time it takes to generate the Qt5 documentation by as + much as 90%. The new way to run QDoc is \e{single execution} mode. + Single execution mode is not currently available in the Qt5 build + system, which still uses the \e {standard} mode. Single execution + mode is only available when you run QDoc yourself, which you will + want to do often as you document your module and integrate your + documentation with the other Qt modules. + + To run QDoc in single execution mode, add \c {-single-exec} to the + command line and pass QDoc a master \c qdocconf file that is + simply a list of file paths for qdocconf files of all the Qt5 + modules. For example: + + \code + /Users/me/qt5/qtbase/bin/qdoc -outputdir /Users/me/qt5/qtbase/doc -installdir /Users/me/qt5/qtbase/doc /Users/me/qt5/master.qdocconf -single-exec + \endcode + + The qdocconf file, \c {master.qdocconf}, just lists the qdocconf files for all the Qt5 modules to be processed: + + \badcode + /Users/me/qt5/qtbase/src/corelib/doc/qtcore.qdocconf + /Users/me/qt5/qtbase/src/network/doc/qtnetwork.qdocconf + /Users/me/qt5/qtbase/src/sql/doc/qtsql.qdocconf + /Users/me/qt5/qtbase/src/xml/doc/qtxml.qdocconf + /Users/me/qt5/qtbase/src/testlib/doc/qttestlib.qdocconf + /Users/me/qt5/qtbase/src/concurrent/doc/qtconcurrent.qdocconf + /Users/me/qt5/qtbase/src/gui/doc/qtgui.qdocconf + /Users/me/qt5/qtbase/src/platformheaders/doc/qtplatformheaders.qdocconf + /Users/me/qt5/qtbase/src/widgets/doc/qtwidgets.qdocconf + /Users/me/qt5/qtbase/src/opengl/doc/qtopengl.qdocconf + /Users/me/qt5/qtbase/src/printsupport/doc/qtprintsupport.qdocconf + /Users/me/qt5/qtbase/src/tools/qdoc/doc/config/qdoc.qdocconf + /Users/me/qt5/qtbase/qmake/doc/qmake.qdocconf + /Users/me/qt5/qtsvg/src/svg/doc/qtsvg.qdocconf + /Users/me/qt5/qtxmlpatterns/src/xmlpatterns/doc/qtxmlpatterns.qdocconf + /Users/me/qt5/qtdeclarative/src/qml/doc/qtqml.qdocconf + /Users/me/qt5/qtdeclarative/src/quick/doc/qtquick.qdocconf + /Users/me/qt5/qtquickcontrols/src/controls/doc/qtquickcontrols.qdocconf + /Users/me/qt5/qtquickcontrols/src/layouts/doc/qtquicklayouts.qdocconf + /Users/me/qt5/qtquickcontrols/src/dialogs/doc/qtquickdialogs.qdocconf + /Users/me/qt5/qtmultimedia/src/multimedia/doc/qtmultimedia.qdocconf + /Users/me/qt5/qtmultimedia/src/multimediawidgets/doc/qtmultimediawidgets.qdocconf + /Users/me/qt5/qtactiveqt/src/activeqt/doc/activeqt.qdocconf + /Users/me/qt5/qtsensors/src/sensors/doc/qtsensors.qdocconf + /Users/me/qt5/qtwebkit/Source/qtwebkit.qdocconf + /Users/me/qt5/qttools/src/assistant/help/doc/qthelp.qdocconf + /Users/me/qt5/qttools/src/assistant/assistant/doc/qtassistant.qdocconf + /Users/me/qt5/qttools/src/designer/src/uitools/doc/qtuitools.qdocconf + /Users/me/qt5/qttools/src/designer/src/designer/doc/qtdesigner.qdocconf + /Users/me/qt5/qttools/src/linguist/linguist/doc/qtlinguist.qdocconf + /Users/me/qt5/qtwebkit-examples/doc/qtwebkitexamples.qdocconf + /Users/me/qt5/qtimageformats/src/imageformats/doc/qtimageformats.qdocconf + /Users/me/qt5/qtgraphicaleffects/src/effects/doc/qtgraphicaleffects.qdocconf + /Users/me/qt5/qtscript/src/script/doc/qtscript.qdocconf + /Users/me/qt5/qtscript/src/scripttools/doc/qtscripttools.qdocconf + /Users/me/qt5/qtserialport/src/serialport/doc/qtserialport.qdocconf + /Users/me/qt5/qtdoc/doc/config/qtdoc.qdocconf + \endcode + + \section3 Why Standard Mode Is Slow + + Currently, the Qt5 build system does not use QDoc's \e {single + execution} mode for generating the Qt5 documentation. It runs QDoc + in the \e {standard} mode. The standard mode was came about + because it was the easiest way to get convert the Qt4 QDoc to + handle the modularization of Qt in Qt5. In Qt4, QDoc ran once over + all the Qt4 sources to generate the HTML documentation for Qt. + While generating the Qt documentation, Qt4 QDoc also generated an + \e {index file} for Qt. That index file was meant to be used as + input to subsequent QDoc runs for generating HTML documentation + for other software libraries/products that were based on Qt. The + Qt index file allowed QDoc to link documentation written for those + other libraries/products to the Qt4 documentation. + + When Qt5 came along, Qt was divided into modules. Since then, + many new modules have been added to Qt. As of version 5.5, there + are over 40 separate modules in Qt5, each with its own + documentation that links to (depends on) the documentation of + other Qt modules. + + In \e {standard mode}, QDoc runs twice for each module. The first + QDoc run for a particular Qt module, parses all the module's + source files and then uses the information to generate the + module's index file. It is called the \e{prepare phase} because + it \e prepares the module's index file. The second QDoc run for + the module also parses all the module's source files and then + generates the module's documentation pages. This is called the \e + {generate phase} because it generates the module's documentation. + + The module's documentation will likely contain HTML links to the + documentation of one or more of the other Qt modules. For example, + most Qt5 modules contain links to documentation in QtCore. When a + Qt module contains links into the documentation of other Qt + module's, that module is said to depend on those other Qt modules. + Hence when QDoc runs the \e {generate phase} for that module, it + must also load the index files for those modules so it can create + those thinks. + + Hence, when the Qt build system generates the Qt documentation, it + first runs QDoc once for each module to perform the \e {prepare + phase} to generate all the index files. Then it runs QDoc once for + each module to perform the \e {generate phase}, where it uses the + dependent index files to generate the module's documentation, + including any cross-module links it finds. Each execution of + QDoc, both \e {prepare phase} and \e {generate phase}, parses + all the source files that are included in the module, and in the + \e {generate phase} also parses the index files for the dependent + modules. Nothing is retained or retainable between QDoc runs. + + \section3 Why Single Execution Mode Is Much Faster + + As the name implies, single execution mode uses a single QDoc + process to generate all the Qt5 documentation. The single QDoc + process still performs a \e{prepare phase} for each module and + then a \e{generate phase} for each module, but there are a few + differences. It begins by reading the master qdocconf file. Then + it reads each qdocconf file in the master list and performs the + \e{prepare phase} for each module. During the \e{prepare phase}, + all the source files for the module are parsed to build a syntax + tree for the module. The module's index file is then generated, + although QDoc will not re-read the index files in the \e{generate + phase}. The important difference here is that the module's syntax + tree is retained after the index file is generated, so that after + the \e{prepare phase} has been run for all the modules, QDoc still + has all the syntax trees it built. + + QDoc then processes each module again for the \e{generate phase}. + But now QDoc doesn't need to re-parse each module's source files, + because the module's syntax tree is still in memory. Nor does QDoc + need to re-read the index files for the dependent modules, again + because it still has the syntax trees for those modules in memry. + It remains only to traverse each module's syntax tree to generate + the documentation pages. + + Hence, QDoc parses each source file once and only once and doesn't + need to read index files. This is what makes single execution mode + much faster than the standard mode. It is anticipated that the Qt + build system will eventually run QDoc in single execution mode. + However, changes to the master qdocconf file might be required, so + the method described above for running QDoc in single execution + mode might have to change, watch this space for updates. + + \section1 How QDoc Works + + QDoc begins by reading the configuration file you specified on the + command line. It stores all the variables from the configuration + file for later use. One of the first variables it uses is \c + {outputformats}. This variable tells QDoc which output generators + it will run. The default value is \e {HTML}, so if you don't set + \c {outputformats} in your configuration file, QDoc will generate + HTML output. That's usually what you will want anyway, but you can + also specify \e {DITAXML} to get DITA XML output instead. + + Next, QDoc uses the values of the + \l {headerdirs-variable} + {headerdirs} variable and/or the \l + {22-qdoc-configuration-generalvariables.html#headers-variable} + {headers} variable to find and parse all the header files for your + project. QDoc does \e not scan header files for QDoc comments. It + parses the header files to build a master tree of all the items + that should be documented, in other words, the items that QDoc should find + QDoc comments for. + + After parsing all the header files and building the master tree of + items to be documented, QDoc uses the value of the \l + {22-qdoc-configuration-generalvariables.html#sourcedirs-variable} + {sourcedirs} variable and/or the value of the \l + {22-qdoc-configuration-generalvariables.html#sources-variable} + {sources} variable to find and parse all the \c {.cpp} and \c + {.qdoc} files for your project. These are the files QDoc scans for + \e {QDoc comments}. Remember that a QDoc comment begins with + an exclamation mark: \b {/*!} . + + For each QDoc comment it finds, it searches the master tree for + the item where the documentation belongs. Then it interprets the + qdoc commands in the comment and stores the interpreted commands + and the comment text in the tree node for the item. + + Finally, QDoc traverses the master tree. For each node, if the + node has stored documentation, QDoc calls the output generator + specified by the \c {outputformats} variable to format and write + the documentation in the directory specified in the configuration + file in the \l + {22-qdoc-configuration-generalvariables.html#outputdir-variable} + {outputdir} variable. + + \section1 Command Types + + QDoc interprets three types of commands: + + \list + \li \l {Topic Commands} + \li \l {Context Commands} + \li \l {Markup Commands} + \endlist + + Topic commands identify the element you are documenting, for example + a C++ class, function, type, or an extra page of text + that doesn't map to an underlying C++ element. + + Context commands tell QDoc how the element being documented + relates to other documented elements, for example, next and previous page + links, inclusion in page groups, or library modules. Context + commands can also provide information about the documented element + that QDoc can't get from the source files, for example, whether the + element is thread-safe, whether it is an overloaded or reimplemented function, + or whether it has been deprecated. + + Markup commands tell QDoc how text and image elements in the + document should be rendered, or about the document's outline + structure. +*/ + diff --git a/src/qdoc/doc/qdoc-manual-markupcmds.qdoc b/src/qdoc/doc/qdoc-manual-markupcmds.qdoc new file mode 100644 index 000000000..49cbfc065 --- /dev/null +++ b/src/qdoc/doc/qdoc-manual-markupcmds.qdoc @@ -0,0 +1,4081 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page 03-qdoc-commands-markup.html + \contentspage QDoc Manual + \previouspage Naming Things + \nextpage Text Markup + + \title Markup Commands + + The markup commands indicate the generated documentation's visual + appearance and logical structure. + + \list + \li \l {a-command} {\\a} + \li \l {annotatedlist-command} {\\annotatedlist} + \li \l {b-command} {\\b} \span {class="newStuff"} + \li \l {b-command} {\\bold} {(deprecated, use \\b)} + \li \l {brief-command} {\\brief} + \li \l {c-command} {\\c} + \li \l {caption-command} {\\caption} + \li \l {chapter-command} {\\chapter} + \li \l {code-command} {\\code} + \li \l {codeline-command} {\\codeline} + \li \l {div-command} {\\div} + \li \l {dots-command} {\\dots} + \li \l {e-command} {\\e} \span {class="newStuff"} + \li \l {else-command} {\\else} + \li \l {endif-command} {\\endif} + \li \l {footnote-command} {\\footnote} + \li \l {generatelist-command} {\\generatelist} + \li \l {header-command} {\\header} + \li \l {e-command} {\\i} \span {class="newStuff"} {(deprecated, use \\e)} + \li \l {if-command} {\\if} + \li \l {image-command} {\\image} + \li \l {include-command} {\\include} + \li \l {include-command} {\\input} + \li \l {inlineimage-command} {\\inlineimage} + \li \l {keyword-command} {\\keyword} + \li \l {l-command} {\\l} + \li \l {legalese-command} {\\legalese} + \li \l {li-command} {\\li} \span {class="newStuff"} + \li \l {list-command} {\\list} + \li \l {meta-command} {\\meta} + \li \l {noautolist-command} {\\noautolist} + \li \l {newcode-command} {\\newcode} + \li \l {li-command} {\\o} \span {class="newStuff"} {(deprecated, use \\li)} + \li \l {note-command} {\\note} + \li \l {oldcode-command} {\\oldcode} + \li \l {omit-command} {\\omit} + \li \l {part-command} {\\part} + \li \l {printline-command} {\\printline} + \li \l {printto-command} {\\printto} + \li \l {printuntil-command} {\\printuntil} + \li \l {quotation-command} {\\quotation} + \li \l {quotefile-command} {\\quotefile} + \li \l {quotefromfile-command} {\\quotefromfile} + \li \l {raw-command} {\\raw} + \li \l {row-command} {\\row} + \li \l {sa-command} {\\sa} + \li \l {sectionOne-command} {\\section1} + \li \l {sectionTwo-command} {\\section2} + \li \l {sectionThree-command} {\\section3} + \li \l {sectionFour-command} {\\section4} + \li \l {skipline-command} {\\skipline} + \li \l {skipto-command} {\\skipto} + \li \l {skipuntil-command} {\\skipuntil} + \li \l {snippet-command} {\\snippet} + \li \l {span-command} {\\span} + \li \l {sub-command} {\\sub} + \li \l {sup-command} {\\sup} + \li \l {table-command} {\\table} + \li \l {tableofcontents-command} {\\tableofcontents} + \li \l {target-command} {\\target} + \li \l {tt-command} {\\tt} + \li \l {uicontrol-command} {\\uicontrol} {(new 25/3/2012)} + \li \l {underline-command} {\\underline} + \li \l {raw-command} {\\unicode} + \li \l {warning-command} {\\warning} + \li \l {backslash-command} {\\\\} + \endlist +*/ + + +/*! + \page 04-qdoc-commands-textmarkup.html + \contentspage QDoc Manual + \previouspage Markup Commands + \nextpage Document Structure + + \title Text Markup + + The text formatting commands indicate how text is to be rendered. + + \target a-command + \section1 \\a (parameter marker) + + The \\a command tells QDoc the next word is a formal parameter name. + + A warning is emitted when a formal parameter is not documented or + is misspelled, so when you document a function you should mention + each formal parameter by name in the function description, + preceded by the \\a command. The parameter name is then rendered + in italics. + + \code + / *! + Constructs a line edit containing the text + \a contents. The \a parent parameter is sent + to the QWidget constructor. + * / + + QLineEdit::QLineEdit(const QString &contents, QWidget *parent) :QWidget(parent) + { + ... + } + + \endcode + + QDoc renders this as: + + \quotation + \b {QLineEdit::QLineEdit ( const QString & + contents, QWidget *parent )} + + Constructs a line edit containing the text \a contents. + The \a parent parameter is sent to the QWidget constructor. + \endquotation + + The formal parameter name may be enclosed between curly brackets, + but that isn't required. + + \target c-command + \section1 \\c (code font) + + The \\c command is used for rendering variable names, user-defined + class names, and C++ keywords (for example, \c int and \c for) in the code + font. + + The command renders its argument using a monospace font. For + example: + + \code + / *! + The \c AnalogClock class provides a clock widget with hour + and minute hands that is automatically updated every + few seconds. + * / + \endcode + + QDoc renders this as: + + \quotation + The \c AnalogClock class provides a clock widget with hour + and minute hands, which are automatically updated every + few seconds. + \endquotation + + If the text to be rendered in the code font contains spaces, enclose the + entire text in curly brackets. + + \code + \c {QLineEdit::QLineEdit(const QString &contents, QWidget *parent) :QWidget(parent)} + \endcode + + QDoc renders this as: + + \quotation + \c {QLineEdit::QLineEdit(const QString &contents, QWidget *parent) :QWidget(parent)} + \endquotation + + The \\c command accepts the special character \c \ within its + argument, which renders it as a normal character. So if you want + to use nested commands, you must use the \l {tt-command} {teletype + (\\tt)} command instead. + + See also \l {tt-command} {\\tt} and \l {code-command} {\\code}. + + \target div-command + \section1 \\div + + The \\div and \\enddiv commands delimit a large or small block of + text (which may include other QDoc commands) to which special + formatting attributes should be applied. + + An argument must be provided in curly braces, as in the qdoc + comment shown below. The argument is not interpreted but is used + as attribute(s) of the tag that is output by qdoc. + + For example, we might want to render an inline image so that it + floats to the right of the current block of text: + + \code + / *! + \div {class="float-right"} + \inlineimage qml-column.png + \enddiv + + * / + \endcode + + If qdoc is generating HTML, it will translate these commands to: + + \code + <div class="float-right"><p><img src="images/qml-column.png" /></p></div> + \endcode + + For HTML, the attribute value \e {float-right} then will refer to + a clause in the style.css file, which in this case could be: + + \code + div.float-right + { + float: right; margin-left: 2em + } + \endcode + + If qdoc is generating DITA XML, it will translate the commands to: + + \code + <sectiondiv outputclass="float-right"> + <p> + <fig> + <image href="images/qml-column.png" placement="inline"/> + </fig> + </p> + </sectiondiv> + \endcode + + Your DITA XML publishing program must then recognize the \e + {outputclass} attribute value. + + \note Note that the \b {\\div} command can be nested. + + Below you can find an example taken from the index.qdoc file used to + generate index.html for Qt 4.7: + + \code + \div {class="indexbox guide"} + \div {class="heading"} + Qt Developer Guide + \enddiv + \div {class="indexboxcont indexboxbar"} + \div {class="section indexIcon"} \emptyspan + \enddiv + \div {class="section"} + Qt is a cross-platform application and UI + framework. Using Qt, you can write web-enabled + applications once and deploy them across desktop, + mobile and embedded operating systems without + rewriting the source code. + \enddiv + \div {class="section sectionlist"} + \list + \li \l{Getting Started} + \li \l{Installation} {Installation} + \li \l{how-to-learn-qt.html} {How to learn Qt} + \li \l{tutorials.html} {Tutorials} + \li \l{Qt Examples} {Examples} + \li \l{qt4-7-intro.html} {What's new in Qt 4.7} + \endlist + \enddiv + \enddiv + \enddiv + \endcode + + When all the class attribute values are defined as they are in the + style.css file that is used for rendering the Qt documentation, + the above example is rendered as: + + \div {class="indexbox guide"} + \div {class="heading"} + Qt Developer Guide + \enddiv + \div {class="indexboxcont indexboxbar"} + \div {class="section indexIcon"} \emptyspan + \enddiv + \div {class="section"} + Qt is a cross-platform application and UI + framework. Using Qt, you can write web-enabled + applications once and deploy them across desktop, + mobile and embedded operating systems without + rewriting the source code. + \enddiv + \div {class="section sectionlist"} + \list + \li Getting Started + \li Installation + \li How to learn Qt + \li Tutorials + \li Examples + \li What's new in Qt 4.7 + \endlist + \enddiv + \enddiv + \enddiv + + When generating DITA XML, qdoc outputs the nested \e {div} commands as: + + \code + <sectiondiv outputclass="indexbox guide"> + <sectiondiv outputclass="heading"> + <p>Qt Developer Guide</p> + </sectiondiv> + <sectiondiv outputclass="indexboxcont indexboxbar"> + <sectiondiv outputclass="section indexIcon"/> + <sectiondiv outputclass="section"> + <p>Qt is a cross-platform application and UI + framework. Using Qt, you can write + web-enabled applications once and deploy + them across desktop, mobile and embedded + operating systems without rewriting the + source code. + </p> + </sectiondiv> + <sectiondiv outputclass="section sectionlist"> + <ul> + <li> + <xref href="gettingstarted.xml#id-606ee7a8-219b-47b7-8f94-91bc8c76e54c">Getting started</xref> + </li> + <li> + <xref href="installation.xml#id-075c20e2-aa1e-4f88-a316-a46517e50443">Installation</xref> + </li> + <li> + <xref href="how-to-learn-qt.xml#id-49f509b5-52f9-4cd9-9921-74217b9a5182">How to learn Qt</xref> + </li> + <li> + <xref href="tutorials.xml#id-a737f955-a904-455f-b4aa-0dc69ed5a64f">Tutorials</xref> + </li> + <li> + <xref href="all-examples.xml#id-98d95159-d65b-4706-b08f-13d80080448d">Examples</xref> + </li> + <li> + <xref href="qt4-7-intro.xml#id-519ae0e3-4242-4c2a-b2be-e05d1e95f177">What's new in Qt 4.7</xref> + </li> + </ul> + </sectiondiv> + </sectiondiv> + </sectiondiv> + \endcode + + Your DITA XML publishing program must recognize the values of the + \e {outputclass} attribute. + + See also \l {span-command} {\\span}. + + \target span-command + \section1 \\span + + The \\span command applies special formatting to a small block of text. + + Two arguments must be provided, each argument in curly braces, as + shown in the QDoc comment below. The first argument is not + interpreted, but specifies the formatting attribute(s) of the tag + output by QDoc. The second argument is the text to be rendered with + the special formatting attributes. + + For example, we might want to render the first word of each + element in a numeric list in blue. + + \code + / *! + Global variables with complex types: + \list 1 + \li \span {class="variableName"} {mutableComplex1} in globals.cpp at line 14 + \li \span {class="variableName"} {mutableComplex2} in globals.cpp at line 15 + \li \span {class="variableName"} {constComplex1} in globals.cpp at line 16 + \li \span {class="variableName"} {constComplex2} in globals.cpp at line 17 + \endlist + * / + \endcode + + Class \e {variableName} refers to a clause in your style.css. + + \code + .variableName + { + font-family: courier; + color: blue + } + \endcode + + Using the \e {variableName} clause shown above, the example is rendered as: + + Global variables with complex types: + \list 1 + \li \span {class="variableName"} {mutableComplex1} in globals.cpp at line 14 + \li \span {class="variableName"} {mutableComplex2} in globals.cpp at line 15 + \li \span {class="variableName"} {constComplex1} in globals.cpp at line 16 + \li \span {class="variableName"} {constComplex2} in globals.cpp at line 17 + \endlist + + \note The \b span command does not cause a new paragraph to be + started. + + See also \l {div-command} {\\div}. + + \target tt-command + \section1 \\tt (teletype font) + + The \\tt command renders its argument in a monospace font. This + command behaves just like the \l {c-command} {\\c} command, except + that \\tt allows you to nest QDoc commands within the argument + (e.g. \l {e-command} {\\e}, \l {b-command} {\\b} and \l + {underline-command} {\\underline}). + + \code + / *! + After having populated the main container with + child widgets, \c setupUi() scans the main container's list of + slots for names with the form + \tt{on_\e{objectName}_\e{signalName}().} + * / + \endcode + + QDoc renders this as: + + \quotation + After having populated the main container with + child widgets, \c setupUi() scans the main container's list of + slots for names with the form + \tt{on_\e{objectName}_\e{signalName}().} + \endquotation + + If the text to be rendered in the code font contains spaces, enclose the + entire text in curly brackets. + + \code + \tt {QLineEdit::QLineEdit(const QString &contents, QWidget *parent) :QWidget(parent)} + \endcode + + QDoc renders this as: + + \quotation + \tt {QLineEdit::QLineEdit(const QString &contents, QWidget *parent) :QWidget(parent)} + \endquotation + + See also \l {c-command} {\\c}. + + \target b-command + \section1 \\b + + The \\b command renders its argument in bold font. This command used + to be called \\bold. + + \code + / *! + This is regular text; \b {this text is + rendered using the \\b command}. + * / + \endcode + + QDoc renders this as: + + \quotation + This is regular text; \b {this text is rendered using + the \\b command}. + \endquotation + + \target e-command + \section1 \\e (emphasis, italics) \span {class="newStuff"} {(new 5/3/2012)} + + The \\e command renders its argument in a special font, normally italics. This + command used to be called \\i, which is now deprecated. Use \e for italics. + + If the argument contains spaces or other punctuation, enclose the + argument in curly brackets. + + \code + / *! + Here, we render \e {a few words} in italics. + * / + \endcode + + QDoc renders this as: + + \quotation + Here, we render \e {a few words} in italics. + \endquotation + + If you want to use other QDoc commands within an argument that + contains spaces, you always need to enclose the argument in + braces. But QDoc is smart enough to count parentheses [3], so you + don't need braces in cases like this: + + \code + / *! + An argument can sometimes contain whitespaces, + for example: \e QPushButton(tr("A Brand New Button")) + * / + \endcode + + QDoc renders this as: + + \quotation + An argument can sometimes contain whitespaces, + for example: \e QPushButton(tr("A Brand New Button")) + \endquotation + + Finally, trailing punctuation is not included in an argument [4], + nor is "'s" [5] + + \raw HTML + <table align="center" cellpadding="2" + cellspacing="1" border="0"> + <tr valign="top" bgcolor="#a2c511"> + <th></th> + <th>QDoc Syntax</th> + <th>Generated Documentation</th> + </tr> + + <tr valign="top" bgcolor="#d0d0d0"> + <td>1</td> + <td>A variation of a command button is a \e menu + button.</td> + <td>A variation of a command button is a <i>menu</i> + button.</td> + </tr> + + <tr valign="top" bgcolor="#c0c0c0"> + <td>2</td> + <td>The QPushButton widget provides a + \e {command button}.</td> + <td>The QPushButton widget provides a + <i>command button</i>.</td> + </tr> + + <tr valign="top" bgcolor="#d0d0d0"> + <td>3</td> + <td>Another class of buttons are option buttons + \e (see QRadioButton).</td> + <td>Another class of buttons are option buttons + <i> (see QRadioButton)</i>.</td> + </tr> + + <tr valign="top" bgcolor="#c0c0c0"> + <td>4</td> + <td>A push button emits the signal \e clicked().</td> + <td>A push button emits the signal <i>clicked</i>().</td> + </tr> + + <tr valign="top" bgcolor="#d0d0d0"> + <td>5</td> + <td>The \e QPushButton's checked property is + false by default.</td> + <td>The <i>QPushButton</i>'s checked property is + false by default.</td> + </tr> + + </table> + \endraw + + \target sub-command + \section1 \\sub + + The \\sub command renders its argument lower than the baseline of + the regular text, using a smaller font. + + \code + / *! + Definition (Range): Consider the sequence + {x\sub n}\sub {n > 1} . The set + + {x\sub 2, x\sub 3, x\sub 4, ...} = {x\sub n ; n = 2, 3, 4, ...} + + is called the range of the sequence. + * / + \endcode + + QDoc renders this as: + + \quotation + Definition (Range): Consider the sequence + {x\sub n}\sub {n > 1} . The set + + {x\sub 2, x\sub 3, x\sub 4, ...} = {x\sub n ; n = 2, 3, 4, ...} + + is called the range of the sequence. + \endquotation + + If the argument contains spaces or other punctuation, enclose the + argument in curly brackets. + + \target sup-command + \section1 \\sup + + The \\sup command renders its argument higher than + the baseline of the regular text, using a smaller font. + + \code + / *! + The series + + 1 + a + a\sup 2 + a\sup 3 + a\sup 4 + ... + + is called the \i {geometric series}. + * / + \endcode + + QDoc renders this as: + + \quotation + The series + + 1 + a + a\sup 2 + a\sup 3 + a\sup 4 + ... + + is called the \e {geometric series}. + \endquotation + + If the argument contains spaces or other punctuation, enclose the + argument in curly brackets. + + \target uicontrol-command + \section1 \\uicontrol + + The \\uicontrol command is used to mark content as being used for UI + control elements. When using HTML, the output is rendered in bold. + When using DITA XML the content is enclosed in a \c{uicontrol} tag. + + \sa \\b + + \target underline-command + \section1 \\underline + + The \\underline command renders its argument underlined. + + \code + / *! + The \underline {F}ile menu gives the users the possibility + to edit an existing file, or save a new or modified + file, and exit the application. + * / + \endcode + + QDoc renders this as: + + \quotation + The \underline {F}ile menu gives the users the possibility + to edit an existing file, or save a new or modified + file, and exit the application. + \endquotation + + If the argument contains spaces or other punctuation, enclose the + argument in curly brackets. + + \target backslash-command + \section1 \\\\ (double backslash) + + The \\\\ command expands to a double backslash. + + QDoc commands always start with a single backslash. To display a + single backslash in the text you need to type two backslashes. If + you want to display two backslashes, you need to type four. + + \code + / *! + The \\\\ command is useful if you want a + backslash to appear verbatim, for example, + writing C:\\windows\\home\\. + * / + \endcode + + QDoc renders this as: + + \quotation + The \\\\ command is useful if you want a + backslash to appear verbatim, for example, + writing C:\\windows\\home\\. + \endquotation + + However, if you want your text to appear in a monospace font as + well, you can use the \l {c-command} {\\c} command instead, which + accepts and renders the backslash as any other character. For + example: + + \code + / *! + The \\c command is useful if you want a + backslash to appear verbatim, and the word + that contains it written in a monospace font, + like this: \c {C:\windows\home\}. + * / + \endcode + + QDoc renders this as: + + \quotation + The \\c command is useful if you want a + backslash to appear verbatim, and the word + that contains it written in a monospace font, + like this: \c {C:\windows\home\}. + \endquotation + +*/ + + +/*! + \page 05-qdoc-commands-documentstructure.html + \previouspage Text Markup + \contentspage QDoc Manual + \nextpage Including Code Inline + + \title Document Structure + + The document structuring commands are for dividing your document + into sections. QDoc supports six kinds of sections: \c \part, \c + \chapter, \c \section1, \c \section2, \c \section3, and \c + \section4. The \c \section1..4 commands are the most useful. They + correspond to the traditional section, subsection, etc used in + outlining. + + \target part-command + \section1 \\part + + The \\part command is intended for use in a large document, like a + book. + + In general a document structuring command considers everything + that follows it until the first line break as its argument. The + argument is rendered as the unit's title. If the title needs to be + spanned over several lines, make sure that each line (except the + last one) is ended with a backslash. + + In total, there are six levels of sections in QDoc: \c \part, \c + \chapter, \c \section1, \c \section2, \c \section3 and \c + \section4. \c \section1 to \c \section4 correspond to the + traditional section, subsection, subsubsection and + subsubsubsection. + + There is a strict ordering of the section units: + + \code + part + | + chapter + | + section1 + | + section2 + | + section3 + | + section4 + \endcode + + For example, a \c section1 unit can only appear as the top level + section or inside a \c chapter unit. Skipping a section unit, for + example from \c part to \c section1, is not allowed. + + You can \e begin with either of the three: \c part, \c chapter or + \c section1. + + + \code + / *! + \part Basic Qt + + This is the first part. + + + \chapter Getting Started + + This is the first part's first chapter. + + + \section1 Hello Qt + + This is the first chapter's first section. + + + \section1 Making Connections + + This is the first chapter's second section. + + + \section1 Using the Reference Documentation + + This is the first chapter's third section. + + + \chapter Creating Dialogs + + This is the first part's second chapter. + + + \section1 Subclassing QDialog + + This is the second chapter's first section. + + ... + + + \part Intermediate Qt + + This is the second part. + + + \chapter Layout Management + + This is the second part's first chapter. + + + \section1 Basic Layouts + + This is the first chapter's first section. + + ... + * / + \endcode + + QDoc renders this as: + + \quotation + \raw HTML + <a name="Basic Qt"> + <h1>Basic Qt</h1> + </a> + <p>This is the first part.</p> + + <a name="Getting started"> + <h2>Getting Started</h2> + </a> + This is the first part's first chapter.</p> + + <a name="Hello Qt"> + <h3>Hello Qt</h3> + </a> + <p>This is the first chapter's first section.</p> + + <a name="Making Connections"> + <h3>Making Connections</h3> + </a> + <p>This is the first chapter's second section.</p> + + <a name="Using the Reference Documentation"> + <h3>Using the Reference Documentation</h3> + </a> + <p>This is the first chapter's third section.</p> + + <a name="Creating Dialogs"> + <h2>Creating Dialogs</h2> + </a> + <p>This is the first part's second chapter.</p> + + <a name="Subclassing QDialog"> + <h3>Subclassing QDialog</h3> + </a> + <p>This is the second chapter's first section.</p> + + ... + + <a name="Intermediate Qt"> + <h1>Intermediate Qt</h1> + </a> + <p>This is the second part.</p> + + <a name="Layout Management"> + <h2>Layout Management</h2> + </a> + <p>This is the second part's first chapter.</p> + + <a name="Basic Layouts"> + <h3>Basic Layouts</h3> + </a> + <p>This is the first chapter's first section.</p> + + ... + + \endraw + \endquotation + + Each section is a logical unit in the document. The section + heading appears in the automatically generated table of contents + that normally appears in the upper right-hand corner of the page. + + \target chapter-command + \section1 \\chapter + + The \\chapter command is intended for use in + larger documents, and divides the document into chapters. + + See \l{part} {\\part} for an explanation of the various + section units, command argument, and rendering. + + \target sectionOne-command + \section1 \\section1 + + The \\section1 command starts a new section. + + See \l{part} {\\part} for an explanation of the various + section units, command argument, and rendering. + + \target sectionTwo-command + \section1 \\section2 + + The \\section2 command starts a new section. + + See \l{part} {\\part} for an explanation of the various + section units, command argument, and rendering. + + \target sectionThree-command + \section1 \\section3 + + The \\section3 command starts a new section. + + See \l{part} {\\part} for an explanation of the various + section units, command argument, and rendering. + + \target sectionFour-command + \section1 \\section4 + + The \\section4 command starts a new section. + + See \l{part} {\\part} for an explanation of the various + section units, command argument, and rendering. + +*/ + + +/*! + \page 06-qdoc-commands-includecodeinline.html + \previouspage Document Structure + \contentspage QDoc Manual + \nextpage Including External Code + + \title Including Code Inline + + The following commands are used to render source code without + formatting. The source code begins on a new line, rendered in the + code. + + \b{Note:} Although all these commands are for rendering C++ + code, the + \l{07-0-qdoc-commands-includingexternalcode.html#snippet-command} + {\\snippet} and + \l{07-0-qdoc-commands-includingexternalcode.html#codeline-command} + {\\codeline} commands are preferred over the others. These + commands allow equivalent code snippets for other Qt language + bindings to be substituted for the C++ snippets in the + documentation. + + \target code-command + \section1 \\code + + The \\code and \\endcode commands enclose a snippet of source code. + + \note The \l {c-command} {\\c} command can be used for short code + fragments within a sentence. The \\code command is for longer code + snippets. It renders the code verbatim in a separate paragraph in + the code font. + + When processing any of the \\code, \l {newcode-command} {\\newcode} or \l + {oldcode-command} {\\oldcode} commands, QDoc removes all + indentation that is common for the verbatim code blocks within a + \c{/}\c{*!} ... \c{*}\c{/} comment before it adds the standard + indentation. For that reason the recommended style is to use 8 + spaces for the verbatim code contained within these commands + + \note This doesn't apply to externally quoted code using the \l + {quotefromfile-command} {\\quotefromfile} or \l + {quotefile-command} {\\quotefile} command. + + \code + / *! + \code + #include <QApplication> + #include <QPushButton> + + int main(int argc, char *argv[]) + { + ... + } + \ endcode + * / + \endcode + + QDoc renders this as: + + \code + #include <QApplication> + #include <QPushButton> + + int main(int argc, char *argv[]) + { + ... + } + \endcode + + Other QDoc commands are disabled within \\code... \\endcode, and + the special character '\\' is accepted and rendered like the rest + of the code. + + To include code snippets from an external file, use the + \l{07-0-qdoc-commands-includingexternalcode.html#snippet-command} + {\\snippet} and + \l{07-0-qdoc-commands-includingexternalcode.html#codeline-command} + {\\codeline} commands. + + See also \l {c-command} {\\c}, \l + {07-0-qdoc-commands-includingexternalcode.html#quotefromfile-command} + {\\quotefromfile}, \l{newcode-command} {\\newcode}, and \l {oldcode-command} + {\\oldcode}. + + \target newcode-command + \section1 \\newcode + + The \\newcode, \\oldcode, and \\endcode commands enable you to + show how to port a snippet of code to a new version of an API. + + The \\newcode command and its companion the \\oldcode command are + a convenience combination of the \l {code-command} {\\code} commands: + this combination provides a text relating the two code snippets to each + other. + + The \\newcode command requires a preceding \\oldcode statement. + + Like the \l{code-command}{\\code} command, the \\newcode command renders its + code on a new line in the documentation using a monospace font and the + standard indentation. + + \code + / *! + \oldcode + if (printer->setup(parent)) + ... + \newcode + QPrintDialog dialog(printer, parent); + if (dialog.exec()) + ... + \ endcode + * / + \endcode + + QDoc renders this as: + + \quotation + \oldcode + if (printer->setup(parent)) + ... + \newcode + QPrintDialog dialog(printer, parent); + if (dialog.exec()) + ... + \endcode + \endquotation + + Other QDoc commands are disabled within \\oldcode ... \\endcode, + and the '\\' character doesn't need to be escaped. + + \target oldcode-command + \section1 \\oldcode + + The \\oldcode command requires a corresponding + \\newcode statement; otherwise QDoc fails to parse the command + and emits a warning. + + See also \l {newcode-command} {\\newcode}. + + \target qml-command + \section1 \\qml + + The \\qml and \\endqml commands enclose a snippet of QML source + code. Currently, QDoc handles \\qml and \\endqml in exactly the same + way as \\code and \\endcode. + + \code + / *! + \qml + import QtQuick 1.0 + + Row { + Rectangle { + width: 100; height: 100 + color: "blue" + transform: Translate { y: 20 } + } + Rectangle { + width: 100; height: 100 + color: "red" + transform: Translate { y: -20 } + } + } + \endqml + * / + \endcode + + QDoc renders this as: + + \qml + import QtQuick 1.0 + + Row { + Rectangle { + width: 100; height: 100 + color: "blue" + transform: Translate { y: 20 } + } + Rectangle { + width: 100; height: 100 + color: "red" + transform: Translate { y: -20 } + } + } + \endqml +*/ + + +/*! + \page 07-0-qdoc-commands-includingexternalcode.html + \previouspage Including Code Inline + \contentspage QDoc Manual + \nextpage Creating Links + + \title Including External Code + + The following commands enable you to include code snippets from + external files. You can make QDoc include the complete contents of + a file, or you can quote specific parts of the file and skip + others. The typical use of the latter is to quote a file chunk by + chunk. + + \b{Note:} Although all these commands are for rendering C++ + code, the + \l{07-0-qdoc-commands-includingexternalcode.html#snippet-command} + {\\snippet} and + \l{07-0-qdoc-commands-includingexternalcode.html#codeline-command} + {\\codeline} commands are preferred over the others. These + commands allow equivalent code snippets for other Qt language + bindings to be substituted for the C++ snippets in the + documentation. + + \target quotefile-command + \section1 \\quotefile + + The \\quotefile command expands to the complete contents of the + file given as argument. + + The command considers the rest of the line as part of its + argument, make sure to follow the file name with a line break. + + The file's contents is rendered in a separate paragraph, using a + monospace font and the standard indentation. The code is shown + verbatim. + + \code + / *! + This is a simple "Hello world" example: + + \quotefile examples/main.cpp + + It contains only the bare minimum you need + to get a Qt application up and running. + * / + \endcode + + QDoc renders this as: + + \quotation + This is a simple "Hello world" example: + + \quotefile examples/main.cpp + + It contains only the bare minimum you need to get a Qt + application up and running. + \endquotation + + See also \l {quotefromfile-command} {\\quotefromfile} and + \l {code-command} {\\code}. + + + \target quotefromfile-command + \section1 \\quotefromfile + + The \\quotefromfile command opens the file given as argument for + quoting. + + The command considers the rest of the line as part of its + argument, make sure to follow the file name with a line break. + + The command is intended for use when quoting parts from file with + the walkthrough commands: \l {printline-command} {\\printline}, \l + {printto-command} {\\printto}, \l {printuntil-command} + {\\printuntil}, \l {skipline-command} {\\skipline}, \l + {skipto-command} {\\skipto}, \l {skipuntil-command} + {\\skipuntil}. This enables you to quote specific portions of a + file. + + \code + / *! + The whole application is contained within + the \c main() function: + + \quotefromfile examples/main.cpp + + \skipto main + \printuntil app(argc, argv) + + First we create a QApplication object using + the \c argc and \c argv parameters. + + \skipto QPushButton + \printuntil resize + + Then we create a QPushButton, and give it a reasonable + size using the QWidget::resize() function. + + ... + * / + \endcode + + QDoc renders this as: + + \quotation + The whole application is contained within + the \c main() function: + + \quotefromfile examples/main.cpp + + \skipto main + \printuntil app(argc, argv) + + First we create a QApplication object using the \c argc + and \c argv parameters. + + \skipto QPushButton + \printuntil resize + + Then we create a QPushButton, and give it a reasonable + size using the QWidget::resize() function. + + ... + \endquotation + + QDoc remembers which file it is quoting from, and the current + position in that file (see \l {file} {\\printline} for more + information). There is no need to "close" the file. + + See also \l {quotefile-command} {\\quotefile}, \l {code-command} + {\\code} and \l {dots} {\\dots}. + + \target printline-command + \section1 \\printline + + The \\printline command expands to the line from the current + position to the next non-blank line of the current source file. + + To ensure that the documentation remains synchronized with the + source file, a substring of the line must be specified as an + argument to the command. Note that the command considers the rest + of the line as part of its argument, make sure to follow the + substring with a line break. + + The line from the source file is rendered as a separate paragraph, + using a monospace font and the standard indentation. The code is + shown verbatim. + + \code + / *! + There has to be exactly one QApplication object + in every GUI application that uses Qt. + + \quotefromfile examples/main.cpp + + \printline QApplication + + This line includes the QApplication class + definition. QApplication manages various + application-wide resources, such as the + default font and cursor. + + \printline QPushButton + + This line includes the QPushButton class + definition. The QPushButton widget provides a command + button. + + \printline main + + The main function... + * / + \endcode + + QDoc renders this as: + + \quotation + There has to be exactly one QApplication object + in every GUI application that uses Qt. + + \quotefromfile examples/main.cpp + + \skipto QApplication + \printline QApplication + + This line includes the QApplication class + definition. QApplication manages various + application-wide resources, such as the + default font and cursor. + + \printline QPushButton + + This line includes the QPushButton class + definition. The QPushButton widget provides a command + button. + + \printline main + + The main function... + \endquotation + + \target file + + QDoc reads the file sequentially. To move the current position + forward you can use either of the \l {skipline-command} + {\\skip...} commands. To move the current position backward, you + can use the \l {quotefromfile-command} {\\quotefromfile} command + again. + + \target substring + + If the substring argument is surrounded by slashes it is + interpreted as a \l {QRegExp}{regular expression}. + + \code + / *! + \quotefromfile examples/mainwindow.cpp + + \skipto closeEvent + \printuntil /^\}/ + + Close events are sent to widgets that the users want to + close, usually by clicking \c File|Exit or by clicking + the \c X title bar button. By reimplementing the event + handler, we can intercept attempts to close the + application. + * / + \endcode + + QDoc renders this as: + + \quotation + \quotefromfile examples/mainwindow.cpp + + \skipto closeEvent + \printuntil /^\}/ + + Close events are sent to widgets that the users want to + close, usually by clicking \c File|Exit or by clicking + the \c X title bar button. By reimplementing the event + handler, we can intercept attempts to close the + application. + \endquotation + + (\l {widgets/scribble} {The complete example file...}) + + The regular expression \c /^\}/ makes QDoc print until the first + '}' character occurring at the beginning of the line without + indentation. /.../ encloses the regular expression, and '^' means + the beginning of the line. The '}' character must be escaped since + it is a special character in regular expressions. + + QDoc will emit a warning if the specified substring or regular + expression cannot be located, i.e. if the source code has changed. + + See also \l {printto-command} {\\printto} and \l + {printuntil-command} {\\printuntil}. + + \target printto-command + \section1 \\printto + + The \\printto command expands to all the lines from the current + position up to and \e excluding the next line containing a given + substring. + + The command considers the rest of the line as part of its + argument, make sure to follow the substring with a line break. The + command also follows the same conventions for \l {file} + {positioning} and \l {substring} {argument} as the \l + {printline-command} {\\printline} command. + + The lines from the source file are rendered in a separate + paragraph, using a monospace font and the standard + indentation. The code is shown verbatim. + + \code + / *! + The whole application is contained within the + \c main() function: + + \quotefromfile examples/main.cpp + \printto hello + + First we create a QApplication object using the \c argc and + \c argv parameters... + * / + \endcode + + QDoc renders this as: + + \quotation + The whole application is contained within the + \c main() function: + + \quotefromfile examples/main.cpp + \skipto main + \printto hello + + First we create a QApplication object using the \c argc + and \c argv parameters... + \endquotation + + See also \l {printline-command} {\\printline} and \l + {printuntil-command} {\\printuntil}. + + \target printuntil-command + \section1 \\printuntil + + The \\printuntil command expands to all the lines from the current + position up to and \e including the next line containing a given + substring. + + The command considers the rest of the line as part of its + argument, make sure to follow the substring with a line break. The + command also follows the same conventions for \l {file} + {positioning} and \l {substring} {argument} as the \l + {printline-command} {\\printline} command. + + The lines from the source file are rendered in a separate + paragraph, using a monospace font and the standard + indentation. The code is shown verbatim. + + \code + / *! + The whole application is contained within the + \c main() function: + + \quotefromfile examples/main.cpp + \skipto main + \printuntil hello + + First we create a QApplication object using the + \c argc and \c argv parameters, then we create + a QPushButton. + * / + \endcode + + QDoc renders this as: + + \quotation + The whole application is contained within the + \c main() function: + + \quotefromfile examples/main.cpp + \skipto main + \printuntil hello + + First we create a \l + {http://doc.qt.io/qt-5/qapplication.html} {QApplication} + object using the \c argc and \c argv parameters, then we + create a \l + {http://doc.qt.io/qt-5/qpushbutton.html} {QPushButton}. + \endquotation + + See also \l {printline-command} {\\printline} and \l + {printto-command} {\\printto}. + + \target skipline-command + \section1 \\skipline + + The \\skipline command ignores the next non-blank line in the + current source file. + + Doc reads the file sequentially, and the \\skipline command is + used to move the current position (omitting a line of the source + file). See the remark about \l {file} {file positioning} above. + + The command considers the rest of the line as part of its + argument, make sure to follow the substring with a line break. The + command also follows the same conventions for \l {substring} + {argument} as the \l {printline-command} {\\printline} command, + and it is used in conjunction with the \l {quotefromfile-command} + {\\quotefromfile} command. + + \code + / *! + QPushButton is a GUI push button that the user + can press and release. + + \quotefromfile examples/main.cpp + \skipline QApplication + \printline QPushButton + + This line includes the QPushButton class + definition. For each class that is part of the + public Qt API, there exists a header file of + the same name that contains its definition. + * / + \endcode + + QDoc renders this as: + + \quotation + \l + QPushButton is a GUI push button that the user + can press and release. + + \quotefromfile examples/main.cpp + \skipto QApplication + \skipline QApplication + \printline QPushButton + + This line includes the QPushButton class + definition. For each class that is part of the public + Qt API, there exists a header file of the same name + that contains its definition. + \endquotation + + See also \l {skipto-command} {\\skipto}, \l {skipuntil-command} + {\\skipuntil} and \l {dots} {\\dots}. + + \target skipto-command + \section1 \\skipto + + The \\skipto command ignores all the lines from the current + position up to and \e excluding the next line containing a given + substring. + + QDoc reads the file sequentially, and the \\skipto command is used + to move the current position (omitting one or several lines of the + source file). See the remark about \l {file} {file positioning} + above. + + The command considers the rest of the line as part of its + argument, make sure to follow the substring with a line break. + + The command also follows the same conventions for \l {substring} + {argument} as the \l {printline-command} {\\printline} command, + and it is used in conjunction with the \l {quotefromfile-command} + {\\quotefromfile} command. + + \code + / *! + The whole application is contained within + the \c main() function: + + \quotefromfile examples/main.cpp + \skipto main + \printuntil } + + First we create a QApplication object. There + has to be exactly one such object in + every GUI application that uses Qt. Then + we create a QPushButton, resize it to a reasonable + size... + * / + \endcode + + QDoc renders this as: + + \quotation + The whole application is contained within + the \c main() function: + + \quotefromfile examples/main.cpp + \skipto main + \printuntil } + + First we create a QApplication object. There has to be + exactly one such object in every GUI application that + uses Qt. Then we create a QPushButton, resize it to a + reasonable size ... + \endquotation + + See also \l {skipline-command} {\\skipline}, \l + {skipuntil-command} {\\skipuntil} and \l {dots} {\\dots}. + + \target skipuntil-command + \section1 \\skipuntil + + The \\skipuntil command ignores all the lines from the current + position up to and \e including the next line containing a given + substring. + + QDoc reads the file sequentially, and the \\skipuntil command is + used to move the current position (omitting one or several lines + of the source file). See the remark about \l {file} {file + positioning} above. + + The command considers the rest of the line as part of its + argument, make sure to follow the substring with a line break. + + The command also follows the same conventions for \l {substring} + {argument} as the \l {printline-command} {\\printline} command, + and it is used in conjunction with the \l {quotefromfile-command} + {\\quotefromfile} command. + + \code + / *! + The first thing we did in the \c main() function + was to create a QApplication object \c app. + + \quotefromfile examples/main.cpp + \skipuntil show + \dots + \printuntil } + + In the end we must remember to make \c main() pass the + control to Qt. QCoreApplication::exec() will return when + the application exits... + * / + \endcode + + QDoc renders this as: + + \quotation + The first thing we did in the \c main() function was to + create a QApplication object \c app. + + \quotefromfile examples/main.cpp + \skipuntil show + \dots + \printuntil } + + In the end we must remember to make \c main() pass the + control to Qt. QCoreApplication::exec() + will return when the application exits... + \endquotation + + See also \l {skipline-command} {\\skipline}, \l {skipto-command} + {\\skipto} and \l {dots} {\\dots}. + + \target dots-command + \section1 \\dots + + The \\dots command indicates that parts of the source file have + been omitted when quoting a file. + + The command is used in conjunction with the \l + {quotefromfile-command} {\\quotefromfile} command, and should be + stated on its own line. The dots are rendered on a new line, using + a monospace font. + + \code + / *! + \quotefromfile examples/main.cpp + \skipto main + \printuntil { + \dots + \skipuntil exec + \printline } + * / + \endcode + + QDoc renders this as: + + \quotefromfile examples/main.cpp + \skipto main + \printuntil { + \dots + \skipuntil exec + \printline } + + The default indentation is 4 spaces, but this can be adjusted + using the command's optional argument. + + \code + / *! + \dots 0 + \dots + \dots 8 + \dots 12 + \dots 16 + * / + \endcode + + QDoc renders this as: + + \dots 0 + \dots + \dots 8 + \dots 12 + \dots 16 + + See also \l {skipline-command} {\\skipline}, \l {skipto-command} + {\\skipto} and \l {skipuntil-command} {\\skipuntil}. + + \target snippet-command + \section1 \\snippet + + The \\snippet command causes a code snippet to be included + verbatim as preformatted text, which may be syntax highlighted. + + Each code snippet is referenced by the file that holds it and by + a unique identifier for that file. Snippet files are typically + stored in a \c{snippets} directory inside the documentation + directory (for example, \c{$QTDIR/doc/src/snippets}). + + For example, the following documentation references a snippet in a + file residing in a subdirectory of the documentation directory: + + \code + \snippet snippets/textdocument-resources/main.cpp Adding a resource + \endcode + + The text following the file name is the unique identifier for the + snippet. This is used to delimit the quoted code in the relevant + snippet file, as shown in the following example that corresponds to + the above \c{\\snippet} command: + + \dots + \code + QImage image(64, 64, QImage::Format_RGB32); + image.fill(qRgb(255, 160, 128)); + + //! [Adding a resource] + document->addResource(QTextDocument::ImageResource, + QUrl("mydata://image.png"), QVariant(image)); + //! [Adding a resource] + \endcode + \dots + + \target codeline-command + \section1 \\codeline + + The \\codeline command inserts a blank line of preformatted + text. It is used to insert gaps between snippets without closing + the current preformatted text area and opening a new one. + +*/ + + +/*! + \page 08-qdoc-commands-creatinglinks.html + \previouspage Including External Code + \contentspage QDoc Manual + \nextpage Including Images + + \title Creating Links + + These commands are for creating hyperlinks to classes, functions, + examples, and other targets. + + \target l-command + \section1 \\l (link) + + The \\l link command is used to create a hyperlink to many + different kinds of targets. The command's general syntax is: + + \code + \l [ link criteria ] { link target } { link text } + \endcode + + ...where the \c {link criteria} in square brackets are optional + but may be required when the \c {link target} is ambiguous. See + \l {Fixing Ambiguous Links} below. + + Here is an example using the \\l command to link to an external page: + + \code + / *! + Read the \l {http://doc.qt.io/qt-5/} + {Qt 5.0 Documentation} carefully. + * / + \endcode + + QDoc renders this as: + + \quotation + Read the \l {http://doc.qt.io/qt-5/} + {Qt 5.0 Documentation} carefully. + \endquotation + + If the link target is equivalent to the link text, the second + argument can be omitted. + + For example, if you have documentation like: + + \code + / *! + \target assertions + + Assertions make some statement about the text at the + point where they occur in the regexp, but they do not + match any characters. + + ... + + Regexps are built up from expressions, quantifiers, and + \l {assertions} {assertions}. + * / + \endcode + + You can simplify this as follows: + + \code + / *! + \target assertions + + Assertions make some statement about the text at the + point where they occur in the regexp, but they do not + match any characters. + + ... + + Regexps are built up from expressions, quantifiers, and + \l assertions. + * / + \endcode + + For the one-parameter version, the braces can often be omitted. + The \\l command supports several ways of linking: + + \list + + \li \c {\l QWidget} - The name of a class documented with the \l + {class-command} {\\class} command. + + \li \c {\l QWidget::sizeHint()} - The signature of a function without + parameters. If a matching function without parameters can't be found, + the link is satisfied with the first matching function found. + + \li \c {\l QWidget::removeAction(QAction* action)} - The signature + of a function with parameters. If an exact match is not found, the + link is not satisfied and qdoc reports a \e {Can't link to...} error. + + \li \c {\l <QtGlobal>} - The subject of a \l {headerfile-command} + {\\headerfile} command. + + \li \c {\l widgets/wiggly} - The relative path used in an \l + {example-command} {\\example} command. + + \li \c {\l {QWidget Class Reference}} - The title used in a + \l {title-command} {\\title} command. + + \li \c {\l {Introduction to QDoc}}- The text from one of the + \l{part-command} {\\part}, \l{chapter} {\\chapter}, or \l + {sectionOne-command} {\\section} commands. + + \li \c {\l fontmatching} - The argument of a \l {target-command} + {\\target} command. + + \li \c {\l {Shared Classes}} - A keyword named in a \l + {keyword-command} {\\keyword} command. + + \li \c {\l http://qt-project.org/} - A URL. + + \endlist + + QDoc also tries to make a link out of any word that doesn't + resemble a normal English word, for example, Qt class names or + functions, like QWidget or QWidget::sizeHint(). In these cases, + the \\l command can actually be omitted, but by using the command, + you ensure that QDoc will emit a warning if it cannot find the + link target. In addition, if you only want the function name to + appear in the link, you can use the following syntax: + + \list + \li \c {\l {QWidget::} {sizeHint()}} + \endlist + + QDoc renders this as: + + \quotation + \l {QWidget::} {sizeHint()} + \endquotation + + \section2 Fixing Ambiguous Links + + Because of the modularization of Qt beginning with Qt 5.0, The + possibility that qdoc will have to deal with ambiguous links has + increased. An ambiguous link is one that has a matching target in + more than one Qt module, e.g. the same section title can appear in + more than one Qt module, or the name of a C++ class in one module + can also be the name of a QML type in another module. A real + example in Qt5 is the name Qt itself. Qt is the name of both a C++ + namespace in QtCore and a QML type in QtQml. + + Suppose we want to link to the \l {Qt} {Qt C++ namespace}. At the + time qdoc generated this HTML page, that link was correct. Does + it still go to the C++ namespace? Qdoc generated that link from + this link command: + + \list + \li \c {\l {Qt} {Qt C++ namespace}} + \endlist + + Now suppose we want to link to the \l [QML] {Qt} {Qt QML type}. + At the time qdoc generated this HTML page, that link was also + correct, but we had to use this link command: + + \list + \li \c {\l [QML] {Qt} {Qt QML type}} + \endlist + + The \e {QML} in \e {square brackets} tells qdoc to accept a + matching target only if the traget is on a QML page. Qdoc actually + finds the C++ namespace target first, but since that target is on + a C++ page, qdoc ignores it and keeps looking until it finds the + same target on a QML page. + + Without the guidance in the \e{\\l command} in the optional \e + {square bracket} argument, qdoc links to the first matching target + it finds. qdoc can't warn that the link was ambiguous in such + cases because it doesn't know that another matching target exists. + + \section2 What arguments can appear in square brackets? + + A link command with square bracket argument has the following syntax: + \list + \c {\l [QML|CPP|DOC|QtModuleName] {link target} {link text}} + \endlist + + The \e {square bracket} argument is only allowed in the \c {\\l + (link)} command. The example above shows how \c QML is used as the + \e {square brackets} argument to force qdoc to match a QML target. + Most often, this will be a QML type, but it can also be a QML + member function of property. + + In the example, qdoc didn't need a \e {square bracket} argument to + find the Qt C++ namespace page, because that one was the first + matching target qdoc found anyway. However, to force qdoc to find + a C++ target when a matching QML target gets in the way, \c CPP + can be used as the \e {square bracket} argument. For example: + + \list + \li \c {\l [CPP] {Qt} {Qt C++ namespace}} + \endlist + + ...will force qdoc to ignore the Qt QML type and continue + searching until it matches the Qt C++ namespace. + + If the link target is neither a C++ nor a QML entity, \c {DOC} can + be used as the \e {square bracket} argument to prevent qdoc from + matching either of those. At this writing, there were no cases of + ambiguous links where using \c {DOC} was required. + + Often, the documentor knows which Qt module the link target is + in. When the module name is known, use the module name as the \e + {square bracket} argument. In the example above, if we know that + the QML type named Qt is located in the QtQml module, we can write + the link command like this: + + \list + \li \c {\l [QtQml] {Qt} {Qt QML type}} + \endlist + + When a module name is used as the \e {square bracket} argument, + qdoc will search for link the target in that module only. This + makes searching for link targets more efficient. + + Finally, the module name and entity type arguments can be + combined, separated by a blank, so something like this is also + allowed: + + \list + \li \c {\l [CPP QtQml] {Window} {C++ class Window}} + \endlist + + As of this writing, there were no cases where combining the two + was required. + + See also \l {sa-command} {\\sa}, \l {target-command} {\\target}, + and \l {keyword-command} {\\keyword}. + + + \target sa-command + \section1 \\sa (see also) + + The \\sa command defines a list of links that will be rendered in + a separate "See also" section at the bottom of the documentation + unit. + + The command takes a comma-separated list of links as its + argument. If the line ends with a comma, you can continue + the list on the next line. The general syntax is: + + \code + \sa {the first link}, {the second link}, + {the third link}, ... + \endcode + + QDoc will automatically try to generate "See also" links + interconnecting a property's various functions. For example, a + setVisible() function will automatically get a link to visible() + and vice versa. + + In general, QDoc will generate "See also" links that interconnect + the functions that access the same property. It recognizes four + different syntax versions: + + \list + \li \c property() + \li \c setProperty() + \li \c isProperty() + \li \c hasProperty() + \endlist + + The \\sa command supports the same kind of links as the \l + {l-command} {\\l} command. + + \code + / *! + Appends the actions \a actions to this widget's + list of actions. + + \sa removeAction(), QMenu, addAction() + * / + void QWidget::addActions(QList<QAction *> actions) + { + ... + } + \endcode + + QDoc renders this as: + + \quotation + \b {void QWidget::addActions ( QList<QAction*> + \e actions )} + + Appends the actions \e actions to this widget's list of + actions. + + See also \l {QWidget::removeAction()} {removeAction()}, + \l QMenu, and \l {QWidget::addAction()} {addAction()}. + \endquotation + + See also \l {l-command} {\\l}, \l {target-command} {\\target} and + \l {keyword-command} {\\keyword}. + + + \target target-command + \section1 \\target + + The \\target command names a place in the documentation that you + can link to using the \l {l-command} {\\l (link)} and \l + {sa-command} {\\sa (see also)} commands. + + The text up to the line break becomes the target name. Be sure to + follow the target name with a line break. Curly brackets are not + required around the target name, but they may be required when the + target name is used in a link command. See below. + + \code + / *! + \target capturing parentheses + \section1 Capturing Text + + Parentheses allow us to group elements together so that + we can quantify and capture them. + + ... + * / + \endcode + + The target name \e{capturing parentheses} can be linked from + within the same document containing the target in the following way: + + \list + \li \c {\l {capturing parentheses}} (from within the same QDoc comment) + \endlist + + \note The brackets in the link example are required because the + target name contains spaces. + + See also \l {l-command} {\\l}, \l {sa-command} {\\sa} and \l + {keyword-command} {\\keyword}. + + \target keyword-command + \section1 \\keyword + + The \\keyword command names a place in the documentation that you + can link to using the \l {l-command} {\\l (link)} and \l + {sa-command} {\\sa (see also)} commands. + + The \\keyword command is like the \l {target-command} {\\target} + command, except when linking to keyword the link goes to the top of + the QDoc comment where the \\keyword appears in. If you want to + create a link target to a \c section unit within a \\page, use + \\target instead. A keyword can be linked from anywhere using a + simple syntax. + + Keywords must be unique over all the documents processed during + the QDoc run. The command uses the rest of the line as its + argument. Be sure to follow the keyword with a line break. + + + \code + / *! + \class QRegExp + \reentrant + \brief The QRegExp class provides pattern + matching using regular expressions. + \ingroup tools + \ingroup misc + \ingroup shared + + \keyword regular expression + + Regular expressions, or "regexps", provide a way to + find patterns within text. + + ... + * / + \endcode + + The location marked with the keyword can be linked to with: + + \code + / *! + When a string is surrounded by slashes, it is + interpreted as a \l {QRegExp}{regular expression}. + * / + \endcode + + QDoc renders this as: + + \quotation + When a string is surrounded by slashes, it is + interpreted as a \l {regular expression}. + \endquotation + + If the keyword text contains spaces, the brackets are required. + + See also \l {l-command} {\\l (link)}, \l {sa-command} {\\sa (see + also)} and \l {target-command} {\\target}. + +*/ + + +/*! + \page 09-qdoc-commands-includingimages.html + \previouspage Creating Links + \contentspage QDoc Manual + \nextpage Tables and Lists + + \title Including Images + + The graphic commands makes it possible to include images in the + documentation. The images can be rendered as separate paragraphs, + or within running text. + + \target image-command + \section1 \\image + + The \\image command expands to the image specified by its first + argument, and renders it centered as a separate paragraph. + + The command takes two arguments. The first argument is the name of + the image file. The second argument is optional and is a simple + description of the image, equivalent to the HTML alt="" in an image + tag. The description is used for tooltips and for browsers that don't + support images, like the Lynx text browser. + + The remaining text \e{after} the file name is the optional, + description argument. Be sure to follow the file name or the + description with a line break. Curly brackets are required if the + description argument spans multiple lines. + + \code + / *! + Qt is a C++ toolkit for cross-platform GUI application development. + + \image happyguy.jpg "Happy guy" + + Qt provides single-source portability across Microsoft + Windows, OS X, Linux, and all major commercial Unix + variants. It is also available for embedded devices. + * / + \endcode + + QDoc renders this as: + + \quotation + Qt is a C++ toolkit for cross-platform GUI application development. + + \image happyguy.jpg image "Happy guy" + + Qt provides single-source portability across Microsoft + Windows, OS X, Linux, and all major commercial Unix + variants. It is also available for embedded devices. + \endquotation + + See also \l {inlineimage-command} {\\inlineimage} and \l + {caption-command} {\\caption}. + + \target inlineimage-command + \section1 \\inlineimage + + The \\inlineimage command expands to the image specified by its + argument. The image is rendered inline with the rest of the text. + + The command takes two arguments. The first argument is the name of + the image file. The second argument is optional and is a simple + description of the image, equivalent to the HTML alt="" in an image + tag. The description is used for tooltips, and for when a browser + doesn't support images, like the Lynx text browser. + + The most common use of the \\inlineimage command is in lists and + tables. Here is an example of including inline images in a list: + + \code + / *! + \list 1 + \li \inlineimage happy.gif Oh so happy! + \li \inlineimage happy.gif Oh so happy! + \li \inlineimage happy.gif Oh so happy! + \endlist + * / + \endcode + + QDoc renders this as: + + \list 1 + \li \inlineimage happy.gif Oh so happy! + \li \inlineimage happy.gif Oh so happy! + \li \inlineimage happy.gif Oh so happy! + \endlist + + Here is an example of including inline images in a table: + + \code + / *! + \table + \header + \li Qt + \li Qt Creator + \row + \li \inlineimage happy.gif Oh so happy! + \li \inlineimage happy.gif Oh so happy! + \row + \li \inlineimage happy.gif Oh so happy! + \li \inlineimage happy.gif Oh so happy! + \endtable + * / + \endcode + + QDoc renders this as: + + \raw HTML + <table align="center" cellpadding="2" + cellspacing="1" border="0"> + <tr valign="top" bgcolor="#a2c511"> + <th>Qt</th> + <th>Qt Creator</th> + </tr> + <tr valign="top" bgcolor="#f0f0f0"> + <td><img src="images/happy.gif" alt="Oh so happy!" /> + </td> + <td><img src="images/happy.gif" alt="Oh so happy!" /> + </td> + </tr> + <tr valign="top" bgcolor="#f0f0f0"> + <td><img src="images/happy.gif" alt="Oh so happy!"/> + </td> + <td><img src="images/happy.gif" alt="Oh so happy!" /> + </td> + </tr> + </table> + \endraw + + The command can also be used to insert an image inline with the + text. + + \code + / *! + \inlineimage training.jpg Qt Training + The Qt Programming course is offered as a + five day Open Enrollment Course. The classes + are open to the public. Although the course is open + to anyone who wants to learn, attendees should + have significant experience in C++ development + to derive maximum benefit from the course. + * / + \endcode + + QDoc renders this as: + + \quotation + \inlineimage training.jpg Qt Training + The Qt Programming course is offered as a + five day Open Enrollment Course. The classes + are open to the public. Although the course is open + to anyone who wants to learn, attendees should + have significant experience in C++ development + to derive maximum benefit from the course. + \endquotation + + See also \l {image-command} {\\image} and \l {caption-command} {\\caption}. + + \target caption-command + \section1 \\caption + + The \\caption command provides a caption for an image. + + The command takes all the text up to the end of the paragraph to + be the caption. Experiment until you get the effect you want. + + \code + / *! + \table 100% + \row + \li \image windowsvista-pushbutton.png + \caption The QPushButton widget provides a command button. + \li \image windowsvista-toolbutton.png + \caption The QToolButton class provides a quick-access button to commands + or options, usually used inside a QToolBar. + \endtable + * / + \endcode + + QDoc renders this as: + + \table 100% + \row + \li \image windowsvista-pushbutton.png + \caption The QPushButton widget provides a command button. + \li \image windowsvista-toolbutton.png + \caption The QToolButton class provides a quick-access button to commands + or options, usually used inside a QToolBar. + \endtable + + See also \l {image-command} {\\image} and \l {inlineimage-command} + {\\inlineimage} +*/ + + +/*! + \page 10-qdoc-commands-tablesandlists.html + \previouspage Including Images + \contentspage QDoc Manual + \nextpage Special Content + + \title Tables and Lists + + These commands enable creating lists and tables. A list is + rendered left aligned as a separate paragraph. A table is rendered + centered as a separate paragraph. The table width depends on the + width of its contents. + + \target table-command + \section1 \\table + + The \\table and \\endtable commands delimit the contents of a + table. + + The command accepts a single argument specifying the table's width + as a percentage of the page width: + + \code + / *! + \table 100 % + + ... + + \endtable + * / + \endcode + + The code above ensures that the table will fill all available + space. If the table's width is smaller than 100 %, the table will + be centered in the generated documentation. + + A table can contain headers, rows and columns. A row starts with a + \l {row-command} {\\row} command and consists of cells, each of which + starts with an \l {li-command} {\\li} command. There is also a \l + {header-command} {\\header} command which is a special kind of row + that has a special format. + + \code + / *! + \table + \header + \li Qt Core Feature + \li Brief Description + \row + \li \l {Signal and Slots} + \li Signals and slots are used for communication + between objects. + \row + \li \l {Layout Management} + \li The Qt layout system provides a simple + and powerful way of specifying the layout + of child widgets. + \row + \li \l {Drag and Drop} + \li Drag and drop provides a simple visual + mechanism which users can use to transfer + information between and within applications. + \endtable + * / + \endcode + + QDoc renders this as: + + \raw HTML + <table align="center" cellpadding="2" + cellspacing="1" border="0"> + <tr valign="top" bgcolor="#a2c511"> + <th>Qt Core Feature</th> + <th>Brief Description</th> + </tr> + + <tr valign="top" bgcolor="#d0d0d0"> + <td> + <a href="http://doc.qt.io/qt-5/signalsandslots.html"> + Signals and Slots</a> + </td> + <td>Signals and slots are used for communication + between objects.</td> + </tr> + + <tr valign="top" bgcolor="#c0c0c0"> + <td> + <a href="http://doc.qt.io/qt-5/layout.html"> + Layout Management</a></td> + <td>The Qt layout system provides a simple + and powerful way of specifying the layout + of child widgets.</td> + </tr> + + <tr valign="top" bgcolor="#d0d0d0"> + <td> + <a href="http://doc.qt.io/qt-5/dnd.html"> + Drag and Drop</a></td> + <td>Drag and drop provides a simple visual + mechanism which users can use to transfer + information between and within applications.</td> + </tr> + + </table> + \endraw + + You can also make cells span several rows and columns. For + example: + + \code + / *! + \table + \header + \li {3,1} This header cell spans three columns, + but only one row. + \row + \li {2, 1} This table cell spans two columns, + but only one row + \li {1, 2} This table cell spans only one column, + but two rows. + \row + \li A regular table cell + \li A regular table cell + \endtable + * / + \endcode + + QDoc renders this as: + + \raw HTML + <table align="center" cellpadding="2" cellspacing="1" + border="0"> + + <tr valign="top" bgcolor="#a2c511"> + <th colspan="3" rowspan=" 1"> + This header cell spans three columns, but only one row. + </th> + </tr> + + <tr valign="top" bgcolor="#d0d0d0"> + <td colspan="2" rowspan=" 1"> + This table cell spans two columns, but only one row. + </td> + <td rowspan=" 2"> + This table cell spans only one column, but two rows. + </td> + </tr> + + <tr valign="top" bgcolor="#c0c0c0"> + <td>A regular table cell</td> + <td>A regular table cell</td> + </tr> + + </table> + \endraw + + See also \l {header-command} {\\header}, \l {row-command} {\\row} and \l {li-command} {\\li}. + + \target header-command + \section1 \\header + + The \\header command indicates that the following table cells are + the current table's column headers. + + The command can only be used within the \l{table-command} + {\\table...\\endtable} commands. A header can contain several + cells. A cell is created with the \l {li-command} {\\li} command. + + A header cell's text is centered within the table cell and + rendered using a bold font. + + \code + / *! + \table + \header + \li Qt Core Feature + \li Brief Description + \row + \li \l {Signal and Slots} + \li Signals and slots are used for communication + between objects. + \endtable + * / + \endcode + + QDoc renders this as: + + \raw HTML + <table align="center" cellpadding="2" + cellspacing="1" border="0"> + <tr valign="top" bgcolor="#a2c511"> + <th>Qt Core Feature</th> + <th>Brief Description</th> + </tr> + + <tr valign="top" bgcolor="#d0d0d0"> + <td> + <a href="http://doc.qt.io/qt-5/signalsandslots.html"> + Signals and Slots</a> + </td> + <td>Signals and slots are used for communication + between objects.</td> + </tr> + </table> + \endraw + + See also \l {table-command} {\\table}, \l {row-command} {\\row} and \l {li-command} {\\li}. + + \target row-command + \section1 \\row + + The \\row command begins a new row in a table. The \l {li-command} + {\\li items} that belong in the new row will immediately follow the + \\row. + + The command can only be used within the \l{table-command} + {\\table...\\endtable} commands. A row can contain several + cells. A cell is created with the \l {li-command} {\\li} command. + + The background cell color of each row alternates between two + shades of grey, making it easier to distinguish the rows from each + other. The cells' contents is left aligned. + + \code + / *! + \table + \header + \li Qt Core Feature + \li Brief Description + \row + \li \l {Signal and Slots} + \li Signals and slots are used for communication + between objects. + \row + \li \l {Layout Management} + \li The Qt layout system provides a simple + and powerful way of specifying the layout + of child widgets. + \row + \li \l {Drag and Drop} + \li Drag and drop provides a simple visual + mechanism which users can use to transfer + information between and within applications. + \endtable + * / + \endcode + + QDoc renders this as: + + \raw HTML + <table align="center" cellpadding="2" + cellspacing="1" border="0"> + <tr valign="top" bgcolor="#a2c511"> + <th>Qt Core Feature</th> + <th>Brief Description</th> + </tr> + + <tr valign="top" bgcolor="#d0d0d0"> + <td> + <a href="http://doc.qt.io/qt-5/signalsandslots.html"> + Signals and Slots</a> + </td> + <td>Signals and slots are used for communication + between objects.</td> + </tr> + + <tr valign="top" bgcolor="#c0c0c0"> + <td> + <a href="http://doc.qt.io/qt-5/layout.html"> + Layout Management</a></td> + <td>The Qt layout system provides a simple + and powerful way of specifying the layout + of child widgets.</td> + </tr> + + <tr valign="top" bgcolor="#d0d0d0"> + <td> + <a href="http://doc.qt.io/qt-5/dnd.html"> + Drag and Drop</a></td> + <td>Drag and drop provides a simple visual + mechanism which users can use to transfer + information between and within applications.</td> + </tr> + + </table> + \endraw + + See also \l {table-command} {\\table}, \l {header-command} + {\\header}, and \l {li-command} {\\li}. + + \target value-command + \section1 \\value + + The \\value command starts the documentation of a C++ enum item. + + The command's first argument is the enum item. Then follows its + associated description. The description argument ends at the next + blank line or \\value. The arguments are rendered within a table. + + The documentation will be located in the associated class, header + file or namespace documentation. See the \l {enum-command} + {\\enum} documentation for an example. + + \note Since Qt 5.4, \\value command can also be used outside the + \l {enum-command} {\\enum} topic. In this case, QDoc renders a + two-column table listing the constant name (taken as-is from the + first argument) and its description. This can be used, for + example, in \l {qmlproperty-command}{\\qmlproperty} topic for + documenting acceptable values for a QML enumeration property. + + See also \l {enum-command} {\\enum} and \l {omitvalue-command} {\\omitvalue}. + + \target omitvalue-command + \section1 \\omitvalue + + The \\omitvalue command excludes a C++ enum item from the + documentation. + + The command's only argument is the name of the enum item that will + be omitted. See the \l {enum-command} {\\enum} documentation for + an example. + + See also \l {enum-command} {\\enum} and \l {value-command} + {\\value}. + + \target list-command + \section1 \\list + + The \\list and \\endlist commands delimit a list of items. + + Create each list item with the \l {li-command} {\\li} command. A + list always contains one or more items. Lists can be nested. For + example: + + \code + / *! + \list + \li Qt Reference Documentation: Getting Started + \list + \li How to Learn Qt + \li Installation + \list + \li Qt/X11 + \li Qt/Windows + \li Qt/Mac + \li Qt/Embedded + \endlist + \li Tutorial and Examples + \endlist + \endlist + * / + \endcode + + QDoc renders this as: + + \list + \li Qt Reference Documentation: Getting Started + \list + \li How to Learn Qt + \li Installation + \list + \li Qt/X11 + \li Qt/Windows + \li Qt/Mac + \li Qt/Embedded + \endlist + \li Tutorial and Examples + \endlist + \endlist + + The \\list command takes an optional argument providing + alternative appearances for the list items. + + \code + / *! + \list + \li How to Learn Qt + \li Installation + \li Tutorial and Examples + \endlist + * / + \endcode + + QDoc renders the list items with bullets (the default): + + \list + \li How to Learn Qt + \li Installation + \li Tutorial and Examples + \endlist + + \warning There appears to be a bug in qdoc here. If you include + any of the argument types, you get a numeric list. We're looking + into it. + + If you provide 'A' as an argument to the \\list command, the + bullets are replaced with characters in alphabetical order: + + \list A + \li How to Learn Qt + \li Installation + \li Tutorial and Examples + \endlist + + If you replace 'A' with '1', the list items are numbered in + ascending order: + + \list 1 + \li How to Learn Qt + \li Installation + \li Tutorial and Examples + + \endlist + + If you provide 'i' as the argument, the bullets are replaced with + roman numerals: + + \list i + \li How to Learn Qt + \li Installation + \li Tutorial and Examples + \endlist + + Finally, you can make the list items appear with roman numbers + following in ascending order if you provide 'I' as the optional + argument: + + \list I + \li How to Learn Qt + \li Installation + \li Tutorial and Examples + \endlist + + You can also make the listing start at any character or number by + simply provide the number or character you want to start at. For + example: + + \code + / *! + \list G + \li How to Learn Qt + \li Installation + \li Tutorial and Examples + \endlist + * / + \endcode + + \note This doesn't work in DITA XML, so don't use it because it + produces a DITA XML file that doesn't validate. There probably is + a way to do this in DITA, so if we figure it out, we will put it + in. But this capability is not used anywhere other than right + here, so it probably isn't important. For now, if you use this + option, qdoc will ignore it and produce a list without it. + + QDoc renders this as: + + \list G + \li How to Learn Qt + \li Installation + \li Tutorial and Examples + \endlist + + See also \l {li-command} {\\li}. + + \target li-command + \section1 \\li (table cell, list item) + + The \\li command marks a table cell or a list item. This command + is only used in \l{table-command} {tables} and \l{list-command} + {lists}. + + It considers everything as its argument until the next \\li command, until the + next \l {table-command} {\\endtable}, or \l {list-command} {\\endlist} + command. See \l {table-command} {\\table} and \l {list-command} {\\list} + for examples. + + If the command is used within a table, you can also specify + how many rows or columns the item should span. + + \code + / *! + \table + \header + \li {3,1} This header cell spans three columns + but only one row. + \row + \li {2, 1} This table item spans two columns + but only one row + \li {1, 2} This table item spans only one column, + but two rows. + \row + \li A regular table item + \li A regular table item + \endtable + * / + \endcode + + QDoc renders this as: + + \raw HTML + <table align="center" cellpadding="2" cellspacing="1" + border="0"> + + <tr valign="top" bgcolor="#a2c511"> + <th colspan="3" rowspan=" 1"> + This header cell spans three columns, but only one row. + </th> + </tr> + + <tr valign="top" bgcolor="#d0d0d0"> + <td colspan="2" rowspan=" 1"> + This table item spans two columns, but only one row. + </td> + <td rowspan=" 2"> + This table item spans only one column, but two rows. + </td> + </tr> + + <tr valign="top" bgcolor="#c0c0c0"> + <td>A regular table item</td> + <td>A regular table item</td> + </tr> + + </table> + \endraw + + If not specified, the item will span one column and one row. + + See also \l {table-command} {\\table}, \l {header-command} + {\\header}, and \l {list-command} {\\list}. + +*/ + + +/*! + \page 11-qdoc-commands-specialcontent.html + \previouspage Tables and Lists + \contentspage QDoc Manual + \nextpage Miscellaneous + + \title Special Content + + The document contents commands identify parts of the documentation, + parts with a special rendering, conceptual meaning or + function. + + \target quotation-command + \section1 \\quotation + + The \\quotation and \\endquotation commands delimit a long quotation. + + The text in the delimited block is surrounded by + \b{<blockquote>} and \b{</blockquote>} in the html output, + e.g.: + + \code + / *! + Although the prospect of a significantly broader market is + good news for Firstlogic, the notion also posed some + challenges. Dave Dobson, director of technology for the La + Crosse, Wisconsin-based company, said: + + \quotation + As our solutions were being adopted into new + environments, we saw an escalating need for easier + integration with a wider range of enterprise + applications. + \endquotation + * / + \endcode + + The text in the \b{\\quotation} block will appear in the generated HTML as: + + \code + <blockquote> + <p>As our solutions were being adopted into new environments, + we saw an escalating need for easier integration with a wider + range of enterprise applications.</p> + </blockquote> + \endcode + + The built-in style sheet for most browsers will render the + contents of the <blockquote> tag with left and right + indentations. The example above would be rendered as: + + \quotation + As our solutions were being adopted into new + environments, we saw an escalating need for easier + integration with a wider range of enterprise + applications. + \endquotation + + But you can redefine the \b{<blockquote>} tag in your style.css file. + + \target footnote-command + \section1 \\footnote + + The \\footnote and \\endfootnote commands delimit a footnote. + + The footnote is rendered at the bottom of the page. + + \warning The \b{\\footnote} and \b{\\endfootnote} commands + have not been implemented. The footnote is rendered as a regular + HTML paragraph. + + \target note-command + \section1 \\note + + The \\note command defines a new paragraph preceded by "Note:" + in bold. + + \target tableofcontents-command + \section1 \\tableofcontents + + The \\tableofcontents command has been disabled because QDoc + now generates a table of contents automatically. + + The automatically generated table of contents appears in the upper + righthand corner of the page. + + \target brief-command + \section1 \\brief + + The \\brief command introduces a one-sentence description of a + class, namespace, header file, property, or variable. + + The brief text is used to introduce the documentation of the + associated object, and in lists generated using the \l + {generatelist-command} {\\generatelist} command and the \l + {annotatedlist-command} {\\annotatedlist} command. + + The \\brief command can be used in two significant different ways: + \l {brief class} {One for classes, namespaces and header files}, + and \l {brief-property} {one for properties and variables}. + + \target brief-property + + When the \\brief command is used to describe a property or a + variable, the brief text must be a sentence fragment starting with + "whether" (for a boolean property or variable) or starting with + "the" (for any other property or variable). + + For example the boolean QWidget::isWindow property: + + \code + / *! + \property QWidget::isActiveWindow + \brief Whether this widget's window is the active window + + The active window is the window that contains the widget that + has keyboard focus. + + When popup windows are visible, this property is \c true + for both the active window \e and the popup. + + \sa activateWindow(), QApplication::activeWindow() + * / + \endcode + + and the QWidget::geometry property + + \code + / *! + \property QWidget::geometry + \brief The geometry of the widget relative to its parent and + excluding the window frame + + When changing the geometry, the widget, if visible, + receives a move event (moveEvent()) and/or a resize + event (resizeEvent()) immediately. + + ... + + \sa frameGeometry(), rect(), ... + * / + \endcode + + QDoc renders this as: + + \quotation + \raw HTML + <h3>geometry : + <a href="http://doc.qt.io/qt-5/qrect.html">QRect</a> + </h3> + \endraw + + This property holds the geometry of the widget relative + to its parent and excluding the window frame. + + ... + + Access functions: + \list + \li \b {const QRect & geometry () const} + \li \b {void setGeometry ( int x, int y, int w, int h )} + \li \b {void setGeometry ( const QRect & )} + \endlist + + See also \l + {QWidget::frameGeometry()} {frameGeometry()}, \l + {QWidget::rect()} {rect()}, ... + \endquotation + + \target brief class + + When the \\brief command is used to describe a class, we recommend + using a complete sentence like this: + + \code + The <classname> class is|provides|contains|specifies... + \endcode + + \warning Do not repeat your detailed description with the same sentence as + the brief statement will be the first paragraph of the detailed + description. + + \code + / *! + \class PreviewWindow + \brief The PreviewWindow class is a custom widget + displaying the names of its currently set + window flags in a read-only text editor. + + The PreviewWindow class inherits QWidget. The widget + displays the names of its window flags set with the + setWindowFlags() function. It is also provided with a + QPushButton that closes the window. + + ... + + \sa QWidget + * / + \endcode + + QDoc renders this as: + + \quotation + \raw HTML + <h1>PreviewWindow Class Reference</h1> + \endraw + + The PreviewWindow class is a custom widget displaying + the names of its currently set window flags in a + read-only text editor. \l {preview window} {More...} + + \raw HTML + <h3>Properties</h3> + \endraw + + \list + \li 52 properties inherited from QWidget + \li 1 property inherited from QObject + \endlist + + \raw HTML + <h3>Public Functions</h3> + \endraw + + \list + \li \l {constructor} {PreviewWindow}(QWidget *parent = 0) + \li void \l {function} {setWindowFlags}(Qt::WindowFlags flags) + \endlist + + \list + \li 183 public functions inherited from QWidget + \li 28 public functions inherited from QObject + \endlist + + \raw HTML + <h3>Public Slots</h3> + \endraw + + \list + \li 17 public slots inherited from QWidget + \li 1 public slot inherited from QObject + \endlist + + \raw HTML + <h3>Additional Inherited Members</h3> + \endraw + + \list + \li 1 signal inherited from QWidget + \li 1 signal inherited from QObject + \li 4 static public members inherited from QWidget + \li 4 static public members inherited from QObject + \li 39 protected functions inherited from QWidget + \li 7 protected functions inherited from QObject + \endlist + + \target preview window + + \raw HTML + <hr /> + <h2>Detailed Description</h2> + \endraw + + The PreviewWindow class is a custom widget displaying + the names of its currently set window flags in a + read-only text editor. + + The PreviewWindow class inherits QWidget. The widget + displays the names of its window flags set with the \l + {function} {setWindowFlags()} function. It is also + provided with a QPushButton that closes the window. + + ... + + See also QWidget. + + \raw HTML + <hr /> + <h2>Member Function Documentation</h2> + \endraw + + \target constructor + \raw HTML + <h3>PreviewWindow(QWidget *parent = 0)</h3> + \endraw + + Constructs a preview window widget with \e parent. + + \target function + \raw HTML + <h3>setWindowFlags(Qt::WindowFlags flags)</h3> + \endraw + + Sets the widgets flags using the + QWidget::setWindowFlags() function. + + Then runs through the available window flags, + creating a text that contains the names of the flags + that matches the flags parameter, displaying + the text in the widgets text editor. + \endquotation + + Using \\brief in a \l{namespace-command}{\\namespace}: + + \code + / *! + \namespace Qt + + \brief The Qt namespace contains miscellaneous identifiers + used throughout the Qt library. + * / + \endcode + + Using \\brief in a \l{headerfile-command}{\\headerfile}: + + \code + / *! + \headerfile <QtGlobal> + \title Global Qt Declarations + + \brief The <QtGlobal> header file provides basic + declarations and is included by all other Qt headers. + + \sa <QtAlgorithms> + * / + \endcode + + See also \l{property-command} {\\property}, \l{class-command} + {\\class}, \l{namespace-command} {\\namespace} and + \l{headerfile-command} {\\headerfile}. + + \target legalese-command + \section1 \\legalese + + The \\legalese and \\endlegalese commands delimit a license agreement. + + In the generated HTML, the delimited text is surrounded by a \b + {<div class="LegaleseLeft">} and \b {</div>} tags. + + An example of a license agreement enclosed in \\legalese + and \\endlegalese: + + \code + / *! + \legalese + Copyright 1996 Daniel Dardailler. + + Permission to use, copy, modify, distribute, and sell this + software for any purpose is hereby granted without fee, + provided that the above copyright notice appear in all + copies and that both that copyright notice and this + permission notice appear in supporting documentation, and + that the name of Daniel Dardailler not be used in + advertising or publicity pertaining to distribution of the + software without specific, written prior permission. Daniel + Dardailler makes no representations about the suitability of + this software for any purpose. It is provided "as is" + without express or implied warranty. + + Modifications Copyright 1999 Matt Koss, under the same + license as above. + \endlegalese + * / + \endcode + + It will appear in the generated HTML as: + + \code + <div class="LegaleseLeft"> + <p>Copyright 1996 Daniel Dardailler.</p> + <p>Permission to use, copy, modify, distribute, and sell + this software for any purpose is hereby granted without fee, + provided that the above copyright notice appear in all + copies and that both that copyright notice and this + permission notice appear in supporting documentation, and + that the name of Daniel Dardailler not be used in + advertising or publicity pertaining to distribution of the + software without specific, written prior permission. Daniel + Dardailler makes no representations about the suitability of + this software for any purpose. It is provided "as is" + without express or implied warranty.</p> + + <p>Modifications Copyright 1999 Matt Koss, under the same + license as above.</p> + </div> + \endcode + + If the \\endlegalese command is omitted, QDoc will process the + \\legalese command but considers the rest of the documentation + page as the license agreement. + + Ideally, the license text is located with the licensed code. + + Elsewhere, the documentation identified as \e{\\legalese} command + can be accumulated using \l {generatelist-command} {\\generatelist} + with \c {legalese-command} as the argument. This is useful for + generating an overview of the license agreements associated with + the source code. + + \target warning-command + \section1 \\warning + + The \\warning command prepends "Warning:" to the command's + argument, in bold font. + + \code + / *! + Qt::HANDLE is a platform-specific handle type + for system objects. This is equivalent to + \c{void *} on Windows and OS X, and to + \c{unsigned long} on X11. + + \warning Using this type is not portable. + * / + \endcode + + QDoc renders this as: + + \quotation + Qt::HANDLE is a platform-specific handle type + for system objects. This is equivalent to + \c{void *} on Windows and OS X, and to + \c{unsigned long} on X11. + + \warning Using this type is not portable. + \endquotation + +*/ + + +/*! + \page 12-0-qdoc-commands-miscellaneous.html + \previouspage Special Content + \contentspage QDoc Manual + \nextpage Creating DITA Maps + + \title Miscellaneous + + These commands provide miscellaneous functions connected to the + visual appearance of the documentation, and to the process of + generating the documentation. + + \target annotatedlist-command + \section1 \\annotatedlist + + The \\annotatedlist command expands to a list of the members of a + group, each member listed with its \e {brief} text. Below is an + example from the Qt Reference Documentation: + + \code + / *! + ... + \section1 Drag and Drop Classes + + These classes deal with drag and drop and the necessary mime type + encoding and decoding. + + \annotatedlist draganddrop + + * / + \endcode + + This generates a list of all the C++ classes and/or QML types in + the \e{draganddrop} group. A C++ class or QML type in the + \e{draganddrop} group will have \e{\\ingroup draganddrop} in its + \e{\\class} or \e{\\qmltype} comment. + + + \target generatelist-command + \section1 \\generatelist + + The \\generatelist command expands to a list of links to the + documentation entities in a group. Below is an example from the Qt + Reference Documentation: + + \code + / *! + \page classes.html + \title All Classes + + For a shorter list that only includes the most + frequently used classes, see \l{Qt's Main Classes}. + + \generatelist classes Q + * / + \endcode + + This generates the \e {All Classes} page. The command accepts the + following arguments: + + \target table example + \section2 \c annotatedclasses + + The \c annotatedclasses argument provides a table containing the + names of all the classes, and a description of each class. Each + class name is a link to the class's reference documentation. For + example: + + \table + \row + \li QDial + \li Rounded range control (like a speedometer or potentiometer) + \row + \li QDialog + \li The base class of dialog windows + \row + \li QDir + \li Access to directory structures and their contents + \endtable + + A C++ class is documented with the \l {class-command} {\\class} + command. The annotation for the class is taken from the argument + of the class comment's \l {brief-command} {\\brief} command. + + \target list example + \section2 \c {classes <prefix>} + + The \c classes argument provides a complete alphabetical list of + the classes. The second argument, \c{<prefix>}, is the common + prefix for the class names. The class names will be sorted on the + character that follows the common prefix. e.g. The common prefix + for the Qt classes is \c Q. The common prefix argument is + optional. If no common prefix is provided, the class names will + be sorted on their first character. + + Each class name becomes a link to the class's reference + documentation. This command is used to generate the + \e {All Classes} page this way: + + \code + / *! + \page classes.html + \title All Classes + \ingroup classlists + + \brief Alphabetical list of classes. + + This is a list of all Qt classes. For a list of the classes + provided for compatibility with Qt3, see \l{Qt3 Support + Classes}. For classes that have been deprecated, see the + \l{Obsolete Classes} list. + + \generatelist classes Q + * / + \endcode + + A C++ class is documented with the \l {class-command} {\\class} + command. + + \section2 \c classesbymodule + + When this argument is used, a second argument is required, which + specifies the module whose classes are to be listed. QDoc + generates a table containing those classes. Each class is listed + with the text of its \l{brief-command} {\\brief} command. + + For example, this command can be used on a module page as follows: + + \code + / *! + \page phonon-module.html + \module Phonon + \title Phonon Module + \ingroup modules + + \brief Contains namespaces and classes for multimedia functionality. + + \generatelist{classesbymodule Phonon} + + ... + + * / + \endcode + + Each class that is a member of the specified module must be marked + with the \l {inmodule-command} {\\inmodule} command in its \\class + comment. + + \section2 \c qmltypesbymodule + + Similar to \c classesbymodule argument, but used for listing the + QML types from the QML module specified with the second argument. + + \note Support for this argument was introduced in QDoc 5.6. + + \section2 \c jstypesbymodule + + Similar to \c classesbymodule argument, but used for listing the + JavaScript types from the module specified with the second argument. + + \note Support for this argument was introduced in QDoc 5.6. + + \section2 \c compatclasses + + The \c compatclasses argument generates a list in alphabetical + order of the support classes. It is normally used only to + generate the Qt3 Support Classes page this way: + + \code + / *! + \page compatclasses.html + \title Qt3 Support Classes + \ingroup classlists + + \brief Enable porting of code from Qt 3 to Qt 4. + + These are the classes that Qt provides for compatibility with Qt + 3. Most of these are provided by the Qt3Support module. + + \generatelist compatclasses + * / + \endcode + + A support class is identified in the \\class comment with the \l + {compat-command} {\\compat} command. + + \section2 \c functionindex + + The \c functionindex argument provides a complete alphabetical + list of all the documented member functions. It is normally used + only to generate the \e {Qt function index} page + this way: + + \code + / *! + \page functions.html + \title All Functions + \ingroup funclists + + \brief All documented Qt functions listed alphabetically with a + link to where each one is declared. + + This is the list of all documented member functions and global + functions in the Qt API. Each function has a link to the + class or header file where it is declared and documented. + + \generatelist functionindex + * / + \endcode + + \section2 \c legalese + + The \c legalese argument tells QDoc to generate a complete list of + licenses in the documentation. Each license is identified using + the \l {legalese-command} {\\legalese} command. This command is + used to generate the \e {Qt license information} + page this way: + + \code + / *! + \page licenses.html + \title Other Licenses Used in Qt + \ingroup licensing + \brief Information about other licenses used for Qt components and third-party code. + + Qt contains some code that is not provided under the + \l{GNU General Public License (GPL)}, + \l{GNU Lesser General Public License (LGPL)} or the + \l{Qt Commercial Edition}{Qt Commercial License Agreement}, but rather under + specific licenses from the original authors. Some pieces of code were developed + by The Qt Company and others originated from third parties. + This page lists the licenses used, names the authors, and links + to the places where it is used. + + The Qt Company gratefully acknowledges these and other contributions + to Qt. We recommend that programs that use Qt also acknowledge + these contributions, and quote these license statements in an + appendix to the documentation. + + See also: \l{Licenses for Fonts Used in Qt for Embedded Linux} + + \generatelist legalese + * / + \endcode + + \section2 \c overviews + + The \c overviews argument is used to tell QDoc to generate a list + by concatenating the contents of all the \l {group-command} + {\\group} pages. Qt uses it to generate the \e {overviews} page + this way: + + \code + / *! + \page overviews.html + + \title All Overviews and HOWTOs + + \generatelist overviews + * / + \endcode + + \section2 \c related + + The \c related argument is used in combination with the \l + {group-command} {\\group} and \l {ingroup-command} {\\ingroup} + commands to list all the overviews related to a specified + group. For example, the page for the \e {Programming with Qt} + page is generated this way: + + \code + / *! + \group qt-basic-concepts + \title Programming with Qt + + \brief The basic architecture of the Qt cross-platform application and UI framework. + + Qt is a cross-platform application and UI framework for + writing web-enabled applications for desktop, mobile, and + embedded operating systems. This page contains links to + articles and overviews explaining key components and + techniuqes used in Qt development. + + \generatelist {related} + * / + \endcode + + Each page listed on this group page contains the command: + + \code + \ingroup qt-basic-concepts + \endcode + + \target if-command + \section1 \\if + + The \\if command and the corresponding \\endif command + enclose parts of a QDoc comment that only will be included if + the condition specified by the command's argument is true. + + The command reads the rest of the line and parses it as an C++ #if + statement. + + \code + / *! + \if defined(opensourceedition) + + \b{Note:} This edition is for the development of + \l{Qt Open Source Edition} {Free and Open Source} + software only; see \l{Qt Commercial Editions}. + + \endif + * / + \endcode + + This QDoc comment will only be rendered if the \c + opensourceedition preprocessor symbol is defined, and specified in + the \l {defines-variable} {defines} variable in the configuration + file to make QDoc process the code within #ifdef and #endif: + + \code + defines = opensourceedition + \endcode + + You can also define the preprocessor symbol manually on the + command line. For more information see the documentation of the \l + {defines-variable} {defines} variable. + + See also \l{endif-command} {\\endif}, \l{else-command} {\\else}, + \l {defines-variable} {defines} and \l {falsehoods-variable} + {falsehoods}. + + \target endif-command + \section1 \\endif + + The \\endif command and the corresponding \\if command + enclose parts of a QDoc comment that will be included if + the condition specified by the \l {if-command} {\\if} command's + argument is true. + + For more information, see the documentation of the \l {if-command} + {\\if} command. + + See also \l{if-command} {\\if}, \l{else-command} {\\else}, \l + {defines-variable} {defines} and \l {falsehoods-variable} + {falsehoods}. + + \target else-command + \section1 \\else + + The \\else command specifies an alternative if the + condition in the \l {if-command} {\\if} command is false. + + The \\else command can only be used within \l {if-command} + {\\if...\\endif} commands, but is useful when there is only two + alternatives. + + \code + / *! + The Qt 3 support library is provided to keep old + source code working. + + In addition to the \c Qt3Support classes, Qt 4 provides + compatibility functions when it's possible for an old + API to cohabit with the new one. + + \if !defined(QT3_SUPPORT) + \if defined(QT3_SUPPORTWARNINGS) + The compiler emits a warning when a + compatibility function is called. (This works + only with GCC 3.2+ and MSVC 7.) + \else + To use the Qt 3 support library, you need to + have the line QT += qt3support in your .pro + file (qmake automatically define the + QT3_SUPPORT symbol, turning on compatibility + function support). + + You can also define the symbol manually (for example, + if you don't want to link against the \c + Qt3Support library), or you can define \c + QT3_SUPPORT_WARNINGS instead, telling the + compiler to emit a warning when a compatibility + function is called. (This works only with GCC + 3.2+ and MSVC 7.) + \endif + \endif + * / + \endcode + + If the \c QT3_SUPPORT is defined, the comment will be rendered + like this: + + \quotation + The Qt 3 support library is provided to keep old source + code working. + + In addition to the Qt3Support classes, Qt 4 provides + compatibility functions when it's possible for an old + API to cohabit with the new one. + \endquotation + + If \c QT3_SUPPORT is not defined but \c QT3_SUPPORT_WARNINGS is + defined, the comment will be rendered like this: + + \quotation + The Qt 3 support library is provided to keep old source + code working. + + In addition to the Qt3Support classes, Qt 4 provides + compatibility functions when it's possible for an old + API to cohabit with the new one. + + The compiler emits a warning when a compatibility + function is called. (This works only with GCC 3.2+ and + MSVC 7.) + \endquotation + + If none of the symbols are defined, the comment will be + rendered as + + \quotation + The Qt 3 support library is provided to keep old + source code working. + + In addition to the \c Qt3Support classes, Qt 4 provides + compatibility functions when it's possible for an old + API to cohabit with the new one. + + To use the Qt 3 support library, you need to have the + line QT += qt3support in your .pro file (qmake + automatically define the QT3_SUPPORT symbol, turning on + compatibility function support). + + You can also define the symbol manually (e.g., if you + don't want to link against the \c Qt3Support library), + or you can define \c QT3_SUPPORT_WARNINGS instead, + telling the compiler to emit a warning when a + compatibility function is called. (This works only with + GCC 3.2+ and MSVC 7.) + \endquotation + + See also \l{if-command} {\\if}, \l{endif-command} {\\endif}, \l + {defines-variable} {defines} and \l {falsehoods-variable} + {falsehoods}. + + \target include-command + \section1 \\include + + The \\include command sends all or part of the file specified by + its first argument to the QDoc input stream to be processed as a + QDoc comment snippet. This command is often assigned the alias, + \e {input}, in the QDoc configuration file, for example \e {alias.include + = input}. + + The command is useful when some snippet of commands and text is to + be used in multiple places in the documentation. In that case, + move the snippet into a separate file and use the \\include + command wherever you want to insert the snippet into the + documentation. To prevent QDoc from reading the file as a + stand-alone page of documentation, we recommend that you use the + \c .qdocinc extension for these \e {include} files. + + The command can have either one or two arguments. The first + argument is always a file name. The contents of the file must be + QDoc input, in other words, a sequence of QDoc commands and text, but + without the enclosing QDoc comment \c{/}\c{*!} ... \c{*}\c{/} delimiters. + If you want to include the entire named file, don't use the second + argument. If you want to include only part of the file, see the + \l{2-argument-form}{two argument form} below. Here is an example + of the one argument form: + + \code + / *! + \page corefeatures.html + \title Core Features + + \include examples/signalandslots.qdocinc + \include examples/objectmodel.qdocinc + \include examples/layoutmanagement.qdocinc + * / + \endcode + + QDoc renders this page \l{corefeatures.html} {as shown here}. + + \target 2-argument-form} + \section2 \\include filename snippet-identifier + + It is a waste of time to make a separate \c .qdocinc file for every + QDoc include snippet you want to use in multiple places in the + documentation, especially given that you probably have to put the + copyright/license notice in every one of these files. So if you + have a large number of snippets to be included, you can put them all in a + single file if you want, and surround each one with: + \code + //! [snippet-id1] + + QDoc commands and text... + + //! [snippet-id1] + + //! [snippet-id2] + + More QDoc commands and text... + + //! [snippet-id2] + \endcode + + Then you can use the two-argument form of the command: + + \code + \input examples/signalandslots.qdocinc snippet-id2 + \input examples/objectmodel.qdocinc another-snippet-id + \endcode + + It works as expected. The sequence of QDoc commands and text found + between the two tags with the same name as the second argument is + sent to the QDoc input stream. You can even nest these snippets, + although it's not clear why you would want to do that. + + \target meta-command + \section1 \\meta + + The \\meta command is mainly used for including metadata in DITA + XML files. It is also used when generating HTML output for specifying + the \e maintainer(s) of a C++ class. + + The command has two arguments: the first argument is the name of the + metadata attribute, and the second argument is the + value for the attribute. Each argument should be enclosed in curly + brackets, as shown in this example: + + \code + / *! + \class QWidget + \brief The QWidget class is the base class of all user interface objects. + + \ingroup basicwidgets + + \meta {technology} {User Interface} + \meta {platform} {OS X 10.6} + \meta {platform} {Symbian} + \meta {platform} {MeeGo} + \meta {audience} {user} + \meta {audience} {programmer} + \meta {audience} {designer} + * / + \endcode + + When running QDoc to generate HTML, the example above will have no + effect on the generated output, but if you run QDoc to generate + DITA XML, the example will generate the following: + + \code + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE cxxClass PUBLIC "-//NOKIA//DTD DITA C++ API Class Reference Type v0.6.0//EN" "dtd/cxxClass.dtd"> + <!--qwidget.cpp--> + <cxxClass id="id-9a14268e-6b09-4eee-b940-21a00a0961df"> + <apiName>QWidget</apiName> + <shortdesc>the QWidget class is the base class of all user interface objects.</shortdesc> + <prolog> + <author>Qt Development Frameworks</author> + <publisher>Qt Project</publisher> + <copyright> + <copyryear year="2015"/> + <copyrholder>Qt Project</copyrholder> + </copyright> + <permissions view="all"/> + <metadata> + <audience type="designer"/> + <audience type="programmer"/> + <audience type="user"/> + <category>Class reference</category> + <prodinfo> + <prodname>Qt Reference Documentation</prodname> + <vrmlist> + <vrm version="4" release="7" modification="3"/> + </vrmlist> + <component>QtGui</component> + </prodinfo> + <othermeta name="platform" content="MeeGo"/> + <othermeta name="platform" content="Symbian"/> + <othermeta name="platform" content="OS X 10.6"/> + <othermeta name="technology" content="User Interface"/> + </metadata> + </prolog> + \endcode + + In the example output, several values have been set using default + values obtained from the QDoc configuration file. See \l + {Generating DITA XML Output} for details. + + \target noautolist-command + \section1 \\noautolist + + The \\noautolist command indicates that the annotated list of C++ + classes or QML types, which is automatically generated at the + bottom of the C++ or QML module page should be omitted, because + the classes or types have been listed manually. This command can + also be used with the \l {group-command}{\\group} command to omit + the list of group members, when they are listed manually. + + The command must stand on its own line. See \l {Qt Sensors QML Types} for + an example. The page is generated from \c {qtsensors5.qdoc}. There you will + find a qdoc comment containing the \c{\qmlmodule} command for the QtSensors + module. The same qdoc comment contains two \c {\annotated-list} commands to + list the QML types in two separate groups. The QML types have been divided + into these two groups because it makes more sense to list them this way than + it does to list them in a single alphabetical list. At the bottom of the + comment, \c {\noautolist} has been used to tell qdoc not to generate the + automatic annotated list. + + This command was introduced in QDoc 5.6. + + \target omit-command + \section1 \\omit + + The \\omit command and the corresponding \\endomit command + delimit parts of the documentation that you want QDoc to skip. For + example: + + \code + / *! + \table + \row + \li Basic Widgets + \li Basic GUI widgets such as buttons, comboboxes + and scrollbars. + + \omit + \row + \li Component Model + \li Interfaces and helper classes for the Qt + Component Model. + \endomit + + \row + \li Database Classes + \li Database related classes, e.g. for SQL databases. + \endtable + * / + \endcode + + QDoc renders this as: + + \raw HTML + <table align="center" cellpadding="2" + cellspacing="1" border="0"> + + <tr valign="top" bgcolor="#d0d0d0"> + <td>Basic Widgets</td> + <td>Basic GUI widgets such as buttons, comboboxes + and scrollbars.</td> + </tr> + + <tr valign="top" bgcolor="#c0c0c0"> + <td>Database Classes</td> + <td>Database related classes, e.g. for SQL databases.</td> + </tr> + </table> + \endraw + + \target raw-command + \section1 \\raw \span {class="newStuff"} {(avoid)} + + The \\raw command and the corresponding + \\endraw command delimit a block of raw mark-up language code. + + \note Avoid using this command if possible, because it generates + DITA XML code that causes problems. If you are trying to generate + special table or list behavior, try to get the behavior you want + using the \l {span-command} {\\span} and \l {div-command} {\\div} + commands in your \l {table-command} {\\table} or \l {list-command} + {\\list}. + + The command takes an argument specifying the code's format. + Currently, the only supported format is HTML. + + The \\raw command is useful if you want some special HTML effects + in your documentation. + + \code + / *! + Qt has some predefined QColor objects. + + \raw HTML + <style type="text/css" id="colorstyles"> + #color-blue { background-color: #0000ff; color: #ffffff } + #color-darkBlue { background-color: #000080; color: #ffffff } + #color-cyan { background-color: #00ffff; color: #000000 } + </style> + + <p> + <tt id="color-blue">Blue(#0000ff)</tt>, + <tt id="color-darkBlue">dark blue(#000080)</tt> and + <tt id="color-cyan">cyan(#00ffff)</tt>. + </p> + \endraw + * / + \endcode + + QDoc renders this as: + + \quotation + Qt has some predefined QColor objects. + + \raw HTML + <style type="text/css" id="colorstyles"> + #color-blue { background-color: #0000ff; color: #ffffff } + #color-darkBlue { background-color: #000080; color: #ffffff } + #color-cyan { background-color: #00ffff; color: #000000 } + </style> + + <p> + <tt id="color-blue">Blue(#0000ff)</tt>, + <tt id="color-darkBlue">dark blue(#000080)</tt> and + <tt id="color-cyan">cyan(#00ffff)</tt>. + </p> + \endraw + \endquotation + + \note But you can achieve the exact same thing using qdoc + commands. In this case, all you have to do is include the color + styles in your style.css file. Then you can write: + + \code + \tt {\span {id="color-blue"} {Blue(#0000ff)}}, + \tt {\span {id="color-darkBlue"} {dark blue(#000080)}} and + \tt {\span {id="color-cyan"} {cyan(#00ffff)}}. + \endcode + + ...which is rendered as: + + \tt {\span {id="color-blue"} {Blue(#0000ff)}}, + \tt {\span {id="color-darkBlue"} {dark blue(#000080)}} and + \tt {\span {id="color-cyan"} {cyan(#00ffff)}}. + + \target unicode-command + \section1 \\unicode + + The \\unicode command allows you to insert an arbitrary Unicode + character in the document. + + The command takes an argument specifying the character as an + integer. By default, base 10 is assumed, unless a '0x' or '0' + prefix is specified (for base 16 and 8, respectively). For + example: + + \code + O G\unicode{0xEA}nio e as Rosas + + \unicode 0xC0 table en famille avec 15 \unicode 0x20AC par jour + + \unicode 0x3A3 \e{a}\sub{\e{i}} + \endcode + + QDoc renders this as: + + \quotation + O G\unicode{0xEA}nio e as Rosas + + \unicode 0xC0 table en famille avec 15 \unicode 0x20AC par jour + + \unicode 0x3A3 \e{a}\sub{\e{i}} + \endquotation +*/ + diff --git a/src/qdoc/doc/qdoc-manual-qdocconf.qdoc b/src/qdoc/doc/qdoc-manual-qdocconf.qdoc new file mode 100644 index 000000000..69980c1e1 --- /dev/null +++ b/src/qdoc/doc/qdoc-manual-qdocconf.qdoc @@ -0,0 +1,1714 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page 21-0-qdoc-configuration.html + \previouspage Creating DITA Maps + \contentspage QDoc Manual + \nextpage Generic Configuration Variables + + \title The QDoc Configuration File + + Before running QDoc, you must create a QDoc configuration file to + tell QDoc where to find the source files that contain the QDoc + comments. The pathname to your configuration file is passed to + QDoc on the command line: + + \quotation + \c {/current/dir$ ../../bin/qdoc ./config.qdocconf} + \endquotation + + \section1 General Description + + The configuration file is a list of entries of the form \e + {"variable = value"}. Using the configuration variables, you can + define where QDoc should find the various source files, images and + examples, where to put generated documentation etc. The + configuration file can also contain directives like \c + include. For an example, see \l {minimal-qdocconf}{a minimal qdocconf file}. + + You can also use configuration variables to get QDoc to support + \l{Supporting Derived Projects} {derived projects}, i.e QDoc can + generate links in your project's documentation to elements in the + Qt online documentation. See the \l {Supporting Derived projects} + section. + + The value of a configuration variable can be set using either '=' + or '+='. The difference is that '=' overrides the previous value, + while '+=' adds a new value to the current one. + + Some configuration variables accept a list of strings as their + value, for example: + \l {sourcedirs-variable} + {\c{sourcedirs}}, while others accept only a single string. Double + quotes around a value string are optional, but including them allows + you to use special characters like '=' and ' \" ' within the value + string, for example: + + \badcode + HTML.postheader = "<a href=\"index.html\">Home</a>" + \endcode + + If an entry spans many lines, use a backslash at the end of every + line but the last: + + \badcode + sourcedirs = kernel \ + tools \ + widgets + \endcode + + \section1 Configuration Variables + + \section1 Variable List + + \list + \li \l {alias-variable} {alias} + \li \l {Cpp.ignoredirectives-variable} {Cpp.ignoredirectives} + \li \l {Cpp.ignoretokens-variable} {Cpp.ignoretokens} + \li \l {defines-variable} {defines} + \li \l {edition-variable} {edition} + \li \l {exampledirs-variable} {exampledirs} + \li \l {examples-variable} {examples} + \li \l {examples.fileextensions-variable} {examples.fileextensions} + \li \l {excludedirs-variable} {excludedirs} + \li \l {excludefiles-variable} {excludefiles} + \li \l {extraimages-variable} {extraimages} + \li \l {falsehoods-variable} {falsehoods} + \li \l {headerdirs-variable} {headerdirs} + \li \l {headers-variable} {headers} + \li \l {headers.fileextensions-variable} {headers.fileextensions} + \li \l {HTML.footer-variable} {HTML.footer} + \li \l {HTML.postheader-variable} {HTML.postheader} + \li \l {HTML.style-variable} {HTML.style} + \li \l {imagedirs-variable} {imagedirs} + \li \l {images-variable} {images} + \li \l {images.fileextensions-variable} {images.fileextensions} + \li \l {language-variable} {language} + \li \l {macro-variable} {macro} + \li \l {manifestmeta-variable} {manifestmeta} + \li \l {outputdir-variable} {outputdir} + \li \l {outputformats-variable} {outputformats} + \li \l {outputprefixes-variable} {outputprefixes} + \li \l {outputsuffixes-variable} {outputsuffixes} + \li \l {sourcedirs-variable} {sourcedirs} + \li \l {sources-variable} {sources} + \li \l {sources.fileextensions-variable} {sources.fileextensions} + \li \l {spurious-variable} {spurious} + \li \l {tabsize-variable} {tabsize} + \li \l {version-variable} {version} + \li \l {versionsym-variable} {versionsym} + \endlist + + \section1 Categories + + \list + \li \l {Generic Configuration Variables} + \li \l {C++ Specific Configuration Variables} + \li \l {HTML Specific Configuration Variables} + \endlist + + \section1 Configuration File Examples + + \list + \li A minimum configuration file: \l minimum.qdocconf + \li The Qt configuration file: \l qtgui.qdocconf + \endlist +*/ + + +/*! + \page 22-qdoc-configuration-generalvariables.html + \previouspage The QDoc Configuration File + \contentspage QDoc Manual + \nextpage Creating Help Project Files + + \title Generic Configuration Variables + + With the general QDoc configuration variables, you can define + where QDoc will find the various source files it needs to generate + the documentation, as well as the directory to put the generated + documentation. You can also do some minor manipulation of QDoc + itself, controlling its output and processing behavior. + + \target alias-variable + \section1 alias + + The \c alias variable renames a QDoc command. + + The general syntax is \tt {alias.\e{original-command-name} = \e + temporary-command-name}. + + \badcode + alias.e = i + \endcode + + This renames the built-in command \\e (italics) to be \\i. The \c + alias variable is often used for compatibility reasons. + + See also \l {macro-variable} {macro}. + + \target codeindent-variable + \section1 codeindent + + The \c codeindent variable specifies the level of indentation that + QDoc uses when writing code snippets. + + QDoc originally used a hard-coded value of four spaces for code + indentation to ensure that code snippets could be easily + distinguished from surrounding text. Since we can use \l{HTML + Specific Configuration Variables#HTML.stylesheets} {stylesheets} + to adjust the appearance of certain types of HTML elements, this + level of indentation is not always required. + + \target codeprefix-variable + \target codesuffix-variable + \section1 codeprefix, codesuffix + + The \c codeprefix and \c codesuffix variables specify a pair of + strings that each code snippet is enclosed in. + + \target defines-variable + \section1 defines + + The \c defines variable specifies the C++ preprocessor symbols + that QDoc will recognize and respond to. + + When a preprocessor symbol is specified using the \c defines + variable, you can also use the \l {if-command} {\\if} command to + enclose documentation that only will be included if the + preprocessor symbol is defined. + + The values of the variable are regular expressions (see QRegExp + for details). By default, no symbol is defined, meaning that code + protected with #ifdef...#endif will be ignored. + + \badcode + defines = Q_QDOC \ + QT_.*_SUPPORT \ + QT_.*_LIB \ + QT_COMPAT \ + QT3_SUPPORT \ + Q_OS_.* \ + Q_BYTE_ORDER \ + __cplusplus + \endcode + + This ensures that QDoc will process the code that requires these + symbols to be defined. For example: + + \code + #ifdef Q_OS_WIN + HDC getDC() const; + void releaseDC(HDC) const; + #endif + \endcode + + Since the Q_OS_.* regular expression (specified using the \c + defines variable) matches Q_OS_WIN, QDoc will process the code + within #ifdef and #endif in our example. + + You can also define preprocessor symbols manually on the command + line using the -D option. For example: + + \badcode + currentdirectory$ qdoc -Dconsoleedition qtgui.qdocconf + \endcode + + In this case the -D option ensures that the \c consoleedition + preprocessor symbol is defined when QDoc processes the source + files defined in the qtgui.qdocconf file. + + See also \l {falsehoods-variable} {falsehoods} and \l {if-command} {\\if}. + + \target edition-variable + \section1 edition + + The \c edition variable specifies which modules are included in + each edition of a package, and provides QDoc with information to + provide class lists for each edition. + + This feature is mostly used when providing documentation for Qt + packages. + + The \c edition variable is always used with a particular edition + name to define the modules for that edition: + + \badcode + edition.Console = QtCore QtNetwork QtSql QtXml + edition.Desktop = QtCore QtGui QtNetwork QtOpenGL QtSql QtXml \ + QtDesigner QtAssistant Qt3Support QAxContainer \ + QAxServer + edition.DesktopLight = QtCore QtGui Qt3SupportLight + \endcode + + In the above examples, the \c Console edition only includes the + contents of four modules. Only the classes from these modules will + be used when the \l{Miscellaneous#generatelist-command} + {generatelist} command is used to generate a list of classes for + this edition: + + \badcode + \generatelist{classesbyedition Console} + \endcode + + \target exampledirs-variable + \section1 exampledirs + + The \c exampledirs variable specifies the directories containing + the source code of the example files. + + The \l {examples-variable} {examples} and \l + {exampledirs-variable} {exampledirs} variables are used by the \l + {quotefromfile-command} {\\quotefromfile}, \l {quotefile-command} + {\\quotefile} and \l {example-command} {\\example} commands. If + both the \l {examples-variable} {examples} and \l + {exampledirs-variable} {exampledirs} variables are defined, QDoc + will search in both, first in \l {examples-variable} {examples} + then in \l {exampledirs-variable} {exampledirs}. + + QDoc will search through the directories in the specified order, + and accept the first matching file it finds. It will only search + in the specified directories, \e not in subdirectories. + + \badcode + exampledirs = $QTDIR/doc/src \ + $QTDIR/examples \ + $QTDIR \ + $QTDIR/qmake/examples + + examples = $QTDIR/examples/widgets/analogclock/analogclock.cpp + \endcode + + When processing + + \badcode + \quotefromfile widgets/calculator/calculator.cpp + \endcode + + QDoc will see if there is a file called \c calculator.cpp + listed as a value in the \l {examples-variable} {\c examples} variable. If + there isn't, it will search in the \c exampledirs variable, and + first see if there exists a file called + + \badcode + $QTDIR/doc/src/widgets/calculator/calculator.cpp + \endcode + + If it doesn't, QDoc will continue looking for a file called + + \badcode + $QTDIR/examples/widgets/calculator/calculator.cpp + \endcode + + and so forth. + + See also \l {examples-variable}{examples}. + + \target examples-variable + \section1 examples + + The \c examples variable allows you to specify individual example + files in addition to those located in the directories specified by + the \l {exampledirs-variable} {\c exampledirs} variable. + + The \c examples and \l {exampledirs-variable} {\c exampledirs} + variables are used by the \l {quotefromfile-command} + {\\quotefromfile}, \l {quotefile-command} {\\quotefile} and \l + {example-command} {\\example} commands. If both the \c examples and \l + {exampledirs-variable} {\c exampledirs} variables are defined, + QDoc will search in both, first in \c examples then in \l + {exampledirs-variable} {\c exampledirs}. + + QDoc will search through the values listed for the \c examples + variable, in the specified order, and accept the first one it + finds. + + For an extensive example, see the \l {exampledirs-variable} {\c + exampledirs} command. But note that if you know the file is listed + in the \c examples variable, you don't need to specify its path: + + \badcode + \quotefromfile calculator.cpp + \endcode + + See also \l {exampledirs-variable} {exampledirs}. + + \target examples.fileextensions-variable + \section1 examples.fileextensions + + The \c examples.fileextensions variable specifies the file + extensions that qdoc will look for when collecting example files + for display in the documentation. + + The default extensions are *.cpp, *.h, *.js, *.xq, *.svg, *.xml + and *.ui. + + The extensions are given as standard wildcard expressions. You + can add a file extension to the filter using '+='. For example: + + \badcode + examples.fileextensions += *.qrc + \endcode + + See also \l{headers.fileextensions}. + + \target excludedirs-variable + \section1 excludedirs + + The \c excludedirs variable is for listing directories that should \e{not} + be processed by qdoc, even if the same directories are included by the + \l {sourcedirs-variable} {sourcedirs} or \l {headerdirs-variable} {headerdirs} + variables. + + For example: + + \badcode + sourcedirs = src/corelib + excludedirs = src/corelib/tmp + \endcode + + When executed, QDoc will exclude the listed directories from + further consideration. Files in these directories will not be + read by qdoc. + + See also \l {excludefiles-variable} {excludefiles}. + + \target excludefiles-variable + \section1 excludefiles + + The \c excludefiles variable allows you to specify individual files + that should \e{not} be processed by qdoc. + + \badcode + excludefiles += $QT_CORE_SOURCES/../../src/widgets/kernel/qwidget.h \ + $QT_CORE_SOURCES/../../src/widgets/kernel/qwidget.cpp + \endcode + + If you include the above in your qdocconf file for qtbase, there + will be no qwidget.html generated for html and no qwidget.xml + generated for DITA XML. + + See also \l {excludedirs-variable} {excludedirs}. + + \target extraimages-variable + \section1 extraimages + + The \c extraimages variable tells QDoc to incorporate specific + images in the generated documentation. + + QDoc will not recognize images used within HTML (or any other + markup language). If we want the images to be copied from the + directories specified by \l {imagedirs} {\c imagedirs} (the images + in question must be located in these directories) to the output + directory, we must specify the images using the \c extraimages + variable. + + The general syntax is \tt {extraimages.\e{format} = \e image}. The + file extension is optional. + + For example, in \l qtgui.qdocconf we use a couple of images within + the HTML.postheader variable which value is pure HTML. For that + reason, these images are specified using the \c extraimages + variable: + + \badcode + extraimages.HTML = qt-logo + \endcode + + See also \l images and \l imagedirs. + + \target falsehoods-variable + \section1 falsehoods + + The \c falsehoods variable defines the truth value of specified + preprocessor symbols as false. + + If this variable is not set for a preprocessor symbol, QDoc + assumes its truth value is true. The exception is '0', which value + always is false. + + QDoc will recognize, and is able to evaluate, the following + preprocessor syntax: + + \code + #ifdef NOTYET + ... + #endif + + #if defined (NOTYET) + ... + #end if + \endcode + + However, faced with unknown syntax like + + \code + #if NOTYET + ... + #endif + \endcode + + QDoc will evaluate it as true by default, \e unless the + preprocessor symbol is specified within the \c falsehoods variable + entry: + + \badcode + falsehoods = NOTYET + \endcode + + See also \l defines. + + \target generateindex-variable + \section1 generateindex + + The \c generateindex variable contains a boolean value that + specifies whether to generate an index file when HTML + documentation is generated. + + By default, an index file is always generated with HTML + documentation, so this variable is typically only used when + disabling this feature (by setting the value to \c false) or when + enabling index generation for the WebXML output (by setting the + value to \c true). + + \target headerdirs-variable + \section1 headerdirs + + The \c headerdirs variable specifies the directories containing + the header files associated with the \c .cpp source files used in + the documentation. + + \badcode + headerdirs = $QTDIR/src \ + $QTDIR/extensions/activeqt \ + $QTDIR/extensions/motif \ + $QTDIR/tools/designer/src/lib/extension \ + $QTDIR/tools/designer/src/lib/sdk \ + $QTDIR/tools/designer/src/lib/uilib + \endcode + + When executed, the first thing QDoc will do is to read through the + headers specified in the \l {headers} {\c headers} variable, and + the ones located in the directories specified in the \c headerdir + variable (including all subdirectories), building an internal + structure of the classes and their functions. + + Then it will read through the sources specified in the \l + {sources-variable} {\c sources}, and the ones located in the + directories specified in the \l {sourcedirs-variable} {\c + sourcedirs} varible (including all subdirectories), merging the + documentation with the structure it retrieved from the header + files. + + If both the \c headers and \c headerdirs variables are defined, + QDoc will read through both, first \l {headers} {\c headers} then + \c headerdirs. + + In the specified directories, QDoc will only read the files with + the \c fileextensions specified in the \l {headers.fileextensions} + {\c headers.fileextensions} variable. The default extensions are + *.ch, *.h, *.h++, *.hh, *.hpp, and *.hxx". The files specified by + \l {headers} {\c headers} will be read without taking into account + their fileextensions. + + See also \l headers and \l headers.fileextensions. + + \target headers-variable + \section1 headers + + The \c headers variable allows you to specify individual header + files in addition to those located in the directories specified by + the \l {headerdirs} {\c headerdirs} variable. + + \badcode + headers = $QTDIR/src/gui/widgets/qlineedit.h \ + $QTDIR/src/gui/widgets/qpushbutton.h + \endcode + + When processing the \c headers variable, QDoc behaves in the same + way as it does when processing the \l {headerdirs} {\c headerdirs} + variable. For more information, see the \l {headerdirs} {\c + headerdirs} variable. + + See also \l headerdirs. + + \target headers.fileextensions-variable + \section1 headers.fileextensions + + The \c headers.fileextensions variable specify the extension used + by the headers. + + When processing the header files specified in the \l {headerdirs} + {\c headerdirs} variable, QDoc will only read the files with the + fileextensions specified in the \c headers.fileextensions + variable. In this way QDoc avoids spending time reading irrelevant + files. + + The default extensions are *.ch, *.h, *.h++, *.hh, *.hpp, and + *.hxx. + + The extensions are given as standard wildcard expressions. You + can add a file extension to the filter using '+='. For example: + + \badcode + header.fileextensions += *.H + \endcode + + \warning The above assignment may not work as described. + + See also \l headerdirs. + + \target imagedirs-variable + \section1 imagedirs + + The \c imagedirs variable specifies the directories containing the + images used in the documentation. + + The \l {images} {\c images} and \c imagedirs variables are used by + the \l {image-command} {\\image} and \l {inlineimage-command} + {\\inlineimage} commands. If both the \l {images} {\c images} and + \c imagedirs variables are defined, QDoc will search in both. First + in \l {images} {\c images}, then in \c imagedirs. + + QDoc will search through the directories in the specified order, + and accept the first matching file it finds. It will only search + in the specified directories, \e not in subdirectories. + + \badcode + imagedirs = $QTDIR/doc/src/images \ + $QTDIR/examples + + images = $QTDIR/doc/src/images/calculator-example.png + \endcode + + When processing + + \badcode + \image calculator-example.png + \endcode + + QDoc will then see if there is a file called + calculator-example.png listed as a value in the \c images + variable. If there isn't, it will search in the \c imagedirs + variable for: + + \badcode + $QTDIR/doc/src/images/calculator-example.png + \endcode + + If the file doesn't exist, QDoc will look for a file called + + \badcode + $QTDIR/examples/calculator-example.png + \endcode + + You can filter the images in an image directory using the \l + {images.fileextensions} {\c images.fileextensions} variable. The + general idea behind the \l {images.fileextensions} {\c images.fileextensions} + variable is to enable different image format for different output format. + + \warning The \l {images.fileextensions} {\c images.fileextensions} + variable's functionality is preliminary since QDoc at this point + only supports HTML. + + See also \l images and \l images.fileextensions. + + \target images-variable + \section1 images + + The \c images variable allows you to specify individual image + files in addition to those located in the directories specified by + the \l {imagedirs} {\c imagedirs} variable. + + \badcode + images = $QTDIR/doc/src/images/calculator-example.png + \endcode + + When processing the \c images variable, QDoc behaves in the same + way as it does when processing the \l {imagedirs} {\c imagedirs} + variable. For more information, see the \l {imagedirs} {\c + imagedirs} variable. + + See also \l imagedirs and \l images.fileextensions. + + \target images.fileextensions-variable + \section1 images.fileextensions + + The images.fileextensions variable filters the files within an + image directory. + + The variable's values (the extensions) are given as standard + wildcard expressions. The general syntax is: \tt + {images.fileextensions.\e{format} = *.\e{extension}}. + + The idea is to enable different image format for different output + format. + + \badcode + images.fileextensions.HTML = *.png + images.fileextensions.LOUT = *.eps + \endcode + + Then, when processing the \l {image-command} {\\image} and \l + {inlineimage-command} {\\inlineimage} commands, QDoc will only + search for files with extensions specified in the variable + containing the list of output formats. + + \warning This is only a preliminary functionality since QDoc at this + point only supports HTML. + + The default extensions for HTML are *.png, *.jpg, *.jpeg, and + *.gif. + + You can add a file extension to the filter using '+='. For + example: + + \badcode + images.fileextensions.HTML += *.eps + \endcode + + See also \l imagedirs and \l images. + + \target language-variable + \section1 language + + The \c language variable specifies the language of the source code + that is used in the documentation. + + Currently, C++ is the only language that QDoc understands. It is + also the default language, and doesn't really need to be + specified. However, a possible example of a language variable + statement: + + \badcode + language = Cpp + \endcode + + This identifies C++ as the language of the Qt source code. + + \target macro-variable + \section1 macro + + The \c macro variable is used to create your own simple QDoc + commands. The syntax is \tt {macro.\e{command} = \e{definition}}, + where the definition is written using QDoc syntax. + + A macro variable can be restricted for use in one type of output + generation. By appending \c {.HTML} to the macro name, for + example, the macro is only used when generating HTML output. By + appending \c {.DITAXML} to the macro name, the macro is only used + when generating DITA XML. + + \badcode + macro.gui = "\\b" + macro.raisedaster.HTML = "<sup>*</sup>" + \endcode + + The first macro defines the \\gui command to render its argument + using a bold font. The second macro defines the \\raisedaster + command to render a superscript asterisk, but only when generating + HTML. + + A macro can also take up to seven parameters: + + \badcode + macro.hello = "Hello \1!" + \endcode + + Parameters are passed to macros the same way as to other commands: + + \badcode + \hello World + \endcode + + See also \l {alias-variable} {alias}. + + \target manifestmeta-variable + \section1 manifestmeta + + The \c manifestmeta variable specifies additional meta-content + for the example manifest files generated by QDoc. + + See the \l{Manifest Meta Content} section for more information. + + \target naturallanguage-variable + \section1 naturallanguage + + The \c naturallanguage variable specifies the natural language + used for the documentation generated by qdoc. + + \badcode + naturallanguage = zh-Hans + \endcode + + By default, the natural language is \c en for compatibility with + legacy documentation. + + qdoc will add the natural language information to the HTML it + generates, using the \c lang and \c xml:lang attributes. + + See also \l {sourceencoding-variable} {sourceencoding}, + \l {outputencoding-variable} {outputencoding}, + \l{http://www.w3.org/TR/xhtml1/#C_7} + {C.7. The lang and xml:lang Attributes} and + \l{http://www.w3.org/TR/i18n-html-tech-lang/#ri20040429.113217290} + {Best Practice 13: Using Hans and Hant codes}. + + \target outputdir-variable + \section1 outputdir + + The \c outputdir variable specifies the directory where QDoc will + put the generated documentation. + + \badcode + outputdir = $QTDIR/doc/html + \endcode + + locates the generated Qt reference documentation in + $QTDIR/doc/html. For example, the documentation of the QWidget + class is located in + + \badcode + $QTDIR/doc/html/qwidget.html + \endcode + + The associated images will be put in an \c images subdirectory. + + \warning When running QDoc multiple times using the same output + directory, all files from the previous run will be lost. + + \target outputencoding-variable + \section1 outputencoding + + The \c outputencoding variable specifies the encoding used for the + documentation generated by qdoc. + + \badcode + outputencoding = UTF-8 + \endcode + + By default, the output encoding is \c ISO-8859-1 (Latin1) for + compatibility with legacy documentation. When generating + documentation for some languages, particularly non-European + languages, this is not sufficient and an encoding such as UTF-8 is + required. + + qdoc will encode HTML using this encoding and generate the correct + declarations to indicate to browsers which encoding is being + used. The \l naturallanguage configuration variable should also be + specified to provide browsers with a complete set of character + encoding and language information. + + See also \l outputencoding and \l naturallanguage. + + \target outputformats-variable + \section1 outputformats + + The \c outputformats variable specifies the format of + the generated documentation. + + Currently, QDoc only supports the HTML format. It is also + the default format, and doesn't need to be specified. + + \target outputprefixes-variable + \section1 outputprefixes + + The \c outputprefixes variable specifies a mapping between types of files + and the prefixes to prepend to the HTML file names in the generated + documentation. + + \badcode + outputprefixes = QML JS + outputprefixes.QML = uicomponents- + outputprefixes.JS = uicomponents- + \endcode + + By default, files containing the API documentation for QML types + are prefixed with "qml-", and javaScript types with "js-". In the + above example, the prefix \c "uicomponents" is used instead for + both. + + The output prefix is applied to file names for documentation on + QML and JS types. + + \target outputsuffixes-variable + \section1 outputsuffixes + + The \c outputsuffixes variable specifies a mapping between types of + files and module name suffixes to append to the HTML file names. + + \badcode + outputsuffixes = QML + outputsuffixes.QML = -tp + \endcode + + Given a QML module name \e FooBar and the default + \l {outputprefixes-variable}{output prefix} ("qml-"), the file name of + the generated HTML page for a QML type \e FooWidget would be + \c qml-foobar-tp-foowidget.html. + + By default, no suffix is used. The output suffix, if defined, is applied + to file names for documentation on QML and JS types, and their respective + module pages. + + The \c outputsuffixes variable was introduced in QDoc 5.6. + + \target qhp-variable + \section1 qhp + + The \c qhp variable is used to define the information to be + written out to Qt Help Project (\c{qhp}) files. + + See the \l{Creating Help Project Files} chapter for information + about this process. + + \target sourcedirs-variable + \section1 sourcedirs + + The \c sourcedirs variable specifies the directories containing + the \c .cpp or \c .qdoc files used in the documentation. + + \badcode + sourcedirs += .. \ + ../../../examples/gui/doc/src + \endcode + + When executed, the first thing QDoc will do is to read through the + headers specified in the \l {header-command} {\c header} variable, + and the ones located in the directories specified in the \c + headerdir variable (including all subdirectories), building an + internal structure of the classes and their functions. + + Then it will read through the sources specified in the \l + {sources} {\c sources}, and the ones located in the directories + specified in the \l {sourcedirs} {\c sourcedirs} variable + (including all subdirectories), merging the documentation with the + structure it retrieved from the header files. + + If both the \c sources and \c sourcedirs variables are defined, + QDoc will read through both, first \l {sources} {\c sources} then + \c sourcedirs. + + In the specified directories, QDoc will only read the files with + the \c fileextensions specified in the \l {sources.fileextensions} + {\c sources.fileextensions} variable. The default extensions are + *.c++, *.cc, *.cpp and *.cxx. The files specified by \l {sources} + {\c sources} will be read independent of their fileextensions. + + See also \l {sources-variable} {sources} and + \l {sources.fileextensions-variable} {sources.fileextensions}. + + \target sourceencoding-variable + \section1 sourceencoding + + The \c sourceencoding variable specifies the encoding used for the + source code and documentation. + + \badcode + sourceencoding = UTF-8 + \endcode + + By default, the source encoding is \c ISO-8859-1 (Latin1) for + compatibility with legacy documentation. For some languages, + particularly non-European languages, this is not sufficient and an + encoding such as UTF-8 is required. + + Although qdoc will use the encoding to read source and + documentation files, limitations of C++ compilers may prevent you + from using non-ASCII characters in source code comments. In cases + like these, it is possible to write API documentation completely + in documentation files. + + See also \l {naturallanguage-variable} {naturallanguage} and + \l {outputencoding-variable} {outputencoding}. + + \target sources-variable + \section1 sources + + The \c sources variable allows you to specify individual source + files in addition to those located in the directories specified by + the \l {sourcedirs-variable} {sourcedirs} variable. + + \badcode + sources = $QTDIR/src/gui/widgets/qlineedit.cpp \ + $QTDIR/src/gui/widgets/qpushbutton.cpp + \endcode + + When processing the \c sources variable, QDoc behaves in the same + way as it does when processing the \l {sourcedirs-variable} + {sourcedirs} variable. For more information, see the \l + {sourcedirs-variable} {sourcedirs} variable. + + See also \l {sourcedirs-variable} {sourcedirs}. + + \target sources.fileextensions-variable + \section1 sources.fileextensions + + The \c sources.fileextensions variable filters the files within a + source directory. + + When processing the source files specified in the \l {sourcedirs} + {\c sourcedirs} variable, QDoc will only read the files with the + fileextensions specified in the \c sources.fileextensions + variable. In this way QDoc avoid spending time reading irrelevant + files. + + The default extensions are *.c++, *.cc, *.cpp and *.cxx. + + The extensions are given as standard wildcard expressions. You + can add a file extension to the filter using '+='. For example: + + \badcode + sources.fileextensions += *.CC + \endcode + + \warning The above assignment may not work as described. + + See also \l {sourcedirs-variable} {sourcedirs} and \l + (sources-variable} {sources}. + + + \target spurious-variable + \section1 spurious + + The \c spurious variable excludes specified QDoc warnings from the + output. The warnings are specified using standard wildcard + expressions. + + \badcode + spurious = "Cannot find .*" \ + "Missing .*" + \endcode + + makes sure that warnings matching either of these expressions, + will not be part of the output when running QDoc. For example + would the following warning be omitted from the output: + + \badcode + src/opengl/qgl_mac.cpp:156: Missing parameter name + \endcode + + \target syntaxhighlighting + \section1 syntaxhighlighting + + The \c syntaxhighlighting variable specifies whether QDoc should + perform syntax highlighting on source code quoted in the + documentation it generates. + + \badcode + syntaxhighlighting = true + \endcode + + will enable syntax highlighting for all supported programming + languages. + + \target tabsize-variable + \section1 tabsize + + The \c tabsize variable defines the size of a tab character. + + \badcode + tabsize = 4 + \endcode + + will give the tab character the size of 4 spaces. The default + value of the variable is 8, and doesn't need to be specified. + + \target tagfile-variable + \section1 tagfile + + The \c tagfile variable specifies the Doxygen tag file to be + written when HTML is generated. + + \target version-variable + \section1 version + + The \c version variable specifies the version number of the + documented software. + + \badcode + version = 5.6.0 + \endcode + + When a version number is specified (using the \tt{\l version} or + \tt {\l versionsym} variables in a \c .qdocconf file), it is + accessible through the corresponding \\version command for use in + the documentation. + + \warning The \\version command's functionality is not fully + implemented; currently it only works within raw HTML code. + + See also \l versionsym. + + \target versionsym-variable + \section1 versionsym + + The \c versionsym variable specifies a C++ preprocessor symbol + that defines the version number of the documented software. + + \badcode + versionsym = QT_VERSION_STR + \endcode + + QT_VERSION_STR is defined in qglobal.h as follows + + \badcode + #define QT_VERSION_STR "4.0.1" + \endcode + + When a version number is specified (using the \tt{\l version} or + \tt {\l versionsym} variables in a \c .qdocconf file), it is + accessible through the corresponding \\version command for use in + the documentation. + + \warning The \\version command's functionality is not fully + implemented. Currently, it only works within raw HTML code. + + See also \l {version} {\\version}. +*/ + +/*! + \page 22-creating-help-project-files.html + \previouspage Generic Configuration Variables + \contentspage QDoc Manual + \nextpage C++ Specific Configuration Variables + + \title Creating Help Project Files + + \section1 Overview + + Qt Assistant uses a system for managing Qt documentation that requires + QDoc to generate inventories of files in a format that is similar to the + old style DCF format, but with additional features. + + QDoc allows configuration variables to be used to specify which pages are + to be used in each documentation set it generates. These are specified as + subvariables of the \c qhp variable with each set declared using a unique + identifier as a subvariable. + + For example, the configuration file for the Qt Quick documentation set + specifies information about the set as subvariables with the + \c{qhp.QtQuick} prefix: + + \badcode + qhp.projects = QtQuick + + qhp.QtQuick.file = qtquick.qhp + qhp.QtQuick.namespace = org.qt-project.qtquick.$QT_VERSION_TAG + qhp.QtQuick.virtualFolder = qtquick + qhp.QtQuick.indexTitle = Qt Quick + qhp.QtQuick.indexRoot = + + qhp.QtQuick.filterAttributes = qtquick $QT_VERSION qtrefdoc + qhp.QtQuick.customFilters.Qt.name = QtQuick $QT_VERSION + qhp.QtQuick.customFilters.Qt.filterAttributes = qtquick $QT_VERSION + + qhp.QtQuick.subprojects = qmltypes classes examples + + qhp.QtQuick.subprojects.qmltypes.title = QML Types + qhp.QtQuick.subprojects.qmltypes.indexTitle = Qt Quick QML Types + qhp.QtQuick.subprojects.qmltypes.selectors = qmlclass + qhp.QtQuick.subprojects.qmltypes.sortPages = true + + qhp.QtQuick.subprojects.classes.title = Classes + qhp.QtQuick.subprojects.classes.title = C++ Classes + qhp.QtQuick.subprojects.classes.indexTitle = Qt Quick C++ Classes + qhp.QtQuick.subprojects.classes.selectors = class fake:headerfile + qhp.QtQuick.subprojects.classes.sortPages = true + + qhp.QtQuick.subprojects.examples.title = Examples + qhp.QtQuick.subprojects.examples.indexTitle = Qt Quick Examples and Tutorials + qhp.QtQuick.subprojects.examples.selectors = fake:example + \endcode + + The documentation set may include one or more subprojects, which are added + to the table of contents under the name specified by \c title. The page + in the documentation referred to by the \c indexTitle acts as the index page + for the subproject. The page types to list under the subproject are specified + by \c selectors. The entries are alphabetically sorted if \c sortPages is set + to \c true. + + \section2 Using Selectors + + The \c selectors property specifies which page types are listed under the + table of contents entry for a subproject. Multiple selectors can be listed, + separated by whitespace. + + \table + \header \li Selector \li Description + \row \li \c namespace \li Namespaces + \row \li \c class \li Classes + \row \li \c qmltype \li QML Types + \row \li \c qmlclass \li Alias for \c qmltype. + \row \li \c module \li C++ Modules + \row \li \c qmlmodule \li QML Modules + \row \li \c doc[:subtype] \li Documentation pages with a specified + \c subtype. Multiple subtypes can be + listed as a comma-separated list. + \row \li \c fake \li Alias for \c doc. + \row \li \c group[:groupname] \li Documentation pages for members of a + specified group, as added using the + \l {ingroup-command} + {\\ingroup} groupname command. + Multiple group names can be listed as + a comma-separated list. + (Introduced in QDoc 5.6). + \endtable + + Available subtypes for the \c doc selector: + + \table + \header \li Subtype \li Description + \row \li \c example \li Examples + \row \li \c headerfile \li Header files + \row \li \c page \li Documentation pages defined with the + \l {page-command} {\\page} command. + \endtable + + For example, the following configuration would select example pages and + pages that include the \c {\ingroup tutorials} command: + + \badcode + qhp.QtQuickControls.subprojects = examples + qhp.QtQuickControls.subprojects.examples.title = Examples and Tutorials + qhp.QtQuickControls.subprojects.examples.indexTitle = Qt Quick Controls Examples + qhp.QtQuickControls.subprojects.examples.selectors = doc:example group:tutorials + qhp.QtQuickControls.subprojects.examples.sortPages = true + \endcode + + \section2 Adding Table of Contents + + To create a table of contents for a manual, create a subproject with + a \c{type} property and set it to \c{manual}. The page in the documentation + referred to by the \c{indexTitle} property must contain a list of links + that acts as a table of contents for the whole manual. QDoc will take the + information in this list and create a table of contents for the subproject. + + For example, the configuration file for Qt Creator defines only one + subproject for its documentation, including all the documentation in a + single manual: + + \badcode + qhp.QtCreator.subprojects = manual + qhp.QtCreator.subprojects.manual.title = Qt Creator Manual + qhp.QtCreator.subprojects.manual.indexTitle = Qt Creator Manual + qhp.QtCreator.subprojects.manual.type = manual + \endcode + + In this example, the page entitled "Qt Creator Manual" contains a nested + list of links to pages in the documentation which is duplicated in + Qt Assistant's Contents tab. +*/ + +/*! + \page 23-qdoc-configuration-cppvariables.html + \previouspage Creating Help Project Files + \contentspage QDoc Manual + \nextpage HTML Specific Configuration Variables + + \title C++ Specific Configuration Variables + + The C++ specific configuration variables are provided to avoid + erroneous documentation due to non-standard C++ constructs. + + \target Cpp.ignoredirectives-variable + \section1 Cpp.ignoredirectives + The \c Cpp.ignoredirectives variable makes QDoc ignore the + specified non-standard constructs, within C++ source code. + + If not specified by the \tt {\l Cpp.ignoretokens} or \tt {\l + Cpp.ignoredirectives} variables, non-standard constructs + (typically macros) can result in erroneous documentation. + + \badcode + Cpp.ignoredirectives = Q_DECLARE_INTERFACE \ + Q_DECLARE_OPERATORS_FOR_FLAGS \ + Q_DECLARE_PRIVATE \ + Q_DECLARE_PUBLIC \ + Q_DISABLE_COPY \ + Q_DUMMY_COMPARISON_OPERATOR \ + Q_ENUMS \ + Q_FLAGS \ + Q_INTERFACES \ + __attribute__ + \endcode + + makes sure that when processing the code below, for example, QDoc + will simply ignore the 'Q_ENUMS' and 'Q_FLAGS' expressions: + + \code + class Q_CORE_EXPORT Qt { + Q_OBJECT + Q_ENUMS(Orientation TextFormat BackgroundMode + DateFormat ScrollBarPolicy FocusPolicy + ContextMenuPolicy CaseSensitivity + LayoutDirection ArrowType) + Q_ENUMS(ToolButtonStyle) + Q_FLAGS(Alignment) + Q_FLAGS(Orientations) + Q_FLAGS(DockWidgetAreas) + + public: + ... + }; + \endcode + + The Q_OBJECT macro, however, is an exception: QDoc recognizes this + particular non-standard construct, so there is no need specifying + it using the \tt {\l Cpp.ignoredirectives} variable. + + Regarding the Q_CORE_EXPORT macro; see the documentation of the + \tt {\l Cpp.ignoretokens} variable. + + See also \l Cpp.ignoretokens. + + \target Cpp.ignoretokens-variable + \section1 Cpp.ignoretokens + + The \c Cpp.ignoretokens variable makes QDoc ignore the specified + non-standard constructs, within C++ source code. + + If not specified by the \tt {\l Cpp.ignoretokens} or \tt {\l + Cpp.ignoredirectives} variables, non-standard constructs + (typically macros) can result in erroneous documentation. + + In \l qtgui.qdocconf: + + \badcode + Cpp.ignoretokens = QAXFACTORY_EXPORT \ + QM_EXPORT_CANVAS \ + ... + Q_COMPAT_EXPORT \ + Q_CORE_EXPORT \ + Q_EXPLICIT \ + Q_EXPORT \ + ... + Q_XML_EXPORT + \endcode + + makes sure that when processing the code below, for example, QDoc + will simply ignore the 'Q_CORE_EXPORT' expression: + + \code + class Q_CORE_EXPORT Qt { + Q_OBJECT + Q_ENUMS(Orientation TextFormat BackgroundMode + DateFormat ScrollBarPolicy FocusPolicy + ContextMenuPolicy CaseSensitivity + LayoutDirection ArrowType) + Q_ENUMS(ToolButtonStyle) + Q_FLAGS(Alignment) + Q_FLAGS(Orientations) + Q_FLAGS(DockWidgetAreas) + public: + ... + }; + \endcode + + Regarding the Q_OBJECT, Q_ENUMS and Q_FLAGS macros; see the + documentation of the \tt {\l Cpp.ignoredirectives} variable. + + See also \l Cpp.ignoredirectives. +*/ + +/*! + \page 24-qdoc-configuration-htmlvariables.html + \previouspage C++ Specific Configuration Variables + \contentspage QDoc Manual + \nextpage Supporting Derived Projects + + \title HTML Specific Configuration Variables + + The HTML specific configuration variables define the generated + documentation's style, or define the contents of the + documentation's footer or postheader. The format of the variable + values are raw HTML. + + \target HTML.footer-variable + \section1 HTML.footer + + The \c HTML.footer variable defines the content of the generated + HTML documentation's footer. + + The footer is rendered at the bottom of the generated + documentation page. + + The variable's value is given as raw HTML code enclosed by + quotation marks. Note that if the value spans several lines, each + line needs to be enclosed by quotation marks. + + \badcode + HTML.footer = "<p /><address><hr /><div align=\"center\">\n" \ + ... + "</tr></table></div></address>" + \endcode + + \target HTML.postheader-variable + \section1 HTML.postheader + + The \c HTML.postheader variable defines the content of the + generated HTML documentation's postheader. + + The header is rendered at the top of the generated documentation + page. + + The variable's value is given as raw HTML enclosed by quotation + marks. Note that if the value spans several lines, each line needs + to be enclosed by quotation marks. + + \badcode + HTML.postheader = "<table border=\"0\"..." \ + ... + "<img src=\"images/qt-logo.png\" \ + "align=\"right\" width=\"203\" height=\"32\""\ + "border=\"0\" />" \ + "</td></tr>" \ + "</table>" + \endcode + + The complete variable entry in \l qtgui.qdocconf provides the + standard header of the \l {http://doc.qt.io/qt-5/qtgui-index.html} + {Qt GUI Documentation}. + + \target HTML.style-variable + \section1 HTML.style + + The HTML.style variable defines the style for + the generated HTML documentation. + + The variable's value is given as raw HTML enclosed by quotation + marks. Note that if the value spans several lines, each line needs + to be enclosed by quotation marks. + + \badcode + HTML.style = "h3.fn,span.fn" \ + "{ margin-left: 1cm; text-indent: -1cm; }\n" \ + "a:link { color: #004faf; text-decoration: none }\n" \ + "a:visited" \ + "{ color: #672967; text-decoration: none }\n" \ + "td.postheader { font-family: sans-serif }\n" \ + "tr.address { font-family: sans-serif }\n" \ + "body { background: #ffffff; color: black; }" + \endcode + + \target HTML.stylesheets-variable + \section1 HTML.stylesheets + + The HTML.stylesheets variable defines a list of stylesheets + to use for the generated HTML documentation. + + Using separate stylesheets for the documentation makes it easier + to customize and experiment with the style used once the contents + has been generated. Typically, it is only necessary to define a + single stylesheet for any set of documentation; for example: + + \badcode + HTML.stylesheets = classic.css + \endcode + + QDoc expects to find stylesheets in the directory containing the + \l qtgui.qdocconf file, and it will copy those specified to the output + directory alongside the HTML pages. + + \target HTML.tocdepth + \section1 HTML.tocdepth + + The HTML.tocdepth variable defines how many document sections are printed in + the table of contents. Setting tocdepth to \c 0 disables the table of + contents while not setting the variable prints all document sections. + +*/ + +/*! + \page 25-qdoc-configuration-derivedprojects.html + \previouspage HTML Specific Configuration Variables + \contentspage QDoc Manual + \nextpage Example Manifest Files + + \title Supporting Derived Projects + + Some configuration variables allow you to use QDoc to support + Qt-based projects. They allow your project to contain links to the + online Qt documentation, which means that QDoc will be able to + create links to the class reference documentation, without any + explicit linking command. + + \target description-variable + \section1 description + + The description variable holds a short description of the + associated project. + + See also \l project. + + \target indexes-variable + \section1 indexes + + The \c indexes variable lists the index files that will be used to + generate references. + + For example. to make a derived Qt project contain links to the Qt + Reference documentation, you need to specify the associated index + file: + + \badcode + indexes = $QTDIR/doc/html/qt.index + \endcode + + See also \l project and \l url. + + \target project-variable + \section1 project + + The \c project variable provides a name for the project associated + with the \c .qdocconf file. + + The project's name is used to form a file name for the associated + project's \e index file. + + \badcode + project = QtCreator + \endcode + + This will cause an index file called \c qtcreator.index to be + created. + + See also \l description and \l indexes. + + \target url-variable + \section1 url + + The \c url variable holds the base URL for the reference + documentation associated with the current project. + + The URL is stored in the generated index file for the + project. When we use the index on its own, QDoc will use this as + the base URL when constructing links to classes, functions, and + other things listed in the index. + + \badcode + project = Qt + description = Qt Reference Documentation + url = http://doc.qt.io/qt-4.8/ + + ... + \endcode + + This makes sure that whenever \c qt.index is used to generate + references to for example Qt classes, the base URL is \c + http://doc.qt.io/qt-4.8/. + + See also \l indexes. + + \target howto + \section1 How to Support Derived Projects + + This feature makes use of the comprehensive indexes generated by + QDoc when it creates the Qt reference documentation. + + For example, \l qtgui.qdocconf (the configuration file for Qt) + contains the following variable definitions: + + \badcode + project = Qt + description = Qt Reference Documentation + url = http://doc.qt.io/qt-4.8/ + + ... + \endcode + + The \l project variable name is used to form a file name for the + index file; in this case the \c qt.index file is created. The \l + url is stored in the index file. Afterwards, QDoc will use this + as the base URL when constructing links to classes, functions, + and other things listed in the index. + +*/ + +/*! + \page 26-qdoc-configuration-example-manifest-files.html + \previouspage Supporting Derived Projects + \contentspage QDoc Manual + + \title Example Manifest Files + + QDoc generates XML files that contain information about all documented + examples and demos. These files, named \c {examples-manifest.xml} and + \c {demos-manifest.xml}, are used by Qt Creator to present a list of + examples in its welcome screen and to link to their documentation. + + \section1 Manifest XML Structure + + A manifest file has the following structure: + + \badcode + <?xml version="1.0" encoding="UTF-8"?> + <instructionals module="QtGui"> + <examples> + <example + name="Analog Clock Window Example" + docUrl="qthelp://org.qt-project.qtgui.502/qtgui/analogclock.html" + projectPath="gui/analogclock/analogclock.pro" + imageUrl="qthelp://org.qt-project.qtgui.502/qtgui/images/analogclock-window-example.png"> + <description><![CDATA[The Analog Clock Window example shows how + to draw the contents of a custom window.]]></description> + <tags>analog,clock,window</tags> + <fileToOpen>gui/analogclock/main.cpp</fileToOpen> + </example> + ... + </examples> + </instructionals> + \endcode + + Each \c {<example>} element contains information about a name, + description, the location of the project file and documentation, + as well as a list of tags associated with the example. + + \target metacontent + \section1 Manifest Meta Content + + It is possible to augment the manifest files with additional + meta-content - that is, extra attributes and tags for selected + examples, using the \c manifestmeta configuration command. + + One use case for meta-content is highlighting a number of prominent + examples. Another is improving search functionality by adding + relevant keywords as tags for a certain category of examples. + + The examples for which meta-content is applied to is specified using + one or more filters. Matching examples to filters is done based on + names, with each example name prefixed with a module name and a + slash. Simple wildcard matching is supported; by using \c {*} at the + end it's possible to match multiple examples with a single string. + + Example: + + \badcode + manifestmeta.filters = highlighted sql webkit global + + manifestmeta.highlighted.names = "QtGui/Analog Clock Window Example" \ + "QtWidgets/Analog Clock Example" + manifestmeta.highlighted.attributes = isHighlighted:true + + manifestmeta.sql.names = "QtSql/*" + manifestmeta.sql.tags = database,sql + + manifestmeta.webkit.names = "QtWebKitExamples/*" + manifestmeta.webkit.tags = webkit + + manifestmeta.global.names = * + manifestmeta.global.tags = qt5 + \endcode + + Above, an \c isHighlighted attribute is added to two examples. If + the attribute value is omitted, QDoc uses the string \c {true} by + default. Extra tags are added for Qt WebKit and Qt SQL examples, and + another tag is applied to all examples by using just \c {*} as the + match string. +*/ +/*! + \page 21-3-qt-dita-xml-output.html + \previouspage minimum.qdocconf + \contentspage QDoc Manual + \nextpage QA Pages + + \title Generating DITA XML Output + + QDoc can generate \l {http://dita.xml.org} {DITA XML output}. + + In your configuration file, set your \c {outputformats} variable + to \c {DITAXML}, and send the output to an appropriate directory: + + \badcode + outputdir = $QTDIR/doc/ditaxml + outputformats = DITAXML + \endcode + + And include these macros in your configuration file to prevent + QDoc from doing some escaping that doesn't validate in XML: + + \badcode + macro.aacute.DITAXML = "á" + macro.Aring.DITAXML = "Å" + macro.aring.DITAXML = "å" + macro.Auml.DITAXML = "Ä" + macro.br.DITAXML = " " + macro.BR.DITAXML = " " + macro.copyright.DITAXML = "©" + macro.eacute.DITAXML = "é" + macro.hr.DITAXML = " " + macro.iacute.DITAXML = "í" + macro.oslash.DITAXML = "ø" + macro.ouml.DITAXML = "ö" + macro.raisedaster.DITAXML = "<sup>*</sup>" + macro.rarrow.DITAXML = "→" + macro.reg.DITAXML = "<sup>®</sup>" + macro.uuml.DITAXML = "ü" + macro.mdash.DITAXML = "—" + macro.emptyspan.DITAXML = " " + \endcode + + You can also set default values for some of the tags in the DITA + \c {<prolog>} and \c {<metadata>} elements: + + \badcode + dita.metadata.default.author = Qt Development Frameworks + dita.metadata.default.permissions = all + dita.metadata.default.publisher = Qt Project + dita.metadata.default.copyryear = 2015 + dita.metadata.default.copyrholder = Qt Project + dita.metadata.default.audience = programmer + \endcode + + See the \l {meta-command} + {\\meta} command for more details on DITA metadata. + +*/ + + +/*! + \page 21-1-minimum-qdocconf.html + \previouspage qtgui.qdocconf + \contentspage QDoc Manual + \nextpage Generating DITA XML Output + + \title minimum.qdocconf + + \quotefile examples/minimum.qdocconf +*/ + +/*! + \page 21-2-qtgui-qdocconf.html + \previouspage Supporting Derived Projects + \contentspage QDoc Manual + \nextpage minimum.qdocconf + + \title qtgui.qdocconf + + \quotefile files/qtgui.qdocconf +*/ diff --git a/src/qdoc/doc/qdoc-manual-topiccmds.qdoc b/src/qdoc/doc/qdoc-manual-topiccmds.qdoc new file mode 100644 index 000000000..1dfd03163 --- /dev/null +++ b/src/qdoc/doc/qdoc-manual-topiccmds.qdoc @@ -0,0 +1,1594 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page 13-qdoc-commands-topics.html + \previouspage Command Index + \contentspage QDoc Manual + \nextpage Context Commands + + \title Topic Commands + + A topic command tells QDoc which source code element is being + documented. Some topic commands allow you to create documentation + pages that aren't tied to any underlying source code element. + + When QDoc processes a QDoc comment, it tries to connect the + comment to an element in the source code by first looking for a + topic command that names the source code element. If there is no + topic command, QDoc tries to connect the comment to the source + code element that immediately follows the comment. If it can't do + either of these and if there is no topic command that indicates + the comment does not have an underlying source code element (e.g. + \l{page-command} {\\page}), then the comment is discarded. + + \target topic argument + + The name of the entity being documented is usually the only + argument for a topic command. Use the complete name. Sometimes + there can be a second parameter in the argument. See e.g. \l + {page-command} {\\page}. + + \code + \enum QComboBox::InsertPolicy + \endcode + + The \l {fn-command} {\\fn} command is a special case. For the \l + {fn-command} {\\fn} command, use the function's signature + including the class qualifier. + + \code + \fn void QGraphicsWidget::setWindowFlags(Qt::WindowFlags wFlags) + \endcode + + A topic command can appear anywhere in a comment but must stand + alone on its own line. It is good practice is to let the topic command + be the first line of the comment. If the argument spans several + lines, make sure that each line (except the last one) is ended + with a backslash. Moreover, QDoc counts parentheses, which means + that if it encounters a '(' it considers everything until the + closing ')' as its argument. + + If a topic command is repeated with different arguments, the + same documentation will appear for both the units. + + \code + / *! + \fn void PreviewWindow::setWindowFlags() + \fn void ControllerWindow::setWindowFlags() + + Sets the widgets flags using the QWidget::setWindowFlags() + function. + + Then runs through the available window flags, creating a text + that contains the names of the flags that matches the flags + parameter, displaying the text in the widgets text editor. + * / + \endcode + + The \c PreviewWindow::setWindowFlags() and \c + ControllerWindow::setWindowFlags() functions will get the same + documentation. + + \target class-command + \section1 \\class + + The \\class command is for documenting a C++ class. The argument + is the complete name of the class. The command tells QDoc that a + class is part of the public API, and lets you enter a detailed + description. + + \code + / *! + \class QMap::iterator + + \brief The QMap::iterator class provides an STL-style + non-const iterator for QMap and QMultiMap. + + QMap features both \l{STL-style iterators} and + \l{Java-style iterators}. The STL-style iterators ... + * / + \endcode + + The HTML documentation for the named class is written to a + \c{.html} file named from the class name, in lower case, and with + the double colon qualifier(s) replaced with '-'. For example, the + documentation for the \c QMap::Iterator class is written to \c + qmap-iterator.html. + + \target framework + + The file contains the class description from the \\class comment, + plus the documentation generated from QDoc comments for all the + class members: a list of the class's types, properties, + functions, signals, and slots. + + In addition to the detailed description of the class, the \\class + comment typically contains a \l {brief-command} {\\brief} command + and one or more \l{Markup Commands}. See the \\class command for + any of the Qt class for examples. Here is a very simple example: + + \code + / *! + \class PreviewWindow + \brief The PreviewWindow class is a custom widget. + displaying the names of its currently set + window flags in a read-only text editor. + + \ingroup miscellaneous + + The PreviewWindow class inherits QWidget. The widget + displays the names of its window flags set with the \l + {function} {setWindowFlags()} function. It is also + provided with a QPushButton that closes the window. + + ... + + \sa QWidget + * / + \endcode + + The way QDoc renders this \\class will depend a lot on your \c + {style.css} file, but the general outline of the class reference + page will look like this: + + \quotation + \raw HTML + <h1>PreviewWindow Class Reference</h1> + \endraw + + The PreviewWindow class is a custom widget displaying + the names of its currently set window flags in a + read-only text editor. \l {preview window} {More...} + + \raw HTML + <h3>Properties</h3> + \endraw + + \list + \li 52 properties inherited from QWidget + \li 1 property inherited from QObject + \endlist + + \raw HTML + <h3>Public Functions</h3> + \endraw + + \list + \li \l {constructor} {PreviewWindow}(QWidget *parent = 0) + \li void \l {function} {setWindowFlags}(Qt::WindowFlags flags) + \endlist + + \list + \li 183 public functions inherited from QWidget + \li 28 public functions inherited from QObject + \endlist + + \raw HTML + <h3>Public Slots</h3> + \endraw + + \list + \li 17 public slots inherited from QWidget + \li 1 public slot inherited from QObject + \endlist + + \raw HTML + <h3>Additional Inherited Members</h3> + \endraw + + \list + \li 1 signal inherited from QWidget + \li 1 signal inherited from QObject + \li 4 static public members inherited from QWidget + \li 4 static public members inherited from QObject + \li 39 protected functions inherited from QWidget + \li 7 protected functions inherited from QObject + \endlist + + \target preview window + + \raw HTML + <hr /> + <h2>Detailed Description</h2> + \endraw + + The PreviewWindow class is a custom widget displaying + the names of its currently set window flags in a + read-only text editor. + + The PreviewWindow class inherits QWidget. The widget + displays the names of its window flags set with the \l + {function} {setWindowFlags()} function. It is also + provided with a QPushButton that closes the window. + + ... + + See also QWidget. + + \raw HTML + <hr /> + <h2>Member Function Documentation</h2> + \endraw + + \target constructor + \raw HTML + <h3>PreviewWindow(QWidget *parent = 0)</h3> + \endraw + + Constructs a preview window widget with \e parent. + + \target function + \raw HTML + <h3>setWindowFlags(Qt::WindowFlags flags)</h3> + \endraw + + Sets the widgets flags using the + QWidget::setWindowFlags() function. + + Then runs through the available window flags, + creating a text that contains the names of the flags + that matches the flags parameter, displaying + the text in the widgets text editor. + \endquotation + + \target enum-command + \section1 \\enum + + The \\enum command is for documenting a C++ enum type. The + argument is the full name of the enum type. + + The enum values are documented in the \\enum comment using the \l + {value-command} {\\value} command. If an enum value is not + documented with \\value, QDoc emits a warning. These warnings can + be avoided using the \l {omitvalue-command} {\\omitvalue} command + to tell QDoc that an enum value should not be documented. The enum + documentation will be included on the class reference page, header + file page, or namespace page where the enum type is defined. For + example, consider the enum type \c {Corner} in the Qt namespace: + + \code + enum Corner { + TopLeftCorner = 0x00000, + TopRightCorner = 0x00001, + BottomLeftCorner = 0x00002, + BottomRightCorner = 0x00003 + #if defined(QT3_SUPPORT) && !defined(Q_MOC_RUN) + ,TopLeft = TopLeftCorner, + TopRight = TopRightCorner, + BottomLeft = BottomLeftCorner, + BottomRight = BottomRightCorner + #endif + }; + \endcode + + This enum can be cocumented this way: + + \code + / *! + \enum Qt::Corner + + This enum type specifies a corner in a rectangle: + + \value TopLeftCorner + The top-left corner of the rectangle. + \value TopRightCorner + The top-right corner of the rectangle. + \value BottomLeftCorner + The bottom-left corner of the rectangle. + \value BottomRightCorner + The bottom-right corner of the rectangle. + + \omitvalue TopLeft + \omitvalue TopRight + \omitvalue BottomLeft + \omitvalue BottomRight + * / + \endcode + + Note the inclusion of the namespace qualifier. QDoc will render + this enum type in \c {qt.html} like this: + + \quotation + \raw HTML + <h3 class="fn"><a name="Corner-enum"></a>enum Qt::Corner</h3> + + <p>This enum type specifies a corner in a rectangle:</p> + + <table border="1" cellpadding="2" cellspacing="1" width="100%"> + <tr> + <th width="25%">Constant</th> + <th width="15%">Value</th> + <th width="60%">Description</th> + </tr> + + <tr> + <td valign="top"><tt>Qt::TopLeftCorner</tt></td> + <td align="center" valign="top"><tt>0x00000</tt></td> + <td valign="top">The top-left corner of the rectangle.</td> + </tr> + + <tr> + <td valign="top"><tt>Qt::TopRightCorner</tt></td> + <td align="center" valign="top"><tt>0x00001</tt></td> + <td valign="top">The top-right corner of the rectangle.</td> + </tr> + + <tr> + <td valign="top"><tt>Qt::BottomLeftCorner</tt></td> + <td align="center" valign="top"><tt>0x00002</tt></td> + <td valign="top">The bottom-left corner of the rectangle.</td> + </tr> + + <tr> + <td valign="top"><tt>Qt::BottomRightCorner</tt></td> + <td align="center" valign="top"><tt>0x00003</tt></td> + <td valign="top">The bottom-right corner of the rectangle.</td> + </tr> + + </table> + \endraw + \endquotation + + See also \l {value-command} {\\value} and \l {omitvalue-command} {\\omitvalue}. + + \target example-command + \section1 \\example + + The \\example command is for documenting an example. The argument + is the example's path relative to omne of the paths listed in the + \l {exampledirs-variable} {exampledirs} variable in the QDoc + configuration file. + + The documentation page will be output to \c {path-to-example}.html. + QDoc will add a list of all the example's source files at the top + of the page. + + For example, if \l {exampledirs-variable} {exampledirs} contains + \c $QTDIR/examples/widgets/imageviewer, then + + \code + / *! + \example widgets/imageviewer + \title ImageViewer Example + \subtitle + + The example shows how to combine QLabel and QScrollArea + to display an image. + + ... + * / + \endcode + + QDoc renders this example in widgets-imageviewer.html: + + \quotation + \raw HTML + <center><h1>Image Viewer Example</h1></center> + \endraw + + Files: + \list + \li \l{http://doc.qt.io/qt-5/qtwidgets-widgets-imageviewer-imageviewer-cpp.html} + {widgets/imageviewer/imageviewer.cpp} + \li \l{http://doc.qt.io/qt-5/qtwidgets-widgets-imageviewer-imageviewer-h.html} + {widgets/imageviewer/imageviewer.h} + \li \l{http://doc.qt.io/qt-5/qtwidgets-widgets-imageviewer-main-cpp.html} + {widgets/imageviewer/main.cpp} + \endlist + + The example shows how to combine QLabel and QScrollArea + to display an image. + + ... + \endquotation + + \target externalpage-command + \section1 \\externalpage + + The \\externalpage command assigns a title to an external URL. + + \code + / *! + \externalpage http://doc.qt.io/ + \title Qt Documentation Site + * / + \endcode + + This allows you to include a link to the external page in your + documentation this way: + + \code + / *! + At the \l {Qt Documentation Site} you can find the latest + documentation for Qt, Qt Creator, the Qt SDK and much more. + * / + \endcode + + QDoc renders this as: + + \quotation + At the \l {http://doc.qt.io/}{Qt Documentation Site} + you can find the latest documentation for Qt, Qt Creator, the Qt SDK + and much more. + \endquotation + + To achieve the same result without using the \\externalpage + command, you would have to hard-code the address into your + documentation: + + \code + / *! + At the \l {http://doc.qt.io/}{Qt Documentation Site} + you can find the latest documentation for Qt, Qt Creator, the Qt SDK + and much more. + * / + \endcode + + The \\externalpage command makes it easier to maintain the + documentation. If the address changes, you only need to change the + argument of the \\externalpage command. + + \target fn-command + \section1 \\fn (function) + + The \\fn command is for documenting a function. The argument is + the function's signature, including its return type, const-ness, + and list of formal arguments with types. If the named function + doesn't exist, QDoc emits a warning. + + \note The \\fn command is QDoc's default command: when no + topic command can be found in a QDoc comment, QDoc tries to tie + the documentation to the following code as if it is the + documentation for a function. Hence, it is normally not necessary + to include this command when documenting a function, if the + function's QDoc comment is written immediately above the function + implementation in the \c .cpp file. But it must be present when + documenting an inline function in the \c .cpp file that is + implemented in the \c .h file. + + \code + / *! + \fn bool QToolBar::isAreaAllowed(Qt::ToolBarArea area) const + + Returns \c true if this toolbar is dockable in the given + \a area; otherwise returns \c false. + * / + \endcode + + QDoc renders this as: + + \quotation + \raw HTML + <h3>bool QToolBar::isAreaAllowed(Qt::ToolBarArea area) const + </h3> + \endraw + + Returns \c true if this toolbar is dockable in the given + \a area; otherwise returns \c false. + \endquotation + + See also \l {overload-command} {\\overload}. + + \target group-command + \section1 \\group + + The \\group command creates a separate page that lists the classes + belonging to the group. The argument is the group name. + + A class is included in a group by using the \l {ingroup-command} + {\\ingroup} command. Overview pages can also be related to a group + using the same command, but the list of overview pages must be + requested explicitly using the \l {generatelist-command} + {\\generatelist} command (see example below). + + The \\group command is typically followed by a \l {title-command} + {\\title} command and a short introduction to the group. The + HTML page for the group is written to a \c {.html} file put in + <lower-case>\e{group}.html. + + Each class name is listed as a link to the class reference page + followed by the text from the class's \l {brief-command} {\\brief} + texts. + + \code + / *! + \group io + + \title Input/Output and Networking + + These classes are used to handle input and output to + and from external devices, processes, files etc., as + well as manipulating files and directories. + * / + \endcode + + QDoc generates a group page in \c{io.html} that will look + like this: + + \quotation + \raw HTML + + <h1>Input/Output and Networking</h1> + + <p>These classes are used to handle input and output + to and from external devices, processes, files etc., as + well as manipulating files and directories.</p> + + <p> + <table width="100%"> + <tr valign="top" bgcolor="#e0e0e0"> + <td><b> + <a href="http://doc.qt.io/qt-5/qabstractsocket.html">QAbstractSocket</a> + </b></td> + <td> + The base functionality common to all socket types + </td></tr> + + <tr valign="top" bgcolor="#e0e0e0"> + <td><b> + <a href="http://doc.qt.io/qt-5/qbuffer.html">QBuffer</a> + </b></td> + <td> + QIODevice interface for a QByteArray + </td></tr> + + <tr valign="top" bgcolor="#e0e0e0"> + <td><b> + <a href="http://doc.qt.io/qt-5/qclipboard.html">QClipboard</a> + </b></td> + <td> + Access to the window system clipboard + </td></tr> + </table> + \endraw + \endquotation + + Note that overview pages related to the group, must be listed + explicitly using the \l {generatelist-command} {\\generatelist} + command with the \c related argument. + + \code + / *! + \group architecture + + \title Architecture + + These documents describe aspects of Qt's architecture + and design, including overviews of core Qt features and + technologies. + + \generatelist{related} + * / + \endcode + + See also \l {ingroup-command} {\\ingroup} and \l + {generatelist-command} {\\generatelist}. + + \target headerfile-command + \section1 \\headerfile + + The \\headerfile command is for documenting the global functions, + types and macros that are declared in a header file, but not in a + namespace. The argument is the name of the header file. The HTML + page is written to a \c {.html} file constructed from the header + file argument. + + The documentation for a function, type, or macro that is declared + in the header file being documented, is included in the header file + page using the \l {relates-command} {\\relates} command. + + If the argument doesn't exist as a header file, the \\headerfile + command creates a documentation page for the header file anyway. + + \code + / *! + \headerfile <QtAlgorithms> + + \title Generic Algorithms + + \brief The <QtAlgorithms> header file provides + generic template-based algorithms. + + Qt provides a number of global template functions in \c + <QtAlgorithms> that work on containers and perform + well-know algorithms. + * / + \endcode + + QDoc generates a header file page \c{qtalgorithms.html} that looks + like this: + + \quotation + \raw HTML + <center><h1><QtAlgorithms> - + Generic Algorithms</h1></center> + <p>The <QtAlgorithms> header file provides generic + template-based algorithms. + <a href="13-qdoc-commands-topics.html#header-command">More...</a> + </p> + + <h3>Functions</h3> + <ul> + <li>RandomAccessIterator + <a href="http://doc.qt.io/qt-5/qtalgorithms-obsolete.html#qBinaryFind">qBinaryFind</a></b> + (RandomAccessIterator begin, RandomAccessIterator end, + const T & value)</li> + <li>...</li></ul> + <hr /> + \endraw + + \target header + + \raw HTML + <h2>Detailed Description</h2> + <p>The <QtAlgorithms> header file provides generic + template-based algorithms. </p> + \endraw + + Qt provides a number of global template functions in \c + <QtAlgorithms> that work on containers and perform + well-know algorithms. + + ... + \endquotation + + \target macro-command + \section1 \\macro + + The \\macro command is for documenting a C++ macro. The argument + is the macro in one of three styles: function-like macros like + Q_ASSERT(), declaration-style macros like Q_PROPERTY(), and macros + without parentheses like Q_OBJECT. + + The \\macro comment must contain a \l {relates-command} + {\\relates} command that attaches the macro comment to a class, + header file, or namespace. Otherwise, the documentation will be + lost. Here are three example macro comments followed by what they + might look like in \c {qtglobal.html} or \c {qobject.html}: + + \code + / *! + \macro void Q_ASSERT(bool test) + \relates <QtGlobal> + + Prints a warning message containing the source code + file name and line number if \a test is false. + + ... + + \sa Q_ASSERT_X(), qFatal(), {Debugging Techniques} + * / + \endcode + + \quotation + \raw HTML + <h3>void Q_ASSERT ( bool <i>test</i> )</h3> + \endraw + + Prints a warning message containing the source code + file name and line number if \a test is false. + + ... + + See also Q_ASSERT_X(), qFatal() and \l {Debugging Techniques}. + + \endquotation + + \code + / *! + \macro Q_PROPERTY(...) + \relates QObject + + This macro declares a QObject property. The syntax is: + + ... + + \sa {Qt's Property System} + * / + \endcode + + \quotation + \raw HTML + <h3>Q_PROPERTY ( ... )</h3> + \endraw + + This macro declares a QObject property. The syntax is: + + ... + + See also \l {Qt's Property System}. + \endquotation + + \code + / *! + \macro Q_OBJECT + \relates QObject + + The Q_OBJECT macro must appear in the private section + of a class definition that declares its own signals and + slots, or that uses other services provided by Qt's + meta-object system. + + ... + + \sa {Meta-Object System}, {Signals and Slots}, {Qt's + Property System} + * / + \endcode + + \quotation + \raw HTML + <h3>Q_OBJECT</h3> + \endraw + + The Q_OBJECT macro must appear in the private section + of a class definition that declares its own signals and + slots or that uses other services provided by Qt's + meta-object system. + + ... + + See also \l {Meta-Object System}, \l {Signals & + Slots} and \l {Qt's Property System}. + \endquotation + + \target module-command + \section1 \\module + + The \\module creates a page that lists the classes belonging to + the module specified by the command's argument. A class included + in the module by including the \l {inmodule-command} {\\inmodule} + command in the \\class comment. + + The \\module command is typically followed by a \l {title-command} + {\\title} and a \l {brief-command} {\\brief} command. Each class + is listed as a link to the class reference page followed by the + text from the class's \l {brief-command} {\\brief} command. For + example: + + \code + / *! + \module QtNetwork + + \title Qt Network Module + + \brief Contains classes for writing TCP/IP clients and servers. + + The network module provides classes to make network + programming easier and portable. It offers both + high-level classes such as QNetworkAccessManager that + implements application-level protocols, and + lower-level classes such as QTcpSocket, QTcpServer, and + QUdpSocket. + * / + \endcode + + QDoc renders this in \c {qtnetwork.html} like this: + + \quotation + \raw HTML + <h1><center>Qt Network Module</center></h1> + \endraw + + The Qt Network module offers classes that allow you to + write TCP/IP clients and servers.\l {module + details} {More...} + + \raw HTML + <p> + <table width="100%"> + <tr valign="top" bgcolor="#d0d0d0"> + <td><b> + <a href="http://doc.qt.io/qt-5/qabstractsocket.html">QAbstractSocket</a> + </b></td> + <td> + The base functionality common to all socket types + </td></tr> + + <tr valign="top" bgcolor="#d0d0d0"> + <td><b> + <a href="http://doc.qt.io/archives/qt-4.7/qftp.html">QFtp</a> + </b></td> + <td> + Implementation of the FTP protocol + </td></tr> + + <tr valign="top" bgcolor="#d0d0d0"> + <td>...</td> + <td>...</td> + </tr> + </table> + + <p><hr /></p> + \endraw + + \target module details + + \raw HTML + <h2>Detailed Description</h2> + + <p> + The Qt Network module offers classes that allow you to + write TCP/IP clients and servers. + </p> + + <p> + The network module provides classes to make network + programming easier and portable. It offers both + high-level classes such as QNetworkAccessManager that + implements application-level protocols, and + lower-level classes such as QTcpSocket, QTcpServer, and + QUdpSocket. + </p> + \endraw + + ... + + \endquotation + + The \l {noautolist-command} {\\noautolist} command can be used here + to omit the automatically generated list of classes at the end. + + See also \l {inmodule-command} {\\inmodule} + + \target namespace-command + \section1 \\namespace + + The \\namespace command is for documenting the contents of the C++ + namespace named as its argument. The documentation outline QDoc + generates for a namespace is similar to the outline it generates + for a C++ class. + + \code + / *! + \namespace Qt + + \brief Contains miscellaneous identifiers used throughout the Qt library. + * / + \endcode + + QDoc renders this in \c{qt.html} like this: + + \quotation + \raw HTML + <center><h1>Qt Namespace Reference</h1></center> + <p>The Qt namespace contains miscellaneous + identifiers used throughout the Qt library. + <a href="13-qdoc-commands-topics.html#name">More...</a> + </p> + + <pre>#include <Qt></pre> + <ul> + <li> + <a href="http://doc.qt.io/archives/qt-4.7/qt-qt3.html"> + Qt 3 support members</a></li> + </ul> + + + <h3>Types</h3> + <ul> + <li>flags + <a href="http://doc.qt.io/archives/qt-4.7/qt.html#AlignmentFlag-enum">Alignment</a></b></li> + <li>...</li></ul> + <hr /> + \endraw + + \target name + + \raw HTML + <h2>Detailed Description</h2> + <p>Contains miscellaneous identifiers + used throughout the Qt library.</p> + \endraw + + ... + \endquotation + + \target page-command + \section1 \\page + + The \\page command is for creating a stand-alone documentation + page. The argument can consist of two parts separated by a + space. The first part is the name of the file where QDoc should + store the page. The second part, if present, is a word that + specifies the page type. Currently, the second part can be one of + the following list of words: + + \list + + \li faq - A frequently asked question. + + \li howto - A user guide on how to use some components of the + software. + + \li example - A page that describes a working example. + + \li overview - For text pages that provide an overview of some + important subject. + + \li tutorial - For text pages that are part of a tutorial. + + \li api - This is the type of page used for C++ class references and + QML type references. You should never use this one for the pages + you write, because this one is reserved for qdoc. + + \endlist + + The page title is set using the \l {title-command} {\\title} + command. + + \code + / *! + \page aboutqt.html + + \title About Qt + + Qt is a C++ toolkit for cross-platform GUI + application development. Qt provides single-source + portability across Microsoft Windows, OS X, Linux, + and all major commercial Unix variants. + + Qt provides application developers with all the + functionality needed to build applications with + state-of-the-art graphical user interfaces. Qt is fully + object-oriented, easily extensible, and allows true + component programming. + + ... + * / + \endcode + + QDoc renders this page in \c {aboutqt.html}. + + \target property-command + \section1 \\property + + The \\property command is for documenting a Qt property. The + argument is the full property name. + + A property is defined using the Q_PROPERTY() macro. The macro + takes as arguments the property's name and its set, reset and get + functions. + + \code + Q_PROPERTY(QString state READ state WRITE setState) + \endcode + + The set, reset and get functions don't need to be documented, + documenting the property is sufficient. QDoc will generate a list + of the access function that will appear in the property + documentation which in turn will be located in the documentation + of the class that defines the property. + + The \\property command comment typically includes a \l + {brief-command} {\\brief} command. For properties the \l + {brief-command} {\\brief} command's argument is a sentence + fragment that will be included in a one line description of the + property. The command follows the same rules for the \l + {brief-property} {description} as the \l {variable-command} + {\\variable} command. + + \code + / *! + \property QPushButton::flat + \brief Whether the border is disabled. + + This property's default is false. + * / + \endcode + + QDoc includes this in \c {qpushbutton.html} like this: + + \quotation + \raw HTML + <h3>flat : bool</h3> + \endraw + + This property holds whether the border is disabled. + + This property's default is false. + + Access functions: + + \list + \li \b { bool isFlat () const} + \li \b { void setFlat ( bool )} + \endlist + + \endquotation + + \code + / *! + \property QWidget::width + \brief The width of the widget excluding any window frame. + + See the \l {Window Geometry} documentation for an + overview of window geometry. + + \sa geometry, height, size + * / + \endcode + + QDoc includes this in \c {qwidget.html} like this: + + \quotation + \raw HTML + <h3>width : const int</h3> + \endraw + + This property holds the width of the widget excluding + any window frame. + + See the \l {Window Geometry} documentation for an + overview of window geometry. + + Access functions: + + \list + \li \b { int width () const} + \endlist + + See also \l{QWidget::geometry} {geometry}, + \l{QWidget::height} {height}, and \l{QWidget::size} {size}. + \endquotation + + \target qmlattachedproperty-command + \section1 \\qmlattachedproperty + + The \\qmlattachedproperty command is for documenting a QML + property that will be attached to some QML type. See + \l{http://qt-project.org/doc/qt-4.7/qdeclarativeintroduction.html#attached-properties} + {Attached Properties}. The argument is the rest of the line. The + argument text should be the property type, followed by the QML + element name where the property is being declared, the \c{::} + qualifier, and finally the property name. If we have a QML + attached property named \c isCurrentItem in QML \c ListView, + and the property has type \c {bool}, the \\qmlattachedproperty for + it would look like this: + + \code + / *! + \qmlattachedproperty bool ListView::isCurrentItem + This attached property is \c true if this delegate is the current + item; otherwise false. + + It is attached to each instance of the delegate. + + This property may be used to adjust the appearance of the current + item, for example: + + \snippet doc/src/snippets/declarative/listview/listview.qml isCurrentItem + * / + \endcode + + QDoc includes this attached property on the QML reference page for the + \l{http://qt-project.org/doc/qt-4.7/qml-listview.html#isCurrentItem-prop} + {ListView} element. + + \target qmlattachedsignal-command + \section1 \\qmlattachedsignal + + The \\qmlattachedsignal command is for documenting an attachable + \l{Signal and Handler Event System}{signal}. The \\qmlattachedsignal + command is used just like the \l{qmlsignal-command} {\\qmlsignal} command. + + The argument is the rest of the line. It should be the name of the + QML type where the signal is declared, the \c{::} + qualifier, and finally the signal name. For example, a QML + attached signal named \c add() in the \c GridView + element is documented like this: + + \code + / *! + \qmlattachedsignal GridView::add() + This attached signal is emitted immediately after an item is added to the view. + * / + \endcode + + QDoc includes this documentation on the QML reference page for the + \l GridView element. + + \target qmlbasictype-command + \section1 \\qmlbasictype + + The \\qmlbasictype command is for documenting a basic type for QML. + The argument is the type name. The type must be included in the + QML basic types group using the \l{ingroup-command}{\\ingroup} + command as shown below. This will cause QDoc to include the + documentation for the type on the + \l{http://qt-project.org/doc/qt-4.7/qdeclarativebasictypes.html} + {QML Basic Types} page. The \l{brief-command} {\\brief} command + is also required, because it appears on the + \l{http://qt-project.org/doc/qt-4.7/qdeclarativebasictypes.html} + {QML Basic Types} page as well. + + \code + / *! + \qmlbasictype int + \ingroup qmlbasictypes + + \brief An integer is a whole number, for example 0, 10, or -20. + + An integer is a whole number, e.g. 0, 10, or -20. The possible + \c int values range from around -2000000000 to around + 2000000000, although most elements will only accept a reduced + range (which they mention in their documentation). + + Example: + \qml + Item { width: 100; height: 200 } + \endqml + + \sa {QML Basic Types} + * / + \endcode + + QDoc outputs this as \l{http://qt-project.org/doc/qt-4.7/qml-int.html} + {qml-int.html}. + + \target qmlclass-command + \section1 \\qmlclass + + This command is deprecated. Use \l{qmltype-command} {\\qmltype} + instead. + + The \\qmlclass command is for documenting a QML type that is + instantiated by a C++ class. The command has two arguments. The + first argument is the name of the QML type. The second argument + is the name of the C++ class that instantiates the QML type. + + \code + / *! + \qmlclass Transform QGraphicsTransform + \ingroup qml-transform-elements + \since 4.7 + \brief Provides a way of building advanced transformations on Items. + + The Transform element is a base type which cannot be + instantiated directly. The following concrete Transform types + are available: + + \list + \li \l Rotation + \li \l Scale + \li \l Translate + \endlist + + The Transform elements let you create and control advanced + transformations that can be configured independently using + specialized properties. + + You can assign any number of Transform elements to an \l + Item. Each Transform is applied in order, one at a time. + + * / + \endcode + + This example generates the + \l {http://qt-project.org/doc/qt-4.7/qml-transform.html} {QML Transform} + page. The \\qmlclass comment should include the \l + {since-command} {\\since} command, because all QML types are + new. It should also include the \l{brief-command} {\\brief} + command. If a type is a member of a group of QML + types, it should also include one or more \l{ingroup-command} + {\\ingroup} commands. + + \target qmlmethod-command + \section1 \\qmlmethod + + The \\qmlmethod command is for documenting a QML method. The + argument is the complete method signature, including return + type and parameter names and types. + + \code + / *! + \qmlmethod void TextInput::select(int start, int end) + + Causes the text from \a start to \a end to be selected. + + If either start or end is out of range, the selection is not changed. + + After having called this, selectionStart will become the lesser, and + selectionEnd the greater (regardless of the order passed to this method). + + \sa selectionStart, selectionEnd + * / + \endcode + + QDoc includes this documentation on the element reference page for the + \l{http://qt-project.org/doc/qt-4.7/qml-textinput.html#select-method} + {TextInput} element. + + \target qmltype-command + \section1 \\qmltype + + The \\qmltype command is for documenting a QML type. The command + has one argument, which is the name of the QML type. + + If the QML type is instantiated by a C++ class, that class must be + specified using the \l{instantiates-command} {\\instantiates} + context command. + + \code + / *! + \qmltype Transform + \instantiates QGraphicsTransform + \ingroup qml-transform-elements + \since 4.7 + \brief The Transform elements provide a way to build + advanced transformations on Items. + + The Transform element is a base type which cannot be + instantiated directly. The concrete Transform types are: + + \list + \li \l Rotation + \li \l Scale + \li \l Translate + \endlist + + The Transform elements let you create and control advanced + transformations that can be configured independently using + specialized properties. + + You can assign any number of Transform elements to an \l + Item. Each Transform is applied in order, one at a time. + + * / + \endcode + + The example generates the \l + {http://qt-project.org/doc/qt-4.7/qml-transform.html} {QML Transform} + page. The \e{\\qmltype} comment includes \l{instantiates-command} + {\\instantiates} to specify that a Transform is instantiated by + the C++ class QGraphicsTransform. A \\qmltype comment should + always include a \l {since-command} {\\since} command, because all + QML types are new. It should also include a \l{brief-command} + {\\brief} description. If a QML type is a member of a QML type group, + the \\qmltype comment should include one or more \l{ingroup-command} + {\\ingroup} commands. + + + \target qmlproperty-command + \section1 \\qmlproperty + + The \\qmlproperty command is for documenting a QML property. The + argument is the rest of the line. The argument text should be the + property type, followed by the QML type name, the \c{::} + qualifier, and finally the property name. If we have a QML + property named \c x in QML type \c Translate, and the property + has type \c {real}, the \\qmlproperty for it would look like this: + + \code + / *! + \qmlproperty real Translate::x + + The translation along the X axis. + * / + \endcode + + QDoc includes this QML property on the QML reference page for the + \l {http://qt-project.org/doc/qt-4.7/qml-translate.html} {Translate} + element. + + If the QML property is of enumeration type, or it holds a bit-wise + combination of flags, the \l{value-command}{\\value} command can + be used to document the acceptable values. + + \target qmlsignal-command + \section1 \\qmlsignal + + The \\qmlsignal command is for documenting a QML signal. + The argument is the rest of the line. The arguments should be: the QML type + where the signal is declared, the \c{::} qualifier, and finally the signal + name. If we have a QML signal named \c clicked(), the documentation for it + would look like this: + + \code + / *! + \qmlsignal UIComponents::Button::clicked() + This signal is emitted when the user clicks the button. A click is defined + as a press followed by a release. The corresponding handler is + \c onClicked. + * / + \endcode + + QDoc includes this documentation on the QML reference page for the + \l{http://qt-project.org/doc/qt-4.7/qml-mousearea.html#onEntered-signal} + {MouseArea} element. + + \target qmlmodule-command + \section1 \\qmlmodule + + Insert the \c{\\qmlmodule} command to create a \c QML module page. A QML + module is a collection of QML types or any related material. This + command is similar to the \l{group-command}. + + A QML class may belong to a module by inserting the + \l{inqmlmodule-command}{\\inqmlmodule} command as a topic command. + Every member of a group must be linked to using the module name and two + colons (\c{::}). + + \code + \beginqdoc + A link to the TabWidget of the UI Component is \l {UIComponent::TabWidget}. + \endqdoc + \endcode + + QDoc will generate a page for the module with a listing of the members + of the module. + + \code + \qmlmodule ClickableComponents + + This is a list of the Clickable Components set. A Clickable component + responds to a \c clicked() event. + \endcode + + The \l{componentset}{UIComponents} example demonstrates proper usage of + QDoc commands to document QML types and QML modules. + + \target inqmlmodule-command + \section1 \\inqmlmodule + + A QML class may belong to a \l{qmlmodule-command}{QML module} by inserting + the \l{inqmlmodule-command}{\\inqmlmodule} command as a topic command, with + the module name (without a version number) as the only argument. Every + member of a group must be linked to using the module name and two colons + (\c{::}). + + \code + \qmltype ClickableButton + \inqmlmodule ClickableComponents + + A clickable button that responds to the \c click() event. + \endcode + + To link to the \c ClickableButton, use the + \c{\l ClickableComponents::ClickableButton} format. + + The \l{componentset}{UIComponents} example demonstrates proper usage of + QDoc commands to document QML types and QML modules. + + The \l {noautolist-command} {\\noautolist} command can be used here + to omit the automatically generated list of types at the end. + + \target instantiates-command + \section1 \\instantiates + + The \\instantiates command is used in the \l{qmltype-command} {QML + type} comment of an elemental QML type to specify the name of the + C++ class that instantiates the QML type. + + If the QML type is not instantiated by a C++ class, this command + is not used. + + \code + / *! + \qmltype Transform + \instantiates QGraphicsTransform + \ingroup qml-transform-elements + \since 4.7 + \brief Provides elements provide a way to build + advanced transformations on Items. + + The Transform element is a base type which cannot be + instantiated directly. + * / + \endcode + + The example generates the \l + {http://qt-project.org/doc/qt-4.7/qml-transform.html} {QML Transform} + page. The \e{\\qmltype} comment includes \l{instantiates-command} + {\\instantiates} to specify that a Transform is instantiated by + the C++ class QGraphicsTransform. A \\qmltype comment should + + \target typedef-command + \section1 \\typedef + + The \\typedef command is for documenting a C++ typedef. The + argument is the name of the typedef. The documentation for + the typedef will be included in the reference documentation + for the class, namespace, or header file in which the typedef + is declared. To relate the \\typedef to a class, namespace, or + header file, the \\typedef comment must contain a + \l {relates-command} {\\relates} command. + + \code + / *! + \typedef QObjectList + \relates QObject + + Synonym for QList<QObject>. + * / + \endcode + + QDoc includes this in \c {qobject.html} as: + + \quotation + \raw HTML + <h3>typedef QObjectList</h3> + \endraw + + Synonym for QList<QObject>. + \endquotation + + Another, although more rare, example: + + \code + / *! + \typedef QMsgHandler + \relates QtGlobal + + This is a typedef for a pointer to a function with the + following signature: + + \code + void myMsgHandler(QtMsgType, const char *); + \ endcode + + \sa QtMsgType, qInstallMsgHandler() + * / + \endcode + + QDoc includes this in \c {qtglobal.html} as: + + \quotation + \raw HTML + <h3>typedef QtMsgHandler</h3> + \endraw + + This is a typedef for a pointer to a function with the + following signature: + + \raw HTML + <tt> + <pre> void myMsgHandler(QtMsgType, const char *);</pre> + </tt> + \endraw + + See also QtMsgType and qInstallMsgHandler(). + \endquotation + + Other typedefs are located on the reference page for the class + that defines them. + + \code + / *! + \typedef QLinkedList::Iterator + + Qt-style synonym for QList::iterator. + * / + \endcode + + QDoc includes this one on the reference page for class QLinkedList as: + + \quotation + \raw HTML + <h3>typedef QLinkedList::Iterator</h3> + \endraw + + Qt-style synonym for QList::iterator. + \endquotation + + \target variable-command + \section1 \\variable + + The \\variable command is for documenting a class member variable + or a constant. The argument is the variable or constant name. The + \\variable command comment includes a \l {brief-command} {\\brief} + command. QDoc generates the documentation based on the text from + \\brief command. + + The documentation will be located in the in the associated class, + header file, or namespace documentation. + + In case of a member variable: + + \code + / *! + \variable QStyleOption::palette + \brief The palette that should be used when painting + the control + * / + \endcode + + QDoc includes this in qstyleoption.html as: + + \quotation + \raw HTML + <h3> + <a href="http://doc.qt.io/qt-5/qpalette.html"> + QPalette + </a> + QStyleOption::palette + </h3> + \endraw + + This variable holds the palette that should be used + when painting the control. + \endquotation + + You can also document constants with the \\variable command. For + example, suppose you have the \c Type and \c UserType constants in + the QTreeWidgetItem class: + + \code + enum { Type = 0, UserType = 1000 }; + \endcode + + For these, the \\variable command can be used this way: + + \code + / *! + \variable QTreeWidgetItem::Type + + The default type for tree widget items. + + \sa UserType, type() + * / + \endcode + \code + / *! + \variable QTreeWidgetItem::UserType + + The minimum value for custom types. Values below + UserType are reserved by Qt. + + \sa Type, type() + * / + \endcode + + QDoc includes these in qtreewidget.html as: + + \quotation + \raw HTML + <h3> + const int QTreeWidgetItem::Type + </h3> + \endraw + + The default type for tree widget items. + + See also \l {QTreeWidgetItem::UserType} {UserType} and \l + {QTreeWidgetItem::type()} {type()}. + + \raw HTML + <h3> + const int QTreeWidgetItem::UserType + </h3> + \endraw + + The minimum value for custom types. Values below + UserType are reserved by Qt. + + See also \l {QTreeWidgetItem::Type} {Type} and + \l{QTreeWidgetItem::type()} {type()}. + + \endquotation +*/ diff --git a/src/qdoc/doc/qdoc-manual.qdoc b/src/qdoc/doc/qdoc-manual.qdoc new file mode 100644 index 000000000..a0e35c2e9 --- /dev/null +++ b/src/qdoc/doc/qdoc-manual.qdoc @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page qdoc-index.html + \nextpage Introduction to QDoc + + \title QDoc Manual + + \list + \li \l {Introduction to QDoc} + \li \l {Getting Started with QDoc} + \li \l {Command Index} + \li \l {Topic Commands} + \li \l {Context Commands} + \list + \li \l {Document Navigation} + \li \l {Status} + \li \l {Thread Support} + \li \l {Relating Things} + \li \l {Grouping Things} + \li \l {Naming Things} + \endlist + \li \l{Markup Commands} + \list + \li \l {Text Markup} + \li \l {Document Structure} + \li \l {Including Code Inline} + \li \l {Including External Code} + \li \l {Creating Links} + \li \l {Including Images} + \li \l {Tables and Lists} + \li \l {Special Content} + \li \l {Miscellaneous} + \endlist + \li \l{Creating DITA Maps} + \li \l {The QDoc Configuration File} + \list + \li \l {Generic Configuration Variables} + \li \l {Creating Help Project Files} + \li \l {C++ Specific Configuration Variables} + \li \l {HTML Specific Configuration Variables} + \li \l {Supporting Derived Projects} + \li \l {Example Manifest Files} + \li \l {qtgui.qdocconf} + \li \l {minimum.qdocconf} + \li \l {Generating DITA XML Output} + \endlist + \li \l {QA Pages} + \endlist + +*/ + + diff --git a/src/qdoc/doc/qdoc-minimum-qdocconf.qdoc b/src/qdoc/doc/qdoc-minimum-qdocconf.qdoc new file mode 100644 index 000000000..1fcd23a0f --- /dev/null +++ b/src/qdoc/doc/qdoc-minimum-qdocconf.qdoc @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ +/*! +\page qdoc-minimum-qdocconf.html +\keyword minimal-qdocconf +\title A Minimal qdocconf File + +\brief Describes a minimal .qdocconf file + +Below you will find the full contents of qtgui.qdocconf. The subsequent section +will discuss every statement in the qdocconf file. + +Each line from the qdocconf file is first quoted. Below each statement you will +find the meaning. + +\badcode + include(compat.qdocconf) + outputdir = html + headerdirs = . + sourcedirs = . + exampledirs = . + imagedirs = ./images +\endcode + +\b Notes: + +\badcode + include(compat.qdocconf) +\endcode + +For compatibility with older versions of Qt, it is recommended +to include compat.qdocconf. + +\code + outputdir = html +\endcode + +QDoc will put the documentation generated in the html directory. + +\badcode + headerdirs = . +\endcode + +The header file associated with the \e .cpp source files can be found in the +current directory. + +\badcode + sourcedirs = . +\endcode + +The current directory is the directory containing the source files: the \e .cpp +and \e .qdoc files used in the documentation. + +\badcode + exampledirs = . +\endcode + +The source code of the example files can be found in the current directory. + +\badcode + imagedirs = ./images +\endcode + +The image files can be found in the underlying directory \c images. +*/ diff --git a/src/qdoc/doc/qtgui-qdocconf.qdoc b/src/qdoc/doc/qtgui-qdocconf.qdoc new file mode 100644 index 000000000..d90584ff4 --- /dev/null +++ b/src/qdoc/doc/qtgui-qdocconf.qdoc @@ -0,0 +1,299 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + +\page qtgui-qdocconf.html +\title qtgui.qdocconf with Comments + +\brief A walkthrough of a typical qdocconf file. + +This document goes through a typical Qt 5 qdocconf file. The contents is taken from +Qt GUI's \e qtgui.qdocconf file. + +Below you will find the full contents of \c qtgui.qdocconf. The subsequent section will discuss +every statement in the qdocconf file. + +\badcode + include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) + + project = QtGui + description = Qt GUI Reference Documentation + url = http://doc.qt.io/qt-5 + version = $QT_VERSION + + examplesinstallpath = gui + + qhp.projects = QtGui + + qhp.QtGui.file = qtgui.qhp + qhp.QtGui.namespace = org.qt-project.qtgui.$QT_VERSION_TAG + qhp.QtGui.virtualFolder = qtgui + qhp.QtGui.indexTitle = Qt GUI + qhp.QtGui.indexRoot = + + qhp.QtGui.filterAttributes = qtgui $QT_VERSION qtrefdoc + qhp.QtGui.customFilters.Qt.name = Qtgui $QT_VERSION + qhp.QtGui.customFilters.Qt.filterAttributes = qtgui $QT_VERSION + + qhp.QtGui.subprojects = classes + qhp.QtGui.subprojects.classes.title = C++ Classes + qhp.QtGui.subprojects.classes.indexTitle = Qt GUI C++ Classes + qhp.QtGui.subprojects.classes.selectors = class fake:headerfile + qhp.QtGui.subprojects.classes.sortPages = true + + tagfile = ../../../doc/qtgui/qtgui.tags + + depends += \ + qtcore \ + qtnetwork \ + qtopengl \ + qtsvg \ + qtqml \ + qtquick \ + qtwidgets \ + qtdoc + + headerdirs += .. + + sourcedirs += .. \ + ../../../examples/gui/doc/src + + excludedirs = ../../../examples/gui/doc/src/tmp + + exampledirs += ../../../examples/gui \ + snippets + + imagedirs += images \ + ../../../examples/gui/doc/images \ + ../../../doc/src/images \ +\endcode + +\title Qtgui.qdocconf with notes + +\badcode + include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) +\endcode + +QDoc inherits the default templates, macros, and settings from the directory +specified from the \c $QT_INSTALL_DOCS variable. \c qmake prints the value of +the variable. +\badcode + qmake -query +\endcode + +\b {See also}: \l {include}. + +\badcode + project = QtGui +\endcode + +The \c project variable sets the name of the QDoc build. This name is also +used to form the index file, which, in this case, will be \e qtgui.index. The +name of the index file doesn't adopt the uppercase letters of the project name. + +\b {See also}: \l {project}. + +\badcode + description = Qt GUI Reference Documentation +\endcode + +A short description of the project concerned. + +\badcode + url = http://doc.qt.io/qt-5 +\endcode + +The \c url variable holds the base url of the project. + +The URL is stored in the generated index file for the project. +QDoc will use this as the base URL when constructing external links +to content listed in the index. + +\note QDoc omits this value when the -installdir argument +is specified when running QDoc. + +\keyword examplesinstallpath + +\badcode + examplesinstallpath = gui +\endcode + +This \c examplesinstallpath variable indicates that the examples will be +installed in the \e gui directory under the parent examples directory +(for Qt, this is $QT_INSTALL_EXAMPLES). + +\note The examplepath variable has to match the example directory specified in + \c exampledirs. + +\b {See also}: \l {exampledirs}. + +\badcode + qhp.projects = QtGui + qhp.QtGui.file = qtgui.qhp +\endcode + +The following parameters are for creating a QHP file (\e .qhp). The +\e qhelpgenerator program can convert the QHP file into a QCH file (\e .qch), +which can be opened in Qt Assistant or Qt Creator. + +\badcode + qhp.QtGui.namespace = org.qt-project.qtgui.$QT_VERSION_TAG +\endcode + +A unique identifier which enables QHelpEngine to retrieve the helpfile +from a given link. This namespace is also used as a base url for links +to the helpfile. + +\badcode + qhp.QtGui.virtualFolder = qtgui +\endcode + +Virtual folders group documentation together into a single location. A +virtual folder will become the root directory of all files referenced in +a compressed help file. + +When two manuals are located in the same virtual folder, it is possible to +refer to sections of the other manual using relative paths. The virtual +folder tag is mandatory and the folder must not contain any '/'. + +\badcode + qhp.QtGui.indexTitle = Qt GUI +\endcode + +This is the title of the page that has the contents. + +\badcode + qhp.QtGui.indexRoot = +\endcode + +Specifies the title of the root (namespace) page to generate the documentation for. +Typically defined as an empty string. + +\badcode + qhp.QtGui.filterAttributes = qtgui $QT_VERSION qtrefdoc + qhp.QtGui.customFilters.Qt.name = QtGui $QT_VERSION + qhp.QtGui.customFilters.Qt.filterAttributes = qtgui $QT_VERSION +\endcode + +The documentation set (one per QDoc project) can have any number of filter +attributes assigned to it. A filter attribute is an ordinary string which +can be freely chosen. Additionally, custom filters that reference above +attributes can be defined. Qt Assistant will display the name of the custom +filter in its \gui{Filtered by} drop-down list. Only the documentation sets +that have their filter attributes match the attributes of the selected +custom filter will be shown. + +\badcode + qhp.QtGui.subprojects = classes + qhp.QtGui.subprojects.classes.title = C++ Classes + qhp.QtGui.subprojects.classes.indexTitle = Qt GUI C++ Classes +\endcode +The subprojects specify the sections that are displayed in the table of contents +for this project. In this example, the subproject, which is displayed in +the Assistant's sidebar, is named "C++ Classes" and its index is the page +titled "QT GUI C++ Classes". + +\badcode + qhp.QtGui.subprojects.classes.selectors = class fake:headerfile +\endcode + +Lists all C++ classes and header files. + +See \l {Creating Help Project Files} for more information. + +\badcode + tagfile = ../../../doc/qtgui/qtgui.tags +\endcode + +This specifies the Doxygen tag file that needs to be written when the html is generated +by QDoc. + +\badcode +depends += \ + qtcore \ + qtnetwork \ + qtopengl \ + qtsvg \ + qtqml \ + qtquick \ + qtwidgets \ + qtdoc +\endcode + +Specifies the modules QDoc needs to load for generating output for Qt GUI. +QDoc loads the index files for all modules listed in the depends statement in +order to enable linking to pages in these modules. + +\badcode + headerdirs += .. +\endcode + +Add the parent directory to the list of directories containing the header files +associated with the \e .cpp source files. + +\badcode + sourcedirs += .. \ + ../../../examples/gui/doc/src +\endcode + +Add the specified directories to the list of directories containing the \e .cpp and +\e .qdoc files used in the documentation. + +\badcode + excludedirs = ../../../examples/gui/doc/src/tmp +\endcode + +The \c excludedirs variable is for listing directories that should not be processed +by qdoc, even if the same directories are included by the \c sourcedirs or \c headerdirs +variables. + +When executed, QDoc will ignore the directories listed. +\b {See also}: \l {excludefiles}. + +\badcode + exampledirs += ../../../examples/gui \ + snippets +\endcode +\b {See also}: \l {examples-variable}{examples}, \l {examplesinstallpath}. + +Add the two directories specified to the list of directories containing the source +code of the example files. + +If QDoc encounters both \c exampledirs and \c examples, it will look first in the +\c examples directory. QDoc will accept the first matching file it finds. QDoc will +search in the directories specified, not in their subdirectories. + +\badcode + imagedirs += images \ + ../../../examples/gui/doc/images \ + ../../../doc/src/images \ +\endcode + +Add the directories specified above to the list of directories where the images +can be found. +*/ diff --git a/src/qdoc/editdistance.cpp b/src/qdoc/editdistance.cpp new file mode 100644 index 000000000..c3336d2ab --- /dev/null +++ b/src/qdoc/editdistance.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + editdistance.cpp +*/ + +#include "editdistance.h" + +QT_BEGIN_NAMESPACE + +int editDistance( const QString& s, const QString& t ) +{ +#define D( i, j ) d[(i) * n + (j)] + int i; + int j; + int m = s.length() + 1; + int n = t.length() + 1; + int *d = new int[m * n]; + int result; + + for ( i = 0; i < m; i++ ) + D( i, 0 ) = i; + for ( j = 0; j < n; j++ ) + D( 0, j ) = j; + for ( i = 1; i < m; i++ ) { + for ( j = 1; j < n; j++ ) { + if ( s[i - 1] == t[j - 1] ) { + D( i, j ) = D( i - 1, j - 1 ); + } else { + int x = D( i - 1, j ); + int y = D( i - 1, j - 1 ); + int z = D( i, j - 1 ); + D( i, j ) = 1 + qMin( qMin(x, y), z ); + } + } + } + result = D( m - 1, n - 1 ); + delete[] d; + return result; +#undef D +} + +QString nearestName( const QString& actual, const QSet<QString>& candidates ) +{ + if (actual.isEmpty()) + return QString(); + + int deltaBest = 10000; + int numBest = 0; + QString best; + + QSet<QString>::ConstIterator c = candidates.constBegin(); + while ( c != candidates.constEnd() ) { + if ( (*c)[0] == actual[0] ) { + int delta = editDistance( actual, *c ); + if ( delta < deltaBest ) { + deltaBest = delta; + numBest = 1; + best = *c; + } else if ( delta == deltaBest ) { + numBest++; + } + } + ++c; + } + + if ( numBest == 1 && deltaBest <= 2 && + actual.length() + best.length() >= 5 ) { + return best; + } else { + return QString(); + } +} + +QT_END_NAMESPACE diff --git a/src/qdoc/editdistance.h b/src/qdoc/editdistance.h new file mode 100644 index 000000000..2a9b1710f --- /dev/null +++ b/src/qdoc/editdistance.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + editdistance.h +*/ + +#ifndef EDITDISTANCE_H +#define EDITDISTANCE_H + +#include <qset.h> +#include <qstring.h> + +QT_BEGIN_NAMESPACE + +int editDistance( const QString& s, const QString& t ); +QString nearestName( const QString& actual, const QSet<QString>& candidates ); + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/generator.cpp b/src/qdoc/generator.cpp new file mode 100644 index 000000000..3ce5bf99d --- /dev/null +++ b/src/qdoc/generator.cpp @@ -0,0 +1,2171 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + generator.cpp +*/ +#include <qdir.h> +#include <qdebug.h> +#include "codemarker.h" +#include "config.h" +#include "doc.h" +#include "editdistance.h" +#include "generator.h" +#include "openedlist.h" +#include "quoter.h" +#include "separator.h" +#include "tokenizer.h" +#include "qdocdatabase.h" + +QT_BEGIN_NAMESPACE + +Generator* Generator::currentGenerator_; +QStringList Generator::exampleDirs; +QStringList Generator::exampleImgExts; +QMap<QString, QMap<QString, QString> > Generator::fmtLeftMaps; +QMap<QString, QMap<QString, QString> > Generator::fmtRightMaps; +QList<Generator *> Generator::generators; +QStringList Generator::imageDirs; +QStringList Generator::imageFiles; +QMap<QString, QStringList> Generator::imgFileExts; +QString Generator::outDir_; +QString Generator::outSubdir_; +QStringList Generator::outFileNames_; +QSet<QString> Generator::outputFormats; +QHash<QString, QString> Generator::outputPrefixes; +QHash<QString, QString> Generator::outputSuffixes; +QString Generator::project_; +QStringList Generator::scriptDirs; +QStringList Generator::scriptFiles; +QString Generator::sinceTitles[] = +{ + " New Namespaces", + " New Classes", + " New Member Functions", + " New Functions in Namespaces", + " New Global Functions", + " New Macros", + " New Enum Types", + " New Typedefs", + " New Properties", + " New Variables", + " New QML Types", + " New QML Properties", + " New QML Signals", + " New QML Signal Handlers", + " New QML Methods", + "" +}; +QStringList Generator::styleDirs; +QStringList Generator::styleFiles; +bool Generator::debugging_ = false; +bool Generator::noLinkErrors_ = false; +bool Generator::autolinkErrors_ = false; +bool Generator::redirectDocumentationToDevNull_ = false; +Generator::QDocPass Generator::qdocPass_ = Generator::Neither; +bool Generator::qdocSingleExec_ = false; +bool Generator::qdocWriteQaPages_ = false; +bool Generator::useOutputSubdirs_ = true; +QmlTypeNode* Generator::qmlTypeContext_ = 0; + +void Generator::startDebugging(const QString& message) +{ + debugging_ = true; + qDebug() << "START DEBUGGING:" << message; +} + +void Generator::stopDebugging(const QString& message) +{ + debugging_ = false; + qDebug() << "STOP DEBUGGING:" << message; +} + +/*! + Prints \a message as an aid to debugging the release version. + */ +void Generator::debug(const QString& message) +{ + if (debugging()) + qDebug() << " DEBUG:" << message; +} + +/*! + Constructs the generator base class. Prepends the newly + constructed generator to the list of output generators. + Sets a pointer to the QDoc database singleton, which is + available to the generator subclasses. + */ +Generator::Generator() + : amp("&"), + gt(">"), + lt("<"), + quot("""), + tag("</?@[^>]*>"), + inLink_(false), + inContents_(false), + inSectionHeading_(false), + inTableHeader_(false), + threeColumnEnumValueTable_(true), + showInternal_(false), + singleExec_(false), + numTableRows_(0) +{ + qdb_ = QDocDatabase::qdocDB(); + generators.prepend(this); +} + +/*! + Destroys the generator after removing it from the list of + output generators. + */ +Generator::~Generator() +{ + generators.removeAll(this); +} + +void Generator::appendFullName(Text& text, + const Node *apparentNode, + const Node *relative, + const Node *actualNode) +{ + if (actualNode == 0) + actualNode = apparentNode; + text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode)) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, apparentNode->plainFullName(relative)) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); +} + +void Generator::appendFullName(Text& text, + const Node *apparentNode, + const QString& fullName, + const Node *actualNode) +{ + if (actualNode == 0) + actualNode = apparentNode; + text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode)) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, fullName) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); +} + +void Generator::appendFullNames(Text& text, const NodeList& nodes, const Node* relative) +{ + NodeList::ConstIterator n = nodes.constBegin(); + int index = 0; + while (n != nodes.constEnd()) { + appendFullName(text,*n,relative); + text << comma(index++,nodes.count()); + ++n; + } +} + +void Generator::appendSortedNames(Text& text, const ClassNode* cn, const QList<RelatedClass>& rc) +{ + QList<RelatedClass>::ConstIterator r; + QMap<QString,Text> classMap; + int index = 0; + + r = rc.constBegin(); + while (r != rc.constEnd()) { + ClassNode* rcn = (*r).node_; + if (rcn && rcn->access() == Node::Public && + rcn->status() != Node::Internal && + !rcn->doc().isEmpty()) { + Text className; + appendFullName(className, rcn, cn); + classMap[className.toString().toLower()] = className; + } + ++r; + } + + QStringList classNames = classMap.keys(); + classNames.sort(); + + foreach (const QString &className, classNames) { + text << classMap[className]; + text << comma(index++, classNames.count()); + } +} + +void Generator::appendSortedQmlNames(Text& text, const Node* base, const NodeList& subs) +{ + QMap<QString,Text> classMap; + int index = 0; + + for (int i = 0; i < subs.size(); ++i) { + Text t; + if (!base->isQtQuickNode() || !subs[i]->isQtQuickNode() || + (base->logicalModuleName() == subs[i]->logicalModuleName())) { + appendFullName(t, subs[i], base); + classMap[t.toString().toLower()] = t; + } + } + + QStringList names = classMap.keys(); + names.sort(); + + foreach (const QString &name, names) { + text << classMap[name]; + text << comma(index++, names.count()); + } +} + +/*! + For debugging qdoc. + */ +void Generator::writeOutFileNames() +{ + QFile files("outputlist.txt"); + if (!files.open(QFile::WriteOnly)) + return; + QTextStream filesout(&files); + foreach (const QString &file, outFileNames_) { + filesout << file << "\n"; + } +} + +/*! + Creates the file named \a fileName in the output directory. + Attaches a QTextStream to the created file, which is written + to all over the place using out(). + */ +void Generator::beginSubPage(const Aggregate* node, const QString& fileName) +{ + QString path = outputDir() + QLatin1Char('/'); + if (Generator::useOutputSubdirs() && !node->outputSubdirectory().isEmpty() && + !outputDir().endsWith(node->outputSubdirectory())) + path += node->outputSubdirectory() + QLatin1Char('/'); + path += fileName; + + QFile* outFile = new QFile(redirectDocumentationToDevNull_ ? QStringLiteral("/dev/null") : path); + if (!redirectDocumentationToDevNull_ && outFile->exists()) + node->location().error(tr("HTML file already exists; overwriting %1").arg(outFile->fileName())); + if (!outFile->open(QFile::WriteOnly)) + node->location().fatal(tr("Cannot open output file '%1'").arg(outFile->fileName())); + Generator::debug("Writing: " + path); + outFileNames_ << fileName; + QTextStream* out = new QTextStream(outFile); + +#ifndef QT_NO_TEXTCODEC + if (outputCodec) + out->setCodec(outputCodec); +#endif + outStreamStack.push(out); + const_cast<Aggregate*>(node)->setOutputFileName(fileName); +} + +/*! + Flush the text stream associated with the subpage, and + then pop it off the text stream stack and delete it. + This terminates output of the subpage. + */ +void Generator::endSubPage() +{ + outStreamStack.top()->flush(); + delete outStreamStack.top()->device(); + delete outStreamStack.pop(); +} + +QString Generator::fileBase(const Node *node) const +{ + if (node->relates()) + node = node->relates(); + else if (!node->isAggregate()) + node = node->parent(); + if (node->type() == Node::QmlPropertyGroup) { + node = node->parent(); + } + + if (node->hasFileNameBase()) + return node->fileNameBase(); + + QString base; + if (node->isDocumentNode()) { + base = node->name(); + if (base.endsWith(".html") && !node->isExampleFile()) + base.truncate(base.length() - 5); + + if (node->isExample() || node->isExampleFile()) { + QString modPrefix(node->physicalModuleName()); + if (modPrefix.isEmpty()) { + modPrefix = project_; + } + base.prepend(modPrefix.toLower() + QLatin1Char('-')); + } + if (node->isExample()) { + base.append(QLatin1String("-example")); + } + } + else if (node->isQmlType() || node->isQmlBasicType() || + node->isJsType() || node->isJsBasicType()) { + base = node->name(); + /* + To avoid file name conflicts in the html directory, + we prepend a prefix (by default, "qml-") and an optional suffix + to the file name. The suffix, if one exists, is appended to the + module name. + */ + if (!node->logicalModuleName().isEmpty()) { + base.prepend(node->logicalModuleName() + + outputSuffix(node) + + QLatin1Char('-')); + } + base.prepend(outputPrefix(node)); + } + else if (node->isCollectionNode()) { + base = node->name() + outputSuffix(node); + if (base.endsWith(".html")) + base.truncate(base.length() - 5); + + if (node->isQmlModule()) { + base.append("-qmlmodule"); + } + else if (node->isJsModule()) { + base.append("-jsmodule"); + } + else if (node->isModule()) { + base.append("-module"); + } + // Why not add "-group" for group pages? + } + else { + const Node *p = node; + forever { + const Node *pp = p->parent(); + base.prepend(p->name()); + if (!pp || pp->name().isEmpty() || pp->isDocumentNode()) + break; + base.prepend(QLatin1Char('-')); + p = pp; + } + } + + // the code below is effectively equivalent to: + // base.replace(QRegExp("[^A-Za-z0-9]+"), " "); + // base = base.trimmed(); + // base.replace(QLatin1Char(' '), QLatin1Char('-')); + // base = base.toLower(); + // as this function accounted for ~8% of total running time + // we optimize a bit... + + QString res; + // +5 prevents realloc in fileName() below + res.reserve(base.size() + 5); + bool begun = false; + for (int i = 0; i != base.size(); ++i) { + QChar c = base.at(i); + uint u = c.unicode(); + if (u >= 'A' && u <= 'Z') + u += 'a' - 'A'; + if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) { + res += QLatin1Char(u); + begun = true; + } + else if (begun) { + res += QLatin1Char('-'); + begun = false; + } + } + while (res.endsWith(QLatin1Char('-'))) + res.chop(1); + Node* n = const_cast<Node*>(node); + n->setFileNameBase(res); + return res; +} + +/*! + If the \a node has a URL, return the URL as the file name. + Otherwise, construct the file name from the fileBase() and + the fileExtension(), and return the constructed name. + */ +QString Generator::fileName(const Node* node) const +{ + if (!node->url().isEmpty()) + return node->url(); + + QString name = fileBase(node); + name += QLatin1Char('.'); + name += fileExtension(); + return name; +} + +QString Generator::cleanRef(const QString& ref) +{ + QString clean; + + if (ref.isEmpty()) + return clean; + + clean.reserve(ref.size() + 20); + const QChar c = ref[0]; + const uint u = c.unicode(); + + if ((u >= 'a' && u <= 'z') || + (u >= 'A' && u <= 'Z') || + (u >= '0' && u <= '9')) { + clean += c; + } else if (u == '~') { + clean += "dtor."; + } else if (u == '_') { + clean += "underscore."; + } else { + clean += QLatin1Char('A'); + } + + for (int i = 1; i < (int) ref.length(); i++) { + const QChar c = ref[i]; + const uint u = c.unicode(); + if ((u >= 'a' && u <= 'z') || + (u >= 'A' && u <= 'Z') || + (u >= '0' && u <= '9') || u == '-' || + u == '_' || u == ':' || u == '.') { + clean += c; + } else if (c.isSpace()) { + clean += QLatin1Char('-'); + } else if (u == '!') { + clean += "-not"; + } else if (u == '&') { + clean += "-and"; + } else if (u == '<') { + clean += "-lt"; + } else if (u == '=') { + clean += "-eq"; + } else if (u == '>') { + clean += "-gt"; + } else if (u == '#') { + clean += QLatin1Char('#'); + } else { + clean += QLatin1Char('-'); + clean += QString::number((int)u, 16); + } + } + return clean; +} + +QMap<QString, QString>& Generator::formattingLeftMap() +{ + return fmtLeftMaps[format()]; +} + +QMap<QString, QString>& Generator::formattingRightMap() +{ + return fmtRightMaps[format()]; +} + +/*! + Returns the full document location. + */ +QString Generator::fullDocumentLocation(const Node *node, bool useSubdir) +{ + if (!node) + return QString(); + if (!node->url().isEmpty()) + return node->url(); + + QString parentName; + QString anchorRef; + QString fdl; + + /* + If the useSubdir parameter is set, then the output is + being sent to subdirectories of the output directory. + Prepend the subdirectory name + '/' to the result. + */ + if (useSubdir) { + fdl = node->outputSubdirectory(); + if (!fdl.isEmpty()) + fdl.append(QLatin1Char('/')); + } + if (node->isNamespace()) { + + // The root namespace has no name - check for this before creating + // an attribute containing the location of any documentation. + + if (!fileBase(node).isEmpty()) + parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension(); + else + return QString(); + } + else if (node->isQmlType() || node->isQmlBasicType() || + node->isJsType() || node->isJsBasicType()) { + QString fb = fileBase(node); + if (fb.startsWith(outputPrefix(node))) + return fb + QLatin1Char('.') + currentGenerator()->fileExtension(); + else { + QString mq; + if (!node->logicalModuleName().isEmpty()) { + mq = node->logicalModuleName().replace(QChar('.'),QChar('-')); + mq = mq.toLower() + QLatin1Char('-'); + } + return fdl + outputPrefix(node) + mq + fileBase(node) + + QLatin1Char('.') + currentGenerator()->fileExtension(); + } + } + else if (node->isDocumentNode() || node->isCollectionNode()) { + parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension(); + } + else if (fileBase(node).isEmpty()) + return QString(); + + Node *parentNode = 0; + + if ((parentNode = node->relates())) { + parentName = fullDocumentLocation(node->relates()); + } + else if ((parentNode = node->parent())) { + if (parentNode->isQmlPropertyGroup() || parentNode->isJsPropertyGroup()) { + parentNode = parentNode->parent(); + parentName = fullDocumentLocation(parentNode); + } + else { + parentName = fullDocumentLocation(node->parent()); + } + } + + switch (node->type()) { + case Node::Class: + case Node::Namespace: + parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension(); + break; + case Node::Function: + { + const FunctionNode *fn = static_cast<const FunctionNode *>(node); + + if (fn->metaness() == FunctionNode::Dtor) + anchorRef = "#dtor." + fn->name().mid(1); + + else if (fn->hasOneAssociatedProperty() && fn->doc().isEmpty()) + return fullDocumentLocation(fn->firstAssociatedProperty()); + + else if (fn->overloadNumber() > 0) + anchorRef = QLatin1Char('#') + cleanRef(fn->name()) + + QLatin1Char('-') + QString::number(fn->overloadNumber()); + else + anchorRef = QLatin1Char('#') + cleanRef(fn->name()); + break; + } + /* + Use node->name() instead of fileBase(node) as + the latter returns the name in lower-case. For + HTML anchors, we need to preserve the case. + */ + case Node::Enum: + anchorRef = QLatin1Char('#') + node->name() + "-enum"; + break; + case Node::Typedef: + { + const TypedefNode *tdef = static_cast<const TypedefNode *>(node); + if (tdef->associatedEnum()) { + return fullDocumentLocation(tdef->associatedEnum()); + } + anchorRef = QLatin1Char('#') + node->name() + "-typedef"; + break; + } + case Node::Property: + anchorRef = QLatin1Char('#') + node->name() + "-prop"; + break; + case Node::QmlProperty: + if (node->isAttached()) + anchorRef = QLatin1Char('#') + node->name() + "-attached-prop"; + else + anchorRef = QLatin1Char('#') + node->name() + "-prop"; + break; + case Node::QmlSignal: + anchorRef = QLatin1Char('#') + node->name() + "-signal"; + break; + case Node::QmlSignalHandler: + anchorRef = QLatin1Char('#') + node->name() + "-signal-handler"; + break; + case Node::QmlMethod: + anchorRef = QLatin1Char('#') + node->name() + "-method"; + break; + case Node::Variable: + anchorRef = QLatin1Char('#') + node->name() + "-var"; + break; + case Node::QmlType: + case Node::Document: + case Node::Group: + case Node::Module: + case Node::QmlModule: + { + parentName = fileBase(node); + parentName.replace(QLatin1Char('/'), QLatin1Char('-')).replace(QLatin1Char('.'), QLatin1Char('-')); + parentName += QLatin1Char('.') + currentGenerator()->fileExtension(); + } + break; + default: + break; + } + + // Various objects can be compat (deprecated) or obsolete. + // Is this even correct? + if (!node->isClass() && !node->isNamespace()) { + switch (node->status()) { + case Node::Compat: + parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(), + "-compat." + currentGenerator()->fileExtension()); + break; + case Node::Obsolete: + parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(), + "-obsolete." + currentGenerator()->fileExtension()); + break; + default: + ; + } + } + + return fdl + parentName.toLower() + anchorRef; +} + +void Generator::generateAlsoList(const Node *node, CodeMarker *marker) +{ + QList<Text> alsoList = node->doc().alsoList(); + supplementAlsoList(node, alsoList); + + if (!alsoList.isEmpty()) { + Text text; + text << Atom::ParaLeft + << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD) + << "See also " + << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD); + + for (int i = 0; i < alsoList.size(); ++i) + text << alsoList.at(i) << separator(i, alsoList.size()); + + text << Atom::ParaRight; + generateText(text, node, marker); + } +} + +int Generator::generateAtom(const Atom * /* atom */, + const Node * /* relative */, + CodeMarker * /* marker */) +{ + return 0; +} + +const Atom *Generator::generateAtomList(const Atom *atom, + const Node *relative, + CodeMarker *marker, + bool generate, + int &numAtoms) +{ + while (atom) { + if (atom->type() == Atom::FormatIf) { + int numAtoms0 = numAtoms; + bool rightFormat = canHandleFormat(atom->string()); + atom = generateAtomList(atom->next(), + relative, + marker, + generate && rightFormat, + numAtoms); + if (!atom) + return 0; + + if (atom->type() == Atom::FormatElse) { + ++numAtoms; + atom = generateAtomList(atom->next(), + relative, + marker, + generate && !rightFormat, + numAtoms); + if (!atom) + return 0; + } + + if (atom->type() == Atom::FormatEndif) { + if (generate && numAtoms0 == numAtoms) { + relative->location().warning(tr("Output format %1 not handled %2") + .arg(format()).arg(outFileName())); + Atom unhandledFormatAtom(Atom::UnhandledFormat, format()); + generateAtomList(&unhandledFormatAtom, + relative, + marker, + generate, + numAtoms); + } + atom = atom->next(); + } + } + else if (atom->type() == Atom::FormatElse || + atom->type() == Atom::FormatEndif) { + return atom; + } + else { + int n = 1; + if (generate) { + n += generateAtom(atom, relative, marker); + numAtoms += n; + } + while (n-- > 0) + atom = atom->next(); + } + } + return 0; +} + +/*! + Generate the body of the documentation from the qdoc comment + found with the entity represented by the \a node. + */ +void Generator::generateBody(const Node *node, CodeMarker *marker) +{ + bool quiet = false; + + if (node->type() == Node::Document) { + const DocumentNode *dn = static_cast<const DocumentNode *>(node); + if ((dn->docSubtype() == Node::File) || (dn->docSubtype() == Node::Image)) { + quiet = true; + } + } + if (node->doc().isEmpty()) { + if (!node->isWrapper() && !quiet && !node->isReimplemented()) { // ### might be unnecessary + node->location().warning(tr("No documentation for '%1'").arg(node->plainFullName())); + } + } + else { + if (node->type() == Node::Function) { + const FunctionNode *func = static_cast<const FunctionNode *>(node); + if (func->reimplementedFrom() != 0) + generateReimplementedFrom(func, marker); + } + + if (!generateText(node->doc().body(), node, marker)) { + if (node->isReimplemented()) + return; + } + + if (node->type() == Node::Enum) { + const EnumNode *enume = (const EnumNode *) node; + + QSet<QString> definedItems; + QList<EnumItem>::ConstIterator it = enume->items().constBegin(); + while (it != enume->items().constEnd()) { + definedItems.insert((*it).name()); + ++it; + } + + QSet<QString> documentedItems = enume->doc().enumItemNames().toSet(); + QSet<QString> allItems = definedItems + documentedItems; + if (allItems.count() > definedItems.count() || + allItems.count() > documentedItems.count()) { + QSet<QString>::ConstIterator a = allItems.constBegin(); + while (a != allItems.constEnd()) { + if (!definedItems.contains(*a)) { + QString details; + QString best = nearestName(*a, definedItems); + if (!best.isEmpty() && !documentedItems.contains(best)) + details = tr("Maybe you meant '%1'?").arg(best); + + node->doc().location().warning(tr("No such enum item '%1' in %2") + .arg(*a).arg(node->plainFullName()), details); + if (*a == "Void") + qDebug() << "VOID:" << node->name() << definedItems; + } + else if (!documentedItems.contains(*a)) { + node->doc().location().warning(tr("Undocumented enum item '%1' in %2") + .arg(*a).arg(node->plainFullName())); + } + ++a; + } + } + } + else if (node->type() == Node::Function) { + const FunctionNode *func = static_cast<const FunctionNode *>(node); + QSet<QString> definedParams; + QVector<Parameter>::ConstIterator p = func->parameters().constBegin(); + while (p != func->parameters().constEnd()) { + if ((*p).name().isEmpty() && (*p).dataType() != QLatin1String("...") + && (*p).dataType() != QLatin1String("void") + && func->name() != QLatin1String("operator++") + && func->name() != QLatin1String("operator--")) { + node->doc().location().warning(tr("Missing parameter name")); + } + else { + definedParams.insert((*p).name()); + } + ++p; + } + + QSet<QString> documentedParams = func->doc().parameterNames(); + QSet<QString> allParams = definedParams + documentedParams; + if (allParams.count() > definedParams.count() + || allParams.count() > documentedParams.count()) { + QSet<QString>::ConstIterator a = allParams.constBegin(); + while (a != allParams.constEnd()) { + if (!definedParams.contains(*a)) { + QString details; + QString best = nearestName(*a, definedParams); + if (!best.isEmpty()) + details = tr("Maybe you meant '%1'?").arg(best); + + node->doc().location().warning( + tr("No such parameter '%1' in %2").arg(*a).arg(node->plainFullName()), + details); + } + else if (!(*a).isEmpty() && !documentedParams.contains(*a)) { + bool needWarning = (func->status() > Node::Obsolete); + if (func->overloadNumber() > 0) { + FunctionNode *primaryFunc = func->parent()->findFunctionNode(func->name(), QString()); + if (primaryFunc) { + foreach (const Parameter ¶m, + primaryFunc->parameters()) { + if (param.name() == *a) { + needWarning = false; + break; + } + } + } + } + if (needWarning && !func->isReimplemented()) + node->doc().location().warning( + tr("Undocumented parameter '%1' in %2") + .arg(*a).arg(node->plainFullName())); + } + ++a; + } + } + /* + Something like this return value check should + be implemented at some point. + */ + if (func->status() > Node::Obsolete && func->returnType() == "bool" + && func->reimplementedFrom() == 0 && !func->isOverload()) { + QString body = func->doc().body().toString(); + if (!body.contains("return", Qt::CaseInsensitive)) + node->doc().location().warning(tr("Undocumented return value")); + } + } + } + + if (node->isDocumentNode()) { + const DocumentNode *dn = static_cast<const DocumentNode *>(node); + if (dn->isExample()) { + generateExampleFiles(dn, marker); + } + else if (dn->docSubtype() == Node::File) { + Text text; + Quoter quoter; + Doc::quoteFromFile(dn->doc().location(), quoter, dn->name()); + QString code = quoter.quoteTo(dn->location(), QString(), QString()); + CodeMarker *codeMarker = CodeMarker::markerForFileName(dn->name()); + text << Atom(codeMarker->atomType(), code); + generateText(text, dn, codeMarker); + } + } +} + +void Generator::generateClassLikeNode(Aggregate* /* classe */, CodeMarker* /* marker */) +{ +} + +void Generator::generateExampleFiles(const DocumentNode *dn, CodeMarker *marker) +{ + if (dn->childNodes().isEmpty()) + return; + generateFileList(dn, marker, Node::File, QString("Files:")); + generateFileList(dn, marker, Node::Image, QString("Images:")); +} + +void Generator::generateDocumentNode(DocumentNode* /* dn */, CodeMarker* /* marker */) +{ +} + +void Generator::generateCollectionNode(CollectionNode* , CodeMarker* ) +{ +} + +/*! + This function is called when the documentation for an + example is being formatted. It outputs the list of source + files comprising the example, and the list of images used + by the example. The images are copied into a subtree of + \c{...doc/html/images/used-in-examples/...} + */ +void Generator::generateFileList(const DocumentNode* dn, + CodeMarker* marker, + Node::DocSubtype subtype, + const QString& tag) +{ + int count = 0; + Text text; + OpenedList openedList(OpenedList::Bullet); + + text << Atom::ParaLeft << tag << Atom::ParaRight + << Atom(Atom::ListLeft, openedList.styleString()); + + foreach (const Node* child, dn->childNodes()) { + if (child->docSubtype() == subtype) { + ++count; + QString file = child->name(); + if (subtype == Node::Image) { + if (!file.isEmpty()) { + QDir dirInfo; + QString userFriendlyFilePath; + const QString prefix("/images/used-in-examples/"); + QString srcPath = Config::findFile(dn->location(), + QStringList(), + exampleDirs, + file, + exampleImgExts, + userFriendlyFilePath); + outFileNames_ << prefix.mid(1) + userFriendlyFilePath; + userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/')); + QString imgOutDir = outDir_ + prefix + userFriendlyFilePath; + if (!dirInfo.mkpath(imgOutDir)) + dn->location().fatal(tr("Cannot create output directory '%1'").arg(imgOutDir)); + Config::copyFile(dn->location(), srcPath, file, imgOutDir); + } + + } + + openedList.next(); + text << Atom(Atom::ListItemNumber, openedList.numberString()) + << Atom(Atom::ListItemLeft, openedList.styleString()) + << Atom::ParaLeft + << Atom(Atom::Link, file) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << file + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom::ParaRight + << Atom(Atom::ListItemRight, openedList.styleString()); + } + } + text << Atom(Atom::ListRight, openedList.styleString()); + if (count > 0) + generateText(text, dn, marker); +} + +void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker) +{ + if (!classe->derivedClasses().isEmpty()) { + Text text; + text << Atom::ParaLeft + << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD) + << "Inherited by: " + << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD); + + appendSortedNames(text, classe, classe->derivedClasses()); + text << Atom::ParaRight; + generateText(text, classe, marker); + } +} + +void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker) +{ + QList<RelatedClass>::ConstIterator r; + int index; + + if (!classe->baseClasses().isEmpty()) { + Text text; + text << Atom::ParaLeft + << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD) + << "Inherits: " + << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD); + + r = classe->baseClasses().constBegin(); + index = 0; + while (r != classe->baseClasses().constEnd()) { + if ((*r).node_) { + text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node_)) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, (*r).signature_) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + + if ((*r).access_ == Node::Protected) { + text << " (protected)"; + } + else if ((*r).access_ == Node::Private) { + text << " (private)"; + } + text << separator(index++, classe->baseClasses().count()); + } + ++r; + } + text << Atom::ParaRight; + generateText(text, classe, marker); + } +} + +/*! + Recursive writing of HTML files from the root \a node. + */ +void Generator::generateAggregate(Aggregate* node) +{ + if (!node->url().isNull()) + return; + if (node->isIndexNode()) + return; + if (node->isInternal() && !showInternal_) + return; + + if (node->isDocumentNode()) { + DocumentNode* docNode = static_cast<DocumentNode*>(node); + if (docNode->docSubtype() == Node::ExternalPage) + return; + if (docNode->docSubtype() == Node::Image) + return; + if (docNode->docSubtype() == Node::Page) { + if (node->count() > 0) + qDebug("PAGE %s HAS CHILDREN", qPrintable(docNode->title())); + } + } + else if (node->isQmlPropertyGroup() || node->isJsPropertyGroup()) + return; + + /* + Obtain a code marker for the source file. + */ + CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath()); + + if (node->parent() != 0) { + if ((node->isNamespace() && node->status() != Node::Intermediate) + || node->isClass()) { + beginSubPage(node, fileName(node)); + generateClassLikeNode(node, marker); + endSubPage(); + } + if (node->isQmlType() || node->isJsType()) { + beginSubPage(node, fileName(node)); + QmlTypeNode* qcn = static_cast<QmlTypeNode*>(node); + generateQmlTypePage(qcn, marker); + endSubPage(); + } + else if (node->isDocumentNode()) { + beginSubPage(node, fileName(node)); + generateDocumentNode(static_cast<DocumentNode*>(node), marker); + endSubPage(); + } + else if (node->isQmlBasicType() || node->isJsBasicType()) { + beginSubPage(node, fileName(node)); + QmlBasicTypeNode* qbtn = static_cast<QmlBasicTypeNode*>(node); + generateQmlBasicTypePage(qbtn, marker); + endSubPage(); + } + else if (node->isCollectionNode()) { + /* + A collection node collects: groups, C++ modules, + QML modules or JavaScript modules. + + Don't output an HTML page for the collection + node unless the \group, \module, \qmlmodule or + \jsmodule command was actually seen by qdoc in + the qdoc comment for the node. + + A key prerequisite in this case is the call to + mergeCollections(cn). We must determine whether + this group, module, QML module, or JavaScript + module has members in other modules. We know at + this point that cn's members list contains only + members in the current module. Therefore, before + outputting the page for cn, we must search for + members of cn in the other modules and add them + to the members list. + */ + CollectionNode* cn = static_cast<CollectionNode*>(node); + if (cn->wasSeen()) { + qdb_->mergeCollections(cn); + beginSubPage(node, fileName(node)); + generateCollectionNode(cn, marker); + endSubPage(); + } + } + } + + int i = 0; + while (i < node->childNodes().count()) { + Node *c = node->childNodes().at(i); + if (c->isAggregate() && c->access() != Node::Private) { + generateAggregate((Aggregate*)c); + } + ++i; + } +} + +/*! + Generate a list of maintainers in the output + */ +void Generator::generateMaintainerList(const Aggregate* node, CodeMarker* marker) +{ + QStringList sl = getMetadataElements(node,"maintainer"); + + if (!sl.isEmpty()) { + Text text; + text << Atom::ParaLeft + << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD) + << "Maintained by: " + << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD); + + for (int i = 0; i < sl.size(); ++i) + text << sl.at(i) << separator(i, sl.size()); + + text << Atom::ParaRight; + generateText(text, node, marker); + } +} + +/*! + Output the "Inherit by" list for the QML element, + if it is inherited by any other elements. + */ +void Generator::generateQmlInheritedBy(const QmlTypeNode* qcn, + CodeMarker* marker) +{ + if (qcn) { + NodeList subs; + QmlTypeNode::subclasses(qcn->name(),subs); + if (!subs.isEmpty()) { + Text text; + text << Atom::ParaLeft << "Inherited by "; + appendSortedQmlNames(text,qcn,subs); + text << Atom::ParaRight; + generateText(text, qcn, marker); + } + } +} + +/*! + */ +void Generator::generateQmlInherits(QmlTypeNode* , CodeMarker* ) +{ + // stub. +} + +/*! + Extract sections of markup text surrounded by \e qmltext + and \e endqmltext and output them. + */ +bool Generator::generateQmlText(const Text& text, + const Node *relative, + CodeMarker *marker, + const QString& /* qmlName */ ) +{ + const Atom* atom = text.firstAtom(); + bool result = false; + + if (atom != 0) { + initializeTextOutput(); + while (atom) { + if (atom->type() != Atom::QmlText) + atom = atom->next(); + else { + atom = atom->next(); + while (atom && (atom->type() != Atom::EndQmlText)) { + int n = 1 + generateAtom(atom, relative, marker); + while (n-- > 0) + atom = atom->next(); + } + } + } + result = true; + } + return result; +} + +void Generator::generateReimplementedFrom(const FunctionNode *func, + CodeMarker *marker) +{ + if (func->reimplementedFrom() != 0) { + const FunctionNode *from = func->reimplementedFrom(); + if (from->access() != Node::Private && + from->parent()->access() != Node::Private) { + Text text; + text << Atom::ParaLeft << "Reimplemented from "; + QString fullName = from->parent()->name() + "::" + from->name() + "()"; + appendFullName(text, from->parent(), fullName, from); + text << "." << Atom::ParaRight; + generateText(text, func, marker); + } + } +} + +void Generator::generateSince(const Node *node, CodeMarker *marker) +{ + if (!node->since().isEmpty()) { + Text text; + text << Atom::ParaLeft + << "This " + << typeString(node); + if (node->type() == Node::Enum) + text << " was introduced or modified in "; + else + text << " was introduced in "; + + QStringList since = node->since().split(QLatin1Char(' ')); + if (since.count() == 1) { + // If there is only one argument, assume it is the Qt version number. + text << " Qt " << since[0]; + } else { + // Otherwise, reconstruct the <project> <version> string. + text << " " << since.join(' '); + } + + text << "." << Atom::ParaRight; + generateText(text, node, marker); + } +} + +void Generator::generateStatus(const Node *node, CodeMarker *marker) +{ + Text text; + + switch (node->status()) { + case Node::Active: + // Do nothing. + break; + case Node::Preliminary: + text << Atom::ParaLeft + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) + << "This " + << typeString(node) + << " is under development and is subject to change." + << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) + << Atom::ParaRight; + break; + case Node::Deprecated: + text << Atom::ParaLeft; + if (node->isAggregate()) + text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD); + text << "This " << typeString(node) << " is deprecated."; + if (node->isAggregate()) + text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD); + text << Atom::ParaRight; + break; + case Node::Obsolete: + text << Atom::ParaLeft; + if (node->isAggregate()) + text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD); + text << "This " << typeString(node) << " is obsolete."; + if (node->isAggregate()) + text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD); + text << " It is provided to keep old source code working. " + << "We strongly advise against " + << "using it in new code." << Atom::ParaRight; + break; + case Node::Compat: + // reimplemented in HtmlGenerator subclass + if (node->isAggregate()) { + text << Atom::ParaLeft + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) + << "This " + << typeString(node) + << " is part of the Qt compatibility layer." + << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) + << " It is provided to keep old source code working. " + << "We strongly advise against using it in new code." + << Atom::ParaRight; + } + break; + case Node::Internal: + default: + break; + } + generateText(text, node, marker); +} + +/*! + Generates a bold line that says: + "The signal is private, not emitted by the user. + The function is public so the user can pass it to connect()." + */ +void Generator::generatePrivateSignalNote(const Node* node, CodeMarker* marker) +{ + Text text; + text << Atom::ParaLeft + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) + << "Note: " + << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) + << "This is a private signal. It can be used in signal connections but cannot be emitted by the user." + << Atom::ParaRight; + generateText(text, node, marker); +} + +/*! + Generate the documentation for \a relative. i.e. \a relative + is the node that reporesentas the entity where a qdoc comment + was found, and \a text represents the qdoc comment. + */ +bool Generator::generateText(const Text& text, + const Node *relative, + CodeMarker *marker) +{ + bool result = false; + if (text.firstAtom() != 0) { + int numAtoms = 0; + initializeTextOutput(); + generateAtomList(text.firstAtom(), + relative, + marker, + true, + numAtoms); + result = true; + } + return result; +} + +void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker) +{ + Text text; + Node::ThreadSafeness threadSafeness = node->threadSafeness(); + + Text rlink; + rlink << Atom(Atom::Link,"reentrant") + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << "reentrant" + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + + Text tlink; + tlink << Atom(Atom::Link,"thread-safe") + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << "thread-safe" + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + + switch (threadSafeness) { + case Node::UnspecifiedSafeness: + break; + case Node::NonReentrant: + text << Atom::ParaLeft + << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD) + << "Warning:" + << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD) + << " This " + << typeString(node) + << " is not " + << rlink + << "." + << Atom::ParaRight; + break; + case Node::Reentrant: + case Node::ThreadSafe: + text << Atom::ParaLeft + << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD) + << "Note:" + << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD) + << " "; + + if (node->isAggregate()) { + const Aggregate* innerNode = static_cast<const Aggregate*>(node); + text << "All functions in this " + << typeString(node) + << " are "; + if (threadSafeness == Node::ThreadSafe) + text << tlink; + else + text << rlink; + + bool exceptions = false; + NodeList reentrant; + NodeList threadsafe; + NodeList nonreentrant; + NodeList::ConstIterator c = innerNode->childNodes().constBegin(); + while (c != innerNode->childNodes().constEnd()) { + + if ((*c)->status() != Node::Obsolete){ + switch ((*c)->threadSafeness()) { + case Node::Reentrant: + reentrant.append(*c); + if (threadSafeness == Node::ThreadSafe) + exceptions = true; + break; + case Node::ThreadSafe: + threadsafe.append(*c); + if (threadSafeness == Node::Reentrant) + exceptions = true; + break; + case Node::NonReentrant: + nonreentrant.append(*c); + exceptions = true; + break; + default: + break; + } + } + ++c; + } + if (!exceptions) + text << "."; + else if (threadSafeness == Node::Reentrant) { + if (nonreentrant.isEmpty()) { + if (!threadsafe.isEmpty()) { + text << ", but "; + appendFullNames(text,threadsafe,innerNode); + singularPlural(text,threadsafe); + text << " also " << tlink << "."; + } + else + text << "."; + } + else { + text << ", except for "; + appendFullNames(text,nonreentrant,innerNode); + text << ", which"; + singularPlural(text,nonreentrant); + text << " nonreentrant."; + if (!threadsafe.isEmpty()) { + text << " "; + appendFullNames(text,threadsafe,innerNode); + singularPlural(text,threadsafe); + text << " " << tlink << "."; + } + } + } + else { // thread-safe + if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) { + text << ", except for "; + if (!reentrant.isEmpty()) { + appendFullNames(text,reentrant,innerNode); + text << ", which"; + singularPlural(text,reentrant); + text << " only " << rlink; + if (!nonreentrant.isEmpty()) + text << ", and "; + } + if (!nonreentrant.isEmpty()) { + appendFullNames(text,nonreentrant,innerNode); + text << ", which"; + singularPlural(text,nonreentrant); + text << " nonreentrant."; + } + text << "."; + } + } + } + else { + text << "This " << typeString(node) << " is "; + if (threadSafeness == Node::ThreadSafe) + text << tlink; + else + text << rlink; + text << "."; + } + text << Atom::ParaRight; + } + generateText(text,node,marker); +} + +/*! + If the node is an overloaded signal, and a node with an example on how to connect to it + */ +void Generator::generateOverloadedSignal(const Node* node, CodeMarker* marker) +{ + if (node->type() != Node::Function) + return; + const FunctionNode *func = static_cast<const FunctionNode *>(node); + if (func->metaness() != FunctionNode::Signal) + return; + if (node->parent()->overloads(node->name()).count() <= 1) + return; + + + // Compute a friendly name for the object of that instance. + // e.g: "QAbstractSocket" -> "abstractSocket" + QString objectName = node->parent()->name(); + if (objectName.size() >= 2) { + if (objectName[0] == 'Q') + objectName = objectName.mid(1); + objectName[0] = objectName[0].toLower(); + } + + + // We have an overloaded signal, show an example + QString code = "connect(" + objectName + ", static_cast<" + func->returnType() + + QLatin1Char('(') + func->parent()->name() + "::*)("; + for (int i = 0; i < func->parameters().size(); ++i) { + if (i != 0) + code += ", "; + const Parameter &p = func->parameters().at(i); + code += p.dataType() + p.rightType(); + } + + code += QLatin1Char(')'); + if (func->isConst()) + code += " const"; + code += ">(&" + func->parent()->name() + "::" + func->name() + "),\n [=]("; + + for (int i = 0; i < func->parameters().size(); ++i) { + if (i != 0) + code += ", "; + const Parameter &p = func->parameters().at(i); + code += p.dataType(); + if (code[code.size()-1].isLetterOrNumber()) + code += QLatin1Char(' '); + code += p.name() + p.rightType(); + } + + code += "){ /* ... */ });"; + + Text text; + text << Atom::ParaLeft + << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD) + << "Note:" + << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD) + << "Signal " + << Atom(Atom::FormattingLeft,ATOM_FORMATTING_ITALIC) + << node->name() + << Atom(Atom::FormattingRight,ATOM_FORMATTING_ITALIC) + << " is overloaded in this class. " + "To connect to this one using the function pointer syntax, you must " + "specify the signal type in a static cast, as shown in this example:" + << Atom(Atom::Code, marker->markedUpCode(code, node, func->location())); + + generateText(text, node, marker); +} + + +/*! + Traverses the database recursivly to generate all the documentation. + */ +void Generator::generateDocs() +{ + generateAggregate(qdb_->primaryTreeRoot()); +} + +Generator *Generator::generatorForFormat(const QString& format) +{ + QList<Generator *>::ConstIterator g = generators.constBegin(); + while (g != generators.constEnd()) { + if ((*g)->format() == format) + return *g; + ++g; + } + return 0; +} + +/*! + Looks up the tag \a t in the map of metadata values for the + current topic in \a inner. If a value for the tag is found, + the value is returned. + + \note If \a t is found in the metadata map, it is erased. + i.e. Once you call this function for a particular \a t, + you consume \a t. + */ +QString Generator::getMetadataElement(const Aggregate* inner, const QString& t) +{ + QString s; + QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap()); + QStringMultiMap::iterator i = metaTagMap.find(t); + if (i != metaTagMap.end()) { + s = i.value(); + metaTagMap.erase(i); + } + return s; +} + +/*! + Looks up the tag \a t in the map of metadata values for the + current topic in \a inner. If values for the tag are found, + they are returned in a string list. + + \note If \a t is found in the metadata map, all the pairs + having the key \a t are erased. i.e. Once you call this + function for a particular \a t, you consume \a t. + */ +QStringList Generator::getMetadataElements(const Aggregate* inner, const QString& t) +{ + QStringList s; + QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap()); + s = metaTagMap.values(t); + if (!s.isEmpty()) + metaTagMap.remove(t); + return s; +} + +/*! + Returns a relative path name for an image. + */ +QString Generator::imageFileName(const Node *relative, const QString& fileBase) +{ + QString userFriendlyFilePath; + QString filePath = Config::findFile(relative->doc().location(), + imageFiles, + imageDirs, + fileBase, + imgFileExts[format()], + userFriendlyFilePath); + + if (filePath.isEmpty()) + return QString(); + + QString path = Config::copyFile(relative->doc().location(), + filePath, + userFriendlyFilePath, + outputDir() + QLatin1String("/images")); + int images_slash = path.lastIndexOf("images/"); + QString relImagePath; + if (images_slash != -1) + relImagePath = path.mid(images_slash); + return relImagePath; +} + +QString Generator::indent(int level, const QString& markedCode) +{ + if (level == 0) + return markedCode; + + QString t; + int column = 0; + + int i = 0; + while (i < (int) markedCode.length()) { + if (markedCode.at(i) == QLatin1Char('\n')) { + column = 0; + } + else { + if (column == 0) { + for (int j = 0; j < level; j++) + t += QLatin1Char(' '); + } + column++; + } + t += markedCode.at(i++); + } + return t; +} + +void Generator::initialize(const Config &config) +{ + + if (config.getBool(QString("HTML.nosubdirs"))) + resetUseOutputSubdirs(); + + outFileNames_.clear(); + outputFormats = config.getOutputFormats(); + redirectDocumentationToDevNull_ = config.getBool(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL); + if (!outputFormats.isEmpty()) { + outDir_ = config.getOutputDir(); + if (outDir_.isEmpty()) { + config.lastLocation().fatal(tr("No output directory specified in " + "configuration file or on the command line")); + } + else { + outSubdir_ = outDir_.mid(outDir_.lastIndexOf('/') + 1); + } + + QDir dirInfo; + if (dirInfo.exists(outDir_)) { + if (!generating() && Generator::useOutputSubdirs()) { + if (!Config::removeDirContents(outDir_)) + config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_)); + } + } + else { + if (!dirInfo.mkpath(outDir_)) + config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_)); + } + + if (!dirInfo.exists(outDir_ + "/images") && !dirInfo.mkdir(outDir_ + "/images")) + config.lastLocation().fatal(tr("Cannot create images directory '%1'").arg(outDir_ + "/images")); + } + + imageFiles = config.getCanonicalPathList(CONFIG_IMAGES); + imageDirs = config.getCanonicalPathList(CONFIG_IMAGEDIRS); + scriptFiles = config.getCanonicalPathList(CONFIG_SCRIPTS); + scriptDirs = config.getCanonicalPathList(CONFIG_SCRIPTDIRS); + styleFiles = config.getCanonicalPathList(CONFIG_STYLES); + styleDirs = config.getCanonicalPathList(CONFIG_STYLEDIRS); + exampleDirs = config.getCanonicalPathList(CONFIG_EXAMPLEDIRS); + exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS); + + QString imagesDotFileExtensions = CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS; + QSet<QString> formats = config.subVars(imagesDotFileExtensions); + QSet<QString>::ConstIterator f = formats.constBegin(); + while (f != formats.constEnd()) { + imgFileExts[*f] = config.getStringList(imagesDotFileExtensions + Config::dot + *f); + ++f; + } + + QList<Generator *>::ConstIterator g = generators.constBegin(); + while (g != generators.constEnd()) { + if (outputFormats.contains((*g)->format())) { + currentGenerator_ = (*g); + (*g)->initializeGenerator(config); + QStringList extraImages = config.getCanonicalPathList((*g)->format() + + Config::dot + + CONFIG_EXTRAIMAGES, true); + QStringList::ConstIterator e = extraImages.constBegin(); + while (e != extraImages.constEnd()) { + QString filePath = *e; + if (!filePath.isEmpty()) + Config::copyFile(config.lastLocation(), filePath, filePath, + (*g)->outputDir() + "/images"); + ++e; + } + + // Documentation template handling + QStringList scripts = config.getCanonicalPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS, true); + if (!scripts.isEmpty()) { + QDir dirInfo; + if (!dirInfo.exists(outDir_ + "/scripts") && !dirInfo.mkdir(outDir_ + "/scripts")) { + config.lastLocation().fatal(tr("Cannot create scripts directory '%1'") + .arg(outDir_ + "/scripts")); + } + else { + e = scripts.constBegin(); + while (e != scripts.constEnd()) { + QString filePath = *e; + if (!filePath.isEmpty()) + Config::copyFile(config.lastLocation(), filePath, filePath, + (*g)->outputDir() + "/scripts"); + ++e; + } + } + } + + QStringList styles = config.getCanonicalPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS, true); + if (!styles.isEmpty()) { + QDir dirInfo; + if (!dirInfo.exists(outDir_ + "/style") && !dirInfo.mkdir(outDir_ + "/style")) { + config.lastLocation().fatal(tr("Cannot create style directory '%1'") + .arg(outDir_ + "/style")); + } + else { + e = styles.constBegin(); + while (e != styles.constEnd()) { + QString filePath = *e; + if (!filePath.isEmpty()) + Config::copyFile(config.lastLocation(), filePath, filePath, + (*g)->outputDir() + "/style"); + ++e; + } + } + } + } + ++g; + } + + QRegExp secondParamAndAbove("[\2-\7]"); + QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING); + QSet<QString>::ConstIterator n = formattingNames.constBegin(); + while (n != formattingNames.constEnd()) { + QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n; + QSet<QString> formats = config.subVars(formattingDotName); + QSet<QString>::ConstIterator f = formats.constBegin(); + while (f != formats.constEnd()) { + QString def = config.getString(formattingDotName + Config::dot + *f); + if (!def.isEmpty()) { + int numParams = Config::numParams(def); + int numOccs = def.count("\1"); + if (numParams != 1) { + config.lastLocation().warning(tr("Formatting '%1' must " + "have exactly one " + "parameter (found %2)") + .arg(*n).arg(numParams)); + } + else if (numOccs > 1) { + config.lastLocation().fatal(tr("Formatting '%1' must " + "contain exactly one " + "occurrence of '\\1' " + "(found %2)") + .arg(*n).arg(numOccs)); + } + else { + int paramPos = def.indexOf("\1"); + fmtLeftMaps[*f].insert(*n, def.left(paramPos)); + fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1)); + } + } + ++f; + } + ++n; + } + + project_ = config.getString(CONFIG_PROJECT); + + outputPrefixes.clear(); + QStringList items = config.getStringList(CONFIG_OUTPUTPREFIXES); + if (!items.isEmpty()) { + foreach (const QString &prefix, items) + outputPrefixes[prefix] = config.getString(CONFIG_OUTPUTPREFIXES + Config::dot + prefix); + } + else { + outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-"); + outputPrefixes[QLatin1String("JS")] = QLatin1String("js-"); + } + + outputSuffixes.clear(); + items = config.getStringList(CONFIG_OUTPUTSUFFIXES); + if (!items.isEmpty()) { + foreach (const QString &suffix, items) + outputSuffixes[suffix] = config.getString(CONFIG_OUTPUTSUFFIXES + Config::dot + suffix); + } + + noLinkErrors_ = config.getBool(CONFIG_NOLINKERRORS); + autolinkErrors_ = config.getBool(CONFIG_AUTOLINKERRORS); +} + +/*! + Appends each directory path in \a moreImageDirs to the + list of image directories. + */ +void Generator::augmentImageDirs(QSet<QString>& moreImageDirs) +{ + if (moreImageDirs.isEmpty()) + return; + QSet<QString>::const_iterator i = moreImageDirs.begin(); + while (i != moreImageDirs.end()) { + imageDirs.append(*i); + ++i; + } +} + +/*! + Sets the generator's pointer to the Config instance. + */ +void Generator::initializeGenerator(const Config& config) +{ + config_ = &config; + showInternal_ = config.getBool(CONFIG_SHOWINTERNAL); + singleExec_ = config.getBool(CONFIG_SINGLEEXEC); +} + +bool Generator::matchAhead(const Atom *atom, Atom::AtomType expectedAtomType) +{ + return atom->next() != 0 && atom->next()->type() == expectedAtomType; +} + +/*! + Used for writing to the current output stream. Returns a + reference to the current output stream, which is then used + with the \c {<<} operator for writing. + */ +QTextStream &Generator::out() +{ + return *outStreamStack.top(); +} + +QString Generator::outFileName() +{ + return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName(); +} + +QString Generator::outputPrefix(const Node *node) +{ + // Prefix is applied to QML and JS types + if (node->isQmlType() || node->isQmlBasicType()) + return outputPrefixes[QLatin1String("QML")]; + if (node->isJsType() || node->isJsBasicType()) + return outputPrefixes[QLatin1String("JS")]; + return QString(); +} + +QString Generator::outputSuffix(const Node *node) +{ + // Suffix is applied to QML and JS types, as + // well as module pages. + if (node->isQmlModule() || node->isQmlType() || node->isQmlBasicType()) + return outputSuffixes[QLatin1String("QML")]; + if (node->isJsModule() || node->isJsType() || node->isJsBasicType()) + return outputSuffixes[QLatin1String("JS")]; + return QString(); +} + +bool Generator::parseArg(const QString& src, + const QString& tag, + int* pos, + int n, + QStringRef* contents, + QStringRef* par1, + bool debug) +{ +#define SKIP_CHAR(c) \ + if (debug) \ + qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \ + if (i >= n || src[i] != c) { \ + if (debug) \ + qDebug() << " char '" << c << "' not found"; \ + return false; \ +} \ + ++i; + + +#define SKIP_SPACE \ + while (i < n && src[i] == ' ') \ + ++i; + + int i = *pos; + int j = i; + + // assume "<@" has been parsed outside + //SKIP_CHAR('<'); + //SKIP_CHAR('@'); + + if (tag != QStringRef(&src, i, tag.length())) { + if (0 && debug) + qDebug() << "tag " << tag << " not found at " << i; + return false; + } + + if (debug) + qDebug() << "haystack:" << src << "needle:" << tag << "i:" <<i; + + // skip tag + i += tag.length(); + + // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)"); + if (par1) { + SKIP_SPACE; + // read parameter name + j = i; + while (i < n && src[i].isLetter()) + ++i; + if (src[i] == '=') { + if (debug) + qDebug() << "read parameter" << QString(src.data() + j, i - j); + SKIP_CHAR('='); + SKIP_CHAR('"'); + // skip parameter name + j = i; + while (i < n && src[i] != '"') + ++i; + *par1 = QStringRef(&src, j, i - j); + SKIP_CHAR('"'); + SKIP_SPACE; + } else { + if (debug) + qDebug() << "no optional parameter found"; + } + } + SKIP_SPACE; + SKIP_CHAR('>'); + + // find contents up to closing "</@tag> + j = i; + for (; true; ++i) { + if (i + 4 + tag.length() > n) + return false; + if (src[i] != '<') + continue; + if (src[i + 1] != '/') + continue; + if (src[i + 2] != '@') + continue; + if (tag != QStringRef(&src, i + 3, tag.length())) + continue; + if (src[i + 3 + tag.length()] != '>') + continue; + break; + } + + *contents = QStringRef(&src, j, i - j); + + i += tag.length() + 4; + + *pos = i; + if (debug) + qDebug() << " tag " << tag << " found: pos now: " << i; + return true; +#undef SKIP_CHAR +} + +QString Generator::plainCode(const QString& markedCode) +{ + QString t = markedCode; + t.replace(tag, QString()); + t.replace(quot, QLatin1String("\"")); + t.replace(gt, QLatin1String(">")); + t.replace(lt, QLatin1String("<")); + t.replace(amp, QLatin1String("&")); + return t; +} + +void Generator::setImageFileExtensions(const QStringList& extensions) +{ + imgFileExts[format()] = extensions; +} + +void Generator::singularPlural(Text& text, const NodeList& nodes) +{ + if (nodes.count() == 1) + text << " is"; + else + text << " are"; +} + +int Generator::skipAtoms(const Atom *atom, Atom::AtomType type) const +{ + int skipAhead = 0; + atom = atom->next(); + while (atom != 0 && atom->type() != type) { + skipAhead++; + atom = atom->next(); + } + return skipAhead; +} + +/*! + Resets the variables used during text output. + */ +void Generator::initializeTextOutput() +{ + inLink_ = false; + inContents_ = false; + inSectionHeading_ = false; + inTableHeader_ = false; + numTableRows_ = 0; + threeColumnEnumValueTable_ = true; + link_.clear(); + sectionNumber_.clear(); +} + +void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList) +{ + if (node->type() == Node::Function) { + const FunctionNode *func = static_cast<const FunctionNode *>(node); + if (func->overloadNumber() == 0) { + QString alternateName; + const FunctionNode *alternateFunc = 0; + + if (func->name().startsWith("set") && func->name().size() >= 4) { + alternateName = func->name()[3].toLower(); + alternateName += func->name().mid(4); + alternateFunc = func->parent()->findFunctionNode(alternateName, QString()); + + if (!alternateFunc) { + alternateName = "is" + func->name().mid(3); + alternateFunc = func->parent()->findFunctionNode(alternateName, QString()); + if (!alternateFunc) { + alternateName = "has" + func->name().mid(3); + alternateFunc = func->parent()->findFunctionNode(alternateName, QString()); + } + } + } + else if (!func->name().isEmpty()) { + alternateName = "set"; + alternateName += func->name()[0].toUpper(); + alternateName += func->name().mid(1); + alternateFunc = func->parent()->findFunctionNode(alternateName, QString()); + } + + if (alternateFunc && alternateFunc->access() != Node::Private) { + int i; + for (i = 0; i < alsoList.size(); ++i) { + if (alsoList.at(i).toString().contains(alternateName)) + break; + } + + if (i == alsoList.size()) { + alternateName += "()"; + + Text also; + also << Atom(Atom::Link, alternateName) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << alternateName + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + alsoList.prepend(also); + } + } + } + } +} + +void Generator::terminate() +{ + QList<Generator *>::ConstIterator g = generators.constBegin(); + while (g != generators.constEnd()) { + if (outputFormats.contains((*g)->format())) + (*g)->terminateGenerator(); + ++g; + } + + fmtLeftMaps.clear(); + fmtRightMaps.clear(); + imgFileExts.clear(); + imageFiles.clear(); + imageDirs.clear(); + outDir_.clear(); +} + +void Generator::terminateGenerator() +{ +} + +/*! + Trims trailing whitespace off the \a string and returns + the trimmed string. + */ +QString Generator::trimmedTrailing(const QString& string, const QString &prefix, const QString &suffix) +{ + QString trimmed = string; + while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace()) + trimmed.truncate(trimmed.length() - 1); + + trimmed.append(suffix); + trimmed.prepend(prefix); + return trimmed; +} + +QString Generator::typeString(const Node *node) +{ + switch (node->type()) { + case Node::Namespace: + return "namespace"; + case Node::Class: + return "class"; + case Node::QmlType: + return "type"; + case Node::QmlBasicType: + return "type"; + case Node::Document: + return "documentation"; + case Node::Enum: + return "enum"; + case Node::Typedef: + return "typedef"; + case Node::Function: + return "function"; + case Node::Property: + return "property"; + case Node::QmlPropertyGroup: + return "property group"; + case Node::QmlProperty: + return "QML property"; + case Node::QmlSignal: + return "QML signal"; + case Node::QmlSignalHandler: + return "QML signal handler"; + case Node::QmlMethod: + return "QML method"; + default: + return "documentation"; + } +} + +void Generator::unknownAtom(const Atom *atom) +{ + Location::internalError(tr("unknown atom type '%1' in %2 generator") + .arg(atom->typeString()).arg(format())); +} + +QT_END_NAMESPACE diff --git a/src/qdoc/generator.h b/src/qdoc/generator.h new file mode 100644 index 000000000..025f51698 --- /dev/null +++ b/src/qdoc/generator.h @@ -0,0 +1,260 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef GENERATOR_H +#define GENERATOR_H + +#include <qfile.h> +#include <qfileinfo.h> +#include <qlist.h> +#include <qmap.h> +#include <qregexp.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qtextstream.h> +#include "config.h" +#include "node.h" +#include "text.h" + +QT_BEGIN_NAMESPACE + +typedef QMultiMap<QString, Node*> NodeMultiMap; +typedef QMap<Node*, NodeMultiMap> ParentMaps; + +class Config; +class CodeMarker; +class Location; +class QDocDatabase; + +class Generator +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::Generator) + +public: + enum QDocPass { Neither, Prepare, Generate }; + enum ListType { Generic, Obsolete }; + + Generator(); + virtual ~Generator(); + + virtual bool canHandleFormat(const QString &format) { return format == this->format(); } + virtual QString format() = 0; + virtual void generateDocs(); + virtual void initializeGenerator(const Config &config); + virtual void terminateGenerator(); + + QString fullDocumentLocation(const Node *node, bool useSubdir = false); + const Config* config() { return config_; } + + static Generator *currentGenerator() { return currentGenerator_; } + static Generator *generatorForFormat(const QString& format); + static void initialize(const Config& config); + static const QString& outputDir() { return outDir_; } + static const QString& outputSubdir() { return outSubdir_; } + static void terminate(); + static const QStringList& outputFileNames() { return outFileNames_; } + static void writeOutFileNames(); + static void augmentImageDirs(QSet<QString>& moreImageDirs); + static void debug(const QString& message); + static void startDebugging(const QString& message); + static void stopDebugging(const QString& message); + static bool debugging() { return debugging_; } + static bool noLinkErrors() { return noLinkErrors_; } + static bool autolinkErrors() { return autolinkErrors_; } + static void setQDocPass(QDocPass t) { qdocPass_ = t; } + static bool preparing() { return (qdocPass_ == Prepare); } + static bool generating() { return (qdocPass_ == Generate); } + static bool singleExec() { return qdocSingleExec_; } + static bool writeQaPages() { return qdocWriteQaPages_; } + static void setSingleExec() { qdocSingleExec_ = true; } + static void setWriteQaPages() { qdocWriteQaPages_ = true; } + static QString defaultModuleName() { return project_; } + static void resetUseOutputSubdirs() { useOutputSubdirs_ = false; } + static bool useOutputSubdirs() { return useOutputSubdirs_; } + static void setQmlTypeContext(QmlTypeNode* t) { qmlTypeContext_ = t; } + static QmlTypeNode* qmlTypeContext() { return qmlTypeContext_; } + static QString cleanRef(const QString& ref); + +protected: + virtual void beginSubPage(const Aggregate* node, const QString& fileName); + virtual void endSubPage(); + virtual QString fileBase(const Node* node) const; + virtual QString fileExtension() const = 0; + virtual void generateQAPage() { } + virtual void generateAlsoList(const Node *node, CodeMarker *marker); + virtual int generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker); + virtual void generateBody(const Node *node, CodeMarker *marker); + virtual void generateClassLikeNode(Aggregate* inner, CodeMarker* marker); + virtual void generateQmlTypePage(QmlTypeNode* , CodeMarker* ) { } + virtual void generateQmlBasicTypePage(QmlBasicTypeNode* , CodeMarker* ) { } + virtual void generateDocumentNode(DocumentNode* dn, CodeMarker* marker); + virtual void generateCollectionNode(CollectionNode* cn, CodeMarker* marker); + virtual void generateInheritedBy(const ClassNode *classe, CodeMarker *marker); + virtual void generateInherits(const ClassNode *classe, CodeMarker *marker); + virtual void generateAggregate(Aggregate* node); + virtual void generateMaintainerList(const Aggregate* node, CodeMarker* marker); + virtual void generateQmlInheritedBy(const QmlTypeNode* qcn, CodeMarker* marker); + virtual void generateQmlInherits(QmlTypeNode* qcn, CodeMarker* marker); + virtual bool generateQmlText(const Text& text, + const Node *relative, + CodeMarker *marker, + const QString& qmlName); + virtual bool generateText(const Text& text, const Node *relative, CodeMarker *marker); + virtual QString imageFileName(const Node *relative, const QString& fileBase); + virtual int skipAtoms(const Atom *atom, Atom::AtomType type) const; + virtual QString typeString(const Node *node); + + static bool matchAhead(const Atom *atom, Atom::AtomType expectedAtomType); + static QString outputPrefix(const Node* node); + static QString outputSuffix(const Node* node); + static void singularPlural(Text& text, const NodeList& nodes); + static void supplementAlsoList(const Node *node, QList<Text> &alsoList); + static QString trimmedTrailing(const QString &string, + const QString &prefix, + const QString &suffix); + static QString sinceTitles[]; + + void initializeTextOutput(); + QString fileName(const Node* node) const; + QMap<QString, QString> &formattingLeftMap(); + QMap<QString, QString> &formattingRightMap(); + const Atom* generateAtomList(const Atom *atom, + const Node *relative, + CodeMarker *marker, + bool generate, + int& numGeneratedAtoms); + void generateExampleFiles(const DocumentNode *dn, CodeMarker *marker); + void generateFileList(const DocumentNode* dn, + CodeMarker* marker, + Node::DocSubtype subtype, + const QString& tag); + void generateSince(const Node *node, CodeMarker *marker); + void generateStatus(const Node *node, CodeMarker *marker); + void generatePrivateSignalNote(const Node* node, CodeMarker* marker); + void generateThreadSafeness(const Node *node, CodeMarker *marker); + QString getMetadataElement(const Aggregate* inner, const QString& t); + QStringList getMetadataElements(const Aggregate* inner, const QString& t); + void generateOverloadedSignal(const Node *node, CodeMarker *marker); + QString indent(int level, const QString& markedCode); + QTextStream& out(); + QString outFileName(); + bool parseArg(const QString& src, + const QString& tag, + int* pos, + int n, + QStringRef* contents, + QStringRef* par1 = 0, + bool debug = false); + QString plainCode(const QString& markedCode); + void setImageFileExtensions(const QStringList& extensions); + void unknownAtom(const Atom *atom); + void appendSortedQmlNames(Text& text, const Node* base, const NodeList& subs); + + QMap<QString, QStringList> editionGroupMap; + QMap<QString, QStringList> editionModuleMap; + QString naturalLanguage; +#ifndef QT_NO_TEXTCODEC + QTextCodec* outputCodec; + QString outputEncoding; +#endif + QString tagFile_; + QStack<QTextStream*> outStreamStack; + + void appendFullName(Text& text, + const Node *apparentNode, + const Node *relative, + const Node *actualNode = 0); + void appendFullName(Text& text, + const Node *apparentNode, + const QString& fullName, + const Node *actualNode); + void appendFullNames(Text& text, const NodeList& nodes, const Node* relative); + void appendSortedNames(Text& text, const ClassNode *classe, const QList<RelatedClass> &classes); + +private: + static Generator* currentGenerator_; + static QStringList exampleDirs; + static QStringList exampleImgExts; + static QMap<QString, QMap<QString, QString> > fmtLeftMaps; + static QMap<QString, QMap<QString, QString> > fmtRightMaps; + static QList<Generator *> generators; + static QStringList imageDirs; + static QStringList imageFiles; + static QMap<QString, QStringList> imgFileExts; + static QString project_; + static QString outDir_; + static QString outSubdir_; + static QStringList outFileNames_; + static QSet<QString> outputFormats; + static QHash<QString, QString> outputPrefixes; + static QHash<QString, QString> outputSuffixes; + static QStringList scriptDirs; + static QStringList scriptFiles; + static QStringList styleDirs; + static QStringList styleFiles; + static bool debugging_; + static bool noLinkErrors_; + static bool autolinkErrors_; + static bool redirectDocumentationToDevNull_; + static QDocPass qdocPass_; + static bool qdocSingleExec_; + static bool qdocWriteQaPages_; + static bool useOutputSubdirs_; + static QmlTypeNode* qmlTypeContext_; + + void generateReimplementedFrom(const FunctionNode *func, CodeMarker *marker); + + QString amp; + QString gt; + QString lt; + QString quot; + QRegExp tag; + + protected: + const Config* config_; + QDocDatabase* qdb_; + bool inLink_; + bool inContents_; + bool inSectionHeading_; + bool inTableHeader_; + bool threeColumnEnumValueTable_; + bool showInternal_; + bool singleExec_; + int numTableRows_; + QString link_; + QString sectionNumber_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/helpprojectwriter.cpp b/src/qdoc/helpprojectwriter.cpp new file mode 100644 index 000000000..8161913f1 --- /dev/null +++ b/src/qdoc/helpprojectwriter.cpp @@ -0,0 +1,858 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qcryptographichash.h> +#include <qdebug.h> +#include <qhash.h> +#include <qmap.h> + +#include "atom.h" +#include "helpprojectwriter.h" +#include "htmlgenerator.h" +#include "config.h" +#include "node.h" +#include "qdocdatabase.h" +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +HelpProjectWriter::HelpProjectWriter(const Config &config, + const QString &defaultFileName, + Generator* g) +{ + reset(config, defaultFileName, g); +} + +void HelpProjectWriter::reset(const Config &config, + const QString &defaultFileName, + Generator* g) +{ + projects.clear(); + gen_ = g; + /* + Get the pointer to the singleton for the qdoc database and + store it locally. This replaces all the local accesses to + the node tree, which are now private. + */ + qdb_ = QDocDatabase::qdocDB(); + + // The output directory should already have been checked by the calling + // generator. + outputDir = config.getOutputDir(); + + QStringList names = config.getStringList(CONFIG_QHP + Config::dot + "projects"); + + foreach (const QString &projectName, names) { + HelpProject project; + project.name = projectName; + + QString prefix = CONFIG_QHP + Config::dot + projectName + Config::dot; + project.helpNamespace = config.getString(prefix + "namespace"); + project.virtualFolder = config.getString(prefix + "virtualFolder"); + project.fileName = config.getString(prefix + "file"); + if (project.fileName.isEmpty()) + project.fileName = defaultFileName; + project.extraFiles = config.getStringSet(prefix + "extraFiles"); + project.extraFiles += config.getStringSet(CONFIG_QHP + Config::dot + "extraFiles"); + project.indexTitle = config.getString(prefix + "indexTitle"); + project.indexRoot = config.getString(prefix + "indexRoot"); + project.filterAttributes = config.getStringList(prefix + "filterAttributes").toSet(); + project.includeIndexNodes = config.getBool(prefix + "includeIndexNodes"); + QSet<QString> customFilterNames = config.subVars(prefix + "customFilters"); + foreach (const QString &filterName, customFilterNames) { + QString name = config.getString(prefix + "customFilters" + Config::dot + filterName + Config::dot + "name"); + QSet<QString> filters = config.getStringList(prefix + "customFilters" + Config::dot + filterName + Config::dot + "filterAttributes").toSet(); + project.customFilters[name] = filters; + } + //customFilters = config.defs. + + foreach (QString name, config.getStringSet(prefix + "excluded")) + project.excluded.insert(name.replace(QLatin1Char('\\'), QLatin1Char('/'))); + + foreach (const QString &name, config.getStringList(prefix + "subprojects")) { + SubProject subproject; + QString subprefix = prefix + "subprojects" + Config::dot + name + Config::dot; + subproject.title = config.getString(subprefix + "title"); + subproject.indexTitle = config.getString(subprefix + "indexTitle"); + subproject.sortPages = config.getBool(subprefix + "sortPages"); + subproject.type = config.getString(subprefix + "type"); + readSelectors(subproject, config.getStringList(subprefix + "selectors")); + project.subprojects.append(subproject); + } + + if (project.subprojects.isEmpty()) { + SubProject subproject; + readSelectors(subproject, config.getStringList(prefix + "selectors")); + project.subprojects.insert(0, subproject); + } + + projects.append(project); + } +} + +void HelpProjectWriter::readSelectors(SubProject &subproject, const QStringList &selectors) +{ + QHash<QString, Node::NodeType> typeHash; + typeHash["namespace"] = Node::Namespace; + typeHash["class"] = Node::Class; + typeHash["doc"] = Node::Document; + typeHash["fake"] = Node::Document; // Legacy alias for 'doc' + typeHash["enum"] = Node::Enum; + typeHash["typedef"] = Node::Typedef; + typeHash["function"] = Node::Function; + typeHash["property"] = Node::Property; + typeHash["variable"] = Node::Variable; + typeHash["group"] = Node::Group; + typeHash["module"] = Node::Module; + typeHash["qmlmodule"] = Node::QmlModule; + typeHash["qmlproperty"] = Node::QmlProperty; + typeHash["qmlsignal"] = Node::QmlSignal; + typeHash["qmlsignalhandler"] = Node::QmlSignalHandler; + typeHash["qmlmethod"] = Node::QmlMethod; + typeHash["qmlpropertygroup"] = Node::QmlPropertyGroup; + typeHash["qmlclass"] = Node::QmlType; // Legacy alias for 'qmltype' + typeHash["qmltype"] = Node::QmlType; + typeHash["qmlbasictype"] = Node::QmlBasicType; + + QHash<QString, Node::DocSubtype> docSubtypeHash; + docSubtypeHash["example"] = Node::Example; + docSubtypeHash["headerfile"] = Node::HeaderFile; + docSubtypeHash["file"] = Node::File; + docSubtypeHash["page"] = Node::Page; + docSubtypeHash["externalpage"] = Node::ExternalPage; + + QSet<Node::DocSubtype> allSubTypes = QSet<Node::DocSubtype>::fromList(docSubtypeHash.values()); + + foreach (const QString &selector, selectors) { + QStringList pieces = selector.split(QLatin1Char(':')); + if (pieces.size() == 1) { + QString lower = selector.toLower(); + if (typeHash.contains(lower)) + subproject.selectors[typeHash[lower]] = allSubTypes; + } else if (pieces.size() >= 2) { + QString docType = pieces[0].toLower(); + pieces = pieces[1].split(QLatin1Char(',')); + if (typeHash.contains(docType)) { + QSet<Node::DocSubtype> docSubtypes; + for (int i = 0; i < pieces.size(); ++i) { + QString piece = pieces[i].toLower(); + if (typeHash[docType] == Node::Group) { + subproject.groups << piece; + continue; + } + if (docSubtypeHash.contains(piece)) + docSubtypes.insert(docSubtypeHash[piece]); + } + subproject.selectors[typeHash[docType]] = docSubtypes; + } + } + } +} + +void HelpProjectWriter::addExtraFile(const QString &file) +{ + for (int i = 0; i < projects.size(); ++i) + projects[i].extraFiles.insert(file); +} + +void HelpProjectWriter::addExtraFiles(const QSet<QString> &files) +{ + for (int i = 0; i < projects.size(); ++i) + projects[i].extraFiles.unite(files); +} + +/* + Returns a list of strings describing the keyword details for a given node. + + The first string is the human-readable name to be shown in Assistant. + The second string is a unique identifier. + The third string is the location of the documentation for the keyword. +*/ +QStringList HelpProjectWriter::keywordDetails(const Node *node) const +{ + QStringList details; + + if (node->parent() && !node->parent()->name().isEmpty()) { + // "name" + if (node->type() == Node::Enum || node->type() == Node::Typedef) + details << node->parent()->name()+"::"+node->name(); + else + details << node->name(); + // "id" + details << node->parent()->name()+"::"+node->name(); + } + else if (node->isQmlType() || node->isQmlBasicType()) { + details << node->name(); + details << "QML." + node->name(); + } + else if (node->isJsType() || node->isJsBasicType()) { + details << node->name(); + details << "JS." + node->name(); + } + else if (node->isDocumentNode()) { + const DocumentNode *fake = static_cast<const DocumentNode *>(node); + details << fake->fullTitle(); + details << fake->fullTitle(); + } + else { + details << node->name(); + details << node->name(); + } + details << gen_->fullDocumentLocation(node, false); + return details; +} + +bool HelpProjectWriter::generateSection(HelpProject &project, + QXmlStreamWriter & /* writer */, + const Node *node) +{ + if (!node->url().isEmpty() && !(project.includeIndexNodes && !node->url().startsWith("http"))) + return false; + + if (node->access() == Node::Private || node->status() == Node::Internal) + return false; + + if (node->name().isEmpty()) + return true; + + QString docPath = node->doc().location().filePath(); + if (!docPath.isEmpty() && project.excluded.contains(docPath)) + return false; + + QString objName = node->isDocumentNode() ? node->fullTitle() : node->fullDocumentName(); + // Only add nodes to the set for each subproject if they match a selector. + // Those that match will be listed in the table of contents. + + for (int i = 0; i < project.subprojects.length(); i++) { + SubProject subproject = project.subprojects[i]; + // No selectors: accept all nodes. + if (subproject.selectors.isEmpty()) { + project.subprojects[i].nodes[objName] = node; + } + else if (subproject.selectors.contains(node->type())) { + // Add all group members for 'group:name' selector + if (node->isGroup()) { + if (project.subprojects[i].groups.contains(node->name())) { + const CollectionNode* cn = static_cast<const CollectionNode*>(node); + foreach (const Node* m, cn->members()) { + QString memberName = m->isDocumentNode() + ? m->fullTitle() : m->fullDocumentName(); + project.subprojects[i].nodes[memberName] = m; + } + } + } + // Accept only the node types in the selectors hash. + else if (node->type() != Node::Document) + project.subprojects[i].nodes[objName] = node; + else { + // Accept only doc nodes with subtypes contained in the selector's + // mask. + const DocumentNode *docNode = static_cast<const DocumentNode *>(node); + if (subproject.selectors[node->type()].contains(docNode->docSubtype()) && + docNode->docSubtype() != Node::ExternalPage && + !docNode->fullTitle().isEmpty()) { + + project.subprojects[i].nodes[objName] = node; + } + } + } + } + + switch (node->type()) { + + case Node::Class: + project.keywords.append(keywordDetails(node)); + break; + case Node::QmlType: + case Node::QmlBasicType: + if (node->doc().hasKeywords()) { + foreach (const Atom* keyword, node->doc().keywords()) { + if (!keyword->string().isEmpty()) { + QStringList details; + details << keyword->string() + << keyword->string() + << gen_->fullDocumentLocation(node, false); + project.keywords.append(details); + } + else + node->doc().location().warning(tr("Bad keyword in %1").arg(gen_->fullDocumentLocation(node, false))); + } + } + project.keywords.append(keywordDetails(node)); + break; + + case Node::Namespace: + project.keywords.append(keywordDetails(node)); + break; + + case Node::Enum: + project.keywords.append(keywordDetails(node)); + { + const EnumNode *enumNode = static_cast<const EnumNode*>(node); + foreach (const EnumItem &item, enumNode->items()) { + QStringList details; + + if (enumNode->itemAccess(item.name()) == Node::Private) + continue; + + if (!node->parent()->name().isEmpty()) { + details << node->parent()->name()+"::"+item.name(); // "name" + details << node->parent()->name()+"::"+item.name(); // "id" + } else { + details << item.name(); // "name" + details << item.name(); // "id" + } + details << gen_->fullDocumentLocation(node, false); + project.keywords.append(details); + } + } + break; + + case Node::Group: + case Node::Module: + case Node::QmlModule: + { + const CollectionNode* cn = static_cast<const CollectionNode*>(node); + if (!cn->fullTitle().isEmpty()) { + if (cn->doc().hasKeywords()) { + foreach (const Atom* keyword, cn->doc().keywords()) { + if (!keyword->string().isEmpty()) { + QStringList details; + details << keyword->string() + << keyword->string() + << gen_->fullDocumentLocation(node, false); + project.keywords.append(details); + } + else + cn->doc().location().warning( + tr("Bad keyword in %1").arg(gen_->fullDocumentLocation(node, false)) + ); + } + } + project.keywords.append(keywordDetails(node)); + } + } + break; + + case Node::Property: + case Node::QmlProperty: + case Node::QmlSignal: + case Node::QmlSignalHandler: + case Node::QmlMethod: + project.keywords.append(keywordDetails(node)); + break; + + case Node::Function: + { + const FunctionNode *funcNode = static_cast<const FunctionNode *>(node); + + // Only insert keywords for non-constructors. Constructors are covered + // by the classes themselves. + + if (funcNode->metaness() != FunctionNode::Ctor) + project.keywords.append(keywordDetails(node)); + + // Insert member status flags into the entries for the parent + // node of the function, or the node it is related to. + // Since parent nodes should have already been inserted into + // the set of files, we only need to ensure that related nodes + // are inserted. + + if (node->relates()) { + project.memberStatus[node->relates()].insert(node->status()); + } else if (node->parent()) + project.memberStatus[node->parent()].insert(node->status()); + } + break; + + case Node::Typedef: + { + const TypedefNode *typedefNode = static_cast<const TypedefNode *>(node); + QStringList typedefDetails = keywordDetails(node); + const EnumNode *enumNode = typedefNode->associatedEnum(); + // Use the location of any associated enum node in preference + // to that of the typedef. + if (enumNode) + typedefDetails[2] = gen_->fullDocumentLocation(enumNode, false); + + project.keywords.append(typedefDetails); + } + break; + + case Node::Variable: + { + project.keywords.append(keywordDetails(node)); + } + break; + + // Document nodes (such as manual pages) contain subtypes, titles and other + // attributes. + case Node::Document: { + const DocumentNode *docNode = static_cast<const DocumentNode*>(node); + if (docNode->docSubtype() != Node::ExternalPage && + docNode->docSubtype() != Node::Image && + !docNode->fullTitle().isEmpty()) { + + if (docNode->docSubtype() != Node::File) { + if (docNode->doc().hasKeywords()) { + foreach (const Atom *keyword, docNode->doc().keywords()) { + if (!keyword->string().isEmpty()) { + QStringList details; + details << keyword->string() + << keyword->string() + << gen_->fullDocumentLocation(node, false); + project.keywords.append(details); + } else + docNode->doc().location().warning( + tr("Bad keyword in %1").arg(gen_->fullDocumentLocation(node, false)) + ); + } + } + project.keywords.append(keywordDetails(node)); + } + } + break; + } + default: + ; + } + + // Add all images referenced in the page to the set of files to include. + const Atom *atom = node->doc().body().firstAtom(); + while (atom) { + if (atom->type() == Atom::Image || atom->type() == Atom::InlineImage) { + // Images are all placed within a single directory regardless of + // whether the source images are in a nested directory structure. + QStringList pieces = atom->string().split(QLatin1Char('/')); + project.files.insert("images/" + pieces.last()); + } + atom = atom->next(); + } + + return true; +} + +void HelpProjectWriter::generateSections(HelpProject &project, + QXmlStreamWriter &writer, const Node *node) +{ + /* + Don't include index nodes in the help file. Or DITA map nodes. + */ + if (node->isIndexNode() || node->docSubtype() == Node::DitaMap) + return; + if (!generateSection(project, writer, node)) + return; + + if (node->isAggregate()) { + const Aggregate *inner = static_cast<const Aggregate *>(node); + + // Ensure that we don't visit nodes more than once. + QMap<QString, const Node*> childMap; + foreach (const Node *childNode, inner->childNodes()) { + if (childNode->isIndexNode()) + continue; + + if (childNode->access() == Node::Private) + continue; + + if (childNode->type() == Node::Document) { + childMap[static_cast<const DocumentNode *>(childNode)->fullTitle()] = childNode; + } + else if (childNode->isQmlPropertyGroup() || childNode->isJsPropertyGroup()) { + /* + Don't visit QML/JS property group nodes, + but visit their children, which are all + QML/JS property nodes. + + This is probably not correct anymore, + because The Qml/Js Property Group is + an actual documented thing. + */ + const Aggregate* inner = static_cast<const Aggregate*>(childNode); + foreach (const Node* n, inner->childNodes()) { + if (n->access() == Node::Private) + continue; + childMap[n->fullDocumentName()] = n; + } + } + else { + // Store member status of children + project.memberStatus[node].insert(childNode->status()); + if (childNode->relates()) { + project.memberStatus[childNode->relates()].insert(childNode->status()); + } + + if (childNode->type() == Node::Function) { + const FunctionNode *funcNode = static_cast<const FunctionNode *>(childNode); + if (funcNode->isOverload()) + continue; + } + childMap[childNode->fullDocumentName()] = childNode; + } + } + foreach (const Node *child, childMap) + generateSections(project, writer, child); + } +} + +void HelpProjectWriter::generate() +{ + for (int i = 0; i < projects.size(); ++i) + generateProject(projects[i]); +} + +void HelpProjectWriter::writeHashFile(QFile &file) +{ + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(&file); + + QFile hashFile(file.fileName() + ".sha1"); + if (!hashFile.open(QFile::WriteOnly | QFile::Text)) + return; + + hashFile.write(hash.result().toHex()); + hashFile.close(); +} + +void HelpProjectWriter::writeSection(QXmlStreamWriter &writer, const QString &path, + const QString &value) +{ + writer.writeStartElement(QStringLiteral("section")); + writer.writeAttribute(QStringLiteral("ref"), path); + writer.writeAttribute(QStringLiteral("title"), value); + writer.writeEndElement(); // section +} + +/* + Write subsections for all members, compatibility members and obsolete members. +*/ +void HelpProjectWriter::addMembers(HelpProject &project, QXmlStreamWriter &writer, + const Node *node) +{ + QString href = gen_->fullDocumentLocation(node, false); + href = href.left(href.size()-5); + if (href.isEmpty()) + return; + + bool derivedClass = false; + if (node->type() == Node::Class) + derivedClass = !(static_cast<const ClassNode *>(node)->baseClasses().isEmpty()); + + // Do not generate a 'List of all members' for namespaces or header files, + // but always generate it for derived classes and QML classes + if (!node->isNamespace() && !node->isHeaderFile() && + (derivedClass || node->isQmlType() || node->isJsType() || + !project.memberStatus[node].isEmpty())) { + QString membersPath = href + QStringLiteral("-members.html"); + writeSection(writer, membersPath, tr("List of all members")); + } + if (project.memberStatus[node].contains(Node::Compat)) { + QString compatPath = href + QStringLiteral("-compat.html"); + writeSection(writer, compatPath, tr("Compatibility members")); + } + if (project.memberStatus[node].contains(Node::Obsolete)) { + QString obsoletePath = href + QStringLiteral("-obsolete.html"); + writeSection(writer, obsoletePath, tr("Obsolete members")); + } +} + +void HelpProjectWriter::writeNode(HelpProject &project, QXmlStreamWriter &writer, + const Node *node) +{ + QString href = gen_->fullDocumentLocation(node, false); + QString objName = node->name(); + + switch (node->type()) { + + case Node::Class: + writer.writeStartElement("section"); + writer.writeAttribute("ref", href); + if (node->parent() && !node->parent()->name().isEmpty()) + writer.writeAttribute("title", tr("%1::%2 Class Reference").arg(node->parent()->name()).arg(objName)); + else + writer.writeAttribute("title", tr("%1 Class Reference").arg(objName)); + + addMembers(project, writer, node); + writer.writeEndElement(); // section + break; + + case Node::Namespace: + writeSection(writer, href, objName); + break; + + case Node::QmlType: + writer.writeStartElement("section"); + writer.writeAttribute("ref", href); + writer.writeAttribute("title", tr("%1 Type Reference").arg(node->fullTitle())); + addMembers(project, writer, node); + writer.writeEndElement(); // section + break; + + case Node::Document: { + // Document nodes (such as manual pages) contain subtypes, titles and other + // attributes. + const DocumentNode *docNode = static_cast<const DocumentNode*>(node); + + writer.writeStartElement("section"); + writer.writeAttribute("ref", href); + writer.writeAttribute("title", docNode->fullTitle()); + + if (docNode->docSubtype() == Node::HeaderFile) + addMembers(project, writer, node); + + writer.writeEndElement(); // section + } + break; + case Node::Group: + case Node::Module: + case Node::QmlModule: + { + const CollectionNode* cn = static_cast<const CollectionNode*>(node); + writer.writeStartElement("section"); + writer.writeAttribute("ref", href); + writer.writeAttribute("title", cn->fullTitle()); + writer.writeEndElement(); // section + } + break; + default: + ; + } +} + +void HelpProjectWriter::generateProject(HelpProject &project) +{ + const Node *rootNode; + + // Restrict searching only to the local (primary) tree + QVector<Tree*> searchOrder = qdb_->searchOrder(); + qdb_->setLocalSearch(); + + if (!project.indexRoot.isEmpty()) + rootNode = qdb_->findDocumentNodeByTitle(project.indexRoot); + else + rootNode = qdb_->primaryTreeRoot(); + + if (!rootNode) + return; + + project.files.clear(); + project.keywords.clear(); + + QFile file(outputDir + QDir::separator() + project.fileName); + if (!file.open(QFile::WriteOnly | QFile::Text)) + return; + + QXmlStreamWriter writer(&file); + writer.setAutoFormatting(true); + writer.writeStartDocument(); + writer.writeStartElement("QtHelpProject"); + writer.writeAttribute("version", "1.0"); + + // Write metaData, virtualFolder and namespace elements. + writer.writeTextElement("namespace", project.helpNamespace); + writer.writeTextElement("virtualFolder", project.virtualFolder); + + // Write customFilter elements. + QHash<QString, QSet<QString> >::ConstIterator it; + for (it = project.customFilters.constBegin(); it != project.customFilters.constEnd(); ++it) { + writer.writeStartElement("customFilter"); + writer.writeAttribute("name", it.key()); + foreach (const QString &filter, it.value()) + writer.writeTextElement("filterAttribute", filter); + writer.writeEndElement(); // customFilter + } + + // Start the filterSection. + writer.writeStartElement("filterSection"); + + // Write filterAttribute elements. + foreach (const QString &filterName, project.filterAttributes) + writer.writeTextElement("filterAttribute", filterName); + + writer.writeStartElement("toc"); + writer.writeStartElement("section"); + const Node* node = qdb_->findDocumentNodeByTitle(project.indexTitle); + if (node == 0) + node = qdb_->findNodeByNameAndType(QStringList("index.html"), Node::Document); + QString indexPath; + if (node) + indexPath = gen_->fullDocumentLocation(node, false); + else + indexPath = "index.html"; + writer.writeAttribute("ref", indexPath); + writer.writeAttribute("title", project.indexTitle); + + generateSections(project, writer, rootNode); + + for (int i = 0; i < project.subprojects.length(); i++) { + SubProject subproject = project.subprojects[i]; + + if (subproject.type == QLatin1String("manual")) { + + const Node *indexPage = qdb_->findNodeForTarget(subproject.indexTitle, 0); + if (indexPage) { + Text indexBody = indexPage->doc().body(); + const Atom *atom = indexBody.firstAtom(); + QStack<int> sectionStack; + bool inItem = false; + + while (atom) { + switch (atom->type()) { + case Atom::ListLeft: + sectionStack.push(0); + break; + case Atom::ListRight: + if (sectionStack.pop() > 0) + writer.writeEndElement(); // section + break; + case Atom::ListItemLeft: + inItem = true; + break; + case Atom::ListItemRight: + inItem = false; + break; + case Atom::Link: + if (inItem) { + if (sectionStack.top() > 0) + writer.writeEndElement(); // section + + const Node *page = qdb_->findNodeForTarget(atom->string(), 0); + writer.writeStartElement("section"); + QString indexPath = gen_->fullDocumentLocation(page, false); + writer.writeAttribute("ref", indexPath); + writer.writeAttribute("title", atom->string()); + + sectionStack.top() += 1; + } + break; + default: + ; + } + + if (atom == indexBody.lastAtom()) + break; + atom = atom->next(); + } + } else + rootNode->doc().location().warning( + tr("Failed to find index: %1").arg(subproject.indexTitle) + ); + + } else { + + writer.writeStartElement("section"); + QString indexPath = gen_->fullDocumentLocation(qdb_->findNodeForTarget(subproject.indexTitle, 0), + false); + writer.writeAttribute("ref", indexPath); + writer.writeAttribute("title", subproject.title); + + if (subproject.sortPages) { + QStringList titles = subproject.nodes.keys(); + titles.sort(); + foreach (const QString &title, titles) { + writeNode(project, writer, subproject.nodes[title]); + } + } else { + // Find a contents node and navigate from there, using the NextLink values. + QSet<QString> visited; + bool contentsFound = false; + foreach (const Node *node, subproject.nodes) { + QString nextTitle = node->links().value(Node::NextLink).first; + if (!nextTitle.isEmpty() && + node->links().value(Node::ContentsLink).first.isEmpty()) { + + const Node *nextPage = qdb_->findNodeForTarget(nextTitle, 0); + + // Write the contents node. + writeNode(project, writer, node); + contentsFound = true; + + while (nextPage) { + writeNode(project, writer, nextPage); + nextTitle = nextPage->links().value(Node::NextLink).first; + if (nextTitle.isEmpty() || visited.contains(nextTitle)) + break; + nextPage = qdb_->findNodeForTarget(nextTitle, 0); + visited.insert(nextTitle); + } + break; + } + } + // No contents/nextpage links found, write all nodes unsorted + if (!contentsFound) { + foreach (const Node *node, subproject.nodes) + writeNode(project, writer, node); + } + } + + writer.writeEndElement(); // section + } + } + + // Restore original search order + qdb_->setSearchOrder(searchOrder); + + writer.writeEndElement(); // section + writer.writeEndElement(); // toc + + writer.writeStartElement("keywords"); + foreach (const QStringList &details, project.keywords) { + writer.writeStartElement("keyword"); + writer.writeAttribute("name", details[0]); + writer.writeAttribute("id", details[1]); + writer.writeAttribute("ref", details[2]); + writer.writeEndElement(); //keyword + } + writer.writeEndElement(); // keywords + + writer.writeStartElement("files"); + + // The list of files to write is the union of generated files and + // other files (images and extras) included in the project + QSet<QString> files = QSet<QString>::fromList(gen_->outputFileNames()); + files.unite(project.files); + files.unite(project.extraFiles); + foreach (const QString &usedFile, files) { + if (!usedFile.isEmpty()) + writer.writeTextElement("file", usedFile); + } + writer.writeEndElement(); // files + + writer.writeEndElement(); // filterSection + writer.writeEndElement(); // QtHelpProject + writer.writeEndDocument(); + writeHashFile(file); + file.close(); +} + +QT_END_NAMESPACE diff --git a/src/qdoc/helpprojectwriter.h b/src/qdoc/helpprojectwriter.h new file mode 100644 index 000000000..e4cd1404c --- /dev/null +++ b/src/qdoc/helpprojectwriter.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef HELPPROJECTWRITER_H +#define HELPPROJECTWRITER_H + +#include <qstring.h> +#include <qxmlstream.h> + +#include "config.h" +#include "node.h" + +QT_BEGIN_NAMESPACE + +class QDocDatabase; +class Generator; +typedef QPair<QString, const Node*> QStringNodePair; + +struct SubProject +{ + QString title; + QString indexTitle; + QHash<Node::NodeType, QSet<DocumentNode::DocSubtype> > selectors; + bool sortPages; + QString type; + QHash<QString, const Node *> nodes; + QStringList groups; +}; + +struct HelpProject +{ + QString name; + QString helpNamespace; + QString virtualFolder; + QString fileName; + QString indexRoot; + QString indexTitle; + QList<QStringList> keywords; + QSet<QString> files; + QSet<QString> extraFiles; + QSet<QString> filterAttributes; + QHash<QString, QSet<QString> > customFilters; + QSet<QString> excluded; + QList<SubProject> subprojects; + QHash<const Node *, QSet<Node::Status> > memberStatus; + bool includeIndexNodes; +}; + +class HelpProjectWriter +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::HelpProjectWriter) + +public: + HelpProjectWriter(const Config &config, + const QString &defaultFileName, + Generator* g); + void reset(const Config &config, + const QString &defaultFileName, + Generator* g); + void addExtraFile(const QString &file); + void addExtraFiles(const QSet<QString> &files); + void generate(); + +private: + void generateProject(HelpProject &project); + void generateSections(HelpProject &project, QXmlStreamWriter &writer, + const Node *node); + bool generateSection(HelpProject &project, QXmlStreamWriter &writer, + const Node *node); + QStringList keywordDetails(const Node *node) const; + void writeHashFile(QFile &file); + void writeNode(HelpProject &project, QXmlStreamWriter &writer, const Node *node); + void readSelectors(SubProject &subproject, const QStringList &selectors); + void addMembers(HelpProject &project, QXmlStreamWriter &writer, + const Node *node); + void writeSection(QXmlStreamWriter &writer, const QString &path, + const QString &value); + + QDocDatabase* qdb_; + Generator* gen_; + + QString outputDir; + QList<HelpProject> projects; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/htmlgenerator.cpp b/src/qdoc/htmlgenerator.cpp new file mode 100644 index 000000000..3c03a0801 --- /dev/null +++ b/src/qdoc/htmlgenerator.cpp @@ -0,0 +1,4898 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + htmlgenerator.cpp +*/ + +#include "codemarker.h" +#include "codeparser.h" +#include "helpprojectwriter.h" +#include "htmlgenerator.h" +#include "node.h" +#include "qdocdatabase.h" +#include "separator.h" +#include "tree.h" +#include <ctype.h> +#include <qdebug.h> +#include <qlist.h> +#include <qiterator.h> +#include <qtextcodec.h> +#include <quuid.h> +#include <qmap.h> + +QT_BEGIN_NAMESPACE + +#define COMMAND_VERSION Doc::alias("version") +int HtmlGenerator::id = 0; +bool HtmlGenerator::debugging_on = false; + +QString HtmlGenerator::divNavTop; + +static bool showBrokenLinks = false; + +static QRegExp linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)"); +static QRegExp funcTag("(<@func target=\"([^\"]*)\">)(.*)(</@func>)"); +static QRegExp typeTag("(<@(type|headerfile|func)(?: +[^>]*)?>)(.*)(</@\\2>)"); +static QRegExp spanTag("</@(?:comment|preprocessor|string|char|number|op|type|name|keyword)>"); +static QRegExp unknownTag("</?@[^>]*>"); + +static void addLink(const QString &linkTarget, + const QStringRef &nestedStuff, + QString *res) +{ + if (!linkTarget.isEmpty()) { + *res += QLatin1String("<a href=\""); + *res += linkTarget; + *res += QLatin1String("\">"); + *res += nestedStuff; + *res += QLatin1String("</a>"); + } + else { + *res += nestedStuff; + } +} + +/*! + Constructs the HTML output generator. + */ +HtmlGenerator::HtmlGenerator() + : codeIndent(0), + helpProjectWriter(0), + inObsoleteLink(false), + funcLeftParen("\\S(\\()"), + obsoleteLinks(false) +{ +} + +/*! + Destroys the HTML output generator. Deletes the singleton + instance of HelpProjectWriter. + */ +HtmlGenerator::~HtmlGenerator() +{ + if (helpProjectWriter) { + delete helpProjectWriter; + helpProjectWriter = 0; + } +} + +/*! + Initializes the HTML output generator's data structures + from the configuration class \a config. + */ +void HtmlGenerator::initializeGenerator(const Config &config) +{ + static const struct { + const char *key; + const char *left; + const char *right; + } defaults[] = { + { ATOM_FORMATTING_BOLD, "<b>", "</b>" }, + { ATOM_FORMATTING_INDEX, "<!--", "-->" }, + { ATOM_FORMATTING_ITALIC, "<i>", "</i>" }, + { ATOM_FORMATTING_PARAMETER, "<i>", "</i>" }, + { ATOM_FORMATTING_SUBSCRIPT, "<sub>", "</sub>" }, + { ATOM_FORMATTING_SUPERSCRIPT, "<sup>", "</sup>" }, + { ATOM_FORMATTING_TELETYPE, "<code>", "</code>" }, // <tt> tag is not supported in HTML5 + { ATOM_FORMATTING_UICONTROL, "<b>", "</b>" }, + { ATOM_FORMATTING_UNDERLINE, "<u>", "</u>" }, + { 0, 0, 0 } + }; + + Generator::initializeGenerator(config); + obsoleteLinks = config.getBool(CONFIG_OBSOLETELINKS); + setImageFileExtensions(QStringList() << "png" << "jpg" << "jpeg" << "gif"); + + /* + The formatting maps are owned by Generator. They are cleared in + Generator::terminate(). + */ + int i = 0; + while (defaults[i].key) { + formattingLeftMap().insert(QLatin1String(defaults[i].key), QLatin1String(defaults[i].left)); + formattingRightMap().insert(QLatin1String(defaults[i].key), QLatin1String(defaults[i].right)); + i++; + } + + style = config.getString(HtmlGenerator::format() + + Config::dot + + CONFIG_STYLE); + endHeader = config.getString(HtmlGenerator::format() + + Config::dot + + CONFIG_ENDHEADER); + postHeader = config.getString(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_POSTHEADER); + postPostHeader = config.getString(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_POSTPOSTHEADER); + prologue = config.getString(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_PROLOGUE); + + footer = config.getString(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_FOOTER); + address = config.getString(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_ADDRESS); + pleaseGenerateMacRef = config.getBool(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_GENERATEMACREFS); + noNavigationBar = config.getBool(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_NONAVIGATIONBAR); + tocDepth = config.getInt(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_TOCDEPTH); + + project = config.getString(CONFIG_PROJECT); + + projectDescription = config.getString(CONFIG_DESCRIPTION); + if (projectDescription.isEmpty() && !project.isEmpty()) + projectDescription = project + QLatin1String(" Reference Documentation"); + + projectUrl = config.getString(CONFIG_URL); + tagFile_ = config.getString(CONFIG_TAGFILE); + +#ifndef QT_NO_TEXTCODEC + outputEncoding = config.getString(CONFIG_OUTPUTENCODING); + if (outputEncoding.isEmpty()) + outputEncoding = QLatin1String("UTF-8"); + outputCodec = QTextCodec::codecForName(outputEncoding.toLocal8Bit()); +#endif + + naturalLanguage = config.getString(CONFIG_NATURALLANGUAGE); + if (naturalLanguage.isEmpty()) + naturalLanguage = QLatin1String("en"); + + QSet<QString> editionNames = config.subVars(CONFIG_EDITION); + QSet<QString>::ConstIterator edition = editionNames.constBegin(); + while (edition != editionNames.constEnd()) { + QString editionName = *edition; + QStringList editionModules = config.getStringList(CONFIG_EDITION + + Config::dot + + editionName + + Config::dot + + "modules"); + QStringList editionGroups = config.getStringList(CONFIG_EDITION + + Config::dot + + editionName + + Config::dot + + "groups"); + + if (!editionModules.isEmpty()) + editionModuleMap[editionName] = editionModules; + if (!editionGroups.isEmpty()) + editionGroupMap[editionName] = editionGroups; + + ++edition; + } + + codeIndent = config.getInt(CONFIG_CODEINDENT); // QTBUG-27798 + codePrefix = config.getString(CONFIG_CODEPREFIX); + codeSuffix = config.getString(CONFIG_CODESUFFIX); + + /* + The help file write should be allocated once and only once + per qdoc execution. + */ + if (helpProjectWriter) + helpProjectWriter->reset(config, project.toLower() + ".qhp", this); + else + helpProjectWriter = new HelpProjectWriter(config, project.toLower() + ".qhp", this); + + // Documentation template handling + headerScripts = config.getString(HtmlGenerator::format() + Config::dot + CONFIG_HEADERSCRIPTS); + headerStyles = config.getString(HtmlGenerator::format() + Config::dot + CONFIG_HEADERSTYLES); + + QString prefix = CONFIG_QHP + Config::dot + project + Config::dot; + manifestDir = QLatin1String("qthelp://") + config.getString(prefix + QLatin1String("namespace")); + manifestDir += QLatin1Char('/') + config.getString(prefix + QLatin1String("virtualFolder")) + QLatin1Char('/'); + readManifestMetaContent(config); + examplesPath = config.getString(CONFIG_EXAMPLESINSTALLPATH); + if (!examplesPath.isEmpty()) + examplesPath += QLatin1Char('/'); + + //retrieve the config for the navigation bar + homepage = config.getString(CONFIG_NAVIGATION + + Config::dot + + CONFIG_HOMEPAGE); + + landingpage = config.getString(CONFIG_NAVIGATION + + Config::dot + + CONFIG_LANDINGPAGE); + + cppclassespage = config.getString(CONFIG_NAVIGATION + + Config::dot + + CONFIG_CPPCLASSESPAGE); + + qmltypespage = config.getString(CONFIG_NAVIGATION + + Config::dot + + CONFIG_QMLTYPESPAGE); + + buildversion = config.getString(CONFIG_BUILDVERSION); +} + +/*! + Gracefully terminates the HTML output generator. + */ +void HtmlGenerator::terminateGenerator() +{ + Generator::terminateGenerator(); +} + +QString HtmlGenerator::format() +{ + return "HTML"; +} + +/*! + Generate targets for any \keyword commands that were seen + in the qdoc comment for the \a node. + */ +void HtmlGenerator::generateKeywordAnchors(const Node* node) +{ + Q_UNUSED(node); + // Disabled: keywords always link to the top of the QDoc + // comment they appear in, and do not use a dedicated anchor. +#if 0 + if (!node->doc().isEmpty()) { + const QList<Atom*>& keywords = node->doc().keywords(); + foreach (Atom* a, keywords) { + out() << QLatin1String("<a name=\"") << Doc::canonicalTitle(a->string()) << QLatin1String("\"></a>"); + } + } +#endif +} + +/*! + If qdoc is in the \c {-prepare} phase, traverse the primary + tree to generate the index file for the current module. + + If qdoc is in the \c {-generate} phase, traverse the primary + tree to generate all the HTML documentation for the current + module. Then generate the help file and the tag file. + */ +void HtmlGenerator::generateDocs() +{ + Node* qflags = qdb_->findClassNode(QStringList("QFlags")); + if (qflags) + qflagsHref_ = linkForNode(qflags,0); + if (!preparing()) + Generator::generateDocs(); + if (Generator::generating() && Generator::writeQaPages()) + generateQAPage(); + + if (!generating()) { + QString fileBase = project.toLower().simplified().replace(QLatin1Char(' '), QLatin1Char('-')); + qdb_->generateIndex(outputDir() + QLatin1Char('/') + fileBase + ".index", + projectUrl, + projectDescription, + this, + true); + } + + if (!preparing()) { + helpProjectWriter->generate(); + generateManifestFiles(); + /* + Generate the XML tag file, if it was requested. + */ + qdb_->generateTagFile(tagFile_, this); + } +} + +/*! + Output the module's Quality Assurance page. + */ +void HtmlGenerator::generateQAPage() +{ + NamespaceNode* node = qdb_->primaryTreeRoot(); + beginSubPage(node, "aaa-" + defaultModuleName().toLower() + "-qa-page.html"); + CodeMarker* marker = CodeMarker::markerForFileName(node->location().filePath()); + QString title = "Quality Assurance Page for " + defaultModuleName(); + QString t = "Quality assurance information for checking the " + defaultModuleName() + " documentation."; + generateHeader(title, node, marker); + generateTitle(title, Text() << t, LargeSubTitle, node, marker); + + QStringList strings; + QVector<int> counts; + QString depends = qdb_->getLinkCounts(strings, counts); + if (!strings.isEmpty()) { + t = "Intermodule Link Counts"; + QString ref = registerRef(t); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">" << protectEnc(t) << "</h2>\n"; + out() << "<table class=\"valuelist\"><tr valign=\"top\" " + << "class=\"even\"><th class=\"tblConst\">Destination Module</th>" + << "<th class=\"tblval\">Link Count</th></tr>\n"; + QString fileName; + for (int i = 0; i< strings.size(); ++i) { + fileName = generateLinksToLinksPage(strings.at(i), marker); + out() << "<tr><td class=\"topAlign\"><tt>" + << "<a href=\"" << fileName << "\">" + << strings.at(i) << "</a>" + << "</tt></td><td class=\"topAlign\"><tt>" << counts.at(i) + << "</tt></td></tr>\n"; + } + int count = 0; + fileName = generateLinksToBrokenLinksPage(marker, count); + if (count != 0) { + out() << "<tr><td class=\"topAlign\"><tt>" + << "<a href=\"" << fileName << "\">" + << "Broken Links" << "</a>" + << "</tt></td><td class=\"topAlign\"><tt>" << count + << "</tt></td></tr>\n"; + + } + + out() << "</table>\n"; + t = "The Optimal \"depends\" Variable"; + out() << "<h2>" << protectEnc(t) << "</h2>\n"; + t = "Consider replacing the depends variable in " + defaultModuleName().toLower() + + ".qdocconf with this one, if the two are not identical:"; + out() << "<p>" << protectEnc(t) << "</p>\n"; + out() << "<p>" << protectEnc(depends) << "</p>\n"; + } + generateFooter(); + endSubPage(); +} + +/*! + This function writes an html file containing a list of + links to links that originate in the current module and + go to targets in the specified \a module. The \a marker + is used for the same thing the marker is always used for. + */ +QString HtmlGenerator::generateLinksToLinksPage(const QString& module, CodeMarker* marker) +{ + NamespaceNode* node = qdb_->primaryTreeRoot(); + QString fileName = "aaa-links-to-" + module + ".html"; + beginSubPage(node, fileName); + QString title = "Links from " + defaultModuleName() + " to " + module; + generateHeader(title, node, marker); + generateTitle(title, Text(), SmallSubTitle, node, marker); + out() << "<p>This is a list of links from " << defaultModuleName() + << " to " << module << ". "; + out() << "Click on a link to go to the location of the link. The link is marked "; + out() << "with red asterisks. "; + out() << "Click on the marked link to see if it goes to the right place.</p>\n"; + TargetList* tlist = qdb_->getTargetList(module); + if (tlist) { + out() << "<table class=\"valuelist\"><tr valign=\"top\" class=\"odd\"><th class=\"tblConst\">Link to link...</th><th class=\"tblval\">In file...</th><th class=\"tbldscr\">Somewhere after line number...</th></tr>\n"; + foreach (TargetLoc* t, *tlist) { + // e.g.: <a name="link-8421"></a><a href="layout.html">Layout Management</a> + out() << "<tr><td class=\"topAlign\">"; + out() << "<a href=\"" << t->fileName_ << "#" << t->target_ << "\">"; + out() << t->text_ << "</a></td>"; + out() << "<td class=\"topAlign\">"; + QString f = t->loc_->doc().location().filePath(); + out() << f << "</td>"; + out() << "<td class=\"topAlign\">"; + out() << t->loc_->doc().location().lineNo() << "</td></tr>\n"; + } + out() << "</table>\n"; + } + generateFooter(); + endSubPage(); + return fileName; +} + +/*! + This function writes an html file containing a list of + links to broken links that originate in the current + module and go nowwhere. It returns the name of the file + it generates, and it sets \a count to the number of + broken links that were found. The \a marker is used for + the same thing the marker is always used for. + */ +QString HtmlGenerator::generateLinksToBrokenLinksPage(CodeMarker* marker, int& count) +{ + QString fileName; + NamespaceNode* node = qdb_->primaryTreeRoot(); + TargetList* tlist = qdb_->getTargetList("broken"); + if (tlist && !tlist->isEmpty()) { + count = tlist->size(); + fileName = "aaa-links-to-broken-links.html"; + beginSubPage(node, fileName); + QString title = "Broken links in " + defaultModuleName(); + generateHeader(title, node, marker); + generateTitle(title, Text(), SmallSubTitle, node, marker); + out() << "<p>This is a list of broken links in " << defaultModuleName() << ". "; + out() << "Click on a link to go to the broken link. "; + out() << "The link's target could not be found.</p>\n"; + out() << "<table class=\"valuelist\"><tr valign=\"top\" class=\"odd\"><th class=\"tblConst\">Link to broken link...</th><th class=\"tblval\">In file...</th><th class=\"tbldscr\">Somewhere after line number...</th></tr>\n"; + foreach (TargetLoc* t, *tlist) { + // e.g.: <a name="link-8421"></a><a href="layout.html">Layout Management</a> + out() << "<tr><td class=\"topAlign\">"; + out() << "<a href=\"" << t->fileName_ << "#" << t->target_ << "\">"; + out() << t->text_ << "</a></td>"; + out() << "<td class=\"topAlign\">"; + QString f = t->loc_->doc().location().filePath(); + out() << f << "</td>"; + out() << "<td class=\"topAlign\">"; + out() << t->loc_->doc().location().lineNo() << "</td></tr>\n"; + } + out() << "</table>\n"; + generateFooter(); + endSubPage(); + } + return fileName; +} + +/*! + Generate html from an instance of Atom. + */ +int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker) +{ + int idx, skipAhead = 0; + static bool in_para = false; + + switch (atom->type()) { + case Atom::AutoLink: + case Atom::NavAutoLink: + if (!inLink_ && !inContents_ && !inSectionHeading_) { + const Node *node = 0; + QString link = getAutoLink(atom, relative, &node); + if (link.isEmpty()) { + if (autolinkErrors()) + relative->doc().location().warning(tr("Can't autolink to '%1'").arg(atom->string())); + } + else if (node && node->status() == Node::Obsolete) { + if ((relative->parent() != node) && !relative->isObsolete()) + link.clear(); + } + if (link.isEmpty()) { + out() << protectEnc(atom->string()); + } + else { + if (Generator::writeQaPages() && node && (atom->type() != Atom::NavAutoLink)) { + QString text = atom->string(); + QString target = qdb_->getNewLinkTarget(relative, node, outFileName(), text); + out() << "<a id=\"" << Doc::canonicalTitle(target) << "\" class=\"qa-mark\"></a>"; + } + beginLink(link, node, relative); + generateLink(atom, marker); + endLink(); + } + } + else { + out() << protectEnc(atom->string()); + } + break; + case Atom::BaseName: + break; + case Atom::BriefLeft: + // Do not output the brief for QML nodes, doc nodes or collections + // (groups, modules, qml module nodes) + if (relative->isQmlType() || + relative->isDocumentNode() || + relative->isCollectionNode() || + relative->isJsType()) { + skipAhead = skipAtoms(atom, Atom::BriefRight); + break; + } + + out() << "<p>"; + if (relative->type() == Node::Property || + relative->type() == Node::Variable) { + QString str; + atom = atom->next(); + while (atom != 0 && atom->type() != Atom::BriefRight) { + if (atom->type() == Atom::String || + atom->type() == Atom::AutoLink) + str += atom->string(); + skipAhead++; + atom = atom->next(); + } + str[0] = str[0].toLower(); + if (str.endsWith(QLatin1Char('.'))) + str.truncate(str.length() - 1); + out() << "This "; + if (relative->type() == Node::Property) + out() << "property"; + else + out() << "variable"; + QStringList words = str.split(QLatin1Char(' ')); + if (!(words.first() == QLatin1String("contains") || words.first() == QLatin1String("specifies") + || words.first() == QLatin1String("describes") || words.first() == QLatin1String("defines") + || words.first() == QLatin1String("holds") || words.first() == QLatin1String("determines"))) + out() << " holds "; + else + out() << ' '; + out() << str << '.'; + } + break; + case Atom::BriefRight: + if (!relative->isDocumentNode()) + out() << "</p>\n"; + break; + case Atom::C: + // This may at one time have been used to mark up C++ code but it is + // now widely used to write teletype text. As a result, text marked + // with the \c command is not passed to a code marker. + out() << formattingLeftMap()[ATOM_FORMATTING_TELETYPE]; + if (inLink_) { + out() << protectEnc(plainCode(atom->string())); + } + else { + out() << protectEnc(plainCode(atom->string())); + } + out() << formattingRightMap()[ATOM_FORMATTING_TELETYPE]; + break; + case Atom::CaptionLeft: + out() << "<p class=\"figCaption\">"; + in_para = true; + break; + case Atom::CaptionRight: + endLink(); + if (in_para) { + out() << "</p>\n"; + in_para = false; + } + break; + case Atom::Code: + out() << "<pre class=\"cpp\">" + << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),relative), codePrefix, codeSuffix) + << "</pre>\n"; + break; + case Atom::Qml: + out() << "<pre class=\"qml\">" + << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),relative), codePrefix, codeSuffix) + << "</pre>\n"; + break; + case Atom::JavaScript: + out() << "<pre class=\"js\">" + << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),relative), codePrefix, codeSuffix) + << "</pre>\n"; + break; + case Atom::CodeNew: + out() << "<p>you can rewrite it as</p>\n" + << "<pre class=\"cpp\">" + << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),relative), codePrefix, codeSuffix) + << "</pre>\n"; + break; + case Atom::CodeOld: + out() << "<p>For example, if you have code like</p>\n"; + // fallthrough + case Atom::CodeBad: + out() << "<pre class=\"cpp\">" + << trimmedTrailing(protectEnc(plainCode(indent(codeIndent,atom->string()))), codePrefix, codeSuffix) + << "</pre>\n"; + break; + case Atom::DivLeft: + out() << "<div"; + if (!atom->string().isEmpty()) + out() << ' ' << atom->string(); + out() << '>'; + break; + case Atom::DivRight: + out() << "</div>"; + break; + case Atom::FootnoteLeft: + // ### For now + if (in_para) { + out() << "</p>\n"; + in_para = false; + } + out() << "<!-- "; + break; + case Atom::FootnoteRight: + // ### For now + out() << "-->"; + break; + case Atom::FormatElse: + case Atom::FormatEndif: + case Atom::FormatIf: + break; + case Atom::FormattingLeft: + if (atom->string().startsWith("span ")) { + out() << '<' + atom->string() << '>'; + } + else + out() << formattingLeftMap()[atom->string()]; + if (atom->string() == ATOM_FORMATTING_PARAMETER) { + if (atom->next() != 0 && atom->next()->type() == Atom::String) { + QRegExp subscriptRegExp("([a-z]+)_([0-9n])"); + if (subscriptRegExp.exactMatch(atom->next()->string())) { + out() << subscriptRegExp.cap(1) << "<sub>" + << subscriptRegExp.cap(2) << "</sub>"; + skipAhead = 1; + } + } + } + break; + case Atom::FormattingRight: + if (atom->string() == ATOM_FORMATTING_LINK) { + endLink(); + } + else if (atom->string().startsWith("span ")) { + out() << "</span>"; + } + else { + out() << formattingRightMap()[atom->string()]; + } + break; + case Atom::AnnotatedList: + { + const CollectionNode* cn = qdb_->getCollectionNode(atom->string(), Node::DOC); + if (cn) + generateList(cn, marker, atom->string()); + } + break; + case Atom::GeneratedList: + if (atom->string() == QLatin1String("annotatedclasses")) { + generateAnnotatedList(relative, marker, qdb_->getCppClasses()); + } + else if (atom->string() == QLatin1String("classes")) { + generateCompactList(Generic, relative, qdb_->getCppClasses(), true, QStringLiteral("")); + } + else if (atom->string().contains("classes ")) { + QString rootName = atom->string().mid(atom->string().indexOf("classes") + 7).trimmed(); + generateCompactList(Generic, relative, qdb_->getCppClasses(), true, rootName); + } + else if (atom->string() == QLatin1String("qmlbasictypes")) { + generateCompactList(Generic, relative, qdb_->getQmlBasicTypes(), true, QStringLiteral("")); + } + else if (atom->string() == QLatin1String("qmltypes")) { + generateCompactList(Generic, relative, qdb_->getQmlTypes(), true, QStringLiteral("")); + } + else if ((idx = atom->string().indexOf(QStringLiteral("bymodule"))) != -1) { + QString moduleName = atom->string().mid(idx + 8).trimmed(); + Node::Genus genus = Node::CPP; + if (atom->string().startsWith(QLatin1String("qml"))) + genus = Node::QML; + else if (atom->string().startsWith(QLatin1String("js"))) + genus = Node::JS; + QDocDatabase* qdb = QDocDatabase::qdocDB(); + const CollectionNode* cn = qdb->getCollectionNode(moduleName, genus); + if (cn) { + if (genus == Node::CPP) { + NodeMap m; + cn->getMemberClasses(m); + if (!m.isEmpty()) { + generateAnnotatedList(relative, marker, m); + } + } + else + generateAnnotatedList(relative, marker, cn->members()); + } + } + else if (atom->string() == QLatin1String("classhierarchy")) { + generateClassHierarchy(relative, qdb_->getCppClasses()); + } + else if (atom->string() == QLatin1String("obsoleteclasses")) { + generateCompactList(Generic, relative, qdb_->getObsoleteClasses(), false, QStringLiteral("Q")); + } + else if (atom->string() == QLatin1String("obsoleteqmltypes")) { + generateCompactList(Generic, relative, qdb_->getObsoleteQmlTypes(), false, QStringLiteral("")); + } + else if (atom->string() == QLatin1String("obsoletecppmembers")) { + generateCompactList(Obsolete, relative, qdb_->getClassesWithObsoleteMembers(), false, QStringLiteral("Q")); + } + else if (atom->string() == QLatin1String("obsoleteqmlmembers")) { + generateCompactList(Obsolete, relative, qdb_->getQmlTypesWithObsoleteMembers(), false, QStringLiteral("")); + } + else if (atom->string() == QLatin1String("functionindex")) { + generateFunctionIndex(relative); + } + else if (atom->string() == QLatin1String("legalese")) { + generateLegaleseList(relative, marker); + } + else if (atom->string() == QLatin1String("overviews")) { + generateList(relative, marker, "overviews"); + } + else if (atom->string() == QLatin1String("cpp-modules")) { + generateList(relative, marker, "cpp-modules"); + } + else if (atom->string() == QLatin1String("qml-modules")) { + generateList(relative, marker, "qml-modules"); + } + else if (atom->string() == QLatin1String("namespaces")) { + generateAnnotatedList(relative, marker, qdb_->getNamespaces()); + } + else if (atom->string() == QLatin1String("related")) { + generateList(relative, marker, "related"); + } +#if 0 + /* + This is not used in Qt5, as of 10/02/2014 + Remove permanently if it is not missed. + */ + else if (atom->string() == QLatin1String("relatedinline")) { + const DocumentNode *dn = static_cast<const DocumentNode *>(relative); + if (dn && !dn->members().isEmpty()) { + // Reverse the list into the original scan order. + // Should be sorted. But on what? It may not be a + // regular class or page definition. + QList<const Node *> list; + foreach (const Node *node, dn->members()) + list.prepend(node); + foreach (const Node *node, list) + generateBody(node, marker); + } + } +#endif + break; + case Atom::SinceList: + { + const NodeMultiMap& nsmap = qdb_->getSinceMap(atom->string()); + const NodeMap& ncmap = qdb_->getClassMap(atom->string()); + const NodeMap& nqcmap = qdb_->getQmlTypeMap(atom->string()); + + if (!nsmap.isEmpty()) { + QList<Section> sections; + QList<Section>::ConstIterator s; + + for (int i=0; i<LastSinceType; ++i) + sections.append(Section(sinceTitle(i),QString(),QString(),QString())); + + NodeMultiMap::const_iterator n = nsmap.constBegin(); + while (n != nsmap.constEnd()) { + Node* node = n.value(); + switch (node->type()) { + case Node::QmlType: + sections[QmlClass].appendMember(node); + break; + case Node::Namespace: + sections[Namespace].appendMember(node); + break; + case Node::Class: + sections[Class].appendMember(node); + break; + case Node::Enum: + sections[Enum].appendMember(node); + break; + case Node::Typedef: + sections[Typedef].appendMember(node); + break; + case Node::Function: { + const FunctionNode* fn = static_cast<const FunctionNode*>(node); + if (fn->isMacro()) + sections[Macro].appendMember(node); + else { + Node* p = fn->parent(); + if (p) { + if (p->type() == Node::Class) + sections[MemberFunction].appendMember(node); + else if (p->type() == Node::Namespace) { + if (p->name().isEmpty()) + sections[GlobalFunction].appendMember(node); + else + sections[NamespaceFunction].appendMember(node); + } + else + sections[GlobalFunction].appendMember(node); + } + else + sections[GlobalFunction].appendMember(node); + } + break; + } + case Node::Property: + sections[Property].appendMember(node); + break; + case Node::Variable: + sections[Variable].appendMember(node); + break; + case Node::QmlProperty: + sections[QmlProperty].appendMember(node); + break; + case Node::QmlSignal: + sections[QmlSignal].appendMember(node); + break; + case Node::QmlSignalHandler: + sections[QmlSignalHandler].appendMember(node); + break; + case Node::QmlMethod: + sections[QmlMethod].appendMember(node); + break; + default: + break; + } + ++n; + } + + out() << "<ul>\n"; + s = sections.constBegin(); + while (s != sections.constEnd()) { + if (!(*s).members.isEmpty()) { + + out() << "<li>" + << "<a href=\"#" + << Doc::canonicalTitle((*s).name) + << "\">" + << (*s).name + << "</a></li>\n"; + } + ++s; + } + out() << "</ul>\n"; + + int idx = 0; + s = sections.constBegin(); + while (s != sections.constEnd()) { + if (!(*s).members.isEmpty()) { + out() << "<a name=\"" + << Doc::canonicalTitle((*s).name) + << "\"></a>\n"; + out() << "<h3>" << protectEnc((*s).name) << "</h3>\n"; + if (idx == Class) + generateCompactList(Generic, 0, ncmap, false, QStringLiteral("Q")); + else if (idx == QmlClass) + generateCompactList(Generic, 0, nqcmap, false, QStringLiteral("")); + else if (idx == MemberFunction) { + ParentMaps parentmaps; + ParentMaps::iterator pmap; + NodeList::const_iterator i = s->members.constBegin(); + while (i != s->members.constEnd()) { + Node* p = (*i)->parent(); + pmap = parentmaps.find(p); + if (pmap == parentmaps.end()) + pmap = parentmaps.insert(p,NodeMultiMap()); + pmap->insert((*i)->name(),(*i)); + ++i; + } + pmap = parentmaps.begin(); + while (pmap != parentmaps.end()) { + NodeList nlist = pmap->values(); + out() << "<p>Class "; + + out() << "<a href=\"" + << linkForNode(pmap.key(), 0) + << "\">"; + QStringList pieces = pmap.key()->fullName().split("::"); + out() << protectEnc(pieces.last()); + out() << "</a>" << ":</p>\n"; + + generateSection(nlist, 0, marker, CodeMarker::Summary); + out() << "<br/>"; + ++pmap; + } + } + else + generateSection(s->members, 0, marker, CodeMarker::Summary); + } + ++idx; + ++s; + } + } + } + break; + case Atom::BR: + out() << "<br />\n"; + break; + case Atom::HR: + out() << "<hr />\n"; + break; + case Atom::Image: + case Atom::InlineImage: + { + QString fileName = imageFileName(relative, atom->string()); + QString text; + if (atom->next() != 0) + text = atom->next()->string(); + if (atom->type() == Atom::Image) + out() << "<p class=\"centerAlign\">"; + if (fileName.isEmpty()) { + relative->location().warning(tr("Missing image: %1").arg(protectEnc(atom->string()))); + out() << "<font color=\"red\">[Missing image " + << protectEnc(atom->string()) << "]</font>"; + } + else { + QString prefix; + out() << "<img src=\"" << protectEnc(prefix + fileName) << '"'; + if (!text.isEmpty()) + out() << " alt=\"" << protectEnc(text) << '"'; + else + out() << " alt=\"\""; + out() << " />"; + helpProjectWriter->addExtraFile(fileName); + if (relative->isExample()) { + const ExampleNode* cen = static_cast<const ExampleNode*>(relative); + if (cen->imageFileName().isEmpty()) { + ExampleNode* en = const_cast<ExampleNode*>(cen); + en->setImageFileName(fileName); + } + } + } + if (atom->type() == Atom::Image) + out() << "</p>"; + } + break; + case Atom::ImageText: + break; + case Atom::ImportantLeft: + out() << "<p>"; + out() << formattingLeftMap()[ATOM_FORMATTING_BOLD]; + out() << "Important: "; + out() << formattingRightMap()[ATOM_FORMATTING_BOLD]; + break; + case Atom::ImportantRight: + out() << "</p>"; + break; + case Atom::NoteLeft: + out() << "<p>"; + out() << formattingLeftMap()[ATOM_FORMATTING_BOLD]; + out() << "Note: "; + out() << formattingRightMap()[ATOM_FORMATTING_BOLD]; + break; + case Atom::NoteRight: + out() << "</p>"; + break; + case Atom::LegaleseLeft: + out() << "<div class=\"LegaleseLeft\">"; + break; + case Atom::LegaleseRight: + out() << "</div>"; + break; + case Atom::LineBreak: + out() << "<br/>"; + break; + case Atom::Link: + case Atom::NavLink: + { + inObsoleteLink = false; + const Node *node = 0; + QString link = getLink(atom, relative, &node); + if (link.isEmpty() && (node != relative) && !noLinkErrors()) { + relative->doc().location().warning(tr("Can't link to '%1'").arg(atom->string())); + if (Generator::writeQaPages() && (atom->type() != Atom::NavAutoLink)) { + QString text = atom->next()->next()->string(); + QString target = qdb_->getNewLinkTarget(relative, node, outFileName(), text, true); + out() << "<a id=\"" << Doc::canonicalTitle(target) << "\" class=\"qa-mark\"></a>"; + } + } + else { + if (Generator::writeQaPages() && node && (atom->type() != Atom::NavLink)) { + QString text = atom->next()->next()->string(); + QString target = qdb_->getNewLinkTarget(relative, node, outFileName(), text); + out() << "<a id=\"" << Doc::canonicalTitle(target) << "\" class=\"qa-mark\"></a>"; + } + /* + mws saw this on 17/10/2014. + Is this correct? Setting node to 0 means the + following test always fails. Did we decide to + no longer warn about linking to obsolete things? + */ + node = 0; + if (node && node->status() == Node::Obsolete) { + if ((relative->parent() != node) && !relative->isObsolete()) { + inObsoleteLink = true; + if (obsoleteLinks) { + relative->doc().location().warning(tr("Link to obsolete item '%1' in %2") + .arg(atom->string()) + .arg(relative->plainFullName())); + } + } + } + } + beginLink(link, node, relative); + skipAhead = 1; + } + break; + case Atom::LinkNode: + { + const Node *node = CodeMarker::nodeForString(atom->string()); + beginLink(linkForNode(node, relative), node, relative); + skipAhead = 1; + } + break; + case Atom::ListLeft: + if (in_para) { + out() << "</p>\n"; + in_para = false; + } + if (atom->string() == ATOM_LIST_BULLET) { + out() << "<ul>\n"; + } + else if (atom->string() == ATOM_LIST_TAG) { + out() << "<dl>\n"; + } + else if (atom->string() == ATOM_LIST_VALUE) { + out() << "<div class=\"table\"><table class=\"valuelist\">"; + threeColumnEnumValueTable_ = isThreeColumnEnumValueTable(atom); + if (threeColumnEnumValueTable_) { + if (++numTableRows_ % 2 == 1) + out() << "<tr valign=\"top\" class=\"odd\">"; + else + out() << "<tr valign=\"top\" class=\"even\">"; + + out() << "<th class=\"tblConst\">Constant</th>"; + + // If not in \enum topic, skip the value column + if (relative->type() == Node::Enum) + out() << "<th class=\"tblval\">Value</th>"; + + out() << "<th class=\"tbldscr\">Description</th></tr>\n"; + } + else { + out() << "<tr><th class=\"tblConst\">Constant</th><th class=\"tblVal\">Value</th></tr>\n"; + } + } + else { + out() << "<ol class="; + if (atom->string() == ATOM_LIST_UPPERALPHA) { + out() << "\"A\""; + } /* why type? changed to */ + else if (atom->string() == ATOM_LIST_LOWERALPHA) { + out() << "\"a\""; + } + else if (atom->string() == ATOM_LIST_UPPERROMAN) { + out() << "\"I\""; + } + else if (atom->string() == ATOM_LIST_LOWERROMAN) { + out() << "\"i\""; + } + else { // (atom->string() == ATOM_LIST_NUMERIC) + out() << "\"1\""; + } + if (atom->next() != 0 && atom->next()->string().toInt() != 1) + out() << " start=\"" << atom->next()->string() << '"'; + out() << ">\n"; + } + break; + case Atom::ListItemNumber: + break; + case Atom::ListTagLeft: + if (atom->string() == ATOM_LIST_TAG) { + out() << "<dt>"; + } + else { // (atom->string() == ATOM_LIST_VALUE) + // ### Trenton + + QString t= protectEnc(plainCode(marker->markedUpEnumValue(atom->next()->string(),relative))); + out() << "<tr><td class=\"topAlign\"><code>" << t << "</code>"; + + if (relative->type() == Node::Enum) { + out() << "</td><td class=\"topAlign\">"; + const EnumNode *enume = static_cast<const EnumNode *>(relative); + QString itemValue = enume->itemValue(atom->next()->string()); + + if (itemValue.isEmpty()) + out() << '?'; + else + out() << "<code>" << protectEnc(itemValue) << "</code>"; + } + skipAhead = 1; + } + break; + case Atom::ListTagRight: + if (atom->string() == ATOM_LIST_TAG) + out() << "</dt>\n"; + break; + case Atom::ListItemLeft: + if (atom->string() == ATOM_LIST_TAG) { + out() << "<dd>"; + } + else if (atom->string() == ATOM_LIST_VALUE) { + if (threeColumnEnumValueTable_) { + out() << "</td><td class=\"topAlign\">"; + if (matchAhead(atom, Atom::ListItemRight)) + out() << " "; + } + } + else { + out() << "<li>"; + } + if (matchAhead(atom, Atom::ParaLeft)) + skipAhead = 1; + break; + case Atom::ListItemRight: + if (atom->string() == ATOM_LIST_TAG) { + out() << "</dd>\n"; + } + else if (atom->string() == ATOM_LIST_VALUE) { + out() << "</td></tr>\n"; + } + else { + out() << "</li>\n"; + } + break; + case Atom::ListRight: + if (atom->string() == ATOM_LIST_BULLET) { + out() << "</ul>\n"; + } + else if (atom->string() == ATOM_LIST_TAG) { + out() << "</dl>\n"; + } + else if (atom->string() == ATOM_LIST_VALUE) { + out() << "</table></div>\n"; + } + else { + out() << "</ol>\n"; + } + break; + case Atom::Nop: + break; + case Atom::ParaLeft: + out() << "<p>"; + in_para = true; + break; + case Atom::ParaRight: + endLink(); + if (in_para) { + out() << "</p>\n"; + in_para = false; + } + //if (!matchAhead(atom, Atom::ListItemRight) && !matchAhead(atom, Atom::TableItemRight)) + // out() << "</p>\n"; + break; + case Atom::QuotationLeft: + out() << "<blockquote>"; + break; + case Atom::QuotationRight: + out() << "</blockquote>\n"; + break; + case Atom::RawString: + out() << atom->string(); + break; + case Atom::SectionLeft: + out() << "<a name=\"" << Doc::canonicalTitle(Text::sectionHeading(atom).toString()) + << "\"></a>" << divNavTop << '\n'; + break; + case Atom::SectionRight: + break; + case Atom::SectionHeadingLeft: { + int unit = atom->string().toInt() + hOffset(relative); + out() << "<h" + QString::number(unit) + QLatin1Char(' '); + if (unit < 3) { + out() << "id=\"" << Doc::canonicalTitle(Text::sectionHeading(atom).toString()) << "\""; + } + out() << '>'; + inSectionHeading_ = true; + break; + } + case Atom::SectionHeadingRight: + out() << "</h" + QString::number(atom->string().toInt() + hOffset(relative)) + ">\n"; + inSectionHeading_ = false; + break; + case Atom::SidebarLeft: + break; + case Atom::SidebarRight: + break; + case Atom::String: + if (inLink_ && !inContents_ && !inSectionHeading_) { + generateLink(atom, marker); + } + else { + out() << protectEnc(atom->string()); + } + break; + case Atom::TableLeft: + { + QString p1, p2; + QString attr = "generic"; + QString width; + if (in_para) { + out() << "</p>\n"; + in_para = false; + } + if (atom->count() > 0) { + p1 = atom->string(0); + if (atom->count() > 1) + p2 = atom->string(1); + } + if (!p1.isEmpty()) { + if (p1 == QLatin1String("borderless")) + attr = p1; + else if (p1.contains(QLatin1Char('%'))) + width = p1; + } + if (!p2.isEmpty()) { + if (p2 == QLatin1String("borderless")) + attr = p2; + else if (p2.contains(QLatin1Char('%'))) + width = p2; + } + out() << "<div class=\"table\"><table class=\"" << attr << '"'; + if (!width.isEmpty()) + out() << " width=\"" << width << '"'; + out() << ">\n "; + numTableRows_ = 0; + } + break; + case Atom::TableRight: + out() << "</table></div>\n"; + break; + case Atom::TableHeaderLeft: + out() << "<thead><tr class=\"qt-style\">"; + inTableHeader_ = true; + break; + case Atom::TableHeaderRight: + out() << "</tr>"; + if (matchAhead(atom, Atom::TableHeaderLeft)) { + skipAhead = 1; + out() << "\n<tr class=\"qt-style\">"; + } + else { + out() << "</thead>\n"; + inTableHeader_ = false; + } + break; + case Atom::TableRowLeft: + if (!atom->string().isEmpty()) + out() << "<tr " << atom->string() << '>'; + else if (++numTableRows_ % 2 == 1) + out() << "<tr valign=\"top\" class=\"odd\">"; + else + out() << "<tr valign=\"top\" class=\"even\">"; + break; + case Atom::TableRowRight: + out() << "</tr>\n"; + break; + case Atom::TableItemLeft: + { + if (inTableHeader_) + out() << "<th "; + else + out() << "<td "; + + for (int i=0; i<atom->count(); ++i) { + if (i > 0) + out() << ' '; + QString p = atom->string(i); + if (p.contains('=')) { + out() << p; + } + else { + QStringList spans = p.split(QLatin1Char(',')); + if (spans.size() == 2) { + if (spans.at(0) != "1") + out() << " colspan=\"" << spans.at(0) << '"'; + if (spans.at(1) != "1") + out() << " rowspan=\"" << spans.at(1) << '"'; + } + } + } + if (inTableHeader_) + out() << '>'; + else { + out() << '>'; + //out() << "><p>"; + } + if (matchAhead(atom, Atom::ParaLeft)) + skipAhead = 1; + } + break; + case Atom::TableItemRight: + if (inTableHeader_) + out() << "</th>"; + else { + out() << "</td>"; + //out() << "</p></td>"; + } + if (matchAhead(atom, Atom::ParaLeft)) + skipAhead = 1; + break; + case Atom::TableOfContents: + break; + case Atom::Keyword: + break; + case Atom::Target: + out() << "<a name=\"" << Doc::canonicalTitle(atom->string()) << "\"></a>"; + break; + case Atom::UnhandledFormat: + out() << "<b class=\"redFont\"><Missing HTML></b>"; + break; + case Atom::UnknownCommand: + out() << "<b class=\"redFont\"><code>\\" << protectEnc(atom->string()) + << "</code></b>"; + break; + case Atom::QmlText: + case Atom::EndQmlText: + // don't do anything with these. They are just tags. + break; + default: + unknownAtom(atom); + } + return skipAhead; +} + +/*! + Generate a reference page for a C++ class or a C++ namespace. + */ +void HtmlGenerator::generateClassLikeNode(Aggregate* inner, CodeMarker* marker) +{ + QList<Section> sections; + QList<Section>::ConstIterator s; + + QString title; + QString rawTitle; + QString fullTitle; + if (inner->type() == Node::Namespace) { + rawTitle = inner->plainName(); + fullTitle = inner->plainFullName(); + title = rawTitle + " Namespace"; + } + else if (inner->type() == Node::Class) { + rawTitle = inner->plainName(); + fullTitle = inner->plainFullName(); + title = rawTitle + " Class"; + } + + Text subtitleText; + if (rawTitle != fullTitle) + subtitleText << "(" << Atom(Atom::AutoLink, fullTitle) << ")" << Atom(Atom::LineBreak); + + generateHeader(title, inner, marker); + + sections = marker->sections(inner, CodeMarker::Summary, CodeMarker::Okay); + generateTableOfContents(inner,marker,§ions); + generateKeywordAnchors(inner); + generateTitle(title, subtitleText, SmallSubTitle, inner, marker); + generateBrief(inner, marker); + generateRequisites(inner, marker); + generateStatus(inner, marker); + generateThreadSafeness(inner, marker); + + out() << "<ul>\n"; + + QString membersLink = generateListOfAllMemberFile(inner, marker); + if (!membersLink.isEmpty()) + out() << "<li><a href=\"" << membersLink << "\">" + << "List of all members, including inherited members</a></li>\n"; + + QString obsoleteLink = generateLowStatusMemberFile(inner, + marker, + CodeMarker::Obsolete); + if (!obsoleteLink.isEmpty()) { + out() << "<li><a href=\"" << obsoleteLink << "\">" + << "Obsolete members</a></li>\n"; + } + + QString compatLink = generateLowStatusMemberFile(inner, + marker, + CodeMarker::Compat); + if (!compatLink.isEmpty()) + out() << "<li><a href=\"" << compatLink << "\">" + << "Compatibility members</a></li>\n"; + + out() << "</ul>\n"; + + bool needOtherSection = false; + + /* + sections is built above for the call to generateTableOfContents(). + */ + s = sections.constBegin(); + while (s != sections.constEnd()) { + if (s->members.isEmpty() && s->reimpMembers.isEmpty()) { + if (!s->inherited.isEmpty()) + needOtherSection = true; + } + else { + if (!s->members.isEmpty()) { + // out() << "<hr />\n"; + QString ref = registerRef((*s).name.toLower()); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << "\n"; + out() << "<h2 id=\"" << ref << "\">" << protectEnc((*s).name) << "</h2>\n"; + generateSection(s->members, inner, marker, CodeMarker::Summary); + } + if (!s->reimpMembers.isEmpty()) { + QString name = QString("Reimplemented ") + (*s).name; + QString ref = registerRef(name.toLower()); + // out() << "<hr />\n"; + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << "\n"; + out() << "<h2 id=\"" << ref << "\">" << protectEnc(name) << "</h2>\n"; + generateSection(s->reimpMembers, inner, marker, CodeMarker::Summary); + } + + if (!s->inherited.isEmpty()) { + out() << "<ul>\n"; + generateSectionInheritedList(*s, inner); + out() << "</ul>\n"; + } + } + ++s; + } + + if (needOtherSection) { + out() << "<h3>Additional Inherited Members</h3>\n" + "<ul>\n"; + + s = sections.constBegin(); + while (s != sections.constEnd()) { + if (s->members.isEmpty() && !s->inherited.isEmpty()) + generateSectionInheritedList(*s, inner); + ++s; + } + out() << "</ul>\n"; + } + + QString detailsRef = registerRef("details"); + out() << "<a name=\"" << detailsRef << "\"></a>" << divNavTop << '\n'; + + if (!inner->doc().isEmpty()) { + generateExtractionMark(inner, DetailedDescriptionMark); + //out() << "<hr />\n" + out() << "<div class=\"descr\">\n" // QTBUG-9504 + << "<h2 id=\"" << detailsRef << "\">" << "Detailed Description" << "</h2>\n"; + generateBody(inner, marker); + out() << "</div>\n"; // QTBUG-9504 + generateAlsoList(inner, marker); + generateMaintainerList(inner, marker); + generateExtractionMark(inner, EndMark); + } + + sections = marker->sections(inner, CodeMarker::Detailed, CodeMarker::Okay); + s = sections.constBegin(); + while (s != sections.constEnd()) { + //out() << "<hr />\n"; + if (!(*s).divClass.isEmpty()) + out() << "<div class=\"" << (*s).divClass << "\">\n"; // QTBUG-9504 + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + + NodeList::ConstIterator m = (*s).members.constBegin(); + while (m != (*s).members.constEnd()) { + if ((*m)->access() != Node::Private) { // ### check necessary? + if ((*m)->type() != Node::Class) + generateDetailedMember(*m, inner, marker); + else { + out() << "<h3> class "; + generateFullName(*m, inner); + out() << "</h3>"; + generateBrief(*m, marker, inner); + } + + QStringList names; + names << (*m)->name(); + if ((*m)->type() == Node::Function) { + const FunctionNode *func = reinterpret_cast<const FunctionNode *>(*m); + if (func->metaness() == FunctionNode::Ctor || + func->metaness() == FunctionNode::Dtor || + func->overloadNumber() != 0) + names.clear(); + } + else if ((*m)->type() == Node::Property) { + const PropertyNode *prop = reinterpret_cast<const PropertyNode *>(*m); + if (!prop->getters().isEmpty() && + !names.contains(prop->getters().first()->name())) + names << prop->getters().first()->name(); + if (!prop->setters().isEmpty()) + names << prop->setters().first()->name(); + if (!prop->resetters().isEmpty()) + names << prop->resetters().first()->name(); + if (!prop->notifiers().isEmpty()) + names << prop->notifiers().first()->name(); + } + else if ((*m)->type() == Node::Enum) { + const EnumNode *enume = reinterpret_cast<const EnumNode*>(*m); + if (enume->flagsType()) + names << enume->flagsType()->name(); + + foreach (const QString &enumName, + enume->doc().enumItemNames().toSet() - + enume->doc().omitEnumItemNames().toSet()) + names << plainCode(marker->markedUpEnumValue(enumName, + enume)); + } + } + ++m; + } + if (!(*s).divClass.isEmpty()) + out() << "</div>\n"; // QTBUG-9504 + ++s; + } + generateFooter(inner); +} + +/*! + Generate the HTML page for a QML type. \qcn is the QML type. + \marker is the code markeup object. + */ +void HtmlGenerator::generateQmlTypePage(QmlTypeNode* qcn, CodeMarker* marker) +{ + Generator::setQmlTypeContext(qcn); + SubTitleSize subTitleSize = LargeSubTitle; + QList<Section>::const_iterator s; + QString htmlTitle = qcn->fullTitle(); + if (qcn->isJsType()) + htmlTitle += " JavaScript Type"; + else + htmlTitle += " QML Type"; + + generateHeader(htmlTitle, qcn, marker); + QList<Section> sections = marker->qmlSections(qcn, CodeMarker::Summary); + generateTableOfContents(qcn, marker, §ions); + marker = CodeMarker::markerForLanguage(QLatin1String("QML")); + generateKeywordAnchors(qcn); + generateTitle(htmlTitle, Text() << qcn->subTitle(), subTitleSize, qcn, marker); + generateBrief(qcn, marker); + generateQmlRequisites(qcn, marker); + + QString allQmlMembersLink = generateAllQmlMembersFile(qcn, marker); + QString obsoleteLink = generateQmlMemberFile(qcn, marker, CodeMarker::Obsolete); + if (!allQmlMembersLink.isEmpty() || !obsoleteLink.isEmpty()) { + out() << "<ul>\n"; + if (!allQmlMembersLink.isEmpty()) { + out() << "<li><a href=\"" << allQmlMembersLink << "\">" + << "List of all members, including inherited members</a></li>\n"; + } + if (!obsoleteLink.isEmpty()) { + out() << "<li><a href=\"" << obsoleteLink << "\">" + << "Obsolete members</a></li>\n"; + } + out() << "</ul>\n"; + } + + s = sections.constBegin(); + while (s != sections.constEnd()) { + QString ref = registerRef((*s).name.toLower()); + out() << "<a name=\"" << ref + << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">" << protectEnc((*s).name) << "</h2>\n"; + generateQmlSummary(*s, qcn, marker); + ++s; + } + + generateExtractionMark(qcn, DetailedDescriptionMark); + QString detailsRef = registerRef("details"); + out() << "<a name=\"" << detailsRef << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << detailsRef << "\">" << "Detailed Description" << "</h2>\n"; + generateBody(qcn, marker); + ClassNode* cn = qcn->classNode(); + if (cn) + generateQmlText(cn->doc().body(), cn, marker, qcn->name()); + generateAlsoList(qcn, marker); + generateExtractionMark(qcn, EndMark); + //out() << "<hr />\n"; + + sections = marker->qmlSections(qcn,CodeMarker::Detailed); + s = sections.constBegin(); + while (s != sections.constEnd()) { + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + NodeList::ConstIterator m = (*s).members.constBegin(); + while (m != (*s).members.constEnd()) { + generateDetailedQmlMember(*m, qcn, marker); + out() << "<br/>\n"; + ++m; + } + ++s; + } + generateFooter(qcn); + Generator::setQmlTypeContext(0); +} + +/*! + Generate the HTML page for the QML basic type represented + by the QML basic type node \a qbtn. + */ +void HtmlGenerator::generateQmlBasicTypePage(QmlBasicTypeNode* qbtn, CodeMarker* marker) +{ + SubTitleSize subTitleSize = LargeSubTitle; + QList<Section>::const_iterator s; + QString htmlTitle = qbtn->fullTitle(); + if (qbtn->isJsType()) + htmlTitle += " JavaScript Basic Type"; + else + htmlTitle += " QML Basic Type"; + + marker = CodeMarker::markerForLanguage(QLatin1String("QML")); + + generateHeader(htmlTitle, qbtn, marker); + QList<Section> sections = marker->sections(qbtn, CodeMarker::Summary, CodeMarker::Okay); + generateTableOfContents(qbtn,marker,§ions); + generateKeywordAnchors(qbtn); + generateTitle(htmlTitle, + Text() << qbtn->subTitle(), + subTitleSize, + qbtn, + marker); + generateExtractionMark(qbtn, DetailedDescriptionMark); + out() << "<div class=\"descr\"> <a name=\"" << registerRef("details") << "\"></a>\n"; // QTBUG-9504 + + generateBody(qbtn, marker); + out() << "</div>\n"; // QTBUG-9504 + generateAlsoList(qbtn, marker); + generateExtractionMark(qbtn, EndMark); + generateFooter(qbtn); +} + +/*! + Generate the HTML page for an entity that doesn't map + to any underlying parsable C++ class or QML component. + */ +void HtmlGenerator::generateDocumentNode(DocumentNode* dn, CodeMarker* marker) +{ + /* + If the document node is a page node, and if the page type + is DITA map page, write the node's contents as a dita + map and return without doing anything else. + */ + if (dn->docSubtype() == Node::Page && dn->pageType() == Node::DitaMapPage) { + const DitaMapNode* dmn = static_cast<const DitaMapNode*>(dn); + writeDitaMap(dmn); + return; + } + + SubTitleSize subTitleSize = LargeSubTitle; + QList<Section> sections; + QList<Section>::const_iterator s; + QString fullTitle = dn->fullTitle(); + + generateHeader(fullTitle, dn, marker); + /* + Generate the TOC for the new doc format. + Don't generate a TOC for the home page. + */ + if ((dn->name() != QLatin1String("index.html"))) + generateTableOfContents(dn,marker,0); + + generateKeywordAnchors(dn); + generateTitle(fullTitle, + Text() << dn->subTitle(), + subTitleSize, + dn, + marker); + + if (dn->docSubtype() == Node::HeaderFile) { + // Generate brief text and status for modules. + generateBrief(dn, marker); + generateStatus(dn, marker); + generateSince(dn, marker); + + out() << "<ul>\n"; + + QString membersLink = generateListOfAllMemberFile(dn, marker); + if (!membersLink.isEmpty()) + out() << "<li><a href=\"" << membersLink << "\">" + << "List of all members, including inherited members</a></li>\n"; + + QString obsoleteLink = generateLowStatusMemberFile(dn, + marker, + CodeMarker::Obsolete); + if (!obsoleteLink.isEmpty()) { + out() << "<li><a href=\"" << obsoleteLink << "\">" + << "Obsolete members</a></li>\n"; + } + + QString compatLink = generateLowStatusMemberFile(dn, + marker, + CodeMarker::Compat); + if (!compatLink.isEmpty()) + out() << "<li><a href=\"" << compatLink << "\">" + << "Compatibility members</a></li>\n"; + + out() << "</ul>\n"; + } + + sections = marker->sections(dn, CodeMarker::Summary, CodeMarker::Okay); + s = sections.constBegin(); + while (s != sections.constEnd()) { + QString ref = registerRef((*s).name); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">" << protectEnc((*s).name) << "</h2>\n"; + generateSectionList(*s, dn, marker, CodeMarker::Summary); + ++s; + } + + generateExtractionMark(dn, DetailedDescriptionMark); + out() << "<div class=\"descr\"> <a name=\"" << registerRef("details") << "\"></a>\n"; // QTBUG-9504 + + generateBody(dn, marker); + out() << "</div>\n"; // QTBUG-9504 + generateAlsoList(dn, marker); + generateExtractionMark(dn, EndMark); + + sections = marker->sections(dn, CodeMarker::Detailed, CodeMarker::Okay); + s = sections.constBegin(); + while (s != sections.constEnd()) { + //out() << "<hr />\n"; + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + + NodeList::ConstIterator m = (*s).members.constBegin(); + while (m != (*s).members.constEnd()) { + generateDetailedMember(*m, dn, marker); + ++m; + } + ++s; + } + generateFooter(dn); +} + +/*! + Generate the HTML page for a group, module, or QML module. + */ +void HtmlGenerator::generateCollectionNode(CollectionNode* cn, CodeMarker* marker) +{ + SubTitleSize subTitleSize = LargeSubTitle; + QList<Section> sections; + QList<Section>::const_iterator s; + QString fullTitle = cn->fullTitle(); + QString ref; + + generateHeader(fullTitle, cn, marker); + generateTableOfContents(cn,marker,0); + generateKeywordAnchors(cn); + generateTitle(fullTitle, Text() << cn->subTitle(), subTitleSize, cn, marker); + + if (cn->isModule()) { + // Generate brief text and status for modules. + generateBrief(cn, marker); + generateStatus(cn, marker); + generateSince(cn, marker); + + NodeMultiMap nmm; + cn->getMemberNamespaces(nmm); + if (!nmm.isEmpty()) { + ref = registerRef("namespaces"); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">Namespaces</h2>\n"; + generateAnnotatedList(cn, marker, nmm); + } + nmm.clear(); + cn->getMemberClasses(nmm); + if (!nmm.isEmpty()) { + ref = registerRef("classes"); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">Classes</h2>\n"; + generateAnnotatedList(cn, marker, nmm); + } + nmm.clear(); + } + + sections = marker->sections(cn, CodeMarker::Summary, CodeMarker::Okay); + s = sections.constBegin(); + while (s != sections.constEnd()) { + ref = registerRef((*s).name); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">" << protectEnc((*s).name) << "</h2>\n"; + generateSectionList(*s, cn, marker, CodeMarker::Summary); + ++s; + } + + Text brief = cn->doc().briefText(); + if (cn->isModule() && !brief.isEmpty()) { + generateExtractionMark(cn, DetailedDescriptionMark); + ref = registerRef("details"); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n'; + out() << "<div class=\"descr\">\n"; // QTBUG-9504 + out() << "<h2 id=\"" << ref << "\">" << "Detailed Description" << "</h2>\n"; + } + else { + generateExtractionMark(cn, DetailedDescriptionMark); + out() << "<div class=\"descr\"> <a name=\"" << registerRef("details") << "\"></a>\n"; // QTBUG-9504 + } + + generateBody(cn, marker); + out() << "</div>\n"; // QTBUG-9504 + generateAlsoList(cn, marker); + generateExtractionMark(cn, EndMark); + + if (!cn->noAutoList()) { + if (cn->isGroup()) + generateAnnotatedList(cn, marker, cn->members()); + else if (cn->isQmlModule() || cn->isJsModule()) + generateAnnotatedList(cn, marker, cn->members()); + } + + sections = marker->sections(cn, CodeMarker::Detailed, CodeMarker::Okay); + s = sections.constBegin(); + while (s != sections.constEnd()) { + //out() << "<hr />\n"; + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + + NodeList::ConstIterator m = (*s).members.constBegin(); + while (m != (*s).members.constEnd()) { + generateDetailedMember(*m, cn, marker); + ++m; + } + ++s; + } + generateFooter(cn); +} + +/*! + Returns "html" for this subclass of Generator. + */ +QString HtmlGenerator::fileExtension() const +{ + return "html"; +} + +/*! + Output navigation list in the html file. + */ +void HtmlGenerator::generateNavigationBar(const QString &title, + const Node *node, + CodeMarker *marker, + const QString &buildversion, + bool tableItems) +{ + if (noNavigationBar) + return; + + Text navigationbar; + + // Set list item types based on the navigation bar type + Atom::AtomType itemLeft = tableItems ? + Atom::TableItemLeft : Atom::ListItemLeft; + Atom::AtomType itemRight = tableItems ? + Atom::TableItemRight : Atom::ListItemRight; + + if (homepage == title) + return; + if (!homepage.isEmpty()) + navigationbar << Atom(itemLeft) + << Atom(Atom::NavAutoLink, homepage) + << Atom(itemRight); + if (!landingpage.isEmpty() && landingpage != title) + navigationbar << Atom(itemLeft) + << Atom(Atom::NavAutoLink, landingpage) + << Atom(itemRight); + + if (node->isClass()) { + if (!cppclassespage.isEmpty()) + navigationbar << Atom(itemLeft) + << Atom(Atom::NavLink, cppclassespage) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, QLatin1String("C++ Classes")) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom(itemRight); + + if (!node->name().isEmpty()) + navigationbar << Atom(itemLeft) + << Atom(Atom::String, node->name()) + << Atom(itemRight); + } + else if (node->isQmlType() || node->isQmlBasicType() || + node->isJsType() || node->isJsBasicType()) { + if (!qmltypespage.isEmpty()) + navigationbar << Atom(itemLeft) + << Atom(Atom::NavLink, qmltypespage) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, QLatin1String("QML Types")) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom(itemRight) + << Atom(itemLeft) + << Atom(Atom::String, title) + << Atom(itemRight); + } + else { + if (node->isExampleFile()) { + navigationbar << Atom(itemLeft) + << Atom(Atom::NavLink, node->parent()->name()) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, node->parent()->title()) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom(itemRight); + + } + navigationbar << Atom(itemLeft) + << Atom(Atom::String, title) + << Atom(itemRight); + } + + generateText(navigationbar, node, marker); + + if (buildversion.isEmpty()) + return; + + if (tableItems) { + out() << "</tr></table><table class=\"buildversion\"><tr>\n" + << "<td id=\"buildversion\" width=\"100%\" align=\"right\">" + << buildversion << "</td>\n"; + } else { + out() << "<li id=\"buildversion\">" << buildversion << "</li>\n"; + } +} + +void HtmlGenerator::generateHeader(const QString& title, + const Node *node, + CodeMarker *marker) +{ +#ifndef QT_NO_TEXTCODEC + out() << QString("<?xml version=\"1.0\" encoding=\"%1\"?>\n").arg(outputEncoding); +#else + out() << QString("<?xml version=\"1.0\"?>\n"); +#endif + out() << "<!DOCTYPE html>\n"; + out() << QString("<html lang=\"%1\">\n").arg(naturalLanguage); + out() << "<head>\n"; + out() << " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"; + if (node && !node->doc().location().isEmpty()) + out() << "<!-- " << node->doc().location().fileName() << " -->\n"; + + QString shortVersion = qdb_->version(); + if (shortVersion.count(QChar('.')) == 2) + shortVersion.truncate(shortVersion.lastIndexOf(QChar('.'))); + + //determine the rest of the <title> element content: "title | titleSuffix version" + QString titleSuffix; + if (!landingpage.isEmpty()) { + //for normal pages: "title | landingpage version" + titleSuffix = landingpage; + } + else if (!homepage.isEmpty()) { + //for pages that set the homepage but not landing page: "title | homepage version" + if (title != homepage) + titleSuffix = homepage; + } + else if (!project.isEmpty()) { + //for projects outside of Qt or Qt 5: "title | project version" + if (title != project) + titleSuffix = project; + } + else + //default: "title | Qt version" + titleSuffix = QLatin1String("Qt "); + + //for pages that duplicate the title and suffix (landing pages, home pages, + // and module landing pages, clear the duplicate + if (title == titleSuffix) + titleSuffix.clear(); + + //for pages that duplicate the version, clear the duplicate + if (title.contains(shortVersion) || titleSuffix.contains(shortVersion)) + shortVersion.clear(); + + QString divider; + if (!titleSuffix.isEmpty() && !title.isEmpty()) + divider = QLatin1String(" | "); + + // Generating page title + out() << " <title>" + << protectEnc(title) + << divider + << titleSuffix + << QLatin1Char(' ') + << shortVersion + << "</title>\n"; + + // Include style sheet and script links. + out() << headerStyles; + out() << headerScripts; + if (endHeader.isEmpty()) + out() << "</head>\n<body>\n"; + else + out() << endHeader; + +#ifdef GENERATE_MAC_REFS + if (mainPage) + generateMacRef(node, marker); +#endif + + out() << QString(postHeader).replace("\\" + COMMAND_VERSION, qdb_->version()); + bool usingTable = postHeader.trimmed().endsWith(QLatin1String("<tr>")); + generateNavigationBar(title, node, marker, buildversion, usingTable); + out() << QString(postPostHeader).replace("\\" + COMMAND_VERSION, qdb_->version()); + + navigationLinks.clear(); + refMap.clear(); + + if (node && !node->links().empty()) { + QPair<QString,QString> linkPair; + QPair<QString,QString> anchorPair; + const Node *linkNode; + + if (node->links().contains(Node::PreviousLink)) { + linkPair = node->links()[Node::PreviousLink]; + linkNode = qdb_->findNodeForTarget(linkPair.first, node); + if (!linkNode) + node->doc().location().warning(tr("Cannot link to '%1'").arg(linkPair.first)); + if (!linkNode || linkNode == node) + anchorPair = linkPair; + else + anchorPair = anchorForNode(linkNode); + + out() << " <link rel=\"prev\" href=\"" + << anchorPair.first << "\" />\n"; + + navigationLinks += "<a class=\"prevPage\" href=\"" + anchorPair.first + "\">"; + if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) + navigationLinks += protect(anchorPair.second); + else + navigationLinks += protect(linkPair.second); + navigationLinks += "</a>\n"; + } + if (node->links().contains(Node::NextLink)) { + linkPair = node->links()[Node::NextLink]; + linkNode = qdb_->findNodeForTarget(linkPair.first, node); + if (!linkNode) + node->doc().location().warning(tr("Cannot link to '%1'").arg(linkPair.first)); + if (!linkNode || linkNode == node) + anchorPair = linkPair; + else + anchorPair = anchorForNode(linkNode); + + out() << " <link rel=\"next\" href=\"" + << anchorPair.first << "\" />\n"; + + navigationLinks += "<a class=\"nextPage\" href=\"" + anchorPair.first + "\">"; + if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) + navigationLinks += protect(anchorPair.second); + else + navigationLinks += protect(linkPair.second); + navigationLinks += "</a>\n"; + } + if (node->links().contains(Node::StartLink)) { + linkPair = node->links()[Node::StartLink]; + linkNode = qdb_->findNodeForTarget(linkPair.first, node); + if (!linkNode) + node->doc().location().warning(tr("Cannot link to '%1'").arg(linkPair.first)); + if (!linkNode || linkNode == node) + anchorPair = linkPair; + else + anchorPair = anchorForNode(linkNode); + out() << " <link rel=\"start\" href=\"" + << anchorPair.first << "\" />\n"; + } + } + + if (node && !node->links().empty()) + out() << "<p class=\"naviNextPrevious headerNavi\">\n" << navigationLinks << "</p><p/>\n"; +} + +void HtmlGenerator::generateTitle(const QString& title, + const Text &subTitle, + SubTitleSize subTitleSize, + const Node *relative, + CodeMarker *marker) +{ + out() << QString(prologue).replace("\\" + COMMAND_VERSION, qdb_->version()); + if (!title.isEmpty()) + out() << "<h1 class=\"title\">" << protectEnc(title) << "</h1>\n"; + if (!subTitle.isEmpty()) { + out() << "<span"; + if (subTitleSize == SmallSubTitle) + out() << " class=\"small-subtitle\">"; + else + out() << " class=\"subtitle\">"; + generateText(subTitle, relative, marker); + out() << "</span>\n"; + } +} + +void HtmlGenerator::generateFooter(const Node *node) +{ + if (node && !node->links().empty()) + out() << "<p class=\"naviNextPrevious footerNavi\">\n" << navigationLinks << "</p>\n"; + + out() << QString(footer).replace("\\" + COMMAND_VERSION, qdb_->version()) + << QString(address).replace("\\" + COMMAND_VERSION, qdb_->version()); + + out() << "</body>\n"; + out() << "</html>\n"; +} + +/*! +Lists the required imports and includes in a table. +The number of rows is known, so this path is simpler than the generateSection() path. +*/ +void HtmlGenerator::generateRequisites(Aggregate *inner, CodeMarker *marker) +{ + QMap<QString, Text> requisites; + Text text; + + const QString headerText = "Header"; + const QString sinceText = "Since"; + const QString inheritedBytext = "Inherited By"; + const QString inheritsText = "Inherits"; + const QString instantiatedByText = "Instantiated By"; + const QString qtVariableText = "qmake"; + + //add the includes to the map + if (!inner->includes().isEmpty()) { + text.clear(); + text << highlightedCode(indent(codeIndent, + marker->markedUpIncludes(inner->includes())), + inner); + requisites.insert(headerText, text); + } + + //The order of the requisites matter + QStringList requisiteorder; + requisiteorder << headerText + << qtVariableText + << sinceText + << instantiatedByText + << inheritsText + << inheritedBytext; + + //add the since and project into the map + if (!inner->since().isEmpty()) { + text.clear(); + QStringList since = inner->since().split(QLatin1Char(' ')); + if (since.count() == 1) { + // If there is only one argument, assume it is the Qt version number. + text << " Qt " << since[0]; + } + else { + //Otherwise, reconstruct the <project> <version> string. + text << " " << since.join(' '); + } + text << Atom::ParaRight; + requisites.insert(sinceText, text); + } + + if (inner->type() == Node::Class || inner->type() == Node::Namespace) { + //add the QT variable to the map + if (!inner->physicalModuleName().isEmpty()) { + const CollectionNode* cn = qdb_->getCollectionNode(inner->physicalModuleName(), Node::CPP); + if (cn && !cn->qtVariable().isEmpty()) { + text.clear(); + text << "QT += " + cn->qtVariable(); + requisites.insert(qtVariableText, text); + } + } + } + + if (inner->type() == Node::Class) { + ClassNode* classe = static_cast<ClassNode*>(inner); + if (classe->qmlElement() != 0 && classe->status() != Node::Internal) { + text.clear(); + text << Atom(Atom::LinkNode, CodeMarker::stringForNode(classe->qmlElement())) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, classe->qmlElement()->name()) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + requisites.insert(instantiatedByText, text); + + } + + //add the inherits to the map + QList<RelatedClass>::ConstIterator r; + int index; + if (!classe->baseClasses().isEmpty()) { + text.clear(); + r = classe->baseClasses().constBegin(); + index = 0; + while (r != classe->baseClasses().constEnd()) { + if ((*r).node_) { + text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node_)) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, (*r).signature_) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + + if ((*r).access_ == Node::Protected) { + text << " (protected)"; + } + else if ((*r).access_ == Node::Private) { + text << " (private)"; + } + text << comma(index++, classe->baseClasses().count()); + } + ++r; + } + text << Atom::ParaRight; + requisites.insert(inheritsText, text); + } + + //add the inherited-by to the map + if (!classe->derivedClasses().isEmpty()) { + text.clear(); + text << Atom::ParaLeft; + appendSortedNames(text, classe, classe->derivedClasses()); + text << Atom::ParaRight; + requisites.insert(inheritedBytext, text); + } + } + + if (!requisites.isEmpty()) { + //generate the table + out() << "<div class=\"table\"><table class=\"alignedsummary\">\n"; + + QStringList::ConstIterator i; + for (i = requisiteorder.constBegin(); i != requisiteorder.constEnd(); ++i) { + + if (requisites.contains(*i)) { + out() << "<tr>" + << "<td class=\"memItemLeft rightAlign topAlign\"> " + << *i << ":" + "</td><td class=\"memItemRight bottomAlign\"> "; + + if (*i == headerText) + out() << requisites.value(*i).toString(); + else + generateText(requisites.value(*i), inner, marker); + out() << "</td></tr>"; + } + } + out() << "</table></div>"; + } +} + +/*! +Lists the required imports and includes in a table. +The number of rows is known, so this path is simpler than the generateSection() path. +*/ +void HtmlGenerator::generateQmlRequisites(QmlTypeNode *qcn, CodeMarker *marker) +{ + if (!qcn) + return; + QMap<QString, Text> requisites; + Text text; + + const QString importText = "Import Statement:"; + const QString sinceText = "Since:"; + const QString inheritedBytext = "Inherited By:"; + const QString inheritsText = "Inherits:"; + const QString instantiatesText = "Instantiates:"; + + //The order of the requisites matter + QStringList requisiteorder; + requisiteorder << importText + << sinceText + << instantiatesText + << inheritsText + << inheritedBytext; + + //add the module name and version to the map + QString logicalModuleVersion; + const CollectionNode* collection = qdb_->getCollectionNode(qcn->logicalModuleName(), qcn->genus()); + if (collection) + logicalModuleVersion = collection->logicalModuleVersion(); + else + logicalModuleVersion = qcn->logicalModuleVersion(); + text.clear(); + text << "import " + qcn->logicalModuleName() + QLatin1Char(' ') + logicalModuleVersion; + requisites.insert(importText, text); + + //add the since and project into the map + if (!qcn->since().isEmpty()) { + text.clear(); + QStringList since = qcn->since().split(QLatin1Char(' ')); + if (since.count() == 1) { + // If there is only one argument, assume it is the Qt version number. + text << " Qt " << since[0]; + } + else { + //Otherwise, reconstruct the <project> <version> string. + text << " " << since.join(' '); + } + text << Atom::ParaRight; + requisites.insert(sinceText, text); + } + + //add the instantiates to the map + ClassNode* cn = qcn->classNode(); + if (cn && (cn->status() != Node::Internal)) { + text.clear(); + text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn)); + text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn)); + text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + text << Atom(Atom::String, cn->name()); + text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + requisites.insert(instantiatesText, text); + } + + //add the inherits to the map + QmlTypeNode* base = qcn->qmlBaseNode(); + while (base && base->isInternal()) { + base = base->qmlBaseNode(); + } + if (base) { + text.clear(); + text << Atom::ParaLeft + << Atom(Atom::LinkNode,CodeMarker::stringForNode(base)) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, base->name()) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom::ParaRight; + requisites.insert(inheritsText, text); + } + + //add the inherited-by to the map + NodeList subs; + QmlTypeNode::subclasses(qcn->name(), subs); + if (!subs.isEmpty()) { + text.clear(); + text << Atom::ParaLeft; + appendSortedQmlNames(text, qcn, subs); + text << Atom::ParaRight; + requisites.insert(inheritedBytext, text); + } + + if (!requisites.isEmpty()) { + //generate the table + out() << "<div class=\"table\"><table class=\"alignedsummary\">\n"; + + QStringList::ConstIterator i; + for (i = requisiteorder.constBegin(); i != requisiteorder.constEnd(); ++i) { + + if (requisites.contains(*i)) { + out() << "<tr>" + << "<td class=\"memItemLeft rightAlign topAlign\"> " + << *i + << "</td><td class=\"memItemRight bottomAlign\"> "; + + if (*i == importText) + out()<<requisites.value(*i).toString(); + else + generateText(requisites.value(*i), qcn, marker); + out() << "</td></tr>"; + } + } + out() << "</table></div>"; + } +} + +void HtmlGenerator::generateBrief(const Node *node, CodeMarker *marker, + const Node *relative) +{ + Text brief = node->doc().briefText(); + if (!brief.isEmpty()) { + generateExtractionMark(node, BriefMark); + out() << "<p>"; + generateText(brief, node, marker); + + if (!relative || node == relative) + out() << " <a href=\"#"; + else + out() << " <a href=\"" << linkForNode(node, relative) << '#'; + out() << registerRef("details") << "\">More...</a></p>\n"; + + + generateExtractionMark(node, EndMark); + } +} + +void HtmlGenerator::generateIncludes(const Aggregate *inner, CodeMarker *marker) +{ + if (!inner->includes().isEmpty()) { + out() << "<pre class=\"cpp\">" + << trimmedTrailing(highlightedCode(indent(codeIndent, + marker->markedUpIncludes(inner->includes())), + inner), codePrefix, codeSuffix) + << "</pre>"; + } +} + +/*! + Revised for the new doc format. + Generates a table of contents beginning at \a node. + */ +void HtmlGenerator::generateTableOfContents(const Node *node, + CodeMarker *marker, + QList<Section>* sections) +{ + QList<Atom*> toc; + if (node->doc().hasTableOfContents()) + toc = node->doc().tableOfContents(); + if (tocDepth == 0 || (toc.isEmpty() && !sections && !node->isModule())) { + generateSidebar(); + return; + } + + QStringList sectionNumber; + int detailsBase = 0; + + // disable nested links in table of contents + inContents_ = true; + inLink_ = true; + + out() << "<div class=\"sidebar\">\n"; + out() << "<div class=\"toc\">\n"; + out() << "<h3><a name=\"toc\">Contents</a></h3>\n"; + sectionNumber.append("1"); + out() << "<ul>\n"; + + if (node->isModule()) { + if (node->hasNamespaces()) { + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef("namespaces") + << "\">Namespaces</a></li>\n"; + } + if (node->hasClasses()) { + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef("classes") + << "\">Classes</a></li>\n"; + } + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef("details") + << "\">Detailed Description</a></li>\n"; + for (int i = 0; i < toc.size(); ++i) { + if (toc.at(i)->string().toInt() == 1) { + detailsBase = 1; + break; + } + } + } + else if (sections && (node->isClass() || + node->isNamespace() || + node->isQmlType() || + node->isJsType())) { + QList<Section>::ConstIterator s = sections->constBegin(); + while (s != sections->constEnd()) { + if (!s->members.isEmpty()) { + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef((*s).pluralMember) + << "\">" << (*s).name + << "</a></li>\n"; + } + if (!s->reimpMembers.isEmpty()) { + QString ref = QString("Reimplemented ") + (*s).pluralMember; + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef(ref.toLower()) + << "\">" << QString("Reimplemented ") + (*s).name + << "</a></li>\n"; + } + ++s; + } + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef("details") + << "\">Detailed Description</a></li>\n"; + for (int i = 0; i < toc.size(); ++i) { + if (toc.at(i)->string().toInt() == 1) { + detailsBase = 1; + break; + } + } + } + + for (int i = 0; i < toc.size(); ++i) { + Atom *atom = toc.at(i); + int nextLevel = atom->string().toInt() + detailsBase; + if (nextLevel >= 0) { + if (sectionNumber.size() < nextLevel) { + do { + sectionNumber.append("1"); + } while (sectionNumber.size() < nextLevel); + } + else { + while (sectionNumber.size() > nextLevel) { + sectionNumber.removeLast(); + } + sectionNumber.last() = QString::number(sectionNumber.last().toInt() + 1); + } + } + + //restrict the ToC depth to the one set by the HTML.tocdepth variable or + //print all levels if tocDepth is not set. + if (sectionNumber.size() <= tocDepth || tocDepth < 0) { + int numAtoms; + Text headingText = Text::sectionHeading(atom); + QString s = headingText.toString(); + out() << "<li class=\"level" + << sectionNumber.size() + << "\">"; + out() << "<a href=\"" + << '#' + << Doc::canonicalTitle(s) + << "\">"; + generateAtomList(headingText.firstAtom(), node, marker, true, numAtoms); + out() << "</a></li>\n"; + } + } + while (!sectionNumber.isEmpty()) { + sectionNumber.removeLast(); + } + out() << "</ul>\n"; + out() << "</div>\n"; + out() << "<div class=\"sidebar-content\" id=\"sidebar-content\"></div>"; + out() << "</div>\n"; + inContents_ = false; + inLink_ = false; +} + +/*! + Outputs a placeholder div where the style can add customized sidebar content. + */ +void HtmlGenerator::generateSidebar() { + out() << "<div class=\"sidebar\">"; + out() << "<div class=\"sidebar-content\" id=\"sidebar-content\"></div>"; + out() << "</div>\n"; +} + +QString HtmlGenerator::generateListOfAllMemberFile(const Aggregate *inner, + CodeMarker *marker) +{ + QList<Section> sections; + QList<Section>::ConstIterator s; + + sections = marker->sections(inner, + CodeMarker::Subpage, + CodeMarker::Okay); + if (sections.isEmpty()) + return QString(); + + QString fileName = fileBase(inner) + "-members." + fileExtension(); + beginSubPage(inner, fileName); + QString title = "List of All Members for " + inner->name(); + generateHeader(title, inner, marker); + generateSidebar(); + generateTitle(title, Text(), SmallSubTitle, inner, marker); + out() << "<p>This is the complete list of members for "; + generateFullName(inner, 0); + out() << ", including inherited members.</p>\n"; + + Section section = sections.first(); + generateSectionList(section, inner, marker, CodeMarker::Subpage); + + generateFooter(); + endSubPage(); + return fileName; +} + +/*! + This function creates an html page on which are listed all + the members of QML class \a qml_cn, including the inherited + members. The \a marker is used for formatting stuff. + */ +QString HtmlGenerator::generateAllQmlMembersFile(QmlTypeNode* qml_cn, CodeMarker* marker) +{ + QList<Section> sections; + QList<Section>::ConstIterator s; + + sections = marker->qmlSections(qml_cn,CodeMarker::Subpage); + if (sections.isEmpty()) + return QString(); + + QString fileName = fileBase(qml_cn) + "-members." + fileExtension(); + beginSubPage(qml_cn, fileName); + QString title = "List of All Members for " + qml_cn->name(); + generateHeader(title, qml_cn, marker); + generateSidebar(); + generateTitle(title, Text(), SmallSubTitle, qml_cn, marker); + out() << "<p>This is the complete list of members for "; + generateFullName(qml_cn, 0); + out() << ", including inherited members.</p>\n"; + + ClassKeysNodesList& cknl = sections.first().classKeysNodesList_; + if (!cknl.isEmpty()) { + for (int i=0; i<cknl.size(); i++) { + ClassKeysNodes* ckn = cknl[i]; + const QmlTypeNode* qcn = ckn->first; + KeysAndNodes& kn = ckn->second; + QStringList& keys = kn.first; + NodeList& nodes = kn.second; + if (nodes.isEmpty()) + continue; + if (i != 0) { + out() << "<p>The following members are inherited from "; + generateFullName(qcn,0); + out() << ".</p>\n"; + } + out() << "<ul>\n"; + for (int j=0; j<keys.size(); j++) { + if (nodes[j]->access() == Node::Private || nodes[j]->status() == Node::Internal) { + continue; + } + out() << "<li class=\"fn\">"; + QString prefix; + if (!keys.isEmpty()) { + prefix = keys.at(j).mid(1); + prefix = prefix.left(keys.at(j).indexOf("::")+1); + } + generateQmlItem(nodes[j], qml_cn, marker, true); + if (nodes[j]->isAttached()) + out() << " [attached]"; + //generateSynopsis(nodes[j], qcn, marker, CodeMarker::Subpage, false, &prefix); + out() << "</li>\n"; + } + out() << "</ul>\n"; + } + } + + generateFooter(); + endSubPage(); + return fileName; +} + +QString HtmlGenerator::generateLowStatusMemberFile(Aggregate *inner, + CodeMarker *marker, + CodeMarker::Status status) +{ + QList<Section> sections = marker->sections(inner, + CodeMarker::Summary, + status); + QMutableListIterator<Section> j(sections); + while (j.hasNext()) { + if (j.next().members.size() == 0) + j.remove(); + } + if (sections.isEmpty()) + return QString(); + + int i; + + QString title; + QString fileName; + + if (status == CodeMarker::Compat) { + title = "Compatibility Members for " + inner->name(); + fileName = fileBase(inner) + "-compat." + fileExtension(); + } + else { + title = "Obsolete Members for " + inner->name(); + fileName = fileBase(inner) + "-obsolete." + fileExtension(); + } + if (status == CodeMarker::Obsolete) { + QString link; + if (useOutputSubdirs() && !Generator::outputSubdir().isEmpty()) + link = QString("../" + Generator::outputSubdir() + QLatin1Char('/')); + link += fileName; + inner->setObsoleteLink(link); + } + + beginSubPage(inner, fileName); + generateHeader(title, inner, marker); + generateSidebar(); + generateTitle(title, Text(), SmallSubTitle, inner, marker); + + if (status == CodeMarker::Compat) { + out() << "<p><b>The following members of class " + << "<a href=\"" << linkForNode(inner, 0) << "\">" + << protectEnc(inner->name()) << "</a>" + << " are part of the " + "Qt compatibility layer.</b> We advise against " + "using them in new code.</p>\n"; + } + else { + out() << "<p><b>The following members of class " + << "<a href=\"" << linkForNode(inner, 0) << "\">" + << protectEnc(inner->name()) << "</a>" + << " are obsolete.</b> " + << "They are provided to keep old source code working. " + << "We strongly advise against using them in new code.</p>\n"; + } + + for (i = 0; i < sections.size(); ++i) { + out() << "<h2>" << protectEnc(sections.at(i).name) << "</h2>\n"; + generateSectionList(sections.at(i), inner, marker, CodeMarker::Summary); + } + + sections = marker->sections(inner, CodeMarker::Detailed, status); + for (i = 0; i < sections.size(); ++i) { + //out() << "<hr />\n"; + out() << "<h2>" << protectEnc(sections.at(i).name) << "</h2>\n"; + + NodeList::ConstIterator m = sections.at(i).members.constBegin(); + while (m != sections.at(i).members.constEnd()) { + if ((*m)->access() != Node::Private) + generateDetailedMember(*m, inner, marker); + ++m; + } + } + + generateFooter(); + endSubPage(); + return fileName; +} + +/*! + Generates a separate file where certain members of the QML + type \a qcn are listed. The \a marker is used to generate + the section lists, which are then traversed and output here. + + Note that this function currently only handles correctly the + case where \a status is \c {CodeMarker::Obsolete}. + */ +QString HtmlGenerator::generateQmlMemberFile(QmlTypeNode* qcn, + CodeMarker *marker, + CodeMarker::Status status) +{ + QList<Section> sections = marker->qmlSections(qcn, CodeMarker::Summary, status); + QMutableListIterator<Section> j(sections); + while (j.hasNext()) { + if (j.next().members.size() == 0) + j.remove(); + } + if (sections.isEmpty()) + return QString(); + + QString title = "Obsolete Members for " + qcn->name(); + QString fileName = fileBase(qcn) + "-obsolete." + fileExtension(); + + if (status == CodeMarker::Obsolete) { + QString link; + if (useOutputSubdirs() && !Generator::outputSubdir().isEmpty()) + link = QString("../" + Generator::outputSubdir() + QLatin1Char('/')); + link += fileName; + qcn->setObsoleteLink(link); + } + + beginSubPage(qcn, fileName); + generateHeader(title, qcn, marker); + generateSidebar(); + generateTitle(title, Text(), SmallSubTitle, qcn, marker); + + out() << "<p><b>The following members of QML type " + << "<a href=\"" << linkForNode(qcn, 0) << "\">" + << protectEnc(qcn->name()) << "</a>" + << " are obsolete.</b> " + << "They are provided to keep old source code working. " + << "We strongly advise against using them in new code.</p>\n"; + + QList<Section>::const_iterator s = sections.constBegin(); + while (s != sections.constEnd()) { + QString ref = registerRef((*s).name.toLower()); + out() << "<a name=\"" << ref + << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">" << protectEnc((*s).name) << "</h2>\n"; + generateQmlSummary(*s, qcn, marker); + ++s; + } + + sections = marker->qmlSections(qcn, CodeMarker::Detailed, status); + QMutableListIterator<Section> k(sections); + while (k.hasNext()) { + if (k.next().members.size() == 0) + k.remove(); + } + if (sections.isEmpty()) + return QString(); + + s = sections.constBegin(); + while (s != sections.constEnd()) { + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + NodeList::ConstIterator m = (*s).members.constBegin(); + while (m != (*s).members.constEnd()) { + generateDetailedQmlMember(*m, qcn, marker); + out() << "<br/>\n"; + ++m; + } + ++s; + } + + generateFooter(); + endSubPage(); + return fileName; +} + +void HtmlGenerator::generateClassHierarchy(const Node *relative, NodeMap& classMap) +{ + if (classMap.isEmpty()) + return; + + NodeMap topLevel; + NodeMap::Iterator c = classMap.begin(); + while (c != classMap.end()) { + ClassNode *classe = static_cast<ClassNode *>(*c); + if (classe->baseClasses().isEmpty()) + topLevel.insert(classe->name(), classe); + ++c; + } + + QStack<NodeMap > stack; + stack.push(topLevel); + + out() << "<ul>\n"; + while (!stack.isEmpty()) { + if (stack.top().isEmpty()) { + stack.pop(); + out() << "</ul>\n"; + } + else { + ClassNode* child = static_cast<ClassNode*>(*stack.top().begin()); + out() << "<li>"; + generateFullName(child, relative); + out() << "</li>\n"; + stack.top().erase(stack.top().begin()); + + NodeMap newTop; + foreach (const RelatedClass &d, child->derivedClasses()) { + if (d.node_ && !d.isPrivate() && !d.node_->isInternal() && d.node_->hasDoc()) + newTop.insert(d.node_->name(), d.node_); + } + if (!newTop.isEmpty()) { + stack.push(newTop); + out() << "<ul>\n"; + } + } + } +} + +/*! + Output an annotated list of the nodes in \a nodeMap. + A two-column table is output. + */ +void HtmlGenerator::generateAnnotatedList(const Node* relative, + CodeMarker* marker, + const NodeMultiMap& nmm) +{ + if (nmm.isEmpty()) + return; + generateAnnotatedList(relative, marker, nmm.values()); +} + +/*! + */ +void HtmlGenerator::generateAnnotatedList(const Node *relative, + CodeMarker *marker, + const NodeList& unsortedNodes) +{ + NodeMultiMap nmm; + bool allInternal = true; + foreach (Node* node, unsortedNodes) { + if (!node->isInternal() && !node->isObsolete()) { + allInternal = false; + nmm.insert(node->fullName(relative), node); + } + } + if (allInternal) + return; + out() << "<div class=\"table\"><table class=\"annotated\">\n"; + int row = 0; + NodeList nodes = nmm.values(); + foreach (const Node* node, nodes) { + if (++row % 2 == 1) + out() << "<tr class=\"odd topAlign\">"; + else + out() << "<tr class=\"even topAlign\">"; + out() << "<td class=\"tblName\"><p>"; + generateFullName(node, relative); + out() << "</p></td>"; + + if (!node->isDocumentNode()) { + Text brief = node->doc().trimmedBriefText(node->name()); + if (!brief.isEmpty()) { + out() << "<td class=\"tblDescr\"><p>"; + generateText(brief, node, marker); + out() << "</p></td>"; + } + else if (!node->reconstitutedBrief().isEmpty()) { + out() << "<td class=\"tblDescr\"><p>"; + out() << node->reconstitutedBrief(); + out() << "</p></td>"; + } + } + else { + out() << "<td class=\"tblDescr\"><p>"; + if (!node->reconstitutedBrief().isEmpty()) { + out() << node->reconstitutedBrief(); + } + else + out() << protectEnc(node->doc().briefText().toString()); + out() << "</p></td>"; + } + out() << "</tr>\n"; + } + out() << "</table></div>\n"; +} + +/*! + This function finds the common prefix of the names of all + the classes in the class map \a nmm and then generates a + compact list of the class names alphabetized on the part + of the name not including the common prefix. You can tell + the function to use \a comonPrefix as the common prefix, + but normally you let it figure it out itself by looking at + the name of the first and last classes in the class map + \a nmm. + */ +void HtmlGenerator::generateCompactList(ListType listType, + const Node *relative, + const NodeMultiMap &nmm, + bool includeAlphabet, + QString commonPrefix) +{ + if (nmm.isEmpty()) + return; + + const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_' + int commonPrefixLen = commonPrefix.length(); + + /* + Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z, + underscore (_). QAccel will fall in paragraph 10 (A) and + QXtWidget in paragraph 33 (X). This is the only place where we + assume that NumParagraphs is 37. Each paragraph is a NodeMultiMap. + */ + NodeMultiMap paragraph[NumParagraphs+1]; + QString paragraphName[NumParagraphs+1]; + QSet<char> usedParagraphNames; + + NodeMultiMap::ConstIterator c = nmm.constBegin(); + while (c != nmm.constEnd()) { + QStringList pieces = c.key().split("::"); + QString key; + int idx = commonPrefixLen; + if (idx > 0 && !pieces.last().startsWith(commonPrefix)) + idx = 0; + if (pieces.size() == 1) + key = pieces.last().mid(idx).toLower(); + else + key = pieces.last().toLower(); + + int paragraphNr = NumParagraphs - 1; + + if (key[0].digitValue() != -1) { + paragraphNr = key[0].digitValue(); + } + else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) { + paragraphNr = 10 + key[0].unicode() - 'a'; + } + + paragraphName[paragraphNr] = key[0].toUpper(); + usedParagraphNames.insert(key[0].toLower().cell()); + paragraph[paragraphNr].insert(c.key(), c.value()); + ++c; + } + + /* + Each paragraph j has a size: paragraph[j].count(). In the + discussion, we will assume paragraphs 0 to 5 will have sizes + 3, 1, 4, 1, 5, 9. + + We now want to compute the paragraph offset. Paragraphs 0 to 6 + start at offsets 0, 3, 4, 8, 9, 14, 23. + */ + int paragraphOffset[NumParagraphs + 1]; // 37 + 1 + paragraphOffset[0] = 0; + for (int i=0; i<NumParagraphs; i++) // i = 0..36 + paragraphOffset[i+1] = paragraphOffset[i] + paragraph[i].count(); + + /* + Output the alphabet as a row of links. + */ + if (includeAlphabet) { + out() << "<p class=\"centerAlign functionIndex\"><b>"; + for (int i = 0; i < 26; i++) { + QChar ch('a' + i); + if (usedParagraphNames.contains(char('a' + i))) + out() << QString("<a href=\"#%1\">%2</a> ").arg(ch).arg(ch.toUpper()); + } + out() << "</b></p>\n"; + } + + /* + Output a <div> element to contain all the <dl> elements. + */ + out() << "<div class=\"flowListDiv\">\n"; + numTableRows_ = 0; + + int curParNr = 0; + int curParOffset = 0; + QString previousName; + bool multipleOccurrences = false; + + for (int i=0; i<nmm.count(); i++) { + while ((curParNr < NumParagraphs) && + (curParOffset == paragraph[curParNr].count())) { + ++curParNr; + curParOffset = 0; + } + + /* + Starting a new paragraph means starting a new <dl>. + */ + if (curParOffset == 0) { + if (i > 0) + out() << "</dl>\n"; + if (++numTableRows_ % 2 == 1) + out() << "<dl class=\"flowList odd\">"; + else + out() << "<dl class=\"flowList even\">"; + out() << "<dt class=\"alphaChar\">"; + if (includeAlphabet) { + QChar c = paragraphName[curParNr][0].toLower(); + out() << QString("<a name=\"%1\"></a>").arg(c); + } + out() << "<b>" + << paragraphName[curParNr] + << "</b>"; + out() << "</dt>\n"; + } + + /* + Output a <dd> for the current offset in the current paragraph. + */ + out() << "<dd>"; + if ((curParNr < NumParagraphs) && + !paragraphName[curParNr].isEmpty()) { + NodeMultiMap::Iterator it; + NodeMultiMap::Iterator next; + it = paragraph[curParNr].begin(); + for (int i=0; i<curParOffset; i++) + ++it; + + if (listType == Generic) { + /* + Previously, we used generateFullName() for this, but we + require some special formatting. + */ + out() << "<a href=\"" << linkForNode(it.value(), relative) << "\">"; + } + else if (listType == Obsolete) { + QString fileName = fileBase(it.value()) + "-obsolete." + fileExtension(); + QString link; + if (useOutputSubdirs()) { + link = QString("../" + it.value()->outputSubdirectory() + QLatin1Char('/')); + } + link += fileName; + out() << "<a href=\"" << link << "\">"; + } + + QStringList pieces; + if (it.value()->isQmlType() || it.value()->isJsType()) { + QString name = it.value()->name(); + next = it; + ++next; + if (name != previousName) + multipleOccurrences = false; + if ((next != paragraph[curParNr].end()) && (name == next.value()->name())) { + multipleOccurrences = true; + previousName = name; + } + if (multipleOccurrences) + name += ": " + it.value()->tree()->camelCaseModuleName(); + pieces << name; + } + else + pieces = it.value()->fullName(relative).split("::"); + out() << protectEnc(pieces.last()); + out() << "</a>"; + if (pieces.size() > 1) { + out() << " ("; + generateFullName(it.value()->parent(), relative); + out() << ')'; + } + } + out() << "</dd>\n"; + curParOffset++; + } + if (nmm.count() > 0) + out() << "</dl>\n"; + + out() << "</div>\n"; +} + +void HtmlGenerator::generateFunctionIndex(const Node *relative) +{ + out() << "<p class=\"centerAlign functionIndex\"><b>"; + for (int i = 0; i < 26; i++) { + QChar ch('a' + i); + out() << QString("<a href=\"#%1\">%2</a> ").arg(ch).arg(ch.toUpper()); + } + out() << "</b></p>\n"; + + char nextLetter = 'a'; + char currentLetter; + + out() << "<ul>\n"; + NodeMapMap& funcIndex = qdb_->getFunctionIndex(); + QMap<QString, NodeMap >::ConstIterator f = funcIndex.constBegin(); + while (f != funcIndex.constEnd()) { + out() << "<li>"; + out() << protectEnc(f.key()) << ':'; + + currentLetter = f.key()[0].unicode(); + while (islower(currentLetter) && currentLetter >= nextLetter) { + out() << QString("<a name=\"%1\"></a>").arg(nextLetter); + nextLetter++; + } + + NodeMap::ConstIterator s = (*f).constBegin(); + while (s != (*f).constEnd()) { + out() << ' '; + generateFullName((*s)->parent(), relative, *s); + ++s; + } + out() << "</li>"; + out() << '\n'; + ++f; + } + out() << "</ul>\n"; +} + +void HtmlGenerator::generateLegaleseList(const Node *relative, CodeMarker *marker) +{ + TextToNodeMap& legaleseTexts = qdb_->getLegaleseTexts(); + QMap<Text, const Node *>::ConstIterator it = legaleseTexts.constBegin(); + while (it != legaleseTexts.constEnd()) { + Text text = it.key(); + //out() << "<hr />\n"; + generateText(text, relative, marker); + out() << "<ul>\n"; + do { + out() << "<li>"; + generateFullName(it.value(), relative); + out() << "</li>\n"; + ++it; + } while (it != legaleseTexts.constEnd() && it.key() == text); + out() << "</ul>\n"; + } +} + +void HtmlGenerator::generateQmlItem(const Node *node, + const Node *relative, + CodeMarker *marker, + bool summary) +{ + QString marked = marker->markedUpQmlItem(node,summary); + QRegExp templateTag("(<[^@>]*>)"); + if (marked.indexOf(templateTag) != -1) { + QString contents = protectEnc(marked.mid(templateTag.pos(1), + templateTag.cap(1).length())); + marked.replace(templateTag.pos(1), templateTag.cap(1).length(), + contents); + } + marked.replace(QRegExp("<@param>([a-z]+)_([1-9n])</@param>"), + "<i>\\1<sub>\\2</sub></i>"); + marked.replace("<@param>", "<i>"); + marked.replace("</@param>", "</i>"); + + if (summary) + marked.replace("@name>", "b>"); + + marked.replace("<@extra>", "<code>"); + marked.replace("</@extra>", "</code>"); + + if (summary) { + marked.remove("<@type>"); + marked.remove("</@type>"); + } + out() << highlightedCode(marked, relative, false); +} + +void HtmlGenerator::generateList(const Node* relative, CodeMarker* marker, const QString& selector) +{ + CNMap cnm; + Node::Genus genus = Node::DontCare; + if (selector == QLatin1String("overviews")) + genus = Node::DOC; + else if (selector == QLatin1String("cpp-modules")) + genus = Node::CPP; + else if (selector == QLatin1String("qml-modules")) + genus = Node::QML; + else if (selector == QLatin1String("js-modules")) + genus = Node::JS; + if (genus != Node::DontCare) { + NodeList nl; + qdb_->mergeCollections(genus, cnm, relative); + CollectionList cl = cnm.values(); + foreach (CollectionNode* cn, cl) + nl.append(cn); + generateAnnotatedList(relative, marker, nl); + } + else { + /* + \generatelist {selector} is only allowed in a + comment where the topic is \group, \module, + \qmlmodule, or \jsmodule + */ + if (!relative || !relative->isCollectionNode()) { + relative->doc().location().warning(tr("\\generatelist {%1} is only allowed in \\group, " + "\\module, \\qmlmodule, and \\jsmodule comments.").arg(selector)); + return; + } + Node* n = const_cast<Node*>(relative); + CollectionNode* cn = static_cast<CollectionNode*>(n); + qdb_->mergeCollections(cn); + generateAnnotatedList(cn, marker, cn->members()); + } +} + +void HtmlGenerator::generateSection(const NodeList& nl, + const Node *relative, + CodeMarker *marker, + CodeMarker::SynopsisStyle style) +{ + bool alignNames = true; + if (!nl.isEmpty()) { + bool twoColumn = false; + if (style == CodeMarker::Subpage) { + alignNames = false; + twoColumn = (nl.count() >= 16); + } + else if (nl.first()->type() == Node::Property) { + twoColumn = (nl.count() >= 5); + alignNames = false; + } + if (alignNames) { + out() << "<div class=\"table\"><table class=\"alignedsummary\">\n"; + } + else { + if (twoColumn) + out() << "<div class=\"table\"><table class=\"propsummary\">\n" + << "<tr><td class=\"topAlign\">"; + out() << "<ul>\n"; + } + + int i = 0; + NodeList::ConstIterator m = nl.constBegin(); + while (m != nl.constEnd()) { + if ((*m)->access() == Node::Private) { + ++m; + continue; + } + + if (alignNames) { + out() << "<tr><td class=\"memItemLeft rightAlign topAlign\"> "; + } + else { + if (twoColumn && i == (int) (nl.count() + 1) / 2) + out() << "</ul></td><td class=\"topAlign\"><ul>\n"; + out() << "<li class=\"fn\">"; + } + + generateSynopsis(*m, relative, marker, style, alignNames); + if (alignNames) + out() << "</td></tr>\n"; + else + out() << "</li>\n"; + i++; + ++m; + } + if (alignNames) + out() << "</table></div>\n"; + else { + out() << "</ul>\n"; + if (twoColumn) + out() << "</td></tr>\n</table></div>\n"; + } + } +} + +void HtmlGenerator::generateSectionList(const Section& section, + const Node *relative, + CodeMarker *marker, + CodeMarker::SynopsisStyle style) +{ + bool alignNames = true; + if (!section.members.isEmpty()) { + bool hasPrivateSignals = false; + bool twoColumn = false; + if (style == CodeMarker::Subpage) { + alignNames = false; + twoColumn = (section.members.count() >= 16); + } + else if (section.members.first()->type() == Node::Property) { + twoColumn = (section.members.count() >= 5); + alignNames = false; + } + if (alignNames) { + out() << "<div class=\"table\"><table class=\"alignedsummary\">\n"; + } + else { + if (twoColumn) + out() << "<div class=\"table\"><table class=\"propsummary\">\n" + << "<tr><td class=\"topAlign\">"; + out() << "<ul>\n"; + } + + int i = 0; + NodeList::ConstIterator m = section.members.constBegin(); + while (m != section.members.constEnd()) { + if ((*m)->access() == Node::Private) { + ++m; + continue; + } + + if (alignNames) { + out() << "<tr><td class=\"memItemLeft topAlign rightAlign\"> "; + } + else { + if (twoColumn && i == (int) (section.members.count() + 1) / 2) + out() << "</ul></td><td class=\"topAlign\"><ul>\n"; + out() << "<li class=\"fn\">"; + } + + QString prefix; + if (!section.keys.isEmpty()) { + prefix = section.keys.at(i).mid(1); + prefix = prefix.left(section.keys.at(i).indexOf("::")+1); + } + generateSynopsis(*m, relative, marker, style, alignNames, &prefix); + if ((*m)->isFunction()) { + const FunctionNode* fn = static_cast<const FunctionNode*>(*m); + if (fn->isPrivateSignal()) { + hasPrivateSignals = true; + if (alignNames) + out() << "</td><td class=\"memItemRight bottomAlign\">[see note below]"; + } + } + if (alignNames) + out() << "</td></tr>\n"; + else + out() << "</li>\n"; + i++; + ++m; + } + if (alignNames) + out() << "</table></div>\n"; + else { + out() << "</ul>\n"; + if (twoColumn) + out() << "</td></tr>\n</table></div>\n"; + } + if (hasPrivateSignals && alignNames) { + generatePrivateSignalNote(relative, marker); + } + } + + if (style == CodeMarker::Summary && !section.inherited.isEmpty()) { + out() << "<ul>\n"; + generateSectionInheritedList(section, relative); + out() << "</ul>\n"; + } +} + +void HtmlGenerator::generateSectionInheritedList(const Section& section, const Node *relative) +{ + QList<QPair<Aggregate *, int> >::ConstIterator p = section.inherited.constBegin(); + while (p != section.inherited.constEnd()) { + out() << "<li class=\"fn\">"; + out() << (*p).second << ' '; + if ((*p).second == 1) { + out() << section.singularMember; + } + else { + out() << section.pluralMember; + } + out() << " inherited from <a href=\"" << fileName((*p).first) + << '#' << Generator::cleanRef(section.name.toLower()) << "\">" + << protectEnc((*p).first->plainFullName(relative)) + << "</a></li>\n"; + ++p; + } +} + +// generateSynopsis(qmn,relative,marker,CodeMarker::Detailed,false); +void HtmlGenerator::generateSynopsis(const Node *node, + const Node *relative, + CodeMarker *marker, + CodeMarker::SynopsisStyle style, + bool alignNames, + const QString* prefix) +{ + QString marked = marker->markedUpSynopsis(node, relative, style); + + if (prefix) + marked.prepend(*prefix); + QRegExp templateTag("(<[^@>]*>)"); + if (marked.indexOf(templateTag) != -1) { + QString contents = protectEnc(marked.mid(templateTag.pos(1), + templateTag.cap(1).length())); + marked.replace(templateTag.pos(1), templateTag.cap(1).length(), + contents); + } + marked.replace(QRegExp("<@param>([a-z]+)_([1-9n])</@param>"), + "<i>\\1<sub>\\2</sub></i>"); + marked.replace("<@param>", "<i>"); + marked.replace("</@param>", "</i>"); + + if (style == CodeMarker::Summary) { + marked.remove("<@name>"); // was "<b>" + marked.remove("</@name>"); // was "</b>" + } + + if (style == CodeMarker::Subpage) { + QRegExp extraRegExp("<@extra>.*</@extra>"); + extraRegExp.setMinimal(true); + marked.remove(extraRegExp); + } else { + marked.replace("<@extra>", "<code>"); + marked.replace("</@extra>", "</code>"); + } + + if (style != CodeMarker::Detailed) { + marked.remove("<@type>"); + marked.remove("</@type>"); + } + + out() << highlightedCode(marked, relative, alignNames); +} + +QString HtmlGenerator::highlightedCode(const QString& markedCode, + const Node* relative, + bool alignNames) +{ + QString src = markedCode; + QString html; + html.reserve(src.size()); + QStringRef arg; + QStringRef par1; + + const QChar charLangle = '<'; + const QChar charAt = '@'; + + static const QString typeTag("type"); + static const QString headerTag("headerfile"); + static const QString funcTag("func"); + static const QString linkTag("link"); + + // replace all <@link> tags: "(<@link node=\"([^\"]+)\">).*(</@link>)" + // replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)(</@func>)" + // replace all "(<@(type|headerfile)(?: +[^>]*)?>)(.*)(</@\\2>)" tags + bool done = false; + for (int i = 0, srcSize = src.size(); i < srcSize;) { + if (src.at(i) == charLangle && src.at(i + 1) == charAt) { + if (alignNames && !done) { + html += QLatin1String("</td><td class=\"memItemRight bottomAlign\">"); + done = true; + } + i += 2; + if (parseArg(src, linkTag, &i, srcSize, &arg, &par1)) { + html += QLatin1String("<b>"); + const Node* n = CodeMarker::nodeForString(par1.toString()); + QString link = linkForNode(n, relative); + addLink(link, arg, &html); + html += QLatin1String("</b>"); + } + else if (parseArg(src, funcTag, &i, srcSize, &arg, &par1)) { + const Node* n = qdb_->findFunctionNode(par1.toString(), relative, Node::DontCare); + QString link = linkForNode(n, relative); + addLink(link, arg, &html); + par1 = QStringRef(); + } + else if (parseArg(src, typeTag, &i, srcSize, &arg, &par1)) { + par1 = QStringRef(); + const Node* n = qdb_->findTypeNode(arg.toString(), relative); + html += QLatin1String("<span class=\"type\">"); + if (n && (n->isQmlBasicType() || n->isJsBasicType())) { + if (relative && (relative->isQmlType() || relative->isJsType())) + addLink(linkForNode(n,relative), arg, &html); + else + html += arg; + } + else + addLink(linkForNode(n,relative), arg, &html); + html += QLatin1String("</span>"); + } + else if (parseArg(src, headerTag, &i, srcSize, &arg, &par1)) { + par1 = QStringRef(); + if (arg.at(0) == QChar('&')) + html += arg; + else { + const Node* n = qdb_->findNodeForInclude(QStringList(arg.toString())); + if (n && n != relative) + addLink(linkForNode(n,relative), arg, &html); + else + html += arg; + } + } + else { + html += charLangle; + html += charAt; + } + } + else { + html += src.at(i++); + } + } + + // replace all + // "<@comment>" -> "<span class=\"comment\">"; + // "<@preprocessor>" -> "<span class=\"preprocessor\">"; + // "<@string>" -> "<span class=\"string\">"; + // "<@char>" -> "<span class=\"char\">"; + // "<@number>" -> "<span class=\"number\">"; + // "<@op>" -> "<span class=\"operator\">"; + // "<@type>" -> "<span class=\"type\">"; + // "<@name>" -> "<span class=\"name\">"; + // "<@keyword>" -> "<span class=\"keyword\">"; + // "</@(?:comment|preprocessor|string|char|number|op|type|name|keyword)>" -> "</span>" + src = html; + html = QString(); + html.reserve(src.size()); + static const QLatin1String spanTags[] = { + QLatin1String("comment>"), QLatin1String("<span class=\"comment\">"), + QLatin1String("preprocessor>"), QLatin1String("<span class=\"preprocessor\">"), + QLatin1String("string>"), QLatin1String("<span class=\"string\">"), + QLatin1String("char>"), QLatin1String("<span class=\"char\">"), + QLatin1String("number>"), QLatin1String("<span class=\"number\">"), + QLatin1String("op>"), QLatin1String("<span class=\"operator\">"), + QLatin1String("type>"), QLatin1String("<span class=\"type\">"), + QLatin1String("name>"), QLatin1String("<span class=\"name\">"), + QLatin1String("keyword>"), QLatin1String("<span class=\"keyword\">") + }; + int nTags = 9; + // Update the upper bound of k in the following code to match the length + // of the above array. + for (int i = 0, n = src.size(); i < n;) { + if (src.at(i) == QLatin1Char('<')) { + if (src.at(i + 1) == QLatin1Char('@')) { + i += 2; + bool handled = false; + for (int k = 0; k != nTags; ++k) { + const QLatin1String& tag = spanTags[2 * k]; + if (i + tag.size() <= src.length() && + tag == QStringRef(&src, i, tag.size())) { + html += spanTags[2 * k + 1]; + i += tag.size(); + handled = true; + break; + } + } + if (!handled) { + // drop 'our' unknown tags (the ones still containing '@') + while (i < n && src.at(i) != QLatin1Char('>')) + ++i; + ++i; + } + continue; + } + else if (src.at(i + 1) == QLatin1Char('/') && src.at(i + 2) == QLatin1Char('@')) { + i += 3; + bool handled = false; + for (int k = 0; k != nTags; ++k) { + const QLatin1String& tag = spanTags[2 * k]; + if (i + tag.size() <= src.length() && + tag == QStringRef(&src, i, tag.size())) { + html += QLatin1String("</span>"); + i += tag.size(); + handled = true; + break; + } + } + if (!handled) { + // drop 'our' unknown tags (the ones still containing '@') + while (i < n && src.at(i) != QLatin1Char('>')) + ++i; + ++i; + } + continue; + } + } + html += src.at(i); + ++i; + } + return html; +} + +void HtmlGenerator::generateLink(const Atom* atom, CodeMarker* marker) +{ + static QRegExp camelCase("[A-Z][A-Z][a-z]|[a-z][A-Z0-9]|_"); + + if (funcLeftParen.indexIn(atom->string()) != -1 && marker->recognizeLanguage("Cpp")) { + // hack for C++: move () outside of link + int k = funcLeftParen.pos(1); + out() << protectEnc(atom->string().left(k)); + if (link_.isEmpty()) { + if (showBrokenLinks) + out() << "</i>"; + } else { + out() << "</a>"; + } + inLink_ = false; + out() << protectEnc(atom->string().mid(k)); + } else { + out() << protectEnc(atom->string()); + } +} + +QString HtmlGenerator::registerRef(const QString& ref) +{ + QString clean = Generator::cleanRef(ref); + + for (;;) { + QString& prevRef = refMap[clean.toLower()]; + if (prevRef.isEmpty()) { + prevRef = ref; + break; + } else if (prevRef == ref) { + break; + } + clean += QLatin1Char('x'); + } + return clean; +} + +QString HtmlGenerator::protectEnc(const QString &string) +{ +#ifndef QT_NO_TEXTCODEC + return protect(string, outputEncoding); +#else + return protect(string); +#endif +} + +QString HtmlGenerator::protect(const QString &string, const QString &outputEncoding) +{ +#define APPEND(x) \ + if (html.isEmpty()) { \ + html = string; \ + html.truncate(i); \ +} \ + html += (x); + + QString html; + int n = string.length(); + + for (int i = 0; i < n; ++i) { + QChar ch = string.at(i); + + if (ch == QLatin1Char('&')) { + APPEND("&"); + } else if (ch == QLatin1Char('<')) { + APPEND("<"); + } else if (ch == QLatin1Char('>')) { + APPEND(">"); + } else if (ch == QLatin1Char('"')) { + APPEND("""); + } else if ((outputEncoding == QLatin1String("ISO-8859-1") && ch.unicode() > 0x007F) + || (ch == QLatin1Char('*') && i + 1 < n && string.at(i) == QLatin1Char('/')) + || (ch == QLatin1Char('.') && i > 2 && string.at(i - 2) == QLatin1Char('.'))) { + // we escape '*/' and the last dot in 'e.g.' and 'i.e.' for the Javadoc generator + APPEND("&#x"); + html += QString::number(ch.unicode(), 16); + html += QLatin1Char(';'); + } else { + if (!html.isEmpty()) + html += ch; + } + } + + if (!html.isEmpty()) + return html; + return string; + +#undef APPEND +} + +QString HtmlGenerator::fileBase(const Node *node) const +{ + QString result; + + result = Generator::fileBase(node); + + if (!node->isAggregate()) { + switch (node->status()) { + case Node::Compat: + result += QLatin1String("-compat"); + break; + case Node::Obsolete: + result += QLatin1String("-obsolete"); + break; + default: + ; + } + } + return result; +} + +QString HtmlGenerator::fileName(const Node *node) +{ + if (node->type() == Node::Document) { + if (static_cast<const DocumentNode *>(node)->docSubtype() == Node::ExternalPage) + return node->name(); + if (static_cast<const DocumentNode *>(node)->docSubtype() == Node::Image) + return node->name(); + } + return Generator::fileName(node); +} + +QString HtmlGenerator::refForNode(const Node *node) +{ + const FunctionNode *func; + const TypedefNode *typedeffe; + QString ref; + + switch (node->type()) { + case Node::Namespace: + case Node::Class: + default: + break; + case Node::Enum: + ref = node->name() + "-enum"; + break; + case Node::Typedef: + typedeffe = static_cast<const TypedefNode *>(node); + if (typedeffe->associatedEnum()) { + return refForNode(typedeffe->associatedEnum()); + } + else { + ref = node->name() + "-typedef"; + } + break; + case Node::Function: + func = static_cast<const FunctionNode *>(node); + if (func->hasOneAssociatedProperty() && func->doc().isEmpty()) { + return refForNode(func->firstAssociatedProperty()); + } + else { + ref = func->name(); + if (func->overloadNumber() != 0) + ref += QLatin1Char('-') + QString::number(func->overloadNumber()); + } + break; + case Node::Document: + break; + case Node::QmlProperty: + if (node->isAttached()) + ref = node->name() + "-attached-prop"; + else + ref = node->name() + "-prop"; + break; + case Node::QmlPropertyGroup: + case Node::Property: + ref = node->name() + "-prop"; + break; + case Node::QmlSignal: + ref = node->name() + "-signal"; + break; + case Node::QmlSignalHandler: + ref = node->name() + "-signal-handler"; + break; + case Node::QmlMethod: + func = static_cast<const FunctionNode *>(node); + ref = func->name() + "-method"; + if (func->overloadNumber() != 0) + ref += QLatin1Char('-') + QString::number(func->overloadNumber()); + break; + case Node::Variable: + ref = node->name() + "-var"; + break; + } + return registerRef(ref); +} + +/*! + This function is called for links, i.e. for words that + are marked with the qdoc link command. For autolinks + that are not marked with the qdoc link command, the + getAutoLink() function is called + + It returns the string for a link found by using the data + in the \a atom to search the database. It also sets \a node + to point to the target node for that link. \a relative points + to the node holding the qdoc comment where the link command + was found. + */ +QString HtmlGenerator::getLink(const Atom *atom, const Node *relative, const Node** node) +{ + const QString& t = atom->string(); + if (t.at(0) == QChar('h')) { + if (t.startsWith("http:") || t.startsWith("https:")) + return t; + } + else if (t.at(0) == QChar('f')) { + if (t.startsWith("file:") || t.startsWith("ftp:")) + return t; + } + else if (t.at(0) == QChar('m')) { + if (t.startsWith("mailto:")) + return t; + } + return getAutoLink(atom, relative, node); +} + +/*! + This function is called for autolinks, i.e. for words that + are not marked with the qdoc link command that qdoc has + reason to believe should be links. For links marked with + the qdoc link command, the getLink() function is called. + + It returns the string for a link found by using the data + in the \a atom to search the database. It also sets \a node + to point to the target node for that link. \a relative points + to the node holding the qdoc comment where the link command + was found. + */ +QString HtmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const Node** node) +{ + QString ref; + + *node = qdb_->findNodeForAtom(atom, relative, ref); + if (!(*node)) { + return QString(); + } + + QString link = (*node)->url(); + if (link.isEmpty()) { + link = linkForNode(*node, relative); + if ((*node)->docSubtype() == Node::Image) + link = "images/used-in-examples/" + link; + } + if (!ref.isEmpty()) { + int hashtag = link.lastIndexOf(QChar('#')); + if (hashtag != -1) + link.truncate(hashtag); + link += QLatin1Char('#') + ref; + } + return link; +} + +/*! + Construct the link string for the \a node and return it. + The \a relative node is use to decide the link we are + generating is in the same file as the target. Note the + relative node can be 0, which pretty much guarantees + that the link and the target aren't in the same file. + */ +QString HtmlGenerator::linkForNode(const Node *node, const Node *relative) +{ + if (node == 0) + return QString(); + if (!node->url().isEmpty()) + return node->url(); + if (fileBase(node).isEmpty()) + return QString(); + if (node->access() == Node::Private) + return QString(); + + QString fn = fileName(node); + if (node && node->parent() && + (node->parent()->isQmlType() || node->parent()->isJsType()) + && node->parent()->isAbstract()) { + if (Generator::qmlTypeContext()) + fn = fileName(Generator::qmlTypeContext()); + } + QString link = fn; + + if (!node->isAggregate() || node->isQmlPropertyGroup() || node->isJsPropertyGroup()) { + QString ref = refForNode(node); + if (relative && fn == fileName(relative) && ref == refForNode(relative)) + return QString(); + + link += QLatin1Char('#'); + link += ref; + } + /* + If the output is going to subdirectories, then if the + two nodes will be output to different directories, then + the link must go up to the parent directory and then + back down into the other subdirectory. + */ + if (node && relative && (node != relative)) { + if (useOutputSubdirs() && !node->isExternalPage() && + node->outputSubdirectory() != relative->outputSubdirectory()) { + if (link.startsWith(QString(node->outputSubdirectory() + QLatin1Char('/')))) { + link.prepend(QString("../")); + } + else { + link.prepend(QString("../" + node->outputSubdirectory() + QLatin1Char('/'))); + } + } + } + return link; +} + +void HtmlGenerator::generateFullName(const Node *apparentNode, const Node *relative, const Node *actualNode) +{ + if (actualNode == 0) + actualNode = apparentNode; + out() << "<a href=\"" << linkForNode(actualNode, relative); + if (true || relative == 0 || relative->status() != actualNode->status()) { + switch (actualNode->status()) { + case Node::Obsolete: + out() << "\" class=\"obsolete"; + break; + case Node::Compat: + out() << "\" class=\"compat"; + break; + default: + ; + } + } + out() << "\">"; + out() << protectEnc(apparentNode->fullName(relative)); + out() << "</a>"; +} + +void HtmlGenerator::generateDetailedMember(const Node *node, + const Aggregate *relative, + CodeMarker *marker) +{ + const EnumNode *etn; +#ifdef GENERATE_MAC_REFS + generateMacRef(node, marker); +#endif + generateExtractionMark(node, MemberMark); + generateKeywordAnchors(node); + QString nodeRef = refForNode(node); + if (node->isEnumType() && (etn = static_cast<const EnumNode *>(node))->flagsType()) { +#ifdef GENERATE_MAC_REFS + generateMacRef(etn->flagsType(), marker); +#endif + out() << "<h3 class=\"flags\" id=\"" << nodeRef << "\">"; + out() << "<a name=\"" + nodeRef + "\"></a>"; + generateSynopsis(etn, relative, marker, CodeMarker::Detailed); + out() << "<br/>"; + generateSynopsis(etn->flagsType(), + relative, + marker, + CodeMarker::Detailed); + out() << "</h3>\n"; + } + else { + out() << "<h3 class=\"fn\" id=\"" << nodeRef << "\">"; + out() << "<a name=\"" + nodeRef + "\"></a>"; + generateSynopsis(node, relative, marker, CodeMarker::Detailed); + out() << "</h3>" << divNavTop << '\n'; + } + + generateStatus(node, marker); + generateBody(node, marker); + generateOverloadedSignal(node, marker); + generateThreadSafeness(node, marker); + generateSince(node, marker); + + if (node->isProperty()) { + const PropertyNode *property = static_cast<const PropertyNode *>(node); + Section section; + + section.members += property->getters(); + section.members += property->setters(); + section.members += property->resetters(); + + if (!section.members.isEmpty()) { + out() << "<p><b>Access functions:</b></p>\n"; + generateSectionList(section, node, marker, CodeMarker::Accessors); + } + + Section notifiers; + notifiers.members += property->notifiers(); + + if (!notifiers.members.isEmpty()) { + out() << "<p><b>Notifier signal:</b></p>\n"; + //out() << "<p>This signal is emitted when the property value is changed.</p>\n"; + generateSectionList(notifiers, node, marker, CodeMarker::Accessors); + } + } + else if (node->isFunction()) { + const FunctionNode* fn = static_cast<const FunctionNode*>(node); + if (fn->isPrivateSignal()) + generatePrivateSignalNote(node, marker); + generateAssociatedPropertyNotes(fn); + } + else if (node->isEnumType()) { + const EnumNode *etn = static_cast<const EnumNode *>(node); + if (etn->flagsType()) { + out() << "<p>The " << protectEnc(etn->flagsType()->name()) + << " type is a typedef for " + << "<a href=\"" << qflagsHref_ << "\">QFlags</a><" + << protectEnc(etn->name()) + << ">. It stores an OR combination of " + << protectEnc(etn->name()) + << " values.</p>\n"; + } + } + generateAlsoList(node, marker); + generateExtractionMark(node, EndMark); +} + +int HtmlGenerator::hOffset(const Node *node) +{ + switch (node->type()) { + case Node::Namespace: + case Node::Class: + return 2; + case Node::QmlBasicType: + case Node::QmlType: + case Node::Document: + return 1; + case Node::Enum: + case Node::Typedef: + case Node::Function: + case Node::Property: + default: + return 3; + } +} + +bool HtmlGenerator::isThreeColumnEnumValueTable(const Atom *atom) +{ + while (atom != 0 && !(atom->type() == Atom::ListRight && atom->string() == ATOM_LIST_VALUE)) { + if (atom->type() == Atom::ListItemLeft && !matchAhead(atom, Atom::ListItemRight)) + return true; + atom = atom->next(); + } + return false; +} + + +const QPair<QString,QString> HtmlGenerator::anchorForNode(const Node *node) +{ + QPair<QString,QString> anchorPair; + + anchorPair.first = Generator::fileName(node); + if (node->type() == Node::Document) { + const DocumentNode *docNode = static_cast<const DocumentNode*>(node); + anchorPair.second = docNode->title(); + } + + return anchorPair; +} + +void HtmlGenerator::generateStatus(const Node *node, CodeMarker *marker) +{ + Text text; + + switch (node->status()) { + case Node::Obsolete: + if (node->isAggregate()) + Generator::generateStatus(node, marker); + break; + case Node::Compat: + // Porting to Qt 4 no longer supported + break; + default: + Generator::generateStatus(node, marker); + } +} + +#ifdef GENERATE_MAC_REFS +/* + No longer valid. + */ +void HtmlGenerator::generateMacRef(const Node *node, CodeMarker *marker) +{ + if (!pleaseGenerateMacRef || marker == 0) + return; + + QStringList macRefs = marker->macRefsForNode(node); + foreach (const QString &macRef, macRefs) + out() << "<a name=\"" << "//apple_ref/" << macRef << "\"></a>\n"; +} +#endif + +void HtmlGenerator::beginLink(const QString &link, const Node *node, const Node *relative) +{ + link_ = link; + if (link_.isEmpty()) { + if (showBrokenLinks) + out() << "<i>"; + } + else if (node == 0 || + (relative != 0 && node->status() == relative->status())) { + out() << "<a href=\"" << link_ << "\">"; + } + else { + switch (node->status()) { + case Node::Obsolete: + out() << "<a href=\"" << link_ << "\" class=\"obsolete\">"; + break; + case Node::Compat: + out() << "<a href=\"" << link_ << "\" class=\"compat\">"; + break; + default: + out() << "<a href=\"" << link_ << "\">"; + } + } + inLink_ = true; +} + +void HtmlGenerator::endLink() +{ + if (inLink_) { + if (link_.isEmpty()) { + if (showBrokenLinks) + out() << "</i>"; + } + else { + if (inObsoleteLink) { + out() << "<sup>(obsolete)</sup>"; + } + out() << "</a>"; + } + } + inLink_ = false; + inObsoleteLink = false; +} + +/*! + Generates the summary for the \a section. Only used for + sections of QML element documentation. + */ +void HtmlGenerator::generateQmlSummary(const Section& section, + const Node *relative, + CodeMarker *marker) +{ + if (!section.members.isEmpty()) { + out() << "<ul>\n"; + NodeList::ConstIterator m; + m = section.members.constBegin(); + while (m != section.members.constEnd()) { + out() << "<li class=\"fn\">"; + generateQmlItem(*m,relative,marker,true); + if ((*m)->type() == Node::QmlPropertyGroup) { + const QmlPropertyGroupNode* qpgn = static_cast<const QmlPropertyGroupNode*>(*m); + if (!qpgn->childNodes().isEmpty()) { + NodeList::ConstIterator p = qpgn->childNodes().constBegin(); + out() << "<ul>\n"; + while (p != qpgn->childNodes().constEnd()) { + if ((*p)->type() == Node::QmlProperty) { + out() << "<li class=\"fn\">"; + generateQmlItem(*p, relative, marker, true); + out() << "</li>\n"; + } + ++p; + } + out() << "</ul>\n"; + } + } + out() << "</li>\n"; + ++m; + } + out() << "</ul>\n"; + } +} + +/*! + Outputs the html detailed documentation for a section + on a QML element reference page. + */ +void HtmlGenerator::generateDetailedQmlMember(Node *node, + const Aggregate *relative, + CodeMarker *marker) +{ + QmlPropertyNode* qpn = 0; +#ifdef GENERATE_MAC_REFS + generateMacRef(node, marker); +#endif + generateExtractionMark(node, MemberMark); + generateKeywordAnchors(node); + + QString qmlItemHeader("<div class=\"qmlproto\">\n" + "<div class=\"table\"><table class=\"qmlname\">\n" + "<tr valign=\"top\" class=\"odd\" id=\"%1\">\n" + "<td class=\"%2\"><p>\n" + "<a name=\"%3\"></a>"); + + QString qmlItemFooter("</p></td></tr>\n" + "</table></div>\n" + "</div>"); + + out() << "<div class=\"qmlitem\">"; + QString nodeRef = refForNode(node); + if (node->type() == Node::QmlPropertyGroup) { + const QmlPropertyGroupNode* qpgn = static_cast<const QmlPropertyGroupNode*>(node); + NodeList::ConstIterator p = qpgn->childNodes().constBegin(); + out() << "<div class=\"qmlproto\">"; + out() << "<div class=\"table\"><table class=\"qmlname\">"; + + QString heading = qpgn->name() + " group"; + out() << "<tr valign=\"top\" class=\"even\" id=\"" << nodeRef << "\">"; + out() << "<th class=\"centerAlign\"><p>"; + out() << "<a name=\"" + nodeRef + "\"></a>"; + out() << "<b>" << heading << "</b>"; + out() << "</p></th></tr>"; + while (p != qpgn->childNodes().constEnd()) { + if ((*p)->type() == Node::QmlProperty) { + qpn = static_cast<QmlPropertyNode*>(*p); + nodeRef = refForNode(qpn); + out() << "<tr valign=\"top\" class=\"odd\" id=\"" << nodeRef << "\">"; + out() << "<td class=\"tblQmlPropNode\"><p>"; + out() << "<a name=\"" + nodeRef + "\"></a>"; + + if (!qpn->isWritable()) + out() << "<span class=\"qmlreadonly\">read-only</span>"; + if (qpn->isDefault()) + out() << "<span class=\"qmldefault\">default</span>"; + generateQmlItem(qpn, relative, marker, false); + out() << "</p></td></tr>"; + } + ++p; + } + out() << "</table></div>"; + out() << "</div>"; + } + else if (node->type() == Node::QmlProperty) { + qpn = static_cast<QmlPropertyNode*>(node); + out() << qmlItemHeader.arg(nodeRef, "tblQmlPropNode", refForNode(qpn)); + if (!qpn->isReadOnlySet()) { + if (qpn->declarativeCppNode()) + qpn->setReadOnly(!qpn->isWritable()); + } + if (qpn->isReadOnly()) + out() << "<span class=\"qmlreadonly\">read-only</span>"; + if (qpn->isDefault()) + out() << "<span class=\"qmldefault\">default</span>"; + generateQmlItem(qpn, relative, marker, false); + out() << qmlItemFooter; + } + else if (node->type() == Node::QmlSignal || + node->type() == Node::QmlSignalHandler || + node->type() == Node::QmlMethod) { + out() << qmlItemHeader.arg(nodeRef, "tblQmlFuncNode", refForNode(node)); + generateSynopsis(node, relative, marker, CodeMarker::Detailed, false); + out() << qmlItemFooter; + } + out() << "<div class=\"qmldoc\">"; + generateStatus(node, marker); + generateBody(node, marker); + generateThreadSafeness(node, marker); + generateSince(node, marker); + generateAlsoList(node, marker); + out() << "</div>"; + out() << "</div>"; + generateExtractionMark(node, EndMark); +} + +/*! + Output the "Inherits" line for the QML element, + if there should be one. + */ +void HtmlGenerator::generateQmlInherits(QmlTypeNode* qcn, CodeMarker* marker) +{ + if (!qcn) + return; + QmlTypeNode* base = qcn->qmlBaseNode(); + while (base && base->isInternal()) { + base = base->qmlBaseNode(); + } + if (base) { + Text text; + text << Atom::ParaLeft << "Inherits "; + text << Atom(Atom::LinkNode,CodeMarker::stringForNode(base)); + text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + text << Atom(Atom::String, base->name()); + text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + text << Atom::ParaRight; + generateText(text, qcn, marker); + } +} + +/*! + Output the "[Xxx instantiates the C++ class QmlGraphicsXxx]" + line for the QML element, if there should be one. + + If there is no class node, or if the class node status + is set to Node::Internal, do nothing. + */ +void HtmlGenerator::generateQmlInstantiates(QmlTypeNode* qcn, CodeMarker* marker) +{ + ClassNode* cn = qcn->classNode(); + if (cn && (cn->status() != Node::Internal)) { + Text text; + text << Atom::ParaLeft; + text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn)); + text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + QString name = qcn->name(); + /* + Remove the "QML:" prefix, if present. + It shouldn't be present anymore. + */ + if (name.startsWith(QLatin1String("QML:"))) + name = name.mid(4); // remove the "QML:" prefix + text << Atom(Atom::String, name); + text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + text << " instantiates the C++ class "; + text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn)); + text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + text << Atom(Atom::String, cn->name()); + text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + text << Atom::ParaRight; + generateText(text, qcn, marker); + } +} + +/*! + Output the "[QmlGraphicsXxx is instantiated by QML Type Xxx]" + line for the class, if there should be one. + + If there is no QML element, or if the class node status + is set to Node::Internal, do nothing. + */ +void HtmlGenerator::generateInstantiatedBy(ClassNode* cn, CodeMarker* marker) +{ + if (cn && cn->status() != Node::Internal && cn->qmlElement() != 0) { + const QmlTypeNode* qcn = cn->qmlElement(); + Text text; + text << Atom::ParaLeft; + text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn)); + text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + text << Atom(Atom::String, cn->name()); + text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + if (qcn->isQmlType()) + text << " is instantiated by QML Type "; + else + text << " is instantiated by Javascript Type "; + text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn)); + text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + text << Atom(Atom::String, qcn->name()); + text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + text << Atom::ParaRight; + generateText(text, cn, marker); + } +} + +void HtmlGenerator::generateExtractionMark(const Node *node, ExtractionMarkType markType) +{ + if (markType != EndMark) { + out() << "<!-- $$$" + node->name(); + if (markType == MemberMark) { + if (node->type() == Node::Function) { + const FunctionNode *func = static_cast<const FunctionNode *>(node); + if (!func->hasAssociatedProperties()) { + if (func->overloadNumber() == 0) + out() << "[overload1]"; + out() << "$$$" + func->name() + func->rawParameters().remove(' '); + } + } else if (node->type() == Node::Property) { + out() << "-prop"; + const PropertyNode *prop = static_cast<const PropertyNode *>(node); + const NodeList &list = prop->functions(); + foreach (const Node *propFuncNode, list) { + if (propFuncNode->type() == Node::Function) { + const FunctionNode *func = static_cast<const FunctionNode *>(propFuncNode); + out() << "$$$" + func->name() + func->rawParameters().remove(' '); + } + } + } else if (node->type() == Node::Enum) { + const EnumNode *enumNode = static_cast<const EnumNode *>(node); + foreach (const EnumItem &item, enumNode->items()) + out() << "$$$" + item.name(); + } + } else if (markType == BriefMark) { + out() << "-brief"; + } else if (markType == DetailedDescriptionMark) { + out() << "-description"; + } + out() << " -->\n"; + } else { + out() << "<!-- @@@" + node->name() + " -->\n"; + } +} + + +/*! + This function outputs one or more manifest files in XML. + They are used by Creator. + */ +void HtmlGenerator::generateManifestFiles() +{ + generateManifestFile("examples", "example"); + generateManifestFile("demos", "demo"); + qdb_->exampleNodeMap().clear(); + manifestMetaContent.clear(); +} + +/*! + This function is called by generateManifestFiles(), once + for each manifest file to be generated. \a manifest is the + type of manifest file. + */ +void HtmlGenerator::generateManifestFile(const QString &manifest, const QString &element) +{ + ExampleNodeMap& exampleNodeMap = qdb_->exampleNodeMap(); + if (exampleNodeMap.isEmpty()) + return; + QString fileName = manifest +"-manifest.xml"; + QFile file(outputDir() + QLatin1Char('/') + fileName); + bool demos = false; + if (manifest == QLatin1String("demos")) + demos = true; + + bool proceed = false; + ExampleNodeMap::Iterator i = exampleNodeMap.begin(); + while (i != exampleNodeMap.end()) { + const ExampleNode* en = i.value(); + if (demos) { + if (en->name().startsWith("demos")) { + proceed = true; + break; + } + } + else if (!en->name().startsWith("demos")) { + proceed = true; + break; + } + ++i; + } + if (!proceed || !file.open(QFile::WriteOnly | QFile::Text)) + return; + + QXmlStreamWriter writer(&file); + writer.setAutoFormatting(true); + writer.writeStartDocument(); + writer.writeStartElement("instructionals"); + writer.writeAttribute("module", project); + writer.writeStartElement(manifest); + + QStringList usedAttributes; + i = exampleNodeMap.begin(); + while (i != exampleNodeMap.end()) { + const ExampleNode* en = i.value(); + if (demos) { + if (!en->name().startsWith("demos")) { + ++i; + continue; + } + } + else if (en->name().startsWith("demos")) { + ++i; + continue; + } + // attributes that are always written for the element + usedAttributes.clear(); + usedAttributes << "name" << "docUrl" << "projectPath"; + + writer.writeStartElement(element); + writer.writeAttribute("name", en->title()); + QString docUrl = manifestDir + fileBase(en) + ".html"; + writer.writeAttribute("docUrl", docUrl); + QStringList proFiles; + foreach (const Node* child, en->childNodes()) { + if (child->docSubtype() == Node::File) { + QString file = child->name(); + if (file.endsWith(".pro") || file.endsWith(".qmlproject")) { + proFiles << file; + } + } + } + if (!proFiles.isEmpty()) { + if (proFiles.size() == 1) { + writer.writeAttribute("projectPath", examplesPath + proFiles[0]); + } + else { + QString exampleName = en->name().split('/').last(); + bool proWithExampleNameFound = false; + for (int j = 0; j < proFiles.size(); j++) + { + if (proFiles[j].endsWith(QStringLiteral("%1/%1.pro").arg(exampleName)) + || proFiles[j].endsWith(QStringLiteral("%1/%1.qmlproject").arg(exampleName))) { + writer.writeAttribute("projectPath", examplesPath + proFiles[j]); + proWithExampleNameFound = true; + break; + } + } + if (!proWithExampleNameFound) + writer.writeAttribute("projectPath", examplesPath + proFiles[0]); + } + } + if (!en->imageFileName().isEmpty()) { + writer.writeAttribute("imageUrl", manifestDir + en->imageFileName()); + usedAttributes << "imageUrl"; + } + + QString fullName = project + QLatin1Char('/') + en->title(); + QSet<QString> tags; + for (int idx=0; idx < manifestMetaContent.size(); ++idx) { + foreach (const QString &name, manifestMetaContent[idx].names) { + bool match = false; + int wildcard = name.indexOf(QChar('*')); + switch (wildcard) { + case -1: // no wildcard, exact match + match = (fullName == name); + break; + case 0: // '*' matches all + match = true; + break; + default: // match with wildcard at the end + match = fullName.startsWith(name.left(wildcard)); + } + if (match) { + tags += manifestMetaContent[idx].tags; + foreach (const QString &attr, manifestMetaContent[idx].attributes) { + QLatin1Char div(':'); + QStringList attrList = attr.split(div); + if (attrList.count() == 1) + attrList.append(QStringLiteral("true")); + QString attrName = attrList.takeFirst(); + if (!usedAttributes.contains(attrName)) { + writer.writeAttribute(attrName, attrList.join(div)); + usedAttributes << attrName; + } + } + } + } + } + + writer.writeStartElement("description"); + Text brief = en->doc().briefText(); + if (!brief.isEmpty()) + writer.writeCDATA(brief.toString()); + else + writer.writeCDATA(QString("No description available")); + writer.writeEndElement(); // description + + // Add words from module name as tags + // QtQuickControls -> qt,quick,controls + // QtOpenGL -> qt,opengl + QRegExp re("([A-Z]+[a-z0-9]*(3D|GL)?)"); + int pos = 0; + while ((pos = re.indexIn(project, pos)) != -1) { + tags << re.cap(1).toLower(); + pos += re.matchedLength(); + } + tags += QSet<QString>::fromList(en->title().toLower().split(QLatin1Char(' '))); + + // Clean up tags, exclude invalid and common words + QSet<QString>::iterator tag_it = tags.begin(); + QSet<QString> modified; + while (tag_it != tags.end()) { + QString s = *tag_it; + if (s.at(0) == '(') + s.remove(0, 1).chop(1); + if (s.endsWith(QLatin1Char(':'))) + s.chop(1); + + if (s.length() < 2 + || s.at(0).isDigit() + || s.at(0) == '-' + || s == QLatin1String("qt") + || s == QLatin1String("the") + || s == QLatin1String("and") + || s.startsWith(QLatin1String("example")) + || s.startsWith(QLatin1String("chapter"))) + tag_it = tags.erase(tag_it); + else if (s != *tag_it) { + modified << s; + tag_it = tags.erase(tag_it); + } + else + ++tag_it; + } + tags += modified; + + if (!tags.isEmpty()) { + writer.writeStartElement("tags"); + bool wrote_one = false; + foreach (const QString &tag, tags) { + if (wrote_one) + writer.writeCharacters(","); + writer.writeCharacters(tag); + wrote_one = true; + } + writer.writeEndElement(); // tags + } + + QString ename = en->name().mid(en->name().lastIndexOf('/')+1); + QMap<int, const Node*> filesToOpen; + foreach (const Node* child, en->childNodes()) { + if (child->docSubtype() == Node::File) { + QFileInfo fileInfo(child->name()); + QString fileName = fileInfo.fileName().toLower(); + // open .qml, .cpp and .h files with a + // basename matching the example (project) name + // QMap key indicates the priority - + // the lowest value will be the top-most file + if ((fileInfo.baseName().compare(ename, Qt::CaseInsensitive) == 0)) { + if (fileName.endsWith(".qml")) + filesToOpen.insert(0, child); + else if (fileName.endsWith(".cpp")) + filesToOpen.insert(1, child); + else if (fileName.endsWith(".h")) + filesToOpen.insert(2, child); + } + // main.qml takes precedence over main.cpp + else if (fileName.endsWith("main.qml")) { + filesToOpen.insert(3, child); + } + else if (fileName.endsWith("main.cpp")) { + filesToOpen.insert(4, child); + } + } + } + + QMap<int, const Node*>::const_iterator it = filesToOpen.constEnd(); + while (it != filesToOpen.constBegin()) { + writer.writeStartElement("fileToOpen"); + if (--it == filesToOpen.constBegin()) { + writer.writeAttribute(QStringLiteral("mainFile"), QStringLiteral("true")); + } + writer.writeCharacters(examplesPath + it.value()->name()); + writer.writeEndElement(); + } + + writer.writeEndElement(); // example + ++i; + } + + writer.writeEndElement(); // examples + writer.writeEndElement(); // instructionals + writer.writeEndDocument(); + file.close(); +} + +/*! + Reads metacontent - additional attributes and tags to apply + when generating manifest files, read from config. Takes the + configuration class \a config as a parameter. + + The manifest metacontent map is cleared immediately after + the manifest files have been generated. + */ +void HtmlGenerator::readManifestMetaContent(const Config &config) +{ + QStringList names = config.getStringList(CONFIG_MANIFESTMETA + Config::dot + QStringLiteral("filters")); + + foreach (const QString &manifest, names) { + ManifestMetaFilter filter; + QString prefix = CONFIG_MANIFESTMETA + Config::dot + manifest + Config::dot; + filter.names = config.getStringSet(prefix + QStringLiteral("names")); + filter.attributes = config.getStringSet(prefix + QStringLiteral("attributes")); + filter.tags = config.getStringSet(prefix + QStringLiteral("tags")); + manifestMetaContent.append(filter); + } +} + +/*! + Find global entities that have documentation but no + \e{relates} comand. Report these as errors if they + are not also marked \e {internal}. + + type: Class + type: Namespace + + subtype: Example + subtype: External page + subtype: Group + subtype: Header file + subtype: Module + subtype: Page + subtype: QML basic type + subtype: QML class + subtype: QML module + */ +void HtmlGenerator::reportOrphans(const Aggregate* parent) +{ + const NodeList& children = parent->childNodes(); + if (children.size() == 0) + return; + + bool related; + QString message; + for (int i=0; i<children.size(); ++i) { + Node* child = children[i]; + if (!child || child->isInternal() || child->doc().isEmpty()) + continue; + if (child->relates()) { + related = true; + message = child->relates()->name(); + } + else { + related = false; + message = "has documentation but no \\relates command"; + } + switch (child->type()) { + case Node::Namespace: + break; + case Node::Class: + break; + case Node::QmlType: + break; + case Node::QmlBasicType: + break; + case Node::Group: + break; + case Node::Module: + break; + case Node::QmlModule: + break; + case Node::Document: + switch (child->docSubtype()) { + case Node::Example: + break; + case Node::HeaderFile: + break; + case Node::File: + break; + case Node::Image: + break; + case Node::Page: + break; + case Node::ExternalPage: + break; + default: + break; + } + break; + case Node::Enum: + if (!related) + child->location().warning(tr("Global enum, %1, %2").arg(child->name()).arg(message)); + break; + case Node::Typedef: + if (!related) + child->location().warning(tr("Global typedef, %1, %2").arg(child->name()).arg(message)); + break; + case Node::Function: + if (!related) { + const FunctionNode* fn = static_cast<const FunctionNode*>(child); + if (fn->isMacro()) + child->location().warning(tr("Global macro, %1, %2").arg(child->name()).arg(message)); + else + child->location().warning(tr("Global function, %1(), %2").arg(child->name()).arg(message)); + } + break; + case Node::Property: + break; + case Node::Variable: + if (!related) + child->location().warning(tr("Global variable, %1, %2").arg(child->name()).arg(message)); + break; + case Node::QmlPropertyGroup: + break; + case Node::QmlProperty: + if (!related) + child->location().warning(tr("Global QML property, %1, %2").arg(child->name()).arg(message)); + break; + case Node::QmlSignal: + if (!related) + child->location().warning(tr("Global QML, signal, %1 %2").arg(child->name()).arg(message)); + break; + case Node::QmlSignalHandler: + if (!related) + child->location().warning(tr("Global QML signal handler, %1, %2").arg(child->name()).arg(message)); + break; + case Node::QmlMethod: + if (!related) + child->location().warning(tr("Global QML method, %1, %2").arg(child->name()).arg(message)); + break; + default: + break; + } + } +} + +/*! + Returns a reference to the XML stream writer currently in use. + There is one XML stream writer open for each XML file being + written, and they are kept on a stack. The one on top of the + stack is the one being written to at the moment. In the HTML + output generator, it is perhaps impossible for there to ever + be more than one writer open. + */ +QXmlStreamWriter& HtmlGenerator::xmlWriter() +{ + return *xmlWriterStack.top(); +} + +/*! + This function is only called for writing ditamaps. + + Calls beginSubPage() in the base class to open the file. + Then creates a new XML stream writer using the IO device + from opened file and pushes the XML writer onto a stackj. + Creates the file named \a fileName in the output directory. + Attaches a QTextStream to the created file, which is written + to all over the place using out(). Finally, it sets some + parameters in the XML writer and calls writeStartDocument(). + + It also ensures that a GUID map is created for the output file. + */ +void HtmlGenerator::beginDitamapPage(const Aggregate* node, const QString& fileName) +{ + Generator::beginSubPage(node,fileName); + QXmlStreamWriter* writer = new QXmlStreamWriter(out().device()); + xmlWriterStack.push(writer); + writer->setAutoFormatting(true); + writer->setAutoFormattingIndent(4); + writer->writeStartDocument(); +} + +/*! + This function is only called for writing ditamaps. + + Calls writeEndDocument() and then pops the XML stream writer + off the stack and deletes it. Then it calls endSubPage() in + the base class to close the device. + */ +void HtmlGenerator::endDitamapPage() +{ + xmlWriter().writeEndDocument(); + delete xmlWriterStack.pop(); + Generator::endSubPage(); +} + +/*! + This function is only called for writing ditamaps. + + Creates the DITA map from the topicrefs in \a node, + which is a DitaMapNode. + */ +void HtmlGenerator::writeDitaMap(const DitaMapNode* node) +{ + beginDitamapPage(node,node->name()); + + QString doctype = "<!DOCTYPE map PUBLIC \"-//OASIS//DTD DITA Map//EN\" \"map.dtd\">"; + + xmlWriter().writeDTD(doctype); + xmlWriter().writeStartElement("map"); + xmlWriter().writeStartElement("topicmeta"); + xmlWriter().writeStartElement("shortdesc"); + xmlWriter().writeCharacters(node->title()); + xmlWriter().writeEndElement(); // </shortdesc> + xmlWriter().writeEndElement(); // </topicmeta> + DitaRefList map = node->map(); + writeDitaRefs(map); + endDitamapPage(); +} + +/*! + Write the \a ditarefs to the current output file. + */ +void HtmlGenerator::writeDitaRefs(const DitaRefList& ditarefs) +{ + foreach (DitaRef* t, ditarefs) { + if (t->isMapRef()) + xmlWriter().writeStartElement("mapref"); + else + xmlWriter().writeStartElement("topicref"); + xmlWriter().writeAttribute("navtitle",t->navtitle()); + if (t->href().isEmpty()) { + const DocumentNode* dn = qdb_->findDocumentNodeByTitle(t->navtitle()); + if (dn) + xmlWriter().writeAttribute("href",fileName(dn)); + } + else + xmlWriter().writeAttribute("href",t->href()); + if (t->subrefs() && !t->subrefs()->isEmpty()) + writeDitaRefs(*(t->subrefs())); + xmlWriter().writeEndElement(); // </topicref> or </mapref> + } +} + +/*! + Generates bold Note lines that explain how function \a fn + is associated with each of its associated properties. + */ +void HtmlGenerator::generateAssociatedPropertyNotes(const FunctionNode* fn) +{ + if (fn->hasAssociatedProperties()) { + out() << "<p><b>Note:</b> "; + foreach (const PropertyNode* pn, fn->associatedProperties()) { + QString msg; + switch (pn->role(fn)) { + case PropertyNode::Getter: + msg = QStringLiteral("Getter function "); + break; + case PropertyNode::Setter: + msg = QStringLiteral("Setter function "); + break; + case PropertyNode::Resetter: + msg = QStringLiteral("Resetter function "); + break; + case PropertyNode::Notifier: + msg = QStringLiteral("Notifier signal "); + break; + default: + break; + } + QString link = linkForNode(pn, 0); + out() << msg << "for property <a href=\"" << link << "\">" << pn->name() << "</a>. "; + } + out() << "</p>"; + } +} + +QT_END_NAMESPACE diff --git a/src/qdoc/htmlgenerator.h b/src/qdoc/htmlgenerator.h new file mode 100644 index 000000000..0cf367b43 --- /dev/null +++ b/src/qdoc/htmlgenerator.h @@ -0,0 +1,292 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + htmlgenerator.h +*/ + +#ifndef HTMLGENERATOR_H +#define HTMLGENERATOR_H + +#include <qhash.h> +#include <qregexp.h> +#include <qxmlstream.h> +#include "codemarker.h" +#include "config.h" +#include "generator.h" + +QT_BEGIN_NAMESPACE + +class HelpProjectWriter; + +class HtmlGenerator : public Generator +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::HtmlGenerator) + +public: + enum SinceType { + Namespace, + Class, + MemberFunction, + NamespaceFunction, + GlobalFunction, + Macro, + Enum, + Typedef, + Property, + Variable, + QmlClass, + QmlProperty, + QmlSignal, + QmlSignalHandler, + QmlMethod, + LastSinceType + }; + +public: + HtmlGenerator(); + ~HtmlGenerator(); + + virtual void initializeGenerator(const Config& config) Q_DECL_OVERRIDE; + virtual void terminateGenerator() Q_DECL_OVERRIDE; + virtual QString format() Q_DECL_OVERRIDE; + virtual void generateDocs() Q_DECL_OVERRIDE; + void generateManifestFiles(); + + QString protectEnc(const QString &string); + static QString protect(const QString &string, const QString &encoding = "ISO-8859-1"); + static QString sinceTitle(int i) { return sinceTitles[i]; } + +protected: + virtual void generateQAPage() Q_DECL_OVERRIDE; + QString generateLinksToLinksPage(const QString& module, CodeMarker* marker); + QString generateLinksToBrokenLinksPage(CodeMarker* marker, int& count); + virtual int generateAtom(const Atom *atom, + const Node *relative, + CodeMarker *marker) Q_DECL_OVERRIDE; + virtual void generateClassLikeNode(Aggregate* inner, CodeMarker* marker) Q_DECL_OVERRIDE; + virtual void generateQmlTypePage(QmlTypeNode* qcn, CodeMarker* marker) Q_DECL_OVERRIDE; + virtual void generateQmlBasicTypePage(QmlBasicTypeNode* qbtn, CodeMarker* marker) Q_DECL_OVERRIDE; + virtual void generateDocumentNode(DocumentNode* dn, CodeMarker* marker) Q_DECL_OVERRIDE; + virtual void generateCollectionNode(CollectionNode* cn, CodeMarker* marker) Q_DECL_OVERRIDE; + virtual QString fileExtension() const Q_DECL_OVERRIDE; + virtual QString refForNode(const Node *node); + virtual QString linkForNode(const Node *node, const Node *relative); + + void generateManifestFile(const QString &manifest, const QString &element); + void readManifestMetaContent(const Config &config); + void generateKeywordAnchors(const Node* node); + void generateAssociatedPropertyNotes(const FunctionNode* fn); + +private: + enum SubTitleSize { SmallSubTitle, LargeSubTitle }; + enum ExtractionMarkType { + BriefMark, + DetailedDescriptionMark, + MemberMark, + EndMark + }; + + struct ManifestMetaFilter + { + QSet<QString> names; + QSet<QString> attributes; + QSet<QString> tags; + }; + + const QPair<QString,QString> anchorForNode(const Node *node); + void generateNavigationBar(const QString &title, + const Node *node, + CodeMarker *marker, + const QString &buildversion, + bool tableItems = false); + void generateHeader(const QString& title, + const Node *node = 0, + CodeMarker *marker = 0); + void generateTitle(const QString& title, + const Text &subTitle, + SubTitleSize subTitleSize, + const Node *relative, + CodeMarker *marker); + void generateFooter(const Node *node = 0); + void generateRequisites(Aggregate *inner, + CodeMarker *marker); + void generateQmlRequisites(QmlTypeNode *qcn, + CodeMarker *marker); + void generateBrief(const Node *node, + CodeMarker *marker, + const Node *relative = 0); + void generateIncludes(const Aggregate *inner, CodeMarker *marker); + void generateTableOfContents(const Node *node, + CodeMarker *marker, + QList<Section>* sections = 0); + void generateSidebar(); + QString generateListOfAllMemberFile(const Aggregate *inner, + CodeMarker *marker); + QString generateAllQmlMembersFile(QmlTypeNode* qml_cn, CodeMarker* marker); + QString generateLowStatusMemberFile(Aggregate *inner, + CodeMarker *marker, + CodeMarker::Status status); + QString generateQmlMemberFile(QmlTypeNode* qcn, + CodeMarker *marker, + CodeMarker::Status status); + void generateClassHierarchy(const Node *relative, NodeMap &classMap); + void generateAnnotatedList(const Node* relative, CodeMarker* marker, const NodeMultiMap& nodeMap); + void generateAnnotatedList(const Node* relative, CodeMarker* marker, const NodeList& nodes); + void generateCompactList(ListType listType, + const Node *relative, + const NodeMultiMap &classMap, + bool includeAlphabet, + QString commonPrefix); + void generateFunctionIndex(const Node *relative); + void generateLegaleseList(const Node *relative, CodeMarker *marker); + void generateList(const Node* relative, CodeMarker* marker, const QString& selector); + void generateSectionList(const Section& section, + const Node *relative, + CodeMarker *marker, + CodeMarker::SynopsisStyle style); + void generateQmlSummary(const Section& section, + const Node *relative, + CodeMarker *marker); + void generateQmlItem(const Node *node, + const Node *relative, + CodeMarker *marker, + bool summary); + void generateDetailedQmlMember(Node *node, + const Aggregate *relative, + CodeMarker *marker); + void generateQmlInherits(QmlTypeNode* qcn, CodeMarker* marker) Q_DECL_OVERRIDE; + void generateQmlInstantiates(QmlTypeNode* qcn, CodeMarker* marker); + void generateInstantiatedBy(ClassNode* cn, CodeMarker* marker); + + void generateSection(const NodeList& nl, + const Node *relative, + CodeMarker *marker, + CodeMarker::SynopsisStyle style); + void generateSynopsis(const Node *node, + const Node *relative, + CodeMarker *marker, + CodeMarker::SynopsisStyle style, + bool alignNames = false, + const QString* prefix = 0); + void generateSectionInheritedList(const Section& section, const Node *relative); + QString highlightedCode(const QString& markedCode, + const Node* relative, + bool alignNames = false); + + void generateFullName(const Node *apparentNode, const Node *relative, const Node *actualNode = 0); + void generateDetailedMember(const Node *node, + const Aggregate *relative, + CodeMarker *marker); + void generateLink(const Atom *atom, CodeMarker *marker); + void generateStatus(const Node *node, CodeMarker *marker); + + QString getLink(const Atom *atom, const Node *relative, const Node** node); + QString getAutoLink(const Atom *atom, const Node *relative, const Node** node); + + QString registerRef(const QString& ref); + virtual QString fileBase(const Node *node) const Q_DECL_OVERRIDE; + QString fileName(const Node *node); + static int hOffset(const Node *node); + static bool isThreeColumnEnumValueTable(const Atom *atom); +#ifdef GENERATE_MAC_REFS + void generateMacRef(const Node *node, CodeMarker *marker); +#endif + void beginLink(const QString &link, const Node *node, const Node *relative); + void endLink(); + void generateExtractionMark(const Node *node, ExtractionMarkType markType); + void reportOrphans(const Aggregate* parent); + + void beginDitamapPage(const Aggregate* node, const QString& fileName); + void endDitamapPage(); + void writeDitaMap(const DitaMapNode* node); + void writeDitaRefs(const DitaRefList& ditarefs); + QXmlStreamWriter& xmlWriter(); + + QHash<QString, QString> refMap; + int codeIndent; + QString codePrefix; + QString codeSuffix; + HelpProjectWriter *helpProjectWriter; + bool inObsoleteLink; + QRegExp funcLeftParen; + QString style; + QString headerScripts; + QString headerStyles; + QString endHeader; + QString postHeader; + QString postPostHeader; + QString prologue; + QString footer; + QString address; + bool pleaseGenerateMacRef; + bool noNavigationBar; + QString project; + QString projectDescription; + QString projectUrl; + QString navigationLinks; + QString manifestDir; + QString examplesPath; + QStringList stylesheets; + QStringList customHeadElements; + bool obsoleteLinks; + QStack<QXmlStreamWriter*> xmlWriterStack; + static int id; + QList<ManifestMetaFilter> manifestMetaContent; + QString homepage; + QString landingpage; + QString cppclassespage; + QString qmltypespage; + QString buildversion; + QString qflagsHref_; + int tocDepth; + +public: + static bool debugging_on; + static QString divNavTop; +}; + +#define HTMLGENERATOR_ADDRESS "address" +#define HTMLGENERATOR_FOOTER "footer" +#define HTMLGENERATOR_GENERATEMACREFS "generatemacrefs" // ### document me +#define HTMLGENERATOR_POSTHEADER "postheader" +#define HTMLGENERATOR_POSTPOSTHEADER "postpostheader" +#define HTMLGENERATOR_PROLOGUE "prologue" +#define HTMLGENERATOR_NONAVIGATIONBAR "nonavigationbar" +#define HTMLGENERATOR_NOSUBDIRS "nosubdirs" +#define HTMLGENERATOR_TOCDEPTH "tocdepth" + + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/jscodemarker.cpp b/src/qdoc/jscodemarker.cpp new file mode 100644 index 000000000..669040da0 --- /dev/null +++ b/src/qdoc/jscodemarker.cpp @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + jscodemarker.cpp +*/ + +#include "jscodemarker.h" + +#include "atom.h" +#include "node.h" +#include "qmlmarkupvisitor.h" +#include "text.h" +#include "tree.h" +#include "generator.h" + +#include <private/qqmljsast_p.h> +#include <private/qqmljsengine_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> + +QT_BEGIN_NAMESPACE + +JsCodeMarker::JsCodeMarker() +{ +} + +JsCodeMarker::~JsCodeMarker() +{ +} + +/*! + Returns \c true if the \a code is recognized by the parser. + */ +bool JsCodeMarker::recognizeCode(const QString &code) +{ + QQmlJS::Engine engine; + QQmlJS::Lexer lexer(&engine); + QQmlJS::Parser parser(&engine); + + QString newCode = code; + QList<QQmlJS::AST::SourceLocation> pragmas = extractPragmas(newCode); + lexer.setCode(newCode, 1); + + return parser.parseProgram(); +} + +/*! + Returns \c true if \a ext is any of a list of file extensions + for the QML language. + */ +bool JsCodeMarker::recognizeExtension(const QString &ext) +{ + return ext == "js" || ext == "json"; +} + +/*! + Returns \c true if the \a language is recognized. We recognize JavaScript, + ECMAScript and JSON. + */ +bool JsCodeMarker::recognizeLanguage(const QString &language) +{ + return language == "JavaScript" || language == "ECMAScript" || language == "JSON"; +} + +/*! + Returns the type of atom used to represent JavaScript code in the documentation. +*/ +Atom::AtomType JsCodeMarker::atomType() const +{ + return Atom::JavaScript; +} + +QString JsCodeMarker::markedUpCode(const QString &code, + const Node *relative, + const Location &location) +{ + return addMarkUp(code, relative, location); +} + +QString JsCodeMarker::addMarkUp(const QString &code, + const Node * /* relative */, + const Location &location) +{ + QQmlJS::Engine engine; + QQmlJS::Lexer lexer(&engine); + + QString newCode = code; + QList<QQmlJS::AST::SourceLocation> pragmas = extractPragmas(newCode); + lexer.setCode(newCode, 1); + + QQmlJS::Parser parser(&engine); + QString output; + + if (parser.parseProgram()) { + QQmlJS::AST::Node *ast = parser.rootNode(); + // Pass the unmodified code to the visitor so that pragmas and other + // unhandled source text can be output. + QmlMarkupVisitor visitor(code, pragmas, &engine); + QQmlJS::AST::Node::accept(ast, &visitor); + output = visitor.markedUpCode(); + } else { + location.warning(location.fileName() + + tr("Unable to parse JavaScript: \"%1\" at line %2, column %3").arg( + parser.errorMessage()).arg(parser.errorLineNumber()).arg( + parser.errorColumnNumber())); + output = protect(code); + } + return output; +} + +QT_END_NAMESPACE diff --git a/src/qdoc/jscodemarker.h b/src/qdoc/jscodemarker.h new file mode 100644 index 000000000..ccba2ca4c --- /dev/null +++ b/src/qdoc/jscodemarker.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + jscodemarker.h +*/ + +#ifndef JSCODEMARKER_H +#define JSCODEMARKER_H + +#include "qmlcodemarker.h" + +QT_BEGIN_NAMESPACE + +class JsCodeMarker : public QmlCodeMarker +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::JsCodeMarker) + +public: + JsCodeMarker(); + ~JsCodeMarker(); + + virtual bool recognizeCode(const QString &code) Q_DECL_OVERRIDE; + virtual bool recognizeExtension(const QString &ext) Q_DECL_OVERRIDE; + virtual bool recognizeLanguage(const QString &language) Q_DECL_OVERRIDE; + virtual Atom::AtomType atomType() const Q_DECL_OVERRIDE; + + virtual QString markedUpCode(const QString &code, + const Node *relative, + const Location &location) Q_DECL_OVERRIDE; + +private: + QString addMarkUp(const QString &code, const Node *relative, + const Location &location); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/location.cpp b/src/qdoc/location.cpp new file mode 100644 index 000000000..98b63fd03 --- /dev/null +++ b/src/qdoc/location.cpp @@ -0,0 +1,448 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qdebug.h> +#include "config.h" +#include "location.h" +#include "generator.h" +#include <qdir.h> +#include <qregexp.h> +#include <stdlib.h> +#include <limits.h> + +#include <stdio.h> + +QT_BEGIN_NAMESPACE + +const Location Location::null; + +int Location::tabSize; +QString Location::programName; +QRegExp *Location::spuriousRegExp = 0; +bool Location::logProgress_ = false; + +/*! + \class Location + + \brief The Location class provides a way to mark a location in a file. + + It maintains a stack of file positions. A file position + consists of the file path, line number, and column number. + The location is used for printing error messages that are + tied to a location in a file. + */ + +/*! + Constructs an empty location. + */ +Location::Location() + : stk(0), stkTop(&stkBottom), stkDepth(0), etcetera(false) +{ + // nothing. +} + +/*! + Constructs a location with (fileName, 1, 1) on its file + position stack. + */ +Location::Location(const QString& fileName) + : stk(0), stkTop(&stkBottom), stkDepth(0), etcetera(false) +{ + push(fileName); +} + +/*! + The copy constructor copies the contents of \a other into + this Location using the assignment operator. + */ +Location::Location(const Location& other) + : stk(0), stkTop(&stkBottom), stkDepth(0), etcetera(false) +{ + *this = other; +} + +/*! + The assignment operator does a deep copy of the entire + state of \a other into this Location. + */ +Location& Location::operator=(const Location& other) +{ + QStack<StackEntry> *oldStk = stk; + + stkBottom = other.stkBottom; + if (other.stk == 0) { + stk = 0; + stkTop = &stkBottom; + } + else { + stk = new QStack<StackEntry>(*other.stk); + stkTop = &stk->top(); + } + stkDepth = other.stkDepth; + etcetera = other.etcetera; + delete oldStk; + return *this; +} + +/*! + If the file position on top of the stack has a line number + less than 1, set its line number to 1 and its column number + to 1. Otherwise, do nothing. + */ +void Location::start() +{ + if (stkTop->lineNo < 1) { + stkTop->lineNo = 1; + stkTop->columnNo = 1; + } +} + +/*! + Advance the current file position, using \a ch to decide how to do + that. If \a ch is a \c{'\\n'}, increment the current line number and + set the column number to 1. If \ch is a \c{'\\t'}, increment to the + next tab column. Otherwise, increment the column number by 1. + + The current file position is the one on top of the position stack. + */ +void Location::advance(QChar ch) +{ + if (ch == QLatin1Char('\n')) { + stkTop->lineNo++; + stkTop->columnNo = 1; + } + else if (ch == QLatin1Char('\t')) { + stkTop->columnNo = + 1 + tabSize * (stkTop->columnNo + tabSize-1) / tabSize; + } + else { + stkTop->columnNo++; + } +} + +/*! + Pushes \a filePath onto the file position stack. The current + file position becomes (\a filePath, 1, 1). + + \sa pop() +*/ +void Location::push(const QString& filePath) +{ + if (stkDepth++ >= 1) { + if (stk == 0) + stk = new QStack<StackEntry>; + stk->push(StackEntry()); + stkTop = &stk->top(); + } + + stkTop->filePath = filePath; + stkTop->lineNo = INT_MIN; + stkTop->columnNo = 1; +} + +/*! + Pops the top of the internal stack. The current file position + becomes the next one in the new top of stack. + + \sa push() +*/ +void Location::pop() +{ + if (--stkDepth == 0) { + stkBottom = StackEntry(); + } + else { + stk->pop(); + if (stk->isEmpty()) { + delete stk; + stk = 0; + stkTop = &stkBottom; + } + else { + stkTop = &stk->top(); + } + } +} + +/*! \fn bool Location::isEmpty() const + + Returns \c true if there is no file name set yet; returns \c false + otherwise. The functions filePath(), lineNo() and columnNo() + must not be called on an empty Location object. + */ + +/*! \fn const QString& Location::filePath() const + Returns the current path and file name. If the Location is + empty, the returned string is null. + + \sa lineNo(), columnNo() + */ + +/*! + Returns the file name part of the file path, ie the current + file. Returns an empty string if the file path is empty. + */ +QString Location::fileName() const +{ + QString fp = filePath(); + return (fp.isEmpty() ? fp : fp.mid(fp.lastIndexOf('/') + 1)); +} + + +/*! + Returns the suffix of the file name. Returns an empty string + if the file path is empty. + */ +QString Location::fileSuffix() const +{ + QString fp = filePath(); + return (fp.isEmpty() ? fp : fp.mid(fp.lastIndexOf('.') + 1)); +} + +/*! + \brief Returns \a path which is canonicalized and relative to the config file. + + QDir::relativeFilePath does not canonicalize the paths, so + if the config file is located at qtbase\src\widgets\doc\qtwidgets.qdocconf + and it has a reference to any ancestor folder (e.g. ".." or even "../doc") + */ +QString Location::canonicalRelativePath(const QString &path) +{ + QDir configFileDir(QDir::current()); + QDir dir(path); + const QString canon = dir.canonicalPath(); + return configFileDir.relativeFilePath(canon); +} + +/*! \fn int Location::lineNo() const + Returns the current line number. + Must not be called on an empty Location object. + + \sa filePath(), columnNo() +*/ + +/*! \fn int Location::columnNo() const + Returns the current column number. + Must not be called on an empty Location object. + + \sa filePath(), lineNo() +*/ + +/*! + Writes \a message and \a detals to stderr as a formatted + warning message. Does not write the message if qdoc is in + the Prepare phase. + */ +void Location::warning(const QString& message, const QString& details) const +{ + if (!Generator::preparing() || Generator::singleExec()) + emitMessage(Warning, message, details); +} + +/*! + Writes \a message and \a detals to stderr as a formatted + error message. Does not write the message if qdoc is in + the Prepare phase. + */ +void Location::error(const QString& message, const QString& details) const +{ + if (!Generator::preparing() || Generator::singleExec()) + emitMessage(Error, message, details); +} + +/*! + Writes \a message and \a detals to stderr as a formatted + error message and then exits the program. qdoc prints fatal + errors in either phase (Prepare or Generate). + */ +void Location::fatal(const QString& message, const QString& details) const +{ + emitMessage(Error, message, details); + information(message); + information(details); + information("Aborting"); + exit(EXIT_FAILURE); +} + +/*! + Writes \a message and \a detals to stderr as a formatted + report message. + */ +void Location::report(const QString& message, const QString& details) const +{ + emitMessage(Report, message, details); +} + +/*! + Gets several parameters from the \a config, including + tab size, program name, and a regular expression that + appears to be used for matching certain error messages + so that emitMessage() can avoid printing them. + */ +void Location::initialize(const Config& config) +{ + tabSize = config.getInt(CONFIG_TABSIZE); + programName = config.programName(); + + QRegExp regExp = config.getRegExp(CONFIG_SPURIOUS); + if (regExp.isValid()) { + spuriousRegExp = new QRegExp(regExp); + } + else { + config.lastLocation().warning(tr("Invalid regular expression '%1'") + .arg(regExp.pattern())); + } +} + +/*! + Apparently, all this does is delete the regular expression + used for intercepting certain error messages that should + not be emitted by emitMessage(). + */ +void Location::terminate() +{ + delete spuriousRegExp; + spuriousRegExp = 0; +} + +/*! + Prints \a message to \c stdout followed by a \c{'\n'}. + */ +void Location::information(const QString& message) +{ + printf("%s\n", message.toLatin1().data()); + fflush(stdout); +} + +/*! + Prints \a message to \c stderr followed by a \c{'\n'}, + but only if the -log-progress option is set. + */ +void Location::logToStdErr(const QString& message) +{ + if (logProgress_) { + fprintf(stderr, "LOG: %s\n", message.toLatin1().data()); + fflush(stderr); + } +} + +/*! + Report a program bug, including the \a hint. + */ +void Location::internalError(const QString& hint) +{ + Location::null.fatal(tr("Internal error (%1)").arg(hint), + tr("There is a bug in %1. Seek advice from your local" + " %2 guru.") + .arg(programName).arg(programName)); +} + +/*! + Formats \a message and \a details into a single string + and outputs that string to \c stderr. \a type specifies + whether the \a message is an error or a warning. + */ +void Location::emitMessage(MessageType type, + const QString& message, + const QString& details) const +{ + if (type == Warning && + spuriousRegExp != 0 && + spuriousRegExp->exactMatch(message)) + return; + + QString result = message; + if (!details.isEmpty()) + result += "\n[" + details + QLatin1Char(']'); + result.replace("\n", "\n "); + if (type == Error) + result.prepend(tr(": error: ")); + else if (type == Warning) + result.prepend(tr(": warning: ")); + if (type != Report) + result.prepend(toString()); + fprintf(stderr, "%s\n", result.toLatin1().data()); + fflush(stderr); +} + +/*! + Converts the location to a string to be prepended to error + messages. + */ +QString Location::toString() const +{ + QString str; + + if (isEmpty()) { + str = programName; + } else { + Location loc2 = *this; + loc2.setEtc(false); + loc2.pop(); + if (!loc2.isEmpty()) { + QString blah = tr("In file included from "); + for (;;) { + str += blah; + str += loc2.top(); + loc2.pop(); + if (loc2.isEmpty()) + break; + str += tr(","); + str += QLatin1Char('\n'); + blah.fill(' '); + } + str += tr(":"); + str += QLatin1Char('\n'); + } + str += top(); + } + return str; +} + +QString Location::top() const +{ + QString str = filePath(); + if (!QDir::isAbsolutePath(str)) { + QDir path(str); + str = path.absolutePath(); + } + if (lineNo() >= 1) { + str += QLatin1Char(':'); + str += QString::number(lineNo()); + } + if (etc()) + str += QLatin1String(" (etc.)"); + return str; +} + +QT_END_NAMESPACE diff --git a/src/qdoc/location.h b/src/qdoc/location.h new file mode 100644 index 000000000..1cb8e3aec --- /dev/null +++ b/src/qdoc/location.h @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + location.h +*/ + +#ifndef LOCATION_H +#define LOCATION_H + +#include <qstack.h> +#include <qcoreapplication.h> + +QT_BEGIN_NAMESPACE + +class Config; +class QRegExp; + +class Location +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::Location) + +public: + Location(); + Location(const QString& filePath); + Location(const Location& other); + ~Location() { delete stk; } + + Location& operator=(const Location& other); + + void start(); + void advance(QChar ch); + void advanceLines(int n) { stkTop->lineNo += n; stkTop->columnNo = 1; } + + void push(const QString& filePath); + void pop(); + void setEtc(bool etc) { etcetera = etc; } + void setLineNo(int no) { stkTop->lineNo = no; } + void setColumnNo(int no) { stkTop->columnNo = no; } + + bool isEmpty() const { return stkDepth == 0; } + int depth() const { return stkDepth; } + const QString& filePath() const { return stkTop->filePath; } + QString fileName() const; + QString fileSuffix() const; + int lineNo() const { return stkTop->lineNo; } + int columnNo() const { return stkTop->columnNo; } + bool etc() const { return etcetera; } + void warning(const QString& message, + const QString& details = QString()) const; + void error(const QString& message, + const QString& details = QString()) const; + void fatal(const QString& message, + const QString& details = QString()) const; + void report(const QString& message, + const QString& details = QString()) const; + + static const Location null; + + static void initialize(const Config& config); + static void terminate(); + static void information(const QString& message); + static void internalError(const QString& hint); + static void logToStdErr(const QString& message); + static void startLoggingProgress() { logProgress_ = true; } + static void stopLoggingProgress() { logProgress_ = false; } + static QString canonicalRelativePath(const QString &path); + +private: + enum MessageType { Warning, Error, Report }; + + struct StackEntry + { + QString filePath; + int lineNo; + int columnNo; + }; + friend class QTypeInfo<StackEntry>; + + void emitMessage(MessageType type, + const QString& message, + const QString& details) const; + QString toString() const; + QString top() const; + +private: + StackEntry stkBottom; + QStack<StackEntry> *stk; + StackEntry *stkTop; + int stkDepth; + bool etcetera; + + static int tabSize; + static QString programName; + static QRegExp *spuriousRegExp; + static bool logProgress_; +}; +Q_DECLARE_TYPEINFO(Location::StackEntry, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(Location, Q_COMPLEX_TYPE); // stkTop = &stkBottom + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/main.cpp b/src/qdoc/main.cpp new file mode 100644 index 000000000..d6e00b483 --- /dev/null +++ b/src/qdoc/main.cpp @@ -0,0 +1,794 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qglobal.h> +#include <stdlib.h> +#include "codemarker.h" +#include "codeparser.h" +#include "config.h" +#include "cppcodemarker.h" +#include "cppcodeparser.h" +#include "doc.h" +#include "htmlgenerator.h" +#include "location.h" +#include "plaincodemarker.h" +#include "puredocparser.h" +#include "tokenizer.h" +#include "tree.h" +#include "qdocdatabase.h" +#include "jscodemarker.h" +#include "qmlcodemarker.h" +#include "qmlcodeparser.h" +#include <qdatetime.h> +#include <qdebug.h> +#include "qtranslator.h" +#ifndef QT_BOOTSTRAPPED +# include "qcoreapplication.h" +#endif +#include "qcommandlineoption.h" +#include "qcommandlineparser.h" + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +bool creationTimeBefore(const QFileInfo &fi1, const QFileInfo &fi2) +{ + return fi1.lastModified() < fi2.lastModified(); +} + +static bool highlighting = false; +static bool showInternal = false; +static bool singleExec = false; +static bool writeQaPages = false; +static bool redirectDocumentationToDevNull = false; +static bool noLinkErrors = false; +static bool autolinkErrors = false; +static bool obsoleteLinks = false; +static QStringList defines; +static QStringList dependModules; +static QStringList indexDirs; +static QString currentDir; +static QString prevCurrentDir; +static QHash<QString,QString> defaults; +#ifndef QT_NO_TRANSLATION +typedef QPair<QString, QTranslator*> Translator; +static QList<Translator> translators; +#endif + +/*! + Read some XML indexes containing definitions from other + documentation sets. \a config contains a variable that + lists directories where index files can bge found. It also + contains the \c depends variable, which lists the modules + that the current module depends on. +*/ +static void loadIndexFiles(Config& config) +{ + QDocDatabase* qdb = QDocDatabase::qdocDB(); + QStringList indexFiles; + QStringList configIndexes = config.getStringList(CONFIG_INDEXES); + foreach (const QString &index, configIndexes) { + QFileInfo fi(index); + if (fi.exists() && fi.isFile()) + indexFiles << index; + else + Location::null.warning(QString("Index file not found: %1").arg(index)); + } + + dependModules += config.getStringList(CONFIG_DEPENDS); + dependModules.removeDuplicates(); + + bool noOutputSubdirs = false; + QString singleOutputSubdir; + if (config.getBool(QString("HTML.nosubdirs"))) { + noOutputSubdirs = true; + singleOutputSubdir = config.getString("HTML.outputsubdir"); + if (singleOutputSubdir.isEmpty()) + singleOutputSubdir = "html"; + } + + if (dependModules.size() > 0) { + if (indexDirs.size() > 0) { + for (int i = 0; i < indexDirs.size(); i++) { + if (indexDirs[i].startsWith("..")) { + const QString prefix(QDir(currentDir).relativeFilePath(prevCurrentDir)); + if (!prefix.isEmpty()) + indexDirs[i].prepend(prefix + QLatin1Char('/')); + } + } + /* + Add all subdirectories of the indexdirs as dependModules, + when an asterisk is used in the 'depends' list. + */ + if (dependModules.contains("*")) { + dependModules.removeOne("*"); + for (int i = 0; i < indexDirs.size(); i++) { + QDir scanDir = QDir(indexDirs[i]); + scanDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); + QFileInfoList dirList = scanDir.entryInfoList(); + for (int j = 0; j < dirList.size(); j++) { + if (dirList[j].fileName().toLower() != config.getString(CONFIG_PROJECT).toLower()) + dependModules.append(dirList[j].fileName()); + } + } + } + for (int i = 0; i < dependModules.size(); i++) { + QString indexToAdd; + QList<QFileInfo> foundIndices; + for (int j = 0; j < indexDirs.size(); j++) { + QString fileToLookFor = indexDirs[j] + QLatin1Char('/'); + if (noOutputSubdirs) + fileToLookFor += singleOutputSubdir + QLatin1Char('/'); + else + fileToLookFor += dependModules[i] + QLatin1Char('/'); + fileToLookFor += dependModules[i] + QLatin1String(".index"); + if (QFile::exists(fileToLookFor)) { + QFileInfo tempFileInfo(fileToLookFor); + if (!foundIndices.contains(tempFileInfo)) + foundIndices.append(tempFileInfo); + } + } + std::sort(foundIndices.begin(), foundIndices.end(), creationTimeBefore); + if (foundIndices.size() > 1) { + /* + QDoc should always use the last entry in the multimap when there are + multiple index files for a module, since the last modified file has the + highest UNIX timestamp. + */ + QStringList indexPaths; + for (int k = 0; k < foundIndices.size(); k++) + indexPaths << foundIndices[k].absoluteFilePath(); + Location::null.warning(QString("Multiple index files found for dependency \"%1\":\n%2").arg( + dependModules[i], indexPaths.join('\n'))); + Location::null.warning(QString("Using %1 as index file for dependency \"%2\"").arg( + foundIndices[foundIndices.size() - 1].absoluteFilePath(), + dependModules[i])); + indexToAdd = foundIndices[foundIndices.size() - 1].absoluteFilePath(); + } + else if (foundIndices.size() == 1) { + indexToAdd = foundIndices[0].absoluteFilePath(); + } + if (!indexToAdd.isEmpty()) { + if (!indexFiles.contains(indexToAdd)) + indexFiles << indexToAdd; + } + else { + Location::null.warning(QString("\"%1\" Cannot locate index file for dependency \"%2\"").arg( + config.getString(CONFIG_PROJECT), dependModules[i])); + } + } + } + else { + Location::null.warning(QLatin1String("Dependent modules specified, but no index directories were set. There will probably be errors for missing links.")); + } + } + qdb->readIndexes(indexFiles); +} + +/*! + Processes the qdoc config file \a fileName. This is the + controller for all of qdoc. + */ +static void processQdocconfFile(const QString &fileName) +{ + /* + The Config instance represents the configuration data for qdoc. + All the other classes are initialized with the config. Below, we + initialize the configuration with some default values. + + I don't think the call to translate() does anything here. For one + thing, the translators haven't been installed at this point. And + I doubt any translator would translate QDoc anyway. But I left it + here because it does no harm. + */ + Config config(QCoreApplication::translate("QDoc", "qdoc")); + + QHash<QString,QString>::iterator iter; + for (iter = defaults.begin(); iter != defaults.end(); ++iter) + config.setStringList(iter.key(), QStringList() << iter.value()); + + config.setStringList(CONFIG_SYNTAXHIGHLIGHTING, QStringList(highlighting ? "true" : "false")); + config.setStringList(CONFIG_SHOWINTERNAL, QStringList(showInternal ? "true" : "false")); + config.setStringList(CONFIG_SINGLEEXEC, QStringList(singleExec ? "true" : "false")); + config.setStringList(CONFIG_WRITEQAPAGES, QStringList(writeQaPages ? "true" : "false")); + config.setStringList(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL, QStringList(redirectDocumentationToDevNull ? "true" : "false")); + config.setStringList(CONFIG_NOLINKERRORS, QStringList(noLinkErrors ? "true" : "false")); + config.setStringList(CONFIG_AUTOLINKERRORS, QStringList(autolinkErrors ? "true" : "false")); + config.setStringList(CONFIG_OBSOLETELINKS, QStringList(obsoleteLinks ? "true" : "false")); + + prevCurrentDir = QDir::currentPath(); + + /* + With the default configuration values in place, load + the qdoc configuration file. Note that the configuration + file may include other configuration files. + + The Location class keeps track of the current location + in the file being processed, mainly for error reporting + purposes. + */ + Location::initialize(config); + config.load(fileName); + QString project = config.getString(CONFIG_PROJECT); + //qDebug() << "Start project:" << project; + /* + Add the defines to the configuration variables. + */ + QStringList defs = defines + config.getStringList(CONFIG_DEFINES); + config.setStringList(CONFIG_DEFINES,defs); + Location::terminate(); + + currentDir = QFileInfo(fileName).path(); + if (!currentDir.isEmpty()) + QDir::setCurrent(currentDir); + + QString phase = " in -"; + if (Generator::singleExec()) + phase += "single exec mode, "; + else + phase += "separate exec mode, "; + if (Generator::preparing()) + phase += "prepare phase "; + else if (Generator::generating()) + phase += "generate phase "; + QString msg = "Running qdoc for " + config.getString(CONFIG_PROJECT) + phase; + Location::logToStdErr(msg); + + /* + Initialize all the classes and data structures with the + qdoc configuration. This is safe to do for each qdocconf + file processed, because all the data structures created + are either cleared after they have been used, or they + are cleared in the terminate() functions below. + */ + Location::initialize(config); + Tokenizer::initialize(config); + Doc::initialize(config); + CodeMarker::initialize(config); + CodeParser::initialize(config); + Generator::initialize(config); + +#ifndef QT_NO_TRANSLATION + /* + Load the language translators, if the configuration specifies any, + but only if they haven't already been loaded. This works in both + -prepare/-generate mode and -singleexec mode. + */ + QStringList fileNames = config.getStringList(CONFIG_TRANSLATORS); + QStringList::ConstIterator fn = fileNames.constBegin(); + while (fn != fileNames.constEnd()) { + bool found = false; + if (!translators.isEmpty()) { + for (int i=0; i<translators.size(); ++i) { + if (translators.at(i).first == *fn) { + found = true; + break; + } + } + } + if (!found) { + QTranslator *translator = new QTranslator(0); + if (!translator->load(*fn)) { + config.lastLocation().error(QCoreApplication::translate("QDoc", "Cannot load translator '%1'").arg(*fn)); + } + else { + QCoreApplication::instance()->installTranslator(translator); + translators.append(Translator(*fn, translator)); + } + } + ++fn; + } +#endif + + //QSet<QString> outputLanguages = config.getStringSet(CONFIG_OUTPUTLANGUAGES); + + /* + Get the source language (Cpp) from the configuration + and the location in the configuration file where the + source language was set. + */ + QString lang = config.getString(CONFIG_LANGUAGE); + Location langLocation = config.lastLocation(); + + /* + Initialize the qdoc database, where all the parsed source files + will be stored. The database includes a tree of nodes, which gets + built as the source files are parsed. The documentation output is + generated by traversing that tree. + + Note: qdocDB() allocates a new instance only if no instance exists. + So it is safe to call qdocDB() any time. + */ + QDocDatabase* qdb = QDocDatabase::qdocDB(); + qdb->setVersion(config.getString(CONFIG_VERSION)); + qdb->setShowInternal(config.getBool(CONFIG_SHOWINTERNAL)); + qdb->setSingleExec(config.getBool(CONFIG_SINGLEEXEC)); + /* + By default, the only output format is HTML. + */ + QSet<QString> outputFormats = config.getOutputFormats(); + Location outputFormatsLocation = config.lastLocation(); + + qdb->clearSearchOrder(); + if (!Generator::singleExec()) { + if (!Generator::preparing()) { + Generator::debug(" loading index files"); + loadIndexFiles(config); + Generator::debug(" done loading index files"); + } + qdb->newPrimaryTree(project); + } + else if (Generator::preparing()) + qdb->newPrimaryTree(project); + else + qdb->setPrimaryTree(project); + + dependModules = config.getStringList(CONFIG_DEPENDS); + dependModules.removeDuplicates(); + qdb->setSearchOrder(dependModules); + + QSet<QString> excludedDirs; + QSet<QString> excludedFiles; + QStringList excludedDirsList; + QStringList excludedFilesList; + + if (!Generator::singleExec() || !Generator::generating()) { + QStringList headerList; + QStringList sourceList; + + Generator::debug("Reading excludedirs"); + excludedDirsList = config.getCanonicalPathList(CONFIG_EXCLUDEDIRS); + foreach (const QString &excludeDir, excludedDirsList) { + QString p = QDir::fromNativeSeparators(excludeDir); + QDir tmp(p); + if (tmp.exists()) + excludedDirs.insert(p); + } + + Generator::debug("Reading excludefiles"); + excludedFilesList = config.getCanonicalPathList(CONFIG_EXCLUDEFILES); + foreach (const QString& excludeFile, excludedFilesList) { + QString p = QDir::fromNativeSeparators(excludeFile); + excludedFiles.insert(p); + } + + Generator::debug("Reading headerdirs"); + headerList = config.getAllFiles(CONFIG_HEADERS,CONFIG_HEADERDIRS,excludedDirs,excludedFiles); + QMap<QString,QString> headers; + QMultiMap<QString,QString> headerFileNames; + for (int i=0; i<headerList.size(); ++i) { + if (headerList[i].contains(QString("doc/snippets"))) + continue; + if (headers.contains(headerList[i])) + continue; + headers.insert(headerList[i],headerList[i]); + QString t = headerList[i].mid(headerList[i].lastIndexOf('/')+1); + headerFileNames.insert(t,t); + } + + Generator::debug("Reading sourcedirs"); + sourceList = config.getAllFiles(CONFIG_SOURCES,CONFIG_SOURCEDIRS,excludedDirs,excludedFiles); + QMap<QString,QString> sources; + QMultiMap<QString,QString> sourceFileNames; + for (int i=0; i<sourceList.size(); ++i) { + if (sourceList[i].contains(QString("doc/snippets"))) + continue; + if (sources.contains(sourceList[i])) + continue; + sources.insert(sourceList[i],sourceList[i]); + QString t = sourceList[i].mid(sourceList[i].lastIndexOf('/')+1); + sourceFileNames.insert(t,t); + } + /* + Find all the qdoc files in the example dirs, and add + them to the source files to be parsed. + */ + Generator::debug("Reading exampledirs"); + QStringList exampleQdocList = config.getExampleQdocFiles(excludedDirs, excludedFiles); + for (int i=0; i<exampleQdocList.size(); ++i) { + if (!sources.contains(exampleQdocList[i])) { + sources.insert(exampleQdocList[i],exampleQdocList[i]); + QString t = exampleQdocList[i].mid(exampleQdocList[i].lastIndexOf('/')+1); + sourceFileNames.insert(t,t); + } + } + + Generator::debug("Adding doc/image dirs found in exampledirs to imagedirs"); + QSet<QString> exampleImageDirs; + QStringList exampleImageList = config.getExampleImageFiles(excludedDirs, excludedFiles); + for (int i=0; i<exampleImageList.size(); ++i) { + if (exampleImageList[i].contains("doc/images")) { + QString t = exampleImageList[i].left(exampleImageList[i].lastIndexOf("doc/images")+10); + if (!exampleImageDirs.contains(t)) { + exampleImageDirs.insert(t); + } + } + } + Generator::augmentImageDirs(exampleImageDirs); + + /* + Parse each header file in the set using the appropriate parser and add it + to the big tree. + */ + QSet<CodeParser *> usedParsers; + + Generator::debug("Parsing header files"); + int parsed = 0; + QMap<QString,QString>::ConstIterator h = headers.constBegin(); + while (h != headers.constEnd()) { + CodeParser *codeParser = CodeParser::parserForHeaderFile(h.key()); + if (codeParser) { + ++parsed; + Generator::debug(QString("Parsing " + h.key())); + codeParser->parseHeaderFile(config.location(), h.key()); + usedParsers.insert(codeParser); + } + ++h; + } + + foreach (CodeParser *codeParser, usedParsers) + codeParser->doneParsingHeaderFiles(); + + usedParsers.clear(); + qdb->resolveInheritance(); + + /* + Parse each source text file in the set using the appropriate parser and + add it to the big tree. + */ + parsed = 0; + Generator::debug("Parsing source files"); + QMap<QString,QString>::ConstIterator s = sources.constBegin(); + while (s != sources.constEnd()) { + CodeParser *codeParser = CodeParser::parserForSourceFile(s.key()); + if (codeParser) { + ++parsed; + Generator::debug(QString("Parsing " + s.key())); + codeParser->parseSourceFile(config.location(), s.key()); + usedParsers.insert(codeParser); + } + ++s; + } + Generator::debug(QString("Parsing done.")); + + /* + Currently these doneParsingSourceFiles() calls do nothing. + */ + foreach (CodeParser *codeParser, usedParsers) + codeParser->doneParsingSourceFiles(); + + /* + Now the primary tree has been built from all the header and + source files. Resolve all the class names, function names, + targets, URLs, links, and other stuff that needs resolving. + */ + Generator::debug("Resolving stuff prior to generating docs"); + qdb->resolveIssues(); + } + else { + Generator::debug("Reading excludedirs"); + excludedDirsList = config.getCanonicalPathList(CONFIG_EXCLUDEDIRS); + foreach (const QString &excludeDir, excludedDirsList) { + QString p = QDir::fromNativeSeparators(excludeDir); + QDir tmp(p); + if (tmp.exists()) + excludedDirs.insert(p); + } + + Generator::debug("Reading excludefiles"); + excludedFilesList = config.getCanonicalPathList(CONFIG_EXCLUDEFILES); + foreach (const QString& excludeFile, excludedFilesList) { + QString p = QDir::fromNativeSeparators(excludeFile); + excludedFiles.insert(p); + } + + Generator::debug("Adding doc/image dirs found in exampledirs to imagedirs"); + QSet<QString> exampleImageDirs; + QStringList exampleImageList = config.getExampleImageFiles(excludedDirs, excludedFiles); + for (int i=0; i<exampleImageList.size(); ++i) { + if (exampleImageList[i].contains("doc/images")) { + QString t = exampleImageList[i].left(exampleImageList[i].lastIndexOf("doc/images")+10); + if (!exampleImageDirs.contains(t)) { + exampleImageDirs.insert(t); + } + } + } + Generator::augmentImageDirs(exampleImageDirs); + qdb->resolveStuff(); + } + + /* + The primary tree is built and all the stuff that needed + resolving has been resolved. Now traverse the tree and + generate the documentation output. More than one output + format can be requested. The tree is traversed for each + one. + */ + Generator::debug("Generating docs"); + QSet<QString>::ConstIterator of = outputFormats.constBegin(); + while (of != outputFormats.constEnd()) { + Generator* generator = Generator::generatorForFormat(*of); + if (generator == 0) + outputFormatsLocation.fatal(QCoreApplication::translate("QDoc", + "Unknown output format '%1'").arg(*of)); + generator->generateDocs(); + ++of; + } +#if 0 + if (Generator::generating() && Generator::writeQaPages()) + qdb->printLinkCounts(project); +#endif + qdb->clearLinkCounts(); + + Generator::debug("Terminating qdoc classes"); + if (Generator::debugging()) + Generator::stopDebugging(project); + + QDocDatabase::qdocDB()->setVersion(QString()); + Generator::terminate(); + CodeParser::terminate(); + CodeMarker::terminate(); + Doc::terminate(); + Tokenizer::terminate(); + Location::terminate(); + QDir::setCurrent(prevCurrentDir); + + Generator::debug("qdoc classes terminated"); +} + +extern Q_CORE_EXPORT QBasicAtomicInt qt_qhash_seed; +QT_END_NAMESPACE + +int main(int argc, char **argv) +{ + QT_USE_NAMESPACE + +#ifndef QT_BOOTSTRAPPED + qt_qhash_seed.testAndSetRelaxed(-1, 0); // set the hash seed to 0 if it wasn't set yet +#endif + QCoreApplication app(argc, argv); + app.setApplicationVersion(QStringLiteral(QT_VERSION_STR)); + + /* + Create code parsers for the languages to be parsed, + and create a tree for C++. + */ + CppCodeParser cppParser; + QmlCodeParser qmlParser; + PureDocParser docParser; + + /* + Create code markers for plain text, C++, + javascript, and QML. + */ + PlainCodeMarker plainMarker; + CppCodeMarker cppMarker; + JsCodeMarker jsMarker; + QmlCodeMarker qmlMarker; + + HtmlGenerator htmlGenerator; + + QCommandLineParser parser; + parser.setApplicationDescription(QCoreApplication::translate("qdoc", "Qt documentation generator")); + parser.addHelpOption(); + parser.addVersionOption(); + + parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + + parser.addPositionalArgument("file1.qdocconf ...", QCoreApplication::translate("qdoc", "Input files")); + + QCommandLineOption defineOption(QStringList() << QStringLiteral("D")); + defineOption.setDescription(QCoreApplication::translate("qdoc", "Define the argument as a macro while parsing sources")); + defineOption.setValueName(QStringLiteral("macro[=def]")); + parser.addOption(defineOption); + + QCommandLineOption dependsOption(QStringList() << QStringLiteral("depends")); + dependsOption.setDescription(QCoreApplication::translate("qdoc", "Specify dependent modules")); + dependsOption.setValueName(QStringLiteral("module")); + parser.addOption(dependsOption); + + QCommandLineOption highlightingOption(QStringList() << QStringLiteral("highlighting")); + highlightingOption.setDescription(QCoreApplication::translate("qdoc", "Turn on syntax highlighting (makes qdoc run slower)")); + parser.addOption(highlightingOption); + + QCommandLineOption showInternalOption(QStringList() << QStringLiteral("showinternal")); + showInternalOption.setDescription(QCoreApplication::translate("qdoc", "Include content marked internal")); + parser.addOption(showInternalOption); + + QCommandLineOption redirectDocumentationToDevNullOption(QStringList() << QStringLiteral("redirect-documentation-to-dev-null")); + redirectDocumentationToDevNullOption.setDescription(QCoreApplication::translate("qdoc", "Save all documentation content to /dev/null. Useful if someone is interested in qdoc errors only.")); + parser.addOption(redirectDocumentationToDevNullOption); + + QCommandLineOption noExamplesOption(QStringList() << QStringLiteral("no-examples")); + noExamplesOption.setDescription(QCoreApplication::translate("qdoc", "Do not generate documentation for examples")); + parser.addOption(noExamplesOption); + + QCommandLineOption indexDirOption(QStringList() << QStringLiteral("indexdir")); + indexDirOption.setDescription(QCoreApplication::translate("qdoc", "Specify a directory where QDoc should search for index files to load")); + indexDirOption.setValueName(QStringLiteral("dir")); + parser.addOption(indexDirOption); + + QCommandLineOption installDirOption(QStringList() << QStringLiteral("installdir")); + installDirOption.setDescription(QCoreApplication::translate("qdoc", "Specify the directory where the output will be after running \"make install\"")); + installDirOption.setValueName(QStringLiteral("dir")); + parser.addOption(installDirOption); + + QCommandLineOption obsoleteLinksOption(QStringList() << QStringLiteral("obsoletelinks")); + obsoleteLinksOption.setDescription(QCoreApplication::translate("qdoc", "Report links from obsolete items to non-obsolete items")); + parser.addOption(obsoleteLinksOption); + + QCommandLineOption outputDirOption(QStringList() << QStringLiteral("outputdir")); + outputDirOption.setDescription(QCoreApplication::translate("qdoc", "Specify output directory, overrides setting in qdocconf file")); + outputDirOption.setValueName(QStringLiteral("dir")); + parser.addOption(outputDirOption); + + QCommandLineOption outputFormatOption(QStringList() << QStringLiteral("outputformat")); + outputFormatOption.setDescription(QCoreApplication::translate("qdoc", "Specify output format, overrides setting in qdocconf file")); + outputFormatOption.setValueName(QStringLiteral("format")); + parser.addOption(outputFormatOption); + + QCommandLineOption noLinkErrorsOption(QStringList() << QStringLiteral("no-link-errors")); + noLinkErrorsOption.setDescription(QCoreApplication::translate("qdoc", "Do not print link errors (i.e. missing targets)")); + parser.addOption(noLinkErrorsOption); + + QCommandLineOption autoLinkErrorsOption(QStringList() << QStringLiteral("autolink-errors")); + autoLinkErrorsOption.setDescription(QCoreApplication::translate("qdoc", "Show errors when automatic linking fails")); + parser.addOption(autoLinkErrorsOption); + + QCommandLineOption debugOption(QStringList() << QStringLiteral("debug")); + debugOption.setDescription(QCoreApplication::translate("qdoc", "Enable debug output")); + parser.addOption(debugOption); + + QCommandLineOption prepareOption(QStringList() << QStringLiteral("prepare")); + prepareOption.setDescription(QCoreApplication::translate("qdoc", "Run qdoc only to generate an index file, not the docs")); + parser.addOption(prepareOption); + + QCommandLineOption generateOption(QStringList() << QStringLiteral("generate")); + generateOption.setDescription(QCoreApplication::translate("qdoc", "Run qdoc to read the index files and generate the docs")); + parser.addOption(generateOption); + + QCommandLineOption logProgressOption(QStringList() << QStringLiteral("log-progress")); + logProgressOption.setDescription(QCoreApplication::translate("qdoc", "Log progress on stderr.")); + parser.addOption(logProgressOption); + + QCommandLineOption singleExecOption(QStringList() << QStringLiteral("single-exec")); + singleExecOption.setDescription(QCoreApplication::translate("qdoc", "Run qdoc once over all the qdoc conf files.")); + parser.addOption(singleExecOption); + + QCommandLineOption writeQaPagesOption(QStringList() << QStringLiteral("write-qa-pages")); + writeQaPagesOption.setDescription(QCoreApplication::translate("qdoc", "Write QA pages.")); + parser.addOption(writeQaPagesOption); + + parser.process(app); + + defines += parser.values(defineOption); + dependModules += parser.values(dependsOption); + highlighting = parser.isSet(highlightingOption); + showInternal = parser.isSet(showInternalOption); + singleExec = parser.isSet(singleExecOption); + writeQaPages = parser.isSet(writeQaPagesOption); + redirectDocumentationToDevNull = parser.isSet(redirectDocumentationToDevNullOption); + Config::generateExamples = !parser.isSet(noExamplesOption); + foreach (const QString &indexDir, parser.values(indexDirOption)) { + if (QFile::exists(indexDir)) + indexDirs += indexDir; + else + qDebug() << "Cannot find index directory" << indexDir; + } + if (parser.isSet(installDirOption)) + Config::installDir = parser.value(installDirOption); + obsoleteLinks = parser.isSet(obsoleteLinksOption); + if (parser.isSet(outputDirOption)) + Config::overrideOutputDir = parser.value(outputDirOption); + foreach (const QString &format, parser.values(outputFormatOption)) + Config::overrideOutputFormats.insert(format); + noLinkErrors = parser.isSet(noLinkErrorsOption); + autolinkErrors = parser.isSet(autoLinkErrorsOption); + if (parser.isSet(debugOption)) + Generator::startDebugging(QString("command line")); + if (parser.isSet(prepareOption)) + Generator::setQDocPass(Generator::Prepare); + if (parser.isSet(generateOption)) + Generator::setQDocPass(Generator::Generate); + if (parser.isSet(singleExecOption)) + Generator::setSingleExec(); + if (parser.isSet(writeQaPagesOption)) + Generator::setWriteQaPages(); + if (parser.isSet(logProgressOption)) + Location::startLoggingProgress(); + + /* + The default indent for code is 0. + The default value for false is 0. + The default supported file extensions are cpp, h, qdoc and qml. + The default language is c++. + The default output format is html. + The default tab size is 8. + And those are all the default values for configuration variables. + */ + if (defaults.isEmpty()) { + defaults.insert(CONFIG_CODEINDENT, QLatin1String("0")); + defaults.insert(CONFIG_FALSEHOODS, QLatin1String("0")); + defaults.insert(CONFIG_FILEEXTENSIONS, QLatin1String("*.cpp *.h *.qdoc *.qml")); + defaults.insert(CONFIG_LANGUAGE, QLatin1String("Cpp")); + defaults.insert(CONFIG_OUTPUTFORMATS, QLatin1String("HTML")); + defaults.insert(CONFIG_TABSIZE, QLatin1String("8")); + } + + QStringList qdocFiles = parser.positionalArguments(); + if (qdocFiles.isEmpty()) + parser.showHelp(); + + if (singleExec) + qdocFiles = Config::loadMaster(qdocFiles.at(0)); + + /* + Main loop is now modified to handle single exec mode. + */ + if (Generator::singleExec()) + Generator::setQDocPass(Generator::Prepare); + foreach (const QString &qf, qdocFiles) { + dependModules.clear(); + processQdocconfFile(qf); + } + if (Generator::singleExec()) { + Generator::setQDocPass(Generator::Generate); + QDocDatabase* qdb = QDocDatabase::qdocDB(); + qdb->processForest(); + foreach (const QString &qf, qdocFiles) { + dependModules.clear(); + processQdocconfFile(qf); + } + } + +#ifndef QT_NO_TRANSLATION + if (!translators.isEmpty()) { + for (int i=0; i<translators.size(); ++i) { + delete translators.at(i).second; + } + } + translators.clear(); +#endif + QmlTypeNode::terminate(); + +#ifdef DEBUG_SHUTDOWN_CRASH + qDebug() << "main(): Delete qdoc database"; +#endif + QDocDatabase::destroyQdocDB(); +#ifdef DEBUG_SHUTDOWN_CRASH + qDebug() << "main(): qdoc database deleted"; +#endif + + return EXIT_SUCCESS; +} diff --git a/src/qdoc/node.cpp b/src/qdoc/node.cpp new file mode 100644 index 000000000..c1cf076f4 --- /dev/null +++ b/src/qdoc/node.cpp @@ -0,0 +1,3038 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "node.h" +#include "tree.h" +#include "codemarker.h" +#include "cppcodeparser.h" +#include <quuid.h> +#include "qdocdatabase.h" +#include <qdebug.h> +#include "generator.h" +#include "tokenizer.h" + +QT_BEGIN_NAMESPACE + +int Node::propertyGroupCount_ = 0; +QStringMap Node::operators_; +QMap<QString,Node::NodeType> Node::goals_; + +/*! + Initialize the map of search goals. This is called once + by QDocDatabase::initializeDB(). The map key is a string + representing a value in the enum Node::NodeType. The map value + is the enum value. + + There should be an entry in the map for each value in the + NodeType enum. + */ +void Node::initialize() +{ + goals_.insert("class", Node::Class); + goals_.insert("qmltype", Node::QmlType); + goals_.insert("page", Node::Document); + goals_.insert("function", Node::Function); + goals_.insert("property", Node::Property); + goals_.insert("variable", Node::Variable); + goals_.insert("group", Node::Group); + goals_.insert("module", Node::Module); + goals_.insert("qmlmodule", Node::QmlModule); + goals_.insert("qmppropertygroup", Node::QmlPropertyGroup); + goals_.insert("qmlproperty", Node::QmlProperty); + goals_.insert("qmlsignal", Node::QmlSignal); + goals_.insert("qmlsignalhandler", Node::QmlSignalHandler); + goals_.insert("qmlmethod", Node::QmlMethod); + goals_.insert("qmlbasictype", Node::QmlBasicType); + goals_.insert("enum", Node::Enum); + goals_.insert("typedef", Node::Typedef); + goals_.insert("namespace", Node::Namespace); +} + +/*! + Increment the number of property groups seen in the current + file, and return the new value. + */ +int Node::incPropertyGroupCount() { return ++propertyGroupCount_; } + +/*! + Reset the number of property groups seen in the current file + to 0, because we are starting a new file. + */ +void Node::clearPropertyGroupCount() { propertyGroupCount_ = 0; } + +/*! + \class Node + \brief The Node class is a node in the Tree. + + A Node represents a class or function or something else + from the source code.. + */ + +/*! + When this Node is destroyed, if it has a parent Node, it + removes itself from the parent node's child list. + */ +Node::~Node() +{ + if (parent_) + parent_->removeChild(this); + + if (relatesTo_) + removeRelates(); +} + +/*! + Removes this node from the aggregate's list of related + nodes, or if this node has created a dummy "relates" + aggregate, deletes it. +*/ +void Node::removeRelates() +{ + if (!relatesTo_) + return; + + if (relatesTo_->isDocumentNode() && !relatesTo_->parent()) { + delete relatesTo_; + relatesTo_ = 0; + } else { + relatesTo_->removeRelated(this); + } +} + +/*! + Returns this node's name member. Appends "()" to the returned + name, if this node is a function node. + */ +QString Node::plainName() const +{ + if (type() == Node::Function) + return name_ + QLatin1String("()"); + return name_; +} + +/*! + Constructs and returns the node's fully qualified name by + recursively ascending the parent links and prepending each + parent name + "::". Breaks out when the parent pointer is + \a relative. Almost all calls to this function pass 0 for + \a relative. + */ +QString Node::plainFullName(const Node* relative) const +{ + if (name_.isEmpty()) + return QLatin1String("global"); + + QString fullName; + const Node* node = this; + while (node) { + fullName.prepend(node->plainName()); + if (node->parent() == relative || node->parent()->name().isEmpty()) + break; + fullName.prepend(QLatin1String("::")); + node = node->parent(); + } + return fullName; +} + +/*! + Constructs and returns this node's full name. + */ +QString Node::fullName(const Node* relative) const +{ + if ((isDocumentNode() || isGroup()) && !title().isEmpty()) + return title(); + return plainFullName(relative); +} + +/*! + Try to match this node's type and subtype with one of the + pairs in \a types. If a match is found, return true. If no + match is found, return false. + + \a types is a list of type/subtype pairs, where the first + value in the pair is a Node::NodeType, and the second value is + a Node::DocSubtype. The second value is used in the match if + this node's type is Node::Document. + */ +bool Node::match(const NodeTypeList& types) const +{ + for (int i=0; i<types.size(); ++i) { + if (type() == types.at(i).first) { + if (type() == Node::Document) { + if (docSubtype() == types.at(i).second) + return true; + } + else + return true; + } + } + return false; +} + +/*! + Sets this Node's Doc to \a doc. If \a replace is false and + this Node already has a Doc, a warning is reported that the + Doc is being overridden, and it reports where the previous + Doc was found. If \a replace is true, the Doc is replaced + silently. + */ +void Node::setDoc(const Doc& doc, bool replace) +{ + if (!doc_.isEmpty() && !replace) { + doc.location().warning(tr("Overrides a previous doc")); + doc_.location().warning(tr("(The previous doc is here)")); + } + doc_ = doc; +} + +/*! + Construct a node with the given \a type and having the + given \a parent and \a name. The new node is added to the + parent's child list. + */ +Node::Node(NodeType type, Aggregate *parent, const QString& name) + : nodeType_((unsigned char) type), + access_((unsigned char) Public), + safeness_((unsigned char) UnspecifiedSafeness), + pageType_((unsigned char) NoPageType), + status_((unsigned char) Active), + indexNodeFlag_(false), + parent_(parent), + relatesTo_(0), + name_(name) +{ + if (parent_) + parent_->addChild(this); + outSubDir_ = Generator::outputSubdir(); + if (operators_.isEmpty()) { + operators_.insert("++","inc"); + operators_.insert("--","dec"); + operators_.insert("==","eq"); + operators_.insert("!=","ne"); + operators_.insert("<<","lt-lt"); + operators_.insert(">>","gt-gt"); + operators_.insert("+=","plus-assign"); + operators_.insert("-=","minus-assign"); + operators_.insert("*=","mult-assign"); + operators_.insert("/=","div-assign"); + operators_.insert("%=","mod-assign"); + operators_.insert("&=","bitwise-and-assign"); + operators_.insert("|=","bitwise-or-assign"); + operators_.insert("^=","bitwise-xor-assign"); + operators_.insert("<<=","bitwise-left-shift-assign"); + operators_.insert(">>=","bitwise-right-shift-assign"); + operators_.insert("||","logical-or"); + operators_.insert("&&","logical-and"); + operators_.insert("()","call"); + operators_.insert("[]","subscript"); + operators_.insert("->","pointer"); + operators_.insert("->*","pointer-star"); + operators_.insert("+","plus"); + operators_.insert("-","minus"); + operators_.insert("*","mult"); + operators_.insert("/","div"); + operators_.insert("%","mod"); + operators_.insert("|","bitwise-or"); + operators_.insert("&","bitwise-and"); + operators_.insert("^","bitwise-xor"); + operators_.insert("!","not"); + operators_.insert("~","bitwise-not"); + operators_.insert("<=","lt-eq"); + operators_.insert(">=","gt-eq"); + operators_.insert("<","lt"); + operators_.insert(">","gt"); + operators_.insert("=","assign"); + operators_.insert(",","comma"); + operators_.insert("delete[]","delete-array"); + operators_.insert("delete","delete"); + operators_.insert("new[]","new-array"); + operators_.insert("new","new"); + } +} + +/*! \fn QString Node::url() const + Returns the node's URL. + */ + +/*! \fn void Node::setUrl(const QString &url) + Sets the node's URL to \a url + */ + +/*! + Returns this node's page type as a string, for use as an + attribute value in XML or HTML. + */ +QString Node::pageTypeString() const +{ + return pageTypeString((PageType) pageType_); +} + +/*! + Returns the page type \a t as a string, for use as an + attribute value in XML or HTML. + */ +QString Node::pageTypeString(unsigned char t) +{ + switch ((PageType)t) { + case Node::ApiPage: + return "api"; + case Node::ArticlePage: + return "article"; + case Node::ExamplePage: + return "example"; + case Node::HowToPage: + return "howto"; + case Node::OverviewPage: + return "overview"; + case Node::TutorialPage: + return "tutorial"; + case Node::FAQPage: + return "faq"; + case Node::DitaMapPage: + return "ditamap"; + default: + return "article"; + } +} + +/*! + Returns this node's type as a string for use as an + attribute value in XML or HTML. + */ +QString Node::nodeTypeString() const +{ + return nodeTypeString(type()); +} + +/*! + Returns the node type \a t as a string for use as an + attribute value in XML or HTML. + */ +QString Node::nodeTypeString(unsigned char t) +{ + switch ((NodeType)t) { + case Namespace: + return "namespace"; + case Class: + return "class"; + case Document: + return "document"; + case Enum: + return "enum"; + case Typedef: + return "typedef"; + case Function: + return "function"; + case Property: + return "property"; + case Variable: + return "variable"; + case Group: + return "group"; + case Module: + return "module"; + case QmlType: + return "QML type"; + case QmlBasicType: + return "QML basic type"; + case QmlModule: + return "QML module"; + case QmlProperty: + return "QML property"; + case QmlPropertyGroup: + return "QML property group"; + case QmlSignal: + return "QML signal"; + case QmlSignalHandler: + return "QML signal handler"; + case QmlMethod: + return "QML method"; + default: + break; + } + return QString(); +} + +/*! + Returns this node's subtype as a string for use as an + attribute value in XML or HTML. This is only useful + in the case where the node type is Document. + */ +QString Node::nodeSubtypeString() const +{ + return nodeSubtypeString(docSubtype()); +} + +/*! + Returns the node subtype \a t as a string for use as an + attribute value in XML or HTML. This is only useful + in the case where the node type is Document. + */ +QString Node::nodeSubtypeString(unsigned char t) +{ + switch ((DocSubtype)t) { + case Example: + return "example"; + case HeaderFile: + return "header file"; + case File: + return "file"; + case Image: + return "image"; + case Page: + return "page"; + case ExternalPage: + return "external page"; + case DitaMap: + return "ditamap"; + case NoSubtype: + default: + break; + } + return QString(); +} + +/*! + Set the page type according to the string \a t. + */ +void Node::setPageType(const QString& t) +{ + if ((t == "API") || (t == "api")) + pageType_ = (unsigned char) ApiPage; + else if (t == "howto") + pageType_ = (unsigned char) HowToPage; + else if (t == "overview") + pageType_ = (unsigned char) OverviewPage; + else if (t == "tutorial") + pageType_ = (unsigned char) TutorialPage; + else if (t == "faq") + pageType_ = (unsigned char) FAQPage; + else if (t == "article") + pageType_ = (unsigned char) ArticlePage; + else if (t == "example") + pageType_ = (unsigned char) ExamplePage; + else if (t == "ditamap") + pageType_ = (unsigned char) DitaMapPage; +} + +/*! Converts the boolean value \a b to an enum representation + of the boolean type, which includes an enum value for the + \e {default value} of the item, i.e. true, false, or default. + */ +Node::FlagValue Node::toFlagValue(bool b) +{ + return b ? FlagValueTrue : FlagValueFalse; +} + +/*! + Converts the enum \a fv back to a boolean value. + If \a fv is neither the true enum value nor the + false enum value, the boolean value returned is + \a defaultValue. + + Note that runtimeDesignabilityFunction() should be called + first. If that function returns the name of a function, it + means the function must be called at runtime to determine + whether the property is Designable. + */ +bool Node::fromFlagValue(FlagValue fv, bool defaultValue) +{ + switch (fv) { + case FlagValueTrue: + return true; + case FlagValueFalse: + return false; + default: + return defaultValue; + } +} + +/*! + Sets the pointer to the node that this node relates to. + */ +void Node::setRelates(Aggregate *pseudoParent) +{ + if (pseudoParent == parent()) + return; + + removeRelates(); + relatesTo_ = pseudoParent; + pseudoParent->addRelated(this); +} + +/*! + Sets the (unresolved) entity \a name that this node relates to. + */ +void Node::setRelates(const QString& name) +{ + removeRelates(); + // Create a dummy aggregate for writing the name into the index + relatesTo_ = new DocumentNode(0, name, Node::NoSubtype, Node::NoPageType); +} + +/*! + This function creates a pair that describes a link. + The pair is composed from \a link and \a desc. The + \a linkType is the map index the pair is filed under. + */ +void Node::setLink(LinkType linkType, const QString &link, const QString &desc) +{ + QPair<QString,QString> linkPair; + linkPair.first = link; + linkPair.second = desc; + linkMap_[linkType] = linkPair; +} + +/*! + Sets the information about the project and version a node was introduced + in. The string is simplified, removing excess whitespace before being + stored. +*/ +void Node::setSince(const QString &since) +{ + since_ = since.simplified(); +} + +/*! + Returns a string representing the access specifier. + */ +QString Node::accessString() const +{ + switch ((Access) access_) { + case Protected: + return "protected"; + case Private: + return "private"; + case Public: + default: + break; + } + return "public"; +} + +/*! + Extract a class name from the type \a string and return it. + */ +QString Node::extractClassName(const QString &string) const +{ + QString result; + for (int i=0; i<=string.size(); ++i) { + QChar ch; + if (i != string.size()) + ch = string.at(i); + + QChar lower = ch.toLower(); + if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) || + ch.digitValue() >= 0 || + ch == QLatin1Char('_') || + ch == QLatin1Char(':')) { + result += ch; + } + else if (!result.isEmpty()) { + if (result != QLatin1String("const")) + return result; + result.clear(); + } + } + return result; +} + +/*! + Returns a string representing the access specifier. + */ +QString RelatedClass::accessString() const +{ + switch (access_) { + case Node::Protected: + return "protected"; + case Node::Private: + return "private"; + case Node::Public: + default: + break; + } + return "public"; +} + +/*! + Returns the inheritance status. + */ +Node::Status Node::inheritedStatus() const +{ + Status parentStatus = Active; + if (parent_) + parentStatus = parent_->inheritedStatus(); + return (Status)qMin((int)status_, (int)parentStatus); +} + +/*! + Returns the thread safeness value for whatever this node + represents. But if this node has a parent and the thread + safeness value of the parent is the same as the thread + safeness value of this node, what is returned is the + value \c{UnspecifiedSafeness}. Why? + */ +Node::ThreadSafeness Node::threadSafeness() const +{ + if (parent_ && (ThreadSafeness) safeness_ == parent_->inheritedThreadSafeness()) + return UnspecifiedSafeness; + return (ThreadSafeness) safeness_; +} + +/*! + If this node has a parent, the parent's thread safeness + value is returned. Otherwise, this node's thread safeness + value is returned. Why? + */ +Node::ThreadSafeness Node::inheritedThreadSafeness() const +{ + if (parent_ && (ThreadSafeness) safeness_ == UnspecifiedSafeness) + return parent_->inheritedThreadSafeness(); + return (ThreadSafeness) safeness_; +} + +#if 0 +/*! + Returns the sanitized file name without the path. + If the file is an html file, the html suffix + is removed. Why? + */ +QString Node::fileBase() const +{ + QString base = name(); + if (base.endsWith(".html")) + base.chop(5); + base.replace(QRegExp("[^A-Za-z0-9]+"), " "); + base = base.trimmed(); + base.replace(QLatin1Char(' '), QLatin1Char('-')); + return base.toLower(); +} +/*! + Returns this node's Universally Unique IDentifier as a + QString. Creates the UUID first, if it has not been created. + */ +QString Node::guid() const +{ + if (uuid_.isEmpty()) + uuid_ = idForNode(); + return uuid_; +} +#endif + +/*! + If this node is a QML or JS type node, return a pointer to + it. If it is a child of a QML or JS type node, return the + pointer to its parent QMLor JS type node. Otherwise return + 0; + */ +QmlTypeNode* Node::qmlTypeNode() +{ + if (isQmlNode() || isJsNode()) { + Node* n = this; + while (n && !(n->isQmlType() || n->isJsType())) + n = n->parent(); + if (n && (n->isQmlType() || n->isJsType())) + return static_cast<QmlTypeNode*>(n); + } + return 0; +} + +/*! + If this node is a QML node, find its QML class node, + and return a pointer to the C++ class node from the + QML class node. That pointer will be null if the QML + class node is a component. It will be non-null if + the QML class node is a QML element. + */ +ClassNode* Node::declarativeCppNode() +{ + QmlTypeNode* qcn = qmlTypeNode(); + if (qcn) + return qcn->classNode(); + return 0; +} + +/*! + Returns \c true if the node's status is Internal, or if its + parent is a class with internal status. + */ +bool Node::isInternal() const +{ + if (status() == Internal) + return true; + if (parent() && parent()->status() == Internal) + return true; + if (relates() && relates()->status() == Internal) + return true; + return false; +} + +/*! + Returns a pointer to the Tree this node is in. + */ +Tree* Node::tree() const +{ + return (parent() ? parent()->tree() : 0); +} + +/*! + Returns a pointer to the root of the Tree this node is in. + */ +const Node* Node::root() const +{ + return (parent() ? parent()->root() : this); +} + +/*! + Sets the node's declaration location, its definition + location, or both, depending on the suffix of the file + name from the file path in location \a t. + */ +void Node::setLocation(const Location& t) +{ + QString suffix = t.fileSuffix(); + if (suffix == "h") + declLocation_ = t; + else if (suffix == "cpp") + defLocation_ = t; + else { + declLocation_ = t; + defLocation_ = t; + } +} + +/*! + \class Aggregate + */ + +/*! + The inner node destructor deletes the children and removes + this node from its related nodes. + */ +Aggregate::~Aggregate() +{ + removeFromRelated(); + deleteChildren(); +} + +/*! + If \a genus is \c{Node::DontCare}, find the first node in + this node's child list that has the given \a name. If this + node is a QML type, be sure to also look in the children + of its property group nodes. Return the matching node or 0. + + If \a genus is either \c{Node::CPP} or \c {Node::QML}, then + find all this node's children that have the given \a name, + and return the one that satisfies the \a genus requirement. + */ +Node *Aggregate::findChildNode(const QString& name, Node::Genus genus) const +{ + if (genus == Node::DontCare) { + Node *node = childMap_.value(name); + if (node && !node->isQmlPropertyGroup()) // mws asks: Why not property group? + return node; + if (isQmlType() || isJsType()) { + for (int i=0; i<children_.size(); ++i) { + Node* n = children_.at(i); + if (n->isQmlPropertyGroup() || isJsPropertyGroup()) { + node = static_cast<Aggregate*>(n)->findChildNode(name, genus); + if (node) + return node; + } + } + } + } + else { + NodeList nodes = childMap_.values(name); + if (!nodes.isEmpty()) { + for (int i=0; i<nodes.size(); ++i) { + Node* node = nodes.at(i); + if (genus == node->genus()) + return node; + } + } + } + return primaryFunctionMap_.value(name); +} + +/*! + Find all the child nodes of this node that are named + \a name and return them in \a nodes. + */ +void Aggregate::findChildren(const QString& name, NodeList& nodes) const +{ + nodes = childMap_.values(name); + Node* n = primaryFunctionMap_.value(name); + if (n) { + nodes.append(n); + NodeList t = secondaryFunctionMap_.value(name); + if (!t.isEmpty()) + nodes.append(t); + } + if (!nodes.isEmpty() || !(isQmlNode() || isJsNode())) + return; + int i = name.indexOf(QChar('.')); + if (i < 0) + return; + QString qmlPropGroup = name.left(i); + NodeList t = childMap_.values(qmlPropGroup); + if (t.isEmpty()) + return; + foreach (Node* n, t) { + if (n->isQmlPropertyGroup() || n->isJsPropertyGroup()) { + n->findChildren(name, nodes); + if (!nodes.isEmpty()) + break; + } + } +} + +/*! + This function is like findChildNode(), but if a node + with the specified \a name is found but it is not of the + specified \a type, 0 is returned. + */ +Node* Aggregate::findChildNode(const QString& name, NodeType type) +{ + if (type == Function) + return primaryFunctionMap_.value(name); + else { + NodeList nodes = childMap_.values(name); + for (int i=0; i<nodes.size(); ++i) { + Node* node = nodes.at(i); + if (node->type() == type) + return node; + } + } + return 0; +} + +/*! + Find a function node that is a child of this nose, such + that the function node has the specified \a name. + */ +FunctionNode *Aggregate::findFunctionNode(const QString& name, const QString& params) const +{ + FunctionNode* pfn = static_cast<FunctionNode*>(primaryFunctionMap_.value(name)); + FunctionNode* fn = pfn; + if (fn) { + const QVector<Parameter>* funcParams = &(fn->parameters()); + if (params.isEmpty() && funcParams->isEmpty()) + return fn; + bool isQPrivateSignal = false; // Not used in the search + QVector<Parameter> testParams; + if (!params.isEmpty()) { + CppCodeParser* cppParser = CppCodeParser::cppParser(); + cppParser->parseParameters(params, testParams, isQPrivateSignal); + } + NodeList funcs = secondaryFunctionMap_.value(name); + int i = -1; + while (fn) { + if (testParams.size() == funcParams->size()) { + if (testParams.isEmpty()) + return fn; + bool different = false; + for (int j=0; j<testParams.size(); j++) { + if (testParams.at(j).dataType() != funcParams->at(j).dataType()) { + different = true; + break; + } + } + if (!different) + return fn; + } + if (++i < funcs.size()) { + fn = static_cast<FunctionNode*>(funcs.at(i)); + funcParams = &(fn->parameters()); + } + else + fn = 0; + } + if (!fn && !testParams.empty()) + return 0; + } + /* + Most \l commands that link to functions don't include + the parameter declarations in the function signature, + so if the \l is meant to go to a function that does + have parameters, the algorithm above won't find it. + Therefore we must return the pointer to the function + in the primary function map in the cases where the + parameters should have been specified in the \l command. + */ + return (fn ? fn : pfn); +} + +/*! + Find the function node that is a child of this node, such + that the function has the same name and signature as the + \a clone node. + */ +FunctionNode *Aggregate::findFunctionNode(const FunctionNode *clone) const +{ + QMap<QString,Node*>::ConstIterator c = primaryFunctionMap_.constFind(clone->name()); + if (c != primaryFunctionMap_.constEnd()) { + if (isSameSignature(clone, (FunctionNode *) *c)) { + return (FunctionNode *) *c; + } + else if (secondaryFunctionMap_.contains(clone->name())) { + const NodeList& secs = secondaryFunctionMap_[clone->name()]; + NodeList::ConstIterator s = secs.constBegin(); + while (s != secs.constEnd()) { + if (isSameSignature(clone, (FunctionNode *) *s)) + return (FunctionNode *) *s; + ++s; + } + } + } + return 0; +} + +/*! + Returns the list of keys from the primary function map. + */ +QStringList Aggregate::primaryKeys() +{ + QStringList t; + QMap<QString, Node*>::iterator i = primaryFunctionMap_.begin(); + while (i != primaryFunctionMap_.end()) { + t.append(i.key()); + ++i; + } + return t; +} + +/*! + Returns the list of keys from the secondary function map. + */ +QStringList Aggregate::secondaryKeys() +{ + QStringList t; + QMap<QString, NodeList>::iterator i = secondaryFunctionMap_.begin(); + while (i != secondaryFunctionMap_.end()) { + t.append(i.key()); + ++i; + } + return t; +} + +/*! + Mark all child nodes that have no documentation as having + private access and internal status. qdoc will then ignore + them for documentation purposes. Some nodes have an + Intermediate status, meaning that they should be ignored, + but not their children. + */ +void Aggregate::makeUndocumentedChildrenInternal() +{ + foreach (Node *child, childNodes()) { + if (child->doc().isEmpty() && child->status() != Node::Intermediate) { + child->setAccess(Node::Private); + child->setStatus(Node::Internal); + } + } +} + +/*! + This is where we set the overload numbers for function nodes. + \note Overload numbers for related non-members are handled + separately. + */ +void Aggregate::normalizeOverloads() +{ + QMap<QString, Node *>::Iterator p1 = primaryFunctionMap_.begin(); + while (p1 != primaryFunctionMap_.end()) { + FunctionNode *primaryFunc = (FunctionNode *) *p1; + if (primaryFunc->status() != Active || primaryFunc->access() == Private) { + if (secondaryFunctionMap_.contains(primaryFunc->name())) { + /* + Either the primary function is not active or it is private. + It therefore can't be the primary function. Search the list + of overloads to find one that can be the primary function. + */ + NodeList& overloads = secondaryFunctionMap_[primaryFunc->name()]; + NodeList::ConstIterator s = overloads.constBegin(); + while (s != overloads.constEnd()) { + FunctionNode *overloadFunc = (FunctionNode *) *s; + /* + Any non-obsolete, non-private function (i.e., visible function) + is preferable to the current primary function. Swap the primary + and overload functions. + */ + if (overloadFunc->status() == Active && overloadFunc->access() != Private) { + primaryFunc->setOverloadNumber(overloadFunc->overloadNumber()); + overloads.replace(overloads.indexOf(overloadFunc), primaryFunc); + *p1 = overloadFunc; + overloadFunc->setOverloadFlag(false); + overloadFunc->setOverloadNumber(0); + break; + } + ++s; + } + } + } + ++p1; + } + /* + Ensure that none of the primary functions is marked with \overload. + */ + QMap<QString, Node *>::Iterator p = primaryFunctionMap_.begin(); + while (p != primaryFunctionMap_.end()) { + FunctionNode *primaryFunc = (FunctionNode *) *p; + if (primaryFunc->isOverload()) { + if (secondaryFunctionMap_.contains(primaryFunc->name())) { + /* + The primary function is marked with \overload. Find an + overload in the secondary function map that is not marked + with \overload but that is active and not private. Then + swap it with the primary function. + */ + NodeList& overloads = secondaryFunctionMap_[primaryFunc->name()]; + NodeList::ConstIterator s = overloads.constBegin(); + while (s != overloads.constEnd()) { + FunctionNode *overloadFunc = (FunctionNode *) *s; + if (!overloadFunc->isOverload()) { + if (overloadFunc->status() == Active && overloadFunc->access() != Private) { + primaryFunc->setOverloadNumber(overloadFunc->overloadNumber()); + overloads.replace(overloads.indexOf(overloadFunc), primaryFunc); + *p = overloadFunc; + overloadFunc->setOverloadFlag(false); + overloadFunc->setOverloadNumber(0); + break; + } + } + ++s; + } + } + } + ++p; + } + /* + Recursive part. + */ + NodeList::ConstIterator c = childNodes().constBegin(); + while (c != childNodes().constEnd()) { + if ((*c)->isAggregate()) + ((Aggregate *) *c)->normalizeOverloads(); + ++c; + } +} + +/*! + */ +void Aggregate::removeFromRelated() +{ + while (!related_.isEmpty()) { + Node *p = static_cast<Node *>(related_.takeFirst()); + + if (p != 0 && p->relates() == this) p->clearRelated(); + } +} + +/*! + Deletes all this node's children. + */ +void Aggregate::deleteChildren() +{ + NodeList childrenCopy = children_; + // Clear internal collections before deleting child nodes + children_.clear(); + childMap_.clear(); + enumChildren_.clear(); + primaryFunctionMap_.clear(); + secondaryFunctionMap_.clear(); + qDeleteAll(childrenCopy); +} + +/*! \fn bool Aggregate::isAggregate() const + Returns \c true because this is an inner node. + */ + +/*! + Returns \c true if the node is a class node or a QML type node + that is marked as being a wrapper class or QML type, or if + it is a member of a wrapper class or type. + */ +bool Node::isWrapper() const +{ + return (parent_ ? parent_->isWrapper() : false); +} + +/*! + Finds the enum type node that has \a enumValue as one of + its enum values and returns a pointer to it. Returns 0 if + no enum type node is found that has \a enumValue as one + of its values. + */ +const EnumNode *Aggregate::findEnumNodeForValue(const QString &enumValue) const +{ + foreach (const Node *node, enumChildren_) { + const EnumNode *en = static_cast<const EnumNode *>(node); + if (en->hasItem(enumValue)) + return en; + } + return 0; +} + +/*! + Returns a node list containing all the member functions of + some class such that the functions overload the name \a funcName. + */ +NodeList Aggregate::overloads(const QString &funcName) const +{ + NodeList result; + Node *primary = primaryFunctionMap_.value(funcName); + if (primary) { + result << primary; + result += secondaryFunctionMap_[funcName]; + } + return result; +} + +/*! + Construct an inner node (i.e., not a leaf node) of the + given \a type and having the given \a parent and \a name. + */ +Aggregate::Aggregate(NodeType type, Aggregate *parent, const QString& name) + : Node(type, parent, name) +{ + switch (type) { + case Class: + case QmlType: + case Namespace: + setPageType(ApiPage); + break; + default: + break; + } +} + +/*! + Appends an \a include file to the list of include files. + */ +void Aggregate::addInclude(const QString& include) +{ + includes_.append(include); +} + +/*! + Sets the list of include files to \a includes. + */ +void Aggregate::setIncludes(const QStringList& includes) +{ + includes_ = includes; +} + +/*! + f1 is always the clone + */ +bool Aggregate::isSameSignature(const FunctionNode *f1, const FunctionNode *f2) +{ + if (f1->parameters().size() != f2->parameters().size()) + return false; + if (f1->isConst() != f2->isConst()) + return false; + + QVector<Parameter>::ConstIterator p1 = f1->parameters().constBegin(); + QVector<Parameter>::ConstIterator p2 = f2->parameters().constBegin(); + while (p2 != f2->parameters().constEnd()) { + if ((*p1).hasType() && (*p2).hasType()) { + if ((*p1).rightType() != (*p2).rightType()) + return false; + + QString t1 = p1->dataType(); + QString t2 = p2->dataType(); + + if (t1.length() < t2.length()) + qSwap(t1, t2); + + /* + ### hack for C++ to handle superfluous + "Foo::" prefixes gracefully + */ + if (t1 != t2 && t1 != (f2->parent()->name() + "::" + t2)) + return false; + } + ++p1; + ++p2; + } + return true; +} + +/*! + Adds the \a child to this node's child list. It might also + be necessary to update this node's internal collections and + the child's parent pointer and output subdirectory. + */ +void Aggregate::addChild(Node *child) +{ + children_.append(child); + if (child->type() == Function + || child->type() == QmlMethod + || child->type() == QmlSignal) { + FunctionNode *func = static_cast<FunctionNode*>(child); + QString name = func->name(); + if (!primaryFunctionMap_.contains(name)) { + primaryFunctionMap_.insert(name, func); + func->setOverloadNumber(0); + } + else { + NodeList &overloads = secondaryFunctionMap_[name]; + overloads.append(func); + func->setOverloadNumber(overloads.size()); + } + } + else { + if (child->type() == Enum) + enumChildren_.append(child); + childMap_.insertMulti(child->name(), child); + } + if (child->parent() == 0) { + child->setParent(this); + child->setOutputSubdirectory(this->outputSubdirectory()); + } +} + +/*! + Adds the \a child to this node's child map using \a title + as the key. The \a child is not added to the child list + again, because it is presumed to already be there. We just + want to be able to find the child by its \a title. + */ +void Aggregate::addChild(Node* child, const QString& title) +{ + childMap_.insertMulti(title, child); +} + +/*! + The \a child is removed from this node's child list and + from this node's internal collections. The child's parent + pointer is set to 0, but its output subdirectory is not + changed. + */ +void Aggregate::removeChild(Node *child) +{ + children_.removeAll(child); + enumChildren_.removeAll(child); + if (child->type() == Function + || child->type() == QmlMethod + || child->type() == QmlSignal) { + QMap<QString, Node *>::Iterator primary = primaryFunctionMap_.find(child->name()); + NodeList& overloads = secondaryFunctionMap_[child->name()]; + if (primary != primaryFunctionMap_.end() && *primary == child) { + primaryFunctionMap_.erase(primary); + if (!overloads.isEmpty()) { + FunctionNode* fn = static_cast<FunctionNode*>(overloads.takeFirst()); + fn->setOverloadNumber(0); + primaryFunctionMap_.insert(child->name(), fn); + } + } + else + overloads.removeAll(child); + } + QMap<QString, Node *>::Iterator ent = childMap_.find(child->name()); + while (ent != childMap_.end() && ent.key() == child->name()) { + if (*ent == child) { + childMap_.erase(ent); + break; + } + ++ent; + } + if (child->title().isEmpty()) + return; + ent = childMap_.find(child->title()); + while (ent != childMap_.end() && ent.key() == child->title()) { + if (*ent == child) { + childMap_.erase(ent); + break; + } + ++ent; + } + child->setParent(0); +} + +/*! + Recursively sets the output subdirectory for children + */ +void Aggregate::setOutputSubdirectory(const QString &t) +{ + Node::setOutputSubdirectory(t); + for (int i = 0; i < childNodes().size(); ++i) + childNodes().at(i)->setOutputSubdirectory(t); +} + +/*! + Find the module (Qt Core, Qt GUI, etc.) to which the class belongs. + We do this by obtaining the full path to the header file's location + and examine everything between "src/" and the filename. This is + semi-dirty because we are assuming a particular directory structure. + + This function is only really useful if the class's module has not + been defined in the header file with a QT_MODULE macro or with an + \inmodule command in the documentation. +*/ +QString Node::physicalModuleName() const +{ + if (!physicalModuleName_.isEmpty()) + return physicalModuleName_; + + QString path = location().filePath(); + QString pattern = QString("src") + QDir::separator(); + int start = path.lastIndexOf(pattern); + + if (start == -1) + return QString(); + + QString moduleDir = path.mid(start + pattern.size()); + int finish = moduleDir.indexOf(QDir::separator()); + + if (finish == -1) + return QString(); + + QString physicalModuleName = moduleDir.left(finish); + + if (physicalModuleName == "corelib") + return "QtCore"; + else if (physicalModuleName == "uitools") + return "QtUiTools"; + else if (physicalModuleName == "gui") + return "QtGui"; + else if (physicalModuleName == "network") + return "QtNetwork"; + else if (physicalModuleName == "opengl") + return "QtOpenGL"; + else if (physicalModuleName == "svg") + return "QtSvg"; + else if (physicalModuleName == "sql") + return "QtSql"; + else if (physicalModuleName == "qtestlib") + return "QtTest"; + else if (moduleDir.contains("webkit")) + return "QtWebKit"; + else if (physicalModuleName == "xml") + return "QtXml"; + else + return QString(); +} + +/*! + Removes a node from the list of nodes related to this one. + If it is a function node, also remove from the primary/ + secondary function maps. + */ +void Aggregate::removeRelated(Node *pseudoChild) +{ + related_.removeAll(pseudoChild); + + if (pseudoChild->isFunction()) { + QMap<QString, Node *>::Iterator p = primaryFunctionMap_.find(pseudoChild->name()); + while (p != primaryFunctionMap_.end()) { + if (p.value() == pseudoChild) { + primaryFunctionMap_.erase(p); + break; + } + ++p; + } + NodeList& overloads = secondaryFunctionMap_[pseudoChild->name()]; + overloads.removeAll(pseudoChild); + } +} + +/*! + Adds \a pseudoChild to the list of nodes related to this one. Resolve a correct + overload number for a related non-member function. + */ +void Aggregate::addRelated(Node *pseudoChild) +{ + related_.append(pseudoChild); + + if (pseudoChild->isFunction()) { + FunctionNode* fn = static_cast<FunctionNode*>(pseudoChild); + if (primaryFunctionMap_.contains(pseudoChild->name())) { + secondaryFunctionMap_[pseudoChild->name()].append(pseudoChild); + fn->setOverloadNumber(secondaryFunctionMap_[pseudoChild->name()].size()); + fn->setOverloadFlag(true); + } + else { + primaryFunctionMap_.insert(pseudoChild->name(), pseudoChild); + fn->setOverloadNumber(0); + fn->setOverloadFlag(false); + } + } +} + +/*! + If this node has a child that is a QML property named \a n, + return the pointer to that child. + */ +QmlPropertyNode* Aggregate::hasQmlProperty(const QString& n) const +{ + foreach (Node* child, childNodes()) { + if (child->type() == Node::QmlProperty) { + if (child->name() == n) + return static_cast<QmlPropertyNode*>(child); + } + else if (child->isQmlPropertyGroup()) { + QmlPropertyNode* t = child->hasQmlProperty(n); + if (t) + return t; + } + } + return 0; +} + +/*! + If this node has a child that is a QML property named \a n + whose type (attached or normal property) matches \a attached, + return the pointer to that child. + */ +QmlPropertyNode* Aggregate::hasQmlProperty(const QString& n, bool attached) const +{ + foreach (Node* child, childNodes()) { + if (child->type() == Node::QmlProperty) { + if (child->name() == n && child->isAttached() == attached) + return static_cast<QmlPropertyNode*>(child); + } + else if (child->isQmlPropertyGroup()) { + QmlPropertyNode* t = child->hasQmlProperty(n, attached); + if (t) + return t; + } + } + return 0; +} + +/*! + \class LeafNode + */ + +/*! \fn bool LeafNode::isAggregate() const + Returns \c false because this is a LeafNode. + */ + +/*! + Constructs a leaf node named \a name of the specified + \a type. The new leaf node becomes a child of \a parent. + */ +LeafNode::LeafNode(NodeType type, Aggregate *parent, const QString& name) + : Node(type, parent, name) +{ + switch (type) { + case Enum: + case Function: + case Typedef: + case Variable: + case QmlProperty: + case QmlSignal: + case QmlSignalHandler: + case QmlMethod: + case QmlBasicType: + setPageType(ApiPage); + break; + default: + break; + } +} + +/*! + This constructor should only be used when this node's parent + is meant to be \a parent, but this node is not to be listed + as a child of \a parent. It is currently only used for the + documentation case where a \e{qmlproperty} command is used + to override the QML definition of a QML property. + */ +LeafNode::LeafNode(Aggregate* parent, NodeType type, const QString& name) + : Node(type, 0, name) +{ + setParent(parent); + switch (type) { + case Enum: + case Function: + case Typedef: + case Variable: + case QmlProperty: + case QmlSignal: + case QmlSignalHandler: + case QmlMethod: + setPageType(ApiPage); + break; + default: + break; + } +} + + +/*! + \class NamespaceNode + */ + +/*! + Constructs a namespace node. + */ +NamespaceNode::NamespaceNode(Aggregate *parent, const QString& name) + : Aggregate(Namespace, parent, name), seen_(false), tree_(0) +{ + setGenus(Node::CPP); + setPageType(ApiPage); +} + +/*! + \class ClassNode + \brief This class represents a C++ class. + */ + +/*! + Constructs a class node. A class node will generate an API page. + */ +ClassNode::ClassNode(Aggregate *parent, const QString& name) + : Aggregate(Class, parent, name) +{ + abstract_ = false; + wrapper_ = false; + qmlelement = 0; + setGenus(Node::CPP); + setPageType(ApiPage); +} + +/*! + Adds the base class \a node to this class's list of base + classes. The base class has the specified \a access. This + is a resolved base class. + */ +void ClassNode::addResolvedBaseClass(Access access, ClassNode* node) +{ + bases_.append(RelatedClass(access, node)); + node->derived_.append(RelatedClass(access, this)); +} + +/*! + Adds the derived class \a node to this class's list of derived + classes. The derived class inherits this class with \a access. + */ +void ClassNode::addDerivedClass(Access access, ClassNode* node) +{ + derived_.append(RelatedClass(access, node)); +} + +/*! + Add an unresolved base class to this class node's list of + base classes. The unresolved base class will be resolved + before the generate phase of qdoc. In an unresolved base + class, the pointer to the base class node is 0. + */ +void ClassNode::addUnresolvedBaseClass(Access access, + const QStringList& path, + const QString& signature) +{ + bases_.append(RelatedClass(access, path, signature)); +} + +/*! + Add an unresolved \c using clause to this class node's list + of \c using clauses. The unresolved \c using clause will be + resolved before the generate phase of qdoc. In an unresolved + \c using clause, the pointer to the function node is 0. + */ +void ClassNode::addUnresolvedUsingClause(const QString& signature) +{ + usingClauses_.append(UsingClause(signature)); +} + +/*! + */ +void ClassNode::fixBaseClasses() +{ + int i; + i = 0; + QSet<ClassNode *> found; + + // Remove private and duplicate base classes. + while (i < bases_.size()) { + ClassNode* bc = bases_.at(i).node_; + if (bc && (bc->access() == Node::Private || found.contains(bc))) { + RelatedClass rc = bases_.at(i); + bases_.removeAt(i); + ignoredBases_.append(rc); + const QList<RelatedClass> &bb = bc->baseClasses(); + for (int j = bb.size() - 1; j >= 0; --j) + bases_.insert(i, bb.at(j)); + } + else { + ++i; + } + found.insert(bc); + } + + i = 0; + while (i < derived_.size()) { + ClassNode* dc = derived_.at(i).node_; + if (dc && dc->access() == Node::Private) { + derived_.removeAt(i); + const QList<RelatedClass> &dd = dc->derivedClasses(); + for (int j = dd.size() - 1; j >= 0; --j) + derived_.insert(i, dd.at(j)); + } + else { + ++i; + } + } +} + +/*! + Not sure why this is needed. + */ +void ClassNode::fixPropertyUsingBaseClasses(PropertyNode* pn) +{ + QList<RelatedClass>::const_iterator bc = baseClasses().constBegin(); + while (bc != baseClasses().constEnd()) { + ClassNode* cn = bc->node_; + if (cn) { + Node* n = cn->findChildNode(pn->name(), Node::Property); + if (n) { + PropertyNode* baseProperty = static_cast<PropertyNode*>(n); + cn->fixPropertyUsingBaseClasses(baseProperty); + pn->setOverriddenFrom(baseProperty); + } + else + cn->fixPropertyUsingBaseClasses(pn); + } + ++bc; + } +} + +/*! + Search the child list to find the property node with the + specified \a name. + */ +PropertyNode* ClassNode::findPropertyNode(const QString& name) +{ + Node* n = findChildNode(name, Node::Property); + + if (n) + return static_cast<PropertyNode*>(n); + + PropertyNode* pn = 0; + + const QList<RelatedClass> &bases = baseClasses(); + if (!bases.isEmpty()) { + for (int i = 0; i < bases.size(); ++i) { + ClassNode* cn = bases[i].node_; + if (cn) { + pn = cn->findPropertyNode(name); + if (pn) + break; + } + } + } + const QList<RelatedClass>& ignoredBases = ignoredBaseClasses(); + if (!ignoredBases.isEmpty()) { + for (int i = 0; i < ignoredBases.size(); ++i) { + ClassNode* cn = ignoredBases[i].node_; + if (cn) { + pn = cn->findPropertyNode(name); + if (pn) + break; + } + } + } + + return pn; +} + +/*! + This function does a recursive search of this class node's + base classes looking for one that has a QML element. If it + finds one, it returns the pointer to that QML element. If + it doesn't find one, it returns null. + */ +QmlTypeNode* ClassNode::findQmlBaseNode() +{ + QmlTypeNode* result = 0; + const QList<RelatedClass>& bases = baseClasses(); + + if (!bases.isEmpty()) { + for (int i = 0; i < bases.size(); ++i) { + ClassNode* cn = bases[i].node_; + if (cn && cn->qmlElement()) { + return cn->qmlElement(); + } + } + for (int i = 0; i < bases.size(); ++i) { + ClassNode* cn = bases[i].node_; + if (cn) { + result = cn->findQmlBaseNode(); + if (result != 0) { + return result; + } + } + } + } + return result; +} + +/*! + \class DocumentNode + */ + +/*! + The type of a DocumentNode is Document, and it has a \a subtype, + which specifies the type of DocumentNode. The page type for + the page index is set here. + */ +DocumentNode::DocumentNode(Aggregate* parent, const QString& name, DocSubtype subtype, Node::PageType ptype) + : Aggregate(Document, parent, name), nodeSubtype_(subtype) +{ + setGenus(Node::DOC); + switch (subtype) { + case Page: + setPageType(ptype); + break; + case DitaMap: + setPageType(ptype); + break; + case Example: + setPageType(ExamplePage); + break; + default: + break; + } +} + +/*! \fn QString DocumentNode::title() const + Returns the document node's title. This is used for the page title. +*/ + +/*! + Sets the document node's \a title. This is used for the page title. + */ +void DocumentNode::setTitle(const QString &title) +{ + title_ = title; + parent()->addChild(this, title); +} + +/*! + Returns the document node's full title, which is usually + just title(), but for some DocSubtype values is different + from title() + */ +QString DocumentNode::fullTitle() const +{ + if (nodeSubtype_ == File) { + if (title().isEmpty()) + return name().mid(name().lastIndexOf('/') + 1) + " Example File"; + else + return title(); + } + else if (nodeSubtype_ == Image) { + if (title().isEmpty()) + return name().mid(name().lastIndexOf('/') + 1) + " Image File"; + else + return title(); + } + else if (nodeSubtype_ == HeaderFile) { + if (title().isEmpty()) + return name(); + else + return name() + " - " + title(); + } + else { + return title(); + } +} + +/*! + Returns the subtitle. + */ +QString DocumentNode::subTitle() const +{ + if (!subtitle_.isEmpty()) + return subtitle_; + + if ((nodeSubtype_ == File) || (nodeSubtype_ == Image)) { + if (title().isEmpty() && name().contains(QLatin1Char('/'))) + return name(); + } + return QString(); +} + +/*! + \class EnumNode + */ + +/*! + The constructor for the node representing an enum type + has a \a parent class and an enum type \a name. + */ +EnumNode::EnumNode(Aggregate *parent, const QString& name) + : LeafNode(Enum, parent, name), flagsType_(0) +{ + setGenus(Node::CPP); +} + +/*! + Add \a item to the enum type's item list. + */ +void EnumNode::addItem(const EnumItem& item) +{ + items_.append(item); + names_.insert(item.name()); +} + +/*! + Returns the access level of the enumeration item named \a name. + Apparently it is private if it has been omitted by qdoc's + omitvalue command. Otherwise it is public. + */ +Node::Access EnumNode::itemAccess(const QString &name) const +{ + if (doc().omitEnumItemNames().contains(name)) + return Private; + return Public; +} + +/*! + Returns the enum value associated with the enum \a name. + */ +QString EnumNode::itemValue(const QString &name) const +{ + foreach (const EnumItem &item, items_) { + if (item.name() == name) + return item.value(); + } + return QString(); +} + +/*! + \class TypedefNode + */ + +/*! + */ +TypedefNode::TypedefNode(Aggregate *parent, const QString& name) + : LeafNode(Typedef, parent, name), associatedEnum_(0) +{ + setGenus(Node::CPP); +} + +/*! + */ +void TypedefNode::setAssociatedEnum(const EnumNode *enume) +{ + associatedEnum_ = enume; +} + +/*! + \class Parameter + \brief The class Parameter contains one parameter. + + A parameter can be a function parameter or a macro + parameter. + */ + +/*! + Constructs this parameter from the left and right types + \a dataType and rightType, the parameter \a name, and the + \a defaultValue. In practice, \a rightType is not used, + and I don't know what is was meant for. + */ +Parameter::Parameter(const QString& dataType, + const QString& rightType, + const QString& name, + const QString& defaultValue) + : dataType_(dataType), + rightType_(rightType), + name_(name), + defaultValue_(defaultValue) +{ + // nothing. +} + +/*! + Standard copy constructor copies \p. + */ +Parameter::Parameter(const Parameter& p) + : dataType_(p.dataType_), + rightType_(p.rightType_), + name_(p.name_), + defaultValue_(p.defaultValue_) +{ + // nothing. +} + +/*! + standard assignment operator assigns \p. + */ +Parameter& Parameter::operator=(const Parameter& p) +{ + dataType_ = p.dataType_; + rightType_ = p.rightType_; + name_ = p.name_; + defaultValue_ = p.defaultValue_; + return *this; +} + +/*! + Reconstructs the text describing the parameter and + returns it. If \a value is true, the default value + will be included, if there is one. + */ +QString Parameter::reconstruct(bool value) const +{ + QString p = dataType_ + rightType_; + if (!p.endsWith(QChar('*')) && !p.endsWith(QChar('&')) && !p.endsWith(QChar(' '))) + p += QLatin1Char(' '); + p += name_; + if (value && !defaultValue_.isEmpty()) + p += " = " + defaultValue_; + return p; +} + + +/*! + \class FunctionNode + */ + +/*! + Construct a function node for a C++ function. It's parent + is \a parent, and it's name is \a name. + + Do not set overloadNumber_ in the initializer list because it + is set by addChild() in the Node base class. + */ +FunctionNode::FunctionNode(Aggregate *parent, const QString& name) + : LeafNode(Function, parent, name), + metaness_(Plain), + virtualness_(NonVirtual), + const_(false), + static_(false), + reimplemented_(false), + attached_(false), + privateSignal_(false), + overload_(false), + reimplementedFrom_(0) +{ + setGenus(Node::CPP); +} + +/*! + Construct a function node for a QML method or signal, specified + by \a type. It's parent is \a parent, and it's name is \a name. + If \a attached is true, it is an attached method or signal. + + Do not set overloadNumber_ in the initializer list because it + is set by addChild() in the Node base class. + */ +FunctionNode::FunctionNode(NodeType type, Aggregate *parent, const QString& name, bool attached) + : LeafNode(type, parent, name), + metaness_(Plain), + virtualness_(NonVirtual), + const_(false), + static_(false), + reimplemented_(false), + attached_(attached), + privateSignal_(false), + overload_(false), + reimplementedFrom_(0) +{ + setGenus(Node::QML); + if (type == QmlMethod || type == QmlSignal) { + if (name.startsWith("__")) + setStatus(Internal); + } + else if (type == Function) + setGenus(Node::CPP); +} + +/*! + Sets the \a virtualness of this function. If the \a virtualness + is PureVirtual, and if the parent() is a ClassNode, set the parent's + \e abstract flag to true. + */ +void FunctionNode::setVirtualness(Virtualness v) +{ + virtualness_ = v; + if ((v == PureVirtual) && parent() && (parent()->type() == Node::Class)) + parent()->setAbstract(true); +} + +/*! \fn void FunctionNode::setOverloadFlag(bool b) + Sets this function node's overload flag to \a b. + It does not set the overload number. + */ + +/*! \fn void FunctionNode::setOverloadNumber(unsigned char n) + Sets this function node's overload number to \a n. + It does not set the overload flag. + */ + +/*! + Sets the function node's reimplementation flag to \a b. + When \a b is true, it is supposed to mean that this function + is a reimplementation of a virtual function in a base class, + but it really just means the \e {\\reimp} command was seen in + the qdoc comment. + */ +void FunctionNode::setReimplemented(bool b) +{ + reimplemented_ = b; +} + +/*! + Append \a parameter to the parameter list. + */ +void FunctionNode::addParameter(const Parameter& parameter) +{ + parameters_.append(parameter); +} + +/*! + */ +void FunctionNode::borrowParameterNames(const FunctionNode *source) +{ + QVector<Parameter>::Iterator t = parameters_.begin(); + QVector<Parameter>::ConstIterator s = source->parameters_.constBegin(); + while (s != source->parameters_.constEnd() && t != parameters_.end()) { + if (!(*s).name().isEmpty()) + (*t).setName((*s).name()); + ++s; + ++t; + } +} + +/*! + If this function is a reimplementation, \a from points + to the FunctionNode of the function being reimplemented. + */ +void FunctionNode::setReimplementedFrom(FunctionNode *f) +{ + reimplementedFrom_ = f; + f->reimplementedBy_.append(this); +} + +/*! + Adds the "associated" property \a p to this function node. + The function might be the setter or getter for a property, + for example. + */ +void FunctionNode::addAssociatedProperty(PropertyNode *p) +{ + associatedProperties_.append(p); +} + +/*! + Returns true if this function has at least one property + that is active, i.e. at least one property that is not + obsolete. + */ +bool FunctionNode::hasActiveAssociatedProperty() const +{ + if (associatedProperties_.isEmpty()) + return false; + foreach (const PropertyNode* p, associatedProperties_) { + if (!p->isObsolete()) + return true; + } + return false; +} + +/*! \fn unsigned char FunctionNode::overloadNumber() const + Returns the overload number for this function. + */ + +/*! + Returns the list of parameter names. + */ +QStringList FunctionNode::parameterNames() const +{ + QStringList names; + QVector<Parameter>::ConstIterator p = parameters().constBegin(); + while (p != parameters().constEnd()) { + names << (*p).name(); + ++p; + } + return names; +} + +/*! + Returns a raw list of parameters. If \a names is true, the + names are included. If \a values is true, the default values + are included, if any are present. + */ +QString FunctionNode::rawParameters(bool names, bool values) const +{ + QString raw; + foreach (const Parameter ¶meter, parameters()) { + raw += parameter.dataType() + parameter.rightType(); + if (names) + raw += parameter.name(); + if (values) + raw += parameter.defaultValue(); + } + return raw; +} + +/*! + Returns the list of reconstructed parameters. If \a values + is true, the default values are included, if any are present. + */ +QStringList FunctionNode::reconstructParameters(bool values) const +{ + QStringList reconstructedParameters; + QVector<Parameter>::ConstIterator p = parameters().constBegin(); + while (p != parameters().constEnd()) { + reconstructedParameters << (*p).reconstruct(values); + ++p; + } + return reconstructedParameters; +} + +/*! + Reconstructs and returns the function's signature. If \a values + is true, the default values of the parameters are included, if + present. + */ +QString FunctionNode::signature(bool values) const +{ + QString s; + if (!returnType().isEmpty()) + s = returnType() + QLatin1Char(' '); + s += name() + QLatin1Char('('); + QStringList reconstructedParameters = reconstructParameters(values); + int p = reconstructedParameters.size(); + if (p > 0) { + for (int i=0; i<p; i++) { + s += reconstructedParameters[i]; + if (i < (p-1)) + s += ", "; + } + } + s += QLatin1Char(')'); + return s; +} + +/*! + Returns true if function \a fn has role \a r for this + property. + */ +PropertyNode::FunctionRole PropertyNode::role(const FunctionNode* fn) const +{ + for (int i=0; i<4; i++) { + if (functions_[i].contains(const_cast<FunctionNode*>(fn))) + return (FunctionRole) i; + } + return Notifier; +} + +/*! + Print some debugging stuff. + */ +void FunctionNode::debug() const +{ + qDebug("QML METHOD %s returnType_ %s parentPath_ %s", + qPrintable(name()), qPrintable(returnType_), qPrintable(parentPath_.join(' '))); +} + +/*! + \class PropertyNode + + This class describes one instance of using the Q_PROPERTY macro. + */ + +/*! + The constructor sets the \a parent and the \a name, but + everything else is set to default values. + */ +PropertyNode::PropertyNode(Aggregate *parent, const QString& name) + : LeafNode(Property, parent, name), + stored_(FlagValueDefault), + designable_(FlagValueDefault), + scriptable_(FlagValueDefault), + writable_(FlagValueDefault), + user_(FlagValueDefault), + const_(false), + final_(false), + revision_(-1), + overrides_(0) +{ + setGenus(Node::CPP); +} + +/*! + Sets this property's \e {overridden from} property to + \a baseProperty, which indicates that this property + overrides \a baseProperty. To begin with, all the values + in this property are set to the corresponding values in + \a baseProperty. + + We probably should ensure that the constant and final + attributes are not being overridden improperly. + */ +void PropertyNode::setOverriddenFrom(const PropertyNode* baseProperty) +{ + for (int i = 0; i < NumFunctionRoles; ++i) { + if (functions_[i].isEmpty()) + functions_[i] = baseProperty->functions_[i]; + } + if (stored_ == FlagValueDefault) + stored_ = baseProperty->stored_; + if (designable_ == FlagValueDefault) + designable_ = baseProperty->designable_; + if (scriptable_ == FlagValueDefault) + scriptable_ = baseProperty->scriptable_; + if (writable_ == FlagValueDefault) + writable_ = baseProperty->writable_; + if (user_ == FlagValueDefault) + user_ = baseProperty->user_; + overrides_ = baseProperty; +} + +/*! + */ +QString PropertyNode::qualifiedDataType() const +{ + if (setters().isEmpty() && resetters().isEmpty()) { + if (type_.contains(QLatin1Char('*')) || type_.contains(QLatin1Char('&'))) { + // 'QWidget *' becomes 'QWidget *' const + return type_ + " const"; + } + else { + /* + 'int' becomes 'const int' ('int const' is + correct C++, but looks wrong) + */ + return "const " + type_; + } + } + else { + return type_; + } +} + +bool QmlTypeNode::qmlOnly = false; +QMultiMap<QString,Node*> QmlTypeNode::inheritedBy; + +/*! + Constructs a Qml class node. The new node has the given + \a parent and \a name. + */ +QmlTypeNode::QmlTypeNode(Aggregate *parent, const QString& name) + : Aggregate(QmlType, parent, name), + abstract_(false), + cnodeRequired_(false), + wrapper_(false), + cnode_(0), + logicalModule_(0), + qmlBaseNode_(0) +{ + int i = 0; + if (name.startsWith("QML:")) { + qDebug() << "BOGUS QML qualifier:" << name; + i = 4; + } + setTitle(name.mid(i)); + setPageType(Node::ApiPage); + setGenus(Node::QML); +} + +/*! + Needed for printing a debug messages. + */ +QmlTypeNode::~QmlTypeNode() +{ + // nothing. +} + +/*! + Clear the static maps so that subsequent runs don't try to use + contents from a previous run. + */ +void QmlTypeNode::terminate() +{ + inheritedBy.clear(); +} + +/*! + Record the fact that QML class \a base is inherited by + QML class \a sub. + */ +void QmlTypeNode::addInheritedBy(const QString& base, Node* sub) +{ + if (sub->isInternal()) + return; + if (inheritedBy.constFind(base,sub) == inheritedBy.constEnd()) + inheritedBy.insert(base,sub); +} + +/*! + Loads the list \a subs with the nodes of all the subclasses of \a base. + */ +void QmlTypeNode::subclasses(const QString& base, NodeList& subs) +{ + subs.clear(); + if (inheritedBy.count(base) > 0) { + subs = inheritedBy.values(base); + } +} + +QmlTypeNode* QmlTypeNode::qmlBaseNode() +{ + if (!qmlBaseNode_ && !qmlBaseName_.isEmpty()) { + qmlBaseNode_ = QDocDatabase::qdocDB()->findQmlType(qmlBaseName_); + } + return qmlBaseNode_; +} + +/*! + If this QML type node has a base type node, + return the fully qualified name of that QML + type, i.e. <QML-module-name>::<QML-type-name>. + */ +QString QmlTypeNode::qmlFullBaseName() const +{ + QString result; + if (qmlBaseNode_) { + result = qmlBaseNode_->logicalModuleName() + "::" + qmlBaseNode_->name(); + } + return result; +} + +/*! + If the QML type's QML module pointer is set, return the QML + module name from the QML module node. Otherwise, return the + empty string. + */ +QString QmlTypeNode::logicalModuleName() const +{ + return (logicalModule_ ? logicalModule_->logicalModuleName() : QString()); +} + +/*! + If the QML type's QML module pointer is set, return the QML + module version from the QML module node. Otherwise, return + the empty string. + */ +QString QmlTypeNode::logicalModuleVersion() const +{ + return (logicalModule_ ? logicalModule_->logicalModuleVersion() : QString()); +} + +/*! + If the QML type's QML module pointer is set, return the QML + module identifier from the QML module node. Otherwise, return + the empty string. + */ +QString QmlTypeNode::logicalModuleIdentifier() const +{ + return (logicalModule_ ? logicalModule_->logicalModuleIdentifier() : QString()); +} + +/*! + Constructs a Qml basic type node. The new node has the given + \a parent and \a name. + */ +QmlBasicTypeNode::QmlBasicTypeNode(Aggregate *parent, + const QString& name) + : Aggregate(QmlBasicType, parent, name) +{ + setTitle(name); + setGenus(Node::QML); +} + +/*! + Constructor for the Qml property group node. \a parent is + always a QmlTypeNode. + */ +QmlPropertyGroupNode::QmlPropertyGroupNode(QmlTypeNode* parent, const QString& name) + : Aggregate(QmlPropertyGroup, parent, name) +{ + idNumber_ = -1; + setGenus(Node::QML); +} + +/*! + Return the property group node's id number for use in + constructing an id attribute for the property group. + If the id number is currently -1, increment the global + property group count and set the id number to the new + value. + */ +QString QmlPropertyGroupNode::idNumber() +{ + if (idNumber_ == -1) + idNumber_ = incPropertyGroupCount(); + return QString().setNum(idNumber_); +} + +/*! + Constructor for the QML property node. + */ +QmlPropertyNode::QmlPropertyNode(Aggregate* parent, + const QString& name, + const QString& type, + bool attached) + : LeafNode(QmlProperty, parent, name), + type_(type), + stored_(FlagValueDefault), + designable_(FlagValueDefault), + isAlias_(false), + isdefault_(false), + attached_(attached), + readOnly_(FlagValueDefault) +{ + setPageType(ApiPage); + if (type_ == QString("alias")) + isAlias_ = true; + if (name.startsWith("__")) + setStatus(Internal); + setGenus(Node::QML); +} + +/*! + Returns \c true if a QML property or attached property is + not read-only. The algorithm for figuring this out is long + amd tedious and almost certainly will break. It currently + doesn't work for the qmlproperty: + + \code + bool PropertyChanges::explicit, + \endcode + + ...because the tokenizer gets confused on \e{explicit}. + */ +bool QmlPropertyNode::isWritable() +{ + if (readOnly_ != FlagValueDefault) + return !fromFlagValue(readOnly_, false); + + QmlTypeNode* qcn = qmlTypeNode(); + if (qcn) { + if (qcn->cppClassRequired()) { + if (qcn->classNode()) { + PropertyNode* pn = findCorrespondingCppProperty(); + if (pn) + return pn->isWritable(); + else + defLocation().warning(tr("No Q_PROPERTY for QML property %1::%2::%3 " + "in C++ class documented as QML type: " + "(property not found in the C++ class or its base classes)") + .arg(logicalModuleName()).arg(qmlTypeName()).arg(name())); + } + else + defLocation().warning(tr("No Q_PROPERTY for QML property %1::%2::%3 " + "in C++ class documented as QML type: " + "(C++ class not specified or not found).") + .arg(logicalModuleName()).arg(qmlTypeName()).arg(name())); + } + } + return true; +} + +/*! + Returns a pointer this QML property's corresponding C++ + property, if it has one. + */ +PropertyNode* QmlPropertyNode::findCorrespondingCppProperty() +{ + PropertyNode* pn; + Node* n = parent(); + while (n && !(n->isQmlType() || n->isJsType())) + n = n->parent(); + if (n) { + QmlTypeNode* qcn = static_cast<QmlTypeNode*>(n); + ClassNode* cn = qcn->classNode(); + if (cn) { + /* + If there is a dot in the property name, first + find the C++ property corresponding to the QML + property group. + */ + QStringList dotSplit = name().split(QChar('.')); + pn = cn->findPropertyNode(dotSplit[0]); + if (pn) { + /* + Now find the C++ property corresponding to + the QML property in the QML property group, + <group>.<property>. + */ + if (dotSplit.size() > 1) { + QStringList path(extractClassName(pn->qualifiedDataType())); + Node* nn = QDocDatabase::qdocDB()->findClassNode(path); + if (nn) { + ClassNode* cn = static_cast<ClassNode*>(nn); + PropertyNode *pn2 = cn->findPropertyNode(dotSplit[1]); + /* + If found, return the C++ property + corresponding to the QML property. + Otherwise, return the C++ property + corresponding to the QML property + group. + */ + return (pn2 ? pn2 : pn); + } + } + else + return pn; + } + } + } + return 0; +} + +/*! + This returns the name of the owning QML type. + */ +QString QmlPropertyNode::element() const +{ + if (parent()->isQmlPropertyGroup()) + return parent()->element(); + return parent()->name(); +} + +/*! + Construct the full document name for this node and return it. + */ +QString Node::fullDocumentName() const +{ + QStringList pieces; + const Node* n = this; + + do { + if (!n->name().isEmpty() && !n->isQmlPropertyGroup()) + pieces.insert(0, n->name()); + + if ((n->isQmlType() || n->isJsType()) && !n->logicalModuleName().isEmpty()) { + pieces.insert(0, n->logicalModuleName()); + break; + } + + if (n->isDocumentNode()) + break; + + // Examine the parent node if one exists. + if (n->parent()) + n = n->parent(); + else + break; + } while (true); + + // Create a name based on the type of the ancestor node. + QString concatenator = "::"; + if (n->isQmlType() || n->isJsType()) + concatenator = QLatin1Char('.'); + + if (n->isDocumentNode()) + concatenator = QLatin1Char('#'); + + return pieces.join(concatenator); +} + +/*! + Returns the \a str as an NCName, which means the name can + be used as the value of an \e id attribute. Search for NCName + on the internet for details of what can be an NCName. + */ +QString Node::cleanId(const QString &str) +{ + QString clean; + QString name = str.simplified(); + + if (name.isEmpty()) + return clean; + + name = name.replace("::","-"); + name = name.replace(QLatin1Char(' '), QLatin1Char('-')); + name = name.replace("()","-call"); + + clean.reserve(name.size() + 20); + if (!str.startsWith("id-")) + clean = "id-"; + const QChar c = name[0]; + const uint u = c.unicode(); + + if ((u >= 'a' && u <= 'z') || + (u >= 'A' && u <= 'Z') || + (u >= '0' && u <= '9')) { + clean += c; + } + else if (u == '~') { + clean += "dtor."; + } + else if (u == '_') { + clean += "underscore."; + } + else { + clean += QLatin1Char('a'); + } + + for (int i = 1; i < (int) name.length(); i++) { + const QChar c = name[i]; + const uint u = c.unicode(); + if ((u >= 'a' && u <= 'z') || + (u >= 'A' && u <= 'Z') || + (u >= '0' && u <= '9') || u == '-' || + u == '_' || u == '.') { + clean += c; + } + else if (c.isSpace() || u == ':' ) { + clean += QLatin1Char('-'); + } + else if (u == '!') { + clean += "-not"; + } + else if (u == '&') { + clean += "-and"; + } + else if (u == '<') { + clean += "-lt"; + } + else if (u == '=') { + clean += "-eq"; + } + else if (u == '>') { + clean += "-gt"; + } + else if (u == '#') { + clean += "-hash"; + } + else if (u == '(') { + clean += QLatin1Char('-'); + } + else if (u == ')') { + clean += QLatin1Char('-'); + } + else { + clean += QLatin1Char('-'); + clean += QString::number((int)u, 16); + } + } + return clean; +} + +#if 0 +/*! + Creates a string that can be used as a UUID for the node, + depending on the type and subtype of the node. Uniquenss + is not guaranteed, but it is expected that strings created + here will be unique within an XML document. Hence, the + returned string can be used as the value of an \e id + attribute. + */ +QString Node::idForNode() const +{ + const FunctionNode* func; + const TypedefNode* tdn; + QString str; + + switch (type()) { + case Node::Namespace: + str = "namespace-" + fullDocumentName(); + break; + case Node::Class: + str = "class-" + fullDocumentName(); + break; + case Node::Enum: + str = "enum-" + name(); + break; + case Node::Typedef: + tdn = static_cast<const TypedefNode*>(this); + if (tdn->associatedEnum()) { + return tdn->associatedEnum()->idForNode(); + } + else { + str = "typedef-" + name(); + } + break; + case Node::Function: + func = static_cast<const FunctionNode*>(this); + if (func->associatedProperty()) { + return func->associatedProperty()->idForNode(); + } + else { + if (func->name().startsWith("operator")) { + str.clear(); + /* + The test below should probably apply to all + functions, but for now, overloaded operators + are the only ones that produce duplicate id + attributes in the DITA XML files. + */ + if (relatesTo_) + str = "nonmember-"; + QString op = func->name().mid(8); + if (!op.isEmpty()) { + int i = 0; + while (i<op.size() && op.at(i) == ' ') + ++i; + if (i>0 && i<op.size()) { + op = op.mid(i); + } + if (!op.isEmpty()) { + i = 0; + while (i < op.size()) { + const QChar c = op.at(i); + const uint u = c.unicode(); + if ((u >= 'a' && u <= 'z') || + (u >= 'A' && u <= 'Z') || + (u >= '0' && u <= '9')) + break; + ++i; + } + str += "operator-"; + if (i>0) { + QString tail = op.mid(i); + op = op.left(i); + if (operators_.contains(op)) { + str += operators_.value(op); + if (!tail.isEmpty()) + str += QLatin1Char('-') + tail; + } + else + qDebug() << "qdoc internal error: Operator missing from operators_ map:" << op; + } + else { + str += op; + } + } + } + } + else if (parent_) { + if (parent_->isClass()) + str = "class-member-" + func->name(); + else if (parent_->isNamespace()) + str = "namespace-member-" + func->name(); + else if (parent_->isQmlType()) + str = "qml-method-" + parent_->name().toLower() + "-" + func->name(); + else if (parent_->isJsType()) + str = "js-method-" + parent_->name().toLower() + "-" + func->name(); + else if (parent_->type() == Document) { + qDebug() << "qdoc internal error: Node subtype not handled:" + << parent_->docSubtype() << func->name(); + } + else + qDebug() << "qdoc internal error: Node type not handled:" + << parent_->type() << func->name(); + + } + if (func->overloadNumber() != 0) + str += QLatin1Char('-') + QString::number(func->overloadNumber()); + } + break; + case Node::QmlType: + if (genus() == QML) + str = "qml-class-" + name(); + else + str = "js-type-" + name(); + break; + case Node::QmlBasicType: + if (genus() == QML) + str = "qml-basic-type-" + name(); + else + str = "js-basic-type-" + name(); + break; + case Node::Document: + { + switch (docSubtype()) { + case Node::Page: + case Node::HeaderFile: + str = title(); + if (str.isEmpty()) { + str = name(); + if (str.endsWith(".html")) + str.remove(str.size()-5,5); + } + str.replace(QLatin1Char('/'), QLatin1Char('-')); + break; + case Node::File: + str = name(); + str.replace(QLatin1Char('/'), QLatin1Char('-')); + break; + case Node::Example: + str = name(); + str.replace(QLatin1Char('/'), QLatin1Char('-')); + break; + default: + qDebug() << "ERROR: A case was not handled in Node::idForNode():" + << "docSubtype():" << docSubtype() << "type():" << type(); + break; + } + } + break; + case Node::Group: + case Node::Module: + str = title(); + if (str.isEmpty()) { + str = name(); + if (str.endsWith(".html")) + str.remove(str.size()-5,5); + } + str.replace(QLatin1Char('/'), QLatin1Char('-')); + break; + case Node::QmlModule: + if (genus() == QML) + str = "qml-module-" + name(); + else + str = "js-module-" + name(); + break; + case Node::QmlProperty: + if (genus() == QML) + str = "qml-"; + else + str = "js-"; + if (isAttached()) + str += "attached-property-" + name(); + else + str += "property-" + name(); + break; + case Node::QmlPropertyGroup: + { + Node* n = const_cast<Node*>(this); + if (genus() == QML) + str = "qml-propertygroup-" + n->name(); + else + str = "js-propertygroup-" + n->name(); + } + break; + case Node::Property: + str = "property-" + name(); + break; + case Node::QmlSignal: + if (genus() == QML) + str = "qml-signal-" + name(); + else + str = "js-signal-" + name(); + break; + case Node::QmlSignalHandler: + if (genus() == QML) + str = "qml-signal-handler-" + name(); + else + str = "js-signal-handler-" + name(); + break; + case Node::QmlMethod: + func = static_cast<const FunctionNode*>(this); + if (genus() == QML) + str = "qml-method-"; + else + str = "js-method-"; + str += parent_->name().toLower() + "-" + func->name(); + if (func->overloadNumber() != 0) + str += QLatin1Char('-') + QString::number(func->overloadNumber()); + break; + case Node::Variable: + str = "var-" + name(); + break; + default: + qDebug() << "ERROR: A case was not handled in Node::idForNode():" + << "type():" << type() << "docSubtype():" << docSubtype(); + break; + } + if (str.isEmpty()) { + qDebug() << "ERROR: A link text was empty in Node::idForNode():" + << "type():" << type() << "docSubtype():" << docSubtype() + << "name():" << name() + << "title():" << title(); + } + else { + str = cleanId(str); + } + return str; +} +#endif + +/*! + Prints the inner node's list of children. + For debugging only. + */ +void Aggregate::printChildren(const QString& title) +{ + qDebug() << title << name() << children_.size(); + if (children_.size() > 0) { + for (int i=0; i<children_.size(); ++i) { + Node* n = children_.at(i); + qDebug() << " CHILD:" << n->name() << n->nodeTypeString() << n->nodeSubtypeString(); + } + } +} + +/*! + Returns \c true if the collection node's member list is + not empty. + */ +bool CollectionNode::hasMembers() const +{ + return !members_.isEmpty(); +} + +/*! + Appends \a node to the collection node's member list, if + and only if it isn't already in the member list. + */ +void CollectionNode::addMember(Node* node) +{ + if (!members_.contains(node)) + members_.append(node); +} + +/*! + Returns \c true if this collection node contains at least + one namespace node. + */ +bool CollectionNode::hasNamespaces() const +{ + if (!members_.isEmpty()) { + NodeList::const_iterator i = members_.begin(); + while (i != members_.end()) { + if ((*i)->isNamespace()) + return true; + ++i; + } + } + return false; +} + +/*! + Returns \c true if this collection node contains at least + one class node. + */ +bool CollectionNode::hasClasses() const +{ + if (!members_.isEmpty()) { + NodeList::const_iterator i = members_.cbegin(); + while (i != members_.cend()) { + if ((*i)->isClass()) + return true; + ++i; + } + } + return false; +} + +/*! + Loads \a out with all this collection node's members that + are namespace nodes. + */ +void CollectionNode::getMemberNamespaces(NodeMap& out) +{ + out.clear(); + NodeList::const_iterator i = members_.cbegin(); + while (i != members_.cend()) { + if ((*i)->isNamespace()) + out.insert((*i)->name(),(*i)); + ++i; + } +} + +/*! + Loads \a out with all this collection node's members that + are class nodes. + */ +void CollectionNode::getMemberClasses(NodeMap& out) const +{ + out.clear(); + NodeList::const_iterator i = members_.cbegin(); + while (i != members_.cend()) { + if ((*i)->isClass()) + out.insert((*i)->name(),(*i)); + ++i; + } +} + +/*! + Prints the collection node's list of members. + For debugging only. + */ +void CollectionNode::printMembers(const QString& title) +{ + qDebug() << title << name() << members_.size(); + if (members_.size() > 0) { + for (int i=0; i<members_.size(); ++i) { + Node* n = members_.at(i); + qDebug() << " MEMBER:" << n->name() << n->nodeTypeString() << n->nodeSubtypeString(); + } + } +} + +/*! + Sets the document node's \a title. This is used for the page title. + */ +void CollectionNode::setTitle(const QString& title) +{ + title_ = title; + parent()->addChild(this, title); +} + +/*! + This function splits \a arg on the blank character to get a + logical module name and version number. If the version number + is present, it spilts the version number on the '.' character + to get a major version number and a minor vrsion number. If + the version number is present, both the major and minor version + numbers should be there, but the minor version number is not + absolutely necessary. + */ +void CollectionNode::setLogicalModuleInfo(const QString& arg) +{ + QStringList blankSplit = arg.split(QLatin1Char(' ')); + logicalModuleName_ = blankSplit[0]; + if (blankSplit.size() > 1) { + QStringList dotSplit = blankSplit[1].split(QLatin1Char('.')); + logicalModuleVersionMajor_ = dotSplit[0]; + if (dotSplit.size() > 1) + logicalModuleVersionMinor_ = dotSplit[1]; + else + logicalModuleVersionMinor_ = "0"; + } +} + +/*! + This function accepts the logical module \a info as a string + list. If the logical module info contains the version number, + it spilts the version number on the '.' character to get the + major and minor vrsion numbers. Both major and minor version + numbers should be provided, but the minor version number is + not strictly necessary. + */ +void CollectionNode::setLogicalModuleInfo(const QStringList& info) +{ + logicalModuleName_ = info[0]; + if (info.size() > 1) { + QStringList dotSplit = info[1].split(QLatin1Char('.')); + logicalModuleVersionMajor_ = dotSplit[0]; + if (dotSplit.size() > 1) + logicalModuleVersionMinor_ = dotSplit[1]; + else + logicalModuleVersionMinor_ = "0"; + } +} + +QT_END_NAMESPACE diff --git a/src/qdoc/node.h b/src/qdoc/node.h new file mode 100644 index 000000000..6b1032805 --- /dev/null +++ b/src/qdoc/node.h @@ -0,0 +1,1167 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef NODE_H +#define NODE_H + +#include <qdir.h> +#include <qmap.h> +#include <qpair.h> +#include <qstringlist.h> + +#include "codechunk.h" +#include "doc.h" + +QT_BEGIN_NAMESPACE + +class Node; +class Tree; +class EnumNode; +class ClassNode; +class Aggregate; +class ExampleNode; +class TypedefNode; +class QmlTypeNode; +class QDocDatabase; +class FunctionNode; +class PropertyNode; +class CollectionNode; +class QmlPropertyNode; + +typedef QList<Node*> NodeList; +typedef QList<PropertyNode*> PropNodeList; +typedef QMap<QString, Node*> NodeMap; +typedef QMultiMap<QString, Node*> NodeMultiMap; +typedef QPair<int, int> NodeTypePair; +typedef QList<NodeTypePair> NodeTypeList; +typedef QMap<QString, CollectionNode*> CNMap; +typedef QMultiMap<QString, CollectionNode*> CNMultiMap; + +class Node +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::Node) + +public: + enum NodeType { + NoType, + Namespace, + Class, + Document, + Enum, + Typedef, + Function, + Property, + Variable, + Group, + Module, + QmlType, + QmlModule, + QmlPropertyGroup, + QmlProperty, + QmlSignal, + QmlSignalHandler, + QmlMethod, + QmlBasicType, + LastType + }; + + enum DocSubtype { + NoSubtype, + Example, + HeaderFile, + File, + Image, + Page, + ExternalPage, + DitaMap, + LastSubtype + }; + + enum Genus { DontCare, CPP, JS, QML, DOC }; + + enum Access { Public, Protected, Private }; + + enum Status { + Compat, + Obsolete, + Deprecated, + Preliminary, + Active, + Internal, + Intermediate + }; // don't reorder this enum + + enum ThreadSafeness { + UnspecifiedSafeness, + NonReentrant, + Reentrant, + ThreadSafe + }; + + enum LinkType { + StartLink, + NextLink, + PreviousLink, + ContentsLink, + IndexLink + }; + + enum PageType { + NoPageType, + ApiPage, + ArticlePage, + ExamplePage, + HowToPage, + OverviewPage, + TutorialPage, + FAQPage, + DitaMapPage, + OnBeyondZebra + }; + + enum FlagValue { + FlagValueDefault = -1, + FlagValueFalse = 0, + FlagValueTrue = 1 + }; + + virtual ~Node(); + + QString plainName() const; + QString plainFullName(const Node* relative = 0) const; + QString fullName(const Node* relative=0) const; + + const QString& fileNameBase() const { return fileNameBase_; } + bool hasFileNameBase() const { return !fileNameBase_.isEmpty(); } + void setFileNameBase(const QString& t) { fileNameBase_ = t; } + Node::Genus genus() const { return (Genus) genus_; } + void setGenus(Genus t) { genus_ = (unsigned char) t; } + + void setAccess(Access t) { access_ = (unsigned char) t; } + void setLocation(const Location& t); + void setDoc(const Doc& doc, bool replace = false); + void setStatus(Status t) { + if (status_ == (unsigned char) Obsolete && t == Deprecated) + return; + status_ = (unsigned char) t; + } + void setThreadSafeness(ThreadSafeness t) { safeness_ = (unsigned char) t; } + void setSince(const QString &since); + void setRelates(Aggregate* pseudoParent); + void setRelates(const QString &name); + void setPhysicalModuleName(const QString &name) { physicalModuleName_ = name; } + void setUrl(const QString& url) { url_ = url; } + void setTemplateStuff(const QString &t) { templateStuff_ = t; } + void setReconstitutedBrief(const QString &t) { reconstitutedBrief_ = t; } + void setPageType(PageType t) { pageType_ = (unsigned char) t; } + void setPageType(const QString& t); + void setParent(Aggregate* n) { parent_ = n; } + void setIndexNodeFlag() { indexNodeFlag_ = true; } + virtual void setOutputFileName(const QString& ) { } + + bool isQmlNode() const { return genus() == QML; } + bool isJsNode() const { return genus() == JS; } + bool isCppNode() const { return genus() == CPP; } + + virtual bool isAggregate() const = 0; + virtual bool isCollectionNode() const { return false; } + virtual bool isDocumentNode() const { return false; } + virtual bool isGroup() const { return false; } + virtual bool isModule() const { return false; } + virtual bool isQmlModule() const { return false; } + virtual bool isJsModule() const { return false; } + virtual bool isQmlType() const { return false; } + virtual bool isJsType() const { return false; } + virtual bool isQmlBasicType() const { return false; } + virtual bool isJsBasicType() const { return false; } + virtual bool isEnumType() const { return false; } + virtual bool isExample() const { return false; } + virtual bool isExampleFile() const { return false; } + virtual bool isHeaderFile() const { return false; } + virtual bool isLeaf() const { return false; } + virtual bool isReimplemented() const { return false; } + virtual bool isFunction() const { return false; } + virtual bool isNamespace() const { return false; } + virtual bool isClass() const { return false; } + virtual bool isQtQuickNode() const { return false; } + virtual bool isAbstract() const { return false; } + virtual bool isProperty() const { return false; } + virtual bool isQmlProperty() const { return false; } + virtual bool isJsProperty() const { return false; } + virtual bool isQmlPropertyGroup() const { return false; } + virtual bool isJsPropertyGroup() const { return false; } + virtual bool isQmlSignal() const { return false; } + virtual bool isJsSignal() const { return false; } + virtual bool isQmlSignalHandler() const { return false; } + virtual bool isJsSignalHandler() const { return false; } + virtual bool isQmlMethod() const { return false; } + virtual bool isJsMethod() const { return false; } + virtual bool isAttached() const { return false; } + virtual bool isAlias() const { return false; } + virtual bool isWrapper() const; + virtual bool isReadOnly() const { return false; } + virtual bool isDefault() const { return false; } + virtual bool isExternalPage() const { return false; } + virtual void addMember(Node* ) { } + virtual bool hasMembers() const { return false; } + virtual bool hasNamespaces() const { return false; } + virtual bool hasClasses() const { return false; } + virtual void setAbstract(bool ) { } + virtual void setWrapper() { } + virtual QString title() const { return name(); } + virtual QString fullTitle() const { return name(); } + virtual QString subTitle() const { return QString(); } + virtual void setTitle(const QString& ) { } + virtual void setSubTitle(const QString& ) { } + virtual QmlPropertyNode* hasQmlProperty(const QString& ) const { return 0; } + virtual QmlPropertyNode* hasQmlProperty(const QString&, bool ) const { return 0; } + virtual void getMemberNamespaces(NodeMap& ) { } + virtual void getMemberClasses(NodeMap& ) const { } + virtual bool isInternal() const; + virtual void setDataType(const QString& ) { } + virtual void setReadOnly(bool ) { } + virtual Node* disambiguate(NodeType , DocSubtype ) { return this; } + virtual bool wasSeen() const { return false; } + virtual void appendGroupName(const QString& ) { } + virtual QString element() const { return QString(); } + virtual Tree* tree() const; + virtual void findChildren(const QString& , NodeList& nodes) const { nodes.clear(); } + virtual void setNoAutoList(bool ) { } + bool isIndexNode() const { return indexNodeFlag_; } + NodeType type() const { return (NodeType) nodeType_; } + virtual DocSubtype docSubtype() const { return NoSubtype; } + bool match(const NodeTypeList& types) const; + Aggregate* parent() const { return parent_; } + const Node* root() const; + Aggregate* relates() const { return relatesTo_; } + const QString& name() const { return name_; } + QString physicalModuleName() const; + QString url() const { return url_; } + virtual QString nameForLists() const { return name_; } + virtual QString outputFileName() const { return QString(); } + virtual QString obsoleteLink() const { return QString(); } + virtual void setObsoleteLink(const QString& ) { }; + virtual void setQtVariable(const QString& ) { } + virtual QString qtVariable() const { return QString(); } + + const QMap<LinkType, QPair<QString,QString> >& links() const { return linkMap_; } + void setLink(LinkType linkType, const QString &link, const QString &desc); + + Access access() const { return (Access) access_; } + bool isPrivate() const { return (Access) access_ == Private; } + QString accessString() const; + const Location& declLocation() const { return declLocation_; } + const Location& defLocation() const { return defLocation_; } + const Location& location() const { return (defLocation_.isEmpty() ? declLocation_ : defLocation_); } + const Doc& doc() const { return doc_; } + bool hasDoc() const { return !doc_.isEmpty(); } + Status status() const { return (Status) status_; } + Status inheritedStatus() const; + bool isObsolete() const { return (status_ == (unsigned char) Obsolete); } + ThreadSafeness threadSafeness() const; + ThreadSafeness inheritedThreadSafeness() const; + QString since() const { return since_; } + QString templateStuff() const { return templateStuff_; } + const QString& reconstitutedBrief() const { return reconstitutedBrief_; } + PageType pageType() const { return (PageType) pageType_; } + QString pageTypeString() const; + QString nodeTypeString() const; + QString nodeSubtypeString() const; + virtual void addPageKeywords(const QString& ) { } + + void clearRelated() { relatesTo_ = 0; } + + //QString guid() const; + QString extractClassName(const QString &string) const; + virtual QString qmlTypeName() const { return name_; } + virtual QString qmlFullBaseName() const { return QString(); } + virtual QString logicalModuleName() const { return QString(); } + virtual QString logicalModuleVersion() const { return QString(); } + virtual QString logicalModuleIdentifier() const { return QString(); } + virtual void setLogicalModuleInfo(const QString& ) { } + virtual void setLogicalModuleInfo(const QStringList& ) { } + virtual CollectionNode* logicalModule() const { return 0; } + virtual void setQmlModule(CollectionNode* ) { } + virtual ClassNode* classNode() { return 0; } + virtual void setClassNode(ClassNode* ) { } + virtual const Node* applyModuleName(const Node* ) const { return 0; } + virtual QString idNumber() { return "0"; } + QmlTypeNode* qmlTypeNode(); + ClassNode* declarativeCppNode(); + const QString& outputSubdirectory() const { return outSubDir_; } + virtual void setOutputSubdirectory(const QString& t) { outSubDir_ = t; } + QString fullDocumentName() const; + static QString cleanId(const QString &str); + //QString idForNode() const; + + static FlagValue toFlagValue(bool b); + static bool fromFlagValue(FlagValue fv, bool defaultValue); + + static QString pageTypeString(unsigned char t); + static QString nodeTypeString(unsigned char t); + static QString nodeSubtypeString(unsigned char t); + static int incPropertyGroupCount(); + static void clearPropertyGroupCount(); + static void initialize(); + static NodeType goal(const QString& t) { return goals_.value(t); } + +protected: + Node(NodeType type, Aggregate* parent, const QString& name); + void removeRelates(); + +private: + + unsigned char nodeType_; + unsigned char genus_; + unsigned char access_; + unsigned char safeness_; + unsigned char pageType_; + unsigned char status_; + bool indexNodeFlag_; + + Aggregate* parent_; + Aggregate* relatesTo_; + QString name_; + Location declLocation_; + Location defLocation_; + Doc doc_; + QMap<LinkType, QPair<QString, QString> > linkMap_; + QString fileNameBase_; + QString physicalModuleName_; + QString url_; + QString since_; + QString templateStuff_; + QString reconstitutedBrief_; + //mutable QString uuid_; + QString outSubDir_; + static QStringMap operators_; + static int propertyGroupCount_; + static QMap<QString,Node::NodeType> goals_; +}; +Q_DECLARE_TYPEINFO(Node::DocSubtype, Q_PRIMITIVE_TYPE); + +class Aggregate : public Node +{ +public: + virtual ~Aggregate(); + + Node* findChildNode(const QString& name, Node::Genus genus) const; + Node* findChildNode(const QString& name, NodeType type); + virtual void findChildren(const QString& name, NodeList& nodes) const Q_DECL_OVERRIDE; + FunctionNode* findFunctionNode(const QString& name, const QString& params) const; + FunctionNode* findFunctionNode(const FunctionNode* clone) const; + void addInclude(const QString &include); + void setIncludes(const QStringList &includes); + void normalizeOverloads(); + void makeUndocumentedChildrenInternal(); + void deleteChildren(); + void removeFromRelated(); + + virtual bool isAggregate() const Q_DECL_OVERRIDE { return true; } + virtual bool isLeaf() const Q_DECL_OVERRIDE { return false; } + const EnumNode* findEnumNodeForValue(const QString &enumValue) const; + const NodeList & childNodes() const { return children_; } + const NodeList & relatedNodes() const { return related_; } + + int count() const { return children_.size(); } + NodeList overloads(const QString &funcName) const; + const QStringList& includes() const { return includes_; } + + QStringList primaryKeys(); + QStringList secondaryKeys(); + const QStringList& pageKeywords() const { return pageKeywds; } + virtual void addPageKeywords(const QString& t) Q_DECL_OVERRIDE { pageKeywds << t; } + virtual void setOutputFileName(const QString& f) Q_DECL_OVERRIDE { outputFileName_ = f; } + virtual QString outputFileName() const Q_DECL_OVERRIDE { return outputFileName_; } + virtual QmlPropertyNode* hasQmlProperty(const QString& ) const Q_DECL_OVERRIDE; + virtual QmlPropertyNode* hasQmlProperty(const QString&, bool attached) const Q_DECL_OVERRIDE; + void addChild(Node* child, const QString& title); + const QStringList& groupNames() const { return groupNames_; } + virtual void appendGroupName(const QString& t) Q_DECL_OVERRIDE { groupNames_.append(t); } + void printChildren(const QString& title); + void addChild(Node* child); + void removeChild(Node* child); + void setOutputSubdirectory(const QString& t) Q_DECL_OVERRIDE; + const NodeMap& primaryFunctionMap() { return primaryFunctionMap_; } + const QMap<QString, NodeList>& secondaryFunctionMap() { return secondaryFunctionMap_; } + +protected: + Aggregate(NodeType type, Aggregate* parent, const QString& name); + +private: + friend class Node; + + static bool isSameSignature(const FunctionNode* f1, const FunctionNode* f2); + void removeRelated(Node* pseudoChild); + void addRelated(Node* pseudoChild); + + QString outputFileName_; + QStringList pageKeywds; + QStringList includes_; + QStringList groupNames_; + NodeList children_; + NodeList enumChildren_; + NodeList related_; + NodeMap childMap_; + NodeMap primaryFunctionMap_; + QMap<QString, NodeList> secondaryFunctionMap_; +}; + +class LeafNode : public Node +{ +public: + LeafNode(); + virtual ~LeafNode() { } + + virtual bool isAggregate() const Q_DECL_OVERRIDE { return false; } + virtual bool isLeaf() const Q_DECL_OVERRIDE { return true; } + +protected: + LeafNode(NodeType type, Aggregate* parent, const QString& name); + LeafNode(Aggregate* parent, NodeType type, const QString& name); +}; + +class NamespaceNode : public Aggregate +{ +public: + NamespaceNode(Aggregate* parent, const QString& name); + virtual ~NamespaceNode() { } + virtual bool isNamespace() const Q_DECL_OVERRIDE { return true; } + virtual Tree* tree() const Q_DECL_OVERRIDE { return (parent() ? parent()->tree() : tree_); } + virtual bool wasSeen() const Q_DECL_OVERRIDE { return seen_; } + + void markSeen() { seen_ = true; } + void markNotSeen() { seen_ = false; } + void setTree(Tree* t) { tree_ = t; } + const NodeList& orphans() const { return orphans_; } + void addOrphan(Node* child) { orphans_.append(child); } + + private: + bool seen_; + Tree* tree_; + NodeList orphans_; +}; + +struct RelatedClass +{ + RelatedClass() { } + // constructor for resolved base class + RelatedClass(Node::Access access, ClassNode* node) + : access_(access), node_(node) { } + // constructor for unresolved base class + RelatedClass(Node::Access access, const QStringList& path, const QString& signature) + : access_(access), node_(0), path_(path), signature_(signature) { } + QString accessString() const; + bool isPrivate() const { return (access_ == Node::Private); } + + Node::Access access_; + ClassNode* node_; + QStringList path_; + QString signature_; +}; + +struct UsingClause +{ + UsingClause() { } + UsingClause(const QString& signature) : node_(0), signature_(signature) { } + const QString& signature() const { return signature_; } + const Node* node() { return node_; } + void setNode(const Node* n) { node_ = n; } + + const Node* node_; + QString signature_; +}; + +class ClassNode : public Aggregate +{ +public: + ClassNode(Aggregate* parent, const QString& name); + virtual ~ClassNode() { } + virtual bool isClass() const Q_DECL_OVERRIDE { return true; } + virtual bool isWrapper() const Q_DECL_OVERRIDE { return wrapper_; } + virtual QString obsoleteLink() const Q_DECL_OVERRIDE { return obsoleteLink_; } + virtual void setObsoleteLink(const QString& t) Q_DECL_OVERRIDE { obsoleteLink_ = t; } + virtual void setWrapper() Q_DECL_OVERRIDE { wrapper_ = true; } + + void addResolvedBaseClass(Access access, ClassNode* node); + void addDerivedClass(Access access, ClassNode* node); + void addUnresolvedBaseClass(Access access, const QStringList& path, const QString& signature); + void addUnresolvedUsingClause(const QString& signature); + void fixBaseClasses(); + void fixPropertyUsingBaseClasses(PropertyNode* pn); + + QList<RelatedClass>& baseClasses() { return bases_; } + QList<RelatedClass>& derivedClasses() { return derived_; } + QList<RelatedClass>& ignoredBaseClasses() { return ignoredBases_; } + QList<UsingClause>& usingClauses() { return usingClauses_; } + + const QList<RelatedClass> &baseClasses() const { return bases_; } + const QList<RelatedClass> &derivedClasses() const { return derived_; } + const QList<RelatedClass> &ignoredBaseClasses() const { return ignoredBases_; } + const QList<UsingClause>& usingClauses() const { return usingClauses_; } + + QmlTypeNode* qmlElement() { return qmlelement; } + void setQmlElement(QmlTypeNode* qcn) { qmlelement = qcn; } + virtual bool isAbstract() const Q_DECL_OVERRIDE { return abstract_; } + virtual void setAbstract(bool b) Q_DECL_OVERRIDE { abstract_ = b; } + PropertyNode* findPropertyNode(const QString& name); + QmlTypeNode* findQmlBaseNode(); + +private: + QList<RelatedClass> bases_; + QList<RelatedClass> derived_; + QList<RelatedClass> ignoredBases_; + QList<UsingClause> usingClauses_; + bool abstract_; + bool wrapper_; + QString obsoleteLink_; + QmlTypeNode* qmlelement; +}; + +class DocumentNode : public Aggregate +{ +public: + + DocumentNode(Aggregate* parent, + const QString& name, + DocSubtype docSubtype, + PageType ptype); + virtual ~DocumentNode() { } + + virtual bool isDocumentNode() const Q_DECL_OVERRIDE { return true; } + virtual void setTitle(const QString &title) Q_DECL_OVERRIDE; + virtual void setSubTitle(const QString &subTitle) Q_DECL_OVERRIDE { subtitle_ = subTitle; } + + DocSubtype docSubtype() const Q_DECL_OVERRIDE { return nodeSubtype_; } + virtual QString title() const Q_DECL_OVERRIDE { return title_; } + virtual QString fullTitle() const Q_DECL_OVERRIDE; + virtual QString subTitle() const Q_DECL_OVERRIDE; + virtual QString imageFileName() const { return QString(); } + virtual QString nameForLists() const Q_DECL_OVERRIDE { return title(); } + virtual void setImageFileName(const QString& ) { } + + virtual bool isHeaderFile() const Q_DECL_OVERRIDE { return (docSubtype() == Node::HeaderFile); } + virtual bool isExample() const Q_DECL_OVERRIDE { return (docSubtype() == Node::Example); } + virtual bool isExampleFile() const Q_DECL_OVERRIDE { return (parent() && parent()->isExample()); } + virtual bool isExternalPage() const Q_DECL_OVERRIDE { return nodeSubtype_ == ExternalPage; } + +protected: + DocSubtype nodeSubtype_; + QString title_; + QString subtitle_; +}; + +class ExampleNode : public DocumentNode +{ +public: + ExampleNode(Aggregate* parent, const QString& name) + : DocumentNode(parent, name, Node::Example, Node::ExamplePage) { } + virtual ~ExampleNode() { } + virtual QString imageFileName() const Q_DECL_OVERRIDE { return imageFileName_; } + virtual void setImageFileName(const QString& ifn) Q_DECL_OVERRIDE { imageFileName_ = ifn; } + +private: + QString imageFileName_; +}; + +struct ImportRec { + QString name_; // module name + QString version_; // <major> . <minor> + QString importId_; // "as" name + QString importUri_; // subdirectory of module directory + + ImportRec(const QString& name, + const QString& version, + const QString& importId, + const QString& importUri) + : name_(name), version_(version), importId_(importId), importUri_(importUri) { } + QString& name() { return name_; } + QString& version() { return version_; } + QString& importId() { return importId_; } + QString& importUri() { return importUri_; } + bool isEmpty() const { return name_.isEmpty(); } +}; + +typedef QList<ImportRec> ImportList; + +class QmlTypeNode : public Aggregate +{ +public: + QmlTypeNode(Aggregate* parent, const QString& name); + virtual ~QmlTypeNode(); + virtual bool isQmlType() const Q_DECL_OVERRIDE { return genus() == Node::QML; } + virtual bool isJsType() const Q_DECL_OVERRIDE { return genus() == Node::JS; } + virtual bool isQtQuickNode() const Q_DECL_OVERRIDE { + return (logicalModuleName() == QLatin1String("QtQuick")); + } + virtual ClassNode* classNode() Q_DECL_OVERRIDE { return cnode_; } + virtual void setClassNode(ClassNode* cn) Q_DECL_OVERRIDE { cnode_ = cn; } + virtual bool isAbstract() const Q_DECL_OVERRIDE { return abstract_; } + virtual bool isWrapper() const Q_DECL_OVERRIDE { return wrapper_; } + virtual void setAbstract(bool b) Q_DECL_OVERRIDE { abstract_ = b; } + virtual void setWrapper() Q_DECL_OVERRIDE { wrapper_ = true; } + virtual bool isInternal() const Q_DECL_OVERRIDE { return (status() == Internal); } + virtual QString qmlFullBaseName() const Q_DECL_OVERRIDE; + virtual QString obsoleteLink() const Q_DECL_OVERRIDE { return obsoleteLink_; } + virtual void setObsoleteLink(const QString& t) Q_DECL_OVERRIDE { obsoleteLink_ = t; }; + virtual QString logicalModuleName() const Q_DECL_OVERRIDE; + virtual QString logicalModuleVersion() const Q_DECL_OVERRIDE; + virtual QString logicalModuleIdentifier() const Q_DECL_OVERRIDE; + virtual CollectionNode* logicalModule() const Q_DECL_OVERRIDE { return logicalModule_; } + virtual void setQmlModule(CollectionNode* t) Q_DECL_OVERRIDE { logicalModule_ = t; } + + const ImportList& importList() const { return importList_; } + void setImportList(const ImportList& il) { importList_ = il; } + const QString& qmlBaseName() const { return qmlBaseName_; } + void setQmlBaseName(const QString& name) { qmlBaseName_ = name; } + bool qmlBaseNodeNotSet() const { return (qmlBaseNode_ == 0); } + QmlTypeNode* qmlBaseNode(); + void setQmlBaseNode(QmlTypeNode* b) { qmlBaseNode_ = b; } + void requireCppClass() { cnodeRequired_ = true; } + bool cppClassRequired() const { return cnodeRequired_; } + static void addInheritedBy(const QString& base, Node* sub); + static void subclasses(const QString& base, NodeList& subs); + static void terminate(); + +public: + static bool qmlOnly; + static QMultiMap<QString,Node*> inheritedBy; + +private: + bool abstract_; + bool cnodeRequired_; + bool wrapper_; + ClassNode* cnode_; + QString qmlBaseName_; + QString obsoleteLink_; + CollectionNode* logicalModule_; + QmlTypeNode* qmlBaseNode_; + ImportList importList_; +}; + +class QmlBasicTypeNode : public Aggregate +{ +public: + QmlBasicTypeNode(Aggregate* parent, + const QString& name); + virtual ~QmlBasicTypeNode() { } + virtual bool isQmlBasicType() const Q_DECL_OVERRIDE { return (genus() == Node::QML); } + virtual bool isJsBasicType() const Q_DECL_OVERRIDE { return (genus() == Node::JS); } +}; + +class QmlPropertyGroupNode : public Aggregate +{ +public: + QmlPropertyGroupNode(QmlTypeNode* parent, const QString& name); + virtual ~QmlPropertyGroupNode() { } + virtual bool isQtQuickNode() const Q_DECL_OVERRIDE { return parent()->isQtQuickNode(); } + virtual QString qmlTypeName() const Q_DECL_OVERRIDE { return parent()->qmlTypeName(); } + virtual QString logicalModuleName() const Q_DECL_OVERRIDE { + return parent()->logicalModuleName(); + } + virtual QString logicalModuleVersion() const Q_DECL_OVERRIDE { + return parent()->logicalModuleVersion(); + } + virtual QString logicalModuleIdentifier() const Q_DECL_OVERRIDE { + return parent()->logicalModuleIdentifier(); + } + virtual QString idNumber() Q_DECL_OVERRIDE; + virtual bool isQmlPropertyGroup() const Q_DECL_OVERRIDE { return genus() == Node::QML; } + virtual bool isJsPropertyGroup() const Q_DECL_OVERRIDE { return genus() == Node::JS; } + virtual QString element() const Q_DECL_OVERRIDE { return parent()->name(); } + + private: + int idNumber_; +}; + +class QmlPropertyNode : public LeafNode +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::QmlPropertyNode) + +public: + QmlPropertyNode(Aggregate *parent, + const QString& name, + const QString& type, + bool attached); + virtual ~QmlPropertyNode() { } + + virtual void setDataType(const QString& dataType) Q_DECL_OVERRIDE { type_ = dataType; } + void setStored(bool stored) { stored_ = toFlagValue(stored); } + void setDesignable(bool designable) { designable_ = toFlagValue(designable); } + virtual void setReadOnly(bool ro) Q_DECL_OVERRIDE { readOnly_ = toFlagValue(ro); } + void setDefault() { isdefault_ = true; } + + const QString &dataType() const { return type_; } + QString qualifiedDataType() const { return type_; } + bool isReadOnlySet() const { return (readOnly_ != FlagValueDefault); } + bool isStored() const { return fromFlagValue(stored_,true); } + bool isDesignable() const { return fromFlagValue(designable_,false); } + bool isWritable(); + virtual bool isQmlProperty() const Q_DECL_OVERRIDE { return genus() == QML; } + virtual bool isJsProperty() const Q_DECL_OVERRIDE { return genus() == JS; } + virtual bool isDefault() const Q_DECL_OVERRIDE { return isdefault_; } + virtual bool isReadOnly() const Q_DECL_OVERRIDE { return fromFlagValue(readOnly_,false); } + virtual bool isAlias() const Q_DECL_OVERRIDE { return isAlias_; } + virtual bool isAttached() const Q_DECL_OVERRIDE { return attached_; } + virtual bool isQtQuickNode() const Q_DECL_OVERRIDE { return parent()->isQtQuickNode(); } + virtual QString qmlTypeName() const Q_DECL_OVERRIDE { return parent()->qmlTypeName(); } + virtual QString logicalModuleName() const Q_DECL_OVERRIDE { + return parent()->logicalModuleName(); + } + virtual QString logicalModuleVersion() const Q_DECL_OVERRIDE { + return parent()->logicalModuleVersion(); + } + virtual QString logicalModuleIdentifier() const Q_DECL_OVERRIDE { + return parent()->logicalModuleIdentifier(); + } + virtual QString element() const Q_DECL_OVERRIDE; + + private: + PropertyNode* findCorrespondingCppProperty(); + +private: + QString type_; + FlagValue stored_; + FlagValue designable_; + bool isAlias_; + bool isdefault_; + bool attached_; + FlagValue readOnly_; +}; + +class EnumItem +{ +public: + EnumItem() { } + EnumItem(const QString& name, const QString& value) + : name_(name), value_(value) { } + + const QString& name() const { return name_; } + const QString& value() const { return value_; } + +private: + QString name_; + QString value_; +}; + +class EnumNode : public LeafNode +{ +public: + EnumNode(Aggregate* parent, const QString& name); + virtual ~EnumNode() { } + + void addItem(const EnumItem& item); + void setFlagsType(TypedefNode* typedeff); + bool hasItem(const QString &name) const { return names_.contains(name); } + virtual bool isEnumType() const Q_DECL_OVERRIDE { return true; } + + const QList<EnumItem>& items() const { return items_; } + Access itemAccess(const QString& name) const; + const TypedefNode* flagsType() const { return flagsType_; } + QString itemValue(const QString &name) const; + +private: + QList<EnumItem> items_; + QSet<QString> names_; + const TypedefNode* flagsType_; +}; + +class TypedefNode : public LeafNode +{ +public: + TypedefNode(Aggregate* parent, const QString& name); + virtual ~TypedefNode() { } + + const EnumNode* associatedEnum() const { return associatedEnum_; } + +private: + void setAssociatedEnum(const EnumNode* t); + + friend class EnumNode; + + const EnumNode* associatedEnum_; +}; + +inline void EnumNode::setFlagsType(TypedefNode* t) +{ + flagsType_ = t; + t->setAssociatedEnum(this); +} + +class Parameter +{ +public: + Parameter() {} + Parameter(const QString& dataType, + const QString& rightType = QString(), + const QString& name = QString(), + const QString& defaultValue = QString()); + Parameter(const Parameter& p); + + Parameter& operator=(const Parameter& p); + + void setName(const QString& name) { name_ = name; } + + bool hasType() const { return dataType_.length() + rightType_.length() > 0; } + const QString& dataType() const { return dataType_; } + const QString& rightType() const { return rightType_; } + const QString& name() const { return name_; } + const QString& defaultValue() const { return defaultValue_; } + + QString reconstruct(bool value = false) const; + + public: + QString dataType_; + QString rightType_; // mws says remove this 04/08/2015 + QString name_; + QString defaultValue_; +}; + +//friend class QTypeInfo<Parameter>; +//Q_DECLARE_TYPEINFO(Parameter, Q_MOVABLE_TYPE); + +class FunctionNode : public LeafNode +{ +public: + enum Metaness { + Plain, + Signal, + Slot, + Ctor, + Dtor, + MacroWithParams, + MacroWithoutParams, + Native }; + enum Virtualness { NonVirtual, NormalVirtual, PureVirtual }; + + FunctionNode(Aggregate* parent, const QString &name); + FunctionNode(NodeType type, Aggregate* parent, const QString &name, bool attached); + virtual ~FunctionNode() { } + + void setReturnType(const QString& t) { returnType_ = t; } + void setParentPath(const QStringList& p) { parentPath_ = p; } + void setMetaness(Metaness t) { metaness_ = t; } + void setVirtualness(Virtualness v); + void setConst(bool b) { const_ = b; } + void setStatic(bool b) { static_ = b; } + unsigned char overloadNumber() const { return overloadNumber_; } + void setOverloadFlag(bool b) { overload_ = b; } + void setOverloadNumber(unsigned char n) { overloadNumber_ = n; } + void setReimplemented(bool b); + void addParameter(const Parameter& parameter); + inline void setParameters(const QVector<Parameter>& parameters); + void borrowParameterNames(const FunctionNode* source); + void setReimplementedFrom(FunctionNode* from); + + const QString& returnType() const { return returnType_; } + Metaness metaness() const { return metaness_; } + bool isMacro() const { + return metaness_ == MacroWithParams || metaness_ == MacroWithoutParams; + } + Virtualness virtualness() const { return virtualness_; } + bool isConst() const { return const_; } + bool isStatic() const { return static_; } + bool isOverload() const { return overload_; } + bool isReimplemented() const Q_DECL_OVERRIDE { return reimplemented_; } + bool isFunction() const Q_DECL_OVERRIDE { return true; } + virtual bool isQmlSignal() const Q_DECL_OVERRIDE { + return (type() == Node::QmlSignal) && (genus() == Node::QML); + } + virtual bool isJsSignal() const Q_DECL_OVERRIDE { + return (type() == Node::QmlSignal) && (genus() == Node::JS); + } + virtual bool isQmlSignalHandler() const Q_DECL_OVERRIDE { + return (type() == Node::QmlSignalHandler) && (genus() == Node::QML); + } + virtual bool isJsSignalHandler() const Q_DECL_OVERRIDE { + return (type() == Node::QmlSignalHandler) && (genus() == Node::JS); + } + virtual bool isQmlMethod() const Q_DECL_OVERRIDE { + return (type() == Node::QmlMethod) && (genus() == Node::QML); + } + virtual bool isJsMethod() const Q_DECL_OVERRIDE { + return (type() == Node::QmlMethod) && (genus() == Node::JS); + } + const QVector<Parameter>& parameters() const { return parameters_; } + void clearParams() { parameters_.clear(); } + QStringList parameterNames() const; + QString rawParameters(bool names = false, bool values = false) const; + const FunctionNode* reimplementedFrom() const { return reimplementedFrom_; } + const QList<FunctionNode*> &reimplementedBy() const { return reimplementedBy_; } + const PropNodeList& associatedProperties() const { return associatedProperties_; } + const QStringList& parentPath() const { return parentPath_; } + bool hasAssociatedProperties() const { return !associatedProperties_.isEmpty(); } + bool hasOneAssociatedProperty() const { return (associatedProperties_.size() == 1); } + PropertyNode* firstAssociatedProperty() const { return associatedProperties_[0]; } + bool hasActiveAssociatedProperty() const; + + QStringList reconstructParameters(bool values = false) const; + QString signature(bool values = false) const; + virtual QString element() const Q_DECL_OVERRIDE { return parent()->name(); } + virtual bool isAttached() const Q_DECL_OVERRIDE { return attached_; } + virtual bool isQtQuickNode() const Q_DECL_OVERRIDE { return parent()->isQtQuickNode(); } + virtual QString qmlTypeName() const Q_DECL_OVERRIDE { return parent()->qmlTypeName(); } + virtual QString logicalModuleName() const Q_DECL_OVERRIDE { + return parent()->logicalModuleName(); + } + virtual QString logicalModuleVersion() const Q_DECL_OVERRIDE { + return parent()->logicalModuleVersion(); + } + virtual QString logicalModuleIdentifier() const Q_DECL_OVERRIDE { + return parent()->logicalModuleIdentifier(); + } + bool isPrivateSignal() const { return privateSignal_; } + void setPrivateSignal() { privateSignal_ = true; } + + void debug() const; + +private: + void addAssociatedProperty(PropertyNode* property); + + friend class Aggregate; + friend class PropertyNode; + + QString returnType_; + QStringList parentPath_; + Metaness metaness_; + Virtualness virtualness_; + bool const_ : 1; + bool static_ : 1; + bool reimplemented_: 1; + bool attached_: 1; + bool privateSignal_: 1; + bool overload_ : 1; + unsigned char overloadNumber_; + QVector<Parameter> parameters_; + const FunctionNode* reimplementedFrom_; + PropNodeList associatedProperties_; + QList<FunctionNode*> reimplementedBy_; +}; + +class PropertyNode : public LeafNode +{ +public: + enum FunctionRole { Getter, Setter, Resetter, Notifier }; + enum { NumFunctionRoles = Notifier + 1 }; + + PropertyNode(Aggregate* parent, const QString& name); + virtual ~PropertyNode() { } + + virtual void setDataType(const QString& dataType) Q_DECL_OVERRIDE { type_ = dataType; } + virtual bool isProperty() const Q_DECL_OVERRIDE { return true; } + void addFunction(FunctionNode* function, FunctionRole role); + void addSignal(FunctionNode* function, FunctionRole role); + void setStored(bool stored) { stored_ = toFlagValue(stored); } + void setDesignable(bool designable) { designable_ = toFlagValue(designable); } + void setScriptable(bool scriptable) { scriptable_ = toFlagValue(scriptable); } + void setWritable(bool writable) { writable_ = toFlagValue(writable); } + void setUser(bool user) { user_ = toFlagValue(user); } + void setOverriddenFrom(const PropertyNode* baseProperty); + void setRuntimeDesFunc(const QString& rdf) { runtimeDesFunc_ = rdf; } + void setRuntimeScrFunc(const QString& scrf) { runtimeScrFunc_ = scrf; } + void setConstant() { const_ = true; } + void setFinal() { final_ = true; } + void setRevision(int revision) { revision_ = revision; } + + const QString &dataType() const { return type_; } + QString qualifiedDataType() const; + NodeList functions() const; + NodeList functions(FunctionRole role) const { return functions_[(int)role]; } + NodeList getters() const { return functions(Getter); } + NodeList setters() const { return functions(Setter); } + NodeList resetters() const { return functions(Resetter); } + NodeList notifiers() const { return functions(Notifier); } + FunctionRole role(const FunctionNode* fn) const; + bool isStored() const { return fromFlagValue(stored_, storedDefault()); } + bool isDesignable() const { return fromFlagValue(designable_, designableDefault()); } + bool isScriptable() const { return fromFlagValue(scriptable_, scriptableDefault()); } + const QString& runtimeDesignabilityFunction() const { return runtimeDesFunc_; } + const QString& runtimeScriptabilityFunction() const { return runtimeScrFunc_; } + bool isWritable() const { return fromFlagValue(writable_, writableDefault()); } + bool isUser() const { return fromFlagValue(user_, userDefault()); } + bool isConstant() const { return const_; } + bool isFinal() const { return final_; } + const PropertyNode* overriddenFrom() const { return overrides_; } + + bool storedDefault() const { return true; } + bool userDefault() const { return false; } + bool designableDefault() const { return !setters().isEmpty(); } + bool scriptableDefault() const { return true; } + bool writableDefault() const { return !setters().isEmpty(); } + +private: + QString type_; + QString runtimeDesFunc_; + QString runtimeScrFunc_; + NodeList functions_[NumFunctionRoles]; + FlagValue stored_; + FlagValue designable_; + FlagValue scriptable_; + FlagValue writable_; + FlagValue user_; + bool const_; + bool final_; + int revision_; + const PropertyNode* overrides_; +}; + +inline void FunctionNode::setParameters(const QVector<Parameter> &p) +{ + parameters_ = p; +} + +inline void PropertyNode::addFunction(FunctionNode* function, FunctionRole role) +{ + functions_[(int)role].append(function); + function->addAssociatedProperty(this); +} + +inline void PropertyNode::addSignal(FunctionNode* function, FunctionRole role) +{ + functions_[(int)role].append(function); + function->addAssociatedProperty(this); +} + +inline NodeList PropertyNode::functions() const +{ + NodeList list; + for (int i = 0; i < NumFunctionRoles; ++i) + list += functions_[i]; + return list; +} + +class VariableNode : public LeafNode +{ +public: + VariableNode(Aggregate* parent, const QString &name); + virtual ~VariableNode() { } + + void setLeftType(const QString &leftType) { lrftType_ = leftType; } + void setRightType(const QString &rightType) { rightType_ = rightType; } + void setStatic(bool b) { static_ = b; } + + const QString &leftType() const { return lrftType_; } + const QString &rightType() const { return rightType_; } + QString dataType() const { return lrftType_ + rightType_; } + bool isStatic() const { return static_; } + +private: + QString lrftType_; + QString rightType_; + bool static_; +}; + +inline VariableNode::VariableNode(Aggregate* parent, const QString &name) + : LeafNode(Variable, parent, name), static_(false) +{ + setGenus(Node::CPP); +} + +class DitaMapNode : public DocumentNode +{ +public: + DitaMapNode(Aggregate* parent, const QString& name) + : DocumentNode(parent, name, Node::Page, Node::DitaMapPage) { } + virtual ~DitaMapNode() { } + + const DitaRefList& map() const { return doc().ditamap(); } +}; + +class CollectionNode : public Aggregate +{ + public: + CollectionNode(NodeType type, + Aggregate* parent, + const QString& name, + Genus genus) + : Aggregate(type, parent, name), seen_(false), noAutoList_(false) + { + setPageType(Node::OverviewPage); + setGenus(genus); + } + virtual ~CollectionNode() { } + + virtual bool isCollectionNode() const Q_DECL_OVERRIDE { return true; } + virtual bool isGroup() const Q_DECL_OVERRIDE { return genus() == Node::DOC; } + virtual bool isModule() const Q_DECL_OVERRIDE { return genus() == Node::CPP; } + virtual bool isQmlModule() const Q_DECL_OVERRIDE { return genus() == Node::QML; } + virtual bool isJsModule() const Q_DECL_OVERRIDE { return genus() == Node::JS; } + virtual QString qtVariable() const Q_DECL_OVERRIDE { return qtVariable_; } + virtual void setQtVariable(const QString& v) Q_DECL_OVERRIDE { qtVariable_ = v; } + virtual void addMember(Node* node) Q_DECL_OVERRIDE; + virtual bool hasMembers() const Q_DECL_OVERRIDE; + virtual bool hasNamespaces() const Q_DECL_OVERRIDE; + virtual bool hasClasses() const Q_DECL_OVERRIDE; + virtual void getMemberNamespaces(NodeMap& out) Q_DECL_OVERRIDE; + virtual void getMemberClasses(NodeMap& out) const Q_DECL_OVERRIDE; + virtual bool wasSeen() const Q_DECL_OVERRIDE { return seen_; } + virtual QString title() const Q_DECL_OVERRIDE { return title_; } + virtual QString subTitle() const Q_DECL_OVERRIDE { return subtitle_; } + virtual QString fullTitle() const Q_DECL_OVERRIDE { return title_; } + virtual QString nameForLists() const Q_DECL_OVERRIDE { return title_; } + virtual void setTitle(const QString &title) Q_DECL_OVERRIDE; + virtual void setSubTitle(const QString &subTitle) Q_DECL_OVERRIDE { subtitle_ = subTitle; } + + virtual QString logicalModuleName() const Q_DECL_OVERRIDE { return logicalModuleName_; } + virtual QString logicalModuleVersion() const Q_DECL_OVERRIDE { + return logicalModuleVersionMajor_ + QLatin1Char('.') + logicalModuleVersionMinor_; + } + virtual QString logicalModuleIdentifier() const Q_DECL_OVERRIDE { + return logicalModuleName_ + logicalModuleVersionMajor_; + } + virtual void setLogicalModuleInfo(const QString& arg) Q_DECL_OVERRIDE; + virtual void setLogicalModuleInfo(const QStringList& info) Q_DECL_OVERRIDE; + + const NodeList& members() const { return members_; } + void printMembers(const QString& title); + + void markSeen() { seen_ = true; } + void markNotSeen() { seen_ = false; } + bool noAutoList() const { return noAutoList_; } + virtual void setNoAutoList(bool b) Q_DECL_OVERRIDE { noAutoList_ = b; } + + private: + bool seen_; + bool noAutoList_; + QString title_; + QString subtitle_; + NodeList members_; + QString logicalModuleName_; + QString logicalModuleVersionMajor_; + QString logicalModuleVersionMinor_; + QString qtVariable_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/openedlist.cpp b/src/qdoc/openedlist.cpp new file mode 100644 index 000000000..6bb4a270e --- /dev/null +++ b/src/qdoc/openedlist.cpp @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + openedlist.cpp +*/ + +#include <qregexp.h> + +#include "atom.h" +#include "openedlist.h" + +QT_BEGIN_NAMESPACE + +static const char roman[] = "m\2d\5c\2l\5x\2v\5i"; + +OpenedList::OpenedList( Style style ) + : sty( style ), ini( 1 ), nex( 0 ) +{ +} + +OpenedList::OpenedList( const Location& location, const QString& hint ) + : sty( Bullet ), ini( 1 ) +{ + QRegExp hintSyntax( "(\\W*)([0-9]+|[A-Z]+|[a-z]+)(\\W*)" ); + + if ( hintSyntax.exactMatch(hint) ) { + bool ok; + int asNumeric = hint.toInt( &ok ); + int asRoman = fromRoman( hintSyntax.cap(2) ); + int asAlpha = fromAlpha( hintSyntax.cap(2) ); + + if ( ok ) { + sty = Numeric; + ini = asNumeric; + } else if ( asRoman > 0 && asRoman != 100 && asRoman != 500 ) { + sty = ( hint == hint.toLower() ) ? LowerRoman : UpperRoman; + ini = asRoman; + } else { + sty = ( hint == hint.toLower() ) ? LowerAlpha : UpperAlpha; + ini = asAlpha; + } + pref = hintSyntax.cap( 1 ); + suff = hintSyntax.cap( 3 ); + } else if ( !hint.isEmpty() ) { + location.warning( tr("Unrecognized list style '%1'").arg(hint) ); + } + nex = ini - 1; +} + +QString OpenedList::styleString() const +{ + switch ( style() ) { + case Bullet: + default: + return ATOM_LIST_BULLET; + case Tag: + return ATOM_LIST_TAG; + case Value: + return ATOM_LIST_VALUE; + case Numeric: + return ATOM_LIST_NUMERIC; + case UpperAlpha: + return ATOM_LIST_UPPERALPHA; + case LowerAlpha: + return ATOM_LIST_LOWERALPHA; + case UpperRoman: + return ATOM_LIST_UPPERROMAN; + case LowerRoman: + return ATOM_LIST_LOWERROMAN; + } +} + +QString OpenedList::numberString() const +{ + return QString::number( number() ); + /* + switch ( style() ) { + case Numeric: + return QString::number( number() ); + case UpperAlpha: + return toAlpha( number() ).toUpper(); + case LowerAlpha: + return toAlpha( number() ); + case UpperRoman: + return toRoman( number() ).toUpper(); + case LowerRoman: + return toRoman( number() ); + case Bullet: + default: + return "*"; + }*/ +} + +QString OpenedList::toAlpha( int n ) +{ + QString str; + + while ( n > 0 ) { + n--; + str.prepend( (n % 26) + 'a' ); + n /= 26; + } + return str; +} + +int OpenedList::fromAlpha( const QString& str ) +{ + int n = 0; + int u; + + for ( int i = 0; i < (int) str.length(); i++ ) { + u = str[i].toLower().unicode(); + if ( u >= 'a' && u <= 'z' ) { + n *= 26; + n += u - 'a' + 1; + } else { + return 0; + } + } + return n; +} + +QString OpenedList::toRoman( int n ) +{ + /* + See p. 30 of Donald E. Knuth's "TeX: The Program". + */ + QString str; + int j = 0; + int k; + int u; + int v = 1000; + + for ( ;; ) { + while ( n >= v ) { + str += roman[j]; + n -= v; + } + + if ( n <= 0 ) + break; + + k = j + 2; + u = v / roman[k - 1]; + if ( roman[k - 1] == 2 ) { + k += 2; + u /= 5; + } + if ( n + u >= v ) { + str += roman[k]; + n += u; + } else { + j += 2; + v /= roman[j - 1]; + } + } + return str; +} + +int OpenedList::fromRoman( const QString& str ) +{ + int n = 0; + int j; + int u; + int v = 0; + + for ( int i = str.length() - 1; i >= 0; i-- ) { + j = 0; + u = 1000; + while ( roman[j] != 'i' && roman[j] != str[i].toLower() ) { + j += 2; + u /= roman[j - 1]; + } + if ( u < v ) { + n -= u; + } else { + n += u; + } + v = u; + } + + if ( str.toLower() == toRoman(n) ) { + return n; + } else { + return 0; + } +} + +QT_END_NAMESPACE diff --git a/src/qdoc/openedlist.h b/src/qdoc/openedlist.h new file mode 100644 index 000000000..3a564b018 --- /dev/null +++ b/src/qdoc/openedlist.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + openedlist.h +*/ + +#ifndef OPENEDLIST_H +#define OPENEDLIST_H + +#include <qstring.h> + +#include "location.h" + +QT_BEGIN_NAMESPACE + +class OpenedList +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::OpenedList) + +public: + enum Style { Bullet, Tag, Value, Numeric, UpperAlpha, LowerAlpha, + UpperRoman, LowerRoman }; + + OpenedList() + : sty( Bullet ), ini( 1 ), nex( 0 ) { } + OpenedList( Style style ); + OpenedList( const Location& location, const QString& hint ); + + void next() { nex++; } + + bool isStarted() const { return nex >= ini; } + Style style() const { return sty; } + QString styleString() const; + int number() const { return nex; } + QString numberString() const; + QString prefix() const { return pref; } + QString suffix() const { return suff; } + +private: + static QString toAlpha( int n ); + static int fromAlpha( const QString& str ); + static QString toRoman( int n ); + static int fromRoman( const QString& str ); + + Style sty; + int ini; + int nex; + QString pref; + QString suff; +}; +Q_DECLARE_TYPEINFO(OpenedList, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/plaincodemarker.cpp b/src/qdoc/plaincodemarker.cpp new file mode 100644 index 000000000..bf2a55b2d --- /dev/null +++ b/src/qdoc/plaincodemarker.cpp @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "plaincodemarker.h" + +QT_BEGIN_NAMESPACE + +PlainCodeMarker::PlainCodeMarker() +{ +} + +PlainCodeMarker::~PlainCodeMarker() +{ +} + +bool PlainCodeMarker::recognizeCode( const QString& /* code */ ) +{ + return true; +} + +bool PlainCodeMarker::recognizeExtension( const QString& /* ext */ ) +{ + return true; +} + +bool PlainCodeMarker::recognizeLanguage( const QString& /* lang */ ) +{ + return false; +} + +Atom::AtomType PlainCodeMarker::atomType() const +{ + return Atom::Code; +} + +QString PlainCodeMarker::markedUpCode( const QString& code, + const Node * /* relative */, + const Location & /* location */ ) +{ + return protect( code ); +} + +QString PlainCodeMarker::markedUpSynopsis( const Node * /* node */, + const Node * /* relative */, + SynopsisStyle /* style */ ) +{ + return "foo"; +} + +QString PlainCodeMarker::markedUpName( const Node * /* node */ ) +{ + return QString(); +} + +QString PlainCodeMarker::markedUpFullName( const Node * /* node */, + const Node * /* relative */ ) +{ + return QString(); +} + +QString PlainCodeMarker::markedUpEnumValue(const QString & /* enumValue */, + const Node * /* relative */) +{ + return QString(); +} + +QString PlainCodeMarker::markedUpIncludes( const QStringList& /* includes */ ) +{ + return QString(); +} + +QString PlainCodeMarker::functionBeginRegExp( const QString& /* funcName */ ) +{ + return QString(); +} + +QString PlainCodeMarker::functionEndRegExp( const QString& /* funcName */ ) +{ + return QString(); +} + +QList<Section> PlainCodeMarker::sections(const Aggregate * /* innerNode */, + SynopsisStyle /* style */, + Status /* status */) +{ + return QList<Section>(); +} + +QT_END_NAMESPACE diff --git a/src/qdoc/plaincodemarker.h b/src/qdoc/plaincodemarker.h new file mode 100644 index 000000000..b83a3e469 --- /dev/null +++ b/src/qdoc/plaincodemarker.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + plaincodemarker.h +*/ + +#ifndef PLAINCODEMARKER_H +#define PLAINCODEMARKER_H + +#include "codemarker.h" + +QT_BEGIN_NAMESPACE + +class PlainCodeMarker : public CodeMarker +{ +public: + PlainCodeMarker(); + ~PlainCodeMarker(); + + bool recognizeCode( const QString& code ) Q_DECL_OVERRIDE; + bool recognizeExtension( const QString& ext ) Q_DECL_OVERRIDE; + bool recognizeLanguage( const QString& lang ) Q_DECL_OVERRIDE; + Atom::AtomType atomType() const Q_DECL_OVERRIDE; + QString markedUpCode( const QString& code, const Node *relative, const Location &location ) Q_DECL_OVERRIDE; + QString markedUpSynopsis( const Node *node, const Node *relative, + SynopsisStyle style ) Q_DECL_OVERRIDE; + QString markedUpName( const Node *node ) Q_DECL_OVERRIDE; + QString markedUpFullName( const Node *node, const Node *relative ) Q_DECL_OVERRIDE; + QString markedUpEnumValue(const QString &enumValue, const Node *relative) Q_DECL_OVERRIDE; + QString markedUpIncludes( const QStringList& includes ) Q_DECL_OVERRIDE; + QString functionBeginRegExp( const QString& funcName ) Q_DECL_OVERRIDE; + QString functionEndRegExp( const QString& funcName ) Q_DECL_OVERRIDE; + QList<Section> sections(const Aggregate *innerNode, SynopsisStyle style, Status status) Q_DECL_OVERRIDE; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/puredocparser.cpp b/src/qdoc/puredocparser.cpp new file mode 100644 index 000000000..b47f1fc45 --- /dev/null +++ b/src/qdoc/puredocparser.cpp @@ -0,0 +1,225 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + puredocparser.cpp +*/ + +#include <qfile.h> +#include <stdio.h> +#include <errno.h> +#include "codechunk.h" +#include "config.h" +#include "tokenizer.h" +#include <qdebug.h> +#include "qdocdatabase.h" +#include "puredocparser.h" + +QT_BEGIN_NAMESPACE + +/*! + Constructs the pure doc parser. +*/ +PureDocParser::PureDocParser() +{ +} + +/*! + Destroys the pure doc parser. + */ +PureDocParser::~PureDocParser() +{ +} + +/*! + Returns a list of the kinds of files that the pure doc + parser is meant to parse. The elements of the list are + file suffixes. + */ +QStringList PureDocParser::sourceFileNameFilter() +{ + return QStringList() << "*.qdoc" << "*.qtx" << "*.qtt" << "*.js"; +} + +/*! + Parses the source file identified by \a filePath and adds its + parsed contents to the database. The \a location is used for + reporting errors. + */ +void PureDocParser::parseSourceFile(const Location& location, const QString& filePath) +{ + QFile in(filePath); + currentFile_ = filePath; + if (!in.open(QIODevice::ReadOnly)) { + location.error(tr("Can't open source file '%1' (%2)").arg(filePath).arg(strerror(errno))); + currentFile_.clear(); + return; + } + + reset(); + Location fileLocation(filePath); + Tokenizer fileTokenizer(fileLocation, in); + tokenizer = &fileTokenizer; + readToken(); + + /* + The set of open namespaces is cleared before parsing + each source file. The word "source" here means cpp file. + */ + qdb_->clearOpenNamespaces(); + + processQdocComments(); + in.close(); + currentFile_.clear(); +} + +/*! + This is called by parseSourceFile() to do the actual parsing + and tree building. It only processes qdoc comments. It skips + everything else. + */ +bool PureDocParser::processQdocComments() +{ + const QSet<QString>& topicCommandsAllowed = topicCommands(); + const QSet<QString>& otherMetacommandsAllowed = otherMetaCommands(); + const QSet<QString>& metacommandsAllowed = topicCommandsAllowed + otherMetacommandsAllowed; + + while (tok != Tok_Eoi) { + if (tok == Tok_Doc) { + /* + lexeme() returns an entire qdoc comment. + */ + QString comment = lexeme(); + Location start_loc(location()); + readToken(); + + Doc::trimCStyleComment(start_loc,comment); + Location end_loc(location()); + + /* + Doc parses the comment. + */ + Doc doc(start_loc, end_loc, comment, metacommandsAllowed, topicCommandsAllowed); + + QString topic; + bool isQmlPropertyTopic = false; + bool isJsPropertyTopic = false; + + const TopicList& topics = doc.topicsUsed(); + if (!topics.isEmpty()) { + topic = topics[0].topic; + if ((topic == COMMAND_QMLPROPERTY) || + (topic == COMMAND_QMLPROPERTYGROUP) || + (topic == COMMAND_QMLATTACHEDPROPERTY)) { + isQmlPropertyTopic = true; + } + else if ((topic == COMMAND_JSPROPERTY) || + (topic == COMMAND_JSPROPERTYGROUP) || + (topic == COMMAND_JSATTACHEDPROPERTY)) { + isJsPropertyTopic = true; + } + } + + NodeList nodes; + DocList docs; + + if (topic.isEmpty()) { + doc.location().warning(tr("This qdoc comment contains no topic command " + "(e.g., '\\%1', '\\%2').") + .arg(COMMAND_MODULE).arg(COMMAND_PAGE)); + } + else if (isQmlPropertyTopic || isJsPropertyTopic) { + Doc nodeDoc = doc; + processQmlProperties(nodeDoc, nodes, docs, isJsPropertyTopic); + } + else { + ArgList args; + QSet<QString> topicCommandsUsed = topicCommandsAllowed & doc.metaCommandsUsed(); + if (topicCommandsUsed.count() > 0) { + topic = *topicCommandsUsed.begin(); + args = doc.metaCommandArgs(topic); + } + if (topicCommandsUsed.count() > 1) { + QString topics; + QSet<QString>::ConstIterator t = topicCommandsUsed.constBegin(); + while (t != topicCommandsUsed.constEnd()) { + topics += " \\" + *t + QLatin1Char(','); + ++t; + } + topics[topics.lastIndexOf(',')] = '.'; + int i = topics.lastIndexOf(','); + topics[i] = ' '; + topics.insert(i+1,"and"); + doc.location().warning(tr("Multiple topic commands found in comment: %1").arg(topics)); + } + ArgList::ConstIterator a = args.cbegin(); + while (a != args.cend()) { + Doc nodeDoc = doc; + Node* node = processTopicCommand(nodeDoc,topic,*a); + if (node != 0) { + nodes.append(node); + docs.append(nodeDoc); + } + ++a; + } + } + + Node* treeRoot = QDocDatabase::qdocDB()->primaryTreeRoot(); + NodeList::Iterator n = nodes.begin(); + QList<Doc>::Iterator d = docs.begin(); + while (n != nodes.end()) { + processOtherMetaCommands(*d, *n); + (*n)->setDoc(*d); + checkModuleInclusion(*n); + if ((*n)->isAggregate() && ((Aggregate *)*n)->includes().isEmpty()) { + Aggregate *m = static_cast<Aggregate *>(*n); + while (m->parent() && m->parent() != treeRoot) + m = m->parent(); + if (m == *n) + ((Aggregate *)*n)->addInclude((*n)->name()); + else + ((Aggregate *)*n)->setIncludes(m->includes()); + } + ++d; + ++n; + } + } + else { + readToken(); + } + } + return true; +} + + +QT_END_NAMESPACE diff --git a/src/qdoc/puredocparser.h b/src/qdoc/puredocparser.h new file mode 100644 index 000000000..20748c882 --- /dev/null +++ b/src/qdoc/puredocparser.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + puredocparser.h +*/ + +#ifndef PUREDOCPARSER_H +#define PUREDOCPARSER_H + +#include <qset.h> + +#include "cppcodeparser.h" +#include "location.h" + +QT_BEGIN_NAMESPACE + +class Config; +class Node; +class QString; + +class PureDocParser : public CppCodeParser +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::PureDocParser) + +public: + PureDocParser(); + virtual ~PureDocParser(); + + virtual QStringList sourceFileNameFilter() Q_DECL_OVERRIDE; + virtual void parseSourceFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; + + private: + bool processQdocComments(); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/qdoc.pro b/src/qdoc/qdoc.pro new file mode 100644 index 000000000..6333a046e --- /dev/null +++ b/src/qdoc/qdoc.pro @@ -0,0 +1,96 @@ +!force_bootstrap { + load(qfeatures) + requires(!contains(QT_DISABLED_FEATURES, xmlstreamwriter)) +} + +option(host_build) +QT = core qmldevtools-private + +DEFINES += \ + QDOC2_COMPAT + +INCLUDEPATH += $$QT_SOURCE_TREE/src/tools/qdoc \ + $$QT_SOURCE_TREE/src/tools/qdoc/qmlparser + +# Increase the stack size on MSVC to 4M to avoid a stack overflow +win32-msvc*:{ + QMAKE_LFLAGS += /STACK:4194304 +} + +HEADERS += atom.h \ + codechunk.h \ + codemarker.h \ + codeparser.h \ + config.h \ + cppcodemarker.h \ + cppcodeparser.h \ + doc.h \ + editdistance.h \ + generator.h \ + helpprojectwriter.h \ + htmlgenerator.h \ + location.h \ + node.h \ + openedlist.h \ + plaincodemarker.h \ + puredocparser.h \ + qdocdatabase.h \ + qdoctagfiles.h \ + qdocindexfiles.h \ + quoter.h \ + separator.h \ + text.h \ + tokenizer.h \ + tree.h +SOURCES += atom.cpp \ + codechunk.cpp \ + codemarker.cpp \ + codeparser.cpp \ + config.cpp \ + cppcodemarker.cpp \ + cppcodeparser.cpp \ + doc.cpp \ + editdistance.cpp \ + generator.cpp \ + helpprojectwriter.cpp \ + htmlgenerator.cpp \ + location.cpp \ + main.cpp \ + node.cpp \ + openedlist.cpp \ + plaincodemarker.cpp \ + puredocparser.cpp \ + qdocdatabase.cpp \ + qdoctagfiles.cpp \ + qdocindexfiles.cpp \ + quoter.cpp \ + separator.cpp \ + text.cpp \ + tokenizer.cpp \ + tree.cpp \ + yyindent.cpp + +### QML/JS Parser ### + +HEADERS += jscodemarker.h \ + qmlcodemarker.h \ + qmlcodeparser.h \ + qmlmarkupvisitor.h \ + qmlvisitor.h + +SOURCES += jscodemarker.cpp \ + qmlcodemarker.cpp \ + qmlcodeparser.cpp \ + qmlmarkupvisitor.cpp \ + qmlvisitor.cpp + +### Documentation for qdoc ### + +qtPrepareTool(QDOC, qdoc) +qtPrepareTool(QHELPGENERATOR, qhelpgenerator) + +QMAKE_DOCS = $$PWD/doc/config/qdoc.qdocconf + +load(qt_tool) + +TR_EXCLUDE += $$PWD/* diff --git a/src/qdoc/qdocdatabase.cpp b/src/qdoc/qdocdatabase.cpp new file mode 100644 index 000000000..28373bd3b --- /dev/null +++ b/src/qdoc/qdocdatabase.cpp @@ -0,0 +1,1744 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "generator.h" +#include "atom.h" +#include "tree.h" +#include "qdocdatabase.h" +#include "qdoctagfiles.h" +#include "qdocindexfiles.h" +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +static NodeMap emptyNodeMap_; +static NodeMultiMap emptyNodeMultiMap_; +bool QDocDatabase::debug = false; + +/*! \class QDocForest + This class manages a collection of trees. Each tree is an + instance of class Tree, which is a private class. + + The forest is populated as each index file is loaded. + Each index file adds a tree to the forest. Each tree + is named with the name of the module it represents. + + The search order is created by searchOrder(), if it has + not already been created. The search order and module + names arrays have parallel structure, i.e. modulNames_[i] + is the module name of the Tree at searchOrder_[i]. + */ + +/*! + Destroys the qdoc forest. This requires deleting + each Tree in the forest. Note that the forest has + been transferred into the search order array, so + what is really being used to destroy the forest + is the search order array. + */ +QDocForest::~QDocForest() +{ + for (int i=0; i<searchOrder_.size(); ++i) + delete searchOrder_.at(i); + forest_.clear(); + searchOrder_.clear(); + indexSearchOrder_.clear(); + moduleNames_.clear(); + primaryTree_ = 0; +} + +/*! + Initializes the forest prior to a traversal and + returns a pointer to the root node of the primary + tree. If the forest is empty, it return 0 + */ +NamespaceNode* QDocForest::firstRoot() +{ + currentIndex_ = 0; + return (!searchOrder().isEmpty() ? searchOrder()[0]->root() : 0); +} + +/*! + Increments the forest's current tree index. If the current + tree index is still within the forest, the function returns + the root node of the current tree. Otherwise it returns 0. + */ +NamespaceNode* QDocForest::nextRoot() +{ + ++currentIndex_; + return (currentIndex_ < searchOrder().size() ? searchOrder()[currentIndex_]->root() : 0); +} + +/*! + Initializes the forest prior to a traversal and + returns a pointer to the primary tree. If the + forest is empty, it returns 0. + */ +Tree* QDocForest::firstTree() +{ + currentIndex_ = 0; + return (!searchOrder().isEmpty() ? searchOrder()[0] : 0); +} + +/*! + Increments the forest's current tree index. If the current + tree index is still within the forest, the function returns + the pointer to the current tree. Otherwise it returns 0. + */ +Tree* QDocForest::nextTree() +{ + ++currentIndex_; + return (currentIndex_ < searchOrder().size() ? searchOrder()[currentIndex_] : 0); +} + +/*! + \fn Tree* QDocForest::primaryTree() + + Returns the pointer to the primary tree. + */ + +/*! + Finds the tree for module \a t in the forest and + sets the primary tree to be that tree. After the + primary tree is set, that tree is removed from the + forest. + + \node It gets re-inserted into the forest after the + search order is built. + */ +void QDocForest::setPrimaryTree(const QString& t) +{ + QString T = t.toLower(); + primaryTree_ = findTree(T); + forest_.remove(T); + if (!primaryTree_) + qDebug() << "ERROR: Could not set primary tree to:" << t; +} + +/*! + If the search order array is empty, create the search order. + If the search order array is not empty, do nothing. + */ +void QDocForest::setSearchOrder(QStringList& t) +{ + if (!searchOrder_.isEmpty()) + return; + + /* Allocate space for the search order. */ + searchOrder_.reserve(forest_.size()+1); + searchOrder_.clear(); + moduleNames_.reserve(forest_.size()+1); + moduleNames_.clear(); + + /* The primary tree is always first in the search order. */ + QString primaryName = primaryTree()->physicalModuleName(); + searchOrder_.append(primaryTree_); + moduleNames_.append(primaryName); + forest_.remove(primaryName); + + QMap<QString, Tree*>::iterator i; + foreach (const QString &m, t) { + if (primaryName != m) { + i = forest_.find(m); + if (i != forest_.end()) { + searchOrder_.append(i.value()); + moduleNames_.append(m); + forest_.remove(m); + } + } + } + /* + If any trees remain in the forest, just add them + to the search order sequentially, because we don't + know any better at this point. + */ + if (!forest_.isEmpty()) { + i = forest_.begin(); + while (i != forest_.end()) { + searchOrder_.append(i.value()); + moduleNames_.append(i.key()); + ++i; + } + forest_.clear(); + } + + /* + Rebuild the forest after constructing the search order. + It was destroyed during construction of the search order, + but it is needed for module-specific searches. + + Note that this loop also inserts the primary tree into the + forrest. That is a requirement. + */ + for (int i=0; i<searchOrder_.size(); ++i) { + if (!forest_.contains(moduleNames_.at(i))) { + forest_.insert(moduleNames_.at(i), searchOrder_.at(i)); + } + } +#if 0 + qDebug() << " SEARCH ORDER:"; + for (int i=0; i<moduleNames_.size(); ++i) + qDebug() << " " << i+1 << "." << moduleNames_.at(i); + qDebug() << " FOREST:" << forest_.keys(); + qDebug() << "SEARCH ORDER:" << moduleNames_; +#endif +} + +/*! + Returns an ordered array of Tree pointers that represents + the order in which the trees should be searched. The first + Tree in the array is the tree for the current module, i.e. + the module for which qdoc is generating documentation. + + The other Tree pointers in the array represent the index + files that were loaded in preparation for generating this + module's documentation. Each Tree pointer represents one + index file. The index file Tree points have been ordered + heuristically to, hopefully, minimize searching. Thr order + will probably be changed. + + If the search order array is empty, this function calls + indexSearchOrder(). The search order array is empty while + the index files are being loaded, but some searches must + be performed during this time, notably searches for base + class nodes. These searches require a temporary search + order. The temporary order changes throughout the loading + of the index files, but it is always the tree for the + current index file first, followed by the trees for the + index files that have already been loaded. The only + ordering required in this temporary search order is that + the current tree must be searched first. + */ +const QVector<Tree*>& QDocForest::searchOrder() +{ + if (searchOrder_.isEmpty()) + return indexSearchOrder(); + return searchOrder_; +} + +/*! + There are two search orders used by qdoc when searching for + things. The normal search order is returned by searchOrder(), + but this normal search order is not known until all the index + files have been read. At that point, setSearchOrder() is + called. + + During the reading of the index files, the vector holding + the normal search order remains empty. Whenever the search + order is requested, if that vector is empty, this function + is called to return a temporary search order, which includes + all the index files that have been read so far, plus the + one being read now. That one is prepended to the front of + the vector. + */ +const QVector<Tree*>& QDocForest::indexSearchOrder() +{ + if (forest_.size() > indexSearchOrder_.size()) + indexSearchOrder_.prepend(primaryTree_); + return indexSearchOrder_; +} + +/*! + Create a new Tree for the index file for the specified + \a module and add it to the forest. Return the pointer + to its root. + */ +NamespaceNode* QDocForest::newIndexTree(const QString& module) +{ + primaryTree_ = new Tree(module, qdb_); + forest_.insert(module.toLower(), primaryTree_); + return primaryTree_->root(); +} + +/*! + Create a new Tree for use as the primary tree. This tree + will represent the primary module. \a module is camel case. + */ +void QDocForest::newPrimaryTree(const QString& module) +{ + primaryTree_ = new Tree(module, qdb_); +} + +/*! + Searches through the forest for a node named \a targetPath + and returns a pointer to it if found. The \a relative node + is the starting point. It only makes sense for the primary + tree, which is searched first. After the primary tree has + been searched, \a relative is set to 0 for searching the + other trees, which are all index trees. With relative set + to 0, the starting point for each index tree is the root + of the index tree. + */ +const Node* QDocForest::findNodeForTarget(QStringList& targetPath, + const Node* relative, + Node::Genus genus, + QString& ref) +{ + int flags = SearchBaseClasses | SearchEnumValues; + + QString entity = targetPath.takeFirst(); + QStringList entityPath = entity.split("::"); + + QString target; + if (!targetPath.isEmpty()) + target = targetPath.takeFirst(); + + foreach (Tree* t, searchOrder()) { + const Node* n = t->findNodeForTarget(entityPath, target, relative, flags, genus, ref); + if (n) + return n; + relative = 0; + } + return 0; +} + +/*! + Print the list of module names ordered according + to how many successful searches each tree had. + */ +void QDocForest::printLinkCounts(const QString& project) +{ + Location::null.report(QString("%1: Link Counts").arg(project)); + QMultiMap<int, QString> m; + foreach (Tree* t, searchOrder()) { + if (t->linkCount() < 0) + m.insert(t->linkCount(), t->physicalModuleName()); + } + QString depends = "depends +="; + QString module = project.toLower(); + QMultiMap<int, QString>::iterator i = m.begin(); + while (i != m.end()) { + QString line = " " + i.value(); + if (i.value() != module) + depends += QLatin1Char(' ') + i.value(); + int pad = 30 - line.length(); + for (int k=0; k<pad; ++k) + line += QLatin1Char(' '); + line += "%1"; + Location::null.report(line.arg(-(i.key()))); + ++i; + } + Location::null.report("Optimal depends variable:"); + Location::null.report(depends); +} + +/*! + Print the list of module names ordered according + to how many successful searches each tree had. + */ +QString QDocForest::getLinkCounts(QStringList& strings, QVector<int>& counts) +{ + QMultiMap<int, QString> m; + foreach (Tree* t, searchOrder()) { + if (t->linkCount() < 0) + m.insert(t->linkCount(), t->physicalModuleName()); + } + QString depends = "depends +="; + QString module = Generator::defaultModuleName().toLower(); + QMultiMap<int, QString>::iterator i = m.begin(); + while (i != m.end()) { + if (i.value() != module) { + counts.append(-(i.key())); + strings.append(i.value()); + depends += QLatin1Char(' ') + i.value(); + } + ++i; + } + return depends; +} + +/*! + */ +const Node* QDocForest::findFunctionNode(const QString& target, + const Node* relative, + Node::Genus genus) +{ + QString function, params; + int length = target.length(); + if (target.endsWith(QChar(')'))) { + int position = target.lastIndexOf(QChar('(')); + params = target.mid(position+1, length-position-2); + function = target.left(position); + } + else + function = target; + foreach (Tree* t, searchOrder()) { + const Node* n = t->findFunctionNode(function, params, relative, genus); + if (n) + return n; + relative = 0; + } + return 0; +} + +/*! \class QDocDatabase + This class provides exclusive access to the qdoc database, + which consists of a forrest of trees and a lot of maps and + other useful data structures. + */ + +QDocDatabase* QDocDatabase::qdocDB_ = NULL; +NodeMap QDocDatabase::typeNodeMap_; + +/*! + Constructs the singleton qdoc database object. The singleton + constructs the \a forest_ object, which is also a singleton. + \a showInternal_ is normally false. If it is true, qdoc will + write documentation for nodes marked \c internal. + + \a singleExec_ is false when qdoc is being used in the standard + way of running qdoc twices for each module, first with -prepare + and then with -generate. First the -prepare phase is run for + each module, then the -generate phase is run for each module. + + When \a singleExec_ is true, qdoc is run only once. During the + single execution, qdoc processes the qdocconf files for all the + modules sequentially in a loop. Each source file for each module + is read exactly once. + */ +QDocDatabase::QDocDatabase() + : showInternal_(false), singleExec_(false), forest_(this) +{ + // nothing +} + +/*! + Destroys the qdoc database object. This requires destroying + the forest object, which contains an array of tree pointers. + Each tree is deleted. + */ +QDocDatabase::~QDocDatabase() +{ + // nothing. +} + +/*! + Creates the singleton. Allows only one instance of the class + to be created. Returns a pointer to the singleton. +*/ +QDocDatabase* QDocDatabase::qdocDB() +{ + if (!qdocDB_) { + qdocDB_ = new QDocDatabase; + initializeDB(); + } + return qdocDB_; +} + +/*! + Destroys the singleton. + */ +void QDocDatabase::destroyQdocDB() +{ + if (qdocDB_) { + delete qdocDB_; + qdocDB_ = 0; + } +} + +/*! + Initialize data structures in the singleton qdoc database. + + In particular, the type node map is initialized with a lot + type names that don't refer to documented types. For example, + the C++ standard types are included. These might be documented + here at some point, but for now they are not. Other examples + include \c array and \c data, which are just generic names + used as place holders in function signatures that appear in + the documentation. + + Also calls Node::initialize() to initialize the search goal map. + */ +void QDocDatabase::initializeDB() +{ + Node::initialize(); + typeNodeMap_.insert( "accepted", 0); + typeNodeMap_.insert( "actionPerformed", 0); + typeNodeMap_.insert( "activated", 0); + typeNodeMap_.insert( "alias", 0); + typeNodeMap_.insert( "anchors", 0); + typeNodeMap_.insert( "any", 0); + typeNodeMap_.insert( "array", 0); + typeNodeMap_.insert( "autoSearch", 0); + typeNodeMap_.insert( "axis", 0); + typeNodeMap_.insert( "backClicked", 0); + typeNodeMap_.insert( "bool", 0); + typeNodeMap_.insert( "boomTime", 0); + typeNodeMap_.insert( "border", 0); + typeNodeMap_.insert( "buttonClicked", 0); + typeNodeMap_.insert( "callback", 0); + typeNodeMap_.insert( "char", 0); + typeNodeMap_.insert( "clicked", 0); + typeNodeMap_.insert( "close", 0); + typeNodeMap_.insert( "closed", 0); + typeNodeMap_.insert( "color", 0); + typeNodeMap_.insert( "cond", 0); + typeNodeMap_.insert( "data", 0); + typeNodeMap_.insert( "dataReady", 0); + typeNodeMap_.insert( "dateString", 0); + typeNodeMap_.insert( "dateTimeString", 0); + typeNodeMap_.insert( "datetime", 0); + typeNodeMap_.insert( "day", 0); + typeNodeMap_.insert( "deactivated", 0); + typeNodeMap_.insert( "double", 0); + typeNodeMap_.insert( "drag", 0); + typeNodeMap_.insert( "easing", 0); + typeNodeMap_.insert( "enumeration", 0); + typeNodeMap_.insert( "error", 0); + typeNodeMap_.insert( "exposure", 0); + typeNodeMap_.insert( "fatalError", 0); + typeNodeMap_.insert( "fileSelected", 0); + typeNodeMap_.insert( "flags", 0); + typeNodeMap_.insert( "float", 0); + typeNodeMap_.insert( "focus", 0); + typeNodeMap_.insert( "focusZone", 0); + typeNodeMap_.insert( "format", 0); + typeNodeMap_.insert( "framePainted", 0); + typeNodeMap_.insert( "from", 0); + typeNodeMap_.insert( "frontClicked", 0); + typeNodeMap_.insert( "function", 0); + typeNodeMap_.insert( "hasOpened", 0); + typeNodeMap_.insert( "hovered", 0); + typeNodeMap_.insert( "hoveredTitle", 0); + typeNodeMap_.insert( "hoveredUrl", 0); + typeNodeMap_.insert( "imageCapture", 0); + typeNodeMap_.insert( "imageProcessing", 0); + typeNodeMap_.insert( "index", 0); + typeNodeMap_.insert( "initialized", 0); + typeNodeMap_.insert( "int", 0); + typeNodeMap_.insert( "isLoaded", 0); + typeNodeMap_.insert( "item", 0); + typeNodeMap_.insert( "jsdict", 0); + typeNodeMap_.insert( "jsobject", 0); + typeNodeMap_.insert( "key", 0); + typeNodeMap_.insert( "keysequence", 0); + typeNodeMap_.insert( "list", 0); + typeNodeMap_.insert( "listViewClicked", 0); + typeNodeMap_.insert( "loadRequest", 0); + typeNodeMap_.insert( "locale", 0); + typeNodeMap_.insert( "location", 0); + typeNodeMap_.insert( "long", 0); + typeNodeMap_.insert( "message", 0); + typeNodeMap_.insert( "messageReceived", 0); + typeNodeMap_.insert( "mode", 0); + typeNodeMap_.insert( "month", 0); + typeNodeMap_.insert( "name", 0); + typeNodeMap_.insert( "number", 0); + typeNodeMap_.insert( "object", 0); + typeNodeMap_.insert( "offset", 0); + typeNodeMap_.insert( "ok", 0); + typeNodeMap_.insert( "openCamera", 0); + typeNodeMap_.insert( "openImage", 0); + typeNodeMap_.insert( "openVideo", 0); + typeNodeMap_.insert( "padding", 0); + typeNodeMap_.insert( "parent", 0); + typeNodeMap_.insert( "path", 0); + typeNodeMap_.insert( "photoModeSelected", 0); + typeNodeMap_.insert( "position", 0); + typeNodeMap_.insert( "precision", 0); + typeNodeMap_.insert( "presetClicked", 0); + typeNodeMap_.insert( "preview", 0); + typeNodeMap_.insert( "previewSelected", 0); + typeNodeMap_.insert( "progress", 0); + typeNodeMap_.insert( "puzzleLost", 0); + typeNodeMap_.insert( "qmlSignal", 0); + typeNodeMap_.insert( "real", 0); + typeNodeMap_.insert( "rectangle", 0); + typeNodeMap_.insert( "request", 0); + typeNodeMap_.insert( "requestId", 0); + typeNodeMap_.insert( "section", 0); + typeNodeMap_.insert( "selected", 0); + typeNodeMap_.insert( "send", 0); + typeNodeMap_.insert( "settingsClicked", 0); + typeNodeMap_.insert( "shoe", 0); + typeNodeMap_.insert( "short", 0); + typeNodeMap_.insert( "signed", 0); + typeNodeMap_.insert( "sizeChanged", 0); + typeNodeMap_.insert( "size_t", 0); + typeNodeMap_.insert( "sockaddr", 0); + typeNodeMap_.insert( "someOtherSignal", 0); + typeNodeMap_.insert( "sourceSize", 0); + typeNodeMap_.insert( "startButtonClicked", 0); + typeNodeMap_.insert( "state", 0); + typeNodeMap_.insert( "std::initializer_list", 0); + typeNodeMap_.insert( "std::list", 0); + typeNodeMap_.insert( "std::map", 0); + typeNodeMap_.insert( "std::pair", 0); + typeNodeMap_.insert( "std::string", 0); + typeNodeMap_.insert( "std::vector", 0); + typeNodeMap_.insert( "string", 0); + typeNodeMap_.insert( "stringlist", 0); + typeNodeMap_.insert( "swapPlayers", 0); + typeNodeMap_.insert( "symbol", 0); + typeNodeMap_.insert( "t", 0); + typeNodeMap_.insert( "T", 0); + typeNodeMap_.insert( "tagChanged", 0); + typeNodeMap_.insert( "timeString", 0); + typeNodeMap_.insert( "timeout", 0); + typeNodeMap_.insert( "to", 0); + typeNodeMap_.insert( "toggled", 0); + typeNodeMap_.insert( "type", 0); + typeNodeMap_.insert( "unsigned", 0); + typeNodeMap_.insert( "urllist", 0); + typeNodeMap_.insert( "va_list", 0); + typeNodeMap_.insert( "value", 0); + typeNodeMap_.insert( "valueEmitted", 0); + typeNodeMap_.insert( "videoFramePainted", 0); + typeNodeMap_.insert( "videoModeSelected", 0); + typeNodeMap_.insert( "videoRecorder", 0); + typeNodeMap_.insert( "void", 0); + typeNodeMap_.insert( "volatile", 0); + typeNodeMap_.insert( "wchar_t", 0); + typeNodeMap_.insert( "x", 0); + typeNodeMap_.insert( "y", 0); + typeNodeMap_.insert( "zoom", 0); + typeNodeMap_.insert( "zoomTo", 0); +} + +/*! \fn NamespaceNode* QDocDatabase::primaryTreeRoot() + Returns a pointer to the root node of the primary tree. + */ + +/*! + \fn const CNMap& QDocDatabase::groups() + Returns a const reference to the collection of all + group nodes in the primary tree. +*/ + +/*! + \fn const CNMap& QDocDatabase::modules() + Returns a const reference to the collection of all + module nodes in the primary tree. +*/ + +/*! + \fn const CNMap& QDocDatabase::qmlModules() + Returns a const reference to the collection of all + QML module nodes in the primary tree. +*/ + +/*! + \fn const CNMap& QDocDatabase::jsModules() + Returns a const reference to the collection of all + JovaScript module nodes in the primary tree. +*/ + +/*! \fn CollectionNode* QDocDatabase::findGroup(const QString& name) + Find the group node named \a name and return a pointer + to it. If a matching node is not found, add a new group + node named \a name and return a pointer to that one. + + If a new group node is added, its parent is the tree root, + and the new group node is marked \e{not seen}. + */ + +/*! \fn CollectionNode* QDocDatabase::findModule(const QString& name) + Find the module node named \a name and return a pointer + to it. If a matching node is not found, add a new module + node named \a name and return a pointer to that one. + + If a new module node is added, its parent is the tree root, + and the new module node is marked \e{not seen}. + */ + +/*! \fn CollectionNode* QDocDatabase::findQmlModule(const QString& name, bool javaScript) + Find the QML module node named \a name and return a pointer + to it. If a matching node is not found, add a new QML module + node named \a name and return a pointer to that one. + + If \a javaScript is set, the return collection must be a + JavaScript module. + + If a new QML or JavaScript module node is added, its parent + is the tree root, and the new node is marked \e{not seen}. + */ + +/*! \fn CollectionNode* QDocDatabase::addGroup(const QString& name) + Looks up the group named \a name in the primary tree. If + a match is found, a pointer to the node is returned. + Otherwise, a new group node named \a name is created and + inserted into the collection, and the pointer to that node + is returned. + */ + +/*! \fn CollectionNode* QDocDatabase::addModule(const QString& name) + Looks up the module named \a name in the primary tree. If + a match is found, a pointer to the node is returned. + Otherwise, a new module node named \a name is created and + inserted into the collection, and the pointer to that node + is returned. + */ + +/*! \fn CollectionNode* QDocDatabase::addQmlModule(const QString& name) + Looks up the QML module named \a name in the primary tree. + If a match is found, a pointer to the node is returned. + Otherwise, a new QML module node named \a name is created + and inserted into the collection, and the pointer to that + node is returned. + */ + +/*! \fn CollectionNode* QDocDatabase::addJsModule(const QString& name) + Looks up the JavaScript module named \a name in the primary + tree. If a match is found, a pointer to the node is returned. + Otherwise, a new JavaScript module node named \a name is + created and inserted into the collection, and the pointer to + that node is returned. + */ + +/*! \fn CollectionNode* QDocDatabase::addToGroup(const QString& name, Node* node) + Looks up the group node named \a name in the collection + of all group nodes. If a match is not found, a new group + node named \a name is created and inserted into the collection. + Then append \a node to the group's members list, and append the + group node to the member list of the \a node. The parent of the + \a node is not changed by this function. Returns a pointer to + the group node. + */ + +/*! \fn CollectionNode* QDocDatabase::addToModule(const QString& name, Node* node) + Looks up the module node named \a name in the collection + of all module nodes. If a match is not found, a new module + node named \a name is created and inserted into the collection. + Then append \a node to the module's members list. The parent of + \a node is not changed by this function. Returns the module node. + */ + +/*! \fn Collection* QDocDatabase::addToQmlModule(const QString& name, Node* node) + Looks up the QML module named \a name. If it isn't there, + create it. Then append \a node to the QML module's member + list. The parent of \a node is not changed by this function. + */ + +/*! \fn Collection* QDocDatabase::addToJsModule(const QString& name, Node* node) + Looks up the JavaScript module named \a name. If it isn't there, + create it. Then append \a node to the JavaScript module's member + list. The parent of \a node is not changed by this function. + */ + +/*! + Looks up the QML type node identified by the qualified Qml + type \a name and returns a pointer to the QML type node. + */ +QmlTypeNode* QDocDatabase::findQmlType(const QString& name) +{ + QmlTypeNode* qcn = forest_.lookupQmlType(name); + if (qcn) + return qcn; + return 0; +} + +/*! + Looks up the QML type node identified by the Qml module id + \a qmid and QML type \a name and returns a pointer to the + QML type node. The key is \a qmid + "::" + \a name. + + If the QML module id is empty, it looks up the QML type by + \a name only. + */ +QmlTypeNode* QDocDatabase::findQmlType(const QString& qmid, const QString& name) +{ + if (!qmid.isEmpty()) { + QString t = qmid + "::" + name; + QmlTypeNode* qcn = forest_.lookupQmlType(t); + if (qcn) + return qcn; + } + + QStringList path(name); + Node* n = forest_.findNodeByNameAndType(path, Node::QmlType); + if (n && (n->isQmlType() || n->isJsType())) + return static_cast<QmlTypeNode*>(n); + return 0; +} + +/*! + Looks up the QML type node identified by the Qml module id + constructed from the strings in the \a import record and the + QML type \a name and returns a pointer to the QML type node. + If a QML type node is not found, 0 is returned. + */ +QmlTypeNode* QDocDatabase::findQmlType(const ImportRec& import, const QString& name) +{ + if (!import.isEmpty()) { + QStringList dotSplit; + dotSplit = name.split(QLatin1Char('.')); + QString qmName; + if (import.importUri_.isEmpty()) + qmName = import.name_; + else + qmName = import.importUri_; + for (int i=0; i<dotSplit.size(); ++i) { + QString qualifiedName = qmName + "::" + dotSplit[i]; + QmlTypeNode* qcn = forest_.lookupQmlType(qualifiedName); + if (qcn) + return qcn; + } + } + return 0; +} + +/*! + This function calls a set of functions for each tree in the + forest that has not already been analyzed. In this way, when + running qdoc in \e singleExec mode, each tree is analyzed in + turn, and its classes and types are added to the appropriate + node maps. + */ +void QDocDatabase::processForest() +{ + Tree* t = forest_.firstTree(); + while (t) { + findAllClasses(t->root()); + findAllFunctions(t->root()); + findAllObsoleteThings(t->root()); + findAllLegaleseTexts(t->root()); + findAllSince(t->root()); + t->setTreeHasBeenAnalyzed(); + t = forest_.nextTree(); + } + resolveNamespaces(); +} + +/*! + This function calls \a func for each tree in the forest, + but only if Tree::treeHasBeenAnalyzed() returns false for + the tree. In this way, when running qdoc in \e singleExec + mode, each tree is analyzed in turn, and its classes and + types are added to the appropriate node maps. + */ +void QDocDatabase::processForest(void (QDocDatabase::*func) (Aggregate*)) +{ + Tree* t = forest_.firstTree(); + while (t) { + if (!t->treeHasBeenAnalyzed()) { + (this->*(func))(t->root()); + } + t = forest_.nextTree(); + } +} + +/*! + Constructs the collection of legalese texts, if it has not + already been constructed and returns a reference to it. + */ +TextToNodeMap& QDocDatabase::getLegaleseTexts() +{ + if (legaleseTexts_.isEmpty()) + processForest(&QDocDatabase::findAllLegaleseTexts); + return legaleseTexts_; +} + +/*! + Construct the data structures for obsolete things, if they + have not already been constructed. Returns a reference to + the map of C++ classes with obsolete members. + */ +NodeMultiMap& QDocDatabase::getClassesWithObsoleteMembers() +{ + if (obsoleteClasses_.isEmpty() && obsoleteQmlTypes_.isEmpty()) + processForest(&QDocDatabase::findAllObsoleteThings); + return classesWithObsoleteMembers_; +} + +/*! + Construct the data structures for obsolete things, if they + have not already been constructed. Returns a reference to + the map of obsolete QML types. + */ +NodeMultiMap& QDocDatabase::getObsoleteQmlTypes() +{ + if (obsoleteClasses_.isEmpty() && obsoleteQmlTypes_.isEmpty()) + processForest(&QDocDatabase::findAllObsoleteThings); + return obsoleteQmlTypes_; +} + +/*! + Construct the data structures for obsolete things, if they + have not already been constructed. Returns a reference to + the map of QML types with obsolete members. + */ +NodeMultiMap& QDocDatabase::getQmlTypesWithObsoleteMembers() +{ + if (obsoleteClasses_.isEmpty() && obsoleteQmlTypes_.isEmpty()) + processForest(&QDocDatabase::findAllObsoleteThings); + return qmlTypesWithObsoleteMembers_; +} + +/*! \fn NodeMultiMap& QDocDatabase::getNamespaces() + Returns a reference to the map of all namespace nodes. + This function must not be called in the -prepare phase. + */ + +/*! + Construct the data structures for QML basic types, if they + have not already been constructed. Returns a reference to + the map of QML basic types. + */ +NodeMultiMap& QDocDatabase::getQmlBasicTypes() +{ + if (cppClasses_.isEmpty() && qmlBasicTypes_.isEmpty()) + processForest(&QDocDatabase::findAllClasses); + return qmlBasicTypes_; +} + +/*! + Construct the data structures for obsolete things, if they + have not already been constructed. Returns a reference to + the multimap of QML types. + */ +NodeMultiMap& QDocDatabase::getQmlTypes() +{ + if (cppClasses_.isEmpty() && qmlTypes_.isEmpty()) + processForest(&QDocDatabase::findAllClasses); + return qmlTypes_; +} + +/*! + Construct the data structures for obsolete things, if they + have not already been constructed. Returns a reference to + the map of obsolete C++ clases. + */ +NodeMultiMap& QDocDatabase::getObsoleteClasses() +{ + if (obsoleteClasses_.isEmpty() && obsoleteQmlTypes_.isEmpty()) + processForest(&QDocDatabase::findAllObsoleteThings); + return obsoleteClasses_; +} + +/*! + Construct the C++ class data structures, if they have not + already been constructed. Returns a reference to the map + of all C++ classes. + */ +NodeMultiMap& QDocDatabase::getCppClasses() +{ + if (cppClasses_.isEmpty() && qmlTypes_.isEmpty()) + processForest(&QDocDatabase::findAllClasses); + return cppClasses_; +} + +/*! + Finds all the C++ class nodes and QML type nodes and + sorts them into maps. + */ +void QDocDatabase::findAllClasses(Aggregate* node) +{ + NodeList::const_iterator c = node->childNodes().constBegin(); + while (c != node->childNodes().constEnd()) { + if ((*c)->access() != Node::Private && (!(*c)->isInternal() || showInternal_) && + (*c)->tree()->camelCaseModuleName() != QString("QDoc")) { + if ((*c)->type() == Node::Class && !(*c)->doc().isEmpty()) { + QString className = (*c)->name(); + if ((*c)->parent() && + (*c)->parent()->type() == Node::Namespace && + !(*c)->parent()->name().isEmpty()) + className = (*c)->parent()->name()+"::"+className; + + cppClasses_.insert(className, *c); + } + else if (((*c)->isQmlType() || (*c)->isQmlBasicType() || + (*c)->isJsType() || (*c)->isJsBasicType()) && !(*c)->doc().isEmpty()) { + QString qmlTypeName = (*c)->name(); + if (qmlTypeName.startsWith(QLatin1String("QML:"))) + qmlTypes_.insert(qmlTypeName.mid(4),*c); + else + qmlTypes_.insert(qmlTypeName,*c); + + //also add to the QML basic type map + if ((*c)->isQmlBasicType() || (*c)->isJsType()) + qmlBasicTypes_.insert(qmlTypeName,*c); + } + else if ((*c)->isAggregate()) { + findAllClasses(static_cast<Aggregate*>(*c)); + } + } + ++c; + } +} + +/*! + Construct the function index data structure and return it. + This data structure is used to output the function index page. + */ +NodeMapMap& QDocDatabase::getFunctionIndex() +{ + processForest(&QDocDatabase::findAllFunctions); + return funcIndex_; +} + +/*! + Finds all the function nodes + */ +void QDocDatabase::findAllFunctions(Aggregate* node) +{ + NodeList::ConstIterator c = node->childNodes().constBegin(); + while (c != node->childNodes().constEnd()) { + if ((*c)->access() != Node::Private) { + if ((*c)->isAggregate()) { + findAllFunctions(static_cast<Aggregate*>(*c)); + } + else if ((*c)->type() == Node::Function) { + const FunctionNode* func = static_cast<const FunctionNode*>(*c); + if ((func->status() > Node::Obsolete) && + !func->isInternal() && + (func->metaness() != FunctionNode::Ctor) && + (func->metaness() != FunctionNode::Dtor)) { + funcIndex_[(*c)->name()].insert((*c)->parent()->fullDocumentName(), *c); + } + } + } + ++c; + } +} + +/*! + Finds all the nodes containing legalese text and puts them + in a map. + */ +void QDocDatabase::findAllLegaleseTexts(Aggregate* node) +{ + NodeList::ConstIterator c = node->childNodes().constBegin(); + while (c != node->childNodes().constEnd()) { + if ((*c)->access() != Node::Private) { + if (!(*c)->doc().legaleseText().isEmpty()) + legaleseTexts_.insertMulti((*c)->doc().legaleseText(), *c); + if ((*c)->isAggregate()) + findAllLegaleseTexts(static_cast<Aggregate *>(*c)); + } + ++c; + } +} + +/*! + Finds all the namespace nodes and puts them in an index. + */ +void QDocDatabase::findAllNamespaces(Aggregate* node) +{ + NodeList::ConstIterator c = node->childNodes().constBegin(); + while (c != node->childNodes().constEnd()) { + if ((*c)->access() != Node::Private || (*c)->isNamespace()) { + if ((*c)->isAggregate()) { + findAllNamespaces(static_cast<Aggregate *>(*c)); + if ((*c)->isNamespace()) { + // Ensure that the namespace's name is not empty (the root + // namespace has no name). + if (!(*c)->name().isEmpty()) { + nmm_.insert((*c)->name(), *c); + } + } + } + } + ++c; + } +} + +/*! + Finds all nodes with status = Obsolete and sorts them into + maps. They can be C++ classes, QML types, or they can be + functions, enum types, typedefs, methods, etc. + */ +void QDocDatabase::findAllObsoleteThings(Aggregate* node) +{ + NodeList::const_iterator c = node->childNodes().constBegin(); + while (c != node->childNodes().constEnd()) { + if ((*c)->access() != Node::Private) { + QString name = (*c)->name(); + if ((*c)->status() == Node::Obsolete) { + if ((*c)->type() == Node::Class) { + if ((*c)->parent() && (*c)->parent()->type() == Node::Namespace && + !(*c)->parent()->name().isEmpty()) + name = (*c)->parent()->name() + "::" + name; + obsoleteClasses_.insert(name, *c); + } + else if ((*c)->isQmlType() || (*c)->isJsType()) { + if (name.startsWith(QLatin1String("QML:"))) + name = name.mid(4); + name = (*c)->logicalModuleName() + "::" + name; + obsoleteQmlTypes_.insert(name,*c); + } + } + else if ((*c)->type() == Node::Class) { + Aggregate* n = static_cast<Aggregate*>(*c); + bool inserted = false; + NodeList::const_iterator p = n->childNodes().constBegin(); + while (p != n->childNodes().constEnd()) { + if ((*p)->access() != Node::Private) { + switch ((*p)->type()) { + case Node::Enum: + case Node::Typedef: + case Node::Function: + case Node::Property: + case Node::Variable: + if ((*p)->status() == Node::Obsolete) { + if ((*c)->parent() && (*c)->parent()->type() == Node::Namespace && + !(*c)->parent()->name().isEmpty()) + name = (*c)->parent()->name() + "::" + name; + classesWithObsoleteMembers_.insert(name, *c); + inserted = true; + } + break; + default: + break; + } + } + if (inserted) + break; + ++p; + } + } + else if ((*c)->isQmlType() || (*c)->isJsType()) { + Aggregate* n = static_cast<Aggregate*>(*c); + bool inserted = false; + NodeList::const_iterator p = n->childNodes().constBegin(); + while (p != n->childNodes().constEnd()) { + if ((*p)->access() != Node::Private) { + switch ((*c)->type()) { + case Node::QmlProperty: + case Node::QmlSignal: + case Node::QmlSignalHandler: + case Node::QmlMethod: + if ((*c)->parent()) { + Node* parent = (*c)->parent(); + if ((parent->isQmlPropertyGroup() || + parent->isJsPropertyGroup()) && parent->parent()) + parent = parent->parent(); + if (parent && (parent->isQmlType() || parent->isJsType()) && + !parent->name().isEmpty()) + name = parent->name() + "::" + name; + } + qmlTypesWithObsoleteMembers_.insert(name,*c); + inserted = true; + break; + default: + break; + } + } + if (inserted) + break; + ++p; + } + } + else if ((*c)->isAggregate()) { + findAllObsoleteThings(static_cast<Aggregate*>(*c)); + } + } + ++c; + } +} + +/*! + Finds all the nodes where a \e{since} command appeared in the + qdoc comment and sorts them into maps according to the kind of + node. + + This function is used for generating the "New Classes... in x.y" + section on the \e{What's New in Qt x.y} page. + */ +void QDocDatabase::findAllSince(Aggregate* node) +{ + NodeList::const_iterator child = node->childNodes().constBegin(); + while (child != node->childNodes().constEnd()) { + QString sinceString = (*child)->since(); + // Insert a new entry into each map for each new since string found. + if (((*child)->access() != Node::Private) && !sinceString.isEmpty()) { + NodeMultiMapMap::iterator nsmap = newSinceMaps_.find(sinceString); + if (nsmap == newSinceMaps_.end()) + nsmap = newSinceMaps_.insert(sinceString,NodeMultiMap()); + + NodeMapMap::iterator ncmap = newClassMaps_.find(sinceString); + if (ncmap == newClassMaps_.end()) + ncmap = newClassMaps_.insert(sinceString,NodeMap()); + + NodeMapMap::iterator nqcmap = newQmlTypeMaps_.find(sinceString); + if (nqcmap == newQmlTypeMaps_.end()) + nqcmap = newQmlTypeMaps_.insert(sinceString,NodeMap()); + + if ((*child)->type() == Node::Function) { + // Insert functions into the general since map. + FunctionNode *func = static_cast<FunctionNode *>(*child); + if ((func->status() > Node::Obsolete) && + (func->metaness() != FunctionNode::Ctor) && + (func->metaness() != FunctionNode::Dtor)) { + nsmap.value().insert(func->name(),(*child)); + } + } + else { + if ((*child)->type() == Node::Class) { + // Insert classes into the since and class maps. + QString className = (*child)->name(); + if ((*child)->parent() && !(*child)->parent()->name().isEmpty()) { + className = (*child)->parent()->name()+"::"+className; + } + nsmap.value().insert(className,(*child)); + ncmap.value().insert(className,(*child)); + } + else if ((*child)->isQmlType() || (*child)->isJsType()) { + // Insert QML elements into the since and element maps. + QString className = (*child)->name(); + if ((*child)->parent() && !(*child)->parent()->name().isEmpty()) { + className = (*child)->parent()->name()+"::"+className; + } + nsmap.value().insert(className,(*child)); + nqcmap.value().insert(className,(*child)); + } + else if ((*child)->isQmlProperty() || (*child)->isJsProperty()) { + // Insert QML properties into the since map. + QString propertyName = (*child)->name(); + nsmap.value().insert(propertyName,(*child)); + } + else { + // Insert external documents into the general since map. + QString name = (*child)->name(); + if ((*child)->parent() && !(*child)->parent()->name().isEmpty()) { + name = (*child)->parent()->name()+"::"+name; + } + nsmap.value().insert(name,(*child)); + } + } + } + // Recursively find child nodes with since commands. + if ((*child)->isAggregate()) + findAllSince(static_cast<Aggregate *>(*child)); + + ++child; + } +} + +/*! + Find the \a key in the map of new class maps, and return a + reference to the value, which is a NodeMap. If \a key is not + found, return a reference to an empty NodeMap. + */ +const NodeMap& QDocDatabase::getClassMap(const QString& key) +{ + if (newSinceMaps_.isEmpty() && newClassMaps_.isEmpty() && newQmlTypeMaps_.isEmpty()) + processForest(&QDocDatabase::findAllSince); + NodeMapMap::const_iterator i = newClassMaps_.constFind(key); + if (i != newClassMaps_.constEnd()) + return i.value(); + return emptyNodeMap_; +} + +/*! + Find the \a key in the map of new QML type maps, and return a + reference to the value, which is a NodeMap. If the \a key is not + found, return a reference to an empty NodeMap. + */ +const NodeMap& QDocDatabase::getQmlTypeMap(const QString& key) +{ + if (newSinceMaps_.isEmpty() && newClassMaps_.isEmpty() && newQmlTypeMaps_.isEmpty()) + processForest(&QDocDatabase::findAllSince); + NodeMapMap::const_iterator i = newQmlTypeMaps_.constFind(key); + if (i != newQmlTypeMaps_.constEnd()) + return i.value(); + return emptyNodeMap_; +} + +/*! + Find the \a key in the map of new \e {since} maps, and return + a reference to the value, which is a NodeMultiMap. If \a key + is not found, return a reference to an empty NodeMultiMap. + */ +const NodeMap& QDocDatabase::getSinceMap(const QString& key) +{ + if (newSinceMaps_.isEmpty() && newClassMaps_.isEmpty() && newQmlTypeMaps_.isEmpty()) + processForest(&QDocDatabase::findAllSince); + NodeMultiMapMap::const_iterator i = newSinceMaps_.constFind(key); + if (i != newSinceMaps_.constEnd()) + return i.value(); + return emptyNodeMultiMap_; +} + +/*! + Performs several housekeeping algorithms that create + certain data structures and resolve lots of links, prior + to generating documentation. + */ +void QDocDatabase::resolveIssues() { + primaryTreeRoot()->normalizeOverloads(); + fixInheritance(); + resolveProperties(); + primaryTreeRoot()->makeUndocumentedChildrenInternal(); + resolveQmlInheritance(primaryTreeRoot()); + primaryTree()->resolveTargets(primaryTreeRoot()); + primaryTree()->resolveCppToQmlLinks(); + if (!Generator::singleExec()) { + QDocIndexFiles::qdocIndexFiles()->resolveRelates(); + QDocIndexFiles::destroyQDocIndexFiles(); + } + if (Generator::generating()) + resolveNamespaces(); +} + +void QDocDatabase::resolveStuff() +{ + primaryTree()->resolveInheritance(); + resolveQmlInheritance(primaryTreeRoot()); + //primaryTree()->resolveTargets(primaryTreeRoot()); + primaryTree()->resolveCppToQmlLinks(); + primaryTree()->resolveUsingClauses(); + resolveNamespaces(); +} + +/*! + */ +void QDocDatabase::resolveNamespaces() +{ + if (!namespaceIndex_.isEmpty()) + return; + Tree* t = forest_.firstTree(); + while (t) { + findAllNamespaces(t->root()); + t = forest_.nextTree(); + } + QList<QString> keys = nmm_.uniqueKeys(); + foreach (const QString &s, keys) { + NamespaceNode* ns = 0; + QList<Node*> nodes = nmm_.values(s); + int count = nmm_.remove(s); + if (count > 1) { + foreach (Node* n, nodes) { + // Treat public namespaces from index trees as 'seen' + if (n->isNamespace() && (n->wasSeen() || (n->isIndexNode() && n->access() == Node::Public))) { + ns = static_cast<NamespaceNode*>(n); + ns->markSeen(); + break; + } + } + } + else if (count == 1) + ns = static_cast<NamespaceNode*>(nodes.at(0)); + if (ns && ns->wasSeen()) { + if (count >1) { + foreach (Node* n, nodes) { + if (n->isNamespace()) { + NamespaceNode* NS = static_cast<NamespaceNode*>(n); + if ((NS != ns) && !NS->childNodes().isEmpty()) { + const NodeList& children = NS->childNodes(); + int i = children.size() - 1; + while (i >= 0) { + Node* child = children.at(i--); + if (!child) + continue; + if (!child->isClass() + && !child->isQmlType() + && !child->isNamespace()) { + NS->removeChild(child); + ns->addChild(child); + } + else { + NS->setStatus(Node::Intermediate); + NS->setAccess(Node::Public); + ns->addOrphan(child); + } + } + } + } + } + } + namespaceIndex_.insert(ns->name(), ns); + } + } +} +#if 0 +/*! + */ +const Node* QDocDatabase::findFunctionNode(const QString& target, + const Node* relative, + Node::Genus genus) +{ + return forest_.findFunctionNode(target, relative, genus); +} +#endif +/*! + This function is called for autolinking to a \a type, + which could be a function return type or a parameter + type. The tree node that represents the \a type is + returned. All the trees are searched until a match is + found. When searching the primary tree, the search + begins at \a relative and proceeds up the parent chain. + When searching the index trees, the search begins at the + root. + */ +const Node* QDocDatabase::findTypeNode(const QString& type, const Node* relative) +{ + QStringList path = type.split("::"); + if ((path.size() == 1) && (path.at(0)[0].isLower() || path.at(0) == QString("T"))) { + NodeMap::iterator i = typeNodeMap_.find(path.at(0)); + if (i != typeNodeMap_.end()) + return i.value(); + } + return forest_.findTypeNode(path, relative); +} + +/*! + Finds the node that will generate the documentation that + contains the \a target and returns a pointer to it. + + Can this be improved by using the target map in Tree? + */ +const Node* QDocDatabase::findNodeForTarget(const QString& target, const Node* relative) +{ + const Node* node = 0; + if (target.isEmpty()) + node = relative; + else if (target.endsWith(".html")) + node = findNodeByNameAndType(QStringList(target), Node::Document); + else { + QStringList path = target.split("::"); + int flags = SearchBaseClasses | SearchEnumValues; // | NonFunction; + foreach (Tree* t, searchOrder()) { + const Node* n = t->findNode(path, relative, flags, Node::DontCare); + if (n) + return n; + relative = 0; + } + node = findDocumentNodeByTitle(target); + } + return node; +} + +/*! + For each QML Type node in the tree beginning at \a root, + if it has a QML base type name but its QML base type node + pointer is 0, use the QML base type name to look up the + base type node. If the node is found in the tree, set the + node's QML base type node pointer. + */ +void QDocDatabase::resolveQmlInheritance(Aggregate* root) +{ + NodeMap previousSearches; + // Do we need recursion? + foreach (Node* child, root->childNodes()) { + if (child->isQmlType() || child->isJsType()) { + QmlTypeNode* qcn = static_cast<QmlTypeNode*>(child); + if (qcn->qmlBaseNodeNotSet() && !qcn->qmlBaseName().isEmpty()) { + QmlTypeNode* bqcn = static_cast<QmlTypeNode*>(previousSearches.value(qcn->qmlBaseName())); + if (bqcn) + qcn->setQmlBaseNode(bqcn); + else { + if (!qcn->importList().isEmpty()) { + const ImportList& imports = qcn->importList(); + for (int i=0; i<imports.size(); ++i) { + bqcn = findQmlType(imports[i], qcn->qmlBaseName()); + if (bqcn) + break; + } + } + if (bqcn == 0) { + bqcn = findQmlType(QString(), qcn->qmlBaseName()); + } + if (bqcn) { + qcn->setQmlBaseNode(bqcn); + previousSearches.insert(qcn->qmlBaseName(), bqcn); + } +#if 0 + else { + qDebug() << "Temporary error message (ignore): UNABLE to resolve QML base type:" + << qcn->qmlBaseName() << "for QML type:" << qcn->name(); + } +#endif + } + } + } + } +} + +/*! + Generates a tag file and writes it to \a name. + */ +void QDocDatabase::generateTagFile(const QString& name, Generator* g) +{ + if (!name.isEmpty()) { + QDocTagFiles::qdocTagFiles()->generateTagFile(name, g); + QDocTagFiles::destroyQDocTagFiles(); + } +} + +/*! + Reads and parses the qdoc index files listed in \a t. + */ +void QDocDatabase::readIndexes(const QStringList& t) +{ + QStringList indexFiles; + foreach (const QString& f, t) { + QString fn = f.mid(f.lastIndexOf(QChar('/'))+1); + if (!isLoaded(fn)) + indexFiles << f; + else + qDebug() << "This index file is already in memory:" << f; + } + QDocIndexFiles::qdocIndexFiles()->readIndexes(indexFiles); +} + +/*! + Generates a qdoc index file and write it to \a fileName. The + index file is generated with the parameters \a url, \a title, + \a g, and \a generateInternalNodes. + */ +void QDocDatabase::generateIndex(const QString& fileName, + const QString& url, + const QString& title, + Generator* g, + bool generateInternalNodes) +{ + QString t = fileName.mid(fileName.lastIndexOf(QChar('/'))+1); + primaryTree()->setIndexFileName(t); + QDocIndexFiles::qdocIndexFiles()->generateIndex(fileName, url, title, g, generateInternalNodes); + QDocIndexFiles::destroyQDocIndexFiles(); +} + +/*! + If there are open namespaces, search for the function node + having the same function name as the \a clone node in each + open namespace. The \a parentPath is a portion of the path + name provided with the function name at the point of + reference. \a parentPath is usually a class name. Return + the pointer to the function node if one is found in an + open namespace. Otherwise return 0. + + This open namespace concept is of dubious value and might + be removed. + */ +FunctionNode* QDocDatabase::findNodeInOpenNamespace(const QStringList& parentPath, + const FunctionNode* clone) +{ + FunctionNode* fn = 0; + if (!openNamespaces_.isEmpty()) { + foreach (const QString& t, openNamespaces_) { + QStringList path = t.split("::") + parentPath; + fn = findFunctionNode(path, clone); + if (fn) + break; + } + } + return fn; +} + +/*! + Find a node of the specified \a type that is reached with + the specified \a path qualified with the name of one of the + open namespaces (might not be any open ones). If the node + is found in an open namespace, prefix \a path with the name + of the open namespace and "::" and return a pointer to the + node. Othewrwise return 0. + + This function only searches in the current primary tree. + */ +Node* QDocDatabase::findNodeInOpenNamespace(QStringList& path, Node::NodeType type) +{ + if (path.isEmpty()) + return 0; + Node* n = 0; + if (!openNamespaces_.isEmpty()) { + foreach (const QString& t, openNamespaces_) { + QStringList p; + if (t != path[0]) + p = t.split("::") + path; + else + p = path; + n = primaryTree()->findNodeByNameAndType(p, type); + if (n) { + path = p; + break; + } + } + } + return n; +} + +/*! + Finds all the collection nodes of the specified \a genus + into the collection node map \a cnm. Nodes that match the + \a relative node are not included. + */ +void QDocDatabase::mergeCollections(Node::Genus genus, CNMap& cnm, const Node* relative) +{ + cnm.clear(); + CNMultiMap cnmm; + foreach (Tree* t, searchOrder()) { + CNMap* m = t->getCollectionMap(genus); + if (m && !m->isEmpty()) { + CNMap::const_iterator i = m->cbegin(); + while (i != m->cend()) { + if (!i.value()->isInternal()) + cnmm.insert(i.key(), i.value()); + ++i; + } + } + } + if (cnmm.isEmpty()) + return; + QRegExp singleDigit("\\b([0-9])\\b"); + QStringList keys = cnmm.uniqueKeys(); + foreach (const QString &key, keys) { + QList<CollectionNode*> values = cnmm.values(key); + CollectionNode* n = 0; + foreach (CollectionNode* v, values) { + if (v && v->wasSeen() && (v != relative)) { + n = v; + break; + } + } + if (n) { + if (values.size() > 1) { + foreach (CollectionNode* v, values) { + if (v != n) { + // Allow multiple (major) versions of QML/JS modules + if (n->type() == Node::QmlModule + && n->logicalModuleIdentifier() != v->logicalModuleIdentifier()) { + if (v->wasSeen() && v != relative && !v->members().isEmpty()) + cnm.insert(v->fullTitle().toLower(), v); + continue; + } + foreach (Node* t, v->members()) + n->addMember(t); + } + } + } + if (!n->members().isEmpty()) { + QString sortKey = n->fullTitle().toLower(); + if (sortKey.startsWith("the ")) + sortKey.remove(0, 4); + sortKey.replace(singleDigit, "0\\1"); + cnm.insert(sortKey, n); + } + } + } +} + +/*! + Finds all the collection nodes with the same name + and genus as \a c and merges their members into the + members list of \a c. + + For QML and JS modules, the merge is done only if + the module identifier matches between the nodes, to avoid + merging modules with different (major) versions. + */ +void QDocDatabase::mergeCollections(CollectionNode* c) +{ + foreach (Tree* t, searchOrder()) { + CollectionNode* cn = t->getCollection(c->name(), c->genus()); + if (cn && cn != c) { + if (cn->type() == Node::QmlModule + && cn->logicalModuleIdentifier() != c->logicalModuleIdentifier()) + continue; + foreach (Node* n, cn->members()) + c->addMember(n); + } + } +} + +/*! + Searches for the node that matches the path in \a atom. The + \a relative node is used if the first leg of the path is + empty, i.e. if the path begins with a hashtag. The function + also sets \a ref if there remains an unused leg in the path + after the node is found. The node is returned as well as the + \a ref. If the returned node pointer is null, \a ref is not + valid. + */ +const Node* QDocDatabase::findNodeForAtom(const Atom* a, const Node* relative, QString& ref) +{ + const Node* node = 0; + + Atom* atom = const_cast<Atom*>(a); + QStringList targetPath = atom->string().split(QLatin1Char('#')); + QString first = targetPath.first().trimmed(); + if (Generator::debugging()) + qDebug() << " first:" << first; + + Tree* domain = 0; + Node::Genus genus = Node::DontCare; + // Reserved for future use + //Node::NodeType goal = Node::NoType; + + if (atom->isLinkAtom()) { + domain = atom->domain(); + genus = atom->genus(); + // Reserved for future use + //goal = atom->goal(); + } + + if (first.isEmpty()) + node = relative; // search for a target on the current page. + else if (domain) { + if (first.endsWith(".html")) + node = domain->findNodeByNameAndType(QStringList(first), Node::Document); + else if (first.endsWith(QChar(')'))) { + QString function, params; + int length = first.length(); + int position = first.lastIndexOf(QChar('(')); + params = first.mid(position+1, length-position-2); + function = first.left(position); + node = domain->findFunctionNode(function, params, 0, genus); + } + if (!node) { + int flags = SearchBaseClasses | SearchEnumValues; + QStringList nodePath = first.split("::"); + QString target; + targetPath.removeFirst(); + if (!targetPath.isEmpty()) + target = targetPath.takeFirst(); + if (relative && relative->tree()->physicalModuleName() != domain->physicalModuleName()) + relative = 0; + return domain->findNodeForTarget(nodePath, target, relative, flags, genus, ref); + } + } + else { + if (first.endsWith(".html")) + node = findNodeByNameAndType(QStringList(first), Node::Document); + else if (first.endsWith(QChar(')'))) { + node = findFunctionNode(first, relative, genus); + if (Generator::debugging()) + qDebug() << " node:" << node; + } + if (!node) + return findNodeForTarget(targetPath, relative, genus, ref); + } + + if (node && ref.isEmpty()) { + if (!node->url().isEmpty()) + return node; + targetPath.removeFirst(); + if (!targetPath.isEmpty()) { + ref = node->root()->tree()->getRef(targetPath.first(), node); + if (ref.isEmpty()) + node = 0; + } + } + return node; +} + +QT_END_NAMESPACE diff --git a/src/qdoc/qdocdatabase.h b/src/qdoc/qdocdatabase.h new file mode 100644 index 000000000..80a92af4e --- /dev/null +++ b/src/qdoc/qdocdatabase.h @@ -0,0 +1,455 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDOCDATABASE_H +#define QDOCDATABASE_H + +#include <qstring.h> +#include <qmap.h> +#include "tree.h" +#include "config.h" +#include "text.h" +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +typedef QMap<QString, NodeMap> NodeMapMap; +typedef QMap<QString, NodeMultiMap> NodeMultiMapMap; +typedef QMultiMap<QString, Node*> QDocMultiMap; +typedef QMap<Text, const Node*> TextToNodeMap; +typedef QList<CollectionNode*> CollectionList; + +class Atom; +class Generator; +class QDocDatabase; + +enum FindFlag { + SearchBaseClasses = 0x1, + SearchEnumValues = 0x2, + NonFunction = 0x4 +}; + +class QDocForest +{ + public: + friend class QDocDatabase; + QDocForest(QDocDatabase* qdb) + : qdb_(qdb), primaryTree_(0), currentIndex_(0) { } + ~QDocForest(); + + NamespaceNode* firstRoot(); + NamespaceNode* nextRoot(); + Tree* firstTree(); + Tree* nextTree(); + Tree* primaryTree() { return primaryTree_; } + Tree* findTree(const QString& t) { return forest_.value(t); } + QStringList keys() { + return forest_.keys(); + } + NamespaceNode* primaryTreeRoot() { return (primaryTree_ ? primaryTree_->root() : 0); } + bool isEmpty() { return searchOrder().isEmpty(); } + bool done() { return (currentIndex_ >= searchOrder().size()); } + const QVector<Tree*>& searchOrder(); + const QVector<Tree*>& indexSearchOrder(); + void setSearchOrder(QStringList& t); + bool isLoaded(const QString& fn) { + foreach (Tree* t, searchOrder()) { + if (fn == t->indexFileName()) + return true; + } + return false; + } + + const Node* findNode(const QStringList& path, + const Node* relative, + int findFlags, + Node::Genus genus) { + foreach (Tree* t, searchOrder()) { + const Node* n = t->findNode(path, relative, findFlags, genus); + if (n) + return n; + relative = 0; + } + return 0; + } + + Node* findNodeByNameAndType(const QStringList& path, Node::NodeType type) { + foreach (Tree* t, searchOrder()) { + Node* n = t->findNodeByNameAndType(path, type); + if (n) + return n; + } + return 0; + } + + ClassNode* findClassNode(const QStringList& path) { + foreach (Tree* t, searchOrder()) { + ClassNode* n = t->findClassNode(path); + if (n) + return n; + } + return 0; + } + + Node* findNodeForInclude(const QStringList& path) { + foreach (Tree* t, searchOrder()) { + Node* n = t->findNodeForInclude(path); + if (n) + return n; + } + return 0; + } + + Aggregate* findRelatesNode(const QStringList& path) { + foreach (Tree* t, searchOrder()) { + Aggregate* n = t->findRelatesNode(path); + if (n) + return n; + } + return 0; + } + + const Node* findFunctionNode(const QString& target, + const Node* relative, + Node::Genus genus); + + const Node* findNodeForTarget(QStringList& targetPath, + const Node* relative, + Node::Genus genus, + QString& ref); + + const Node* findTypeNode(const QStringList& path, const Node* relative) + { + foreach (Tree* t, searchOrder()) { + int flags = SearchBaseClasses | SearchEnumValues | NonFunction; + const Node* n = t->findNode(path, relative, flags, Node::DontCare); + if (n) + return n; + relative = 0; + } + return 0; + } + + const DocumentNode* findDocumentNodeByTitle(const QString& title) + { + foreach (Tree* t, searchOrder()) { + const DocumentNode* n = t->findDocumentNodeByTitle(title); + if (n) + return n; + } + return 0; + } + + const CollectionNode* getCollectionNode(const QString& name, Node::Genus genus) + { + foreach (Tree* t, searchOrder()) { + const CollectionNode* cn = t->getCollection(name, genus); + if (cn) + return cn; + } + return 0; + } + + QmlTypeNode* lookupQmlType(const QString& name) + { + foreach (Tree* t, searchOrder()) { + QmlTypeNode* qcn = t->lookupQmlType(name); + if (qcn) + return qcn; + } + return 0; + } + void clearSearchOrder() { searchOrder_.clear(); } + void clearLinkCounts() + { + foreach (Tree* t, searchOrder()) + t->clearLinkCount(); + } + void printLinkCounts(const QString& project); + QString getLinkCounts(QStringList& strings, QVector<int>& counts); + + private: + void newPrimaryTree(const QString& module); + void setPrimaryTree(const QString& t); + NamespaceNode* newIndexTree(const QString& module); + + private: + QDocDatabase* qdb_; + Tree* primaryTree_; + int currentIndex_; + QMap<QString, Tree*> forest_; + QVector<Tree*> searchOrder_; + QVector<Tree*> indexSearchOrder_; + QVector<QString> moduleNames_; +}; + +class QDocDatabase +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::QDocDatabase) + + public: + static QDocDatabase* qdocDB(); + static void destroyQdocDB(); + ~QDocDatabase(); + + Tree* findTree(const QString& t) { return forest_.findTree(t); } + + const CNMap& groups() { return primaryTree()->groups(); } + const CNMap& modules() { return primaryTree()->modules(); } + const CNMap& qmlModules() { return primaryTree()->qmlModules(); } + const CNMap& jsModules() { return primaryTree()->jsModules(); } + + CollectionNode* addGroup(const QString& name) { return primaryTree()->addGroup(name); } + CollectionNode* addModule(const QString& name) { return primaryTree()->addModule(name); } + CollectionNode* addQmlModule(const QString& name) { return primaryTree()->addQmlModule(name); } + CollectionNode* addJsModule(const QString& name) { return primaryTree()->addJsModule(name); } + + CollectionNode* addToGroup(const QString& name, Node* node) { + return primaryTree()->addToGroup(name, node); + } + CollectionNode* addToModule(const QString& name, Node* node) { + return primaryTree()->addToModule(name, node); + } + CollectionNode* addToQmlModule(const QString& name, Node* node) { + return primaryTree()->addToQmlModule(name, node); + } + CollectionNode* addToJsModule(const QString& name, Node* node) { + return primaryTree()->addToJsModule(name, node); + } + + void addExampleNode(ExampleNode* n) { primaryTree()->addExampleNode(n); } + ExampleNodeMap& exampleNodeMap() { return primaryTree()->exampleNodeMap(); } + + QmlTypeNode* findQmlType(const QString& name); + QmlTypeNode* findQmlType(const QString& qmid, const QString& name); + QmlTypeNode* findQmlType(const ImportRec& import, const QString& name); + + private: + void findAllClasses(Aggregate *node); + void findAllFunctions(Aggregate *node); + void findAllLegaleseTexts(Aggregate *node); + void findAllNamespaces(Aggregate *node); + void findAllObsoleteThings(Aggregate* node); + void findAllSince(Aggregate *node); + + public: + /******************************************************************* + special collection access functions + ********************************************************************/ + NodeMultiMap& getCppClasses(); + NodeMultiMap& getObsoleteClasses(); + NodeMultiMap& getClassesWithObsoleteMembers(); + NodeMultiMap& getObsoleteQmlTypes(); + NodeMultiMap& getQmlTypesWithObsoleteMembers(); + NodeMultiMap& getNamespaces() { resolveNamespaces(); return namespaceIndex_; } + NodeMultiMap& getQmlBasicTypes(); + NodeMultiMap& getQmlTypes(); + NodeMapMap& getFunctionIndex(); + TextToNodeMap& getLegaleseTexts(); + const NodeMap& getClassMap(const QString& key); + const NodeMap& getQmlTypeMap(const QString& key); + const NodeMap& getSinceMap(const QString& key); + + /******************************************************************* + Many of these will be either eliminated or replaced. + ********************************************************************/ + void resolveInheritance() { primaryTree()->resolveInheritance(); } + void resolveQmlInheritance(Aggregate* root); + void resolveIssues(); + void resolveStuff(); + void fixInheritance() { primaryTree()->fixInheritance(); } + void resolveProperties() { primaryTree()->resolveProperties(); } + + void insertTarget(const QString& name, + const QString& title, + TargetRec::TargetType type, + Node* node, + int priority) { + primaryTree()->insertTarget(name, title, type, node, priority); + } + + /******************************************************************* + The functions declared below are called for the current tree only. + ********************************************************************/ + FunctionNode* findFunctionNode(const QStringList& parentPath, const FunctionNode* clone) { + return primaryTree()->findFunctionNode(parentPath, clone); + } + FunctionNode* findNodeInOpenNamespace(const QStringList& parentPath, const FunctionNode* clone); + Node* findNodeInOpenNamespace(QStringList& path, Node::NodeType type); + const Node* checkForCollision(const QString& name) { + return primaryTree()->checkForCollision(name); + } + /*******************************************************************/ + + /******************************************************************* + The functions declared below handle the parameters in '[' ']'. + ********************************************************************/ + const Node* findNodeForAtom(const Atom* atom, const Node* relative, QString& ref); + /*******************************************************************/ + + /******************************************************************* + The functions declared below are called for all trees. + ********************************************************************/ + ClassNode* findClassNode(const QStringList& path) { return forest_.findClassNode(path); } + Node* findNodeForInclude(const QStringList& path) { return forest_.findNodeForInclude(path); } + Aggregate* findRelatesNode(const QStringList& path) { return forest_.findRelatesNode(path); } + const Node* findFunctionNode(const QString& target, const Node* relative, Node::Genus genus) { + return forest_.findFunctionNode(target, relative, genus); + } + const Node* findTypeNode(const QString& type, const Node* relative); + const Node* findNodeForTarget(const QString& target, const Node* relative); + const DocumentNode* findDocumentNodeByTitle(const QString& title) { + return forest_.findDocumentNodeByTitle(title); + } + Node* findNodeByNameAndType(const QStringList& path, Node::NodeType type) { + return forest_.findNodeByNameAndType(path, type); + } + const CollectionNode* getCollectionNode(const QString& name, Node::Genus genus) { + return forest_.getCollectionNode(name, genus); + } + + private: + const Node* findNodeForTarget(QStringList& targetPath, + const Node* relative, + Node::Genus genus, + QString& ref) { + return forest_.findNodeForTarget(targetPath, relative, genus, ref); + } + + /*******************************************************************/ + public: + void addPropertyFunction(PropertyNode* property, + const QString& funcName, + PropertyNode::FunctionRole funcRole) { + primaryTree()->addPropertyFunction(property, funcName, funcRole); + } + + void setVersion(const QString& v) { version_ = v; } + QString version() const { return version_; } + + void generateTagFile(const QString& name, Generator* g); + void readIndexes(const QStringList& indexFiles); + void generateIndex(const QString& fileName, + const QString& url, + const QString& title, + Generator* g, + bool generateInternalNodes = false); + + void clearOpenNamespaces() { openNamespaces_.clear(); } + void insertOpenNamespace(const QString& path) { openNamespaces_.insert(path); } + void setShowInternal(bool value) { showInternal_ = value; } + void setSingleExec(bool value) { singleExec_ = value; } + void processForest(); + + // Try to make this function private. + QDocForest& forest() { return forest_; } + NamespaceNode* primaryTreeRoot() { return forest_.primaryTreeRoot(); } + void newPrimaryTree(const QString& module) { forest_.newPrimaryTree(module); } + void setPrimaryTree(const QString& t) { forest_.setPrimaryTree(t); } + NamespaceNode* newIndexTree(const QString& module) { return forest_.newIndexTree(module); } + const QVector<Tree*>& searchOrder() { return forest_.searchOrder(); } + void setLocalSearch() { forest_.searchOrder_ = QVector<Tree*>(1, primaryTree()); } + void setSearchOrder(const QVector<Tree*>& searchOrder) { forest_.searchOrder_ = searchOrder; } + void setSearchOrder(QStringList& t) { forest_.setSearchOrder(t); } + void mergeCollections(Node::Genus genus, CNMap& cnm, const Node* relative); + void mergeCollections(CollectionNode* c); + void clearSearchOrder() { forest_.clearSearchOrder(); } + void incrementLinkCount(const Node* t) { t->tree()->incrementLinkCount(); } + void clearLinkCounts() { forest_.clearLinkCounts(); } + void printLinkCounts(const QString& t) { forest_.printLinkCounts(t); } + QString getLinkCounts(QStringList& strings, QVector<int>& counts) { + return forest_.getLinkCounts(strings, counts); + } + QString getNewLinkTarget(const Node* locNode, + const Node* t, + const QString& fileName, + QString& text, + bool broken = false) { + return primaryTree()->getNewLinkTarget(locNode, t, fileName, text, broken); + } + TargetList* getTargetList(const QString& t) { return primaryTree()->getTargetList(t); } + QStringList getTargetListKeys() { return primaryTree()->getTargetListKeys(); } + QStringList keys() { return forest_.keys(); } + void resolveNamespaces(); + + private: + friend class QDocIndexFiles; + friend class QDocTagFiles; + + const Node* findNode(const QStringList& path, + const Node* relative, + int findFlags, + Node::Genus genus) { + return forest_.findNode(path, relative, findFlags, genus); + } + void processForest(void (QDocDatabase::*) (Aggregate*)); + bool isLoaded(const QString& t) { return forest_.isLoaded(t); } + static void initializeDB(); + + private: + QDocDatabase(); + QDocDatabase(QDocDatabase const& ) : showInternal_(false), forest_(this) { } + QDocDatabase& operator=(QDocDatabase const& ); + Tree* primaryTree() { return forest_.primaryTree(); } + + public: + static bool debug; + + private: + static QDocDatabase* qdocDB_; + static NodeMap typeNodeMap_; + bool showInternal_; + bool singleExec_; + QString version_; + QDocForest forest_; + + NodeMultiMap cppClasses_; + NodeMultiMap obsoleteClasses_; + NodeMultiMap classesWithObsoleteMembers_; + NodeMultiMap obsoleteQmlTypes_; + NodeMultiMap qmlTypesWithObsoleteMembers_; + NodeMultiMap namespaceIndex_; + NodeMultiMap nmm_; + NodeMultiMap qmlBasicTypes_; + NodeMultiMap qmlTypes_; + NodeMapMap newClassMaps_; + NodeMapMap newQmlTypeMaps_; + NodeMultiMapMap newSinceMaps_; + NodeMapMap funcIndex_; + TextToNodeMap legaleseTexts_; + QSet<QString> openNamespaces_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/qdocindexfiles.cpp b/src/qdoc/qdocindexfiles.cpp new file mode 100644 index 000000000..b466f5981 --- /dev/null +++ b/src/qdoc/qdocindexfiles.cpp @@ -0,0 +1,1628 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qxmlstream.h" +#include "qdocindexfiles.h" +#include "qdoctagfiles.h" +#include "config.h" +#include "qdocdatabase.h" +#include "location.h" +#include "atom.h" +#include "generator.h" +#include <qdebug.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +static Node* top = 0; + +/*! + \class QDocIndexFiles + + This class handles qdoc index files. + */ + +QDocIndexFiles* QDocIndexFiles::qdocIndexFiles_ = NULL; + +/*! + Constructs the singleton QDocIndexFiles. + */ +QDocIndexFiles::QDocIndexFiles() + : gen_( 0 ) +{ + qdb_ = QDocDatabase::qdocDB(); +} + +/*! + Destroys the singleton QDocIndexFiles. + */ +QDocIndexFiles::~QDocIndexFiles() +{ + qdb_ = 0; + gen_ = 0; +} + +/*! + Creates the singleton. Allows only one instance of the class + to be created. Returns a pointer to the singleton. + */ +QDocIndexFiles* QDocIndexFiles::qdocIndexFiles() +{ + if (!qdocIndexFiles_) + qdocIndexFiles_ = new QDocIndexFiles; + return qdocIndexFiles_; +} + +/*! + Destroys the singleton. + */ +void QDocIndexFiles::destroyQDocIndexFiles() +{ + if (qdocIndexFiles_) { + delete qdocIndexFiles_; + qdocIndexFiles_ = 0; + } +} + +/*! + Reads and parses the list of index files in \a indexFiles. + */ +void QDocIndexFiles::readIndexes(const QStringList& indexFiles) +{ + relatedList_.clear(); + foreach (const QString& indexFile, indexFiles) { + QString msg = "Loading index file: " + indexFile; + Location::logToStdErr(msg); + //qDebug() << msg; + readIndexFile(indexFile); + } +} + +static bool readingRoot = true; + +/*! + Reads and parses the index file at \a path. + */ +void QDocIndexFiles::readIndexFile(const QString& path) +{ + QFile file(path); + if (!file.open(QFile::ReadOnly)) { + qWarning() << "Could not read index file" << path; + return; + } + + QXmlStreamReader reader(&file); + reader.setNamespaceProcessing(false); + + if (!reader.readNextStartElement()) + return; + + if (reader.name() != QLatin1String("INDEX")) + return; + + QXmlStreamAttributes attrs = reader.attributes(); + + // Generate a relative URL between the install dir and the index file + // when the -installdir command line option is set. + QString indexUrl; + if (Config::installDir.isEmpty()) { + indexUrl = attrs.value(QLatin1String("url")).toString(); + } + else { + // Use a fake directory, since we will copy the output to a sub directory of + // installDir when using "make install". This is just for a proper relative path. + //QDir installDir(path.section('/', 0, -3) + "/outputdir"); + QDir installDir(path.section('/', 0, -3) + '/' + Generator::outputSubdir()); + indexUrl = installDir.relativeFilePath(path).section('/', 0, -2); + } + project_ = attrs.value(QLatin1String("project")).toString(); + basesList_.clear(); + + NamespaceNode* root = qdb_->newIndexTree(project_); + + // Scan all elements in the XML file, constructing a map that contains + // base classes for each class found. + while (reader.readNextStartElement()) { + readingRoot = true; + readIndexSection(reader, root, indexUrl); + } + + // Now that all the base classes have been found for this index, + // arrange them into an inheritance hierarchy. + resolveIndex(); +} + +/*! + Read a <section> element from the index file and create the + appropriate node(s). + */ +void QDocIndexFiles::readIndexSection(QXmlStreamReader& reader, + Node* current, + const QString& indexUrl) +{ + QXmlStreamAttributes attributes = reader.attributes(); + QStringRef elementName = reader.name(); + + QString name = attributes.value(QLatin1String("name")).toString(); + QString href = attributes.value(QLatin1String("href")).toString(); + Node* node; + Location location; + Aggregate* parent = 0; + + bool hasReadChildren = false; + + if (current->isAggregate()) + parent = static_cast<Aggregate*>(current); + + QString filePath; + int lineNo = 0; + if (attributes.hasAttribute(QLatin1String("filepath"))) { + filePath = attributes.value(QLatin1String("filepath")).toString(); + lineNo = attributes.value("lineno").toInt(); + } + if (elementName == QLatin1String("namespace")) { + node = new NamespaceNode(parent, name); + + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + name.toLower() + ".html"); + else if (!indexUrl.isNull()) + location = Location(name.toLower() + ".html"); + + } + else if (elementName == QLatin1String("class")) { + node = new ClassNode(parent, name); + if (attributes.hasAttribute(QLatin1String("bases"))) { + QString bases = attributes.value(QLatin1String("bases")).toString(); + if (!bases.isEmpty()) + basesList_.append(QPair<ClassNode*,QString>(static_cast<ClassNode*>(node), bases)); + } + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + name.toLower() + ".html"); + else if (!indexUrl.isNull()) + location = Location(name.toLower() + ".html"); + bool abstract = false; + if (attributes.value(QLatin1String("abstract")) == QLatin1String("true")) + abstract = true; + node->setAbstract(abstract); + } + else if (elementName == QLatin1String("qmlclass")) { + QmlTypeNode* qcn = new QmlTypeNode(parent, name); + qcn->setTitle(attributes.value(QLatin1String("title")).toString()); + QString logicalModuleName = attributes.value(QLatin1String("qml-module-name")).toString(); + if (!logicalModuleName.isEmpty()) + qdb_->addToQmlModule(logicalModuleName, qcn); + bool abstract = false; + if (attributes.value(QLatin1String("abstract")) == QLatin1String("true")) + abstract = true; + qcn->setAbstract(abstract); + QString qmlFullBaseName = attributes.value(QLatin1String("qml-base-type")).toString(); + if (!qmlFullBaseName.isEmpty()) { + qcn->setQmlBaseName(qmlFullBaseName); + } + if (attributes.hasAttribute(QLatin1String("location"))) + name = attributes.value("location").toString(); + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + name); + else if (!indexUrl.isNull()) + location = Location(name); + node = qcn; + } + else if (elementName == QLatin1String("jstype")) { + QmlTypeNode* qcn = new QmlTypeNode(parent, name); + qcn->setGenus(Node::JS); + qcn->setTitle(attributes.value(QLatin1String("title")).toString()); + QString logicalModuleName = attributes.value(QLatin1String("js-module-name")).toString(); + if (!logicalModuleName.isEmpty()) + qdb_->addToQmlModule(logicalModuleName, qcn); + bool abstract = false; + if (attributes.value(QLatin1String("abstract")) == QLatin1String("true")) + abstract = true; + qcn->setAbstract(abstract); + QString qmlFullBaseName = attributes.value(QLatin1String("js-base-type")).toString(); + if (!qmlFullBaseName.isEmpty()) { + qcn->setQmlBaseName(qmlFullBaseName); + } + if (attributes.hasAttribute(QLatin1String("location"))) + name = attributes.value("location").toString(); + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + name); + else if (!indexUrl.isNull()) + location = Location(name); + node = qcn; + } + else if (elementName == QLatin1String("qmlbasictype")) { + QmlBasicTypeNode* qbtn = new QmlBasicTypeNode(parent, name); + qbtn->setTitle(attributes.value(QLatin1String("title")).toString()); + if (attributes.hasAttribute(QLatin1String("location"))) + name = attributes.value("location").toString(); + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + name); + else if (!indexUrl.isNull()) + location = Location(name); + node = qbtn; + } + else if (elementName == QLatin1String("jsbasictype")) { + QmlBasicTypeNode* qbtn = new QmlBasicTypeNode(parent, name); + qbtn->setGenus(Node::JS); + qbtn->setTitle(attributes.value(QLatin1String("title")).toString()); + if (attributes.hasAttribute(QLatin1String("location"))) + name = attributes.value("location").toString(); + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + name); + else if (!indexUrl.isNull()) + location = Location(name); + node = qbtn; + } + else if (elementName == QLatin1String("qmlpropertygroup")) { + QmlTypeNode* qcn = static_cast<QmlTypeNode*>(parent); + QmlPropertyGroupNode* qpgn = new QmlPropertyGroupNode(qcn, name); + if (attributes.hasAttribute(QLatin1String("location"))) + name = attributes.value("location").toString(); + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + name); + else if (!indexUrl.isNull()) + location = Location(name); + node = qpgn; + } + else if (elementName == QLatin1String("jspropertygroup")) { + QmlTypeNode* qcn = static_cast<QmlTypeNode*>(parent); + QmlPropertyGroupNode* qpgn = new QmlPropertyGroupNode(qcn, name); + qpgn->setGenus(Node::JS); + if (attributes.hasAttribute(QLatin1String("location"))) + name = attributes.value("location").toString(); + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + name); + else if (!indexUrl.isNull()) + location = Location(name); + node = qpgn; + } + else if (elementName == QLatin1String("qmlproperty")) { + QString type = attributes.value(QLatin1String("type")).toString(); + bool attached = false; + if (attributes.value(QLatin1String("attached")) == QLatin1String("true")) + attached = true; + bool readonly = false; + if (attributes.value(QLatin1String("writable")) == QLatin1String("false")) + readonly = true; + QmlPropertyNode* qpn = new QmlPropertyNode(parent, name, type, attached); + qpn->setReadOnly(readonly); + node = qpn; + } + else if (elementName == QLatin1String("jsproperty")) { + QString type = attributes.value(QLatin1String("type")).toString(); + bool attached = false; + if (attributes.value(QLatin1String("attached")) == QLatin1String("true")) + attached = true; + bool readonly = false; + if (attributes.value(QLatin1String("writable")) == QLatin1String("false")) + readonly = true; + QmlPropertyNode* qpn = new QmlPropertyNode(parent, name, type, attached); + qpn->setGenus(Node::JS); + qpn->setReadOnly(readonly); + node = qpn; + } + else if ((elementName == QLatin1String("qmlmethod")) || + (elementName == QLatin1String("qmlsignal")) || + (elementName == QLatin1String("qmlsignalhandler"))) { + Node::NodeType t = Node::QmlMethod; + if (elementName == QLatin1String("qmlsignal")) + t = Node::QmlSignal; + else if (elementName == QLatin1String("qmlsignalhandler")) + t = Node::QmlSignalHandler; + bool attached = false; + FunctionNode* fn = new FunctionNode(t, parent, name, attached); + node = fn; + } + else if ((elementName == QLatin1String("jsmethod")) || + (elementName == QLatin1String("jssignal")) || + (elementName == QLatin1String("jssignalhandler"))) { + Node::NodeType t = Node::QmlMethod; + if (elementName == QLatin1String("jssignal")) + t = Node::QmlSignal; + else if (elementName == QLatin1String("jssignalhandler")) + t = Node::QmlSignalHandler; + bool attached = false; + FunctionNode* fn = new FunctionNode(t, parent, name, attached); + fn->setGenus(Node::JS); + node = fn; + } + else if (elementName == QLatin1String("group")) { + CollectionNode* cn = qdb_->addGroup(name); + cn->setTitle(attributes.value(QLatin1String("title")).toString()); + cn->setSubTitle(attributes.value(QLatin1String("subtitle")).toString()); + if (attributes.value(QLatin1String("seen")) == QLatin1String("true")) + cn->markSeen(); + node = cn; + } + else if (elementName == QLatin1String("module")) { + CollectionNode* cn = qdb_->addModule(name); + cn->setTitle(attributes.value(QLatin1String("title")).toString()); + cn->setSubTitle(attributes.value(QLatin1String("subtitle")).toString()); + if (attributes.value(QLatin1String("seen")) == QLatin1String("true")) + cn->markSeen(); + node = cn; + } + else if (elementName == QLatin1String("qmlmodule")) { + QString t = attributes.value(QLatin1String("qml-module-name")).toString(); + CollectionNode* cn = qdb_->addQmlModule(t); + QStringList info; + info << t << attributes.value(QLatin1String("qml-module-version")).toString(); + cn->setLogicalModuleInfo(info); + cn->setTitle(attributes.value(QLatin1String("title")).toString()); + cn->setSubTitle(attributes.value(QLatin1String("subtitle")).toString()); + if (attributes.value(QLatin1String("seen")) == QLatin1String("true")) + cn->markSeen(); + node = cn; + } + else if (elementName == QLatin1String("jsmodule")) { + QString t = attributes.value(QLatin1String("js-module-name")).toString(); + CollectionNode* cn = qdb_->addJsModule(t); + QStringList info; + info << t << attributes.value(QLatin1String("js-module-version")).toString(); + cn->setLogicalModuleInfo(info); + cn->setTitle(attributes.value(QLatin1String("title")).toString()); + cn->setSubTitle(attributes.value(QLatin1String("subtitle")).toString()); + if (attributes.value(QLatin1String("seen")) == QLatin1String("true")) + cn->markSeen(); + node = cn; + } + else if (elementName == QLatin1String("page")) { + Node::DocSubtype subtype; + Node::PageType ptype = Node::NoPageType; + QString attr = attributes.value(QLatin1String("subtype")).toString(); + if (attr == QLatin1String("example")) { + subtype = Node::Example; + ptype = Node::ExamplePage; + } + else if (attr == QLatin1String("header")) { + subtype = Node::HeaderFile; + ptype = Node::ApiPage; + } + else if (attr == QLatin1String("file")) { + subtype = Node::File; + ptype = Node::NoPageType; + } + else if (attr == QLatin1String("page")) { + subtype = Node::Page; + ptype = Node::ArticlePage; + } + else if (attr == QLatin1String("externalpage")) { + subtype = Node::ExternalPage; + ptype = Node::ArticlePage; + } + else + goto done; + + DocumentNode* docNode = new DocumentNode(parent, name, subtype, ptype); + docNode->setTitle(attributes.value(QLatin1String("title")).toString()); + + if (attributes.hasAttribute(QLatin1String("location"))) + name = attributes.value(QLatin1String("location")).toString(); + + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + name); + else if (!indexUrl.isNull()) + location = Location(name); + + node = docNode; + + } + else if (elementName == QLatin1String("enum")) { + EnumNode* enumNode = new EnumNode(parent, name); + + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html"); + else if (!indexUrl.isNull()) + location = Location(parent->name().toLower() + ".html"); + + while (reader.readNextStartElement()) { + QXmlStreamAttributes childAttributes = reader.attributes(); + if (reader.name() == QLatin1String("value")) { + + EnumItem item(childAttributes.value(QLatin1String("name")).toString(), childAttributes.value(QLatin1String("value")).toString()); + enumNode->addItem(item); + } else if (reader.name() == QLatin1String("keyword")) { + insertTarget(TargetRec::Keyword, childAttributes, enumNode); + } else if (reader.name() == QLatin1String("target")) { + insertTarget(TargetRec::Target, childAttributes, enumNode); + } + reader.skipCurrentElement(); + } + + node = enumNode; + + hasReadChildren = true; + } + else if (elementName == QLatin1String("typedef")) { + node = new TypedefNode(parent, name); + + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html"); + else if (!indexUrl.isNull()) + location = Location(parent->name().toLower() + ".html"); + + } + else if (elementName == QLatin1String("property")) { + node = new PropertyNode(parent, name); + + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html"); + else if (!indexUrl.isNull()) + location = Location(parent->name().toLower() + ".html"); + + } + else if (elementName == QLatin1String("function")) { + FunctionNode::Virtualness virt; + QString t = attributes.value(QLatin1String("virtual")).toString(); + if (t == QLatin1String("non")) + virt = FunctionNode::NonVirtual; + else if (t == QLatin1String("virtual")) + virt = FunctionNode::NormalVirtual; + else if (t == QLatin1String("pure")) + virt = FunctionNode::PureVirtual; + else + goto done; + + t = attributes.value(QLatin1String("meta")).toString(); + FunctionNode::Metaness meta; + if (t == QLatin1String("plain")) + meta = FunctionNode::Plain; + else if (t == QLatin1String("signal")) + meta = FunctionNode::Signal; + else if (t == QLatin1String("slot")) + meta = FunctionNode::Slot; + else if (t == QLatin1String("constructor")) + meta = FunctionNode::Ctor; + else if (t == QLatin1String("destructor")) + meta = FunctionNode::Dtor; + else if (t == QLatin1String("macro")) + meta = FunctionNode::MacroWithParams; + else if (t == QLatin1String("macrowithparams")) + meta = FunctionNode::MacroWithParams; + else if (t == QLatin1String("macrowithoutparams")) + meta = FunctionNode::MacroWithoutParams; + else + goto done; + + FunctionNode* functionNode = new FunctionNode(parent, name); + functionNode->setReturnType(attributes.value(QLatin1String("return")).toString()); + functionNode->setVirtualness(virt); + functionNode->setMetaness(meta); + functionNode->setConst(attributes.value(QLatin1String("const")) == QLatin1String("true")); + functionNode->setStatic(attributes.value(QLatin1String("static")) == QLatin1String("true")); + if (attributes.value(QLatin1String("overload")) == QLatin1String("true")) { + functionNode->setOverloadFlag(true); + functionNode->setOverloadNumber(attributes.value(QLatin1String("overload-number")).toUInt()); + } + else { + functionNode->setOverloadFlag(false); + functionNode->setOverloadNumber(0); + } + if (attributes.hasAttribute(QLatin1String("relates")) + && attributes.value(QLatin1String("relates")) != parent->name()) { + relatedList_.append( + QPair<FunctionNode*,QString>(functionNode, + attributes.value(QLatin1String("relates")).toString())); + } + /* + Note: The "signature" attribute was written to the + index file, but it is not read back in. Is that ok? + */ + + while (reader.readNextStartElement()) { + QXmlStreamAttributes childAttributes = reader.attributes(); + if (reader.name() == QLatin1String("parameter")) { + // Do not use the default value for the parameter; it is not + // required, and has been known to cause problems. + Parameter parameter(childAttributes.value(QLatin1String("left")).toString(), + childAttributes.value(QLatin1String("right")).toString(), + childAttributes.value(QLatin1String("name")).toString(), + QString()); // childAttributes.value(QLatin1String("default")) + functionNode->addParameter(parameter); + } else if (reader.name() == QLatin1String("keyword")) { + insertTarget(TargetRec::Keyword, childAttributes, functionNode); + } else if (reader.name() == QLatin1String("target")) { + insertTarget(TargetRec::Target, childAttributes, functionNode); + } + reader.skipCurrentElement(); + } + + node = functionNode; + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html"); + else if (!indexUrl.isNull()) + location = Location(parent->name().toLower() + ".html"); + + hasReadChildren = true; + } + else if (elementName == QLatin1String("variable")) { + node = new VariableNode(parent, name); + if (!indexUrl.isEmpty()) + location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html"); + else if (!indexUrl.isNull()) + location = Location(parent->name().toLower() + ".html"); + } + else if (elementName == QLatin1String("keyword")) { + insertTarget(TargetRec::Keyword, attributes, current); + goto done; + } + else if (elementName == QLatin1String("target")) { + insertTarget(TargetRec::Target, attributes, current); + goto done; + } + else if (elementName == QLatin1String("contents")) { + insertTarget(TargetRec::Contents, attributes, current); + goto done; + } + else + goto done; + + { + QString access = attributes.value(QLatin1String("access")).toString(); + if (access == "public") + node->setAccess(Node::Public); + else if (access == "protected") + node->setAccess(Node::Protected); + else if ((access == "private") || (access == "internal")) + node->setAccess(Node::Private); + else + node->setAccess(Node::Public); + + if ((elementName != QLatin1String("page")) && + (elementName != QLatin1String("qmlclass")) && + (elementName != QLatin1String("qmlbasictype")) && + (elementName != QLatin1String("jstype")) && + (elementName != QLatin1String("jsbasictype"))) { + QString threadSafety = attributes.value(QLatin1String("threadsafety")).toString(); + if (threadSafety == QLatin1String("non-reentrant")) + node->setThreadSafeness(Node::NonReentrant); + else if (threadSafety == QLatin1String("reentrant")) + node->setThreadSafeness(Node::Reentrant); + else if (threadSafety == QLatin1String("thread safe")) + node->setThreadSafeness(Node::ThreadSafe); + else + node->setThreadSafeness(Node::UnspecifiedSafeness); + } + else + node->setThreadSafeness(Node::UnspecifiedSafeness); + + QString status = attributes.value(QLatin1String("status")).toString(); + if (status == QLatin1String("compat")) + node->setStatus(Node::Compat); + else if (status == QLatin1String("obsolete")) + node->setStatus(Node::Obsolete); + else if (status == QLatin1String("deprecated")) + node->setStatus(Node::Obsolete); + else if (status == QLatin1String("preliminary")) + node->setStatus(Node::Preliminary); + else if (status == QLatin1String("active")) + node->setStatus(Node::Active); + else if (status == QLatin1String("internal")) + node->setStatus(Node::Internal); + else + node->setStatus(Node::Active); + + QString physicalModuleName = attributes.value(QLatin1String("module")).toString(); + if (!physicalModuleName.isEmpty()) + qdb_->addToModule(physicalModuleName, node); + if (!href.isEmpty()) { + if (node->isExternalPage()) + node->setUrl(href); + else if (!indexUrl.isEmpty()) + node->setUrl(indexUrl + QLatin1Char('/') + href); + } + + QString since = attributes.value(QLatin1String("since")).toString(); + if (!since.isEmpty()) { + node->setSince(since); + } + + QString groupsAttr = attributes.value(QLatin1String("groups")).toString(); + if (!groupsAttr.isEmpty()) { + QStringList groupNames = groupsAttr.split(QLatin1Char(',')); + foreach (const QString &name, groupNames) { + qdb_->addToGroup(name, node); + } + } + + // Create some content for the node. + QSet<QString> emptySet; + Location t(filePath); + if (!filePath.isEmpty()) { + t.setLineNo(lineNo); + node->setLocation(t); + location = t; + } + Doc doc(location, location, " ", emptySet, emptySet); // placeholder + node->setDoc(doc); + node->setIndexNodeFlag(); + node->setOutputSubdirectory(project_.toLower()); + QString briefAttr = attributes.value(QLatin1String("brief")).toString(); + if (!briefAttr.isEmpty()) { + node->setReconstitutedBrief(briefAttr); + } + + if (!hasReadChildren) { + bool useParent = (elementName == QLatin1String("namespace") && name.isEmpty()); + while (reader.readNextStartElement()) { + if (useParent) + readIndexSection(reader, parent, indexUrl); + else + readIndexSection(reader, node, indexUrl); + } + } + } + + done: + while (!reader.isEndElement()) { + if (reader.readNext() == QXmlStreamReader::Invalid) { + break; + } + } +} + +void QDocIndexFiles::insertTarget(TargetRec::TargetType type, + const QXmlStreamAttributes &attributes, + Node *node) +{ + int priority; + switch (type) { + case TargetRec::Keyword: + priority = 1; + break; + case TargetRec::Target: + priority = 2; + break; + case TargetRec::Contents: + priority = 3; + break; + default: + return; + } + + QString name = attributes.value(QLatin1String("name")).toString(); + QString title = attributes.value(QLatin1String("title")).toString(); + qdb_->insertTarget(name, title, type, node, priority); +} + +/*! + This function tries to resolve class inheritance immediately + after the index file is read. It is not always possible to + resolve a class inheritance at this point, because the base + class might be in an index file that hasn't been read yet, or + it might be in one of the header files that will be read for + the current module. These cases will be resolved after all + the index files and header and source files have been read, + just prior to beginning the generate phase for the current + module. + + I don't think this is completely correct because it always + sets the access to public. + */ +void QDocIndexFiles::resolveIndex() +{ + QPair<ClassNode*,QString> pair; + foreach (pair, basesList_) { + foreach (const QString& base, pair.second.split(QLatin1Char(','))) { + QStringList basePath = base.split(QString("::")); + Node* n = qdb_->findClassNode(basePath); + if (n) + pair.first->addResolvedBaseClass(Node::Public, static_cast<ClassNode*>(n)); + else + pair.first->addUnresolvedBaseClass(Node::Public, basePath, QString()); + } + } + // No longer needed. + basesList_.clear(); +} + +/* + Goes though the list of nodes that are related to other aggregates + that were read from all index files, and tries to find the aggregate + nodes from the database. Calls the node's setRelates() for each + aggregate that is found in the local module (primary tree). + + This function is meant to be called before starting the doc generation, + after all the index files are read. + */ +void QDocIndexFiles::resolveRelates() +{ + if (relatedList_.isEmpty()) + return; + + // Restrict searching only to the local (primary) tree + QVector<Tree*> searchOrder = qdb_->searchOrder(); + qdb_->setLocalSearch(); + + QPair<FunctionNode*,QString> relatedPair; + foreach (relatedPair, relatedList_) { + QStringList path = relatedPair.second.split("::"); + Node* n = qdb_->findRelatesNode(path); + if (n) + relatedPair.first->setRelates(static_cast<Aggregate*>(n)); + } + // Restore original search order + qdb_->setSearchOrder(searchOrder); + relatedList_.clear(); +} + +/*! + Generate the index section with the given \a writer for the \a node + specified, returning true if an element was written; otherwise returns + false. + */ +bool QDocIndexFiles::generateIndexSection(QXmlStreamWriter& writer, + Node* node, + bool generateInternalNodes) +{ + /* + Don't include index nodes in a new index file. Or DITA map nodes. + */ + if (node->isIndexNode() || node->docSubtype() == Node::DitaMap) + return false; + + QString nodeName; + QString logicalModuleName; + QString logicalModuleVersion; + QString qmlFullBaseName; + switch (node->type()) { + case Node::Namespace: + nodeName = "namespace"; + break; + case Node::Class: + nodeName = "class"; + break; + case Node::QmlType: + { + if (node->isQmlNode()) + nodeName = "qmlclass"; + else + nodeName = "jstype"; + CollectionNode* cn = node->logicalModule(); + if (cn) + logicalModuleName = cn->logicalModuleName(); + qmlFullBaseName = node->qmlFullBaseName(); + } + break; + case Node::QmlBasicType: + if (node->isQmlNode()) + nodeName = "qmlbasictype"; + else + nodeName = "jsbasictype"; + break; + case Node::Document: + nodeName = "page"; + break; + case Node::Group: + nodeName = "group"; + break; + case Node::Module: + nodeName = "module"; + break; + case Node::QmlModule: + if (node->isQmlNode()) + nodeName = "qmlmodule"; + else + nodeName = "jsmodule"; + break; + case Node::Enum: + nodeName = "enum"; + break; + case Node::Typedef: + nodeName = "typedef"; + break; + case Node::Property: + nodeName = "property"; + break; + case Node::Function: + nodeName = "function"; + break; + case Node::Variable: + nodeName = "variable"; + break; + case Node::QmlProperty: + if (node->isQmlNode()) + nodeName = "qmlproperty"; + else + nodeName = "jsProperty"; + break; + case Node::QmlPropertyGroup: + if (node->isQmlNode()) + nodeName = "qmlpropertygroup"; + else + nodeName = "jspropertygroup"; + break; + case Node::QmlSignal: + if (node->isQmlNode()) + nodeName = "qmlsignal"; + else + nodeName = "jssignal"; + break; + case Node::QmlSignalHandler: + if (node->isQmlNode()) + nodeName = "qmlsignalhandler"; + else + nodeName = "jssignalhandler"; + break; + case Node::QmlMethod: + if (node->isQmlNode()) + nodeName = "qmlmethod"; + else + nodeName = "jsmethod"; + break; + default: + return false; + } + + QString access; + switch (node->access()) { + case Node::Public: + access = "public"; + break; + case Node::Protected: + access = "protected"; + break; + case Node::Private: + { + access = "private"; + bool b = generateInternalNodes; + if (b) + b = false; + } + break; + default: + return false; + } + + QString objName = node->name(); + // Special case: only the root node should have an empty name. + if (objName.isEmpty() && node != qdb_->primaryTreeRoot()) + return false; + + writer.writeStartElement(nodeName); + + QXmlStreamAttributes attributes; + + if (!node->isDocumentNode() && !node->isCollectionNode()) { + QString threadSafety; + switch (node->threadSafeness()) { + case Node::NonReentrant: + threadSafety = "non-reentrant"; + break; + case Node::Reentrant: + threadSafety = "reentrant"; + break; + case Node::ThreadSafe: + threadSafety = "thread safe"; + break; + case Node::UnspecifiedSafeness: + default: + threadSafety = "unspecified"; + break; + } + writer.writeAttribute("threadsafety", threadSafety); + } + + QString status; + switch (node->status()) { + case Node::Compat: + status = "compat"; + break; + case Node::Obsolete: + status = "obsolete"; + break; + case Node::Deprecated: + status = "obsolete"; + break; + case Node::Preliminary: + status = "preliminary"; + break; + case Node::Active: + status = "active"; + break; + case Node::Internal: + status = "internal"; + break; + default: + status = "main"; + break; + } + + writer.writeAttribute("name", objName); + + // Write module and base type info for QML/JS types + if (node->type() == Node::QmlType || node->type() == Node::QmlModule) { + QString baseNameAttr("qml-base-type"); + QString moduleNameAttr("qml-module-name"); + QString moduleVerAttr("qml-module-version"); + if (node->isJsNode()) { + baseNameAttr = "js-base-type"; + moduleNameAttr = "js-module-name"; + moduleVerAttr = "js-module-version"; + } + if (node->type() == Node::QmlModule) { + logicalModuleName = node->logicalModuleName(); + logicalModuleVersion = node->logicalModuleVersion(); + } + if (!logicalModuleName.isEmpty()) + writer.writeAttribute(moduleNameAttr, logicalModuleName); + if (!logicalModuleVersion.isEmpty()) + writer.writeAttribute(moduleVerAttr, logicalModuleVersion); + if (!qmlFullBaseName.isEmpty()) + writer.writeAttribute(baseNameAttr, qmlFullBaseName); + } + + QString href; + if (!node->isExternalPage()) { + QString fullName = node->fullDocumentName(); + if (fullName != objName) + writer.writeAttribute("fullname", fullName); +#if 0 + if (Generator::useOutputSubdirs()) + href = node->outputSubdirectory(); + if (!href.isEmpty()) + href.append(QLatin1Char('/')); + href.append(gen_->fullDocumentLocation(node)); +#endif + href = gen_->fullDocumentLocation(node); + } + else + href = node->name(); + if (node->isQmlNode() || node->isJsNode()) { + Aggregate* p = node->parent(); + if (p) { + if (p->isQmlPropertyGroup() || p->isJsPropertyGroup()) + p = p->parent(); + if (p && (p->isQmlType() || p->isJsType()) && p->isAbstract()) + href.clear(); + } + } + if (!href.isEmpty()) + writer.writeAttribute("href", href); + + writer.writeAttribute("status", status); + if (!node->isDocumentNode() && !node->isCollectionNode()) { + writer.writeAttribute("access", access); + if (node->isAbstract()) + writer.writeAttribute("abstract", "true"); + } + const Location& declLocation = node->declLocation(); + if (!declLocation.fileName().isEmpty()) + writer.writeAttribute("location", declLocation.fileName()); + if (!declLocation.filePath().isEmpty()) { + writer.writeAttribute("filepath", declLocation.filePath()); + writer.writeAttribute("lineno", QString("%1").arg(declLocation.lineNo())); + } + + if (!node->since().isEmpty()) { + writer.writeAttribute("since", node->since()); + } + + QString brief = node->doc().trimmedBriefText(node->name()).toString(); + switch (node->type()) { + case Node::Class: + { + // Classes contain information about their base classes. + const ClassNode* classNode = static_cast<const ClassNode*>(node); + QList<RelatedClass> bases = classNode->baseClasses(); + QSet<QString> baseStrings; + foreach (const RelatedClass& related, bases) { + ClassNode* n = related.node_; + if (n) + baseStrings.insert(n->fullName()); + } + if (!baseStrings.isEmpty()) + writer.writeAttribute("bases", QStringList(baseStrings.toList()).join(QLatin1Char(','))); + if (!node->physicalModuleName().isEmpty()) + writer.writeAttribute("module", node->physicalModuleName()); + if (!classNode->groupNames().isEmpty()) + writer.writeAttribute("groups", classNode->groupNames().join(QLatin1Char(','))); + if (!brief.isEmpty()) + writer.writeAttribute("brief", brief); + } + break; + case Node::Namespace: + { + const NamespaceNode* namespaceNode = static_cast<const NamespaceNode*>(node); + if (!namespaceNode->physicalModuleName().isEmpty()) + writer.writeAttribute("module", namespaceNode->physicalModuleName()); + if (!namespaceNode->groupNames().isEmpty()) + writer.writeAttribute("groups", namespaceNode->groupNames().join(QLatin1Char(','))); + if (!brief.isEmpty()) + writer.writeAttribute("brief", brief); + } + break; + case Node::QmlType: + { + const QmlTypeNode* qcn = static_cast<const QmlTypeNode*>(node); + writer.writeAttribute("title", qcn->title()); + writer.writeAttribute("fulltitle", qcn->fullTitle()); + writer.writeAttribute("subtitle", qcn->subTitle()); + if (!qcn->groupNames().isEmpty()) + writer.writeAttribute("groups", qcn->groupNames().join(QLatin1Char(','))); + if (!brief.isEmpty()) + writer.writeAttribute("brief", brief); + } + break; + case Node::Document: + { + /* + Document nodes (such as manual pages) have a subtype, + a title, and other attributes. + */ + bool writeModuleName = false; + const DocumentNode* docNode = static_cast<const DocumentNode*>(node); + switch (docNode->docSubtype()) { + case Node::Example: + writer.writeAttribute("subtype", "example"); + writeModuleName = true; + break; + case Node::HeaderFile: + writer.writeAttribute("subtype", "header"); + writeModuleName = true; + break; + case Node::File: + writer.writeAttribute("subtype", "file"); + break; + case Node::Page: + writer.writeAttribute("subtype", "page"); + writeModuleName = true; + break; + case Node::ExternalPage: + writer.writeAttribute("subtype", "externalpage"); + break; + default: + break; + } + writer.writeAttribute("title", docNode->title()); + writer.writeAttribute("fulltitle", docNode->fullTitle()); + writer.writeAttribute("subtitle", docNode->subTitle()); + if (!node->physicalModuleName().isEmpty() && writeModuleName) { + writer.writeAttribute("module", node->physicalModuleName()); + } + if (!docNode->groupNames().isEmpty()) + writer.writeAttribute("groups", docNode->groupNames().join(QLatin1Char(','))); + if (!brief.isEmpty()) + writer.writeAttribute("brief", brief); + } + break; + case Node::Group: + { + const CollectionNode* cn = static_cast<const CollectionNode*>(node); + writer.writeAttribute("seen", cn->wasSeen() ? "true" : "false"); + writer.writeAttribute("title", cn->title()); + if (!cn->subTitle().isEmpty()) + writer.writeAttribute("subtitle", cn->subTitle()); + if (!cn->physicalModuleName().isEmpty()) + writer.writeAttribute("module", cn->physicalModuleName()); + if (!cn->groupNames().isEmpty()) + writer.writeAttribute("groups", cn->groupNames().join(QLatin1Char(','))); + /* + This is not read back in, so it probably + shouldn't be written out in the first place. + */ + if (!cn->members().isEmpty()) { + QStringList names; + foreach (const Node* member, cn->members()) + names.append(member->name()); + writer.writeAttribute("members", names.join(QLatin1Char(','))); + } + if (!brief.isEmpty()) + writer.writeAttribute("brief", brief); + } + break; + case Node::Module: + { + const CollectionNode* cn = static_cast<const CollectionNode*>(node); + writer.writeAttribute("seen", cn->wasSeen() ? "true" : "false"); + writer.writeAttribute("title", cn->title()); + if (!cn->subTitle().isEmpty()) + writer.writeAttribute("subtitle", cn->subTitle()); + if (!cn->physicalModuleName().isEmpty()) + writer.writeAttribute("module", cn->physicalModuleName()); + if (!cn->groupNames().isEmpty()) + writer.writeAttribute("groups", cn->groupNames().join(QLatin1Char(','))); + /* + This is not read back in, so it probably + shouldn't be written out in the first place. + */ + if (!cn->members().isEmpty()) { + QStringList names; + foreach (const Node* member, cn->members()) + names.append(member->name()); + writer.writeAttribute("members", names.join(QLatin1Char(','))); + } + if (!brief.isEmpty()) + writer.writeAttribute("brief", brief); + } + break; + case Node::QmlModule: + { + const CollectionNode* cn = static_cast<const CollectionNode*>(node); + writer.writeAttribute("seen", cn->wasSeen() ? "true" : "false"); + writer.writeAttribute("title", cn->title()); + if (!cn->subTitle().isEmpty()) + writer.writeAttribute("subtitle", cn->subTitle()); + if (!cn->physicalModuleName().isEmpty()) + writer.writeAttribute("module", cn->physicalModuleName()); + if (!cn->groupNames().isEmpty()) + writer.writeAttribute("groups", cn->groupNames().join(QLatin1Char(','))); + /* + This is not read back in, so it probably + shouldn't be written out in the first place. + */ + if (!cn->members().isEmpty()) { + QStringList names; + foreach (const Node* member, cn->members()) + names.append(member->name()); + writer.writeAttribute("members", names.join(QLatin1Char(','))); + } + if (!brief.isEmpty()) + writer.writeAttribute("brief", brief); + } + break; + case Node::Function: + { + /* + Function nodes contain information about the type of + function being described. + */ + const FunctionNode* functionNode = static_cast<const FunctionNode*>(node); + switch (functionNode->virtualness()) { + case FunctionNode::NonVirtual: + writer.writeAttribute("virtual", "non"); + break; + case FunctionNode::NormalVirtual: + writer.writeAttribute("virtual", "virtual"); + break; + case FunctionNode::PureVirtual: + writer.writeAttribute("virtual", "pure"); + break; + default: + break; + } + + switch (functionNode->metaness()) { + case FunctionNode::Plain: + writer.writeAttribute("meta", "plain"); + break; + case FunctionNode::Signal: + writer.writeAttribute("meta", "signal"); + break; + case FunctionNode::Slot: + writer.writeAttribute("meta", "slot"); + break; + case FunctionNode::Ctor: + writer.writeAttribute("meta", "constructor"); + break; + case FunctionNode::Dtor: + writer.writeAttribute("meta", "destructor"); + break; + case FunctionNode::MacroWithParams: + writer.writeAttribute("meta", "macrowithparams"); + break; + case FunctionNode::MacroWithoutParams: + writer.writeAttribute("meta", "macrowithoutparams"); + break; + default: + break; + } + writer.writeAttribute("const", functionNode->isConst()?"true":"false"); + writer.writeAttribute("static", functionNode->isStatic()?"true":"false"); + writer.writeAttribute("overload", functionNode->isOverload()?"true":"false"); + if (functionNode->isOverload()) + writer.writeAttribute("overload-number", QString::number(functionNode->overloadNumber())); + if (functionNode->relates()) { + writer.writeAttribute("relates", functionNode->relates()->name()); + } + if (functionNode->hasAssociatedProperties()) { + QString associatedProperties; + foreach (PropertyNode* pn, functionNode->associatedProperties()) { + if (!associatedProperties.isEmpty()) + associatedProperties += QLatin1String(", "); + associatedProperties += pn->name(); + } + writer.writeAttribute("associated-property", associatedProperties); + } + writer.writeAttribute("type", functionNode->returnType()); + if (!brief.isEmpty()) + writer.writeAttribute("brief", brief); + + /* + Note: The "signature" attribute is written to the + index file, but it is not read back in. Is that ok? + */ + QString signature = functionNode->signature(); + if (functionNode->isConst()) + signature += " const"; + writer.writeAttribute("signature", signature); + + for (int i = 0; i < functionNode->parameters().size(); ++i) { + Parameter parameter = functionNode->parameters()[i]; + writer.writeStartElement("parameter"); + writer.writeAttribute("left", parameter.dataType()); + writer.writeAttribute("right", parameter.rightType()); + writer.writeAttribute("name", parameter.name()); + writer.writeAttribute("default", parameter.defaultValue()); + writer.writeEndElement(); // parameter + } + } + break; + case Node::QmlProperty: + { + QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node); + writer.writeAttribute("type", qpn->dataType()); + writer.writeAttribute("attached", qpn->isAttached() ? "true" : "false"); + writer.writeAttribute("writable", qpn->isWritable() ? "true" : "false"); + if (!brief.isEmpty()) + writer.writeAttribute("brief", brief); + } + break; + case Node::QmlPropertyGroup: + { + if (!brief.isEmpty()) + writer.writeAttribute("brief", brief); + } + break; + case Node::Property: + { + const PropertyNode* propertyNode = static_cast<const PropertyNode*>(node); + writer.writeAttribute("type", propertyNode->dataType()); + if (!brief.isEmpty()) + writer.writeAttribute("brief", brief); + foreach (const Node* fnNode, propertyNode->getters()) { + if (fnNode) { + const FunctionNode* functionNode = static_cast<const FunctionNode*>(fnNode); + writer.writeStartElement("getter"); + writer.writeAttribute("name", functionNode->name()); + writer.writeEndElement(); // getter + } + } + foreach (const Node* fnNode, propertyNode->setters()) { + if (fnNode) { + const FunctionNode* functionNode = static_cast<const FunctionNode*>(fnNode); + writer.writeStartElement("setter"); + writer.writeAttribute("name", functionNode->name()); + writer.writeEndElement(); // setter + } + } + foreach (const Node* fnNode, propertyNode->resetters()) { + if (fnNode) { + const FunctionNode* functionNode = static_cast<const FunctionNode*>(fnNode); + writer.writeStartElement("resetter"); + writer.writeAttribute("name", functionNode->name()); + writer.writeEndElement(); // resetter + } + } + foreach (const Node* fnNode, propertyNode->notifiers()) { + if (fnNode) { + const FunctionNode* functionNode = static_cast<const FunctionNode*>(fnNode); + writer.writeStartElement("notifier"); + writer.writeAttribute("name", functionNode->name()); + writer.writeEndElement(); // notifier + } + } + } + break; + case Node::Variable: + { + const VariableNode* variableNode = static_cast<const VariableNode*>(node); + writer.writeAttribute("type", variableNode->dataType()); + writer.writeAttribute("static", variableNode->isStatic() ? "true" : "false"); + if (!brief.isEmpty()) + writer.writeAttribute("brief", brief); + } + break; + case Node::Enum: + { + const EnumNode* enumNode = static_cast<const EnumNode*>(node); + if (enumNode->flagsType()) { + writer.writeAttribute("typedef",enumNode->flagsType()->fullDocumentName()); + } + foreach (const EnumItem& item, enumNode->items()) { + writer.writeStartElement("value"); + writer.writeAttribute("name", item.name()); + writer.writeAttribute("value", item.value()); + writer.writeEndElement(); // value + } + } + break; + case Node::Typedef: + { + const TypedefNode* typedefNode = static_cast<const TypedefNode*>(node); + if (typedefNode->associatedEnum()) { + writer.writeAttribute("enum",typedefNode->associatedEnum()->fullDocumentName()); + } + } + break; + default: + break; + } + + /* + For our pages, we canonicalize the target, keyword and content + item names so that they can be used by qdoc for other sets of + documentation. + + The reason we do this here is that we don't want to ruin + externally composed indexes, containing non-qdoc-style target names + when reading in indexes. + + targets and keywords are now allowed in any node, not just inner nodes. + */ + + if (node->doc().hasTargets()) { + bool external = false; + if (node->type() == Node::Document) { + const DocumentNode* docNode = static_cast<const DocumentNode*>(node); + if (docNode->docSubtype() == Node::ExternalPage) + external = true; + } + foreach (const Atom* target, node->doc().targets()) { + QString title = target->string(); + QString name = Doc::canonicalTitle(title); + writer.writeStartElement("target"); + if (!external) + writer.writeAttribute("name", name); + else + writer.writeAttribute("name", title); + if (name != title) + writer.writeAttribute("title", title); + writer.writeEndElement(); // target + } + } + if (node->doc().hasKeywords()) { + foreach (const Atom* keyword, node->doc().keywords()) { + QString title = keyword->string(); + QString name = Doc::canonicalTitle(title); + writer.writeStartElement("keyword"); + writer.writeAttribute("name", name); + if (name != title) + writer.writeAttribute("title", title); + writer.writeEndElement(); // keyword + } + } + + // Inner nodes and function nodes contain child nodes of some sort, either + // actual child nodes or function parameters. For these, we close the + // opening tag, create child elements, then add a closing tag for the + // element. Elements for all other nodes are closed in the opening tag. + + if (node->isAggregate()) { + const Aggregate* inner = static_cast<const Aggregate*>(node); + + if (inner->doc().hasTableOfContents()) { + for (int i = 0; i < inner->doc().tableOfContents().size(); ++i) { + Atom* item = inner->doc().tableOfContents()[i]; + int level = inner->doc().tableOfContentsLevels()[i]; + QString title = Text::sectionHeading(item).toString(); + writer.writeStartElement("contents"); + writer.writeAttribute("name", Doc::canonicalTitle(title)); + writer.writeAttribute("title", title); + writer.writeAttribute("level", QString::number(level)); + writer.writeEndElement(); // contents + } + } + } + return true; +} + +/*! + Returns \c true if the node \a n1 is less than node \a n2. The + comparison is performed by comparing properties of the nodes + in order of increasing complexity. +*/ +bool compareNodes(const Node* n1, const Node* n2) +{ + // Private nodes can occur in any order since they won't normally be + // written to the index. + if (n1->access() == Node::Private && n2->access() == Node::Private) + return false; + + if (n1->location().filePath() < n2->location().filePath()) + return true; + else if (n1->location().filePath() > n2->location().filePath()) + return false; + + if (n1->type() < n2->type()) + return true; + else if (n1->type() > n2->type()) + return false; + + if (n1->name() < n2->name()) + return true; + else if (n1->name() > n2->name()) + return false; + + if (n1->access() < n2->access()) + return true; + else if (n1->access() > n2->access()) + return false; + + if (n1->type() == Node::Function && n2->type() == Node::Function) { + const FunctionNode* f1 = static_cast<const FunctionNode*>(n1); + const FunctionNode* f2 = static_cast<const FunctionNode*>(n2); + + if (f1->isConst() < f2->isConst()) + return true; + else if (f1->isConst() > f2->isConst()) + return false; + + if (f1->signature() < f2->signature()) + return true; + else if (f1->signature() > f2->signature()) + return false; + } + + if (n1->isDocumentNode() && n2->isDocumentNode()) { + const DocumentNode* f1 = static_cast<const DocumentNode*>(n1); + const DocumentNode* f2 = static_cast<const DocumentNode*>(n2); + if (f1->fullTitle() < f2->fullTitle()) + return true; + else if (f1->fullTitle() > f2->fullTitle()) + return false; + } + else if (n1->isCollectionNode() && n2->isCollectionNode()) { + const CollectionNode* f1 = static_cast<const CollectionNode*>(n1); + const CollectionNode* f2 = static_cast<const CollectionNode*>(n2); + if (f1->fullTitle() < f2->fullTitle()) + return true; + else if (f1->fullTitle() > f2->fullTitle()) + return false; + } + + return false; +} + +/*! + Generate index sections for the child nodes of the given \a node + using the \a writer specified. If \a generateInternalNodes is true, + nodes marked as internal will be included in the index; otherwise, + they will be omitted. +*/ +void QDocIndexFiles::generateIndexSections(QXmlStreamWriter& writer, + Node* node, + bool generateInternalNodes) +{ + /* + Note that groups, modules, and QML modules are written + after all the other nodes. + */ + if (node->isGroup() || node->isModule() || node->isQmlModule() || node->isJsModule()) + return; + + if (generateIndexSection(writer, node, generateInternalNodes)) { + if (node->isAggregate()) { + const Aggregate* inner = static_cast<const Aggregate*>(node); + + NodeList cnodes = inner->childNodes(); + std::sort(cnodes.begin(), cnodes.end(), compareNodes); + + foreach (Node* child, cnodes) { + generateIndexSections(writer, child, generateInternalNodes); + } + } + + if (node == top) { + /* + We wait until the end of the index file to output the group, module, + and QML module elements. By outputting them at the end, when we read + the index file back in, all the group, module, and QML module member + elements will have already been created. It is then only necessary to + create the group, module, or QML module element and add each member to + its member list. + */ + const CNMap& groups = qdb_->groups(); + if (!groups.isEmpty()) { + CNMap::ConstIterator g = groups.constBegin(); + while (g != groups.constEnd()) { + if (generateIndexSection(writer, g.value(), generateInternalNodes)) + writer.writeEndElement(); + ++g; + } + } + + const CNMap& modules = qdb_->modules(); + if (!modules.isEmpty()) { + CNMap::ConstIterator g = modules.constBegin(); + while (g != modules.constEnd()) { + if (generateIndexSection(writer, g.value(), generateInternalNodes)) + writer.writeEndElement(); + ++g; + } + } + + const CNMap& qmlModules = qdb_->qmlModules(); + if (!qmlModules.isEmpty()) { + CNMap::ConstIterator g = qmlModules.constBegin(); + while (g != qmlModules.constEnd()) { + if (generateIndexSection(writer, g.value(), generateInternalNodes)) + writer.writeEndElement(); + ++g; + } + } + + const CNMap& jsModules = qdb_->jsModules(); + if (!jsModules.isEmpty()) { + CNMap::ConstIterator g = jsModules.constBegin(); + while (g != jsModules.constEnd()) { + if (generateIndexSection(writer, g.value(), generateInternalNodes)) + writer.writeEndElement(); + ++g; + } + } + } + + writer.writeEndElement(); + } +} + +/*! + Outputs an index file. + */ +void QDocIndexFiles::generateIndex(const QString& fileName, + const QString& url, + const QString& title, + Generator* g, + bool generateInternalNodes) +{ + QFile file(fileName); + if (!file.open(QFile::WriteOnly | QFile::Text)) + return; + + QString msg = "Writing index file: " + fileName; + Location::logToStdErr(msg); + + gen_ = g; + QXmlStreamWriter writer(&file); + writer.setAutoFormatting(true); + writer.writeStartDocument(); + writer.writeDTD("<!DOCTYPE QDOCINDEX>"); + + writer.writeStartElement("INDEX"); + writer.writeAttribute("url", url); + writer.writeAttribute("title", title); + writer.writeAttribute("version", qdb_->version()); + writer.writeAttribute("project", g->config()->getString(CONFIG_PROJECT)); + + top = qdb_->primaryTreeRoot(); + generateIndexSections(writer, top, generateInternalNodes); + + writer.writeEndElement(); // INDEX + writer.writeEndElement(); // QDOCINDEX + writer.writeEndDocument(); + file.close(); +} + +QT_END_NAMESPACE diff --git a/src/qdoc/qdocindexfiles.h b/src/qdoc/qdocindexfiles.h new file mode 100644 index 000000000..9873322b3 --- /dev/null +++ b/src/qdoc/qdocindexfiles.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDOCINDEXFILES_H +#define QDOCINDEXFILES_H + +#include "node.h" +#include "tree.h" + +QT_BEGIN_NAMESPACE + +class Atom; +class Generator; +class QStringList; +class QDocDatabase; +class QXmlStreamWriter; +class QXmlStreamAttributes; + +class QDocIndexFiles +{ + friend class QDocDatabase; + + private: + static QDocIndexFiles* qdocIndexFiles(); + static void destroyQDocIndexFiles(); + + QDocIndexFiles(); + ~QDocIndexFiles(); + + void readIndexes(const QStringList& indexFiles); + void generateIndex(const QString& fileName, + const QString& url, + const QString& title, + Generator* g, + bool generateInternalNodes = false); + + void readIndexFile(const QString& path); + void readIndexSection(QXmlStreamReader &reader, Node* current, const QString& indexUrl); + void insertTarget(TargetRec::TargetType type, const QXmlStreamAttributes &attributes, Node *node); + void resolveIndex(); + void resolveRelates(); + bool generateIndexSection(QXmlStreamWriter& writer, Node* node, bool generateInternalNodes = false); + void generateIndexSections(QXmlStreamWriter& writer, Node* node, bool generateInternalNodes = false); + + private: + static QDocIndexFiles* qdocIndexFiles_; + QDocDatabase* qdb_; + Generator* gen_; + QString project_; + QVector<QPair<ClassNode*,QString> > basesList_; + QVector<QPair<FunctionNode*,QString> > relatedList_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/qdoctagfiles.cpp b/src/qdoc/qdoctagfiles.cpp new file mode 100644 index 000000000..1210ac7c3 --- /dev/null +++ b/src/qdoc/qdoctagfiles.cpp @@ -0,0 +1,385 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "node.h" +#include "qdoctagfiles.h" +#include "qdocdatabase.h" + +#include "atom.h" +#include "doc.h" +#include "htmlgenerator.h" +#include "location.h" +#include "node.h" +#include "text.h" +#include <limits.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QDocTagFiles + + This class handles the generation of the qdoc tag file. + */ + +QDocTagFiles* QDocTagFiles::qdocTagFiles_ = NULL; + +/*! + Constructs the singleton. \a qdb is the pointer to the + qdoc database that is used when reading and writing the + index files. + */ +QDocTagFiles::QDocTagFiles() +{ + qdb_ = QDocDatabase::qdocDB(); +} + +/*! + Destroys the singleton QDocTagFiles. + */ +QDocTagFiles::~QDocTagFiles() +{ + qdb_ = 0; +} + +/*! + Creates the singleton. Allows only one instance of the class + to be created. Returns a pointer to the singleton. + */ +QDocTagFiles* QDocTagFiles::qdocTagFiles() +{ + if (!qdocTagFiles_) + qdocTagFiles_ = new QDocTagFiles; + return qdocTagFiles_; +} + +/*! + Destroys the singleton. + */ +void QDocTagFiles::destroyQDocTagFiles() +{ + if (qdocTagFiles_) { + delete qdocTagFiles_; + qdocTagFiles_ = 0; + } +} + +/*! + Generate the tag file section with the given \a writer for the \a node + specified, returning true if an element was written; otherwise returns + false. + */ +void QDocTagFiles::generateTagFileCompounds(QXmlStreamWriter& writer, const Aggregate* inner) +{ + foreach (const Node* node, inner->childNodes()) { + if (!node->url().isEmpty()) + continue; + + QString kind; + switch (node->type()) { + case Node::Namespace: + kind = "namespace"; + break; + case Node::Class: + case Node::QmlType: + kind = "class"; + break; + case Node::Enum: + case Node::Typedef: + case Node::Property: + case Node::Function: + case Node::Variable: + default: + continue; + } + + QString access; + switch (node->access()) { + case Node::Public: + access = "public"; + break; + case Node::Protected: + access = "protected"; + break; + case Node::Private: + default: + continue; + } + + QString objName = node->name(); + + // Special case: only the root node should have an empty name. + if (objName.isEmpty() && node != qdb_->primaryTreeRoot()) + continue; + + // *** Write the starting tag for the element here. *** + writer.writeStartElement("compound"); + writer.writeAttribute("kind", kind); + + if (node->type() == Node::Class) { + writer.writeTextElement("name", node->fullDocumentName()); + writer.writeTextElement("filename", gen_->fullDocumentLocation(node, false)); + + // Classes contain information about their base classes. + const ClassNode* classNode = static_cast<const ClassNode*>(node); + QList<RelatedClass> bases = classNode->baseClasses(); + foreach (const RelatedClass& related, bases) { + ClassNode* n = related.node_; + if (n) + writer.writeTextElement("base", n->name()); + } + + // Recurse to write all members. + generateTagFileMembers(writer, static_cast<const Aggregate*>(node)); + writer.writeEndElement(); + + // Recurse to write all compounds. + generateTagFileCompounds(writer, static_cast<const Aggregate*>(node)); + } + else { + writer.writeTextElement("name", node->fullDocumentName()); + writer.writeTextElement("filename", gen_->fullDocumentLocation(node, false)); + + // Recurse to write all members. + generateTagFileMembers(writer, static_cast<const Aggregate*>(node)); + writer.writeEndElement(); + + // Recurse to write all compounds. + generateTagFileCompounds(writer, static_cast<const Aggregate*>(node)); + } + } +} + +/*! + Writes all the members of the \a inner node with the \a writer. + The node represents a C++ class, namespace, etc. + */ +void QDocTagFiles::generateTagFileMembers(QXmlStreamWriter& writer, const Aggregate* inner) +{ + foreach (const Node* node, inner->childNodes()) { + if (!node->url().isEmpty()) + continue; + + QString nodeName; + QString kind; + switch (node->type()) { + case Node::Enum: + nodeName = "member"; + kind = "enum"; + break; + case Node::Typedef: + nodeName = "member"; + kind = "typedef"; + break; + case Node::Property: + nodeName = "member"; + kind = "property"; + break; + case Node::Function: + nodeName = "member"; + kind = "function"; + break; + case Node::Namespace: + nodeName = "namespace"; + break; + case Node::Class: + nodeName = "class"; + break; + case Node::Variable: + default: + continue; + } + + QString access; + switch (node->access()) { + case Node::Public: + access = "public"; + break; + case Node::Protected: + access = "protected"; + break; + case Node::Private: + default: + continue; + } + + QString objName = node->name(); + + // Special case: only the root node should have an empty name. + if (objName.isEmpty() && node != qdb_->primaryTreeRoot()) + continue; + + // *** Write the starting tag for the element here. *** + writer.writeStartElement(nodeName); + if (!kind.isEmpty()) + writer.writeAttribute("kind", kind); + + switch (node->type()) { + case Node::Class: + writer.writeCharacters(node->fullDocumentName()); + writer.writeEndElement(); + break; + case Node::Namespace: + writer.writeCharacters(node->fullDocumentName()); + writer.writeEndElement(); + break; + case Node::Function: + { + /* + Function nodes contain information about + the type of function being described. + */ + + const FunctionNode* functionNode = static_cast<const FunctionNode*>(node); + writer.writeAttribute("protection", access); + + switch (functionNode->virtualness()) { + case FunctionNode::NonVirtual: + writer.writeAttribute("virtualness", "non"); + break; + case FunctionNode::NormalVirtual: + writer.writeAttribute("virtualness", "virtual"); + break; + case FunctionNode::PureVirtual: + writer.writeAttribute("virtual", "pure"); + break; + default: + break; + } + writer.writeAttribute("static", functionNode->isStatic() ? "yes" : "no"); + + if (functionNode->virtualness() == FunctionNode::NonVirtual) + writer.writeTextElement("type", functionNode->returnType()); + else + writer.writeTextElement("type", "virtual " + functionNode->returnType()); + + writer.writeTextElement("name", objName); + QStringList pieces = gen_->fullDocumentLocation(node, false).split(QLatin1Char('#')); + writer.writeTextElement("anchorfile", pieces[0]); + writer.writeTextElement("anchor", pieces[1]); + QString signature = functionNode->signature(); + signature = signature.mid(signature.indexOf(QChar('('))).trimmed(); + if (functionNode->isConst()) + signature += " const"; + if (functionNode->virtualness() == FunctionNode::PureVirtual) + signature += " = 0"; + writer.writeTextElement("arglist", signature); + } + writer.writeEndElement(); // member + break; + case Node::Property: + { + const PropertyNode* propertyNode = static_cast<const PropertyNode*>(node); + writer.writeAttribute("type", propertyNode->dataType()); + writer.writeTextElement("name", objName); + QStringList pieces = gen_->fullDocumentLocation(node, false).split(QLatin1Char('#')); + writer.writeTextElement("anchorfile", pieces[0]); + writer.writeTextElement("anchor", pieces[1]); + writer.writeTextElement("arglist", QString()); + } + writer.writeEndElement(); // member + break; + case Node::Enum: + { + const EnumNode* enumNode = static_cast<const EnumNode*>(node); + writer.writeTextElement("name", objName); + QStringList pieces = gen_->fullDocumentLocation(node, false).split(QLatin1Char('#')); + writer.writeTextElement("anchor", pieces[1]); + writer.writeTextElement("arglist", QString()); + writer.writeEndElement(); // member + + for (int i = 0; i < enumNode->items().size(); ++i) { + EnumItem item = enumNode->items().value(i); + writer.writeStartElement("member"); + writer.writeAttribute("name", item.name()); + writer.writeTextElement("anchor", pieces[1]); + writer.writeTextElement("arglist", QString()); + writer.writeEndElement(); // member + } + } + break; + case Node::Typedef: + { + const TypedefNode* typedefNode = static_cast<const TypedefNode*>(node); + if (typedefNode->associatedEnum()) + writer.writeAttribute("type", typedefNode->associatedEnum()->fullDocumentName()); + else + writer.writeAttribute("type", QString()); + writer.writeTextElement("name", objName); + QStringList pieces = gen_->fullDocumentLocation(node, false).split(QLatin1Char('#')); + writer.writeTextElement("anchorfile", pieces[0]); + writer.writeTextElement("anchor", pieces[1]); + writer.writeTextElement("arglist", QString()); + } + writer.writeEndElement(); // member + break; + + case Node::Variable: + default: + break; + } + } +} + +/*! + Writes a tag file named \a fileName. + */ +void QDocTagFiles::generateTagFile(const QString& fileName, Generator* g) +{ + QFile file(fileName); + QFileInfo fileInfo(fileName); + + // If no path was specified or it doesn't exist, + // default to the output directory + if (fileInfo.fileName() == fileName || !fileInfo.dir().exists()) + file.setFileName(gen_->outputDir() + QLatin1Char('/') + + fileInfo.fileName()); + + if (!file.open(QFile::WriteOnly | QFile::Text)) { + Location::null.warning( + QString("Failed to open %1 for writing.").arg(file.fileName())); + return; + } + + gen_ = g; + QXmlStreamWriter writer(&file); + writer.setAutoFormatting(true); + writer.writeStartDocument(); + writer.writeStartElement("tagfile"); + generateTagFileCompounds(writer, qdb_->primaryTreeRoot()); + writer.writeEndElement(); // tagfile + writer.writeEndDocument(); + file.close(); +} + +QT_END_NAMESPACE diff --git a/src/qdoc/qdoctagfiles.h b/src/qdoc/qdoctagfiles.h new file mode 100644 index 000000000..ad1d383bf --- /dev/null +++ b/src/qdoc/qdoctagfiles.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDOCTAGFILES_H +#define QDOCTAGFILES_H + +#include "qxmlstream.h" + +QT_BEGIN_NAMESPACE + +class Aggregate; +class QDocDatabase; +class Generator; + +class QDocTagFiles +{ + friend class QDocDatabase; + + private: + static QDocTagFiles* qdocTagFiles(); + static void destroyQDocTagFiles(); + + QDocTagFiles(); + ~QDocTagFiles(); + + void generateTagFileCompounds(QXmlStreamWriter& writer, const Aggregate* inner); + void generateTagFileMembers(QXmlStreamWriter& writer, const Aggregate* inner); + void generateTagFile(const QString& fileName, Generator* g); + + private: + static QDocTagFiles* qdocTagFiles_; + QDocDatabase* qdb_; + Generator* gen_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/qmlcodemarker.cpp b/src/qdoc/qmlcodemarker.cpp new file mode 100644 index 000000000..089e1d1c8 --- /dev/null +++ b/src/qdoc/qmlcodemarker.cpp @@ -0,0 +1,274 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + qmlcodemarker.cpp +*/ + +#include "qmlcodemarker.h" + +#include "atom.h" +#include "node.h" +#include "qmlmarkupvisitor.h" +#include "text.h" +#include "tree.h" +#include "generator.h" + +#include <private/qqmljsast_p.h> +#include <private/qqmljsastfwd_p.h> +#include <private/qqmljsengine_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> + +QT_BEGIN_NAMESPACE + +QmlCodeMarker::QmlCodeMarker() +{ +} + +QmlCodeMarker::~QmlCodeMarker() +{ +} + +/*! + Returns \c true if the \a code is recognized by the parser. + */ +bool QmlCodeMarker::recognizeCode(const QString &code) +{ + QQmlJS::Engine engine; + QQmlJS::Lexer lexer(&engine); + QQmlJS::Parser parser(&engine); + + QString newCode = code; + extractPragmas(newCode); + lexer.setCode(newCode, 1); + + return parser.parse(); +} + +/*! + Returns \c true if \a ext is any of a list of file extensions + for the QML language. + */ +bool QmlCodeMarker::recognizeExtension(const QString &ext) +{ + return ext == "qml"; +} + +/*! + Returns \c true if the \a language is recognized. Only "QML" is + recognized by this marker. + */ +bool QmlCodeMarker::recognizeLanguage(const QString &language) +{ + return language == "QML"; +} + +/*! + Returns the type of atom used to represent QML code in the documentation. +*/ +Atom::AtomType QmlCodeMarker::atomType() const +{ + return Atom::Qml; +} + +QString QmlCodeMarker::markedUpCode(const QString &code, + const Node *relative, + const Location &location) +{ + return addMarkUp(code, relative, location); +} + +QString QmlCodeMarker::markedUpName(const Node *node) +{ + QString name = linkTag(node, taggedNode(node)); + if (node->type() == Node::QmlMethod) + name += "()"; + return name; +} + +QString QmlCodeMarker::markedUpFullName(const Node *node, const Node *relative) +{ + if (node->name().isEmpty()) { + return "global"; + } + else { + QString fullName; + for (;;) { + fullName.prepend(markedUpName(node)); + if (node->parent() == relative || node->parent()->name().isEmpty()) + break; + fullName.prepend("<@op>::</@op>"); + node = node->parent(); + } + return fullName; + } +} + +QString QmlCodeMarker::markedUpIncludes(const QStringList& includes) +{ + QString code; + + QStringList::ConstIterator inc = includes.constBegin(); + while (inc != includes.constEnd()) { + code += "import " + *inc + QLatin1Char('\n'); + ++inc; + } + Location location; + return addMarkUp(code, 0, location); +} + +QString QmlCodeMarker::functionBeginRegExp(const QString& funcName) +{ + return QLatin1Char('^') + QRegExp::escape("function " + funcName) + QLatin1Char('$'); + +} + +QString QmlCodeMarker::functionEndRegExp(const QString& /* funcName */) +{ + return "^\\}$"; +} + +QString QmlCodeMarker::addMarkUp(const QString &code, + const Node * /* relative */, + const Location &location) +{ + QQmlJS::Engine engine; + QQmlJS::Lexer lexer(&engine); + + QString newCode = code; + QList<QQmlJS::AST::SourceLocation> pragmas = extractPragmas(newCode); + lexer.setCode(newCode, 1); + + QQmlJS::Parser parser(&engine); + QString output; + + if (parser.parse()) { + QQmlJS::AST::UiProgram *ast = parser.ast(); + // Pass the unmodified code to the visitor so that pragmas and other + // unhandled source text can be output. + QmlMarkupVisitor visitor(code, pragmas, &engine); + QQmlJS::AST::Node::accept(ast, &visitor); + output = visitor.markedUpCode(); + } else { + location.warning(tr("Unable to parse QML snippet: \"%1\" at line %2, column %3").arg( + parser.errorMessage()).arg(parser.errorLineNumber()).arg( + parser.errorColumnNumber())); + output = protect(code); + } + + return output; +} + +/* + Copied and pasted from + src/declarative/qml/qqmlscriptparser.cpp. +*/ +static void replaceWithSpace(QString &str, int idx, int n) +{ + QChar *data = str.data() + idx; + const QChar space(QLatin1Char(' ')); + for (int ii = 0; ii < n; ++ii) + *data++ = space; +} + +/* + Copied and pasted from + src/declarative/qml/qqmlscriptparser.cpp then modified to + return a list of removed pragmas. + + Searches for ".pragma <value>" or ".import <stuff>" declarations + in \a script. Currently supported pragmas are: library +*/ +QList<QQmlJS::AST::SourceLocation> QmlCodeMarker::extractPragmas(QString &script) +{ + const QString pragma(QLatin1String("pragma")); + const QString library(QLatin1String("library")); + QList<QQmlJS::AST::SourceLocation> removed; + + QQmlJS::Lexer l(0); + l.setCode(script, 0); + + int token = l.lex(); + + while (true) { + if (token != QQmlJSGrammar::T_DOT) + return removed; + + int startOffset = l.tokenOffset(); + int startLine = l.tokenStartLine(); + int startColumn = l.tokenStartColumn(); + + token = l.lex(); + + if (token != QQmlJSGrammar::T_PRAGMA && token != QQmlJSGrammar::T_IMPORT) + return removed; + int endOffset = 0; + while (startLine == l.tokenStartLine()) { + endOffset = l.tokenLength() + l.tokenOffset(); + token = l.lex(); + } + replaceWithSpace(script, startOffset, endOffset - startOffset); + removed.append(QQmlJS::AST::SourceLocation(startOffset, + endOffset - startOffset, + startLine, + startColumn)); +#if 0 + token = l.lex(); + if (Generator::debugging()) + qDebug() << " third token"; + if (token != QQmlJSGrammar::T_IDENTIFIER || + l.tokenStartLine() != startLine) + return removed; + + QString pragmaValue = script.mid(l.tokenOffset(), l.tokenLength()); + int endOffset = l.tokenLength() + l.tokenOffset(); + + token = l.lex(); + if (l.tokenStartLine() == startLine) + return removed; + + if (pragmaValue == QLatin1String("library")) { + replaceWithSpace(script, startOffset, endOffset - startOffset); + removed.append( + QQmlJS::AST::SourceLocation( + startOffset, endOffset - startOffset, + startLine, startColumn)); + } else + return removed; +#endif + } + return removed; +} + +QT_END_NAMESPACE diff --git a/src/qdoc/qmlcodemarker.h b/src/qdoc/qmlcodemarker.h new file mode 100644 index 000000000..96353ebe5 --- /dev/null +++ b/src/qdoc/qmlcodemarker.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + qmlcodemarker.h +*/ + +#ifndef QMLCODEMARKER_H +#define QMLCODEMARKER_H + +#include "cppcodemarker.h" + +#include <private/qqmljsastfwd_p.h> + +QT_BEGIN_NAMESPACE + +class QmlCodeMarker : public CppCodeMarker +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::QmlCodeMarker) + +public: + QmlCodeMarker(); + ~QmlCodeMarker(); + + virtual bool recognizeCode(const QString &code) Q_DECL_OVERRIDE; + virtual bool recognizeExtension(const QString &ext) Q_DECL_OVERRIDE; + virtual bool recognizeLanguage(const QString &language) Q_DECL_OVERRIDE; + virtual Atom::AtomType atomType() const Q_DECL_OVERRIDE; + virtual QString markedUpCode(const QString &code, + const Node *relative, + const Location &location) Q_DECL_OVERRIDE; + + virtual QString markedUpName(const Node *node) Q_DECL_OVERRIDE; + virtual QString markedUpFullName(const Node *node, const Node *relative) Q_DECL_OVERRIDE; + virtual QString markedUpIncludes(const QStringList &includes) Q_DECL_OVERRIDE; + virtual QString functionBeginRegExp(const QString &funcName) Q_DECL_OVERRIDE; + virtual QString functionEndRegExp(const QString &funcName) Q_DECL_OVERRIDE; + + /* Copied from src/declarative/qml/qdeclarativescriptparser.cpp */ + QList<QQmlJS::AST::SourceLocation> extractPragmas(QString &script); + +private: + QString addMarkUp(const QString &code, const Node *relative, + const Location &location); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/qmlcodeparser.cpp b/src/qdoc/qmlcodeparser.cpp new file mode 100644 index 000000000..e94df4753 --- /dev/null +++ b/src/qdoc/qmlcodeparser.cpp @@ -0,0 +1,333 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + qmlcodeparser.cpp +*/ + +#include "qmlcodeparser.h" + +#include "node.h" +#include "config.h" +#include "qmlvisitor.h" + +#include <private/qqmljsast_p.h> +#include <private/qqmljsastvisitor_p.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +#define COMMAND_STARTPAGE Doc::alias("startpage") +#define COMMAND_VARIABLE Doc::alias("variable") + +#define COMMAND_DEPRECATED Doc::alias("deprecated") +#define COMMAND_INGROUP Doc::alias("ingroup") +#define COMMAND_INTERNAL Doc::alias("internal") +#define COMMAND_OBSOLETE Doc::alias("obsolete") +#define COMMAND_PAGEKEYWORDS Doc::alias("pagekeywords") +#define COMMAND_PRELIMINARY Doc::alias("preliminary") +#define COMMAND_SINCE Doc::alias("since") +#define COMMAND_WRAPPER Doc::alias("wrapper") +#define COMMAND_NOAUTOLIST Doc::alias("noautolist") + +#define COMMAND_ABSTRACT Doc::alias("abstract") +#define COMMAND_QMLABSTRACT Doc::alias("qmlabstract") +#define COMMAND_QMLCLASS Doc::alias("qmlclass") +#define COMMAND_QMLTYPE Doc::alias("qmltype") +#define COMMAND_QMLMODULE Doc::alias("qmlmodule") +#define COMMAND_QMLPROPERTY Doc::alias("qmlproperty") +#define COMMAND_QMLPROPERTYGROUP Doc::alias("qmlpropertygroup") +#define COMMAND_QMLATTACHEDPROPERTY Doc::alias("qmlattachedproperty") +#define COMMAND_QMLINHERITS Doc::alias("inherits") +#define COMMAND_QMLINSTANTIATES Doc::alias("instantiates") +#define COMMAND_INQMLMODULE Doc::alias("inqmlmodule") +#define COMMAND_QMLSIGNAL Doc::alias("qmlsignal") +#define COMMAND_QMLATTACHEDSIGNAL Doc::alias("qmlattachedsignal") +#define COMMAND_QMLMETHOD Doc::alias("qmlmethod") +#define COMMAND_QMLATTACHEDMETHOD Doc::alias("qmlattachedmethod") +#define COMMAND_QMLDEFAULT Doc::alias("default") +#define COMMAND_QMLREADONLY Doc::alias("readonly") +#define COMMAND_QMLBASICTYPE Doc::alias("qmlbasictype") +#define COMMAND_QMLMODULE Doc::alias("qmlmodule") + +#define COMMAND_JSTYPE Doc::alias("jstype") +#define COMMAND_JSMODULE Doc::alias("jsmodule") +#define COMMAND_JSPROPERTY Doc::alias("jsproperty") +#define COMMAND_JSPROPERTYGROUP Doc::alias("jspropertygroup") +#define COMMAND_JSATTACHEDPROPERTY Doc::alias("jsattachedproperty") +#define COMMAND_INJSMODULE Doc::alias("injsmodule") +#define COMMAND_JSSIGNAL Doc::alias("jssignal") +#define COMMAND_JSATTACHEDSIGNAL Doc::alias("jsattachedsignal") +#define COMMAND_JSMETHOD Doc::alias("jsmethod") +#define COMMAND_JSATTACHEDMETHOD Doc::alias("jsattachedmethod") +#define COMMAND_JSBASICTYPE Doc::alias("jsbasictype") +#define COMMAND_JSMODULE Doc::alias("jsmodule") + +/*! + Constructs the QML code parser. + */ +QmlCodeParser::QmlCodeParser() + : lexer( 0 ), + parser( 0 ) +{ +} + +/*! + Destroys the QML code parser. + */ +QmlCodeParser::~QmlCodeParser() +{ +} + +/*! + Initializes the code parser base class. The \a config argument + is passed to the initialization functions in the base class. + + Also creates a lexer and parser from QQmlJS. + */ +void QmlCodeParser::initializeParser(const Config &config) +{ + CodeParser::initializeParser(config); + + lexer = new QQmlJS::Lexer(&engine); + parser = new QQmlJS::Parser(&engine); +} + +/*! + Terminates the QML code parser. Deletes the lexer and parser + created by the constructor. + */ +void QmlCodeParser::terminateParser() +{ + delete lexer; + delete parser; +} + +/*! + Returns "QML". + */ +QString QmlCodeParser::language() +{ + return "QML"; +} + +/*! + Returns a string list containing "*.qml". This is the only + file type parsed by the QMLN parser. + */ +QStringList QmlCodeParser::sourceFileNameFilter() +{ + return QStringList() << "*.qml"; +} + +/*! + Parses the source file at \a filePath and inserts the contents + into the database. The \a location is used for error reporting. + + If it can't open the file at \a filePath, it reports an error + and returns without doing anything. + */ +void QmlCodeParser::parseSourceFile(const Location& location, const QString& filePath) +{ + QFile in(filePath); + currentFile_ = filePath; + if (!in.open(QIODevice::ReadOnly)) { + location.error(tr("Cannot open QML file '%1'").arg(filePath)); + currentFile_.clear(); + return; + } + + QString document = in.readAll(); + in.close(); + + Location fileLocation(filePath); + + QString newCode = document; + extractPragmas(newCode); + lexer->setCode(newCode, 1); + + const QSet<QString>& topicCommandsAllowed = topicCommands(); + const QSet<QString>& otherMetacommandsAllowed = otherMetaCommands(); + const QSet<QString>& metacommandsAllowed = topicCommandsAllowed + otherMetacommandsAllowed; + + if (parser->parse()) { + QQmlJS::AST::UiProgram *ast = parser->ast(); + QmlDocVisitor visitor(filePath, + newCode, + &engine, + metacommandsAllowed, + topicCommandsAllowed); + QQmlJS::AST::Node::accept(ast, &visitor); + } + foreach (const QQmlJS::DiagnosticMessage &msg, parser->diagnosticMessages()) { + qDebug().nospace() << qPrintable(filePath) << ':' << msg.loc.startLine + << ": QML syntax error at col " << msg.loc.startColumn + << ": " << qPrintable(msg.message); + } + currentFile_.clear(); +} + +/*! + Performs cleanup after qdoc is done parsing all the QML files. + Currently, no cleanup is required. + */ +void QmlCodeParser::doneParsingSourceFiles() +{ +} + +static QSet<QString> topicCommands_; +/*! + Returns the set of strings representing the topic commands. + */ +const QSet<QString>& QmlCodeParser::topicCommands() +{ + if (topicCommands_.isEmpty()) { + topicCommands_ << COMMAND_VARIABLE + << COMMAND_QMLCLASS + << COMMAND_QMLTYPE + << COMMAND_QMLPROPERTY + << COMMAND_QMLPROPERTYGROUP + << COMMAND_QMLATTACHEDPROPERTY + << COMMAND_QMLSIGNAL + << COMMAND_QMLATTACHEDSIGNAL + << COMMAND_QMLMETHOD + << COMMAND_QMLATTACHEDMETHOD + << COMMAND_QMLBASICTYPE + << COMMAND_JSTYPE + << COMMAND_JSPROPERTY + << COMMAND_JSPROPERTYGROUP + << COMMAND_JSATTACHEDPROPERTY + << COMMAND_JSSIGNAL + << COMMAND_JSATTACHEDSIGNAL + << COMMAND_JSMETHOD + << COMMAND_JSATTACHEDMETHOD + << COMMAND_JSBASICTYPE; + } + return topicCommands_; +} + +static QSet<QString> otherMetaCommands_; +/*! + Returns the set of strings representing the common metacommands + plus some other metacommands. + */ +const QSet<QString>& QmlCodeParser::otherMetaCommands() +{ + if (otherMetaCommands_.isEmpty()) { + otherMetaCommands_ = commonMetaCommands(); + otherMetaCommands_ << COMMAND_STARTPAGE + << COMMAND_QMLINHERITS + << COMMAND_QMLDEFAULT + << COMMAND_QMLREADONLY + << COMMAND_DEPRECATED + << COMMAND_INGROUP + << COMMAND_INTERNAL + << COMMAND_OBSOLETE + << COMMAND_PRELIMINARY + << COMMAND_SINCE + << COMMAND_ABSTRACT + << COMMAND_QMLABSTRACT + << COMMAND_INQMLMODULE + << COMMAND_INJSMODULE + << COMMAND_WRAPPER + << COMMAND_NOAUTOLIST; + } + return otherMetaCommands_; +} + +/*! + Copy and paste from src/declarative/qml/qdeclarativescriptparser.cpp. + This function blanks out the section of the \a str beginning at \a idx + and running for \a n characters. +*/ +static void replaceWithSpace(QString &str, int idx, int n) +{ + QChar *data = str.data() + idx; + const QChar space(QLatin1Char(' ')); + for (int ii = 0; ii < n; ++ii) + *data++ = space; +} + +/*! + Copy & paste from src/declarative/qml/qdeclarativescriptparser.cpp, + then modified to return no values. + + Searches for ".pragma <value>" declarations within \a script. + Currently supported pragmas are: library +*/ +void QmlCodeParser::extractPragmas(QString &script) +{ + const QString pragma(QLatin1String("pragma")); + const QString library(QLatin1String("library")); + + QQmlJS::Lexer l(0); + l.setCode(script, 0); + + int token = l.lex(); + + while (true) { + if (token != QQmlJSGrammar::T_DOT) + return; + + int startOffset = l.tokenOffset(); + int startLine = l.tokenStartLine(); + + token = l.lex(); + + if (token != QQmlJSGrammar::T_IDENTIFIER || + l.tokenStartLine() != startLine || + script.mid(l.tokenOffset(), l.tokenLength()) != pragma) + return; + + token = l.lex(); + + if (token != QQmlJSGrammar::T_IDENTIFIER || + l.tokenStartLine() != startLine) + return; + + QString pragmaValue = script.mid(l.tokenOffset(), l.tokenLength()); + int endOffset = l.tokenLength() + l.tokenOffset(); + + token = l.lex(); + if (l.tokenStartLine() == startLine) + return; + + if (pragmaValue == QLatin1String("library")) + replaceWithSpace(script, startOffset, endOffset - startOffset); + else + return; + } + return; +} + +QT_END_NAMESPACE diff --git a/src/qdoc/qmlcodeparser.h b/src/qdoc/qmlcodeparser.h new file mode 100644 index 000000000..8b5667532 --- /dev/null +++ b/src/qdoc/qmlcodeparser.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + qmlcodeparser.h +*/ + +#ifndef QMLCODEPARSER_H +#define QMLCODEPARSER_H + +#include "codeparser.h" + +#include <qset.h> +#include <private/qqmljsengine_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> + +QT_BEGIN_NAMESPACE + +class Config; +class Node; +class QString; + +class QmlCodeParser : public CodeParser +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::QmlCodeParser) + +public: + QmlCodeParser(); + virtual ~QmlCodeParser(); + + virtual void initializeParser(const Config& config) Q_DECL_OVERRIDE; + virtual void terminateParser() Q_DECL_OVERRIDE; + virtual QString language() Q_DECL_OVERRIDE; + virtual QStringList sourceFileNameFilter() Q_DECL_OVERRIDE; + virtual void parseSourceFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; + virtual void doneParsingSourceFiles() Q_DECL_OVERRIDE; + + /* Copied from src/declarative/qml/qdeclarativescriptparser.cpp */ + void extractPragmas(QString &script); + +protected: + const QSet<QString>& topicCommands(); + const QSet<QString>& otherMetaCommands(); + +private: + QQmlJS::Engine engine; + QQmlJS::Lexer *lexer; + QQmlJS::Parser *parser; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/qmlmarkupvisitor.cpp b/src/qdoc/qmlmarkupvisitor.cpp new file mode 100644 index 000000000..17dd99325 --- /dev/null +++ b/src/qdoc/qmlmarkupvisitor.cpp @@ -0,0 +1,844 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlmarkupvisitor.h" + +#include <qstringlist.h> +#include <qglobal.h> +#include <private/qqmljsast_p.h> +#include <private/qqmljsastfwd_p.h> +#include <private/qqmljsengine_p.h> + +QT_BEGIN_NAMESPACE + +QmlMarkupVisitor::QmlMarkupVisitor(const QString &source, + const QList<QQmlJS::AST::SourceLocation> &pragmas, + QQmlJS::Engine *engine) +{ + this->source = source; + this->engine = engine; + + cursor = 0; + extraIndex = 0; + + // Merge the lists of locations of pragmas and comments in the source code. + int i = 0; + int j = 0; + const QList<QQmlJS::AST::SourceLocation> comments = engine->comments(); + while (i < comments.size() && j < pragmas.length()) { + if (comments[i].offset < pragmas[j].offset) { + extraTypes.append(Comment); + extraLocations.append(comments[i]); + ++i; + } else { + extraTypes.append(Pragma); + extraLocations.append(comments[j]); + ++j; + } + } + + while (i < comments.size()) { + extraTypes.append(Comment); + extraLocations.append(comments[i]); + ++i; + } + + while (j < pragmas.length()) { + extraTypes.append(Pragma); + extraLocations.append(pragmas[j]); + ++j; + } +} + +QmlMarkupVisitor::~QmlMarkupVisitor() +{ +} + +// The protect() function is a copy of the one from CppCodeMarker. + +static const QString samp = QLatin1String("&"); +static const QString slt = QLatin1String("<"); +static const QString sgt = QLatin1String(">"); +static const QString squot = QLatin1String("""); + +QString QmlMarkupVisitor::protect(const QString& str) +{ + int n = str.length(); + QString marked; + marked.reserve(n * 2 + 30); + const QChar *data = str.constData(); + for (int i = 0; i != n; ++i) { + switch (data[i].unicode()) { + case '&': marked += samp; break; + case '<': marked += slt; break; + case '>': marked += sgt; break; + case '"': marked += squot; break; + default : marked += data[i]; + } + } + return marked; +} + +QString QmlMarkupVisitor::markedUpCode() +{ + if (int(cursor) < source.length()) + addExtra(cursor, source.length()); + + return output; +} + +void QmlMarkupVisitor::addExtra(quint32 start, quint32 finish) +{ + if (extraIndex >= extraLocations.length()) { + QString extra = source.mid(start, finish - start); + if (extra.trimmed().isEmpty()) + output += extra; + else + output += protect(extra); // text that should probably have been caught by the parser + + cursor = finish; + return; + } + + while (extraIndex < extraLocations.length()) { + if (extraTypes[extraIndex] == Comment) { + if (extraLocations[extraIndex].offset - 2 >= start) + break; + } else { + if (extraLocations[extraIndex].offset >= start) + break; + } + extraIndex++; + } + + quint32 i = start; + while (i < finish && extraIndex < extraLocations.length()) { + quint32 j = extraLocations[extraIndex].offset - 2; + if (i <= j && j < finish) { + if (i < j) + output += protect(source.mid(i, j - i)); + + quint32 l = extraLocations[extraIndex].length; + if (extraTypes[extraIndex] == Comment) { + if (source.mid(j, 2) == QLatin1String("/*")) + l += 4; + else + l += 2; + output += QLatin1String("<@comment>"); + output += protect(source.mid(j, l)); + output += QLatin1String("</@comment>"); + } else + output += protect(source.mid(j, l)); + + extraIndex++; + i = j + l; + } else + break; + } + + QString extra = source.mid(i, finish - i); + if (extra.trimmed().isEmpty()) + output += extra; + else + output += protect(extra); // text that should probably have been caught by the parser + + cursor = finish; +} + +void QmlMarkupVisitor::addMarkedUpToken( + QQmlJS::AST::SourceLocation &location, const QString &tagName, + const QHash<QString, QString> &attributes) +{ + if (!location.isValid()) + return; + + if (cursor < location.offset) + addExtra(cursor, location.offset); + else if (cursor > location.offset) + return; + + output += QString(QLatin1String("<@%1")).arg(tagName); + foreach (const QString &key, attributes) + output += QString(QLatin1String(" %1=\"%2\"")).arg(key).arg(attributes[key]); + output += QString(QLatin1String(">%2</@%3>")).arg(protect(sourceText(location)), tagName); + cursor += location.length; +} + +QString QmlMarkupVisitor::sourceText(QQmlJS::AST::SourceLocation &location) +{ + return source.mid(location.offset, location.length); +} + +void QmlMarkupVisitor::addVerbatim(QQmlJS::AST::SourceLocation first, + QQmlJS::AST::SourceLocation last) +{ + if (!first.isValid()) + return; + + quint32 start = first.begin(); + quint32 finish; + if (last.isValid()) + finish = last.end(); + else + finish = first.end(); + + if (cursor < start) + addExtra(cursor, start); + else if (cursor > start) + return; + + QString text = source.mid(start, finish - start); + output += protect(text); + cursor = finish; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::UiImport *uiimport) +{ + addVerbatim(uiimport->importToken); + if (!uiimport->importUri) + addMarkedUpToken(uiimport->fileNameToken, QLatin1String("headerfile")); + return false; +} + +void QmlMarkupVisitor::endVisit(QQmlJS::AST::UiImport *uiimport) +{ + addVerbatim(uiimport->versionToken); + addVerbatim(uiimport->asToken); + addMarkedUpToken(uiimport->importIdToken, QLatin1String("headerfile")); + addVerbatim(uiimport->semicolonToken); +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::UiPublicMember *member) +{ + if (member->type == QQmlJS::AST::UiPublicMember::Property) { + addVerbatim(member->defaultToken); + addVerbatim(member->readonlyToken); + addVerbatim(member->propertyToken); + addVerbatim(member->typeModifierToken); + addMarkedUpToken(member->typeToken, QLatin1String("type")); + addMarkedUpToken(member->identifierToken, QLatin1String("name")); + addVerbatim(member->colonToken); + if (member->binding) + QQmlJS::AST::Node::accept(member->binding, this); + else if (member->statement) + QQmlJS::AST::Node::accept(member->statement, this); + } else { + addVerbatim(member->propertyToken); + addVerbatim(member->typeModifierToken); + addMarkedUpToken(member->typeToken, QLatin1String("type")); + //addVerbatim(member->identifierToken); + QQmlJS::AST::Node::accept(member->parameters, this); + } + addVerbatim(member->semicolonToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::UiObjectInitializer *initializer) +{ + addVerbatim(initializer->lbraceToken, initializer->lbraceToken); + return true; +} + +void QmlMarkupVisitor::endVisit(QQmlJS::AST::UiObjectInitializer *initializer) +{ + addVerbatim(initializer->rbraceToken, initializer->rbraceToken); +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::UiObjectBinding *binding) +{ + QQmlJS::AST::Node::accept(binding->qualifiedId, this); + addVerbatim(binding->colonToken); + QQmlJS::AST::Node::accept(binding->qualifiedTypeNameId, this); + QQmlJS::AST::Node::accept(binding->initializer, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::UiScriptBinding *binding) +{ + QQmlJS::AST::Node::accept(binding->qualifiedId, this); + addVerbatim(binding->colonToken); + QQmlJS::AST::Node::accept(binding->statement, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::UiArrayBinding *binding) +{ + QQmlJS::AST::Node::accept(binding->qualifiedId, this); + addVerbatim(binding->colonToken); + addVerbatim(binding->lbracketToken); + QQmlJS::AST::Node::accept(binding->members, this); + addVerbatim(binding->rbracketToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::UiArrayMemberList *list) +{ + for (QQmlJS::AST::UiArrayMemberList *it = list; it; it = it->next) { + QQmlJS::AST::Node::accept(it->member, this); + //addVerbatim(it->commaToken); + } + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::UiQualifiedId *id) +{ + addMarkedUpToken(id->identifierToken, QLatin1String("name")); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::ThisExpression *expression) +{ + addVerbatim(expression->thisToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::IdentifierExpression *identifier) +{ + addMarkedUpToken(identifier->identifierToken, QLatin1String("name")); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::NullExpression *null) +{ + addMarkedUpToken(null->nullToken, QLatin1String("number")); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::TrueLiteral *literal) +{ + addMarkedUpToken(literal->trueToken, QLatin1String("number")); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::FalseLiteral *literal) +{ + addMarkedUpToken(literal->falseToken, QLatin1String("number")); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::NumericLiteral *literal) +{ + addMarkedUpToken(literal->literalToken, QLatin1String("number")); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::StringLiteral *literal) +{ + addMarkedUpToken(literal->literalToken, QLatin1String("string")); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::RegExpLiteral *literal) +{ + addVerbatim(literal->literalToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::ArrayLiteral *literal) +{ + addVerbatim(literal->lbracketToken); + QQmlJS::AST::Node::accept(literal->elements, this); + addVerbatim(literal->rbracketToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::ObjectLiteral *literal) +{ + addVerbatim(literal->lbraceToken); + return true; +} + +void QmlMarkupVisitor::endVisit(QQmlJS::AST::ObjectLiteral *literal) +{ + addVerbatim(literal->rbraceToken); +} + + +bool QmlMarkupVisitor::visit(QQmlJS::AST::ElementList *list) +{ + for (QQmlJS::AST::ElementList *it = list; it; it = it->next) { + QQmlJS::AST::Node::accept(it->expression, this); + //addVerbatim(it->commaToken); + } + QQmlJS::AST::Node::accept(list->elision, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::Elision *elision) +{ + addVerbatim(elision->commaToken, elision->commaToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::PropertyNameAndValue *list) +{ + QQmlJS::AST::Node::accept(list->name, this); + addVerbatim(list->colonToken, list->colonToken); + QQmlJS::AST::Node::accept(list->value, this); + addVerbatim(list->commaToken, list->commaToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::ArrayMemberExpression *expression) +{ + QQmlJS::AST::Node::accept(expression->base, this); + addVerbatim(expression->lbracketToken); + QQmlJS::AST::Node::accept(expression->expression, this); + addVerbatim(expression->rbracketToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::FieldMemberExpression *expression) +{ + QQmlJS::AST::Node::accept(expression->base, this); + addVerbatim(expression->dotToken); + addMarkedUpToken(expression->identifierToken, QLatin1String("name")); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::NewMemberExpression *expression) +{ + addVerbatim(expression->newToken); + QQmlJS::AST::Node::accept(expression->base, this); + addVerbatim(expression->lparenToken); + QQmlJS::AST::Node::accept(expression->arguments, this); + addVerbatim(expression->rparenToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::NewExpression *expression) +{ + addVerbatim(expression->newToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::ArgumentList *list) +{ + addVerbatim(list->commaToken, list->commaToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::PostIncrementExpression *expression) +{ + addVerbatim(expression->incrementToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::PostDecrementExpression *expression) +{ + addVerbatim(expression->decrementToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::DeleteExpression *expression) +{ + addVerbatim(expression->deleteToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::VoidExpression *expression) +{ + addVerbatim(expression->voidToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::TypeOfExpression *expression) +{ + addVerbatim(expression->typeofToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::PreIncrementExpression *expression) +{ + addVerbatim(expression->incrementToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::PreDecrementExpression *expression) +{ + addVerbatim(expression->decrementToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::UnaryPlusExpression *expression) +{ + addVerbatim(expression->plusToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::UnaryMinusExpression *expression) +{ + addVerbatim(expression->minusToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::TildeExpression *expression) +{ + addVerbatim(expression->tildeToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::NotExpression *expression) +{ + addVerbatim(expression->notToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::BinaryExpression *expression) +{ + QQmlJS::AST::Node::accept(expression->left, this); + addMarkedUpToken(expression->operatorToken, QLatin1String("op")); + QQmlJS::AST::Node::accept(expression->right, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::ConditionalExpression *expression) +{ + QQmlJS::AST::Node::accept(expression->expression, this); + addVerbatim(expression->questionToken); + QQmlJS::AST::Node::accept(expression->ok, this); + addVerbatim(expression->colonToken); + QQmlJS::AST::Node::accept(expression->ko, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::Expression *expression) +{ + QQmlJS::AST::Node::accept(expression->left, this); + addVerbatim(expression->commaToken); + QQmlJS::AST::Node::accept(expression->right, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::Block *block) +{ + addVerbatim(block->lbraceToken); + return true; +} + +void QmlMarkupVisitor::endVisit(QQmlJS::AST::Block *block) +{ + addVerbatim(block->rbraceToken); +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::VariableStatement *statement) +{ + addVerbatim(statement->declarationKindToken); + QQmlJS::AST::Node::accept(statement->declarations, this); + addVerbatim(statement->semicolonToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::VariableDeclarationList *list) +{ + for (QQmlJS::AST::VariableDeclarationList *it = list; it; it = it->next) { + QQmlJS::AST::Node::accept(it->declaration, this); + addVerbatim(it->commaToken); + } + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::VariableDeclaration *declaration) +{ + addMarkedUpToken(declaration->identifierToken, QLatin1String("name")); + QQmlJS::AST::Node::accept(declaration->expression, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::EmptyStatement *statement) +{ + addVerbatim(statement->semicolonToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::ExpressionStatement *statement) +{ + QQmlJS::AST::Node::accept(statement->expression, this); + addVerbatim(statement->semicolonToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::IfStatement *statement) +{ + addMarkedUpToken(statement->ifToken, QLatin1String("keyword")); + addVerbatim(statement->lparenToken); + QQmlJS::AST::Node::accept(statement->expression, this); + addVerbatim(statement->rparenToken); + QQmlJS::AST::Node::accept(statement->ok, this); + if (statement->ko) { + addMarkedUpToken(statement->elseToken, QLatin1String("keyword")); + QQmlJS::AST::Node::accept(statement->ko, this); + } + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::DoWhileStatement *statement) +{ + addMarkedUpToken(statement->doToken, QLatin1String("keyword")); + QQmlJS::AST::Node::accept(statement->statement, this); + addMarkedUpToken(statement->whileToken, QLatin1String("keyword")); + addVerbatim(statement->lparenToken); + QQmlJS::AST::Node::accept(statement->expression, this); + addVerbatim(statement->rparenToken); + addVerbatim(statement->semicolonToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::WhileStatement *statement) +{ + addMarkedUpToken(statement->whileToken, QLatin1String("keyword")); + addVerbatim(statement->lparenToken); + QQmlJS::AST::Node::accept(statement->expression, this); + addVerbatim(statement->rparenToken); + QQmlJS::AST::Node::accept(statement->statement, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::ForStatement *statement) +{ + addMarkedUpToken(statement->forToken, QLatin1String("keyword")); + addVerbatim(statement->lparenToken); + QQmlJS::AST::Node::accept(statement->initialiser, this); + addVerbatim(statement->firstSemicolonToken); + QQmlJS::AST::Node::accept(statement->condition, this); + addVerbatim(statement->secondSemicolonToken); + QQmlJS::AST::Node::accept(statement->expression, this); + addVerbatim(statement->rparenToken); + QQmlJS::AST::Node::accept(statement->statement, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::LocalForStatement *statement) +{ + addMarkedUpToken(statement->forToken, QLatin1String("keyword")); + addVerbatim(statement->lparenToken); + addMarkedUpToken(statement->varToken, QLatin1String("keyword")); + QQmlJS::AST::Node::accept(statement->declarations, this); + addVerbatim(statement->firstSemicolonToken); + QQmlJS::AST::Node::accept(statement->condition, this); + addVerbatim(statement->secondSemicolonToken); + QQmlJS::AST::Node::accept(statement->expression, this); + addVerbatim(statement->rparenToken); + QQmlJS::AST::Node::accept(statement->statement, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::ForEachStatement *statement) +{ + addMarkedUpToken(statement->forToken, QLatin1String("keyword")); + addVerbatim(statement->lparenToken); + QQmlJS::AST::Node::accept(statement->initialiser, this); + addVerbatim(statement->inToken); + QQmlJS::AST::Node::accept(statement->expression, this); + addVerbatim(statement->rparenToken); + QQmlJS::AST::Node::accept(statement->statement, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::LocalForEachStatement *statement) +{ + addMarkedUpToken(statement->forToken, QLatin1String("keyword")); + addVerbatim(statement->lparenToken); + addMarkedUpToken(statement->varToken, QLatin1String("keyword")); + QQmlJS::AST::Node::accept(statement->declaration, this); + addVerbatim(statement->inToken); + QQmlJS::AST::Node::accept(statement->expression, this); + addVerbatim(statement->rparenToken); + QQmlJS::AST::Node::accept(statement->statement, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::ContinueStatement *statement) +{ + addMarkedUpToken(statement->continueToken, QLatin1String("keyword")); + addMarkedUpToken(statement->identifierToken, QLatin1String("name")); + addVerbatim(statement->semicolonToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::BreakStatement *statement) +{ + addMarkedUpToken(statement->breakToken, QLatin1String("keyword")); + addMarkedUpToken(statement->identifierToken, QLatin1String("name")); + addVerbatim(statement->semicolonToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::ReturnStatement *statement) +{ + addMarkedUpToken(statement->returnToken, QLatin1String("keyword")); + QQmlJS::AST::Node::accept(statement->expression, this); + addVerbatim(statement->semicolonToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::WithStatement *statement) +{ + addMarkedUpToken(statement->withToken, QLatin1String("keyword")); + addVerbatim(statement->lparenToken); + QQmlJS::AST::Node::accept(statement->expression, this); + addVerbatim(statement->rparenToken); + QQmlJS::AST::Node::accept(statement->statement, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::CaseBlock *block) +{ + addVerbatim(block->lbraceToken); + return true; +} + +void QmlMarkupVisitor::endVisit(QQmlJS::AST::CaseBlock *block) +{ + addVerbatim(block->rbraceToken, block->rbraceToken); +} + + +bool QmlMarkupVisitor::visit(QQmlJS::AST::SwitchStatement *statement) +{ + addMarkedUpToken(statement->switchToken, QLatin1String("keyword")); + addVerbatim(statement->lparenToken); + QQmlJS::AST::Node::accept(statement->expression, this); + addVerbatim(statement->rparenToken); + QQmlJS::AST::Node::accept(statement->block, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::CaseClause *clause) +{ + addMarkedUpToken(clause->caseToken, QLatin1String("keyword")); + QQmlJS::AST::Node::accept(clause->expression, this); + addVerbatim(clause->colonToken); + QQmlJS::AST::Node::accept(clause->statements, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::DefaultClause *clause) +{ + addMarkedUpToken(clause->defaultToken, QLatin1String("keyword")); + addVerbatim(clause->colonToken, clause->colonToken); + return true; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::LabelledStatement *statement) +{ + addMarkedUpToken(statement->identifierToken, QLatin1String("name")); + addVerbatim(statement->colonToken); + QQmlJS::AST::Node::accept(statement->statement, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::ThrowStatement *statement) +{ + addMarkedUpToken(statement->throwToken, QLatin1String("keyword")); + QQmlJS::AST::Node::accept(statement->expression, this); + addVerbatim(statement->semicolonToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::Catch *c) +{ + addMarkedUpToken(c->catchToken, QLatin1String("keyword")); + addVerbatim(c->lparenToken); + addMarkedUpToken(c->identifierToken, QLatin1String("name")); + addVerbatim(c->rparenToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::Finally *f) +{ + addMarkedUpToken(f->finallyToken, QLatin1String("keyword")); + QQmlJS::AST::Node::accept(f->statement, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::TryStatement *statement) +{ + addMarkedUpToken(statement->tryToken, QLatin1String("keyword")); + QQmlJS::AST::Node::accept(statement->statement, this); + QQmlJS::AST::Node::accept(statement->catchExpression, this); + QQmlJS::AST::Node::accept(statement->finallyExpression, this); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::FunctionExpression *expression) +{ + addMarkedUpToken(expression->functionToken, QLatin1String("keyword")); + addMarkedUpToken(expression->identifierToken, QLatin1String("name")); + addVerbatim(expression->lparenToken); + QQmlJS::AST::Node::accept(expression->formals, this); + addVerbatim(expression->rparenToken); + addVerbatim(expression->lbraceToken); + QQmlJS::AST::Node::accept(expression->body, this); + addVerbatim(expression->rbraceToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::FunctionDeclaration *declaration) +{ + addMarkedUpToken(declaration->functionToken, QLatin1String("keyword")); + addMarkedUpToken(declaration->identifierToken, QLatin1String("name")); + addVerbatim(declaration->lparenToken); + QQmlJS::AST::Node::accept(declaration->formals, this); + addVerbatim(declaration->rparenToken); + addVerbatim(declaration->lbraceToken); + QQmlJS::AST::Node::accept(declaration->body, this); + addVerbatim(declaration->rbraceToken); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::FormalParameterList *list) +{ + addVerbatim(list->commaToken); + addMarkedUpToken(list->identifierToken, QLatin1String("name")); + return false; +} + +bool QmlMarkupVisitor::visit(QQmlJS::AST::DebuggerStatement *statement) +{ + addVerbatim(statement->debuggerToken); + addVerbatim(statement->semicolonToken); + return true; +} + +// Elements and items are represented by UiObjectDefinition nodes. + +bool QmlMarkupVisitor::visit(QQmlJS::AST::UiObjectDefinition *definition) +{ + QHash<QString, QString> attributes; + addMarkedUpToken(definition->qualifiedTypeNameId->identifierToken, QLatin1String("type")); + QQmlJS::AST::Node::accept(definition->initializer, this); + return false; +} + +QT_END_NAMESPACE diff --git a/src/qdoc/qmlmarkupvisitor.h b/src/qdoc/qmlmarkupvisitor.h new file mode 100644 index 000000000..ab1180fd4 --- /dev/null +++ b/src/qdoc/qmlmarkupvisitor.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLMARKUPVISITOR_H +#define QMLMARKUPVISITOR_H + +#include "node.h" +#include "tree.h" + +#include <qstring.h> +#include <private/qqmljsastvisitor_p.h> +#include <private/qqmljsengine_p.h> + +QT_BEGIN_NAMESPACE + +class QmlMarkupVisitor : public QQmlJS::AST::Visitor +{ +public: + enum ExtraType{ + Comment, + Pragma + }; + + QmlMarkupVisitor(const QString &code, + const QList<QQmlJS::AST::SourceLocation> &pragmas, + QQmlJS::Engine *engine); + virtual ~QmlMarkupVisitor(); + + QString markedUpCode(); + + virtual bool visit(QQmlJS::AST::UiImport *) Q_DECL_OVERRIDE; + virtual void endVisit(QQmlJS::AST::UiImport *) Q_DECL_OVERRIDE; + + virtual bool visit(QQmlJS::AST::UiPublicMember *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::UiObjectDefinition *) Q_DECL_OVERRIDE; + + virtual bool visit(QQmlJS::AST::UiObjectInitializer *) Q_DECL_OVERRIDE; + virtual void endVisit(QQmlJS::AST::UiObjectInitializer *) Q_DECL_OVERRIDE; + + virtual bool visit(QQmlJS::AST::UiObjectBinding *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::UiScriptBinding *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::UiArrayBinding *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::UiArrayMemberList *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::UiQualifiedId *) Q_DECL_OVERRIDE; + + virtual bool visit(QQmlJS::AST::ThisExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::IdentifierExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::NullExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::TrueLiteral *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::FalseLiteral *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::NumericLiteral *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::StringLiteral *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::RegExpLiteral *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::ArrayLiteral *) Q_DECL_OVERRIDE; + + virtual bool visit(QQmlJS::AST::ObjectLiteral *) Q_DECL_OVERRIDE; + virtual void endVisit(QQmlJS::AST::ObjectLiteral *) Q_DECL_OVERRIDE; + + virtual bool visit(QQmlJS::AST::ElementList *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::Elision *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::PropertyNameAndValue *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::ArrayMemberExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::FieldMemberExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::NewMemberExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::NewExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::ArgumentList *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::PostIncrementExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::PostDecrementExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::DeleteExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::VoidExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::TypeOfExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::PreIncrementExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::PreDecrementExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::UnaryPlusExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::UnaryMinusExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::TildeExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::NotExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::BinaryExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::ConditionalExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::Expression *) Q_DECL_OVERRIDE; + + virtual bool visit(QQmlJS::AST::Block *) Q_DECL_OVERRIDE; + virtual void endVisit(QQmlJS::AST::Block *) Q_DECL_OVERRIDE; + + virtual bool visit(QQmlJS::AST::VariableStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::VariableDeclarationList *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::VariableDeclaration *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::EmptyStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::ExpressionStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::IfStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::DoWhileStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::WhileStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::ForStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::LocalForStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::ForEachStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::LocalForEachStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::ContinueStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::BreakStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::ReturnStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::WithStatement *) Q_DECL_OVERRIDE; + + virtual bool visit(QQmlJS::AST::CaseBlock *) Q_DECL_OVERRIDE; + virtual void endVisit(QQmlJS::AST::CaseBlock *) Q_DECL_OVERRIDE; + + virtual bool visit(QQmlJS::AST::SwitchStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::CaseClause *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::DefaultClause *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::LabelledStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::ThrowStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::TryStatement *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::Catch *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::Finally *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::FunctionDeclaration *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::FunctionExpression *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::FormalParameterList *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::DebuggerStatement *) Q_DECL_OVERRIDE; + +protected: + QString protect(const QString &string); + +private: + typedef QHash<QString, QString> StringHash; + void addExtra(quint32 start, quint32 finish); + void addMarkedUpToken(QQmlJS::AST::SourceLocation &location, + const QString &text, + const StringHash &attributes = StringHash()); + void addVerbatim(QQmlJS::AST::SourceLocation first, + QQmlJS::AST::SourceLocation last = QQmlJS::AST::SourceLocation()); + QString sourceText(QQmlJS::AST::SourceLocation &location); + + QQmlJS::Engine *engine; + QVector<ExtraType> extraTypes; + QList<QQmlJS::AST::SourceLocation> extraLocations; + QString source; + QString output; + quint32 cursor; + int extraIndex; +}; +Q_DECLARE_TYPEINFO(QmlMarkupVisitor::ExtraType, Q_PRIMITIVE_TYPE); + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/qmlvisitor.cpp b/src/qdoc/qmlvisitor.cpp new file mode 100644 index 000000000..654922929 --- /dev/null +++ b/src/qdoc/qmlvisitor.cpp @@ -0,0 +1,827 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlvisitor.h" + +#include "node.h" +#include "codeparser.h" +#include "qdocdatabase.h" +#include "tokenizer.h" + +#include <qfileinfo.h> +#include <qstringlist.h> +#include <qglobal.h> +#include <qdebug.h> +#include <private/qqmljsast_p.h> +#include <private/qqmljsastfwd_p.h> +#include <private/qqmljsengine_p.h> + +QT_BEGIN_NAMESPACE + +#define COMMAND_DEPRECATED Doc::alias(QLatin1String("deprecated")) +#define COMMAND_INGROUP Doc::alias(QLatin1String("ingroup")) +#define COMMAND_INTERNAL Doc::alias(QLatin1String("internal")) +#define COMMAND_OBSOLETE Doc::alias(QLatin1String("obsolete")) +#define COMMAND_PAGEKEYWORDS Doc::alias(QLatin1String("pagekeywords")) +#define COMMAND_PRELIMINARY Doc::alias(QLatin1String("preliminary")) +#define COMMAND_SINCE Doc::alias(QLatin1String("since")) +#define COMMAND_WRAPPER Doc::alias(QLatin1String("wrapper")) +#define COMMAND_NOAUTOLIST Doc::alias(QLatin1String("noautolist")) + +#define COMMAND_ABSTRACT Doc::alias(QLatin1String("abstract")) +#define COMMAND_QMLABSTRACT Doc::alias(QLatin1String("qmlabstract")) +#define COMMAND_QMLCLASS Doc::alias(QLatin1String("qmlclass")) +#define COMMAND_QMLTYPE Doc::alias(QLatin1String("qmltype")) +#define COMMAND_QMLMODULE Doc::alias(QLatin1String("qmlmodule")) +#define COMMAND_QMLPROPERTY Doc::alias(QLatin1String("qmlproperty")) +#define COMMAND_QMLPROPERTYGROUP Doc::alias(QLatin1String("qmlpropertygroup")) +#define COMMAND_QMLATTACHEDPROPERTY Doc::alias(QLatin1String("qmlattachedproperty")) +#define COMMAND_QMLINHERITS Doc::alias(QLatin1String("inherits")) +#define COMMAND_QMLINSTANTIATES Doc::alias(QLatin1String("instantiates")) +#define COMMAND_INQMLMODULE Doc::alias(QLatin1String("inqmlmodule")) +#define COMMAND_QMLSIGNAL Doc::alias(QLatin1String("qmlsignal")) +#define COMMAND_QMLATTACHEDSIGNAL Doc::alias(QLatin1String("qmlattachedsignal")) +#define COMMAND_QMLMETHOD Doc::alias(QLatin1String("qmlmethod")) +#define COMMAND_QMLATTACHEDMETHOD Doc::alias(QLatin1String("qmlattachedmethod")) +#define COMMAND_QMLDEFAULT Doc::alias(QLatin1String("default")) +#define COMMAND_QMLREADONLY Doc::alias(QLatin1String("readonly")) +#define COMMAND_QMLBASICTYPE Doc::alias(QLatin1String("qmlbasictype")) + +#define COMMAND_JSTYPE Doc::alias(QLatin1String("jstype")) +#define COMMAND_JSMODULE Doc::alias(QLatin1String("jsmodule")) +#define COMMAND_JSPROPERTY Doc::alias(QLatin1String("jsproperty")) +#define COMMAND_JSPROPERTYGROUP Doc::alias(QLatin1String("jspropertygroup")) +#define COMMAND_JSATTACHEDPROPERTY Doc::alias(QLatin1String("jsattachedproperty")) +#define COMMAND_INJSMODULE Doc::alias(QLatin1String("injsmodule")) +#define COMMAND_JSSIGNAL Doc::alias(QLatin1String("jssignal")) +#define COMMAND_JSATTACHEDSIGNAL Doc::alias(QLatin1String("jsattachedsignal")) +#define COMMAND_JSMETHOD Doc::alias(QLatin1String("jsmethod")) +#define COMMAND_JSATTACHEDMETHOD Doc::alias(QLatin1String("jsattachedmethod")) +#define COMMAND_JSBASICTYPE Doc::alias(QLatin1String("jsbasictype")) + +/*! + The constructor stores all the parameters in local data members. + */ +QmlDocVisitor::QmlDocVisitor(const QString &filePath, + const QString &code, + QQmlJS::Engine *engine, + const QSet<QString> &commands, + const QSet<QString> &topics) + : nestingLevel(0) +{ + lastEndOffset = 0; + this->filePath_ = filePath; + this->name = QFileInfo(filePath).baseName(); + document = code; + this->engine = engine; + this->commands_ = commands; + this->topics_ = topics; + current = QDocDatabase::qdocDB()->primaryTreeRoot(); +} + +/*! + The destructor does nothing. + */ +QmlDocVisitor::~QmlDocVisitor() +{ + // nothing. +} + +/*! + Returns the location of the nearest comment above the \a offset. + */ +QQmlJS::AST::SourceLocation QmlDocVisitor::precedingComment(quint32 offset) const +{ + QListIterator<QQmlJS::AST::SourceLocation> it(engine->comments()); + it.toBack(); + + while (it.hasPrevious()) { + + QQmlJS::AST::SourceLocation loc = it.previous(); + + if (loc.begin() <= lastEndOffset) { + // Return if we reach the end of the preceding structure. + break; + } + else if (usedComments.contains(loc.begin())) { + // Return if we encounter a previously used comment. + break; + } + else if (loc.begin() > lastEndOffset && loc.end() < offset) { + // Only examine multiline comments in order to avoid snippet markers. + if (document.at(loc.offset - 1) == QLatin1Char('*')) { + QString comment = document.mid(loc.offset, loc.length); + if (comment.startsWith(QLatin1Char('!')) || comment.startsWith(QLatin1Char('*'))) { + return loc; + } + } + } + } + + return QQmlJS::AST::SourceLocation(); +} + +class QmlSignatureParser +{ + public: + QmlSignatureParser(FunctionNode* func, const QString& signature, const Location& loc); + void readToken() { tok_ = tokenizer_->getToken(); } + QString lexeme() { return tokenizer_->lexeme(); } + QString previousLexeme() { return tokenizer_->previousLexeme(); } + + bool match(int target); + bool matchDataType(CodeChunk* dataType, QString* var); + bool matchParameter(); + bool matchFunctionDecl(); + + private: + QString signature_; + QStringList names_; + QString funcName_; + Tokenizer* tokenizer_; + int tok_; + FunctionNode* func_; + const Location& location_; +}; + +/*! + Finds the nearest unused qdoc comment above the QML entity + represented by the \a node and processes the qdoc commands + in that comment. The processed documentation is stored in + the \a node. + + If a qdoc comment is found for \a location, true is returned. + If a comment is not found there, false is returned. + */ +bool QmlDocVisitor::applyDocumentation(QQmlJS::AST::SourceLocation location, Node* node) +{ + QQmlJS::AST::SourceLocation loc = precedingComment(location.begin()); + + if (loc.isValid()) { + QString source = document.mid(loc.offset, loc.length); + Location start(filePath_); + start.setLineNo(loc.startLine); + start.setColumnNo(loc.startColumn); + Location finish(filePath_); + finish.setLineNo(loc.startLine); + finish.setColumnNo(loc.startColumn); + + Doc doc(start, finish, source.mid(1), commands_, topics_); + const TopicList& topicsUsed = doc.topicsUsed(); + NodeList nodes; + Node* nodePassedIn = node; + Aggregate* parent = nodePassedIn->parent(); + node->setDoc(doc); + nodes.append(node); + if (topicsUsed.size() > 0) { + for (int i=0; i<topicsUsed.size(); ++i) { + QString topic = topicsUsed.at(i).topic; + QString args = topicsUsed.at(i).args; + if ((topic == COMMAND_QMLPROPERTY) || (topic == COMMAND_QMLATTACHEDPROPERTY) || + (topic == COMMAND_JSPROPERTY) || (topic == COMMAND_JSATTACHEDPROPERTY)) { + QmlPropArgs qpa; + if (splitQmlPropertyArg(doc, args, qpa)) { + if (qpa.name_ == nodePassedIn->name()) { + if (nodePassedIn->isAlias()) + nodePassedIn->setDataType(qpa.type_); + } + else { + bool isAttached = (topic == COMMAND_QMLATTACHEDPROPERTY) || + (topic == COMMAND_JSATTACHEDPROPERTY); + QmlPropertyNode* n = parent->hasQmlProperty(qpa.name_, isAttached); + if (n == 0) + n = new QmlPropertyNode(parent, qpa.name_, qpa.type_, isAttached); + n->setLocation(doc.location()); + n->setDoc(doc); + n->setReadOnly(nodePassedIn->isReadOnly()); + if (nodePassedIn->isDefault()) + n->setDefault(); + if (isAttached) + n->setReadOnly(0); + if ((topic == COMMAND_JSPROPERTY) || + (topic == COMMAND_JSATTACHEDPROPERTY)) + n->setGenus(Node::JS); + nodes.append(n); + } + } + else + qDebug() << " FAILED TO PARSE QML OR JS PROPERTY:" << topic << args; + } + else if ((topic == COMMAND_QMLMETHOD) || (topic == COMMAND_QMLATTACHEDMETHOD) || + (topic == COMMAND_JSMETHOD) || (topic == COMMAND_JSATTACHEDMETHOD)) { + if (node->isFunction()) { + FunctionNode* fn = static_cast<FunctionNode*>(node); + QmlSignatureParser qsp(fn, args, doc.location()); + } + } + } + } + for (int i=0; i<nodes.size(); ++i) + applyMetacommands(loc, nodes.at(i), doc); + usedComments.insert(loc.offset); + if (doc.isEmpty()) { + return false; + } + return true; + } + Location codeLoc(filePath_); + codeLoc.setLineNo(location.startLine); + node->setLocation(codeLoc); + return false; +} + +QmlSignatureParser::QmlSignatureParser(FunctionNode* func, const QString& signature, const Location& loc) + : signature_(signature), func_(func), location_(loc) +{ + QByteArray latin1 = signature.toLatin1(); + Tokenizer stringTokenizer(location_, latin1); + stringTokenizer.setParsingFnOrMacro(true); + tokenizer_ = &stringTokenizer; + readToken(); + matchFunctionDecl(); +} + +/*! + If the current token matches \a target, read the next + token and return true. Otherwise, don't read the next + token, and return false. + */ +bool QmlSignatureParser::match(int target) +{ + if (tok_ == target) { + readToken(); + return true; + } + return false; +} + +/*! + Parse a QML data type into \a dataType and an optional + variable name into \a var. + */ +bool QmlSignatureParser::matchDataType(CodeChunk* dataType, QString* var) +{ + /* + This code is really hard to follow... sorry. The loop is there to match + Alpha::Beta::Gamma::...::Omega. + */ + for (;;) { + bool virgin = true; + + if (tok_ != Tok_Ident) { + while (match(Tok_signed) || + match(Tok_unsigned) || + match(Tok_short) || + match(Tok_long) || + match(Tok_int64)) { + dataType->append(previousLexeme()); + virgin = false; + } + } + + if (virgin) { + if (match(Tok_Ident)) { + dataType->append(previousLexeme()); + } + else if (match(Tok_void) || + match(Tok_int) || + match(Tok_char) || + match(Tok_double) || + match(Tok_Ellipsis)) + dataType->append(previousLexeme()); + else + return false; + } + else if (match(Tok_int) || + match(Tok_char) || + match(Tok_double)) { + dataType->append(previousLexeme()); + } + + if (match(Tok_Gulbrandsen)) + dataType->append(previousLexeme()); + else + break; + } + + while (match(Tok_Ampersand) || + match(Tok_Aster) || + match(Tok_const) || + match(Tok_Caret)) + dataType->append(previousLexeme()); + + /* + The usual case: Look for an optional identifier, then for + some array brackets. + */ + dataType->appendHotspot(); + + if ((var != 0) && match(Tok_Ident)) + *var = previousLexeme(); + + if (tok_ == Tok_LeftBracket) { + int bracketDepth0 = tokenizer_->bracketDepth(); + while ((tokenizer_->bracketDepth() >= bracketDepth0 && tok_ != Tok_Eoi) || + tok_ == Tok_RightBracket) { + dataType->append(lexeme()); + readToken(); + } + } + return true; +} + +bool QmlSignatureParser::matchParameter() +{ + QString name; + CodeChunk dataType; + CodeChunk defaultValue; + + bool result = matchDataType(&dataType, &name); + if (name.isEmpty()) { + name = dataType.toString(); + dataType.clear(); + } + + if (!result) + return false; + if (match(Tok_Equal)) { + int parenDepth0 = tokenizer_->parenDepth(); + while (tokenizer_->parenDepth() >= parenDepth0 && + (tok_ != Tok_Comma || + tokenizer_->parenDepth() > parenDepth0) && + tok_ != Tok_Eoi) { + defaultValue.append(lexeme()); + readToken(); + } + } + func_->addParameter(Parameter(dataType.toString(), "", name, defaultValue.toString())); + return true; +} + +bool QmlSignatureParser::matchFunctionDecl() +{ + CodeChunk returnType; + + int firstBlank = signature_.indexOf(QChar(' ')); + int leftParen = signature_.indexOf(QChar('(')); + if ((firstBlank > 0) && (leftParen - firstBlank) > 1) { + if (!matchDataType(&returnType, 0)) + return false; + } + + while (match(Tok_Ident)) { + names_.append(previousLexeme()); + if (!match(Tok_Gulbrandsen)) { + funcName_ = previousLexeme(); + names_.pop_back(); + break; + } + } + + if (tok_ != Tok_LeftParen) + return false; + + readToken(); + + func_->setLocation(location_); + func_->setReturnType(returnType.toString()); + + if (tok_ != Tok_RightParen) { + func_->clearParams(); + do { + if (!matchParameter()) + return false; + } while (match(Tok_Comma)); + } + if (!match(Tok_RightParen)) + return false; + return true; +} + +/*! + A QML property argument has the form... + + <type> <component>::<name> + <type> <QML-module>::<component>::<name> + + This function splits the argument into one of those + two forms. The three part form is the old form, which + was used before the creation of QtQuick 2 and Qt + Components. A <QML-module> is the QML equivalent of a + C++ namespace. So this function splits \a arg on "::" + and stores the parts in the \e {type}, \e {module}, + \e {component}, and \a {name}, fields of \a qpa. If it + is successful, it returns \c true. If not enough parts + are found, a qdoc warning is emitted and false is + returned. + */ +bool QmlDocVisitor::splitQmlPropertyArg(const Doc& doc, + const QString& arg, + QmlPropArgs& qpa) +{ + qpa.clear(); + QStringList blankSplit = arg.split(QLatin1Char(' ')); + if (blankSplit.size() > 1) { + qpa.type_ = blankSplit[0]; + QStringList colonSplit(blankSplit[1].split("::")); + if (colonSplit.size() == 3) { + qpa.module_ = colonSplit[0]; + qpa.component_ = colonSplit[1]; + qpa.name_ = colonSplit[2]; + return true; + } + else if (colonSplit.size() == 2) { + qpa.component_ = colonSplit[0]; + qpa.name_ = colonSplit[1]; + return true; + } + else if (colonSplit.size() == 1) { + qpa.name_ = colonSplit[0]; + return true; + } + QString msg = "Unrecognizable QML module/component qualifier for " + arg; + doc.location().warning(tr(msg.toLatin1().data())); + } + else { + QString msg = "Missing property type for " + arg; + doc.location().warning(tr(msg.toLatin1().data())); + } + return false; +} + +/*! + Applies the metacommands found in the comment. + */ +void QmlDocVisitor::applyMetacommands(QQmlJS::AST::SourceLocation, + Node* node, + Doc& doc) +{ + QDocDatabase* qdb = QDocDatabase::qdocDB(); + QSet<QString> metacommands = doc.metaCommandsUsed(); + if (metacommands.count() > 0) { + metacommands.subtract(topics_); + QSet<QString>::iterator i = metacommands.begin(); + while (i != metacommands.end()) { + QString command = *i; + ArgList args = doc.metaCommandArgs(command); + if ((command == COMMAND_QMLABSTRACT) || (command == COMMAND_ABSTRACT)) { + if (node->isQmlType() || node->isJsType()) { + node->setAbstract(true); + } + } + else if (command == COMMAND_DEPRECATED) { + node->setStatus(Node::Obsolete); + } + else if ((command == COMMAND_INQMLMODULE) || (command == COMMAND_INJSMODULE)) { + qdb->addToQmlModule(args[0].first,node); + } + else if (command == COMMAND_QMLINHERITS) { + if (node->name() == args[0].first) + doc.location().warning(tr("%1 tries to inherit itself").arg(args[0].first)); + else if (node->isQmlType() || node->isJsType()) { + QmlTypeNode *qmlType = static_cast<QmlTypeNode*>(node); + qmlType->setQmlBaseName(args[0].first); + QmlTypeNode::addInheritedBy(args[0].first,node); + } + } + else if (command == COMMAND_QMLDEFAULT) { + if (node->isQmlProperty() || node->isJsProperty()) { + QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node); + qpn->setDefault(); + } + } + else if (command == COMMAND_QMLREADONLY) { + if (node->isQmlProperty() || node->isJsProperty()) { + QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node); + qpn->setReadOnly(1); + } + } + else if ((command == COMMAND_INGROUP) && !args.isEmpty()) { + ArgList::ConstIterator argsIter = args.constBegin(); + while (argsIter != args.constEnd()) { + QDocDatabase::qdocDB()->addToGroup(argsIter->first, node); + ++argsIter; + } + } + else if (command == COMMAND_INTERNAL) { + node->setStatus(Node::Internal); + } + else if (command == COMMAND_OBSOLETE) { + node->setStatus(Node::Obsolete); + } + else if (command == COMMAND_PAGEKEYWORDS) { + // Not done yet. Do we need this? + } + else if (command == COMMAND_PRELIMINARY) { + node->setStatus(Node::Preliminary); + } + else if (command == COMMAND_SINCE) { + QString arg = args[0].first; //.join(' '); + node->setSince(arg); + } + else if (command == COMMAND_WRAPPER) { + node->setWrapper(); + } + else { + doc.location().warning(tr("The \\%1 command is ignored in QML files").arg(command)); + } + ++i; + } + } +} + +/*! + Reconstruct the qualified \a id using dot notation + and return the fully qualified string. + */ +QString QmlDocVisitor::getFullyQualifiedId(QQmlJS::AST::UiQualifiedId *id) +{ + QString result; + if (id) { + result = id->name.toString(); + id = id->next; + while (id != 0) { + result += QChar('.') + id->name.toString(); + id = id->next; + } + } + return result; +} + +/*! + Begin the visit of the object \a definition, recording it in the + qdoc database. Increment the object nesting level, which is used + to test whether we are at the public API level. The public level + is level 1. +*/ +bool QmlDocVisitor::visit(QQmlJS::AST::UiObjectDefinition *definition) +{ + QString type = getFullyQualifiedId(definition->qualifiedTypeNameId); + nestingLevel++; + + if (current->type() == Node::Namespace) { + QmlTypeNode *component = new QmlTypeNode(current, name); + component->setTitle(name); + component->setImportList(importList); + importList.clear(); + if (applyDocumentation(definition->firstSourceLocation(), component)) { + QmlTypeNode::addInheritedBy(type, component); + component->setQmlBaseName(type); + } + current = component; + } + + return true; +} + +/*! + End the visit of the object \a definition. In particular, + decrement the object nesting level, which is used to test + whether we are at the public API level. The public API + level is level 1. It won't decrement below 0. + */ +void QmlDocVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *definition) +{ + if (nestingLevel > 0) { + --nestingLevel; + } + lastEndOffset = definition->lastSourceLocation().end(); +} + +bool QmlDocVisitor::visit(QQmlJS::AST::UiImport *import) +{ + QString name = document.mid(import->fileNameToken.offset, import->fileNameToken.length); + if (name[0] == '\"') + name = name.mid(1, name.length()-2); + QString version = document.mid(import->versionToken.offset, import->versionToken.length); + QString importId = document.mid(import->importIdToken.offset, import->importIdToken.length); + QString importUri = getFullyQualifiedId(import->importUri); + QString reconstructed = importUri + QLatin1Char(' ') + version; + importList.append(ImportRec(name, version, importId, importUri)); + + return true; +} + +void QmlDocVisitor::endVisit(QQmlJS::AST::UiImport *definition) +{ + lastEndOffset = definition->lastSourceLocation().end(); +} + +bool QmlDocVisitor::visit(QQmlJS::AST::UiObjectBinding *) +{ + ++nestingLevel; + return true; +} + +void QmlDocVisitor::endVisit(QQmlJS::AST::UiObjectBinding *) +{ + --nestingLevel; +} + +bool QmlDocVisitor::visit(QQmlJS::AST::UiArrayBinding *) +{ + return true; +} + +void QmlDocVisitor::endVisit(QQmlJS::AST::UiArrayBinding *) +{ +} + +/*! + Visits the public \a member declaration, which can be a + signal or a property. It is a custom signal or property. + Only visit the \a member if the nestingLevel is 1. +*/ +bool QmlDocVisitor::visit(QQmlJS::AST::UiPublicMember *member) +{ + if (nestingLevel > 1) { + return true; + } + switch (member->type) { + case QQmlJS::AST::UiPublicMember::Signal: + { + if (current->isQmlType() || current->isJsType()) { + QmlTypeNode *qmlType = static_cast<QmlTypeNode *>(current); + if (qmlType) { + + QString name = member->name.toString(); + FunctionNode *qmlSignal = new FunctionNode(Node::QmlSignal, current, name, false); + + QVector<Parameter> parameters; + for (QQmlJS::AST::UiParameterList *it = member->parameters; it; it = it->next) { + if (!it->type.isEmpty() && !it->name.isEmpty()) + parameters.append(Parameter(it->type.toString(), QString(), it->name.toString())); + } + + qmlSignal->setParameters(parameters); + applyDocumentation(member->firstSourceLocation(), qmlSignal); + } + } + break; + } + case QQmlJS::AST::UiPublicMember::Property: + { + QString type = member->memberType.toString(); + QString name = member->name.toString(); + if (current->isQmlType() || current->isJsType()) { + QmlTypeNode *qmlType = static_cast<QmlTypeNode *>(current); + if (qmlType) { + QString name = member->name.toString(); + QmlPropertyNode* qmlPropNode = qmlType->hasQmlProperty(name); + if (qmlPropNode == 0) { + qmlPropNode = new QmlPropertyNode(qmlType, name, type, false); + if (current->isJsType()) + qmlPropNode->setGenus(Node::JS); + } + qmlPropNode->setReadOnly(member->isReadonlyMember); + if (member->isDefaultMember) + qmlPropNode->setDefault(); + applyDocumentation(member->firstSourceLocation(), qmlPropNode); + } + } + break; + } + default: + return false; + } + + return true; +} + +/*! + End the visit of the \a member. + */ +void QmlDocVisitor::endVisit(QQmlJS::AST::UiPublicMember* member) +{ + lastEndOffset = member->lastSourceLocation().end(); +} + +bool QmlDocVisitor::visit(QQmlJS::AST::IdentifierPropertyName *) +{ + return true; +} + +/*! + Begin the visit of the function declaration \a fd, but only + if the nesting level is 1. + */ +bool QmlDocVisitor::visit(QQmlJS::AST::FunctionDeclaration* fd) +{ + if (nestingLevel > 1) { + return true; + } + if (current->isQmlType() || current->isJsType()) { + QmlTypeNode* qmlType = static_cast<QmlTypeNode*>(current); + if (qmlType) { + QString name = fd->name.toString(); + FunctionNode* qmlMethod = new FunctionNode(Node::QmlMethod, current, name, false); + if (current->isJsType()) + qmlMethod->setGenus(Node::JS); + int overloads = 0; + NodeList::ConstIterator i = current->childNodes().constBegin(); + while (i != current->childNodes().constEnd()) { + if ((*i)->name() == name) + overloads++; + i++; + } + if (overloads > 1) + qmlMethod->setOverloadFlag(true); + QVector<Parameter> parameters; + QQmlJS::AST::FormalParameterList* formals = fd->formals; + if (formals) { + QQmlJS::AST::FormalParameterList* fpl = formals; + do { + parameters.append(Parameter(QString(), QString(), fpl->name.toString())); + fpl = fpl->next; + } while (fpl && fpl != formals); + qmlMethod->setParameters(parameters); + } + applyDocumentation(fd->firstSourceLocation(), qmlMethod); + } + } + return true; +} + +/*! + End the visit of the function declaration, \a fd. + */ +void QmlDocVisitor::endVisit(QQmlJS::AST::FunctionDeclaration* fd) +{ + lastEndOffset = fd->lastSourceLocation().end(); +} + +/*! + Begin the visit of the signal handler declaration \a sb, but only + if the nesting level is 1. + + This visit is now deprecated. It has been decided to document + public signals. If a signal handler must be discussed in the + documentation, that discussion must take place in the comment + for the signal. + */ +bool QmlDocVisitor::visit(QQmlJS::AST::UiScriptBinding* ) +{ +#if 0 + if (nestingLevel > 1) { + return true; + } + if (current->isQmlType() || current->isJsType()) { + QString handler = sb->qualifiedId->name.toString(); + if (handler.length() > 2 && handler.startsWith("on") && handler.at(2).isUpper()) { + QmlTypeNode* qmlType = static_cast<QmlTypeNode*>(current); + if (qmlType) { + FunctionNode* qmlSH = new FunctionNode(Node::QmlSignalHandler,current,handler,false); + applyDocumentation(sb->firstSourceLocation(), qmlSH); + } + } + } +#endif + return true; +} + +void QmlDocVisitor::endVisit(QQmlJS::AST::UiScriptBinding* sb) +{ + lastEndOffset = sb->lastSourceLocation().end(); +} + +bool QmlDocVisitor::visit(QQmlJS::AST::UiQualifiedId* ) +{ + return true; +} + +void QmlDocVisitor::endVisit(QQmlJS::AST::UiQualifiedId* ) +{ + // nothing. +} + +QT_END_NAMESPACE diff --git a/src/qdoc/qmlvisitor.h b/src/qdoc/qmlvisitor.h new file mode 100644 index 000000000..8a23eef75 --- /dev/null +++ b/src/qdoc/qmlvisitor.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLVISITOR_H +#define QMLVISITOR_H + +#include "node.h" + +#include <qstring.h> +#include <private/qqmljsastvisitor_p.h> +#include <private/qqmljsengine_p.h> + +QT_BEGIN_NAMESPACE + +struct QmlPropArgs +{ + QString type_; + QString module_; + QString component_; + QString name_; + + void clear() { + type_.clear(); + module_.clear(); + component_.clear(); + name_.clear(); + } +}; + +class QmlDocVisitor : public QQmlJS::AST::Visitor +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::QmlDocVisitor) + +public: + QmlDocVisitor(const QString &filePath, + const QString &code, + QQmlJS::Engine *engine, + const QSet<QString> &commands, + const QSet<QString> &topics); + virtual ~QmlDocVisitor(); + + bool visit(QQmlJS::AST::UiImport *import) Q_DECL_OVERRIDE; + void endVisit(QQmlJS::AST::UiImport *definition) Q_DECL_OVERRIDE; + + bool visit(QQmlJS::AST::UiObjectDefinition *definition) Q_DECL_OVERRIDE; + void endVisit(QQmlJS::AST::UiObjectDefinition *definition) Q_DECL_OVERRIDE; + + bool visit(QQmlJS::AST::UiPublicMember *member) Q_DECL_OVERRIDE; + void endVisit(QQmlJS::AST::UiPublicMember *definition) Q_DECL_OVERRIDE; + + virtual bool visit(QQmlJS::AST::UiObjectBinding *) Q_DECL_OVERRIDE; + virtual void endVisit(QQmlJS::AST::UiObjectBinding *) Q_DECL_OVERRIDE; + virtual void endVisit(QQmlJS::AST::UiArrayBinding *) Q_DECL_OVERRIDE; + virtual bool visit(QQmlJS::AST::UiArrayBinding *) Q_DECL_OVERRIDE; + + bool visit(QQmlJS::AST::IdentifierPropertyName *idproperty) Q_DECL_OVERRIDE; + + bool visit(QQmlJS::AST::FunctionDeclaration *) Q_DECL_OVERRIDE; + void endVisit(QQmlJS::AST::FunctionDeclaration *) Q_DECL_OVERRIDE; + + bool visit(QQmlJS::AST::UiScriptBinding *) Q_DECL_OVERRIDE; + void endVisit(QQmlJS::AST::UiScriptBinding *) Q_DECL_OVERRIDE; + + bool visit(QQmlJS::AST::UiQualifiedId *) Q_DECL_OVERRIDE; + void endVisit(QQmlJS::AST::UiQualifiedId *) Q_DECL_OVERRIDE; + +private: + QString getFullyQualifiedId(QQmlJS::AST::UiQualifiedId *id); + QQmlJS::AST::SourceLocation precedingComment(quint32 offset) const; + bool applyDocumentation(QQmlJS::AST::SourceLocation location, Node *node); + void applyMetacommands(QQmlJS::AST::SourceLocation location, Node* node, Doc& doc); + bool splitQmlPropertyArg(const Doc& doc, + const QString& arg, + QmlPropArgs& qpa); + + QQmlJS::Engine *engine; + quint32 lastEndOffset; + quint32 nestingLevel; + QString filePath_; + QString name; + QString document; + ImportList importList; + QSet<QString> commands_; + QSet<QString> topics_; + QSet<quint32> usedComments; + Aggregate *current; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/quoter.cpp b/src/qdoc/quoter.cpp new file mode 100644 index 000000000..25cf27f73 --- /dev/null +++ b/src/qdoc/quoter.cpp @@ -0,0 +1,374 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qfileinfo.h> +#include <qregexp.h> +#include <qdebug.h> + +#include "quoter.h" + +QT_BEGIN_NAMESPACE + +QHash<QString,QString> Quoter::commentHash; + +static void replaceMultipleNewlines(QString &s) +{ + const int n = s.size(); + bool slurping = false; + int j = -1; + const QChar newLine = QLatin1Char('\n'); + QChar *d = s.data(); + for (int i = 0; i != n; ++i) { + const QChar c = d[i]; + bool hit = (c == newLine); + if (slurping && hit) + continue; + d[++j] = c; + slurping = hit; + } + s.resize(++j); +} + +// This is equivalent to line.split( QRegExp("\n(?!\n|$)") ) but much faster +QStringList Quoter::splitLines(const QString &line) +{ + QStringList result; + int i = line.size(); + while (true) { + int j = i - 1; + while (j >= 0 && line.at(j) == QLatin1Char('\n')) + --j; + while (j >= 0 && line.at(j) != QLatin1Char('\n')) + --j; + result.prepend(line.mid(j + 1, i - j - 1)); + if (j < 0) + break; + i = j; + } + return result; +} + +/* + Transforms 'int x = 3 + 4' into 'int x=3+4'. A white space is kept + between 'int' and 'x' because it is meaningful in C++. +*/ +static void trimWhiteSpace( QString& str ) +{ + enum { Normal, MetAlnum, MetSpace } state = Normal; + const int n = str.length(); + + int j = -1; + QChar *d = str.data(); + for ( int i = 0; i != n; ++i ) { + const QChar c = d[i]; + if ( c.isLetterOrNumber() ) { + if ( state == Normal ) { + state = MetAlnum; + } else { + if ( state == MetSpace ) + str[++j] = c; + state = Normal; + } + str[++j] = c; + } else if ( c.isSpace() ) { + if ( state == MetAlnum ) + state = MetSpace; + } else { + state = Normal; + str[++j] = c; + } + } + str.resize(++j); +} + +Quoter::Quoter() + : silent( false ) +{ + /* We're going to hard code these delimiters: + * C++, Qt, Qt Script, Java: + //! [<id>] + * .pro, .py files: + #! [<id>] + * .html, .qrc, .ui, .xq, .xml .dita files: + <!-- [<id>] --> + */ + if (!commentHash.size()) { + commentHash["pro"] = "#!"; + commentHash["py"] = "#!"; + commentHash["html"] = "<!--"; + commentHash["qrc"] = "<!--"; + commentHash["ui"] = "<!--"; + commentHash["xml"] = "<!--"; + commentHash["dita"] = "<!--"; + commentHash["xq"] = "<!--"; + } +} + +void Quoter::reset() +{ + silent = false; + plainLines.clear(); + markedLines.clear(); + codeLocation = Location::null; +} + +void Quoter::quoteFromFile( const QString& userFriendlyFilePath, + const QString& plainCode, + const QString& markedCode ) +{ + silent = false; + + /* + Split the source code into logical lines. Empty lines are + treated specially. Before: + + p->alpha(); + p->beta(); + + p->gamma(); + + + p->delta(); + + After: + + p->alpha(); + p->beta();\n + p->gamma();\n\n + p->delta(); + + Newlines are preserved because they affect codeLocation. + */ + codeLocation = Location( userFriendlyFilePath ); + + plainLines = splitLines(plainCode); + markedLines = splitLines(markedCode); + if (markedLines.count() != plainLines.count()) { + codeLocation.warning(tr("Something is wrong with qdoc's handling of marked code")); + markedLines = plainLines; + } + + /* + Squeeze blanks (cat -s). + */ + QStringList::Iterator m = markedLines.begin(); + while ( m != markedLines.end() ) { + replaceMultipleNewlines( *m ); + ++m; + } + codeLocation.start(); +} + +QString Quoter::quoteLine( const Location& docLocation, const QString& command, + const QString& pattern ) +{ + if ( plainLines.isEmpty() ) { + failedAtEnd( docLocation, command ); + return QString(); + } + + if ( pattern.isEmpty() ) { + docLocation.warning( tr("Missing pattern after '\\%1'").arg(command) ); + return QString(); + } + + if ( match(docLocation, pattern, plainLines.first()) ) + return getLine(); + + if ( !silent ) { + docLocation.warning( tr("Command '\\%1' failed").arg(command) ); + codeLocation.warning( tr("Pattern '%1' didn't match here") + .arg(pattern) ); + silent = true; + } + return QString(); +} + +QString Quoter::quoteSnippet(const Location &docLocation, const QString &identifier) +{ + QString comment = commentForCode(); + QString delimiter = comment + QString(" [%1]").arg(identifier); + QString t; + int indent = 0; + + while (!plainLines.isEmpty()) { + if (match(docLocation, delimiter, plainLines.first())) { + QString startLine = getLine(); + while (indent < startLine.length() && startLine[indent] == QLatin1Char(' ')) + indent++; + break; + } + getLine(); + } + while (!plainLines.isEmpty()) { + QString line = plainLines.first(); + if (match(docLocation, delimiter, line)) { + QString lastLine = getLine(indent); + int dIndex = lastLine.indexOf(delimiter); + if (dIndex > 0) { + // The delimiter might be preceded on the line by other + // delimeters, so look for the first comment on the line. + QString leading = lastLine.left(dIndex); + dIndex = leading.indexOf(comment); + if (dIndex != -1) + leading = leading.left(dIndex); + if (leading.endsWith(QLatin1String("<@comment>"))) + leading.chop(10); + if (!leading.trimmed().isEmpty()) + t += leading; + } + return t; + } + + t += removeSpecialLines(line, comment, indent); + } + failedAtEnd(docLocation, QString("snippet (%1)").arg(delimiter)); + return t; +} + +QString Quoter::quoteTo( const Location& docLocation, const QString& command, + const QString& pattern ) +{ + QString t; + QString comment = commentForCode(); + + if ( pattern.isEmpty() ) { + while ( !plainLines.isEmpty() ) { + QString line = plainLines.first(); + t += removeSpecialLines(line, comment); + } + } else { + while ( !plainLines.isEmpty() ) { + if ( match(docLocation, pattern, plainLines.first()) ) { + return t; + } + t += getLine(); + } + failedAtEnd( docLocation, command ); + } + return t; +} + +QString Quoter::quoteUntil( const Location& docLocation, const QString& command, + const QString& pattern ) +{ + QString t = quoteTo( docLocation, command, pattern ); + t += getLine(); + return t; +} + +QString Quoter::getLine(int unindent) +{ + if ( plainLines.isEmpty() ) + return QString(); + + plainLines.removeFirst(); + + QString t = markedLines.takeFirst(); + int i = 0; + while (i < unindent && i < t.length() && t[i] == QLatin1Char(' ')) + i++; + + t = t.mid(i); + t += QLatin1Char('\n'); + codeLocation.advanceLines( t.count( QLatin1Char('\n') ) ); + return t; +} + +bool Quoter::match( const Location& docLocation, const QString& pattern0, + const QString& line ) +{ + QString str = line; + while ( str.endsWith(QLatin1Char('\n')) ) + str.truncate( str.length() - 1 ); + + QString pattern = pattern0; + if ( pattern.startsWith(QLatin1Char('/')) + && pattern.endsWith(QLatin1Char('/')) + && pattern.length() > 2 ) { + QRegExp rx( pattern.mid(1, pattern.length() - 2) ); + if ( !silent && !rx.isValid() ) { + docLocation.warning( tr("Invalid regular expression '%1'") + .arg(rx.pattern()) ); + silent = true; + } + return str.indexOf( rx ) != -1; + } + trimWhiteSpace(str); + trimWhiteSpace(pattern); + return str.indexOf(pattern) != -1; +} + +void Quoter::failedAtEnd( const Location& docLocation, const QString& command ) +{ + if (!silent && !command.isEmpty()) { + if ( codeLocation.filePath().isEmpty() ) { + docLocation.warning( tr("Unexpected '\\%1'").arg(command) ); + } else { + docLocation.warning( tr("Command '\\%1' failed at end of file '%2'") + .arg(command).arg(codeLocation.filePath()) ); + } + silent = true; + } +} + +QString Quoter::commentForCode() const +{ + QString suffix = QFileInfo(codeLocation.fileName()).suffix(); + return commentHash.value(suffix, "//!"); +} + +QString Quoter::removeSpecialLines(const QString &line, const QString &comment, int unindent) +{ + QString t; + + // Remove special macros to support Qt namespacing. + QString trimmed = line.trimmed(); + if (trimmed.startsWith("QT_BEGIN_NAMESPACE")) { + getLine(); + } else if (trimmed.startsWith("QT_END_NAMESPACE")) { + getLine(); + t += QLatin1Char('\n'); + } else if (!trimmed.startsWith(comment)) { + // Ordinary code + t += getLine(unindent); + } else { + // Comments + if (line.contains(QLatin1Char('\n'))) + t += QLatin1Char('\n'); + getLine(); + } + return t; +} + +QT_END_NAMESPACE diff --git a/src/qdoc/quoter.h b/src/qdoc/quoter.h new file mode 100644 index 000000000..7877eda69 --- /dev/null +++ b/src/qdoc/quoter.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + quoter.h +*/ + +#ifndef QUOTER_H +#define QUOTER_H + +#include <qhash.h> +#include <qstringlist.h> + +#include "location.h" + +QT_BEGIN_NAMESPACE + +class Quoter +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::Quoter) + +public: + Quoter(); + + void reset(); + void quoteFromFile( const QString& userFriendlyFileName, + const QString& plainCode, const QString& markedCode ); + QString quoteLine( const Location& docLocation, const QString& command, + const QString& pattern ); + QString quoteTo( const Location& docLocation, const QString& command, + const QString& pattern ); + QString quoteUntil( const Location& docLocation, const QString& command, + const QString& pattern ); + QString quoteSnippet(const Location &docLocation, const QString &identifier); + + static QStringList splitLines(const QString &line); + +private: + QString getLine(int unindent = 0); + void failedAtEnd( const Location& docLocation, const QString& command ); + bool match( const Location& docLocation, const QString& pattern, + const QString& line ); + QString commentForCode() const; + QString removeSpecialLines(const QString &line, const QString &comment, + int unindent = 0); + + bool silent; + QStringList plainLines; + QStringList markedLines; + Location codeLocation; + static QHash<QString,QString> commentHash; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/separator.cpp b/src/qdoc/separator.cpp new file mode 100644 index 000000000..3cbcb6930 --- /dev/null +++ b/src/qdoc/separator.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + separator.cpp +*/ + +#include "separator.h" +#include <qcoreapplication.h> + +QT_BEGIN_NAMESPACE + +QString separator(int index, int count) +{ + if (index == count - 1) + return QCoreApplication::translate("QDoc", ".", "terminator"); + if (count == 2) + return QCoreApplication::translate("QDoc", " and ", "separator when N = 2"); + if (index == 0) + return QCoreApplication::translate("QDoc", ", ", "first separator when N > 2"); + if (index < count - 2) + return QCoreApplication::translate("QDoc", ", ", "general separator when N > 2"); + return QCoreApplication::translate("QDoc", ", and ", "last separator when N > 2"); +} + +QString comma(int index, int count) +{ + if (index == count - 1) + return QString(); + if (count == 2) + return QCoreApplication::translate("QDoc", " and ", "separator when N = 2"); + if (index == 0) + return QCoreApplication::translate("QDoc", ", ", "first separator when N > 2"); + if (index < count - 2) + return QCoreApplication::translate("QDoc", ", ", "general separator when N > 2"); + return QCoreApplication::translate("QDoc", ", and ", "last separator when N > 2"); +} + +QT_END_NAMESPACE diff --git a/src/qdoc/separator.h b/src/qdoc/separator.h new file mode 100644 index 000000000..281d95994 --- /dev/null +++ b/src/qdoc/separator.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + separator.h +*/ + +#ifndef SEPARATOR_H +#define SEPARATOR_H + +#include <qstring.h> + +QT_BEGIN_NAMESPACE + +QString separator( int index, int count ); +QString comma( int index, int count ); + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/text.cpp b/src/qdoc/text.cpp new file mode 100644 index 000000000..fa105a834 --- /dev/null +++ b/src/qdoc/text.cpp @@ -0,0 +1,300 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + text.cpp +*/ + +#include <qregexp.h> +#include "text.h" +#include <stdio.h> + +QT_BEGIN_NAMESPACE + +Text::Text() + : first(0), last(0) +{ +} + +Text::Text(const QString &str) + : first(0), last(0) +{ + operator<<(str); +} + +Text::Text(const Text& text) + : first(0), last(0) +{ + operator=(text); +} + +Text::~Text() +{ + clear(); +} + +Text& Text::operator=(const Text& text) +{ + if (this != &text) { + clear(); + operator<<(text); + } + return *this; +} + +Text& Text::operator<<(Atom::AtomType atomType) +{ + return operator<<(Atom(atomType)); +} + +Text& Text::operator<<(const QString& string) +{ + return operator<<(Atom(Atom::String, string)); +} + +Text& Text::operator<<(const Atom& atom) +{ + if (atom.count() < 2) { + if (first == 0) { + first = new Atom(atom.type(), atom.string()); + last = first; + } + else + last = new Atom(last, atom.type(), atom.string()); + } + else { + if (first == 0) { + first = new Atom(atom.type(), atom.string(), atom.string(1)); + last = first; + } + else + last = new Atom(last, atom.type(), atom.string(), atom.string(1)); + } + return *this; +} + +/*! + Special output operator for LinkAtom. It makes a copy of + the LinkAtom \a atom and connects the cop;y to the list + in this Text. + */ +Text& Text::operator<<(const LinkAtom& atom) +{ + if (first == 0) { + first = new LinkAtom(atom); + last = first; + } + else + last = new LinkAtom(last, atom); + return *this; +} + +Text& Text::operator<<(const Text& text) +{ + const Atom* atom = text.firstAtom(); + while (atom != 0) { + operator<<(*atom); + atom = atom->next(); + } + return *this; +} + +void Text::stripFirstAtom() +{ + if (first != 0) { + if (first == last) + last = 0; + Atom* oldFirst = first; + first = first->next(); + delete oldFirst; + } +} + +void Text::stripLastAtom() +{ + if (last != 0) { + Atom* oldLast = last; + if (first == last) { + first = 0; + last = 0; + } else { + last = first; + while (last->next() != oldLast) + last = last->next(); + last->setNext(0); + } + delete oldLast; + } +} + +/*! + This function traverses the atom list of the Text object, + extracting all the string parts. It concatenates them to + a result string and returns it. + */ +QString Text::toString() const +{ + QString str; + const Atom* atom = firstAtom(); + while (atom != 0) { + if (atom->type() == Atom::String || + atom->type() == Atom::AutoLink || + atom->type() == Atom::C || + atom->type() == Atom::GuidLink) + str += atom->string(); + atom = atom->next(); + } + return str; +} + +Text Text::subText(Atom::AtomType left, Atom::AtomType right, const Atom* from, bool inclusive) const +{ + const Atom* begin = from ? from : firstAtom(); + const Atom* end; + + while (begin != 0 && begin->type() != left) + begin = begin->next(); + if (begin != 0) { + if (!inclusive) + begin = begin->next(); + } + + end = begin; + while (end != 0 && end->type() != right) + end = end->next(); + if (end == 0) + begin = 0; + else if (inclusive) + end = end->next(); + return subText(begin, end); +} + +Text Text::sectionHeading(const Atom* sectionLeft) +{ + if (sectionLeft != 0) { + const Atom* begin = sectionLeft; + while (begin != 0 && begin->type() != Atom::SectionHeadingLeft) + begin = begin->next(); + if (begin != 0) + begin = begin->next(); + + const Atom* end = begin; + while (end != 0 && end->type() != Atom::SectionHeadingRight) + end = end->next(); + + if (end != 0) + return subText(begin, end); + } + return Text(); +} + +const Atom* Text::sectionHeadingAtom(const Atom* sectionLeft) +{ + if (sectionLeft != 0) { + const Atom* begin = sectionLeft; + while (begin != 0 && begin->type() != Atom::SectionHeadingLeft) + begin = begin->next(); + if (begin != 0) + begin = begin->next(); + + return begin; + } + return 0; +} + +void Text::dump() const +{ + const Atom* atom = firstAtom(); + while (atom != 0) { + QString str = atom->string(); + str.replace("\\", "\\\\"); + str.replace("\"", "\\\""); + str.replace("\n", "\\n"); + str.replace(QRegExp("[^\x20-\x7e]"), "?"); + if (!str.isEmpty()) + str = " \"" + str + QLatin1Char('"'); + fprintf(stderr, " %-15s%s\n", atom->typeString().toLatin1().data(), str.toLatin1().data()); + atom = atom->next(); + } +} + +Text Text::subText(const Atom* begin, const Atom* end) +{ + Text text; + if (begin != 0) { + while (begin != end) { + text << *begin; + begin = begin->next(); + } + } + return text; +} + +void Text::clear() +{ + while (first != 0) { + Atom* atom = first; + first = first->next(); + delete atom; + } + first = 0; + last = 0; +} + +int Text::compare(const Text &text1, const Text &text2) +{ + if (text1.isEmpty()) + return text2.isEmpty() ? 0 : -1; + if (text2.isEmpty()) + return 1; + + const Atom* atom1 = text1.firstAtom(); + const Atom* atom2 = text2.firstAtom(); + + for (;;) { + if (atom1->type() != atom2->type()) + return (int)atom1->type() - (int)atom2->type(); + int cmp = QString::compare(atom1->string(), atom2->string()); + if (cmp != 0) + return cmp; + + if (atom1 == text1.lastAtom()) + return atom2 == text2.lastAtom() ? 0 : -1; + if (atom2 == text2.lastAtom()) + return 1; + atom1 = atom1->next(); + atom2 = atom2->next(); + } +} + +QT_END_NAMESPACE diff --git a/src/qdoc/text.h b/src/qdoc/text.h new file mode 100644 index 000000000..40ecf3edd --- /dev/null +++ b/src/qdoc/text.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + text.h +*/ + +#ifndef TEXT_H +#define TEXT_H + +#include "atom.h" + +QT_BEGIN_NAMESPACE + +class Text +{ +public: + Text(); + explicit Text(const QString &str); + Text(const Text& text); + ~Text(); + + Text& operator=(const Text& text); + + Atom *firstAtom() { return first; } + Atom *lastAtom() { return last; } + Text& operator<<(Atom::AtomType atomType); + Text& operator<<(const QString& string); + Text& operator<<(const Atom& atom); + Text& operator<<(const LinkAtom& atom); + Text& operator<<(const Text& text); + void stripFirstAtom(); + void stripLastAtom(); + + bool isEmpty() const { return first == 0; } + QString toString() const; + const Atom *firstAtom() const { return first; } + const Atom *lastAtom() const { return last; } + Text subText(Atom::AtomType left, Atom::AtomType right, const Atom *from = 0, bool inclusive = false) const; + void dump() const; + void clear(); + + static Text subText(const Atom *begin, const Atom *end = 0); + static Text sectionHeading(const Atom *sectionBegin); + static const Atom *sectionHeadingAtom(const Atom *sectionLeft); + static int compare(const Text &text1, const Text &text2); + +private: + + Atom *first; + Atom *last; +}; + +inline bool operator==(const Text &text1, const Text &text2) +{ return Text::compare(text1, text2) == 0; } +inline bool operator!=(const Text &text1, const Text &text2) +{ return Text::compare(text1, text2) != 0; } +inline bool operator<(const Text &text1, const Text &text2) +{ return Text::compare(text1, text2) < 0; } +inline bool operator<=(const Text &text1, const Text &text2) +{ return Text::compare(text1, text2) <= 0; } +inline bool operator>(const Text &text1, const Text &text2) +{ return Text::compare(text1, text2) > 0; } +inline bool operator>=(const Text &text1, const Text &text2) +{ return Text::compare(text1, text2) >= 0; } + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/tokenizer.cpp b/src/qdoc/tokenizer.cpp new file mode 100644 index 000000000..987fff548 --- /dev/null +++ b/src/qdoc/tokenizer.cpp @@ -0,0 +1,799 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "config.h" +#include "tokenizer.h" +#include "generator.h" + +#include <qfile.h> +#include <qhash.h> +#include <qregexp.h> +#include <qstring.h> +#include <qtextcodec.h> + +#include <ctype.h> +#include <string.h> + +QT_BEGIN_NAMESPACE + +#define LANGUAGE_CPP "Cpp" + +/* qmake ignore Q_OBJECT */ + +/* + Keep in sync with tokenizer.h. +*/ +static const char *kwords[] = { + "char", "class", "const", "double", "enum", "explicit", + "friend", "inline", "int", "long", "namespace", "operator", + "private", "protected", "public", "short", "signals", "signed", + "slots", "static", "struct", "template", "typedef", "typename", + "union", "unsigned", "using", "virtual", "void", "volatile", + "__int64", + "Q_OBJECT", + "Q_OVERRIDE", + "Q_PROPERTY", + "Q_PRIVATE_PROPERTY", + "Q_DECLARE_SEQUENTIAL_ITERATOR", + "Q_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR", + "Q_DECLARE_ASSOCIATIVE_ITERATOR", + "Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR", + "Q_DECLARE_FLAGS", + "Q_SIGNALS", + "Q_SLOTS", + "QT_COMPAT", + "QT_COMPAT_CONSTRUCTOR", + "QT_DEPRECATED", + "QT_MOC_COMPAT", + "QT_MODULE", + "QT3_SUPPORT", + "QT3_SUPPORT_CONSTRUCTOR", + "QT3_MOC_SUPPORT", + "QDOC_PROPERTY", + "QPrivateSignal" +}; + +static const int KwordHashTableSize = 4096; +static int kwordHashTable[KwordHashTableSize]; + +static QHash<QByteArray, bool> *ignoredTokensAndDirectives = 0; + +static QRegExp *comment = 0; +static QRegExp *versionX = 0; +static QRegExp *definedX = 0; + +static QRegExp *defines = 0; +static QRegExp *falsehoods = 0; + +#ifndef QT_NO_TEXTCODEC +static QTextCodec *sourceCodec = 0; +#endif + +/* + This function is a perfect hash function for the 37 keywords of C99 + (with a hash table size of 512). It should perform well on our + Qt-enhanced C++ subset. +*/ +static int hashKword(const char *s, int len) +{ + return (((uchar) s[0]) + (((uchar) s[2]) << 5) + + (((uchar) s[len - 1]) << 3)) % KwordHashTableSize; +} + +static void insertKwordIntoHash(const char *s, int number) +{ + int k = hashKword(s, int(strlen(s))); + while (kwordHashTable[k]) { + if (++k == KwordHashTableSize) + k = 0; + } + kwordHashTable[k] = number; +} + +Tokenizer::Tokenizer(const Location& loc, QFile &in) +{ + init(); + yyIn = in.readAll(); + yyPos = 0; + start(loc); +} + +Tokenizer::Tokenizer(const Location& loc, const QByteArray &in) + : yyIn(in) +{ + init(); + yyPos = 0; + start(loc); +} + +Tokenizer::~Tokenizer() +{ + delete[] yyLexBuf1; + delete[] yyLexBuf2; +} + +int Tokenizer::getToken() +{ + char *t = yyPrevLex; + yyPrevLex = yyLex; + yyLex = t; + + while (yyCh != EOF) { + yyTokLoc = yyCurLoc; + yyLexLen = 0; + + if (isspace(yyCh)) { + do { + yyCh = getChar(); + } while (isspace(yyCh)); + } + else if (isalpha(yyCh) || yyCh == '_') { + do { + yyCh = getChar(); + } while (isalnum(yyCh) || yyCh == '_'); + + int k = hashKword(yyLex, int(yyLexLen)); + for (;;) { + int i = kwordHashTable[k]; + if (i == 0) { + return Tok_Ident; + } + else if (i == -1) { + if (!parsingMacro && ignoredTokensAndDirectives->contains(yyLex)) { + if (ignoredTokensAndDirectives->value(yyLex)) { // it's a directive + int parenDepth = 0; + while (yyCh != EOF && (yyCh != ')' || parenDepth > 1)) { + if (yyCh == '(') + ++parenDepth; + else if (yyCh == ')') + --parenDepth; + yyCh = getChar(); + } + if (yyCh == ')') + yyCh = getChar(); + } + break; + } + } + else if (strcmp(yyLex, kwords[i - 1]) == 0) { + int ret = (int) Tok_FirstKeyword + i - 1; + if (ret != Tok_typename) + return ret; + break; + } + + if (++k == KwordHashTableSize) + k = 0; + } + } + else if (isdigit(yyCh)) { + do { + yyCh = getChar(); + } while (isalnum(yyCh) || yyCh == '.' || yyCh == '+' || + yyCh == '-'); + return Tok_Number; + } + else { + switch (yyCh) { + case '!': + case '%': + yyCh = getChar(); + if (yyCh == '=') + yyCh = getChar(); + return Tok_SomeOperator; + case '"': + yyCh = getChar(); + + while (yyCh != EOF && yyCh != '"') { + if (yyCh == '\\') + yyCh = getChar(); + yyCh = getChar(); + } + yyCh = getChar(); + + if (yyCh == EOF) + yyTokLoc.warning(tr("Unterminated C++ string literal"), + tr("Maybe you forgot '/*!' at the beginning of the file?")); + else + return Tok_String; + break; + case '#': + return getTokenAfterPreprocessor(); + case '&': + yyCh = getChar(); + /* + Removed check for '&&', only interpret '&=' as an operator. + '&&' is also used for an rvalue reference. QTBUG-32675 + */ + if (yyCh == '=') { + yyCh = getChar(); + return Tok_SomeOperator; + } + else { + return Tok_Ampersand; + } + case '\'': + yyCh = getChar(); + /* + Allow empty character literal. QTBUG-25775 + */ + if (yyCh == '\'') { + yyCh = getChar(); + break; + } + if (yyCh == '\\') + yyCh = getChar(); + do { + yyCh = getChar(); + } while (yyCh != EOF && yyCh != '\''); + + if (yyCh == EOF) { + yyTokLoc.warning(tr("Unterminated C++ character literal")); + } + else { + yyCh = getChar(); + return Tok_Number; + } + break; + case '(': + yyCh = getChar(); + if (yyNumPreprocessorSkipping == 0) + yyParenDepth++; + if (isspace(yyCh)) { + do { + yyCh = getChar(); + } while (isspace(yyCh)); + yyLexLen = 1; + yyLex[1] = '\0'; + } + if (yyCh == '*') { + yyCh = getChar(); + return Tok_LeftParenAster; + } + return Tok_LeftParen; + case ')': + yyCh = getChar(); + if (yyNumPreprocessorSkipping == 0) + yyParenDepth--; + return Tok_RightParen; + case '*': + yyCh = getChar(); + if (yyCh == '=') { + yyCh = getChar(); + return Tok_SomeOperator; + } else { + return Tok_Aster; + } + case '^': + yyCh = getChar(); + if (yyCh == '=') { + yyCh = getChar(); + return Tok_SomeOperator; + } else { + return Tok_Caret; + } + case '+': + yyCh = getChar(); + if (yyCh == '+' || yyCh == '=') + yyCh = getChar(); + return Tok_SomeOperator; + case ',': + yyCh = getChar(); + return Tok_Comma; + case '-': + yyCh = getChar(); + if (yyCh == '-' || yyCh == '=') { + yyCh = getChar(); + } else if (yyCh == '>') { + yyCh = getChar(); + if (yyCh == '*') + yyCh = getChar(); + } + return Tok_SomeOperator; + case '.': + yyCh = getChar(); + if (yyCh == '*') { + yyCh = getChar(); + } else if (yyCh == '.') { + do { + yyCh = getChar(); + } while (yyCh == '.'); + return Tok_Ellipsis; + } else if (isdigit(yyCh)) { + do { + yyCh = getChar(); + } while (isalnum(yyCh) || yyCh == '.' || yyCh == '+' || + yyCh == '-'); + return Tok_Number; + } + return Tok_SomeOperator; + case '/': + yyCh = getChar(); + if (yyCh == '/') { + do { + yyCh = getChar(); + } while (yyCh != EOF && yyCh != '\n'); + } else if (yyCh == '*') { + bool metDoc = false; // empty doc is no doc + bool metSlashAsterBang = false; + bool metAster = false; + bool metAsterSlash = false; + + yyCh = getChar(); + if (yyCh == '!') + metSlashAsterBang = true; + + while (!metAsterSlash) { + if (yyCh == EOF) { + yyTokLoc.warning(tr("Unterminated C++ comment")); + break; + } else { + if (yyCh == '*') { + metAster = true; + } else if (metAster && yyCh == '/') { + metAsterSlash = true; + } else { + metAster = false; + if (isgraph(yyCh)) + metDoc = true; + } + } + yyCh = getChar(); + } + if (metSlashAsterBang && metDoc) + return Tok_Doc; + else if (yyParenDepth > 0) + return Tok_Comment; + } else { + if (yyCh == '=') + yyCh = getChar(); + return Tok_SomeOperator; + } + break; + case ':': + yyCh = getChar(); + if (yyCh == ':') { + yyCh = getChar(); + return Tok_Gulbrandsen; + } else { + return Tok_Colon; + } + case ';': + yyCh = getChar(); + return Tok_Semicolon; + case '<': + yyCh = getChar(); + if (yyCh == '<') { + yyCh = getChar(); + if (yyCh == '=') + yyCh = getChar(); + return Tok_SomeOperator; + } else if (yyCh == '=') { + yyCh = getChar(); + return Tok_SomeOperator; + } else { + return Tok_LeftAngle; + } + case '=': + yyCh = getChar(); + if (yyCh == '=') { + yyCh = getChar(); + return Tok_SomeOperator; + } else { + return Tok_Equal; + } + case '>': + yyCh = getChar(); + if (yyCh == '>') { + yyCh = getChar(); + if (yyCh == '=') + yyCh = getChar(); + return Tok_SomeOperator; + } else if (yyCh == '=') { + yyCh = getChar(); + return Tok_SomeOperator; + } else { + return Tok_RightAngle; + } + case '?': + yyCh = getChar(); + return Tok_SomeOperator; + case '[': + yyCh = getChar(); + if (yyNumPreprocessorSkipping == 0) + yyBracketDepth++; + return Tok_LeftBracket; + case '\\': + yyCh = getChar(); + yyCh = getChar(); // skip one character + break; + case ']': + yyCh = getChar(); + if (yyNumPreprocessorSkipping == 0) + yyBracketDepth--; + return Tok_RightBracket; + case '{': + yyCh = getChar(); + if (yyNumPreprocessorSkipping == 0) + yyBraceDepth++; + return Tok_LeftBrace; + case '}': + yyCh = getChar(); + if (yyNumPreprocessorSkipping == 0) + yyBraceDepth--; + return Tok_RightBrace; + case '|': + yyCh = getChar(); + if (yyCh == '|' || yyCh == '=') + yyCh = getChar(); + return Tok_SomeOperator; + case '~': + yyCh = getChar(); + return Tok_Tilde; + case '@': + yyCh = getChar(); + return Tok_At; + default: + // ### We should really prevent qdoc from looking at snippet files rather than + // ### suppress warnings when reading them. + if (yyNumPreprocessorSkipping == 0 && !(yyTokLoc.fileName().endsWith(".qdoc") || + yyTokLoc.fileName().endsWith(".js"))) { + yyTokLoc.warning(tr("Hostile character 0x%1 in C++ source") + .arg((uchar)yyCh, 1, 16)); + } + yyCh = getChar(); + } + } + } + + if (yyPreprocessorSkipping.count() > 1) { + yyTokLoc.warning(tr("Expected #endif before end of file")); + // clear it out or we get an infinite loop! + while (!yyPreprocessorSkipping.isEmpty()) { + popSkipping(); + } + } + + strcpy(yyLex, "end-of-input"); + yyLexLen = strlen(yyLex); + return Tok_Eoi; +} + +void Tokenizer::initialize(const Config &config) +{ + QString versionSym = config.getString(CONFIG_VERSIONSYM); + + QString sourceEncoding = config.getString(CONFIG_SOURCEENCODING); + if (sourceEncoding.isEmpty()) + sourceEncoding = QLatin1String("ISO-8859-1"); +#ifndef QT_NO_TEXTCODEC + sourceCodec = QTextCodec::codecForName(sourceEncoding.toLocal8Bit()); +#endif + + comment = new QRegExp("/(?:\\*.*\\*/|/.*\n|/[^\n]*$)"); + comment->setMinimal(true); + versionX = new QRegExp("$cannot possibly match^"); + if (!versionSym.isEmpty()) + versionX->setPattern("[ \t]*(?:" + QRegExp::escape(versionSym) + + ")[ \t]+\"([^\"]*)\"[ \t]*"); + definedX = new QRegExp("defined ?\\(?([A-Z_0-9a-z]+) ?\\)"); + + QStringList d = config.getStringList(CONFIG_DEFINES); + d += "qdoc"; + defines = new QRegExp(d.join('|')); + falsehoods = new QRegExp(config.getStringList(CONFIG_FALSEHOODS).join('|')); + + /* + The keyword hash table is always cleared before any words are inserted. + */ + memset(kwordHashTable, 0, sizeof(kwordHashTable)); + for (int i = 0; i < Tok_LastKeyword - Tok_FirstKeyword + 1; i++) + insertKwordIntoHash(kwords[i], i + 1); + + ignoredTokensAndDirectives = new QHash<QByteArray, bool>; + + QStringList tokens = config.getStringList(LANGUAGE_CPP + Config::dot + CONFIG_IGNORETOKENS); + foreach (const QString &t, tokens) { + const QByteArray tb = t.toLatin1(); + ignoredTokensAndDirectives->insert(tb, false); + insertKwordIntoHash(tb.data(), -1); + } + + QStringList directives = config.getStringList(LANGUAGE_CPP + Config::dot + + CONFIG_IGNOREDIRECTIVES); + foreach (const QString &d, directives) { + const QByteArray db = d.toLatin1(); + ignoredTokensAndDirectives->insert(db, true); + insertKwordIntoHash(db.data(), -1); + } +} + +/*! + The heap allocated variables are freed here. The keyword + hash table is not cleared here, but it is cleared in the + initialize() function, before any keywords are inserted. + */ +void Tokenizer::terminate() +{ + delete comment; + comment = 0; + delete versionX; + versionX = 0; + delete definedX; + definedX = 0; + delete defines; + defines = 0; + delete falsehoods; + falsehoods = 0; + delete ignoredTokensAndDirectives; + ignoredTokensAndDirectives = 0; +} + +void Tokenizer::init() +{ + yyLexBuf1 = new char[(int) yyLexBufSize]; + yyLexBuf2 = new char[(int) yyLexBufSize]; + yyPrevLex = yyLexBuf1; + yyPrevLex[0] = '\0'; + yyLex = yyLexBuf2; + yyLex[0] = '\0'; + yyLexLen = 0; + yyPreprocessorSkipping.push(false); + yyNumPreprocessorSkipping = 0; + yyBraceDepth = 0; + yyParenDepth = 0; + yyBracketDepth = 0; + yyCh = '\0'; + parsingMacro = false; +} + +void Tokenizer::start(const Location& loc) +{ + yyTokLoc = loc; + yyCurLoc = loc; + yyCurLoc.start(); + strcpy(yyPrevLex, "beginning-of-input"); + strcpy(yyLex, "beginning-of-input"); + yyLexLen = strlen(yyLex); + yyBraceDepth = 0; + yyParenDepth = 0; + yyBracketDepth = 0; + yyCh = '\0'; + yyCh = getChar(); +} + +/* + Returns the next token, if # was met. This function interprets the + preprocessor directive, skips over any #ifdef'd out tokens, and returns the + token after all of that. +*/ +int Tokenizer::getTokenAfterPreprocessor() +{ + yyCh = getChar(); + while (isspace(yyCh) && yyCh != '\n') + yyCh = getChar(); + + /* + #directive condition + */ + QString directive; + QString condition; + + while (isalpha(yyCh)) { + directive += QChar(yyCh); + yyCh = getChar(); + } + if (!directive.isEmpty()) { + while (yyCh != EOF && yyCh != '\n') { + if (yyCh == '\\') { + yyCh = getChar(); + if (yyCh == '\r') + yyCh = getChar(); + } + condition += yyCh; + yyCh = getChar(); + } + condition.remove(*comment); + condition = condition.simplified(); + + /* + The #if, #ifdef, #ifndef, #elif, #else, and #endif + directives have an effect on the skipping stack. For + instance, if the code processed so far is + + #if 1 + #if 0 + #if 1 + // ... + #else + + the skipping stack contains, from bottom to top, false true + true (assuming 0 is false and 1 is true). If at least one + entry of the stack is true, the tokens are skipped. + + This mechanism is simple yet hard to understand. + */ + if (directive[0] == QChar('i')) { + if (directive == QString("if")) + pushSkipping(!isTrue(condition)); + else if (directive == QString("ifdef")) + pushSkipping(!defines->exactMatch(condition)); + else if (directive == QString("ifndef")) + pushSkipping(defines->exactMatch(condition)); + } else if (directive[0] == QChar('e')) { + if (directive == QString("elif")) { + bool old = popSkipping(); + if (old) + pushSkipping(!isTrue(condition)); + else + pushSkipping(true); + } else if (directive == QString("else")) { + pushSkipping(!popSkipping()); + } else if (directive == QString("endif")) { + popSkipping(); + } + } else if (directive == QString("define")) { + if (versionX->exactMatch(condition)) + yyVersion = versionX->cap(1); + } + } + + int tok; + do { + /* + We set yyLex now, and after getToken() this will be + yyPrevLex. This way, we skip over the preprocessor + directive. + */ + qstrcpy(yyLex, yyPrevLex); + + /* + If getToken() meets another #, it will call + getTokenAfterPreprocessor() once again, which could in turn + call getToken() again, etc. Unless there are 10,000 or so + preprocessor directives in a row, this shouldn't overflow + the stack. + */ + tok = getToken(); + } while (yyNumPreprocessorSkipping > 0 && tok != Tok_Eoi); + return tok; +} + +/* + Pushes a new skipping value onto the stack. This corresponds to entering a + new #if block. +*/ +void Tokenizer::pushSkipping(bool skip) +{ + yyPreprocessorSkipping.push(skip); + if (skip) + yyNumPreprocessorSkipping++; +} + +/* + Pops a skipping value from the stack. This corresponds to reaching a #endif. +*/ +bool Tokenizer::popSkipping() +{ + if (yyPreprocessorSkipping.isEmpty()) { + yyTokLoc.warning(tr("Unexpected #elif, #else or #endif")); + return true; + } + + bool skip = yyPreprocessorSkipping.pop(); + if (skip) + yyNumPreprocessorSkipping--; + return skip; +} + +/* + Returns \c true if the condition evaluates as true, otherwise false. The + condition is represented by a string. Unsophisticated parsing techniques are + used. The preprocessing method could be named StriNg-Oriented PreProcessing, + as SNOBOL stands for StriNg-Oriented symBOlic Language. +*/ +bool Tokenizer::isTrue(const QString &condition) +{ + int firstOr = -1; + int firstAnd = -1; + int parenDepth = 0; + + /* + Find the first logical operator at top level, but be careful + about precedence. Examples: + + X || Y // the or + X || Y || Z // the leftmost or + X || Y && Z // the or + X && Y || Z // the or + (X || Y) && Z // the and + */ + for (int i = 0; i < (int) condition.length() - 1; i++) { + QChar ch = condition[i]; + if (ch == QChar('(')) { + parenDepth++; + } else if (ch == QChar(')')) { + parenDepth--; + } else if (parenDepth == 0) { + if (condition[i + 1] == ch) { + if (ch == QChar('|')) { + firstOr = i; + break; + } else if (ch == QChar('&')) { + if (firstAnd == -1) + firstAnd = i; + } + } + } + } + if (firstOr != -1) + return isTrue(condition.left(firstOr)) || + isTrue(condition.mid(firstOr + 2)); + if (firstAnd != -1) + return isTrue(condition.left(firstAnd)) && + isTrue(condition.mid(firstAnd + 2)); + + QString t = condition.simplified(); + if (t.isEmpty()) + return true; + + if (t[0] == QChar('!')) + return !isTrue(t.mid(1)); + if (t[0] == QChar('(') && t.endsWith(QChar(')'))) + return isTrue(t.mid(1, t.length() - 2)); + + if (definedX->exactMatch(t)) + return defines->exactMatch(definedX->cap(1)); + else + return !falsehoods->exactMatch(t); +} + +QString Tokenizer::lexeme() const +{ +#ifndef QT_NO_TEXTCODEC + return sourceCodec->toUnicode(yyLex); +#else + return QString::fromUtf8(yyLex); +#endif +} + +QString Tokenizer::previousLexeme() const +{ +#ifndef QT_NO_TEXTCODEC + return sourceCodec->toUnicode(yyPrevLex); +#else + return QString::fromUtf8(yyPrevLex); +#endif +} + +QT_END_NAMESPACE diff --git a/src/qdoc/tokenizer.h b/src/qdoc/tokenizer.h new file mode 100644 index 000000000..41a3ffd93 --- /dev/null +++ b/src/qdoc/tokenizer.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + tokenizer.h +*/ + +#ifndef TOKENIZER_H +#define TOKENIZER_H + +#include <qfile.h> +#include <qstack.h> +#include <qstring.h> + +#include "location.h" + +QT_BEGIN_NAMESPACE + +/* + Here come the C++ tokens we support. The first part contains + all-purpose tokens; then come keywords. + + If you add a keyword, make sure to modify the keyword array in + tokenizer.cpp as well, and possibly adjust Tok_FirstKeyword and + Tok_LastKeyword. +*/ +enum { Tok_Eoi, Tok_Ampersand, Tok_Aster, Tok_Caret, Tok_LeftParen, + Tok_RightParen, Tok_LeftParenAster, Tok_Equal, Tok_LeftBrace, + Tok_RightBrace, Tok_Semicolon, Tok_Colon, Tok_LeftAngle, + Tok_RightAngle, Tok_Comma, Tok_Ellipsis, Tok_Gulbrandsen, + Tok_LeftBracket, Tok_RightBracket, Tok_Tilde, Tok_SomeOperator, + Tok_Number, Tok_String, Tok_Doc, Tok_Comment, Tok_Ident, Tok_At, + Tok_char, Tok_class, Tok_const, Tok_double, Tok_enum, + Tok_explicit, Tok_friend, Tok_inline, Tok_int, Tok_long, + Tok_namespace, Tok_operator, Tok_private, Tok_protected, + Tok_public, Tok_short, Tok_signals, Tok_signed, Tok_slots, + Tok_static, Tok_struct, Tok_template, Tok_typedef, + Tok_typename, Tok_union, Tok_unsigned, Tok_using, Tok_virtual, + Tok_void, Tok_volatile, Tok_int64, Tok_Q_OBJECT, Tok_Q_OVERRIDE, + Tok_Q_PROPERTY, Tok_Q_PRIVATE_PROPERTY, Tok_Q_DECLARE_SEQUENTIAL_ITERATOR, + Tok_Q_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR, + Tok_Q_DECLARE_ASSOCIATIVE_ITERATOR, + Tok_Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR, + Tok_Q_DECLARE_FLAGS, Tok_Q_SIGNALS, Tok_Q_SLOTS, Tok_QT_COMPAT, + Tok_QT_COMPAT_CONSTRUCTOR, Tok_QT_DEPRECATED, Tok_QT_MOC_COMPAT, + Tok_QT_MODULE, Tok_QT3_SUPPORT, Tok_QT3_SUPPORT_CONSTRUCTOR, + Tok_QT3_MOC_SUPPORT, Tok_QDOC_PROPERTY, Tok_QPrivateSignal, + Tok_FirstKeyword = Tok_char, Tok_LastKeyword = Tok_QPrivateSignal }; + +/* + The Tokenizer class implements lexical analysis of C++ source + files. + + Not every operator or keyword of C++ is recognized; only those + that are interesting to us. Some Qt keywords or macros are also + recognized. +*/ + +class Tokenizer +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::Tokenizer) + +public: + Tokenizer(const Location& loc, const QByteArray &in); + Tokenizer(const Location& loc, QFile &file); + + ~Tokenizer(); + + int getToken(); + void setParsingFnOrMacro(bool macro) { parsingMacro = macro; } + bool parsingFnOrMacro() const { return parsingMacro; } + + const Location &location() const { return yyTokLoc; } + QString previousLexeme() const; + QString lexeme() const; + QString version() const { return yyVersion; } + int braceDepth() const { return yyBraceDepth; } + int parenDepth() const { return yyParenDepth; } + int bracketDepth() const { return yyBracketDepth; } + Location& tokenLocation() { return yyTokLoc; } + + static void initialize(const Config &config); + static void terminate(); + static bool isTrue(const QString &condition); + +private: + void init(); + void start(const Location& loc); + /* + This limit on the length of a lexeme seems fairly high, but a + doc comment can be arbitrarily long. The previous 65,536 limit + was reached by Mark Summerfield. + */ + enum { yyLexBufSize = 524288 }; + + int getch() + { + return yyPos == yyIn.size() ? EOF : yyIn[yyPos++]; + } + + inline int getChar() + { + if (yyCh == EOF) + return EOF; + if (yyLexLen < yyLexBufSize - 1) { + yyLex[yyLexLen++] = (char) yyCh; + yyLex[yyLexLen] = '\0'; + } + yyCurLoc.advance(yyCh); + int ch = getch(); + if (ch == EOF) + return EOF; + // cast explicitly to make sure the value of ch + // is in range [0..255] to avoid assert messages + // when using debug CRT that checks its input. + return int(uint(uchar(ch))); + } + + int getTokenAfterPreprocessor(); + void pushSkipping(bool skip); + bool popSkipping(); + + Location yyTokLoc; + Location yyCurLoc; + char *yyLexBuf1; + char *yyLexBuf2; + char *yyPrevLex; + char *yyLex; + size_t yyLexLen; + QStack<bool> yyPreprocessorSkipping; + int yyNumPreprocessorSkipping; + int yyBraceDepth; + int yyParenDepth; + int yyBracketDepth; + int yyCh; + + QString yyVersion; + bool parsingMacro; + +protected: + QByteArray yyIn; + int yyPos; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/tree.cpp b/src/qdoc/tree.cpp new file mode 100644 index 000000000..f8f88e6b9 --- /dev/null +++ b/src/qdoc/tree.cpp @@ -0,0 +1,1525 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "doc.h" +#include "htmlgenerator.h" +#include "location.h" +#include "node.h" +#include "text.h" +#include "tree.h" +#include "qdocdatabase.h" +#include <limits.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +/*! + \class Tree + + This class constructs and maintains a tree of instances of + the subclasses of Node. + + This class is now private. Only class QDocDatabase has access. + Please don't change this. If you must access class Tree, do it + though the pointer to the singleton QDocDatabase. + + Tree is being converted to a forest. A static member provides a + map of Tree* values with the module names as the keys. There is + one Tree in the map for each index file read, and there is one + tree that is not in the map for the module whose documentation + is being generated. + */ + +/*! + Constructs a Tree. \a qdb is the pointer to the singleton + qdoc database that is constructing the tree. This might not + be necessary, and it might be removed later. + + \a camelCaseModuleName is the project name for this tree, + which was obtained from the qdocconf file via the Config + singleton. + */ +Tree::Tree(const QString& camelCaseModuleName, QDocDatabase* qdb) + : treeHasBeenAnalyzed_(false), + docsHaveBeenGenerated_(false), + linkCount_(0), + camelCaseModuleName_(camelCaseModuleName), + physicalModuleName_(camelCaseModuleName.toLower()), + qdb_(qdb), + root_(0, QString()), + targetListMap_(0) +{ + root_.setPhysicalModuleName(physicalModuleName_); + root_.setTree(this); + if (Generator::writeQaPages()) { + targetListMap_ = new TargetListMap; + } +} + +/*! + Destroys the Tree. The root node is a data member + of this object, so its destructor is called. The + destructor of each child node is called, and these + destructors are recursive. Thus the entire tree is + destroyed. + + There are two maps of targets, keywords, and contents. + One map is indexed by ref, the other by title. The ref + is just the canonical form of the title. Both maps + use the same set of TargetRec objects as the values, + so the destructor only deletes the values from one of + the maps. Then it clears both maps. + */ +Tree::~Tree() +{ + TargetMap::iterator i = nodesByTargetRef_.begin(); + while (i != nodesByTargetRef_.end()) { + delete i.value(); + ++i; + } + nodesByTargetRef_.clear(); + nodesByTargetTitle_.clear(); + if (Generator::writeQaPages() && targetListMap_) { + TargetListMap::iterator i = targetListMap_->begin(); + while (i != targetListMap_->end()) { + TargetList* tlist = i.value(); + if (tlist) { + foreach (TargetLoc* tloc, *tlist) + delete tloc; + } + delete tlist; + ++i; + } + } +} + +/* API members */ + +/*! + Calls findClassNode() first with \a path and \a start. If + it finds a node, the node is returned. If not, it calls + findNamespaceNode() with the same parameters. The result + is returned. + */ +Node* Tree::findNodeForInclude(const QStringList& path) const +{ + Node* n = findClassNode(path); + if (!n) + n = findNamespaceNode(path); + return n; +} + +/*! + Find the C++ class node named \a path. Begin the search at the + \a start node. If the \a start node is 0, begin the search + at the root of the tree. Only a C++ class node named \a path is + acceptible. If one is not found, 0 is returned. + */ +ClassNode* Tree::findClassNode(const QStringList& path, const Node* start) const +{ + if (!start) + start = const_cast<NamespaceNode*>(root()); + return static_cast<ClassNode*>(findNodeRecursive(path, 0, start, Node::Class)); +} + +/*! + Find the Namespace node named \a path. Begin the search at + the root of the tree. Only a Namespace node named \a path + is acceptible. If one is not found, 0 is returned. + */ +NamespaceNode* Tree::findNamespaceNode(const QStringList& path) const +{ + Node* start = const_cast<NamespaceNode*>(root()); + return static_cast<NamespaceNode*>(findNodeRecursive(path, 0, start, Node::Namespace)); +} + +/*! + This function first ignores the \a clone node and searches + for the parent node with \a parentPath. If that search is + successful, it searches for a child node of the parent that + matches the \a clone node. If it finds a node that is just + like the \a clone, it returns a pointer to the found node. + + Apparently the search order is important here. Don't change + it unless you know what you are doing, or you will introduce + qdoc warnings. + */ +FunctionNode* Tree::findFunctionNode(const QStringList& parentPath, const FunctionNode* clone) +{ + const Node* parent = findNamespaceNode(parentPath); + if (parent == 0) + parent = findClassNode(parentPath, 0); + if (parent == 0) + parent = findNode(parentPath, 0, 0, Node::DontCare); + if (parent == 0 || !parent->isAggregate()) + return 0; + return ((const Aggregate*)parent)->findFunctionNode(clone); +} + + +/*! + Find the Qml type node named \a path. Begin the search at the + \a start node. If the \a start node is 0, begin the search + at the root of the tree. Only a Qml type node named <\a path is + acceptible. If one is not found, 0 is returned. + */ +QmlTypeNode* Tree::findQmlTypeNode(const QStringList& path) +{ + /* + If the path contains one or two double colons ("::"), + check first to see if the first two path strings refer + to a QML element. If they do, path[0] will be the QML + module identifier, and path[1] will be the QML type. + If the anser is yes, the reference identifies a QML + class node. + */ + if (path.size() >= 2 && !path[0].isEmpty()) { + QmlTypeNode* qcn = qdb_->findQmlType(path[0], path[1]); + if (qcn) + return qcn; + } + return static_cast<QmlTypeNode*>(findNodeRecursive(path, 0, root(), Node::QmlType)); +} + +/*! + This function begins searching the tree at \a relative for + the \l {FunctionNode} {function node} identified by \a path. + The \a findFlags are used to restrict the search. If a node + that matches the \a path is found, it is returned. Otherwise, + 0 is returned. If \a relative is 0, the root of the tree is + used as the starting point. + */ +const FunctionNode* Tree::findFunctionNode(const QStringList& path, + const QString& params, + const Node* relative, + int findFlags, + Node::Genus genus) const +{ + if (path.size() == 3 && !path[0].isEmpty() && + ((genus == Node::QML) || (genus == Node::DontCare))) { + QmlTypeNode* qcn = lookupQmlType(QString(path[0] + "::" + path[1])); + if (!qcn) { + QStringList p(path[1]); + Node* n = findNodeByNameAndType(p, Node::QmlType); + if (n && (n->isQmlType() || n->isJsType())) + qcn = static_cast<QmlTypeNode*>(n); + } + if (qcn) + return static_cast<const FunctionNode*>(qcn->findFunctionNode(path[2], params)); + } + + if (!relative) + relative = root(); + else if (genus != Node::DontCare) { + if (genus != relative->genus()) + relative = root(); + } + + do { + const Node* node = relative; + int i; + + for (i = 0; i < path.size(); ++i) { + if (node == 0 || !node->isAggregate()) + break; + + const Node* next; + if (i == path.size() - 1) + next = ((const Aggregate*) node)->findFunctionNode(path.at(i), params); + else + next = ((const Aggregate*) node)->findChildNode(path.at(i), genus); + + if (!next && node->isClass() && (findFlags & SearchBaseClasses)) { + NodeList baseClasses = allBaseClasses(static_cast<const ClassNode*>(node)); + foreach (const Node* baseClass, baseClasses) { + if (i == path.size() - 1) + next = static_cast<const Aggregate*>(baseClass)->findFunctionNode(path.at(i), params); + else + next = static_cast<const Aggregate*>(baseClass)->findChildNode(path.at(i), genus); + + if (next) + break; + } + } + + node = next; + } + if (node && i == path.size() && node->isFunction()) { + // CppCodeParser::processOtherMetaCommand ensures that reimplemented + // functions are private. + const FunctionNode* func = static_cast<const FunctionNode*>(node); + while (func->access() == Node::Private) { + const FunctionNode* from = func->reimplementedFrom(); + if (from != 0) { + if (from->access() != Node::Private) + return from; + else + func = from; + } + else + break; + } + return func; + } + relative = relative->parent(); + } while (relative); + + return 0; +} + +static NodeTypeList t; +static const NodeTypeList& relatesTypes() +{ + if (t.isEmpty()) { + t.reserve(3); + t.append(NodeTypePair(Node::Class, Node::NoSubtype)); + t.append(NodeTypePair(Node::Namespace, Node::NoSubtype)); + t.append(NodeTypePair(Node::Document, Node::HeaderFile)); + } + return t; +} + +/*! + This function searches for the node specified by \a path. + The matching node can be one of several different types + including a C++ class, a C++ namespace, or a C++ header + file. + + I'm not sure if it can be a QML type, but if that is a + possibility, the code can easily accommodate it. + + If a matching node is found, a pointer to it is returned. + Otherwise 0 is returned. + */ +Aggregate* Tree::findRelatesNode(const QStringList& path) +{ + Node* n = findNodeRecursive(path, 0, root(), relatesTypes()); + return ((n && n->isAggregate()) ? static_cast<Aggregate*>(n) : 0); +} + +/*! + Inserts function name \a funcName and function role \a funcRole into + the property function map for the specified \a property. + */ +void Tree::addPropertyFunction(PropertyNode* property, + const QString& funcName, + PropertyNode::FunctionRole funcRole) +{ + unresolvedPropertyMap[property].insert(funcRole, funcName); +} + +/*! + This function resolves C++ inheritance and reimplementation + settings for each C++ class node found in the tree beginning + at \a n. It also calls itself recursively for each C++ class + node or namespace node it encounters. For each child of \a n + that is a class node, it calls resolveInheritanceHelper(). + + This function does not resolve QML inheritance. + */ +void Tree::resolveInheritance(Aggregate* n) +{ + if (!n) + n = root(); + + for (int pass = 0; pass < 2; pass++) { + NodeList::ConstIterator c = n->childNodes().constBegin(); + while (c != n->childNodes().constEnd()) { + if ((*c)->type() == Node::Class) { + resolveInheritanceHelper(pass, (ClassNode*)*c); + resolveInheritance((ClassNode*)*c); + } + else if ((*c)->type() == Node::Namespace) { + NamespaceNode* ns = static_cast<NamespaceNode*>(*c); + resolveInheritance(ns); + } + ++c; + } + } +} + +/*! + This function is run twice for eachclass node \a cn in the + tree. First it is run with \a pass set to 0 for each + class node \a cn. Then it is run with \a pass set to 1 for + eachclass node \a cn. + + In \a pass 0, all the base classes ofclass node \a cn are + found and added to the base class list forclass node \a cn. + + In \a pass 1, each child ofclass node \a cn that is a function + that is reimplemented from one of the base classes is marked + as being reimplemented from that class. + + Some property node fixing up is also done in \a pass 1. + */ +void Tree::resolveInheritanceHelper(int pass, ClassNode* cn) +{ + if (pass == 0) { + QList<RelatedClass>& bases = cn->baseClasses(); + QList<RelatedClass>::iterator b = bases.begin(); + while (b != bases.end()) { + if (!(*b).node_) { + Node* n = qdb_->findClassNode((*b).path_); + /* + If the node for the base class was not found, + the reason might be that the subclass is in a + namespace and the base class is in the same + namespace, but the base class name was not + qualified with the namespace name. That is the + case most of the time. Then restart the search + at the parent of the subclass node (the namespace + node) using the unqualified base class name. + */ + if (!n) { + Aggregate* parent = cn->parent(); + if (parent) + // Exclude the root namespace + if (parent->isNamespace() && !parent->name().isEmpty()) + n = findClassNode((*b).path_, parent); + } + if (n) { + ClassNode* bcn = static_cast<ClassNode*>(n); + (*b).node_ = bcn; + bcn->addDerivedClass((*b).access_, cn); + } + } + ++b; + } + } + else { + NodeList::ConstIterator c = cn->childNodes().constBegin(); + while (c != cn->childNodes().constEnd()) { + if ((*c)->type() == Node::Function) { + FunctionNode* func = (FunctionNode*)* c; + FunctionNode* from = findVirtualFunctionInBaseClasses(cn, func); + if (from != 0) { + if (func->virtualness() == FunctionNode::NonVirtual) + func->setVirtualness(FunctionNode::NormalVirtual); + func->setReimplementedFrom(from); + } + } + else if ((*c)->type() == Node::Property) + cn->fixPropertyUsingBaseClasses(static_cast<PropertyNode*>(*c)); + ++c; + } + } +} + +/*! + */ +void Tree::resolveProperties() +{ + PropertyMap::ConstIterator propEntry; + + propEntry = unresolvedPropertyMap.constBegin(); + while (propEntry != unresolvedPropertyMap.constEnd()) { + PropertyNode* property = propEntry.key(); + Aggregate* parent = property->parent(); + QString getterName = (*propEntry)[PropertyNode::Getter]; + QString setterName = (*propEntry)[PropertyNode::Setter]; + QString resetterName = (*propEntry)[PropertyNode::Resetter]; + QString notifierName = (*propEntry)[PropertyNode::Notifier]; + + NodeList::ConstIterator c = parent->childNodes().constBegin(); + while (c != parent->childNodes().constEnd()) { + if ((*c)->type() == Node::Function) { + FunctionNode* function = static_cast<FunctionNode*>(*c); + if (function->access() == property->access() && + (function->status() == property->status() || + function->doc().isEmpty())) { + if (function->name() == getterName) { + property->addFunction(function, PropertyNode::Getter); + } + else if (function->name() == setterName) { + property->addFunction(function, PropertyNode::Setter); + } + else if (function->name() == resetterName) { + property->addFunction(function, PropertyNode::Resetter); + } + else if (function->name() == notifierName) { + property->addSignal(function, PropertyNode::Notifier); + } + } + } + ++c; + } + ++propEntry; + } + + propEntry = unresolvedPropertyMap.constBegin(); + while (propEntry != unresolvedPropertyMap.constEnd()) { + PropertyNode* property = propEntry.key(); + // redo it to set the property functions + if (property->overriddenFrom()) + property->setOverriddenFrom(property->overriddenFrom()); + ++propEntry; + } + + unresolvedPropertyMap.clear(); +} + +/*! + For each QML class node that points to a C++ class node, + follow its C++ class node pointer and set the C++ class + node's QML class node pointer back to the QML class node. + */ +void Tree::resolveCppToQmlLinks() +{ + + foreach (Node* child, root_.childNodes()) { + if (child->isQmlType() || child->isJsType()) { + QmlTypeNode* qcn = static_cast<QmlTypeNode*>(child); + ClassNode* cn = const_cast<ClassNode*>(qcn->classNode()); + if (cn) + cn->setQmlElement(qcn); + } + } +} + +/*! + For each C++ class node, resolve any \c using clauses + that appeared in the class declaration. + */ +void Tree::resolveUsingClauses() +{ + foreach (Node* child, root_.childNodes()) { + if (child->isClass()) { + ClassNode* cn = static_cast<ClassNode*>(child); + QList<UsingClause>& usingClauses = cn->usingClauses(); + QList<UsingClause>::iterator uc = usingClauses.begin(); + while (uc != usingClauses.end()) { + if (!(*uc).node()) { + const Node* n = qdb_->findFunctionNode((*uc).signature(), cn, Node::CPP); + if (n) + (*uc).setNode(n); + } + ++uc; + } + } + } +} + +/*! + */ +void Tree::fixInheritance(NamespaceNode* rootNode) +{ + if (!rootNode) + rootNode = root(); + + NodeList::ConstIterator c = rootNode->childNodes().constBegin(); + while (c != rootNode->childNodes().constEnd()) { + if ((*c)->type() == Node::Class) + static_cast<ClassNode*>(*c)->fixBaseClasses(); + else if ((*c)->type() == Node::Namespace) { + NamespaceNode* ns = static_cast<NamespaceNode*>(*c); + fixInheritance(ns); + } + ++c; + } +} + +/*! + */ +FunctionNode* Tree::findVirtualFunctionInBaseClasses(ClassNode* cn, FunctionNode* clone) +{ + const QList<RelatedClass>& rc = cn->baseClasses(); + QList<RelatedClass>::ConstIterator r = rc.constBegin(); + while (r != rc.constEnd()) { + FunctionNode* func; + if ((*r).node_) { + if (((func = findVirtualFunctionInBaseClasses((*r).node_, clone)) != 0 || + (func = (*r).node_->findFunctionNode(clone)) != 0)) { + if (func->virtualness() != FunctionNode::NonVirtual) + return func; + } + } + ++r; + } + return 0; +} + +/*! + */ +NodeList Tree::allBaseClasses(const ClassNode* classNode) const +{ + NodeList result; + foreach (const RelatedClass& r, classNode->baseClasses()) { + if (r.node_) { + result += r.node_; + result += allBaseClasses(r.node_); + } + } + return result; +} + +/*! + Find the node with the specified \a path name that is of + the specified \a type and \a subtype. Begin the search at + the \a start node. If the \a start node is 0, begin the + search at the tree root. \a subtype is not used unless + \a type is \c{Document}. + */ +Node* Tree::findNodeByNameAndType(const QStringList& path, Node::NodeType type) const +{ + return findNodeRecursive(path, 0, root(), type); +} + +/*! + Recursive search for a node identified by \a path. Each + path element is a name. \a pathIndex specifies the index + of the name in \a path to try to match. \a start is the + node whose children shoulod be searched for one that has + that name. Each time a match is found, increment the + \a pathIndex and call this function recursively. + + If the end of the path is reached (i.e. if a matching + node is found for each name in the \a path), the \a type + must match the type of the last matching node, and if the + type is \e{Document}, the \a subtype must match as well. + + If the algorithm is successful, the pointer to the final + node is returned. Otherwise 0 is returned. + */ +Node* Tree::findNodeRecursive(const QStringList& path, + int pathIndex, + const Node* start, + Node::NodeType type) const +{ + if (!start || path.isEmpty()) + return 0; // no place to start, or nothing to search for. + Node* node = const_cast<Node*>(start); + if (start->isLeaf()) { + if (pathIndex >= path.size()) + return node; // found a match. + return 0; // premature leaf + } + + Aggregate* current = static_cast<Aggregate*>(node); + const NodeList& children = current->childNodes(); + const QString& name = path.at(pathIndex); + for (int i=0; i<children.size(); ++i) { + Node* n = children.at(i); + if (!n) + continue; + if (n->isQmlPropertyGroup()) { + if (type == Node::QmlProperty) { + n = findNodeRecursive(path, pathIndex, n, type); + if (n) + return n; + } + } + else if (n->name() == name) { + if (pathIndex+1 >= path.size()) { + if ((n->type() == type) || (type == Node::NoType)) + return n; + continue; + } + else { // Search the children of n for the next name in the path. + n = findNodeRecursive(path, pathIndex+1, n, type); + if (n) + return n; + } + } + } + return 0; +} + +/*! + Recursive search for a node identified by \a path. Each + path element is a name. \a pathIndex specifies the index + of the name in \a path to try to match. \a start is the + node whose children shoulod be searched for one that has + that name. Each time a name match is found, increment the + \a pathIndex and call this function recursively. + + If the end of the path is reached (i.e. if a matching + node is found for each name in the \a path), test the + matching node's type and subtype values against the ones + listed in \a types. If a match is found there, return the + pointer to the final node. Otherwise return 0. + */ +Node* Tree::findNodeRecursive(const QStringList& path, + int pathIndex, + Node* start, + const NodeTypeList& types) const +{ + if (!start || path.isEmpty()) + return 0; + if (start->isLeaf()) + return ((pathIndex >= path.size()) ? start : 0); + if (pathIndex >= path.size()) + return 0; + + Aggregate* current = static_cast<Aggregate*>(start); + const NodeList& children = current->childNodes(); + for (int i=0; i<children.size(); ++i) { + Node* n = children.at(i); + if (n && n->name() == path.at(pathIndex)) { + if (pathIndex+1 >= path.size()) { + if (n->match(types)) + return n; + } + else if (!n->isLeaf()) { + n = findNodeRecursive(path, pathIndex+1, n, types); + if (n) + return n; + } + } + } + return 0; +} + +/*! + Searches the tree for a node that matches the \a path plus + the \a target. The search begins at \a start and moves up + the parent chain from there, or, if \a start is 0, the search + begins at the root. + + The \a flags can indicate whether to search base classes and/or + the enum values in enum types. \a genus can be a further restriction + on what kind of node is an acceptible match, i.e. CPP or QML. + + If a matching node is found, \a ref is an output parameter that + is set to the HTML reference to use for the link. + */ +const Node* Tree::findNodeForTarget(const QStringList& path, + const QString& target, + const Node* start, + int flags, + Node::Genus genus, + QString& ref) const +{ + const Node* node = 0; + if ((genus == Node::DontCare) || (genus == Node::DOC)) { + node = findDocumentNodeByTitle(path.at(0)); + if (node) { + if (!target.isEmpty()) { + ref = getRef(target, node); + if (ref.isEmpty()) + node = 0; + } + if (node) + return node; + } + } + + node = findUnambiguousTarget(path.join(QStringLiteral("::")), ref); + if (node) { + if (!target.isEmpty()) { + ref = getRef(target, node); + if (ref.isEmpty()) + node = 0; + } + if (node) + return node; + } + + const Node* current = start; + if (!current) + current = root(); + + /* + If the path contains one or two double colons ("::"), + check first to see if the first two path strings refer + to a QML element. If they do, path[0] will be the QML + module identifier, and path[1] will be the QML type. + If the answer is yes, the reference identifies a QML + type node. + */ + int path_idx = 0; + if (((genus == Node::QML) || (genus == Node::DontCare)) && + (path.size() >= 2) && !path[0].isEmpty()) { + QmlTypeNode* qcn = lookupQmlType(QString(path[0] + "::" + path[1])); + if (qcn) { + current = qcn; + if (path.size() == 2) { + if (!target.isEmpty()) { + ref = getRef(target, current); + if (!ref.isEmpty()) + return current; + return 0; + } + else + return current; + } + path_idx = 2; + } + } + + while (current) { + if (current->isAggregate()) { + const Node* node = matchPathAndTarget(path, path_idx, target, current, flags, genus, ref); + if (node) + return node; + } + current = current->parent(); + path_idx = 0; + } + return 0; +} + +/*! + First, the \a path is used to find a node. The \a path + matches some part of the node's fully quallified name. + If the \a target is not empty, it must match a target + in the matching node. If the matching of the \a path + and the \a target (if present) is successful, \a ref + is set from the \a target, and the pointer to the + matching node is returned. \a idx is the index into the + \a path where to begin the matching. The function is + recursive with idx being incremented for each recursive + call. + + The matching node must be of the correct \a genus, i.e. + either QML or C++, but \a genus can be set to \c DontCare. + \a flags indicates whether to search base classes and + whether to search for an enum value. \a node points to + the node where the search should begin, assuming the + \a path is a not a fully-qualified name. \a node is + most often the root of this Tree. + */ +const Node* Tree::matchPathAndTarget(const QStringList& path, + int idx, + const QString& target, + const Node* node, + int flags, + Node::Genus genus, + QString& ref) const +{ + /* + If the path has been matched, then if there is a target, + try to match the target. If there is a target, but you + can't match it at the end of the path, give up; return 0. + */ + if (idx == path.size()) { + if (!target.isEmpty()) { + ref = getRef(target, node); + if (ref.isEmpty()) + return 0; + } + if (node->isFunction() && node->name() == node->parent()->name()) + node = node->parent(); + return node; + } + + const Node* t = 0; + QString name = path.at(idx); + QList<Node*> nodes; + node->findChildren(name, nodes); + + foreach (const Node* n, nodes) { + if (genus != Node::DontCare) { + if (n->genus() != genus) + continue; + } + t = matchPathAndTarget(path, idx+1, target, n, flags, genus, ref); + if (t && !t->isPrivate()) + return t; + } + if (target.isEmpty()) { + if ((idx) == (path.size()-1) && node->isAggregate() && (flags & SearchEnumValues)) { + t = static_cast<const Aggregate*>(node)->findEnumNodeForValue(path.at(idx)); + if (t) + return t; + } + } + if (((genus == Node::CPP) || (genus == Node::DontCare)) && + node->isClass() && (flags & SearchBaseClasses)) { + NodeList baseClasses = allBaseClasses(static_cast<const ClassNode*>(node)); + foreach (const Node* bc, baseClasses) { + t = matchPathAndTarget(path, idx, target, bc, flags, genus, ref); + if (t && ! t->isPrivate()) + return t; + if (target.isEmpty()) { + if ((idx) == (path.size()-1) && (flags & SearchEnumValues)) { + t = static_cast<const Aggregate*>(bc)->findEnumNodeForValue(path.at(idx)); + if (t) + return t; + } + } + } + } + return 0; +} + +/*! + Searches the tree for a node that matches the \a path. The + search begins at \a start but can move up the parent chain + recursively if no match is found. + + This findNode() callse the other findNode(), which is not + called anywhere else. + */ +const Node* Tree::findNode(const QStringList& path, + const Node* start, + int findFlags, + Node::Genus genus) const +{ + const Node* current = start; + if (!current) + current = root(); + + do { + const Node* node = current; + int i; + int start_idx = 0; + + /* + If the path contains one or two double colons ("::"), + check first to see if the first two path strings refer + to a QML element. If they do, path[0] will be the QML + module identifier, and path[1] will be the QML type. + If the answer is yes, the reference identifies a QML + type node. + */ + if (((genus == Node::QML) || (genus == Node::DontCare)) && + (path.size() >= 2) && !path[0].isEmpty()) { + QmlTypeNode* qcn = lookupQmlType(QString(path[0] + "::" + path[1])); + if (qcn) { + node = qcn; + if (path.size() == 2) + return node; + start_idx = 2; + } + } + + for (i = start_idx; i < path.size(); ++i) { + if (node == 0 || !node->isAggregate()) + break; + + const Node* next = static_cast<const Aggregate*>(node)->findChildNode(path.at(i), genus); + if (!next && (findFlags & SearchEnumValues) && i == path.size()-1) { + next = static_cast<const Aggregate*>(node)->findEnumNodeForValue(path.at(i)); + } + if (!next && ((genus == Node::CPP) || (genus == Node::DontCare)) && + node->isClass() && (findFlags & SearchBaseClasses)) { + NodeList baseClasses = allBaseClasses(static_cast<const ClassNode*>(node)); + foreach (const Node* baseClass, baseClasses) { + next = static_cast<const Aggregate*>(baseClass)->findChildNode(path.at(i), genus); + if (!next && (findFlags & SearchEnumValues) && i == path.size() - 1) + next = static_cast<const Aggregate*>(baseClass)->findEnumNodeForValue(path.at(i)); + if (next) { + break; + } + } + } + node = next; + } + if (node && i == path.size()) + return node; + current = current->parent(); + } while (current); + + return 0; +} + +/*! + This function searches for a node with a canonical title + constructed from \a target. If the node it finds is \a node, + it returns the ref from that node. Otherwise it returns an + empty string. + */ +QString Tree::getRef(const QString& target, const Node* node) const +{ + TargetMap::const_iterator i = nodesByTargetTitle_.constFind(target); + if (i != nodesByTargetTitle_.constEnd()) { + do { + if (i.value()->node_ == node) + return i.value()->ref_; + ++i; + } while (i != nodesByTargetTitle_.constEnd() && i.key() == target); + } + QString key = Doc::canonicalTitle(target); + i = nodesByTargetRef_.constFind(key); + if (i != nodesByTargetRef_.constEnd()) { + do { + if (i.value()->node_ == node) + return i.value()->ref_; + ++i; + } while (i != nodesByTargetRef_.constEnd() && i.key() == key); + } + return QString(); +} + +/*! + Inserts a new target into the target table. \a name is the + key. The target record contains the \a type, a pointer to + the \a node, the \a priority. and a canonicalized form of + the \a name, which is later used. + */ +void Tree::insertTarget(const QString& name, + const QString& title, + TargetRec::TargetType type, + Node* node, + int priority) +{ + TargetRec* target = new TargetRec(name, title, type, node, priority); + nodesByTargetRef_.insert(name, target); + nodesByTargetTitle_.insert(title, target); +} + +/*! + */ +void Tree::resolveTargets(Aggregate* root) +{ + foreach (Node* child, root->childNodes()) { + if (child->type() == Node::Document) { + DocumentNode* node = static_cast<DocumentNode*>(child); + QString key = node->title(); + if (!key.isEmpty()) { + if (key.contains(QChar(' '))) + key = Doc::canonicalTitle(key); + QList<DocumentNode*> nodes = docNodesByTitle_.values(key); + bool alreadyThere = false; + if (!nodes.empty()) { + for (int i=0; i< nodes.size(); ++i) { + if (nodes[i]->docSubtype() == Node::ExternalPage) { + if (node->name() == nodes[i]->name()) { + alreadyThere = true; + break; + } + } + } + } + if (!alreadyThere) + docNodesByTitle_.insert(key, node); + } + } + + if (child->doc().hasTableOfContents()) { + const QList<Atom*>& toc = child->doc().tableOfContents(); + for (int i = 0; i < toc.size(); ++i) { + QString ref = refForAtom(toc.at(i)); + QString title = Text::sectionHeading(toc.at(i)).toString(); + if (!ref.isEmpty() && !title.isEmpty()) { + QString key = Doc::canonicalTitle(title); + TargetRec* target = new TargetRec(ref, title, TargetRec::Contents, child, 3); + nodesByTargetRef_.insert(key, target); + nodesByTargetTitle_.insert(title, target); + } + } + } + if (child->doc().hasKeywords()) { + const QList<Atom*>& keywords = child->doc().keywords(); + for (int i = 0; i < keywords.size(); ++i) { + QString ref = refForAtom(keywords.at(i)); + QString title = keywords.at(i)->string(); + if (!ref.isEmpty() && !title.isEmpty()) { + TargetRec* target = new TargetRec(ref, title, TargetRec::Keyword, child, 1); + nodesByTargetRef_.insert(Doc::canonicalTitle(title), target); + nodesByTargetTitle_.insert(title, target); + } + } + } + if (child->doc().hasTargets()) { + const QList<Atom*>& targets = child->doc().targets(); + for (int i = 0; i < targets.size(); ++i) { + QString ref = refForAtom(targets.at(i)); + QString title = targets.at(i)->string(); + if (!ref.isEmpty() && !title.isEmpty()) { + QString key = Doc::canonicalTitle(title); + TargetRec* target = new TargetRec(ref, title, TargetRec::Target, child, 2); + nodesByTargetRef_.insert(key, target); + nodesByTargetTitle_.insert(title, target); + } + } + } + if (child->isAggregate()) + resolveTargets(static_cast<Aggregate*>(child)); + } +} + +/*! + This function searches for a \a target anchor node. If it + finds one, it sets \a ref and returns the found node. + */ +const Node* +Tree::findUnambiguousTarget(const QString& target, QString& ref) const +{ + int numBestTargets = 0; + TargetRec* bestTarget = 0; + QList<TargetRec*> bestTargetList; + + QString key = target; + TargetMap::const_iterator i = nodesByTargetTitle_.find(key); + while (i != nodesByTargetTitle_.constEnd()) { + if (i.key() != key) + break; + TargetRec* candidate = i.value(); + if (!bestTarget || (candidate->priority_ < bestTarget->priority_)) { + bestTarget = candidate; + bestTargetList.clear(); + bestTargetList.append(candidate); + numBestTargets = 1; + } else if (candidate->priority_ == bestTarget->priority_) { + bestTargetList.append(candidate); + ++numBestTargets; + } + ++i; + } + if (bestTarget) { + ref = bestTarget->ref_; + return bestTarget->node_; + } + + numBestTargets = 0; + bestTarget = 0; + key = Doc::canonicalTitle(target); + i = nodesByTargetRef_.find(key); + while (i != nodesByTargetRef_.constEnd()) { + if (i.key() != key) + break; + TargetRec* candidate = i.value(); + if (!bestTarget || (candidate->priority_ < bestTarget->priority_)) { + bestTarget = candidate; + bestTargetList.clear(); + bestTargetList.append(candidate); + numBestTargets = 1; + } else if (candidate->priority_ == bestTarget->priority_) { + bestTargetList.append(candidate); + ++numBestTargets; + } + ++i; + } + if (bestTarget) { + ref = bestTarget->ref_; + return bestTarget->node_; + } + + ref.clear(); + return 0; +} + +/*! + This function searches for a node with the specified \a title. + */ +const DocumentNode* Tree::findDocumentNodeByTitle(const QString& title) const +{ + DocumentNodeMultiMap::const_iterator i; + if (title.contains(QChar(' '))) + i = docNodesByTitle_.constFind(Doc::canonicalTitle(title)); + else + i = docNodesByTitle_.constFind(title); + if (i != docNodesByTitle_.constEnd()) { + /* + Reporting all these duplicate section titles is probably + overkill. We should report the duplicate file and let + that suffice. + */ + DocumentNodeMultiMap::const_iterator j = i; + ++j; + if (j != docNodesByTitle_.constEnd() && j.key() == i.key()) { + while (j != docNodesByTitle_.constEnd()) { + if (j.key() == i.key() && j.value()->url().isEmpty()) { + break; // Just report one duplicate for now. + } + ++j; + } + if (j != docNodesByTitle_.cend()) { + i.value()->location().warning("This page title exists in more than one file: " + title); + j.value()->location().warning("[It also exists here]"); + } + } + return i.value(); + } + return 0; +} + +/*! + Returns a canonical title for the \a atom, if the \a atom + is a SectionLeft or a Target. + */ +QString Tree::refForAtom(const Atom* atom) +{ + if (atom) { + if (atom->type() == Atom::SectionLeft) + return Doc::canonicalTitle(Text::sectionHeading(atom).toString()); + if ((atom->type() == Atom::Target) || (atom->type() == Atom::Keyword)) + return Doc::canonicalTitle(atom->string()); + } + return QString(); +} + +/*! + \fn const CNMap& Tree::groups() const + Returns a const reference to the collection of all + group nodes. +*/ + +/*! + \fn const ModuleMap& Tree::modules() const + Returns a const reference to the collection of all + module nodes. +*/ + +/*! + \fn const QmlModuleMap& Tree::qmlModules() const + Returns a const reference to the collection of all + QML module nodes. +*/ + +/*! + Returns a pointer to the collection map specified by \a genus. + Returns null if \a genus is not specified. + */ +CNMap* Tree::getCollectionMap(Node::Genus genus) +{ + switch (genus) { + case Node::DOC: + return &groups_; + case Node::CPP: + return &modules_; + case Node::QML: + return &qmlModules_; + case Node::JS: + return &jsModules_; + default: + break; + } + return 0; +} + +/*! + Returns a pointer to the collection named \a name of the + specified \a genus in this tree. If there is no matching + collection in this tree, 0 is returned. + */ +CollectionNode* Tree::getCollection(const QString& name, Node::Genus genus) +{ + CNMap* m = getCollectionMap(genus); + if (m) { + CNMap::const_iterator i = m->constFind(name); + if (i != m->cend()) + return i.value(); + } + return 0; +} + +/*! + Find the group, module, QML module, or JavaScript module + named \a name and return a pointer to that collection node. + \a genus specifies which kind of collection node you want. + If a collection node with the specified \a name and \a genus + is not found, a new one is created, and the pointer to the + new one is returned. + + If a new collection node is created, its parent is the tree + root, and the new collection node is marked \e{not seen}. + + \a genus must be specified, i.e. it must not be \c{DontCare}. + If it is \c{DontCare}, 0 is returned, which is a programming + error. + */ +CollectionNode* Tree::findCollection(const QString& name, Node::Genus genus) +{ + CNMap* m = getCollectionMap(genus); + if (!m) // error + return 0; + CNMap::const_iterator i = m->constFind(name); + if (i != m->cend()) + return i.value(); + Node::NodeType t = Node::NoType; + switch (genus) { + case Node::DOC: + t = Node::Group; + break; + case Node::CPP: + t = Node::Module; + break; + case Node::QML: + t = Node::QmlModule; + break; + case Node::JS: + t = Node::QmlModule; + break; + default: + break; + } + CollectionNode* cn = new CollectionNode(t, root(), name, genus); + cn->markNotSeen(); + m->insert(name, cn); + return cn; +} + +/*! \fn CollectionNode* Tree::findGroup(const QString& name) + Find the group node named \a name and return a pointer + to it. If the group node is not found, add a new group + node named \a name and return a pointer to the new one. + + If a new group node is added, its parent is the tree root, + and the new group node is marked \e{not seen}. + */ + +/*! \fn CollectionNode* Tree::findModule(const QString& name) + Find the module node named \a name and return a pointer + to it. If a matching node is not found, add a new module + node named \a name and return a pointer to that one. + + If a new module node is added, its parent is the tree root, + and the new module node is marked \e{not seen}. + */ + +/*! \fn CollectionNode* Tree::findQmlModule(const QString& name) + Find the QML module node named \a name and return a pointer + to it. If a matching node is not found, add a new QML module + node named \a name and return a pointer to that one. + + If a new QML module node is added, its parent is the tree root, + and the new node is marked \e{not seen}. + */ + +/*! \fn CollectionNode* Tree::findJsModule(const QString& name) + Find the JavaScript module named \a name and return a pointer + to it. If a matching node is not found, add a new JavaScript + module node named \a name and return a pointer to that one. + + If a new JavaScript module node is added, its parent is the + tree root, and the new node is marked \e{not seen}. + */ + +/*! \fn CollectionNode* Tree::addGroup(const QString& name) + Looks up the group node named \a name in the collection + of all group nodes. If a match is found, a pointer to the + node is returned. Otherwise, a new group node named \a name + is created and inserted into the collection, and the pointer + to that node is returned. + */ + +/*! \fn CollectionNode* Tree::addModule(const QString& name) + Looks up the module node named \a name in the collection + of all module nodes. If a match is found, a pointer to the + node is returned. Otherwise, a new module node named \a name + is created and inserted into the collection, and the pointer + to that node is returned. + */ + +/*! \fn CollectionNode* Tree::addQmlModule(const QString& name) + Looks up the QML module node named \a name in the collection + of all QML module nodes. If a match is found, a pointer to the + node is returned. Otherwise, a new QML module node named \a name + is created and inserted into the collection, and the pointer + to that node is returned. + */ + +/*! \fn CollectionNode* Tree::addJsModule(const QString& name) + Looks up the JavaScript module node named \a name in the collection + of all JavaScript module nodes. If a match is found, a pointer to the + node is returned. Otherwise, a new JavaScrpt module node named \a name + is created and inserted into the collection, and the pointer + to that node is returned. + */ + +/*! + Looks up the group node named \a name in the collection + of all group nodes. If a match is not found, a new group + node named \a name is created and inserted into the collection. + Then append \a node to the group's members list, and append the + group name to the list of group names in \a node. The parent of + \a node is not changed by this function. Returns a pointer to + the group node. + */ +CollectionNode* Tree::addToGroup(const QString& name, Node* node) +{ + CollectionNode* cn = findGroup(name); + if (!node->isInternal()) { + cn->addMember(node); + node->appendGroupName(name); + } + return cn; +} + +/*! + Looks up the module node named \a name in the collection + of all module nodes. If a match is not found, a new module + node named \a name is created and inserted into the collection. + Then append \a node to the module's members list. The parent of + \a node is not changed by this function. Returns the module node. + */ +CollectionNode* Tree::addToModule(const QString& name, Node* node) +{ + CollectionNode* cn = findModule(name); + cn->addMember(node); + node->setPhysicalModuleName(name); + return cn; +} + +/*! + Looks up the QML module named \a name. If it isn't there, + create it. Then append \a node to the QML module's member + list. The parent of \a node is not changed by this function. + Returns the pointer to the QML module node. + */ +CollectionNode* Tree::addToQmlModule(const QString& name, Node* node) +{ + QStringList qmid; + QStringList dotSplit; + QStringList blankSplit = name.split(QLatin1Char(' ')); + qmid.append(blankSplit[0]); + if (blankSplit.size() > 1) { + qmid.append(blankSplit[0] + blankSplit[1]); + dotSplit = blankSplit[1].split(QLatin1Char('.')); + qmid.append(blankSplit[0] + dotSplit[0]); + } + + CollectionNode* cn = findQmlModule(blankSplit[0]); + cn->addMember(node); + node->setQmlModule(cn); + if (node->isQmlType()) { + QmlTypeNode* n = static_cast<QmlTypeNode*>(node); + for (int i=0; i<qmid.size(); ++i) { + QString key = qmid[i] + "::" + node->name(); + insertQmlType(key, n); + } + } + return cn; +} + +/*! + Looks up the QML module named \a name. If it isn't there, + create it. Then append \a node to the QML module's member + list. The parent of \a node is not changed by this function. + Returns the pointer to the QML module node. + */ +CollectionNode* Tree::addToJsModule(const QString& name, Node* node) +{ + QStringList qmid; + QStringList dotSplit; + QStringList blankSplit = name.split(QLatin1Char(' ')); + qmid.append(blankSplit[0]); + if (blankSplit.size() > 1) { + qmid.append(blankSplit[0] + blankSplit[1]); + dotSplit = blankSplit[1].split(QLatin1Char('.')); + qmid.append(blankSplit[0] + dotSplit[0]); + } + + CollectionNode* cn = findJsModule(blankSplit[0]); + cn->addMember(node); + node->setQmlModule(cn); + if (node->isJsType()) { + QmlTypeNode* n = static_cast<QmlTypeNode*>(node); + for (int i=0; i<qmid.size(); ++i) { + QString key = qmid[i] + "::" + node->name(); + insertQmlType(key, n); + } + } + return cn; +} + +/*! + If the QML type map does not contain \a key, insert node + \a n with the specified \a key. + */ +void Tree::insertQmlType(const QString& key, QmlTypeNode* n) +{ + if (!qmlTypeMap_.contains(key)) + qmlTypeMap_.insert(key,n); +} + +/*! + Split \a target on "::" and find the function node with that + path. + */ +const Node* Tree::findFunctionNode(const QString& target, + const QString& params, + const Node* relative, + Node::Genus genus) const +{ + QString t = target; + if (t.endsWith("()")) { + t.chop(2); + } + QStringList path = t.split("::"); + const FunctionNode* fn = findFunctionNode(path, params, relative, SearchBaseClasses, genus); + if (fn && fn->metaness() != FunctionNode::MacroWithoutParams) + return fn; + return 0; +} + +/*! + Search for a node that is identified by \a name. + Return a pointer to a matching node, or 0. +*/ +const Node* Tree::checkForCollision(const QString& name) +{ + return findNode(QStringList(name), 0, 0, Node::DontCare); +} + +/*! + Generate a target of the form link-nnn, where the nnn is + the current link count for this tree. This target string + is returned. It will be output as an HTML anchor just before + an HTML link to the node \a t. + + The node \a t + */ +QString Tree::getNewLinkTarget(const Node* locNode, + const Node* t, + const QString& fileName, + QString& text, + bool broken) +{ + QString physicalModuleName; + if (t && !broken) { + Tree* tree = t->tree(); + if (tree != this) + tree->incrementLinkCount(); + physicalModuleName = tree->physicalModuleName(); + } + else + physicalModuleName = "broken"; + incrementLinkCount(); + QString target = QString("qa-target-%1").arg(-(linkCount())); + TargetLoc* tloc = new TargetLoc(locNode, target, fileName, text, broken); + TargetList* tList = 0; + TargetListMap::iterator i = targetListMap_->find(physicalModuleName); + if (i == targetListMap_->end()) { + tList = new TargetList; + i = targetListMap_->insert(physicalModuleName, tList); + } + else + tList = i.value(); + tList->append(tloc); + return target; +} + +/*! + Look up the target list for the specified \a module + and return a pointer to it. + */ +TargetList* Tree::getTargetList(const QString& module) +{ + return targetListMap_->value(module); +} + +QT_END_NAMESPACE diff --git a/src/qdoc/tree.h b/src/qdoc/tree.h new file mode 100644 index 000000000..1fef15bc6 --- /dev/null +++ b/src/qdoc/tree.h @@ -0,0 +1,255 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + tree.h +*/ + +#ifndef TREE_H +#define TREE_H + +#include <QtCore/qstack.h> +#include "node.h" + +QT_BEGIN_NAMESPACE + +class QStringList; +class QDocDatabase; + +struct TargetRec +{ + public: + enum TargetType { Unknown, Target, Keyword, Contents, Class, Function, Page, Subtitle }; + + TargetRec(const QString& name, + const QString& title, + TargetRec::TargetType type, + Node* node, + int priority) + : node_(node), ref_(name), title_(title), priority_(priority), type_(type) { + // Discard the dedicated ref for keywords - they always + // link to the top of the QDoc comment they appear in + if (type == Keyword) + ref_.clear(); + } + + bool isEmpty() const { return ref_.isEmpty(); } + + Node* node_; + QString ref_; + QString title_; + int priority_; + TargetType type_; +}; + +struct TargetLoc +{ + public: + TargetLoc(const Node* loc, const QString& t, const QString& fileName, const QString& text, bool broken) + : loc_(loc), target_(t), fileName_(fileName), text_(text), broken_(broken) { } + const Node* loc_; + QString target_; + QString fileName_; + QString text_; + bool broken_; +}; + +typedef QMultiMap<QString, TargetRec*> TargetMap; +typedef QMultiMap<QString, DocumentNode*> DocumentNodeMultiMap; +typedef QMap<QString, QmlTypeNode*> QmlTypeMap; +typedef QMultiMap<QString, const ExampleNode*> ExampleNodeMap; +typedef QVector<TargetLoc*> TargetList; +typedef QMap<QString, TargetList*> TargetListMap; + +class Tree +{ + private: + friend class QDocForest; + friend class QDocDatabase; + + typedef QMap<PropertyNode::FunctionRole, QString> RoleMap; + typedef QMap<PropertyNode*, RoleMap> PropertyMap; + + Tree(const QString& camelCaseModuleName, QDocDatabase* qdb); + ~Tree(); + + Node* findNodeForInclude(const QStringList& path) const; + ClassNode* findClassNode(const QStringList& path, const Node* start = 0) const; + NamespaceNode* findNamespaceNode(const QStringList& path) const; + FunctionNode* findFunctionNode(const QStringList& parentPath, const FunctionNode* clone); + const Node* findFunctionNode(const QString& target, + const QString& params, + const Node* relative, + Node::Genus genus) const; + + Node* findNodeRecursive(const QStringList& path, + int pathIndex, + const Node* start, + Node::NodeType type) const; + Node* findNodeRecursive(const QStringList& path, + int pathIndex, + Node* start, + const NodeTypeList& types) const; + + const Node* findNodeForTarget(const QStringList& path, + const QString& target, + const Node* node, + int flags, + Node::Genus genus, + QString& ref) const; + const Node* matchPathAndTarget(const QStringList& path, + int idx, + const QString& target, + const Node* node, + int flags, + Node::Genus genus, + QString& ref) const; + + const Node* findNode(const QStringList &path, + const Node* relative, // = 0, + int findFlags, // = 0, + Node::Genus genus) const; // = Node::DontCare) const; + + QmlTypeNode* findQmlTypeNode(const QStringList& path); + + Node* findNodeByNameAndType(const QStringList& path, Node::NodeType type) const; + Aggregate* findRelatesNode(const QStringList& path); + QString getRef(const QString& target, const Node* node) const; + void insertTarget(const QString& name, + const QString& title, + TargetRec::TargetType type, + Node* node, + int priority); + void resolveTargets(Aggregate* root); + const Node* findUnambiguousTarget(const QString& target, QString& ref) const; + const DocumentNode* findDocumentNodeByTitle(const QString& title) const; + + void addPropertyFunction(PropertyNode *property, + const QString &funcName, + PropertyNode::FunctionRole funcRole); + void resolveInheritance(Aggregate* n = 0); + void resolveInheritanceHelper(int pass, ClassNode* cn); + void resolveProperties(); + void resolveCppToQmlLinks(); + void resolveUsingClauses(); + void fixInheritance(NamespaceNode *rootNode = 0); + NamespaceNode *root() { return &root_; } + + const FunctionNode *findFunctionNode(const QStringList &path, + const QString& params, + const Node *relative = 0, + int findFlags = 0, + Node::Genus genus = Node::DontCare) const; + const NamespaceNode *root() const { return &root_; } + + FunctionNode *findVirtualFunctionInBaseClasses(ClassNode *classe, + FunctionNode *clone); + NodeList allBaseClasses(const ClassNode *classe) const; + QString refForAtom(const Atom* atom); + + CNMap* getCollectionMap(Node::Genus genus); + const CNMap& groups() const { return groups_; } + const CNMap& modules() const { return modules_; } + const CNMap& qmlModules() const { return qmlModules_; } + const CNMap& jsModules() const { return jsModules_; } + + CollectionNode* getCollection(const QString& name, Node::Genus genus); + CollectionNode* findCollection(const QString& name, Node::Genus genus); + + CollectionNode* findGroup(const QString& name) { return findCollection(name, Node::DOC); } + CollectionNode* findModule(const QString& name) { return findCollection(name, Node::CPP); } + CollectionNode* findQmlModule(const QString& name) { return findCollection(name, Node::QML); } + CollectionNode* findJsModule(const QString& name) { return findCollection(name, Node::JS); } + + CollectionNode* addGroup(const QString& name) { return findGroup(name); } + CollectionNode* addModule(const QString& name) { return findModule(name); } + CollectionNode* addQmlModule(const QString& name) { return findQmlModule(name); } + CollectionNode* addJsModule(const QString& name) { return findJsModule(name); } + + CollectionNode* addToGroup(const QString& name, Node* node); + CollectionNode* addToModule(const QString& name, Node* node); + CollectionNode* addToQmlModule(const QString& name, Node* node); + CollectionNode* addToJsModule(const QString& name, Node* node); + + QmlTypeNode* lookupQmlType(const QString& name) const { return qmlTypeMap_.value(name); } + void insertQmlType(const QString& key, QmlTypeNode* n); + void addExampleNode(ExampleNode* n) { exampleNodeMap_.insert(n->title(), n); } + ExampleNodeMap& exampleNodeMap() { return exampleNodeMap_; } + const Node* checkForCollision(const QString& name); + void setIndexFileName(const QString& t) { indexFileName_ = t; } + + bool treeHasBeenAnalyzed() const { return treeHasBeenAnalyzed_; } + bool docsHaveBeenGenerated() const { return docsHaveBeenGenerated_; } + void setTreeHasBeenAnalyzed() { treeHasBeenAnalyzed_ = true; } + void setdocsHaveBeenGenerated() { docsHaveBeenGenerated_ = true; } + QString getNewLinkTarget(const Node* locNode, + const Node* t, + const QString& fileName, + QString& text, + bool broken); + TargetList* getTargetList(const QString& module); + QStringList getTargetListKeys() { return targetListMap_->keys(); } + + public: + const QString& camelCaseModuleName() const { return camelCaseModuleName_; } + const QString& physicalModuleName() const { return physicalModuleName_; } + const QString& indexFileName() const { return indexFileName_; } + long incrementLinkCount() { return --linkCount_; } + void clearLinkCount() { linkCount_ = 0; } + long linkCount() const { return linkCount_; } + +private: + bool treeHasBeenAnalyzed_; + bool docsHaveBeenGenerated_; + long linkCount_; + QString camelCaseModuleName_; + QString physicalModuleName_; + QString indexFileName_; + QDocDatabase* qdb_; + NamespaceNode root_; + PropertyMap unresolvedPropertyMap; + DocumentNodeMultiMap docNodesByTitle_; + TargetMap nodesByTargetRef_; + TargetMap nodesByTargetTitle_; + CNMap groups_; + CNMap modules_; + CNMap qmlModules_; + CNMap jsModules_; + QmlTypeMap qmlTypeMap_; + ExampleNodeMap exampleNodeMap_; + TargetListMap* targetListMap_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/yyindent.cpp b/src/qdoc/yyindent.cpp new file mode 100644 index 000000000..e48a84708 --- /dev/null +++ b/src/qdoc/yyindent.cpp @@ -0,0 +1,1182 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + This file is a self-contained interactive indenter for C++ and Qt + Script. + + The general problem of indenting a C++ program is ill posed. On + the one hand, an indenter has to analyze programs written in a + free-form formal language that is best described in terms of + tokens, not characters, not lines. On the other hand, indentation + applies to lines and white space characters matter, and otherwise + the programs to indent are formally invalid in general, as they + are begin edited. + + The approach taken here works line by line. We receive a program + consisting of N lines or more, and we want to compute the + indentation appropriate for the Nth line. Lines beyond the Nth + lines are of no concern to us, so for simplicity we pretend the + program has exactly N lines and we call the Nth line the "bottom + line". Typically, we have to indent the bottom line when it's + still empty, so we concentrate our analysis on the N - 1 lines + that precede. + + By inspecting the (N - 1)-th line, the (N - 2)-th line, ... + backwards, we determine the kind of the bottom line and indent it + accordingly. + + * The bottom line is a comment line. See + bottomLineStartsInCComment() and + indentWhenBottomLineStartsInCComment(). + * The bottom line is a continuation line. See isContinuationLine() + and indentForContinuationLine(). + * The bottom line is a standalone line. See + indentForStandaloneLine(). + + Certain tokens that influence the indentation, notably braces, + are looked for in the lines. This is done by simple string + comparison, without a real tokenizer. Confusing constructs such + as comments and string literals are removed beforehand. +*/ + +#include <qregexp.h> +#include <qstringlist.h> + +QT_BEGIN_NAMESPACE + +/* qmake ignore Q_OBJECT */ + +/* + The indenter avoids getting stuck in almost infinite loops by + imposing arbitrary limits on the number of lines it analyzes when + looking for a construct. + + For example, the indenter never considers more than BigRoof lines + backwards when looking for the start of a C-style comment. +*/ +static const int SmallRoof = 40; +static const int BigRoof = 400; + +/* + The indenter supports a few parameters: + + * ppHardwareTabSize is the size of a '\t' in your favorite editor. + * ppIndentSize is the size of an indentation, or software tab + size. + * ppContinuationIndentSize is the extra indent for a continuation + line, when there is nothing to align against on the previous + line. + * ppCommentOffset is the indentation within a C-style comment, + when it cannot be picked up. +*/ + +static int ppHardwareTabSize = 8; +static int ppIndentSize = 4; +static int ppContinuationIndentSize = 8; + +static const int ppCommentOffset = 2; + +void setTabSize( int size ) +{ + ppHardwareTabSize = size; +} + +void setIndentSize( int size ) +{ + ppIndentSize = size; + ppContinuationIndentSize = 2 * size; +} + +static QRegExp *literal = 0; +static QRegExp *label = 0; +static QRegExp *inlineCComment = 0; +static QRegExp *braceX = 0; +static QRegExp *iflikeKeyword = 0; + +/* + Returns the first non-space character in the string t, or + QChar::Null if the string is made only of white space. +*/ +static QChar firstNonWhiteSpace( const QString& t ) +{ + int i = 0; + while ( i < (int) t.length() ) { + if ( !t[i].isSpace() ) + return t[i]; + i++; + } + return QChar::Null; +} + +/* + Returns \c true if string t is made only of white space; otherwise + returns \c false. +*/ +static bool isOnlyWhiteSpace( const QString& t ) +{ + return firstNonWhiteSpace( t ).isNull(); +} + +/* + Assuming string t is a line, returns the column number of a given + index. Column numbers and index are identical for strings that don't + contain '\t's. +*/ +int columnForIndex( const QString& t, int index ) +{ + int col = 0; + if ( index > (int) t.length() ) + index = t.length(); + + for ( int i = 0; i < index; i++ ) { + if ( t[i] == QChar('\t') ) { + col = ( (col / ppHardwareTabSize) + 1 ) * ppHardwareTabSize; + } else { + col++; + } + } + return col; +} + +/* + Returns the indentation size of string t. +*/ +int indentOfLine( const QString& t ) +{ + return columnForIndex( t, t.indexOf(firstNonWhiteSpace(t)) ); +} + +/* + Replaces t[k] by ch, unless t[k] is '\t'. Tab characters are better + left alone since they break the "index equals column" rule. No + provisions are taken against '\n' or '\r', which shouldn't occur in + t anyway. +*/ +static inline void eraseChar( QString& t, int k, QChar ch ) +{ + if ( t[k] != '\t' ) + t[k] = ch; +} + +/* + Removes some nefast constructs from a code line and returns the + resulting line. +*/ +static QString trimmedCodeLine( const QString& t ) +{ + QString trimmed = t; + int k; + + /* + Replace character and string literals by X's, since they may + contain confusing characters (such as '{' and ';'). "Hello!" is + replaced by XXXXXXXX. The literals are rigourously of the same + length before and after; otherwise, we would break alignment of + continuation lines. + */ + k = 0; + while ( (k = trimmed.indexOf(*literal, k)) != -1 ) { + for ( int i = 0; i < literal->matchedLength(); i++ ) + eraseChar( trimmed, k + i, 'X' ); + k += literal->matchedLength(); + } + + /* + Replace inline C-style comments by spaces. Other comments are + handled elsewhere. + */ + k = 0; + while ( (k = trimmed.indexOf(*inlineCComment, k)) != -1 ) { + for ( int i = 0; i < inlineCComment->matchedLength(); i++ ) + eraseChar( trimmed, k + i, ' ' ); + k += inlineCComment->matchedLength(); + } + + /* + Replace goto and switch labels by whitespace, but be careful + with this case: + + foo1: bar1; + bar2; + */ + while ( trimmed.lastIndexOf(':') != -1 && trimmed.indexOf(*label) != -1 ) { + QString cap1 = label->cap( 1 ); + int pos1 = label->pos( 1 ); + int stop = cap1.length(); + + if ( pos1 + stop < (int) trimmed.length() && ppIndentSize < stop ) + stop = ppIndentSize; + + int i = 0; + while ( i < stop ) { + eraseChar( trimmed, pos1 + i, ' ' ); + i++; + } + while ( i < (int) cap1.length() ) { + eraseChar( trimmed, pos1 + i, ';' ); + i++; + } + } + + /* + Remove C++-style comments. + */ + k = trimmed.indexOf( "//" ); + if ( k != -1 ) + trimmed.truncate( k ); + + return trimmed; +} + +/* + Returns '(' if the last parenthesis is opening, ')' if it is + closing, and QChar::Null if there are no parentheses in t. +*/ +static inline QChar lastParen( const QString& t ) +{ + int i = t.length(); + while ( i > 0 ) { + i--; + if ( t[i] == QChar('(') || t[i] == QChar(')') ) + return t[i]; + } + return QChar::Null; +} + +/* + Returns \c true if typedIn the same as okayCh or is null; otherwise + returns \c false. +*/ +static inline bool okay( QChar typedIn, QChar okayCh ) +{ + return typedIn == QChar::Null || typedIn == okayCh; +} + +/* + The "linizer" is a group of functions and variables to iterate + through the source code of the program to indent. The program is + given as a list of strings, with the bottom line being the line + to indent. The actual program might contain extra lines, but + those are uninteresting and not passed over to us. +*/ + +struct LinizerState +{ + QString line; + int braceDepth; + bool leftBraceFollows; + + QStringList::ConstIterator iter; + bool inCComment; + bool pendingRightBrace; +}; + +static QStringList *yyProgram = 0; +static LinizerState *yyLinizerState = 0; + +// shorthands +static const QString *yyLine = 0; +static const int *yyBraceDepth = 0; +static const bool *yyLeftBraceFollows = 0; + +/* + Saves and restores the state of the global linizer. This enables + backtracking. +*/ +#define YY_SAVE() \ + LinizerState savedState = *yyLinizerState +#define YY_RESTORE() \ + *yyLinizerState = savedState + +/* + Advances to the previous line in yyProgram and update yyLine + accordingly. yyLine is cleaned from comments and other damageable + constructs. Empty lines are skipped. +*/ +static bool readLine() +{ + int k; + + yyLinizerState->leftBraceFollows = + ( firstNonWhiteSpace(yyLinizerState->line) == QChar('{') ); + + do { + if ( yyLinizerState->iter == yyProgram->constBegin() ) { + yyLinizerState->line.clear(); + return false; + } + + --yyLinizerState->iter; + yyLinizerState->line = *yyLinizerState->iter; + + yyLinizerState->line = trimmedCodeLine( yyLinizerState->line ); + + /* + Remove C-style comments that span multiple lines. If the + bottom line starts in a C-style comment, we are not aware + of that and eventually yyLine will contain a slash-aster. + + Notice that both if's can be executed, since + yyLinizerState->inCComment is potentially set to false in + the first if. The order of the if's is also important. + */ + + if ( yyLinizerState->inCComment ) { + QString slashAster( "/*" ); + + k = yyLinizerState->line.indexOf( slashAster ); + if ( k == -1 ) { + yyLinizerState->line.clear(); + } else { + yyLinizerState->line.truncate( k ); + yyLinizerState->inCComment = false; + } + } + + if ( !yyLinizerState->inCComment ) { + QString asterSlash( "*/" ); + + k = yyLinizerState->line.indexOf( asterSlash ); + if ( k != -1 ) { + for ( int i = 0; i < k + 2; i++ ) + eraseChar( yyLinizerState->line, i, ' ' ); + yyLinizerState->inCComment = true; + } + } + + /* + Remove preprocessor directives. + */ + k = 0; + while ( k < (int) yyLinizerState->line.length() ) { + QChar ch = yyLinizerState->line[k]; + if ( ch == QChar('#') ) { + yyLinizerState->line.clear(); + } else if ( !ch.isSpace() ) { + break; + } + k++; + } + + /* + Remove trailing spaces. + */ + k = yyLinizerState->line.length(); + while ( k > 0 && yyLinizerState->line[k - 1].isSpace() ) + k--; + yyLinizerState->line.truncate( k ); + + /* + '}' increment the brace depth and '{' decrements it and not + the other way around, as we are parsing backwards. + */ + yyLinizerState->braceDepth += + yyLinizerState->line.count( '}' ) - + yyLinizerState->line.count( '{' ); + + /* + We use a dirty trick for + + } else ... + + We don't count the '}' yet, so that it's more or less + equivalent to the friendly construct + + } + else ... + */ + if ( yyLinizerState->pendingRightBrace ) + yyLinizerState->braceDepth++; + yyLinizerState->pendingRightBrace = + ( yyLinizerState->line.indexOf(*braceX) == 0 ); + if ( yyLinizerState->pendingRightBrace ) + yyLinizerState->braceDepth--; + } while ( yyLinizerState->line.isEmpty() ); + + return true; +} + +/* + Resets the linizer to its initial state, with yyLine containing the + line above the bottom line of the program. +*/ +static void startLinizer() +{ + yyLinizerState->braceDepth = 0; + yyLinizerState->inCComment = false; + yyLinizerState->pendingRightBrace = false; + + yyLine = &yyLinizerState->line; + yyBraceDepth = &yyLinizerState->braceDepth; + yyLeftBraceFollows = &yyLinizerState->leftBraceFollows; + + yyLinizerState->iter = yyProgram->constEnd(); + --yyLinizerState->iter; + yyLinizerState->line = *yyLinizerState->iter; + readLine(); +} + +/* + Returns \c true if the start of the bottom line of yyProgram (and + potentially the whole line) is part of a C-style comment; + otherwise returns \c false. +*/ +static bool bottomLineStartsInCComment() +{ + QString slashAster( "/*" ); + QString asterSlash( "*/" ); + + /* + We could use the linizer here, but that would slow us down + terribly. We are better to trim only the code lines we need. + */ + QStringList::ConstIterator p = yyProgram->constEnd(); + --p; // skip bottom line + + for ( int i = 0; i < BigRoof; i++ ) { + if ( p == yyProgram->constBegin() ) + return false; + --p; + + if ( (*p).indexOf(slashAster) != -1 || (*p).indexOf(asterSlash) != -1 ) { + QString trimmed = trimmedCodeLine( *p ); + + if ( trimmed.indexOf(slashAster) != -1 ) { + return true; + } else if ( trimmed.indexOf(asterSlash) != -1 ) { + return false; + } + } + } + return false; +} + +/* + Returns the recommended indent for the bottom line of yyProgram + assuming that it starts in a C-style comment, a condition that is + tested elsewhere. + + Essentially, we're trying to align against some text on the + previous line. +*/ +static int indentWhenBottomLineStartsInCComment() +{ + int k = yyLine->lastIndexOf( "/*" ); + if ( k == -1 ) { + /* + We found a normal text line in a comment. Align the + bottom line with the text on this line. + */ + return indentOfLine( *yyLine ); + } else { + /* + The C-style comment starts on this line. If there is + text on the same line, align with it. Otherwise, align + with the slash-aster plus a given offset. + */ + int indent = columnForIndex( *yyLine, k ); + k += 2; + while ( k < (int) yyLine->length() ) { + if ( !(*yyLine)[k].isSpace() ) + return columnForIndex( *yyLine, k ); + k++; + } + return indent + ppCommentOffset; + } +} + +/* + A function called match...() modifies the linizer state. If it + returns \c true, yyLine is the top line of the matched construct; + otherwise, the linizer is left in an unknown state. + + A function called is...() keeps the linizer state intact. +*/ + +/* + Returns \c true if the current line (and upwards) forms a braceless + control statement; otherwise returns \c false. + + The first line of the following example is a "braceless control + statement": + + if ( x ) + y; +*/ +static bool matchBracelessControlStatement() +{ + int delimDepth = 0; + + if ( yyLine->endsWith("else") ) + return true; + + if ( !yyLine->endsWith(QLatin1Char(')')) ) + return false; + + for ( int i = 0; i < SmallRoof; i++ ) { + int j = yyLine->length(); + while ( j > 0 ) { + j--; + QChar ch = (*yyLine)[j]; + + switch ( ch.unicode() ) { + case ')': + delimDepth++; + break; + case '(': + delimDepth--; + if ( delimDepth == 0 ) { + if ( yyLine->indexOf(*iflikeKeyword) != -1 ) { + /* + We have + + if ( x ) + y + + "if ( x )" is not part of the statement + "y". + */ + return true; + } + } + if ( delimDepth == -1 ) { + /* + We have + + if ( (1 + + 2) + + and not + + if ( 1 + + 2 ) + */ + return false; + } + break; + case '{': + case '}': + case ';': + /* + We met a statement separator, but not where we + expected it. What follows is probably a weird + continuation line. Be careful with ';' in for, + though. + */ + if ( ch != QChar(';') || delimDepth == 0 ) + return false; + } + } + + if ( !readLine() ) + break; + } + return false; +} + +/* + Returns \c true if yyLine is an unfinished line; otherwise returns + false. + + In many places we'll use the terms "standalone line", "unfinished + line" and "continuation line". The meaning of these should be + evident from this code example: + + a = b; // standalone line + c = d + // unfinished line + e + // unfinished continuation line + f + // unfinished continuation line + g; // continuation line +*/ +static bool isUnfinishedLine() +{ + bool unf = false; + + YY_SAVE(); + + if ( yyLine->isEmpty() ) + return false; + + QChar lastCh = (*yyLine)[(int) yyLine->length() - 1]; + if ( QString("{};").indexOf(lastCh) == -1 && !yyLine->endsWith("...") ) { + /* + It doesn't end with ';' or similar. If it's neither + "Q_OBJECT" nor "if ( x )", it must be an unfinished line. + */ + unf = ( yyLine->indexOf("Q_OBJECT") == -1 && + !matchBracelessControlStatement() ); + } else if ( lastCh == QChar(';') ) { + if ( lastParen(*yyLine) == QChar('(') ) { + /* + Exception: + + for ( int i = 1; i < 10; + */ + unf = true; + } else if ( readLine() && yyLine->endsWith(QLatin1Char(';')) && + lastParen(*yyLine) == QChar('(') ) { + /* + Exception: + + for ( int i = 1; + i < 10; + */ + unf = true; + } + } + + YY_RESTORE(); + return unf; +} + +/* + Returns \c true if yyLine is a continuation line; otherwise returns + false. +*/ +static bool isContinuationLine() +{ + bool cont = false; + + YY_SAVE(); + if ( readLine() ) + cont = isUnfinishedLine(); + YY_RESTORE(); + return cont; +} + +/* + Returns the recommended indent for the bottom line of yyProgram, + assuming it's a continuation line. + + We're trying to align the continuation line against some parenthesis + or other bracked left opened on a previous line, or some interesting + operator such as '='. +*/ +static int indentForContinuationLine() +{ + int braceDepth = 0; + int delimDepth = 0; + + bool leftBraceFollowed = *yyLeftBraceFollows; + + for ( int i = 0; i < SmallRoof; i++ ) { + int hook = -1; + + int j = yyLine->length(); + while ( j > 0 && hook < 0 ) { + j--; + QChar ch = (*yyLine)[j]; + + switch ( ch.unicode() ) { + case ')': + case ']': + delimDepth++; + break; + case '}': + braceDepth++; + break; + case '(': + case '[': + delimDepth--; + /* + An unclosed delimiter is a good place to align at, + at least for some styles (including Qt's). + */ + if ( delimDepth == -1 ) + hook = j; + break; + case '{': + braceDepth--; + /* + A left brace followed by other stuff on the same + line is typically for an enum or an initializer. + Such a brace must be treated just like the other + delimiters. + */ + if ( braceDepth == -1 ) { + if ( j < (int) yyLine->length() - 1 ) { + hook = j; + } else { + return 0; // shouldn't happen + } + } + break; + case '=': + /* + An equal sign is a very natural alignment hook + because it's usually the operator with the lowest + precedence in statements it appears in. Case in + point: + + int x = 1 + + 2; + + However, we have to beware of constructs such as + default arguments and explicit enum constant + values: + + void foo( int x = 0, + int y = 0 ); + + And not + + void foo( int x = 0, + int y = 0 ); + + These constructs are caracterized by a ',' at the + end of the unfinished lines or by unbalanced + parentheses. + */ + if ( QString("!=<>").indexOf((*yyLine)[j - 1]) == -1 && + (*yyLine)[j + 1] != '=' ) { + if ( braceDepth == 0 && delimDepth == 0 && + j < (int) yyLine->length() - 1 && + !yyLine->endsWith(QLatin1Char(',')) && + (yyLine->contains('(') == yyLine->contains(')')) ) + hook = j; + } + } + } + + if ( hook >= 0 ) { + /* + Yes, we have a delimiter or an operator to align + against! We don't really align against it, but rather + against the following token, if any. In this example, + the following token is "11": + + int x = ( 11 + + 2 ); + + If there is no such token, we use a continuation indent: + + static QRegExp foo( QString( + "foo foo foo foo foo foo foo foo foo") ); + */ + hook++; + while ( hook < (int) yyLine->length() ) { + if ( !(*yyLine)[hook].isSpace() ) + return columnForIndex( *yyLine, hook ); + hook++; + } + return indentOfLine( *yyLine ) + ppContinuationIndentSize; + } + + if ( braceDepth != 0 ) + break; + + /* + The line's delimiters are balanced. It looks like a + continuation line or something. + */ + if ( delimDepth == 0 ) { + if ( leftBraceFollowed ) { + /* + We have + + int main() + { + + or + + Bar::Bar() + : Foo( x ) + { + + The "{" should be flush left. + */ + if ( !isContinuationLine() ) + return indentOfLine( *yyLine ); + } else if ( isContinuationLine() || yyLine->endsWith(QLatin1Char(',')) ) { + /* + We have + + x = a + + b + + c; + + or + + int t[] = { + 1, 2, 3, + 4, 5, 6 + + The "c;" should fall right under the "b +", and the + "4, 5, 6" right under the "1, 2, 3,". + */ + return indentOfLine( *yyLine ); + } else { + /* + We have + + stream << 1 + + 2; + + We could, but we don't, try to analyze which + operator has precedence over which and so on, to + obtain the excellent result + + stream << 1 + + 2; + + We do have a special trick above for the assignment + operator above, though. + */ + return indentOfLine( *yyLine ) + ppContinuationIndentSize; + } + } + + if ( !readLine() ) + break; + } + return 0; +} + +/* + Returns the recommended indent for the bottom line of yyProgram if + that line is standalone (or should be indented likewise). + + Indenting a standalone line is tricky, mostly because of braceless + control statements. Grossly, we are looking backwards for a special + line, a "hook line", that we can use as a starting point to indent, + and then modify the indentation level according to the braces met + along the way to that hook. + + Let's consider a few examples. In all cases, we want to indent the + bottom line. + + Example 1: + + x = 1; + y = 2; + + The hook line is "x = 1;". We met 0 opening braces and 0 closing + braces. Therefore, "y = 2;" inherits the indent of "x = 1;". + + Example 2: + + if ( x ) { + y; + + The hook line is "if ( x ) {". No matter what precedes it, "y;" has + to be indented one level deeper than the hook line, since we met one + opening brace along the way. + + Example 3: + + if ( a ) + while ( b ) { + c; + } + d; + + To indent "d;" correctly, we have to go as far as the "if ( a )". + Compare with + + if ( a ) { + while ( b ) { + c; + } + d; + + Still, we're striving to go back as little as possible to + accommodate people with irregular indentation schemes. A hook line + near at hand is much more reliable than a remote one. +*/ +static int indentForStandaloneLine() +{ + for ( int i = 0; i < SmallRoof; i++ ) { + if ( !*yyLeftBraceFollows ) { + YY_SAVE(); + + if ( matchBracelessControlStatement() ) { + /* + The situation is this, and we want to indent "z;": + + if ( x && + y ) + z; + + yyLine is "if ( x &&". + */ + return indentOfLine( *yyLine ) + ppIndentSize; + } + YY_RESTORE(); + } + + if ( yyLine->endsWith(QLatin1Char(';')) || yyLine->contains('{') ) { + /* + The situation is possibly this, and we want to indent + "z;": + + while ( x ) + y; + z; + + We return the indent of "while ( x )". In place of "y;", + any arbitrarily complex compound statement can appear. + */ + + if ( *yyBraceDepth > 0 ) { + do { + if ( !readLine() ) + break; + } while ( *yyBraceDepth > 0 ); + } + + LinizerState hookState; + + while ( isContinuationLine() ) + readLine(); + hookState = *yyLinizerState; + + readLine(); + if ( *yyBraceDepth <= 0 ) { + do { + if ( !matchBracelessControlStatement() ) + break; + hookState = *yyLinizerState; + } while ( readLine() ); + } + + *yyLinizerState = hookState; + + while ( isContinuationLine() ) + readLine(); + + /* + Never trust lines containing only '{' or '}', as some + people (Richard M. Stallman) format them weirdly. + */ + if ( yyLine->trimmed().length() > 1 ) + return indentOfLine( *yyLine ) - *yyBraceDepth * ppIndentSize; + } + + if ( !readLine() ) + return -*yyBraceDepth * ppIndentSize; + } + return 0; +} + +/* + Constructs global variables used by the indenter. +*/ +static void initializeIndenter() +{ + literal = new QRegExp( "([\"'])(?:\\\\.|[^\\\\])*\\1" ); + literal->setMinimal( true ); + label = new QRegExp( + "^\\s*((?:case\\b([^:]|::)+|[a-zA-Z_0-9]+)(?:\\s+slots)?:)(?!:)" ); + inlineCComment = new QRegExp( "/\\*.*\\*/" ); + inlineCComment->setMinimal( true ); + braceX = new QRegExp( "^\\s*\\}\\s*(?:else|catch)\\b" ); + iflikeKeyword = new QRegExp( "\\b(?:catch|do|for|if|while)\\b" ); + + yyLinizerState = new LinizerState; +} + +/* + Destroys global variables used by the indenter. +*/ +static void terminateIndenter() +{ + delete literal; + delete label; + delete inlineCComment; + delete braceX; + delete iflikeKeyword; + delete yyLinizerState; +} + +/* + Returns the recommended indent for the bottom line of program. + Unless null, typedIn stores the character of yyProgram that + triggered reindentation. + + This function works better if typedIn is set properly; it is + slightly more conservative if typedIn is completely wild, and + slighly more liberal if typedIn is always null. The user might be + annoyed by the liberal behavior. +*/ +int indentForBottomLine( const QStringList& program, QChar typedIn ) +{ + if ( program.isEmpty() ) + return 0; + + initializeIndenter(); + + yyProgram = new QStringList( program ); + startLinizer(); + + const QString& bottomLine = program.last(); + QChar firstCh = firstNonWhiteSpace( bottomLine ); + int indent; + + if ( bottomLineStartsInCComment() ) { + /* + The bottom line starts in a C-style comment. Indent it + smartly, unless the user has already played around with it, + in which case it's better to leave her stuff alone. + */ + if ( isOnlyWhiteSpace(bottomLine) ) { + indent = indentWhenBottomLineStartsInCComment(); + } else { + indent = indentOfLine( bottomLine ); + } + } else if ( okay(typedIn, '#') && firstCh == QChar('#') ) { + /* + Preprocessor directives go flush left. + */ + indent = 0; + } else { + if ( isUnfinishedLine() ) { + indent = indentForContinuationLine(); + } else { + indent = indentForStandaloneLine(); + } + + if ( okay(typedIn, '}') && firstCh == QChar('}') ) { + /* + A closing brace is one level more to the left than the + code it follows. + */ + indent -= ppIndentSize; + } else if ( okay(typedIn, ':') ) { + QRegExp caseLabel( + "\\s*(?:case\\b(?:[^:]|::)+" + "|(?:public|protected|private|signals|default)(?:\\s+slots)?\\s*" + ")?:.*" ); + + if ( caseLabel.exactMatch(bottomLine) ) { + /* + Move a case label (or the ':' in front of a + constructor initialization list) one level to the + left, but only if the user did not play around with + it yet. Some users have exotic tastes in the + matter, and most users probably are not patient + enough to wait for the final ':' to format their + code properly. + + We don't attempt the same for goto labels, as the + user is probably the middle of "foo::bar". (Who + uses goto, anyway?) + */ + if ( indentOfLine(bottomLine) <= indent ) + indent -= ppIndentSize; + else + indent = indentOfLine( bottomLine ); + } + } + } + delete yyProgram; + terminateIndenter(); + return qMax( 0, indent ); +} + +QT_END_NAMESPACE + +#ifdef Q_TEST_YYINDENT +/* + Test driver. +*/ + +#include <qfile.h> +#include <qtextstream.h> + +#include <errno.h> + +QT_BEGIN_NAMESPACE + +static QString fileContents( const QString& fileName ) +{ + QFile f( fileName ); + if ( !f.open(QFile::ReadOnly) ) { + qWarning( "yyindent error: Cannot open file '%s' for reading: %s", + fileName.toLatin1().data(), strerror(errno) ); + return QString(); + } + + QTextStream t( &f ); + QString contents = t.read(); + f.close(); + if ( contents.isEmpty() ) + qWarning( "yyindent error: File '%s' is empty", fileName.toLatin1().data() ); + return contents; +} + +QT_END_NAMESPACE + +int main( int argc, char **argv ) +{ + QT_USE_NAMESPACE + + if ( argc != 2 ) { + qWarning( "usage: yyindent file.cpp" ); + return 1; + } + + QString code = fileContents( argv[1] ); + QStringList program = QStringList::split( '\n', code, true ); + QStringList p; + QString out; + + while ( !program.isEmpty() && program.last().trimmed().isEmpty() ) + program.remove( program.fromLast() ); + + QStringList::ConstIterator line = program.constBegin(); + while ( line != program.constEnd() ) { + p.push_back( *line ); + QChar typedIn = firstNonWhiteSpace( *line ); + if ( p.last().endsWith(QLatin1Char(':')) ) + typedIn = ':'; + + int indent = indentForBottomLine( p, typedIn ); + + if ( !(*line).trimmed().isEmpty() ) { + for ( int j = 0; j < indent; j++ ) + out += QLatin1Char(' '); + out += (*line).trimmed(); + } + out += QLatin1Char('\n'); + ++line; + } + + while ( out.endsWith(QLatin1Char('\n')) ) + out.truncate( out.length() - 1 ); + + printf( "%s\n", out.toLatin1().data() ); + return 0; +} + +#endif // Q_TEST_YYINDENT diff --git a/src/src.pro b/src/src.pro index 494898fee..387d54f70 100644 --- a/src/src.pro +++ b/src/src.pro @@ -14,6 +14,7 @@ qtHaveModule(widgets) { } SUBDIRS += linguist \ + qdoc \ qtplugininfo if(!android|android_app):!ios: SUBDIRS += qtpaths |