From 6ec316b8ffdbb6143359f42ca589267849ad6f11 Mon Sep 17 00:00:00 2001
From: Erik Verbruggen <erik.verbruggen@nokia.com>
Date: Thu, 8 Oct 2009 13:48:39 +0200
Subject: [PATCH] Added help-tooltips for QML types.

---
 src/plugins/qmleditor/qmleditor.pro           |   1 +
 .../qmleditor/qmlexpressionundercursor.h      |   1 -
 src/plugins/qmleditor/qmlhoverhandler.cpp     | 133 +++++++++++++++---
 src/plugins/qmleditor/qmlhoverhandler.h       |  10 ++
 src/plugins/qmleditor/qmllookupcontext.cpp    |  26 +++-
 src/plugins/qmleditor/qmllookupcontext.h      |   1 +
 src/plugins/qmleditor/qmlsymbol.cpp           |   1 +
 src/plugins/qmleditor/qmlsymbol.h             |   4 +-
 8 files changed, 154 insertions(+), 23 deletions(-)

diff --git a/src/plugins/qmleditor/qmleditor.pro b/src/plugins/qmleditor/qmleditor.pro
index 5f6a5a9642b..3dfa9367bc9 100644
--- a/src/plugins/qmleditor/qmleditor.pro
+++ b/src/plugins/qmleditor/qmleditor.pro
@@ -4,6 +4,7 @@ include(../../qtcreatorplugin.pri)
 include(qmleditor_dependencies.pri)
 include(parser/parser.pri)
 include(rewriter/rewriter.pri)
+CONFIG += help
 DEFINES += QMLEDITOR_LIBRARY \
     QT_CREATOR
 INCLUDEPATH += parser \
diff --git a/src/plugins/qmleditor/qmlexpressionundercursor.h b/src/plugins/qmleditor/qmlexpressionundercursor.h
index 144edde2ced..28e8d87d64f 100644
--- a/src/plugins/qmleditor/qmlexpressionundercursor.h
+++ b/src/plugins/qmleditor/qmlexpressionundercursor.h
@@ -40,7 +40,6 @@ public:
 private:
     void parseExpression(const QTextBlock &block);
 
-    QmlJS::AST::ExpressionNode *tryExpression(const QString &text);
     QmlJS::AST::Statement *tryStatement(const QString &text);
     QmlJS::AST::UiObjectMember *tryBinding(const QString &text);
 
diff --git a/src/plugins/qmleditor/qmlhoverhandler.cpp b/src/plugins/qmleditor/qmlhoverhandler.cpp
index 890a4167fec..18eb5ab9df0 100644
--- a/src/plugins/qmleditor/qmlhoverhandler.cpp
+++ b/src/plugins/qmleditor/qmlhoverhandler.cpp
@@ -27,8 +27,12 @@
 **
 **************************************************************************/
 
-#include "qmlhoverhandler.h"
 #include "qmleditor.h"
+#include "qmlexpressionundercursor.h"
+#include "qmlhoverhandler.h"
+#include "qmllookupcontext.h"
+#include "qmlresolveexpression.h"
+#include "qmlsymbol.h"
 
 #include <coreplugin/icore.h>
 #include <coreplugin/uniqueidmanager.h>
@@ -38,7 +42,6 @@
 #include <texteditor/basetexteditor.h>
 #include <debugger/debuggerconstants.h>
 
-#include <QtCore/QDebug>
 #include <QtCore/QDir>
 #include <QtCore/QFileInfo>
 #include <QtCore/QSettings>
@@ -47,13 +50,30 @@
 #include <QtGui/QTextBlock>
 #include <QtHelp/QHelpEngineCore>
 
-using namespace QmlEditor::Internal;
 using namespace Core;
+using namespace QmlEditor;
+using namespace QmlEditor::Internal;
 
 QmlHoverHandler::QmlHoverHandler(QObject *parent)
     : QObject(parent)
