00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "katecompletionwidget.h"
00021
00022 #include <QtGui/QBoxLayout>
00023 #include <QtGui/QApplication>
00024 #include <QtGui/QDesktopWidget>
00025 #include <QtGui/QHeaderView>
00026 #include <QtCore/QTimer>
00027 #include <QtGui/QLabel>
00028 #include <QtGui/QToolButton>
00029 #include <QtGui/QSizeGrip>
00030 #include <QtGui/QPushButton>
00031 #include <QtGui/QAbstractScrollArea>
00032 #include <QtGui/QScrollBar>
00033 #include <QtCore/QMutex>
00034
00035 #include <kicon.h>
00036 #include <kdialog.h>
00037
00038 #include <ktexteditor/cursorfeedback.h>
00039 #include <ktexteditor/codecompletionmodelcontrollerinterface.h>
00040
00041 #include "kateview.h"
00042 #include "katesmartmanager.h"
00043 #include "katerenderer.h"
00044 #include "kateconfig.h"
00045 #include "katedocument.h"
00046 #include "katesmartrange.h"
00047 #include "kateedit.h"
00048
00049 #include "katecompletionmodel.h"
00050 #include "katecompletiontree.h"
00051 #include "katecompletionconfig.h"
00052 #include "kateargumenthinttree.h"
00053 #include "kateargumenthintmodel.h"
00054
00055
00056
00057 const bool hideAutomaticCompletionOnExactMatch = true;
00058
00059 KTextEditor::CodeCompletionModelControllerInterface* modelController(KTextEditor::CodeCompletionModel *model)
00060 {
00061 static KTextEditor::CodeCompletionModelControllerInterface defaultIf;
00062 KTextEditor::CodeCompletionModelControllerInterface* ret =
00063 qobject_cast<KTextEditor::CodeCompletionModelControllerInterface*>(model);
00064 if (!ret) {
00065 ret = &defaultIf;
00066 }
00067 return ret;
00068 }
00069
00070
00071 KateCompletionWidget::KateCompletionWidget(KateView* parent)
00072 : QFrame(parent, Qt::ToolTip)
00073 , m_presentationModel(new KateCompletionModel(this))
00074 , m_entryList(new KateCompletionTree(this))
00075 , m_argumentHintModel(new KateArgumentHintModel(this))
00076 , m_argumentHintTree(new KateArgumentHintTree(this))
00077 , m_automaticInvocationDelay(300)
00078 , m_filterInstalled(false)
00079 , m_configWidget(new KateCompletionConfig(m_presentationModel, view()))
00080 , m_lastInsertionByUser(false)
00081 , m_inCompletionList(false)
00082 , m_isSuspended(false)
00083 , m_dontShowArgumentHints(false)
00084 , m_needShow(false)
00085 , m_hadCompletionNavigation(false)
00086 , m_expandedAddedHeightBase(0)
00087 , m_lastInvocationType(KTextEditor::CodeCompletionModel::AutomaticInvocation)
00088 {
00089 connect(parent, SIGNAL(navigateAccept()), SLOT(navigateAccept()));
00090 connect(parent, SIGNAL(navigateBack()), SLOT(navigateBack()));
00091 connect(parent, SIGNAL(navigateDown()), SLOT(navigateDown()));
00092 connect(parent, SIGNAL(navigateLeft()), SLOT(navigateLeft()));
00093 connect(parent, SIGNAL(navigateRight()), SLOT(navigateRight()));
00094 connect(parent, SIGNAL(navigateUp()), SLOT(navigateUp()));
00095
00096 qRegisterMetaType<KTextEditor::Cursor>("KTextEditor::Cursor");
00097
00098 setFrameStyle( QFrame::Box | QFrame::Plain );
00099 setLineWidth( 1 );
00100
00101
00102 m_entryList->setModel(m_presentationModel);
00103 m_entryList->setColumnWidth(0, 0);
00104 m_entryList->setColumnWidth(1, 0);
00105 m_entryList->setColumnWidth(2, 0);
00106
00107 m_entryList->setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
00108
00109 m_argumentHintTree->setParent(0, Qt::ToolTip);
00110 m_argumentHintTree->setModel(m_argumentHintModel);
00111
00112 connect(m_entryList->verticalScrollBar(), SIGNAL(valueChanged(int)), m_presentationModel, SLOT(placeExpandingWidgets()));
00113 connect(m_argumentHintTree->verticalScrollBar(), SIGNAL(valueChanged(int)), m_argumentHintModel, SLOT(placeExpandingWidgets()));
00114 connect(view(), SIGNAL(focusOut(KTextEditor::View*)), this, SLOT(viewFocusOut()));
00115
00116 m_automaticInvocationTimer = new QTimer(this);
00117 m_automaticInvocationTimer->setSingleShot(true);
00118 connect(m_automaticInvocationTimer, SIGNAL(timeout()), this, SLOT(automaticInvocation()));
00119
00120
00121
00122
00123
00124
00125 connect(m_presentationModel, SIGNAL(modelReset()), this, SLOT(modelReset()));
00126 connect(m_presentationModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), SLOT(rowsInserted(const QModelIndex&, int, int)));
00127 connect(m_argumentHintModel, SIGNAL(contentStateChanged(bool)), this, SLOT(argumentHintsChanged(bool)));
00128
00129
00130 connect(view(), SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(cursorPositionChanged()), Qt::QueuedConnection);
00131 connect(view()->doc()->history(), SIGNAL(editDone(KateEditInfo*)), SLOT(editDone(KateEditInfo*)));
00132 connect(view(), SIGNAL(verticalScrollPositionChanged (KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(updatePositionSlot()), Qt::QueuedConnection);
00133
00134
00135
00136
00137 setFocusPolicy(Qt::ClickFocus);
00138 m_argumentHintTree->setFocusPolicy(Qt::ClickFocus);
00139
00140 foreach (QWidget* childWidget, findChildren<QWidget*>())
00141 childWidget->setFocusPolicy(Qt::NoFocus);
00142
00143
00144 m_entryList->move(frameWidth(), frameWidth());
00145 }
00146
00147 KateCompletionWidget::~KateCompletionWidget() {
00148 }
00149
00150 void KateCompletionWidget::viewFocusOut() {
00151 abortCompletion();
00152 }
00153
00154 void KateCompletionWidget::modelContentChanged() {
00155 if(m_completionRanges.isEmpty()) {
00156 kDebug( 13035 ) << "content changed, but no completion active";
00157 abortCompletion();
00158 return;
00159 }
00160
00161 if(!view()->hasFocus()) {
00162 kDebug( 13035 ) << "view does not have focus";
00163 return;
00164 }
00165
00166 if(!m_waitingForReset.isEmpty()) {
00167 kDebug( 13035 ) << "waiting for" << m_waitingForReset.size() << "completion-models to reset";
00168 return;
00169 }
00170
00171 int realItemCount = 0;
00172 foreach (KTextEditor::CodeCompletionModel* model, m_presentationModel->completionModels())
00173 realItemCount += model->rowCount();
00174 if( !m_isSuspended && ((isHidden() && m_argumentHintTree->isHidden()) || m_needShow) && realItemCount != 0 ) {
00175 m_needShow = false;
00176 updateAndShow();
00177 }
00178
00179 if(m_argumentHintModel->rowCount(QModelIndex()) == 0)
00180 m_argumentHintTree->hide();
00181
00182 if(m_presentationModel->rowCount(QModelIndex()) == 0)
00183 hide();
00184
00185
00186
00187 m_entryList->setCurrentIndex(model()->index(0,0));
00188 if(!model()->indexIsItem(m_entryList->currentIndex())) {
00189 QModelIndex firstIndex = model()->index(0,0, m_entryList->currentIndex());
00190 m_entryList->setCurrentIndex(firstIndex);
00191
00192 }
00193
00194 updateHeight();
00195
00196
00197 if( m_argumentHintTree->isHidden() && !m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0 )
00198 m_argumentHintTree->show();
00199
00200 if(hideAutomaticCompletionOnExactMatch && !isHidden() &&
00201 m_lastInvocationType == KTextEditor::CodeCompletionModel::AutomaticInvocation &&
00202 m_presentationModel->shouldMatchHideCompletionList())
00203 hide();
00204 else if(isHidden() && !m_presentationModel->shouldMatchHideCompletionList() &&
00205 m_presentationModel->rowCount(QModelIndex()))
00206 show();
00207 }
00208
00209 KateArgumentHintTree* KateCompletionWidget::argumentHintTree() const {
00210 return m_argumentHintTree;
00211 }
00212
00213 KateArgumentHintModel* KateCompletionWidget::argumentHintModel() const {
00214 return m_argumentHintModel;
00215 }
00216
00217 const KateCompletionModel* KateCompletionWidget::model() const {
00218 return m_presentationModel;
00219 }
00220
00221 KateCompletionModel* KateCompletionWidget::model() {
00222 return m_presentationModel;
00223 }
00224
00225 void KateCompletionWidget::rowsInserted(const QModelIndex& parent, int rowFrom, int rowEnd)
00226 {
00227 m_entryList->setAnimated(false);
00228 if(!model()->isGroupingEnabled())
00229 return;
00230
00231 if (!parent.isValid())
00232 for (int i = rowFrom; i <= rowEnd; ++i)
00233 m_entryList->expand(m_presentationModel->index(i, 0, parent));
00234 }
00235
00236 KateView * KateCompletionWidget::view( ) const
00237 {
00238 return static_cast<KateView*>(const_cast<QObject*>(parent()));
00239 }
00240
00241 void KateCompletionWidget::argumentHintsChanged(bool hasContent)
00242 {
00243 m_dontShowArgumentHints = !hasContent;
00244
00245 if( m_dontShowArgumentHints )
00246 m_argumentHintTree->hide();
00247 else
00248 updateArgumentHintGeometry();
00249 }
00250
00251 void KateCompletionWidget::startCompletion(KTextEditor::CodeCompletionModel::InvocationType invocationType)
00252 {
00253 if(invocationType == KTextEditor::CodeCompletionModel::UserInvocation)
00254 abortCompletion();
00255 startCompletion(KTextEditor::Range(KTextEditor::Cursor(-1, -1), KTextEditor::Cursor(-1, -1)), 0, invocationType);
00256 }
00257
00258
00259 void KateCompletionWidget::deleteCompletionRanges()
00260 {
00261 foreach(CompletionRange r, m_completionRanges)
00262 delete r.range;
00263 m_completionRanges.clear();
00264 }
00265
00266 void KateCompletionWidget::startCompletion(const KTextEditor::Range& word, KTextEditor::CodeCompletionModel* model, KTextEditor::CodeCompletionModel::InvocationType invocationType)
00267 {
00268 m_isSuspended = false;
00269 m_inCompletionList = true;
00270 m_needShow = true;
00271
00272 m_lastInvocationType = invocationType;
00273
00274 disconnect(this->model(), SIGNAL(contentGeometryChanged()), this, SLOT(modelContentChanged()));
00275
00276 m_dontShowArgumentHints = true;
00277
00278 QList<KTextEditor::CodeCompletionModel*> models;
00279 if (model) {
00280 models << model;
00281 } else {
00282 models = m_sourceModels;
00283 }
00284
00285 foreach(KTextEditor::CodeCompletionModel* model, m_completionRanges.keys())
00286 if(!models.contains(model))
00287 models << model;
00288
00289 if (!m_filterInstalled) {
00290 if (!QApplication::activeWindow()) {
00291 kWarning(13035) << "No active window to install event filter on!!";
00292 return;
00293 }
00294
00295 QApplication::activeWindow()->installEventFilter(this);
00296 m_filterInstalled = true;
00297 }
00298
00299 m_presentationModel->clearCompletionModels();
00300
00301 if(invocationType == KTextEditor::CodeCompletionModel::UserInvocation) {
00302 QMutexLocker lock(view()->doc()->smartMutex());
00303 deleteCompletionRanges();
00304 }
00305
00306 foreach (KTextEditor::CodeCompletionModel* model, models) {
00307 KTextEditor::Range range;
00308 if (word.isValid()) {
00309 range = word;
00310 } else {
00311 range = modelController(model)->completionRange(view(), view()->cursorPosition());
00312 }
00313 if(!range.isValid()) {
00314 if(m_completionRanges.contains(model)) {
00315 QMutexLocker lock(view()->doc()->smartMutex());
00316 KTextEditor::SmartRange *oldRange = m_completionRanges[model].range;
00317 m_completionRanges.remove(model);
00318 delete oldRange;
00319 }
00320 models.removeAll(model);
00321 continue;
00322 }
00323 if(m_completionRanges.contains(model)) {
00324 if(*m_completionRanges[model].range == range) {
00325 continue;
00326 }
00327 else {
00328 QMutexLocker lock(view()->doc()->smartMutex());
00329 KTextEditor::SmartRange *oldRange = m_completionRanges[model].range;
00330 m_completionRanges.remove(model);
00331 delete oldRange;
00332 }
00333 }
00334
00335 connect(model, SIGNAL(waitForReset()), this, SLOT(waitForModelReset()));
00336
00337 model->completionInvoked(view(), range, invocationType);
00338
00339 disconnect(model, SIGNAL(waitForReset()), this, SLOT(waitForModelReset()));
00340
00341 QMutexLocker lock(view()->doc()->smartMutex());
00342
00343 m_completionRanges[model] = view()->doc()->smartManager()->newSmartRange(range);
00344 m_completionRanges[model].range->setInsertBehavior(KTextEditor::SmartRange::ExpandRight | KTextEditor::SmartRange::ExpandLeft);
00345
00346
00347 m_completionRanges[model].leftBoundary = view()->cursorPosition();
00348
00349
00350 if(invocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation)
00351 if(range.start() < m_completionRanges[model].leftBoundary)
00352 m_completionRanges[model].leftBoundary = range.start();
00353
00354 if(!m_completionRanges[model].range->isValid()) {
00355 kWarning(13035) << "Could not construct valid smart-range from" << range << "instead got" << *m_completionRanges[model].range;
00356 lock.unlock();
00357 abortCompletion();
00358 return;
00359 }
00360
00361 lock.unlock();
00362
00363 connect(m_completionRanges[model].range->smartStart().notifier(), SIGNAL(characterDeleted(KTextEditor::SmartCursor*, bool)),
00364 SLOT(startCharacterDeleted(KTextEditor::SmartCursor*, bool)));
00365 }
00366
00367 m_presentationModel->setCompletionModels(models);
00368
00369 cursorPositionChanged();
00370
00371 if (!m_completionRanges.isEmpty()) {
00372 connect(this->model(), SIGNAL(contentGeometryChanged()), this, SLOT(modelContentChanged()));
00373
00374 modelContentChanged();
00375 }
00376 else {
00377 abortCompletion();
00378 }
00379 }
00380
00381 void KateCompletionWidget::waitForModelReset()
00382 {
00383 KTextEditor::CodeCompletionModel* senderModel = qobject_cast<KTextEditor::CodeCompletionModel*>(sender());
00384 if(!senderModel) {
00385 kWarning() << "waitForReset signal from bad model";
00386 return;
00387 }
00388 m_waitingForReset.insert(senderModel);
00389 }
00390
00391 void KateCompletionWidget::updateAndShow()
00392 {
00393 if(!view()->hasFocus()) {
00394 kDebug( 13035 ) << "view does not have focus";
00395 return;
00396 }
00397
00398 setUpdatesEnabled(false);
00399
00400 modelReset();
00401
00402 m_argumentHintModel->buildRows();
00403 if( m_argumentHintModel->rowCount(QModelIndex()) != 0 )
00404 argumentHintsChanged(true);
00405
00406
00407
00408
00409
00410 updatePosition(true);
00411 m_entryList->resizeColumns(false, true, true);
00412 updatePosition(true);
00413 m_entryList->resizeColumns(false, true, true);
00414
00415 setUpdatesEnabled(true);
00416
00417 if(m_argumentHintModel->rowCount(QModelIndex())) {
00418 updateArgumentHintGeometry();
00419 m_argumentHintTree->show();
00420 } else
00421 m_argumentHintTree->hide();
00422
00423 if (m_presentationModel->rowCount() && (!m_presentationModel->shouldMatchHideCompletionList() ||
00424 !hideAutomaticCompletionOnExactMatch ||
00425 m_lastInvocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation) )
00426 show();
00427 else
00428 hide();
00429 }
00430
00431 void KateCompletionWidget::updatePositionSlot()
00432 {
00433 updatePosition();
00434 }
00435
00436 bool KateCompletionWidget::updatePosition(bool force)
00437 {
00438 if (!force && !isCompletionActive())
00439 return false;
00440
00441 if (!completionRange()) {
00442 return false;
00443 }
00444 QPoint cursorPosition = view()->cursorToCoordinate(completionRange()->start());
00445 if (cursorPosition == QPoint(-1,-1)) {
00446
00447 abortCompletion();
00448 return false;
00449 }
00450
00451 QPoint p = view()->mapToGlobal( cursorPosition );
00452 int x = p.x() - m_entryList->columnTextViewportPosition(m_presentationModel->translateColumn(KTextEditor::CodeCompletionModel::Name)) - 4 - (m_entryList->viewport()->pos().x());
00453 int y = p.y();
00454
00455
00456
00457
00458 y += view()->renderer()->config()->fontMetrics().height();
00459
00460 bool borderHit = false;
00461
00462 if (x + width() > QApplication::desktop()->screenGeometry(view()).right()) {
00463 x = QApplication::desktop()->screenGeometry(view()).right() - width();
00464 borderHit = true;
00465 }
00466
00467 if( x < QApplication::desktop()->screenGeometry(view()).left() ) {
00468 x = QApplication::desktop()->screenGeometry(view()).left();
00469 borderHit = true;
00470 }
00471
00472 move( QPoint(x,y) );
00473
00474 updateHeight();
00475
00476 updateArgumentHintGeometry();
00477
00478
00479
00480 return borderHit;
00481 }
00482
00483 void KateCompletionWidget::updateArgumentHintGeometry()
00484 {
00485 if( !m_dontShowArgumentHints ) {
00486
00487 QRect geom = m_argumentHintTree->geometry();
00488 geom.moveTo(pos());
00489 geom.setWidth(width());
00490 geom.moveBottom(pos().y() - view()->renderer()->config()->fontMetrics().height()*2);
00491 m_argumentHintTree->updateGeometry(geom);
00492 }
00493 }
00494
00495
00496 bool hasAtLeastNRows(int rows, QAbstractItemModel* model) {
00497 int count = 0;
00498 for(int row = 0; row < model->rowCount(); ++row) {
00499 ++count;
00500
00501 QModelIndex index(model->index(row, 0));
00502 if(index.isValid())
00503 count += model->rowCount(index);
00504
00505 if(count > rows)
00506 return true;
00507 }
00508 return false;
00509 }
00510
00511 void KateCompletionWidget::updateHeight()
00512 {
00513 QRect geom = geometry();
00514
00515 int minBaseHeight = 10;
00516 int maxBaseHeight = 300;
00517
00518 int baseHeight = 0;
00519 int calculatedCustomHeight = 0;
00520
00521 if(hasAtLeastNRows(15, m_presentationModel)) {
00522
00523 baseHeight = maxBaseHeight;
00524 }else{
00525
00526 for(int row = 0; row < m_presentationModel->rowCount(); ++row) {
00527 baseHeight += treeView()->sizeHintForRow(row);
00528
00529 QModelIndex index(m_presentationModel->index(row, 0));
00530 if(index.isValid()) {
00531 for(int row2 = 0; row2 < m_presentationModel->rowCount(index); ++row2) {
00532 int h = 0;
00533 for(int a = 0; a < m_presentationModel->columnCount(index); ++a) {
00534 int localHeight = treeView()->sizeHintForIndex(index.child(row2, a)).height();
00535 if(localHeight > h)
00536 h = localHeight;
00537 }
00538 baseHeight += h;
00539 if(baseHeight > maxBaseHeight)
00540 break;
00541 }
00542
00543 if(baseHeight > maxBaseHeight)
00544 break;
00545 }
00546 }
00547
00548 calculatedCustomHeight = baseHeight;
00549 }
00550
00551 baseHeight += 2*frameWidth();
00552
00553 if(m_entryList->horizontalScrollBar()->isVisible())
00554 baseHeight += m_entryList->horizontalScrollBar()->height();
00555
00556
00557 if(baseHeight < minBaseHeight)
00558 baseHeight = minBaseHeight;
00559 if(baseHeight > maxBaseHeight) {
00560 baseHeight = maxBaseHeight;
00561 m_entryList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
00562 }else{
00563
00564
00565 m_entryList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
00566 }
00567
00568 int newExpandingAddedHeight = 0;
00569
00570 if(baseHeight == maxBaseHeight && model()->expandingWidgetsHeight()) {
00571
00572 if(calculatedCustomHeight && calculatedCustomHeight > baseHeight && calculatedCustomHeight < (maxBaseHeight + model()->expandingWidgetsHeight()))
00573 newExpandingAddedHeight = calculatedCustomHeight - baseHeight;
00574 else
00575 newExpandingAddedHeight = model()->expandingWidgetsHeight();
00576 }
00577
00578 if( m_expandedAddedHeightBase != baseHeight && m_expandedAddedHeightBase - baseHeight > -2 && m_expandedAddedHeightBase - baseHeight < 2 )
00579 {
00580
00581
00582
00583 baseHeight = m_expandedAddedHeightBase;
00584 }
00585
00586 int screenBottom = QApplication::desktop()->screenGeometry(view()).bottom();
00587
00588
00589 int bottomPosition = baseHeight + newExpandingAddedHeight + geometry().top();
00590
00591 if( bottomPosition > screenBottom ) {
00592 newExpandingAddedHeight -= bottomPosition - (screenBottom);
00593 }
00594
00595 int finalHeight = baseHeight+newExpandingAddedHeight;
00596
00597 if( finalHeight < 10 ) {
00598 m_entryList->resize(m_entryList->width(), height() - 2*frameWidth());
00599 return;
00600 }
00601
00602 m_expandedAddedHeightBase = geometry().height();
00603
00604 geom.setHeight(finalHeight);
00605
00606
00607 m_entryList->setScrollingEnabled(false);
00608
00609 if(geometry() != geom)
00610 setGeometry(geom);
00611
00612 QSize entryListSize = QSize(m_entryList->width(), finalHeight - 2*frameWidth());
00613 if(m_entryList->size() != entryListSize)
00614 m_entryList->resize(entryListSize);
00615
00616
00617 m_entryList->setScrollingEnabled(true);
00618 }
00619
00620 void KateCompletionWidget::cursorPositionChanged( )
00621 {
00622 if (m_completionRanges.isEmpty())
00623 return;
00624
00625 KTextEditor::Cursor cursor = view()->cursorPosition();
00626
00627 QList<KTextEditor::CodeCompletionModel*> checkCompletionRanges = m_completionRanges.keys();
00628
00629 QMutexLocker lock(view()->doc()->smartMutex());
00630 lock.unlock();
00631
00632
00633 for(QList<KTextEditor::CodeCompletionModel*>::iterator it = checkCompletionRanges.begin();
00634 it != checkCompletionRanges.end(); ++it) {
00635 if(!m_completionRanges.contains(*it))
00636 continue;
00637
00638 KTextEditor::CodeCompletionModel *model = *it;
00639 KateSmartRange* range = m_completionRanges[*it].range;
00640
00641 modelController(model)->updateCompletionRange(view(), *range);
00642 QString currentCompletion = modelController(model)->filterString(view(), *range, view()->cursorPosition());
00643 bool abort = modelController(model)->shouldAbortCompletion(view(), *range, currentCompletion);
00644
00645 if(view()->cursorPosition() < m_completionRanges[*it].leftBoundary) {
00646 kDebug() << "aborting because of boundary";
00647 abort = true;
00648 }
00649
00650 if(!m_completionRanges.contains(*it))
00651 continue;
00652
00653 if (abort) {
00654 if (m_completionRanges.count() == 1) {
00655
00656 abortCompletion();
00657 return;
00658 } else {
00659 {
00660 lock.relock();
00661 delete m_completionRanges[*it].range;
00662 m_completionRanges.remove(*it);
00663 lock.unlock();
00664 }
00665
00666 modelController(model)->aborted(view());
00667 m_presentationModel->removeCompletionModel(model);
00668 }
00669 } else {
00670 m_presentationModel->setCurrentCompletion(model, currentCompletion);
00671 }
00672 }
00673
00674 m_entryList->scheduleUpdate();
00675 }
00676
00677 bool KateCompletionWidget::isCompletionActive( ) const
00678 {
00679 return !m_completionRanges.isEmpty() && ((!isHidden() && isVisible()) || (!m_argumentHintTree->isHidden() && m_argumentHintTree->isVisible()));
00680 }
00681
00682 void KateCompletionWidget::abortCompletion( )
00683 {
00684 kDebug(13035) ;
00685
00686 m_isSuspended = false;
00687
00688 bool wasActive = isCompletionActive();
00689
00690 clear();
00691
00692 if(!isHidden())
00693 hide();
00694 if(!m_argumentHintTree->isHidden())
00695 m_argumentHintTree->hide();
00696
00697 if (wasActive)
00698 view()->sendCompletionAborted();
00699 }
00700
00701 void KateCompletionWidget::clear() {
00702 m_presentationModel->clearCompletionModels();
00703 m_argumentHintTree->clearCompletion();
00704 m_argumentHintModel->clear();
00705
00706 foreach(KTextEditor::CodeCompletionModel* model, m_completionRanges.keys())
00707 modelController(model)->aborted(view());
00708
00709 QMutexLocker lock(view()->doc()->smartMutex());
00710
00711 deleteCompletionRanges();
00712 }
00713
00714 bool KateCompletionWidget::navigateAccept() {
00715 m_hadCompletionNavigation = true;
00716
00717 if(currentEmbeddedWidget())
00718 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetAccept");
00719
00720 QModelIndex index = selectedIndex();
00721 if( index.isValid() ) {
00722 index.data(KTextEditor::CodeCompletionModel::AccessibilityAccept);
00723 return true;
00724 }
00725 return false;
00726 }
00727
00728 void KateCompletionWidget::execute()
00729 {
00730 kDebug(13035) ;
00731
00732 if (!isCompletionActive())
00733 return;
00734
00735 QModelIndex index = selectedIndex();
00736
00737 if (!index.isValid())
00738 return abortCompletion();
00739
00740 QModelIndex toExecute;
00741
00742 if(index.model() == m_presentationModel)
00743 toExecute = m_presentationModel->mapToSource(index);
00744 else
00745 toExecute = m_argumentHintModel->mapToSource(index);
00746
00747 if (!toExecute.isValid()) {
00748 kWarning() << k_funcinfo << "Could not map index" << m_entryList->selectionModel()->currentIndex() << "to source index.";
00749 return abortCompletion();
00750 }
00751
00752
00753 view()->doc()->editStart(true, Kate::CodeCompletionEdit);
00754
00755 KTextEditor::SmartCursor* oldPos = view()->doc()->smartManager()->newSmartCursor(view()->cursorPosition(), KTextEditor::SmartCursor::StayOnInsert);
00756
00757 KTextEditor::CodeCompletionModel* model = static_cast<KTextEditor::CodeCompletionModel*>(const_cast<QAbstractItemModel*>(toExecute.model()));
00758 Q_ASSERT(model);
00759
00760 KTextEditor::CodeCompletionModel2* model2 = qobject_cast<KTextEditor::CodeCompletionModel2*>(model);
00761
00762 Q_ASSERT(m_completionRanges.contains(model));
00763 KTextEditor::Cursor start = m_completionRanges[model].range->start();
00764
00765
00766
00767 view()->doc()->smartMutex()->unlock();
00768
00769 if(model2)
00770 model2->executeCompletionItem2(view()->document(), *m_completionRanges[model].range, toExecute);
00771 else if(toExecute.parent().isValid())
00772
00773 view()->document()->replaceText(*m_completionRanges[model].range, model->data(toExecute.sibling(toExecute.row(), KTextEditor::CodeCompletionModel::Name)).toString());
00774 else
00775 model->executeCompletionItem(view()->document(), *m_completionRanges[model].range, toExecute.row());
00776
00777
00778 view()->doc()->smartMutex()->lock();
00779
00780 view()->doc()->editEnd();
00781
00782 abortCompletion();
00783
00784 view()->sendCompletionExecuted(start, model, toExecute);
00785
00786 KTextEditor::Cursor newPos = view()->cursorPosition();
00787
00788 if(newPos > *oldPos) {
00789 m_automaticInvocationAt = newPos;
00790 m_automaticInvocationLine = view()->doc()->text(KTextEditor::Range(*oldPos, newPos));
00791 kDebug() << "executed, starting automatic invocation with line" << m_automaticInvocationLine;
00792 m_lastInsertionByUser = false;
00793 m_automaticInvocationTimer->start();
00794 }
00795
00796 view()->doc()->smartMutex()->lock();
00797 delete oldPos;
00798 view()->doc()->smartMutex()->unlock();
00799 }
00800
00801 void KateCompletionWidget::resizeEvent( QResizeEvent * event )
00802 {
00803 QFrame::resizeEvent(event);
00804 }
00805
00806 void KateCompletionWidget::showEvent ( QShowEvent * event )
00807 {
00808 m_isSuspended = false;
00809
00810 QFrame::showEvent(event);
00811
00812 if( !m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0 )
00813 m_argumentHintTree->show();
00814 }
00815
00816 KateSmartRange * KateCompletionWidget::completionRange(KTextEditor::CodeCompletionModel* model) const
00817 {
00818 if (!model) {
00819 if (m_completionRanges.isEmpty()) return 0;
00820
00821 return m_completionRanges.begin()->range;
00822 }
00823 if(m_completionRanges.contains(model))
00824 return m_completionRanges[model].range;
00825 else
00826 return 0;
00827 }
00828
00829 QMap<KTextEditor::CodeCompletionModel*, KateCompletionWidget::CompletionRange> KateCompletionWidget::completionRanges( ) const
00830 {
00831 return m_completionRanges;
00832 }
00833
00834 void KateCompletionWidget::modelReset( )
00835 {
00836 setUpdatesEnabled(false);
00837 m_entryList->setAnimated(false);
00838 m_argumentHintTree->setAnimated(false);
00841 for(int row = 0; row < m_argumentHintModel->rowCount(QModelIndex()); ++row) {
00842 QModelIndex index(m_argumentHintModel->index(row, 0, QModelIndex()));
00843 if(!m_argumentHintTree->isExpanded(index)) {
00844 m_argumentHintTree->expand(index);
00845 }
00846 }
00847
00848 for(int row = 0; row < m_entryList->model()->rowCount(QModelIndex()); ++row) {
00849 QModelIndex index(m_entryList->model()->index(row, 0, QModelIndex()));
00850 if(!m_entryList->isExpanded(index)) {
00851 m_entryList->expand(index);
00852 }
00853 }
00854 setUpdatesEnabled(true);
00855 }
00856
00857 KateCompletionTree* KateCompletionWidget::treeView() const {
00858 return m_entryList;
00859 }
00860
00861 QModelIndex KateCompletionWidget::selectedIndex() const {
00862 if(!isCompletionActive())
00863 return QModelIndex();
00864
00865 if( m_inCompletionList )
00866 return m_entryList->currentIndex();
00867 else
00868 return m_argumentHintTree->currentIndex();
00869 }
00870
00871 bool KateCompletionWidget::navigateLeft() {
00872 m_hadCompletionNavigation = true;
00873 if(currentEmbeddedWidget())
00874 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetLeft");
00875
00876 QModelIndex index = selectedIndex();
00877
00878 if( index.isValid() ) {
00879 index.data(KTextEditor::CodeCompletionModel::AccessibilityPrevious);
00880
00881 return true;
00882 }
00883 return false;
00884 }
00885
00886 bool KateCompletionWidget::navigateRight() {
00887 m_hadCompletionNavigation = true;
00888 if(currentEmbeddedWidget())
00889 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetRight");
00890
00891 QModelIndex index = selectedIndex();
00892
00893 if( index.isValid() ) {
00894 index.data(KTextEditor::CodeCompletionModel::AccessibilityNext);
00895 return true;
00896 }
00897
00898 return false;
00899 }
00900
00901 bool KateCompletionWidget::hadNavigation() const {
00902 return m_hadCompletionNavigation;
00903 }
00904
00905 void KateCompletionWidget::resetHadNavigation() {
00906 m_hadCompletionNavigation = false;
00907 }
00908
00909
00910 bool KateCompletionWidget::navigateBack() {
00911 m_hadCompletionNavigation = true;
00912 if(currentEmbeddedWidget())
00913 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetBack");
00914 return false;
00915 }
00916
00917 bool KateCompletionWidget::toggleExpanded(bool forceExpand, bool forceUnExpand) {
00918 if ( (canExpandCurrentItem() || forceExpand ) && !forceUnExpand) {
00919 bool ret = canExpandCurrentItem();
00920 setCurrentItemExpanded(true);
00921 return ret;
00922 } else if (canCollapseCurrentItem() || forceUnExpand) {
00923 bool ret = canCollapseCurrentItem();
00924 setCurrentItemExpanded(false);
00925 return ret;
00926 }
00927 return false;
00928 }
00929
00930 bool KateCompletionWidget::canExpandCurrentItem() const {
00931 if( m_inCompletionList ) {
00932 if( !m_entryList->currentIndex().isValid() ) return false;
00933 return model()->isExpandable( m_entryList->currentIndex() ) && !model()->isExpanded( m_entryList->currentIndex() );
00934 } else {
00935 if( !m_argumentHintTree->currentIndex().isValid() ) return false;
00936 return argumentHintModel()->isExpandable( m_argumentHintTree->currentIndex() ) && !argumentHintModel()->isExpanded( m_argumentHintTree->currentIndex() );
00937 }
00938 }
00939
00940 bool KateCompletionWidget::canCollapseCurrentItem() const {
00941 if( m_inCompletionList ) {
00942 if( !m_entryList->currentIndex().isValid() ) return false;
00943 return model()->isExpandable( m_entryList->currentIndex() ) && model()->isExpanded( m_entryList->currentIndex() );
00944 }else{
00945 if( !m_argumentHintTree->currentIndex().isValid() ) return false;
00946 return m_argumentHintModel->isExpandable( m_argumentHintTree->currentIndex() ) && m_argumentHintModel->isExpanded( m_argumentHintTree->currentIndex() );
00947 }
00948 }
00949
00950 void KateCompletionWidget::setCurrentItemExpanded( bool expanded ) {
00951 if( m_inCompletionList ) {
00952 if( !m_entryList->currentIndex().isValid() ) return;
00953 model()->setExpanded(m_entryList->currentIndex(), expanded);
00954 updateHeight();
00955 }else{
00956 if( !m_argumentHintTree->currentIndex().isValid() ) return;
00957 m_argumentHintModel->setExpanded(m_argumentHintTree->currentIndex(), expanded);
00958 }
00959 }
00960
00961 void KateCompletionWidget::startCharacterDeleted( KTextEditor::SmartCursor*, bool deletedBefore )
00962 {
00963 if (deletedBefore)
00964
00965 QTimer::singleShot(0, this, SLOT(abortCompletion()));
00966 }
00967
00968 bool KateCompletionWidget::eventFilter( QObject * watched, QEvent * event )
00969 {
00970 bool ret = QFrame::eventFilter(watched, event);
00971
00972 if (watched != this)
00973 if (event->type() == QEvent::Move)
00974 updatePosition();
00975
00976 return ret;
00977 }
00978
00979 bool KateCompletionWidget::navigateDown() {
00980 m_hadCompletionNavigation = true;
00981 if(currentEmbeddedWidget()) {
00982 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetDown");
00983 }
00984 return false;
00985 }
00986
00987 bool KateCompletionWidget::navigateUp() {
00988 m_hadCompletionNavigation = true;
00989 if(currentEmbeddedWidget())
00990 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetUp");
00991 return false;
00992 }
00993
00994 QWidget* KateCompletionWidget::currentEmbeddedWidget() {
00995 QModelIndex index = selectedIndex();
00996 if(!index.isValid())
00997 return 0;
00998 if( qobject_cast<const ExpandingWidgetModel*>(index.model()) ) {
00999 const ExpandingWidgetModel* model = static_cast<const ExpandingWidgetModel*>(index.model());
01000 if( model->isExpanded(index) )
01001 return model->expandingWidget(index);
01002 }
01003 return 0;
01004 }
01005
01006 void KateCompletionWidget::cursorDown()
01007 {
01008 bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid();
01009
01010 if( m_inCompletionList )
01011 m_entryList->nextCompletion();
01012 else {
01013 if( !m_argumentHintTree->nextCompletion() )
01014 switchList();
01015 }
01016
01017 if(wasPartiallyExpanded != model()->partiallyExpandedRow().isValid())
01018 updateHeight();
01019 }
01020
01021 void KateCompletionWidget::cursorUp()
01022 {
01023 bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid();
01024
01025 if( m_inCompletionList ) {
01026 if( !m_entryList->previousCompletion() )
01027 switchList();
01028 }else{
01029 m_argumentHintTree->previousCompletion();
01030 }
01031
01032 if(wasPartiallyExpanded != model()->partiallyExpandedRow().isValid())
01033 updateHeight();
01034 }
01035
01036 void KateCompletionWidget::pageDown( )
01037 {
01038 bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid();
01039
01040 if( m_inCompletionList )
01041 m_entryList->pageDown();
01042 else {
01043 if( !m_argumentHintTree->pageDown() )
01044 switchList();
01045 }
01046
01047 if(wasPartiallyExpanded != model()->partiallyExpandedRow().isValid())
01048 updateHeight();
01049 }
01050
01051 void KateCompletionWidget::pageUp( )
01052 {
01053 bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid();
01054
01055 if( m_inCompletionList ) {
01056 if( !m_entryList->pageUp() )
01057 switchList();
01058 }else{
01059 m_argumentHintTree->pageUp();
01060 }
01061
01062 if(wasPartiallyExpanded != model()->partiallyExpandedRow().isValid())
01063 updateHeight();
01064 }
01065
01066 void KateCompletionWidget::top( )
01067 {
01068 bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid();
01069
01070 if( m_inCompletionList )
01071 m_entryList->top();
01072 else
01073 m_argumentHintTree->top();
01074
01075 if(wasPartiallyExpanded != model()->partiallyExpandedRow().isValid())
01076 updateHeight();
01077 }
01078
01079 void KateCompletionWidget::bottom( )
01080 {
01081 bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid();
01082
01083 if( m_inCompletionList )
01084 m_entryList->bottom();
01085 else
01086 m_argumentHintTree->bottom();
01087
01088 if(wasPartiallyExpanded != model()->partiallyExpandedRow().isValid())
01089 updateHeight();
01090 }
01091
01092 void KateCompletionWidget::switchList() {
01093 if( m_inCompletionList ) {
01094 if( m_argumentHintModel->rowCount(QModelIndex()) != 0 ) {
01095 m_entryList->setCurrentIndex(QModelIndex());
01096 m_argumentHintTree->setCurrentIndex(m_argumentHintModel->index(m_argumentHintModel->rowCount(QModelIndex())-1, 0));
01097 }
01098 } else {
01099 if( m_presentationModel->rowCount(QModelIndex()) != 0 ) {
01100 m_argumentHintTree->setCurrentIndex(QModelIndex());
01101 m_entryList->setCurrentIndex(m_presentationModel->index(0, 0));
01102 if(model()->hasGroups())
01103 m_entryList->nextCompletion();
01104 }
01105 }
01106 m_inCompletionList = !m_inCompletionList;
01107 }
01108
01109 void KateCompletionWidget::showConfig( )
01110 {
01111 abortCompletion();
01112
01113 m_configWidget->exec();
01114 }
01115
01116 void KateCompletionWidget::completionModelReset()
01117 {
01118 KTextEditor::CodeCompletionModel* model = qobject_cast<KTextEditor::CodeCompletionModel*>(sender());
01119 if(!model) {
01120 kWarning() << "bad sender";
01121 return;
01122 }
01123
01124 if(!m_waitingForReset.contains(model))
01125 return;
01126
01127 m_waitingForReset.remove(model);
01128
01129 if(m_waitingForReset.isEmpty()) {
01130 if(!isCompletionActive()) {
01131 kDebug() << "all completion-models we waited for are ready. Last one: " << model->objectName();
01132
01133
01134 QMetaObject::invokeMethod(this, "modelContentChanged", Qt::QueuedConnection);
01135 }
01136 }
01137 }
01138
01139 void KateCompletionWidget::modelDestroyed(QObject* model) {
01140 unregisterCompletionModel(static_cast<KTextEditor::CodeCompletionModel*>(model));
01141 }
01142
01143 void KateCompletionWidget::registerCompletionModel(KTextEditor::CodeCompletionModel* model)
01144 {
01145 connect(model, SIGNAL(destroyed(QObject*)), SLOT(modelDestroyed(QObject*)));
01146
01147 connect(model, SIGNAL(modelReset()), SLOT(completionModelReset()));
01148
01149 m_sourceModels.append(model);
01150
01151 if (isCompletionActive()) {
01152 m_presentationModel->addCompletionModel(model);
01153 }
01154 }
01155
01156 void KateCompletionWidget::unregisterCompletionModel(KTextEditor::CodeCompletionModel* model)
01157 {
01158 disconnect(model, SIGNAL(destroyed(QObject*)), this, SLOT(modelDestroyed(QObject*)));
01159 disconnect(model, SIGNAL(modelReset()), this, SLOT(completionModelReset()));
01160
01161 m_sourceModels.removeAll(model);
01162 abortCompletion();
01163 }
01164
01165 int KateCompletionWidget::automaticInvocationDelay() const {
01166 return m_automaticInvocationDelay;
01167 }
01168
01169 void KateCompletionWidget::setAutomaticInvocationDelay(int delay) {
01170 m_automaticInvocationDelay = delay;
01171 }
01172
01173 void KateCompletionWidget::editDone(KateEditInfo * edit)
01174 {
01175 if(!edit->newText().join("\n").trimmed().isEmpty())
01176 m_lastInsertionByUser = edit->editSource() == Kate::UserInputEdit;
01177
01178 if (!view()->config()->automaticCompletionInvocation()
01179 || (edit->editSource() != Kate::UserInputEdit)
01180 || edit->isRemoval()
01181 || (edit->editSource() != Kate::UserInputEdit && edit->editSource() != Kate::CodeCompletionEdit)
01182 || edit->newText().isEmpty() )
01183 {
01184 m_automaticInvocationLine.clear();
01185 m_automaticInvocationTimer->stop();
01186 return;
01187 }
01188
01189 if(m_automaticInvocationAt != edit->newRange().start()) {
01190 m_automaticInvocationLine.clear();
01191 m_lastInsertionByUser = edit->editSource() == Kate::UserInputEdit;
01192 }
01193
01194 m_automaticInvocationLine += edit->newText().last();
01195 m_automaticInvocationAt = edit->newRange().end();
01196
01197 if (m_automaticInvocationLine.isEmpty()) {
01198 m_automaticInvocationTimer->stop();
01199 return;
01200 }
01201
01202 m_automaticInvocationTimer->start(m_automaticInvocationDelay);
01203 }
01204
01205 void KateCompletionWidget::automaticInvocation()
01206 {
01207 if(m_automaticInvocationAt != view()->cursorPosition())
01208 return;
01209 bool start = false;
01210
01211 foreach (KTextEditor::CodeCompletionModel *model, m_sourceModels) {
01212 if(m_completionRanges.contains(model))
01213 continue;
01214 start = modelController(model)->shouldStartCompletion(view(), m_automaticInvocationLine, m_lastInsertionByUser, view()->cursorPosition());
01215 if (start) break;
01216 }
01217 if (start) {
01218
01219 startCompletion(KTextEditor::CodeCompletionModel::AutomaticInvocation);
01220 }
01221 }
01222
01223 void KateCompletionWidget::userInvokedCompletion()
01224 {
01225 startCompletion(KTextEditor::CodeCompletionModel::UserInvocation);
01226 }
01227
01228 #include "katecompletionwidget.moc"
01229
01230