vnote/src/vtexteditcompleter.cpp
2018-08-03 19:13:33 +08:00

322 lines
8.0 KiB
C++

#include "vtexteditcompleter.h"
#include <QStringListModel>
#include <QStyledItemDelegate>
#include <QScrollBar>
#include <QDebug>
#include <QEvent>
#include <QKeyEvent>
#include "utils/vutils.h"
#include "veditor.h"
#include "vmainwindow.h"
extern VMainWindow *g_mainWin;
VTextEditCompleter::VTextEditCompleter(QObject *p_parent)
: QCompleter(p_parent),
m_initialized(false)
{
}
void VTextEditCompleter::performCompletion(const QStringList &p_words,
const QString &p_prefix,
Qt::CaseSensitivity p_cs,
bool p_reversed,
const QRect &p_rect,
VEditor *p_editor)
{
init();
m_editor = p_editor;
setWidget(m_editor->getEditor());
m_model->setStringList(p_words);
setCaseSensitivity(p_cs);
setCompletionPrefix(p_prefix);
int cnt = completionCount();
if (cnt == 0) {
finishCompletion();
return;
}
selectRow(p_reversed ? cnt - 1 : 0);
if (cnt == 1 && currentCompletion() == p_prefix) {
finishCompletion();
return;
}
g_mainWin->setCaptainModeEnabled(false);
m_insertedCompletion = p_prefix;
insertCurrentCompletion();
auto pu = popup();
QRect rt(p_rect);
rt.setWidth(pu->sizeHintForColumn(0) + pu->verticalScrollBar()->sizeHint().width());
complete(rt);
}
void VTextEditCompleter::init()
{
if (m_initialized) {
return;
}
m_initialized = true;
m_model = new QStringListModel(this);
setModel(m_model);
popup()->setProperty("TextEdit", true);
popup()->setItemDelegate(new QStyledItemDelegate(this));
connect(this, static_cast<void(QCompleter::*)(const QString &)>(&QCompleter::activated),
this, [this](const QString &p_text) {
insertCompletion(p_text);
finishCompletion();
});
}
void VTextEditCompleter::selectNextCompletion(bool p_reversed)
{
QModelIndex curIndex = popup()->currentIndex();
if (p_reversed) {
if (curIndex.isValid()) {
int row = curIndex.row();
if (row == 0) {
setCurrentIndex(QModelIndex());
} else {
selectRow(row - 1);
}
} else {
selectRow(completionCount() - 1);
}
} else {
if (curIndex.isValid()) {
int row = curIndex.row();
if (!selectRow(row + 1)) {
setCurrentIndex(QModelIndex());
}
} else {
selectRow(0);
}
}
}
bool VTextEditCompleter::selectRow(int p_row)
{
if (setCurrentRow(p_row)) {
setCurrentIndex(currentIndex());
return true;
}
return false;
}
void VTextEditCompleter::setCurrentIndex(QModelIndex p_index, bool p_select)
{
auto pu = popup();
if (!pu) {
return;
}
if (!p_select) {
pu->selectionModel()->setCurrentIndex(p_index, QItemSelectionModel::NoUpdate);
} else {
if (!p_index.isValid()) {
pu->selectionModel()->clear();
} else {
pu->selectionModel()->setCurrentIndex(p_index, QItemSelectionModel::ClearAndSelect);
}
}
p_index = pu->selectionModel()->currentIndex();
if (!p_index.isValid()) {
pu->scrollToTop();
} else {
pu->scrollTo(p_index);
}
}
bool VTextEditCompleter::eventFilter(QObject *p_obj, QEvent *p_eve)
{
switch (p_eve->type()) {
case QEvent::KeyPress:
{
if (p_obj != popup() || !m_editor) {
break;
}
bool exited = false;
QKeyEvent *ke = static_cast<QKeyEvent *>(p_eve);
const int key = ke->key();
const int modifiers = ke->modifiers();
switch (key) {
case Qt::Key_N:
V_FALLTHROUGH;
case Qt::Key_P:
if (VUtils::isControlModifierForVim(modifiers)) {
selectNextCompletion(key == Qt::Key_P);
insertCurrentCompletion();
return true;
}
break;
case Qt::Key_J:
V_FALLTHROUGH;
case Qt::Key_K:
if (VUtils::isControlModifierForVim(modifiers)) {
selectNextCompletion(key == Qt::Key_K);
return true;
}
break;
case Qt::Key_BracketLeft:
if (!VUtils::isControlModifierForVim(modifiers)) {
break;
}
V_FALLTHROUGH;
case Qt::Key_Escape:
exited = true;
// Propogate this event to the editor widget for Vim mode.
if (!m_editor->getVim()) {
finishCompletion();
return true;
}
break;
case Qt::Key_E:
if (VUtils::isControlModifierForVim(modifiers)) {
cancelCompletion();
return true;
}
break;
case Qt::Key_Enter:
case Qt::Key_Return:
{
if (m_insertedCompletion != currentCompletion()) {
insertCurrentCompletion();
finishCompletion();
return true;
} else {
exited = true;
}
break;
}
default:
break;
}
int cursorPos = -1;
if (!exited) {
cursorPos = m_editor->textCursorW().position();
}
bool ret = QCompleter::eventFilter(p_obj, p_eve);
if (!exited) {
// Detect if cursor position changed after key press.
int pos = m_editor->textCursorW().position();
if (pos == cursorPos - 1) {
// Deleted one char.
if (m_insertedCompletion.size() > 1) {
updatePrefix(m_editor->fetchCompletionPrefix());
} else {
exited = true;
}
} else if (pos == cursorPos + 1) {
// Added one char.
QString prefix = m_editor->fetchCompletionPrefix();
if (prefix.size() == m_insertedCompletion.size() + 1
&& prefix.startsWith(m_insertedCompletion)) {
updatePrefix(prefix);
} else {
exited = true;
}
} else if (pos != cursorPos) {
exited = true;
}
}
if (exited) {
// finishCompletion() will do clean up. Must be called after QCompleter::eventFilter().
finishCompletion();
}
return ret;
}
case QEvent::Hide:
{
// Completion exited.
cleanUp();
break;
}
default:
break;
}
return QCompleter::eventFilter(p_obj, p_eve);
}
void VTextEditCompleter::insertCurrentCompletion()
{
QString completion;
QModelIndex curIndex = popup()->currentIndex();
if (curIndex.isValid()) {
completion = currentCompletion();
} else {
completion = completionPrefix();
}
insertCompletion(completion);
}
void VTextEditCompleter::insertCompletion(const QString &p_completion)
{
if (m_insertedCompletion == p_completion) {
return;
}
m_editor->insertCompletion(m_insertedCompletion, p_completion);
m_insertedCompletion = p_completion;
}
void VTextEditCompleter::cancelCompletion()
{
insertCompletion(completionPrefix());
finishCompletion();
}
void VTextEditCompleter::cleanUp()
{
// Do not clean up m_editor and m_insertedCompletion, since activated()
// signal is after the HideEvent.
setWidget(NULL);
g_mainWin->setCaptainModeEnabled(true);
}
void VTextEditCompleter::updatePrefix(const QString &p_prefix)
{
m_insertedCompletion = p_prefix;
setCompletionPrefix(p_prefix);
int cnt = completionCount();
if (cnt == 0) {
finishCompletion();
} else if (cnt == 1) {
setCurrentRow(0);
if (currentCompletion() == p_prefix) {
finishCompletion();
}
}
}