From 2d2ea660b0f8e86b8cc931970b244101626d5094 Mon Sep 17 00:00:00 2001
From: Leandro Melo <leandro.melo@nokia.com>
Date: Tue, 20 Jul 2010 12:35:08 +0200
Subject: [PATCH] C++ tooltip: Arrow diagrams to show inheritance.

---
 src/libs/utils/htmldocextractor.cpp         |   2 +-
 src/plugins/cppeditor/cppeditor.qrc         |   2 +
 src/plugins/cppeditor/cpphoverhandler.cpp   | 220 +++++++++++++++++---
 src/plugins/cppeditor/cpphoverhandler.h     |  16 +-
 src/plugins/cppeditor/images/larrow.png     | Bin 0 -> 455 bytes
 src/plugins/cppeditor/images/rightarrow.png | Bin 0 -> 449 bytes
 6 files changed, 205 insertions(+), 35 deletions(-)
 create mode 100644 src/plugins/cppeditor/images/larrow.png
 create mode 100644 src/plugins/cppeditor/images/rightarrow.png

diff --git a/src/libs/utils/htmldocextractor.cpp b/src/libs/utils/htmldocextractor.cpp
index 2edb4c83850..90d2ee6e0ff 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 61cf4a08a65..0df45087536 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 61d3f7a3f7a..2b4f7a3cdc1 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>&nbsp;&nbsp;"
+                                      "<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 84c58733d48..59c987d6d5f 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
GIT binary patch
literal 455
zcmV;&0XY7NP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igY{
z3KtgyIyDaf00BoyL_t(I%bk-kYr;SnhM&aXF0S4D1KBzi3M~W$g(3&~Lpu5kYBzs?
zW57x5)<00_+^KXaSp`KgaJ}5u!RCl2mfCmv?z_AX_q})Vy4@~+(PFW1wTQSn0GQ9`
zZo^u8P%EWqx7#Ji0ieoQ><tEk%9A)i(V&!)IF3CL!59NTqtQ5klmJ<lQLEMPeShB!
z0@5^Px7z{G>-A1%<L3Yo;pH*}z^>P%X^PgG;cy5bN-6sNKFj6uG$7Y&bj^F`hR?eW
zQ4|3%o6RH)Lz>NI8DOn-fgS7Z_R7_x(MT#YFvd`?*Io1Bq9wmP2?4xPi1`Tj$%l9B
zI0AZ=8Du9$D5?tX18lcjzO+8u>;OR!ptYv;^n8}tWw6<7NRniK8;_IyNI$t)Yu(yf
x3)5*Cuv)D!#<-93Yb~ye|3(Qg9*_C2z5&S_<FXg-###UX002ovPDHLkV1mb1!ax83

literal 0
HcmV?d00001

diff --git a/src/plugins/cppeditor/images/rightarrow.png b/src/plugins/cppeditor/images/rightarrow.png
new file mode 100644
index 0000000000000000000000000000000000000000..26b153d2aa9ac2decea3ecf148e4107fac857f4f
GIT binary patch
literal 449
zcmV;y0Y3hTP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igY{
z3KlfKMs+*@00BWsL_t(I%bk<4YQj(yhQC~cySR4q1+qC63M~W$g(3lckd8h<?dAix
zXA-;h1%%F>N|%yVP!t2VH|KV+xkM9b?Z4!l<o+M`{5RL@^?Lk8i^am#)>>El0Q33W
zeGrkOI*KAXolXgI1gJ6=-wlVu%AGhs(NKIEV*qG08b^>4Aj>jpwHit(0F+XsY07@T
z2cX~YpUp<)0BbGFcEGY7Ae#+onxeI4G#UZeD2f;i2CP=A^MG8h;SKNITR!i*gkcCk
z9LF{Y0-DWc86YAquv5L=UAuZb9^1+cj4{;fbpRf(TK1P;!UA3?#C(K@<ik64oIt(G
z46-vL6jcRR0K46eFRd>&JAm)|Xsu~Izg%Q?8Em&(k|a5t#?$mL(oZfT;(8H**{lp$
ruh$r3+~WLLi+%BKlmL^-g#YRrY@FuX<B4v-00000NkvXXu0mjfwKBHk

literal 0
HcmV?d00001

-- 
GitLab