/********************************************************************** ** Copyright (C) 2005-2008 Trolltech ASA. All rights reserved. ** ** This file is part of TQt 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 TQt 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 TQt Commercial licenses may use this file in ** accordance with the TQt 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 TQColor getColor( const TQString &type ) { if ( type == "function" || type == "slot" || type == "package" ) return TQt::blue; else if ( type == "variable" || type == "widget" || type == "dir" ) return TQt::darkRed; else if ( type == "object" || type == "class" ) return TQt::darkBlue; else if ( type == "property" ) return TQt::darkGreen; else if ( type == "enum" ) return TQt::darkYellow; return TQt::black; } class CompletionItem : public TQListBoxItem { public: CompletionItem( TQListBox *lb, const TQString &txt, const TQString &t, const TQString &p, const TQString &pre, const TQString &p2 ) : TQListBoxItem( lb ), type( t ), postfix( p ), prefix( pre ), postfix2( p2 ), parag( 0 ), lastState( FALSE ) { setText( txt ); } ~CompletionItem() { delete parag; } void paint( TQPainter *painter ) { if ( lastState != isSelected() ) { delete parag; parag = 0; } lastState = isSelected(); if ( !parag ) setupParagraph(); parag->paint( *painter, listBox()->colorGroup() ); } int height( const TQListBox * ) const { if ( !parag ) ( (CompletionItem*)this )->setupParagraph(); return parag->rect().height(); } int width( const TQListBox * ) const { if ( !parag ) ( (CompletionItem*)this )->setupParagraph(); return parag->rect().width() - 2; } TQString text() const { return TQListBoxItem::text() + postfix; } private: void setupParagraph(); TQString type, postfix, prefix, postfix2; TQTextParagraph *parag; bool lastState; }; void CompletionItem::setupParagraph() { if ( !parag ) { TQTextFormatter *formatter; formatter = new TQTextFormatterBreakWords; formatter->setWrapEnabled( FALSE ); parag = new TQTextParagraph( 0 ); parag->setTabStops( listBox()->fontMetrics().width( "propertyXXXX" ) ); parag->pseudoDocument()->pFormatter = formatter; parag->insert( 0, " " + type + ( type.isEmpty() ? " " : "\t" ) + prefix + TQListBoxItem::text() + postfix + postfix2 ); bool selCol = isSelected() && listBox()->colorGroup().highlightedText() != listBox()->colorGroup().text(); TQColor sc = selCol ? listBox()->colorGroup().highlightedText() : getColor( type ); TQTextFormat *f1 = parag->formatCollection()->format( listBox()->font(), sc ); TQTextFormat *f3 = parag->formatCollection()->format( listBox()->font(), isSelected() ? listBox()->colorGroup().highlightedText() : listBox()->colorGroup().text() ); TQFont f( listBox()->font() ); f.setBold( TRUE ); TQTextFormat *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() + TQListBoxItem::text().length(), f2 ); if ( !postfix.isEmpty() ) parag->setFormat( type.length() + 2 + prefix.length() + TQListBoxItem::text().length(), postfix.length(), f3 ); parag->setFormat( type.length() + 2 + prefix.length() + TQListBoxItem::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 TQVBox( e->topLevelWidget(), 0, WType_Popup ); completionPopup->setFrameStyle( TQFrame::Box | TQFrame::Plain ); completionPopup->setLineWidth( 1 ); functionLabel = new ArgHintWidget( e->topLevelWidget(), "editor_function_lbl" ); functionLabel->hide(); completionListBox = new TQListBox( completionPopup, "editor_completion_lb" ); completionListBox->setFrameStyle( TQFrame::NoFrame ); completionListBox->installEventFilter( this ); completionListBox->setHScrollBarMode( TQScrollView::AlwaysOn ); completionListBox->setVScrollBarMode( TQScrollView::AlwaysOn ); completionListBox->setCornerWidget( new TQSizeGrip( 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 TQString &s, TQTextDocument *, bool strict ) { TQChar key( s[ 0 ] ); TQMap::Iterator it = completionMap.find( key ); if ( it == completionMap.end() ) { completionMap.insert( key, TQStringList( s ) ); } else { if ( strict ) { TQStringList::Iterator sit; for ( sit = (*it).begin(); sit != (*it).end(); ) { TQStringList::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 ); } } TQValueList EditorCompletion::completionList( const TQString &s, TQTextDocument *doc ) const { if ( doc ) ( (EditorCompletion*)this )->updateCompletionMap( doc ); TQChar key( s[ 0 ] ); TQMap::ConstIterator it = completionMap.find( key ); if ( it == completionMap.end() ) return TQValueList(); TQStringList::ConstIterator it2 = (*it).begin(); TQValueList 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( TQTextDocument *doc ) { bool strict = TRUE; if ( doc != lastDoc ) strict = FALSE; lastDoc = doc; TQTextParagraph *s = doc->firstParagraph(); if ( !s->extraData() ) s->setExtraData( new ParagData ); while ( s ) { if ( s->length() == ( (ParagData*)s->extraData() )->lastLengthForCompletion ) { s = s->next(); continue; } TQChar c; TQString 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 = TQString::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; TQTextCursor *cursor = curEditor->textCursor(); TQTextDocument *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; TQChar c = cursor->paragraph()->at( idx - 1 )->c; if ( !c.isLetter() && !c.isNumber() && c != '_' && c != '#' ) return FALSE; TQString s; idx--; completionOffset = 1; for (;;) { s.prepend( TQString( 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; TQValueList lst( completionList( s, doc ) ); if ( lst.count() > 1 ) { TQTextStringChar *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 ( TQValueList::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() + TQSize( completionListBox->verticalScrollBar()->width() + 4, completionListBox->horizontalScrollBar()->height() + 4 ) ); completionListBox->setCurrentItem( 0 ); completionListBox->setFocus(); if ( curEditor->mapToGlobal( TQPoint( 0, y ) ).y() + h + completionPopup->height() < TQApplication::desktop()->height() ) completionPopup->move( curEditor->mapToGlobal( curEditor-> contentsToViewport( TQPoint( x, y + h ) ) ) ); else completionPopup->move( curEditor->mapToGlobal( curEditor-> contentsToViewport( TQPoint( x, y - completionPopup->height() ) ) ) ); completionPopup->show(); } else if ( lst.count() == 1 ) { curEditor->insert( lst.first().text.mid( completionOffset, 0xFFFFFF ), (uint) ( TQTextEdit::RedoIndentation | TQTextEdit::CheckNewLines | TQTextEdit::RemoveSelected ) ); } else { return FALSE; } return TRUE; } bool EditorCompletion::eventFilter( TQObject *o, TQEvent *e ) { if ( !enabled ) return FALSE; if ( e->type() == TQEvent::KeyPress && ::tqt_cast(o)) { curEditor = (Editor*)o; TQKeyEvent *ke = (TQKeyEvent*)e; if ( ke->key() == Key_Tab ) { TQString s = curEditor->textCursor()->paragraph()->string()->toString(). left( curEditor->textCursor()->index() ); if ( curEditor->document()->hasSelection( TQTextDocument::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() == TQEvent::KeyPress ) { TQKeyEvent *ke = (TQKeyEvent*)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(); } TQApplication::sendEvent( curEditor, e ); return TRUE; } } else if ( e->type() == TQEvent::MouseButtonDblClick ) { completeCompletion(); return TRUE; } } if ( o == functionLabel || ( ::tqt_cast(o) && functionLabel->isVisible() ) ) { if ( e->type() == TQEvent::KeyPress ) { TQKeyEvent *ke = (TQKeyEvent*)e; if ( ke->key() == Key_Escape ) { functionLabel->hide(); } else { if ( !doArgumentHint( ke->text() == "(" ) ) functionLabel->hide(); if ( o == functionLabel ) { TQApplication::sendEvent( curEditor, e ); return TRUE; } } } } return FALSE; } void EditorCompletion::completeCompletion() { int idx = curEditor->textCursor()->index(); TQString s = completionListBox->currentText().mid( searchString.length() ); curEditor->insert( s, (uint) ( TQTextEdit::RedoIndentation | TQTextEdit::CheckNewLines | TQTextEdit::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 = ""; TQString object; int i = curEditor->textCursor()->index(); i--; TQTextParagraph *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 TQString & ) { return FALSE; } static void strip( TQString &txt ) { int i = txt.find( "(" ); if ( i == -1 ) return; txt = txt.left( i ); } bool EditorCompletion::continueComplete() { if ( searchString.isEmpty() ) { completionListBox->clear(); for ( TQValueList::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; } TQListBoxItem *i = completionListBox->findItem( searchString ); if ( !i ) return FALSE; TQString txt1 = i->text(); TQString txt2 = searchString; strip( txt1 ); strip( txt2 ); if ( txt1 == txt2 && !i->next() ) return FALSE; TQValueList res; for ( TQValueList::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 ( TQValueList::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 ) { TQTextCursor *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 = TQMAX( j, 0 ); TQString function( cursor->paragraph()->string()->toString().mid( j, i - j + 1 ) ); TQString 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; } } TQChar sep; TQString pre, post; TQValueList argl = functionParameters( function, sep, pre, post ); if ( argl.isEmpty() ) return FALSE; TQString label; int w = 0; int num = 0; if ( !functionLabel->isVisible() ) functionLabel->setNumFunctions( (int)argl.count() ); for ( TQValueList::Iterator vit = argl.begin(); vit != argl.end(); ++vit, ++num ) { TQStringList 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--; } TQString func = function; int pnt = -1; pnt = func.findRev( '.' ); if ( pnt == -1 ) func.findRev( '>' ); if ( pnt != -1 ) func = func.mid( pnt + 1 ); TQString s = func + "( "; if ( s[ 0 ] == '\"' ) s.remove( (uint)0, 1 ); i = 0; for ( TQStringList::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 = TQMAX( w, functionLabel->fontMetrics().width( s ) + 10 ); } w += 16; if ( label.isEmpty() ) return FALSE; if ( functionLabel->isVisible() ) { functionLabel->resize( w + 50, TQMAX( functionLabel->fontMetrics().height(), 16 ) ); } else { TQTextStringChar *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, TQMAX( functionLabel->fontMetrics().height(), 16 ) ); functionLabel->move( curEditor->mapToGlobal( curEditor->contentsToViewport( TQPoint( x, y + h ) ) ) ); if ( functionLabel->x() + functionLabel->width() > TQApplication::desktop()->width() ) functionLabel->move( TQMAX( 0, TQApplication::desktop()->width() - functionLabel->width() ), functionLabel->y() ); functionLabel->show(); curEditor->setFocus(); } TQTimer::singleShot( 0, functionLabel, TQ_SLOT( relayout() ) ); return TRUE; } TQValueList EditorCompletion::functionParameters( const TQString &, TQChar &, TQString &, TQString & ) { return TQValueList(); } void EditorCompletion::setContext( TQObject * ) { } void EditorCompletion::showCompletion( const TQValueList &lst ) { TQTextCursor *cursor = curEditor->textCursor(); TQTextStringChar *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 ( TQValueList::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() + TQSize( completionListBox->verticalScrollBar()->width() + 4, completionListBox->horizontalScrollBar()->height() + 4 ) ); completionListBox->setCurrentItem( 0 ); completionListBox->setFocus(); if ( curEditor->mapToGlobal( TQPoint( 0, y ) ).y() + h + completionPopup->height() < TQApplication::desktop()->height() ) completionPopup->move( curEditor->mapToGlobal( curEditor-> contentsToViewport( TQPoint( x, y + h ) ) ) ); else completionPopup->move( curEditor->mapToGlobal( curEditor-> contentsToViewport( TQPoint( x, y - completionPopup->height() ) ) ) ); completionPopup->show(); }