mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Merge branch 'master' of https://github.com/fourtf/chatterino2
This commit is contained in:
commit
bf8bf37eab
23 changed files with 299 additions and 76 deletions
10
README.md
10
README.md
|
@ -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™
|
||||
|
||||
## 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)
|
||||
|
||||
|
|
|
@ -262,7 +262,8 @@ SOURCES += \
|
|||
src/debug/Benchmark.cpp \
|
||||
src/common/UsernameSet.cpp \
|
||||
src/widgets/settingspages/AdvancedPage.cpp \
|
||||
src/util/IncognitoBrowser.cpp
|
||||
src/util/IncognitoBrowser.cpp \
|
||||
src/widgets/splits/ClosedSplits.cpp
|
||||
|
||||
HEADERS += \
|
||||
src/Application.hpp \
|
||||
|
@ -464,7 +465,8 @@ HEADERS += \
|
|||
src/messages/MessageContainer.hpp \
|
||||
src/common/UsernameSet.hpp \
|
||||
src/widgets/settingspages/AdvancedPage.hpp \
|
||||
src/util/IncognitoBrowser.hpp
|
||||
src/util/IncognitoBrowser.hpp \
|
||||
src/widgets/splits/ClosedSplits.hpp
|
||||
|
||||
RESOURCES += \
|
||||
resources/resources.qrc \
|
||||
|
|
|
@ -221,6 +221,11 @@ bool Channel::hasModRights() const
|
|||
return this->isMod() || this->isBroadcaster();
|
||||
}
|
||||
|
||||
bool Channel::isLive() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> Channel::getEmpty()
|
||||
{
|
||||
static std::shared_ptr<Channel> channel(new Channel("", Type::None));
|
||||
|
|
|
@ -69,6 +69,7 @@ public:
|
|||
virtual bool isMod() const;
|
||||
virtual bool isBroadcaster() const;
|
||||
virtual bool hasModRights() const;
|
||||
virtual bool isLive() const;
|
||||
|
||||
static std::shared_ptr<Channel> getEmpty();
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ enum class HighlightState {
|
|||
None,
|
||||
Highlighted,
|
||||
NewMessage,
|
||||
Notification,
|
||||
};
|
||||
|
||||
inline QString qS(const std::string &string)
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
|
||||
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()
|
||||
{
|
||||
// Initialize the connections
|
||||
|
@ -38,12 +42,29 @@ AbstractIrcServer::AbstractIrcServer()
|
|||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::disconnected,
|
||||
[this] { this->onDisconnected(); });
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::socketError,
|
||||
[this] { this->onSocketError(); });
|
||||
|
||||
// listen to reconnect request
|
||||
this->readConnection_->reconnectRequested.connect(
|
||||
[this] { this->connect(); });
|
||||
// this->writeConnection->reconnectRequested.connect([this] {
|
||||
// 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()
|
||||
|
@ -215,6 +236,8 @@ void AbstractIrcServer::onConnected()
|
|||
|
||||
chan->addMessage(connected);
|
||||
}
|
||||
|
||||
this->falloffCounter_ = 1;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onDisconnected()
|
||||
|
@ -235,6 +258,11 @@ void AbstractIrcServer::onDisconnected()
|
|||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onSocketError()
|
||||
{
|
||||
this->reconnectTimer_.start();
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
|
||||
const QString &channelName)
|
||||
{
|
||||
|
|
|
@ -54,6 +54,7 @@ protected:
|
|||
|
||||
virtual void onConnected();
|
||||
virtual void onDisconnected();
|
||||
virtual void onSocketError();
|
||||
|
||||
virtual std::shared_ptr<Channel> getCustomChannel(
|
||||
const QString &channelName);
|
||||
|
@ -70,6 +71,9 @@ private:
|
|||
std::unique_ptr<IrcConnection> writeConnection_ = nullptr;
|
||||
std::unique_ptr<IrcConnection> readConnection_ = nullptr;
|
||||
|
||||
QTimer reconnectTimer_;
|
||||
int falloffCounter_ = 1;
|
||||
|
||||
std::mutex connectionMutex_;
|
||||
|
||||
// bool autoReconnect_ = false;
|
||||
|
|
|
@ -29,8 +29,7 @@
|
|||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
auto parseRecentMessages(const QJsonObject &jsonRoot,
|
||||
TwitchChannel &channel)
|
||||
auto parseRecentMessages(const QJsonObject &jsonRoot, ChannelPtr channel)
|
||||
{
|
||||
QJsonArray jsonMessages = jsonRoot.value("messages").toArray();
|
||||
std::vector<MessagePtr> messages;
|
||||
|
@ -46,7 +45,7 @@ namespace {
|
|||
assert(privMsg);
|
||||
|
||||
MessageParseArgs args;
|
||||
TwitchMessageBuilder builder(&channel, privMsg, args);
|
||||
TwitchMessageBuilder builder(channel.get(), privMsg, args);
|
||||
if (!builder.isIgnored()) {
|
||||
messages.push_back(builder.build());
|
||||
}
|
||||
|
@ -90,7 +89,6 @@ TwitchChannel::TwitchChannel(const QString &name,
|
|||
{
|
||||
log("[TwitchChannel:{}] Opened", name);
|
||||
|
||||
this->tabHighlightRequested.connect([](HighlightState state) {});
|
||||
this->liveStatusChanged.connect([this]() {
|
||||
if (this->isLive() == 1) {
|
||||
}
|
||||
|
@ -413,8 +411,6 @@ void TwitchChannel::setLive(bool newLiveStatus)
|
|||
}
|
||||
auto live = makeSystemMessage(this->getName() + " is live");
|
||||
this->addMessage(live);
|
||||
this->tabHighlightRequested.invoke(
|
||||
HighlightState::Notification);
|
||||
} else {
|
||||
auto offline =
|
||||
makeSystemMessage(this->getName() + " is offline");
|
||||
|
@ -548,13 +544,13 @@ void TwitchChannel::loadRecentMessages()
|
|||
// can't be concurrent right now due to SignalVector
|
||||
// request.setExecuteConcurrently(true);
|
||||
|
||||
request.onSuccess([that = this](auto result) -> Outcome {
|
||||
auto messages = parseRecentMessages(result.parseJson(), *that);
|
||||
request.onSuccess([weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||
auto shared = weak.lock();
|
||||
if (!shared) return Failure;
|
||||
|
||||
// postToThread([that, weak = weakOf<Channel>(that),
|
||||
// messages = std::move(messages)]() mutable {
|
||||
that->addMessagesAtStart(messages);
|
||||
// });
|
||||
auto messages = parseRecentMessages(result.parseJson(), shared);
|
||||
|
||||
shared->addMessagesAtStart(messages);
|
||||
|
||||
return Success;
|
||||
});
|
||||
|
|
|
@ -66,7 +66,7 @@ public:
|
|||
const QString &subscriptionUrl();
|
||||
const QString &channelUrl();
|
||||
const QString &popoutPlayerUrl();
|
||||
bool isLive() const;
|
||||
virtual bool isLive() const override;
|
||||
QString roomId() const;
|
||||
AccessGuard<const RoomModes> accessRoomModes() const;
|
||||
AccessGuard<const StreamStatus> accessStreamStatus() const;
|
||||
|
@ -92,7 +92,6 @@ public:
|
|||
pajlada::Signals::NoArgSignal userStateChanged;
|
||||
pajlada::Signals::NoArgSignal liveStatusChanged;
|
||||
pajlada::Signals::NoArgSignal roomModesChanged;
|
||||
pajlada::Signals::Signal<HighlightState> tabHighlightRequested;
|
||||
|
||||
protected:
|
||||
void addRecentChatter(const MessagePtr &message) override;
|
||||
|
|
|
@ -105,10 +105,6 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
|||
QColor("#000"),
|
||||
{QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")},
|
||||
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
|
||||
this->tabs.notified = {
|
||||
fg,
|
||||
{QColor("#fff"), QColor("#fff"), QColor("#fff")},
|
||||
{QColor("#F824A8"), QColor("#F824A8"), QColor("#F824A8")}};
|
||||
} else {
|
||||
this->tabs.regular = {
|
||||
QColor("#aaa"),
|
||||
|
@ -127,10 +123,6 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
|||
QColor("#fff"),
|
||||
{QColor("#555555"), QColor("#555555"), QColor("#555555")},
|
||||
{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;
|
||||
|
|
|
@ -50,7 +50,6 @@ public:
|
|||
TabColors newMessage;
|
||||
TabColors highlighted;
|
||||
TabColors selected;
|
||||
TabColors notified;
|
||||
QColor border;
|
||||
QColor bottomLine;
|
||||
} tabs;
|
||||
|
|
|
@ -15,8 +15,10 @@
|
|||
#include "widgets/dialogs/UpdateDialog.hpp"
|
||||
#include "widgets/dialogs/WelcomeDialog.hpp"
|
||||
#include "widgets/helper/EffectLabel.hpp"
|
||||
#include "widgets/helper/NotebookTab.hpp"
|
||||
#include "widgets/helper/Shortcut.hpp"
|
||||
#include "widgets/helper/TitlebarButton.hpp"
|
||||
#include "widgets/splits/ClosedSplits.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
#include "widgets/splits/SplitContainer.hpp"
|
||||
|
||||
|
@ -304,6 +306,26 @@ void Window::addShortcuts()
|
|||
// Close tab
|
||||
createWindowShortcut(this, "CTRL+SHIFT+W",
|
||||
[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
|
||||
|
|
|
@ -48,19 +48,26 @@ namespace {
|
|||
std::vector<std::shared_ptr<TwitchAccount::EmoteSet>> sets,
|
||||
Channel &globalChannel, Channel &subChannel)
|
||||
{
|
||||
for (const auto &set : sets) {
|
||||
auto &channel = set->key == "0" ? globalChannel : subChannel;
|
||||
QMap<QString, QPair<bool, std::vector<MessagePtr>>> mapOfSets;
|
||||
|
||||
for (const auto &set : sets) {
|
||||
// TITLE
|
||||
auto channelName = set->channelName;
|
||||
auto text =
|
||||
set->key == "0" || set->text.isEmpty() ? "Twitch" : set->text;
|
||||
channel.addMessage(makeTitleMessage(text));
|
||||
|
||||
// EMOTES
|
||||
MessageBuilder builder;
|
||||
builder->flags.set(MessageFlag::Centered);
|
||||
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) {
|
||||
builder
|
||||
.emplace<EmoteElement>(
|
||||
|
@ -70,7 +77,16 @@ namespace {
|
|||
->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
|
||||
|
|
|
@ -552,10 +552,9 @@ void ChannelView::setChannel(ChannelPtr newChannel)
|
|||
this->queueUpdate();
|
||||
|
||||
// Notifications
|
||||
TwitchChannel *tc = dynamic_cast<TwitchChannel *>(newChannel.get());
|
||||
if (tc != nullptr) {
|
||||
tc->tabHighlightRequested.connect([this](HighlightState state) {
|
||||
this->tabHighlightRequested.invoke(HighlightState::Notification);
|
||||
if (auto tc = dynamic_cast<TwitchChannel *>(newChannel.get())) {
|
||||
tc->liveStatusChanged.connect([this]() {
|
||||
this->liveStatusChanged.invoke(); //
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1251,14 +1250,21 @@ void ChannelView::addContextMenuItems(
|
|||
QGuiApplication::clipboard()->setText(copyString);
|
||||
});
|
||||
|
||||
// Join to channel
|
||||
// Open in new split.
|
||||
if (hoveredElement->getLink().type == Link::Url) {
|
||||
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);
|
||||
static QSet<QString> ignoredUsernames{
|
||||
"videos",
|
||||
"settings",
|
||||
"directory",
|
||||
"jobs",
|
||||
"friends",
|
||||
"inventory",
|
||||
"payments",
|
||||
"subscriptions",
|
||||
"messages",
|
||||
};
|
||||
|
||||
auto twitchMatch =
|
||||
|
|
|
@ -61,6 +61,7 @@ public:
|
|||
pajlada::Signals::Signal<QMouseEvent *> mouseDown;
|
||||
pajlada::Signals::NoArgSignal selectionChanged;
|
||||
pajlada::Signals::Signal<HighlightState> tabHighlightRequested;
|
||||
pajlada::Signals::NoArgSignal liveStatusChanged;
|
||||
pajlada::Signals::Signal<const Link &> linkClicked;
|
||||
pajlada::Signals::Signal<QString> joinToChannel;
|
||||
|
||||
|
|
|
@ -168,13 +168,20 @@ void NotebookTab::setSelected(bool value)
|
|||
this->update();
|
||||
}
|
||||
|
||||
void NotebookTab::setLive(bool isLive)
|
||||
{
|
||||
if (this->isLive_ != isLive) {
|
||||
this->isLive_ = isLive;
|
||||
this->update();
|
||||
}
|
||||
}
|
||||
|
||||
void NotebookTab::setHighlightState(HighlightState newHighlightStyle)
|
||||
{
|
||||
if (this->isSelected() || !this->highlightEnabled_) {
|
||||
return;
|
||||
}
|
||||
if (this->highlightState_ != HighlightState::Highlighted &&
|
||||
this->highlightState_ != HighlightState::Notification) {
|
||||
if (this->highlightState_ != HighlightState::Highlighted) {
|
||||
this->highlightState_ = newHighlightStyle;
|
||||
|
||||
this->update();
|
||||
|
@ -252,8 +259,6 @@ void NotebookTab::paintEvent(QPaintEvent *)
|
|||
colors = this->theme->tabs.selected;
|
||||
} else if (this->highlightState_ == HighlightState::Highlighted) {
|
||||
colors = this->theme->tabs.highlighted;
|
||||
} else if (this->highlightState_ == HighlightState::Notification) {
|
||||
colors = this->theme->tabs.notified;
|
||||
} else if (this->highlightState_ == HighlightState::NewMessage) {
|
||||
colors = this->theme->tabs.newMessage;
|
||||
} else {
|
||||
|
@ -297,6 +302,20 @@ void NotebookTab::paintEvent(QPaintEvent *)
|
|||
? colors.line.hover
|
||||
: (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
|
||||
painter.setPen(colors.text);
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ public:
|
|||
bool isSelected() const;
|
||||
void setSelected(bool value);
|
||||
|
||||
void setLive(bool isLive);
|
||||
void setHighlightState(HighlightState style);
|
||||
void setHighlightsEnabled(const bool &newVal);
|
||||
bool hasHighlightsEnabled() const;
|
||||
|
@ -88,6 +89,8 @@ private:
|
|||
bool highlightEnabled_ = true;
|
||||
QAction *highlightNewMessagesAction_;
|
||||
|
||||
bool isLive_{};
|
||||
|
||||
QMenu menu_;
|
||||
|
||||
std::vector<pajlada::Signals::ScopedConnection> managedConnections_;
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
#define THEME_ITEMS "White", "Light", "Dark", "Black"
|
||||
|
||||
#define TAB_X "Show tab close button"
|
||||
#define TAB_PREF "Preferences button (ctrl+p to show)"
|
||||
#define TAB_USER "User button"
|
||||
#define TAB_PREF "Hide preferences button (ctrl+p to show)"
|
||||
#define TAB_USER "Hide user button"
|
||||
|
||||
// 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"
|
||||
|
|
52
src/widgets/splits/ClosedSplits.cpp
Normal file
52
src/widgets/splits/ClosedSplits.cpp
Normal 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
|
32
src/widgets/splits/ClosedSplits.hpp
Normal file
32
src/widgets/splits/ClosedSplits.hpp
Normal 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
|
|
@ -19,9 +19,11 @@
|
|||
#include "widgets/dialogs/UserInfoPopup.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
#include "widgets/helper/DebugPopup.hpp"
|
||||
#include "widgets/helper/NotebookTab.hpp"
|
||||
#include "widgets/helper/ResizingTextEdit.hpp"
|
||||
#include "widgets/helper/SearchPopup.hpp"
|
||||
#include "widgets/helper/Shortcut.hpp"
|
||||
#include "widgets/splits/ClosedSplits.hpp"
|
||||
#include "widgets/splits/SplitContainer.hpp"
|
||||
#include "widgets/splits/SplitHeader.hpp"
|
||||
#include "widgets/splits/SplitInput.hpp"
|
||||
|
@ -290,7 +292,7 @@ void Split::showChangeChannelPopup(const char *dialogTitle, bool empty,
|
|||
if (dialog->hasSeletedChannel()) {
|
||||
this->setChannel(dialog->getSelectedChannel());
|
||||
if (this->isInContainer()) {
|
||||
this->container_->refreshTabTitle();
|
||||
this->container_->refreshTab();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -415,6 +417,10 @@ void Split::deleteFromContainer()
|
|||
{
|
||||
if (this->container_) {
|
||||
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});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ void SplitContainer::setTab(NotebookTab *_tab)
|
|||
|
||||
this->tab_->page = this;
|
||||
|
||||
this->refreshTabTitle();
|
||||
this->refreshTab();
|
||||
}
|
||||
|
||||
void SplitContainer::hideResizeHandles()
|
||||
|
@ -177,7 +177,7 @@ void SplitContainer::addSplit(Split *split)
|
|||
this->unsetCursor();
|
||||
this->splits_.push_back(split);
|
||||
|
||||
this->refreshTabTitle();
|
||||
this->refreshTab();
|
||||
|
||||
split->getChannelView().tabHighlightRequested.connect(
|
||||
[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); });
|
||||
|
||||
this->layout();
|
||||
|
@ -228,7 +232,7 @@ SplitContainer::Position SplitContainer::releaseSplit(Split *split)
|
|||
this->splits_.front()->giveFocus(Qt::MouseFocusReason);
|
||||
}
|
||||
|
||||
this->refreshTabTitle();
|
||||
this->refreshTab();
|
||||
|
||||
// fourtf: really bad
|
||||
split->getChannelView().tabHighlightRequested.disconnectAll();
|
||||
|
@ -568,34 +572,10 @@ void SplitContainer::focusInEvent(QFocusEvent *)
|
|||
}
|
||||
}
|
||||
|
||||
void SplitContainer::refreshTabTitle()
|
||||
void SplitContainer::refreshTab()
|
||||
{
|
||||
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);
|
||||
this->refreshTabTitle();
|
||||
this->refreshTabLiveStatus();
|
||||
}
|
||||
|
||||
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
|
||||
//
|
||||
|
|
|
@ -186,7 +186,9 @@ public:
|
|||
|
||||
int getSplitCount();
|
||||
const std::vector<Split *> getSplits() const;
|
||||
void refreshTabTitle();
|
||||
|
||||
void refreshTab();
|
||||
|
||||
NotebookTab *getTab() const;
|
||||
Node *getBaseNode();
|
||||
|
||||
|
@ -221,6 +223,9 @@ private:
|
|||
void decodeNodeRecusively(QJsonObject &obj, Node *node);
|
||||
Split *getTopRightSplit(Node &node);
|
||||
|
||||
void refreshTabTitle();
|
||||
void refreshTabLiveStatus();
|
||||
|
||||
struct DropRegion {
|
||||
QRect rect;
|
||||
std::pair<int, int> position;
|
||||
|
|
Loading…
Reference in a new issue