This commit is contained in:
fourtf 2018-10-16 14:22:53 +02:00
commit bf8bf37eab
23 changed files with 299 additions and 76 deletions

View file

@ -14,7 +14,15 @@ If you still receive an error about `MSVCR120.dll missing`, then you should inst
Releases for linux and mac will follow soon™ Releases for linux and mac will follow soon™
## Building ## Building
Before building run `git submodule update --init --recursive` to get required submodules. To get source code with required submodules run:
```
git clone --recursive https://github.com/fourtf/chatterino2.git
```
or
```
git clone https://github.com/fourtf/chatterino2.git
git submodule update --init --recursive
```
[Building on Windows](../master/BUILDING_ON_WINDOWS.md) [Building on Windows](../master/BUILDING_ON_WINDOWS.md)

View file

@ -262,7 +262,8 @@ SOURCES += \
src/debug/Benchmark.cpp \ src/debug/Benchmark.cpp \
src/common/UsernameSet.cpp \ src/common/UsernameSet.cpp \
src/widgets/settingspages/AdvancedPage.cpp \ src/widgets/settingspages/AdvancedPage.cpp \
src/util/IncognitoBrowser.cpp src/util/IncognitoBrowser.cpp \
src/widgets/splits/ClosedSplits.cpp
HEADERS += \ HEADERS += \
src/Application.hpp \ src/Application.hpp \
@ -464,7 +465,8 @@ HEADERS += \
src/messages/MessageContainer.hpp \ src/messages/MessageContainer.hpp \
src/common/UsernameSet.hpp \ src/common/UsernameSet.hpp \
src/widgets/settingspages/AdvancedPage.hpp \ src/widgets/settingspages/AdvancedPage.hpp \
src/util/IncognitoBrowser.hpp src/util/IncognitoBrowser.hpp \
src/widgets/splits/ClosedSplits.hpp
RESOURCES += \ RESOURCES += \
resources/resources.qrc \ resources/resources.qrc \

View file

