From 7fbaed0f32d494a750ff5fa06c64b8e396cf1f13 Mon Sep 17 00:00:00 2001 From: Kleber Date: Sat, 30 Mar 2024 00:16:10 -0300 Subject: [PATCH 1/8] Add feature to duplicate tabs --- src/widgets/Notebook.cpp | 41 ++++++++++++++++++++++++++- src/widgets/Notebook.hpp | 9 ++++++ src/widgets/helper/NotebookTab.cpp | 4 +++ src/widgets/splits/SplitContainer.cpp | 28 ++++++++++++++++++ src/widgets/splits/SplitContainer.hpp | 2 ++ 5 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/widgets/Notebook.cpp b/src/widgets/Notebook.cpp index c16181f8072..0a7ceef71e9 100644 --- a/src/widgets/Notebook.cpp +++ b/src/widgets/Notebook.cpp @@ -90,6 +90,12 @@ Notebook::Notebook(QWidget *parent) } NotebookTab *Notebook::addPage(QWidget *page, QString title, bool select) +{ + return this->addPageAt(page, -1, std::move(title), select); +} + +NotebookTab *Notebook::addPageAt(QWidget *page, int position, QString title, + bool select) { // Queue up save because: Tab added getIApp()->getWindows()->queueSave(); @@ -104,7 +110,14 @@ NotebookTab *Notebook::addPage(QWidget *page, QString title, bool select) item.page = page; item.tab = tab; - this->items_.append(item); + if (position == -1) + { + this->items_.push_back(item); + } + else + { + this->items_.insert(position, item); + } page->hide(); page->setParent(this); @@ -168,6 +181,32 @@ void Notebook::removePage(QWidget *page) this->performLayout(true); } +void Notebook::duplicatePage(QWidget *page) +{ + auto *item = this->findItem(page); + assert(item != nullptr); + + auto *container = dynamic_cast(item->page); + if (!container) + { + return; + } + + auto descriptor = container->buildDescriptor(); + + auto *newContainer = new SplitContainer(this); + newContainer->applyFromDescriptor(descriptor); + + int newTabPosition = this->indexOf(page) + 1; + assert(newTabPosition != -1); + auto *tab = this->addPageAt( + newContainer, newTabPosition, + item->tab->hasCustomTitle() ? item->tab->getCustomTitle() : "", false); + tab->setHighlightState(item->tab->highlightState()); + + newContainer->setTab(tab); +} + void Notebook::removeCurrentPage() { if (this->selectedPage_ != nullptr) diff --git a/src/widgets/Notebook.hpp b/src/widgets/Notebook.hpp index 9aa694c66d6..dd83dee7c8e 100644 --- a/src/widgets/Notebook.hpp +++ b/src/widgets/Notebook.hpp @@ -42,7 +42,16 @@ class Notebook : public BaseWidget NotebookTab *addPage(QWidget *page, QString title = QString(), bool select = false); + + /** + * @brief Adds a page to the Notebook at a given position. + * + * @param position if set to -1, adds the page to the end + **/ + NotebookTab *addPageAt(QWidget *page, int position = -1, + QString title = QString(), bool select = false); void removePage(QWidget *page); + void duplicatePage(QWidget *page); void removeCurrentPage(); /** diff --git a/src/widgets/helper/NotebookTab.cpp b/src/widgets/helper/NotebookTab.cpp index da04562fae2..5c9a74a1468 100644 --- a/src/widgets/helper/NotebookTab.cpp +++ b/src/widgets/helper/NotebookTab.cpp @@ -107,6 +107,10 @@ NotebookTab::NotebookTab(Notebook *notebook) getIApp()->getHotkeys()->getDisplaySequence(HotkeyCategory::Window, "popup", {{"window"}})); + this->menu_.addAction("Duplicate Tab", [this]() { + this->notebook_->duplicatePage(this->page); + }); + highlightNewMessagesAction_ = new QAction("Mark Tab as Unread on New Messages", &this->menu_); highlightNewMessagesAction_->setCheckable(true); diff --git a/src/widgets/splits/SplitContainer.cpp b/src/widgets/splits/SplitContainer.cpp index 2a274c6cc82..6bb4070142b 100644 --- a/src/widgets/splits/SplitContainer.cpp +++ b/src/widgets/splits/SplitContainer.cpp @@ -762,6 +762,11 @@ SplitContainer::Node *SplitContainer::getBaseNode() return &this->baseNode_; } +NodeDescriptor SplitContainer::buildDescriptor() +{ + return this->buildDescriptorRecursively(&this->baseNode_); +} + void SplitContainer::applyFromDescriptor(const NodeDescriptor &rootNode) { assert(this->baseNode_.type_ == Node::Type::EmptyRoot); @@ -799,6 +804,29 @@ void SplitContainer::popup() window.show(); } +NodeDescriptor SplitContainer::buildDescriptorRecursively(Node *currentNode) +{ + if (currentNode->children_.empty()) + { + SplitNodeDescriptor result; + result.type_ = "twitch"; + result.channelName_ = currentNode->split_->getChannel()->getName(); + result.filters_ = currentNode->split_->getFilters(); + return result; + } + + ContainerNodeDescriptor descriptor; + for (auto &child : currentNode->children_) + { + descriptor.vertical_ = + currentNode->type_ == Node::Type::VerticalContainer; + descriptor.items_.push_back( + this->buildDescriptorRecursively(child.get())); + } + + return descriptor; +} + void SplitContainer::applyFromDescriptorRecursively( const NodeDescriptor &rootNode, Node *baseNode) { diff --git a/src/widgets/splits/SplitContainer.hpp b/src/widgets/splits/SplitContainer.hpp index 9022085dad8..310b5998e2b 100644 --- a/src/widgets/splits/SplitContainer.hpp +++ b/src/widgets/splits/SplitContainer.hpp @@ -220,6 +220,7 @@ class SplitContainer final : public BaseWidget void hideResizeHandles(); void resetMouseStatus(); + NodeDescriptor buildDescriptor(); void applyFromDescriptor(const NodeDescriptor &rootNode); void popup(); @@ -237,6 +238,7 @@ class SplitContainer final : public BaseWidget void resizeEvent(QResizeEvent *event) override; private: + NodeDescriptor buildDescriptorRecursively(Node *currentNode); void applyFromDescriptorRecursively(const NodeDescriptor &rootNode, Node *baseNode); From 912fcdc2f449aac7f943f4c79f03be660c9cc474 Mon Sep 17 00:00:00 2001 From: Kleber Date: Sat, 30 Mar 2024 14:31:20 -0300 Subject: [PATCH 2/8] Convert Channel::Type to string --- src/widgets/splits/SplitContainer.cpp | 30 +++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/widgets/splits/SplitContainer.cpp b/src/widgets/splits/SplitContainer.cpp index 6bb4070142b..d3189999c32 100644 --- a/src/widgets/splits/SplitContainer.cpp +++ b/src/widgets/splits/SplitContainer.cpp @@ -4,6 +4,7 @@ #include "common/Common.hpp" #include "common/QLogging.hpp" #include "common/WindowDescriptors.hpp" +#include "controllers/completion/TabCompletionModel.hpp" #include "debug/AssertInGuiThread.hpp" #include "singletons/Fonts.hpp" #include "singletons/Theme.hpp" @@ -808,8 +809,33 @@ NodeDescriptor SplitContainer::buildDescriptorRecursively(Node *currentNode) { if (currentNode->children_.empty()) { + const auto channelTypeToString = [](Channel::Type type) { + switch (type) + { + case Channel::Type::Twitch: + return "twitch"; + case Channel::Type::TwitchWhispers: + return "whispers"; + case Channel::Type::TwitchWatching: + return "watching"; + case Channel::Type::TwitchMentions: + return "mentions"; + case Channel::Type::TwitchLive: + return "live"; + case Channel::Type::TwitchAutomod: + return "automod"; + case Channel::Type::Irc: + return "irc"; + case Channel::Type::Misc: + return "misc"; + default: + return ""; + } + }; + SplitNodeDescriptor result; - result.type_ = "twitch"; + result.type_ = + channelTypeToString(currentNode->split_->getChannel()->getType()); result.channelName_ = currentNode->split_->getChannel()->getName(); result.filters_ = currentNode->split_->getFilters(); return result; @@ -877,9 +903,9 @@ void SplitContainer::applyFromDescriptorRecursively( } const auto &splitNode = *inner; auto *split = new Split(this); + split->setFilters(splitNode.filters_); split->setChannel(WindowManager::decodeChannel(splitNode)); split->setModerationMode(splitNode.moderationMode_); - split->setFilters(splitNode.filters_); auto *node = new Node(); node->parent_ = baseNode; From aa787457a1280b6bd6be83cae17f750091b4e6ca Mon Sep 17 00:00:00 2001 From: Kleber Date: Sat, 30 Mar 2024 14:48:02 -0300 Subject: [PATCH 3/8] Remove unnecessary assert and include --- src/widgets/Notebook.cpp | 1 - src/widgets/splits/SplitContainer.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/src/widgets/Notebook.cpp b/src/widgets/Notebook.cpp index 0a7ceef71e9..5a39310e0ed 100644 --- a/src/widgets/Notebook.cpp +++ b/src/widgets/Notebook.cpp @@ -198,7 +198,6 @@ void Notebook::duplicatePage(QWidget *page) newContainer->applyFromDescriptor(descriptor); int newTabPosition = this->indexOf(page) + 1; - assert(newTabPosition != -1); auto *tab = this->addPageAt( newContainer, newTabPosition, item->tab->hasCustomTitle() ? item->tab->getCustomTitle() : "", false); diff --git a/src/widgets/splits/SplitContainer.cpp b/src/widgets/splits/SplitContainer.cpp index d3189999c32..e688f841861 100644 --- a/src/widgets/splits/SplitContainer.cpp +++ b/src/widgets/splits/SplitContainer.cpp @@ -4,7 +4,6 @@ #include "common/Common.hpp" #include "common/QLogging.hpp" #include "common/WindowDescriptors.hpp" -#include "controllers/completion/TabCompletionModel.hpp" #include "debug/AssertInGuiThread.hpp" #include "singletons/Fonts.hpp" #include "singletons/Theme.hpp" From 2f4021bfd9c1b5e156942e3bc8b880524fb5e96c Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 18 May 2024 13:39:57 +0200 Subject: [PATCH 4/8] nit: remove default position parameter in addPageAt --- src/widgets/Notebook.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/Notebook.hpp b/src/widgets/Notebook.hpp index d1b3bd48b5c..df4b116d617 100644 --- a/src/widgets/Notebook.hpp +++ b/src/widgets/Notebook.hpp @@ -48,7 +48,7 @@ class Notebook : public BaseWidget * * @param position if set to -1, adds the page to the end **/ - NotebookTab *addPageAt(QWidget *page, int position = -1, + NotebookTab *addPageAt(QWidget *page, int position, QString title = QString(), bool select = false); void removePage(QWidget *page); void duplicatePage(QWidget *page); From 75027efb07517b70a1a535de97e1f9c6408b17e3 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 18 May 2024 13:57:10 +0200 Subject: [PATCH 5/8] unrelated crash fix: when irc server doesn't exist, log to unknown dir --- src/common/Channel.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/common/Channel.cpp b/src/common/Channel.cpp index 872ba246209..7141dbe7efc 100644 --- a/src/common/Channel.cpp +++ b/src/common/Channel.cpp @@ -92,8 +92,16 @@ void Channel::addMessage(MessagePtr message, auto *irc = dynamic_cast(this); if (irc != nullptr) { - channelPlatform = QString("irc-%1").arg( - irc->server()->userFriendlyIdentifier()); + auto *ircServer = irc->server(); + if (ircServer != nullptr) + { + channelPlatform = QString("irc-%1").arg( + irc->server()->userFriendlyIdentifier()); + } + else + { + channelPlatform = "irc-unknown"; + } } } else if (this->isTwitchChannel()) From bfce89c94d9858b2bf4fc53c63971491d4a842e7 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 18 May 2024 13:57:30 +0200 Subject: [PATCH 6/8] fix: duplicate irc splits correctly --- src/widgets/splits/SplitContainer.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/widgets/splits/SplitContainer.cpp b/src/widgets/splits/SplitContainer.cpp index e688f841861..8c6e9cd6a6b 100644 --- a/src/widgets/splits/SplitContainer.cpp +++ b/src/widgets/splits/SplitContainer.cpp @@ -5,6 +5,8 @@ #include "common/QLogging.hpp" #include "common/WindowDescriptors.hpp" #include "debug/AssertInGuiThread.hpp" +#include "providers/irc/IrcChannel2.hpp" +#include "providers/irc/IrcServer.hpp" #include "singletons/Fonts.hpp" #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" @@ -832,9 +834,29 @@ NodeDescriptor SplitContainer::buildDescriptorRecursively(Node *currentNode) } }; + auto channelType = currentNode->split_->getChannel()->getType(); SplitNodeDescriptor result; - result.type_ = - channelTypeToString(currentNode->split_->getChannel()->getType()); + result.type_ = channelTypeToString(channelType); + + switch (channelType) + { + case Channel::Type::Irc: { + if (auto *ircChannel = dynamic_cast( + currentNode->split_->getChannel().get())) + { + if (ircChannel->server()) + { + result.server_ = ircChannel->server()->id(); + } + } + } + break; + + default: { + } + break; + } + result.channelName_ = currentNode->split_->getChannel()->getName(); result.filters_ = currentNode->split_->getFilters(); return result; From 470cd2ae33114cbb7e83a9996653500e42b484c2 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 18 May 2024 14:12:03 +0200 Subject: [PATCH 7/8] chore: use magicenum to figure out channel type name --- src/common/Channel.hpp | 32 ++++++++++++++++++++++++++- src/widgets/splits/SplitContainer.cpp | 31 ++------------------------ 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/common/Channel.hpp b/src/common/Channel.hpp index 6adac6a76f4..730a92adfbb 100644 --- a/src/common/Channel.hpp +++ b/src/common/Channel.hpp @@ -4,6 +4,7 @@ #include "controllers/completion/TabCompletionModel.hpp" #include "messages/LimitedQueue.hpp" +#include #include #include #include @@ -45,7 +46,7 @@ class Channel : public std::enable_shared_from_this TwitchAutomod, TwitchEnd, Irc, - Misc + Misc, }; explicit Channel(const QString &name, Type type); @@ -151,3 +152,32 @@ class IndirectChannel }; } // namespace chatterino + +template <> +constexpr magic_enum::customize::customize_t + magic_enum::customize::enum_name( + chatterino::Channel::Type value) noexcept +{ + using Type = chatterino::Channel::Type; + switch (value) + { + case Type::Twitch: + return "twitch"; + case Type::TwitchWhispers: + return "whispers"; + case Type::TwitchWatching: + return "watching"; + case Type::TwitchMentions: + return "mentions"; + case Type::TwitchLive: + return "live"; + case Type::TwitchAutomod: + return "automod"; + case Type::Irc: + return "irc"; + case Type::Misc: + return "misc"; + default: + return default_tag; + } +} diff --git a/src/widgets/splits/SplitContainer.cpp b/src/widgets/splits/SplitContainer.cpp index 8c6e9cd6a6b..4a5c3680535 100644 --- a/src/widgets/splits/SplitContainer.cpp +++ b/src/widgets/splits/SplitContainer.cpp @@ -810,33 +810,10 @@ NodeDescriptor SplitContainer::buildDescriptorRecursively(Node *currentNode) { if (currentNode->children_.empty()) { - const auto channelTypeToString = [](Channel::Type type) { - switch (type) - { - case Channel::Type::Twitch: - return "twitch"; - case Channel::Type::TwitchWhispers: - return "whispers"; - case Channel::Type::TwitchWatching: - return "watching"; - case Channel::Type::TwitchMentions: - return "mentions"; - case Channel::Type::TwitchLive: - return "live"; - case Channel::Type::TwitchAutomod: - return "automod"; - case Channel::Type::Irc: - return "irc"; - case Channel::Type::Misc: - return "misc"; - default: - return ""; - } - }; + const auto channelType = currentNode->split_->getChannel()->getType(); - auto channelType = currentNode->split_->getChannel()->getType(); SplitNodeDescriptor result; - result.type_ = channelTypeToString(channelType); + result.type_ = qmagicenum::enumNameString(channelType); switch (channelType) { @@ -851,10 +828,6 @@ NodeDescriptor SplitContainer::buildDescriptorRecursively(Node *currentNode) } } break; - - default: { - } - break; } result.channelName_ = currentNode->split_->getChannel()->getName(); From 0184bc49446f2f6b5f159c268c7e7973eb2b45f0 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 18 May 2024 14:17:16 +0200 Subject: [PATCH 8/8] disable no-recursion clang-tidy check --- .clang-tidy | 1 + 1 file changed, 1 insertion(+) diff --git a/.clang-tidy b/.clang-tidy index 170ad019a41..ae07280120b 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -22,6 +22,7 @@ Checks: "-*, -readability-magic-numbers, -performance-noexcept-move-constructor, -misc-non-private-member-variables-in-classes, + -misc-no-recursion, -cppcoreguidelines-non-private-member-variables-in-classes, -modernize-use-nodiscard, -modernize-use-trailing-return-type,