Compare commits

...

4 commits

Author SHA1 Message Date
dependabot[bot] 2582e34734
chore(deps): bump ZedThree/clang-tidy-review from 0.15.1 to 0.16.0 (#5097)
Bumps [ZedThree/clang-tidy-review](https://github.com/zedthree/clang-tidy-review) from 0.15.1 to 0.16.0.
- [Release notes](https://github.com/zedthree/clang-tidy-review/releases)
- [Changelog](https://github.com/ZedThree/clang-tidy-review/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zedthree/clang-tidy-review/compare/v0.15.1...v0.16.0)

---
updated-dependencies:
- dependency-name: ZedThree/clang-tidy-review
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-18 00:29:23 +00:00
pajlada 11838c8e16
refactor: Move TwitchBadges to Application (#5096)
* refactor: Move TwitchBadges to Application

* refactor: Use named initializers

* refactor: Use `empty()` instead of `size() > 0`

* refactor: use emplace instead of push into the callback queue
2024-01-17 23:53:10 +01:00
pajlada 7d5967c248
Use the same input padding between light & dark themes (#5095) 2024-01-17 20:34:01 +00:00
pajlada 9eeea8f203
refactor: Fix a bunch of minor things (#5094) 2024-01-17 21:05:44 +01:00
22 changed files with 251 additions and 185 deletions

View file

@ -119,7 +119,7 @@ jobs:
- name: clang-tidy review - name: clang-tidy review
timeout-minutes: 20 timeout-minutes: 20
uses: ZedThree/clang-tidy-review@v0.15.1 uses: ZedThree/clang-tidy-review@v0.16.0
with: with:
build_dir: build-clang-tidy build_dir: build-clang-tidy
config_file: ".clang-tidy" config_file: ".clang-tidy"
@ -145,4 +145,4 @@ jobs:
libbenchmark-dev libbenchmark-dev
- name: clang-tidy-review upload - name: clang-tidy-review upload
uses: ZedThree/clang-tidy-review/upload@v0.15.1 uses: ZedThree/clang-tidy-review/upload@v0.16.0

View file

@ -14,6 +14,6 @@ jobs:
if: ${{ github.event.workflow_run.conclusion == 'success' }} if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps: steps:
- uses: ZedThree/clang-tidy-review/post@v0.15.1 - uses: ZedThree/clang-tidy-review/post@v0.16.0
with: with:
lgtm_comment_body: "" lgtm_comment_body: ""

View file

@ -23,6 +23,7 @@
- Minor: Added missing periods at various moderator messages and commands. (#5061) - Minor: Added missing periods at various moderator messages and commands. (#5061)
- Minor: Improved color selection and display. (#5057) - Minor: Improved color selection and display. (#5057)
- Minor: Improved Streamlink documentation in the settings dialog. (#5076) - Minor: Improved Streamlink documentation in the settings dialog. (#5076)
- Minor: Normalized the input padding between light & dark themes. (#5095)
- Bugfix: Fixed an issue where certain emojis did not send to Twitch chat correctly. (#4840) - Bugfix: Fixed an issue where certain emojis did not send to Twitch chat correctly. (#4840)
- Bugfix: Fixed capitalized channel names in log inclusion list not being logged. (#4848) - Bugfix: Fixed capitalized channel names in log inclusion list not being logged. (#4848)
- Bugfix: Trimmed custom streamlink paths on all platforms making sure you don't accidentally add spaces at the beginning or end of its path. (#4834) - Bugfix: Trimmed custom streamlink paths on all platforms making sure you don't accidentally add spaces at the beginning or end of its path. (#4834)
@ -105,6 +106,7 @@
- Dev: Move `clang-tidy` checker to its own CI job. (#4996) - Dev: Move `clang-tidy` checker to its own CI job. (#4996)
- Dev: Refactored the Image Uploader feature. (#4971) - Dev: Refactored the Image Uploader feature. (#4971)
- Dev: Refactored the SplitOverlay code. (#5082) - Dev: Refactored the SplitOverlay code. (#5082)
- Dev: Refactored the TwitchBadges structure, making it less of a singleton. (#5096)
- Dev: Moved the Network files to their own folder. (#5089) - Dev: Moved the Network files to their own folder. (#5089)
- Dev: Fixed deadlock and use-after-free in tests. (#4981) - Dev: Fixed deadlock and use-after-free in tests. (#4981)
- Dev: Moved all `.clang-format` files to the root directory. (#5037) - Dev: Moved all `.clang-format` files to the root directory. (#5037)

View file

@ -122,6 +122,12 @@ public:
return nullptr; return nullptr;
} }
TwitchBadges *getTwitchBadges() override
{
assert(false && "getTwitchBadges was called without being initialized");
return nullptr;
}
Logging *getChatLogger() override Logging *getChatLogger() override
{ {
assert(!"getChatLogger was called without being initialized"); assert(!"getChatLogger was called without being initialized");

View file

@ -12,6 +12,7 @@
#include "controllers/notifications/NotificationController.hpp" #include "controllers/notifications/NotificationController.hpp"
#include "controllers/sound/ISoundController.hpp" #include "controllers/sound/ISoundController.hpp"
#include "providers/seventv/SeventvAPI.hpp" #include "providers/seventv/SeventvAPI.hpp"
#include "providers/twitch/TwitchBadges.hpp"
#include "singletons/ImageUploader.hpp" #include "singletons/ImageUploader.hpp"
#ifdef CHATTERINO_HAVE_PLUGINS #ifdef CHATTERINO_HAVE_PLUGINS
# include "controllers/plugins/PluginController.hpp" # include "controllers/plugins/PluginController.hpp"
@ -133,6 +134,7 @@ Application::Application(Settings &_settings, const Paths &paths,
, sound(&this->emplace<ISoundController>(makeSoundController(_settings))) , sound(&this->emplace<ISoundController>(makeSoundController(_settings)))
, twitchLiveController(&this->emplace<TwitchLiveController>()) , twitchLiveController(&this->emplace<TwitchLiveController>())
, twitchPubSub(new PubSub(TWITCH_PUBSUB_URL)) , twitchPubSub(new PubSub(TWITCH_PUBSUB_URL))
, twitchBadges(new TwitchBadges)
, logging(new Logging(_settings)) , logging(new Logging(_settings))
#ifdef CHATTERINO_HAVE_PLUGINS #ifdef CHATTERINO_HAVE_PLUGINS
, plugins(&this->emplace(new PluginController(paths))) , plugins(&this->emplace(new PluginController(paths)))
@ -153,6 +155,7 @@ Application::~Application() = default;
void Application::fakeDtor() void Application::fakeDtor()
{ {
this->twitchPubSub.reset(); this->twitchPubSub.reset();
this->twitchBadges.reset();
} }
void Application::initialize(Settings &settings, const Paths &paths) void Application::initialize(Settings &settings, const Paths &paths)
@ -320,6 +323,13 @@ ITwitchLiveController *Application::getTwitchLiveController()
return this->twitchLiveController; return this->twitchLiveController;
} }
TwitchBadges *Application::getTwitchBadges()
{
assert(this->twitchBadges);
return this->twitchBadges.get();
}
ITwitchIrcServer *Application::getTwitch() ITwitchIrcServer *Application::getTwitch()
{ {
return this->twitch; return this->twitch;

View file

@ -28,6 +28,7 @@ class ISoundController;
class SoundController; class SoundController;
class ITwitchLiveController; class ITwitchLiveController;
class TwitchLiveController; class TwitchLiveController;
class TwitchBadges;
#ifdef CHATTERINO_HAVE_PLUGINS #ifdef CHATTERINO_HAVE_PLUGINS
class PluginController; class PluginController;
#endif #endif
@ -78,6 +79,7 @@ public:
virtual IUserDataController *getUserData() = 0; virtual IUserDataController *getUserData() = 0;
virtual ISoundController *getSound() = 0; virtual ISoundController *getSound() = 0;
virtual ITwitchLiveController *getTwitchLiveController() = 0; virtual ITwitchLiveController *getTwitchLiveController() = 0;
virtual TwitchBadges *getTwitchBadges() = 0;
virtual ImageUploader *getImageUploader() = 0; virtual ImageUploader *getImageUploader() = 0;
virtual SeventvAPI *getSeventvAPI() = 0; virtual SeventvAPI *getSeventvAPI() = 0;
virtual Updates &getUpdates() = 0; virtual Updates &getUpdates() = 0;
@ -141,6 +143,7 @@ public:
private: private:
TwitchLiveController *const twitchLiveController{}; TwitchLiveController *const twitchLiveController{};
std::unique_ptr<PubSub> twitchPubSub; std::unique_ptr<PubSub> twitchPubSub;
std::unique_ptr<TwitchBadges> twitchBadges;
const std::unique_ptr<Logging> logging; const std::unique_ptr<Logging> logging;
public: public:
@ -215,6 +218,7 @@ public:
IUserDataController *getUserData() override; IUserDataController *getUserData() override;
ISoundController *getSound() override; ISoundController *getSound() override;
ITwitchLiveController *getTwitchLiveController() override; ITwitchLiveController *getTwitchLiveController() override;
TwitchBadges *getTwitchBadges() override;
ImageUploader *getImageUploader() override ImageUploader *getImageUploader() override
{ {
return this->imageUploader; return this->imageUploader;

View file

@ -53,7 +53,7 @@ void BadgeHighlightModel::getRowFromItem(const HighlightBadge &item,
setFilePathItem(row[Column::SoundPath], item.getSoundUrl()); setFilePathItem(row[Column::SoundPath], item.getSoundUrl());
setColorItem(row[Column::Color], *item.getColor()); setColorItem(row[Column::Color], *item.getColor());
TwitchBadges::instance()->getBadgeIcon( getIApp()->getTwitchBadges()->getBadgeIcon(
item.badgeName(), [item, row](QString /*name*/, const QIconPtr pixmap) { item.badgeName(), [item, row](QString /*name*/, const QIconPtr pixmap) {
row[Column::Badge]->setData(QVariant(*pixmap), Qt::DecorationRole); row[Column::Badge]->setData(QVariant(*pixmap), Qt::DecorationRole);
}); });

View file

@ -16,7 +16,7 @@ struct Emote {
ImageSet images; ImageSet images;
Tooltip tooltip; Tooltip tooltip;
Url homePage; Url homePage;
bool zeroWidth; bool zeroWidth{};
EmoteId id; EmoteId id;
EmoteAuthor author; EmoteAuthor author;
/** /**

View file

@ -54,6 +54,7 @@ protected:
virtual Outcome tryAppendEmote(const EmoteName &name) virtual Outcome tryAppendEmote(const EmoteName &name)
{ {
(void)name;
return Failure; return Failure;
} }

View file

@ -23,6 +23,10 @@ public:
enum ConnectionType { Read = 1, Write = 2, Both = 3 }; enum ConnectionType { Read = 1, Write = 2, Both = 3 };
~AbstractIrcServer() override = default; ~AbstractIrcServer() override = default;
AbstractIrcServer(const AbstractIrcServer &) = delete;
AbstractIrcServer(AbstractIrcServer &&) = delete;
AbstractIrcServer &operator=(const AbstractIrcServer &) = delete;
AbstractIrcServer &operator=(AbstractIrcServer &&) = delete;
// initializeIrc must be called from the derived class // initializeIrc must be called from the derived class
// this allows us to initialize the abstract IRC server based on the derived class's parameters // this allows us to initialize the abstract IRC server based on the derived class's parameters
@ -57,7 +61,11 @@ protected:
// initializeConnectionSignals is called on a connection once in its lifetime. // initializeConnectionSignals is called on a connection once in its lifetime.
// it can be used to connect signals to your class // it can be used to connect signals to your class
virtual void initializeConnectionSignals(IrcConnection *connection, virtual void initializeConnectionSignals(IrcConnection *connection,
ConnectionType type){}; ConnectionType type)
{
(void)connection;
(void)type;
}
// initializeConnection is called every time before we try to connect to the IRC server // initializeConnection is called every time before we try to connect to the IRC server
virtual void initializeConnection(IrcConnection *connection, virtual void initializeConnection(IrcConnection *connection,

View file

@ -74,9 +74,9 @@ void PubSubClient::close(const std::string &reason,
}); });
} }
bool PubSubClient::listen(PubSubListenMessage msg) bool PubSubClient::listen(const PubSubListenMessage &msg)
{ {
int numRequestedListens = msg.topics.size(); auto numRequestedListens = msg.topics.size();
if (this->numListens_ + numRequestedListens > PubSubClient::MAX_LISTENS) if (this->numListens_ + numRequestedListens > PubSubClient::MAX_LISTENS)
{ {
@ -84,11 +84,19 @@ bool PubSubClient::listen(PubSubListenMessage msg)
return false; return false;
} }
this->numListens_ += numRequestedListens; this->numListens_ += numRequestedListens;
DebugCount::increase("PubSub topic pending listens", numRequestedListens); DebugCount::increase("PubSub topic pending listens",
static_cast<int64_t>(numRequestedListens));
for (const auto &topic : msg.topics) for (const auto &topic : msg.topics)
{ {
this->listeners_.emplace_back(Listener{topic, false, false, false}); this->listeners_.emplace_back(Listener{
TopicData{
topic,
false,
false,
},
false,
});
} }
qCDebug(chatterinoPubSub) qCDebug(chatterinoPubSub)
@ -127,7 +135,7 @@ PubSubClient::UnlistenPrefixResponse PubSubClient::unlistenPrefix(
this->numListens_ -= numRequestedUnlistens; this->numListens_ -= numRequestedUnlistens;
DebugCount::increase("PubSub topic pending unlistens", DebugCount::increase("PubSub topic pending unlistens",
numRequestedUnlistens); static_cast<int64_t>(numRequestedUnlistens));
PubSubUnlistenMessage message(topics); PubSubUnlistenMessage message(topics);
@ -192,6 +200,7 @@ void PubSubClient::ping()
runAfter(this->heartbeatTimer_, this->clientOptions_.pingInterval_, runAfter(this->heartbeatTimer_, this->clientOptions_.pingInterval_,
[self](auto timer) { [self](auto timer) {
(void)timer;
if (!self->started_) if (!self->started_)
{ {
return; return;

View file

@ -45,7 +45,7 @@ public:
websocketpp::close::status::value code = websocketpp::close::status::value code =
websocketpp::close::status::normal); websocketpp::close::status::normal);
bool listen(PubSubListenMessage msg); bool listen(const PubSubListenMessage &msg);
UnlistenPrefixResponse unlistenPrefix(const QString &prefix); UnlistenPrefixResponse unlistenPrefix(const QString &prefix);
void handleListenResponse(const PubSubMessage &message); void handleListenResponse(const PubSubMessage &message);

View file

@ -1,4 +1,4 @@
#include "TwitchBadges.hpp" #include "providers/twitch/TwitchBadges.hpp"
#include "common/network/NetworkRequest.hpp" #include "common/network/NetworkRequest.hpp"
#include "common/network/NetworkResult.hpp" #include "common/network/NetworkResult.hpp"
@ -45,14 +45,15 @@ void TwitchBadges::loadTwitchBadges()
for (const auto &version : badgeSet.versions) for (const auto &version : badgeSet.versions)
{ {
const auto &emote = Emote{ const auto &emote = Emote{
EmoteName{}, .name = EmoteName{},
ImageSet{ .images =
Image::fromUrl(version.imageURL1x, 1), ImageSet{
Image::fromUrl(version.imageURL2x, .5), Image::fromUrl(version.imageURL1x, 1),
Image::fromUrl(version.imageURL4x, .25), Image::fromUrl(version.imageURL2x, .5),
}, Image::fromUrl(version.imageURL4x, .25),
Tooltip{version.title}, },
version.clickURL, .tooltip = Tooltip{version.title},
.homePage = version.clickURL,
}; };
(*badgeSets)[setID][version.id] = (*badgeSets)[setID][version.id] =
std::make_shared<Emote>(emote); std::make_shared<Emote>(emote);
@ -112,20 +113,19 @@ void TwitchBadges::parseTwitchBadges(QJsonObject root)
auto versionObj = vIt.value().toObject(); auto versionObj = vIt.value().toObject();
auto emote = Emote{ auto emote = Emote{
{""}, .name = {""},
ImageSet{ .images =
Image::fromUrl( ImageSet{
{versionObj.value("image_url_1x").toString()}, 1), Image::fromUrl(
Image::fromUrl( {versionObj.value("image_url_1x").toString()}, 1),
{versionObj.value("image_url_2x").toString()}, .5), Image::fromUrl(
Image::fromUrl( {versionObj.value("image_url_2x").toString()}, .5),
{versionObj.value("image_url_4x").toString()}, .25), Image::fromUrl(
}, {versionObj.value("image_url_4x").toString()}, .25),
Tooltip{versionObj.value("title").toString()}, },
Url{versionObj.value("click_url").toString()}, .tooltip = Tooltip{versionObj.value("title").toString()},
.homePage = Url{versionObj.value("click_url").toString()},
}; };
// "title"
// "clickAction"
(*badgeSets)[key][vIt.key()] = std::make_shared<Emote>(emote); (*badgeSets)[key][vIt.key()] = std::make_shared<Emote>(emote);
} }
@ -176,9 +176,10 @@ std::optional<EmotePtr> TwitchBadges::badge(const QString &set) const
auto it = badgeSets->find(set); auto it = badgeSets->find(set);
if (it != badgeSets->end()) if (it != badgeSets->end())
{ {
if (it->second.size() > 0) const auto &badges = it->second;
if (!badges.empty())
{ {
return it->second.begin()->second; return badges.begin()->second;
} }
} }
return std::nullopt; return std::nullopt;
@ -193,7 +194,7 @@ void TwitchBadges::getBadgeIcon(const QString &name, BadgeIconCallback callback)
{ {
// Badges have not been loaded yet, store callback in a queue // Badges have not been loaded yet, store callback in a queue
std::unique_lock queueLock(this->queueMutex_); std::unique_lock queueLock(this->queueMutex_);
this->callbackQueue_.push({name, std::move(callback)}); this->callbackQueue_.emplace(name, std::move(callback));
return; return;
} }
} }
@ -279,16 +280,4 @@ void TwitchBadges::loadEmoteImage(const QString &name, ImagePtr image,
.execute(); .execute();
} }
TwitchBadges *TwitchBadges::instance_;
TwitchBadges *TwitchBadges::instance()
{
if (TwitchBadges::instance_ == nullptr)
{
TwitchBadges::instance_ = new TwitchBadges();
}
return TwitchBadges::instance_;
}
} // namespace chatterino } // namespace chatterino

View file

@ -32,7 +32,7 @@ class TwitchBadges
using BadgeIconCallback = std::function<void(QString, const QIconPtr)>; using BadgeIconCallback = std::function<void(QString, const QIconPtr)>;
public: public:
static TwitchBadges *instance(); TwitchBadges();
// Get badge from name and version // Get badge from name and version
std::optional<EmotePtr> badge(const QString &set, std::optional<EmotePtr> badge(const QString &set,
@ -46,9 +46,6 @@ public:
BadgeIconCallback callback); BadgeIconCallback callback);
private: private:
static TwitchBadges *instance_;
TwitchBadges();
void loadTwitchBadges(); void loadTwitchBadges();
void parseTwitchBadges(QJsonObject root); void parseTwitchBadges(QJsonObject root);
void loaded(); void loaded();

View file

@ -83,7 +83,6 @@ TwitchChannel::TwitchChannel(const QString &name)
, bttvEmotes_(std::make_shared<EmoteMap>()) , bttvEmotes_(std::make_shared<EmoteMap>())
, ffzEmotes_(std::make_shared<EmoteMap>()) , ffzEmotes_(std::make_shared<EmoteMap>())
, seventvEmotes_(std::make_shared<EmoteMap>()) , seventvEmotes_(std::make_shared<EmoteMap>())
, mod_(false)
{ {
qCDebug(chatterinoTwitch) << "[TwitchChannel" << name << "] Opened"; qCDebug(chatterinoTwitch) << "[TwitchChannel" << name << "] Opened";
@ -322,13 +321,15 @@ void TwitchChannel::refreshFFZChannelEmotes(bool manualRefresh)
[this, weak = weakOf<Channel>(this)](auto &&modBadge) { [this, weak = weakOf<Channel>(this)](auto &&modBadge) {
if (auto shared = weak.lock()) if (auto shared = weak.lock())
{ {
this->ffzCustomModBadge_.set(std::move(modBadge)); this->ffzCustomModBadge_.set(
std::forward<decltype(modBadge)>(modBadge));
} }
}, },
[this, weak = weakOf<Channel>(this)](auto &&vipBadge) { [this, weak = weakOf<Channel>(this)](auto &&vipBadge) {
if (auto shared = weak.lock()) if (auto shared = weak.lock())
{ {
this->ffzCustomVipBadge_.set(std::move(vipBadge)); this->ffzCustomVipBadge_.set(
std::forward<decltype(vipBadge)>(vipBadge));
} }
}, },
manualRefresh); manualRefresh);
@ -778,12 +779,12 @@ void TwitchChannel::setRoomId(const QString &id)
SharedAccessGuard<const TwitchChannel::RoomModes> SharedAccessGuard<const TwitchChannel::RoomModes>
TwitchChannel::accessRoomModes() const TwitchChannel::accessRoomModes() const
{ {
return this->roomModes_.accessConst(); return this->roomModes.accessConst();
} }
void TwitchChannel::setRoomModes(const RoomModes &_roomModes) void TwitchChannel::setRoomModes(const RoomModes &newRoomModes)
{ {
this->roomModes_ = _roomModes; this->roomModes = newRoomModes;
this->roomModesChanged.invoke(); this->roomModesChanged.invoke();
} }
@ -1133,7 +1134,7 @@ const QString &TwitchChannel::popoutPlayerUrl()
return this->popoutPlayerUrl_; return this->popoutPlayerUrl_;
} }
int TwitchChannel::chatterCount() int TwitchChannel::chatterCount() const
{ {
return this->chatterCount_; return this->chatterCount_;
} }
@ -1201,7 +1202,7 @@ void TwitchChannel::loadRecentMessages()
tc->loadingRecentMessages_.clear(); tc->loadingRecentMessages_.clear();
std::vector<MessagePtr> msgs; std::vector<MessagePtr> msgs;
for (MessagePtr msg : messages) for (const auto &msg : messages)
{ {
const auto highlighted = const auto highlighted =
msg->flags.has(MessageFlag::Highlighted); msg->flags.has(MessageFlag::Highlighted);
@ -1351,7 +1352,10 @@ void TwitchChannel::refreshChatters()
} }
}, },
// Refresh chatters should only be used when failing silently is an option // Refresh chatters should only be used when failing silently is an option
[](auto error, auto message) {}); [](auto error, auto message) {
(void)error;
(void)message;
});
} }
void TwitchChannel::addReplyThread(const std::shared_ptr<MessageThread> &thread) void TwitchChannel::addReplyThread(const std::shared_ptr<MessageThread> &thread)
@ -1429,14 +1433,15 @@ void TwitchChannel::refreshBadges()
for (const auto &version : badgeSet.versions) for (const auto &version : badgeSet.versions)
{ {
auto emote = Emote{ auto emote = Emote{
EmoteName{}, .name = EmoteName{},
ImageSet{ .images =
Image::fromUrl(version.imageURL1x, 1), ImageSet{
Image::fromUrl(version.imageURL2x, .5), Image::fromUrl(version.imageURL1x, 1),
Image::fromUrl(version.imageURL4x, .25), Image::fromUrl(version.imageURL2x, .5),
}, Image::fromUrl(version.imageURL4x, .25),
Tooltip{version.title}, },
version.clickURL, .tooltip = Tooltip{version.title},
.homePage = version.clickURL,
}; };
(*badgeSets)[setID][version.id] = (*badgeSets)[setID][version.id] =
std::make_shared<Emote>(emote); std::make_shared<Emote>(emote);
@ -1508,22 +1513,28 @@ void TwitchChannel::refreshCheerEmotes()
// Combine the prefix (e.g. BibleThump) with the tier (1, 100 etc.) // Combine the prefix (e.g. BibleThump) with the tier (1, 100 etc.)
auto emoteTooltip = auto emoteTooltip =
set.prefix + tier.id + "<br>Twitch Cheer Emote"; set.prefix + tier.id + "<br>Twitch Cheer Emote";
cheerEmote.animatedEmote = std::make_shared<Emote>( cheerEmote.animatedEmote = std::make_shared<Emote>(Emote{
Emote{EmoteName{"cheer emote"}, .name = EmoteName{"cheer emote"},
ImageSet{ .images =
tier.darkAnimated.imageURL1x, ImageSet{
tier.darkAnimated.imageURL2x, tier.darkAnimated.imageURL1x,
tier.darkAnimated.imageURL4x, tier.darkAnimated.imageURL2x,
}, tier.darkAnimated.imageURL4x,
Tooltip{emoteTooltip}, Url{}}); },
cheerEmote.staticEmote = std::make_shared<Emote>( .tooltip = Tooltip{emoteTooltip},
Emote{EmoteName{"cheer emote"}, .homePage = Url{},
ImageSet{ });
tier.darkStatic.imageURL1x, cheerEmote.staticEmote = std::make_shared<Emote>(Emote{
tier.darkStatic.imageURL2x, .name = EmoteName{"cheer emote"},
tier.darkStatic.imageURL4x, .images =
}, ImageSet{
Tooltip{emoteTooltip}, Url{}}); tier.darkStatic.imageURL1x,
tier.darkStatic.imageURL2x,
tier.darkStatic.imageURL4x,
},
.tooltip = Tooltip{emoteTooltip},
.homePage = Url{},
});
cheerEmoteSet.cheerEmotes.emplace_back( cheerEmoteSet.cheerEmotes.emplace_back(
std::move(cheerEmote)); std::move(cheerEmote));
@ -1760,7 +1771,7 @@ void TwitchChannel::updateSevenTVActivity()
}); });
} }
void TwitchChannel::listenSevenTVCosmetics() void TwitchChannel::listenSevenTVCosmetics() const
{ {
if (getApp()->twitch->seventvEventAPI) if (getApp()->twitch->seventvEventAPI)
{ {

View file

@ -110,6 +110,11 @@ public:
explicit TwitchChannel(const QString &channelName); explicit TwitchChannel(const QString &channelName);
~TwitchChannel() override; ~TwitchChannel() override;
TwitchChannel(const TwitchChannel &) = delete;
TwitchChannel(TwitchChannel &&) = delete;
TwitchChannel &operator=(const TwitchChannel &) = delete;
TwitchChannel &operator=(TwitchChannel &&) = delete;
void initialize(); void initialize();
// Channel methods // Channel methods
@ -130,7 +135,7 @@ public:
const QString &subscriptionUrl(); const QString &subscriptionUrl();
const QString &channelUrl(); const QString &channelUrl();
const QString &popoutPlayerUrl(); const QString &popoutPlayerUrl();
int chatterCount(); int chatterCount() const;
bool isLive() const override; bool isLive() const override;
QString roomId() const; QString roomId() const;
SharedAccessGuard<const RoomModes> accessRoomModes() const; SharedAccessGuard<const RoomModes> accessRoomModes() const;
@ -300,7 +305,7 @@ private:
* This is done at most once every 60s. * This is done at most once every 60s.
*/ */
void updateSevenTVActivity(); void updateSevenTVActivity();
void listenSevenTVCosmetics(); void listenSevenTVCosmetics() const;
/** /**
* @brief Sets the live status of this Twitch channel * @brief Sets the live status of this Twitch channel
@ -312,7 +317,7 @@ private:
void setVIP(bool value); void setVIP(bool value);
void setStaff(bool value); void setStaff(bool value);
void setRoomId(const QString &id); void setRoomId(const QString &id);
void setRoomModes(const RoomModes &roomModes_); void setRoomModes(const RoomModes &newRoomModes);
void setDisplayName(const QString &name); void setDisplayName(const QString &name);
void setLocalizedName(const QString &name); void setLocalizedName(const QString &name);
@ -371,7 +376,7 @@ private:
const QString popoutPlayerUrl_; const QString popoutPlayerUrl_;
int chatterCount_{}; int chatterCount_{};
UniqueAccess<StreamStatus> streamStatus_; UniqueAccess<StreamStatus> streamStatus_;
UniqueAccess<RoomModes> roomModes_; UniqueAccess<RoomModes> roomModes;
bool disconnected_{}; bool disconnected_{};
std::optional<std::chrono::time_point<std::chrono::system_clock>> std::optional<std::chrono::time_point<std::chrono::system_clock>>
lastConnectedAt_{}; lastConnectedAt_{};

View file

@ -163,7 +163,7 @@ namespace {
} }
if (auto globalBadge = if (auto globalBadge =
TwitchBadges::instance()->badge(badge.key_, badge.value_)) getIApp()->getTwitchBadges()->badge(badge.key_, badge.value_))
{ {
return globalBadge; return globalBadge;
} }

View file

@ -1,5 +1,6 @@
#include "BadgePickerDialog.hpp" #include "BadgePickerDialog.hpp"
#include "Application.hpp"
#include "providers/twitch/TwitchBadges.hpp" #include "providers/twitch/TwitchBadges.hpp"
#include "singletons/Resources.hpp" #include "singletons/Resources.hpp"
@ -57,7 +58,7 @@ BadgePickerDialog::BadgePickerDialog(QList<DisplayBadge> badges,
updateBadge(0); updateBadge(0);
// Set icons. // Set icons.
TwitchBadges::instance()->getBadgeIcons( getIApp()->getTwitchBadges()->getBadgeIcons(
badges, badges,
[&dropdown = this->dropdown_](QString identifier, const QIconPtr icon) { [&dropdown = this->dropdown_](QString identifier, const QIconPtr icon) {
if (!dropdown) if (!dropdown)

View file

@ -78,6 +78,7 @@ class ComboBox : public QComboBox
void wheelEvent(QWheelEvent *event) override void wheelEvent(QWheelEvent *event) override
{ {
(void)event;
} }
}; };
@ -311,8 +312,9 @@ public:
bool filterElements(const QString &query); bool filterElements(const QString &query);
protected: protected:
void resizeEvent(QResizeEvent *ev) override void resizeEvent(QResizeEvent *event) override
{ {
(void)event;
} }
private: private:

View file

@ -959,17 +959,23 @@ void Split::paintEvent(QPaintEvent *)
void Split::mouseMoveEvent(QMouseEvent *event) void Split::mouseMoveEvent(QMouseEvent *event)
{ {
(void)event;
this->handleModifiers(QGuiApplication::queryKeyboardModifiers()); this->handleModifiers(QGuiApplication::queryKeyboardModifiers());
} }
void Split::keyPressEvent(QKeyEvent *event) void Split::keyPressEvent(QKeyEvent *event)
{ {
(void)event;
this->view_->unsetCursor(); this->view_->unsetCursor();
this->handleModifiers(QGuiApplication::queryKeyboardModifiers()); this->handleModifiers(QGuiApplication::queryKeyboardModifiers());
} }
void Split::keyReleaseEvent(QKeyEvent *event) void Split::keyReleaseEvent(QKeyEvent *event)
{ {
(void)event;
this->view_->unsetCursor(); this->view_->unsetCursor();
this->handleModifiers(QGuiApplication::queryKeyboardModifiers()); this->handleModifiers(QGuiApplication::queryKeyboardModifiers());
} }
@ -1005,6 +1011,8 @@ void Split::enterEvent(QEvent * /*event*/)
void Split::leaveEvent(QEvent *event) void Split::leaveEvent(QEvent *event)
{ {
(void)event;
this->isMouseOver_ = false; this->isMouseOver_ = false;
this->overlay_->hide(); this->overlay_->hide();

View file

@ -213,16 +213,17 @@ void SplitInput::scaleChangedEvent(float scale)
this->setMaximumHeight(this->scaledMaxHeight()); this->setMaximumHeight(this->scaledMaxHeight());
} }
this->ui_.textEdit->setFont( this->ui_.textEdit->setFont(
app->fonts->getFont(FontStyle::ChatMedium, this->scale())); app->fonts->getFont(FontStyle::ChatMedium, scale));
this->ui_.textEditLength->setFont( this->ui_.textEditLength->setFont(
app->fonts->getFont(FontStyle::ChatMedium, this->scale())); app->fonts->getFont(FontStyle::ChatMedium, scale));
this->ui_.replyLabel->setFont( this->ui_.replyLabel->setFont(
app->fonts->getFont(FontStyle::ChatMediumBold, this->scale())); app->fonts->getFont(FontStyle::ChatMediumBold, scale));
} }
void SplitInput::themeChangedEvent() void SplitInput::themeChangedEvent()
{ {
QPalette palette, placeholderPalette; QPalette palette;
QPalette placeholderPalette;
palette.setColor(QPalette::WindowText, this->theme->splits.input.text); palette.setColor(QPalette::WindowText, this->theme->splits.input.text);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
@ -239,7 +240,7 @@ void SplitInput::themeChangedEvent()
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
this->ui_.textEdit->setPalette(placeholderPalette); this->ui_.textEdit->setPalette(placeholderPalette);
#endif #endif
auto marginPx = (this->theme->isLightTheme() ? 4 : 2) * this->scale(); auto marginPx = static_cast<int>(2.F * this->scale());
this->ui_.vbox->setContentsMargins(marginPx, marginPx, marginPx, marginPx); this->ui_.vbox->setContentsMargins(marginPx, marginPx, marginPx, marginPx);
this->ui_.emoteButton->getLabel().setStyleSheet("color: #000"); this->ui_.emoteButton->getLabel().setStyleSheet("color: #000");
@ -256,15 +257,12 @@ void SplitInput::themeChangedEvent()
void SplitInput::updateEmoteButton() void SplitInput::updateEmoteButton()
{ {
float scale = this->scale(); auto scale = this->scale();
QString text = "<img src=':/buttons/emote.svg' width='xD' height='xD' />"; auto text =
text.replace("xD", QString::number(int(12 * scale))); QStringLiteral("<img src=':/buttons/%1.svg' width='%2' height='%2' />")
.arg(this->theme->isLightTheme() ? "emoteDark" : "emote")
if (this->theme->isLightTheme()) .arg(int(12 * scale));
{
text.replace("emote", "emoteDark");
}
this->ui_.emoteButton->getLabel().setText(text); this->ui_.emoteButton->getLabel().setText(text);
this->ui_.emoteButton->setFixedHeight(int(18 * scale)); this->ui_.emoteButton->setFixedHeight(int(18 * scale));
@ -274,15 +272,10 @@ void SplitInput::updateCancelReplyButton()
{ {
float scale = this->scale(); float scale = this->scale();
QString text = auto text =
QStringLiteral( QStringLiteral("<img src=':/buttons/%1.svg' width='%2' height='%2' />")
"<img src=':/buttons/cancel.svg' width='%1' height='%1' />") .arg(this->theme->isLightTheme() ? "cancelDark" : "cancel")
.arg(QString::number(int(12 * scale))); .arg(int(12 * scale));
if (this->theme->isLightTheme())
{
text.replace("cancel", "cancelDark");
}
this->ui_.cancelReplyButton->getLabel().setText(text); this->ui_.cancelReplyButton->getLabel().setText(text);
this->ui_.cancelReplyButton->setFixedHeight(int(12 * scale)); this->ui_.cancelReplyButton->setFixedHeight(int(12 * scale));
@ -324,7 +317,7 @@ void SplitInput::openEmotePopup()
this->emotePopup_->activateWindow(); this->emotePopup_->activateWindow();
} }
QString SplitInput::handleSendMessage(std::vector<QString> &arguments) QString SplitInput::handleSendMessage(const std::vector<QString> &arguments)
{ {
auto c = this->split_->getChannel(); auto c = this->split_->getChannel();
if (c == nullptr) if (c == nullptr)
@ -346,40 +339,37 @@ QString SplitInput::handleSendMessage(std::vector<QString> &arguments)
this->postMessageSend(message, arguments); this->postMessageSend(message, arguments);
return ""; return "";
} }
else
// Reply to message
auto *tc = dynamic_cast<TwitchChannel *>(c.get());
if (!tc)
{ {
// Reply to message // this should not fail
auto *tc = dynamic_cast<TwitchChannel *>(c.get());
if (!tc)
{
// this should not fail
return "";
}
QString message = this->ui_.textEdit->toPlainText();
if (this->enableInlineReplying_)
{
// Remove @username prefix that is inserted when doing inline replies
message.remove(0, this->replyThread_->displayName.length() +
1); // remove "@username"
if (!message.isEmpty() && message.at(0) == ' ')
{
message.remove(0, 1); // remove possible space
}
}
message = message.replace('\n', ' ');
QString sendMessage =
getApp()->commands->execCommand(message, c, false);
// Reply within TwitchChannel
tc->sendReply(sendMessage, this->replyThread_->id);
this->postMessageSend(message, arguments);
return ""; return "";
} }
QString message = this->ui_.textEdit->toPlainText();
if (this->enableInlineReplying_)
{
// Remove @username prefix that is inserted when doing inline replies
message.remove(0, this->replyThread_->displayName.length() +
1); // remove "@username"
if (!message.isEmpty() && message.at(0) == ' ')
{
message.remove(0, 1); // remove possible space
}
}
message = message.replace('\n', ' ');
QString sendMessage = getApp()->commands->execCommand(message, c, false);
// Reply within TwitchChannel
tc->sendReply(sendMessage, this->replyThread_->id);
this->postMessageSend(message, arguments);
return "";
} }
void SplitInput::postMessageSend(const QString &message, void SplitInput::postMessageSend(const QString &message,
@ -408,7 +398,7 @@ void SplitInput::addShortcuts()
{ {
HotkeyController::HotkeyMap actions{ HotkeyController::HotkeyMap actions{
{"cursorToStart", {"cursorToStart",
[this](std::vector<QString> arguments) -> QString { [this](const std::vector<QString> &arguments) -> QString {
if (arguments.size() != 1) if (arguments.size() != 1)
{ {
qCWarning(chatterinoHotkeys) qCWarning(chatterinoHotkeys)
@ -419,8 +409,8 @@ void SplitInput::addShortcuts()
} }
QTextCursor cursor = this->ui_.textEdit->textCursor(); QTextCursor cursor = this->ui_.textEdit->textCursor();
auto place = QTextCursor::Start; auto place = QTextCursor::Start;
auto stringTakeSelection = arguments.at(0); const auto &stringTakeSelection = arguments.at(0);
bool select; bool select{};
if (stringTakeSelection == "withSelection") if (stringTakeSelection == "withSelection")
{ {
select = true; select = true;
@ -443,7 +433,7 @@ void SplitInput::addShortcuts()
return ""; return "";
}}, }},
{"cursorToEnd", {"cursorToEnd",
[this](std::vector<QString> arguments) -> QString { [this](const std::vector<QString> &arguments) -> QString {
if (arguments.size() != 1) if (arguments.size() != 1)
{ {
qCWarning(chatterinoHotkeys) qCWarning(chatterinoHotkeys)
@ -454,8 +444,8 @@ void SplitInput::addShortcuts()
} }
QTextCursor cursor = this->ui_.textEdit->textCursor(); QTextCursor cursor = this->ui_.textEdit->textCursor();
auto place = QTextCursor::End; auto place = QTextCursor::End;
auto stringTakeSelection = arguments.at(0); const auto &stringTakeSelection = arguments.at(0);
bool select; bool select{};
if (stringTakeSelection == "withSelection") if (stringTakeSelection == "withSelection")
{ {
select = true; select = true;
@ -478,36 +468,45 @@ void SplitInput::addShortcuts()
return ""; return "";
}}, }},
{"openEmotesPopup", {"openEmotesPopup",
[this](std::vector<QString>) -> QString { [this](const std::vector<QString> &arguments) -> QString {
(void)arguments;
this->openEmotePopup(); this->openEmotePopup();
return ""; return "";
}}, }},
{"sendMessage", {"sendMessage",
[this](std::vector<QString> arguments) -> QString { [this](const std::vector<QString> &arguments) -> QString {
return this->handleSendMessage(arguments); return this->handleSendMessage(arguments);
}}, }},
{"previousMessage", {"previousMessage",
[this](std::vector<QString>) -> QString { [this](const std::vector<QString> &arguments) -> QString {
if (this->prevMsg_.size() && this->prevIndex_) (void)arguments;
if (this->prevMsg_.isEmpty() || this->prevIndex_ == 0)
{ {
if (this->prevIndex_ == (this->prevMsg_.size())) return "";
{
this->currMsg_ = ui_.textEdit->toPlainText();
}
this->prevIndex_--;
this->ui_.textEdit->setPlainText(
this->prevMsg_.at(this->prevIndex_));
this->ui_.textEdit->resetCompletion();
QTextCursor cursor = this->ui_.textEdit->textCursor();
cursor.movePosition(QTextCursor::End);
this->ui_.textEdit->setTextCursor(cursor);
} }
if (this->prevIndex_ == (this->prevMsg_.size()))
{
this->currMsg_ = ui_.textEdit->toPlainText();
}
this->prevIndex_--;
this->ui_.textEdit->setPlainText(
this->prevMsg_.at(this->prevIndex_));
this->ui_.textEdit->resetCompletion();
QTextCursor cursor = this->ui_.textEdit->textCursor();
cursor.movePosition(QTextCursor::End);
this->ui_.textEdit->setTextCursor(cursor);
return ""; return "";
}}, }},
{"nextMessage", {"nextMessage",
[this](std::vector<QString>) -> QString { [this](const std::vector<QString> &arguments) -> QString {
(void)arguments;
// If user did not write anything before then just do nothing. // If user did not write anything before then just do nothing.
if (this->prevMsg_.isEmpty()) if (this->prevMsg_.isEmpty())
{ {
@ -556,19 +555,23 @@ void SplitInput::addShortcuts()
return ""; return "";
}}, }},
{"undo", {"undo",
[this](std::vector<QString>) -> QString { [this](const std::vector<QString> &arguments) -> QString {
(void)arguments;
this->ui_.textEdit->undo(); this->ui_.textEdit->undo();
return ""; return "";
}}, }},
{"redo", {"redo",
[this](std::vector<QString>) -> QString { [this](const std::vector<QString> &arguments) -> QString {
(void)arguments;
this->ui_.textEdit->redo(); this->ui_.textEdit->redo();
return ""; return "";
}}, }},
{"copy", {"copy",
[this](std::vector<QString> arguments) -> QString { [this](const std::vector<QString> &arguments) -> QString {
// XXX: this action is unused at the moment, a qt standard shortcut is used instead // XXX: this action is unused at the moment, a qt standard shortcut is used instead
if (arguments.size() == 0) if (arguments.empty())
{ {
return "copy action takes only one argument: the source " return "copy action takes only one argument: the source "
"of the copy \"split\", \"input\" or " "of the copy \"split\", \"input\" or "
@ -578,8 +581,9 @@ void SplitInput::addShortcuts()
"copied. Automatic will pick whichever has a " "copied. Automatic will pick whichever has a "
"selection"; "selection";
} }
bool copyFromSplit = false; bool copyFromSplit = false;
auto mode = arguments.at(0); const auto &mode = arguments.at(0);
if (mode == "split") if (mode == "split")
{ {
copyFromSplit = true; copyFromSplit = true;
@ -605,22 +609,30 @@ void SplitInput::addShortcuts()
return ""; return "";
}}, }},
{"paste", {"paste",
[this](std::vector<QString>) -> QString { [this](const std::vector<QString> &arguments) -> QString {
(void)arguments;
this->ui_.textEdit->paste(); this->ui_.textEdit->paste();
return ""; return "";
}}, }},
{"clear", {"clear",
[this](std::vector<QString>) -> QString { [this](const std::vector<QString> &arguments) -> QString {
(void)arguments;
this->clearInput(); this->clearInput();
return ""; return "";
}}, }},
{"selectAll", {"selectAll",
[this](std::vector<QString>) -> QString { [this](const std::vector<QString> &arguments) -> QString {
(void)arguments;
this->ui_.textEdit->selectAll(); this->ui_.textEdit->selectAll();
return ""; return "";
}}, }},
{"selectWord", {"selectWord",
[this](std::vector<QString>) -> QString { [this](const std::vector<QString> &arguments) -> QString {
(void)arguments;
auto cursor = this->ui_.textEdit->textCursor(); auto cursor = this->ui_.textEdit->textCursor();
cursor.select(QTextCursor::WordUnderCursor); cursor.select(QTextCursor::WordUnderCursor);
this->ui_.textEdit->setTextCursor(cursor); this->ui_.textEdit->setTextCursor(cursor);
@ -1018,7 +1030,7 @@ void SplitInput::paintEvent(QPaintEvent * /*event*/)
{ {
QPainter painter(this); QPainter painter(this);
int s; int s{};
QColor borderColor; QColor borderColor;
if (this->theme->isLightTheme()) if (this->theme->isLightTheme())
@ -1052,8 +1064,10 @@ void SplitInput::paintEvent(QPaintEvent * /*event*/)
} }
} }
void SplitInput::resizeEvent(QResizeEvent *) void SplitInput::resizeEvent(QResizeEvent *event)
{ {
(void)event;
if (this->height() == this->maximumHeight()) if (this->height() == this->maximumHeight())
{ {
this->ui_.textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); this->ui_.textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);

View file

@ -80,14 +80,13 @@ protected:
virtual void giveFocus(Qt::FocusReason reason); virtual void giveFocus(Qt::FocusReason reason);
QString handleSendMessage(std::vector<QString> &arguments); QString handleSendMessage(const std::vector<QString> &arguments);
void postMessageSend(const QString &message, void postMessageSend(const QString &message,
const std::vector<QString> &arguments); const std::vector<QString> &arguments);
/// Clears the input box, clears reply thread if inline replies are enabled /// Clears the input box, clears reply thread if inline replies are enabled
void clearInput(); void clearInput();
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;