2019-10-11 15:41:33 +02:00
|
|
|
#include "widgets/helper/ResizingTextEdit.hpp"
|
2019-09-23 19:36:52 +02:00
|
|
|
|
2018-06-26 15:33:51 +02:00
|
|
|
#include "common/Common.hpp"
|
|
|
|
#include "common/CompletionModel.hpp"
|
2019-08-17 17:17:38 +02:00
|
|
|
#include "singletons/Settings.hpp"
|
2019-10-11 15:41:33 +02:00
|
|
|
|
|
|
|
#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);
|
2017-08-12 12:20:51 +02:00
|
|
|
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);
|
2017-07-09 16:33:08 +02:00
|
|
|
|
2019-08-18 21:37:20 +02:00
|
|
|
// Whenever the setting for emote completion changes, force a
|
|
|
|
// refresh on the completion model the next time "Tab" is pressed
|
2020-11-08 12:02:19 +01:00
|
|
|
getSettings()->prefixOnlyEmoteCompletion.connect([this] {
|
|
|
|
this->completionInProgress_ = false;
|
|
|
|
});
|
2019-08-18 21:37:20 +02:00
|
|
|
|
2017-07-09 16:33:08 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-08-01 00:10:02 +02:00
|
|
|
QString ResizingTextEdit::textUnderCursor(bool *hadSpace) const
|
2017-07-09 00:09:02 +02:00
|
|
|
{
|
2017-08-01 00:10: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)
|
|
|
|
{
|
2017-08-01 00:10:02 +02:00
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool first = true;
|
|
|
|
QString lastWord;
|
2018-10-21 13:43:02 +02:00
|
|
|
for (auto it = words.crbegin(); it != words.crend(); ++it)
|
|
|
|
{
|
2017-08-01 00:10:02 +02:00
|
|
|
auto word = *it;
|
|
|
|
|
2018-10-21 13:43:02 +02:00
|
|
|
if (first && word.isEmpty())
|
|
|
|
{
|
2017-08-01 00:10:02 +02:00
|
|
|
first = false;
|
2018-10-21 13:43:02 +02:00
|
|
|
if (hadSpace != nullptr)
|
|
|
|
{
|
2017-08-01 00:10:02 +02:00
|
|
|
*hadSpace = true;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
lastWord = word.toString();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-10-21 13:43:02 +02:00
|
|
|
if (lastWord.isEmpty())
|
|
|
|
{
|
2017-08-01 00:10:02 +02:00
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
return lastWord;
|
2017-07-09 00:09:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void ResizingTextEdit::keyPressEvent(QKeyEvent *event)
|
|
|
|
{
|
|
|
|
event->ignore();
|
|
|
|
|
2018-04-03 02:55:32 +02:00
|
|
|
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) &&
|
2020-11-01 14:59:43 +01:00
|
|
|
(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_)
|
|
|
|
{
|
2018-02-05 21:20:38 +01:00
|
|
|
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;
|
|
|
|
}();
|
2017-07-09 16:33:08 +02:00
|
|
|
|
2018-02-05 15:11:50 +01:00
|
|
|
// check if there is something to complete
|
2019-06-22 13:16:16 +02:00
|
|
|
if (currentCompletionPrefix.size() <= 1)
|
2018-10-21 13:43:02 +02:00
|
|
|
{
|
2017-07-09 00:09:02 +02:00
|
|
|
return;
|
|
|
|
}
|
2017-07-09 16:33:08 +02:00
|
|
|
|
2018-08-06 21:17:03 +02:00
|
|
|
auto *completionModel =
|
|
|
|
static_cast<CompletionModel *>(this->completer_->model());
|
2017-12-17 03:06:39 +01:00
|
|
|
|
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))
|
|
|
|
{
|
2018-04-10 01:54:30 +02:00
|
|
|
// wrap over and start again
|
2018-07-06 19:23:47 +02:00
|
|
|
this->completer_->setCurrentRow(0);
|
2018-04-10 01:54:30 +02:00
|
|
|
}
|
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))
|
|
|
|
{
|
2018-04-10 01:54:30 +02:00
|
|
|
// wrap over and start again
|
2018-08-06 21:17:03 +02:00
|
|
|
this->completer_->setCurrentRow(
|
|
|
|
this->completer_->completionCount() - 1);
|
2018-04-10 01:54:30 +02:00
|
|
|
}
|
2017-07-09 00:09:02 +02:00
|
|
|
}
|
2017-07-09 16:33:08 +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;
|
2018-04-10 01:54:30 +02:00
|
|
|
}
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-23 13:54:00 +02:00
|
|
|
void ResizingTextEdit::focusOutEvent(QFocusEvent *event)
|
|
|
|
{
|
|
|
|
QTextEdit::focusOutEvent(event);
|
|
|
|
|
2018-10-21 13:43:02 +02:00
|
|
|
if (event->lostFocus())
|
|
|
|
{
|
2018-06-23 13:54:00 +02:00
|
|
|
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);
|
2019-08-17 17:17:38 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-08-01 00:10:02 +02:00
|
|
|
bool hadSpace = false;
|
|
|
|
auto prefix = this->textUnderCursor(&hadSpace);
|
|
|
|
|
|
|
|
int prefixSize = prefix.size();
|
|
|
|
|
2018-10-21 13:43:02 +02:00
|
|
|
if (hadSpace)
|
|
|
|
{
|
2017-08-01 00:10:02 +02:00
|
|
|
++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);
|
|
|
|
}
|
|
|
|
|
2019-06-09 17:21:31 +02:00
|
|
|
bool ResizingTextEdit::canInsertFromMimeData(const QMimeData *source) const
|
|
|
|
{
|
2019-09-23 19:36:52 +02:00
|
|
|
if (source->hasImage() || source->hasFormat("text/plain"))
|
2019-06-09 17:21:31 +02:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2020-02-08 15:47:27 +01:00
|
|
|
return QTextEdit::canInsertFromMimeData(source);
|
2019-06-09 17:21:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void ResizingTextEdit::insertFromMimeData(const QMimeData *source)
|
|
|
|
{
|
2019-09-23 19:36:52 +02:00
|
|
|
if (source->hasImage() || source->hasUrls())
|
|
|
|
{
|
2019-09-25 22:39:02 +02:00
|
|
|
this->imagePasted.invoke(source);
|
2019-09-23 19:36:52 +02:00
|
|
|
}
|
|
|
|
else
|
2019-06-09 17:21:31 +02:00
|
|
|
{
|
|
|
|
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
|