+    , m_helpEngineNeedsSetup(false)
 {
+    m_modelManager = ExtensionSystem::PluginManager::instance()->getObject<QmlModelManagerInterface>();
+
     ICore *core = ICore::instance();
+    QFileInfo fi(core->settings()->fileName());
+    // FIXME shouldn't the help engine create the directory if it doesn't exist?
+    QDir directory(fi.absolutePath()+"/qtcreator");
+    if (!directory.exists())
+        directory.mkpath(directory.absolutePath());
+
+    m_helpEngine = new QHelpEngineCore(directory.absolutePath()
+                                       + QLatin1String("/helpcollection.qhc"), this);
+    //m_helpEngine->setAutoSaveFilter(false);
+    if (!m_helpEngine->setupData())
+        qWarning() << "Could not initialize help engine:" << m_helpEngine->error();
+    m_helpEngine->setCurrentFilter(tr("Unfiltered"));
+    m_helpEngineNeedsSetup = m_helpEngine->registeredDocumentations().count() == 0;
 
     // Listen for editor opened events in order to connect to tooltip/helpid requests
     connect(core->editorManager(), SIGNAL(editorOpened(Core::IEditor *)),
@@ -78,26 +98,13 @@ void QmlHoverHandler::showToolTip(TextEditor::ITextEditor *editor, const QPoint
     if (! editor)
         return;
 
-    ScriptEditor *ed = qobject_cast<ScriptEditor *>(editor->widget());
-
     ICore *core = ICore::instance();
     const int dbgcontext = core->uniqueIDManager()->uniqueIdentifier(Debugger::Constants::C_GDBDEBUGGER);
 
     if (core->hasContext(dbgcontext))
         return;
 
-    m_toolTip.clear();
-
-    QTextCursor tc = ed->textCursor();
-    tc.setPosition(pos);
-    const unsigned line = tc.block().blockNumber() + 1;
-
-    foreach (const QmlJS::DiagnosticMessage &m, ed->diagnosticMessages()) {
-        if (m.loc.startLine == line) {
-            m_toolTip.append(m.message);
-            break;
-        }
-    }
+    updateHelpIdAndTooltip(editor, pos);
 
     if (m_toolTip.isEmpty())
         QToolTip::hideText();
@@ -114,7 +121,97 @@ void QmlHoverHandler::showToolTip(TextEditor::ITextEditor *editor, const QPoint
     }
 }
 
-void QmlHoverHandler::updateContextHelpId(TextEditor::ITextEditor *, int)
+void QmlHoverHandler::updateContextHelpId(TextEditor::ITextEditor *editor, int pos)
+{
+    updateHelpIdAndTooltip(editor, pos);
+}
+
+static QString buildHelpId(QmlSymbol *symbol)
 {
+    if (!symbol)
+        return QString();
+
+    const QString idTemplate(QLatin1String("QML %1 Element Reference"));
+
+    return idTemplate.arg(symbol->name());
 }
 
+void QmlHoverHandler::updateHelpIdAndTooltip(TextEditor::ITextEditor *editor, int pos)
+{
+    m_helpId.clear();
+    m_toolTip.clear();
+
+    if (!m_modelManager)
+        return;
+
+    ScriptEditor *scriptEditor = qobject_cast<ScriptEditor *>(editor->widget());
+    if (!scriptEditor)
+        return;
+
+    const Snapshot documents = m_modelManager->snapshot();
+    const QString fileName = editor->file()->fileName();
+    QmlDocument::Ptr doc = documents.value(fileName);
+    if (!doc)
+        return; // nothing to do
+
+    QTextCursor tc(scriptEditor->document());
+    tc.setPosition(pos);
+    const unsigned lineNumber = tc.block().blockNumber() + 1;
+
+    // We only want to show F1 if the tooltip matches the help id
+    bool showF1 = true;
+
+    foreach (const QmlJS::DiagnosticMessage &m, scriptEditor->diagnosticMessages()) {
+        if (m.loc.startLine == lineNumber) {
+            m_toolTip = m.message;
+            showF1 = false;
+            break;
+        }
+    }
+
+    if (m_helpId.isEmpty()) {
+        // Move to the end of a qualified name
+        bool stop = false;
+        while (!stop) {
+            const QChar ch = editor->characterAt(tc.position());
+            if (ch.isLetterOrNumber() || ch == QLatin1Char('_') || ch == QLatin1Char('.')) {
+                tc.setPosition(tc.position() + 1);
+            } else {
+                stop = true;
+            }
+        }
+
+        // Fetch the expression's code
+        QmlExpressionUnderCursor expressionUnderCursor;
+        expressionUnderCursor(tc, doc);
+
+        QmlLookupContext context(expressionUnderCursor.expressionScopes(), doc, m_modelManager->snapshot());
+        QmlResolveExpression resolver(context);
+        QmlSymbol *resolvedSymbol = resolver.typeOf(expressionUnderCursor.expressionNode());
+
+        if (resolvedSymbol) {
+            m_helpId = buildHelpId(resolvedSymbol);
+        }
+    }
+
+    if (m_helpEngineNeedsSetup && m_helpEngine->registeredDocumentations().count() > 0) {
+        m_helpEngine->setupData();
+        m_helpEngineNeedsSetup = false;
+    }
+
+    if (!m_toolTip.isEmpty())
+        m_toolTip = Qt::escape(m_toolTip);
+
+    if (!m_helpId.isEmpty() && !m_helpEngine->linksForIdentifier(m_helpId).isEmpty()) {
+        if (showF1) {
+            m_toolTip = QString(QLatin1String("<table><tr><td valign=middle><nobr>%1</td>"
+                                              "<td><img src=\":/cppeditor/images/f1.svg\"></td></tr></table>"))
+                    .arg(m_toolTip);
+        }
+        editor->setContextHelpId(m_helpId);
+    } else if (!m_toolTip.isEmpty()) {
+        m_toolTip = QString(QLatin1String("<nobr>%1")).arg(m_toolTip);
+    } else if (!m_helpId.isEmpty()) {
+        m_toolTip = QString(QLatin1String("<nobr>No help available for \"%1\"")).arg(m_helpId);
+    }
+}
diff --git a/src/plugins/qmleditor/qmlhoverhandler.h b/src/plugins/qmleditor/qmlhoverhandler.h
index 13cca4a57a0..b2050702e42 100644
--- a/src/plugins/qmleditor/qmlhoverhandler.h
+++ b/src/plugins/qmleditor/qmlhoverhandler.h
@@ -30,9 +30,12 @@
 #ifndef QMLHOVERHANDLER_H
 #define QMLHOVERHANDLER_H
 
+#include "qmlmodelmanagerinterface.h"
+
 #include <QtCore/QObject>
 
 QT_BEGIN_NAMESPACE
+class QHelpEngineCore;
 class QPoint;
 QT_END_NAMESPACE
 
@@ -62,7 +65,14 @@ private slots:
     void editorOpened(Core::IEditor *editor);
 
 private:
+    void updateHelpIdAndTooltip(TextEditor::ITextEditor *editor, int pos);
+
+private:
+    QmlModelManagerInterface *m_modelManager;
+    QHelpEngineCore *m_helpEngine;
+    QString m_helpId;
     QString m_toolTip;
+    bool m_helpEngineNeedsSetup;
 };
 
 } // namespace Internal
