OSDN Git Service

Show the signatures in a tooltip when completing functions.
authorRoberto Raggi <roberto.raggi@nokia.com>
Tue, 30 Nov 2010 14:42:18 +0000 (15:42 +0100)
committerRoberto Raggi <roberto.raggi@nokia.com>
Tue, 30 Nov 2010 14:43:17 +0000 (15:43 +0100)
src/libs/glsl/glsltype.h
src/libs/glsl/glsltypes.cpp
src/libs/glsl/glsltypes.h
src/plugins/glsleditor/glslcodecompletion.cpp
src/plugins/glsleditor/glslcodecompletion.h

index 0d3f3c3..18ea941 100644 (file)
@@ -38,6 +38,8 @@ class GLSL_EXPORT Type
 public:
     virtual ~Type();
 
+    virtual QString toString() const = 0;
+
     virtual const UndefinedType *asUndefinedType() const { return 0; }
     virtual const VoidType *asVoidType() const { return 0; }
     virtual const BoolType *asBoolType() const { return 0; }
index 5a74796..d374e35 100644 (file)
@@ -30,6 +30,7 @@
 #include "glsltypes.h"
 #include "glslsymbols.h"
 #include "glslengine.h"
+#include "glslparser.h"
 
 using namespace GLSL;
 
@@ -138,6 +139,20 @@ bool DoubleType::isLessThan(const Type *other) const
     return false;
 }
 
+QString VectorType::toString() const
+{
+    QChar prefix;
+    if (elementType()->asBoolType() != 0)
+        prefix = QLatin1Char('b');
+    else if (elementType()->asIntType() != 0)
+        prefix = QLatin1Char('i');
+    else if (elementType()->asUIntType() != 0)
+        prefix = QLatin1Char('u');
+    else if (elementType()->asDoubleType() != 0)
+        prefix = QLatin1Char('d');
+    return QString("%1vec%2").arg(prefix).arg(_dimension);
+}
+
 void VectorType::add(Symbol *symbol)
 {
     _members.insert(symbol->name(), symbol);
@@ -248,6 +263,20 @@ bool VectorType::isLessThan(const Type *other) const
     return false;
 }
 
