Fix signal connection nodiscard warnings (#4818)

This commit is contained in:
pajlada 2023-09-16 13:52:51 +02:00 committed by GitHub
parent 2d5f078306
commit 8fe3af3522
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 709 additions and 554 deletions

View file

@ -17,6 +17,7 @@
- Dev: Fix clang-tidy `cppcoreguidelines-pro-type-member-init` warnings. (#4426) - Dev: Fix clang-tidy `cppcoreguidelines-pro-type-member-init` warnings. (#4426)
- Dev: Immediate layout for invisible `ChannelView`s is skipped. (#4811) - Dev: Immediate layout for invisible `ChannelView`s is skipped. (#4811)
- Dev: Refactor `Image` & Image's `Frames`. (#4773) - Dev: Refactor `Image` & Image's `Frames`. (#4773)
- Dev: Clarify signal connection lifetimes where applicable. (#4818)
## 2.4.5 ## 2.4.5

View file

@ -97,7 +97,9 @@ Application::Application(Settings &_settings, Paths &_paths)
{ {
this->instance = this; this->instance = this;
this->fonts->fontChanged.connect([this]() { // We can safely ignore this signal's connection since the Application will always
// be destroyed after fonts
std::ignore = this->fonts->fontChanged.connect([this]() {
this->windows->layoutChannelViews(); this->windows->layoutChannelViews();
}); });
} }
@ -188,16 +190,23 @@ int Application::run(QApplication &qtApp)
Updates::instance().checkForUpdates(); Updates::instance().checkForUpdates();
}, },
false); false);
getSettings()->moderationActions.delayedItemsChanged.connect([this] {
this->windows->forceLayoutChannelViews();
});
getSettings()->highlightedMessages.delayedItemsChanged.connect([this] { // We can safely ignore the signal connections since Application will always live longer than
this->windows->forceLayoutChannelViews(); // everything else, including settings. right?
}); // NOTE: SETTINGS_LIFETIME
getSettings()->highlightedUsers.delayedItemsChanged.connect([this] { std::ignore =
this->windows->forceLayoutChannelViews(); getSettings()->moderationActions.delayedItemsChanged.connect([this] {
}); this->windows->forceLayoutChannelViews();
});
std::ignore =
getSettings()->highlightedMessages.delayedItemsChanged.connect([this] {
this->windows->forceLayoutChannelViews();
});
std::ignore =
getSettings()->highlightedUsers.delayedItemsChanged.connect([this] {
this->windows->forceLayoutChannelViews();
});
getSettings()->removeSpacesBetweenEmotes.connect([this] { getSettings()->removeSpacesBetweenEmotes.connect([this] {
this->windows->forceLayoutChannelViews(); this->windows->forceLayoutChannelViews();
@ -279,7 +288,9 @@ void Application::initNm(Paths &paths)
void Application::initPubSub() void Application::initPubSub()
{ {
this->twitch->pubsub->signals_.moderation.chatCleared.connect( // We can safely ignore these signal connections since the twitch object will always
// be destroyed before the Application
std::ignore = this->twitch->pubsub->signals_.moderation.chatCleared.connect(
[this](const auto &action) { [this](const auto &action) {
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) if (chan->isEmpty())
@ -296,7 +307,7 @@ void Application::initPubSub()
}); });
}); });
this->twitch->pubsub->signals_.moderation.modeChanged.connect( std::ignore = this->twitch->pubsub->signals_.moderation.modeChanged.connect(
[this](const auto &action) { [this](const auto &action) {
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) if (chan->isEmpty())
@ -322,28 +333,29 @@ void Application::initPubSub()
}); });
}); });
this->twitch->pubsub->signals_.moderation.moderationStateChanged.connect( std::ignore =
[this](const auto &action) { this->twitch->pubsub->signals_.moderation.moderationStateChanged
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); .connect([this](const auto &action) {
if (chan->isEmpty()) auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
{ if (chan->isEmpty())
return; {
} return;
}
QString text; QString text;
text = QString("%1 %2 %3") text = QString("%1 %2 %3")
.arg(action.source.login, .arg(action.source.login,
(action.modded ? "modded" : "unmodded"), (action.modded ? "modded" : "unmodded"),
action.target.login); action.target.login);
auto msg = makeSystemMessage(text); auto msg = makeSystemMessage(text);
postToThread([chan, msg] { postToThread([chan, msg] {
chan->addMessage(msg); chan->addMessage(msg);
});
}); });
});
this->twitch->pubsub->signals_.moderation.userBanned.connect( std::ignore = this->twitch->pubsub->signals_.moderation.userBanned.connect(
[&](const auto &action) { [&](const auto &action) {
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
@ -358,208 +370,218 @@ void Application::initPubSub()
chan->addOrReplaceTimeout(msg.release()); chan->addOrReplaceTimeout(msg.release());
}); });
}); });
this->twitch->pubsub->signals_.moderation.messageDeleted.connect( std::ignore =
[&](const auto &action) { this->twitch->pubsub->signals_.moderation.messageDeleted.connect(
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); [&](const auto &action) {
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty() || getSettings()->hideDeletionActions) if (chan->isEmpty() || getSettings()->hideDeletionActions)
{
return;
}
MessageBuilder msg;
TwitchMessageBuilder::deletionMessage(action, &msg);
msg->flags.set(MessageFlag::PubSub);
postToThread([chan, msg = msg.release()] {
auto replaced = false;
LimitedQueueSnapshot<MessagePtr> snapshot =
chan->getMessageSnapshot();
int snapshotLength = snapshot.size();
// without parens it doesn't build on windows
int end = (std::max)(0, snapshotLength - 200);
for (int i = snapshotLength - 1; i >= end; --i)
{ {
auto &s = snapshot[i]; return;
if (!s->flags.has(MessageFlag::PubSub) &&
s->timeoutUser == msg->timeoutUser)
{
chan->replaceMessage(s, msg);
replaced = true;
break;
}
} }
if (!replaced)
MessageBuilder msg;
TwitchMessageBuilder::deletionMessage(action, &msg);
msg->flags.set(MessageFlag::PubSub);
postToThread([chan, msg = msg.release()] {
auto replaced = false;
LimitedQueueSnapshot<MessagePtr> snapshot =
chan->getMessageSnapshot();
int snapshotLength = snapshot.size();
// without parens it doesn't build on windows
int end = (std::max)(0, snapshotLength - 200);
for (int i = snapshotLength - 1; i >= end; --i)
{
auto &s = snapshot[i];
if (!s->flags.has(MessageFlag::PubSub) &&
s->timeoutUser == msg->timeoutUser)
{
chan->replaceMessage(s, msg);
replaced = true;
break;
}
}
if (!replaced)
{
chan->addMessage(msg);
}
});
});
std::ignore =
this->twitch->pubsub->signals_.moderation.userUnbanned.connect(
[&](const auto &action) {
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty())
{ {
return;
}
auto msg = MessageBuilder(action).release();
postToThread([chan, msg] {
chan->addMessage(msg); chan->addMessage(msg);
});
});
std::ignore =
this->twitch->pubsub->signals_.moderation.autoModMessageCaught.connect(
[&](const auto &msg, const QString &channelID) {
auto chan = this->twitch->getChannelOrEmptyByID(channelID);
if (chan->isEmpty())
{
return;
} }
});
});
this->twitch->pubsub->signals_.moderation.userUnbanned.connect( switch (msg.type)
[&](const auto &action) { {
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); case PubSubAutoModQueueMessage::Type::
AutoModCaughtMessage: {
if (chan->isEmpty()) if (msg.status == "PENDING")
{
return;
}
auto msg = MessageBuilder(action).release();
postToThread([chan, msg] {
chan->addMessage(msg);
});
});
this->twitch->pubsub->signals_.moderation.autoModMessageCaught.connect(
[&](const auto &msg, const QString &channelID) {
auto chan = this->twitch->getChannelOrEmptyByID(channelID);
if (chan->isEmpty())
{
return;
}
switch (msg.type)
{
case PubSubAutoModQueueMessage::Type::AutoModCaughtMessage: {
if (msg.status == "PENDING")
{
AutomodAction action(msg.data, channelID);
action.reason = QString("%1 level %2")
.arg(msg.contentCategory)
.arg(msg.contentLevel);
action.msgID = msg.messageID;
action.message = msg.messageText;
// this message also contains per-word automod data, which could be implemented
// extract sender data manually because Twitch loves not being consistent
QString senderDisplayName =
msg.senderUserDisplayName; // Might be transformed later
bool hasLocalizedName = false;
if (!msg.senderUserDisplayName.isEmpty())
{ {
// check for non-ascii display names AutomodAction action(msg.data, channelID);
if (QString::compare(msg.senderUserDisplayName, action.reason = QString("%1 level %2")
msg.senderUserLogin, .arg(msg.contentCategory)
Qt::CaseInsensitive) != 0) .arg(msg.contentLevel);
action.msgID = msg.messageID;
action.message = msg.messageText;
// this message also contains per-word automod data, which could be implemented
// extract sender data manually because Twitch loves not being consistent
QString senderDisplayName =
msg.senderUserDisplayName; // Might be transformed later
bool hasLocalizedName = false;
if (!msg.senderUserDisplayName.isEmpty())
{ {
hasLocalizedName = true; // check for non-ascii display names
} if (QString::compare(msg.senderUserDisplayName,
} msg.senderUserLogin,
QColor senderColor = msg.senderUserChatColor; Qt::CaseInsensitive) != 0)
QString senderColor_;
if (!senderColor.isValid() &&
getSettings()->colorizeNicknames)
{
// color may be not present if user is a grey-name
senderColor = getRandomColor(msg.senderUserID);
}
// handle username style based on prefered setting
switch (getSettings()->usernameDisplayMode.getValue())
{
case UsernameDisplayMode::Username: {
if (hasLocalizedName)
{ {
senderDisplayName = msg.senderUserLogin; hasLocalizedName = true;
} }
break;
} }
case UsernameDisplayMode::LocalizedName: { QColor senderColor = msg.senderUserChatColor;
break; QString senderColor_;
if (!senderColor.isValid() &&
getSettings()->colorizeNicknames)
{
// color may be not present if user is a grey-name
senderColor = getRandomColor(msg.senderUserID);
} }
case UsernameDisplayMode::
UsernameAndLocalizedName: {
if (hasLocalizedName)
{
senderDisplayName = QString("%1(%2)").arg(
msg.senderUserLogin,
msg.senderUserDisplayName);
}
break;
}
}
action.target = // handle username style based on prefered setting
ActionUser{msg.senderUserID, msg.senderUserLogin, switch (
senderDisplayName, senderColor}; getSettings()->usernameDisplayMode.getValue())
postToThread([chan, action] { {
const auto p = makeAutomodMessage(action); case UsernameDisplayMode::Username: {
chan->addMessage(p.first); if (hasLocalizedName)
chan->addMessage(p.second); {
}); senderDisplayName = msg.senderUserLogin;
}
break;
}
case UsernameDisplayMode::LocalizedName: {
break;
}
case UsernameDisplayMode::
UsernameAndLocalizedName: {
if (hasLocalizedName)
{
senderDisplayName =
QString("%1(%2)").arg(
msg.senderUserLogin,
msg.senderUserDisplayName);
}
break;
}
}
action.target = ActionUser{
msg.senderUserID, msg.senderUserLogin,
senderDisplayName, senderColor};
postToThread([chan, action] {
const auto p = makeAutomodMessage(action);
chan->addMessage(p.first);
chan->addMessage(p.second);
});
}
// "ALLOWED" and "DENIED" statuses remain unimplemented
// They are versions of automod_message_(denied|approved) but for mods.
} }
// "ALLOWED" and "DENIED" statuses remain unimplemented break;
// They are versions of automod_message_(denied|approved) but for mods.
case PubSubAutoModQueueMessage::Type::INVALID:
default: {
}
break;
} }
break; });
case PubSubAutoModQueueMessage::Type::INVALID: std::ignore =
default: { this->twitch->pubsub->signals_.moderation.autoModMessageBlocked.connect(
[&](const auto &action) {
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty())
{
return;
} }
break;
}
});
this->twitch->pubsub->signals_.moderation.autoModMessageBlocked.connect( postToThread([chan, action] {
[&](const auto &action) { const auto p = makeAutomodMessage(action);
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); chan->addMessage(p.first);
if (chan->isEmpty()) chan->addMessage(p.second);
{ });
return;
}
postToThread([chan, action] {
const auto p = makeAutomodMessage(action);
chan->addMessage(p.first);
chan->addMessage(p.second);
}); });
});
this->twitch->pubsub->signals_.moderation.automodUserMessage.connect( std::ignore =
[&](const auto &action) { this->twitch->pubsub->signals_.moderation.automodUserMessage.connect(
// This condition has been set up to execute isInStreamerMode() as the last thing [&](const auto &action) {
// as it could end up being expensive. // This condition has been set up to execute isInStreamerMode() as the last thing
if (getSettings()->streamerModeHideModActions && isInStreamerMode()) // as it could end up being expensive.
{ if (getSettings()->streamerModeHideModActions &&
return; isInStreamerMode())
} {
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); return;
}
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) if (chan->isEmpty())
{ {
return; return;
} }
auto msg = MessageBuilder(action).release(); auto msg = MessageBuilder(action).release();
postToThread([chan, msg] { postToThread([chan, msg] {
chan->addMessage(msg); chan->addMessage(msg);
});
chan->deleteMessage(msg->id);
}); });
chan->deleteMessage(msg->id);
});
this->twitch->pubsub->signals_.moderation.automodInfoMessage.connect( std::ignore =
[&](const auto &action) { this->twitch->pubsub->signals_.moderation.automodInfoMessage.connect(
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); [&](const auto &action) {
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) if (chan->isEmpty())
{ {
return; return;
} }
postToThread([chan, action] { postToThread([chan, action] {
const auto p = makeAutomodInfoMessage(action); const auto p = makeAutomodInfoMessage(action);
chan->addMessage(p); chan->addMessage(p);
});
}); });
});
this->twitch->pubsub->signals_.pointReward.redeemed.connect( std::ignore = this->twitch->pubsub->signals_.pointReward.redeemed.connect(
[&](auto &data) { [&](auto &data) {
QString channelId = data.value("channel_id").toString(); QString channelId = data.value("channel_id").toString();
if (channelId.isEmpty()) if (channelId.isEmpty())
@ -614,7 +636,9 @@ void Application::initBttvLiveUpdates()
return; return;
} }
this->twitch->bttvLiveUpdates->signals_.emoteAdded.connect( // We can safely ignore these signal connections since the twitch object will always
// be destroyed before the Application
std::ignore = this->twitch->bttvLiveUpdates->signals_.emoteAdded.connect(
[&](const auto &data) { [&](const auto &data) {
auto chan = this->twitch->getChannelOrEmptyByID(data.channelID); auto chan = this->twitch->getChannelOrEmptyByID(data.channelID);
@ -625,7 +649,7 @@ void Application::initBttvLiveUpdates()
} }
}); });
}); });
this->twitch->bttvLiveUpdates->signals_.emoteUpdated.connect( std::ignore = this->twitch->bttvLiveUpdates->signals_.emoteUpdated.connect(
[&](const auto &data) { [&](const auto &data) {
auto chan = this->twitch->getChannelOrEmptyByID(data.channelID); auto chan = this->twitch->getChannelOrEmptyByID(data.channelID);
@ -636,7 +660,7 @@ void Application::initBttvLiveUpdates()
} }
}); });
}); });
this->twitch->bttvLiveUpdates->signals_.emoteRemoved.connect( std::ignore = this->twitch->bttvLiveUpdates->signals_.emoteRemoved.connect(
[&](const auto &data) { [&](const auto &data) {
auto chan = this->twitch->getChannelOrEmptyByID(data.channelID); auto chan = this->twitch->getChannelOrEmptyByID(data.channelID);
@ -659,7 +683,9 @@ void Application::initSeventvEventAPI()
return; return;
} }
this->twitch->seventvEventAPI->signals_.emoteAdded.connect( // We can safely ignore these signal connections since the twitch object will always
// be destroyed before the Application
std::ignore = this->twitch->seventvEventAPI->signals_.emoteAdded.connect(
[&](const auto &data) { [&](const auto &data) {
postToThread([this, data] { postToThread([this, data] {
this->twitch->forEachSeventvEmoteSet( this->twitch->forEachSeventvEmoteSet(
@ -668,7 +694,7 @@ void Application::initSeventvEventAPI()
}); });
}); });
}); });
this->twitch->seventvEventAPI->signals_.emoteUpdated.connect( std::ignore = this->twitch->seventvEventAPI->signals_.emoteUpdated.connect(
[&](const auto &data) { [&](const auto &data) {
postToThread([this, data] { postToThread([this, data] {
this->twitch->forEachSeventvEmoteSet( this->twitch->forEachSeventvEmoteSet(
@ -677,7 +703,7 @@ void Application::initSeventvEventAPI()
}); });
}); });
}); });
this->twitch->seventvEventAPI->signals_.emoteRemoved.connect( std::ignore = this->twitch->seventvEventAPI->signals_.emoteRemoved.connect(
[&](const auto &data) { [&](const auto &data) {
postToThread([this, data] { postToThread([this, data] {
this->twitch->forEachSeventvEmoteSet( this->twitch->forEachSeventvEmoteSet(
@ -686,7 +712,7 @@ void Application::initSeventvEventAPI()
}); });
}); });
}); });
this->twitch->seventvEventAPI->signals_.userUpdated.connect( std::ignore = this->twitch->seventvEventAPI->signals_.userUpdated.connect(
[&](const auto &data) { [&](const auto &data) {
this->twitch->forEachSeventvUser(data.userID, this->twitch->forEachSeventvUser(data.userID,
[data](TwitchChannel &chan) { [data](TwitchChannel &chan) {

View file

@ -106,7 +106,7 @@ void Channel::addMessage(MessagePtr message,
if (this->messages_.pushBack(message, deleted)) if (this->messages_.pushBack(message, deleted))
{ {
this->messageRemovedFromStart.invoke(deleted); this->messageRemovedFromStart(deleted);
} }
this->messageAppended.invoke(message, overridingFlags); this->messageAppended.invoke(message, overridingFlags);
@ -353,6 +353,10 @@ void Channel::onConnected()
{ {
} }
void Channel::messageRemovedFromStart(const MessagePtr &msg)
{
}
// //
// Indirect channel // Indirect channel
// //

View file

@ -52,7 +52,6 @@ public:
pajlada::Signals::Signal<const QString &, const QString &, const QString &, pajlada::Signals::Signal<const QString &, const QString &, const QString &,
bool &> bool &>
sendReplySignal; sendReplySignal;
pajlada::Signals::Signal<MessagePtr &> messageRemovedFromStart;
pajlada::Signals::Signal<MessagePtr &, boost::optional<MessageFlags>> pajlada::Signals::Signal<MessagePtr &, boost::optional<MessageFlags>>
messageAppended; messageAppended;
pajlada::Signals::Signal<std::vector<MessagePtr> &> messagesAddedAtStart; pajlada::Signals::Signal<std::vector<MessagePtr> &> messagesAddedAtStart;
@ -114,6 +113,7 @@ public:
protected: protected:
virtual void onConnected(); virtual void onConnected();
virtual void messageRemovedFromStart(const MessagePtr &msg);
private: private:
const QString name_; const QString name_;

View file

@ -1,4 +1,4 @@
#include "AccountController.hpp" #include "controllers/accounts/AccountController.hpp"
#include "controllers/accounts/Account.hpp" #include "controllers/accounts/Account.hpp"
#include "controllers/accounts/AccountModel.hpp" #include "controllers/accounts/AccountModel.hpp"
@ -10,22 +10,27 @@ namespace chatterino {
AccountController::AccountController() AccountController::AccountController()
: accounts_(SharedPtrElementLess<Account>{}) : accounts_(SharedPtrElementLess<Account>{})
{ {
this->twitch.accounts.itemInserted.connect([this](const auto &args) { // These signal connections can safely be ignored since the twitch object
this->accounts_.insert(std::dynamic_pointer_cast<Account>(args.item)); // will always be destroyed before the AccountController
}); std::ignore =
this->twitch.accounts.itemInserted.connect([this](const auto &args) {
this->accounts_.insert(
std::dynamic_pointer_cast<Account>(args.item));
});
this->twitch.accounts.itemRemoved.connect([this](const auto &args) { std::ignore =
if (args.caller != this) this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
{ if (args.caller != this)
auto &accs = this->twitch.accounts.raw(); {
auto it = std::find(accs.begin(), accs.end(), args.item); auto &accs = this->twitch.accounts.raw();
assert(it != accs.end()); auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end());
this->accounts_.removeAt(it - accs.begin(), this); this->accounts_.removeAt(it - accs.begin(), this);
} }
}); });
this->accounts_.itemRemoved.connect([this](const auto &args) { std::ignore = this->accounts_.itemRemoved.connect([this](const auto &args) {
switch (args.item->getProviderId()) switch (args.item->getProviderId())
{ {
case ProviderId::Twitch: { case ProviderId::Twitch: {

View file

@ -587,8 +587,10 @@ void CommandController::initialize(Settings &, Paths &paths)
this->maxSpaces_ = maxSpaces; this->maxSpaces_ = maxSpaces;
}; };
this->items.itemInserted.connect(addFirstMatchToMap); // We can safely ignore these signal connections since items will be destroyed
this->items.itemRemoved.connect(addFirstMatchToMap); // before CommandController
std::ignore = this->items.itemInserted.connect(addFirstMatchToMap);
std::ignore = this->items.itemRemoved.connect(addFirstMatchToMap);
// Initialize setting manager for commands.json // Initialize setting manager for commands.json
auto path = combinePath(paths.settingsDirectory, "commands.json"); auto path = combinePath(paths.settingsDirectory, "commands.json");
@ -604,7 +606,7 @@ void CommandController::initialize(Settings &, Paths &paths)
// Update the setting when the vector of commands has been updated (most // Update the setting when the vector of commands has been updated (most
// likely from the settings dialog) // likely from the settings dialog)
this->items.delayedItemsChanged.connect([this] { std::ignore = this->items.delayedItemsChanged.connect([this] {
this->commandsSetting_->setValue(this->items.raw()); this->commandsSetting_->setValue(this->items.raw());
}); });

View file

@ -36,9 +36,13 @@ void NotificationController::initialize(Settings &settings, Paths &paths)
this->channelMap[Platform::Twitch].append(channelName); this->channelMap[Platform::Twitch].append(channelName);
} }
this->channelMap[Platform::Twitch].delayedItemsChanged.connect([this] { // We can safely ignore this signal connection since channelMap will always be destroyed
this->twitchSetting_.setValue(this->channelMap[Platform::Twitch].raw()); // before the NotificationController
}); std::ignore =
this->channelMap[Platform::Twitch].delayedItemsChanged.connect([this] {
this->twitchSetting_.setValue(
this->channelMap[Platform::Twitch].raw());
});
liveStatusTimer_ = new QTimer(); liveStatusTimer_ = new QTimer();

View file

@ -50,7 +50,7 @@ AbstractIrcServer::AbstractIrcServer()
this->writeConnection_->connectionLost, [this](bool timeout) { this->writeConnection_->connectionLost, [this](bool timeout) {
qCDebug(chatterinoIrc) qCDebug(chatterinoIrc)
<< "Write connection reconnect requested. Timeout:" << timeout; << "Write connection reconnect requested. Timeout:" << timeout;
this->writeConnection_->smartReconnect.invoke(); this->writeConnection_->smartReconnect();
}); });
// Listen to read connection message signals // Listen to read connection message signals
@ -86,7 +86,7 @@ AbstractIrcServer::AbstractIrcServer()
this->addGlobalSystemMessage( this->addGlobalSystemMessage(
"Server connection timed out, reconnecting"); "Server connection timed out, reconnecting");
} }
this->readConnection_->smartReconnect.invoke(); this->readConnection_->smartReconnect();
}); });
} }

View file

@ -88,7 +88,9 @@ void IrcServerData::setPassword(const QString &password)
Irc::Irc() Irc::Irc()
{ {
this->connections.itemInserted.connect([this](auto &&args) { // We can safely ignore this signal connection since `connections` will always
// be destroyed before the Irc object
std::ignore = this->connections.itemInserted.connect([this](auto &&args) {
// make sure only one id can only exist for one server // make sure only one id can only exist for one server
assert(this->servers_.find(args.item.id) == this->servers_.end()); assert(this->servers_.find(args.item.id) == this->servers_.end());
@ -117,7 +119,9 @@ Irc::Irc()
} }
}); });
this->connections.itemRemoved.connect([this](auto &&args) { // We can safely ignore this signal connection since `connections` will always
// be destroyed before the Irc object
std::ignore = this->connections.itemRemoved.connect([this](auto &&args) {
// restore // restore
if (auto server = this->servers_.find(args.item.id); if (auto server = this->servers_.find(args.item.id);
server != this->servers_.end()) server != this->servers_.end())
@ -141,7 +145,9 @@ Irc::Irc()
} }
}); });
this->connections.delayedItemsChanged.connect([this] { // We can safely ignore this signal connection since `connections` will always
// be destroyed before the Irc object
std::ignore = this->connections.delayedItemsChanged.connect([this] {
this->save(); this->save();
}); });
} }

