mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Display message being replied to above input box (#4350)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
9788d0f8f7
commit
6b73bb53ec
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()))
|
||||||
{
|
{
|
||||||
|
|
134
src/widgets/helper/MessageView.cpp
Normal file
134
src/widgets/helper/MessageView.cpp
Normal 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
|
50
src/widgets/helper/MessageView.hpp
Normal file
50
src/widgets/helper/MessageView.hpp
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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_;
|
||||||
|
|
Loading…
Reference in a new issue