Kate
katecompletionwidget.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries and the Kate part. 00002 * 00003 * Copyright (C) 2005-2006 Hamish Rodda <rodda@kde.org> 00004 * Copyright (C) 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de> 00005 * 00006 * This library is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU Library General Public 00008 * License as published by the Free Software Foundation; either 00009 * version 2 of the License, or (at your option) any later version. 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 #include "katecompletionwidget.h" 00023 00024 #include <QtGui/QBoxLayout> 00025 #include <QtGui/QApplication> 00026 #include <QtGui/QDesktopWidget> 00027 #include <QtGui/QHeaderView> 00028 #include <QtCore/QTimer> 00029 #include <QtGui/QLabel> 00030 #include <QtGui/QToolButton> 00031 #include <QtGui/QSizeGrip> 00032 #include <QtGui/QPushButton> 00033 #include <QtGui/QAbstractScrollArea> 00034 #include <QtGui/QScrollBar> 00035 #include <QtCore/QScopedPointer> 00036 00037 #include <kicon.h> 00038 #include <kdialog.h> 00039 00040 #include <ktexteditor/codecompletionmodelcontrollerinterface.h> 00041 00042 #include "kateview.h" 00043 #include "katerenderer.h" 00044 #include "kateconfig.h" 00045 #include "katedocument.h" 00046 #include "katebuffer.h" 00047 00048 #include "katecompletionmodel.h" 00049 #include "katecompletiontree.h" 00050 #include "katecompletionconfig.h" 00051 #include "kateargumenthinttree.h" 00052 #include "kateargumenthintmodel.h" 00053 00054 //#include "modeltest.h" 00055 00056 const bool hideAutomaticCompletionOnExactMatch = true; 00057 00058 //If this is true, the completion-list is navigated up/down when 'tab' is pressed, instead of doing partial completion 00059 const bool shellLikeTabCompletion = false; 00060 00061 #define CALLCI(WHAT,WHATELSE,WHAT2,model,FUNC) \ 00062 {\ 00063 static KTextEditor::CodeCompletionModelControllerInterface3 defaultIf;\ 00064 KTextEditor::CodeCompletionModelControllerInterface3* ret =\ 00065 qobject_cast<KTextEditor::CodeCompletionModelControllerInterface3*>(model);\ 00066 if (!ret) {\ 00067 WHAT2 defaultIf.FUNC;\ 00068 }else \ 00069 WHAT2 ret->FUNC;\ 00070 } 00071 00072 00073 static KTextEditor::Range _completionRange(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Cursor& cursor){ 00074 CALLCI(return,,return, model,completionRange(view, cursor)); 00075 } 00076 00077 static KTextEditor::Range _updateRange(KTextEditor::CodeCompletionModel *model,KTextEditor::View *view, KTextEditor::Range& range) { 00078 CALLCI(, return range,return, model,updateCompletionRange(view, range)); 00079 } 00080 00081 static QString _filterString(KTextEditor::CodeCompletionModel *model,KTextEditor::View *view, const KTextEditor::Range& range, const KTextEditor::Cursor& cursor) { 00082 CALLCI(return,,return, model,filterString(view, range, cursor)); 00083 } 00084 00085 static bool _shouldAbortCompletion(KTextEditor::CodeCompletionModel *model,KTextEditor::View *view, const KTextEditor::Range& range, const QString& currentCompletion) { 00086 CALLCI(return,,return, model,shouldAbortCompletion(view, range, currentCompletion)); 00087 } 00088 00089 static void _aborted(KTextEditor::CodeCompletionModel *model,KTextEditor::View *view) { 00090 CALLCI(return,,return, model,aborted(view)); 00091 } 00092 00093 static bool _shouldStartCompletion(KTextEditor::CodeCompletionModel *model,KTextEditor::View *view, QString m_automaticInvocationLine,bool m_lastInsertionByUser, const KTextEditor::Cursor& cursor) { 00094 CALLCI(return,,return,model,shouldStartCompletion(view, m_automaticInvocationLine, m_lastInsertionByUser, cursor)); 00095 } 00096 00097 KateCompletionWidget::KateCompletionWidget(KateView* parent) 00098 : QFrame(parent, Qt::ToolTip) 00099 , m_presentationModel(new KateCompletionModel(this)) 00100 , m_entryList(new KateCompletionTree(this)) 00101 , m_argumentHintModel(new KateArgumentHintModel(this)) 00102 , m_argumentHintTree(new KateArgumentHintTree(this)) 00103 , m_automaticInvocationDelay(100) 00104 , m_filterInstalled(false) 00105 , m_configWidget(new KateCompletionConfig(m_presentationModel, view())) 00106 , m_lastInsertionByUser(false) 00107 , m_inCompletionList(false) 00108 , m_isSuspended(false) 00109 , m_dontShowArgumentHints(false) 00110 , m_needShow(false) 00111 , m_hadCompletionNavigation(false) 00112 , m_noAutoHide(false) 00113 , m_completionEditRunning (false) 00114 , m_expandedAddedHeightBase(0) 00115 , m_lastInvocationType(KTextEditor::CodeCompletionModel::AutomaticInvocation) 00116 { 00117 connect(parent, SIGNAL(navigateAccept()), SLOT(navigateAccept())); 00118 connect(parent, SIGNAL(navigateBack()), SLOT(navigateBack())); 00119 connect(parent, SIGNAL(navigateDown()), SLOT(navigateDown())); 00120 connect(parent, SIGNAL(navigateLeft()), SLOT(navigateLeft())); 00121 connect(parent, SIGNAL(navigateRight()), SLOT(navigateRight())); 00122 connect(parent, SIGNAL(navigateUp()), SLOT(navigateUp())); 00123 00124 qRegisterMetaType<KTextEditor::Cursor>("KTextEditor::Cursor"); 00125 00126 setFrameStyle( QFrame::Box | QFrame::Plain ); 00127 setLineWidth( 1 ); 00128 //setWindowOpacity(0.8); 00129 00130 m_entryList->setModel(m_presentationModel); 00131 m_entryList->setColumnWidth(0, 0); //These will be determined automatically in KateCompletionTree::resizeColumns 00132 m_entryList->setColumnWidth(1, 0); 00133 m_entryList->setColumnWidth(2, 0); 00134 00135 m_entryList->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); 00136 00137 m_argumentHintTree->setParent(0, Qt::ToolTip); 00138 m_argumentHintTree->setModel(m_argumentHintModel); 00139 00140 connect(m_entryList->verticalScrollBar(), SIGNAL(valueChanged(int)), m_presentationModel, SLOT(placeExpandingWidgets())); 00141 connect(m_argumentHintTree->verticalScrollBar(), SIGNAL(valueChanged(int)), m_argumentHintModel, SLOT(placeExpandingWidgets())); 00142 connect(view(), SIGNAL(focusOut(KTextEditor::View*)), this, SLOT(viewFocusOut())); 00143 00144 m_automaticInvocationTimer = new QTimer(this); 00145 m_automaticInvocationTimer->setSingleShot(true); 00146 connect(m_automaticInvocationTimer, SIGNAL(timeout()), this, SLOT(automaticInvocation())); 00147 00148 // Keep branches expanded 00149 connect(m_presentationModel, SIGNAL(modelReset()), this, SLOT(modelReset())); 00150 connect(m_presentationModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), SLOT(rowsInserted(const QModelIndex&, int, int))); 00151 connect(m_argumentHintModel, SIGNAL(contentStateChanged(bool)), this, SLOT(argumentHintsChanged(bool))); 00152 00153 // No smart lock, no queued connects 00154 connect(view(), SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(cursorPositionChanged())); 00155 connect(view(), SIGNAL(verticalScrollPositionChanged (KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(updatePositionSlot())); 00156 00160 connect(&view()->doc()->buffer(), SIGNAL(lineWrapped(const KTextEditor::Cursor&)), this, SLOT(wrapLine(const KTextEditor::Cursor&))); 00161 connect(&view()->doc()->buffer(), SIGNAL(lineUnwrapped(int)), this, SLOT(unwrapLine(int))); 00162 connect(&view()->doc()->buffer(), SIGNAL(textInserted(const KTextEditor::Cursor &, const QString &)), this, SLOT(insertText(const KTextEditor::Cursor &, const QString &))); 00163 connect(&view()->doc()->buffer(), SIGNAL(textRemoved(const KTextEditor::Range &, const QString &)), this, SLOT(removeText(const KTextEditor::Range &))); 00164 00165 // This is a non-focus widget, it is passed keyboard input from the view 00166 00167 //We need to do this, because else the focus goes to nirvana without any control when the completion-widget is clicked. 00168 setFocusPolicy(Qt::ClickFocus); 00169 m_argumentHintTree->setFocusPolicy(Qt::ClickFocus); 00170 00171 foreach (QWidget* childWidget, findChildren<QWidget*>()) 00172 childWidget->setFocusPolicy(Qt::NoFocus); 00173 00174 //Position the entry-list so a frame can be drawn around it 00175 m_entryList->move(frameWidth(), frameWidth()); 00176 } 00177 00178 KateCompletionWidget::~KateCompletionWidget() { 00179 } 00180 00181 void KateCompletionWidget::viewFocusOut() { 00182 abortCompletion(); 00183 } 00184 00185 void KateCompletionWidget::modelContentChanged() { 00186 kDebug()<<">>>>>>>>>>>>>>>>"; 00187 if(m_completionRanges.isEmpty()) { 00188 kDebug( 13035 ) << "content changed, but no completion active"; 00189 abortCompletion(); 00190 return; 00191 } 00192 00193 if(!view()->hasFocus()) { 00194 kDebug( 13035 ) << "view does not have focus"; 00195 return; 00196 } 00197 00198 if(!m_waitingForReset.isEmpty()) { 00199 kDebug( 13035 ) << "waiting for" << m_waitingForReset.size() << "completion-models to reset"; 00200 return; 00201 } 00202 00203 int realItemCount = 0; 00204 foreach (KTextEditor::CodeCompletionModel* model, m_presentationModel->completionModels()) 00205 realItemCount += model->rowCount(); 00206 if( !m_isSuspended && ((isHidden() && m_argumentHintTree->isHidden()) || m_needShow) && realItemCount != 0 ) { 00207 m_needShow = false; 00208 updateAndShow(); 00209 } 00210 00211 if(m_argumentHintModel->rowCount(QModelIndex()) == 0) 00212 m_argumentHintTree->hide(); 00213 00214 if(m_presentationModel->rowCount(QModelIndex()) == 0) 00215 hide(); 00216 00217 00218 //With each filtering items can be added or removed, so we have to reset the current index here so we always have a selected item 00219 m_entryList->setCurrentIndex(model()->index(0,0)); 00220 if(!model()->indexIsItem(m_entryList->currentIndex())) { 00221 QModelIndex firstIndex = model()->index(0,0, m_entryList->currentIndex()); 00222 m_entryList->setCurrentIndex(firstIndex); 00223 //m_entryList->scrollTo(firstIndex, QAbstractItemView::PositionAtTop); 00224 } 00225 00226 updateHeight(); 00227 00228 //New items for the argument-hint tree may have arrived, so check whether it needs to be shown 00229 if( m_argumentHintTree->isHidden() && !m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0 ) 00230 m_argumentHintTree->show(); 00231 00232 if(!m_noAutoHide && hideAutomaticCompletionOnExactMatch && !isHidden() && 00233 m_lastInvocationType == KTextEditor::CodeCompletionModel::AutomaticInvocation && 00234 m_presentationModel->shouldMatchHideCompletionList()) 00235 hide(); 00236 else if(isHidden() && !m_presentationModel->shouldMatchHideCompletionList() && 00237 m_presentationModel->rowCount(QModelIndex())) 00238 show(); 00239 } 00240 00241 KateArgumentHintTree* KateCompletionWidget::argumentHintTree() const { 00242 return m_argumentHintTree; 00243 } 00244 00245 KateArgumentHintModel* KateCompletionWidget::argumentHintModel() const { 00246 return m_argumentHintModel; 00247 } 00248 00249 const KateCompletionModel* KateCompletionWidget::model() const { 00250 return m_presentationModel; 00251 } 00252 00253 KateCompletionModel* KateCompletionWidget::model() { 00254 return m_presentationModel; 00255 } 00256 00257 void KateCompletionWidget::rowsInserted(const QModelIndex& parent, int rowFrom, int rowEnd) 00258 { 00259 m_entryList->setAnimated(false); 00260 if(!model()->isGroupingEnabled()) 00261 return; 00262 00263 if (!parent.isValid()) 00264 for (int i = rowFrom; i <= rowEnd; ++i) 00265 m_entryList->expand(m_presentationModel->index(i, 0, parent)); 00266 } 00267 00268 KateView * KateCompletionWidget::view( ) const 00269 { 00270 return static_cast<KateView*>(const_cast<QObject*>(parent())); 00271 } 00272 00273 void KateCompletionWidget::argumentHintsChanged(bool hasContent) 00274 { 00275 m_dontShowArgumentHints = !hasContent; 00276 00277 if( m_dontShowArgumentHints ) 00278 m_argumentHintTree->hide(); 00279 else 00280 updateArgumentHintGeometry(); 00281 } 00282 00283 void KateCompletionWidget::startCompletion(KTextEditor::CodeCompletionModel::InvocationType invocationType, const QList<KTextEditor::CodeCompletionModel*>& models) 00284 { 00285 if(invocationType == KTextEditor::CodeCompletionModel::UserInvocation) 00286 { 00287 abortCompletion(); 00288 } 00289 startCompletion(KTextEditor::Range(KTextEditor::Cursor(-1, -1), KTextEditor::Cursor(-1, -1)), models, invocationType); 00290 } 00291 00292 void KateCompletionWidget::deleteCompletionRanges() 00293 { 00294 //kDebug(); 00295 foreach(const CompletionRange &r, m_completionRanges) 00296 delete r.range; 00297 m_completionRanges.clear(); 00298 } 00299 00300 void KateCompletionWidget::startCompletion(const KTextEditor::Range& word, KTextEditor::CodeCompletionModel* model, KTextEditor::CodeCompletionModel::InvocationType invocationType) 00301 { 00302 QList<KTextEditor::CodeCompletionModel*> models; 00303 if (model) { 00304 models << model; 00305 } else { 00306 models = m_sourceModels; 00307 } 00308 startCompletion(word, models, invocationType); 00309 } 00310 00311 void KateCompletionWidget::startCompletion(const KTextEditor::Range& word, const QList<KTextEditor::CodeCompletionModel*>& modelsToStart, KTextEditor::CodeCompletionModel::InvocationType invocationType) 00312 { 00313 00314 kDebug()<<"============"; 00315 00316 m_isSuspended = false; 00317 m_inCompletionList = true; //Always start at the top of the completion-list 00318 m_needShow = true; 00319 00320 if(m_completionRanges.isEmpty()) 00321 m_noAutoHide = false; //Re-enable auto-hide on every clean restart of the completion 00322 00323 m_lastInvocationType = invocationType; 00324 00325 disconnect(this->model(), SIGNAL(contentGeometryChanged()), this, SLOT(modelContentChanged())); 00326 00327 m_dontShowArgumentHints = true; 00328 00329 QList<KTextEditor::CodeCompletionModel*> models = (modelsToStart.isEmpty() ? m_sourceModels : modelsToStart); 00330 00331 foreach(KTextEditor::CodeCompletionModel* model, m_completionRanges.keys()) 00332 if(!models.contains(model)) 00333 models << model; 00334 00335 if (!m_filterInstalled) { 00336 if (!QApplication::activeWindow()) { 00337 kWarning(13035) << "No active window to install event filter on!!"; 00338 return; 00339 } 00340 // Enable the cc box to move when the editor window is moved 00341 QApplication::activeWindow()->installEventFilter(this); 00342 m_filterInstalled = true; 00343 } 00344 00345 m_presentationModel->clearCompletionModels(); 00346 00347 if(invocationType == KTextEditor::CodeCompletionModel::UserInvocation) { 00348 deleteCompletionRanges(); 00349 } 00350 00351 foreach (KTextEditor::CodeCompletionModel* model, models) { 00352 KTextEditor::Range range; 00353 if (word.isValid()) { 00354 range = word; 00355 kDebug()<<"word is used"; 00356 } else { 00357 range=_completionRange(model,view(), view()->cursorPosition()); 00358 kDebug()<<"completionRange has been called, cursor pos is"<<view()->cursorPosition(); 00359 } 00360 kDebug()<<"range is"<<range; 00361 if(!range.isValid()) { 00362 if(m_completionRanges.contains(model)) { 00363 KTextEditor::MovingRange *oldRange = m_completionRanges[model].range; 00364 kDebug()<<"removing completion range 1"; 00365 m_completionRanges.remove(model); 00366 delete oldRange; 00367 } 00368 models.removeAll(model); 00369 continue; 00370 } 00371 if(m_completionRanges.contains(model)) { 00372 if(*m_completionRanges[model].range == range) { 00373 continue; //Leave it running as it is 00374 } 00375 else { // delete the range that was used previously 00376 KTextEditor::MovingRange *oldRange = m_completionRanges[model].range; 00377 kDebug()<<"removing completion range 2"; 00378 m_completionRanges.remove(model); 00379 delete oldRange; 00380 } 00381 } 00382 00383 connect(model, SIGNAL(waitForReset()), this, SLOT(waitForModelReset())); 00384 00385 kDebug()<<"Before completin invoke: range:"<<range; 00386 model->completionInvoked(view(), range, invocationType); 00387 00388 disconnect(model, SIGNAL(waitForReset()), this, SLOT(waitForModelReset())); 00389 00390 m_completionRanges[model] = view()->doc()->newMovingRange(range, KTextEditor::MovingRange::ExpandRight | KTextEditor::MovingRange::ExpandLeft); 00391 00392 //In automatic invocation mode, hide the completion widget as soon as the position where the completion was started is passed to the left 00393 m_completionRanges[model].leftBoundary = view()->cursorPosition(); 00394 00395 //In manual invocation mode, bound the activity either the the point from where completion was invoked, or to the start of the range 00396 if(invocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation) 00397 if(range.start() < m_completionRanges[model].leftBoundary) 00398 m_completionRanges[model].leftBoundary = range.start(); 00399 00400 if(!m_completionRanges[model].range->toRange().isValid()) { 00401 kWarning(13035) << "Could not construct valid smart-range from" << range << "instead got" << *m_completionRanges[model].range; 00402 abortCompletion(); 00403 return; 00404 } 00405 } 00406 00407 m_presentationModel->setCompletionModels(models); 00408 00409 cursorPositionChanged(); 00410 00411 if (!m_completionRanges.isEmpty()) { 00412 connect(this->model(), SIGNAL(contentGeometryChanged()), this, SLOT(modelContentChanged())); 00413 //Now that all models have been notified, check whether the widget should be displayed instantly 00414 modelContentChanged(); 00415 } 00416 else { 00417 abortCompletion(); 00418 } 00419 } 00420 00421 void KateCompletionWidget::waitForModelReset() 00422 { 00423 KTextEditor::CodeCompletionModel* senderModel = qobject_cast<KTextEditor::CodeCompletionModel*>(sender()); 00424 if(!senderModel) { 00425 kWarning() << "waitForReset signal from bad model"; 00426 return; 00427 } 00428 m_waitingForReset.insert(senderModel); 00429 } 00430 00431 void KateCompletionWidget::updateAndShow() 00432 { 00433 kDebug()<<"*******************************************"; 00434 if(!view()->hasFocus()) { 00435 kDebug( 13035 ) << "view does not have focus"; 00436 return; 00437 } 00438 00439 setUpdatesEnabled(false); 00440 00441 modelReset(); 00442 00443 m_argumentHintModel->buildRows(); 00444 if( m_argumentHintModel->rowCount(QModelIndex()) != 0 ) 00445 argumentHintsChanged(true); 00446 // } 00447 00448 //We do both actions twice here so they are stable, because they influence each other: 00449 //updatePosition updates the height, resizeColumns needs the correct height to decide over 00450 //how many rows it computs the column-width 00451 updatePosition(true); 00452 m_entryList->resizeColumns(true, true); 00453 updatePosition(true); 00454 m_entryList->resizeColumns(true, true); 00455 00456 setUpdatesEnabled(true); 00457 00458 if(m_argumentHintModel->rowCount(QModelIndex())) { 00459 updateArgumentHintGeometry(); 00460 m_argumentHintTree->show(); 00461 } else 00462 m_argumentHintTree->hide(); 00463 00464 if (m_presentationModel->rowCount() && (!m_presentationModel->shouldMatchHideCompletionList() || 00465 !hideAutomaticCompletionOnExactMatch || 00466 m_lastInvocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation) ) 00467 show(); 00468 else 00469 hide(); 00470 } 00471 00472 void KateCompletionWidget::updatePositionSlot() 00473 { 00474 updatePosition(); 00475 } 00476 00477 bool KateCompletionWidget::updatePosition(bool force) 00478 { 00479 if (!force && !isCompletionActive()) 00480 return false; 00481 00482 if (!completionRange()) { 00483 return false; 00484 } 00485 QPoint cursorPosition = view()->cursorToCoordinate(completionRange()->start()); 00486 if (cursorPosition == QPoint(-1,-1)) { 00487 // Start of completion range is now off-screen -> abort 00488 abortCompletion(); 00489 return false; 00490 } 00491 00492 QPoint p = view()->mapToGlobal( cursorPosition ); 00493 int x = p.x() - m_entryList->columnTextViewportPosition(m_presentationModel->translateColumn(KTextEditor::CodeCompletionModel::Name)) - 4 - (m_entryList->viewport()->pos().x()); 00494 int y = p.y(); 00495 00496 y += view()->renderer()->config()->fontMetrics().height() + 4; 00497 00498 bool borderHit = false; 00499 00500 if (x + width() > QApplication::desktop()->screenGeometry(view()).right()) { 00501 x = QApplication::desktop()->screenGeometry(view()).right() - width(); 00502 borderHit = true; 00503 } 00504 00505 if( x < QApplication::desktop()->screenGeometry(view()).left() ) { 00506 x = QApplication::desktop()->screenGeometry(view()).left(); 00507 borderHit = true; 00508 } 00509 00510 move( QPoint(x,y) ); 00511 00512 updateHeight(); 00513 00514 updateArgumentHintGeometry(); 00515 00516 // kDebug() << "updated to" << geometry() << m_entryList->geometry() << borderHit; 00517 00518 return borderHit; 00519 } 00520 00521 void KateCompletionWidget::updateArgumentHintGeometry() 00522 { 00523 if( !m_dontShowArgumentHints ) { 00524 //Now place the argument-hint widget 00525 QRect geom = m_argumentHintTree->geometry(); 00526 geom.moveTo(pos()); 00527 geom.setWidth(width()); 00528 geom.moveBottom(pos().y() - view()->renderer()->config()->fontMetrics().height()*2); 00529 m_argumentHintTree->updateGeometry(geom); 00530 } 00531 } 00532 00533 //Checks whether the given model has at least "rows" rows, also searching the second level of the tree. 00534 bool hasAtLeastNRows(int rows, QAbstractItemModel* model) { 00535 int count = 0; 00536 for(int row = 0; row < model->rowCount(); ++row) { 00537 ++count; 00538 00539 QModelIndex index(model->index(row, 0)); 00540 if(index.isValid()) 00541 count += model->rowCount(index); 00542 00543 if(count > rows) 00544 return true; 00545 } 00546 return false; 00547 } 00548 00549 void KateCompletionWidget::updateHeight() 00550 { 00551 QRect geom = geometry(); 00552 00553 int minBaseHeight = 10; 00554 int maxBaseHeight = 300; 00555 00556 int baseHeight = 0; 00557 int calculatedCustomHeight = 0; 00558 00559 if(hasAtLeastNRows(15, m_presentationModel)) { 00560 //If we know there is enough rows, always use max-height, we don't need to calculate size-hints 00561 baseHeight = maxBaseHeight; 00562 }else{ 00563 //Calculate size-hints to determine the best height 00564 for(int row = 0; row < m_presentationModel->rowCount(); ++row) { 00565 baseHeight += treeView()->sizeHintForRow(row); 00566 00567 QModelIndex index(m_presentationModel->index(row, 0)); 00568 if(index.isValid()) { 00569 for(int row2 = 0; row2 < m_presentationModel->rowCount(index); ++row2) { 00570 int h = 0; 00571 for(int a = 0; a < m_presentationModel->columnCount(index); ++a) { 00572 int localHeight = treeView()->sizeHintForIndex(index.child(row2, a)).height(); 00573 if(localHeight > h) 00574 h = localHeight; 00575 } 00576 baseHeight += h; 00577 if(baseHeight > maxBaseHeight) 00578 break; 00579 } 00580 00581 if(baseHeight > maxBaseHeight) 00582 break; 00583 } 00584 } 00585 00586 calculatedCustomHeight = baseHeight; 00587 } 00588 00589 baseHeight += 2*frameWidth(); 00590 00591 if(m_entryList->horizontalScrollBar()->isVisible()) 00592 baseHeight += m_entryList->horizontalScrollBar()->height(); 00593 00594 00595 if(baseHeight < minBaseHeight) 00596 baseHeight = minBaseHeight; 00597 if(baseHeight > maxBaseHeight) { 00598 baseHeight = maxBaseHeight; 00599 m_entryList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); 00600 }else{ 00601 //Somewhere there seems to be a bug that makes QTreeView add a scroll-bar 00602 //even if the content exactly fits in. So forcefully disable the scroll-bar in that case 00603 m_entryList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 00604 } 00605 00606 int newExpandingAddedHeight = 0; 00607 00608 if(baseHeight == maxBaseHeight && model()->expandingWidgetsHeight()) { 00609 //Eventually add some more height 00610 if(calculatedCustomHeight && calculatedCustomHeight > baseHeight && calculatedCustomHeight < (maxBaseHeight + model()->expandingWidgetsHeight())) 00611 newExpandingAddedHeight = calculatedCustomHeight - baseHeight; 00612 else 00613 newExpandingAddedHeight = model()->expandingWidgetsHeight(); 00614 } 00615 00616 if( m_expandedAddedHeightBase != baseHeight && m_expandedAddedHeightBase - baseHeight > -2 && m_expandedAddedHeightBase - baseHeight < 2 ) 00617 { 00618 //Re-use the stored base-height if it only slightly differs from the current one. 00619 //Reason: Qt seems to apply slightly wrong sizes when the completion-widget is moved out of the screen at the bottom, 00620 // which completely breaks this algorithm. Solution: re-use the old base-size if it only slightly differs from the computed one. 00621 baseHeight = m_expandedAddedHeightBase; 00622 } 00623 00624 int screenBottom = QApplication::desktop()->screenGeometry(view()).bottom(); 00625 00626 //Limit the height to the bottom of the screen 00627 int bottomPosition = baseHeight + newExpandingAddedHeight + geometry().top(); 00628 00629 if( bottomPosition > screenBottom ) { 00630 newExpandingAddedHeight -= bottomPosition - (screenBottom); 00631 } 00632 00633 int finalHeight = baseHeight+newExpandingAddedHeight; 00634 00635 if( finalHeight < 10 ) { 00636 m_entryList->resize(m_entryList->width(), height() - 2*frameWidth()); 00637 return; 00638 } 00639 00640 m_expandedAddedHeightBase = geometry().height(); 00641 00642 geom.setHeight(finalHeight); 00643 00644 //Work around a crash deep within the Qt 4.5 raster engine 00645 m_entryList->setScrollingEnabled(false); 00646 00647 if(geometry() != geom) 00648 setGeometry(geom); 00649 00650 QSize entryListSize = QSize(m_entryList->width(), finalHeight - 2*frameWidth()); 00651 if(m_entryList->size() != entryListSize) 00652 m_entryList->resize(entryListSize); 00653 00654 00655 m_entryList->setScrollingEnabled(true); 00656 } 00657 00658 void KateCompletionWidget::cursorPositionChanged( ) 00659 { 00660 //kDebug(); 00661 if (m_completionRanges.isEmpty()) 00662 return; 00663 00664 QModelIndex oldCurrentSourceIndex; 00665 if(m_inCompletionList && m_entryList->currentIndex().isValid()) 00666 oldCurrentSourceIndex = m_presentationModel->mapToSource(m_entryList->currentIndex()); 00667 00668 KTextEditor::Cursor cursor = view()->cursorPosition(); 00669 00670 QList<KTextEditor::CodeCompletionModel*> checkCompletionRanges = m_completionRanges.keys(); 00671 00672 //Check the models and eventuall abort some 00673 for(QList<KTextEditor::CodeCompletionModel*>::iterator it = checkCompletionRanges.begin(); 00674 it != checkCompletionRanges.end(); ++it) { 00675 if(!m_completionRanges.contains(*it)) 00676 continue; 00677 00678 KTextEditor::CodeCompletionModel *model = *it; 00679 KTextEditor::MovingRange *range = m_completionRanges[*it].range; 00680 00681 kDebug()<<"range before _updateRange:"<< *range; 00682 KTextEditor::Range rangeTE = range->toRange(); 00683 range->setRange (_updateRange(model, view(),rangeTE)); 00684 kDebug()<<"range after _updateRange:"<< *range; 00685 QString currentCompletion = _filterString(model,view(), *range, view()->cursorPosition()); 00686 kDebug()<<"after _filterString, currentCompletion="<< currentCompletion; 00687 bool abort = _shouldAbortCompletion(model,view(), *range, currentCompletion); 00688 kDebug()<<"after _shouldAbortCompletion:abort="<<abort; 00689 if(view()->cursorPosition() < m_completionRanges[*it].leftBoundary) { 00690 kDebug() << "aborting because of boundary: cursor:"<<view()->cursorPosition()<<"completion_Range_left_boundary:"<<m_completionRanges[*it].leftBoundary; 00691 abort = true; 00692 } 00693 00694 if(!m_completionRanges.contains(*it)) 00695 continue; 00696 00697 if (abort) { 00698 if (m_completionRanges.count() == 1) { 00699 //last model - abort whole completion 00700 abortCompletion(); 00701 return; 00702 } else { 00703 { 00704 delete m_completionRanges[*it].range; 00705 kDebug()<<"removing completion range 3"; 00706 m_completionRanges.remove(*it); 00707 } 00708 00709 _aborted(model,view()); 00710 m_presentationModel->removeCompletionModel(model); 00711 } 00712 } else { 00713 m_presentationModel->setCurrentCompletion(model, currentCompletion); 00714 } 00715 } 00716 00717 if(oldCurrentSourceIndex.isValid()) { 00718 QModelIndex idx = m_presentationModel->mapFromSource(oldCurrentSourceIndex); 00719 if(idx.isValid()) { 00720 kDebug() << "setting" << idx; 00721 m_entryList->setCurrentIndex(idx.sibling(idx.row(), 0)); 00722 // m_entryList->nextCompletion(); 00723 // m_entryList->previousCompletion(); 00724 }else{ 00725 kDebug() << "failed to map from source"; 00726 } 00727 } 00728 00729 m_entryList->scheduleUpdate(); 00730 } 00731 00732 bool KateCompletionWidget::isCompletionActive( ) const 00733 { 00734 return !m_completionRanges.isEmpty() && ((!isHidden() && isVisible()) || (!m_argumentHintTree->isHidden() && m_argumentHintTree->isVisible())); 00735 } 00736 00737 void KateCompletionWidget::abortCompletion( ) 00738 { 00739 //kDebug(13035) ; 00740 00741 m_isSuspended = false; 00742 00743 bool wasActive = isCompletionActive(); 00744 00745 clear(); 00746 00747 if(!isHidden()) 00748 hide(); 00749 if(!m_argumentHintTree->isHidden()) 00750 m_argumentHintTree->hide(); 00751 00752 if (wasActive) 00753 view()->sendCompletionAborted(); 00754 } 00755 00756 void KateCompletionWidget::clear() { 00757 m_presentationModel->clearCompletionModels(); 00758 m_argumentHintTree->clearCompletion(); 00759 m_argumentHintModel->clear(); 00760 00761 foreach(KTextEditor::CodeCompletionModel* model, m_completionRanges.keys()) 00762 _aborted(model,view()); 00763 00764 deleteCompletionRanges(); 00765 } 00766 00767 bool KateCompletionWidget::navigateAccept() { 00768 m_hadCompletionNavigation = true; 00769 00770 if(currentEmbeddedWidget()) 00771 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetAccept"); 00772 00773 QModelIndex index = selectedIndex(); 00774 if( index.isValid() ) { 00775 index.data(KTextEditor::CodeCompletionModel::AccessibilityAccept); 00776 return true; 00777 } 00778 return false; 00779 } 00780 00781 void KateCompletionWidget::execute() 00782 { 00783 //kDebug(13035) ; 00784 00785 if (!isCompletionActive()) 00786 return; 00787 00788 QModelIndex index = selectedIndex(); 00789 00790 if (!index.isValid()) 00791 return abortCompletion(); 00792 00793 QModelIndex toExecute; 00794 00795 if(index.model() == m_presentationModel) 00796 toExecute = m_presentationModel->mapToSource(index); 00797 else 00798 toExecute = m_argumentHintModel->mapToSource(index); 00799 00800 if (!toExecute.isValid()) { 00801 kWarning() << k_funcinfo << "Could not map index" << m_entryList->selectionModel()->currentIndex() << "to source index."; 00802 return abortCompletion(); 00803 } 00804 00805 // encapsulate all editing as being from the code completion, and undo-able in one step. 00806 view()->doc()->editStart(); 00807 m_completionEditRunning = true; 00808 00809 // create scoped pointer, to ensure deletion of cursor 00810 QScopedPointer<KTextEditor::MovingCursor> oldPos (view()->doc()->newMovingCursor(view()->cursorPosition(), KTextEditor::MovingCursor::StayOnInsert)); 00811 00812 KTextEditor::CodeCompletionModel* model = static_cast<KTextEditor::CodeCompletionModel*>(const_cast<QAbstractItemModel*>(toExecute.model())); 00813 Q_ASSERT(model); 00814 00815 KTextEditor::CodeCompletionModel2* model2 = qobject_cast<KTextEditor::CodeCompletionModel2*>(model); 00816 00817 Q_ASSERT(m_completionRanges.contains(model)); 00818 KTextEditor::Cursor start = m_completionRanges[model].range->start(); 00819 00820 if(model2) 00821 model2->executeCompletionItem2(view()->document(), *m_completionRanges[model].range, toExecute); 00822 else if(toExecute.parent().isValid()) 00823 //The normale CodeCompletionInterface cannot handle feedback for hierarchical models, so just do the replacement 00824 view()->document()->replaceText(*m_completionRanges[model].range, model->data(toExecute.sibling(toExecute.row(), KTextEditor::CodeCompletionModel::Name)).toString()); 00825 else 00826 model->executeCompletionItem(view()->document(), *m_completionRanges[model].range, toExecute.row()); 00827 00828 view()->doc()->editEnd(); 00829 m_completionEditRunning = false; 00830 00831 abortCompletion(); 00832 00833 view()->sendCompletionExecuted(start, model, toExecute); 00834 00835 KTextEditor::Cursor newPos = view()->cursorPosition(); 00836 00837 if(newPos > *oldPos) { 00838 m_automaticInvocationAt = newPos; 00839 m_automaticInvocationLine = view()->doc()->text(KTextEditor::Range(*oldPos, newPos)); 00840 kDebug() << "executed, starting automatic invocation with line" << m_automaticInvocationLine; 00841 m_lastInsertionByUser = false; 00842 m_automaticInvocationTimer->start(); 00843 } 00844 } 00845 00846 void KateCompletionWidget::resizeEvent( QResizeEvent * event ) 00847 { 00848 QFrame::resizeEvent(event); 00849 } 00850 00851 void KateCompletionWidget::showEvent ( QShowEvent * event ) 00852 { 00853 m_isSuspended = false; 00854 00855 QFrame::showEvent(event); 00856 00857 if( !m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0 ) 00858 m_argumentHintTree->show(); 00859 } 00860 00861 KTextEditor::MovingRange * KateCompletionWidget::completionRange(KTextEditor::CodeCompletionModel* model) const 00862 { 00863 if (!model) { 00864 if (m_completionRanges.isEmpty()) return 0; 00865 00866 KTextEditor::MovingRange* ret = m_completionRanges.begin()->range; 00867 00868 foreach(const CompletionRange &range, m_completionRanges) 00869 if(range.range->start() > ret->start()) 00870 ret = range.range; 00871 return ret; 00872 } 00873 if(m_completionRanges.contains(model)) 00874 return m_completionRanges[model].range; 00875 else 00876 return 0; 00877 } 00878 00879 QMap<KTextEditor::CodeCompletionModel*, KateCompletionWidget::CompletionRange> KateCompletionWidget::completionRanges( ) const 00880 { 00881 return m_completionRanges; 00882 } 00883 00884 void KateCompletionWidget::modelReset( ) 00885 { 00886 setUpdatesEnabled(false); 00887 m_entryList->setAnimated(false); 00888 m_argumentHintTree->setAnimated(false); 00891 for(int row = 0; row < m_argumentHintModel->rowCount(QModelIndex()); ++row) { 00892 QModelIndex index(m_argumentHintModel->index(row, 0, QModelIndex())); 00893 if(!m_argumentHintTree->isExpanded(index)) { 00894 m_argumentHintTree->expand(index); 00895 } 00896 } 00897 00898 for(int row = 0; row < m_entryList->model()->rowCount(QModelIndex()); ++row) { 00899 QModelIndex index(m_entryList->model()->index(row, 0, QModelIndex())); 00900 if(!m_entryList->isExpanded(index)) { 00901 m_entryList->expand(index); 00902 } 00903 } 00904 setUpdatesEnabled(true); 00905 } 00906 00907 KateCompletionTree* KateCompletionWidget::treeView() const { 00908 return m_entryList; 00909 } 00910 00911 QModelIndex KateCompletionWidget::selectedIndex() const { 00912 if(!isCompletionActive()) 00913 return QModelIndex(); 00914 00915 if( m_inCompletionList ) 00916 return m_entryList->currentIndex(); 00917 else 00918 return m_argumentHintTree->currentIndex(); 00919 } 00920 00921 bool KateCompletionWidget::navigateLeft() { 00922 m_hadCompletionNavigation = true; 00923 if(currentEmbeddedWidget()) 00924 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetLeft"); 00925 00926 QModelIndex index = selectedIndex(); 00927 00928 if( index.isValid() ) { 00929 index.data(KTextEditor::CodeCompletionModel::AccessibilityPrevious); 00930 00931 return true; 00932 } 00933 return false; 00934 } 00935 00936 bool KateCompletionWidget::navigateRight() { 00937 m_hadCompletionNavigation = true; 00938 if(currentEmbeddedWidget()) 00939 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetRight"); 00940 00941 QModelIndex index = selectedIndex(); 00942 00943 if( index.isValid() ) { 00944 index.data(KTextEditor::CodeCompletionModel::AccessibilityNext); 00945 return true; 00946 } 00947 00948 return false; 00949 } 00950 00951 bool KateCompletionWidget::hadNavigation() const { 00952 return m_hadCompletionNavigation; 00953 } 00954 00955 void KateCompletionWidget::resetHadNavigation() { 00956 m_hadCompletionNavigation = false; 00957 } 00958 00959 00960 bool KateCompletionWidget::navigateBack() { 00961 m_hadCompletionNavigation = true; 00962 if(currentEmbeddedWidget()) 00963 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetBack"); 00964 return false; 00965 } 00966 00967 bool KateCompletionWidget::toggleExpanded(bool forceExpand, bool forceUnExpand) { 00968 if ( (canExpandCurrentItem() || forceExpand ) && !forceUnExpand) { 00969 bool ret = canExpandCurrentItem(); 00970 setCurrentItemExpanded(true); 00971 return ret; 00972 } else if (canCollapseCurrentItem() || forceUnExpand) { 00973 bool ret = canCollapseCurrentItem(); 00974 setCurrentItemExpanded(false); 00975 return ret; 00976 } 00977 return false; 00978 } 00979 00980 bool KateCompletionWidget::canExpandCurrentItem() const { 00981 if( m_inCompletionList ) { 00982 if( !m_entryList->currentIndex().isValid() ) return false; 00983 return model()->isExpandable( m_entryList->currentIndex() ) && !model()->isExpanded( m_entryList->currentIndex() ); 00984 } else { 00985 if( !m_argumentHintTree->currentIndex().isValid() ) return false; 00986 return argumentHintModel()->isExpandable( m_argumentHintTree->currentIndex() ) && !argumentHintModel()->isExpanded( m_argumentHintTree->currentIndex() ); 00987 } 00988 } 00989 00990 bool KateCompletionWidget::canCollapseCurrentItem() const { 00991 if( m_inCompletionList ) { 00992 if( !m_entryList->currentIndex().isValid() ) return false; 00993 return model()->isExpandable( m_entryList->currentIndex() ) && model()->isExpanded( m_entryList->currentIndex() ); 00994 }else{ 00995 if( !m_argumentHintTree->currentIndex().isValid() ) return false; 00996 return m_argumentHintModel->isExpandable( m_argumentHintTree->currentIndex() ) && m_argumentHintModel->isExpanded( m_argumentHintTree->currentIndex() ); 00997 } 00998 } 00999 01000 void KateCompletionWidget::setCurrentItemExpanded( bool expanded ) { 01001 if( m_inCompletionList ) { 01002 if( !m_entryList->currentIndex().isValid() ) return; 01003 model()->setExpanded(m_entryList->currentIndex(), expanded); 01004 updateHeight(); 01005 }else{ 01006 if( !m_argumentHintTree->currentIndex().isValid() ) return; 01007 m_argumentHintModel->setExpanded(m_argumentHintTree->currentIndex(), expanded); 01008 } 01009 } 01010 01011 bool KateCompletionWidget::eventFilter( QObject * watched, QEvent * event ) 01012 { 01013 bool ret = QFrame::eventFilter(watched, event); 01014 01015 if (watched != this) 01016 if (event->type() == QEvent::Move) 01017 updatePosition(); 01018 01019 return ret; 01020 } 01021 01022 bool KateCompletionWidget::navigateDown() { 01023 m_hadCompletionNavigation = true; 01024 if(currentEmbeddedWidget()) { 01025 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetDown"); 01026 } 01027 return false; 01028 } 01029 01030 bool KateCompletionWidget::navigateUp() { 01031 m_hadCompletionNavigation = true; 01032 if(currentEmbeddedWidget()) 01033 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetUp"); 01034 return false; 01035 } 01036 01037 QWidget* KateCompletionWidget::currentEmbeddedWidget() { 01038 QModelIndex index = selectedIndex(); 01039 if(!index.isValid()) 01040 return 0; 01041 if( qobject_cast<const ExpandingWidgetModel*>(index.model()) ) { 01042 const ExpandingWidgetModel* model = static_cast<const ExpandingWidgetModel*>(index.model()); 01043 if( model->isExpanded(index) ) 01044 return model->expandingWidget(index); 01045 } 01046 return 0; 01047 } 01048 01049 void KateCompletionWidget::cursorDown() 01050 { 01051 bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); 01052 01053 if( m_inCompletionList ) 01054 m_entryList->nextCompletion(); 01055 else { 01056 if( !m_argumentHintTree->nextCompletion() ) 01057 switchList(); 01058 } 01059 01060 if(wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) 01061 updateHeight(); 01062 } 01063 01064 void KateCompletionWidget::cursorUp() 01065 { 01066 bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); 01067 01068 if( m_inCompletionList ) { 01069 if( !m_entryList->previousCompletion() ) 01070 switchList(); 01071 }else{ 01072 m_argumentHintTree->previousCompletion(); 01073 } 01074 01075 if(wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) 01076 updateHeight(); 01077 } 01078 01079 void KateCompletionWidget::pageDown( ) 01080 { 01081 bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); 01082 01083 if( m_inCompletionList ) 01084 m_entryList->pageDown(); 01085 else { 01086 if( !m_argumentHintTree->pageDown() ) 01087 switchList(); 01088 } 01089 01090 if(wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) 01091 updateHeight(); 01092 } 01093 01094 void KateCompletionWidget::pageUp( ) 01095 { 01096 bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); 01097 01098 if( m_inCompletionList ) { 01099 if( !m_entryList->pageUp() ) 01100 switchList(); 01101 }else{ 01102 m_argumentHintTree->pageUp(); 01103 } 01104 01105 if(wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) 01106 updateHeight(); 01107 } 01108 01109 void KateCompletionWidget::top( ) 01110 { 01111 bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); 01112 01113 if( m_inCompletionList ) 01114 m_entryList->top(); 01115 else 01116 m_argumentHintTree->top(); 01117 01118 if(wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) 01119 updateHeight(); 01120 } 01121 01122 void KateCompletionWidget::bottom( ) 01123 { 01124 bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); 01125 01126 if( m_inCompletionList ) 01127 m_entryList->bottom(); 01128 else 01129 m_argumentHintTree->bottom(); 01130 01131 if(wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) 01132 updateHeight(); 01133 } 01134 01135 void KateCompletionWidget::switchList() { 01136 if( m_inCompletionList ) { 01137 if( m_argumentHintModel->rowCount(QModelIndex()) != 0 ) { 01138 m_entryList->setCurrentIndex(QModelIndex()); 01139 m_argumentHintTree->setCurrentIndex(m_argumentHintModel->index(m_argumentHintModel->rowCount(QModelIndex())-1, 0)); 01140 } 01141 } else { 01142 if( m_presentationModel->rowCount(QModelIndex()) != 0 ) { 01143 m_argumentHintTree->setCurrentIndex(QModelIndex()); 01144 m_entryList->setCurrentIndex(m_presentationModel->index(0, 0)); 01145 if(model()->hasGroups()) //If we have groups we have to move on, because the first item is a label 01146 m_entryList->nextCompletion(); 01147 } 01148 } 01149 m_inCompletionList = !m_inCompletionList; 01150 } 01151 01152 void KateCompletionWidget::showConfig( ) 01153 { 01154 abortCompletion(); 01155 01156 m_configWidget->exec(); 01157 } 01158 01159 void KateCompletionWidget::completionModelReset() 01160 { 01161 KTextEditor::CodeCompletionModel* model = qobject_cast<KTextEditor::CodeCompletionModel*>(sender()); 01162 if(!model) { 01163 kWarning() << "bad sender"; 01164 return; 01165 } 01166 01167 if(!m_waitingForReset.contains(model)) 01168 return; 01169 01170 m_waitingForReset.remove(model); 01171 01172 if(m_waitingForReset.isEmpty()) { 01173 if(!isCompletionActive()) { 01174 kDebug() << "all completion-models we waited for are ready. Last one: " << model->objectName(); 01175 //Eventually show the completion-list if this was the last model we were waiting for 01176 //Use a queued connection once again to make sure that KateCompletionModel is notified before we are 01177 QMetaObject::invokeMethod(this, "modelContentChanged", Qt::QueuedConnection); 01178 } 01179 } 01180 } 01181 01182 void KateCompletionWidget::modelDestroyed(QObject* model) { 01183 unregisterCompletionModel(static_cast<KTextEditor::CodeCompletionModel*>(model)); 01184 } 01185 01186 void KateCompletionWidget::registerCompletionModel(KTextEditor::CodeCompletionModel* model) 01187 { 01188 if (m_sourceModels.contains(model)) { 01189 return; 01190 } 01191 01192 connect(model, SIGNAL(destroyed(QObject*)), SLOT(modelDestroyed(QObject*))); 01193 //This connection must not be queued 01194 connect(model, SIGNAL(modelReset()), SLOT(completionModelReset())); 01195 01196 m_sourceModels.append(model); 01197 01198 if (isCompletionActive()) { 01199 m_presentationModel->addCompletionModel(model); 01200 } 01201 } 01202 01203 void KateCompletionWidget::unregisterCompletionModel(KTextEditor::CodeCompletionModel* model) 01204 { 01205 disconnect(model, SIGNAL(destroyed(QObject*)), this, SLOT(modelDestroyed(QObject*))); 01206 disconnect(model, SIGNAL(modelReset()), this, SLOT(completionModelReset())); 01207 01208 m_sourceModels.removeAll(model); 01209 abortCompletion(); 01210 } 01211 01212 int KateCompletionWidget::automaticInvocationDelay() const { 01213 return m_automaticInvocationDelay; 01214 } 01215 01216 void KateCompletionWidget::setAutomaticInvocationDelay(int delay) { 01217 m_automaticInvocationDelay = delay; 01218 } 01219 01220 void KateCompletionWidget::wrapLine (const KTextEditor::Cursor &) 01221 { 01222 m_lastInsertionByUser = !m_completionEditRunning; 01223 01224 // wrap line, be done 01225 m_automaticInvocationLine.clear(); 01226 m_automaticInvocationTimer->stop(); 01227 } 01228 01229 void KateCompletionWidget::unwrapLine (int) 01230 { 01231 m_lastInsertionByUser = !m_completionEditRunning; 01232 01233 // just removal 01234 m_automaticInvocationLine.clear(); 01235 m_automaticInvocationTimer->stop(); 01236 } 01237 01238 void KateCompletionWidget::insertText (const KTextEditor::Cursor &position, const QString &text) 01239 { 01240 m_lastInsertionByUser = !m_completionEditRunning; 01241 01242 // no invoke? 01243 if (!view()->config()->automaticCompletionInvocation()) { 01244 m_automaticInvocationLine.clear(); 01245 m_automaticInvocationTimer->stop(); 01246 return; 01247 } 01248 01249 if(m_automaticInvocationAt != position) { 01250 m_automaticInvocationLine.clear(); 01251 m_lastInsertionByUser = !m_completionEditRunning; 01252 } 01253 01254 m_automaticInvocationLine += text; 01255 m_automaticInvocationAt = position; 01256 m_automaticInvocationAt.setColumn (position.column() + text.length()); 01257 01258 if (m_automaticInvocationLine.isEmpty()) { 01259 m_automaticInvocationTimer->stop(); 01260 return; 01261 } 01262 01263 m_automaticInvocationTimer->start(m_automaticInvocationDelay); 01264 } 01265 01266 void KateCompletionWidget::removeText (const KTextEditor::Range &) 01267 { 01268 m_lastInsertionByUser = !m_completionEditRunning; 01269 01270 // just removal 01271 m_automaticInvocationLine.clear(); 01272 m_automaticInvocationTimer->stop(); 01273 } 01274 01275 #if 0 01276 void KateCompletionWidget::editDone(KateEditInfo * edit) 01277 { 01278 return; 01279 01280 if(!edit->newText().join("\n").trimmed().isEmpty()) 01281 m_lastInsertionByUser = edit->editSource() == Kate::UserInputEdit; 01282 01283 if (!view()->config()->automaticCompletionInvocation() 01284 || (edit->editSource() != Kate::UserInputEdit) 01285 || edit->isRemoval() 01286 || (edit->editSource() != Kate::UserInputEdit && edit->editSource() != Kate::CodeCompletionEdit) 01287 || edit->newText().isEmpty() ) 01288 { 01289 m_automaticInvocationLine.clear(); 01290 m_automaticInvocationTimer->stop(); 01291 return; 01292 } 01293 01294 if(m_automaticInvocationAt != edit->newRange().start()) { 01295 m_automaticInvocationLine.clear(); 01296 m_lastInsertionByUser = edit->editSource() == Kate::UserInputEdit; 01297 } 01298 01299 m_automaticInvocationLine += edit->newText().last(); 01300 m_automaticInvocationAt = edit->newRange().end(); 01301 01302 if (m_automaticInvocationLine.isEmpty()) { 01303 m_automaticInvocationTimer->stop(); 01304 return; 01305 } 01306 01307 m_automaticInvocationTimer->start(m_automaticInvocationDelay); 01308 } 01309 #endif 01310 01311 void KateCompletionWidget::automaticInvocation() 01312 { 01313 kDebug()<<"m_automaticInvocationAt:"<<m_automaticInvocationAt; 01314 kDebug()<<view()->cursorPosition(); 01315 if(m_automaticInvocationAt != view()->cursorPosition()) 01316 return; 01317 01318 bool start = false; 01319 QList<KTextEditor::CodeCompletionModel*> models; 01320 01321 kDebug()<<"checking models"; 01322 foreach (KTextEditor::CodeCompletionModel *model, m_sourceModels) { 01323 kDebug()<<"m_completionRanges contains model?:"<<m_completionRanges.contains(model); 01324 if(m_completionRanges.contains(model)) 01325 continue; 01326 01327 start=_shouldStartCompletion(model,view(), m_automaticInvocationLine, m_lastInsertionByUser, view()->cursorPosition()); 01328 kDebug()<<"start="<<start; 01329 if (start) 01330 { 01331 models << model; 01332 } 01333 } 01334 kDebug()<<"models found:"<<!models.isEmpty(); 01335 if (!models.isEmpty()) { 01336 // Start automatic code completion 01337 startCompletion(KTextEditor::CodeCompletionModel::AutomaticInvocation, models); 01338 } 01339 } 01340 01341 void KateCompletionWidget::userInvokedCompletion() 01342 { 01343 startCompletion(KTextEditor::CodeCompletionModel::UserInvocation); 01344 } 01345 01346 void KateCompletionWidget::tab(bool shift) 01347 { 01348 m_noAutoHide = true; 01349 if(!shift) { 01350 QString prefix = m_presentationModel->commonPrefix((m_inCompletionList && !shellLikeTabCompletion) ? m_entryList->currentIndex() : QModelIndex()); 01351 if(!prefix.isEmpty()) { 01352 view()->insertText(prefix); 01353 }else if(shellLikeTabCompletion) { 01354 cursorDown(); 01355 return; 01356 } 01357 }else{ 01358 if(shellLikeTabCompletion) { 01359 cursorUp(); 01360 return; 01361 } 01362 01363 //Reset left boundaries, so completion isn't stopped 01364 typedef QMap<KTextEditor::CodeCompletionModel*, CompletionRange> CompletionRangeMap; 01365 for(CompletionRangeMap::iterator it = m_completionRanges.begin(); it != m_completionRanges.end(); ++it) 01366 (*it).leftBoundary = (*it).range->start(); 01367 01368 //Remove suffix until the completion-list filter is widened again 01369 uint itemCount = m_presentationModel->filteredItemCount(); 01370 01371 while(view()->cursorPosition().column() > 0 && m_presentationModel->filteredItemCount() == itemCount) { 01372 KTextEditor::Range lastcharRange = KTextEditor::Range(view()->cursorPosition()-KTextEditor::Cursor(0,1), view()->cursorPosition()); 01373 QString cursorText = view()->document()->text(lastcharRange); 01374 if(!cursorText[0].isSpace()) 01375 { 01376 view()->document()->removeText(lastcharRange); 01377 QApplication::sendPostedEvents(); 01378 }else{ 01379 break; 01380 } 01381 } 01382 } 01383 } 01384 01385 #include "katecompletionwidget.moc" 01386 01387 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE 4.6 API Reference