diff options
author | Roberto Raggi <roberto.raggi@nokia.com> | 2010-11-30 15:42:18 +0100 |
---|---|---|
committer | Roberto Raggi <roberto.raggi@nokia.com> | 2010-11-30 15:43:17 +0100 |
commit | ad5476ff5415846eb4928fc547aef36af2fbb71b (patch) | |
tree | cea99db3ce1e2ac4d061f49b36de67a11e64f5ed /src/plugins/glsleditor | |
parent | 20e749df0820e1d8c469a322605ffb69c2e6ad90 (diff) | |
download | qt-creator-ad5476ff5415846eb4928fc547aef36af2fbb71b.tar.gz |
Show the signatures in a tooltip when completing functions.
Diffstat (limited to 'src/plugins/glsleditor')
-rw-r--r-- | src/plugins/glsleditor/glslcodecompletion.cpp | 332 | ||||
-rw-r--r-- | src/plugins/glsleditor/glslcodecompletion.h | 4 |
2 files changed, 314 insertions, 22 deletions
diff --git a/src/plugins/glsleditor/glslcodecompletion.cpp b/src/plugins/glsleditor/glslcodecompletion.cpp index 7ab46dea58..e85c834821 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 11b8f02a45..d3b110f131 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); }; |