/********************************************************************** ** Copyright (C) 2005-2008 Trolltech ASA. All rights reserved. ** ** This file is part of Qt Designer. ** ** This file may be used under the terms of the GNU General ** Public License versions 2.0 or 3.0 as published by the Free ** Software Foundation and appearing in the files LICENSE.GPL2 ** and LICENSE.GPL3 included in the packaging of this file. ** Alternatively you may (at your option) use any later version ** of the GNU General Public License if such license has been ** publicly approved by Trolltech ASA (or its successors, if any) ** and the KDE Free Qt Foundation. ** ** Please review the following information to ensure GNU General ** Public Licensing requirements will be met: ** http://trolltech.com/products/qt/licenses/licensing/opensource/. ** If you are unsure which license is appropriate for your use, please ** review the following information: ** http://trolltech.com/products/qt/licenses/licensing/licensingoverview ** or contact the sales department at sales@trolltech.com. ** ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with ** the Software. ** ** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, ** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted ** herein. ** **********************************************************************/ #include "completion.h" #include "paragdata.h" #include "editor.h" #include #include #include #include #include #include #include "arghintwidget.h" #include #include static QColor getColor( const QString &type ) { if ( type == "function" || type == "slot" || type == "package" ) return Qt::blue; else if ( type == "variable" || type == "widget" || type == "dir" ) return Qt::darkRed; else if ( type == "object" || type == "class" ) return Qt::darkBlue; else if ( type == "property" ) return Qt::darkGreen; else if ( type == "enum" ) return Qt::darkYellow; return Qt::black; } class CompletionItem : public QListBoxItem { public: CompletionItem( QListBox *lb, const QString &txt, const QString &t, const QString &p, const QString &pre, const QString &p2 ) : QListBoxItem( lb ), type( t ), postfix( p ), prefix( pre ), postfix2( p2 ), parag( 0 ), lastState( FALSE ) { setText( txt ); } ~CompletionItem() { delete parag; } void paint( QPainter *painter ) { if ( lastState != isSelected() ) { delete parag; parag = 0; } lastState = isSelected(); if ( !parag ) setupParagraph(); parag->paint( *painter, listBox()->colorGroup() ); } int height( const QListBox * ) const { if ( !parag ) ( (CompletionItem*)this )->setupParagraph(); return parag->rect().height(); } int width( const QListBox * ) const { if ( !parag ) ( (CompletionItem*)this )->setupParagraph(); return parag->rect().width() - 2; } QString text() const { return QListBoxItem::text() + postfix; } private: void setupParagraph(); QString type, postfix, prefix, postfix2; QTextParagraph *parag; bool lastState; }; void CompletionItem::setupParagraph() { if ( !parag ) { QTextFormatter *formatter; formatter = new QTextFormatterBreakWords; formatter->setWrapEnabled( FALSE ); parag = new QTextParagraph( 0 ); parag->setTabStops( listBox()->fontMetrics().width( "propertyXXXX" ) ); parag->pseudoDocument()->pFormatter = formatter; parag->insert( 0, " " + type + ( type.isEmpty() ? " " : "\t" ) + prefix + QListBoxItem::text() + postfix + postfix2 ); bool selCol = isSelected() && listBox()->colorGroup().highlightedText() != listBox()->colorGroup().text(); QColor sc = selCol ? listBox()->colorGroup().highlightedText() : getColor( type ); QTextFormat *f1 = parag->formatCollection()->format( listBox()->font(), sc ); QTextFormat *f3 = parag->formatCollection()->format( listBox()->font(), isSelected() ? listBox()->colorGroup().highlightedText() : listBox()->colorGroup().text() ); QFont f( listBox()->font() ); f.setBold( TRUE ); QTextFormat *f2 = parag->formatCollection()->format( f, isSelected() ? listBox()->colorGroup().highlightedText() : listBox()->colorGroup().text() ); parag->setFormat( 1, type.length() + 1, f1 ); parag->setFormat( type.length() + 2, prefix.length() + QListBoxItem::text().length(), f2 ); if ( !postfix.isEmpty() ) parag->setFormat( type.length() + 2 + prefix.length() + QListBoxItem::text().length(), postfix.length(), f3 ); parag->setFormat( type.length() + 2 + prefix.length() + QListBoxItem::text().length() + postfix.length(), postfix2.length(), f3 ); f1->removeRef(); f2->removeRef(); f3->removeRef(); parag->format(); } } EditorCompletion::EditorCompletion( Editor *e ) { enabled = TRUE; lastDoc = 0; completionPopup = new QVBox( e->topLevelWidget(), 0, WType_Popup ); completionPopup->setFrameStyle( QFrame::Box | QFrame::Plain ); completionPopup->setLineWidth( 1 ); functionLabel = new ArgHintWidget( e->topLevelWidget(), "editor_function_lbl" ); functionLabel->hide(); completionListBox = new QListBox( completionPopup, "editor_completion_lb" ); completionListBox->setFrameStyle( QFrame::NoFrame ); completionListBox->installEventFilter( this ); completionListBox->setHScrollBarMode( QScrollView::AlwaysOn ); completionListBox->setVScrollBarMode( QScrollView::AlwaysOn ); completionListBox->setCornerWidget( new QSizeGrip( completionListBox, "editor_cornerwidget" ) ); completionPopup->installEventFilter( this ); functionLabel->installEventFilter( this ); completionPopup->setFocusProxy( completionListBox ); completionOffset = 0; curEditor = e; curEditor->installEventFilter( this ); } EditorCompletion::~EditorCompletion() { delete completionPopup; delete functionLabel; } void EditorCompletion::addCompletionEntry( const QString &s, QTextDocument *, bool strict ) { QChar key( s[ 0 ] ); QMap::Iterator it = completionMap.find( key ); if ( it == completionMap.end() ) { completionMap.insert( key, QStringList( s ) ); } else { if ( strict ) { QStringList::Iterator sit; for ( sit = (*it).begin(); sit != (*it).end(); ) { QStringList::Iterator it2 = sit; ++sit; if ( (*it2).length() > s.length() && (*it2).left( s.length() ) == s ) { if ( (*it2)[ (int)s.length() ].isLetter() && (*it2)[ (int)s.length() ].upper() != (*it2)[ (int)s.length() ] ) return; } else if ( s.length() > (*it2).length() && s.left( (*it2).length() ) == *it2 ) { if ( s[ (int)(*it2).length() ].isLetter() && s[ (int)(*it2).length() ].upper() != s[ (int)(*it2).length() ] ) (*it).remove( it2 ); } } } (*it).append( s ); } } QValueList EditorCompletion::completionList( const QString &s, QTextDocument *doc ) const { if ( doc ) ( (EditorCompletion*)this )->updateCompletionMap( doc ); QChar key( s[ 0 ] ); QMap::ConstIterator it = completionMap.find( key ); if ( it == completionMap.end() ) return QValueList(); QStringList::ConstIterator it2 = (*it).begin(); QValueList lst; int len = s.length(); for ( ; it2 != (*it).end(); ++it2 ) { CompletionEntry c; c.type = ""; c.text = *it2; c.postfix = ""; c.prefix = ""; c.postfix2 = ""; if ( (int)(*it2).length() > len && (*it2).left( len ) == s && lst.find( c ) == lst.end() ) lst << c; } return lst; } void EditorCompletion::updateCompletionMap( QTextDocument *doc ) { bool strict = TRUE; if ( doc != lastDoc ) strict = FALSE; lastDoc = doc; QTextParagraph *s = doc->firstParagraph(); if ( !s->extraData() ) s->setExtraData( new ParagData ); while ( s ) { if ( s->length() == ( (ParagData*)s->extraData() )->lastLengthForCompletion ) { s = s->next(); continue; } QChar c; QString buffer; for ( int i = 0; i < s->length(); ++i ) { c = s->at( i )->c; if ( c.isLetter() || c.isNumber() || c == '_' || c == '#' ) { buffer += c; } else { addCompletionEntry( buffer, doc, strict ); buffer = QString::null; } } if ( !buffer.isEmpty() ) addCompletionEntry( buffer, doc, strict ); ( (ParagData*)s->extraData() )->lastLengthForCompletion = s->length(); s = s->next(); } } bool EditorCompletion::doCompletion() { searchString = ""; if ( !curEditor ) return FALSE; QTextCursor *cursor = curEditor->textCursor(); QTextDocument *doc = curEditor->document(); if ( cursor->index() > 0 && cursor->paragraph()->at( cursor->index() - 1 )->c == '.' && ( cursor->index() == 1 || cursor->paragraph()->at( cursor->index() - 2 )->c != '.' ) ) return doObjectCompletion(); int idx = cursor->index(); if ( idx == 0 ) return FALSE; QChar c = cursor->paragraph()->at( idx - 1 )->c; if ( !c.isLetter() && !c.isNumber() && c != '_' && c != '#' ) return FALSE; QString s; idx--; completionOffset = 1; for (;;) { s.prepend( QString( cursor->paragraph()->at( idx )->c ) ); idx--; if ( idx < 0 ) break; if ( !cursor->paragraph()->at( idx )->c.isLetter() && !cursor->paragraph()->at( idx )->c.isNumber() && cursor->paragraph()->at( idx )->c != '_' && cursor->paragraph()->at( idx )->c != '#' ) break; completionOffset++; } searchString = s; QValueList lst( completionList( s, doc ) ); if ( lst.count() > 1 ) { QTextStringChar *chr = cursor->paragraph()->at( cursor->index() ); int h = cursor->paragraph()->lineHeightOfChar( cursor->index() ); int x = cursor->paragraph()->rect().x() + chr->x; int y, dummy; cursor->paragraph()->lineHeightOfChar( cursor->index(), &dummy, &y ); y += cursor->paragraph()->rect().y(); completionListBox->clear(); for ( QValueList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) (void)new CompletionItem( completionListBox, (*it).text, (*it).type, (*it).postfix, (*it).prefix, (*it).postfix2 ); cList = lst; completionPopup->resize( completionListBox->sizeHint() + QSize( completionListBox->verticalScrollBar()->width() + 4, completionListBox->horizontalScrollBar()->height() + 4 ) ); completionListBox->setCurrentItem( 0 ); completionListBox->setFocus(); if ( curEditor->mapToGlobal( QPoint( 0, y ) ).y() + h + completionPopup->height() < QApplication::desktop()->height() ) completionPopup->move( curEditor->mapToGlobal( curEditor-> contentsToViewport( QPoint( x, y + h ) ) ) ); else completionPopup->move( curEditor->mapToGlobal( curEditor-> contentsToViewport( QPoint( x, y - completionPopup->height() ) ) ) ); completionPopup->show(); } else if ( lst.count() == 1 ) { curEditor->insert( lst.first().text.mid( completionOffset, 0xFFFFFF ), (uint) ( QTextEdit::RedoIndentation | QTextEdit::CheckNewLines | QTextEdit::RemoveSelected ) ); } else { return FALSE; } return TRUE; } bool EditorCompletion::eventFilter( QObject *o, QEvent *e ) { if ( !enabled ) return FALSE; if ( e->type() == QEvent::KeyPress && ::qt_cast(o)) { curEditor = (Editor*)o; QKeyEvent *ke = (QKeyEvent*)e; if ( ke->key() == Key_Tab ) { QString s = curEditor->textCursor()->paragraph()->string()->toString(). left( curEditor->textCursor()->index() ); if ( curEditor->document()->hasSelection( QTextDocument::Standard ) || s.simplifyWhiteSpace().isEmpty() ) { if ( curEditor->document()->indent() ) { curEditor->indent(); int i = 0; for ( ; i < curEditor->textCursor()->paragraph()->length() - 1; ++i ) { if ( curEditor->textCursor()->paragraph()->at( i )->c != ' ' && curEditor->textCursor()->paragraph()->at( i )->c != '\t' ) break; } curEditor->drawCursor( FALSE ); curEditor->textCursor()->setIndex( i ); curEditor->drawCursor( TRUE ); } else { curEditor->insert( "\t" ); } return TRUE; } } if ( functionLabel->isVisible() ) { if ( ke->key() == Key_Up && ( ke->state() & ControlButton ) == ControlButton ) { functionLabel->gotoPrev(); return TRUE; } else if ( ke->key() == Key_Down && ( ke->state() & ControlButton ) == ControlButton ) { functionLabel->gotoNext(); return TRUE; } } if ( ke->text().length() && !( ke->state() & AltButton ) && ( !ke->ascii() || ke->ascii() >= 32 ) || ( ke->text() == "\t" && !( ke->state() & ControlButton ) ) ) { if ( ke->key() == Key_Tab ) { if ( curEditor->textCursor()->index() == 0 && curEditor->textCursor()->paragraph()->isListItem() ) return FALSE; if ( doCompletion() ) return TRUE; } else if ( ke->key() == Key_Period && ( curEditor->textCursor()->index() == 0 || curEditor->textCursor()->paragraph()->at( curEditor->textCursor()->index() - 1 )->c != '.' ) || ke->key() == Key_Greater && curEditor->textCursor()->index() > 0 && curEditor->textCursor()->paragraph()->at( curEditor->textCursor()->index() - 1 )->c == '-' ) { doObjectCompletion(); } else { if ( !doArgumentHint( ke->text() == "(" ) ) functionLabel->hide(); } } } else if ( o == completionPopup || o == completionListBox || o == completionListBox->viewport() ) { if ( e->type() == QEvent::KeyPress ) { QKeyEvent *ke = (QKeyEvent*)e; if ( ke->key() == Key_Enter || ke->key() == Key_Return || ke->key() == Key_Tab ) { if ( ke->key() == Key_Tab && completionListBox->count() > 1 && completionListBox->currentItem() < (int)completionListBox->count() - 1 ) { completionListBox->setCurrentItem( completionListBox->currentItem() + 1 ); return TRUE; } completeCompletion(); return TRUE; } else if ( ke->key() == Key_Left || ke->key() == Key_Right || ke->key() == Key_Up || ke->key() == Key_Down || ke->key() == Key_Home || ke->key() == Key_End || ke->key() == Key_Prior || ke->key() == Key_Next ) { return FALSE; } else if ( ke->key() != Key_Shift && ke->key() != Key_Control && ke->key() != Key_Alt ) { int l = searchString.length(); if ( ke->key() == Key_Backspace ) { searchString.remove( searchString.length() - 1, 1 ); } else { searchString += ke->text(); l = 1; } if ( !l || !continueComplete() ) { completionPopup->close(); curEditor->setFocus(); } QApplication::sendEvent( curEditor, e ); return TRUE; } } else if ( e->type() == QEvent::MouseButtonDblClick ) { completeCompletion(); return TRUE; } } if ( o == functionLabel || ::qt_cast(o) && functionLabel->isVisible() ) { if ( e->type() == QEvent::KeyPress ) { QKeyEvent *ke = (QKeyEvent*)e; if ( ke->key() == Key_Escape ) { functionLabel->hide(); } else { if ( !doArgumentHint( ke->text() == "(" ) ) functionLabel->hide(); if ( o == functionLabel ) { QApplication::sendEvent( curEditor, e ); return TRUE; } } } } return FALSE; } void EditorCompletion::completeCompletion() { int idx = curEditor->textCursor()->index(); QString s = completionListBox->currentText().mid( searchString.length() ); curEditor->insert( s, (uint) ( QTextEdit::RedoIndentation | QTextEdit::CheckNewLines | QTextEdit::RemoveSelected ) ); int i = s.find( '(' ); completionPopup->close(); curEditor->setFocus(); if ( i != -1 && i < (int)s.length() ) { curEditor->setCursorPosition( curEditor->textCursor()->paragraph()->paragId(), idx + i + 1 ); doArgumentHint( FALSE ); } } void EditorCompletion::setCurrentEdior( Editor *e ) { curEditor = e; curEditor->installEventFilter( this ); } void EditorCompletion::addEditor( Editor *e ) { e->installEventFilter( this ); } bool EditorCompletion::doObjectCompletion() { searchString = ""; QString object; int i = curEditor->textCursor()->index(); i--; QTextParagraph *p = curEditor->textCursor()->paragraph(); for (;;) { if ( i < 0 ) break; if ( p->at( i )->c == ' ' || p->at( i )->c == '\t' ) break; object.prepend( p->at( i )->c ); i--; } if ( object[ (int)object.length() - 1 ] == '-' ) object.remove( object.length() - 1, 1 ); if ( object.isEmpty() ) return FALSE; return doObjectCompletion( object ); } bool EditorCompletion::doObjectCompletion( const QString & ) { return FALSE; } static void strip( QString &txt ) { int i = txt.find( "(" ); if ( i == -1 ) return; txt = txt.left( i ); } bool EditorCompletion::continueComplete() { if ( searchString.isEmpty() ) { completionListBox->clear(); for ( QValueList::ConstIterator it = cList.begin(); it != cList.end(); ++it ) (void)new CompletionItem( completionListBox, (*it).text, (*it).type, (*it).postfix, (*it).prefix, (*it).postfix2 ); completionListBox->setCurrentItem( 0 ); completionListBox->setSelected( completionListBox->currentItem(), TRUE ); return TRUE; } QListBoxItem *i = completionListBox->findItem( searchString ); if ( !i ) return FALSE; QString txt1 = i->text(); QString txt2 = searchString; strip( txt1 ); strip( txt2 ); if ( txt1 == txt2 && !i->next() ) return FALSE; QValueList res; for ( QValueList::ConstIterator it = cList.begin(); it != cList.end(); ++it ) { if ( (*it).text.left( searchString.length() ) == searchString ) res << *it; } if ( res.isEmpty() ) return FALSE; completionListBox->clear(); for ( QValueList::ConstIterator it2 = res.begin(); it2 != res.end(); ++it2 ) (void)new CompletionItem( completionListBox, (*it2).text, (*it2).type, (*it2).postfix, (*it2).prefix, (*it2).postfix2 ); completionListBox->setCurrentItem( 0 ); completionListBox->setSelected( completionListBox->currentItem(), TRUE ); return TRUE; } bool EditorCompletion::doArgumentHint( bool useIndex ) { QTextCursor *cursor = curEditor->textCursor(); int i = cursor->index() ; if ( !useIndex ) { bool foundParen = FALSE; int closeParens = 0; while ( i >= 0 ) { if ( cursor->paragraph()->at( i )->c == ')' && i != cursor->index() ) closeParens++; if ( cursor->paragraph()->at( i )->c == '(' ) { closeParens--; if ( closeParens == -1 ) { foundParen = TRUE; break; } } --i; } if ( !foundParen ) return FALSE; } int j = i - 1; bool foundSpace = FALSE; bool foundNonSpace = FALSE; while ( j >= 0 ) { if ( foundNonSpace && ( cursor->paragraph()->at( j )->c == ' ' || cursor->paragraph()->at( j )->c == ',' ) ) { foundSpace = TRUE; break; } if ( !foundNonSpace && ( cursor->paragraph()->at( j )->c != ' ' || cursor->paragraph()->at( j )->c != ',' ) ) foundNonSpace = TRUE; --j; } if ( foundSpace ) ++j; j = QMAX( j, 0 ); QString function( cursor->paragraph()->string()->toString().mid( j, i - j + 1 ) ); QString part = cursor->paragraph()->string()->toString().mid( j, cursor->index() - j + 1 ); function = function.simplifyWhiteSpace(); for (;;) { if ( function[ (int)function.length() - 1 ] == '(' ) { function.remove( function.length() - 1, 1 ); function = function.simplifyWhiteSpace(); } else if ( function[ (int)function.length() - 1 ] == ')' ) { function.remove( function.length() - 1, 1 ); function = function.simplifyWhiteSpace(); } else { break; } } QChar sep; QString pre, post; QValueList argl = functionParameters( function, sep, pre, post ); if ( argl.isEmpty() ) return FALSE; QString label; int w = 0; int num = 0; if ( !functionLabel->isVisible() ) functionLabel->setNumFunctions( (int)argl.count() ); for ( QValueList::Iterator vit = argl.begin(); vit != argl.end(); ++vit, ++num ) { QStringList args = *vit; int argNum = 0; int inParen = 0; for ( int k = 0; k < (int)part.length(); ++k ) { if ( part[ k ] == sep && inParen < 2 ) argNum++; if ( part[ k ] == '(' ) inParen++; if ( part[ k ] == ')' ) inParen--; } QString func = function; int pnt = -1; pnt = func.findRev( '.' ); if ( pnt == -1 ) func.findRev( '>' ); if ( pnt != -1 ) func = func.mid( pnt + 1 ); QString s = func + "( "; if ( s[ 0 ] == '\"' ) s.remove( (uint)0, 1 ); i = 0; for ( QStringList::Iterator it = args.begin(); it != args.end(); ++it, ++i ) { if ( i == argNum ) s += "" + *it + ""; else s += *it; if ( i < (int)args.count() - 1 ) s += ", "; else s += " "; } s += ")"; s.prepend( pre ); s.append( post ); label += "

