Display message being replied to above input box (#4350)

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
Daniel Sage 2024-07-14 12:06:42 -07:00 committed by GitHub
parent 9788d0f8f7
commit 6b73bb53ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 355 additions and 60 deletions

View file

@ -20,6 +20,7 @@
- Minor: Improve appearance of reply button. (#5491) - Minor: Improve appearance of reply button. (#5491)
- Minor: Introduce HTTP API for plugins. (#5383, #5492, #5494) - Minor: Introduce HTTP API for plugins. (#5383, #5492, #5494)
- Minor: Support more Firefox variants for incognito link opening. (#5503) - Minor: Support more Firefox variants for incognito link opening. (#5503)
- Minor: Replying to a message will now display the message being replied to. (#4350)
- Minor: Links can now have prefixes and suffixes such as parentheses. (#5486) - Minor: Links can now have prefixes and suffixes such as parentheses. (#5486)
- Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426) - Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426)
- Bugfix: If a network request errors with 200 OK, Qt's error code is now reported instead of the HTTP status. (#5378) - Bugfix: If a network request errors with 200 OK, Qt's error code is now reported instead of the HTTP status. (#5378)

View file

@ -643,6 +643,8 @@ set(SOURCE_FILES
widgets/helper/IconDelegate.hpp widgets/helper/IconDelegate.hpp
widgets/helper/InvisibleSizeGrip.cpp widgets/helper/InvisibleSizeGrip.cpp
widgets/helper/InvisibleSizeGrip.hpp widgets/helper/InvisibleSizeGrip.hpp
widgets/helper/MessageView.cpp
widgets/helper/MessageView.hpp
widgets/helper/NotebookButton.cpp widgets/helper/NotebookButton.cpp
widgets/helper/NotebookButton.hpp widgets/helper/NotebookButton.hpp
widgets/helper/NotebookTab.cpp widgets/helper/NotebookTab.cpp

View file

@ -566,7 +566,7 @@ SingleLineTextElement::SingleLineTextElement(const QString &text,
void SingleLineTextElement::addToContainer(MessageLayoutContainer &container, void SingleLineTextElement::addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) MessageElementFlags flags)
{ {
auto *app = getApp(); auto *app = getIApp();
if (flags.hasAny(this->getFlags())) if (flags.hasAny(this->getFlags()))
{ {

View file

@ -0,0 +1,134 @@
#include "widgets/helper/MessageView.hpp"
#include "Application.hpp"
#include "messages/layouts/MessageLayout.hpp"
#include "messages/MessageElement.hpp"
#include "messages/Selection.hpp"
#include "providers/colors/ColorProvider.hpp"
#include "singletons/Theme.hpp"
#include "singletons/WindowManager.hpp"
#include <QApplication>
#include <QPainter>
namespace {
using namespace chatterino;
const Selection EMPTY_SELECTION;
const MessageElementFlags MESSAGE_FLAGS{
MessageElementFlag::Text,
MessageElementFlag::EmojiAll,
MessageElementFlag::EmoteText,
};
} // namespace
namespace chatterino {
MessageView::MessageView() = default;
MessageView::~MessageView() = default;
void MessageView::createMessageLayout()
{
if (this->message_ == nullptr)
{
this->messageLayout_.reset();
return;
}
this->messageLayout_ = std::make_unique<MessageLayout>(this->message_);
}
void MessageView::setMessage(const MessagePtr &message)
{
if (!message)
{
return;
}
auto singleLineMessage = std::make_shared<Message>();
singleLineMessage->elements.emplace_back(
std::make_unique<SingleLineTextElement>(
message->messageText, MESSAGE_FLAGS, MessageColor::Type::System,
FontStyle::ChatMediumSmall));
this->message_ = std::move(singleLineMessage);
this->createMessageLayout();
this->layoutMessage();
}
void MessageView::clearMessage()
{
this->setMessage(nullptr);
}
void MessageView::setWidth(int width)
{
if (this->width_ != width)
{
this->width_ = width;
this->layoutMessage();
}
}
void MessageView::paintEvent(QPaintEvent * /*event*/)
{
QPainter painter(this);
auto ctx = MessagePaintContext{
.painter = painter,
.selection = EMPTY_SELECTION,
.colorProvider = ColorProvider::instance(),
.messageColors = this->messageColors_,
.preferences = this->messagePreferences_,
.canvasWidth = this->width_,
.isWindowFocused = this->window() == QApplication::activeWindow(),
.isMentions = false,
.y = 0,
.messageIndex = 0,
.isLastReadMessage = false,
};
this->messageLayout_->paint(ctx);
}
void MessageView::themeChangedEvent()
{
this->messageColors_.applyTheme(getTheme());
this->messageColors_.regular = getTheme()->splits.input.background;
if (this->messageLayout_)
{
this->messageLayout_->invalidateBuffer();
}
}
void MessageView::scaleChangedEvent(float newScale)
{
(void)newScale;
this->layoutMessage();
}
void MessageView::layoutMessage()
{
if (this->messageLayout_ == nullptr)
{
return;
}
bool updateRequired = this->messageLayout_->layout(
this->width_, this->scale(),
this->scale() * static_cast<float>(this->devicePixelRatio()),
MESSAGE_FLAGS, false);
if (updateRequired)
{
this->setFixedSize(this->width_, this->messageLayout_->getHeight());
this->update();
}
}
} // namespace chatterino

View file

@ -0,0 +1,50 @@
#pragma once
#include "messages/layouts/MessageLayoutContext.hpp"
#include "messages/Message.hpp"
#include "widgets/BaseWidget.hpp"
#include <QWidget>
namespace chatterino {
class MessageLayout;
/// MessageView is a fixed-width widget that displays a single message.
/// For the message to be rendered, you must call setWidth.
class MessageView : public BaseWidget
{
Q_OBJECT
public:
MessageView();
~MessageView() override;
MessageView(const MessageView &) = delete;
MessageView(MessageView &&) = delete;
MessageView &operator=(const MessageView &) = delete;
MessageView &operator=(MessageView &&) = delete;
void setMessage(const MessagePtr &message);
void clearMessage();
void setWidth(int width);
protected:
void paintEvent(QPaintEvent *event) override;
void themeChangedEvent() override;
void scaleChangedEvent(float newScale) override;
private:
void createMessageLayout();
void layoutMessage();
MessagePtr message_;
std::unique_ptr<MessageLayout> messageLayout_;
MessageColors messageColors_;
MessagePreferences messagePreferences_;
int width_{};
};
} // namespace chatterino

View file

@ -7,7 +7,6 @@
#include "controllers/hotkeys/HotkeyController.hpp" #include "controllers/hotkeys/HotkeyController.hpp"
#include "messages/Link.hpp" #include "messages/Link.hpp"
#include "messages/Message.hpp" #include "messages/Message.hpp"
#include "messages/MessageThread.hpp"
#include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchCommon.hpp" #include "providers/twitch/TwitchCommon.hpp"
#include "providers/twitch/TwitchIrcServer.hpp" #include "providers/twitch/TwitchIrcServer.hpp"
@ -19,6 +18,7 @@
#include "widgets/dialogs/EmotePopup.hpp" #include "widgets/dialogs/EmotePopup.hpp"
#include "widgets/helper/ChannelView.hpp" #include "widgets/helper/ChannelView.hpp"
#include "widgets/helper/EffectLabel.hpp" #include "widgets/helper/EffectLabel.hpp"
#include "widgets/helper/MessageView.hpp"
#include "widgets/helper/ResizingTextEdit.hpp" #include "widgets/helper/ResizingTextEdit.hpp"
#include "widgets/Notebook.hpp" #include "widgets/Notebook.hpp"
#include "widgets/Scrollbar.hpp" #include "widgets/Scrollbar.hpp"
@ -84,14 +84,28 @@ void SplitInput::initLayout()
auto layout = auto layout =
layoutCreator.setLayoutType<QVBoxLayout>().withoutMargin().assign( layoutCreator.setLayoutType<QVBoxLayout>().withoutMargin().assign(
&this->ui_.vbox); &this->ui_.vbox);
layout->setSpacing(0);
auto marginPx = this->marginForTheme();
layout->setContentsMargins(marginPx, marginPx, marginPx, marginPx);
// reply label stuff // reply label stuff
auto replyWrapper = auto replyWrapper =
layout.emplace<QWidget>().assign(&this->ui_.replyWrapper); layout.emplace<QWidget>().assign(&this->ui_.replyWrapper);
this->ui_.replyWrapper->setContentsMargins(0, 0, 0, 0); replyWrapper->setContentsMargins(0, 0, 0, 0);
auto replyHbox = replyWrapper.emplace<QHBoxLayout>().withoutMargin().assign( auto replyVbox =
&this->ui_.replyHbox); replyWrapper.setLayoutType<QVBoxLayout>().withoutMargin().assign(
&this->ui_.replyVbox);
replyVbox->setSpacing(0);
auto replyHbox =
replyVbox.emplace<QHBoxLayout>().assign(&this->ui_.replyHbox);
auto messageVbox = layoutCreator.setLayoutType<QVBoxLayout>();
this->ui_.replyMessage = new MessageView();
messageVbox->addWidget(this->ui_.replyMessage, 1, Qt::AlignLeft);
messageVbox->setContentsMargins(10, 0, 0, 0);
replyVbox->addLayout(messageVbox->layout(), 1);
auto replyLabel = replyHbox.emplace<QLabel>().assign(&this->ui_.replyLabel); auto replyLabel = replyHbox.emplace<QLabel>().assign(&this->ui_.replyLabel);
replyLabel->setAlignment(Qt::AlignLeft); replyLabel->setAlignment(Qt::AlignLeft);
@ -107,9 +121,14 @@ void SplitInput::initLayout()
replyCancelButton->hide(); replyCancelButton->hide();
replyLabel->hide(); replyLabel->hide();
auto inputWrapper =
layout.emplace<QWidget>().assign(&this->ui_.inputWrapper);
inputWrapper->setContentsMargins(0, 0, 0, 0);
// hbox for input, right box // hbox for input, right box
auto hboxLayout = auto hboxLayout =
layout.emplace<QHBoxLayout>().withoutMargin().assign(&this->ui_.hbox); inputWrapper.setLayoutType<QHBoxLayout>().withoutMargin().assign(
&this->ui_.inputHbox);
// input // input
auto textEdit = auto textEdit =
@ -176,7 +195,7 @@ void SplitInput::initLayout()
this->openEmotePopup(); this->openEmotePopup();
}); });
// clear input and remove reply thread // clear input and remove reply target
QObject::connect(this->ui_.cancelReplyButton, &EffectLabel::leftClicked, QObject::connect(this->ui_.cancelReplyButton, &EffectLabel::leftClicked,
[this] { [this] {
this->clearInput(); this->clearInput();
@ -211,6 +230,10 @@ void SplitInput::scaleChangedEvent(float scale)
if (!this->hidden) if (!this->hidden)
{ {
this->setMaximumHeight(this->scaledMaxHeight()); this->setMaximumHeight(this->scaledMaxHeight());
if (this->replyTarget_ != nullptr)
{
this->ui_.vbox->setSpacing(this->marginForTheme() * 2);
}
} }
this->ui_.textEdit->setFont( this->ui_.textEdit->setFont(
app->getFonts()->getFont(FontStyle::ChatMedium, scale)); app->getFonts()->getFont(FontStyle::ChatMedium, scale));
@ -236,8 +259,6 @@ void SplitInput::themeChangedEvent()
this->ui_.textEdit->setStyleSheet(this->theme->splits.input.styleSheet); this->ui_.textEdit->setStyleSheet(this->theme->splits.input.styleSheet);
this->ui_.textEdit->setPalette(placeholderPalette); this->ui_.textEdit->setPalette(placeholderPalette);
auto marginPx = static_cast<int>(2.F * this->scale());
this->ui_.vbox->setContentsMargins(marginPx, marginPx, marginPx, marginPx);
this->ui_.emoteButton->getLabel().setStyleSheet("color: #000"); this->ui_.emoteButton->getLabel().setStyleSheet("color: #000");
@ -249,6 +270,14 @@ void SplitInput::themeChangedEvent()
{ {
this->ui_.replyLabel->setStyleSheet("color: #ccc"); this->ui_.replyLabel->setStyleSheet("color: #ccc");
} }
// update vbox
auto marginPx = this->marginForTheme();
this->ui_.vbox->setContentsMargins(marginPx, marginPx, marginPx, marginPx);
if (this->replyTarget_ != nullptr)
{
this->ui_.vbox->setSpacing(this->marginForTheme() * 2);
}
} }
void SplitInput::updateEmoteButton() void SplitInput::updateEmoteButton()
@ -319,7 +348,7 @@ QString SplitInput::handleSendMessage(const std::vector<QString> &arguments)
return ""; return "";
} }
if (!c->isTwitchChannel() || this->replyThread_ == nullptr) if (!c->isTwitchChannel() || this->replyTarget_ == nullptr)
{ {
// standard message send behavior // standard message send behavior
QString message = ui_.textEdit->toPlainText(); QString message = ui_.textEdit->toPlainText();
@ -337,6 +366,10 @@ QString SplitInput::handleSendMessage(const std::vector<QString> &arguments)
// Reply to message // Reply to message
auto *tc = dynamic_cast<TwitchChannel *>(c.get()); auto *tc = dynamic_cast<TwitchChannel *>(c.get());
if (!tc) if (!tc)
{
// Reply to message
auto tc = dynamic_cast<TwitchChannel *>(c.get());
if (!tc)
{ {
// this should not fail // this should not fail
return ""; return "";
@ -347,7 +380,7 @@ QString SplitInput::handleSendMessage(const std::vector<QString> &arguments)
if (this->enableInlineReplying_) if (this->enableInlineReplying_)
{ {
// Remove @username prefix that is inserted when doing inline replies // Remove @username prefix that is inserted when doing inline replies
message.remove(0, this->replyThread_->displayName.length() + message.remove(0, this->replyTarget_->displayName.length() +
1); // remove "@username" 1); // remove "@username"
if (!message.isEmpty() && message.at(0) == ' ') if (!message.isEmpty() && message.at(0) == ' ')
@ -361,7 +394,32 @@ QString SplitInput::handleSendMessage(const std::vector<QString> &arguments)
getIApp()->getCommands()->execCommand(message, c, false); getIApp()->getCommands()->execCommand(message, c, false);
// Reply within TwitchChannel // Reply within TwitchChannel
tc->sendReply(sendMessage, this->replyThread_->id); tc->sendReply(sendMessage, this->replyTarget_->id);
this->postMessageSend(message, arguments);
return "";
}
QString message = this->ui_.textEdit->toPlainText();
if (this->enableInlineReplying_)
{
// Remove @username prefix that is inserted when doing inline replies
message.remove(0, this->replyTarget_->displayName.length() +
1); // remove "@username"
if (!message.isEmpty() && message.at(0) == ' ')
{
message.remove(0, 1); // remove possible space
}
}
message = message.replace('\n', ' ');
QString sendMessage =
getIApp()->getCommands()->execCommand(message, c, false);
// Reply within TwitchChannel
tc->sendReply(sendMessage, this->replyTarget_->id);
this->postMessageSend(message, arguments); this->postMessageSend(message, arguments);
return ""; return "";
@ -385,9 +443,17 @@ void SplitInput::postMessageSend(const QString &message,
} }
int SplitInput::scaledMaxHeight() const int SplitInput::scaledMaxHeight() const
{
if (this->replyTarget_ != nullptr)
{
// give more space for showing the message being replied to
return int(250 * this->scale());
}
else
{ {
return int(150 * this->scale()); return int(150 * this->scale());
} }
}
void SplitInput::addShortcuts() void SplitInput::addShortcuts()
{ {
@ -1001,24 +1067,24 @@ void SplitInput::editTextChanged()
bool hasReply = false; bool hasReply = false;
if (this->enableInlineReplying_) if (this->enableInlineReplying_)
{ {
if (this->replyThread_ != nullptr) if (this->replyTarget_ != nullptr)
{ {
// Check if the input still starts with @username. If not, don't reply. // Check if the input still starts with @username. If not, don't reply.
// //
// We need to verify that // We need to verify that
// 1. the @username prefix exists and // 1. the @username prefix exists and
// 2. if a character exists after the @username, it is a space // 2. if a character exists after the @username, it is a space
QString replyPrefix = "@" + this->replyThread_->displayName; QString replyPrefix = "@" + this->replyTarget_->displayName;
if (!text.startsWith(replyPrefix) || if (!text.startsWith(replyPrefix) ||
(text.length() > replyPrefix.length() && (text.length() > replyPrefix.length() &&
text.at(replyPrefix.length()) != ' ')) text.at(replyPrefix.length()) != ' '))
{ {
this->replyThread_ = nullptr; this->clearReplyTarget();
} }
} }
// Show/hide reply label if inline replies are possible // Show/hide reply label if inline replies are possible
hasReply = this->replyThread_ != nullptr; hasReply = this->replyTarget_ != nullptr;
} }
this->ui_.replyWrapper->setVisible(hasReply); this->ui_.replyWrapper->setVisible(hasReply);
@ -1030,37 +1096,35 @@ void SplitInput::paintEvent(QPaintEvent * /*event*/)
{ {
QPainter painter(this); QPainter painter(this);
int s{}; int s = this->marginForTheme();
QColor borderColor; QColor borderColor =
this->theme->isLightTheme() ? QColor("#ccc") : QColor("#333");
if (this->theme->isLightTheme())
{
s = int(3 * this->scale());
borderColor = QColor("#ccc");
}
else
{
s = int(1 * this->scale());
borderColor = QColor("#333");
}
QMargins removeMargins(s - 1, s - 1, s, s);
QRect baseRect = this->rect(); QRect baseRect = this->rect();
QRect inputBoxRect = this->ui_.inputWrapper->geometry();
inputBoxRect.setX(baseRect.x());
inputBoxRect.setWidth(baseRect.width());
// completeAreaRect includes the reply label painter.fillRect(inputBoxRect, this->theme->splits.input.background);
QRect completeAreaRect = baseRect.marginsRemoved(removeMargins);
painter.fillRect(completeAreaRect, this->theme->splits.input.background);
painter.setPen(borderColor); painter.setPen(borderColor);
painter.drawRect(completeAreaRect); painter.drawRect(inputBoxRect);
if (this->enableInlineReplying_ && this->replyThread_ != nullptr) if (this->enableInlineReplying_ && this->replyTarget_ != nullptr)
{ {
// Move top of rect down to not include reply label QRect replyRect = this->ui_.replyWrapper->geometry();
baseRect.setTop(baseRect.top() + this->ui_.replyWrapper->height()); replyRect.setX(baseRect.x());
replyRect.setWidth(baseRect.width());
QRect onlyInputRect = baseRect.marginsRemoved(removeMargins); painter.fillRect(replyRect, this->theme->splits.input.background);
painter.setPen(borderColor); painter.setPen(borderColor);
painter.drawRect(onlyInputRect); painter.drawRect(replyRect);
QPoint replyLabelBorderStart(
replyRect.x(),
replyRect.y() + this->ui_.replyHbox->geometry().height());
QPoint replyLabelBorderEnd(replyRect.right(),
replyLabelBorderStart.y());
painter.drawLine(replyLabelBorderStart, replyLabelBorderEnd);
} }
} }
@ -1076,6 +1140,8 @@ void SplitInput::resizeEvent(QResizeEvent *event)
{ {
this->ui_.textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); this->ui_.textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
} }
this->ui_.replyMessage->setWidth(this->width());
} }
void SplitInput::giveFocus(Qt::FocusReason reason) void SplitInput::giveFocus(Qt::FocusReason reason)
@ -1083,9 +1149,9 @@ void SplitInput::giveFocus(Qt::FocusReason reason)
this->ui_.textEdit->setFocus(reason); this->ui_.textEdit->setFocus(reason);
} }
void SplitInput::setReply(MessagePtr reply, bool showReplyingLabel) void SplitInput::setReply(MessagePtr target)
{ {
auto oldParent = this->replyThread_; auto oldParent = this->replyTarget_;
if (this->enableInlineReplying_ && oldParent) if (this->enableInlineReplying_ && oldParent)
{ {
// Remove old reply prefix // Remove old reply prefix
@ -1100,12 +1166,24 @@ void SplitInput::setReply(MessagePtr reply, bool showReplyingLabel)
this->ui_.textEdit->resetCompletion(); this->ui_.textEdit->resetCompletion();
} }
this->replyThread_ = std::move(reply); assert(target != nullptr);
this->replyTarget_ = std::move(target);
if (this->enableInlineReplying_) if (this->enableInlineReplying_)
{ {
this->ui_.replyMessage->setMessage(this->replyTarget_);
this->ui_.replyMessage->setWidth(this->width());
// add spacing between reply box and input box
this->ui_.vbox->setSpacing(this->marginForTheme() * 2);
if (!this->isHidden())
{
// update maximum height to give space for message
this->setMaximumHeight(this->scaledMaxHeight());
}
// Only enable reply label if inline replying // Only enable reply label if inline replying
auto replyPrefix = "@" + this->replyThread_->displayName; auto replyPrefix = "@" + this->replyTarget_->displayName;
auto plainText = this->ui_.textEdit->toPlainText().trimmed(); auto plainText = this->ui_.textEdit->toPlainText().trimmed();
// This makes it so if plainText contains "@StreamerFan" and // This makes it so if plainText contains "@StreamerFan" and
@ -1134,7 +1212,7 @@ void SplitInput::setReply(MessagePtr reply, bool showReplyingLabel)
this->ui_.textEdit->moveCursor(QTextCursor::EndOfBlock); this->ui_.textEdit->moveCursor(QTextCursor::EndOfBlock);
this->ui_.textEdit->resetCompletion(); this->ui_.textEdit->resetCompletion();
this->ui_.replyLabel->setText("Replying to @" + this->ui_.replyLabel->setText("Replying to @" +
this->replyThread_->displayName); this->replyTarget_->displayName);
} }
} }
@ -1148,9 +1226,17 @@ void SplitInput::clearInput()
this->currMsg_ = ""; this->currMsg_ = "";
this->ui_.textEdit->setText(""); this->ui_.textEdit->setText("");
this->ui_.textEdit->moveCursor(QTextCursor::Start); this->ui_.textEdit->moveCursor(QTextCursor::Start);
if (this->enableInlineReplying_) this->clearReplyTarget();
}
void SplitInput::clearReplyTarget()
{ {
this->replyThread_ = nullptr; this->replyTarget_.reset();
this->ui_.replyMessage->clearMessage();
this->ui_.vbox->setSpacing(0);
if (!this->isHidden())
{
this->setMaximumHeight(this->scaledMaxHeight());
} }
} }
@ -1177,4 +1263,16 @@ bool SplitInput::shouldPreventInput(const QString &text) const
return text.length() > TWITCH_MESSAGE_LIMIT; return text.length() > TWITCH_MESSAGE_LIMIT;
} }
int SplitInput::marginForTheme() const
{
if (this->theme->isLightTheme())
{
return int(3 * this->scale());
}
else
{
return int(1 * this->scale());
}
}
} // namespace chatterino } // namespace chatterino

View file

@ -20,6 +20,7 @@ class Split;
class EmotePopup; class EmotePopup;
class InputCompletionPopup; class InputCompletionPopup;
class EffectLabel; class EffectLabel;
class MessageView;
class ResizingTextEdit; class ResizingTextEdit;
class ChannelView; class ChannelView;
enum class CompletionKind; enum class CompletionKind;
@ -40,7 +41,7 @@ public:
QString getInputText() const; QString getInputText() const;
void insertText(const QString &text); void insertText(const QString &text);
void setReply(MessagePtr reply, bool showInlineReplying = true); void setReply(MessagePtr target);
void setPlaceholderText(const QString &text); void setPlaceholderText(const QString &text);
/** /**
@ -91,7 +92,7 @@ protected:
void postMessageSend(const QString &message, void postMessageSend(const QString &message,
const std::vector<QString> &arguments); const std::vector<QString> &arguments);
/// Clears the input box, clears reply thread if inline replies are enabled /// Clears the input box, clears reply target if inline replies are enabled
void clearInput(); void clearInput();
void addShortcuts() override; void addShortcuts() override;
@ -109,6 +110,7 @@ protected:
void hideCompletionPopup(); void hideCompletionPopup();
void insertCompletionText(const QString &input_) const; void insertCompletionText(const QString &input_) const;
void openEmotePopup(); void openEmotePopup();
void clearReplyTarget();
void updateCancelReplyButton(); void updateCancelReplyButton();
@ -120,27 +122,35 @@ protected:
// the user's setting is set to Prevent, and the given text goes beyond the Twitch message length limit // the user's setting is set to Prevent, and the given text goes beyond the Twitch message length limit
bool shouldPreventInput(const QString &text) const; bool shouldPreventInput(const QString &text) const;
int marginForTheme() const;
Split *const split_; Split *const split_;
ChannelView *const channelView_; ChannelView *const channelView_;
QPointer<EmotePopup> emotePopup_; QPointer<EmotePopup> emotePopup_;
QPointer<InputCompletionPopup> inputCompletionPopup_; QPointer<InputCompletionPopup> inputCompletionPopup_;
struct { struct {
// vbox for all components
QVBoxLayout *vbox;
// reply widgets
QWidget *replyWrapper;
QVBoxLayout *replyVbox;
QHBoxLayout *replyHbox;
MessageView *replyMessage;
QLabel *replyLabel;
EffectLabel *cancelReplyButton;
// input widgets
QWidget *inputWrapper;
QHBoxLayout *inputHbox;
ResizingTextEdit *textEdit; ResizingTextEdit *textEdit;
QLabel *textEditLength; QLabel *textEditLength;
EffectLabel *sendButton; EffectLabel *sendButton;
EffectLabel *emoteButton; EffectLabel *emoteButton;
} ui_;
QHBoxLayout *hbox; MessagePtr replyTarget_ = nullptr;
QVBoxLayout *vbox;
QWidget *replyWrapper;
QHBoxLayout *replyHbox;
QLabel *replyLabel;
EffectLabel *cancelReplyButton;
} ui_{};
MessagePtr replyThread_ = nullptr;
bool enableInlineReplying_; bool enableInlineReplying_;
pajlada::Signals::SignalHolder managedConnections_; pajlada::Signals::SignalHolder managedConnections_;