From 7efe58cca9e9fd530262d4c74e06ed07add7dfec Mon Sep 17 00:00:00 2001 From: pajlada Date: Tue, 31 Oct 2023 14:54:14 +0100 Subject: [PATCH] refactor: ChannelView (#4926) Co-authored-by: nerix --- CHANGELOG.md | 1 + src/messages/layouts/MessageLayout.hpp | 6 +- src/providers/twitch/TwitchChannel.cpp | 16 + src/providers/twitch/TwitchChannel.hpp | 6 + src/widgets/helper/ChannelView.cpp | 651 +++++++++++++------------ src/widgets/helper/ChannelView.hpp | 52 +- 6 files changed, 386 insertions(+), 346 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 350a25c95..a22a92978 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ - Dev: Removed direct dependency on Qt 5 compatibility module. (#4906) - Dev: Refactor `DebugCount` and add copy button to debug popup. (#4921) - Dev: Changed lifetime of context menus. (#4924) +- Dev: Refactor `ChannelView`, removing a bunch of clang-tidy warnings. (#4926) ## 2.4.6 diff --git a/src/messages/layouts/MessageLayout.hpp b/src/messages/layouts/MessageLayout.hpp index c4f0796a2..d87a526b2 100644 --- a/src/messages/layouts/MessageLayout.hpp +++ b/src/messages/layouts/MessageLayout.hpp @@ -60,7 +60,11 @@ public: void deleteBuffer(); void deleteCache(); - // Elements + /** + * Returns a raw pointer to the element at the given point + * + * If no element is found at the given point, this returns a null pointer + */ const MessageLayoutElement *getElementAt(QPoint point); /** diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index f3a9f7009..110cc4efa 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -1257,6 +1257,22 @@ const std::unordered_map> return this->threads_; } +std::shared_ptr TwitchChannel::getOrCreateThread( + const MessagePtr &message) +{ + assert(message != nullptr); + + auto threadIt = this->threads_.find(message->id); + if (threadIt != this->threads_.end() && !threadIt->second.expired()) + { + return threadIt->second.lock(); + } + + auto thread = std::make_shared(message); + this->addReplyThread(thread); + return thread; +} + void TwitchChannel::cleanUpReplyThreads() { for (auto it = this->threads_.begin(), last = this->threads_.end(); diff --git a/src/providers/twitch/TwitchChannel.hpp b/src/providers/twitch/TwitchChannel.hpp index 0ca895192..28b8c5d23 100644 --- a/src/providers/twitch/TwitchChannel.hpp +++ b/src/providers/twitch/TwitchChannel.hpp @@ -190,6 +190,12 @@ public: const std::unordered_map> &threads() const; + /** + * Get the thread for the given message + * If no thread can be found for the message, create one + */ + std::shared_ptr getOrCreateThread(const MessagePtr &message); + // Only TwitchChannel may invoke this signal pajlada::Signals::NoArgSignal userStateChanged; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 74bfe9652..797ba49fc 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -1,4 +1,4 @@ -#include "ChannelView.hpp" +#include "widgets/helper/ChannelView.hpp" #include "Application.hpp" #include "common/Common.hpp" @@ -67,78 +67,216 @@ #define CHAT_HOVER_PAUSE_DURATION 1000 #define TOOLTIP_EMOTE_ENTRIES_LIMIT 7 -namespace chatterino { namespace { - void addEmoteContextMenuItems(const Emote &emote, - MessageElementFlags creatorFlags, QMenu &menu) - { - auto *openAction = menu.addAction("&Open"); - auto *openMenu = new QMenu(&menu); - openAction->setMenu(openMenu); - auto *copyAction = menu.addAction("&Copy"); - auto *copyMenu = new QMenu(&menu); - copyAction->setMenu(copyMenu); +using namespace chatterino; - // Add copy and open links for 1x, 2x, 3x - auto addImageLink = [&](const ImagePtr &image, char scale) { - if (!image->isEmpty()) - { - copyMenu->addAction("&" + QString(scale) + "x link", - [url = image->url()] { - crossPlatformCopy(url.string); - }); - openMenu->addAction( - "&" + QString(scale) + "x link", [url = image->url()] { - QDesktopServices::openUrl(QUrl(url.string)); - }); - } - }; +constexpr int SCROLLBAR_PADDING = 8; - addImageLink(emote.images.getImage1(), '1'); - addImageLink(emote.images.getImage2(), '2'); - addImageLink(emote.images.getImage3(), '3'); +void addEmoteContextMenuItems(QMenu *menu, const Emote &emote, + MessageElementFlags creatorFlags) +{ + auto *openAction = menu->addAction("&Open"); + auto *openMenu = new QMenu(menu); + openAction->setMenu(openMenu); - // Copy and open emote page link - auto addPageLink = [&](const QString &name) { - copyMenu->addSeparator(); - openMenu->addSeparator(); + auto *copyAction = menu->addAction("&Copy"); + auto *copyMenu = new QMenu(menu); + copyAction->setMenu(copyMenu); - copyMenu->addAction("Copy " + name + " &emote link", - [url = emote.homePage] { + // Add copy and open links for 1x, 2x, 3x + auto addImageLink = [&](const ImagePtr &image, char scale) { + if (!image->isEmpty()) + { + copyMenu->addAction("&" + QString(scale) + "x link", + [url = image->url()] { crossPlatformCopy(url.string); }); - openMenu->addAction("Open " + name + " &emote link", - [url = emote.homePage] { + openMenu->addAction("&" + QString(scale) + "x link", + [url = image->url()] { QDesktopServices::openUrl(QUrl(url.string)); }); - }; + } + }; - if (creatorFlags.has(MessageElementFlag::BttvEmote)) - { - addPageLink("BTTV"); - } - else if (creatorFlags.has(MessageElementFlag::FfzEmote)) - { - addPageLink("FFZ"); - } - else if (creatorFlags.has(MessageElementFlag::SevenTVEmote)) - { - addPageLink("7TV"); - } - } + addImageLink(emote.images.getImage1(), '1'); + addImageLink(emote.images.getImage2(), '2'); + addImageLink(emote.images.getImage3(), '3'); - // Current function: https://www.desmos.com/calculator/vdyamchjwh - qreal highlightEasingFunction(qreal progress) + // Copy and open emote page link + auto addPageLink = [&](const QString &name) { + copyMenu->addSeparator(); + openMenu->addSeparator(); + + copyMenu->addAction("Copy " + name + " &emote link", + [url = emote.homePage] { + crossPlatformCopy(url.string); + }); + openMenu->addAction("Open " + name + " &emote link", + [url = emote.homePage] { + QDesktopServices::openUrl(QUrl(url.string)); + }); + }; + + if (creatorFlags.has(MessageElementFlag::BttvEmote)) { - if (progress <= 0.1) - { - return 1.0 - pow(10.0 * progress, 3.0); - } - return 1.0 + pow((20.0 / 9.0) * (0.5 * progress - 0.5), 3.0); + addPageLink("BTTV"); } + else if (creatorFlags.has(MessageElementFlag::FfzEmote)) + { + addPageLink("FFZ"); + } + else if (creatorFlags.has(MessageElementFlag::SevenTVEmote)) + { + addPageLink("7TV"); + } +} + +void addImageContextMenuItems(QMenu *menu, + const MessageLayoutElement *hoveredElement) +{ + if (hoveredElement == nullptr) + { + return; + } + + const auto &creator = hoveredElement->getCreator(); + auto creatorFlags = creator.getFlags(); + + // Badge actions + if (creatorFlags.hasAny({MessageElementFlag::Badges})) + { + if (const auto *badgeElement = + dynamic_cast(&creator)) + { + addEmoteContextMenuItems(menu, *badgeElement->getEmote(), + creatorFlags); + } + } + + // Emote actions + if (creatorFlags.hasAny( + {MessageElementFlag::EmoteImages, MessageElementFlag::EmojiImage})) + { + if (const auto *emoteElement = + dynamic_cast(&creator)) + { + addEmoteContextMenuItems(menu, *emoteElement->getEmote(), + creatorFlags); + } + else if (const auto *layeredElement = + dynamic_cast(&creator)) + { + // Give each emote its own submenu + for (auto &emote : layeredElement->getUniqueEmotes()) + { + auto *emoteAction = menu->addAction(emote.ptr->name.string); + auto *emoteMenu = new QMenu(menu); + emoteAction->setMenu(emoteMenu); + addEmoteContextMenuItems(emoteMenu, *emote.ptr, emote.flags); + } + } + } + + // add seperator + if (!menu->actions().empty()) + { + menu->addSeparator(); + } +} + +void addLinkContextMenuItems(QMenu *menu, + const MessageLayoutElement *hoveredElement) +{ + if (hoveredElement == nullptr) + { + return; + } + + const auto &link = hoveredElement->getLink(); + + if (link.type != Link::Url) + { + return; + } + + // Link copy + QString url = link.value; + + // open link + menu->addAction("&Open link", [url] { + QDesktopServices::openUrl(QUrl(url)); + }); + // open link default + if (supportsIncognitoLinks()) + { + menu->addAction("Open link &incognito", [url] { + openLinkIncognito(url); + }); + } + menu->addAction("&Copy link", [url] { + crossPlatformCopy(url); + }); + + menu->addSeparator(); +} + +void addHiddenContextMenuItems(QMenu *menu, + const MessageLayoutElement * /*hoveredElement*/, + const MessageLayoutPtr &layout, + QMouseEvent *event) +{ + if (!layout) + { + return; + } + + if (event->modifiers() != Qt::ShiftModifier) + { + // NOTE: We currently require the modifier to be ONLY shift - we might want to check if shift is among the modifiers instead + return; + } + + if (!layout->getMessage()->id.isEmpty()) + { + menu->addAction("Copy message &ID", + [messageID = layout->getMessage()->id] { + crossPlatformCopy(messageID); + }); + } +} + +// Current function: https://www.desmos.com/calculator/vdyamchjwh +qreal highlightEasingFunction(qreal progress) +{ + if (progress <= 0.1) + { + return 1.0 - pow(10.0 * progress, 3.0); + } + return 1.0 + pow((20.0 / 9.0) * (0.5 * progress - 0.5), 3.0); +} + +/// @return the start and end of the word bounds +std::pair getWordBounds(MessageLayout *layout, + const MessageLayoutElement *element, + const QPoint &relativePos) +{ + assert(layout != nullptr); + assert(element != nullptr); + + const auto wordStart = layout->getSelectionIndex(relativePos) - + element->getMouseOverIndex(relativePos); + const auto selectionLength = element->getSelectionIndexCount(); + const auto length = + element->hasTrailingSpace() ? selectionLength - 1 : selectionLength; + + return {wordStart, wordStart + length}; +} + } // namespace +namespace chatterino { + ChannelView::ChannelView(BaseWidget *parent, Split *split, Context context, size_t messagesLimit) : BaseWidget(parent) @@ -160,9 +298,10 @@ ChannelView::ChannelView(BaseWidget *parent, Split *split, Context context, this->pauseTimer_.setSingleShot(true); QObject::connect(&this->pauseTimer_, &QTimer::timeout, this, [this] { - /// remove elements that are finite - for (auto it = this->pauses_.begin(); it != this->pauses_.end();) - it = it->second ? this->pauses_.erase(it) : ++it; + // remove elements that are finite + std::erase_if(this->pauses_, [](const auto &p) { + return p.second.has_value(); + }); this->updatePauses(); }); @@ -171,18 +310,18 @@ ChannelView::ChannelView(BaseWidget *parent, Split *split, Context context, // don't have a SplitInput like the SearchPopup or EmotePopup. // See SplitInput::installKeyPressedEvent for the copy event // from views with a SplitInput. - auto shortcut = new QShortcut(QKeySequence::StandardKey::Copy, this); + auto *shortcut = new QShortcut(QKeySequence::StandardKey::Copy, this); QObject::connect(shortcut, &QShortcut::activated, [this] { this->copySelectedText(); }); - this->clickTimer_ = new QTimer(this); - this->clickTimer_->setSingleShot(true); - this->clickTimer_->setInterval(500); + this->clickTimer_.setSingleShot(true); + this->clickTimer_.setInterval(500); this->scrollTimer_.setInterval(20); - QObject::connect(&this->scrollTimer_, &QTimer::timeout, this, - &ChannelView::scrollUpdateRequested); + QObject::connect(&this->scrollTimer_, &QTimer::timeout, this, [this] { + this->scrollUpdateRequested(); + }); // TODO: Figure out if we need this, and if so, why // StrongFocus means we can focus this event through clicking it @@ -409,7 +548,7 @@ void ChannelView::scaleChangedEvent(float scale) { auto factor = this->qtFontScale(); #ifdef Q_OS_MACOS - factor = scale * 80.f / + factor = scale * 80.F / std::max( 0.01, this->logicalDpiX() * this->devicePixelRatioF()); #endif @@ -645,7 +784,7 @@ bool ChannelView::getEnableScrollingToBottom() const void ChannelView::setOverrideFlags(std::optional value) { - this->overrideFlags_ = std::move(value); + this->overrideFlags_ = value; } const std::optional &ChannelView::getOverrideFlags() const @@ -674,7 +813,7 @@ bool ChannelView::showScrollbarHighlights() const return this->channel_->getType() != Channel::Type::TwitchMentions; } -void ChannelView::setChannel(ChannelPtr underlyingChannel) +void ChannelView::setChannel(const ChannelPtr &underlyingChannel) { /// Clear connections from the last channel this->channelConnections_.clear(); @@ -727,19 +866,23 @@ void ChannelView::setChannel(ChannelPtr underlyingChannel) [this](std::vector &messages) { std::vector filtered; std::copy_if(messages.begin(), messages.end(), - std::back_inserter(filtered), [this](MessagePtr msg) { + std::back_inserter(filtered), [this](const auto &msg) { return this->shouldIncludeMessage(msg); }); if (!filtered.empty()) + { this->channel_->addMessagesAtStart(filtered); + } }); this->channelConnections_.managedConnect( underlyingChannel->messageReplaced, - [this](size_t index, MessagePtr replacement) { + [this](auto index, const auto &replacement) { if (this->shouldIncludeMessage(replacement)) + { this->channel_->replaceMessage(index, replacement); + } }); this->channelConnections_.managedConnect( @@ -747,7 +890,7 @@ void ChannelView::setChannel(ChannelPtr underlyingChannel) std::vector filtered; filtered.reserve(messages.size()); std::copy_if(messages.begin(), messages.end(), - std::back_inserter(filtered), [this](MessagePtr msg) { + std::back_inserter(filtered), [this](const auto &msg) { return this->shouldIncludeMessage(msg); }); this->channel_->fillInMissingMessages(filtered); @@ -762,7 +905,7 @@ void ChannelView::setChannel(ChannelPtr underlyingChannel) this->channel_->messageAppended, [this](MessagePtr &message, std::optional overridingFlags) { - this->messageAppended(message, std::move(overridingFlags)); + this->messageAppended(message, overridingFlags); }); this->channelConnections_.managedConnect( @@ -817,10 +960,12 @@ void ChannelView::setChannel(ChannelPtr underlyingChannel) this->queueUpdate(); // Notifications - if (auto tc = dynamic_cast(underlyingChannel.get())) + auto *twitchChannel = + dynamic_cast(underlyingChannel.get()); + if (twitchChannel != nullptr) { this->channelConnections_.managedConnect( - tc->streamStatusChanged, [this]() { + twitchChannel->streamStatusChanged, [this]() { this->liveStatusChanged.invoke(); }); } @@ -831,11 +976,11 @@ void ChannelView::setFilters(const QList &ids) this->channelFilters_ = std::make_shared(ids); } -const QList ChannelView::getFilterIds() const +QList ChannelView::getFilterIds() const { if (!this->channelFilters_) { - return QList(); + return {}; } return this->channelFilters_->filterIds(); @@ -853,7 +998,9 @@ bool ChannelView::shouldIncludeMessage(const MessagePtr &m) const if (getSettings()->excludeUserMessagesFromFilter && getApp()->accounts->twitch.getCurrent()->getUserName().compare( m->loginName, Qt::CaseInsensitive) == 0) + { return true; + } return this->channelFilters_->filter(m, this->underlyingChannel_); } @@ -964,7 +1111,9 @@ void ChannelView::messageAddedAtStart(std::vector &messages) // alternate color if (!this->lastMessageHasAlternateBackgroundReverse_) + { layout->flags.set(MessageLayoutFlag::AlternateBackground); + } this->lastMessageHasAlternateBackgroundReverse_ = !this->lastMessageHasAlternateBackgroundReverse_; @@ -976,9 +1125,13 @@ void ChannelView::messageAddedAtStart(std::vector &messages) if (!addedMessages.empty()) { if (this->scrollBar_->isAtBottom()) + { this->scrollBar_->scrollToBottom(); + } else + { this->scrollBar_->offset(qreal(addedMessages.size())); + } this->scrollBar_->offsetMaximum(qreal(addedMessages.size())); } @@ -1069,7 +1222,7 @@ void ChannelView::updateLastReadMessage() this->update(); } -void ChannelView::resizeEvent(QResizeEvent *) +void ChannelView::resizeEvent(QResizeEvent * /*event*/) { this->scrollBar_->setGeometry(this->width() - this->scrollBar_->width(), 0, this->scrollBar_->width(), this->height()); @@ -1102,7 +1255,7 @@ void ChannelView::setSelection(const SelectionItem &start, MessageElementFlags ChannelView::getFlags() const { - auto app = getApp(); + auto *app = getApp(); if (this->overrideFlags_) { @@ -1111,12 +1264,11 @@ MessageElementFlags ChannelView::getFlags() const MessageElementFlags flags = app->windows->getWordFlags(); - Split *split = dynamic_cast(this->parentWidget()); + auto *split = dynamic_cast(this->parentWidget()); if (split == nullptr) { - SearchPopup *searchPopup = - dynamic_cast(this->parentWidget()); + auto *searchPopup = dynamic_cast(this->parentWidget()); if (searchPopup != nullptr) { split = dynamic_cast(searchPopup->parentWidget()); @@ -1138,7 +1290,9 @@ MessageElementFlags ChannelView::getFlags() const } if (this->sourceChannel_ == app->twitch->mentionsChannel) + { flags.set(MessageElementFlag::ChannelName); + } if (this->context_ == Context::ReplyThread || getSettings()->hideReplyContext) @@ -1368,13 +1522,15 @@ void ChannelView::drawMessages(QPainter &painter) void ChannelView::wheelEvent(QWheelEvent *event) { - if (!event->angleDelta().y()) + if (event->angleDelta().y() == 0) { + // Ignore any scrolls where no vertical scrolling has taken place return; } - if (event->modifiers() & Qt::ControlModifier) + if (event->modifiers().testFlag(Qt::ControlModifier)) { + // Ignore any scrolls where ctrl is held down - it is used for zoom event->ignore(); return; } @@ -1472,7 +1628,7 @@ void ChannelView::enterEvent(QEvent * /*event*/) { } -void ChannelView::leaveEvent(QEvent *) +void ChannelView::leaveEvent(QEvent * /*event*/) { TooltipWidget::instance()->hide(); @@ -1485,16 +1641,17 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) { /// Pause on hover if (float pauseTime = getSettings()->pauseOnHoverDuration; - pauseTime > 0.001f) + pauseTime > 0.001F) { - this->pause(PauseReason::Mouse, uint(pauseTime * 1000.f)); + this->pause(PauseReason::Mouse, + static_cast(pauseTime * 1000.F)); } - else if (pauseTime < -0.5f) + else if (pauseTime < -0.5F) { this->pause(PauseReason::Mouse); } - auto tooltipWidget = TooltipWidget::instance(); + auto *tooltipWidget = TooltipWidget::instance(); std::shared_ptr layout; QPoint relativePos; int messageIndex; @@ -1528,7 +1685,7 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) if (this->isDoubleClick_ && hoverLayoutElement) { auto [wordStart, wordEnd] = - this->getWordBounds(layout.get(), hoverLayoutElement, relativePos); + getWordBounds(layout.get(), hoverLayoutElement, relativePos); auto hoveredWord = Selection{SelectionItem(messageIndex, wordStart), SelectionItem(messageIndex, wordEnd)}; // combined selection spanning from initially selected word to hoveredWord @@ -1552,10 +1709,10 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) return; } - auto element = &hoverLayoutElement->getCreator(); + auto *element = &hoverLayoutElement->getCreator(); bool isLinkValid = hoverLayoutElement->getLink().isValid(); - auto emoteElement = dynamic_cast(element); - auto layeredEmoteElement = + const auto *emoteElement = dynamic_cast(element); + const auto *layeredEmoteElement = dynamic_cast(element); bool isNotEmote = emoteElement == nullptr && layeredEmoteElement == nullptr; @@ -1566,7 +1723,7 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) } else { - auto badgeElement = dynamic_cast(element); + const auto *badgeElement = dynamic_cast(element); if (badgeElement || emoteElement || layeredEmoteElement) { @@ -1589,14 +1746,14 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) } else if (layeredEmoteElement) { - auto &layeredEmotes = layeredEmoteElement->getEmotes(); + const auto &layeredEmotes = layeredEmoteElement->getEmotes(); // Should never be empty but ensure it if (!layeredEmotes.empty()) { std::vector entries; entries.reserve(layeredEmotes.size()); - auto &emoteTooltips = + const auto &emoteTooltips = layeredEmoteElement->getEmoteTooltips(); // Someone performing some tomfoolery could put an emote with tens, @@ -1664,7 +1821,9 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) ImagePtr thumbnail) { auto shared = weakLayout.lock(); if (!shared) + { return; + } element->setTooltip(tooltipText); element->setThumbnail(thumbnail); }); @@ -1753,13 +1912,17 @@ void ChannelView::mousePressEvent(QMouseEvent *event) { case Qt::LeftButton: { if (this->isScrolling_) + { this->disableScrolling(); + } this->lastLeftPressPosition_ = event->screenPos(); this->isLeftMouseDown_ = true; if (layout->flags.has(MessageLayoutFlag::Collapsed)) + { return; + } if (getSettings()->linksDoubleClickOnly.getValue()) { @@ -1774,7 +1937,9 @@ void ChannelView::mousePressEvent(QMouseEvent *event) case Qt::RightButton: { if (this->isScrolling_) + { this->disableScrolling(); + } this->lastRightPressPosition_ = event->screenPos(); this->isRightMouseDown_ = true; @@ -1794,13 +1959,19 @@ void ChannelView::mousePressEvent(QMouseEvent *event) else { if (this->isScrolling_) + { this->disableScrolling(); + } else if (hoverLayoutElement != nullptr && hoverLayoutElement->getFlags().has( MessageElementFlag::Username)) + { break; + } else if (this->scrollBar_->isVisible()) + { this->enableScrolling(event->screenPos()); + } } } break; @@ -1831,9 +2002,9 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) this->isDoubleClick_ = false; // Was actually not a wanted triple-click if (fabsf(distanceBetweenPoints(this->lastDoubleClickPosition_, - event->screenPos())) > 10.f) + event->screenPos())) > 10.F) { - this->clickTimer_->stop(); + this->clickTimer_.stop(); return; } } @@ -1842,15 +2013,15 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) this->isLeftMouseDown_ = false; if (fabsf(distanceBetweenPoints(this->lastLeftPressPosition_, - event->screenPos())) > 15.f) + event->screenPos())) > 15.F) { return; } // Triple-clicking a message selects the whole message - if (foundElement && this->clickTimer_->isActive() && + if (foundElement && this->clickTimer_.isActive() && (fabsf(distanceBetweenPoints(this->lastDoubleClickPosition_, - event->screenPos())) < 10.f)) + event->screenPos())) < 10.F)) { this->selectWholeMessage(layout.get(), messageIndex); return; @@ -1868,7 +2039,7 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) this->isRightMouseDown_ = false; if (fabsf(distanceBetweenPoints(this->lastRightPressPosition_, - event->screenPos())) > 15.f) + event->screenPos())) > 15.F) { return; } @@ -1883,13 +2054,18 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) if (this->isScrolling_ && this->scrollBar_->isVisible()) { if (event->screenPos() == this->lastMiddlePressPosition_) + { this->enableScrolling(event->screenPos()); + } else + { this->disableScrolling(); + } return; } - else if (foundElement) + + if (foundElement) { const MessageLayoutElement *hoverLayoutElement = layout->getElementAt(relativePos); @@ -1898,14 +2074,14 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) { return; } - else if (hoverLayoutElement->getFlags().has( - MessageElementFlag::Username)) + if (hoverLayoutElement->getFlags().has( + MessageElementFlag::Username)) { openTwitchUsercard(this->channel_->getName(), hoverLayoutElement->getLink().value); return; } - else if (hoverLayoutElement->getLink().isUrl() == false) + if (hoverLayoutElement->getLink().isUrl() == false) { return; } @@ -1973,7 +2149,7 @@ void ChannelView::handleMouseClick(QMouseEvent *event, if ((this->context_ == Context::None) && (hoveredElement != nullptr)) { - auto split = dynamic_cast(this->parentWidget()); + auto *split = dynamic_cast(this->parentWidget()); auto insertText = [=](QString text) { if (split) { @@ -2097,129 +2273,39 @@ void ChannelView::addContextMenuItems( menu->setAttribute(Qt::WA_DeleteOnClose); // Add image options if the element clicked contains an image (e.g. a badge or an emote) - this->addImageContextMenuItems(hoveredElement, layout, event, *menu); + addImageContextMenuItems(menu, hoveredElement); // Add link options if the element clicked contains a link - this->addLinkContextMenuItems(hoveredElement, layout, event, *menu); + addLinkContextMenuItems(menu, hoveredElement); // Add message options - this->addMessageContextMenuItems(hoveredElement, layout, event, *menu); + this->addMessageContextMenuItems(menu, layout); // Add Twitch-specific link options if the element clicked contains a link detected as a Twitch username - this->addTwitchLinkContextMenuItems(hoveredElement, layout, event, *menu); + this->addTwitchLinkContextMenuItems(menu, hoveredElement); // Add hidden options (e.g. copy message ID) if the user held down Shift - this->addHiddenContextMenuItems(hoveredElement, layout, event, *menu); + addHiddenContextMenuItems(menu, hoveredElement, layout, event); // Add executable command options - this->addCommandExecutionContextMenuItems(hoveredElement, layout, event, - *menu); + this->addCommandExecutionContextMenuItems(menu, layout); menu->popup(QCursor::pos()); menu->raise(); } -void ChannelView::addImageContextMenuItems( - const MessageLayoutElement *hoveredElement, MessageLayoutPtr /*layout*/, - QMouseEvent * /*event*/, QMenu &menu) -{ - if (hoveredElement == nullptr) - { - return; - } - - const auto &creator = hoveredElement->getCreator(); - auto creatorFlags = creator.getFlags(); - - // Badge actions - if (creatorFlags.hasAny({MessageElementFlag::Badges})) - { - if (auto badgeElement = dynamic_cast(&creator)) - { - addEmoteContextMenuItems(*badgeElement->getEmote(), creatorFlags, - menu); - } - } - - // Emote actions - if (creatorFlags.hasAny( - {MessageElementFlag::EmoteImages, MessageElementFlag::EmojiImage})) - { - if (auto emoteElement = dynamic_cast(&creator)) - { - addEmoteContextMenuItems(*emoteElement->getEmote(), creatorFlags, - menu); - } - else if (auto layeredElement = - dynamic_cast(&creator)) - { - // Give each emote its own submenu - for (auto &emote : layeredElement->getUniqueEmotes()) - { - auto emoteAction = menu.addAction(emote.ptr->name.string); - auto emoteMenu = new QMenu(&menu); - emoteAction->setMenu(emoteMenu); - addEmoteContextMenuItems(*emote.ptr, emote.flags, *emoteMenu); - } - } - } - - // add seperator - if (!menu.actions().empty()) - { - menu.addSeparator(); - } -} - -void ChannelView::addLinkContextMenuItems( - const MessageLayoutElement *hoveredElement, MessageLayoutPtr /*layout*/, - QMouseEvent * /*event*/, QMenu &menu) -{ - if (hoveredElement == nullptr) - { - return; - } - - const auto &link = hoveredElement->getLink(); - - if (link.type != Link::Url) - { - return; - } - - // Link copy - QString url = link.value; - - // open link - menu.addAction("&Open link", [url] { - QDesktopServices::openUrl(QUrl(url)); - }); - // open link default - if (supportsIncognitoLinks()) - { - menu.addAction("Open link &incognito", [url] { - openLinkIncognito(url); - }); - } - menu.addAction("&Copy link", [url] { - crossPlatformCopy(url); - }); - - menu.addSeparator(); -} -void ChannelView::addMessageContextMenuItems( - const MessageLayoutElement * /*hoveredElement*/, MessageLayoutPtr layout, - QMouseEvent * /*event*/, QMenu &menu) +void ChannelView::addMessageContextMenuItems(QMenu *menu, + const MessageLayoutPtr &layout) { // Copy actions if (!this->selection_.isEmpty()) { - menu.addAction("&Copy selection", [this] { + menu->addAction("&Copy selection", [this] { crossPlatformCopy(this->getSelectedText()); }); } - menu.addAction("Copy &message", [layout] { + menu->addAction("Copy &message", [layout] { QString copyString; layout->addSelectionText(copyString, 0, INT_MAX, CopyMode::OnlyTextAndEmotes); @@ -2227,7 +2313,7 @@ void ChannelView::addMessageContextMenuItems( crossPlatformCopy(copyString); }); - menu.addAction("Copy &full message", [layout] { + menu->addAction("Copy &full message", [layout] { QString copyString; layout->addSelectionText(copyString, 0, INT_MAX, CopyMode::EverythingButReplies); @@ -2239,13 +2325,13 @@ void ChannelView::addMessageContextMenuItems( if (this->canReplyToMessages() && layout->isReplyable()) { const auto &messagePtr = layout->getMessagePtr(); - menu.addAction("&Reply to message", [this, &messagePtr] { + menu->addAction("&Reply to message", [this, &messagePtr] { this->setInputReply(messagePtr); }); if (messagePtr->replyThread != nullptr) { - menu.addAction("View &thread", [this, &messagePtr] { + menu->addAction("View &thread", [this, &messagePtr] { this->showReplyThreadPopup(messagePtr); }); } @@ -2260,8 +2346,8 @@ void ChannelView::addMessageContextMenuItems( if (isSearch || isMentions || isReplyOrUserCard) { const auto &messagePtr = layout->getMessagePtr(); - menu.addAction("&Go to message", [this, &messagePtr, isSearch, - isMentions, isReplyOrUserCard] { + menu->addAction("&Go to message", [this, &messagePtr, isSearch, + isMentions, isReplyOrUserCard] { if (isSearch) { if (const auto &search = @@ -2293,8 +2379,7 @@ void ChannelView::addMessageContextMenuItems( } void ChannelView::addTwitchLinkContextMenuItems( - const MessageLayoutElement *hoveredElement, MessageLayoutPtr /*layout*/, - QMouseEvent * /*event*/, QMenu &menu) + QMenu *menu, const MessageLayoutElement *hoveredElement) { if (hoveredElement == nullptr) { @@ -2335,60 +2420,35 @@ void ChannelView::addTwitchLinkContextMenuItems( auto twitchUsername = twitchMatch.captured("username"); if (!twitchUsername.isEmpty() && !ignoredUsernames.contains(twitchUsername)) { - menu.addSeparator(); - menu.addAction("&Open in new split", [twitchUsername, this] { + menu->addSeparator(); + menu->addAction("&Open in new split", [twitchUsername, this] { this->openChannelIn.invoke(twitchUsername, FromTwitchLinkOpenChannelIn::Split); }); - menu.addAction("Open in new &tab", [twitchUsername, this] { + menu->addAction("Open in new &tab", [twitchUsername, this] { this->openChannelIn.invoke(twitchUsername, FromTwitchLinkOpenChannelIn::Tab); }); - menu.addSeparator(); - menu.addAction("Open player in &browser", [twitchUsername, this] { + menu->addSeparator(); + menu->addAction("Open player in &browser", [twitchUsername, this] { this->openChannelIn.invoke( twitchUsername, FromTwitchLinkOpenChannelIn::BrowserPlayer); }); - menu.addAction("Open in &streamlink", [twitchUsername, this] { + menu->addAction("Open in &streamlink", [twitchUsername, this] { this->openChannelIn.invoke(twitchUsername, FromTwitchLinkOpenChannelIn::Streamlink); }); } } -void ChannelView::addHiddenContextMenuItems( - const MessageLayoutElement * /*hoveredElement*/, MessageLayoutPtr layout, - QMouseEvent *event, QMenu &menu) -{ - if (!layout) - { - return; - } - - if (event->modifiers() != Qt::ShiftModifier) - { - // NOTE: We currently require the modifier to be ONLY shift - we might want to check if shift is among the modifiers instead - return; - } - - if (!layout->getMessage()->id.isEmpty()) - { - menu.addAction("Copy message &ID", - [messageID = layout->getMessage()->id] { - crossPlatformCopy(messageID); - }); - } -} - void ChannelView::addCommandExecutionContextMenuItems( - const MessageLayoutElement * /*hoveredElement*/, MessageLayoutPtr layout, - QMouseEvent * /*event*/, QMenu &menu) + QMenu *menu, const MessageLayoutPtr &layout) { /* Get commands to be displayed in context menu; * only those that had the showInMsgContextMenu check box marked in the Commands page */ std::vector cmds; - for (auto &cmd : getApp()->commands->items) + for (const auto &cmd : getApp()->commands->items) { if (cmd.showInMsgContextMenu) { @@ -2401,9 +2461,9 @@ void ChannelView::addCommandExecutionContextMenuItems( return; } - menu.addSeparator(); - auto *executeAction = menu.addAction("&Execute command"); - auto *cmdMenu = new QMenu(&menu); + menu->addSeparator(); + auto *executeAction = menu->addAction("&Execute command"); + auto *cmdMenu = new QMenu(menu); executeAction->setMenu(cmdMenu); for (auto &cmd : cmds) @@ -2427,7 +2487,7 @@ void ChannelView::addCommandExecutionContextMenuItems( { channel = this->underlyingChannel_; } - auto split = dynamic_cast(this->parentWidget()); + auto *split = dynamic_cast(this->parentWidget()); QString userText; if (split) { @@ -2466,7 +2526,7 @@ void ChannelView::mouseDoubleClickEvent(QMouseEvent *event) this->isDoubleClick_ = true; this->lastDoubleClickPosition_ = event->screenPos(); - this->clickTimer_->start(); + this->clickTimer_.start(); // message under cursor is collapsed if (layout->flags.has(MessageLayoutFlag::Collapsed)) @@ -2483,21 +2543,21 @@ void ChannelView::mouseDoubleClickEvent(QMouseEvent *event) } auto [wordStart, wordEnd] = - this->getWordBounds(layout.get(), hoverLayoutElement, relativePos); + getWordBounds(layout.get(), hoverLayoutElement, relativePos); this->doubleClickSelection_ = {SelectionItem(messageIndex, wordStart), SelectionItem(messageIndex, wordEnd)}; this->setSelection(this->doubleClickSelection_); if (getSettings()->linksDoubleClickOnly) { - auto &link = hoverLayoutElement->getLink(); + const auto &link = hoverLayoutElement->getLink(); this->handleLinkClick(event, link, layout.get()); } } -void ChannelView::hideEvent(QHideEvent *) +void ChannelView::hideEvent(QHideEvent * /*event*/) { - for (auto &layout : this->messagesOnScreen_) + for (const auto &layout : this->messagesOnScreen_) { layout->deleteBuffer(); } @@ -2571,9 +2631,13 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link, case Link::Url: { if (getSettings()->openLinksIncognito && supportsIncognitoLinks()) + { openLinkIncognito(link.value); + } else + { QDesktopServices::openUrl(QUrl(link.value)); + } } break; @@ -2581,11 +2645,11 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link, QString value = link.value; ChannelPtr channel = this->underlyingChannel_; - SearchPopup *searchPopup = + auto *searchPopup = dynamic_cast(this->parentWidget()); if (searchPopup != nullptr) { - Split *split = + auto *split = dynamic_cast(searchPopup->parentWidget()); if (split != nullptr) { @@ -2675,16 +2739,16 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link, case Link::JumpToMessage: { if (this->context_ == Context::Search) { - if (auto search = - dynamic_cast(this->parentWidget())) + auto *search = + dynamic_cast(this->parentWidget()); + if (search != nullptr) { search->goToMessageId(link.value); } + return; } - else - { - this->scrollToMessageId(link.value); - } + + this->scrollToMessageId(link.value); } break; @@ -2729,7 +2793,9 @@ bool ChannelView::tryGetMessageAt(QPoint p, int ChannelView::getLayoutWidth() const { if (this->scrollBar_->isVisible()) - return int(this->width() - scrollbarPadding * this->scale()); + { + return int(this->width() - SCROLLBAR_PADDING * this->scale()); + } return this->width(); } @@ -2742,20 +2808,6 @@ void ChannelView::selectWholeMessage(MessageLayout *layout, int &messageIndex) this->setSelection(msgStart, msgEnd); } -/// @returns [wordStart, wordEnd] position indexes for word hovered by mouse -std::pair ChannelView::getWordBounds( - MessageLayout *layout, const MessageLayoutElement *element, - const QPoint &relativePos) -{ - const auto wordStart = layout->getSelectionIndex(relativePos) - - element->getMouseOverIndex(relativePos); - const auto selectionLength = element->getSelectionIndexCount(); - const auto length = - element->hasTrailingSpace() ? selectionLength - 1 : selectionLength; - - return {wordStart, wordStart + length}; -} - void ChannelView::enableScrolling(const QPointF &scrollStart) { this->isScrolling_ = true; @@ -2766,7 +2818,9 @@ void ChannelView::enableScrolling(const QPointF &scrollStart) this->scrollTimer_.start(); if (!QGuiApplication::overrideCursor()) + { QGuiApplication::setOverrideCursor(this->cursors_.neutral); + } } void ChannelView::disableScrolling() @@ -2806,7 +2860,7 @@ void ChannelView::scrollUpdateRequested() } // "Good" feeling multiplier found by trial-and-error - const qreal multiplier = qreal(0.02); + const qreal multiplier(0.02); this->scrollBar_->offset(multiplier * offset); } @@ -2817,32 +2871,19 @@ void ChannelView::setInputReply(const MessagePtr &message) return; } - std::shared_ptr thread; + auto thread = message->replyThread; - if (message->replyThread == nullptr) + if (!thread) { - auto getThread = [&](TwitchChannel *tc) { - auto threadIt = tc->threads().find(message->id); - if (threadIt != tc->threads().end() && !threadIt->second.expired()) - { - return threadIt->second.lock(); - } - else - { - auto thread = std::make_shared(message); - tc->addReplyThread(thread); - return thread; - } - }; - - if (auto tc = + // Message did not already have a thread attached, try to find or create one + if (auto *tc = dynamic_cast(this->underlyingChannel_.get())) { - thread = getThread(tc); + thread = tc->getOrCreateThread(message); } - else if (auto tc = dynamic_cast(this->channel_.get())) + else if (auto *tc = dynamic_cast(this->channel_.get())) { - thread = getThread(tc); + thread = tc->getOrCreateThread(message); } else { @@ -2852,10 +2893,6 @@ void ChannelView::setInputReply(const MessagePtr &message) return; } } - else - { - thread = message->replyThread; - } this->split_->setInputReply(thread); } @@ -2867,10 +2904,10 @@ void ChannelView::showReplyThreadPopup(const MessagePtr &message) return; } - auto popupParent = + auto *popupParent = static_cast(&(getApp()->windows->getMainWindow())); - auto popup = new ReplyThreadPopup(getSettings()->autoCloseThreadPopup, - popupParent, this->split_); + auto *popup = new ReplyThreadPopup(getSettings()->autoCloseThreadPopup, + popupParent, this->split_); popup->setThread(message->replyThread); diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index 88b66ad21..005ba31d7 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -112,16 +112,17 @@ public: bool pausable() const; void setPausable(bool value); bool paused() const; - void pause(PauseReason reason, std::optional msecs = std::nullopt); + void pause(PauseReason reason, + std::optional msecs = std::nullopt); void unpause(PauseReason reason); MessageElementFlags getFlags() const; ChannelPtr channel(); - void setChannel(ChannelPtr channel_); + void setChannel(const ChannelPtr &underlyingChannel); void setFilters(const QList &ids); - const QList getFilterIds() const; + QList getFilterIds() const; FilterSetPtr getFilterSet() const; ChannelPtr sourceChannel() const; @@ -163,9 +164,9 @@ protected: void themeChangedEvent() override; void scaleChangedEvent(float scale) override; - void resizeEvent(QResizeEvent *) override; + void resizeEvent(QResizeEvent * /*event*/) override; - void paintEvent(QPaintEvent *) override; + void paintEvent(QPaintEvent * /*event*/) override; void wheelEvent(QWheelEvent *event) override; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) @@ -173,14 +174,14 @@ protected: #else void enterEvent(QEvent * /*event*/) override; #endif - void leaveEvent(QEvent *) override; + void leaveEvent(QEvent * /*event*/) override; void mouseMoveEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; - void hideEvent(QHideEvent *) override; + void hideEvent(QHideEvent * /*event*/) override; void showEvent(QShowEvent *event) override; void handleLinkClick(QMouseEvent *event, const Link &link, @@ -212,33 +213,18 @@ private: void setSelection(const SelectionItem &start, const SelectionItem &end); void setSelection(const Selection &newSelection); void selectWholeMessage(MessageLayout *layout, int &messageIndex); - std::pair getWordBounds(MessageLayout *layout, - const MessageLayoutElement *element, - const QPoint &relativePos); void handleMouseClick(QMouseEvent *event, const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout); void addContextMenuItems(const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout, QMouseEvent *event); - void addImageContextMenuItems(const MessageLayoutElement *hoveredElement, - MessageLayoutPtr layout, QMouseEvent *event, - QMenu &menu); - void addLinkContextMenuItems(const MessageLayoutElement *hoveredElement, - MessageLayoutPtr layout, QMouseEvent *event, - QMenu &menu); - void addMessageContextMenuItems(const MessageLayoutElement *hoveredElement, - MessageLayoutPtr layout, QMouseEvent *event, - QMenu &menu); + void addMessageContextMenuItems(QMenu *menu, + const MessageLayoutPtr &layout); void addTwitchLinkContextMenuItems( - const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout, - QMouseEvent *event, QMenu &menu); - void addHiddenContextMenuItems(const MessageLayoutElement *hoveredElement, - MessageLayoutPtr layout, QMouseEvent *event, - QMenu &menu); - void addCommandExecutionContextMenuItems( - const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout, - QMouseEvent *event, QMenu &menu); + QMenu *menu, const MessageLayoutElement *hoveredElement); + void addCommandExecutionContextMenuItems(QMenu *menu, + const MessageLayoutPtr &layout); int getLayoutWidth() const; void updatePauses(); @@ -311,7 +297,7 @@ private: QPointF lastLeftPressPosition_; QPointF lastRightPressPosition_; QPointF lastDoubleClickPosition_; - QTimer *clickTimer_; + QTimer clickTimer_; bool isScrolling_ = false; QPointF lastMiddlePressPosition_; @@ -346,16 +332,6 @@ private: MessageColors messageColors_; MessagePreferences messagePreferences_; - static constexpr int leftPadding = 8; - static constexpr int scrollbarPadding = 8; - -private slots: - void wordFlagsChanged() - { - queueLayout(); - update(); - } - void scrollUpdateRequested(); };