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 <qwhatsthis.h>
00038 #include <qvbox.h>
00039 #include <qlistbox.h>
00040 #include <qtimer.h>
00041 #include <qtooltip.h>
00042 #include <qapplication.h>
00043 #include <qsizegrip.h>
00044 #include <qfontmetrics.h>
00045 #include <qlayout.h>
00046 #include <qregexp.h>
00047 
00054 class KateCCListBox : public QListBox
00055 {
00056   public:
00061     KateCCListBox (QWidget* parent = 0, const char* name = 0, WFlags f = 0):QListBox(parent, name, f)
00062     {
00063     }
00064 
00065     QSize sizeHint()  const
00066     {
00067         int count = this->count();
00068         int height = 20;
00069         int tmpwidth = 8;
00070         //FIXME the height is for some reasons at least 3 items heigh, even if there is only one item in the list
00071         if (count > 0)
00072             if(count < 11)
00073                 height =  count * itemHeight(0);
00074             else  {
00075                 height = 10 * itemHeight(0);
00076                 tmpwidth += verticalScrollBar()->width();
00077             }
00078 
00079         int maxcount = 0, tmpcount = 0;
00080         for (int i = 0; i < count; ++i)
00081             if ( (tmpcount = fontMetrics().width(text(i)) ) > maxcount)
00082                     maxcount = tmpcount;
00083 
00084         if (maxcount > QApplication::desktop()->width()){
00085             tmpwidth = QApplication::desktop()->width() - 5;
00086             height += horizontalScrollBar()->height();
00087         } else
00088             tmpwidth += maxcount;
00089         return QSize(tmpwidth,height);
00090 
00091     }
00092 };
00093 
00094 class KateCompletionItem : public QListBoxText
00095 {
00096   public:
00097     KateCompletionItem( QListBox* lb, KTextEditor::CompletionEntry entry )
00098       : QListBoxText( lb )
00099       , m_entry( entry )
00100     {
00101       if( entry.postfix == "()" ) { // should be configurable
00102         setText( entry.prefix + " " + entry.text + entry.postfix );
00103       } else {
00104         setText( entry.prefix + " " + entry.text + " " + entry.postfix);
00105       }
00106     }
00107 
00108     KTextEditor::CompletionEntry m_entry;
00109 };
00110 
00111 
00112 KateCodeCompletion::KateCodeCompletion( KateView* view )
00113   : QObject( view, "Kate Code Completion" )
00114   , m_view( view )
00115   , m_commentLabel( 0 )
00116 {
00117   m_completionPopup = new QVBox( 0, 0, WType_Popup );
00118   m_completionPopup->setFrameStyle( QFrame::Box | QFrame::Plain );
00119   m_completionPopup->setLineWidth( 1 );
00120 
00121   m_completionListBox = new KateCCListBox( m_completionPopup );
00122   m_completionListBox->setFrameStyle( QFrame::NoFrame );
00123   //m_completionListBox->setCornerWidget( new QSizeGrip( m_completionListBox) );
00124   m_completionListBox->setFocusProxy( m_view->m_viewInternal );
00125 
00126   m_completionListBox->installEventFilter( this );
00127 
00128   m_completionPopup->resize(m_completionListBox->sizeHint() + QSize(2,2));
00129   m_completionPopup->installEventFilter( this );
00130   m_completionPopup->setFocusProxy( m_view->m_viewInternal );
00131 
00132   m_pArgHint = new KateArgHint( m_view );
00133   connect( m_pArgHint, SIGNAL(argHintHidden()),
00134            this, SIGNAL(argHintHidden()) );
00135 
00136   connect( m_view, SIGNAL(cursorPositionChanged()),
00137            this, SLOT(slotCursorPosChanged()) );
00138 }
00139 
00140 KateCodeCompletion::~KateCodeCompletion()
00141 {
00142   delete m_completionPopup;
00143 }
00144 
00145 bool KateCodeCompletion::codeCompletionVisible () {
00146   return m_completionPopup->isVisible();
00147 }
00148 
00149 void KateCodeCompletion::showCompletionBox(
00150     QValueList<KTextEditor::CompletionEntry> complList, int offset, bool casesensitive )
00151 {
00152   kdDebug(13035) << "showCompletionBox " << endl;
00153 
00154   if ( codeCompletionVisible() ) return;
00155 
00156   m_caseSensitive = casesensitive;
00157   m_complList = complList;
00158   m_offset = offset;
00159   m_view->cursorPositionReal( &m_lineCursor, &m_colCursor );
00160   m_colCursor -= offset;
00161 
00162   updateBox( true );
00163 }
00164 
00165 bool KateCodeCompletion::eventFilter( QObject *o, QEvent *e )
00166 {
00167   if ( o != m_completionPopup &&
00168        o != m_completionListBox &&
00169        o != m_completionListBox->viewport() )
00170     return false;
00171 
00172    if( e->type() == QEvent::Hide )
00173    { 
00174      //don't use abortCompletion() as aborting here again will send abort signal
00175      //even on successfull completion we will emit completionAborted() twice...
00176      m_completionPopup->hide();
00177      delete m_commentLabel;
00178      m_commentLabel = 0;
00179      return false;
00180    }
00181 
00182 
00183    if ( e->type() == QEvent::MouseButtonDblClick  ) {
00184     doComplete();
00185     return false;
00186    }
00187 
00188    if ( e->type() == QEvent::MouseButtonPress ) {
00189     QTimer::singleShot(0, this, SLOT(showComment()));
00190     return false;
00191    }
00192 
00193   return false;
00194 }
00195 
00196 void KateCodeCompletion::handleKey (QKeyEvent *e)
00197 {
00198   // close completion if you move out of range
00199   if ((e->key() == Key_Up) && (m_completionListBox->currentItem() == 0))
00200   {
00201     abortCompletion();
00202     m_view->setFocus();
00203     return;
00204   }
00205 
00206   // keyboard movement
00207   if( (e->key() == Key_Up)    || (e->key() == Key_Down ) ||
00208         (e->key() == Key_Home ) || (e->key() == Key_End)   ||
00209         (e->key() == Key_Prior) || (e->key() == Key_Next ))
00210   {
00211     QTimer::singleShot(0,this,SLOT(showComment()));
00212     QApplication::sendEvent( m_completionListBox, (QEvent*)e );
00213     return;
00214   }
00215 
00216   // update the box
00217   updateBox();
00218 }
00219 
00220 void KateCodeCompletion::doComplete()
00221 {
00222   KateCompletionItem* item = static_cast<KateCompletionItem*>(
00223      m_completionListBox->item(m_completionListBox->currentItem()));
00224 
00225   if( item == 0 )
00226     return;
00227 
00228   QString text = item->m_entry.text;
00229   QString currentLine = m_view->currentTextLine();
00230   int len = m_view->cursorColumnReal() - m_colCursor;
00231   QString currentComplText = currentLine.mid(m_colCursor,len);
00232   QString add = text.mid(currentComplText.length());
00233   if( item->m_entry.postfix == "()" )
00234     add += "(";
00235 
00236   emit filterInsertString(&(item->m_entry),&add);
00237   m_view->insertText(add);
00238 
00239   complete( item->m_entry );
00240   m_view->setFocus();
00241 }
00242 
00243 void KateCodeCompletion::abortCompletion()
00244 {
00245   m_completionPopup->hide();
00246   delete m_commentLabel;
00247   m_commentLabel = 0;
00248   emit completionAborted();
00249 }
00250 
00251 void KateCodeCompletion::complete( KTextEditor::CompletionEntry entry )
00252 {
00253   m_completionPopup->hide();
00254   delete m_commentLabel;
00255   m_commentLabel = 0;
00256   emit completionDone( entry );
00257   emit completionDone();
00258 }
00259 
00260 void KateCodeCompletion::updateBox( bool )
00261 {
00262   if( m_colCursor > m_view->cursorColumnReal() ) {
00263     // the cursor is too far left
00264     kdDebug(13035) << "Aborting Codecompletion after sendEvent" << endl;
00265     kdDebug(13035) << m_view->cursorColumnReal() << endl;
00266     abortCompletion();
00267     m_view->setFocus();
00268     return;
00269   }
00270 
00271   m_completionListBox->clear();
00272 
00273   QString currentLine = m_view->currentTextLine();
00274   int len = m_view->cursorColumnReal() - m_colCursor;
00275   QString currentComplText = currentLine.mid(m_colCursor,len);
00276 /* No-one really badly wants those, or?
00277   kdDebug(13035) << "Column: " << m_colCursor << endl;
00278   kdDebug(13035) << "Line: " << currentLine << endl;
00279   kdDebug(13035) << "CurrentColumn: " << m_view->cursorColumnReal() << endl;
00280   kdDebug(13035) << "Len: " << len << endl;
00281   kdDebug(13035) << "Text: '" << currentComplText << "'" << endl;
00282   kdDebug(13035) << "Count: " << m_complList.count() << endl;
00283 */
00284   QValueList<KTextEditor::CompletionEntry>::Iterator it;
00285   if( m_caseSensitive ) {
00286     for( it = m_complList.begin(); it != m_complList.end(); ++it ) {
00287       if( (*it).text.startsWith(currentComplText) ) {
00288         new KateCompletionItem(m_completionListBox,*it);
00289       }
00290     }
00291   } else {
00292     currentComplText = currentComplText.upper();
00293     for( it = m_complList.begin(); it != m_complList.end(); ++it ) {
00294       if( (*it).text.upper().startsWith(currentComplText) ) {
00295         new KateCompletionItem(m_completionListBox,*it);
00296       }
00297     }
00298   }
00299 
00300   if( m_completionListBox->count() == 0 ||
00301       ( m_completionListBox->count() == 1 && // abort if we equaled the last item
00302         currentComplText == m_completionListBox->text(0).stripWhiteSpace() ) ) {
00303     abortCompletion();
00304     m_view->setFocus();
00305     return;
00306   }
00307 
00308     kdDebug(13035)<<"KateCodeCompletion::updateBox: Resizing widget"<<endl;
00309         m_completionPopup->resize(m_completionListBox->sizeHint() + QSize(2,2));
00310     QPoint p = m_view->mapToGlobal( m_view->cursorCoordinates() );
00311         int x = p.x();
00312         int y = p.y() ;
00313         if ( y + m_completionPopup->height() + m_view->renderer()->config()->fontMetrics( )->height() > QApplication::desktop()->height() )
00314                 y -= (m_completionPopup->height() );
00315         else
00316                 y += m_view->renderer()->config()->fontMetrics( )->height();
00317 
00318         if (x + m_completionPopup->width() > QApplication::desktop()->width())
00319                 x = QApplication::desktop()->width() - m_completionPopup->width();
00320 
00321         m_completionPopup->move( QPoint(x,y) );
00322 
00323   m_completionListBox->setCurrentItem( 0 );
00324   m_completionListBox->setSelected( 0, true );
00325   m_completionListBox->setFocus();
00326   m_completionPopup->show();
00327 
00328   QTimer::singleShot(0,this,SLOT(showComment()));
00329 }
00330 
00331 void KateCodeCompletion::showArgHint ( QStringList functionList, const QString& strWrapping, const QString& strDelimiter )
00332 {
00333   unsigned int line, col;
00334   m_view->cursorPositionReal( &line, &col );
00335   m_pArgHint->reset( line, col );
00336   m_pArgHint->setArgMarkInfos( strWrapping, strDelimiter );
00337 
00338   int nNum = 0;
00339   QStringList::Iterator end(functionList.end());
00340   for( QStringList::Iterator it = functionList.begin(); it != end; ++it )
00341   {
00342     kdDebug(13035) << "Insert function text: " << *it << endl;
00343 
00344     m_pArgHint->addFunction( nNum, ( *it ) );
00345 
00346     nNum++;
00347   }
00348 
00349   m_pArgHint->move(m_view->mapToGlobal(m_view->cursorCoordinates() + QPoint(0,m_view->renderer()->config()->fontMetrics( )->height())) );
00350   m_pArgHint->show();
00351 }
00352 
00353 void KateCodeCompletion::slotCursorPosChanged()
00354 {
00355   m_pArgHint->cursorPositionChanged ( m_view, m_view->cursorLine(), m_view->cursorColumnReal() );
00356 }
00357 
00358 void KateCodeCompletion::showComment()
00359 {
00360   if (!m_completionPopup->isVisible())
00361     return;
00362 
00363   KateCompletionItem* item = static_cast<KateCompletionItem*>(m_completionListBox->item(m_completionListBox->currentItem()));
00364 
00365   if( !item )
00366     return;
00367 
00368   if( item->m_entry.comment.isEmpty() )
00369     return;
00370 
00371   delete m_commentLabel;
00372   m_commentLabel = new KateCodeCompletionCommentLabel( 0, item->m_entry.comment );
00373   m_commentLabel->setFont(QToolTip::font());
00374   m_commentLabel->setPalette(QToolTip::palette());
00375 
00376   QPoint rightPoint = m_completionPopup->mapToGlobal(QPoint(m_completionPopup->width(),0));
00377   QPoint leftPoint = m_completionPopup->mapToGlobal(QPoint(0,0));
00378   QRect screen = QApplication::desktop()->screenGeometry ( m_commentLabel );
00379   QPoint finalPoint;
00380   if (rightPoint.x()+m_commentLabel->width() > screen.x() + screen.width())
00381     finalPoint.setX(leftPoint.x()-m_commentLabel->width());
00382   else
00383     finalPoint.setX(rightPoint.x());
00384 
00385   m_completionListBox->ensureCurrentVisible();
00386 
00387   finalPoint.setY(
00388     m_completionListBox->viewport()->mapToGlobal(m_completionListBox->itemRect(
00389       m_completionListBox->item(m_completionListBox->currentItem())).topLeft()).y());
00390 
00391   m_commentLabel->move(finalPoint);
00392   m_commentLabel->show();
00393 }
00394 
00395 KateArgHint::KateArgHint( KateView* parent, const char* name )
00396     : QFrame( parent, name, WType_Popup )
00397 {
00398     setBackgroundColor( black );
00399     setPaletteForegroundColor( Qt::black );
00400 
00401     labelDict.setAutoDelete( true );
00402     layout = new QVBoxLayout( this, 1, 2 );
00403     layout->setAutoAdd( true );
00404     editorView = parent;
00405 
00406     m_markCurrentFunction = true;
00407 
00408     setFocusPolicy( StrongFocus );
00409     setFocusProxy( parent );
00410 
00411     reset( -1, -1 );
00412 }
00413 
00414 KateArgHint::~KateArgHint()
00415 {
00416 }
00417 
00418 void KateArgHint::setArgMarkInfos( const QString& wrapping, const QString& delimiter )
00419 {
00420     m_wrapping = wrapping;
00421     m_delimiter = delimiter;
00422     m_markCurrentFunction = true;
00423 }
00424 
00425 void KateArgHint::reset( int line, int col )
00426 {
00427     m_functionMap.clear();
00428     m_currentFunction = -1;
00429     labelDict.clear();
00430 
00431     m_currentLine = line;
00432     m_currentCol = col - 1;
00433 }
00434 
00435 void KateArgHint::slotDone(bool completed)
00436 {
00437     hide();
00438 
00439     m_currentLine = m_currentCol = -1;
00440 
00441     emit argHintHidden();
00442     if (completed)
00443         emit argHintCompleted();
00444     else
00445         emit argHintAborted();
00446 }
00447 
00448 void KateArgHint::cursorPositionChanged( KateView* view, int line, int col )
00449 {
00450     if( m_currentCol == -1 || m_currentLine == -1 ){
00451         slotDone(false);
00452         return;
00453     }
00454 
00455     int nCountDelimiter = 0;
00456     int count = 0;
00457 
00458     QString currentTextLine = view->doc()->textLine( line );
00459     QString text = currentTextLine.mid( m_currentCol, col - m_currentCol );
00460     QRegExp strconst_rx( "\"[^\"]*\"" );
00461     QRegExp chrconst_rx( "'[^']*'" );
00462 
00463     text = text
00464         .replace( strconst_rx, "\"\"" )
00465         .replace( chrconst_rx, "''" );
00466 
00467     int index = 0;
00468     while( index < (int)text.length() ){
00469         if( text[index] == m_wrapping[0] ){
00470             ++count;
00471         } else if( text[index] == m_wrapping[1] ){
00472             --count;
00473         } else if( count > 0 && text[index] == m_delimiter[0] ){
00474             ++nCountDelimiter;
00475         }
00476         ++index;
00477     }
00478 
00479     if( (m_currentLine > 0 && m_currentLine != line) || (m_currentLine < col) || (count == 0) ){
00480         slotDone(count == 0);
00481         return;
00482     }
00483 
00484     // setCurArg ( nCountDelimiter + 1 );
00485 
00486 }
00487 
00488 void KateArgHint::addFunction( int id, const QString& prot )
00489 {
00490     m_functionMap[ id ] = prot;
00491     QLabel* label = new QLabel( prot.stripWhiteSpace().simplifyWhiteSpace(), this );
00492     label->setBackgroundColor( QColor(255, 255, 238) );
00493     label->show();
00494     labelDict.insert( id, label );
00495 
00496     if( m_currentFunction < 0 )
00497         setCurrentFunction( id );
00498 }
00499 
00500 void KateArgHint::setCurrentFunction( int currentFunction )
00501 {
00502     if( m_currentFunction != currentFunction ){
00503 
00504         if( currentFunction < 0 )
00505             currentFunction = (int)m_functionMap.size() - 1;
00506 
00507         if( currentFunction > (int)m_functionMap.size()-1 )
00508             currentFunction = 0;
00509 
00510         if( m_markCurrentFunction && m_currentFunction >= 0 ){
00511             QLabel* label = labelDict[ m_currentFunction ];
00512             label->setFont( font() );
00513         }
00514 
00515         m_currentFunction = currentFunction;
00516 
00517         if( m_markCurrentFunction ){
00518             QLabel* label = labelDict[ currentFunction ];
00519             QFont fnt( font() );
00520             fnt.setBold( true );
00521             label->setFont( fnt );
00522         }
00523 
00524         adjustSize();
00525     }
00526 }
00527 
00528 void KateArgHint::show()
00529 {
00530     QFrame::show();
00531     adjustSize();
00532 }
00533 
00534 bool KateArgHint::eventFilter( QObject*, QEvent* e )
00535 {
00536     if( isVisible() && e->type() == QEvent::KeyPress ){
00537         QKeyEvent* ke = static_cast<QKeyEvent*>( e );
00538         if( (ke->state() & ControlButton) && ke->key() == Key_Left ){
00539             setCurrentFunction( currentFunction() - 1 );
00540             ke->accept();
00541             return true;
00542         } else if( ke->key() == Key_Escape ){
00543             slotDone(false);
00544             return false;
00545         } else if( (ke->state() & ControlButton) && ke->key() == Key_Right ){
00546             setCurrentFunction( currentFunction() + 1 );
00547             ke->accept();
00548             return true;
00549         }
00550     }
00551 
00552     return false;
00553 }
00554 
00555 void KateArgHint::adjustSize( )
00556 {
00557     QRect screen = QApplication::desktop()->screenGeometry( pos() );
00558 
00559     QFrame::adjustSize();
00560     if( width() > screen.width() )
00561         resize( screen.width(), height() );
00562 
00563     if( x() + width() > screen.x() + screen.width() )
00564         move( screen.x() + screen.width() - width(), y() );
00565 }
00566 
00567 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE Home | KDE Accessibility Home | Description of Access Keys