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,
|
-readability-magic-numbers,
|
||||||
-performance-noexcept-move-constructor,
|
-performance-noexcept-move-constructor,
|
||||||
-misc-non-private-member-variables-in-classes,
|
-misc-non-private-member-variables-in-classes,
|
||||||
|
-misc-no-recursion,
|
||||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||||
-modernize-use-nodiscard,
|
-modernize-use-nodiscard,
|
||||||
-modernize-use-trailing-return-type,
|
-modernize-use-trailing-return-type,
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
- Minor: Add option to customise Moderation buttons with images. (#5369)
|
- 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: 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 `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: 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)
|
- 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)
|
- Dev: Use Qt's high DPI scaling. (#4868, #5400)
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "controllers/completion/TabCompletionModel.hpp"
|
#include "controllers/completion/TabCompletionModel.hpp"
|
||||||
#include "messages/LimitedQueue.hpp"
|
#include "messages/LimitedQueue.hpp"
|
||||||
|
|
||||||
|
#include <magic_enum/magic_enum.hpp>
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
#include <QDate>
|
#include <QDate>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
@ -45,7 +46,7 @@ public:
|
||||||
TwitchAutomod,
|
TwitchAutomod,
|
||||||
TwitchEnd,
|
TwitchEnd,
|
||||||
Irc,
|
Irc,
|
||||||
Misc
|
Misc,
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit Channel(const QString &name, Type type);
|
explicit Channel(const QString &name, Type type);
|
||||||
|
@ -151,3 +152,32 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // 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 <QUuid>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
Notebook::Notebook(QWidget *parent)
|
Notebook::Notebook(QWidget *parent)
|
||||||
|
@ -87,6 +89,12 @@ Notebook::Notebook(QWidget *parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
NotebookTab *Notebook::addPage(QWidget *page, QString title, bool select)
|
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
|
// Queue up save because: Tab added
|
||||||
getIApp()->getWindows()->queueSave();
|
getIApp()->getWindows()->queueSave();
|
||||||
|
@ -101,7 +109,14 @@ NotebookTab *Notebook::addPage(QWidget *page, QString title, bool select)
|
||||||
item.page = page;
|
item.page = page;
|
||||||
item.tab = tab;
|
item.tab = tab;
|
||||||
|
|
||||||
this->items_.append(item);
|
if (position == -1)
|
||||||
|
{
|
||||||
|
this->items_.push_back(item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->items_.insert(position, item);
|
||||||
|
}
|
||||||
|
|
||||||
page->hide();
|
page->hide();
|
||||||
page->setParent(this);
|
page->setParent(this);
|
||||||
|
@ -165,6 +180,48 @@ void Notebook::removePage(QWidget *page)
|
||||||
this->performLayout(true);
|
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()
|
void Notebook::removeCurrentPage()
|
||||||
{
|
{
|
||||||
if (this->selectedPage_ != nullptr)
|
if (this->selectedPage_ != nullptr)
|
||||||
|
|
|
@ -42,7 +42,16 @@ public:
|
||||||
|
|
||||||
NotebookTab *addPage(QWidget *page, QString title = QString(),
|
NotebookTab *addPage(QWidget *page, QString title = QString(),
|
||||||
bool select = false);
|
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 removePage(QWidget *page);
|
||||||
|
void duplicatePage(QWidget *page);
|
||||||
void removeCurrentPage();
|
void removeCurrentPage();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -99,6 +99,10 @@ NotebookTab::NotebookTab(Notebook *notebook)
|
||||||
getIApp()->getHotkeys()->getDisplaySequence(HotkeyCategory::Window,
|
getIApp()->getHotkeys()->getDisplaySequence(HotkeyCategory::Window,
|
||||||
"popup", {{"window"}}));
|
"popup", {{"window"}}));
|
||||||
|
|
||||||
|
this->menu_.addAction("Duplicate Tab", [this]() {
|
||||||
|
this->notebook_->duplicatePage(this->page);
|
||||||
|
});
|
||||||
|
|
||||||
highlightNewMessagesAction_ =
|
highlightNewMessagesAction_ =
|
||||||
new QAction("Mark Tab as Unread on New Messages", &this->menu_);
|
new QAction("Mark Tab as Unread on New Messages", &this->menu_);
|
||||||
highlightNewMessagesAction_->setCheckable(true);
|
highlightNewMessagesAction_->setCheckable(true);
|
||||||
|
|
|
@ -5,9 +5,12 @@
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "common/WindowDescriptors.hpp"
|
#include "common/WindowDescriptors.hpp"
|
||||||
#include "debug/AssertInGuiThread.hpp"
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
|
#include "providers/irc/IrcChannel2.hpp"
|
||||||
|
#include "providers/irc/IrcServer.hpp"
|
||||||
#include "singletons/Fonts.hpp"
|
#include "singletons/Fonts.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
|
#include "util/QMagicEnum.hpp"
|
||||||
#include "widgets/helper/ChannelView.hpp"
|
#include "widgets/helper/ChannelView.hpp"
|
||||||
#include "widgets/helper/NotebookTab.hpp"
|
#include "widgets/helper/NotebookTab.hpp"
|
||||||
#include "widgets/Notebook.hpp"
|
#include "widgets/Notebook.hpp"
|
||||||
|
@ -762,6 +765,11 @@ SplitContainer::Node *SplitContainer::getBaseNode()
|
||||||
return &this->baseNode_;
|
return &this->baseNode_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NodeDescriptor SplitContainer::buildDescriptor() const
|
||||||
|
{
|
||||||
|
return this->buildDescriptorRecursively(&this->baseNode_);
|
||||||
|
}
|
||||||
|
|
||||||
void SplitContainer::applyFromDescriptor(const NodeDescriptor &rootNode)
|
void SplitContainer::applyFromDescriptor(const NodeDescriptor &rootNode)
|
||||||
{
|
{
|
||||||
assert(this->baseNode_.type_ == Node::Type::EmptyRoot);
|
assert(this->baseNode_.type_ == Node::Type::EmptyRoot);
|
||||||
|
@ -799,6 +807,49 @@ void SplitContainer::popup()
|
||||||
window.show();
|
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(
|
void SplitContainer::applyFromDescriptorRecursively(
|
||||||
const NodeDescriptor &rootNode, Node *baseNode)
|
const NodeDescriptor &rootNode, Node *baseNode)
|
||||||
{
|
{
|
||||||
|
@ -849,9 +900,9 @@ void SplitContainer::applyFromDescriptorRecursively(
|
||||||
}
|
}
|
||||||
const auto &splitNode = *inner;
|
const auto &splitNode = *inner;
|
||||||
auto *split = new Split(this);
|
auto *split = new Split(this);
|
||||||
|
split->setFilters(splitNode.filters_);
|
||||||
split->setChannel(WindowManager::decodeChannel(splitNode));
|
split->setChannel(WindowManager::decodeChannel(splitNode));
|
||||||
split->setModerationMode(splitNode.moderationMode_);
|
split->setModerationMode(splitNode.moderationMode_);
|
||||||
split->setFilters(splitNode.filters_);
|
|
||||||
|
|
||||||
auto *node = new Node();
|
auto *node = new Node();
|
||||||
node->parent_ = baseNode;
|
node->parent_ = baseNode;
|
||||||
|
|
|
@ -220,6 +220,7 @@ public:
|
||||||
void hideResizeHandles();
|
void hideResizeHandles();
|
||||||
void resetMouseStatus();
|
void resetMouseStatus();
|
||||||
|
|
||||||
|
NodeDescriptor buildDescriptor() const;
|
||||||
void applyFromDescriptor(const NodeDescriptor &rootNode);
|
void applyFromDescriptor(const NodeDescriptor &rootNode);
|
||||||
|
|
||||||
void popup();
|
void popup();
|
||||||
|
@ -237,6 +238,7 @@ protected:
|
||||||
void resizeEvent(QResizeEvent *event) override;
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
NodeDescriptor buildDescriptorRecursively(const Node *currentNode) const;
|
||||||
void applyFromDescriptorRecursively(const NodeDescriptor &rootNode,
|
void applyFromDescriptorRecursively(const NodeDescriptor &rootNode,
|
||||||
Node *baseNode);
|
Node *baseNode);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue