From 1c702357a6617ce2fac1ecb4c4dab7d639a76bbf Mon Sep 17 00:00:00 2001
From: Christiaan Janssen <christiaan.janssen@nokia.com>
Date: Wed, 8 Sep 2010 16:44:42 +0200
Subject: [PATCH] QmlInspector:  tooltips in qmldebug mode

Reviewed by: Lasse Holmstedt
---
 src/plugins/debugger/qml/qmlengine.cpp        |   8 +-
 src/plugins/debugger/qml/qmlengine.h          |   5 +
 src/plugins/qmljseditor/qmljseditor.h         |   2 +-
 .../qmljsinspector/qmljsclientproxy.cpp       |  30 +++++
 src/plugins/qmljsinspector/qmljsclientproxy.h |   3 +
 src/plugins/qmljsinspector/qmljsinspector.cpp | 105 ++++++++++++++++++
 src/plugins/qmljsinspector/qmljsinspector.h   |  16 +++
 .../qmljsinspector/qmljsinspectorplugin.cpp   |  11 ++
 .../qmljsinspector/qmljslivetextpreview.cpp   |  10 +-
 9 files changed, 177 insertions(+), 13 deletions(-)

diff --git a/src/plugins/debugger/qml/qmlengine.cpp b/src/plugins/debugger/qml/qmlengine.cpp
index d8c2ac69c46..bde16e30dc5 100644
--- a/src/plugins/debugger/qml/qmlengine.cpp
+++ b/src/plugins/debugger/qml/qmlengine.cpp
@@ -30,6 +30,7 @@
 #include "qmlengine.h"
 #include "qmladapter.h"
 
+#include "debuggertooltip.h"
 #include "debuggerconstants.h"
 #include "debuggerplugin.h"
 #include "debuggerdialogs.h"
@@ -170,6 +171,7 @@ void QmlEngine::connectionEstablished()
 
     ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
     pluginManager->addObject(m_adapter);
+    pluginManager->addObject(this);
     m_addedAdapterToObjectPool = true;
 
     plugin()->showMessage(tr("QML Debugger connected."), StatusBar);
@@ -233,6 +235,7 @@ void QmlEngine::shutdownEngineAsSlave()
     if (m_addedAdapterToObjectPool) {
         ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
         pluginManager->removeObject(m_adapter);
+        pluginManager->removeObject(this);
     }
 
     if (m_attachToRunningExternalApp) {
@@ -445,9 +448,8 @@ static QHash<QString, WatchData> m_toolTipCache;
 
 void QmlEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos)
 {
-    Q_UNUSED(mousePos)
-    Q_UNUSED(editor)
-    Q_UNUSED(cursorPos)
+    // this is processed by QML inspector, which has deps to qml js editor. Makes life easier.
+    emit tooltipRequested(mousePos, editor, cursorPos);
 }
 
 //////////////////////////////////////////////////////////////////////
diff --git a/src/plugins/debugger/qml/qmlengine.h b/src/plugins/debugger/qml/qmlengine.h
index 5c0854acf28..31f08d02798 100644
--- a/src/plugins/debugger/qml/qmlengine.h
+++ b/src/plugins/debugger/qml/qmlengine.h
@@ -46,6 +46,10 @@
 
 #include <projectexplorer/applicationlauncher.h>
 
