Add setting to only show tabs with live channels (#4358)

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
Daniel Sage 2023-06-11 02:34:28 -07:00 committed by GitHub
parent c907f2b170
commit 4361790fbd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 412 additions and 136 deletions

View file

@ -7,6 +7,7 @@
- Minor: Added `/shoutout <username>` commands to shoutout specified user. (#4638)
- Minor: Improved editing hotkeys. (#4628)
- Minor: The input completion and quick switcher are now styled to match your theme. (#4671)
- Minor: Added setting to only show tabs with live channels (default toggle hotkey: Ctrl+Shift+L). (#4358)
- Bugfix: Fixed generation of crashdumps by the browser-extension process when the browser was closed. (#4667)
- Bugfix: Fix spacing issue with mentions inside RTL text. (#4677)
- Bugfix: Fixed a crash when opening and closing a reply thread and switching the user. (#4675)

View file

@ -324,13 +324,18 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
{"setTabVisibility",
ActionDefinition{
.displayName = "Set tab visibility",
.argumentDescription = "[on, off, or toggle. default: toggle]",
.argumentDescription = "[on, off, toggle, liveOnly, or "
"toggleLiveOnly. default: toggle]",
.minCountArguments = 0,
.maxCountArguments = 1,
.possibleArguments = HOTKEY_ARG_ON_OFF_TOGGLE,
.possibleArguments{{"Toggle", {}},
{"Set to on", {"on"}},
{"Set to off", {"off"}},
{"Live only on", {"liveOnly"}},
{"Live only toggle", {"toggleLiveOnly"}}},
.argumentsPrompt = "New value:",
.argumentsPromptHover =
"Should the tabs be enabled, disabled or toggled.",
.argumentsPromptHover = "Should the tabs be enabled, disabled, "
"toggled, or live-only.",
}},
}},
};

View file

@ -500,6 +500,10 @@ void HotkeyController::addDefaults(std::set<QString> &addedHotkeys)
this->tryAddDefault(addedHotkeys, HotkeyCategory::Window,
QKeySequence("Ctrl+U"), "setTabVisibility",
{"toggle"}, "toggle tab visibility");
this->tryAddDefault(addedHotkeys, HotkeyCategory::Window,
QKeySequence("Ctrl+Shift+L"), "setTabVisibility",
{"toggleLiveOnly"}, "toggle live tabs only");
}
}

View file

@ -126,6 +126,10 @@ public:
EnumSetting<NotebookTabLocation> tabDirection = {"/appearance/tabDirection",
NotebookTabLocation::Top};
EnumSetting<NotebookTabVisibility> tabVisibility = {
"/appearance/tabVisibility",
NotebookTabVisibility::AllTabs,
};
// BoolSetting collapseLongMessages =
// {"/appearance/messages/collapseLongMessages", false};

View file

@ -92,9 +92,7 @@ NotebookTab *Notebook::addPage(QWidget *page, QString title, bool select)
}
this->performLayout();
tab->show();
tab->setVisible(this->shouldShowTab(tab));
return tab;
}
@ -216,6 +214,7 @@ void Notebook::select(QWidget *page, bool focusPage)
this->selectedPage_ = page;
this->performLayout();
this->updateTabVisibility();
}
bool Notebook::containsPage(QWidget *page)
@ -262,38 +261,102 @@ void Notebook::selectIndex(int index, bool focusPage)
this->select(this->items_[index].page, focusPage);
}
void Notebook::selectVisibleIndex(int index, bool focusPage)
{
if (!this->tabVisibilityFilter_)
{
this->selectIndex(index, focusPage);
return;
}
int i = 0;
for (auto &item : this->items_)
{
if (this->tabVisibilityFilter_(item.tab))
{
if (i == index)
{
// found the index'th visible page
this->select(item.page, focusPage);
return;
}
++i;
}
}
}
void Notebook::selectNextTab(bool focusPage)
{
if (this->items_.size() <= 1)
const int size = this->items_.size();
if (!this->tabVisibilityFilter_)
{
if (size <= 1)
{
return;
}
auto index =
(this->indexOf(this->selectedPage_) + 1) % this->items_.count();
auto index = (this->indexOf(this->selectedPage_) + 1) % size;
this->select(this->items_[index].page, focusPage);
return;
}
// find next tab that is permitted by filter
const int startIndex = this->indexOf(this->selectedPage_);
auto index = (startIndex + 1) % size;
while (index != startIndex)
{
if (this->tabVisibilityFilter_(this->items_[index].tab))
{
this->select(this->items_[index].page, focusPage);
return;
}
index = (index + 1) % size;
}
}
void Notebook::selectPreviousTab(bool focusPage)
{
if (this->items_.size() <= 1)
const int size = this->items_.size();
if (!this->tabVisibilityFilter_)
{
if (size <= 1)
{
return;
}
int index = this->indexOf(this->selectedPage_) - 1;
if (index < 0)
{
index += this->items_.count();
index += size;
}
this->select(this->items_[index].page, focusPage);
return;
}
// find next previous tab that is permitted by filter
const int startIndex = this->indexOf(this->selectedPage_);
auto index = startIndex == 0 ? size - 1 : startIndex - 1;
while (index != startIndex)
{
if (this->tabVisibilityFilter_(this->items_[index].tab))
{
this->select(this->items_[index].page, focusPage);
return;
}
index = index == 0 ? size - 1 : index - 1;
}
}
void Notebook::selectLastTab(bool focusPage)
{
if (!this->tabVisibilityFilter_)
{
const auto size = this->items_.size();
if (size <= 1)
{
@ -301,6 +364,18 @@ void Notebook::selectLastTab(bool focusPage)
}
this->select(this->items_[size - 1].page, focusPage);
return;
}
// find first tab permitted by filter starting from the end
for (auto it = this->items_.rbegin(); it != this->items_.rend(); ++it)
{
if (this->tabVisibilityFilter_(it->tab))
{
this->select(it->page, focusPage);
return;
}
}
}
int Notebook::getPageCount() const
@ -329,6 +404,12 @@ QWidget *Notebook::tabAt(QPoint point, int &index, int maxWidth)
for (auto &item : this->items_)
{
if (!item.tab->isVisible())
{
i++;
continue;
}
auto rect = item.tab->getDesiredRect();
rect.setHeight(int(this->scale() * 24));
@ -381,20 +462,23 @@ void Notebook::setShowTabs(bool value)
{
this->showTabs_ = value;
this->performLayout();
for (auto &item : this->items_)
{
item.tab->setHidden(!value);
}
this->setShowAddButton(value);
this->performLayout();
this->updateTabVisibility();
this->updateTabVisibilityMenuAction();
// show a popup upon hiding tabs
if (!value && getSettings()->informOnTabVisibilityToggle.getValue())
{
this->showTabVisibilityInfoPopup();
}
}
void Notebook::showTabVisibilityInfoPopup()
{
auto unhideSeq = getApp()->hotkeys->getDisplaySequence(
HotkeyCategory::Window, "setTabVisibility",
{std::vector<QString>()});
HotkeyCategory::Window, "setTabVisibility", {std::vector<QString>()});
if (unhideSeq.isEmpty())
{
unhideSeq = getApp()->hotkeys->getDisplaySequence(
@ -409,8 +493,7 @@ void Notebook::setShowTabs(bool value)
if (!unhideSeq.isEmpty())
{
hotkeyInfo =
"(" +
unhideSeq.toString(QKeySequence::SequenceFormat::NativeText) +
"(" + unhideSeq.toString(QKeySequence::SequenceFormat::NativeText) +
")";
}
QMessageBox msgBox(this->window());
@ -432,8 +515,20 @@ void Notebook::setShowTabs(bool value)
{
getSettings()->informOnTabVisibilityToggle.setValue(false);
}
}
void Notebook::refresh()
{
this->performLayout();
this->updateTabVisibility();
}
void Notebook::updateTabVisibility()
{
for (auto &item : this->items_)
{
item.tab->setVisible(this->shouldShowTab(item.tab));
}
updateTabVisibilityMenuAction();
}
void Notebook::updateTabVisibilityMenuAction()
@ -510,6 +605,21 @@ void Notebook::performLayout(bool animated)
const auto buttonWidth = tabHeight;
const auto buttonHeight = tabHeight - 1;
std::vector<Item> filteredItems;
filteredItems.reserve(this->items_.size());
if (this->tabVisibilityFilter_)
{
std::copy_if(this->items_.begin(), this->items_.end(),
std::back_inserter(filteredItems),
[this](const auto &item) {
return this->tabVisibilityFilter_(item.tab);
});
}
else
{
filteredItems.assign(this->items_.begin(), this->items_.end());
}
if (this->tabLocation_ == NotebookTabLocation::Top)
{
auto x = left;
@ -535,14 +645,14 @@ void Notebook::performLayout(bool animated)
{
// layout tabs
/// Notebook tabs need to know if they are in the last row.
auto firstInBottomRow =
this->items_.size() ? &this->items_.front() : nullptr;
auto *firstInBottomRow =
filteredItems.empty() ? nullptr : &filteredItems.front();
for (auto &item : this->items_)
for (auto &item : filteredItems)
{
/// Break line if element doesn't fit.
auto isFirst = &item == &this->items_.front();
auto isLast = &item == &this->items_.back();
auto isFirst = &item == &filteredItems.front();
auto isLast = &item == &filteredItems.back();
auto fitsInLine = ((isLast ? addButtonWidth : 0) + x +
item.tab->width()) <= width();
@ -562,7 +672,7 @@ void Notebook::performLayout(bool animated)
/// Update which tabs are in the last row
auto inLastRow = false;
for (const auto &item : this->items_)
for (const auto &item : filteredItems)
{
if (&item == firstInBottomRow)
{
@ -633,7 +743,7 @@ void Notebook::performLayout(bool animated)
{
return;
}
int count = this->items_.size() + (this->showAddButton_ ? 1 : 0);
int count = filteredItems.size() + (this->showAddButton_ ? 1 : 0);
int columnCount = ceil((float)count / tabsPerColumn);
// only add width of all the tabs if they are not hidden
@ -644,13 +754,15 @@ void Notebook::performLayout(bool animated)
bool isLastColumn = col == columnCount - 1;
auto largestWidth = 0;
int tabStart = col * tabsPerColumn;
int tabEnd = std::min((col + 1) * tabsPerColumn,
(int)this->items_.size());
int tabEnd =
std::min(static_cast<size_t>((col + 1) * tabsPerColumn),
filteredItems.size());
for (int i = tabStart; i < tabEnd; i++)
{
largestWidth = std::max(
this->items_.at(i).tab->normalTabWidth(), largestWidth);
largestWidth =
std::max(filteredItems.at(i).tab->normalTabWidth(),
largestWidth);
}
if (isLastColumn && this->showAddButton_)
@ -664,7 +776,7 @@ void Notebook::performLayout(bool animated)
for (int i = tabStart; i < tabEnd; i++)
{
auto item = this->items_.at(i);
auto item = filteredItems.at(i);
/// Layout tab
item.tab->growWidth(largestWidth);
@ -735,7 +847,7 @@ void Notebook::performLayout(bool animated)
{
return;
}
int count = this->items_.size() + (this->showAddButton_ ? 1 : 0);
int count = filteredItems.size() + (this->showAddButton_ ? 1 : 0);
int columnCount = ceil((float)count / tabsPerColumn);
// only add width of all the tabs if they are not hidden
@ -746,13 +858,15 @@ void Notebook::performLayout(bool animated)
bool isLastColumn = col == columnCount - 1;
auto largestWidth = 0;
int tabStart = col * tabsPerColumn;
int tabEnd = std::min((col + 1) * tabsPerColumn,
(int)this->items_.size());
int tabEnd =
std::min(static_cast<size_t>((col + 1) * tabsPerColumn),
filteredItems.size());
for (int i = tabStart; i < tabEnd; i++)
{
largestWidth = std::max(
this->items_.at(i).tab->normalTabWidth(), largestWidth);
largestWidth =
std::max(filteredItems.at(i).tab->normalTabWidth(),
largestWidth);
}
if (isLastColumn && this->showAddButton_)
@ -771,7 +885,7 @@ void Notebook::performLayout(bool animated)
for (int i = tabStart; i < tabEnd; i++)
{
auto item = this->items_.at(i);
auto item = filteredItems.at(i);
/// Layout tab
item.tab->growWidth(largestWidth);
@ -840,14 +954,14 @@ void Notebook::performLayout(bool animated)
// layout tabs
/// Notebook tabs need to know if they are in the last row.
auto firstInBottomRow =
this->items_.size() ? &this->items_.front() : nullptr;
auto *firstInBottomRow =
filteredItems.empty() ? nullptr : &filteredItems.front();
for (auto &item : this->items_)
for (auto &item : filteredItems)
{
/// Break line if element doesn't fit.
auto isFirst = &item == &this->items_.front();
auto isLast = &item == &this->items_.back();
auto isFirst = &item == &filteredItems.front();
auto isLast = &item == &filteredItems.back();
auto fitsInLine = ((isLast ? addButtonWidth : 0) + x +
item.tab->width()) <= width();
@ -867,7 +981,7 @@ void Notebook::performLayout(bool animated)
/// Update which tabs are in the last row
auto inLastRow = false;
for (const auto &item : this->items_)
for (const auto &item : filteredItems)
{
if (&item == firstInBottomRow)
{
@ -1046,6 +1160,28 @@ size_t Notebook::visibleButtonCount() const
return i;
}
void Notebook::setTabVisibilityFilter(TabVisibilityFilter filter)
{
this->tabVisibilityFilter_ = std::move(filter);
this->performLayout();
this->updateTabVisibility();
}
bool Notebook::shouldShowTab(const NotebookTab *tab) const
{
if (!this->showTabs_)
{
return false;
}
if (this->tabVisibilityFilter_)
{
return this->tabVisibilityFilter_(tab);
}
return true;
}
SplitNotebook::SplitNotebook(Window *parent)
: Notebook(parent)
{
@ -1061,6 +1197,24 @@ SplitNotebook::SplitNotebook(Window *parent)
this->addCustomButtons();
}
getSettings()->tabVisibility.connect(
[this](int val, auto) {
auto visibility = NotebookTabVisibility(val);
switch (visibility)
{
case NotebookTabVisibility::LiveOnly:
this->setTabVisibilityFilter([](const NotebookTab *tab) {
return tab->isLive() || tab->isSelected();
});
break;
case NotebookTabVisibility::AllTabs:
default:
this->setTabVisibilityFilter(nullptr);
break;
}
},
this->signalHolder_, true);
this->signalHolder_.managedConnect(
getApp()->windows->selectSplit, [this](Split *split) {
for (auto &&item : this->items())
@ -1204,7 +1358,6 @@ SplitContainer *SplitNotebook::addPage(bool select)
auto tab = Notebook::addPage(container, QString(), select);
container->setTab(tab);
tab->setParent(this);
tab->setVisible(this->getShowTabs());
return container;
}

View file

@ -9,6 +9,8 @@
#include <QMessageBox>
#include <QWidget>
#include <functional>
namespace chatterino {
class Window;
@ -19,6 +21,17 @@ class SplitContainer;
enum NotebookTabLocation { Top = 0, Left = 1, Right = 2, Bottom = 3 };
// Controls the visibility of tabs in this notebook
enum NotebookTabVisibility : int {
// Show all tabs
AllTabs = 0,
// Only show tabs containing splits that are live
LiveOnly = 1,
};
using TabVisibilityFilter = std::function<bool(const NotebookTab *)>;
class Notebook : public BaseWidget
{
Q_OBJECT
@ -35,6 +48,7 @@ public:
int indexOf(QWidget *page) const;
virtual void select(QWidget *page, bool focusPage = true);
void selectIndex(int index, bool focusPage = true);
void selectVisibleIndex(int index, bool focusPage = true);
void selectNextTab(bool focusPage = true);
void selectPreviousTab(bool focusPage = true);
void selectLastTab(bool focusPage = true);
@ -56,8 +70,6 @@ public:
bool getShowAddButton() const;
void setShowAddButton(bool value);
void performLayout(bool animate = false);
void setTabLocation(NotebookTabLocation location);
bool isNotebookLayoutLocked() const;
@ -65,6 +77,9 @@ public:
void addNotebookActionsToMenu(QMenu *menu);
// Update layout and tab visibility
void refresh();
protected:
virtual void scaleChangedEvent(float scale_) override;
virtual void resizeEvent(QResizeEvent *) override;
@ -85,7 +100,32 @@ protected:
return items_;
}
/**
* @brief Apply the given tab visibility filter
*
* An empty function can be provided to denote that no filter will be applied
*
* Tabs will be redrawn after this function is called.
**/
void setTabVisibilityFilter(TabVisibilityFilter filter);
/**
* @brief shouldShowTab has the final say whether a tab should be visible right now.
**/
bool shouldShowTab(const NotebookTab *tab) const;
private:
void performLayout(bool animate = false);
/**
* @brief Show a popup informing the user of some big tab visibility changes
**/
void showTabVisibilityInfoPopup();
/**
* @brief Updates the visibility state of all tabs
**/
void updateTabVisibility();
void updateTabVisibilityMenuAction();
void resizeAddButton();
@ -113,6 +153,10 @@ private:
NotebookTabLocation tabLocation_ = NotebookTabLocation::Top;
QAction *lockNotebookLayoutAction_;
QAction *showTabsAction_;
// This filter, if set, is used to figure out the visibility of
// the tabs in this notebook.
TabVisibilityFilter tabVisibilityFilter_;
};
class SplitNotebook : public Notebook

View file

@ -354,7 +354,7 @@ void Window::addShortcuts()
int result = target.toInt(&ok);
if (ok)
{
this->notebook_->selectIndex(result);
this->notebook_->selectVisibleIndex(result);
}
else
{
@ -619,46 +619,61 @@ void Window::addShortcuts()
}},
{"setTabVisibility",
[this](std::vector<QString> arguments) -> QString {
auto mode = 2;
if (arguments.size() != 0)
{
auto arg = arguments.at(0);
QString arg = arguments.empty() ? "toggle" : arguments.front();
if (arg == "off")
{
mode = 0;
this->notebook_->setShowTabs(false);
getSettings()->tabVisibility.setValue(
NotebookTabVisibility::AllTabs);
}
else if (arg == "on")
{
mode = 1;
this->notebook_->setShowTabs(true);
getSettings()->tabVisibility.setValue(
NotebookTabVisibility::AllTabs);
}
else if (arg == "toggle")
{
mode = 2;
this->notebook_->setShowTabs(!this->notebook_->getShowTabs());
getSettings()->tabVisibility.setValue(
NotebookTabVisibility::AllTabs);
}
else if (arg == "liveOnly")
{
this->notebook_->setShowTabs(true);
getSettings()->tabVisibility.setValue(
NotebookTabVisibility::LiveOnly);
}
else if (arg == "toggleLiveOnly")
{
if (!this->notebook_->getShowTabs())
{
// Tabs are currently hidden, so the intention is to show
// tabs again before enabling the live only setting
this->notebook_->setShowTabs(true);
getSettings()->tabVisibility.setValue(
NotebookTabVisibility::LiveOnly);
}
else
{
getSettings()->tabVisibility.setValue(
getSettings()->tabVisibility.getEnum() ==
NotebookTabVisibility::LiveOnly
? NotebookTabVisibility::AllTabs
: NotebookTabVisibility::LiveOnly);
}
}
else
{
qCWarning(chatterinoHotkeys)
<< "Invalid argument for setStreamerMode hotkey: "
<< arg;
return QString("Invalid argument for setTabVisibility "
"hotkey: %1. Use \"on\", \"off\" or "
"\"toggle\".")
<< "Invalid argument for setTabVisibility hotkey: " << arg;
return QString("Invalid argument for setTabVisibility hotkey: "
"%1. Use \"on\", \"off\", \"toggle\", "
"\"liveOnly\", or \"toggleLiveOnly\".")
.arg(arg);
}
}
if (mode == 0)
{
this->notebook_->setShowTabs(false);
}
else if (mode == 1)
{
this->notebook_->setShowTabs(true);
}
else if (mode == 2)
{
this->notebook_->setShowTabs(!this->notebook_->getShowTabs());
}
return "";
}},
};

View file

@ -310,7 +310,7 @@ void EmotePopup::addShortcuts()
int result = target.toInt(&ok);
if (ok)
{
this->notebook_->selectIndex(result, false);
this->notebook_->selectVisibleIndex(result, false);
}
else
{

View file

@ -211,12 +211,12 @@ void NotebookButton::dropEvent(QDropEvent *event)
void NotebookButton::hideEvent(QHideEvent *)
{
this->parent_->performLayout();
this->parent_->refresh();
}
void NotebookButton::showEvent(QShowEvent *)
{
this->parent_->performLayout();
this->parent_->refresh();
}
} // namespace chatterino

View file

@ -234,7 +234,7 @@ void NotebookTab::updateSize()
if (this->width() != width || this->height() != height)
{
this->resize(width, height);
this->notebook_->performLayout();
this->notebook_->refresh();
}
}
@ -290,7 +290,7 @@ void NotebookTab::titleUpdated()
{
// Queue up save because: Tab title changed
getApp()->windows->queueSave();
this->notebook_->performLayout();
this->notebook_->refresh();
this->updateSize();
this->update();
}
@ -327,13 +327,21 @@ void NotebookTab::setTabLocation(NotebookTabLocation location)
}
}
void NotebookTab::setLive(bool isLive)
bool NotebookTab::setLive(bool isLive)
{
if (this->isLive_ != isLive)
{
this->isLive_ = isLive;
this->update();
return true;
}
return false;
}
bool NotebookTab::isLive() const
{
return this->isLive_;
}
void NotebookTab::setHighlightState(HighlightState newHighlightStyle)

View file

@ -40,7 +40,18 @@ public:
void setInLastRow(bool value);
void setTabLocation(NotebookTabLocation location);
void setLive(bool isLive);
/**
* @brief Sets the live status of this tab
*
* Returns true if the live status was changed, false if nothing changed.
**/
bool setLive(bool isLive);
/**
* @brief Returns true if any split in this tab is live
**/
bool isLive() const;
void setHighlightState(HighlightState style);
void setHighlightsEnabled(const bool &newVal);
bool hasHighlightsEnabled() const;

View file

@ -199,6 +199,30 @@ void GeneralPage::initLayout(GeneralPageView &layout)
tabDirectionDropdown->setMinimumWidth(
tabDirectionDropdown->minimumSizeHint().width());
layout.addDropdown<std::underlying_type<NotebookTabVisibility>::type>(
"Tab visibility", {"All tabs", "Only live tabs"}, s.tabVisibility,
[](auto val) {
switch (val)
{
case NotebookTabVisibility::LiveOnly:
return "Only live tabs";
case NotebookTabVisibility::AllTabs:
default:
return "All tabs";
}
},
[](auto args) {
if (args.value == "Only live tabs")
{
return NotebookTabVisibility::LiveOnly;
}
else
{
return NotebookTabVisibility::AllTabs;
}
},
false, "Choose which tabs are visible in the notebook");
layout.addCheckbox(
"Show message reply context", s.hideReplyContext, true,
"This setting will only affect how messages are shown. You can reply "

View file

@ -935,7 +935,14 @@ void SplitContainer::refreshTabLiveStatus()
}
}
this->tab_->setLive(liveStatus);
if (this->tab_->setLive(liveStatus))
{
auto *notebook = dynamic_cast<Notebook *>(this->parentWidget());
if (notebook)
{
notebook->refresh();
}
}
}
//