diff --git a/src/completion/kateargumenthinttree.cpp b/src/completion/kateargumenthinttree.cpp index b7f650d9..af8f67e2 100644 --- a/src/completion/kateargumenthinttree.cpp +++ b/src/completion/kateargumenthinttree.cpp @@ -1,316 +1,316 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateargumenthinttree.h" #include #include #include #include #include "kateargumenthintmodel.h" #include "katecompletionwidget.h" #include "expandingtree/expandingwidgetmodel.h" #include "katecompletiondelegate.h" #include "kateview.h" #include KateArgumentHintTree::KateArgumentHintTree(KateCompletionWidget *parent) : ExpandingTree(nullptr), m_parent(parent) //Do not use the completion-widget as widget-parent, because the argument-hint-tree will be rendered separately { setFrameStyle(QFrame::Box | QFrame::Plain); setLineWidth(1); connect(parent, SIGNAL(destroyed(QObject*)), this, SLOT(deleteLater())); setFrameStyle(QFrame::NoFrame); setFrameStyle(QFrame::Box | QFrame::Plain); setFocusPolicy(Qt::NoFocus); setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); setUniformRowHeights(false); setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); header()->hide(); setRootIsDecorated(false); setIndentation(0); setAllColumnsShowFocus(true); setAlternatingRowColors(true); setItemDelegate(new KateCompletionDelegate(parent->argumentHintModel(), parent)); } void KateArgumentHintTree::clearCompletion() { setCurrentIndex(QModelIndex()); } KateArgumentHintModel *KateArgumentHintTree::model() const { return m_parent->argumentHintModel(); } void KateArgumentHintTree::paintEvent(QPaintEvent *event) { QTreeView::paintEvent(event); updateGeometry(); ///@todo delay this. It is needed here, because visualRect(...) returns an invalid rect in updateGeometry before the content is painted } void KateArgumentHintTree::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles) { Q_UNUSED(roles) QTreeView::dataChanged(topLeft, bottomRight); //updateGeometry(); } void KateArgumentHintTree::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { /* qCDebug(LOG_KTE) << "currentChanged()";*/ static_cast(model())->rowSelected(current); QTreeView::currentChanged(current, previous); } void KateArgumentHintTree::rowsInserted(const QModelIndex &parent, int start, int end) { QTreeView::rowsInserted(parent, start, end); updateGeometry(); } int KateArgumentHintTree::sizeHintForColumn(int column) const { return QTreeView::sizeHintForColumn(column); } unsigned int KateArgumentHintTree::rowHeight(const QModelIndex &index) const { uint max = sizeHintForIndex(index).height(); for (int a = 0; a < index.model()->columnCount(index.parent()); ++a) { QModelIndex i = index.sibling(index.row(), a); uint cSize = sizeHintForIndex(i).height(); if (cSize > max) { max = cSize; } } return max; } void KateArgumentHintTree::updateGeometry(QRect geom) { //Avoid recursive calls of updateGeometry static bool updatingGeometry = false; if (updatingGeometry) { return; } updatingGeometry = true; if (model()->rowCount(QModelIndex()) == 0) { /* qCDebug(LOG_KTE) << "KateArgumentHintTree:: empty model";*/ hide(); setGeometry(geom); updatingGeometry = false; return; } int bottom = geom.bottom(); - int totalWidth = resizeColumns(); + int totalWidth = std::max(geom.width(), resizeColumns()); int totalHeight = 0; for (int a = 0; a < model()->rowCount(QModelIndex()); ++a) { QModelIndex index(model()->index(a, 0)); totalHeight += rowHeight(index); for (int b = 0; b < model()->rowCount(index); ++b) { QModelIndex childIndex = index.child(b, 0); totalHeight += rowHeight(childIndex); } } totalHeight += frameWidth() * 2; geom.setHeight(totalHeight); geom.moveBottom(bottom); // if( totalWidth > geom.width() ) geom.setWidth(totalWidth); bool enableScrollBars = false; //Resize and move so it fits the screen horizontally int maxWidth = (QApplication::desktop()->screenGeometry(m_parent->view()).width() * 3) / 4; if (geom.width() > maxWidth) { geom.setWidth(maxWidth); geom.setHeight(geom.height() + horizontalScrollBar()->height() + 2); geom.moveBottom(bottom); enableScrollBars = true; } if (geom.right() > QApplication::desktop()->screenGeometry(m_parent->view()).right()) { geom.moveRight(QApplication::desktop()->screenGeometry(m_parent->view()).right()); } if (geom.left() < QApplication::desktop()->screenGeometry(m_parent->view()).left()) { geom.moveLeft(QApplication::desktop()->screenGeometry(m_parent->view()).left()); } //Resize and move so it fits the screen vertically bool resized = false; if (geom.top() < QApplication::desktop()->screenGeometry(this).top()) { int offset = QApplication::desktop()->screenGeometry(this).top() - geom.top(); geom.setBottom(geom.bottom() - offset); geom.moveTo(geom.left(), QApplication::desktop()->screenGeometry(this).top()); resized = true; } if (geom != geometry()) { setUpdatesEnabled(false); setAnimated(false); setHorizontalScrollBarPolicy(enableScrollBars ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff); /* qCDebug(LOG_KTE) << "KateArgumentHintTree::updateGeometry: updating geometry to " << geom;*/ setGeometry(geom); if (resized && currentIndex().isValid()) { scrollTo(currentIndex()); } setUpdatesEnabled(true); } updatingGeometry = false; } int KateArgumentHintTree::resizeColumns() { int totalSize = 0; for (int a = 0; a < header()->count(); a++) { int columnSize = sizeHintForColumn(a); setColumnWidth(a, columnSize); totalSize += columnSize; } return totalSize; } void KateArgumentHintTree::updateGeometry() { updateGeometry(geometry()); } bool KateArgumentHintTree::nextCompletion() { QModelIndex current; QModelIndex firstCurrent = currentIndex(); do { QModelIndex oldCurrent = currentIndex(); current = moveCursor(MoveDown, Qt::NoModifier); if (current != oldCurrent && current.isValid()) { setCurrentIndex(current); } else { if (firstCurrent.isValid()) { setCurrentIndex(firstCurrent); } return false; } } while (!model()->indexIsItem(current)); return true; } bool KateArgumentHintTree::previousCompletion() { QModelIndex current; QModelIndex firstCurrent = currentIndex(); do { QModelIndex oldCurrent = currentIndex(); current = moveCursor(MoveUp, Qt::NoModifier); if (current != oldCurrent && current.isValid()) { setCurrentIndex(current); } else { if (firstCurrent.isValid()) { setCurrentIndex(firstCurrent); } return false; } } while (!model()->indexIsItem(current)); return true; } bool KateArgumentHintTree::pageDown() { QModelIndex old = currentIndex(); QModelIndex current = moveCursor(MovePageDown, Qt::NoModifier); if (current.isValid()) { setCurrentIndex(current); if (!model()->indexIsItem(current)) if (!nextCompletion()) { previousCompletion(); } } return current != old; } bool KateArgumentHintTree::pageUp() { QModelIndex old = currentIndex(); QModelIndex current = moveCursor(MovePageUp, Qt::NoModifier); if (current.isValid()) { setCurrentIndex(current); if (!model()->indexIsItem(current)) if (!previousCompletion()) { nextCompletion(); } } return current != old; } void KateArgumentHintTree::top() { QModelIndex current = moveCursor(MoveHome, Qt::NoModifier); setCurrentIndex(current); if (current.isValid()) { setCurrentIndex(current); if (!model()->indexIsItem(current)) { nextCompletion(); } } } void KateArgumentHintTree::bottom() { QModelIndex current = moveCursor(MoveEnd, Qt::NoModifier); setCurrentIndex(current); if (current.isValid()) { setCurrentIndex(current); if (!model()->indexIsItem(current)) { previousCompletion(); } } } diff --git a/src/completion/katecompletionwidget.cpp b/src/completion/katecompletionwidget.cpp index 2107540f..aa2af465 100644 --- a/src/completion/katecompletionwidget.cpp +++ b/src/completion/katecompletionwidget.cpp @@ -1,1474 +1,1487 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2005-2006 Hamish Rodda * Copyright (C) 2007-2008 David Nolden * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "katecompletionwidget.h" #include #include "kateview.h" #include "katerenderer.h" #include "kateconfig.h" #include "katedocument.h" #include "katebuffer.h" #include "katecompletionmodel.h" #include "katecompletiontree.h" #include "katecompletionconfig.h" #include "kateargumenthinttree.h" #include "kateargumenthintmodel.h" #include "katepartdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include const bool hideAutomaticCompletionOnExactMatch = true; //If this is true, the completion-list is navigated up/down when 'tab' is pressed, instead of doing partial completion const bool shellLikeTabCompletion = false; #define CALLCI(WHAT,WHATELSE,WHAT2,model,FUNC) \ {\ static KTextEditor::CodeCompletionModelControllerInterface defaultIf;\ KTextEditor::CodeCompletionModelControllerInterface* ret =\ dynamic_cast(model);\ if (!ret) {\ WHAT2 defaultIf.FUNC;\ }else \ WHAT2 ret->FUNC;\ } static KTextEditor::Range _completionRange(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Cursor &cursor) { CALLCI(return,, return, model, completionRange(view, cursor)); } static KTextEditor::Range _updateRange(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, KTextEditor::Range &range) { CALLCI(, return range, return, model, updateCompletionRange(view, range)); } static QString _filterString(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Range &range, const KTextEditor::Cursor &cursor) { CALLCI(return,, return, model, filterString(view, range, cursor)); } static bool _shouldAbortCompletion(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) { CALLCI(return,, return, model, shouldAbortCompletion(view, range, currentCompletion)); } static void _aborted(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view) { CALLCI(return,, return, model, aborted(view)); } static bool _shouldStartCompletion(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, QString m_automaticInvocationLine, bool m_lastInsertionByUser, const KTextEditor::Cursor &cursor) { CALLCI(return,, return, model, shouldStartCompletion(view, m_automaticInvocationLine, m_lastInsertionByUser, cursor)); } KateCompletionWidget::KateCompletionWidget(KTextEditor::ViewPrivate *parent) : QFrame(parent, Qt::ToolTip) , m_presentationModel(new KateCompletionModel(this)) , m_entryList(new KateCompletionTree(this)) , m_argumentHintModel(new KateArgumentHintModel(this)) , m_argumentHintTree(new KateArgumentHintTree(this)) , m_automaticInvocationDelay(100) , m_filterInstalled(false) , m_configWidget(new KateCompletionConfig(m_presentationModel, view())) , m_lastInsertionByUser(false) , m_inCompletionList(false) , m_isSuspended(false) , m_dontShowArgumentHints(false) , m_needShow(false) , m_hadCompletionNavigation(false) , m_noAutoHide(false) , m_completionEditRunning(false) , m_expandedAddedHeightBase(0) , m_lastInvocationType(KTextEditor::CodeCompletionModel::AutomaticInvocation) { connect(parent, SIGNAL(navigateAccept()), SLOT(navigateAccept())); connect(parent, SIGNAL(navigateBack()), SLOT(navigateBack())); connect(parent, SIGNAL(navigateDown()), SLOT(navigateDown())); connect(parent, SIGNAL(navigateLeft()), SLOT(navigateLeft())); connect(parent, SIGNAL(navigateRight()), SLOT(navigateRight())); connect(parent, SIGNAL(navigateUp()), SLOT(navigateUp())); setFrameStyle(QFrame::Box | QFrame::Plain); setLineWidth(1); //setWindowOpacity(0.8); m_entryList->setModel(m_presentationModel); m_entryList->setColumnWidth(0, 0); //These will be determined automatically in KateCompletionTree::resizeColumns m_entryList->setColumnWidth(1, 0); m_entryList->setColumnWidth(2, 0); m_entryList->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); m_argumentHintTree->setParent(nullptr, Qt::ToolTip); m_argumentHintTree->setModel(m_argumentHintModel); // trigger completion on double click on completion list connect(m_entryList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(execute())); connect(m_entryList->verticalScrollBar(), SIGNAL(valueChanged(int)), m_presentationModel, SLOT(placeExpandingWidgets())); connect(m_argumentHintTree->verticalScrollBar(), SIGNAL(valueChanged(int)), m_argumentHintModel, SLOT(placeExpandingWidgets())); connect(view(), SIGNAL(focusOut(KTextEditor::View*)), this, SLOT(viewFocusOut())); m_automaticInvocationTimer = new QTimer(this); m_automaticInvocationTimer->setSingleShot(true); connect(m_automaticInvocationTimer, SIGNAL(timeout()), this, SLOT(automaticInvocation())); // Keep branches expanded connect(m_presentationModel, SIGNAL(modelReset()), this, SLOT(modelReset())); connect(m_presentationModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int))); connect(m_argumentHintModel, SIGNAL(contentStateChanged(bool)), this, SLOT(argumentHintsChanged(bool))); // No smart lock, no queued connects connect(view(), SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(cursorPositionChanged())); connect(view(), SIGNAL(verticalScrollPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(updatePositionSlot())); /** * connect to all possible editing primitives */ connect(&view()->doc()->buffer(), SIGNAL(lineWrapped(KTextEditor::Cursor)), this, SLOT(wrapLine(KTextEditor::Cursor))); connect(&view()->doc()->buffer(), SIGNAL(lineUnwrapped(int)), this, SLOT(unwrapLine(int))); connect(&view()->doc()->buffer(), SIGNAL(textInserted(KTextEditor::Cursor,QString)), this, SLOT(insertText(KTextEditor::Cursor,QString))); connect(&view()->doc()->buffer(), SIGNAL(textRemoved(KTextEditor::Range,QString)), this, SLOT(removeText(KTextEditor::Range))); // This is a non-focus widget, it is passed keyboard input from the view //We need to do this, because else the focus goes to nirvana without any control when the completion-widget is clicked. setFocusPolicy(Qt::ClickFocus); m_argumentHintTree->setFocusPolicy(Qt::ClickFocus); foreach (QWidget *childWidget, findChildren()) { childWidget->setFocusPolicy(Qt::NoFocus); } //Position the entry-list so a frame can be drawn around it m_entryList->move(frameWidth(), frameWidth()); } KateCompletionWidget::~KateCompletionWidget() { } void KateCompletionWidget::viewFocusOut() { if (QApplication::focusWidget() != this) { abortCompletion(); } } void KateCompletionWidget::focusOutEvent(QFocusEvent *) { abortCompletion(); } void KateCompletionWidget::modelContentChanged() { ////qCDebug(LOG_KTE)<<">>>>>>>>>>>>>>>>"; if (m_completionRanges.isEmpty()) { //qCDebug(LOG_KTE) << "content changed, but no completion active"; abortCompletion(); return; } if (!view()->hasFocus()) { //qCDebug(LOG_KTE) << "view does not have focus"; return; } if (!m_waitingForReset.isEmpty()) { //qCDebug(LOG_KTE) << "waiting for" << m_waitingForReset.size() << "completion-models to reset"; return; } int realItemCount = 0; foreach (KTextEditor::CodeCompletionModel *model, m_presentationModel->completionModels()) { realItemCount += model->rowCount(); } if (!m_isSuspended && ((isHidden() && m_argumentHintTree->isHidden()) || m_needShow) && realItemCount != 0) { m_needShow = false; updateAndShow(); } if (m_argumentHintModel->rowCount(QModelIndex()) == 0) { m_argumentHintTree->hide(); } if (m_presentationModel->rowCount(QModelIndex()) == 0) { hide(); } //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 m_entryList->setCurrentIndex(model()->index(0, 0)); if (!model()->indexIsItem(m_entryList->currentIndex())) { QModelIndex firstIndex = model()->index(0, 0, m_entryList->currentIndex()); m_entryList->setCurrentIndex(firstIndex); //m_entryList->scrollTo(firstIndex, QAbstractItemView::PositionAtTop); } updateHeight(); //New items for the argument-hint tree may have arrived, so check whether it needs to be shown if (m_argumentHintTree->isHidden() && !m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0) { m_argumentHintTree->show(); } if (!m_noAutoHide && hideAutomaticCompletionOnExactMatch && !isHidden() && m_lastInvocationType == KTextEditor::CodeCompletionModel::AutomaticInvocation && m_presentationModel->shouldMatchHideCompletionList()) { hide(); } else if (isHidden() && !m_presentationModel->shouldMatchHideCompletionList() && m_presentationModel->rowCount(QModelIndex())) { show(); } } KateArgumentHintTree *KateCompletionWidget::argumentHintTree() const { return m_argumentHintTree; } KateArgumentHintModel *KateCompletionWidget::argumentHintModel() const { return m_argumentHintModel; } const KateCompletionModel *KateCompletionWidget::model() const { return m_presentationModel; } KateCompletionModel *KateCompletionWidget::model() { return m_presentationModel; } void KateCompletionWidget::rowsInserted(const QModelIndex &parent, int rowFrom, int rowEnd) { m_entryList->setAnimated(false); if (!model()->isGroupingEnabled()) { return; } if (!parent.isValid()) for (int i = rowFrom; i <= rowEnd; ++i) { m_entryList->expand(m_presentationModel->index(i, 0, parent)); } } KTextEditor::ViewPrivate *KateCompletionWidget::view() const { return static_cast(const_cast(parent())); } void KateCompletionWidget::argumentHintsChanged(bool hasContent) { m_dontShowArgumentHints = !hasContent; if (m_dontShowArgumentHints) { m_argumentHintTree->hide(); } else { updateArgumentHintGeometry(); } } void KateCompletionWidget::startCompletion(KTextEditor::CodeCompletionModel::InvocationType invocationType, const QList &models) { if (invocationType == KTextEditor::CodeCompletionModel::UserInvocation) { abortCompletion(); } startCompletion(KTextEditor::Range(KTextEditor::Cursor(-1, -1), KTextEditor::Cursor(-1, -1)), models, invocationType); } void KateCompletionWidget::deleteCompletionRanges() { ////qCDebug(LOG_KTE); foreach (const CompletionRange &r, m_completionRanges) { delete r.range; } m_completionRanges.clear(); } void KateCompletionWidget::startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model, KTextEditor::CodeCompletionModel::InvocationType invocationType) { QList models; if (model) { models << model; } else { models = m_sourceModels; } startCompletion(word, models, invocationType); } void KateCompletionWidget::startCompletion(const KTextEditor::Range &word, const QList &modelsToStart, KTextEditor::CodeCompletionModel::InvocationType invocationType) { ////qCDebug(LOG_KTE)<<"============"; m_isSuspended = false; m_inCompletionList = true; //Always start at the top of the completion-list m_needShow = true; if (m_completionRanges.isEmpty()) { m_noAutoHide = false; //Re-enable auto-hide on every clean restart of the completion } m_lastInvocationType = invocationType; disconnect(this->model(), SIGNAL(layoutChanged()), this, SLOT(modelContentChanged())); disconnect(this->model(), SIGNAL(modelReset()), this, SLOT(modelContentChanged())); m_dontShowArgumentHints = true; QList models = (modelsToStart.isEmpty() ? m_sourceModels : modelsToStart); foreach (KTextEditor::CodeCompletionModel *model, m_completionRanges.keys()) if (!models.contains(model)) { models << model; } if (!m_filterInstalled) { if (!QApplication::activeWindow()) { qCWarning(LOG_KTE) << "No active window to install event filter on!!"; return; } // Enable the cc box to move when the editor window is moved QApplication::activeWindow()->installEventFilter(this); m_filterInstalled = true; } m_presentationModel->clearCompletionModels(); if (invocationType == KTextEditor::CodeCompletionModel::UserInvocation) { deleteCompletionRanges(); } foreach (KTextEditor::CodeCompletionModel *model, models) { KTextEditor::Range range; if (word.isValid()) { range = word; //qCDebug(LOG_KTE)<<"word is used"; } else { range = _completionRange(model, view(), view()->cursorPosition()); //qCDebug(LOG_KTE)<<"completionRange has been called, cursor pos is"<cursorPosition(); } //qCDebug(LOG_KTE)<<"range is"<completionInvoked(view(), range, invocationType); disconnect(model, SIGNAL(waitForReset()), this, SLOT(waitForModelReset())); m_completionRanges[model] = CompletionRange(view()->doc()->newMovingRange(range, KTextEditor::MovingRange::ExpandRight | KTextEditor::MovingRange::ExpandLeft)); //In automatic invocation mode, hide the completion widget as soon as the position where the completion was started is passed to the left m_completionRanges[model].leftBoundary = view()->cursorPosition(); //In manual invocation mode, bound the activity either the point from where completion was invoked, or to the start of the range if (invocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation) if (range.start() < m_completionRanges[model].leftBoundary) { m_completionRanges[model].leftBoundary = range.start(); } if (!m_completionRanges[model].range->toRange().isValid()) { qCWarning(LOG_KTE) << "Could not construct valid smart-range from" << range << "instead got" << *m_completionRanges[model].range; abortCompletion(); return; } } m_presentationModel->setCompletionModels(models); cursorPositionChanged(); if (!m_completionRanges.isEmpty()) { connect(this->model(), SIGNAL(layoutChanged()), this, SLOT(modelContentChanged())); connect(this->model(), SIGNAL(modelReset()), this, SLOT(modelContentChanged())); //Now that all models have been notified, check whether the widget should be displayed instantly modelContentChanged(); } else { abortCompletion(); } } void KateCompletionWidget::waitForModelReset() { KTextEditor::CodeCompletionModel *senderModel = qobject_cast(sender()); if (!senderModel) { qCWarning(LOG_KTE) << "waitForReset signal from bad model"; return; } m_waitingForReset.insert(senderModel); } void KateCompletionWidget::updateAndShow() { //qCDebug(LOG_KTE)<<"*******************************************"; if (!view()->hasFocus()) { qCDebug(LOG_KTE) << "view does not have focus"; return; } setUpdatesEnabled(false); modelReset(); m_argumentHintModel->buildRows(); if (m_argumentHintModel->rowCount(QModelIndex()) != 0) { argumentHintsChanged(true); } // } //We do both actions twice here so they are stable, because they influence each other: //updatePosition updates the height, resizeColumns needs the correct height to decide over //how many rows it computs the column-width updatePosition(true); m_entryList->resizeColumns(true, true); updatePosition(true); m_entryList->resizeColumns(true, true); setUpdatesEnabled(true); if (m_argumentHintModel->rowCount(QModelIndex())) { updateArgumentHintGeometry(); m_argumentHintTree->show(); } else { m_argumentHintTree->hide(); } if (m_presentationModel->rowCount() && (!m_presentationModel->shouldMatchHideCompletionList() || !hideAutomaticCompletionOnExactMatch || m_lastInvocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation)) { show(); } else { hide(); } } void KateCompletionWidget::updatePositionSlot() { updatePosition(); } bool KateCompletionWidget::updatePosition(bool force) { if (!force && !isCompletionActive()) { return false; } if (!completionRange()) { return false; } QPoint cursorPosition = view()->cursorToCoordinate(completionRange()->start()); if (cursorPosition == QPoint(-1, -1)) { // Start of completion range is now off-screen -> abort abortCompletion(); return false; } QPoint p = view()->mapToGlobal(cursorPosition); int x = p.x() - m_entryList->columnTextViewportPosition(m_presentationModel->translateColumn(KTextEditor::CodeCompletionModel::Name)) - 7 - (m_entryList->viewport()->pos().x()); int y = p.y(); y += view()->renderer()->config()->fontMetrics().height() + 2; bool borderHit = false; if (x + width() > QApplication::desktop()->screenGeometry(view()).right()) { x = QApplication::desktop()->screenGeometry(view()).right() - width(); borderHit = true; } if (x < QApplication::desktop()->screenGeometry(view()).left()) { x = QApplication::desktop()->screenGeometry(view()).left(); borderHit = true; } move(QPoint(x, y)); updateHeight(); - updateArgumentHintGeometry(); - // //qCDebug(LOG_KTE) << "updated to" << geometry() << m_entryList->geometry() << borderHit; return borderHit; } void KateCompletionWidget::updateArgumentHintGeometry() { if (!m_dontShowArgumentHints) { //Now place the argument-hint widget QRect geom = m_argumentHintTree->geometry(); geom.moveTo(pos()); geom.setWidth(width()); geom.moveBottom(pos().y() - view()->renderer()->config()->fontMetrics().height() * 2); m_argumentHintTree->updateGeometry(geom); } } //Checks whether the given model has at least "rows" rows, also searching the second level of the tree. bool hasAtLeastNRows(int rows, QAbstractItemModel *model) { int count = 0; for (int row = 0; row < model->rowCount(); ++row) { ++count; QModelIndex index(model->index(row, 0)); if (index.isValid()) { count += model->rowCount(index); } if (count > rows) { return true; } } return false; } void KateCompletionWidget::updateHeight() { QRect geom = geometry(); int minBaseHeight = 10; int maxBaseHeight = 300; int baseHeight = 0; int calculatedCustomHeight = 0; if (hasAtLeastNRows(15, m_presentationModel)) { //If we know there is enough rows, always use max-height, we don't need to calculate size-hints baseHeight = maxBaseHeight; } else { //Calculate size-hints to determine the best height for (int row = 0; row < m_presentationModel->rowCount(); ++row) { baseHeight += treeView()->sizeHintForRow(row); QModelIndex index(m_presentationModel->index(row, 0)); if (index.isValid()) { for (int row2 = 0; row2 < m_presentationModel->rowCount(index); ++row2) { int h = 0; for (int a = 0; a < m_presentationModel->columnCount(index); ++a) { int localHeight = treeView()->sizeHintForIndex(index.child(row2, a)).height(); if (localHeight > h) { h = localHeight; } } baseHeight += h; if (baseHeight > maxBaseHeight) { break; } } if (baseHeight > maxBaseHeight) { break; } } } calculatedCustomHeight = baseHeight; } baseHeight += 2 * frameWidth(); if (m_entryList->horizontalScrollBar()->isVisible()) { baseHeight += m_entryList->horizontalScrollBar()->height(); } if (baseHeight < minBaseHeight) { baseHeight = minBaseHeight; } if (baseHeight > maxBaseHeight) { baseHeight = maxBaseHeight; m_entryList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } else { //Somewhere there seems to be a bug that makes QTreeView add a scroll-bar //even if the content exactly fits in. So forcefully disable the scroll-bar in that case m_entryList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } int newExpandingAddedHeight = 0; if (baseHeight == maxBaseHeight && model()->expandingWidgetsHeight()) { //Eventually add some more height if (calculatedCustomHeight && calculatedCustomHeight > baseHeight && calculatedCustomHeight < (maxBaseHeight + model()->expandingWidgetsHeight())) { newExpandingAddedHeight = calculatedCustomHeight - baseHeight; } else { newExpandingAddedHeight = model()->expandingWidgetsHeight(); } } if (m_expandedAddedHeightBase != baseHeight && m_expandedAddedHeightBase - baseHeight > -2 && m_expandedAddedHeightBase - baseHeight < 2) { //Re-use the stored base-height if it only slightly differs from the current one. //Reason: Qt seems to apply slightly wrong sizes when the completion-widget is moved out of the screen at the bottom, // which completely breaks this algorithm. Solution: re-use the old base-size if it only slightly differs from the computed one. baseHeight = m_expandedAddedHeightBase; } int screenBottom = QApplication::desktop()->screenGeometry(view()).bottom(); //Limit the height to the bottom of the screen int bottomPosition = baseHeight + newExpandingAddedHeight + geometry().top(); if (bottomPosition > screenBottom) { newExpandingAddedHeight -= bottomPosition - (screenBottom); } int finalHeight = baseHeight + newExpandingAddedHeight; if (finalHeight < 10) { m_entryList->resize(m_entryList->width(), height() - 2 * frameWidth()); return; } m_expandedAddedHeightBase = geometry().height(); geom.setHeight(finalHeight); //Work around a crash deep within the Qt 4.5 raster engine m_entryList->setScrollingEnabled(false); if (geometry() != geom) { setGeometry(geom); } QSize entryListSize = QSize(m_entryList->width(), finalHeight - 2 * frameWidth()); if (m_entryList->size() != entryListSize) { m_entryList->resize(entryListSize); } m_entryList->setScrollingEnabled(true); } void KateCompletionWidget::cursorPositionChanged() { ////qCDebug(LOG_KTE); if (m_completionRanges.isEmpty()) { return; } QModelIndex oldCurrentSourceIndex; if (m_inCompletionList && m_entryList->currentIndex().isValid()) { oldCurrentSourceIndex = m_presentationModel->mapToSource(m_entryList->currentIndex()); } //Check the models and eventuall abort some const QList checkCompletionRanges = m_completionRanges.keys(); for (QList::const_iterator it = checkCompletionRanges.begin(); it != checkCompletionRanges.end(); ++it) { KTextEditor::CodeCompletionModel *model = *it; if (!m_completionRanges.contains(model)) { continue; } //qCDebug(LOG_KTE)<<"range before _updateRange:"<< *range; // this might invalidate the range, therefore re-check afterwards KTextEditor::Range rangeTE = m_completionRanges[model].range->toRange(); KTextEditor::Range newRange = _updateRange(model, view(), rangeTE); if (!m_completionRanges.contains(model)) { continue; } // update value m_completionRanges[model].range->setRange(newRange); //qCDebug(LOG_KTE)<<"range after _updateRange:"<< *range; QString currentCompletion = _filterString(model, view(), *m_completionRanges[model].range, view()->cursorPosition()); if (!m_completionRanges.contains(model)) { continue; } //qCDebug(LOG_KTE)<<"after _filterString, currentCompletion="<< currentCompletion; bool abort = _shouldAbortCompletion(model, view(), *m_completionRanges[model].range, currentCompletion); if (!m_completionRanges.contains(model)) { continue; } //qCDebug(LOG_KTE)<<"after _shouldAbortCompletion:abort="<cursorPosition() < m_completionRanges[model].leftBoundary) { //qCDebug(LOG_KTE) << "aborting because of boundary: cursor:"<cursorPosition()<<"completion_Range_left_boundary:"<removeCompletionModel(model); } } else { m_presentationModel->setCurrentCompletion(model, currentCompletion); } } if (oldCurrentSourceIndex.isValid()) { QModelIndex idx = m_presentationModel->mapFromSource(oldCurrentSourceIndex); if (idx.isValid()) { //qCDebug(LOG_KTE) << "setting" << idx; m_entryList->setCurrentIndex(idx.sibling(idx.row(), 0)); // m_entryList->nextCompletion(); // m_entryList->previousCompletion(); } else { //qCDebug(LOG_KTE) << "failed to map from source"; } } m_entryList->scheduleUpdate(); } bool KateCompletionWidget::isCompletionActive() const { return !m_completionRanges.isEmpty() && ((!isHidden() && isVisible()) || (!m_argumentHintTree->isHidden() && m_argumentHintTree->isVisible())); } void KateCompletionWidget::abortCompletion() { //qCDebug(LOG_KTE) ; m_isSuspended = false; bool wasActive = isCompletionActive(); if (hasFocus()) { view()->activateWindow(); view()->setFocus(); } clear(); if (!isHidden()) { hide(); } if (!m_argumentHintTree->isHidden()) { m_argumentHintTree->hide(); } if (wasActive) { view()->sendCompletionAborted(); } } void KateCompletionWidget::clear() { m_presentationModel->clearCompletionModels(); m_argumentHintTree->clearCompletion(); m_argumentHintModel->clear(); foreach (KTextEditor::CodeCompletionModel *model, m_completionRanges.keys()) { _aborted(model, view()); } deleteCompletionRanges(); } bool KateCompletionWidget::navigateAccept() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetAccept"); } QModelIndex index = selectedIndex(); if (index.isValid()) { index.data(KTextEditor::CodeCompletionModel::AccessibilityAccept); return true; } return false; } void KateCompletionWidget::execute() { //qCDebug(LOG_KTE) ; if (!isCompletionActive()) { return; } QModelIndex index = selectedIndex(); if (!index.isValid()) { return abortCompletion(); } QModelIndex toExecute; if (index.model() == m_presentationModel) { toExecute = m_presentationModel->mapToSource(index); } else { toExecute = m_argumentHintModel->mapToSource(index); } if (!toExecute.isValid()) { qCWarning(LOG_KTE) << "Could not map index" << m_entryList->selectionModel()->currentIndex() << "to source index."; return abortCompletion(); } // encapsulate all editing as being from the code completion, and undo-able in one step. view()->doc()->editStart(); m_completionEditRunning = true; // create scoped pointer, to ensure deletion of cursor QScopedPointer oldPos(view()->doc()->newMovingCursor(view()->cursorPosition(), KTextEditor::MovingCursor::StayOnInsert)); KTextEditor::CodeCompletionModel *model = static_cast(const_cast(toExecute.model())); Q_ASSERT(model); Q_ASSERT(m_completionRanges.contains(model)); KTextEditor::Cursor start = m_completionRanges[model].range->start(); model->executeCompletionItem(view(), *m_completionRanges[model].range, toExecute); view()->doc()->editEnd(); m_completionEditRunning = false; abortCompletion(); view()->sendCompletionExecuted(start, model, toExecute); KTextEditor::Cursor newPos = view()->cursorPosition(); if (newPos > *oldPos) { m_automaticInvocationAt = newPos; m_automaticInvocationLine = view()->doc()->text(KTextEditor::Range(*oldPos, newPos)); //qCDebug(LOG_KTE) << "executed, starting automatic invocation with line" << m_automaticInvocationLine; m_lastInsertionByUser = false; m_automaticInvocationTimer->start(); } } void KateCompletionWidget::resizeEvent(QResizeEvent *event) { QFrame::resizeEvent(event); + + // keep argument hint geometry in sync + if (m_argumentHintTree->isVisible()) { + updateArgumentHintGeometry(); + } +} + +void KateCompletionWidget::moveEvent(QMoveEvent *event) +{ + QFrame::moveEvent(event); + + // keep argument hint geometry in sync + if (m_argumentHintTree->isVisible()) { + updateArgumentHintGeometry(); + } } void KateCompletionWidget::showEvent(QShowEvent *event) { m_isSuspended = false; QFrame::showEvent(event); if (!m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0) { m_argumentHintTree->show(); } } KTextEditor::MovingRange *KateCompletionWidget::completionRange(KTextEditor::CodeCompletionModel *model) const { if (!model) { if (m_completionRanges.isEmpty()) { return nullptr; } KTextEditor::MovingRange *ret = m_completionRanges.begin()->range; foreach (const CompletionRange &range, m_completionRanges) if (range.range->start() > ret->start()) { ret = range.range; } return ret; } if (m_completionRanges.contains(model)) { return m_completionRanges[model].range; } else { return nullptr; } } QMap KateCompletionWidget::completionRanges() const { return m_completionRanges; } void KateCompletionWidget::modelReset() { setUpdatesEnabled(false); m_entryList->setAnimated(false); m_argumentHintTree->setAnimated(false); ///We need to do this by hand, because QTreeView::expandAll is very inefficient. ///It creates a QPersistentModelIndex for every single item in the whole tree.. for (int row = 0; row < m_argumentHintModel->rowCount(QModelIndex()); ++row) { QModelIndex index(m_argumentHintModel->index(row, 0, QModelIndex())); if (!m_argumentHintTree->isExpanded(index)) { m_argumentHintTree->expand(index); } } for (int row = 0; row < m_entryList->model()->rowCount(QModelIndex()); ++row) { QModelIndex index(m_entryList->model()->index(row, 0, QModelIndex())); if (!m_entryList->isExpanded(index)) { m_entryList->expand(index); } } setUpdatesEnabled(true); } KateCompletionTree *KateCompletionWidget::treeView() const { return m_entryList; } QModelIndex KateCompletionWidget::selectedIndex() const { if (!isCompletionActive()) { return QModelIndex(); } if (m_inCompletionList) { return m_entryList->currentIndex(); } else { return m_argumentHintTree->currentIndex(); } } bool KateCompletionWidget::navigateLeft() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetLeft"); } QModelIndex index = selectedIndex(); if (index.isValid()) { index.data(KTextEditor::CodeCompletionModel::AccessibilityPrevious); return true; } return false; } bool KateCompletionWidget::navigateRight() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { ///@todo post 4.2: Make these slots public interface, or create an interface using virtual functions QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetRight"); } QModelIndex index = selectedIndex(); if (index.isValid()) { index.data(KTextEditor::CodeCompletionModel::AccessibilityNext); return true; } return false; } bool KateCompletionWidget::hadNavigation() const { return m_hadCompletionNavigation; } void KateCompletionWidget::resetHadNavigation() { m_hadCompletionNavigation = false; } bool KateCompletionWidget::navigateBack() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetBack"); } return false; } bool KateCompletionWidget::toggleExpanded(bool forceExpand, bool forceUnExpand) { if ((canExpandCurrentItem() || forceExpand) && !forceUnExpand) { bool ret = canExpandCurrentItem(); setCurrentItemExpanded(true); return ret; } else if (canCollapseCurrentItem() || forceUnExpand) { bool ret = canCollapseCurrentItem(); setCurrentItemExpanded(false); return ret; } return false; } bool KateCompletionWidget::canExpandCurrentItem() const { if (m_inCompletionList) { if (!m_entryList->currentIndex().isValid()) { return false; } return model()->isExpandable(m_entryList->currentIndex()) && !model()->isExpanded(m_entryList->currentIndex()); } else { if (!m_argumentHintTree->currentIndex().isValid()) { return false; } return argumentHintModel()->isExpandable(m_argumentHintTree->currentIndex()) && !argumentHintModel()->isExpanded(m_argumentHintTree->currentIndex()); } } bool KateCompletionWidget::canCollapseCurrentItem() const { if (m_inCompletionList) { if (!m_entryList->currentIndex().isValid()) { return false; } return model()->isExpandable(m_entryList->currentIndex()) && model()->isExpanded(m_entryList->currentIndex()); } else { if (!m_argumentHintTree->currentIndex().isValid()) { return false; } return m_argumentHintModel->isExpandable(m_argumentHintTree->currentIndex()) && m_argumentHintModel->isExpanded(m_argumentHintTree->currentIndex()); } } void KateCompletionWidget::setCurrentItemExpanded(bool expanded) { if (m_inCompletionList) { if (!m_entryList->currentIndex().isValid()) { return; } model()->setExpanded(m_entryList->currentIndex(), expanded); updateHeight(); } else { if (!m_argumentHintTree->currentIndex().isValid()) { return; } m_argumentHintModel->setExpanded(m_argumentHintTree->currentIndex(), expanded); } } bool KateCompletionWidget::eventFilter(QObject *watched, QEvent *event) { bool ret = QFrame::eventFilter(watched, event); if (watched != this) if (event->type() == QEvent::Move) { updatePosition(); } return ret; } bool KateCompletionWidget::navigateDown() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetDown"); } return false; } bool KateCompletionWidget::navigateUp() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetUp"); } return false; } QWidget *KateCompletionWidget::currentEmbeddedWidget() { QModelIndex index = selectedIndex(); if (!index.isValid()) { return nullptr; } if (qobject_cast(index.model())) { const ExpandingWidgetModel *model = static_cast(index.model()); if (model->isExpanded(index)) { return model->expandingWidget(index); } } return nullptr; } void KateCompletionWidget::cursorDown() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { m_entryList->nextCompletion(); } else { if (!m_argumentHintTree->nextCompletion()) { switchList(); } } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::cursorUp() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { if (!m_entryList->previousCompletion()) { switchList(); } } else { m_argumentHintTree->previousCompletion(); } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::pageDown() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { m_entryList->pageDown(); } else { if (!m_argumentHintTree->pageDown()) { switchList(); } } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::pageUp() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { if (!m_entryList->pageUp()) { switchList(); } } else { m_argumentHintTree->pageUp(); } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::top() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { m_entryList->top(); } else { m_argumentHintTree->top(); } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::bottom() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { m_entryList->bottom(); } else { m_argumentHintTree->bottom(); } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::switchList() { if (m_inCompletionList) { if (m_argumentHintModel->rowCount(QModelIndex()) != 0) { m_entryList->setCurrentIndex(QModelIndex()); m_argumentHintTree->setCurrentIndex(m_argumentHintModel->index(m_argumentHintModel->rowCount(QModelIndex()) - 1, 0)); m_inCompletionList = false; } } else { if (m_presentationModel->rowCount(QModelIndex()) != 0) { m_argumentHintTree->setCurrentIndex(QModelIndex()); m_entryList->setCurrentIndex(m_presentationModel->index(0, 0)); if (model()->hasGroups()) { //If we have groups we have to move on, because the first item is a label m_entryList->nextCompletion(); } m_inCompletionList = true; } } } void KateCompletionWidget::showConfig() { abortCompletion(); m_configWidget->exec(); } void KateCompletionWidget::completionModelReset() { KTextEditor::CodeCompletionModel *model = qobject_cast(sender()); if (!model) { qCWarning(LOG_KTE) << "bad sender"; return; } if (!m_waitingForReset.contains(model)) { return; } m_waitingForReset.remove(model); if (m_waitingForReset.isEmpty()) { if (!isCompletionActive()) { //qCDebug(LOG_KTE) << "all completion-models we waited for are ready. Last one: " << model->objectName(); //Eventually show the completion-list if this was the last model we were waiting for //Use a queued connection once again to make sure that KateCompletionModel is notified before we are QMetaObject::invokeMethod(this, "modelContentChanged", Qt::QueuedConnection); } } } void KateCompletionWidget::modelDestroyed(QObject *model) { m_sourceModels.removeAll(static_cast(model)); abortCompletion(); } void KateCompletionWidget::registerCompletionModel(KTextEditor::CodeCompletionModel *model) { if (m_sourceModels.contains(model)) { return; } connect(model, SIGNAL(destroyed(QObject*)), SLOT(modelDestroyed(QObject*))); //This connection must not be queued connect(model, SIGNAL(modelReset()), SLOT(completionModelReset())); m_sourceModels.append(model); if (isCompletionActive()) { m_presentationModel->addCompletionModel(model); } } void KateCompletionWidget::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) { disconnect(model, SIGNAL(destroyed(QObject*)), this, SLOT(modelDestroyed(QObject*))); disconnect(model, SIGNAL(modelReset()), this, SLOT(completionModelReset())); m_sourceModels.removeAll(model); abortCompletion(); } bool KateCompletionWidget::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const { return m_sourceModels.contains(model); } int KateCompletionWidget::automaticInvocationDelay() const { return m_automaticInvocationDelay; } void KateCompletionWidget::setAutomaticInvocationDelay(int delay) { m_automaticInvocationDelay = delay; } void KateCompletionWidget::wrapLine(const KTextEditor::Cursor &) { m_lastInsertionByUser = !m_completionEditRunning; // wrap line, be done m_automaticInvocationLine.clear(); m_automaticInvocationTimer->stop(); } void KateCompletionWidget::unwrapLine(int) { m_lastInsertionByUser = !m_completionEditRunning; // just removal m_automaticInvocationLine.clear(); m_automaticInvocationTimer->stop(); } void KateCompletionWidget::insertText(const KTextEditor::Cursor &position, const QString &text) { m_lastInsertionByUser = !m_completionEditRunning; // no invoke? if (!view()->isAutomaticInvocationEnabled()) { m_automaticInvocationLine.clear(); m_automaticInvocationTimer->stop(); return; } if (m_automaticInvocationAt != position) { m_automaticInvocationLine.clear(); m_lastInsertionByUser = !m_completionEditRunning; } m_automaticInvocationLine += text; m_automaticInvocationAt = position; m_automaticInvocationAt.setColumn(position.column() + text.length()); if (m_automaticInvocationLine.isEmpty()) { m_automaticInvocationTimer->stop(); return; } m_automaticInvocationTimer->start(m_automaticInvocationDelay); } void KateCompletionWidget::removeText(const KTextEditor::Range &) { m_lastInsertionByUser = !m_completionEditRunning; // just removal m_automaticInvocationLine.clear(); m_automaticInvocationTimer->stop(); } void KateCompletionWidget::automaticInvocation() { //qCDebug(LOG_KTE)<<"m_automaticInvocationAt:"<cursorPosition(); if (m_automaticInvocationAt != view()->cursorPosition()) { return; } bool start = false; QList models; //qCDebug(LOG_KTE)<<"checking models"; foreach (KTextEditor::CodeCompletionModel *model, m_sourceModels) { //qCDebug(LOG_KTE)<<"m_completionRanges contains model?:"<cursorPosition()); //qCDebug(LOG_KTE)<<"start="<commonPrefix((m_inCompletionList && !shellLikeTabCompletion) ? m_entryList->currentIndex() : QModelIndex()); if (!prefix.isEmpty()) { view()->insertText(prefix); } else if (shellLikeTabCompletion) { cursorDown(); return; } } else { if (shellLikeTabCompletion) { cursorUp(); return; } //Reset left boundaries, so completion isn't stopped typedef QMap CompletionRangeMap; for (CompletionRangeMap::iterator it = m_completionRanges.begin(); it != m_completionRanges.end(); ++it) { (*it).leftBoundary = (*it).range->start(); } //Remove suffix until the completion-list filter is widened again uint itemCount = m_presentationModel->filteredItemCount(); while (view()->cursorPosition().column() > 0 && m_presentationModel->filteredItemCount() == itemCount) { KTextEditor::Range lastcharRange = KTextEditor::Range(view()->cursorPosition() - KTextEditor::Cursor(0, 1), view()->cursorPosition()); QString cursorText = view()->document()->text(lastcharRange); if (!cursorText[0].isSpace()) { view()->document()->removeText(lastcharRange); QApplication::sendPostedEvents(); } else { break; } } } } diff --git a/src/completion/katecompletionwidget.h b/src/completion/katecompletionwidget.h index 3df2e773..98c6a9df 100644 --- a/src/completion/katecompletionwidget.h +++ b/src/completion/katecompletionwidget.h @@ -1,247 +1,248 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2005-2006 Hamish Rodda * Copyright (C) 2007-2008 David Nolden * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef KATECOMPLETIONWIDGET_H #define KATECOMPLETIONWIDGET_H #include #include #include #include #include #include class QToolButton; class QPushButton; class QLabel; class QTimer; namespace KTextEditor { class ViewPrivate; } class KateCompletionModel; class KateCompletionTree; class KateArgumentHintTree; class KateArgumentHintModel; namespace KTextEditor { class EmbeddedWidgetInterface; } /** * This is the code completion's main widget, and also contains the * core interface logic. * * @author Hamish Rodda */ class KTEXTEDITOR_EXPORT KateCompletionWidget : public QFrame { Q_OBJECT public: explicit KateCompletionWidget(KTextEditor::ViewPrivate *parent); ~KateCompletionWidget(); KTextEditor::ViewPrivate *view() const; KateCompletionTree *treeView() const; bool isCompletionActive() const; void startCompletion(KTextEditor::CodeCompletionModel::InvocationType invocationType, const QList &models = QList()); void startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model, KTextEditor::CodeCompletionModel::InvocationType invocationType = KTextEditor::CodeCompletionModel::ManualInvocation); void startCompletion(const KTextEditor::Range &word, const QList &models = QList(), KTextEditor::CodeCompletionModel::InvocationType invocationType = KTextEditor::CodeCompletionModel::ManualInvocation); void userInvokedCompletion(); public Q_SLOTS: //Executed when return is pressed while completion is active. void execute(); void cursorDown(); void cursorUp(); public: void tab(bool shift); ///Returns whether the current item was expanded/unexpanded bool toggleExpanded(bool forceExpand = false, bool forceUnExpand = false); const KateCompletionModel *model() const; KateCompletionModel *model(); void registerCompletionModel(KTextEditor::CodeCompletionModel *model); void unregisterCompletionModel(KTextEditor::CodeCompletionModel *model); bool isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const; int automaticInvocationDelay() const; void setAutomaticInvocationDelay(int delay); struct CompletionRange { CompletionRange() : range(nullptr) { } explicit CompletionRange(KTextEditor::MovingRange *r) : range(r) { } bool operator==(const CompletionRange &rhs) const { return range->toRange() == rhs.range->toRange(); } KTextEditor::MovingRange *range; //Whenever the cursor goes before this position, the completion is stopped, unless it is invalid. KTextEditor::Cursor leftBoundary; }; KTextEditor::MovingRange *completionRange(KTextEditor::CodeCompletionModel *model = nullptr) const; QMap completionRanges() const; // Navigation void pageDown(); void pageUp(); void top(); void bottom(); QWidget *currentEmbeddedWidget(); bool canExpandCurrentItem() const; bool canCollapseCurrentItem() const; void setCurrentItemExpanded(bool); //Returns true if a screen border has been hit bool updatePosition(bool force = false); bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; KateArgumentHintTree *argumentHintTree() const; KateArgumentHintModel *argumentHintModel() const; ///Called by KateViewInternal, because we need the specific information from the event. void updateHeight(); public Q_SLOTS: void waitForModelReset(); void abortCompletion(); void showConfig(); /* void viewFocusIn(); void viewFocusOut();*/ void updatePositionSlot(); void automaticInvocation(); /* void updateFocus();*/ void argumentHintsChanged(bool hasContent); bool navigateUp(); bool navigateDown(); bool navigateLeft(); bool navigateRight(); bool navigateAccept(); bool navigateBack(); bool hadNavigation() const; void resetHadNavigation(); protected: void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; + void moveEvent(QMoveEvent *event) Q_DECL_OVERRIDE; void focusOutEvent(QFocusEvent * event) Q_DECL_OVERRIDE; private Q_SLOTS: void completionModelReset(); void modelDestroyed(QObject *model); void modelContentChanged(); void cursorPositionChanged(); void modelReset(); void rowsInserted(const QModelIndex &parent, int row, int rowEnd); void viewFocusOut(); void wrapLine(const KTextEditor::Cursor &position); void unwrapLine(int line); void insertText(const KTextEditor::Cursor &position, const QString &text); void removeText(const KTextEditor::Range &range); private: void updateAndShow(); void updateArgumentHintGeometry(); QModelIndex selectedIndex() const; void clear(); //Switch cursor between argument-hint list / completion-list void switchList(); KTextEditor::Range determineRange() const; void completionRangeChanged(KTextEditor::CodeCompletionModel *, const KTextEditor::Range &word); void deleteCompletionRanges(); QList m_sourceModels; KateCompletionModel *m_presentationModel; QMap m_completionRanges; QSet m_waitingForReset; KTextEditor::Cursor m_lastCursorPosition; KateCompletionTree *m_entryList; KateArgumentHintModel *m_argumentHintModel; KateArgumentHintTree *m_argumentHintTree; QTimer *m_automaticInvocationTimer; //QTimer* m_updateFocusTimer; QWidget *m_statusBar; QToolButton *m_sortButton; QLabel *m_sortText; QToolButton *m_filterButton; QLabel *m_filterText; QPushButton *m_configButton; KTextEditor::Cursor m_automaticInvocationAt; QString m_automaticInvocationLine; int m_automaticInvocationDelay; bool m_filterInstalled; class KateCompletionConfig *m_configWidget; bool m_lastInsertionByUser; bool m_inCompletionList; //Are we in the completion-list? If not, we're in the argument-hint list bool m_isSuspended; bool m_dontShowArgumentHints; //Used temporarily to prevent flashing bool m_needShow; bool m_hadCompletionNavigation; bool m_haveExactMatch; bool m_noAutoHide; /** * is a completion edit ongoing? */ bool m_completionEditRunning; int m_expandedAddedHeightBase; KTextEditor::CodeCompletionModel::InvocationType m_lastInvocationType; }; #endif