+namespace Core {
+    class TextEditor;
+}
+
 namespace Debugger {
 namespace Internal {
 
@@ -120,6 +124,7 @@ private:
 
 signals:
     void sendMessage(const QByteArray &msg);
+    void tooltipRequested(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos);
 
 private slots:
     void connectionEstablished();
diff --git a/src/plugins/qmljseditor/qmljseditor.h b/src/plugins/qmljseditor/qmljseditor.h
index b95cd0d7efd..b70ad1ae889 100644
--- a/src/plugins/qmljseditor/qmljseditor.h
+++ b/src/plugins/qmljseditor/qmljseditor.h
@@ -116,7 +116,7 @@ public: // attributes
 
 class SemanticHighlighter;
 
-class SemanticInfo
+class QMLJSEDITOR_EXPORT SemanticInfo
 {
 public:
     SemanticInfo() {}
diff --git a/src/plugins/qmljsinspector/qmljsclientproxy.cpp b/src/plugins/qmljsinspector/qmljsclientproxy.cpp
index 9db56a375ae..4ca4a1d4923 100644
--- a/src/plugins/qmljsinspector/qmljsclientproxy.cpp
+++ b/src/plugins/qmljsinspector/qmljsclientproxy.cpp
@@ -198,6 +198,29 @@ QDeclarativeDebugObjectReference ClientProxy::objectReferenceForId(int debugId,
     return QDeclarativeDebugObjectReference();
 }
 
+QDeclarativeDebugObjectReference ClientProxy::objectReferenceForId(const QString &objectId) const
+{
+    if (!objectId.isEmpty() && objectId[0].isLower()) {
+        const QList<QDeclarativeDebugObjectReference> refs = objectReferences();
+        foreach (const QDeclarativeDebugObjectReference &ref, refs) {
+            if (ref.idString() == objectId)
+                return ref;
+        }
+    }
+    return QDeclarativeDebugObjectReference();
+}
+
+QDeclarativeDebugObjectReference ClientProxy::objectReferenceForLocation(const int line, const int column) const
+{
+    const QList<QDeclarativeDebugObjectReference> refs = objectReferences();
+    foreach (const QDeclarativeDebugObjectReference &ref, refs) {
+        if (ref.source().lineNumber() == line && ref.source().columnNumber() == column)
+            return ref;
+    }
+
+    return QDeclarativeDebugObjectReference();
+}
+
 QList<QDeclarativeDebugObjectReference> ClientProxy::objectReferences() const
 {
     QList<QDeclarativeDebugObjectReference> result;
@@ -253,6 +276,13 @@ bool ClientProxy::resetBindingForObject(int objectDebugId, const QString& proper
     return m_client->resetBindingForObject(objectDebugId, propertyName);
 }
 
+QDeclarativeDebugExpressionQuery *ClientProxy::queryExpressionResult(int objectDebugId, const QString &expr, QObject *parent)
+{
+    if (objectDebugId != -1)
+        return m_client->queryExpressionResult(objectDebugId,expr,parent);
+    return 0;
+}
+
 void ClientProxy::clearComponentCache()
 {
     if (isDesignClientConnected())
diff --git a/src/plugins/qmljsinspector/qmljsclientproxy.h b/src/plugins/qmljsinspector/qmljsclientproxy.h
index ca87e6f7d09..245e04c7160 100644
--- a/src/plugins/qmljsinspector/qmljsclientproxy.h
+++ b/src/plugins/qmljsinspector/qmljsclientproxy.h
@@ -65,11 +65,14 @@ public:
 
     bool setMethodBodyForObject(int objectDebugId, const QString &methodName, const QString &methodBody);
     bool resetBindingForObject(int objectDebugId, const QString &propertyName);
+    QDeclarativeDebugExpressionQuery *queryExpressionResult(int objectDebugId, const QString &expr, QObject *parent=0);
     void clearComponentCache();
 
     // returns the object references
     QList<QDeclarativeDebugObjectReference> objectReferences() const;
     QDeclarativeDebugObjectReference objectReferenceForId(int debugId) const;
+    QDeclarativeDebugObjectReference objectReferenceForId(const QString &objectId) const;
+    QDeclarativeDebugObjectReference objectReferenceForLocation(const int line, const int column) const;
     QList<QDeclarativeDebugObjectReference> rootObjectReference() const;
     DebugIdHash debugIdHash() const { return m_debugIdHash; };
 
diff --git a/src/plugins/qmljsinspector/qmljsinspector.cpp b/src/plugins/qmljsinspector/qmljsinspector.cpp
index 79dbae1e5a7..fc2d5957a1c 100644
--- a/src/plugins/qmljsinspector/qmljsinspector.cpp
+++ b/src/plugins/qmljsinspector/qmljsinspector.cpp
@@ -38,9 +38,11 @@
 
 #include <qmljseditor/qmljseditorconstants.h>
 
+#include <qmljseditor/qmljseditor.h>
 #include <qmljs/qmljsmodelmanagerinterface.h>
 #include <qmljs/qmljsdocument.h>
 
+#include <qmljs/parser/qmljsast_p.h>
 #include <debugger/debuggerrunner.h>
 #include <debugger/debuggerconstants.h>
 #include <debugger/debuggerengine.h>
@@ -49,6 +51,7 @@
 #include <debugger/debuggerrunner.h>
 #include <debugger/debuggeruiswitcher.h>
 #include <debugger/debuggerconstants.h>
+#include <debugger/qml/qmlengine.h>
 
 #include <utils/qtcassert.h>
 #include <utils/styledbar.h>
@@ -100,6 +103,8 @@
 #include <QtGui/QMessageBox>
 #include <QtGui/QTextBlock>
 
+#include <QtGui/QToolTip>
+#include <QtGui/QCursor>
 #include <QtNetwork/QHostAddress>
 
 using namespace QmlJS;
@@ -131,6 +136,8 @@ InspectorUi::InspectorUi(QObject *parent)
     , m_inspectorDockWidget(0)
     , m_settings(new InspectorSettings(this))
     , m_clientProxy(0)
+    , m_qmlEngine(0)
+    , m_debugQuery(0)
     , m_debugProject(0)
 {
     m_instance = this;
@@ -157,6 +164,103 @@ void InspectorUi::restoreSettings()
     m_settings->restoreSettings(Core::ICore::instance()->settings());
 }
 
+void InspectorUi::setDebuggerEngine(Debugger::Internal::QmlEngine *qmlEngine)
+{
+    if (m_qmlEngine && !qmlEngine) {
+        disconnect(m_qmlEngine, SIGNAL(tooltipRequested(QPoint, TextEditor::ITextEditor*, int)),
+                   this, SLOT(showDebuggerTooltip(QPoint, TextEditor::ITextEditor*, int)));
+    }
+
+    m_qmlEngine = qmlEngine;
+    if (m_qmlEngine) {
+        connect(m_qmlEngine, SIGNAL(tooltipRequested(QPoint, TextEditor::ITextEditor*, int)),
+                this, SLOT(showDebuggerTooltip(QPoint, TextEditor::ITextEditor*, int)));
+    }
+}
+
+Debugger::Internal::QmlEngine *InspectorUi::debuggerEngine() const
+{
+    return m_qmlEngine;
+}
+
+void InspectorUi::showDebuggerTooltip(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos)
+{
+    Q_UNUSED(mousePos);
+    if (editor->id() == QmlJSEditor::Constants::C_QMLJSEDITOR_ID) {
+        QmlJSEditor::Internal::QmlJSTextEditor *qmlEditor = static_cast<QmlJSEditor::Internal::QmlJSTextEditor*>(editor->widget());
+
+        QTextCursor tc(qmlEditor->document());
+        tc.setPosition(cursorPos);
+        tc.movePosition(QTextCursor::StartOfWord);
+        tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
+
+        QString wordAtCursor = tc.selectedText();
+        QString query;
+        QLatin1Char doubleQuote('"');
+
+        QmlJS::AST::Node *qmlNode = qmlEditor->semanticInfo().nodeUnderCursor(cursorPos);
+        if (!qmlNode)
+            return;
+        QmlJS::AST::Node *node = qmlEditor->semanticInfo().declaringMemberNoProperties(cursorPos);
+        if (!node)
+            return;
+        QDeclarativeDebugObjectReference ref = m_clientProxy->objectReferenceForLocation(node->uiObjectMemberCast()->firstSourceLocation().startLine, node->uiObjectMemberCast()->firstSourceLocation().startColumn);
+
+        if (ref.debugId() == -1)
+            return;
+
+        if (wordAtCursor == QString("id")) {
+            query = QString("\"id:") + ref.idString() + doubleQuote;
+        } else {
+            if ((qmlNode->kind == QmlJS::AST::Node::Kind_IdentifierExpression) ||
+                (qmlNode->kind == QmlJS::AST::Node::Kind_FieldMemberExpression)) {
+                tc.setPosition(qmlNode->expressionCast()->firstSourceLocation().begin());
+                tc.setPosition(qmlNode->expressionCast()->lastSourceLocation().end(),QTextCursor::KeepAnchor);
+                QString refToLook = tc.selectedText();
+                if ((qmlNode->kind == QmlJS::AST::Node::Kind_IdentifierExpression) &&
+                    (m_clientProxy->objectReferenceForId(refToLook).debugId() == -1)) {
+                    query = doubleQuote + QString("local: ") + refToLook + doubleQuote;
+                    foreach(QDeclarativeDebugPropertyReference property, ref.properties()) {
+                        if (property.name() == wordAtCursor && !property.valueTypeName().isEmpty()) {
+                            query =  doubleQuote + property.name() + QLatin1Char(':') + doubleQuote + QLatin1Char('+') + property.name();
+                            break;
+                        }
+                    }
+                }
+                else
+                    query =doubleQuote + refToLook + QLatin1Char(':') + doubleQuote + QLatin1Char('+') + refToLook;
+            } else {
+                // show properties
+                foreach(QDeclarativeDebugPropertyReference property, ref.properties()) {
+                    if (property.name() == wordAtCursor && !property.valueTypeName().isEmpty()) {
+                        query =  doubleQuote + property.name() + QLatin1Char(':') + doubleQuote + QLatin1Char('+') + property.name();
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (!query.isEmpty()) {
+            m_debugQuery = m_clientProxy->queryExpressionResult(ref.debugId(),query);
+            connect(m_debugQuery,SIGNAL(stateChanged(QDeclarativeDebugQuery::State)),this,SLOT(debugQueryUpdated(QDeclarativeDebugQuery::State)));
+        }
+    }
+}
+
+void InspectorUi::debugQueryUpdated(QDeclarativeDebugQuery::State newState)
+{
+    if (newState != QDeclarativeDebugExpressionQuery::Completed)
+        return;
+    if (!m_debugQuery)
+        return;
+
+    QString text = m_debugQuery->result().toString();
+    if (!text.isEmpty())
+        QToolTip::showText(QCursor::pos(), text);
+
+    disconnect(m_debugQuery,SIGNAL(stateChanged(QDeclarativeDebugQuery::State)),this,SLOT(debugQueryUpdated(QDeclarativeDebugQuery::State)));
+}
+
 void InspectorUi::connected(ClientProxy *clientProxy)
 {
     m_clientProxy = clientProxy;
@@ -203,6 +307,7 @@ void InspectorUi::disconnected()
                m_crumblePath, SLOT(updateContextPath(QStringList)));
 
     m_debugProject = 0;
+    m_qmlEngine = 0;
     resetViews();
 
     setupToolbar(false);
diff --git a/src/plugins/qmljsinspector/qmljsinspector.h b/src/plugins/qmljsinspector/qmljsinspector.h
index 3c53ce2daad..8f1006fed74 100644
--- a/src/plugins/qmljsinspector/qmljsinspector.h
+++ b/src/plugins/qmljsinspector/qmljsinspector.h
@@ -47,6 +47,10 @@ namespace ProjectExplorer {
     class Environment;
 }
 
+namespace TextEditor {
+    class ITextEditor;
+}
+
 namespace Core {
     class IContext;
 }
@@ -55,6 +59,12 @@ namespace QmlJS {
     class ModelManagerInterface;
 }
 
+namespace Debugger {
+namespace Internal {
+    class QmlEngine;
+}
+}
+
 QT_FORWARD_DECLARE_CLASS(QDockWidget)
 
 namespace QmlJSInspector {
@@ -97,6 +107,8 @@ public:
     void setupUi();
     void connected(ClientProxy *clientProxy);
     void disconnected();
+    void setDebuggerEngine(Debugger::Internal::QmlEngine *qmlEngine);
+    Debugger::Internal::QmlEngine *debuggerEngine() const;
 
 signals:
     void statusMessage(const QString &text);
@@ -125,6 +137,8 @@ private slots:
 
     void currentDebugProjectRemoved();
     void updatePendingPreviewDocuments(QmlJS::Document::Ptr doc);
+    void showDebuggerTooltip(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos);
+    void debugQueryUpdated(QDeclarativeDebugQuery::State);
 
 private:
     bool addQuotesForData(const QVariant &value) const;
@@ -146,6 +160,8 @@ private:
 
     InspectorSettings *m_settings;
     ClientProxy *m_clientProxy;
+    Debugger::Internal::QmlEngine *m_qmlEngine;
+    QDeclarativeDebugExpressionQuery *m_debugQuery;
 
     // Qml/JS integration
     QHash<QString, QmlJSLiveTextPreview *> m_textPreviews;
diff --git a/src/plugins/qmljsinspector/qmljsinspectorplugin.cpp b/src/plugins/qmljsinspector/qmljsinspectorplugin.cpp
index befce483c8f..bff091ab214 100644
--- a/src/plugins/qmljsinspector/qmljsinspectorplugin.cpp
+++ b/src/plugins/qmljsinspector/qmljsinspectorplugin.cpp
@@ -36,6 +36,7 @@
 #include <debugger/debuggeruiswitcher.h>
 #include <debugger/debuggerconstants.h>
 #include <debugger/qml/qmladapter.h>
+#include <debugger/qml/qmlengine.h>
 
 #include <qmlprojectmanager/qmlproject.h>
 #include <qmljseditor/qmljseditorconstants.h>
@@ -130,6 +131,12 @@ void InspectorPlugin::objectAdded(QObject *object)
     if (adapter) {
         m_clientProxy = new ClientProxy(adapter);
         m_inspectorUi->connected(m_clientProxy);
+        return;
+    }
+
+    Debugger::Internal::QmlEngine *engine = qobject_cast<Debugger::Internal::QmlEngine*>(object);
+    if (engine) {
+        m_inspectorUi->setDebuggerEngine(engine);
     }
 }
 
@@ -140,6 +147,10 @@ void InspectorPlugin::aboutToRemoveObject(QObject *obj)
         delete m_clientProxy;
         m_clientProxy = 0;
     }
+
+    if (m_inspectorUi->debuggerEngine() == obj) {
+        m_inspectorUi->setDebuggerEngine(0);
+    }
 }
 
 Q_EXPORT_PLUGIN(InspectorPlugin)
diff --git a/src/plugins/qmljsinspector/qmljslivetextpreview.cpp b/src/plugins/qmljsinspector/qmljslivetextpreview.cpp
index b3682395725..94dbe08ee72 100644
--- a/src/plugins/qmljsinspector/qmljslivetextpreview.cpp
+++ b/src/plugins/qmljsinspector/qmljslivetextpreview.cpp
@@ -206,15 +206,7 @@ void QmlJSLiveTextPreview::changeSelectedElements(QList<int> offsets, const QStr
         return;
 
     QDeclarativeDebugObjectReference objectRefUnderCursor;
-    if (!wordAtCursor.isEmpty() && wordAtCursor[0].isLower()) {
-        QList<QDeclarativeDebugObjectReference> refs = m_clientProxy.data()->objectReferences();
-        foreach (const QDeclarativeDebugObjectReference &ref, refs) {
-            if (ref.idString() == wordAtCursor) {
-                objectRefUnderCursor = ref;
-                break;
-            }
-        }
-    }
+    objectRefUnderCursor = m_clientProxy.data()->objectReferenceForId(wordAtCursor);
 
     QList<int> selectedReferences;
     bool containsReferenceUnderCursor = false;
-- 
GitLab