@ -221,6 +221,11 @@ bool Channel::hasModRights() const
return this->isMod() || this->isBroadcaster(); return this->isMod() || this->isBroadcaster();
} }
bool Channel::isLive() const
{
return false;
}
std::shared_ptr<Channel> Channel::getEmpty() std::shared_ptr<Channel> Channel::getEmpty()
{ {
static std::shared_ptr<Channel> channel(new Channel("", Type::None)); static std::shared_ptr<Channel> channel(new Channel("", Type::None));

View file

@ -69,6 +69,7 @@ public:
virtual bool isMod() const; virtual bool isMod() const;
virtual bool isBroadcaster() const; virtual bool isBroadcaster() const;
virtual bool hasModRights() const; virtual bool hasModRights() const;
virtual bool isLive() const;
static std::shared_ptr<Channel> getEmpty(); static std::shared_ptr<Channel> getEmpty();

View file

@ -17,7 +17,6 @@ enum class HighlightState {
None, None,
Highlighted, Highlighted,
NewMessage, NewMessage,
Notification,
}; };
inline QString qS(const std::string &string) inline QString qS(const std::string &string)

View file

@ -11,6 +11,10 @@
namespace chatterino { namespace chatterino {
const int RECONNECT_BASE_INTERVAL = 2000;
// 60 falloff counter means it will try to reconnect at most every 60*2 seconds
const int MAX_FALLOFF_COUNTER = 60;
AbstractIrcServer::AbstractIrcServer() AbstractIrcServer::AbstractIrcServer()
{ {
// Initialize the connections // Initialize the connections
@ -38,12 +42,29 @@ AbstractIrcServer::AbstractIrcServer()
QObject::connect(this->readConnection_.get(), QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::disconnected, &Communi::IrcConnection::disconnected,
[this] { this->onDisconnected(); }); [this] { this->onDisconnected(); });
QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::socketError,
[this] { this->onSocketError(); });
// listen to reconnect request // listen to reconnect request
this->readConnection_->reconnectRequested.connect( this->readConnection_->reconnectRequested.connect(
[this] { this->connect(); }); [this] { this->connect(); });
// this->writeConnection->reconnectRequested.connect([this] { // this->writeConnection->reconnectRequested.connect([this] {
// this->connect(); }); // this->connect(); });
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL);
this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL *
this->falloffCounter_);
this->falloffCounter_ =
std::min(MAX_FALLOFF_COUNTER, this->falloffCounter_ + 1);
if (!this->readConnection_->isConnected()) {
log("Trying to reconnect... {}", this->falloffCounter_);
this->connect();
}
});
} }
void AbstractIrcServer::connect() void AbstractIrcServer::connect()
@ -215,6 +236,8 @@ void AbstractIrcServer::onConnected()
chan->addMessage(connected); chan->addMessage(connected);
} }
this->falloffCounter_ = 1;
} }
void AbstractIrcServer::onDisconnected() void AbstractIrcServer::onDisconnected()
@ -235,6 +258,11 @@ void AbstractIrcServer::onDisconnected()
} }
} }
void AbstractIrcServer::onSocketError()
{
this->reconnectTimer_.start();
}
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel( std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
const QString &channelName) const QString &channelName)
{ {

View file

@ -54,6 +54,7 @@ protected:
virtual void onConnected(); virtual void onConnected();
virtual void onDisconnected(); virtual void onDisconnected();
virtual void onSocketError();
virtual std::shared_ptr<Channel> getCustomChannel( virtual std::shared_ptr<Channel> getCustomChannel(
const QString &channelName); const QString &channelName);
@ -70,6 +71,9 @@ private:
std::unique_ptr<IrcConnection> writeConnection_ = nullptr; std::unique_ptr<IrcConnection> writeConnection_ = nullptr;
std::unique_ptr<IrcConnection> readConnection_ = nullptr; std::unique_ptr<IrcConnection> readConnection_ = nullptr;
QTimer reconnectTimer_;
int falloffCounter_ = 1;
std::mutex connectionMutex_; std::mutex connectionMutex_;
// bool autoReconnect_ = false; // bool autoReconnect_ = false;

View file

@ -29,8 +29,7 @@
namespace chatterino { namespace chatterino {
namespace { namespace {
auto parseRecentMessages(const QJsonObject &jsonRoot, auto parseRecentMessages(const QJsonObject &jsonRoot, ChannelPtr channel)
TwitchChannel &channel)
{ {
QJsonArray jsonMessages = jsonRoot.value("messages").toArray(); QJsonArray jsonMessages = jsonRoot.value("messages").toArray();
std::vector<MessagePtr> messages; std::vector<MessagePtr> messages;
@ -46,7 +45,7 @@ namespace {
assert(privMsg); assert(privMsg);
MessageParseArgs args; MessageParseArgs args;
TwitchMessageBuilder builder(&channel, privMsg, args); TwitchMessageBuilder builder(channel.get(), privMsg, args);
if (!builder.isIgnored()) { if (!builder.isIgnored()) {
messages.push_back(builder.build()); messages.push_back(builder.build());
} }
@ -90,7 +89,6 @@ TwitchChannel::TwitchChannel(const QString &name,
{ {
log("[TwitchChannel:{}] Opened", name); log("[TwitchChannel:{}] Opened", name);
this->tabHighlightRequested.connect([](HighlightState state) {});
this->liveStatusChanged.connect([this]() { this->liveStatusChanged.connect([this]() {
if (this->isLive() == 1) { if (this->isLive() == 1) {
} }
@ -413,8 +411,6 @@ void TwitchChannel::setLive(bool newLiveStatus)
} }
auto live = makeSystemMessage(this->getName() + " is live"); auto live = makeSystemMessage(this->getName() + " is live");
this->addMessage(live); this->addMessage(live);
this->tabHighlightRequested.invoke(
HighlightState::Notification);
} else { } else {
auto offline = auto offline =
makeSystemMessage(this->getName() + " is offline"); makeSystemMessage(this->getName() + " is offline");
@ -548,13 +544,13 @@ void TwitchChannel::loadRecentMessages()
// can't be concurrent right now due to SignalVector // can't be concurrent right now due to SignalVector
// request.setExecuteConcurrently(true); // request.setExecuteConcurrently(true);
request.onSuccess([that = this](auto result) -> Outcome { request.onSuccess([weak = weakOf<Channel>(this)](auto result) -> Outcome {
auto messages = parseRecentMessages(result.parseJson(), *that); auto shared = weak.lock();
if (!shared) return Failure;
// postToThread([that, weak = weakOf<Channel>(that), auto messages = parseRecentMessages(result.parseJson(), shared);
// messages = std::move(messages)]() mutable {
that->addMessagesAtStart(messages); shared->addMessagesAtStart(messages);
// });
return Success; return Success;
}); });

View file

@ -66,7 +66,7 @@ public:
const QString &subscriptionUrl(); const QString &subscriptionUrl();
const QString &channelUrl(); const QString &channelUrl();
const QString &popoutPlayerUrl(); const QString &popoutPlayerUrl();
bool isLive() const; virtual bool isLive() const override;
QString roomId() const; QString roomId() const;
AccessGuard<const RoomModes> accessRoomModes() const; AccessGuard<const RoomModes> accessRoomModes() const;
AccessGuard<const StreamStatus> accessStreamStatus() const; AccessGuard<const StreamStatus> accessStreamStatus() const;
@ -92,7 +92,6 @@ public:
pajlada::Signals::NoArgSignal userStateChanged; pajlada::Signals::NoArgSignal userStateChanged;
pajlada::Signals::NoArgSignal liveStatusChanged; pajlada::Signals::NoArgSignal liveStatusChanged;
pajlada::Signals::NoArgSignal roomModesChanged; pajlada::Signals::NoArgSignal roomModesChanged;
pajlada::Signals::Signal<HighlightState> tabHighlightRequested;
protected: protected:
void addRecentChatter(const MessagePtr &message) override; void addRecentChatter(const MessagePtr &message) override;

View file

@ -105,10 +105,6 @@ void Theme::actuallyUpdate(double hue, double multiplier)
QColor("#000"), QColor("#000"),
{QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")}, {QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")},
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}}; {QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
this->tabs.notified = {
fg,
{QColor("#fff"), QColor("#fff"), QColor("#fff")},
{QColor("#F824A8"), QColor("#F824A8"), QColor("#F824A8")}};
} else { } else {
this->tabs.regular = { this->tabs.regular = {
QColor("#aaa"), QColor("#aaa"),
@ -127,10 +123,6 @@ void Theme::actuallyUpdate(double hue, double multiplier)
QColor("#fff"), QColor("#fff"),
{QColor("#555555"), QColor("#555555"), QColor("#555555")}, {QColor("#555555"), QColor("#555555"), QColor("#555555")},
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}}; {QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
this->tabs.notified = {
fg,
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#F824A8"), QColor("#F824A8"), QColor("#F824A8")}};
} }
this->splits.input.focusedLine = highlighted; this->splits.input.focusedLine = highlighted;

View file

@ -50,7 +50,6 @@ public:
TabColors newMessage; TabColors newMessage;
TabColors highlighted; TabColors highlighted;
TabColors selected; TabColors selected;
TabColors notified;
QColor border; QColor border;
QColor bottomLine; QColor bottomLine;
} tabs; } tabs;

View file

@ -15,8 +15,10 @@
#include "widgets/dialogs/UpdateDialog.hpp" #include "widgets/dialogs/UpdateDialog.hpp"
#include "widgets/dialogs/WelcomeDialog.hpp" #include "widgets/dialogs/WelcomeDialog.hpp"
#include "widgets/helper/EffectLabel.hpp" #include "widgets/helper/EffectLabel.hpp"
#include "widgets/helper/NotebookTab.hpp"
#include "widgets/helper/Shortcut.hpp" #include "widgets/helper/Shortcut.hpp"
#include "widgets/helper/TitlebarButton.hpp" #include "widgets/helper/TitlebarButton.hpp"
#include "widgets/splits/ClosedSplits.hpp"
#include "widgets/splits/Split.hpp" #include "widgets/splits/Split.hpp"
#include "widgets/splits/SplitContainer.hpp" #include "widgets/splits/SplitContainer.hpp"
@ -304,6 +306,26 @@ void Window::addShortcuts()
// Close tab // Close tab
createWindowShortcut(this, "CTRL+SHIFT+W", createWindowShortcut(this, "CTRL+SHIFT+W",
[this] { this->notebook_->removeCurrentPage(); }); [this] { this->notebook_->removeCurrentPage(); });
// Reopen last closed split
createWindowShortcut(this, "CTRL+G", [this] {
if (ClosedSplits::empty()) {
return;
}
ClosedSplits::SplitInfo si = ClosedSplits::pop();
SplitContainer *splitContainer{nullptr};
if (si.tab) {
splitContainer = dynamic_cast<SplitContainer *>(si.tab->page);
}
if (!splitContainer) {
splitContainer = this->notebook_->getOrAddSelectedPage();
}
this->notebook_->select(splitContainer);
Split *split = new Split(splitContainer);
split->setChannel(
getApp()->twitch.server->getOrAddChannel(si.channelName));
splitContainer->appendSplit(split);
});
} }
#define UGLYMACROHACK1(s) #s #define UGLYMACROHACK1(s) #s

View file

@ -48,19 +48,26 @@ namespace {
std::vector<std::shared_ptr<TwitchAccount::EmoteSet>> sets, std::vector<std::shared_ptr<TwitchAccount::EmoteSet>> sets,
Channel &globalChannel, Channel &subChannel) Channel &globalChannel, Channel &subChannel)
{ {
for (const auto &set : sets) { QMap<QString, QPair<bool, std::vector<MessagePtr>>> mapOfSets;
auto &channel = set->key == "0" ? globalChannel : subChannel;
for (const auto &set : sets) {
// TITLE // TITLE
auto channelName = set->channelName;
auto text = auto text =
set->key == "0" || set->text.isEmpty() ? "Twitch" : set->text; set->key == "0" || set->text.isEmpty() ? "Twitch" : set->text;
channel.addMessage(makeTitleMessage(text));
// EMOTES // EMOTES
MessageBuilder builder; MessageBuilder builder;
builder->flags.set(MessageFlag::Centered); builder->flags.set(MessageFlag::Centered);
builder->flags.set(MessageFlag::DisableCompactEmotes); builder->flags.set(MessageFlag::DisableCompactEmotes);
// If value of map is empty, create init pair and add title.
if (mapOfSets.find(channelName) == mapOfSets.end()) {
std::vector<MessagePtr> b;
b.push_back(makeTitleMessage(text));
mapOfSets[channelName] = qMakePair(set->key == "0", b);
}
for (const auto &emote : set->emotes) { for (const auto &emote : set->emotes) {
builder builder
.emplace<EmoteElement>( .emplace<EmoteElement>(
@ -70,7 +77,16 @@ namespace {
->setLink(Link(Link::InsertText, emote.name.string)); ->setLink(Link(Link::InsertText, emote.name.string));
} }
channel.addMessage(builder.release()); mapOfSets[channelName].second.push_back(builder.release());
}
// Output to channel all created messages,
// That contain title or emotes.
foreach (auto pair, mapOfSets) {
auto &channel = pair.first ? globalChannel : subChannel;
for (auto message : pair.second) {
channel.addMessage(message);
}
} }
} }
} // namespace } // namespace

View file

@ -552,10 +552,9 @@ void ChannelView::setChannel(ChannelPtr newChannel)
this->queueUpdate(); this->queueUpdate();
// Notifications // Notifications
TwitchChannel *tc = dynamic_cast<TwitchChannel *>(newChannel.get()); if (auto tc = dynamic_cast<TwitchChannel *>(newChannel.get())) {
if (tc != nullptr) { tc->liveStatusChanged.connect([this]() {
tc->tabHighlightRequested.connect([this](HighlightState state) { this->liveStatusChanged.invoke(); //
this->tabHighlightRequested.invoke(HighlightState::Notification);
}); });
} }
} }
@ -1251,14 +1250,21 @@ void ChannelView::addContextMenuItems(
QGuiApplication::clipboard()->setText(copyString); QGuiApplication::clipboard()->setText(copyString);
}); });
// Join to channel // Open in new split.
if (hoveredElement->getLink().type == Link::Url) { if (hoveredElement->getLink().type == Link::Url) {
static QRegularExpression twitchChannelRegex( static QRegularExpression twitchChannelRegex(
R"(^(?:https?:\/\/)?(?:www\.|go\.)?twitch\.tv\/(?<username>[a-z0-9_]+))", R"(^(?:https?:\/\/)?(?:www\.|go\.)?twitch\.tv\/(?<username>[a-z0-9_]{3,}))",
QRegularExpression::CaseInsensitiveOption); QRegularExpression::CaseInsensitiveOption);
static QSet<QString> ignoredUsernames{ static QSet<QString> ignoredUsernames{
"videos", "videos",
"settings", "settings",
"directory",
"jobs",
"friends",
"inventory",
"payments",
"subscriptions",
"messages",
}; };
auto twitchMatch = auto twitchMatch =

View file

@ -61,6 +61,7 @@ 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> tabHighlightRequested;
pajlada::Signals::NoArgSignal liveStatusChanged;
pajlada::Signals::Signal<const Link &> linkClicked; pajlada::Signals::Signal<const Link &> linkClicked;
pajlada::Signals::Signal<QString> joinToChannel; pajlada::Signals::Signal<QString> joinToChannel;

View file

@ -168,13 +168,20 @@ void NotebookTab::setSelected(bool value)
this->update(); this->update();
} }
void NotebookTab::setLive(bool isLive)
{
if (this->isLive_ != isLive) {
this->isLive_ = isLive;
this->update();
}
}
void NotebookTab::setHighlightState(HighlightState newHighlightStyle) void NotebookTab::setHighlightState(HighlightState newHighlightStyle)
{ {
if (this->isSelected() || !this->highlightEnabled_) { if (this->isSelected() || !this->highlightEnabled_) {
return; return;
} }
if (this->highlightState_ != HighlightState::Highlighted && if (this->highlightState_ != HighlightState::Highlighted) {
this->highlightState_ != HighlightState::Notification) {
this->highlightState_ = newHighlightStyle; this->highlightState_ = newHighlightStyle;
this->update(); this->update();
@ -252,8 +259,6 @@ void NotebookTab::paintEvent(QPaintEvent *)
colors = this->theme->tabs.selected; colors = this->theme->tabs.selected;
} else if (this->highlightState_ == HighlightState::Highlighted) { } else if (this->highlightState_ == HighlightState::Highlighted) {
colors = this->theme->tabs.highlighted; colors = this->theme->tabs.highlighted;
} else if (this->highlightState_ == HighlightState::Notification) {
colors = this->theme->tabs.notified;
} else if (this->highlightState_ == HighlightState::NewMessage) { } else if (this->highlightState_ == HighlightState::NewMessage) {
colors = this->theme->tabs.newMessage; colors = this->theme->tabs.newMessage;
} else { } else {
@ -297,6 +302,20 @@ void NotebookTab::paintEvent(QPaintEvent *)
? colors.line.hover ? colors.line.hover
: (windowFocused ? colors.line.regular : colors.line.unfocused)); : (windowFocused ? colors.line.regular : colors.line.unfocused));
// draw live indicator
if (this->isLive_) {
painter.setPen(QColor(Qt::GlobalColor::red));
QBrush b;
b.setColor(QColor(Qt::GlobalColor::red));
b.setStyle(Qt::SolidPattern);
painter.setBrush(b);
auto x = this->width() - (6.f * scale);
auto y = 4.f * scale;
auto diameter = 4.f * scale;
painter.drawEllipse(QRectF(x, y, diameter, diameter));
}
// set the pen color // set the pen color
painter.setPen(colors.text); painter.setPen(colors.text);

View file

@ -39,6 +39,7 @@ public:
bool isSelected() const; bool isSelected() const;
void setSelected(bool value); void setSelected(bool value);
void setLive(bool isLive);
void setHighlightState(HighlightState style); void setHighlightState(HighlightState style);
void setHighlightsEnabled(const bool &newVal); void setHighlightsEnabled(const bool &newVal);
bool hasHighlightsEnabled() const; bool hasHighlightsEnabled() const;
@ -88,6 +89,8 @@ private:
bool highlightEnabled_ = true; bool highlightEnabled_ = true;
QAction *highlightNewMessagesAction_; QAction *highlightNewMessagesAction_;
bool isLive_{};
QMenu menu_; QMenu menu_;
std::vector<pajlada::Signals::ScopedConnection> managedConnections_; std::vector<pajlada::Signals::ScopedConnection> managedConnections_;

View file

@ -23,8 +23,8 @@
#define THEME_ITEMS "White", "Light", "Dark", "Black" #define THEME_ITEMS "White", "Light", "Dark", "Black"
#define TAB_X "Show tab close button" #define TAB_X "Show tab close button"
#define TAB_PREF "Preferences button (ctrl+p to show)" #define TAB_PREF "Hide preferences button (ctrl+p to show)"
#define TAB_USER "User button" #define TAB_USER "Hide user button"
// clang-format off // clang-format off
#define TIMESTAMP_FORMATS "hh:mm a", "h:mm a", "hh:mm:ss a", "h:mm:ss a", "HH:mm", "H:mm", "HH:mm:ss", "H:mm:ss" #define TIMESTAMP_FORMATS "hh:mm a", "h:mm a", "hh:mm:ss a", "h:mm:ss a", "HH:mm", "H:mm", "HH:mm:ss", "H:mm:ss"

View file

@ -0,0 +1,52 @@
#include "ClosedSplits.hpp"
namespace chatterino {
std::mutex ClosedSplits::m_;
std::vector<ClosedSplits::SplitInfo> ClosedSplits::closedSplits_;
void ClosedSplits::invalidateTab(NotebookTab *const tab)
{
std::lock_guard<std::mutex> lk(ClosedSplits::m_);
auto it = std::find_if(
ClosedSplits::closedSplits_.begin(), ClosedSplits::closedSplits_.end(),
[tab](const auto &item) -> bool { return item.tab == tab; });
if (it == ClosedSplits::closedSplits_.end()) {
return;
}
it->tab = nullptr;
}
void ClosedSplits::push(const SplitInfo &si)
{
std::lock_guard<std::mutex> lk(ClosedSplits::m_);
ClosedSplits::closedSplits_.push_back(si);
}
void ClosedSplits::push(SplitInfo &&si)
{
std::lock_guard<std::mutex> lk(ClosedSplits::m_);
ClosedSplits::closedSplits_.push_back(std::move(si));
}
ClosedSplits::SplitInfo ClosedSplits::pop()
{
std::lock_guard<std::mutex> lk(ClosedSplits::m_);
SplitInfo si = std::move(ClosedSplits::closedSplits_.back());
ClosedSplits::closedSplits_.pop_back();
return si;
}
bool ClosedSplits::empty()
{
std::lock_guard<std::mutex> lk(ClosedSplits::m_);
return ClosedSplits::closedSplits_.empty();
}
std::size_t ClosedSplits::size()
{
std::lock_guard<std::mutex> lk(ClosedSplits::m_);
return ClosedSplits::closedSplits_.size();
}
} // namespace chatterino

View file

@ -0,0 +1,32 @@
#pragma once
#include "common/Channel.hpp"
#include "widgets/helper/NotebookTab.hpp"
#include <deque>
#include <mutex>
#include <utility>
namespace chatterino {
class ClosedSplits
{
public:
struct SplitInfo {
QString channelName;
NotebookTab *tab; // non owning ptr
};
static void invalidateTab(NotebookTab *const tab);
static void push(const SplitInfo &si);
static void push(SplitInfo &&si);
static SplitInfo pop();
static bool empty();
static std::size_t size();
private:
static std::mutex m_;
static std::vector<SplitInfo> closedSplits_;
};
} // namespace chatterino

View file

@ -19,9 +19,11 @@
#include "widgets/dialogs/UserInfoPopup.hpp" #include "widgets/dialogs/UserInfoPopup.hpp"
#include "widgets/helper/ChannelView.hpp" #include "widgets/helper/ChannelView.hpp"
#include "widgets/helper/DebugPopup.hpp" #include "widgets/helper/DebugPopup.hpp"
#include "widgets/helper/NotebookTab.hpp"
#include "widgets/helper/ResizingTextEdit.hpp" #include "widgets/helper/ResizingTextEdit.hpp"
#include "widgets/helper/SearchPopup.hpp" #include "widgets/helper/SearchPopup.hpp"
#include "widgets/helper/Shortcut.hpp" #include "widgets/helper/Shortcut.hpp"
#include "widgets/splits/ClosedSplits.hpp"
#include "widgets/splits/SplitContainer.hpp" #include "widgets/splits/SplitContainer.hpp"
#include "widgets/splits/SplitHeader.hpp" #include "widgets/splits/SplitHeader.hpp"
#include "widgets/splits/SplitInput.hpp" #include "widgets/splits/SplitInput.hpp"
@ -290,7 +292,7 @@ void Split::showChangeChannelPopup(const char *dialogTitle, bool empty,
if (dialog->hasSeletedChannel()) { if (dialog->hasSeletedChannel()) {
this->setChannel(dialog->getSelectedChannel()); this->setChannel(dialog->getSelectedChannel());
if (this->isInContainer()) { if (this->isInContainer()) {
this->container_->refreshTabTitle(); this->container_->refreshTab();
} }
} }
@ -415,6 +417,10 @@ void Split::deleteFromContainer()
{ {
if (this->container_) { if (this->container_) {
this->container_->deleteSplit(this); this->container_->deleteSplit(this);
auto *tab = this->getContainer()->getTab();
tab->connect(tab, &QWidget::destroyed,
[tab]() mutable { ClosedSplits::invalidateTab(tab); });
ClosedSplits::push({this->getChannel()->getName(), tab});
} }
} }

View file

@ -84,7 +84,7 @@ void SplitContainer::setTab(NotebookTab *_tab)
this->tab_->page = this; this->tab_->page = this;
this->refreshTabTitle(); this->refreshTab();
} }
void SplitContainer::hideResizeHandles() void SplitContainer::hideResizeHandles()
@ -177,7 +177,7 @@ void SplitContainer::addSplit(Split *split)
this->unsetCursor(); this->unsetCursor();
this->splits_.push_back(split); this->splits_.push_back(split);
this->refreshTabTitle(); this->refreshTab();
split->getChannelView().tabHighlightRequested.connect( split->getChannelView().tabHighlightRequested.connect(
[this](HighlightState state) { [this](HighlightState state) {
@ -186,6 +186,10 @@ void SplitContainer::addSplit(Split *split)
} }
}); });
split->getChannelView().liveStatusChanged.connect([this]() {
this->refreshTabLiveStatus(); //
});
split->focused.connect([this, split] { this->setSelected(split); }); split->focused.connect([this, split] { this->setSelected(split); });
this->layout(); this->layout();
@ -228,7 +232,7 @@ SplitContainer::Position SplitContainer::releaseSplit(Split *split)
this->splits_.front()->giveFocus(Qt::MouseFocusReason); this->splits_.front()->giveFocus(Qt::MouseFocusReason);
} }
this->refreshTabTitle(); this->refreshTab();
// fourtf: really bad // fourtf: really bad
split->getChannelView().tabHighlightRequested.disconnectAll(); split->getChannelView().tabHighlightRequested.disconnectAll();
@ -568,34 +572,10 @@ void SplitContainer::focusInEvent(QFocusEvent *)
} }
} }
void SplitContainer::refreshTabTitle() void SplitContainer::refreshTab()
{ {
if (this->tab_ == nullptr) { this->refreshTabTitle();
return; this->refreshTabLiveStatus();
}
QString newTitle = "";
bool first = true;
for (const auto &chatWidget : this->splits_) {
auto channelName = chatWidget->getChannel()->getName();
if (channelName.isEmpty()) {
continue;
}
if (!first) {
newTitle += ", ";
}
newTitle += channelName;
first = false;
}
if (newTitle.isEmpty()) {
newTitle = "empty";
}
this->tab_->setDefaultTitle(newTitle);
} }
int SplitContainer::getSplitCount() int SplitContainer::getSplitCount()
@ -677,6 +657,54 @@ void SplitContainer::decodeNodeRecusively(QJsonObject &obj, Node *node)
} }
} }
void SplitContainer::refreshTabTitle()
{
if (this->tab_ == nullptr) {
return;
}
QString newTitle = "";
bool first = true;
for (const auto &chatWidget : this->splits_) {
auto channelName = chatWidget->getChannel()->getName();
if (channelName.isEmpty()) {
continue;
}
if (!first) {
newTitle += ", ";
}
newTitle += channelName;
first = false;
}
if (newTitle.isEmpty()) {
newTitle = "empty";
}
this->tab_->setDefaultTitle(newTitle);
}
void SplitContainer::refreshTabLiveStatus()
{
if (this->tab_ == nullptr) {
return;
}
bool liveStatus = false;
for (const auto &s : this->splits_) {
auto c = s->getChannel();
if (c->isLive()) {
liveStatus = true;
break;
}
}
this->tab_->setLive(liveStatus);
}
// //
// Node // Node
// //

