diff options
Diffstat (limited to 'kate/part/katecodecompletion.cpp')
-rw-r--r-- | kate/part/katecodecompletion.cpp | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/kate/part/katecodecompletion.cpp b/kate/part/katecodecompletion.cpp new file mode 100644 index 000000000..bbc34dfca --- /dev/null +++ b/kate/part/katecodecompletion.cpp @@ -0,0 +1,566 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> + Copyright (C) 2001 by Victor Röder <Victor_Roeder@GMX.de> + Copyright (C) 2002 by Roberto Raggi <roberto@kdevelop.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/******** Partly based on the ArgHintWidget of Qt3 by Trolltech AS *********/ +/* Trolltech doesn't mind, if we license that piece of code as LGPL, because there isn't much + * left from the desigener code */ + +#include "katecodecompletion.h" +#include "katecodecompletion.moc" + +#include "katedocument.h" +#include "kateview.h" +#include "katerenderer.h" +#include "kateconfig.h" +#include "katefont.h" + +#include <kdebug.h> + +#include <qwhatsthis.h> +#include <qvbox.h> +#include <qlistbox.h> +#include <qtimer.h> +#include <qtooltip.h> +#include <qapplication.h> +#include <qsizegrip.h> +#include <qfontmetrics.h> +#include <qlayout.h> +#include <qregexp.h> + +/** + * This class is used as the codecompletion listbox. It can be resized according to its contents, + * therfor the needed size is provided by sizeHint(); + *@short Listbox showing codecompletion + *@author Jonas B. Jacobi <j.jacobi@gmx.de> + */ +class KateCCListBox : public QListBox +{ + public: + /** + @short Create a new CCListBox + */ + KateCCListBox (QWidget* parent = 0, const char* name = 0, WFlags f = 0):QListBox(parent, name, f) + { + } + + QSize sizeHint() const + { + int count = this->count(); + int height = 20; + int tmpwidth = 8; + //FIXME the height is for some reasons at least 3 items heigh, even if there is only one item in the list + if (count > 0) + if(count < 11) + height = count * itemHeight(0); + else { + height = 10 * itemHeight(0); + tmpwidth += verticalScrollBar()->width(); + } + + int maxcount = 0, tmpcount = 0; + for (int i = 0; i < count; ++i) + if ( (tmpcount = fontMetrics().width(text(i)) ) > maxcount) + maxcount = tmpcount; + + if (maxcount > QApplication::desktop()->width()){ + tmpwidth = QApplication::desktop()->width() - 5; + height += horizontalScrollBar()->height(); + } else + tmpwidth += maxcount; + return QSize(tmpwidth,height); + + } +}; + +class KateCompletionItem : public QListBoxText +{ + public: + KateCompletionItem( QListBox* lb, KTextEditor::CompletionEntry entry ) + : QListBoxText( lb ) + , m_entry( entry ) + { + if( entry.postfix == "()" ) { // should be configurable + setText( entry.prefix + " " + entry.text + entry.postfix ); + } else { + setText( entry.prefix + " " + entry.text + " " + entry.postfix); + } + } + + KTextEditor::CompletionEntry m_entry; +}; + + +KateCodeCompletion::KateCodeCompletion( KateView* view ) + : QObject( view, "Kate Code Completion" ) + , m_view( view ) + , m_commentLabel( 0 ) +{ + m_completionPopup = new QVBox( 0, 0, WType_Popup ); + m_completionPopup->setFrameStyle( QFrame::Box | QFrame::Plain ); + m_completionPopup->setLineWidth( 1 ); + + m_completionListBox = new KateCCListBox( m_completionPopup ); + m_completionListBox->setFrameStyle( QFrame::NoFrame ); + //m_completionListBox->setCornerWidget( new QSizeGrip( m_completionListBox) ); + m_completionListBox->setFocusProxy( m_view->m_viewInternal ); + + m_completionListBox->installEventFilter( this ); + + m_completionPopup->resize(m_completionListBox->sizeHint() + QSize(2,2)); + m_completionPopup->installEventFilter( this ); + m_completionPopup->setFocusProxy( m_view->m_viewInternal ); + + m_pArgHint = new KateArgHint( m_view ); + connect( m_pArgHint, SIGNAL(argHintHidden()), + this, SIGNAL(argHintHidden()) ); + + connect( m_view, SIGNAL(cursorPositionChanged()), + this, SLOT(slotCursorPosChanged()) ); +} + +KateCodeCompletion::~KateCodeCompletion() +{ + delete m_completionPopup; +} + +bool KateCodeCompletion::codeCompletionVisible () { + return m_completionPopup->isVisible(); +} + +void KateCodeCompletion::showCompletionBox( + QValueList<KTextEditor::CompletionEntry> complList, int offset, bool casesensitive ) +{ + kdDebug(13035) << "showCompletionBox " << endl; + + if ( codeCompletionVisible() ) return; + + m_caseSensitive = casesensitive; + m_complList = complList; + m_offset = offset; + m_view->cursorPositionReal( &m_lineCursor, &m_colCursor ); + m_colCursor -= offset; + + updateBox( true ); +} + +bool KateCodeCompletion::eventFilter( QObject *o, QEvent *e ) +{ + if ( o != m_completionPopup && + o != m_completionListBox && + o != m_completionListBox->viewport() ) + return false; + + if( e->type() == QEvent::Hide ) + { + //don't use abortCompletion() as aborting here again will send abort signal + //even on successfull completion we will emit completionAborted() twice... + m_completionPopup->hide(); + delete m_commentLabel; + m_commentLabel = 0; + return false; + } + + + if ( e->type() == QEvent::MouseButtonDblClick ) { + doComplete(); + return false; + } + + if ( e->type() == QEvent::MouseButtonPress ) { + QTimer::singleShot(0, this, SLOT(showComment())); + return false; + } + + return false; +} + +void KateCodeCompletion::handleKey (QKeyEvent *e) +{ + // close completion if you move out of range + if ((e->key() == Key_Up) && (m_completionListBox->currentItem() == 0)) + { + abortCompletion(); + m_view->setFocus(); + return; + } + + // keyboard movement + if( (e->key() == Key_Up) || (e->key() == Key_Down ) || + (e->key() == Key_Home ) || (e->key() == Key_End) || + (e->key() == Key_Prior) || (e->key() == Key_Next )) + { + QTimer::singleShot(0,this,SLOT(showComment())); + QApplication::sendEvent( m_completionListBox, (QEvent*)e ); + return; + } + + // update the box + updateBox(); +} + +void KateCodeCompletion::doComplete() +{ + KateCompletionItem* item = static_cast<KateCompletionItem*>( + m_completionListBox->item(m_completionListBox->currentItem())); + + if( item == 0 ) + return; + + QString text = item->m_entry.text; + QString currentLine = m_view->currentTextLine(); + int len = m_view->cursorColumnReal() - m_colCursor; + QString currentComplText = currentLine.mid(m_colCursor,len); + QString add = text.mid(currentComplText.length()); + if( item->m_entry.postfix == "()" ) + add += "("; + + emit filterInsertString(&(item->m_entry),&add); + m_view->insertText(add); + + complete( item->m_entry ); + m_view->setFocus(); +} + +void KateCodeCompletion::abortCompletion() +{ + m_completionPopup->hide(); + delete m_commentLabel; + m_commentLabel = 0; + emit completionAborted(); +} + +void KateCodeCompletion::complete( KTextEditor::CompletionEntry entry ) +{ + m_completionPopup->hide(); + delete m_commentLabel; + m_commentLabel = 0; + emit completionDone( entry ); + emit completionDone(); +} + +void KateCodeCompletion::updateBox( bool ) +{ + if( m_colCursor > m_view->cursorColumnReal() ) { + // the cursor is too far left + kdDebug(13035) << "Aborting Codecompletion after sendEvent" << endl; + kdDebug(13035) << m_view->cursorColumnReal() << endl; + abortCompletion(); + m_view->setFocus(); + return; + } + + m_completionListBox->clear(); + + QString currentLine = m_view->currentTextLine(); + int len = m_view->cursorColumnReal() - m_colCursor; + QString currentComplText = currentLine.mid(m_colCursor,len); +/* No-one really badly wants those, or? + kdDebug(13035) << "Column: " << m_colCursor << endl; + kdDebug(13035) << "Line: " << currentLine << endl; + kdDebug(13035) << "CurrentColumn: " << m_view->cursorColumnReal() << endl; + kdDebug(13035) << "Len: " << len << endl; + kdDebug(13035) << "Text: '" << currentComplText << "'" << endl; + kdDebug(13035) << "Count: " << m_complList.count() << endl; +*/ + QValueList<KTextEditor::CompletionEntry>::Iterator it; + if( m_caseSensitive ) { + for( it = m_complList.begin(); it != m_complList.end(); ++it ) { + if( (*it).text.startsWith(currentComplText) ) { + new KateCompletionItem(m_completionListBox,*it); + } + } + } else { + currentComplText = currentComplText.upper(); + for( it = m_complList.begin(); it != m_complList.end(); ++it ) { + if( (*it).text.upper().startsWith(currentComplText) ) { + new KateCompletionItem(m_completionListBox,*it); + } + } + } + + if( m_completionListBox->count() == 0 || + ( m_completionListBox->count() == 1 && // abort if we equaled the last item + currentComplText == m_completionListBox->text(0).stripWhiteSpace() ) ) { + abortCompletion(); + m_view->setFocus(); + return; + } + + kdDebug(13035)<<"KateCodeCompletion::updateBox: Resizing widget"<<endl; + m_completionPopup->resize(m_completionListBox->sizeHint() + QSize(2,2)); + QPoint p = m_view->mapToGlobal( m_view->cursorCoordinates() ); + int x = p.x(); + int y = p.y() ; + if ( y + m_completionPopup->height() + m_view->renderer()->config()->fontMetrics( )->height() > QApplication::desktop()->height() ) + y -= (m_completionPopup->height() ); + else + y += m_view->renderer()->config()->fontMetrics( )->height(); + + if (x + m_completionPopup->width() > QApplication::desktop()->width()) + x = QApplication::desktop()->width() - m_completionPopup->width(); + + m_completionPopup->move( QPoint(x,y) ); + + m_completionListBox->setCurrentItem( 0 ); + m_completionListBox->setSelected( 0, true ); + m_completionListBox->setFocus(); + m_completionPopup->show(); + + QTimer::singleShot(0,this,SLOT(showComment())); +} + +void KateCodeCompletion::showArgHint ( QStringList functionList, const QString& strWrapping, const QString& strDelimiter ) +{ + unsigned int line, col; + m_view->cursorPositionReal( &line, &col ); + m_pArgHint->reset( line, col ); + m_pArgHint->setArgMarkInfos( strWrapping, strDelimiter ); + + int nNum = 0; + QStringList::Iterator end(functionList.end()); + for( QStringList::Iterator it = functionList.begin(); it != end; ++it ) + { + kdDebug(13035) << "Insert function text: " << *it << endl; + + m_pArgHint->addFunction( nNum, ( *it ) ); + + nNum++; + } + + m_pArgHint->move(m_view->mapToGlobal(m_view->cursorCoordinates() + QPoint(0,m_view->renderer()->config()->fontMetrics( )->height())) ); + m_pArgHint->show(); +} + +void KateCodeCompletion::slotCursorPosChanged() +{ + m_pArgHint->cursorPositionChanged ( m_view, m_view->cursorLine(), m_view->cursorColumnReal() ); +} + +void KateCodeCompletion::showComment() +{ + if (!m_completionPopup->isVisible()) + return; + + KateCompletionItem* item = static_cast<KateCompletionItem*>(m_completionListBox->item(m_completionListBox->currentItem())); + + if( !item ) + return; + + if( item->m_entry.comment.isEmpty() ) + return; + + delete m_commentLabel; + m_commentLabel = new KateCodeCompletionCommentLabel( 0, item->m_entry.comment ); + m_commentLabel->setFont(QToolTip::font()); + m_commentLabel->setPalette(QToolTip::palette()); + + QPoint rightPoint = m_completionPopup->mapToGlobal(QPoint(m_completionPopup->width(),0)); + QPoint leftPoint = m_completionPopup->mapToGlobal(QPoint(0,0)); + QRect screen = QApplication::desktop()->screenGeometry ( m_commentLabel ); + QPoint finalPoint; + if (rightPoint.x()+m_commentLabel->width() > screen.x() + screen.width()) + finalPoint.setX(leftPoint.x()-m_commentLabel->width()); + else + finalPoint.setX(rightPoint.x()); + + m_completionListBox->ensureCurrentVisible(); + + finalPoint.setY( + m_completionListBox->viewport()->mapToGlobal(m_completionListBox->itemRect( + m_completionListBox->item(m_completionListBox->currentItem())).topLeft()).y()); + + m_commentLabel->move(finalPoint); + m_commentLabel->show(); +} + +KateArgHint::KateArgHint( KateView* parent, const char* name ) + : QFrame( parent, name, WType_Popup ) +{ + setBackgroundColor( black ); + setPaletteForegroundColor( Qt::black ); + + labelDict.setAutoDelete( true ); + layout = new QVBoxLayout( this, 1, 2 ); + layout->setAutoAdd( true ); + editorView = parent; + + m_markCurrentFunction = true; + + setFocusPolicy( StrongFocus ); + setFocusProxy( parent ); + + reset( -1, -1 ); +} + +KateArgHint::~KateArgHint() +{ +} + +void KateArgHint::setArgMarkInfos( const QString& wrapping, const QString& delimiter ) +{ + m_wrapping = wrapping; + m_delimiter = delimiter; + m_markCurrentFunction = true; +} + +void KateArgHint::reset( int line, int col ) +{ + m_functionMap.clear(); + m_currentFunction = -1; + labelDict.clear(); + + m_currentLine = line; + m_currentCol = col - 1; +} + +void KateArgHint::slotDone(bool completed) +{ + hide(); + + m_currentLine = m_currentCol = -1; + + emit argHintHidden(); + if (completed) + emit argHintCompleted(); + else + emit argHintAborted(); +} + +void KateArgHint::cursorPositionChanged( KateView* view, int line, int col ) +{ + if( m_currentCol == -1 || m_currentLine == -1 ){ + slotDone(false); + return; + } + + int nCountDelimiter = 0; + int count = 0; + + QString currentTextLine = view->doc()->textLine( line ); + QString text = currentTextLine.mid( m_currentCol, col - m_currentCol ); + QRegExp strconst_rx( "\"[^\"]*\"" ); + QRegExp chrconst_rx( "'[^']*'" ); + + text = text + .replace( strconst_rx, "\"\"" ) + .replace( chrconst_rx, "''" ); + + int index = 0; + while( index < (int)text.length() ){ + if( text[index] == m_wrapping[0] ){ + ++count; + } else if( text[index] == m_wrapping[1] ){ + --count; + } else if( count > 0 && text[index] == m_delimiter[0] ){ + ++nCountDelimiter; + } + ++index; + } + + if( (m_currentLine > 0 && m_currentLine != line) || (m_currentLine < col) || (count == 0) ){ + slotDone(count == 0); + return; + } + + // setCurArg ( nCountDelimiter + 1 ); + +} + +void KateArgHint::addFunction( int id, const QString& prot ) +{ + m_functionMap[ id ] = prot; + QLabel* label = new QLabel( prot.stripWhiteSpace().simplifyWhiteSpace(), this ); + label->setBackgroundColor( QColor(255, 255, 238) ); + label->show(); + labelDict.insert( id, label ); + + if( m_currentFunction < 0 ) + setCurrentFunction( id ); +} + +void KateArgHint::setCurrentFunction( int currentFunction ) +{ + if( m_currentFunction != currentFunction ){ + + if( currentFunction < 0 ) + currentFunction = (int)m_functionMap.size() - 1; + + if( currentFunction > (int)m_functionMap.size()-1 ) + currentFunction = 0; + + if( m_markCurrentFunction && m_currentFunction >= 0 ){ + QLabel* label = labelDict[ m_currentFunction ]; + label->setFont( font() ); + } + + m_currentFunction = currentFunction; + + if( m_markCurrentFunction ){ + QLabel* label = labelDict[ currentFunction ]; + QFont fnt( font() ); + fnt.setBold( true ); + label->setFont( fnt ); + } + + adjustSize(); + } +} + +void KateArgHint::show() +{ + QFrame::show(); + adjustSize(); +} + +bool KateArgHint::eventFilter( QObject*, QEvent* e ) +{ + if( isVisible() && e->type() == QEvent::KeyPress ){ + QKeyEvent* ke = static_cast<QKeyEvent*>( e ); + if( (ke->state() & ControlButton) && ke->key() == Key_Left ){ + setCurrentFunction( currentFunction() - 1 ); + ke->accept(); + return true; + } else if( ke->key() == Key_Escape ){ + slotDone(false); + return false; + } else if( (ke->state() & ControlButton) && ke->key() == Key_Right ){ + setCurrentFunction( currentFunction() + 1 ); + ke->accept(); + return true; + } + } + + return false; +} + +void KateArgHint::adjustSize( ) +{ + QRect screen = QApplication::desktop()->screenGeometry( pos() ); + + QFrame::adjustSize(); + if( width() > screen.width() ) + resize( screen.width(), height() ); + + if( x() + width() > screen.x() + screen.width() ) + move( screen.x() + screen.width() - width(), y() ); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; |