mirror of https://github.com/Nheko-Reborn/nheko
parent
094c0b09ab
commit
b47d2a809c
@ -1,496 +0,0 @@ |
||||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr> |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#include <QAbstractItemView> |
||||
#include <QAbstractTextDocumentLayout> |
||||
#include <QBuffer> |
||||
#include <QClipboard> |
||||
#include <QCompleter> |
||||
#include <QFileDialog> |
||||
#include <QMimeData> |
||||
#include <QMimeDatabase> |
||||
#include <QMimeType> |
||||
#include <QPainter> |
||||
#include <QStyleOption> |
||||
#include <QtConcurrent> |
||||
|
||||
#include "Cache.h" |
||||
#include "ChatPage.h" |
||||
#include "CompletionModelRoles.h" |
||||
#include "CompletionProxyModel.h" |
||||
#include "Logging.h" |
||||
#include "TextInputWidget.h" |
||||
#include "Utils.h" |
||||
#include "emoji/EmojiSearchModel.h" |
||||
#include "emoji/Provider.h" |
||||
#include "ui/FlatButton.h" |
||||
#include "ui/LoadingIndicator.h" |
||||
|
||||
#if defined(Q_OS_MAC) |
||||
#include "emoji/MacHelper.h" |
||||
#endif |
||||
|
||||
static constexpr size_t INPUT_HISTORY_SIZE = 127; |
||||
static constexpr int MAX_TEXTINPUT_HEIGHT = 120; |
||||
static constexpr int ButtonHeight = 22; |
||||
|
||||
FilteredTextEdit::FilteredTextEdit(QWidget *parent) |
||||
: QTextEdit{parent} |
||||
, history_index_{0} |
||||
, suggestionsPopup_{parent} |
||||
, previewDialog_{parent} |
||||
{ |
||||
setFrameStyle(QFrame::NoFrame); |
||||
connect(document()->documentLayout(), |
||||
&QAbstractTextDocumentLayout::documentSizeChanged, |
||||
this, |
||||
&FilteredTextEdit::updateGeometry); |
||||
connect(document()->documentLayout(), |
||||
&QAbstractTextDocumentLayout::documentSizeChanged, |
||||
this, |
||||
[this]() { emit heightChanged(document()->size().toSize().height()); }); |
||||
working_history_.push_back(""); |
||||
connect(this, &QTextEdit::textChanged, this, &FilteredTextEdit::textChanged); |
||||
setAcceptRichText(false); |
||||
|
||||
completer_ = new QCompleter(this); |
||||
completer_->setWidget(this); |
||||
auto model = new emoji::EmojiSearchModel(this); |
||||
model->sort(0, Qt::AscendingOrder); |
||||
completer_->setModel((emoji_completion_model_ = new CompletionProxyModel(model, this))); |
||||
emoji_completion_model_->setFilterRole(CompletionModel::SearchRole); |
||||
completer_->setModelSorting(QCompleter::UnsortedModel); |
||||
completer_->popup()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
||||
completer_->popup()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
||||
|
||||
connect(completer_, |
||||
QOverload<const QModelIndex &>::of(&QCompleter::activated), |
||||
[this](auto &index) { |
||||
emoji_popup_open_ = false; |
||||
auto text = index.data(CompletionModel::CompletionRole).toString(); |
||||
insertCompletion(text); |
||||
}); |
||||
|
||||
connect(this, &FilteredTextEdit::resultsRetrieved, this, &FilteredTextEdit::showResults); |
||||
connect( |
||||
&suggestionsPopup_, &SuggestionsPopup::itemSelected, this, [this](const QString &text) { |
||||
suggestionsPopup_.hide(); |
||||
|
||||
auto cursor = textCursor(); |
||||
const int end = cursor.position(); |
||||
|
||||
cursor.setPosition(atTriggerPosition_, QTextCursor::MoveAnchor); |
||||
cursor.setPosition(end, QTextCursor::KeepAnchor); |
||||
cursor.removeSelectedText(); |
||||
cursor.insertText(text); |
||||
}); |
||||
|
||||
// For cycling through the suggestions by hitting tab.
|
||||
connect(this, |
||||
&FilteredTextEdit::selectNextSuggestion, |
||||
&suggestionsPopup_, |
||||
&SuggestionsPopup::selectNextSuggestion); |
||||
connect(this, |
||||
&FilteredTextEdit::selectPreviousSuggestion, |
||||
&suggestionsPopup_, |
||||
&SuggestionsPopup::selectPreviousSuggestion); |
||||
connect(this, &FilteredTextEdit::selectHoveredSuggestion, this, [this]() { |
||||
suggestionsPopup_.selectHoveredSuggestion<UserItem>(); |
||||
}); |
||||
|
||||
previewDialog_.hide(); |
||||
} |
||||
|
||||
void |
||||
FilteredTextEdit::insertCompletion(QString completion) |
||||
{ |
||||
// Paint the current word and replace it with 'completion'
|
||||
auto cur_text = textAfterPosition(trigger_pos_); |
||||
auto tc = textCursor(); |
||||
tc.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, cur_text.length()); |
||||
tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, cur_text.length()); |
||||
tc.insertText(completion); |
||||
setTextCursor(tc); |
||||
} |
||||
|
||||
void |
||||
FilteredTextEdit::showResults(const std::vector<SearchResult> &results) |
||||
{ |
||||
QPoint pos; |
||||
|
||||
if (isAnchorValid()) { |
||||
auto cursor = textCursor(); |
||||
cursor.setPosition(atTriggerPosition_); |
||||
pos = viewport()->mapToGlobal(cursorRect(cursor).topLeft()); |
||||
} else { |
||||
auto rect = cursorRect(); |
||||
pos = viewport()->mapToGlobal(rect.topLeft()); |
||||
} |
||||
|
||||
suggestionsPopup_.addUsers(results); |
||||
suggestionsPopup_.move(pos.x(), pos.y() - suggestionsPopup_.height() - 10); |
||||
suggestionsPopup_.show(); |
||||
} |
||||
|
||||
void |
||||
FilteredTextEdit::keyPressEvent(QKeyEvent *event) |
||||
{ |
||||
const bool isModifier = (event->modifiers() != Qt::NoModifier); |
||||
|
||||
#if defined(Q_OS_MAC) |
||||
if (event->modifiers() == (Qt::ControlModifier | Qt::MetaModifier) && |
||||
event->key() == Qt::Key_Space) |
||||
MacHelper::showEmojiWindow(); |
||||
#endif |
||||
|
||||
if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_U) |
||||
QTextEdit::setText(""); |
||||
|
||||
// calculate the new query
|
||||
if (textCursor().position() < atTriggerPosition_ || !isAnchorValid()) { |
||||
resetAnchor(); |
||||
closeSuggestions(); |
||||
} |
||||
|
||||
if (suggestionsPopup_.isVisible()) { |
||||
switch (event->key()) { |
||||
case Qt::Key_Down: |
||||
case Qt::Key_Tab: |
||||
emit selectNextSuggestion(); |
||||
return; |
||||
case Qt::Key_Enter: |
||||
case Qt::Key_Return: |
||||
emit selectHoveredSuggestion(); |
||||
return; |
||||
case Qt::Key_Escape: |
||||
closeSuggestions(); |
||||
return; |
||||
case Qt::Key_Up: |
||||
case Qt::Key_Backtab: { |
||||
emit selectPreviousSuggestion(); |
||||
return; |
||||
} |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (emoji_popup_open_) { |
||||
auto fake_key = (event->key() == Qt::Key_Backtab) ? Qt::Key_Up : Qt::Key_Down; |
||||
switch (event->key()) { |
||||
case Qt::Key_Backtab: |
||||
case Qt::Key_Tab: { |
||||
// Simulate up/down arrow press
|
||||
auto ev = new QKeyEvent(QEvent::KeyPress, fake_key, Qt::NoModifier); |
||||
QCoreApplication::postEvent(completer_->popup(), ev); |
||||
return; |
||||
} |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
switch (event->key()) { |
||||
case Qt::Key_At: |
||||
atTriggerPosition_ = textCursor().position(); |
||||
anchorType_ = AnchorType::Sigil; |
||||
|
||||
QTextEdit::keyPressEvent(event); |
||||
break; |
||||
case Qt::Key_Tab: { |
||||
auto cursor = textCursor(); |
||||
const int initialPos = cursor.position(); |
||||
|
||||
cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor); |
||||
auto word = cursor.selectedText(); |
||||
|
||||
const int startOfWord = cursor.position(); |
||||
|
||||
// There is a word to complete.
|
||||
if (initialPos != startOfWord) { |
||||
atTriggerPosition_ = startOfWord; |
||||
anchorType_ = AnchorType::Tab; |
||||
|
||||
emit showSuggestions(word); |
||||
} else { |
||||
QTextEdit::keyPressEvent(event); |
||||
} |
||||
|
||||
break; |
||||
} |
||||
case Qt::Key_Colon: { |
||||
QTextEdit::keyPressEvent(event); |
||||
trigger_pos_ = textCursor().position() - 1; |
||||
emoji_completion_model_->setFilterRegExp(""); |
||||
emoji_popup_open_ = true; |
||||
break; |
||||
} |
||||
case Qt::Key_Return: |
||||
case Qt::Key_Enter: |
||||
if (emoji_popup_open_) { |
||||
if (!completer_->popup()->currentIndex().isValid()) { |
||||
// No completion to select, do normal behavior
|
||||
completer_->popup()->hide(); |
||||
emoji_popup_open_ = false; |
||||
} else { |
||||
event->ignore(); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
if (!(event->modifiers() & Qt::ShiftModifier)) { |
||||
submit(); |
||||
} else { |
||||
QTextEdit::keyPressEvent(event); |
||||
} |
||||
break; |
||||
case Qt::Key_Up: { |
||||
auto initial_cursor = textCursor(); |
||||
QTextEdit::keyPressEvent(event); |
||||
|
||||
if (textCursor() == initial_cursor && textCursor().atStart() && |
||||
history_index_ + 1 < working_history_.size()) { |
||||
++history_index_; |
||||
setPlainText(working_history_[history_index_]); |
||||
moveCursor(QTextCursor::End); |
||||
} else if (textCursor() == initial_cursor) { |
||||
// Move to the start of the text if there aren't any lines to move up to.
|
||||
initial_cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor, 1); |
||||
setTextCursor(initial_cursor); |
||||
} |
||||
|
||||
break; |
||||
} |
||||
case Qt::Key_Down: { |
||||
auto initial_cursor = textCursor(); |
||||
QTextEdit::keyPressEvent(event); |
||||
|
||||
if (textCursor() == initial_cursor && textCursor().atEnd() && history_index_ > 0) { |
||||
--history_index_; |
||||
setPlainText(working_history_[history_index_]); |
||||
moveCursor(QTextCursor::End); |
||||
} else if (textCursor() == initial_cursor) { |
||||
// Move to the end of the text if there aren't any lines to move down to.
|
||||
initial_cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor, 1); |
||||
setTextCursor(initial_cursor); |
||||
} |
||||
|
||||
break; |
||||
} |
||||
default: |
||||
QTextEdit::keyPressEvent(event); |
||||
|
||||
if (isModifier) |
||||
return; |
||||
|
||||
if (emoji_popup_open_ && textAfterPosition(trigger_pos_).length() > 2) { |
||||
// Update completion
|
||||
// Don't include the trigger token in the search
|
||||
emoji_completion_model_->setFilterWildcard( |
||||
textAfterPosition(trigger_pos_).remove(0, 1)); |
||||
completer_->complete(completerRect()); |
||||
} |
||||
|
||||
if (emoji_popup_open_ && (completer_->completionCount() < 1 || |
||||
!textAfterPosition(trigger_pos_) |
||||
.contains(QRegularExpression(":[^\r\n\t\f\v :]+$")))) { |
||||
// No completions for this word or another word than the completer was
|
||||
// started with
|
||||
emoji_popup_open_ = false; |
||||
completer_->popup()->hide(); |
||||
} |
||||
|
||||
if (textCursor().position() == 0) { |
||||
resetAnchor(); |
||||
closeSuggestions(); |
||||
return; |
||||
} |
||||
|
||||
// Check if the current word should be autocompleted.
|
||||
auto cursor = textCursor(); |
||||
cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor); |
||||
auto word = cursor.selectedText(); |
||||
|
||||
if (hasAnchor(cursor.position(), anchorType_) && isAnchorValid()) { |
||||
if (word.isEmpty()) { |
||||
closeSuggestions(); |
||||
return; |
||||
} |
||||
|
||||
emit showSuggestions(word); |
||||
} else { |
||||
resetAnchor(); |
||||
closeSuggestions(); |
||||
} |
||||
|
||||
break; |
||||
} |
||||
} |
||||
|
||||
QRect |
||||
FilteredTextEdit::completerRect() |
||||
{ |
||||
// Move left edge to the beginning of the word
|
||||
auto cursor = textCursor(); |
||||
auto rect = cursorRect(); |
||||
cursor.movePosition( |
||||
QTextCursor::Left, QTextCursor::MoveAnchor, textAfterPosition(trigger_pos_).length()); |
||||
auto cursor_global_x = viewport()->mapToGlobal(cursorRect(cursor).topLeft()).x(); |
||||
auto rect_global_left = viewport()->mapToGlobal(rect.bottomLeft()).x(); |
||||
auto dx = qAbs(rect_global_left - cursor_global_x); |
||||
rect.moveLeft(rect.left() - dx); |
||||
|
||||
auto item_height = completer_->popup()->sizeHintForRow(0); |
||||
auto max_height = item_height * completer_->maxVisibleItems(); |
||||
auto height = (completer_->completionCount() > completer_->maxVisibleItems()) |
||||
? max_height |
||||
: completer_->completionCount() * item_height; |
||||
rect.setWidth(completer_->popup()->sizeHintForColumn(0)); |
||||
rect.moveBottom(-height); |
||||
return rect; |
||||
} |
||||
|
||||
QSize |
||||
FilteredTextEdit::sizeHint() const |
||||
{ |
||||
ensurePolished(); |
||||
auto margins = viewportMargins(); |
||||
margins += document()->documentMargin(); |
||||
QSize size = document()->size().toSize(); |
||||
size.rwidth() += margins.left() + margins.right(); |
||||
size.rheight() += margins.top() + margins.bottom(); |
||||
return size; |
||||
} |
||||
|
||||
QSize |
||||
FilteredTextEdit::minimumSizeHint() const |
||||
{ |
||||
ensurePolished(); |
||||
auto margins = viewportMargins(); |
||||
margins += document()->documentMargin(); |
||||
margins += contentsMargins(); |
||||
QSize size(fontMetrics().averageCharWidth() * 10, |
||||
fontMetrics().lineSpacing() + margins.top() + margins.bottom()); |
||||
return size; |
||||
} |
||||
|
||||
void |
||||
FilteredTextEdit::submit() |
||||
{} |
||||
|
||||
void |
||||
FilteredTextEdit::textChanged() |
||||
{ |
||||
working_history_[history_index_] = toPlainText(); |
||||
} |
||||
|
||||
TextInputWidget::TextInputWidget(QWidget *parent) |
||||
: QWidget(parent) |
||||
{ |
||||
QFont f; |
||||
f.setPointSizeF(f.pointSizeF()); |
||||
const int fontHeight = QFontMetrics(f).height(); |
||||
const int contentHeight = static_cast<int>(fontHeight * 2.5); |
||||
const int InputHeight = static_cast<int>(fontHeight * 1.5); |
||||
|
||||
setFixedHeight(contentHeight); |
||||
setCursor(Qt::ArrowCursor); |
||||
|
||||
topLayout_ = new QHBoxLayout(); |
||||
topLayout_->setSpacing(0); |
||||
topLayout_->setContentsMargins(13, 1, 13, 0); |
||||
|
||||
QIcon send_file_icon; |
||||
send_file_icon.addFile(":/icons/icons/ui/paper-clip-outline.png"); |
||||
|
||||
sendFileBtn_ = new FlatButton(this); |
||||
sendFileBtn_->setToolTip(tr("Send a file")); |
||||
sendFileBtn_->setIcon(send_file_icon); |
||||
sendFileBtn_->setIconSize(QSize(ButtonHeight, ButtonHeight)); |
||||
|
||||
spinner_ = new LoadingIndicator(this); |
||||
spinner_->setFixedHeight(InputHeight); |
||||
spinner_->setFixedWidth(InputHeight); |
||||
spinner_->setObjectName("FileUploadSpinner"); |
||||
spinner_->hide(); |
||||
|
||||
input_ = new FilteredTextEdit(this); |
||||
input_->setFixedHeight(InputHeight); |
||||
input_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
||||
input_->setPlaceholderText(tr("Write a message...")); |
||||
|
||||
connect(input_, |
||||
&FilteredTextEdit::heightChanged, |
||||
this, |
||||
[this, InputHeight, contentHeight](int height) { |
||||
int widgetHeight = |
||||
std::min(MAX_TEXTINPUT_HEIGHT, std::max(height, contentHeight)); |
||||
int textInputHeight = |
||||
std::min(widgetHeight - 1, std::max(height, InputHeight)); |
||||
|
||||
setFixedHeight(widgetHeight); |
||||
input_->setFixedHeight(textInputHeight); |
||||
|
||||
emit heightChanged(widgetHeight); |
||||
}); |
||||
connect(input_, &FilteredTextEdit::showSuggestions, this, [this](const QString &q) { |
||||
if (q.isEmpty()) |
||||
return; |
||||
|
||||
QtConcurrent::run([this, q = q.toLower().toStdString()]() { |
||||
try { |
||||
emit input_->resultsRetrieved(cache::searchUsers( |
||||
ChatPage::instance()->currentRoom().toStdString(), q)); |
||||
} catch (const lmdb::error &e) { |
||||
nhlog::db()->error("Suggestion retrieval failed: {}", e.what()); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
sendMessageBtn_ = new FlatButton(this); |
||||
sendMessageBtn_->setToolTip(tr("Send a message")); |
||||
|
||||
QIcon send_message_icon; |
||||
send_message_icon.addFile(":/icons/icons/ui/cursor.png"); |
||||
sendMessageBtn_->setIcon(send_message_icon); |
||||
sendMessageBtn_->setIconSize(QSize(ButtonHeight, ButtonHeight)); |
||||
|
||||
topLayout_->addWidget(sendFileBtn_); |
||||
topLayout_->addWidget(input_); |
||||
topLayout_->addWidget(sendMessageBtn_); |
||||
|
||||
setLayout(topLayout_); |
||||
|
||||
connect(sendMessageBtn_, &FlatButton::clicked, input_, &FilteredTextEdit::submit); |
||||
connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection())); |
||||
} |
||||
|
||||
void |
||||
TextInputWidget::focusInEvent(QFocusEvent *event) |
||||
{ |
||||
input_->setFocus(event->reason()); |
||||
} |
||||
|
||||
void |
||||
TextInputWidget::paintEvent(QPaintEvent *) |
||||
{ |
||||
QStyleOption opt; |
||||
opt.init(this); |
||||
QPainter p(this); |
||||
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); |
||||
} |
@ -1,173 +0,0 @@ |
||||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr> |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <deque> |
||||
#include <optional> |
||||
|
||||
#include <QCoreApplication> |
||||
#include <QHBoxLayout> |
||||
#include <QPaintEvent> |
||||
#include <QTextEdit> |
||||
#include <QWidget> |
||||
|
||||
#include "dialogs/PreviewUploadOverlay.h" |
||||
#include "popups/SuggestionsPopup.h" |
||||
|
||||
struct SearchResult; |
||||
|
||||
class CompletionProxyModel; |
||||
class FlatButton; |
||||
class LoadingIndicator; |
||||
class QCompleter; |
||||
|
||||
class FilteredTextEdit : public QTextEdit |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit FilteredTextEdit(QWidget *parent = nullptr); |
||||
|
||||
QSize sizeHint() const override; |
||||
QSize minimumSizeHint() const override; |
||||
|
||||
void submit(); |
||||
|
||||
signals: |
||||
void heightChanged(int height); |
||||
void startedUpload(); |
||||
|
||||
//! Trigger the suggestion popup.
|
||||
void showSuggestions(const QString &query); |
||||
void resultsRetrieved(const std::vector<SearchResult> &results); |
||||
void selectNextSuggestion(); |
||||
void selectPreviousSuggestion(); |
||||
void selectHoveredSuggestion(); |
||||
|
||||
public slots: |
||||
void showResults(const std::vector<SearchResult> &results); |
||||
|
||||
protected: |
||||
void keyPressEvent(QKeyEvent *event) override; |
||||
void focusOutEvent(QFocusEvent *event) override |
||||
{ |
||||
suggestionsPopup_.hide(); |
||||
QTextEdit::focusOutEvent(event); |
||||
} |
||||
|
||||
private: |
||||
bool emoji_popup_open_ = false; |
||||
CompletionProxyModel *emoji_completion_model_; |
||||
std::deque<QString> true_history_, working_history_; |
||||
int trigger_pos_; // Where emoji completer was triggered
|
||||
size_t history_index_; |
||||
QCompleter *completer_; |
||||
|
||||
SuggestionsPopup suggestionsPopup_; |
||||
|
||||
enum class AnchorType |
||||
{ |
||||
Tab = 0, |
||||
Sigil = 1, |
||||
}; |
||||
|
||||
AnchorType anchorType_ = AnchorType::Sigil; |
||||
|
||||
int anchorWidth(AnchorType anchor) { return static_cast<int>(anchor); } |
||||
|
||||
void closeSuggestions() { suggestionsPopup_.hide(); } |
||||
void resetAnchor() { atTriggerPosition_ = -1; } |
||||
bool isAnchorValid() { return atTriggerPosition_ != -1; } |
||||
bool hasAnchor(int pos, AnchorType anchor) |
||||
{ |
||||
return pos == atTriggerPosition_ + anchorWidth(anchor); |
||||
} |
||||
QRect completerRect(); |
||||
QString query() |
||||
{ |
||||
auto cursor = textCursor(); |
||||
cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor); |
||||
return cursor.selectedText(); |
||||
} |
||||
QString textAfterPosition(int pos) |
||||
{ |
||||
auto tc = textCursor(); |
||||
tc.setPosition(pos); |
||||
tc.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); |
||||
return tc.selectedText(); |
||||
} |
||||
|
||||
dialogs::PreviewUploadOverlay previewDialog_; |
||||
|
||||
//! Latest position of the '@' character that triggers the username completer.
|
||||
int atTriggerPosition_ = -1; |
||||
|
||||
void insertCompletion(QString completion); |
||||
void textChanged(); |
||||
void afterCompletion(int); |
||||
}; |
||||
|
||||
class TextInputWidget : public QWidget |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor) |
||||
|
||||
public: |
||||
TextInputWidget(QWidget *parent = nullptr); |
||||
|
||||
QColor borderColor() const { return borderColor_; } |
||||
void setBorderColor(QColor &color) { borderColor_ = color; } |
||||
void disableInput() |
||||
{ |
||||
input_->setEnabled(false); |
||||
input_->setPlaceholderText(tr("Connection lost. Nheko is trying to re-connect...")); |
||||
} |
||||
void enableInput() |
||||
{ |
||||
input_->setEnabled(true); |
||||
input_->setPlaceholderText(tr("Write a message...")); |
||||
} |
||||
|
||||
public slots: |
||||
void focusLineEdit() { input_->setFocus(); } |
||||
|
||||
signals: |
||||
void heightChanged(int height); |
||||
|
||||
void sendJoinRoomRequest(const QString &room); |
||||
void sendInviteRoomRequest(const QString &userid, const QString &reason); |
||||
void sendKickRoomRequest(const QString &userid, const QString &reason); |
||||
void sendBanRoomRequest(const QString &userid, const QString &reason); |
||||
void sendUnbanRoomRequest(const QString &userid, const QString &reason); |
||||
void changeRoomNick(const QString &displayname); |
||||
|
||||
protected: |
||||
void focusInEvent(QFocusEvent *event) override; |
||||
void paintEvent(QPaintEvent *) override; |
||||
|
||||
private: |
||||
QHBoxLayout *topLayout_; |
||||
FilteredTextEdit *input_; |
||||
|
||||
LoadingIndicator *spinner_; |
||||
|
||||
FlatButton *sendFileBtn_; |
||||
FlatButton *sendMessageBtn_; |
||||
QColor borderColor_; |
||||
}; |
Loading…
Reference in new issue