mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
322 lines
8.0 KiB
C++
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();
|
|
}
|
|
}
|
|
}
|