diff --git a/src/plugins/qmleditor/qmllookupcontext.cpp b/src/plugins/qmleditor/qmllookupcontext.cpp
index abfca64db86..f846f19403c 100644
--- a/src/plugins/qmleditor/qmllookupcontext.cpp
+++ b/src/plugins/qmleditor/qmllookupcontext.cpp
@@ -1,3 +1,4 @@
+#include <QDebug>
 #include "qmljsast_p.h"
 #include "qmljsengine_p.h"
 
@@ -70,7 +71,7 @@ QmlSymbol *QmlLookupContext::resolve(const QString &name)
     if (ids.contains(name))
         return ids[name];
     else
-        return 0;
+        return resolveType(name);
 }
 
 QmlSymbol *QmlLookupContext::resolveType(const QString &name, const QString &fileName)
@@ -106,7 +107,28 @@ QmlSymbol *QmlLookupContext::resolveType(const QString &name, const QString &fil
         }
     }
 
-    return 0;
+    return resolveBuildinType(name);
+}
+
+QmlSymbol *QmlLookupContext::resolveBuildinType(const QString &name)
+{
+    // FIXME: use a mete-type system here!
+
+    if (name == "Rectangle") {
+        QmlBuildInSymbol *rectSymbol = new QmlBuildInSymbol(name);
+        rectSymbol->addMember(new QmlBuildInSymbol("x"));
+        rectSymbol->addMember(new QmlBuildInSymbol("y"));
+        rectSymbol->addMember(new QmlBuildInSymbol("height"));
+        rectSymbol->addMember(new QmlBuildInSymbol("width"));
+        return rectSymbol;
+    } else if (name == "Item") {
+        return new QmlBuildInSymbol(name);
+    } else if (name == "Text") {
+        QmlBuildInSymbol *textSymbol = new QmlBuildInSymbol(name);
+        return textSymbol;
+    } else {
+        return 0;
+    }
 }
 
 QmlSymbol *QmlLookupContext::resolveProperty(const QString &name, QmlSymbol *scope, const QString &fileName)
diff --git a/src/plugins/qmleditor/qmllookupcontext.h b/src/plugins/qmleditor/qmllookupcontext.h
index d18e0ec5ec9..d1c64dc7112 100644
--- a/src/plugins/qmleditor/qmllookupcontext.h
+++ b/src/plugins/qmleditor/qmllookupcontext.h
@@ -32,6 +32,7 @@ public:
 private:
     QmlSymbol *resolveType(const QString &name, const QString &fileName);
     QmlSymbol *resolveProperty(const QString &name, QmlSymbol *scope, const QString &fileName);
+    QmlSymbol *resolveBuildinType(const QString &name);
 
     static QString toString(QmlJS::AST::UiQualifiedId *id);
 
diff --git a/src/plugins/qmleditor/qmlsymbol.cpp b/src/plugins/qmleditor/qmlsymbol.cpp
index 6eacae86450..afd0f7e719d 100644
--- a/src/plugins/qmleditor/qmlsymbol.cpp
+++ b/src/plugins/qmleditor/qmlsymbol.cpp
@@ -44,6 +44,7 @@ QmlBuildInSymbol::~QmlBuildInSymbol()
 QmlBuildInSymbol *QmlBuildInSymbol::asBuildInSymbol()
 { return this; }
 
+
 QmlSymbolFromFile::QmlSymbolFromFile(const QString &fileName, QmlJS::AST::UiObjectMember *node):
         _fileName(fileName),
         _node(node)
diff --git a/src/plugins/qmleditor/qmlsymbol.h b/src/plugins/qmleditor/qmlsymbol.h
index 8957db80c37..788341e104c 100644
--- a/src/plugins/qmleditor/qmlsymbol.h
+++ b/src/plugins/qmleditor/qmlsymbol.h
@@ -44,8 +44,8 @@ public:
     virtual const QString name() const
     { return _name; }
 
-    // TODO:
-//    virtual const List members();
+    void addMember(QmlBuildInSymbol *symbol)
+    { _members.append(symbol); }
 
 private:
     QString _name;
-- 
GitLab