Add option to subscribe to and pin reply threads (#4680)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
#include "DraggablePopup.hpp"
|
||||
#include "widgets/DraggablePopup.hpp"
|
||||
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "widgets/helper/Button.hpp"
|
||||
|
||||
#include <QMouseEvent>
|
||||
|
||||
@@ -90,4 +94,29 @@ void DraggablePopup::mouseMoveEvent(QMouseEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
Button *DraggablePopup::createPinButton()
|
||||
{
|
||||
auto *button = new Button(this);
|
||||
button->setPixmap(getTheme()->buttons.pin);
|
||||
button->setScaleIndependantSize(18, 18);
|
||||
button->setToolTip("Pin Window");
|
||||
|
||||
bool pinned = false;
|
||||
QObject::connect(
|
||||
button, &Button::leftClicked, [this, button, pinned]() mutable {
|
||||
pinned = !pinned;
|
||||
if (pinned)
|
||||
{
|
||||
this->setActionOnFocusLoss(BaseWindow::Nothing);
|
||||
button->setPixmap(getResources().buttons.pinEnabled);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->setActionOnFocusLoss(BaseWindow::Delete);
|
||||
button->setPixmap(getTheme()->buttons.pin);
|
||||
}
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -26,6 +26,11 @@ protected:
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
|
||||
/// Creates a pin button that is scoped to this window.
|
||||
/// When clicked, the user can toggle whether the window is pinned.
|
||||
/// The window is considered unpinned at the start.
|
||||
Button *createPinButton();
|
||||
|
||||
// lifetimeHack_ is used to check that the window hasn't been destroyed yet
|
||||
std::shared_ptr<bool> lifetimeHack_;
|
||||
|
||||
|
||||
@@ -7,16 +7,18 @@
|
||||
#include "controllers/hotkeys/HotkeyController.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageThread.hpp"
|
||||
#include "providers/twitch/ChannelPointReward.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/helper/Button.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
#include "widgets/helper/ResizingTextEdit.hpp"
|
||||
#include "widgets/Scrollbar.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
#include "widgets/splits/SplitInput.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
|
||||
const QString TEXT_TITLE("Reply Thread - @%1 in #%2");
|
||||
|
||||
namespace chatterino {
|
||||
@@ -72,9 +74,6 @@ ReplyThreadPopup::ReplyThreadPopup(bool closeAutomatically, QWidget *parent,
|
||||
this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory(
|
||||
HotkeyCategory::PopupWindow, actions, this);
|
||||
|
||||
auto layout = LayoutCreator<QWidget>(this->getLayoutContainer())
|
||||
.setLayoutType<QVBoxLayout>();
|
||||
|
||||
// initialize UI
|
||||
this->ui_.threadView =
|
||||
new ChannelView(this, this->split_, ChannelView::Context::ReplyThread);
|
||||
@@ -110,10 +109,57 @@ ReplyThreadPopup::ReplyThreadPopup(bool closeAutomatically, QWidget *parent,
|
||||
}
|
||||
});
|
||||
|
||||
auto layout = LayoutCreator<QWidget>(this->getLayoutContainer())
|
||||
.setLayoutType<QVBoxLayout>();
|
||||
|
||||
layout->setSpacing(0);
|
||||
// provide draggable margin if frameless
|
||||
auto marginPx = closeAutomatically ? 15 : 1;
|
||||
layout->setContentsMargins(marginPx, marginPx, marginPx, marginPx);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
layout->addWidget(this->ui_.threadView, 1);
|
||||
layout->addWidget(this->ui_.replyInput);
|
||||
}
|
||||
@@ -124,6 +170,24 @@ void ReplyThreadPopup::setThread(std::shared_ptr<MessageThread> thread)
|
||||
this->ui_.replyInput->setReply(this->thread_);
|
||||
this->addMessagesFromThread();
|
||||
this->updateInputUI();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void ReplyThreadPopup::addMessagesFromThread()
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <pajlada/signals/scoped-connection.hpp>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
|
||||
class QCheckBox;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class MessageThread;
|
||||
@@ -41,10 +43,13 @@ private:
|
||||
struct {
|
||||
ChannelView *threadView = nullptr;
|
||||
SplitInput *replyInput = nullptr;
|
||||
|
||||
QCheckBox *notificationCheckbox = nullptr;
|
||||
} ui_;
|
||||
|
||||
std::unique_ptr<pajlada::Signals::ScopedConnection> messageConnection_;
|
||||
std::vector<boost::signals2::scoped_connection> bSignals_;
|
||||
boost::signals2::scoped_connection replySubscriptionSignal_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -142,7 +142,6 @@ UserInfoPopup::UserInfoPopup(bool closeAutomatically, QWidget *parent,
|
||||
"split being nullptr causes lots of bugs down the road");
|
||||
this->setWindowTitle("Usercard");
|
||||
this->setStayInScreenRect(true);
|
||||
this->updateFocusLoss();
|
||||
|
||||
HotkeyController::HotkeyMap actions{
|
||||
{"delete",
|
||||
@@ -361,17 +360,7 @@ UserInfoPopup::UserInfoPopup(bool closeAutomatically, QWidget *parent,
|
||||
// button to pin the window (only if we close automatically)
|
||||
if (this->closeAutomatically_)
|
||||
{
|
||||
this->ui_.pinButton = box.emplace<Button>().getElement();
|
||||
this->ui_.pinButton->setPixmap(
|
||||
getApp()->themes->buttons.pin);
|
||||
this->ui_.pinButton->setScaleIndependantSize(18, 18);
|
||||
this->ui_.pinButton->setToolTip("Pin Window");
|
||||
QObject::connect(this->ui_.pinButton, &Button::leftClicked,
|
||||
[this]() {
|
||||
this->closeAutomatically_ =
|
||||
!this->closeAutomatically_;
|
||||
this->updateFocusLoss();
|
||||
});
|
||||
box->addWidget(this->createPinButton());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -920,26 +909,6 @@ void UserInfoPopup::updateUserData()
|
||||
this->ui_.ignoreHighlights->setEnabled(false);
|
||||
}
|
||||
|
||||
void UserInfoPopup::updateFocusLoss()
|
||||
{
|
||||
if (this->closeAutomatically_)
|
||||
{
|
||||
this->setActionOnFocusLoss(BaseWindow::Delete);
|
||||
if (this->ui_.pinButton != nullptr)
|
||||
{
|
||||
this->ui_.pinButton->setPixmap(getApp()->themes->buttons.pin);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this->setActionOnFocusLoss(BaseWindow::Nothing);
|
||||
if (this->ui_.pinButton != nullptr)
|
||||
{
|
||||
this->ui_.pinButton->setPixmap(getResources().buttons.pinEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UserInfoPopup::loadAvatar(const QUrl &url)
|
||||
{
|
||||
QNetworkRequest req(url);
|
||||
|
||||
@@ -37,7 +37,6 @@ private:
|
||||
void installEvents();
|
||||
void updateUserData();
|
||||
void updateLatestMessages();
|
||||
void updateFocusLoss();
|
||||
|
||||
void loadAvatar(const QUrl &url);
|
||||
bool isMod_;
|
||||
@@ -73,8 +72,6 @@ private:
|
||||
Label *followerCountLabel = nullptr;
|
||||
Label *createdDateLabel = nullptr;
|
||||
Label *userIDLabel = nullptr;
|
||||
// Can be uninitialized if usercard is not configured to close on focus loss
|
||||
Button *pinButton = nullptr;
|
||||
Label *followageLabel = nullptr;
|
||||
Label *subageLabel = nullptr;
|
||||
|
||||
|
||||
@@ -916,6 +916,13 @@ void GeneralPage::initLayout(GeneralPageView &layout)
|
||||
"Highlight received inline whispers", s.highlightInlineWhispers, false,
|
||||
"Highlight the whispers shown in all splits.\nIf \"Show Twitch "
|
||||
"whispers inline\" is disabled, this setting will do nothing.");
|
||||
layout.addCheckbox(
|
||||
"Automatically subscribe to participated reply threads",
|
||||
s.autoSubToParticipatedThreads, false,
|
||||
"When enabled, you will automatically subscribe to reply threads you "
|
||||
"participate in.\n"
|
||||
"This means reply threads you participate in will use your "
|
||||
"\"Subscribed Reply Threads\" highlight settings.");
|
||||
layout.addCheckbox("Load message history on connect",
|
||||
s.loadTwitchMessageHistoryOnConnect);
|
||||
// TODO: Change phrasing to use better english once we can tag settings, right now it's kept as history instead of historical so that the setting shows up when the user searches for history
|
||||
|
||||
Reference in New Issue
Block a user