mirror-chatterino2/src/widgets/helper/ResizingTextEdit.cpp

282 lines
6.7 KiB
C++
Raw Normal View History

#include "widgets/helper/ResizingTextEdit.hpp"
2018-06-26 15:33:51 +02:00
#include "common/Common.hpp"
#include "common/CompletionModel.hpp"
#include "singletons/Settings.hpp"
#include <QMimeData>
2017-07-09 00:09:02 +02:00
2018-06-26 16:37:59 +02:00
namespace chatterino {
2017-07-09 00:09:02 +02:00
ResizingTextEdit::ResizingTextEdit()
{
auto sizePolicy = this->sizePolicy();
sizePolicy.setHeightForWidth(true);
sizePolicy.setVerticalPolicy(QSizePolicy::Preferred);
this->setSizePolicy(sizePolicy);
this->setAcceptRichText(false);
2017-07-09 00:09:02 +02:00
2018-08-06 21:17:03 +02:00
QObject::connect(this, &QTextEdit::textChanged, this,
&QWidget::updateGeometry);
// Whenever the setting for emote completion changes, force a
// refresh on the completion model the next time "Tab" is pressed
getSettings()->prefixOnlyEmoteCompletion.connect(
[this] { this->completionInProgress_ = false; });
this->setFocusPolicy(Qt::ClickFocus);
2017-07-09 00:09:02 +02:00
}
QSize ResizingTextEdit::sizeHint() const
{
return QSize(this->width(), this->heightForWidth(this->width()));
}
bool ResizingTextEdit::hasHeightForWidth() const
{
return true;
}
int ResizingTextEdit::heightForWidth(int) const
{
auto margins = this->contentsMargins();
return margins.top() + document()->size().height() + margins.bottom() + 5;
}
QString ResizingTextEdit::textUnderCursor(bool *hadSpace) const
2017-07-09 00:09:02 +02:00
{
auto currentText = this->toPlainText();
QTextCursor tc = this->textCursor();
auto textUpToCursor = currentText.left(tc.selectionStart());
auto words = textUpToCursor.splitRef(' ');
2018-10-21 13:43:02 +02:00
if (words.size() == 0)
{
return QString();
}
bool first = true;
QString lastWord;
2018-10-21 13:43:02 +02:00
for (auto it = words.crbegin(); it != words.crend(); ++it)
{
auto word = *it;
2018-10-21 13:43:02 +02:00
if (first && word.isEmpty())
{
first = false;
2018-10-21 13:43:02 +02:00
if (hadSpace != nullptr)
{
*hadSpace = true;
}
continue;
}
lastWord = word.toString();
break;
}
2018-10-21 13:43:02 +02:00
if (lastWord.isEmpty())
{
return QString();
}
return lastWord;
2017-07-09 00:09:02 +02:00
}
void ResizingTextEdit::keyPressEvent(QKeyEvent *event)
{
event->ignore();
this->keyPressed.invoke(event);
2017-07-09 00:09:02 +02:00
2018-08-06 21:17:03 +02:00
bool doComplete =
(event->key() == Qt::Key_Tab || event->key() == Qt::Key_Backtab) &&
(event->modifiers() & Qt::ControlModifier) == Qt::NoModifier &&
!event->isAccepted();
2018-02-05 15:11:50 +01:00
2018-10-21 13:43:02 +02:00
if (doComplete)
{
2018-02-05 15:11:50 +01:00
// check if there is a completer
2018-10-21 13:43:02 +02:00
if (!this->completer_)
{
return;
}
2018-02-05 15:11:50 +01:00
2017-07-09 00:09:02 +02:00
QString currentCompletionPrefix = this->textUnderCursor();
2019-08-21 01:08:15 +02:00
bool isFirstWord = [&] {
QString plainText = this->toPlainText();
for (int i = this->textCursor().position(); i >= 0; i--)
{
if (plainText[i] == ' ')
{
return false;
}
}
return true;
}();
2018-02-05 15:11:50 +01:00
// check if there is something to complete
if (currentCompletionPrefix.size() <= 1)
2018-10-21 13:43:02 +02:00
{
2017-07-09 00:09:02 +02:00
return;
}
2018-08-06 21:17:03 +02:00
auto *completionModel =
static_cast<CompletionModel *>(this->completer_->model());
2018-10-21 13:43:02 +02:00
if (!this->completionInProgress_)
{
2018-08-06 21:17:03 +02:00
// First type pressing tab after modifying a message, we refresh our
// completion model
2018-07-06 19:23:47 +02:00
this->completer_->setModel(completionModel);
2019-08-21 01:08:15 +02:00
completionModel->refresh(currentCompletionPrefix, isFirstWord);
2018-07-06 19:23:47 +02:00
this->completionInProgress_ = true;
this->completer_->setCompletionPrefix(currentCompletionPrefix);
this->completer_->complete();
2017-07-09 00:09:02 +02:00
return;
}
// scrolling through selections
2018-10-21 13:43:02 +02:00
if (event->key() == Qt::Key_Tab)
{
2018-08-06 21:17:03 +02:00
if (!this->completer_->setCurrentRow(
2018-10-21 13:43:02 +02:00
this->completer_->currentRow() + 1))
{
// wrap over and start again
2018-07-06 19:23:47 +02:00
this->completer_->setCurrentRow(0);
}
2018-10-21 13:43:02 +02:00
}
else
{
2018-08-06 21:17:03 +02:00
if (!this->completer_->setCurrentRow(
2018-10-21 13:43:02 +02:00
this->completer_->currentRow() - 1))
{
// wrap over and start again
2018-08-06 21:17:03 +02:00
this->completer_->setCurrentRow(
this->completer_->completionCount() - 1);
}
2017-07-09 00:09:02 +02:00
}
2018-07-06 19:23:47 +02:00
this->completer_->complete();
2017-07-09 00:09:02 +02:00
return;
}
2018-02-05 15:11:50 +01:00
2020-09-25 22:59:20 +02:00
if (!event->text().isEmpty())
2018-10-21 13:43:02 +02:00
{
2018-07-06 19:23:47 +02:00
this->completionInProgress_ = false;
}
2017-07-09 00:09:02 +02:00
2018-10-21 13:43:02 +02:00
if (!event->isAccepted())
{
2017-07-09 00:09:02 +02:00
QTextEdit::keyPressEvent(event);
}
}
2018-05-25 14:57:17 +02:00
void ResizingTextEdit::focusInEvent(QFocusEvent *event)
{
QTextEdit::focusInEvent(event);
2018-10-21 13:43:02 +02:00
if (event->gotFocus())
{
2018-05-25 14:57:17 +02:00
this->focused.invoke();
}
}
void ResizingTextEdit::focusOutEvent(QFocusEvent *event)
{
QTextEdit::focusOutEvent(event);
2018-10-21 13:43:02 +02:00
if (event->lostFocus())
{
this->focusLost.invoke();
}
}
2017-07-09 00:09:02 +02:00
void ResizingTextEdit::setCompleter(QCompleter *c)
{
2018-10-21 13:43:02 +02:00
if (this->completer_)
{
2018-07-06 19:23:47 +02:00
QObject::disconnect(this->completer_, nullptr, this, nullptr);
2017-07-09 00:09:02 +02:00
}
2018-07-06 19:23:47 +02:00
this->completer_ = c;
2017-07-09 00:09:02 +02:00
2018-10-21 13:43:02 +02:00
if (!this->completer_)
{
2017-07-09 00:09:02 +02:00
return;
}
2018-07-06 19:23:47 +02:00
this->completer_->setWidget(this);
this->completer_->setCompletionMode(QCompleter::InlineCompletion);
this->completer_->setCaseSensitivity(Qt::CaseInsensitive);
if (getSettings()->prefixOnlyEmoteCompletion)
{
this->completer_->setFilterMode(Qt::MatchStartsWith);
}
else
{
this->completer_->setFilterMode(Qt::MatchContains);
}
2018-07-06 19:23:47 +02:00
QObject::connect(completer_,
2018-08-06 21:17:03 +02:00
static_cast<void (QCompleter::*)(const QString &)>(
&QCompleter::highlighted),
2017-07-09 00:09:02 +02:00
this, &ResizingTextEdit::insertCompletion);
}
void ResizingTextEdit::insertCompletion(const QString &completion)
{
2018-10-21 13:43:02 +02:00
if (this->completer_->widget() != this)
{
2017-07-09 00:09:02 +02:00
return;
}
bool hadSpace = false;
auto prefix = this->textUnderCursor(&hadSpace);
int prefixSize = prefix.size();
2018-10-21 13:43:02 +02:00
if (hadSpace)
{
++prefixSize;
}
2017-07-09 00:09:02 +02:00
QTextCursor tc = this->textCursor();
2018-08-06 21:17:03 +02:00
tc.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor,
prefixSize);
2017-07-09 00:09:02 +02:00
tc.insertText(completion);
this->setTextCursor(tc);
}
bool ResizingTextEdit::canInsertFromMimeData(const QMimeData *source) const
{
if (source->hasImage() || source->hasFormat("text/plain"))
{
return true;
}
2020-02-08 15:47:27 +01:00
return QTextEdit::canInsertFromMimeData(source);
}
void ResizingTextEdit::insertFromMimeData(const QMimeData *source)
{
if (source->hasImage() || source->hasUrls())
{
this->imagePasted.invoke(source);
}
else
{
insertPlainText(source->text());
}
}
2017-07-09 00:09:02 +02:00
QCompleter *ResizingTextEdit::getCompleter() const
{
2018-07-06 19:23:47 +02:00
return this->completer_;
2017-07-09 00:09:02 +02:00
}
2018-06-26 16:37:59 +02:00
2018-06-26 17:20:03 +02:00
} // namespace chatterino