This commit is contained in:
hemirt 2024-10-22 18:42:24 +02:00 committed by GitHub
commit 2d5b6e4c33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 345 additions and 19 deletions

View file

@ -37,6 +37,7 @@
- Minor: Indicate when subscriptions and resubscriptions are for multiple months. (#5642) - Minor: Indicate when subscriptions and resubscriptions are for multiple months. (#5642)
- Minor: Proxy URL information is now included in the `/debug-env` command. (#5648) - Minor: Proxy URL information is now included in the `/debug-env` command. (#5648)
- Minor: Make raid entry message usernames clickable. (#5651) - Minor: Make raid entry message usernames clickable. (#5651)
- Minor: Tabs unhighlight when their content is read in other tabs. (#5649)
- Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426, #5612) - Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426, #5612)
- Bugfix: If a network request errors with 200 OK, Qt's error code is now reported instead of the HTTP status. (#5378) - Bugfix: If a network request errors with 200 OK, Qt's error code is now reported instead of the HTTP status. (#5378)
- Bugfix: Fixed restricted users usernames not being clickable. (#5405) - Bugfix: Fixed restricted users usernames not being clickable. (#5405)

View file

@ -204,7 +204,7 @@ void Notebook::duplicatePage(QWidget *page)
{ {
newTabPosition = tabPosition + 1; newTabPosition = tabPosition + 1;
} }
auto newTabHighlightState = item->tab->highlightState();
QString newTabTitle = ""; QString newTabTitle = "";
if (item->tab->hasCustomTitle()) if (item->tab->hasCustomTitle())
{ {
@ -213,7 +213,7 @@ void Notebook::duplicatePage(QWidget *page)
auto *tab = auto *tab =
this->addPageAt(newContainer, newTabPosition, newTabTitle, false); this->addPageAt(newContainer, newTabPosition, newTabTitle, false);
tab->setHighlightState(newTabHighlightState); tab->copyHighlightStateAndSourcesFrom(item->tab);
newContainer->setTab(tab); newContainer->setTab(tab);
} }

View file

@ -2,6 +2,7 @@
#include "widgets/BaseWidget.hpp" #include "widgets/BaseWidget.hpp"
#include <ForwardDecl.hpp>
#include <pajlada/signals/signal.hpp> #include <pajlada/signals/signal.hpp>
#include <pajlada/signals/signalholder.hpp> #include <pajlada/signals/signalholder.hpp>
#include <QList> #include <QList>

View file

@ -1190,11 +1190,13 @@ void ChannelView::messageAppended(MessagePtr &message,
(this->channel_->getType() == Channel::Type::TwitchAutomod && (this->channel_->getType() == Channel::Type::TwitchAutomod &&
getSettings()->enableAutomodHighlight)) getSettings()->enableAutomodHighlight))
{ {
this->tabHighlightRequested.invoke(HighlightState::Highlighted); this->tabHighlightRequested.invoke(HighlightState::Highlighted,
message);
} }
else else
{ {
this->tabHighlightRequested.invoke(HighlightState::NewMessage); this->tabHighlightRequested.invoke(HighlightState::NewMessage,
message);
} }
} }

View file

@ -179,6 +179,9 @@ public:
LimitedQueueSnapshot<MessageLayoutPtr> &getMessagesSnapshot(); LimitedQueueSnapshot<MessageLayoutPtr> &getMessagesSnapshot();
// Returns true if message should be included
bool shouldIncludeMessage(const MessagePtr &m) const;
void queueLayout(); void queueLayout();
void invalidateBuffers(); void invalidateBuffers();
@ -214,7 +217,8 @@ public:
pajlada::Signals::Signal<QMouseEvent *> mouseDown; pajlada::Signals::Signal<QMouseEvent *> mouseDown;
pajlada::Signals::NoArgSignal selectionChanged; pajlada::Signals::NoArgSignal selectionChanged;
pajlada::Signals::Signal<HighlightState> tabHighlightRequested; pajlada::Signals::Signal<HighlightState, const MessagePtr &>
tabHighlightRequested;
pajlada::Signals::NoArgSignal liveStatusChanged; pajlada::Signals::NoArgSignal liveStatusChanged;
pajlada::Signals::Signal<const Link &> linkClicked; pajlada::Signals::Signal<const Link &> linkClicked;
pajlada::Signals::Signal<QString, FromTwitchLinkOpenChannelIn> pajlada::Signals::Signal<QString, FromTwitchLinkOpenChannelIn>
@ -374,9 +378,6 @@ private:
FilterSetPtr channelFilters_; FilterSetPtr channelFilters_;
// Returns true if message should be included
bool shouldIncludeMessage(const MessagePtr &m) const;
// Returns whether the scrollbar should have highlights // Returns whether the scrollbar should have highlights
bool showScrollbarHighlights() const; bool showScrollbarHighlights() const;

View file

@ -1,6 +1,7 @@
#include "widgets/helper/NotebookTab.hpp" #include "widgets/helper/NotebookTab.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "common/Channel.hpp"
#include "common/Common.hpp" #include "common/Common.hpp"
#include "controllers/hotkeys/HotkeyCategory.hpp" #include "controllers/hotkeys/HotkeyCategory.hpp"
#include "controllers/hotkeys/HotkeyController.hpp" #include "controllers/hotkeys/HotkeyController.hpp"
@ -12,9 +13,11 @@
#include "widgets/dialogs/SettingsDialog.hpp" #include "widgets/dialogs/SettingsDialog.hpp"
#include "widgets/Notebook.hpp" #include "widgets/Notebook.hpp"
#include "widgets/splits/DraggedSplit.hpp" #include "widgets/splits/DraggedSplit.hpp"
#include "widgets/splits/Split.hpp"
#include "widgets/splits/SplitContainer.hpp" #include "widgets/splits/SplitContainer.hpp"
#include <boost/bind/bind.hpp> #include <boost/bind/bind.hpp>
#include <boost/container_hash/hash.hpp>
#include <QAbstractAnimation> #include <QAbstractAnimation>
#include <QApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
@ -53,6 +56,27 @@ namespace {
} }
} // namespace } // namespace
std::size_t NotebookTab::HighlightSources::ChannelViewProxyHash::operator()(
const ChannelViewProxy &cp) const noexcept
{
std::size_t seed = 0;
auto first = qHash(cp.channelView->underlyingChannel()->getName());
auto second = qHash(cp.channelView->getFilterIds());
boost::hash_combine(seed, first);
boost::hash_combine(seed, second);
return seed;
}
bool NotebookTab::HighlightSources::ChannelViewProxyEqual::operator()(
const ChannelViewProxy &lp, const ChannelViewProxy &rp) const
{
return lp.channelView->underlyingChannel() ==
rp.channelView->underlyingChannel() &&
lp.channelView->getFilterIds() == rp.channelView->getFilterIds();
}
NotebookTab::NotebookTab(Notebook *notebook) NotebookTab::NotebookTab(Notebook *notebook)
: Button(notebook) : Button(notebook)
, positionChangedAnimation_(this, "pos") , positionChangedAnimation_(this, "pos")
@ -302,10 +326,146 @@ bool NotebookTab::isSelected() const
return this->selected_; return this->selected_;
} }
void NotebookTab::removeNewMessageSource(
const HighlightSources::ChannelViewProxy &source)
{
this->highlightSources_.newMessageSource.erase(source);
}
void NotebookTab::removeHighlightedSource(
const HighlightSources::ChannelViewProxy &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 ChannelView &channelViewSource)
{
auto channelViewProxy =
HighlightSources::ChannelViewProxy{&channelViewSource};
auto sourceChannel = channelViewSource.underlyingChannel();
this->removeHighlightedSource(channelViewProxy);
this->removeNewMessageSource(channelViewProxy);
this->updateHighlightStateDueSourcesChange();
auto *splitNotebook = dynamic_cast<SplitNotebook *>(this->notebook_);
if (splitNotebook)
{
for (int i = 0; i < splitNotebook->getPageCount(); ++i)
{
auto *splitContainer =
dynamic_cast<SplitContainer *>(splitNotebook->getPageAt(i));
if (splitContainer)
{
auto *tab = splitContainer->getTab();
if (tab && tab != this)
{
tab->removeHighlightedSource(channelViewProxy);
tab->removeNewMessageSource(channelViewProxy);
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) void NotebookTab::setSelected(bool value)
{ {
this->selected_ = value; this->selected_ = value;
if (value)
{
auto *splitNotebook = dynamic_cast<SplitNotebook *>(this->notebook_);
if (splitNotebook)
{
for (int i = 0; i < splitNotebook->getPageCount(); ++i)
{
auto *splitContainer =
dynamic_cast<SplitContainer *>(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->highlightState_ = HighlightState::None;
this->update(); this->update();
@ -358,13 +518,23 @@ bool NotebookTab::isLive() const
return this->isLive_; return this->isLive_;
} }
HighlightState NotebookTab::highlightState() const
{
return this->highlightState_;
}
void NotebookTab::setHighlightState(HighlightState newHighlightStyle) void NotebookTab::setHighlightState(HighlightState newHighlightStyle)
{ {
if (this->isSelected()) if (this->isSelected())
{ {
assert(this->highlightSources_.highlightedSource.empty());
assert(this->highlightSources_.newMessageSource.empty());
assert(this->highlightState_ == HighlightState::None);
return; return;
} }
this->highlightSources_.clear();
if (!this->highlightEnabled_ && if (!this->highlightEnabled_ &&
newHighlightStyle == HighlightState::NewMessage) newHighlightStyle == HighlightState::NewMessage)
{ {
@ -381,9 +551,96 @@ void NotebookTab::setHighlightState(HighlightState newHighlightStyle)
this->update(); this->update();
} }
HighlightState NotebookTab::highlightState() const void NotebookTab::updateHighlightState(HighlightState newHighlightStyle,
const ChannelView &channelViewSource,
const MessagePtr &message)
{ {
return this->highlightState_; 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;
}
if (!this->highlightEnabled_ &&
newHighlightStyle == HighlightState::NewMessage)
{
return;
}
// message is highlighting unvisible tab
auto underlyingChannel = channelViewSource.underlyingChannel();
auto newFilters = channelViewSource.getFilterIds();
auto channelViewProxy =
HighlightSources::ChannelViewProxy{&channelViewSource};
// the unvisible tab should unhighlight other tabs iff
// the other tab's filters are more generic therefore
// the other tab's filter set is subset of the unvisible tab
switch (newHighlightStyle)
{
case HighlightState::Highlighted: {
if (!this->highlightSources_.highlightedSource.contains(
channelViewProxy))
{
this->highlightSources_.highlightedSource.insert(
channelViewProxy);
}
break;
}
case HighlightState::NewMessage: {
if (!this->highlightSources_.newMessageSource.contains(
channelViewProxy))
{
this->highlightSources_.newMessageSource.insert(
channelViewProxy);
}
break;
}
case HighlightState::None:
break;
}
if (this->highlightState_ == newHighlightStyle ||
this->highlightState_ == HighlightState::Highlighted)
{
return;
}
this->highlightState_ = newHighlightStyle;
this->update();
}
bool NotebookTab::shouldMessageHighlight(const ChannelView &channelViewSource,
const MessagePtr &message) const
{
auto *visibleSplitContainer =
dynamic_cast<SplitContainer *>(this->notebook_->getSelectedPage());
if (visibleSplitContainer != nullptr)
{
const auto &visibleSplits = visibleSplitContainer->getSplits();
for (const auto &visibleSplit : visibleSplits)
{
if (channelViewSource.underlyingChannel() ==
visibleSplit->getChannel() &&
visibleSplit->getChannelView().shouldIncludeMessage(message) &&
channelViewSource.shouldIncludeMessage(message) &&
channelViewSource.getFilterIds().empty())
{
return false;
}
}
}
return true;
} }
void NotebookTab::setHighlightsEnabled(const bool &newVal) void NotebookTab::setHighlightsEnabled(const bool &newVal)

View file

@ -2,6 +2,7 @@
#include "common/Common.hpp" #include "common/Common.hpp"
#include "widgets/helper/Button.hpp" #include "widgets/helper/Button.hpp"
#include "widgets/helper/ChannelView.hpp"
#include "widgets/Notebook.hpp" #include "widgets/Notebook.hpp"
#include <pajlada/settings/setting.hpp> #include <pajlada/settings/setting.hpp>
@ -59,11 +60,25 @@ public:
**/ **/
bool isLive() const; 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); 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 setHighlightsEnabled(const bool &newVal);
void newHighlightSourceAdded(const ChannelView &channelViewSource);
bool hasHighlightsEnabled() const; bool hasHighlightsEnabled() const;
HighlightState highlightState() const;
void moveAnimated(QPoint targetPos, bool animated = true); void moveAnimated(QPoint targetPos, bool animated = true);
@ -107,6 +122,46 @@ private:
int normalTabWidthForHeight(int height) const; int normalTabWidthForHeight(int height) const;
bool shouldMessageHighlight(const ChannelView &channelViewSource,
const MessagePtr &message) const;
struct HighlightSources {
struct ChannelViewProxy {
const ChannelView *channelView;
};
struct ChannelViewProxyHash {
using is_transparent = void;
std::size_t operator()(const ChannelViewProxy &cp) const noexcept;
};
struct ChannelViewProxyEqual {
bool operator()(const ChannelViewProxy &l,
const ChannelViewProxy &r) const;
};
std::unordered_set<ChannelViewProxy, ChannelViewProxyHash,
ChannelViewProxyEqual>
newMessageSource;
std::unordered_set<ChannelViewProxy, ChannelViewProxyHash,
ChannelViewProxyEqual>
highlightedSource;
void clear()
{
this->newMessageSource.clear();
this->highlightedSource.clear();
}
} highlightSources_;
void removeHighlightStateChangeSources(const HighlightSources &toRemove);
void removeNewMessageSource(
const HighlightSources::ChannelViewProxy &source);
void removeHighlightedSource(
const HighlightSources::ChannelViewProxy &source);
void updateHighlightStateDueSourcesChange();
QPropertyAnimation positionChangedAnimation_; QPropertyAnimation positionChangedAnimation_;
QPoint positionAnimationDesiredPoint_; QPoint positionAnimationDesiredPoint_;

View file

@ -213,13 +213,22 @@ void SplitContainer::addSplit(Split *split)
auto &&conns = this->connectionsPerSplit_[split]; auto &&conns = this->connectionsPerSplit_[split];
conns.managedConnect(split->getChannelView().tabHighlightRequested, conns.managedConnect(
[this](HighlightState state) { split->getChannelView().tabHighlightRequested,
if (this->tab_ != nullptr) [this, split](HighlightState state, const MessagePtr &message) {
{ if (this->tab_ != nullptr)
this->tab_->setHighlightState(state); {
} this->tab_->updateHighlightState(state, split->getChannelView(),
}); message);
}
});
conns.managedConnect(split->channelChanged, [this, split] {
if (this->tab_ != nullptr)
{
this->tab_->newHighlightSourceAdded(split->getChannelView());
}
});
conns.managedConnect(split->getChannelView().liveStatusChanged, [this]() { conns.managedConnect(split->getChannelView().liveStatusChanged, [this]() {
this->refreshTabLiveStatus(); this->refreshTabLiveStatus();