2022-07-31 12:45:25 +02:00
|
|
|
#include "ReplyThreadPopup.hpp"
|
|
|
|
|
|
|
|
#include "Application.hpp"
|
|
|
|
#include "common/Channel.hpp"
|
|
|
|
#include "common/QLogging.hpp"
|
2022-12-31 15:41:01 +01:00
|
|
|
#include "controllers/accounts/AccountController.hpp"
|
2022-07-31 12:45:25 +02:00
|
|
|
#include "controllers/hotkeys/HotkeyController.hpp"
|
2022-12-31 15:41:01 +01:00
|
|
|
#include "messages/Message.hpp"
|
2022-07-31 12:45:25 +02:00
|
|
|
#include "messages/MessageThread.hpp"
|
2022-12-18 15:36:39 +01:00
|
|
|
#include "providers/twitch/TwitchAccount.hpp"
|
2022-12-31 15:41:01 +01:00
|
|
|
#include "providers/twitch/TwitchChannel.hpp"
|
2023-06-17 17:41:52 +02:00
|
|
|
#include "singletons/Settings.hpp"
|
2022-07-31 12:45:25 +02:00
|
|
|
#include "util/LayoutCreator.hpp"
|
2023-06-17 17:41:52 +02:00
|
|
|
#include "widgets/helper/Button.hpp"
|
2022-07-31 12:45:25 +02:00
|
|
|
#include "widgets/helper/ChannelView.hpp"
|
Sort and force grouping of includes (#4172)
This change enforces strict include grouping using IncludeCategories
In addition to adding this to the .clang-format file and applying it in the tests/src and src directories, I also did the following small changes:
In ChatterSet.hpp, I changed lrucache to a <>include
In Irc2.hpp, I change common/SignalVector.hpp to a "project-include"
In AttachedWindow.cpp, NativeMessaging.cpp, WindowsHelper.hpp, BaseWindow.cpp, and StreamerMode.cpp, I disabled clang-format for the windows-includes
In WindowDescriptors.hpp, I added the missing vector include. It was previously not needed because the include was handled by another file that was previously included first.
clang-format minimum version has been bumped, so Ubuntu version used in the check-formatting job has been bumped to 22.04 (which is the latest LTS)
2022-11-27 19:32:53 +01:00
|
|
|
#include "widgets/Scrollbar.hpp"
|
2022-07-31 12:45:25 +02:00
|
|
|
#include "widgets/splits/Split.hpp"
|
|
|
|
#include "widgets/splits/SplitInput.hpp"
|
|
|
|
|
2023-06-17 17:41:52 +02:00
|
|
|
#include <QCheckBox>
|
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
const QString TEXT_TITLE("Reply Thread - @%1 in #%2");
|
|
|
|
|
|
|
|
namespace chatterino {
|
|
|
|
|
|
|
|
ReplyThreadPopup::ReplyThreadPopup(bool closeAutomatically, QWidget *parent,
|
|
|
|
Split *split)
|
|
|
|
: DraggablePopup(closeAutomatically, parent)
|
|
|
|
, split_(split)
|
|
|
|
{
|
|
|
|
this->setWindowTitle(QStringLiteral("Reply Thread"));
|
|
|
|
this->setStayInScreenRect(true);
|
|
|
|
|
|
|
|
HotkeyController::HotkeyMap actions{
|
|
|
|
{"delete",
|
|
|
|
[this](std::vector<QString>) -> QString {
|
|
|
|
this->deleteLater();
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
{"scrollPage",
|
|
|
|
[this](std::vector<QString> arguments) -> QString {
|
|
|
|
if (arguments.empty())
|
|
|
|
{
|
|
|
|
qCWarning(chatterinoHotkeys)
|
|
|
|
<< "scrollPage hotkey called without arguments!";
|
|
|
|
return "scrollPage hotkey called without arguments!";
|
|
|
|
}
|
|
|
|
auto direction = arguments.at(0);
|
|
|
|
|
|
|
|
auto &scrollbar = this->ui_.threadView->getScrollBar();
|
|
|
|
if (direction == "up")
|
|
|
|
{
|
|
|
|
scrollbar.offset(-scrollbar.getLargeChange());
|
|
|
|
}
|
|
|
|
else if (direction == "down")
|
|
|
|
{
|
|
|
|
scrollbar.offset(scrollbar.getLargeChange());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
qCWarning(chatterinoHotkeys) << "Unknown scroll direction";
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}},
|
|
|
|
|
|
|
|
// these actions make no sense in the context of a reply thread, so they aren't implemented
|
|
|
|
{"execModeratorAction", nullptr},
|
|
|
|
{"reject", nullptr},
|
|
|
|
{"accept", nullptr},
|
|
|
|
{"openTab", nullptr},
|
|
|
|
{"search", nullptr},
|
|
|
|
};
|
|
|
|
|
|
|
|
this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory(
|
|
|
|
HotkeyCategory::PopupWindow, actions, this);
|
|
|
|
|
|
|
|
// initialize UI
|
|
|
|
this->ui_.threadView =
|
|
|
|
new ChannelView(this, this->split_, ChannelView::Context::ReplyThread);
|
|
|
|
this->ui_.threadView->setMinimumSize(400, 100);
|
|
|
|
this->ui_.threadView->setSizePolicy(QSizePolicy::Expanding,
|
|
|
|
QSizePolicy::Expanding);
|
|
|
|
this->ui_.threadView->mouseDown.connect([this](QMouseEvent *) {
|
|
|
|
this->giveFocus(Qt::MouseFocusReason);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Create SplitInput with inline replying disabled
|
2022-12-03 14:02:39 +01:00
|
|
|
this->ui_.replyInput =
|
|
|
|
new SplitInput(this, this->split_, this->ui_.threadView, false);
|
2022-07-31 12:45:25 +02:00
|
|
|
|
|
|
|
this->bSignals_.emplace_back(
|
|
|
|
getApp()->accounts->twitch.currentUserChanged.connect([this] {
|
|
|
|
this->updateInputUI();
|
|
|
|
}));
|
|
|
|
|
2022-12-07 19:21:04 +01:00
|
|
|
// clear SplitInput selection when selecting in ChannelView
|
|
|
|
this->ui_.threadView->selectionChanged.connect([this]() {
|
|
|
|
if (this->ui_.replyInput->hasSelection())
|
|
|
|
{
|
|
|
|
this->ui_.replyInput->clearSelection();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// clear ChannelView selection when selecting in SplitInput
|
|
|
|
this->ui_.replyInput->selectionChanged.connect([this]() {
|
|
|
|
if (this->ui_.threadView->hasSelection())
|
|
|
|
{
|
|
|
|
this->ui_.threadView->clearSelection();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-06-17 17:41:52 +02:00
|
|
|
auto layout = LayoutCreator<QWidget>(this->getLayoutContainer())
|
|
|
|
.setLayoutType<QVBoxLayout>();
|
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
layout->setSpacing(0);
|
|
|
|
// provide draggable margin if frameless
|
2022-11-10 20:11:40 +01:00
|
|
|
auto marginPx = closeAutomatically ? 15 : 1;
|
|
|
|
layout->setContentsMargins(marginPx, marginPx, marginPx, marginPx);
|
2023-06-17 17:41:52 +02:00
|
|
|
|
|
|
|
// Top Row
|
|
|
|
bool addCheckbox = getSettings()->enableThreadHighlight;
|
|
|
|
if (addCheckbox || closeAutomatically)
|
|
|
|
{
|
|
|
|
auto *hbox = new QHBoxLayout();
|
|
|
|
|
|
|
|
if (addCheckbox)
|
|
|
|
{
|
|
|
|
this->ui_.notificationCheckbox =
|
|
|
|
new QCheckBox("Subscribe to thread", this);
|
|
|
|
QObject::connect(this->ui_.notificationCheckbox,
|
|
|
|
&QCheckBox::toggled, [this](bool checked) {
|
|
|
|
if (!this->thread_ ||
|
|
|
|
this->thread_->subscribed() == checked)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (checked)
|
|
|
|
{
|
|
|
|
this->thread_->markSubscribed();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this->thread_->markUnsubscribed();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
hbox->addWidget(this->ui_.notificationCheckbox, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (closeAutomatically)
|
|
|
|
{
|
|
|
|
hbox->addWidget(this->createPinButton(), 0, Qt::AlignRight);
|
|
|
|
hbox->setContentsMargins(0, 0, 0, 5);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
hbox->setContentsMargins(10, 0, 0, 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
layout->addLayout(hbox, 1);
|
|
|
|
}
|
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
layout->addWidget(this->ui_.threadView, 1);
|
|
|
|
layout->addWidget(this->ui_.replyInput);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReplyThreadPopup::setThread(std::shared_ptr<MessageThread> thread)
|
|
|
|
{
|
|
|
|
this->thread_ = std::move(thread);
|
|
|
|
this->ui_.replyInput->setReply(this->thread_);
|
|
|
|
this->addMessagesFromThread();
|
|
|
|
this->updateInputUI();
|
2023-06-17 17:41:52 +02:00
|
|
|
|
|
|
|
if (!this->thread_) [[unlikely]]
|
|
|
|
{
|
|
|
|
this->replySubscriptionSignal_ = boost::signals2::scoped_connection{};
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto updateCheckbox = [this]() {
|
|
|
|
if (this->ui_.notificationCheckbox)
|
|
|
|
{
|
|
|
|
this->ui_.notificationCheckbox->setChecked(
|
|
|
|
this->thread_->subscribed());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
updateCheckbox();
|
|
|
|
|
|
|
|
this->replySubscriptionSignal_ =
|
|
|
|
this->thread_->subscriptionUpdated.connect(updateCheckbox);
|
2022-07-31 12:45:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void ReplyThreadPopup::addMessagesFromThread()
|
|
|
|
{
|
|
|
|
this->ui_.threadView->clearMessages();
|
|
|
|
if (!this->thread_)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto &sourceChannel = this->split_->getChannel();
|
|
|
|
this->setWindowTitle(TEXT_TITLE.arg(this->thread_->root()->loginName,
|
|
|
|
sourceChannel->getName()));
|
|
|
|
|
|
|
|
if (sourceChannel->isTwitchChannel())
|
|
|
|
{
|
2023-06-10 13:40:30 +02:00
|
|
|
this->virtualChannel_ =
|
2022-07-31 12:45:25 +02:00
|
|
|
std::make_shared<TwitchChannel>(sourceChannel->getName());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-06-10 13:40:30 +02:00
|
|
|
this->virtualChannel_ = std::make_shared<Channel>(
|
|
|
|
sourceChannel->getName(), Channel::Type::None);
|
2022-07-31 12:45:25 +02:00
|
|
|
}
|
|
|
|
|
2023-06-10 13:40:30 +02:00
|
|
|
this->ui_.threadView->setChannel(this->virtualChannel_);
|
2022-07-31 12:45:25 +02:00
|
|
|
this->ui_.threadView->setSourceChannel(sourceChannel);
|
|
|
|
|
2022-10-16 12:28:22 +02:00
|
|
|
auto overrideFlags =
|
|
|
|
boost::optional<MessageFlags>(this->thread_->root()->flags);
|
|
|
|
overrideFlags->set(MessageFlag::DoNotLog);
|
|
|
|
|
2023-06-10 13:40:30 +02:00
|
|
|
this->virtualChannel_->addMessage(this->thread_->root(), overrideFlags);
|
2022-07-31 12:45:25 +02:00
|
|
|
for (const auto &msgRef : this->thread_->replies())
|
|
|
|
{
|
|
|
|
if (auto msg = msgRef.lock())
|
|
|
|
{
|
2022-10-16 12:28:22 +02:00
|
|
|
auto overrideFlags = boost::optional<MessageFlags>(msg->flags);
|
|
|
|
overrideFlags->set(MessageFlag::DoNotLog);
|
|
|
|
|
2023-06-10 13:40:30 +02:00
|
|
|
this->virtualChannel_->addMessage(msg, overrideFlags);
|
2022-07-31 12:45:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this->messageConnection_ =
|
|
|
|
std::make_unique<pajlada::Signals::ScopedConnection>(
|
2023-06-10 13:40:30 +02:00
|
|
|
sourceChannel->messageAppended.connect([this](MessagePtr &message,
|
|
|
|
auto) {
|
|
|
|
if (message->replyThread == this->thread_)
|
|
|
|
{
|
|
|
|
auto overrideFlags =
|
|
|
|
boost::optional<MessageFlags>(message->flags);
|
|
|
|
overrideFlags->set(MessageFlag::DoNotLog);
|
|
|
|
|
|
|
|
// same reply thread, add message
|
|
|
|
this->virtualChannel_->addMessage(message, overrideFlags);
|
|
|
|
}
|
|
|
|
}));
|
2022-07-31 12:45:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void ReplyThreadPopup::updateInputUI()
|
|
|
|
{
|
|
|
|
auto channel = this->split_->getChannel();
|
|
|
|
// Bail out if not a twitch channel.
|
|
|
|
// Special twitch channels will hide their reply input box.
|
|
|
|
if (!channel || !channel->isTwitchChannel())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this->ui_.replyInput->setVisible(channel->isWritable());
|
|
|
|
|
|
|
|
auto user = getApp()->accounts->twitch.getCurrent();
|
|
|
|
QString placeholderText;
|
|
|
|
|
|
|
|
if (user->isAnon())
|
|
|
|
{
|
|
|
|
placeholderText = QStringLiteral("Log in to send messages...");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
placeholderText =
|
|
|
|
QStringLiteral("Reply as %1...")
|
|
|
|
.arg(getApp()->accounts->twitch.getCurrent()->getUserName());
|
|
|
|
}
|
|
|
|
|
|
|
|
this->ui_.replyInput->setPlaceholderText(placeholderText);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReplyThreadPopup::giveFocus(Qt::FocusReason reason)
|
|
|
|
{
|
|
|
|
this->ui_.replyInput->giveFocus(reason);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReplyThreadPopup::focusInEvent(QFocusEvent *event)
|
|
|
|
{
|
|
|
|
this->giveFocus(event->reason());
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace chatterino
|