View file

@ -38,18 +38,6 @@ IrcConnection::IrcConnection(QObject *parent)
} }
}); });
// Schedule a reconnect that won't violate RECONNECT_MIN_INTERVAL
this->smartReconnect.connect([this] {
if (this->reconnectTimer_.isActive())
{
return;
}
auto delay = this->reconnectBackoff_.next();
qCDebug(chatterinoIrc) << "Reconnecting in" << delay.count() << "ms";
this->reconnectTimer_.start(delay);
});
this->reconnectTimer_.setSingleShot(true); this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] { QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
if (this->isConnected()) if (this->isConnected())
@ -123,6 +111,19 @@ IrcConnection::~IrcConnection()
this->disconnect(); this->disconnect();
} }
void IrcConnection::smartReconnect()
{
if (this->reconnectTimer_.isActive())
{
// Ignore this reconnect request, we already have a reconnect request queued up
return;
}
auto delay = this->reconnectBackoff_.next();
qCDebug(chatterinoIrc) << "Reconnecting in" << delay.count() << "ms";
this->reconnectTimer_.start(delay);
}
void IrcConnection::open() void IrcConnection::open()
{ {
this->expectConnectionLoss_ = false; this->expectConnectionLoss_ = false;

View file

@ -20,7 +20,8 @@ public:
pajlada::Signals::Signal<bool> connectionLost; pajlada::Signals::Signal<bool> connectionLost;
// Request a reconnect with a minimum interval between attempts. // Request a reconnect with a minimum interval between attempts.
pajlada::Signals::NoArgSignal smartReconnect; // This won't violate RECONNECT_MIN_INTERVAL
void smartReconnect();
virtual void open(); virtual void open();
virtual void close(); virtual void close();

View file

@ -20,7 +20,9 @@ TwitchAccountManager::TwitchAccountManager()
currentUser->loadSeventvUserID(); currentUser->loadSeventvUserID();
}); });
this->accounts.itemRemoved.connect([this](const auto &acc) { // We can safely ignore this signal connection since accounts will always be removed
// before TwitchAccountManager
std::ignore = this->accounts.itemRemoved.connect([this](const auto &acc) {
this->removeUser(acc.item.get()); this->removeUser(acc.item.get());
}); });
} }

