From 5fc170ba4bed195bba7832fe2bd75b99826d7794 Mon Sep 17 00:00:00 2001 From: pajlada Date: Sat, 31 Dec 2022 12:56:47 +0100 Subject: [PATCH] Refactor SplitHeader class (#4276) * Flatten static functions in anonymous namespace * SplitHeader ctor: Rename param * Header: Remove unnecessary `virtual`s * auto ptr where possible * Add curly braces * Comment twitch room modes * Treat roomModes->slowMode as an integer * Remove unused `this` from lambdas * Add `unsigned int` overload for localizeNumbers * Move thumbnail max age to a constexpr static & set explicit types * Explicitly use `QObject::connect` * Use `empty()` instead of `size()` * Name unused parameters * Move moderation action refreshing logic from SplitHeader to Split --- src/providers/twitch/TwitchChannel.hpp | 14 + src/util/Helpers.cpp | 6 + src/util/Helpers.hpp | 1 + src/widgets/splits/Split.cpp | 15 +- src/widgets/splits/Split.hpp | 8 + src/widgets/splits/SplitHeader.cpp | 370 +++++++++++++------------ src/widgets/splits/SplitHeader.hpp | 20 +- 7 files changed, 248 insertions(+), 186 deletions(-) diff --git a/src/providers/twitch/TwitchChannel.hpp b/src/providers/twitch/TwitchChannel.hpp index f1e4c0a8f..79226f077 100644 --- a/src/providers/twitch/TwitchChannel.hpp +++ b/src/providers/twitch/TwitchChannel.hpp @@ -75,7 +75,21 @@ public: bool submode = false; bool r9k = false; bool emoteOnly = false; + + /** + * @brief Number of minutes required for users to be followed before typing in chat + * + * Special cases: + * -1 = follower mode off + * 0 = follower mode on, no time requirement + **/ int followerOnly = -1; + + /** + * @brief Number of seconds required to wait before typing emotes + * + * 0 = slow mode off + **/ int slowMode = 0; }; diff --git a/src/util/Helpers.cpp b/src/util/Helpers.cpp index b145d3adb..a913d4fc0 100644 --- a/src/util/Helpers.cpp +++ b/src/util/Helpers.cpp @@ -168,6 +168,12 @@ QString localizeNumbers(const int &number) return locale.toString(number); } +QString localizeNumbers(unsigned int number) +{ + QLocale locale; + return locale.toString(number); +} + QString kFormatNumbers(const int &number) { return QString("%1K").arg(number / 1000); diff --git a/src/util/Helpers.hpp b/src/util/Helpers.hpp index 65d874423..df2dd4d92 100644 --- a/src/util/Helpers.hpp +++ b/src/util/Helpers.hpp @@ -72,6 +72,7 @@ QString formatRichNamedLink(const QString &url, const QString &name, QString shortenString(const QString &str, unsigned maxWidth = 50); QString localizeNumbers(const int &number); +QString localizeNumbers(unsigned int number); QString kFormatNumbers(const int &number); diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index 1261e13f7..b4454ba30 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -198,6 +198,12 @@ Split::Split(QWidget *parent) this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + // update moderation button when items changed + this->signalHolder_.managedConnect( + getSettings()->moderationActions.delayedItemsChanged, [this] { + this->refreshModerationMode(); + }); + this->signalHolder_.managedConnect( modifierStatusChanged, [this](Qt::KeyboardModifiers status) { if ((status == @@ -632,6 +638,12 @@ void Split::joinChannelInNewTab(ChannelPtr channel) container->insertSplit(split); } +void Split::refreshModerationMode() +{ + this->header_->updateModerationModeIcon(); + this->view_->queueLayout(); +} + void Split::openChannelInBrowserPlayer(ChannelPtr channel) { if (auto twitchChannel = dynamic_cast(channel.get())) @@ -723,8 +735,7 @@ void Split::setChannel(IndirectChannel newChannel) void Split::setModerationMode(bool value) { this->moderationMode_ = value; - this->header_->updateModerationModeIcon(); - this->view_->queueLayout(); + this->refreshModerationMode(); } bool Split::getModerationMode() const diff --git a/src/widgets/splits/Split.hpp b/src/widgets/splits/Split.hpp index 9782f5269..515432343 100644 --- a/src/widgets/splits/Split.hpp +++ b/src/widgets/splits/Split.hpp @@ -130,6 +130,14 @@ private: */ void joinChannelInNewTab(ChannelPtr channel); + /** + * @brief Refresh moderation mode layouts/buttons + * + * Should be called after after the moderation mode is changed or + * moderation actions have been changed + **/ + void refreshModerationMode(); + IndirectChannel channel_; bool moderationMode_{}; diff --git a/src/widgets/splits/SplitHeader.cpp b/src/widgets/splits/SplitHeader.cpp index b8f632188..c80ec4414 100644 --- a/src/widgets/splits/SplitHeader.cpp +++ b/src/widgets/splits/SplitHeader.cpp @@ -40,157 +40,192 @@ # include "widgets/StreamView.hpp" #endif -namespace chatterino { namespace { - auto formatRoomMode(TwitchChannel &channel) -> QString + +using namespace chatterino; + +// 5 minutes +constexpr const uint64_t THUMBNAIL_MAX_AGE_MS = 5ULL * 60 * 1000; + +auto formatRoomMode(TwitchChannel &channel) -> QString +{ + QString text; + { - QString text; + auto modes = channel.accessRoomModes(); + if (modes->r9k) { - auto modes = channel.accessRoomModes(); - - if (modes->r9k) - text += "r9k, "; - if (modes->slowMode) - text += - QString("slow(%1), ").arg(localizeNumbers(modes->slowMode)); - if (modes->emoteOnly) - text += "emote, "; - if (modes->submode) - text += "sub, "; - if (modes->followerOnly != -1) + text += "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) { - if (modes->followerOnly != 0) - { - text += QString("follow(%1m), ") - .arg(localizeNumbers(modes->followerOnly)); - } - else - { - text += QString("follow, "); - } + text += QString("follow(%1m), ") + .arg(localizeNumbers(modes->followerOnly)); + } + else + { + text += QString("follow, "); } } + } - if (text.length() > 2) + if (text.length() > 2) + { + text = text.mid(0, text.size() - 2); + } + + if (!text.isEmpty()) + { + static QRegularExpression commaReplacement("^(.+?, .+?,) (.+)$"); + + auto match = commaReplacement.match(text); + if (match.hasMatch()) { - text = text.mid(0, text.size() - 2); + text = match.captured(1) + '\n' + match.captured(2); + } + } + + if (text.isEmpty() && channel.hasModRights()) + { + return "none"; + } + + return text; +} + +auto formatTooltip(const TwitchChannel::StreamStatus &s, QString thumbnail) +{ + auto title = [&s]() -> QString { + if (s.title.isEmpty()) + { + return QStringLiteral(""); } - if (!text.isEmpty()) - { - static QRegularExpression commaReplacement("^(.+?, .+?,) (.+)$"); + return s.title.toHtmlEscaped() + "

"; + }(); - auto match = commaReplacement.match(text); - if (match.hasMatch()) - text = match.captured(1) + '\n' + match.captured(2); + auto tooltip = [&thumbnail]() -> QString { + if (getSettings()->thumbnailSizeStream.getValue() == 0) + { + return QStringLiteral(""); } - if (text.isEmpty() && channel.hasModRights()) - return "none"; - - return text; - } - auto formatTooltip(const TwitchChannel::StreamStatus &s, QString thumbnail) - { - auto title = [&s]() -> QString { - if (s.title.isEmpty()) - { - return QStringLiteral(""); - } - - return s.title.toHtmlEscaped() + "

"; - }(); - - auto tooltip = [&thumbnail]() -> QString { - if (getSettings()->thumbnailSizeStream.getValue() == 0) - { - return QStringLiteral(""); - } - - if (thumbnail.isEmpty()) - { - return QStringLiteral("Couldn't fetch thumbnail
"); - } - - return "
"; - }(); - - auto game = [&s]() -> QString { - if (s.game.isEmpty()) - { - return QStringLiteral(""); - } - - return s.game.toHtmlEscaped() + "
"; - }(); - - auto extraStreamData = [&s]() -> QString { - if (isInStreamerMode() && - getSettings()->streamerModeHideViewerCountAndDuration) - { - return QStringLiteral( - "<Streamer " - "Mode>"); - } - - return QString("%1 for %2 with %3 viewers") - .arg(s.rerun ? "Vod-casting" : "Live") - .arg(s.uptime) - .arg(localizeNumbers(s.viewerCount)); - }(); - - return QString("

" + // - title + // - tooltip + // - game + // - extraStreamData + // - "

" // - ); - } - auto formatOfflineTooltip(const TwitchChannel::StreamStatus &s) - { - return QString("

Offline
%1

") - .arg(s.title.toHtmlEscaped()); - } - auto formatTitle(const TwitchChannel::StreamStatus &s, Settings &settings) - { - auto title = QString(); - - // live - if (s.rerun) - title += " (rerun)"; - else if (s.streamType.isEmpty()) - title += " (" + s.streamType + ")"; - else - title += " (live)"; - - // description - if (settings.headerUptime) - title += " - " + s.uptime; - if (settings.headerViewerCount) - title += " - " + localizeNumbers(s.viewerCount); - if (settings.headerGame && !s.game.isEmpty()) - title += " - " + s.game; - if (settings.headerStreamTitle && !s.title.isEmpty()) + if (thumbnail.isEmpty()) { - title += " - " + s.title.simplified(); + return QStringLiteral("Couldn't fetch thumbnail
"); } - return title; - } - auto distance(QPoint a, QPoint b) - { - auto x = std::abs(a.x() - b.x()); - auto y = std::abs(a.y() - b.y()); + return "
"; + }(); - return std::sqrt(x * x + y * y); + auto game = [&s]() -> QString { + if (s.game.isEmpty()) + { + return QStringLiteral(""); + } + + return s.game.toHtmlEscaped() + "
"; + }(); + + auto extraStreamData = [&s]() -> QString { + if (isInStreamerMode() && + getSettings()->streamerModeHideViewerCountAndDuration) + { + return QStringLiteral( + "<Streamer " + "Mode>"); + } + + return QString("%1 for %2 with %3 viewers") + .arg(s.rerun ? "Vod-casting" : "Live") + .arg(s.uptime) + .arg(localizeNumbers(s.viewerCount)); + }(); + + return QString("

" + // + title + // + tooltip + // + game + // + extraStreamData + // + "

" // + ); +} + +auto formatOfflineTooltip(const TwitchChannel::StreamStatus &s) +{ + return QString("

Offline
%1

") + .arg(s.title.toHtmlEscaped()); +} + +auto formatTitle(const TwitchChannel::StreamStatus &s, Settings &settings) +{ + auto title = QString(); + + // live + if (s.rerun) + { + title += " (rerun)"; } + else if (s.streamType.isEmpty()) + { + title += " (" + s.streamType + ")"; + } + else + { + title += " (live)"; + } + + // description + if (settings.headerUptime) + { + title += " - " + s.uptime; + } + if (settings.headerViewerCount) + { + title += " - " + localizeNumbers(s.viewerCount); + } + if (settings.headerGame && !s.game.isEmpty()) + { + title += " - " + s.game; + } + if (settings.headerStreamTitle && !s.title.isEmpty()) + { + title += " - " + s.title.simplified(); + } + + return title; +} + +auto distance(QPoint a, QPoint b) +{ + auto x = std::abs(a.x() - b.x()); + auto y = std::abs(a.y() - b.y()); + + return std::sqrt(x * x + y * y); +} + } // namespace -SplitHeader::SplitHeader(Split *_split) - : BaseWidget(_split) - , split_(_split) +namespace chatterino { + +SplitHeader::SplitHeader(Split *split) + : BaseWidget(split) + , split_(split) { this->initializeLayout(); @@ -225,7 +260,7 @@ SplitHeader::SplitHeader(Split *_split) void SplitHeader::initializeLayout() { - auto layout = makeLayout({ + auto *layout = makeLayout({ // space makeWidget([](auto w) { w->setScaleIndependantSize(8, 4); @@ -307,24 +342,9 @@ void SplitHeader::initializeLayout() }), }); - // update moderation button when items changed - this->managedConnections_.managedConnect( - getSettings()->moderationActions.delayedItemsChanged, [this] { - if (getSettings()->moderationActions.empty()) - { - if (this->split_->getModerationMode()) - this->split_->setModerationMode(true); - } - else - { - if (this->split_->getModerationMode()) - this->split_->setModerationMode(true); - } - }); - getSettings()->customURIScheme.connect( [this] { - if (const auto drop = this->dropdownButton_) + if (auto *const drop = this->dropdownButton_) { drop->setMenu(this->createMainMenu()); } @@ -444,7 +464,7 @@ std::unique_ptr SplitHeader::createMainMenu() { // "How to..." sub menu - auto subMenu = new QMenu("How to...", this); + auto *subMenu = new QMenu("How to...", this); subMenu->addAction("move split", this->split_, &Split::explainMoving); subMenu->addAction("add/split", this->split_, &Split::explainSplitting); menu->addMenu(subMenu); @@ -453,7 +473,7 @@ std::unique_ptr SplitHeader::createMainMenu() menu->addSeparator(); // sub menu - auto moreMenu = new QMenu("More", this); + auto *moreMenu = new QMenu("More", this); auto modModeSeq = h->getDisplaySequence(HotkeyCategory::Split, "setModerationMode", {{"toggle"}}); @@ -472,14 +492,14 @@ std::unique_ptr SplitHeader::createMainMenu() if (this->split_->getChannel()->getType() == Channel::Type::TwitchMentions) { - auto action = new QAction(this); + auto *action = new QAction(this); action->setText("Enable /mention tab highlights"); action->setCheckable(true); - QObject::connect(moreMenu, &QMenu::aboutToShow, this, [action, this]() { + QObject::connect(moreMenu, &QMenu::aboutToShow, this, [action]() { action->setChecked(getSettings()->highlightMentions); }); - action->connect(action, &QAction::triggered, this, [this]() { + QObject::connect(action, &QAction::triggered, this, []() { getSettings()->highlightMentions = !getSettings()->highlightMentions; }); @@ -495,7 +515,7 @@ std::unique_ptr SplitHeader::createMainMenu() moreMenu->addAction("Subscribe", this->split_, &Split::openSubPage); - auto action = new QAction(this); + auto *action = new QAction(this); action->setText("Notify when live"); action->setCheckable(true); @@ -513,7 +533,7 @@ std::unique_ptr SplitHeader::createMainMenu() action->setChecked(getApp()->notifications->isChannelNotified( this->split_->getChannel()->getName(), Platform::Twitch)); }); - action->connect(action, &QAction::triggered, this, [this]() { + QObject::connect(action, &QAction::triggered, this, [this]() { getApp()->notifications->updateChannelNotification( this->split_->getChannel()->getName(), Platform::Twitch); }); @@ -523,7 +543,7 @@ std::unique_ptr SplitHeader::createMainMenu() if (twitchChannel) { - auto action = new QAction(this); + auto *action = new QAction(this); action->setText("Mute highlight sound"); action->setCheckable(true); @@ -531,7 +551,7 @@ std::unique_ptr SplitHeader::createMainMenu() action->setChecked(getSettings()->isMutedChannel( this->split_->getChannel()->getName())); }); - action->connect(action, &QAction::triggered, this, [this]() { + QObject::connect(action, &QAction::triggered, this, [this]() { getSettings()->toggleMutedChannel( this->split_->getChannel()->getName()); }); @@ -555,11 +575,11 @@ std::unique_ptr SplitHeader::createChatModeMenu() { auto menu = std::make_unique(); - auto setSub = new QAction("Subscriber only", this); - auto setEmote = new QAction("Emote only", this); - auto setSlow = new QAction("Slow", this); - auto setR9k = new QAction("R9K", this); - auto setFollowers = new QAction("Followers only", this); + auto *setSub = new QAction("Subscriber only", this); + auto *setEmote = new QAction("Emote only", this); + auto *setSlow = new QAction("Slow", this); + auto *setR9k = new QAction("R9K", this); + auto *setFollowers = new QAction("Followers only", this); setFollowers->setCheckable(true); setSub->setCheckable(true); @@ -576,7 +596,7 @@ std::unique_ptr SplitHeader::createChatModeMenu() this->managedConnections_.managedConnect( this->modeUpdateRequested_, [this, setSub, setEmote, setSlow, setR9k, setFollowers]() { - auto twitchChannel = + auto *twitchChannel = dynamic_cast(this->split_->getChannel().get()); if (twitchChannel == nullptr) { @@ -587,7 +607,7 @@ std::unique_ptr SplitHeader::createChatModeMenu() auto roomModes = twitchChannel->accessRoomModes(); setR9k->setChecked(roomModes->r9k); - setSlow->setChecked(roomModes->slowMode); + setSlow->setChecked(roomModes->slowMode > 0); setEmote->setChecked(roomModes->emoteOnly); setSub->setChecked(roomModes->submode); setFollowers->setChecked(roomModes->followerOnly != -1); @@ -675,7 +695,7 @@ void SplitHeader::updateRoomModes() void SplitHeader::initializeModeSignals(EffectLabel &label) { this->modeUpdateRequested_.connect([this, &label] { - if (auto twitchChannel = + if (auto *twitchChannel = dynamic_cast(this->split_->getChannel().get())) { label.setEnable(twitchChannel->hasModRights()); @@ -710,7 +730,7 @@ void SplitHeader::handleChannelChanged() this->channelConnections_.clear(); auto channel = this->split_->getChannel(); - if (auto twitchChannel = dynamic_cast(channel.get())) + if (auto *twitchChannel = dynamic_cast(channel.get())) { this->channelConnections_.managedConnect( twitchChannel->liveStatusChanged, [this]() { @@ -750,9 +770,11 @@ void SplitHeader::updateChannelText() auto title = channel->getLocalizedName(); if (indirectChannel.getType() == Channel::Type::TwitchWatching) + { title = "watching: " + (title.isEmpty() ? "none" : title); + } - if (auto twitchChannel = dynamic_cast(channel.get())) + if (auto *twitchChannel = dynamic_cast(channel.get())) { const auto streamStatus = twitchChannel->accessStreamStatus(); @@ -779,7 +801,7 @@ void SplitHeader::updateChannelText() } if (!url.isEmpty() && (!this->lastThumbnail_.isValid() || - this->lastThumbnail_.elapsed() > 5 * 60 * 1000)) + this->lastThumbnail_.elapsed() > THUMBNAIL_MAX_AGE_MS)) { NetworkRequest(url, NetworkRequestType::Get) .onSuccess([this](auto result) -> Outcome { @@ -808,7 +830,7 @@ void SplitHeader::updateChannelText() } } - if (!title.isEmpty() && this->split_->getFilters().size() != 0) + if (!title.isEmpty() && !this->split_->getFilters().empty()) { title += " - filtered"; } @@ -826,7 +848,7 @@ void SplitHeader::updateModerationModeIcon() : getResources().buttons.modModeDisabled); auto channel = this->split_->getChannel(); - auto twitchChannel = dynamic_cast(channel.get()); + auto *twitchChannel = dynamic_cast(channel.get()); if (twitchChannel != nullptr && (twitchChannel->hasModRights() || moderationMode)) @@ -839,7 +861,7 @@ void SplitHeader::updateModerationModeIcon() } } -void SplitHeader::paintEvent(QPaintEvent *) +void SplitHeader::paintEvent(QPaintEvent * /*event*/) { QPainter painter(this); @@ -891,7 +913,7 @@ void SplitHeader::mousePressEvent(QMouseEvent *event) this->doubleClicked_ = false; } -void SplitHeader::mouseReleaseEvent(QMouseEvent *event) +void SplitHeader::mouseReleaseEvent(QMouseEvent * /*event*/) { this->dragging_ = false; } @@ -921,13 +943,13 @@ void SplitHeader::enterEvent(QEvent *event) { if (!this->tooltipText_.isEmpty()) { - auto channel = this->split_->getChannel().get(); + auto *channel = this->split_->getChannel().get(); if (channel->getType() == Channel::Type::Twitch) { dynamic_cast(channel)->refreshTitle(); } - auto tooltip = TooltipWidget::instance(); + auto *tooltip = TooltipWidget::instance(); tooltip->clearImage(); tooltip->setText(this->tooltipText_); tooltip->setWordWrap(true); @@ -994,7 +1016,7 @@ void SplitHeader::reloadChannelEmotes() auto channel = this->split_->getChannel(); - if (auto twitchChannel = dynamic_cast(channel.get())) + if (auto *twitchChannel = dynamic_cast(channel.get())) { twitchChannel->refreshFFZChannelEmotes(true); twitchChannel->refreshBTTVChannelEmotes(true); diff --git a/src/widgets/splits/SplitHeader.hpp b/src/widgets/splits/SplitHeader.hpp index a9a8a1893..6b730703a 100644 --- a/src/widgets/splits/SplitHeader.hpp +++ b/src/widgets/splits/SplitHeader.hpp @@ -25,7 +25,7 @@ class SplitHeader final : public BaseWidget Q_OBJECT public: - explicit SplitHeader(Split *_chatWidget); + explicit SplitHeader(Split *split); void setAddButtonVisible(bool value); void setViewersButtonVisible(bool value); @@ -35,16 +35,16 @@ public: void updateRoomModes(); protected: - virtual void scaleChangedEvent(float) override; - virtual void themeChangedEvent() override; + void scaleChangedEvent(float scale) override; + void themeChangedEvent() override; - virtual void paintEvent(QPaintEvent *) override; - virtual void mousePressEvent(QMouseEvent *event) override; - virtual void mouseReleaseEvent(QMouseEvent *event) override; - virtual void mouseMoveEvent(QMouseEvent *event) override; - virtual void enterEvent(QEvent *) override; - virtual void leaveEvent(QEvent *event) override; - virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void enterEvent(QEvent *event) override; + void leaveEvent(QEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; private: void initializeLayout();