View file

@ -171,7 +171,7 @@ private:
public: public:
SplitContainer(Notebook *parent); SplitContainer(Notebook *parent);
Split* appendNewSplit(bool openChannelNameDialog); Split *appendNewSplit(bool openChannelNameDialog);
void appendSplit(Split *split); void appendSplit(Split *split);
void insertSplit(Split *split, const Position &position); void insertSplit(Split *split, const Position &position);
void insertSplit(Split *split, Direction direction, Split *relativeTo); void insertSplit(Split *split, Direction direction, Split *relativeTo);
@ -186,7 +186,9 @@ public:
int getSplitCount(); int getSplitCount();
const std::vector<Split *> getSplits() const; const std::vector<Split *> getSplits() const;
void refreshTabTitle();
void refreshTab();
NotebookTab *getTab() const; NotebookTab *getTab() const;
Node *getBaseNode(); Node *getBaseNode();
@ -221,6 +223,9 @@ private:
void decodeNodeRecusively(QJsonObject &obj, Node *node); void decodeNodeRecusively(QJsonObject &obj, Node *node);
Split *getTopRightSplit(Node &node); Split *getTopRightSplit(Node &node);
void refreshTabTitle();
void refreshTabLiveStatus();
struct DropRegion { struct DropRegion {
QRect rect; QRect rect;
std::pair<int, int> position; std::pair<int, int> position;