From f837d49ca49f59cbcfd5742dd27edd5db817a9be Mon Sep 17 00:00:00 2001
From: Roberto Raggi <roberto.raggi@nokia.com>
Date: Fri, 22 Jan 2010 10:57:48 +0100
Subject: [PATCH] Show the function arguments hint.

---
 src/plugins/qmljseditor/qmlcodecompletion.cpp | 311 ++++++++++++++++--
 src/plugins/qmljseditor/qmlcodecompletion.h   |   7 +-
 2 files changed, 295 insertions(+), 23 deletions(-)

diff --git a/src/plugins/qmljseditor/qmlcodecompletion.cpp b/src/plugins/qmljseditor/qmlcodecompletion.cpp
index 21afc6666ee..dfc3d11bf82 100644
--- a/src/plugins/qmljseditor/qmlcodecompletion.cpp
+++ b/src/plugins/qmljseditor/qmlcodecompletion.cpp
@@ -40,13 +40,21 @@
 #include <texteditor/basetexteditor.h>
 
 #include <coreplugin/icore.h>
+#include <coreplugin/editormanager/editormanager.h>
 
 #include <QtCore/QFile>
 #include <QtCore/QFileInfo>
 #include <QtCore/QXmlStreamReader>
-#include <QtGui/QPainter>
+#include <QtCore/QDebug>
 
-#include <QtDebug>
+#include <QtGui/QPainter>
+#include <QtGui/QLabel>
+#include <QtGui/QStylePainter>
+#include <QtGui/QStyleOption>
+#include <QtGui/QToolButton>
+#include <QtGui/QHBoxLayout>
+#include <QtGui/QApplication>
+#include <QtGui/QDesktopWidget>
 
 using namespace QmlJSEditor;
 using namespace QmlJSEditor::Internal;
@@ -223,6 +231,239 @@ private:
 
 } // end of anonymous namespace
 
