diff --git a/src/libs/utils/htmldocextractor.cpp b/src/libs/utils/htmldocextractor.cpp index 2edb4c8385043d46d713cee39e0b1538999d5dd8..90d2ee6e0ff6c58c82c04bd2db33fac30be0e9e8 100644 --- a/src/libs/utils/htmldocextractor.cpp +++ b/src/libs/utils/htmldocextractor.cpp @@ -96,7 +96,7 @@ QString HtmlDocExtractor::getClassOrNamespaceDescription(const QString &html, contents = findByPattern(html, pattern); } if (!contents.isEmpty()) - contents.replace(QLatin1String("<h2>Detailed Description</h2>"), name); + contents.remove(QLatin1String("Detailed Description")); formatContents(&contents); return contents; diff --git a/src/plugins/cppeditor/cppeditor.qrc b/src/plugins/cppeditor/cppeditor.qrc index 61cf4a08a657ce9feda22212006a46194c4042e3..0df45087536e80e0fa8418c9b8e2b455f8c6c129 100644 --- a/src/plugins/cppeditor/cppeditor.qrc +++ b/src/plugins/cppeditor/cppeditor.qrc @@ -5,5 +5,7 @@ <file>CppEditor.mimetypes.xml</file> <file>images/qt_c.png</file> <file>images/f1.png</file> + <file>images/larrow.png</file> + <file>images/rightarrow.png</file> </qresource> </RCC> diff --git a/src/plugins/cppeditor/cpphoverhandler.cpp b/src/plugins/cppeditor/cpphoverhandler.cpp index 61d3f7a3f7aa250282c640cdf95af1a73fa9a043..2b4f7a3cdc157ee3fd64b77bf97a50443ec00ba7 100644 --- a/src/plugins/cppeditor/cpphoverhandler.cpp +++ b/src/plugins/cppeditor/cpphoverhandler.cpp @@ -40,7 +40,6 @@ #include <texteditor/basetexteditor.h> #include <texteditor/displaysettings.h> #include <debugger/debuggerconstants.h> -#include <utils/htmldocextractor.h> #include <FullySpecifiedType.h> #include <Scope.h> @@ -54,6 +53,8 @@ #include <QtCore/QDir> #include <QtCore/QFileInfo> +#include <QtCore/QtAlgorithms> +#include <QtCore/QStringBuilder> #include <QtGui/QToolTip> #include <QtGui/QTextCursor> @@ -81,6 +82,42 @@ namespace { ch = doc->characterAt(tc->position()); } } + + void buildClassHierarchyHelper(Symbol *symbol, + const LookupContext &context, + const Overview &overview, + QList<QStringList> *hierarchy) { + if (ClassOrNamespace *classSymbol = context.lookupType(symbol)) { + const QList<ClassOrNamespace *> &bases = classSymbol->usings(); + foreach (ClassOrNamespace *baseClass, bases) { + const QList<Symbol *> &symbols = baseClass->symbols(); + foreach (Symbol *baseSymbol, symbols) { + if (baseSymbol->isClass()) { + hierarchy->back().append(overview.prettyName(baseSymbol->name())); + buildClassHierarchyHelper(baseSymbol, context, overview, hierarchy); + hierarchy->append(hierarchy->back()); + hierarchy->back().removeLast(); + } + } + } + } + } + + void buildClassHierarchy(Symbol *symbol, + const LookupContext &context, + const Overview &overview, + QList<QStringList> *hierarchy) { + if (hierarchy->isEmpty()) + hierarchy->append(QStringList()); + buildClassHierarchyHelper(symbol, context, overview, hierarchy); + hierarchy->removeLast(); + } + + struct ClassHierarchyComp + { + bool operator()(const QStringList &a, const QStringList &b) + { return a.size() < b.size(); } + }; } CppHoverHandler::CppHoverHandler(QObject *parent) @@ -127,7 +164,8 @@ void CppHoverHandler::updateContextHelpId(TextEditor::ITextEditor *editor, int p void CppHoverHandler::showToolTip(TextEditor::ITextEditor *editor, const QPoint &point, int pos) { - if (!editor) + TextEditor::BaseTextEditor *baseEditor = baseTextEditor(editor); + if (!baseEditor) return; editor->setContextHelpId(QString()); @@ -143,24 +181,13 @@ void CppHoverHandler::showToolTip(TextEditor::ITextEditor *editor, const QPoint if (m_toolTip.isEmpty()) { QToolTip::hideText(); } else { - if (m_matchingHelpCandidate != -1) { - QString contents; - TextEditor::BaseTextEditor *baseEditor = baseTextEditor(editor); - if (baseEditor && baseEditor->displaySettings().m_integrateDocsIntoTooltips) - contents = getDocContents(); - if (!contents.isEmpty()) { - m_toolTip = contents; - } else { - m_toolTip = Qt::escape(m_toolTip); - m_toolTip.prepend(QLatin1String("<nobr>")); - m_toolTip.append(QLatin1String("</nobr>")); - } + if (!m_classHierarchy.isEmpty()) + generateDiagramTooltip(baseEditor->displaySettings().m_integrateDocsIntoTooltips); + else + generateNormalTooltip(baseEditor->displaySettings().m_integrateDocsIntoTooltips); - m_toolTip = QString(QLatin1String("<table><tr>" - "<td valign=middle>%1</td>" - "<td><img src=\":/cppeditor/images/f1.png\"></td>" - "</tr></table>")).arg(m_toolTip); - } + if (m_matchingHelpCandidate != -1) + addF1ToTooltip(); const QPoint pnt = point - QPoint(0, #ifdef Q_WS_WIN @@ -220,7 +247,7 @@ void CppHoverHandler::identifyMatch(TextEditor::ITextEditor *editor, int pos) return; const LookupItem &lookupItem = lookupItems.first(); // ### TODO: select the best candidate. - handleLookupItemMatch(lookupItem, !extraSelectionTooltip); + handleLookupItemMatch(lookupItem, typeOfExpression.context(), !extraSelectionTooltip); } evaluateHelpCandidates(); @@ -244,7 +271,7 @@ bool CppHoverHandler::matchIncludeFile(const CPlusPlus::Document::Ptr &document, if (includeFile.line() == line) { m_toolTip = QDir::toNativeSeparators(includeFile.fileName()); const QString &fileName = QFileInfo(includeFile.fileName()).fileName(); - m_helpCandidates.append(HelpCandidate(fileName, fileName, HelpCandidate::Include)); + m_helpCandidates.append(HelpCandidate(fileName, fileName, HelpCandidate::Brief)); return true; } } @@ -267,7 +294,9 @@ bool CppHoverHandler::matchMacroInUse(const CPlusPlus::Document::Ptr &document, return false; } -void CppHoverHandler::handleLookupItemMatch(const LookupItem &lookupItem, const bool assignTooltip) +void CppHoverHandler::handleLookupItemMatch(const LookupItem &lookupItem, + const LookupContext &context, + const bool assignTooltip) { Symbol *matchingDeclaration = lookupItem.declaration(); FullySpecifiedType matchingType = lookupItem.type(); @@ -286,6 +315,11 @@ void CppHoverHandler::handleLookupItemMatch(const LookupItem &lookupItem, const matchingDeclaration->enclosingSymbol()->isEnum()) { qualifiedName.append(overview.prettyName( LookupContext::fullyQualifiedName(matchingDeclaration))); + + if (matchingDeclaration->isClass() || + matchingDeclaration->isForwardClassDeclaration()) { + buildClassHierarchy(matchingDeclaration, context, overview, &m_classHierarchy); + } } else { qualifiedName.append(overview.prettyName(matchingDeclaration->name())); } @@ -310,15 +344,15 @@ void CppHoverHandler::handleLookupItemMatch(const LookupItem &lookupItem, const helpCategory = HelpCandidate::Enum; } else if (matchingDeclaration->isTypedef()) { helpCategory = HelpCandidate::Typedef; - } else if (matchingDeclaration->isStatic() && !matchingDeclaration->isFunction()) { + } else if (matchingDeclaration->isStatic() && + !matchingDeclaration->type()->isFunctionType()) { helpCategory = HelpCandidate::Var; } else { helpCategory = HelpCandidate::Function; } // Help identifiers are simply the name with no signature, arguments or return type. - // They might or might not include a qualification. This is why two candidates are - // created. + // They might or might not include a qualification. This is why two candidates are created. overview.setShowArgumentNames(false); overview.setShowReturnTypes(false); overview.setShowFunctionSignatures(false); @@ -350,12 +384,16 @@ bool CppHoverHandler::helpIdExists(const QString &helpId) const return false; } -QString CppHoverHandler::getDocContents() +QString CppHoverHandler::getDocContents() const { Q_ASSERT(m_matchingHelpCandidate >= 0); + return getDocContents(m_helpCandidates.at(m_matchingHelpCandidate)); +} + +QString CppHoverHandler::getDocContents(const HelpCandidate &help) const +{ QString contents; - const HelpCandidate &help = m_helpCandidates.at(m_matchingHelpCandidate); QMap<QString, QUrl> helpLinks = Core::HelpManager::instance()->linksForIdentifier(help.m_helpId); foreach (const QUrl &url, helpLinks) { @@ -363,15 +401,14 @@ QString CppHoverHandler::getDocContents() const QString &name = removeQualificationIfAny(help.m_helpId); const QByteArray &html = Core::HelpManager::instance()->fileData(url); switch (help.m_category) { - case HelpCandidate::Include: + case HelpCandidate::Brief: contents = m_htmlDocExtractor.getClassOrNamespaceBrief(html, name); break; case HelpCandidate::ClassOrNamespace: contents = m_htmlDocExtractor.getClassOrNamespaceDescription(html, name); break; case HelpCandidate::Function: - contents = - m_htmlDocExtractor.getFunctionDescription(html, help.m_markId, name); + contents = m_htmlDocExtractor.getFunctionDescription(html, help.m_markId, name); break; case HelpCandidate::Enum: contents = m_htmlDocExtractor.getEnumDescription(html, name); @@ -395,14 +432,137 @@ QString CppHoverHandler::getDocContents() return contents; } +void CppHoverHandler::generateDiagramTooltip(const bool integrateDocs) +{ + QString clazz = m_toolTip; + + qSort(m_classHierarchy.begin(), m_classHierarchy.end(), ClassHierarchyComp()); + + QStringList directBaseClasses; + foreach (const QStringList &hierarchy, m_classHierarchy) { + if (hierarchy.size() > 1) + break; + directBaseClasses.append(hierarchy.at(0)); + } + + QString diagram(QLatin1String("<table>")); + for (int i = 0; i < directBaseClasses.size(); ++i) { + if (i == 0) { + diagram.append(QString( + "<tr><td>%1</td><td>" + "<img src=\":/cppeditor/images/rightarrow.png\"></td>" + "<td>%2</td></tr>").arg(m_toolTip).arg(directBaseClasses.at(i))); + } else { + diagram.append(QString( + "<tr><td></td><td>" + "<img src=\":/cppeditor/images/larrow.png\"></td>" + "<td>%1</td></tr>").arg(directBaseClasses.at(i))); + } + } + diagram.append(QLatin1String("</table>")); + m_toolTip = diagram; + + if (integrateDocs) { + if (m_matchingHelpCandidate != -1) { + m_toolTip.append(getDocContents()); + } else { + // Look for documented base classes. Diagram the nearest one or the nearest ones (in + // the case there are many at the same level). + int helpLevel = 0; + QList<int> baseClassesWithHelp; + for (int i = 0; i < m_classHierarchy.size(); ++i) { + const QStringList &hierarchy = m_classHierarchy.at(i); + if (helpLevel != 0 && hierarchy.size() != helpLevel) + break; + + const QString &name = hierarchy.last(); + if (helpIdExists(name)) { + baseClassesWithHelp.append(i); + if (helpLevel == 0) + helpLevel = hierarchy.size(); + } + } + + if (!baseClassesWithHelp.isEmpty()) { + // Choose the first one as the help match. + QString base = m_classHierarchy.at(baseClassesWithHelp.at(0)).last(); + HelpCandidate help(base, base, HelpCandidate::ClassOrNamespace); + m_helpCandidates.append(help); + m_matchingHelpCandidate = m_helpCandidates.size() - 1; + + if (baseClassesWithHelp.size() == 1 && helpLevel == 1) { + m_toolTip.append(getDocContents(help)); + } else { + foreach (int hierarchyIndex, baseClassesWithHelp) { + m_toolTip.append(QLatin1String("<p>")); + const QStringList &hierarchy = m_classHierarchy.at(hierarchyIndex); + Q_ASSERT(helpLevel <= hierarchy.size()); + + // Following contents are inside tables so they are on the exact same + // alignment as the top level diagram. + diagram = QString(QLatin1String("<table><tr><td>%1</td>")).arg(clazz); + for (int i = 0; i < helpLevel; ++i) { + diagram.append( + QLatin1String("<td><img src=\":/cppeditor/images/rightarrow.png\">" + "</td><td>") % + hierarchy.at(i) % + QLatin1String("</td>")); + } + diagram.append(QLatin1String("</tr></table>")); + + base = hierarchy.at(helpLevel - 1); + QString contents = + getDocContents(HelpCandidate(base, base, HelpCandidate::Brief)); + if (!contents.isEmpty()) { + m_toolTip.append(diagram % QLatin1String("<table><tr><td>") % + contents % QLatin1String("</td></tr></table>")); + } + m_toolTip.append(QLatin1String("</p>")); + } + } + } + } + } +} + +void CppHoverHandler::generateNormalTooltip(const bool integrateDocs) +{ + if (m_matchingHelpCandidate != -1) { + QString contents; + if (integrateDocs) + contents = getDocContents(); + if (!contents.isEmpty()) { + HelpCandidate::Category cat = m_helpCandidates.at(m_matchingHelpCandidate).m_category; + if (cat == HelpCandidate::ClassOrNamespace) + m_toolTip.append(contents); + else + m_toolTip = contents; + } else { + m_toolTip = Qt::escape(m_toolTip); + m_toolTip.prepend(QLatin1String("<nobr>")); + m_toolTip.append(QLatin1String("</nobr>")); + } + } +} + +void CppHoverHandler::addF1ToTooltip() +{ + m_toolTip = QString(QLatin1String("<table><tr><td valign=middle>%1</td><td> " + "<img src=\":/cppeditor/images/f1.png\"></td>" + "</tr></table>")).arg(m_toolTip); +} + void CppHoverHandler::resetMatchings() { m_matchingHelpCandidate = -1; m_helpCandidates.clear(); m_toolTip.clear(); + m_classHierarchy.clear(); } TextEditor::BaseTextEditor *CppHoverHandler::baseTextEditor(TextEditor::ITextEditor *editor) { + if (!editor) + return 0; return qobject_cast<TextEditor::BaseTextEditor *>(editor->widget()); } diff --git a/src/plugins/cppeditor/cpphoverhandler.h b/src/plugins/cppeditor/cpphoverhandler.h index 84c58733d484e314f788ab988aa943a3da463c7f..59c987d6d5f3d9fda30925314ce966439f5979c2 100644 --- a/src/plugins/cppeditor/cpphoverhandler.h +++ b/src/plugins/cppeditor/cpphoverhandler.h @@ -30,12 +30,12 @@ #ifndef CPPHOVERHANDLER_H #define CPPHOVERHANDLER_H +#include <cplusplus/CppDocument.h> #include <utils/htmldocextractor.h> #include <QtCore/QObject> #include <QtCore/QList> - -#include <cplusplus/CppDocument.h> +#include <QtCore/QStringList> QT_BEGIN_NAMESPACE class QPoint; @@ -43,6 +43,7 @@ QT_END_NAMESPACE namespace CPlusPlus { class LookupItem; +class LookupContext; } namespace Core { @@ -83,7 +84,7 @@ private: Typedef, Var, Macro, - Include, + Brief, Function }; @@ -101,11 +102,17 @@ private: bool matchIncludeFile(const CPlusPlus::Document::Ptr &document, unsigned line); bool matchMacroInUse(const CPlusPlus::Document::Ptr &document, unsigned pos); void handleLookupItemMatch(const CPlusPlus::LookupItem &lookupItem, + const CPlusPlus::LookupContext &lookupContext, const bool assignTooltip); void evaluateHelpCandidates(); bool helpIdExists(const QString &helpId) const; - QString getDocContents(); + QString getDocContents() const; + QString getDocContents(const HelpCandidate &helpCandidate) const; + + void generateDiagramTooltip(const bool integrateDocs); + void generateNormalTooltip(const bool integrateDocs); + void addF1ToTooltip(); static TextEditor::BaseTextEditor *baseTextEditor(TextEditor::ITextEditor *editor); @@ -113,6 +120,7 @@ private: int m_matchingHelpCandidate; QList<HelpCandidate> m_helpCandidates; QString m_toolTip; + QList<QStringList> m_classHierarchy; Utils::HtmlDocExtractor m_htmlDocExtractor; }; diff --git a/src/plugins/cppeditor/images/larrow.png b/src/plugins/cppeditor/images/larrow.png new file mode 100644 index 0000000000000000000000000000000000000000..02686583044c124fc15a33440d1f2710a7fc252e Binary files /dev/null and b/src/plugins/cppeditor/images/larrow.png differ diff --git a/src/plugins/cppeditor/images/rightarrow.png b/src/plugins/cppeditor/images/rightarrow.png new file mode 100644 index 0000000000000000000000000000000000000000..26b153d2aa9ac2decea3ecf148e4107fac857f4f Binary files /dev/null and b/src/plugins/cppeditor/images/rightarrow.png differ