katecodecompletion.cpp
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> 00003 Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> 00004 Copyright (C) 2001 by Victor Röder <Victor_Roeder@GMX.de> 00005 Copyright (C) 2002 by Roberto Raggi <roberto@kdevelop.org> 00006 00007 This library is free software; you can redistribute it and/or 00008 modify it under the terms of the GNU Library General Public 00009 License version 2 as published by the Free Software Foundation. 00010 00011 This library is distributed in the hope that it will be useful, 00012 but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 Library General Public License for more details. 00015 00016 You should have received a copy of the GNU Library General Public License 00017 along with this library; see the file COPYING.LIB. If not, write to 00018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00019 Boston, MA 02110-1301, USA. 00020 */ 00021 00022 /******** Partly based on the ArgHintWidget of Qt3 by Trolltech AS *********/ 00023 /* Trolltech doesn't mind, if we license that piece of code as LGPL, because there isn't much 00024 * left from the desigener code */ 00025 00026 #include "katecodecompletion.h" 00027 #include "katecodecompletion.moc" 00028 00029 #include "katedocument.h" 00030 #include "kateview.h" 00031 #include "katerenderer.h" 00032 #include "kateconfig.h" 00033 #include "katefont.h" 00034 00035 #include <kdebug.h> 00036 00037 #include <tqwhatsthis.h> 00038 #include <tqvbox.h> 00039 #include <tqlistbox.h> 00040 #include <tqtimer.h> 00041 #include <tqtooltip.h> 00042 #include <tqapplication.h> 00043 #include <tqsizegrip.h> 00044 #include <tqfontmetrics.h> 00045 #include <tqlayout.h> 00046 #include <tqregexp.h> 00047 00054 class KateCCListBox : public TQListBox 00055 { 00056 public: 00060 KateCCListBox (TQWidget* parent = 0, const char* name = 0, WFlags f = 0):TQListBox(parent, name, f) 00061 { 00062 } 00063 00064 TQSize sizeHint() const 00065 { 00066 int count = this->count(); 00067 int height = 20; 00068 int tmpwidth = 8; 00069 //FIXME the height is for some reasons at least 3 items heigh, even if there is only one item in the list 00070 if (count > 0) 00071 if(count < 11) 00072 height = count * itemHeight(0); 00073 else { 00074 height = 10 * itemHeight(0); 00075 tmpwidth += verticalScrollBar()->width(); 00076 } 00077 00078 int maxcount = 0, tmpcount = 0; 00079 for (int i = 0; i < count; ++i) 00080 if ( (tmpcount = fontMetrics().width(text(i)) ) > maxcount) 00081 maxcount = tmpcount; 00082 00083 if (maxcount > TQApplication::desktop()->width()){ 00084 tmpwidth = TQApplication::desktop()->width() - 5; 00085 height += horizontalScrollBar()->height(); 00086 } else 00087 tmpwidth += maxcount; 00088 return TQSize(tmpwidth,height); 00089 00090 } 00091 }; 00092 00093 class KateCompletionItem : public TQListBoxText 00094 { 00095 public: 00096 KateCompletionItem( TQListBox* lb, KTextEditor::CompletionEntry entry ) 00097 : TQListBoxText( lb ) 00098 , m_entry( entry ) 00099 { 00100 if( entry.postfix == "()" ) { // should be configurable 00101 setText( entry.prefix + " " + entry.text + entry.postfix ); 00102 } else { 00103 setText( entry.prefix + " " + entry.text + " " + entry.postfix); 00104 } 00105 } 00106 00107 KTextEditor::CompletionEntry m_entry; 00108 }; 00109 00110 00111 KateCodeCompletion::KateCodeCompletion( KateView* view ) 00112 : TQObject( view, "Kate Code Completion" ) 00113 , m_view( view ) 00114 , m_commentLabel( 0 ) 00115 { 00116 m_completionPopup = new TQVBox( 0, 0, (WFlags)WType_Popup ); 00117 m_completionPopup->setFrameStyle( TQFrame::Box | TQFrame::Plain ); 00118 m_completionPopup->setLineWidth( 1 ); 00119 00120 m_completionListBox = new KateCCListBox( m_completionPopup ); 00121 m_completionListBox->setFrameStyle( TQFrame::NoFrame ); 00122 //m_completionListBox->setCornerWidget( new TQSizeGrip( m_completionListBox) ); 00123 m_completionListBox->setFocusProxy( m_view->m_viewInternal ); 00124 00125 m_completionListBox->installEventFilter( this ); 00126 00127 m_completionPopup->resize(m_completionListBox->sizeHint() + TQSize(2,2)); 00128 m_completionPopup->installEventFilter( this ); 00129 m_completionPopup->setFocusProxy( m_view->m_viewInternal ); 00130 00131 m_pArgHint = new KateArgHint( m_view ); 00132 connect( m_pArgHint, TQT_SIGNAL(argHintHidden()), 00133 this, TQT_SIGNAL(argHintHidden()) ); 00134 00135 connect( m_view, TQT_SIGNAL(cursorPositionChanged()), 00136 this, TQT_SLOT(slotCursorPosChanged()) ); 00137 } 00138 00139 KateCodeCompletion::~KateCodeCompletion() 00140 { 00141 delete m_completionPopup; 00142 } 00143 00144 bool KateCodeCompletion::codeCompletionVisible () { 00145 return m_completionPopup->isVisible(); 00146 } 00147 00148 void KateCodeCompletion::showCompletionBox( 00149 TQValueList<KTextEditor::CompletionEntry> complList, int offset, bool casesensitive ) 00150 { 00151 kdDebug(13035) << "showCompletionBox " << endl; 00152 00153 if ( codeCompletionVisible() ) return; 00154 00155 m_caseSensitive = casesensitive; 00156 m_complList = complList; 00157 m_offset = offset; 00158 m_view->cursorPositionReal( &m_lineCursor, &m_colCursor ); 00159 m_colCursor -= offset; 00160 00161 updateBox( true ); 00162 } 00163 00164 bool KateCodeCompletion::eventFilter( TQObject *o, TQEvent *e ) 00165 { 00166 if ( TQT_BASE_OBJECT(o) != TQT_BASE_OBJECT(m_completionPopup) && 00167 TQT_BASE_OBJECT(o) != TQT_BASE_OBJECT(m_completionListBox) && 00168 TQT_BASE_OBJECT(o) != TQT_BASE_OBJECT(m_completionListBox->viewport()) ) 00169 return false; 00170 00171 if( e->type() == TQEvent::Hide ) 00172 { 00173 //don't use abortCompletion() as aborting here again will send abort signal 00174 //even on successfull completion we will emit completionAborted() twice... 00175 m_completionPopup->hide(); 00176 delete m_commentLabel; 00177 m_commentLabel = 0; 00178 return false; 00179 } 00180 00181 00182 if ( e->type() == TQEvent::MouseButtonDblClick ) { 00183 doComplete(); 00184 return false; 00185 } 00186 00187 if ( e->type() == TQEvent::MouseButtonPress ) { 00188 TQTimer::singleShot(0, this, TQT_SLOT(showComment())); 00189 return false; 00190 } 00191 00192 return false; 00193 } 00194 00195 void KateCodeCompletion::handleKey (TQKeyEvent *e) 00196 { 00197 // close completion if you move out of range 00198 if ((e->key() == Key_Up) && (m_completionListBox->currentItem() == 0)) 00199 { 00200 abortCompletion(); 00201 m_view->setFocus(); 00202 return; 00203 } 00204 00205 // keyboard movement 00206 if( (e->key() == Key_Up) || (e->key() == Key_Down ) || 00207 (e->key() == Key_Home ) || (e->key() == Key_End) || 00208 (e->key() == Key_Prior) || (e->key() == Key_Next )) 00209 { 00210 TQTimer::singleShot(0,this,TQT_SLOT(showComment())); 00211 TQApplication::sendEvent( m_completionListBox, (TQEvent*)e ); 00212 return; 00213 } 00214 00215 // update the box 00216 updateBox(); 00217 } 00218 00219 void KateCodeCompletion::doComplete() 00220 { 00221 KateCompletionItem* item = static_cast<KateCompletionItem*>( 00222 m_completionListBox->item(m_completionListBox->currentItem())); 00223 00224 if( item == 0 ) 00225 return; 00226 00227 TQString text = item->m_entry.text; 00228 TQString currentLine = m_view->currentTextLine(); 00229 int len = m_view->cursorColumnReal() - m_colCursor; 00230 TQString currentComplText = currentLine.mid(m_colCursor,len); 00231 TQString add = text.mid(currentComplText.length()); 00232 if( item->m_entry.postfix == "()" ) 00233 add += "("; 00234 00235 emit filterInsertString(&(item->m_entry),&add); 00236 m_view->insertText(add); 00237 00238 complete( item->m_entry ); 00239 m_view->setFocus(); 00240 } 00241 00242 void KateCodeCompletion::abortCompletion() 00243 { 00244 m_completionPopup->hide(); 00245 delete m_commentLabel; 00246 m_commentLabel = 0; 00247 emit completionAborted(); 00248 } 00249 00250 void KateCodeCompletion::complete( KTextEditor::CompletionEntry entry ) 00251 { 00252 m_completionPopup->hide(); 00253 delete m_commentLabel; 00254 m_commentLabel = 0; 00255 emit completionDone( entry ); 00256 emit completionDone(); 00257 } 00258 00259 void KateCodeCompletion::updateBox( bool ) 00260 { 00261 if( m_colCursor > m_view->cursorColumnReal() ) { 00262 // the cursor is too far left 00263 kdDebug(13035) << "Aborting Codecompletion after sendEvent" << endl; 00264 kdDebug(13035) << m_view->cursorColumnReal() << endl; 00265 abortCompletion(); 00266 m_view->setFocus(); 00267 return; 00268 } 00269 00270 m_completionListBox->clear(); 00271 00272 TQString currentLine = m_view->currentTextLine(); 00273 int len = m_view->cursorColumnReal() - m_colCursor; 00274 TQString currentComplText = currentLine.mid(m_colCursor,len); 00275 /* No-one really badly wants those, or? 00276 kdDebug(13035) << "Column: " << m_colCursor << endl; 00277 kdDebug(13035) << "Line: " << currentLine << endl; 00278 kdDebug(13035) << "CurrentColumn: " << m_view->cursorColumnReal() << endl; 00279 kdDebug(13035) << "Len: " << len << endl; 00280 kdDebug(13035) << "Text: '" << currentComplText << "'" << endl; 00281 kdDebug(13035) << "Count: " << m_complList.count() << endl; 00282 */ 00283 TQValueList<KTextEditor::CompletionEntry>::Iterator it; 00284 if( m_caseSensitive ) { 00285 for( it = m_complList.begin(); it != m_complList.end(); ++it ) { 00286 if( (*it).text.startsWith(currentComplText) ) { 00287 new KateCompletionItem(m_completionListBox,*it); 00288 } 00289 } 00290 } else { 00291 currentComplText = currentComplText.upper(); 00292 for( it = m_complList.begin(); it != m_complList.end(); ++it ) { 00293 if( (*it).text.upper().startsWith(currentComplText) ) { 00294 new KateCompletionItem(m_completionListBox,*it); 00295 } 00296 } 00297 } 00298 00299 if( m_completionListBox->count() == 0 || 00300 ( m_completionListBox->count() == 1 && // abort if we equaled the last item 00301 currentComplText == m_completionListBox->text(0).stripWhiteSpace() ) ) { 00302 abortCompletion(); 00303 m_view->setFocus(); 00304 return; 00305 } 00306 00307 kdDebug(13035)<<"KateCodeCompletion::updateBox: Resizing widget"<<endl; 00308 m_completionPopup->resize(m_completionListBox->sizeHint() + TQSize(2,2)); 00309 TQPoint p = m_view->mapToGlobal( m_view->cursorCoordinates() ); 00310 int x = p.x(); 00311 int y = p.y() ; 00312 if ( y + m_completionPopup->height() + m_view->renderer()->config()->fontMetrics( )->height() > TQApplication::desktop()->height() ) 00313 y -= (m_completionPopup->height() ); 00314 else 00315 y += m_view->renderer()->config()->fontMetrics( )->height(); 00316 00317 if (x + m_completionPopup->width() > TQApplication::desktop()->width()) 00318 x = TQApplication::desktop()->width() - m_completionPopup->width(); 00319 00320 m_completionPopup->move( TQPoint(x,y) ); 00321 00322 m_completionListBox->setCurrentItem( 0 ); 00323 m_completionListBox->setSelected( 0, true ); 00324 m_completionListBox->setFocus(); 00325 m_completionPopup->show(); 00326 00327 TQTimer::singleShot(0,this,TQT_SLOT(showComment())); 00328 } 00329 00330 void KateCodeCompletion::showArgHint ( TQStringList functionList, const TQString& strWrapping, const TQString& strDelimiter ) 00331 { 00332 unsigned int line, col; 00333 m_view->cursorPositionReal( &line, &col ); 00334 m_pArgHint->reset( line, col ); 00335 m_pArgHint->setArgMarkInfos( strWrapping, strDelimiter ); 00336 00337 int nNum = 0; 00338 TQStringList::Iterator end(functionList.end()); 00339 for( TQStringList::Iterator it = functionList.begin(); it != end; ++it ) 00340 { 00341 kdDebug(13035) << "Insert function text: " << *it << endl; 00342 00343 m_pArgHint->addFunction( nNum, ( *it ) ); 00344 00345 nNum++; 00346 } 00347 00348 m_pArgHint->move(m_view->mapToGlobal(m_view->cursorCoordinates() + TQPoint(0,m_view->renderer()->config()->fontMetrics( )->height())) ); 00349 m_pArgHint->show(); 00350 } 00351 00352 void KateCodeCompletion::slotCursorPosChanged() 00353 { 00354 m_pArgHint->cursorPositionChanged ( m_view, m_view->cursorLine(), m_view->cursorColumnReal() ); 00355 } 00356 00357 void KateCodeCompletion::showComment() 00358 { 00359 if (!m_completionPopup->isVisible()) 00360 return; 00361 00362 KateCompletionItem* item = static_cast<KateCompletionItem*>(m_completionListBox->item(m_completionListBox->currentItem())); 00363 00364 if( !item ) 00365 return; 00366 00367 if( item->m_entry.comment.isEmpty() ) 00368 return; 00369 00370 delete m_commentLabel; 00371 m_commentLabel = new KateCodeCompletionCommentLabel( 0, item->m_entry.comment ); 00372 m_commentLabel->setFont(TQToolTip::font()); 00373 m_commentLabel->setPalette(TQToolTip::palette()); 00374 00375 TQPoint rightPoint = m_completionPopup->mapToGlobal(TQPoint(m_completionPopup->width(),0)); 00376 TQPoint leftPoint = m_completionPopup->mapToGlobal(TQPoint(0,0)); 00377 TQRect screen = TQApplication::desktop()->screenGeometry ( m_commentLabel ); 00378 TQPoint finalPoint; 00379 if (rightPoint.x()+m_commentLabel->width() > screen.x() + screen.width()) 00380 finalPoint.setX(leftPoint.x()-m_commentLabel->width()); 00381 else 00382 finalPoint.setX(rightPoint.x()); 00383 00384 m_completionListBox->ensureCurrentVisible(); 00385 00386 finalPoint.setY( 00387 m_completionListBox->viewport()->mapToGlobal(m_completionListBox->itemRect( 00388 m_completionListBox->item(m_completionListBox->currentItem())).topLeft()).y()); 00389 00390 m_commentLabel->move(finalPoint); 00391 m_commentLabel->show(); 00392 } 00393 00394 KateArgHint::KateArgHint( KateView* parent, const char* name ) 00395 : TQFrame( parent, name, (WFlags)WType_Popup ) 00396 { 00397 setBackgroundColor( black ); 00398 setPaletteForegroundColor( Qt::black ); 00399 00400 labelDict.setAutoDelete( true ); 00401 layout = new TQVBoxLayout( this, 1, 2 ); 00402 layout->setAutoAdd( true ); 00403 editorView = parent; 00404 00405 m_markCurrentFunction = true; 00406 00407 setFocusPolicy( TQ_StrongFocus ); 00408 setFocusProxy( parent ); 00409 00410 reset( -1, -1 ); 00411 } 00412 00413 KateArgHint::~KateArgHint() 00414 { 00415 } 00416 00417 void KateArgHint::setArgMarkInfos( const TQString& wrapping, const TQString& delimiter ) 00418 { 00419 m_wrapping = wrapping; 00420 m_delimiter = delimiter; 00421 m_markCurrentFunction = true; 00422 } 00423 00424 void KateArgHint::reset( int line, int col ) 00425 { 00426 m_functionMap.clear(); 00427 m_currentFunction = -1; 00428 labelDict.clear(); 00429 00430 m_currentLine = line; 00431 m_currentCol = col - 1; 00432 } 00433 00434 void KateArgHint::slotDone(bool completed) 00435 { 00436 hide(); 00437 00438 m_currentLine = m_currentCol = -1; 00439 00440 emit argHintHidden(); 00441 if (completed) 00442 emit argHintCompleted(); 00443 else 00444 emit argHintAborted(); 00445 } 00446 00447 void KateArgHint::cursorPositionChanged( KateView* view, int line, int col ) 00448 { 00449 if( m_currentCol == -1 || m_currentLine == -1 ){ 00450 slotDone(false); 00451 return; 00452 } 00453 00454 int nCountDelimiter = 0; 00455 int count = 0; 00456 00457 TQString currentTextLine = view->doc()->textLine( line ); 00458 TQString text = currentTextLine.mid( m_currentCol, col - m_currentCol ); 00459 TQRegExp strconst_rx( "\"[^\"]*\"" ); 00460 TQRegExp chrconst_rx( "'[^']*'" ); 00461 00462 text = text 00463 .replace( strconst_rx, "\"\"" ) 00464 .replace( chrconst_rx, "''" ); 00465 00466 int index = 0; 00467 while( index < (int)text.length() ){ 00468 if( text[index] == m_wrapping[0] ){ 00469 ++count; 00470 } else if( text[index] == m_wrapping[1] ){ 00471 --count; 00472 } else if( count > 0 && text[index] == m_delimiter[0] ){ 00473 ++nCountDelimiter; 00474 } 00475 ++index; 00476 } 00477 00478 if( (m_currentLine > 0 && m_currentLine != line) || (m_currentLine < col) || (count == 0) ){ 00479 slotDone(count == 0); 00480 return; 00481 } 00482 00483 // setCurArg ( nCountDelimiter + 1 ); 00484 00485 } 00486 00487 void KateArgHint::addFunction( int id, const TQString& prot ) 00488 { 00489 m_functionMap[ id ] = prot; 00490 TQLabel* label = new TQLabel( prot.stripWhiteSpace().simplifyWhiteSpace(), this ); 00491 label->setBackgroundColor( TQColor(255, 255, 238) ); 00492 label->show(); 00493 labelDict.insert( id, label ); 00494 00495 if( m_currentFunction < 0 ) 00496 setCurrentFunction( id ); 00497 } 00498 00499 void KateArgHint::setCurrentFunction( int currentFunction ) 00500 { 00501 if( m_currentFunction != currentFunction ){ 00502 00503 if( currentFunction < 0 ) 00504 currentFunction = (int)m_functionMap.size() - 1; 00505 00506 if( currentFunction > (int)m_functionMap.size()-1 ) 00507 currentFunction = 0; 00508 00509 if( m_markCurrentFunction && m_currentFunction >= 0 ){ 00510 TQLabel* label = labelDict[ m_currentFunction ]; 00511 label->setFont( font() ); 00512 } 00513 00514 m_currentFunction = currentFunction; 00515 00516 if( m_markCurrentFunction ){ 00517 TQLabel* label = labelDict[ currentFunction ]; 00518 TQFont fnt( font() ); 00519 fnt.setBold( true ); 00520 label->setFont( fnt ); 00521 } 00522 00523 adjustSize(); 00524 } 00525 } 00526 00527 void KateArgHint::show() 00528 { 00529 TQFrame::show(); 00530 adjustSize(); 00531 } 00532 00533 bool KateArgHint::eventFilter( TQObject*, TQEvent* e ) 00534 { 00535 if( isVisible() && e->type() == TQEvent::KeyPress ){ 00536 TQKeyEvent* ke = TQT_TQKEYEVENT( e ); 00537 if( (ke->state() & ControlButton) && ke->key() == Key_Left ){ 00538 setCurrentFunction( currentFunction() - 1 ); 00539 ke->accept(); 00540 return true; 00541 } else if( ke->key() == Key_Escape ){ 00542 slotDone(false); 00543 return false; 00544 } else if( (ke->state() & ControlButton) && ke->key() == Key_Right ){ 00545 setCurrentFunction( currentFunction() + 1 ); 00546 ke->accept(); 00547 return true; 00548 } 00549 } 00550 00551 return false; 00552 } 00553 00554 void KateArgHint::adjustSize( ) 00555 { 00556 TQRect screen = TQApplication::desktop()->screenGeometry( pos() ); 00557 00558 TQFrame::adjustSize(); 00559 if( width() > screen.width() ) 00560 resize( screen.width(), height() ); 00561 00562 if( x() + width() > screen.x() + screen.width() ) 00563 move( screen.x() + screen.width() - width(), y() ); 00564 } 00565 00566 // kate: space-indent on; indent-width 2; replace-tabs on;