diff --git a/src/widgets/Notebook.cpp b/src/widgets/Notebook.cpp index 2a01020fc..53c6dfcfa 100644 --- a/src/widgets/Notebook.cpp +++ b/src/widgets/Notebook.cpp @@ -204,7 +204,7 @@ void Notebook::duplicatePage(QWidget *page) { newTabPosition = tabPosition + 1; } - auto newTabHighlightState = item->tab->highlightState(); + QString newTabTitle = ""; if (item->tab->hasCustomTitle()) { @@ -213,7 +213,7 @@ void Notebook::duplicatePage(QWidget *page) auto *tab = this->addPageAt(newContainer, newTabPosition, newTabTitle, false); - tab->setHighlightState(newTabHighlightState); + tab->copyHighlightStateAndSourcesFrom(item->tab); newContainer->setTab(tab); } diff --git a/src/widgets/Notebook.hpp b/src/widgets/Notebook.hpp index ac6c4dad7..23888ae61 100644 --- a/src/widgets/Notebook.hpp +++ b/src/widgets/Notebook.hpp @@ -2,6 +2,7 @@ #include "widgets/BaseWidget.hpp" +#include #include #include #include diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 066085030..54d5dded5 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -1190,11 +1190,13 @@ void ChannelView::messageAppended(MessagePtr &message, (this->channel_->getType() == Channel::Type::TwitchAutomod && getSettings()->enableAutomodHighlight)) { - this->tabHighlightRequested.invoke(HighlightState::Highlighted); + this->tabHighlightRequested.invoke(HighlightState::Highlighted, + message); } else { - this->tabHighlightRequested.invoke(HighlightState::NewMessage); + this->tabHighlightRequested.invoke(HighlightState::NewMessage, + message); } } diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index 704210712..52d08803a 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -179,6 +179,9 @@ public: LimitedQueueSnapshot &getMessagesSnapshot(); + // Returns true if message should be included + bool shouldIncludeMessage(const MessagePtr &m) const; + void queueLayout(); void invalidateBuffers(); @@ -214,7 +217,8 @@ public: pajlada::Signals::Signal mouseDown; pajlada::Signals::NoArgSignal selectionChanged; - pajlada::Signals::Signal tabHighlightRequested; + pajlada::Signals::Signal + tabHighlightRequested; pajlada::Signals::NoArgSignal liveStatusChanged; pajlada::Signals::Signal linkClicked; pajlada::Signals::Signal @@ -374,9 +378,6 @@ private: FilterSetPtr channelFilters_; - // Returns true if message should be included - bool shouldIncludeMessage(const MessagePtr &m) const; - // Returns whether the scrollbar should have highlights bool showScrollbarHighlights() const; diff --git a/src/widgets/helper/NotebookTab.cpp b/src/widgets/helper/NotebookTab.cpp index be2b371a3..a04725909 100644 --- a/src/widgets/helper/NotebookTab.cpp +++ b/src/widgets/helper/NotebookTab.cpp @@ -10,8 +10,10 @@ #include "singletons/WindowManager.hpp" #include "util/Helpers.hpp" #include "widgets/dialogs/SettingsDialog.hpp" +#include "widgets/helper/ChannelView.hpp" #include "widgets/Notebook.hpp" #include "widgets/splits/DraggedSplit.hpp" +#include "widgets/splits/Split.hpp" #include "widgets/splits/SplitContainer.hpp" #include @@ -302,10 +304,141 @@ bool NotebookTab::isSelected() const return this->selected_; } +void NotebookTab::removeNewMessageSource(const ChannelPtr &source) +{ + this->highlightSources_.newMessageSource.erase(source); +} + +void NotebookTab::removeHighlightedSource(const ChannelPtr &source) +{ + this->highlightSources_.highlightedSource.erase(source); +} + +void NotebookTab::removeHighlightStateChangeSources( + const HighlightSources &toRemove) +{ + for (const auto &source : toRemove.newMessageSource) + { + this->removeNewMessageSource(source); + } + + for (const auto &source : toRemove.highlightedSource) + { + this->removeHighlightedSource(source); + } +} + +void NotebookTab::newHighlightSourceAdded(const ChannelPtr &source) +{ + this->removeHighlightedSource(source); + this->removeNewMessageSource(source); + this->updateHighlightStateDueSourcesChange(); + + auto *splitNotebook = dynamic_cast(this->notebook_); + if (splitNotebook) + { + for (int i = 0; i < splitNotebook->getPageCount(); ++i) + { + auto *splitContainer = + dynamic_cast(splitNotebook->getPageAt(i)); + if (splitContainer) + { + auto *tab = splitContainer->getTab(); + if (tab && tab != this) + { + tab->removeHighlightedSource(source); + tab->removeNewMessageSource(source); + tab->updateHighlightStateDueSourcesChange(); + } + } + } + } +} + +void NotebookTab::updateHighlightStateDueSourcesChange() +{ + if (!this->highlightSources_.highlightedSource.empty()) + { + assert(this->highlightState_ == HighlightState::Highlighted); + return; + } + + if (!this->highlightSources_.newMessageSource.empty()) + { + if (this->highlightState_ != HighlightState::NewMessage) + { + this->highlightState_ = HighlightState::NewMessage; + this->update(); + } + } + else + { + if (this->highlightState_ != HighlightState::None) + { + this->highlightState_ = HighlightState::None; + this->update(); + } + } + + assert(this->highlightState_ != HighlightState::Highlighted); +} + +void NotebookTab::copyHighlightStateAndSourcesFrom(const NotebookTab *sourceTab) +{ + if (this->isSelected()) + { + assert(this->highlightSources_.highlightedSource.empty()); + assert(this->highlightSources_.newMessageSource.empty()); + assert(this->highlightState_ == HighlightState::None); + return; + } + + this->highlightSources_ = sourceTab->highlightSources_; + + if (!this->highlightEnabled_ && + sourceTab->highlightState_ == HighlightState::NewMessage) + { + return; + } + + if (this->highlightState_ == sourceTab->highlightState_ || + this->highlightState_ == HighlightState::Highlighted) + { + return; + } + + this->highlightState_ = sourceTab->highlightState_; + this->update(); +} + void NotebookTab::setSelected(bool value) { this->selected_ = value; + if (value) + { + auto *splitNotebook = dynamic_cast(this->notebook_); + if (splitNotebook) + { + for (int i = 0; i < splitNotebook->getPageCount(); ++i) + { + auto *splitContainer = + dynamic_cast(splitNotebook->getPageAt(i)); + if (splitContainer) + { + auto *tab = splitContainer->getTab(); + if (tab && tab != this) + { + tab->removeHighlightStateChangeSources( + this->highlightSources_); + tab->updateHighlightStateDueSourcesChange(); + } + } + } + } + } + + this->highlightSources_.clear(); this->highlightState_ = HighlightState::None; this->update(); @@ -358,13 +491,82 @@ bool NotebookTab::isLive() const return this->isLive_; } +HighlightState NotebookTab::highlightState() const +{ + return this->highlightState_; +} + void NotebookTab::setHighlightState(HighlightState newHighlightStyle) { if (this->isSelected()) { + assert(this->highlightSources_.highlightedSource.empty()); + assert(this->highlightSources_.newMessageSource.empty()); + assert(this->highlightState_ == HighlightState::None); return; } + this->highlightSources_.clear(); + + if (!this->highlightEnabled_ && + newHighlightStyle == HighlightState::NewMessage) + { + return; + } + + if (this->highlightState_ == newHighlightStyle || + this->highlightState_ == HighlightState::Highlighted) + { + return; + } + + this->highlightState_ = newHighlightStyle; + this->update(); +} + +void NotebookTab::updateHighlightState(HighlightState newHighlightStyle, + const ChannelView &channelViewSource, + const MessagePtr &message) +{ + if (this->isSelected()) + { + assert(this->highlightSources_.highlightedSource.empty()); + assert(this->highlightSources_.newMessageSource.empty()); + assert(this->highlightState_ == HighlightState::None); + return; + } + + if (!this->shouldMessageHighlight(channelViewSource, message)) + { + return; + } + + auto underlyingChannel = channelViewSource.underlyingChannel(); + + switch (newHighlightStyle) + { + case HighlightState::Highlighted: { + if (!this->highlightSources_.highlightedSource.contains( + underlyingChannel)) + { + this->highlightSources_.highlightedSource.insert( + underlyingChannel); + } + break; + } + case HighlightState::NewMessage: { + if (!this->highlightSources_.newMessageSource.contains( + underlyingChannel)) + { + this->highlightSources_.newMessageSource.insert( + underlyingChannel); + } + break; + } + case HighlightState::None: + break; + } + if (!this->highlightEnabled_ && newHighlightStyle == HighlightState::NewMessage) { @@ -381,9 +583,37 @@ void NotebookTab::setHighlightState(HighlightState newHighlightStyle) this->update(); } -HighlightState NotebookTab::highlightState() const +bool NotebookTab::shouldMessageHighlight(const ChannelView &channelViewSource, + const MessagePtr &message) const { - return this->highlightState_; + auto *visibleSplitContainer = + dynamic_cast(this->notebook_->getSelectedPage()); + if (visibleSplitContainer != nullptr) + { + const auto &visibleSplits = visibleSplitContainer->getSplits(); + for (const auto &visibleSplit : visibleSplits) + { + auto filterIdsSource = channelViewSource.getFilterIds(); + auto filterIdsSplit = visibleSplit->getChannelView().getFilterIds(); + + auto isSubset = [](const QList &sub, + const QList &super) { + return std::ranges::all_of(sub, [&super](const auto &subItem) { + return super.contains(subItem); + }); + }; + + if (channelViewSource.underlyingChannel() == + visibleSplit->getChannel() && + visibleSplit->getChannelView().shouldIncludeMessage(message) && + isSubset(filterIdsSource, filterIdsSplit)) + { + return false; + } + } + } + + return true; } void NotebookTab::setHighlightsEnabled(const bool &newVal) diff --git a/src/widgets/helper/NotebookTab.hpp b/src/widgets/helper/NotebookTab.hpp index 6ae7802d0..cfd2e1d34 100644 --- a/src/widgets/helper/NotebookTab.hpp +++ b/src/widgets/helper/NotebookTab.hpp @@ -14,6 +14,7 @@ namespace chatterino { inline constexpr int NOTEBOOK_TAB_HEIGHT = 28; class SplitContainer; +class ChannelView; class NotebookTab : public Button { @@ -59,11 +60,25 @@ public: **/ bool isLive() const; + /** + * @brief Sets the highlight state of this tab clearing highlight sources + * + * Obeys the HighlightsEnabled setting and highlight states hierarchy + */ void setHighlightState(HighlightState style); - HighlightState highlightState() const; - + /** + * @brief Updates the highlight state and highlight sources of this tab + * + * Obeys the HighlightsEnabled setting and the highlight state hierarchy and tracks the highlight state update sources + */ + void updateHighlightState(HighlightState style, + const ChannelView &channelViewSource, + const MessagePtr &message); + void copyHighlightStateAndSourcesFrom(const NotebookTab *sourceTab); void setHighlightsEnabled(const bool &newVal); + void newHighlightSourceAdded(const ChannelPtr &source); bool hasHighlightsEnabled() const; + HighlightState highlightState() const; void moveAnimated(QPoint targetPos, bool animated = true); @@ -107,6 +122,26 @@ private: int normalTabWidthForHeight(int height) const; + bool shouldMessageHighlight(const ChannelView &channelViewSource, + const MessagePtr &message) const; + + struct HighlightSources { + std::unordered_set newMessageSource; + std::unordered_set highlightedSource; + + void clear() + { + this->newMessageSource.clear(); + this->highlightedSource.clear(); + } + + } highlightSources_; + + void removeHighlightStateChangeSources(const HighlightSources &toRemove); + void removeNewMessageSource(const ChannelPtr &source); + void removeHighlightedSource(const ChannelPtr &source); + void updateHighlightStateDueSourcesChange(); + QPropertyAnimation positionChangedAnimation_; QPoint positionAnimationDesiredPoint_; diff --git a/src/widgets/splits/SplitContainer.cpp b/src/widgets/splits/SplitContainer.cpp index c2ddc1160..4e430c6b0 100644 --- a/src/widgets/splits/SplitContainer.cpp +++ b/src/widgets/splits/SplitContainer.cpp @@ -213,13 +213,25 @@ void SplitContainer::addSplit(Split *split) auto &&conns = this->connectionsPerSplit_[split]; - conns.managedConnect(split->getChannelView().tabHighlightRequested, - [this](HighlightState state) { - if (this->tab_ != nullptr) - { - this->tab_->setHighlightState(state); - } - }); + conns.managedConnect( + split->getChannelView().tabHighlightRequested, + [this, split](HighlightState state, const MessagePtr &message) { + if (this->tab_ != nullptr) + { + this->tab_->updateHighlightState(state, split->getChannelView(), + message); + } + }); + + conns.managedConnect(split->channelChanged, [this, split] { + qDebug() << "Changing Channel" + << split->getChannelView().underlyingChannel()->getName(); + if (this->tab_ != nullptr) + { + this->tab_->newHighlightSourceAdded( + split->getChannelView().underlyingChannel()); + } + }); conns.managedConnect(split->getChannelView().liveStatusChanged, [this]() { this->refreshTabLiveStatus();