diff --git a/src/libs/glsl/glsltype.h b/src/libs/glsl/glsltype.h index 0d3f3c39e9a402d259464258610120a3a95ce9f5..18ea9416d46869e2209d0de33573f58b8f4721dd 100644 --- a/src/libs/glsl/glsltype.h +++ b/src/libs/glsl/glsltype.h @@ -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; } diff --git a/src/libs/glsl/glsltypes.cpp b/src/libs/glsl/glsltypes.cpp index 5a74796b91e8309abe69e5bd94fa94f501c149a1..d374e357899ff93876869479cb227d09022e0232 100644 --- a/src/libs/glsl/glsltypes.cpp +++ b/src/libs/glsl/glsltypes.cpp @@ -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) { diff --git a/src/libs/glsl/glsltypes.h b/src/libs/glsl/glsltypes.h index d4d60bc31abd8d86f10e2f692a404e54791b2ea7..04b4490548a9d8a00aae60eea4c21bf5e9cade9e 100644 --- a/src/libs/glsl/glsltypes.h +++ b/src/libs/glsl/glsltypes.h @@ -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; diff --git a/src/plugins/glsleditor/glslcodecompletion.cpp b/src/plugins/glsleditor/glslcodecompletion.cpp index 7ab46dea583476d4042c0871c7b398742ccc9505..e85c834821512a49f84ca78010c4cc8ac30e577b 100644 --- a/src/plugins/glsleditor/glslcodecompletion.cpp +++ b/src/plugins/glsleditor/glslcodecompletion.cpp @@ -31,13 +31,21 @@ #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" diff --git a/src/plugins/glsleditor/glslcodecompletion.h b/src/plugins/glsleditor/glslcodecompletion.h index 11b8f02a45ae1fe18a38fbf3a44c2326811aaa08..d3b110f13111b014bf72bfd30c70b26f48d58c0d 100644 --- a/src/plugins/glsleditor/glslcodecompletion.h +++ b/src/plugins/glsleditor/glslcodecompletion.h @@ -30,10 +30,13 @@ #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); };