+namespace QmlJSEditor {
+namespace Internal {
+
+class FakeToolTipFrame : public QWidget
+{
+public:
+    FakeToolTipFrame(QWidget *parent = 0) :
+        QWidget(parent, Qt::ToolTip | Qt::WindowStaysOnTopHint)
+    {
+        setFocusPolicy(Qt::NoFocus);
+        setAttribute(Qt::WA_DeleteOnClose);
+
+        // Set the window and button text to the tooltip text color, since this
+        // widget draws the background as a tooltip.
+        QPalette p = palette();
+        const QColor toolTipTextColor = p.color(QPalette::Inactive, QPalette::ToolTipText);
+        p.setColor(QPalette::Inactive, QPalette::WindowText, toolTipTextColor);
+        p.setColor(QPalette::Inactive, QPalette::ButtonText, toolTipTextColor);
+        setPalette(p);
+    }
+
+protected:
+    void paintEvent(QPaintEvent *e);
+    void resizeEvent(QResizeEvent *e);
+};
+
+class FunctionArgumentWidget : public QLabel
+{
+public:
+    FunctionArgumentWidget();
+    void showFunctionHint(const QString &functionName, int minimumArgumentCount, int startPosition);
+
+protected:
+    bool eventFilter(QObject *obj, QEvent *e);
+
+private:
+    void updateArgumentHighlight();
+    void updateHintText();
+
+    QString m_functionName;
+    int m_minimumArgumentCount;
+    int m_startpos;
+    int m_currentarg;
+    int m_current;
+    bool m_escapePressed;
+
+    TextEditor::ITextEditor *m_editor;
+
+    QWidget *m_pager;
+    QLabel *m_numberLabel;
+    FakeToolTipFrame *m_popupFrame;
+};
+
+void FakeToolTipFrame::paintEvent(QPaintEvent *)
+{
+    QStylePainter p(this);
+    QStyleOptionFrame opt;
+    opt.init(this);
+    p.drawPrimitive(QStyle::PE_PanelTipLabel, opt);
+    p.end();
+}
+
+void FakeToolTipFrame::resizeEvent(QResizeEvent *)
+{
+    QStyleHintReturnMask frameMask;
+    QStyleOption option;
+    option.init(this);
+    if (style()->styleHint(QStyle::SH_ToolTip_Mask, &option, this, &frameMask))
+        setMask(frameMask.region);
+}
+
+
+FunctionArgumentWidget::FunctionArgumentWidget():
+    m_minimumArgumentCount(0),
+    m_startpos(-1),
+    m_current(0),
+    m_escapePressed(false)
+{
+    QObject *editorObject = Core::EditorManager::instance()->currentEditor();
+    m_editor = qobject_cast<TextEditor::ITextEditor *>(editorObject);
+
+    m_popupFrame = new FakeToolTipFrame(m_editor->widget());
+
+    setParent(m_popupFrame);
+    setFocusPolicy(Qt::NoFocus);
+
+    m_pager = new QWidget;
+    QHBoxLayout *hbox = new QHBoxLayout(m_pager);
+    hbox->setMargin(0);
+    hbox->setSpacing(0);
+    m_numberLabel = new QLabel;
+    hbox->addWidget(m_numberLabel);
+
+    QHBoxLayout *layout = new QHBoxLayout;
+    layout->setMargin(0);
+    layout->setSpacing(0);
+    layout->addWidget(m_pager);
+    layout->addWidget(this);
+    m_popupFrame->setLayout(layout);
+
+    setTextFormat(Qt::RichText);
+    setMargin(1);
+
+    qApp->installEventFilter(this);
+}
+
+void FunctionArgumentWidget::showFunctionHint(const QString &functionName, int mininumArgumentCount, int startPosition)
+{
+    if (m_startpos == startPosition)
+        return;
+
+    m_functionName = functionName;
+    m_minimumArgumentCount = mininumArgumentCount;
+    m_startpos = startPosition;
+    m_current = 0;
+    m_escapePressed = false;
+
+    // update the text
+    m_currentarg = -1;
+    updateArgumentHighlight();
+
+    m_popupFrame->show();
+}
+
+void FunctionArgumentWidget::updateArgumentHighlight()
+{
+    int curpos = m_editor->position();
+    if (curpos < m_startpos) {
+        m_popupFrame->close();
+        return;
+    }
+
+    updateHintText();
+
+
+#if 0
+    QString str = m_editor->textAt(m_startpos, curpos - m_startpos);
+    int argnr = 0;
+    int parcount = 0;
+    SimpleLexer tokenize;
+    QList<SimpleToken> tokens = tokenize(str);
+    for (int i = 0; i < tokens.count(); ++i) {
+        const SimpleToken &tk = tokens.at(i);
+        if (tk.is(T_LPAREN))
+            ++parcount;
+        else if (tk.is(T_RPAREN))
+            --parcount;
+        else if (! parcount && tk.is(T_COMMA))
+            ++argnr;
+    }
+
+    if (m_currentarg != argnr) {
+        m_currentarg = argnr;
+        updateHintText();
+    }
+
+    if (parcount < 0)
+        m_popupFrame->close();
+#endif
+}
+
+bool FunctionArgumentWidget::eventFilter(QObject *obj, QEvent *e)
+{
+    switch (e->type()) {
+    case QEvent::ShortcutOverride:
+        if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) {
+            m_escapePressed = true;
+        }
+        break;
+    case QEvent::KeyPress:
+        if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) {
+            m_escapePressed = true;
+        }
+        break;
+    case QEvent::KeyRelease:
+        if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && m_escapePressed) {
+            m_popupFrame->close();
+            return false;
+        }
+        updateArgumentHighlight();
+        break;
+    case QEvent::WindowDeactivate:
+    case QEvent::FocusOut:
+        if (obj != m_editor->widget())
+            break;
+        m_popupFrame->close();
+        break;
+    case QEvent::MouseButtonPress:
+    case QEvent::MouseButtonRelease:
+    case QEvent::MouseButtonDblClick:
+    case QEvent::Wheel: {
+            QWidget *widget = qobject_cast<QWidget *>(obj);
+            if (! (widget == this || m_popupFrame->isAncestorOf(widget))) {
+                m_popupFrame->close();
+            }
+        }
+        break;
+    default:
+        break;
+    }
+    return false;
+}
+
+void FunctionArgumentWidget::updateHintText()
+{
+    QString prettyMethod;
+    prettyMethod += QString::fromLatin1("function ");
+    prettyMethod += m_functionName;
+    prettyMethod += QLatin1String("(arguments...)");
+
+    m_numberLabel->setText(prettyMethod);
+
+    m_popupFrame->setFixedWidth(m_popupFrame->minimumSizeHint().width());
+
+    const QDesktopWidget *desktop = QApplication::desktop();
+#ifdef Q_WS_MAC
+    const QRect screen = desktop->availableGeometry(desktop->screenNumber(m_editor->widget()));
+#else
+    const QRect screen = desktop->screenGeometry(desktop->screenNumber(m_editor->widget()));
+#endif
+
+    const QSize sz = m_popupFrame->sizeHint();
+    QPoint pos = m_editor->cursorRect(m_startpos).topLeft();
+    pos.setY(pos.y() - sz.height() - 1);
+
+    if (pos.x() + sz.width() > screen.right())
+        pos.setX(screen.right() - sz.width());
+
+    m_popupFrame->move(pos);
+}
+
+} } // end of namespace QmlJSEditor::Internal
+
 QmlCodeCompletion::QmlCodeCompletion(QmlModelManagerInterface *modelManager, QmlJS::TypeSystem *typeSystem, QObject *parent)
     : TextEditor::ICompletionCollector(parent),
       m_modelManager(modelManager),
@@ -252,8 +493,15 @@ bool QmlCodeCompletion::supportsEditor(TextEditor::ITextEditable *editor)
     return false;
 }
 