" + s + "

"; functionLabel->setFunctionText( num, s ); w = QMAX( w, functionLabel->fontMetrics().width( s ) + 10 ); } w += 16; if ( label.isEmpty() ) return FALSE; if ( functionLabel->isVisible() ) { functionLabel->resize( w + 50, QMAX( functionLabel->fontMetrics().height(), 16 ) ); } else { QTextStringChar *chr = cursor->paragraph()->at( cursor->index() ); int h = cursor->paragraph()->lineHeightOfChar( cursor->index() ); int x = cursor->paragraph()->rect().x() + chr->x; int y, dummy; cursor->paragraph()->lineHeightOfChar( cursor->index(), &dummy, &y ); y += cursor->paragraph()->rect().y(); functionLabel->resize( w + 50, QMAX( functionLabel->fontMetrics().height(), 16 ) ); functionLabel->move( curEditor->mapToGlobal( curEditor->contentsToViewport( QPoint( x, y + h ) ) ) ); if ( functionLabel->x() + functionLabel->width() > QApplication::desktop()->width() ) functionLabel->move( QMAX( 0, QApplication::desktop()->width() - functionLabel->width() ), functionLabel->y() ); functionLabel->show(); curEditor->setFocus(); } QTimer::singleShot( 0, functionLabel, SLOT( relayout() ) ); return TRUE; } QValueList EditorCompletion::functionParameters( const QString &, QChar &, QString &, QString & ) { return QValueList(); } void EditorCompletion::setContext( QObject * ) { } void EditorCompletion::showCompletion( const QValueList &lst ) { QTextCursor *cursor = curEditor->textCursor(); QTextStringChar *chr = cursor->paragraph()->at( cursor->index() ); int h = cursor->paragraph()->lineHeightOfChar( cursor->index() ); int x = cursor->paragraph()->rect().x() + chr->x; int y, dummy; cursor->paragraph()->lineHeightOfChar( cursor->index(), &dummy, &y ); y += cursor->paragraph()->rect().y(); completionListBox->clear(); for ( QValueList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) (void)new CompletionItem( completionListBox, (*it).text, (*it).type, (*it).postfix, (*it).prefix, (*it).postfix2 ); cList = lst; completionPopup->resize( completionListBox->sizeHint() + QSize( completionListBox->verticalScrollBar()->width() + 4, completionListBox->horizontalScrollBar()->height() + 4 ) ); completionListBox->setCurrentItem( 0 ); completionListBox->setFocus(); if ( curEditor->mapToGlobal( QPoint( 0, y ) ).y() + h + completionPopup->height() < QApplication::desktop()->height() ) completionPopup->move( curEditor->mapToGlobal( curEditor-> contentsToViewport( QPoint( x, y + h ) ) ) ); else completionPopup->move( curEditor->mapToGlobal( curEditor-> contentsToViewport( QPoint( x, y - completionPopup->height() ) ) ) ); completionPopup->show(); }