View file

@ -94,25 +94,15 @@ TwitchChannel::TwitchChannel(const QString &name)
})); }));
this->refreshPubSub(); this->refreshPubSub();
this->userStateChanged.connect([this] { // We can safely ignore this signal connection since it's a private signal, meaning
// it will only ever be invoked by TwitchChannel itself
std::ignore = this->userStateChanged.connect([this] {
this->refreshPubSub(); this->refreshPubSub();
}); });
// room id loaded -> refresh live status // We can safely ignore this signal connection this has no external dependencies - once the signal
this->roomIdChanged.connect([this]() { // is destroyed, it will no longer be able to fire
this->refreshPubSub(); std::ignore = this->connected.connect([this]() {
this->refreshBadges();
this->refreshCheerEmotes();
this->refreshFFZChannelEmotes(false);
this->refreshBTTVChannelEmotes(false);
this->refreshSevenTVChannelEmotes(false);
this->joinBttvChannel();
this->listenSevenTVCosmetics();
getIApp()->getTwitchLiveController()->add(
std::dynamic_pointer_cast<TwitchChannel>(shared_from_this()));
});
this->connected.connect([this]() {
if (this->roomId().isEmpty()) if (this->roomId().isEmpty())
{ {
// If we get a reconnected event when the room id is not set, we // If we get a reconnected event when the room id is not set, we
@ -125,18 +115,7 @@ TwitchChannel::TwitchChannel(const QString &name)
this->loadRecentMessagesReconnect(); this->loadRecentMessagesReconnect();
}); });
this->messageRemovedFromStart.connect([this](MessagePtr &msg) {
if (msg->replyThread)
{
if (msg->replyThread->liveCount(msg) == 0)
{
this->threads_.erase(msg->replyThread->rootId());
}
}
});
// timers // timers
QObject::connect(&this->chattersListTimer_, &QTimer::timeout, [this] { QObject::connect(&this->chattersListTimer_, &QTimer::timeout, [this] {
this->refreshChatters(); this->refreshChatters();
}); });
@ -538,6 +517,20 @@ void TwitchChannel::showLoginMessage()
this->addMessage(builder.release()); this->addMessage(builder.release());
} }
void TwitchChannel::roomIdChanged()
{
this->refreshPubSub();
this->refreshBadges();
this->refreshCheerEmotes();
this->refreshFFZChannelEmotes(false);
this->refreshBTTVChannelEmotes(false);
this->refreshSevenTVChannelEmotes(false);
this->joinBttvChannel();
this->listenSevenTVCosmetics();
getIApp()->getTwitchLiveController()->add(
std::dynamic_pointer_cast<TwitchChannel>(shared_from_this()));
}
QString TwitchChannel::prepareMessage(const QString &message) const QString TwitchChannel::prepareMessage(const QString &message) const
{ {
auto app = getApp(); auto app = getApp();
@ -729,7 +722,7 @@ void TwitchChannel::setRoomId(const QString &id)
if (*this->roomID_.accessConst() != id) if (*this->roomID_.accessConst() != id)
{ {
*this->roomID_.access() = id; *this->roomID_.access() = id;
this->roomIdChanged.invoke(); this->roomIdChanged();
this->loadRecentMessages(); this->loadRecentMessages();
} }
} }
@ -1063,6 +1056,17 @@ bool TwitchChannel::tryReplaceLastLiveUpdateAddOrRemove(
return true; return true;
} }
void TwitchChannel::messageRemovedFromStart(const MessagePtr &msg)
{
if (msg->replyThread)
{
if (msg->replyThread->liveCount(msg) == 0)
{
this->threads_.erase(msg->replyThread->rootId());
}
}
}
const QString &TwitchChannel::subscriptionUrl() const QString &TwitchChannel::subscriptionUrl()
{ {
return this->subscriptionUrl_; return this->subscriptionUrl_;

View file

@ -191,8 +191,7 @@ public:
const std::unordered_map<QString, std::weak_ptr<MessageThread>> &threads() const std::unordered_map<QString, std::weak_ptr<MessageThread>> &threads()
const; const;
// Signals // Only TwitchChannel may invoke this signal
pajlada::Signals::NoArgSignal roomIdChanged;
pajlada::Signals::NoArgSignal userStateChanged; pajlada::Signals::NoArgSignal userStateChanged;
/** /**
@ -242,8 +241,6 @@ private:
QString actualDisplayName; QString actualDisplayName;
} nameOptions; } nameOptions;
private:
// Methods
void refreshPubSub(); void refreshPubSub();
void refreshChatters(); void refreshChatters();
void refreshBadges(); void refreshBadges();
@ -252,6 +249,11 @@ private:
void loadRecentMessagesReconnect(); void loadRecentMessagesReconnect();
void cleanUpReplyThreads(); void cleanUpReplyThreads();
void showLoginMessage(); void showLoginMessage();
/// roomIdChanged is called whenever this channel's ID has been changed
/// This should only happen once per channel, whenever the ID goes from unset to set
void roomIdChanged();
/** Joins (subscribes to) a Twitch channel for updates on BTTV. */ /** Joins (subscribes to) a Twitch channel for updates on BTTV. */
void joinBttvChannel() const; void joinBttvChannel() const;
/** /**
@ -335,6 +337,8 @@ private:
std::unordered_map<QString, std::weak_ptr<MessageThread>> threads_; std::unordered_map<QString, std::weak_ptr<MessageThread>> threads_;
protected: protected:
void messageRemovedFromStart(const MessagePtr &msg) override;
Atomic<std::shared_ptr<const EmoteMap>> bttvEmotes_; Atomic<std::shared_ptr<const EmoteMap>> bttvEmotes_;
Atomic<std::shared_ptr<const EmoteMap>> ffzEmotes_; Atomic<std::shared_ptr<const EmoteMap>> ffzEmotes_;
Atomic<std::shared_ptr<const EmoteMap>> seventvEmotes_; Atomic<std::shared_ptr<const EmoteMap>> seventvEmotes_;

View file

@ -133,21 +133,24 @@ void TwitchIrcServer::initializeConnection(IrcConnection *connection,
std::shared_ptr<Channel> TwitchIrcServer::createChannel( std::shared_ptr<Channel> TwitchIrcServer::createChannel(
const QString &channelName) const QString &channelName)
{ {
auto channel = auto channel = std::make_shared<TwitchChannel>(channelName);
std::shared_ptr<TwitchChannel>(new TwitchChannel(channelName));
channel->initialize(); channel->initialize();
channel->sendMessageSignal.connect( // We can safely ignore these signal connections since the TwitchIrcServer is only
// ever destroyed when the full Application state is about to be destroyed, at which point
// no Channel's should live
// NOTE: CHANNEL_LIFETIME
std::ignore = channel->sendMessageSignal.connect(
[this, channel = channel.get()](auto &chan, auto &msg, bool &sent) { [this, channel = channel.get()](auto &chan, auto &msg, bool &sent) {
this->onMessageSendRequested(channel, msg, sent); this->onMessageSendRequested(channel, msg, sent);
}); });
channel->sendReplySignal.connect( std::ignore = channel->sendReplySignal.connect(
[this, channel = channel.get()](auto &chan, auto &msg, auto &replyId, [this, channel = channel.get()](auto &chan, auto &msg, auto &replyId,
bool &sent) { bool &sent) {
this->onReplySendRequested(channel, msg, replyId, sent); this->onReplySendRequested(channel, msg, replyId, sent);
}); });
return std::shared_ptr<Channel>(channel); return channel;
} }
void TwitchIrcServer::privateMessageReceived( void TwitchIrcServer::privateMessageReceived(

View file

@ -14,16 +14,21 @@ namespace chatterino {
void Logging::initialize(Settings &settings, Paths & /*paths*/) void Logging::initialize(Settings &settings, Paths & /*paths*/)
{ {
settings.loggedChannels.delayedItemsChanged.connect([this, &settings]() { // We can safely ignore this signal connection since settings are only-ever destroyed
this->threadGuard.guard(); // on application exit
// NOTE: SETTINGS_LIFETIME
std::ignore = settings.loggedChannels.delayedItemsChanged.connect(
[this, &settings]() {
this->threadGuard.guard();
this->onlyLogListedChannels.clear(); this->onlyLogListedChannels.clear();
for (const auto &loggedChannel : *settings.loggedChannels.readOnly()) for (const auto &loggedChannel :
{ *settings.loggedChannels.readOnly())
this->onlyLogListedChannels.insert(loggedChannel.channelName()); {
} this->onlyLogListedChannels.insert(loggedChannel.channelName());
}); }
});
} }
void Logging::addMessage(const QString &channelName, MessagePtr message, void Logging::addMessage(const QString &channelName, MessagePtr message,

View file

@ -344,9 +344,13 @@ void WindowManager::setEmotePopupPos(QPoint pos)
void WindowManager::initialize(Settings &settings, Paths &paths) void WindowManager::initialize(Settings &settings, Paths &paths)
{ {
(void)paths;
assertInGuiThread(); assertInGuiThread();
getApp()->themes->repaintVisibleChatWidgets_.connect([this] { // We can safely ignore this signal connection since both Themes and WindowManager
// share the Application state lifetime
// NOTE: APPLICATION_LIFETIME
std::ignore = getApp()->themes->repaintVisibleChatWidgets_.connect([this] {
this->repaintVisibleChatWidgets(); this->repaintVisibleChatWidgets();
}); });

View file

@ -1,4 +1,4 @@
#include "InitUpdateButton.hpp" #include "util/InitUpdateButton.hpp"
#include "widgets/dialogs/UpdateDialog.hpp" #include "widgets/dialogs/UpdateDialog.hpp"
#include "widgets/helper/Button.hpp" #include "widgets/helper/Button.hpp"
@ -28,7 +28,11 @@ void initUpdateButton(Button &button,
dialog->show(); dialog->show();
dialog->raise(); dialog->raise();
dialog->buttonClicked.connect([&button](auto buttonType) { // We can safely ignore the signal connection because the dialog will always
// be destroyed before the button is destroyed, since it is destroyed on focus loss
//
// The button is either attached to a Notebook, or a Window frame
std::ignore = dialog->buttonClicked.connect([&button](auto buttonType) {
switch (buttonType) switch (buttonType)
{ {
case UpdateDialog::Dismiss: { case UpdateDialog::Dismiss: {

View file

@ -20,22 +20,23 @@ AccountSwitchWidget::AccountSwitchWidget(QWidget *parent)
this->addItem(userName); this->addItem(userName);
} }
app->accounts->twitch.userListUpdated.connect([=, this]() { this->managedConnections_.managedConnect(
this->blockSignals(true); app->accounts->twitch.userListUpdated, [=, this]() {
this->blockSignals(true);
this->clear(); this->clear();
this->addItem(ANONYMOUS_USERNAME_LABEL); this->addItem(ANONYMOUS_USERNAME_LABEL);
for (const auto &userName : app->accounts->twitch.getUsernames()) for (const auto &userName : app->accounts->twitch.getUsernames())
{ {
this->addItem(userName); this->addItem(userName);
} }
this->refreshSelection(); this->refreshSelection();
this->blockSignals(false); this->blockSignals(false);
}); });
this->refreshSelection(); this->refreshSelection();

View file

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "pajlada/signals/signalholder.hpp"
#include <QListWidget> #include <QListWidget>
namespace chatterino { namespace chatterino {
@ -15,6 +17,8 @@ public:
private: private:
void refreshSelection(); void refreshSelection();
pajlada::Signals::SignalHolder managedConnections_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -246,7 +246,9 @@ EmotePopup::EmotePopup(QWidget *parent)
MessageElementFlag::Default, MessageElementFlag::AlwaysShow, MessageElementFlag::Default, MessageElementFlag::AlwaysShow,
MessageElementFlag::EmoteImages}); MessageElementFlag::EmoteImages});
view->setEnableScrollingToBottom(false); view->setEnableScrollingToBottom(false);
view->linkClicked.connect(clicked); // We can safely ignore this signal connection since the ChannelView is deleted
// either when the notebook is deleted, or when our main layout is deleted.
std::ignore = view->linkClicked.connect(clicked);
if (addToNotebook) if (addToNotebook)
{ {

View file

@ -84,9 +84,12 @@ ReplyThreadPopup::ReplyThreadPopup(bool closeAutomatically, QWidget *parent,
this->ui_.threadView->setMinimumSize(400, 100); this->ui_.threadView->setMinimumSize(400, 100);
this->ui_.threadView->setSizePolicy(QSizePolicy::Expanding, this->ui_.threadView->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Expanding); QSizePolicy::Expanding);
this->ui_.threadView->mouseDown.connect([this](QMouseEvent *) { // We can safely ignore this signal's connection since threadView will always be deleted before
this->giveFocus(Qt::MouseFocusReason); // the ReplyThreadPopup
}); std::ignore =
this->ui_.threadView->mouseDown.connect([this](QMouseEvent *) {
this->giveFocus(Qt::MouseFocusReason);
});
// Create SplitInput with inline replying disabled // Create SplitInput with inline replying disabled
this->ui_.replyInput = this->ui_.replyInput =
@ -97,8 +100,10 @@ ReplyThreadPopup::ReplyThreadPopup(bool closeAutomatically, QWidget *parent,
this->updateInputUI(); this->updateInputUI();
})); }));
// clear SplitInput selection when selecting in ChannelView // We can safely ignore this signal's connection since threadView will always be deleted before
this->ui_.threadView->selectionChanged.connect([this]() { // the ReplyThreadPopup
std::ignore = this->ui_.threadView->selectionChanged.connect([this]() {
// clear SplitInput selection when selecting in ChannelView
if (this->ui_.replyInput->hasSelection()) if (this->ui_.replyInput->hasSelection())
{ {
this->ui_.replyInput->clearSelection(); this->ui_.replyInput->clearSelection();
@ -106,7 +111,9 @@ ReplyThreadPopup::ReplyThreadPopup(bool closeAutomatically, QWidget *parent,
}); });
// clear ChannelView selection when selecting in SplitInput // clear ChannelView selection when selecting in SplitInput
this->ui_.replyInput->selectionChanged.connect([this]() { // We can safely ignore this signal's connection since replyInput will always be deleted before
// the ReplyThreadPopup
std::ignore = this->ui_.replyInput->selectionChanged.connect([this]() {
if (this->ui_.threadView->hasSelection()) if (this->ui_.threadView->hasSelection())
{ {
this->ui_.threadView->clearSelection(); this->ui_.threadView->clearSelection();

View file

@ -170,7 +170,9 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent)
view->getTableView()->horizontalHeader()->setSectionHidden(4, true); view->getTableView()->horizontalHeader()->setSectionHidden(4, true);
view->getTableView()->horizontalHeader()->setSectionHidden(5, true); view->getTableView()->horizontalHeader()->setSectionHidden(5, true);
view->addButtonPressed.connect([] { // We can safely ignore this signal's connection since the button won't be
// accessible after this dialog is closed
std::ignore = view->addButtonPressed.connect([] {
auto unique = IrcServerData{}; auto unique = IrcServerData{};
unique.id = Irc::instance().uniqueId(); unique.id = Irc::instance().uniqueId();

View file

@ -438,8 +438,10 @@ UserInfoPopup::UserInfoPopup(bool closeAutomatically, QWidget *parent,
}); });
// userstate // userstate
this->userStateChanged_.connect([this, mod, unmod, vip, // We can safely ignore this signal connection since this is a private signal, and
unvip]() mutable { // we only connect once
std::ignore = this->userStateChanged_.connect([this, mod, unmod, vip,
unvip]() mutable {
TwitchChannel *twitchChannel = TwitchChannel *twitchChannel =
dynamic_cast<TwitchChannel *>(this->underlyingChannel_.get()); dynamic_cast<TwitchChannel *>(this->underlyingChannel_.get());
@ -469,17 +471,22 @@ UserInfoPopup::UserInfoPopup(bool closeAutomatically, QWidget *parent,
{ {
auto timeout = moderation.emplace<TimeoutWidget>(); auto timeout = moderation.emplace<TimeoutWidget>();
this->userStateChanged_.connect([this, lineMod, timeout]() mutable { // We can safely ignore this signal connection since this is a private signal, and
TwitchChannel *twitchChannel = // we only connect once
dynamic_cast<TwitchChannel *>(this->underlyingChannel_.get()); std::ignore =
this->userStateChanged_.connect([this, lineMod, timeout]() mutable {
TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(
this->underlyingChannel_.get());
bool hasModRights = bool hasModRights =
twitchChannel ? twitchChannel->hasModRights() : false; twitchChannel ? twitchChannel->hasModRights() : false;
lineMod->setVisible(hasModRights); lineMod->setVisible(hasModRights);
timeout->setVisible(hasModRights); timeout->setVisible(hasModRights);
}); });
timeout->buttonClicked.connect([this](auto item) { // We can safely ignore this signal connection since we own the button, and
// the button will always be destroyed before the UserInfoPopup
std::ignore = timeout->buttonClicked.connect([this](auto item) {
TimeoutWidget::Action action; TimeoutWidget::Action action;
int arg; int arg;
std::tie(action, arg) = item; std::tie(action, arg) = item;

View file

@ -227,7 +227,9 @@ void ChannelView::initializeLayout()
void ChannelView::initializeScrollbar() void ChannelView::initializeScrollbar()
{ {
this->scrollBar_->getCurrentValueChanged().connect([this] { // We can safely ignore the scroll bar's signal connection since the scroll bar will
// always be destroyed before the ChannelView
std::ignore = this->scrollBar_->getCurrentValueChanged().connect([this] {
if (this->isVisible()) if (this->isVisible())
{ {
this->performLayout(true); this->performLayout(true);

View file

@ -33,7 +33,8 @@ AccountsPage::AccountsPage()
view->getTableView()->horizontalHeader()->setVisible(false); view->getTableView()->horizontalHeader()->setVisible(false);
view->getTableView()->horizontalHeader()->setStretchLastSection(true); view->getTableView()->horizontalHeader()->setStretchLastSection(true);
view->addButtonPressed.connect([this] { // We can safely ignore this signal connection since we own the view
std::ignore = view->addButtonPressed.connect([this] {
LoginDialog d(this); LoginDialog d(this);
d.exec(); d.exec();
}); });

View file

@ -45,7 +45,8 @@ CommandPage::CommandPage()
view->setTitles({"Trigger", "Command", "Show In\nMessage Menu"}); view->setTitles({"Trigger", "Command", "Show In\nMessage Menu"});
view->getTableView()->horizontalHeader()->setSectionResizeMode( view->getTableView()->horizontalHeader()->setSectionResizeMode(
1, QHeaderView::Stretch); 1, QHeaderView::Stretch);
view->addButtonPressed.connect([] { // We can safely ignore this signal connection since we own the view
std::ignore = view->addButtonPressed.connect([] {
getApp()->commands->items.append( getApp()->commands->items.append(
Command{"/command", "I made a new command HeyGuys"}); Command{"/command", "I made a new command HeyGuys"});
}); });

View file

@ -44,7 +44,8 @@ FiltersPage::FiltersPage()
view->getTableView()->setColumnWidth(2, 125); view->getTableView()->setColumnWidth(2, 125);
}); });
view->addButtonPressed.connect([] { // We can safely ignore this signal connection since we own the view
std::ignore = view->addButtonPressed.connect([] {
ChannelFilterEditorDialog d( ChannelFilterEditorDialog d(
static_cast<QWidget *>(&(getApp()->windows->getMainWindow()))); static_cast<QWidget *>(&(getApp()->windows->getMainWindow())));
if (d.exec() == QDialog::Accepted) if (d.exec() == QDialog::Accepted)

View file

@ -195,13 +195,17 @@ ColorButton *GeneralPageView::addColorButton(
auto dialog = new ColorPickerDialog(QColor(setting), this); auto dialog = new ColorPickerDialog(QColor(setting), this);
dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show(); dialog->show();
dialog->closed.connect([&setting, colorButton](QColor selected) { // We can safely ignore this signal connection, for now, since the
if (selected.isValid()) // colorButton & setting are never deleted and the signal is deleted
{ // once the dialog is closed
setting = selected.name(QColor::HexArgb); std::ignore = dialog->closed.connect(
colorButton->setColor(selected); [&setting, colorButton](QColor selected) {
} if (selected.isValid())
}); {
setting = selected.name(QColor::HexArgb);
colorButton->setColor(selected);
}
});
}); });
this->groups_.back().widgets.push_back({label, {text}}); this->groups_.back().widgets.push_back({label, {text}});

View file

@ -90,7 +90,8 @@ HighlightingPage::HighlightingPage()
view->getTableView()->setColumnWidth(0, 400); view->getTableView()->setColumnWidth(0, 400);
}); });
view->addButtonPressed.connect([] { // We can safely ignore this signal connection since we own the view
std::ignore = view->addButtonPressed.connect([] {
getSettings()->highlightedMessages.append(HighlightPhrase{ getSettings()->highlightedMessages.append(HighlightPhrase{
"my phrase", true, true, false, false, false, "", "my phrase", true, true, false, false, false, "",
*ColorProvider::instance().color( *ColorProvider::instance().color(
@ -141,7 +142,8 @@ HighlightingPage::HighlightingPage()
view->getTableView()->setColumnWidth(0, 200); view->getTableView()->setColumnWidth(0, 200);
}); });
view->addButtonPressed.connect([] { // We can safely ignore this signal connection since we own the view
std::ignore = view->addButtonPressed.connect([] {
getSettings()->highlightedUsers.append(HighlightPhrase{ getSettings()->highlightedUsers.append(HighlightPhrase{
"highlighted user", true, true, false, false, false, "", "highlighted user", true, true, false, false, false, "",
*ColorProvider::instance().color( *ColorProvider::instance().color(
@ -182,7 +184,8 @@ HighlightingPage::HighlightingPage()
view->getTableView()->setColumnWidth(0, 200); view->getTableView()->setColumnWidth(0, 200);
}); });
view->addButtonPressed.connect([this] { // We can safely ignore this signal connection since we own the view
std::ignore = view->addButtonPressed.connect([this] {
auto d = std::make_shared<BadgePickerDialog>( auto d = std::make_shared<BadgePickerDialog>(
availableBadges, this); availableBadges, this);
@ -236,7 +239,8 @@ HighlightingPage::HighlightingPage()
view->getTableView()->setColumnWidth(0, 200); view->getTableView()->setColumnWidth(0, 200);
}); });
view->addButtonPressed.connect([] { // We can safely ignore this signal connection since we own the view
std::ignore = view->addButtonPressed.connect([] {
getSettings()->blacklistedUsers.append( getSettings()->blacklistedUsers.append(
HighlightBlacklistUser{"blacklisted user", false}); HighlightBlacklistUser{"blacklisted user", false});
}); });
@ -329,7 +333,10 @@ void HighlightingPage::openColorDialog(const QModelIndex &clicked,
auto dialog = new ColorPickerDialog(initial, this); auto dialog = new ColorPickerDialog(initial, this);
dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show(); dialog->show();
dialog->closed.connect([=](auto selected) { // We can safely ignore this signal connection since the view and tab are never deleted
// TODO: The QModelIndex clicked is technically not safe to persist here since the model
// can be changed between the color dialog being created & the color dialog being closed
std::ignore = dialog->closed.connect([=](auto selected) {
if (selected.isValid()) if (selected.isValid())
{ {
view->getModel()->setData(clicked, selected, Qt::DecorationRole); view->getModel()->setData(clicked, selected, Qt::DecorationRole);

View file

@ -63,7 +63,8 @@ void addPhrasesTab(LayoutCreator<QVBoxLayout> layout)
view->getTableView()->setColumnWidth(0, 200); view->getTableView()->setColumnWidth(0, 200);
}); });
view->addButtonPressed.connect([] { // We can safely ignore this signal connection since we own the view
std::ignore = view->addButtonPressed.connect([] {
getSettings()->ignoredMessages.append( getSettings()->ignoredMessages.append(
IgnorePhrase{"my pattern", false, false, IgnorePhrase{"my pattern", false, false,
getSettings()->ignoredPhraseReplace.getValue(), true}); getSettings()->ignoredPhraseReplace.getValue(), true});

View file

@ -34,7 +34,8 @@ KeyboardSettingsPage::KeyboardSettingsPage()
view->getTableView()->horizontalHeader()->setSectionResizeMode( view->getTableView()->horizontalHeader()->setSectionResizeMode(
1, QHeaderView::Stretch); 1, QHeaderView::Stretch);
view->addButtonPressed.connect([view, model] { // We can safely ignore this signal connection since we own the view
std::ignore = view->addButtonPressed.connect([view, model] {
EditHotkeyDialog dialog(nullptr); EditHotkeyDialog dialog(nullptr);
bool wasAccepted = dialog.exec() == 1; bool wasAccepted = dialog.exec() == 1;

View file

@ -169,7 +169,8 @@ ModerationPage::ModerationPage()
view->getTableView()->horizontalHeader()->setSectionResizeMode( view->getTableView()->horizontalHeader()->setSectionResizeMode(
0, QHeaderView::Stretch); 0, QHeaderView::Stretch);
view->addButtonPressed.connect([] { // We can safely ignore this signal connection since we own the view
std::ignore = view->addButtonPressed.connect([] {
getSettings()->loggedChannels.append(ChannelLog("channel")); getSettings()->loggedChannels.append(ChannelLog("channel"));
}); });
@ -209,7 +210,8 @@ ModerationPage::ModerationPage()
view->getTableView()->horizontalHeader()->setSectionResizeMode( view->getTableView()->horizontalHeader()->setSectionResizeMode(
0, QHeaderView::Stretch); 0, QHeaderView::Stretch);
view->addButtonPressed.connect([] { // We can safely ignore this signal connection since we own the view
std::ignore = view->addButtonPressed.connect([] {
getSettings()->moderationActions.append( getSettings()->moderationActions.append(
ModerationAction("/timeout {user.name} 300")); ModerationAction("/timeout {user.name} 300"));
}); });

View file

@ -36,7 +36,8 @@ NicknamesPage::NicknamesPage()
view->getTableView()->horizontalHeader()->setSectionResizeMode( view->getTableView()->horizontalHeader()->setSectionResizeMode(
1, QHeaderView::Stretch); 1, QHeaderView::Stretch);
view->addButtonPressed.connect([] { // We can safely ignore this signal connection since we own the view
std::ignore = view->addButtonPressed.connect([] {
getSettings()->nicknames.append( getSettings()->nicknames.append(
Nickname{"Username", "Nickname", false, false}); Nickname{"Username", "Nickname", false, false});
}); });

View file

@ -109,7 +109,8 @@ NotificationPage::NotificationPage()
view->getTableView()->setColumnWidth(0, 200); view->getTableView()->setColumnWidth(0, 200);
}); });
view->addButtonPressed.connect([] { // We can safely ignore this signal connection since we own the view
std::ignore = view->addButtonPressed.connect([] {
getApp() getApp()
->notifications->channelMap[Platform::Twitch] ->notifications->channelMap[Platform::Twitch]
.append("channel"); .append("channel");

View file

@ -247,7 +247,8 @@ Split::Split(QWidget *parent)
this->updateInputPlaceholder(); this->updateInputPlaceholder();
// clear SplitInput selection when selecting in ChannelView // clear SplitInput selection when selecting in ChannelView
this->view_->selectionChanged.connect([this]() { // this connection can be ignored since the ChannelView is owned by this Split
std::ignore = this->view_->selectionChanged.connect([this]() {
if (this->input_->hasSelection()) if (this->input_->hasSelection())
{ {
this->input_->clearSelection(); this->input_->clearSelection();
@ -255,55 +256,60 @@ Split::Split(QWidget *parent)
}); });
// clear ChannelView selection when selecting in SplitInput // clear ChannelView selection when selecting in SplitInput
this->input_->selectionChanged.connect([this]() { // this connection can be ignored since the SplitInput is owned by this Split
std::ignore = this->input_->selectionChanged.connect([this]() {
if (this->view_->hasSelection()) if (this->view_->hasSelection())
{ {
this->view_->clearSelection(); this->view_->clearSelection();
} }
}); });
this->view_->openChannelIn.connect([this]( // this connection can be ignored since the ChannelView is owned by this Split
QString twitchChannel, std::ignore = this->view_->openChannelIn.connect(
FromTwitchLinkOpenChannelIn openIn) { [this](QString twitchChannel, FromTwitchLinkOpenChannelIn openIn) {
ChannelPtr channel = getApp()->twitch->getOrAddChannel(twitchChannel); ChannelPtr channel =
switch (openIn) getApp()->twitch->getOrAddChannel(twitchChannel);
{ switch (openIn)
case FromTwitchLinkOpenChannelIn::Split: {
this->openSplitRequested.invoke(channel); case FromTwitchLinkOpenChannelIn::Split:
break; this->openSplitRequested.invoke(channel);
case FromTwitchLinkOpenChannelIn::Tab: break;
this->joinChannelInNewTab(channel); case FromTwitchLinkOpenChannelIn::Tab:
break; this->joinChannelInNewTab(channel);
case FromTwitchLinkOpenChannelIn::BrowserPlayer: break;
this->openChannelInBrowserPlayer(channel); case FromTwitchLinkOpenChannelIn::BrowserPlayer:
break; this->openChannelInBrowserPlayer(channel);
case FromTwitchLinkOpenChannelIn::Streamlink: break;
this->openChannelInStreamlink(twitchChannel); case FromTwitchLinkOpenChannelIn::Streamlink:
break; this->openChannelInStreamlink(twitchChannel);
default: break;
qCWarning(chatterinoWidget) default:
<< "Unhandled \"FromTwitchLinkOpenChannelIn\" enum value: " qCWarning(chatterinoWidget)
<< static_cast<int>(openIn); << "Unhandled \"FromTwitchLinkOpenChannelIn\" enum "
} "value: "
}); << static_cast<int>(openIn);
}
});
this->input_->textChanged.connect([this](const QString &newText) { // this connection can be ignored since the SplitInput is owned by this Split
if (getSettings()->showEmptyInput) std::ignore =
{ this->input_->textChanged.connect([this](const QString &newText) {
// We always show the input regardless of the text, so we can early out here if (getSettings()->showEmptyInput)
return; {
} // We always show the input regardless of the text, so we can early out here
return;
}
if (newText.isEmpty()) if (newText.isEmpty())
{ {
this->input_->hide(); this->input_->hide();
} }
else if (this->input_->isHidden()) else if (this->input_->isHidden())
{ {
// Text updated and the input was previously hidden, show it // Text updated and the input was previously hidden, show it
this->input_->show(); this->input_->show();
} }
}); });
getSettings()->showEmptyInput.connect( getSettings()->showEmptyInput.connect(
[this](const bool &showEmptyInput, auto) { [this](const bool &showEmptyInput, auto) {
@ -367,7 +373,9 @@ Split::Split(QWidget *parent)
// Forward textEdit's focusLost event // Forward textEdit's focusLost event
this->focusLost.invoke(); this->focusLost.invoke();
}); });
this->input_->ui_.textEdit->imagePasted.connect(
// this connection can be ignored since the SplitInput is owned by this Split
std::ignore = this->input_->ui_.textEdit->imagePasted.connect(
[this](const QMimeData *source) { [this](const QMimeData *source) {
if (!getSettings()->imageUploaderEnabled) if (!getSettings()->imageUploaderEnabled)
return; return;
@ -896,7 +904,9 @@ void Split::showChangeChannelPopup(const char *dialogTitle, bool empty,
dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowTitle(dialogTitle); dialog->setWindowTitle(dialogTitle);
dialog->show(); dialog->show();
dialog->closed.connect([=, this] { // We can safely ignore this signal connection since the dialog will be closed before
// this Split is closed
std::ignore = dialog->closed.connect([=, this] {
if (dialog->hasSeletedChannel()) if (dialog->hasSeletedChannel())
{ {
this->setChannel(dialog->getSelectedChannel()); this->setChannel(dialog->getSelectedChannel());

View file

@ -44,43 +44,45 @@ using namespace chatterino;
// 5 minutes // 5 minutes
constexpr const uint64_t THUMBNAIL_MAX_AGE_MS = 5ULL * 60 * 1000; constexpr const uint64_t THUMBNAIL_MAX_AGE_MS = 5ULL * 60 * 1000;
auto formatRoomMode(TwitchChannel &channel) -> QString auto formatRoomModeUnclean(
const SharedAccessGuard<const TwitchChannel::RoomModes> &modes) -> QString
{ {
QString text; QString text;
if (modes->r9k)
{ {
auto modes = channel.accessRoomModes(); text += "r9k, ";
}
if (modes->r9k) if (modes->slowMode > 0)
{
text += QString("slow(%1), ").arg(localizeNumbers(modes->slowMode));
}
if (modes->emoteOnly)
{
text += "emote, ";
}
if (modes->submode)
{
text += "sub, ";
}
if (modes->followerOnly != -1)
{
if (modes->followerOnly != 0)
{ {
text += "r9k, "; text += QString("follow(%1m), ")
.arg(localizeNumbers(modes->followerOnly));
} }
if (modes->slowMode > 0) else
{ {
text += QString("slow(%1), ").arg(localizeNumbers(modes->slowMode)); text += QString("follow, ");
}
if (modes->emoteOnly)
{
text += "emote, ";
}
if (modes->submode)
{
text += "sub, ";
}
if (modes->followerOnly != -1)
{
if (modes->followerOnly != 0)
{
text += QString("follow(%1m), ")
.arg(localizeNumbers(modes->followerOnly));
}
else
{
text += QString("follow, ");
}
} }
} }
return text;
}
void cleanRoomModeText(QString &text, bool hasModRights)
{
if (text.length() > 2) if (text.length() > 2)
{ {
text = text.mid(0, text.size() - 2); text = text.mid(0, text.size() - 2);
@ -97,12 +99,10 @@ auto formatRoomMode(TwitchChannel &channel) -> QString
} }
} }
if (text.isEmpty() && channel.hasModRights()) if (text.isEmpty() && hasModRights)
{ {
return "none"; text = "none";
} }
return text;
} }
auto formatTooltip(const TwitchChannel::StreamStatus &s, QString thumbnail) auto formatTooltip(const TwitchChannel::StreamStatus &s, QString thumbnail)
@ -231,13 +231,16 @@ SplitHeader::SplitHeader(Split *split)
this->handleChannelChanged(); this->handleChannelChanged();
this->updateModerationModeIcon(); this->updateModerationModeIcon();
this->split_->focused.connect([this]() { // The lifetime of these signals are tied to the lifetime of the Split.
// Since the SplitHeader is owned by the Split, they will always be destroyed
// at the same time.
std::ignore = this->split_->focused.connect([this]() {
this->themeChangedEvent(); this->themeChangedEvent();
}); });
this->split_->focusLost.connect([this]() { std::ignore = this->split_->focusLost.connect([this]() {
this->themeChangedEvent(); this->themeChangedEvent();
}); });
this->split_->channelChanged.connect([this]() { std::ignore = this->split_->channelChanged.connect([this]() {
this->handleChannelChanged(); this->handleChannelChanged();
}); });
@ -257,6 +260,8 @@ SplitHeader::SplitHeader(Split *split)
void SplitHeader::initializeLayout() void SplitHeader::initializeLayout()
{ {
assert(this->layout() == nullptr);
auto *layout = makeLayout<QHBoxLayout>({ auto *layout = makeLayout<QHBoxLayout>({
// space // space
makeWidget<BaseWidget>([](auto w) { makeWidget<BaseWidget>([](auto w) {
@ -277,7 +282,6 @@ void SplitHeader::initializeLayout()
this->modeButton_ = makeWidget<EffectLabel>([&](auto w) { this->modeButton_ = makeWidget<EffectLabel>([&](auto w) {
w->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); w->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
w->hide(); w->hide();
this->initializeModeSignals(*w);
w->setMenu(this->createChatModeMenu()); w->setMenu(this->createChatModeMenu());
}), }),
// moderator // moderator
@ -573,43 +577,23 @@ std::unique_ptr<QMenu> SplitHeader::createChatModeMenu()
{ {
auto menu = std::make_unique<QMenu>(); auto menu = std::make_unique<QMenu>();
auto *setSub = new QAction("Subscriber only", this); this->modeActionSetSub = new QAction("Subscriber only", this);
auto *setEmote = new QAction("Emote only", this); this->modeActionSetEmote = new QAction("Emote only", this);
auto *setSlow = new QAction("Slow", this); this->modeActionSetSlow = new QAction("Slow", this);
auto *setR9k = new QAction("R9K", this); this->modeActionSetR9k = new QAction("R9K", this);
auto *setFollowers = new QAction("Followers only", this); this->modeActionSetFollowers = new QAction("Followers only", this);
setFollowers->setCheckable(true); this->modeActionSetFollowers->setCheckable(true);
setSub->setCheckable(true); this->modeActionSetSub->setCheckable(true);
setEmote->setCheckable(true); this->modeActionSetEmote->setCheckable(true);
setSlow->setCheckable(true); this->modeActionSetSlow->setCheckable(true);
setR9k->setCheckable(true); this->modeActionSetR9k->setCheckable(true);
menu->addAction(setEmote); menu->addAction(this->modeActionSetEmote);
menu->addAction(setSub); menu->addAction(this->modeActionSetSub);
menu->addAction(setSlow); menu->addAction(this->modeActionSetSlow);
menu->addAction(setR9k); menu->addAction(this->modeActionSetR9k);
menu->addAction(setFollowers); menu->addAction(this->modeActionSetFollowers);
this->managedConnections_.managedConnect(
this->modeUpdateRequested_,
[this, setSub, setEmote, setSlow, setR9k, setFollowers]() {
auto *twitchChannel =
dynamic_cast<TwitchChannel *>(this->split_->getChannel().get());
if (twitchChannel == nullptr)
{
this->modeButton_->hide();
return;
}
auto roomModes = twitchChannel->accessRoomModes();
setR9k->setChecked(roomModes->r9k);
setSlow->setChecked(roomModes->slowMode > 0);
setEmote->setChecked(roomModes->emoteOnly);
setSub->setChecked(roomModes->submode);
setFollowers->setChecked(roomModes->followerOnly != -1);
});
auto execCommand = [this](const QString &command) { auto execCommand = [this](const QString &command) {
auto text = getApp()->getCommands()->execCommand( auto text = getApp()->getCommands()->execCommand(
@ -622,44 +606,44 @@ std::unique_ptr<QMenu> SplitHeader::createChatModeMenu()
action->setChecked(!action->isChecked()); action->setChecked(!action->isChecked());
}; };
QObject::connect(setSub, &QAction::triggered, this, QObject::connect(this->modeActionSetSub, &QAction::triggered, this,
[setSub, toggle]() mutable { [this, toggle]() mutable {
toggle("/subscribers", setSub); toggle("/subscribers", this->modeActionSetSub);
}); });
QObject::connect(setEmote, &QAction::triggered, this, QObject::connect(this->modeActionSetEmote, &QAction::triggered, this,
[setEmote, toggle]() mutable { [this, toggle]() mutable {
toggle("/emoteonly", setEmote); toggle("/emoteonly", this->modeActionSetEmote);
}); });
QObject::connect( QObject::connect(this->modeActionSetSlow, &QAction::triggered, this,
setSlow, &QAction::triggered, this, [setSlow, this, execCommand]() { [this, execCommand]() {
if (!setSlow->isChecked()) if (!this->modeActionSetSlow->isChecked())
{ {
execCommand("/slowoff"); execCommand("/slowoff");
setSlow->setChecked(false); this->modeActionSetSlow->setChecked(false);
return; return;
}; };
auto ok = bool(); auto ok = bool();
auto seconds = auto seconds = QInputDialog::getInt(
QInputDialog::getInt(this, "", "Seconds:", 10, 0, 500, 1, &ok, this, "", "Seconds:", 10, 0, 500, 1, &ok,
Qt::FramelessWindowHint); Qt::FramelessWindowHint);
if (ok) if (ok)
{ {
execCommand(QString("/slow %1").arg(seconds)); execCommand(QString("/slow %1").arg(seconds));
} }
else else
{ {
setSlow->setChecked(false); this->modeActionSetSlow->setChecked(false);
} }
}); });
QObject::connect(setFollowers, &QAction::triggered, this, QObject::connect(this->modeActionSetFollowers, &QAction::triggered, this,
[setFollowers, this, execCommand]() { [this, execCommand]() {
if (!setFollowers->isChecked()) if (!this->modeActionSetFollowers->isChecked())
{ {
execCommand("/followersoff"); execCommand("/followersoff");
setFollowers->setChecked(false); this->modeActionSetFollowers->setChecked(false);
return; return;
}; };
auto ok = bool(); auto ok = bool();
@ -673,13 +657,13 @@ std::unique_ptr<QMenu> SplitHeader::createChatModeMenu()
} }
else else
{ {
setFollowers->setChecked(false); this->modeActionSetFollowers->setChecked(false);
} }
}); });
QObject::connect(setR9k, &QAction::triggered, this, QObject::connect(this->modeActionSetR9k, &QAction::triggered, this,
[setR9k, toggle]() mutable { [this, toggle]() mutable {
toggle("/r9kbeta", setR9k); toggle("/r9kbeta", this->modeActionSetR9k);
}); });
return menu; return menu;
@ -687,30 +671,47 @@ std::unique_ptr<QMenu> SplitHeader::createChatModeMenu()
void SplitHeader::updateRoomModes() void SplitHeader::updateRoomModes()
{ {
this->modeUpdateRequested_.invoke(); assert(this->modeButton_ != nullptr);
}
void SplitHeader::initializeModeSignals(EffectLabel &label) // Update the mode button
{ if (auto *twitchChannel =
this->modeUpdateRequested_.connect([this, &label] { dynamic_cast<TwitchChannel *>(this->split_->getChannel().get()))
if (auto *twitchChannel = {
dynamic_cast<TwitchChannel *>(this->split_->getChannel().get())) this->modeButton_->setEnable(twitchChannel->hasModRights());
QString text;
{ {
label.setEnable(twitchChannel->hasModRights()); auto roomModes = twitchChannel->accessRoomModes();
text = formatRoomModeUnclean(roomModes);
// set the label text // Set menu action
auto text = formatRoomMode(*twitchChannel); this->modeActionSetR9k->setChecked(roomModes->r9k);
this->modeActionSetSlow->setChecked(roomModes->slowMode > 0);
this->modeActionSetEmote->setChecked(roomModes->emoteOnly);
this->modeActionSetSub->setChecked(roomModes->submode);
this->modeActionSetFollowers->setChecked(roomModes->followerOnly !=
-1);
}
cleanRoomModeText(text, twitchChannel->hasModRights());
if (!text.isEmpty()) // set the label text
{
label.getLabel().setText(text); if (!text.isEmpty())
label.show(); {
return; this->modeButton_->getLabel().setText(text);
} this->modeButton_->show();
}
else
{
this->modeButton_->hide();
} }
label.hide(); // Update the mode button menu actions
}); }
else
{
this->modeButton_->hide();
}
} }
void SplitHeader::resetThumbnail() void SplitHeader::resetThumbnail()

View file

@ -32,6 +32,8 @@ public:
void updateChannelText(); void updateChannelText();
void updateModerationModeIcon(); void updateModerationModeIcon();
// Invoked when SplitHeader should update anything refering to a TwitchChannel's mode
// has changed (e.g. sub mode toggled)
void updateRoomModes(); void updateRoomModes();
protected: protected:
@ -75,7 +77,14 @@ private:
// ui // ui
Button *dropdownButton_{}; Button *dropdownButton_{};
Label *titleLabel_{}; Label *titleLabel_{};
EffectLabel *modeButton_{}; EffectLabel *modeButton_{};
QAction *modeActionSetEmote{};
QAction *modeActionSetSub{};
QAction *modeActionSetSlow{};
QAction *modeActionSetR9k{};
QAction *modeActionSetFollowers{};
Button *moderationButton_{}; Button *moderationButton_{};
Button *viewersButton_{}; Button *viewersButton_{};
Button *addButton_{}; Button *addButton_{};
@ -86,8 +95,8 @@ private:
bool doubleClicked_{false}; bool doubleClicked_{false};
bool menuVisible_{false}; bool menuVisible_{false};
// signals // managedConnections_ contains connections for signals that are not managed by us
pajlada::Signals::NoArgSignal modeUpdateRequested_; // and don't change when the parent Split changes its underlying channel
pajlada::Signals::SignalHolder managedConnections_; pajlada::Signals::SignalHolder managedConnections_;
pajlada::Signals::SignalHolder channelConnections_; pajlada::Signals::SignalHolder channelConnections_;
std::vector<boost::signals2::scoped_connection> bSignals_; std::vector<boost::signals2::scoped_connection> bSignals_;

View file

@ -63,7 +63,9 @@ SplitInput::SplitInput(QWidget *parent, Split *_chatWidget,
// misc // misc
this->installKeyPressedEvent(); this->installKeyPressedEvent();
this->addShortcuts(); this->addShortcuts();
this->ui_.textEdit->focusLost.connect([this] { // The textEdit's signal will be destroyed before this SplitInput is
// destroyed, so we can safely ignore this signal's connection.
std::ignore = this->ui_.textEdit->focusLost.connect([this] {
this->hideCompletionPopup(); this->hideCompletionPopup();
}); });
this->scaleChangedEvent(this->scale()); this->scaleChangedEvent(this->scale());
@ -293,23 +295,25 @@ void SplitInput::openEmotePopup()
this->emotePopup_ = new EmotePopup(this); this->emotePopup_ = new EmotePopup(this);
this->emotePopup_->setAttribute(Qt::WA_DeleteOnClose); this->emotePopup_->setAttribute(Qt::WA_DeleteOnClose);
this->emotePopup_->linkClicked.connect([this](const Link &link) { // The EmotePopup is closed & destroyed when this is destroyed, meaning it's safe to ignore this connection
if (link.type == Link::InsertText) std::ignore =
{ this->emotePopup_->linkClicked.connect([this](const Link &link) {
QTextCursor cursor = this->ui_.textEdit->textCursor(); if (link.type == Link::InsertText)
QString textToInsert(link.value + " ");
// If symbol before cursor isn't space or empty
// Then insert space before emote.
if (cursor.position() > 0 &&
!this->getInputText()[cursor.position() - 1].isSpace())
{ {
textToInsert = " " + textToInsert; QTextCursor cursor = this->ui_.textEdit->textCursor();
QString textToInsert(link.value + " ");
// If symbol before cursor isn't space or empty
// Then insert space before emote.
if (cursor.position() > 0 &&
!this->getInputText()[cursor.position() - 1].isSpace())
{
textToInsert = " " + textToInsert;
}
this->insertText(textToInsert);
this->ui_.textEdit->activateWindow();
} }
this->insertText(textToInsert); });
this->ui_.textEdit->activateWindow();
}
});
} }
this->emotePopup_->resize(int(300 * this->emotePopup_->scale()), this->emotePopup_->resize(int(300 * this->emotePopup_->scale()),
@ -649,33 +653,40 @@ bool SplitInput::eventFilter(QObject *obj, QEvent *event)
void SplitInput::installKeyPressedEvent() void SplitInput::installKeyPressedEvent()
{ {
this->ui_.textEdit->keyPressed.disconnectAll(); // We can safely ignore this signal's connection because SplitInput owns
this->ui_.textEdit->keyPressed.connect([this](QKeyEvent *event) { // the textEdit object, so it will always be deleted before SplitInput
if (auto *popup = this->inputCompletionPopup_.data()) std::ignore =
{ this->ui_.textEdit->keyPressed.connect([this](QKeyEvent *event) {
if (popup->isVisible()) if (auto *popup = this->inputCompletionPopup_.data())
{ {
if (popup->eventFilter(nullptr, event)) if (popup->isVisible())
{ {
event->accept(); if (popup->eventFilter(nullptr, event))
return; {
event->accept();
return;
}
} }
} }
}
// One of the last remaining of it's kind, the copy shortcut. // One of the last remaining of it's kind, the copy shortcut.
// For some bizarre reason Qt doesn't want this key be rebound. // For some bizarre reason Qt doesn't want this key be rebound.
// TODO(Mm2PL): Revisit in Qt6, maybe something changed? // TODO(Mm2PL): Revisit in Qt6, maybe something changed?
if ((event->key() == Qt::Key_C || event->key() == Qt::Key_Insert) && if ((event->key() == Qt::Key_C || event->key() == Qt::Key_Insert) &&
event->modifiers() == Qt::ControlModifier) event->modifiers() == Qt::ControlModifier)
{
if (this->channelView_->hasSelection())
{ {
this->channelView_->copySelectedText(); if (this->channelView_->hasSelection())
event->accept(); {
this->channelView_->copySelectedText();
event->accept();
}
} }
} });
});
#ifdef DEBUG
assert(this->keyPressedEventInstalled == false);
this->keyPressedEventInstalled = true;
#endif
} }
void SplitInput::mousePressEvent(QMouseEvent *event) void SplitInput::mousePressEvent(QMouseEvent *event)

View file

@ -91,6 +91,9 @@ protected:
void addShortcuts() override; void addShortcuts() override;
void initLayout(); void initLayout();
bool eventFilter(QObject *obj, QEvent *event) override; bool eventFilter(QObject *obj, QEvent *event) override;
#ifdef DEBUG
bool keyPressedEventInstalled{};
#endif
void installKeyPressedEvent(); void installKeyPressedEvent();
void onCursorPositionChanged(); void onCursorPositionChanged();
void onTextChanged(); void onTextChanged();