-bool QmlCodeCompletion::triggersCompletion(TextEditor::ITextEditable *)
-{ return false; }
+bool QmlCodeCompletion::triggersCompletion(TextEditor::ITextEditable *editor)
+{
+    const QChar ch = editor->characterAt(editor->position() - 1);
+
+    if (ch == QLatin1Char('(') || ch == QLatin1Char('.'))
+        return true;
+
+    return false;
+}
 
 int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
 {
@@ -290,11 +538,11 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
 
     const QIcon typeIcon = iconForColor(Qt::yellow);
 
-    QChar previousChar;
+    QChar completionOperator;
     if (m_startPosition > 0)
-        previousChar = editor->characterAt(m_startPosition - 1);
+        completionOperator = editor->characterAt(m_startPosition - 1);
 
-    if (previousChar.isSpace() || previousChar.isNull()) {
+    if (completionOperator.isSpace() || completionOperator.isNull()) {
         // Add the visible components to the completion box.
         foreach (QmlJS::Document::Ptr doc, snapshot) {
             const QFileInfo fileInfo(doc->fileName());
@@ -330,7 +578,7 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
         }
     }
 
-    if (previousChar == QLatin1Char('.')) {
+    if (completionOperator == QLatin1Char('.') || completionOperator == QLatin1Char('(')) {
         const int endOfExpression = m_startPosition - 1;
         int startOfExpression = endOfExpression - 2;
 
@@ -360,15 +608,34 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
 
             const QIcon symbolIcon = iconForColor(Qt::darkCyan);
 
-            EnumerateProperties enumerateProperties;
-            QHashIterator<QString, const Interpreter::Value *> it(enumerateProperties(value));
-            while (it.hasNext()) {
-                it.next();
+            if (value && completionOperator == QLatin1Char('.')) { // member completion
+                EnumerateProperties enumerateProperties;
+                QHashIterator<QString, const Interpreter::Value *> it(enumerateProperties(value));
+                while (it.hasNext()) {
+                    it.next();
 
-                TextEditor::CompletionItem item(this);
-                item.text = it.key();
-                item.icon = symbolIcon;
-                m_completions.append(item);
+                    TextEditor::CompletionItem item(this);
+                    item.text = it.key();
+                    item.icon = symbolIcon;
+                    m_completions.append(item);
+                }
+
+            } else if (value && completionOperator == QLatin1Char('(')) {
+                if (const Interpreter::FunctionValue *f = value->asFunctionValue()) {
+                    QString functionName = expression;
+                    int indexOfDot = expression.lastIndexOf(QLatin1Char('.'));
+                    if (indexOfDot != -1)
+                        functionName = expression.mid(indexOfDot + 1);
+                    // Recreate if necessary
+                    if (!m_functionArgumentWidget)
+                        m_functionArgumentWidget = new QmlJSEditor::Internal::FunctionArgumentWidget;
+
+                    m_functionArgumentWidget->showFunctionHint(functionName,
+                                                               f->argumentCount(),
+                                                               m_startPosition);
+                }
+
+                return -1;
             }
         }
 
@@ -378,12 +645,12 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
         return -1;
     }
 
-    if (previousChar.isNull()
-            || previousChar.isSpace()
-            || previousChar == QLatin1Char('{')
-            || previousChar == QLatin1Char('}')
-            || previousChar == QLatin1Char(':')
-            || previousChar == QLatin1Char(';')) {
+    if (completionOperator.isNull()
+            || completionOperator.isSpace()
+            || completionOperator == QLatin1Char('{')
+            || completionOperator == QLatin1Char('}')
+            || completionOperator == QLatin1Char(':')
+            || completionOperator == QLatin1Char(';')) {
         updateSnippets();
         m_completions.append(m_snippets);
     }
diff --git a/src/plugins/qmljseditor/qmlcodecompletion.h b/src/plugins/qmljseditor/qmlcodecompletion.h
index fc0721fd29e..990e11b48bd 100644
--- a/src/plugins/qmljseditor/qmlcodecompletion.h
+++ b/src/plugins/qmljseditor/qmlcodecompletion.h
@@ -33,6 +33,7 @@
 #include <qmljs/qmljstypesystem.h>
 #include <texteditor/icompletioncollector.h>
 #include <QtCore/QDateTime>
+#include <QtCore/QPointer>
 
 namespace TextEditor {
 class ITextEditable;
@@ -44,6 +45,8 @@ class QmlModelManagerInterface;
 
 namespace Internal {
 
+class FunctionArgumentWidget;
+
 class QmlCodeCompletion: public TextEditor::ICompletionCollector
 {
     Q_OBJECT
@@ -66,6 +69,8 @@ public:
     virtual void cleanup();
 
 private:
+    void updateSnippets();
+
     QmlModelManagerInterface *m_modelManager;
     TextEditor::ITextEditable *m_editor;
     int m_startPosition;
@@ -75,7 +80,7 @@ private:
 
     QList<TextEditor::CompletionItem> m_snippets;
     QDateTime m_snippetFileLastModified;
-    void updateSnippets();
+    QPointer<FunctionArgumentWidget> m_functionArgumentWidget;
 };
 
 
-- 
GitLab