mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Add feature to duplicate tabs (#5277)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
c5802a0f49
commit
d161036b18
|
@ -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,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
- Minor: Add option to customise Moderation buttons with images. (#5369)
|
||||
- Minor: Colored usernames now update on the fly when changing the "Color @usernames" setting. (#5300)
|
||||
- Minor: Added `flags.action` filter variable, allowing you to filter on `/me` messages. (#5397)
|
||||
- Minor: Added the ability to duplicate tabs. (#5277)
|
||||
- Bugfix: If a network request errors with 200 OK, Qt's error code is now reported instead of the HTTP status. (#5378)
|
||||
- Bugfix: Fixed a crash that could occur when logging was enabled in IRC servers that were removed. (#5419)
|
||||
- Dev: Use Qt's high DPI scaling. (#4868, #5400)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "controllers/completion/TabCompletionModel.hpp"
|
||||
#include "messages/LimitedQueue.hpp"
|
||||
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <QDate>
|
||||
#include <QString>
|
||||
|
@ -45,7 +46,7 @@ public:
|
|||
TwitchAutomod,
|
||||
TwitchEnd,
|
||||
Irc,
|
||||
Misc
|
||||
Misc,
|
||||
};
|
||||
|
||||
explicit Channel(const QString &name, Type type);
|
||||
|
@ -151,3 +152,32 @@ private:
|
|||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t
|
||||
magic_enum::customize::enum_name<chatterino::Channel::Type>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
#include <QUuid>
|
||||
#include <QWidget>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
Notebook::Notebook(QWidget *parent)
|
||||
|
@ -87,6 +89,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();
|
||||
|
@ -101,7 +109,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);
|
||||
|
@ -165,6 +180,48 @@ void Notebook::removePage(QWidget *page)
|
|||
this->performLayout(true);
|
||||
}
|
||||
|
||||
void Notebook::duplicatePage(QWidget *page)
|
||||
{
|
||||
auto *item = this->findItem(page);
|
||||
assert(item != nullptr);
|
||||
if (item == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto *container = dynamic_cast<SplitContainer *>(item->page);
|
||||
if (!container)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto *newContainer = new SplitContainer(this);
|
||||
if (!container->getSplits().empty())
|
||||
{
|
||||
auto descriptor = container->buildDescriptor();
|
||||
newContainer->applyFromDescriptor(descriptor);
|
||||
}
|
||||
|
||||
const auto tabPosition = this->indexOf(page);
|
||||
auto newTabPosition = -1;
|
||||
if (tabPosition != -1)
|
||||
{
|
||||
newTabPosition = tabPosition + 1;
|
||||
}
|
||||
auto newTabHighlightState = item->tab->highlightState();
|
||||
QString newTabTitle = "";
|
||||
if (item->tab->hasCustomTitle())
|
||||
{
|
||||
newTabTitle = item->tab->getCustomTitle();
|
||||
}
|
||||
|
||||
auto *tab =
|
||||
this->addPageAt(newContainer, newTabPosition, newTabTitle, false);
|
||||
tab->setHighlightState(newTabHighlightState);
|
||||
|
||||
newContainer->setTab(tab);
|
||||
}
|
||||
|
||||
void Notebook::removeCurrentPage()
|
||||
{
|
||||
if (this->selectedPage_ != nullptr)
|
||||
|
|
|
@ -42,7 +42,16 @@ public:
|
|||
|
||||
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,
|
||||
QString title = QString(), bool select = false);
|
||||
void removePage(QWidget *page);
|
||||
void duplicatePage(QWidget *page);
|
||||
void removeCurrentPage();
|
||||
|
||||
/**
|
||||
|
|
|
@ -99,6 +99,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);
|
||||
|
|
|
@ -5,9 +5,12 @@
|
|||
#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"
|
||||
#include "util/QMagicEnum.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
#include "widgets/helper/NotebookTab.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
|
@ -762,6 +765,11 @@ SplitContainer::Node *SplitContainer::getBaseNode()
|
|||
return &this->baseNode_;
|
||||
}
|
||||
|
||||
NodeDescriptor SplitContainer::buildDescriptor() const
|
||||
{
|
||||
return this->buildDescriptorRecursively(&this->baseNode_);
|
||||
}
|
||||
|
||||
void SplitContainer::applyFromDescriptor(const NodeDescriptor &rootNode)
|
||||
{
|
||||
assert(this->baseNode_.type_ == Node::Type::EmptyRoot);
|
||||
|
@ -799,6 +807,49 @@ void SplitContainer::popup()
|
|||
window.show();
|
||||
}
|
||||
|
||||
NodeDescriptor SplitContainer::buildDescriptorRecursively(
|
||||
const Node *currentNode) const
|
||||
{
|
||||
if (currentNode->children_.empty())
|
||||
{
|
||||
const auto channelType =
|
||||
currentNode->split_->getIndirectChannel().getType();
|
||||
|
||||
SplitNodeDescriptor result;
|
||||
result.type_ = qmagicenum::enumNameString(channelType);
|
||||
|
||||
switch (channelType)
|
||||
{
|
||||
case Channel::Type::Irc: {
|
||||
if (auto *ircChannel = dynamic_cast<IrcChannel *>(
|
||||
currentNode->split_->getChannel().get()))
|
||||
{
|
||||
if (ircChannel->server())
|
||||
{
|
||||
result.server_ = ircChannel->server()->id();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
result.channelName_ = currentNode->split_->getChannel()->getName();
|
||||
result.filters_ = currentNode->split_->getFilters();
|
||||
return result;
|
||||
}
|
||||
|
||||
ContainerNodeDescriptor descriptor;
|
||||
for (const 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)
|
||||
{
|
||||
|
@ -849,9 +900,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;
|
||||
|
|
|
@ -220,6 +220,7 @@ public:
|
|||
void hideResizeHandles();
|
||||
void resetMouseStatus();
|
||||
|
||||
NodeDescriptor buildDescriptor() const;
|
||||
void applyFromDescriptor(const NodeDescriptor &rootNode);
|
||||
|
||||
void popup();
|
||||
|
@ -237,6 +238,7 @@ protected:
|
|||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
NodeDescriptor buildDescriptorRecursively(const Node *currentNode) const;
|
||||
void applyFromDescriptorRecursively(const NodeDescriptor &rootNode,
|
||||
Node *baseNode);
|
||||
|
||||
|
|
Loading…
Reference in a new issue