File indexing completed on 2025-01-05 05:14:51

0001 /*
0002 SPDX-FileCopyrightText: 2021 Hamed Masafi <hamed.masfi@gmail.com>
0003 
0004 SPDX-License-Identifier: GPL-3.0-or-later
0005 */
0006 
0007 #include "completiontextedit.h"
0008 #include <QAbstractItemView>
0009 #include <QCompleter>
0010 #include <QKeyEvent>
0011 #include <QScrollBar>
0012 #include <QStringListModel>
0013 
0014 CompletionTextEdit::CompletionTextEdit(QWidget *parent)
0015     : QTextEdit(parent)
0016     , mCompletionModel(new QStringListModel(this))
0017     , mCompleter(new QCompleter(this))
0018 {
0019     mCompleter->setWidget(this);
0020     mCompleter->setModel(mCompletionModel);
0021     mCompleter->setCompletionMode(QCompleter::PopupCompletion);
0022     mCompleter->setCaseSensitivity(Qt::CaseInsensitive);
0023     QObject::connect(mCompleter, QOverload<const QString &>::of(&QCompleter::activated), this, &CompletionTextEdit::insertCompletion);
0024 }
0025 
0026 void CompletionTextEdit::setCompleter(QCompleter *completer)
0027 {
0028     Q_UNUSED(completer)
0029 
0030     if (mCompleter)
0031         mCompleter->disconnect(this);
0032 }
0033 
0034 QCompleter *CompletionTextEdit::completer() const
0035 {
0036     return mCompleter;
0037 }
0038 
0039 void CompletionTextEdit::addWord(const QString &word)
0040 {
0041     mWords.append(word);
0042 }
0043 
0044 void CompletionTextEdit::addWords(const QStringList &words)
0045 {
0046     mWords.append(words);
0047 }
0048 
0049 void CompletionTextEdit::insertCompletion(const QString &completion)
0050 {
0051     if (mCompleter->widget() != this)
0052         return;
0053     QTextCursor tc = textCursor();
0054     const int extra = completion.length() - mCompleter->completionPrefix().length();
0055     tc.movePosition(QTextCursor::Left);
0056     tc.movePosition(QTextCursor::EndOfWord);
0057     tc.insertText(completion.right(extra));
0058     setTextCursor(tc);
0059 }
0060 
0061 QString CompletionTextEdit::textUnderCursor() const
0062 {
0063     QTextCursor tc = textCursor();
0064     tc.select(QTextCursor::WordUnderCursor);
0065     return tc.selectedText();
0066 }
0067 
0068 const QStringList &CompletionTextEdit::words() const
0069 {
0070     return mWords;
0071 }
0072 
0073 void CompletionTextEdit::setWords(const QStringList &newWords)
0074 {
0075     mWords = newWords;
0076 }
0077 
0078 void CompletionTextEdit::begin()
0079 {
0080     mCompletionModel->setStringList(mWords);
0081 }
0082 
0083 void CompletionTextEdit::focusInEvent(QFocusEvent *e)
0084 {
0085     if (mCompleter)
0086         mCompleter->setWidget(this);
0087     QTextEdit::focusInEvent(e);
0088 }
0089 
0090 void CompletionTextEdit::keyPressEvent(QKeyEvent *e)
0091 {
0092     if (mCompleter && mCompleter->popup()->isVisible()) {
0093         // The following keys are forwarded by the completer to the widget
0094         switch (e->key()) {
0095         case Qt::Key_Enter:
0096         case Qt::Key_Return:
0097         case Qt::Key_Escape:
0098         case Qt::Key_Tab:
0099         case Qt::Key_Backtab:
0100             e->ignore();
0101             return; // let the completer do default behavior
0102         default:
0103             break;
0104         }
0105     }
0106 
0107     const bool isShortcut = (e->modifiers().testFlag(Qt::ControlModifier) && e->key() == Qt::Key_E); // CTRL+E
0108     if (!mCompleter || !isShortcut) // do not process the shortcut when we have a completer
0109         QTextEdit::keyPressEvent(e);
0110 
0111     const bool ctrlOrShift = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::ShiftModifier);
0112     if (!mCompleter || (ctrlOrShift && e->text().isEmpty()))
0113         return;
0114 
0115     static QString eow(QStringLiteral("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=")); // end of word
0116     const bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
0117     QString completionPrefix = textUnderCursor();
0118 
0119     if (!isShortcut && (hasModifier || e->text().isEmpty() || completionPrefix.length() < 3 || eow.contains(e->text().right(1)))) {
0120         mCompleter->popup()->hide();
0121         return;
0122     }
0123 
0124     if (completionPrefix != mCompleter->completionPrefix()) {
0125         mCompleter->setCompletionPrefix(completionPrefix);
0126         mCompleter->popup()->setCurrentIndex(mCompleter->completionModel()->index(0, 0));
0127     }
0128     QRect cr = cursorRect();
0129     cr.setWidth(mCompleter->popup()->sizeHintForColumn(0) + mCompleter->popup()->verticalScrollBar()->sizeHint().width());
0130     mCompleter->complete(cr); // popup it up!
0131 }
0132 
0133 #include "moc_completiontextedit.cpp"