+QString MatrixType::toString() const
+{
+    QChar prefix;
+    if (elementType()->asBoolType() != 0)
+        prefix = QLatin1Char('b');
+    else if (elementType()->asIntType() != 0)
+        prefix = QLatin1Char('i');
+    else if (elementType()->asUIntType() != 0)
+        prefix = QLatin1Char('u');
+    else if (elementType()->asDoubleType() != 0)
+        prefix = QLatin1Char('d');
+    return QString("%1mat%2x%3").arg(prefix, _columns, _rows);
+}
+
 bool MatrixType::isEqualTo(const Type *other) const
 {
     if (other) {
@@ -333,6 +362,25 @@ bool Struct::isLessThan(const Type *other) const
     return false;
 }
 
+QString Function::toString() const
+{
+    QString proto;
+    proto += _returnType->toString();
+    proto += QLatin1Char(' ');
+    proto += name();
+    proto += QLatin1Char('(');
+    for (int i = 0; i < _arguments.size(); ++i) {
+        if (i != 0)
+            proto += QLatin1String(", ");
+        Argument *arg = _arguments.at(i);
+        proto += arg->type()->toString();
+        proto += QLatin1Char(' ');
+        proto += arg->name();
+    }
+    proto += QLatin1Char(')');
+    return proto;
+}
+
 const Type *Function::returnType() const
 {
     return _returnType;
@@ -394,6 +442,11 @@ Symbol *Function::find(const QString &name) const
     return 0;
 }
 
+QString SamplerType::toString() const
+{
+    return QLatin1String(Parser::spell[_kind]);
+}
+
 bool SamplerType::isEqualTo(const Type *other) const
 {
     if (other) {
index d4d60bc..04b4490 100644 (file)
@@ -47,6 +47,7 @@ public:
 class GLSL_EXPORT UndefinedType: public Type
 {
 public:
+    virtual QString toString() const { return QLatin1String("undefined"); }
     virtual const UndefinedType *asUndefinedType() const { return this; }
     virtual bool isEqualTo(const Type *other) const;
     virtual bool isLessThan(const Type *other) const;
@@ -55,6 +56,7 @@ public:
 class GLSL_EXPORT VoidType: public Type
 {
 public:
+    virtual QString toString() const { return QLatin1String("void"); }
     virtual const VoidType *asVoidType() const { return this; }
     virtual bool isEqualTo(const Type *other) const;
     virtual bool isLessThan(const Type *other) const;
@@ -63,6 +65,7 @@ public:
 class GLSL_EXPORT BoolType: public Type
 {
 public:
+    virtual QString toString() const { return QLatin1String("bool"); }
     virtual const BoolType *asBoolType() const { return this; }
     virtual bool isEqualTo(const Type *other) const;
     virtual bool isLessThan(const Type *other) const;
@@ -71,6 +74,7 @@ public:
 class GLSL_EXPORT IntType: public Type
 {
 public:
+    virtual QString toString() const { return QLatin1String("int"); }
     virtual const IntType *asIntType() const { return this; }
     virtual bool isEqualTo(const Type *other) const;
     virtual bool isLessThan(const Type *other) const;
@@ -79,6 +83,7 @@ public:
 class GLSL_EXPORT UIntType: public Type
 {
 public:
+    virtual QString toString() const { return QLatin1String("uint"); }
     virtual const UIntType *asUIntType() const { return this; }
     virtual bool isEqualTo(const Type *other) const;
     virtual bool isLessThan(const Type *other) const;
@@ -87,6 +92,7 @@ public:
 class GLSL_EXPORT FloatType: public Type
 {
 public:
+    virtual QString toString() const { return QLatin1String("float"); }
     virtual const FloatType *asFloatType() const { return this; }
     virtual bool isEqualTo(const Type *other) const;
     virtual bool isLessThan(const Type *other) const;
@@ -95,6 +101,7 @@ public:
 class GLSL_EXPORT DoubleType: public Type
 {
 public:
+    virtual QString toString() const { return QLatin1String("double"); }
     virtual const DoubleType *asDoubleType() const { return this; }
     virtual bool isEqualTo(const Type *other) const;
     virtual bool isLessThan(const Type *other) const;
@@ -120,6 +127,7 @@ public:
     VectorType(const Type *elementType, int dimension)
         : IndexType(elementType), _dimension(dimension) {}
 
+    virtual QString toString() const;
     const Type *elementType() const { return indexElementType(); }
     int dimension() const { return _dimension; }
 
@@ -154,6 +162,7 @@ public:
     int columns() const { return _columns; }
     int rows() const { return _rows; }
 
+    virtual QString toString() const;
     virtual const MatrixType *asMatrixType() const { return this; }
     virtual bool isEqualTo(const Type *other) const;
     virtual bool isLessThan(const Type *other) const;
@@ -188,6 +197,7 @@ public:
     virtual Symbol *find(const QString &name) const;
 
     // as Type
+    virtual QString toString() const { return name(); }
     virtual const Struct *asStructType() const { return this; }
     virtual bool isEqualTo(const Type *other) const;
     virtual bool isLessThan(const Type *other) const;
@@ -215,6 +225,7 @@ public:
     Argument *argumentAt(int index) const;
 
     // as Type
+    virtual QString toString() const;
     virtual const Function *asFunctionType() const { return this; }
     virtual bool isEqualTo(const Type *other) const;
     virtual bool isLessThan(const Type *other) const;
@@ -246,6 +257,7 @@ public:
     // Kind of sampler as a token code; e.g. T_SAMPLER2D.
     int kind() const { return _kind; }
 
+    virtual QString toString() const;
     virtual const SamplerType *asSamplerType() const { return this; }
     virtual bool isEqualTo(const Type *other) const;
     virtual bool isLessThan(const Type *other) const;
@@ -269,6 +281,7 @@ public:
     virtual void add(Symbol *symbol);
 
     // as type
+    virtual QString toString() const { return QLatin1String("overload"); }
     virtual const OverloadSet *asOverloadSetType() const { return this; }
     virtual bool isEqualTo(const Type *other) const;
     virtual bool isLessThan(const Type *other) const;
index 7ab46de..e85c834 100644 (file)
 #include "glsleditorplugin.h"
 #include <glsl/glslengine.h>
 #include <glsl/glslengine.h>
+#include <glsl/glsllexer.h>
 #include <glsl/glslparser.h>
 #include <glsl/glslsemantic.h>
+#include <glsl/glslsymbols.h>
 #include <glsl/glslastdump.h>
 #include <cplusplus/ExpressionUnderCursor.h>
 #include <texteditor/completionsettings.h>
+#include <utils/faketooltip.h>
 #include <QtGui/QIcon>
 #include <QtGui/QPainter>
+#include <QtGui/QLabel>
+#include <QtGui/QToolButton>
+#include <QtGui/QHBoxLayout>
+#include <QtGui/QApplication>
+#include <QtGui/QDesktopWidget>
 #include <QtCore/QDebug>
 
 using namespace GLSLEditor;
@@ -243,6 +251,270 @@ static const char *glsl_keywords[] =
   0
 };
 
+namespace GLSLEditor {
+namespace Internal {
+class FunctionArgumentWidget : public QLabel
+{
+    Q_OBJECT
+
+public:
+    FunctionArgumentWidget();
+    void showFunctionHint(QVector<GLSL::Function *> functionSymbols,
+                          int startPosition);
+
+protected:
+    bool eventFilter(QObject *obj, QEvent *e);
+
+private slots:
+    void nextPage();
+    void previousPage();
+
+private:
+    void updateArgumentHighlight();
+    void updateHintText();
+    void placeInsideScreen();
+
+    GLSL::Function *currentFunction() const
+    { return m_items.at(m_current); }
+
+    int m_startpos;
+    int m_currentarg;
+    int m_current;
+    bool m_escapePressed;
+
+    TextEditor::ITextEditor *m_editor;
+
+    QWidget *m_pager;
+    QLabel *m_numberLabel;
+    Utils::FakeToolTip *m_popupFrame;
+    QVector<GLSL::Function *> m_items;
+};
+
+
+FunctionArgumentWidget::FunctionArgumentWidget():
+    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 Utils::FakeToolTip(m_editor->widget());
+
+    QToolButton *downArrow = new QToolButton;
+    downArrow->setArrowType(Qt::DownArrow);
+    downArrow->setFixedSize(16, 16);
+    downArrow->setAutoRaise(true);
+
+    QToolButton *upArrow = new QToolButton;
+    upArrow->setArrowType(Qt::UpArrow);
+    upArrow->setFixedSize(16, 16);
+    upArrow->setAutoRaise(true);
+
+    setParent(m_popupFrame);
+    setFocusPolicy(Qt::NoFocus);
+
+    m_pager = new QWidget;
+    QHBoxLayout *hbox = new QHBoxLayout(m_pager);
+    hbox->setMargin(0);
+    hbox->setSpacing(0);
+    hbox->addWidget(upArrow);
+    m_numberLabel = new QLabel;
+    hbox->addWidget(m_numberLabel);
+    hbox->addWidget(downArrow);
+
+    QHBoxLayout *layout = new QHBoxLayout;
+    layout->setMargin(0);
+    layout->setSpacing(0);
+    layout->addWidget(m_pager);
+    layout->addWidget(this);
+    m_popupFrame->setLayout(layout);
+
+    connect(upArrow, SIGNAL(clicked()), SLOT(previousPage()));
+    connect(downArrow, SIGNAL(clicked()), SLOT(nextPage()));
+
+    setTextFormat(Qt::RichText);
+
+    qApp->installEventFilter(this);
+}
+
+void FunctionArgumentWidget::showFunctionHint(QVector<GLSL::Function *> functionSymbols,
+                                              int startPosition)
+{
+    Q_ASSERT(!functionSymbols.isEmpty());
+
+    if (m_startpos == startPosition)
+        return;
+
+    m_pager->setVisible(functionSymbols.size() > 1);
+
+    m_items = functionSymbols;
+    m_startpos = startPosition;
+    m_current = 0;
+    m_escapePressed = false;
+
+    // update the text
+    m_currentarg = -1;
+    updateArgumentHighlight();
+
+    m_popupFrame->show();
+}
+
+void FunctionArgumentWidget::nextPage()
+{
+    m_current = (m_current + 1) % m_items.size();
+    updateHintText();
+}
+
+void FunctionArgumentWidget::previousPage()
+{
+    if (m_current == 0)
+        m_current = m_items.size() - 1;
+    else
+        --m_current;
+
+    updateHintText();
+}
+
+void FunctionArgumentWidget::updateArgumentHighlight()
+{
+    int curpos = m_editor->position();
+    if (curpos < m_startpos) {
+        m_popupFrame->close();
+        return;
+    }
+
+    const QByteArray str = m_editor->textAt(m_startpos, curpos - m_startpos).toLatin1();
+
+    int argnr = 0;
+    int parcount = 0;
+    GLSL::Lexer lexer(0, str.constData(), str.length());
+    GLSL::Token tk;
+    QList<GLSL::Token> tokens;
+    do {
+        lexer.yylex(&tk);
+        tokens.append(tk);
+    } while (tk.isNot(GLSL::Parser::EOF_SYMBOL));
+    for (int i = 0; i < tokens.count(); ++i) {
+        const GLSL::Token &tk = tokens.at(i);
+        if (tk.is(GLSL::Parser::T_LEFT_PAREN))
+            ++parcount;
+        else if (tk.is(GLSL::Parser::T_RIGHT_PAREN))
+            --parcount;
+        else if (! parcount && tk.is(GLSL::Parser::T_COMMA))
+            ++argnr;
+    }
+
+    if (m_currentarg != argnr) {
+        m_currentarg = argnr;
+        updateHintText();
+    }
+
+    if (parcount < 0)
+        m_popupFrame->close();
+}
+
+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;
+        }
+        if (m_items.size() > 1) {
+            QKeyEvent *ke = static_cast<QKeyEvent*>(e);
+            if (ke->key() == Qt::Key_Up) {
+                previousPage();
+                return true;
+            } else if (ke->key() == Qt::Key_Down) {
+                nextPage();
+                return true;
+            }
+            return false;
+        }
+        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()
+{
+    setText(currentFunction()->toString());
+
+    m_numberLabel->setText(tr("%1 of %2").arg(m_current + 1).arg(m_items.size()));
+
+    placeInsideScreen();
+}
+
+void FunctionArgumentWidget::placeInsideScreen()
+{
+    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
+
+    m_pager->setFixedWidth(m_pager->minimumSizeHint().width());
+
+    setWordWrap(false);
+    const int maxDesiredWidth = screen.width() - 10;
+    const QSize minHint = m_popupFrame->minimumSizeHint();
+    if (minHint.width() > maxDesiredWidth) {
+        setWordWrap(true);
+        m_popupFrame->setFixedWidth(maxDesiredWidth);
+        const int extra =
+            m_popupFrame->contentsMargins().bottom() + m_popupFrame->contentsMargins().top();
+        m_popupFrame->setFixedHeight(heightForWidth(maxDesiredWidth - m_pager->width()) + extra);
+    } else {
+        m_popupFrame->setFixedSize(minHint);
+    }
+
+    const QSize sz = m_popupFrame->size();
+    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);
+}
+
+} // Internal
+} // GLSLEditor
+
+
+
 CodeCompletion::CodeCompletion(QObject *parent)
     : ICompletionCollector(parent),
       m_editor(0),
@@ -332,7 +604,8 @@ int CodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
     QStringList members;
     QStringList specialMembers;
 
-    if (ch == QLatin1Char('.')) {
+    if (ch == QLatin1Char('.') || ch == QLatin1Char('(')) {
+        const bool memberCompletion = (ch == QLatin1Char('.'));
         QTextCursor tc(edit->document());
         tc.setPosition(pos);
 
@@ -353,32 +626,46 @@ int CodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
 #endif
 
         if (Document::Ptr doc = edit->glslDocument()) {
-            // TODO: ### find the enclosing scope in the previously parsed `doc'.
-            // let's use the global scope for now. This should be good enough
-            // to get some basic completion for the global variables.
             GLSL::Scope *currentScope = doc->scopeAt(pos);
 
             GLSL::Semantic sem;
             GLSL::Semantic::ExprResult exprTy = sem.expression(expr, currentScope, doc->engine());
             if (exprTy.type) {
-                if (const GLSL::VectorType *vecTy = exprTy.type->asVectorType()) {
-                    members = vecTy->members();
-
-                    // Sort the most relevant swizzle orderings to the top.
-                    specialMembers += QLatin1String("xy");
-                    specialMembers += QLatin1String("xyz");
-                    specialMembers += QLatin1String("xyzw");
-                    specialMembers += QLatin1String("rgb");
-                    specialMembers += QLatin1String("rgba");
-                    specialMembers += QLatin1String("st");
-                    specialMembers += QLatin1String("stp");
-                    specialMembers += QLatin1String("stpq");
-
-                } else if (const GLSL::Struct *structTy = exprTy.type->asStructType()) {
-                    members = structTy->members();
-
-                } else {
-                    // some other type
+                if (memberCompletion) {
+                    if (const GLSL::VectorType *vecTy = exprTy.type->asVectorType()) {
+                        members = vecTy->members();
+
+                        // Sort the most relevant swizzle orderings to the top.
+                        specialMembers += QLatin1String("xy");
+                        specialMembers += QLatin1String("xyz");
+                        specialMembers += QLatin1String("xyzw");
+                        specialMembers += QLatin1String("rgb");
+                        specialMembers += QLatin1String("rgba");
+                        specialMembers += QLatin1String("st");
+                        specialMembers += QLatin1String("stp");
+                        specialMembers += QLatin1String("stpq");
+
+                    } else if (const GLSL::Struct *structTy = exprTy.type->asStructType()) {
+                        members = structTy->members();
+
+                    } else {
+                        // some other type
+                    }
+                } else { // function completion
+                    QVector<GLSL::Function *> signatures;
+                    if (const GLSL::Function *funTy = exprTy.type->asFunctionType())
+                        signatures.append(const_cast<GLSL::Function *>(funTy)); // ### get rid of the const_cast
+                    else if (const GLSL::OverloadSet *overload = exprTy.type->asOverloadSetType())
+                        signatures = overload->functions();
+
+                    if (! signatures.isEmpty()) {
+                        // Recreate if necessary
+                        if (!m_functionArgumentWidget)
+                            m_functionArgumentWidget = new FunctionArgumentWidget;
+
+                        m_functionArgumentWidget->showFunctionHint(signatures, pos + 1);
+                        return false;
+                    }
                 }
             } else {
                 // undefined
@@ -503,3 +790,4 @@ void CodeCompletion::cleanup()
     m_startPosition = -1;
 }
 
+#include "glslcodecompletion.moc"
index 11b8f02..d3b110f 100644 (file)
 #define GLSLCODECOMPLETION_H
 
 #include <texteditor/icompletioncollector.h>
+#include <QtCore/QPointer>
 
 namespace GLSLEditor {
 namespace Internal {
 
+class FunctionArgumentWidget;
+
 class CodeCompletion: public TextEditor::ICompletionCollector
 {
     Q_OBJECT
@@ -100,6 +103,7 @@ private:
     TextEditor::ITextEditable *m_editor;
     int m_startPosition;
     bool m_restartCompletion;
+    QPointer<FunctionArgumentWidget> m_functionArgumentWidget;
 
     static bool glslCompletionItemLessThan(const TextEditor::CompletionItem &l, const TextEditor::CompletionItem &r);
 };